diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f9e3f1c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea +cmake-build-debug/ +lib/duktape/ +lib/glad/ + diff --git a/.gitmodules b/.gitmodules index c56acc8..7cd924b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,18 @@ path = vendor/glad url = git@github.com:Dav1dde/glad.git branch = master +[submodule "tools/shaderc"] + path = lib/shaderc + url = git@github.com:google/shaderc.git +[submodule "tools/googletest"] + path = lib/googletest + url = git@github.com:google/googletest.git +[submodule "tools/glslang"] + path = lib/glslang + url = git@github.com:google/glslang.git +[submodule "tools/spirv-tools"] + path = lib/spirv-tools + url = git@github.com:KhronosGroup/SPIRV-Tools.git +[submodule "tools/spirv-headers"] + path = lib/spirv-headers + url = git@github.com:KhronosGroup/SPIRV-Headers.git diff --git a/.idea/engine.iml b/.idea/engine.iml deleted file mode 100644 index f08604b..0000000 --- a/.idea/engine.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 79b3c94..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 4de3850..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 996d53a..08dd391 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,22 +1,49 @@ cmake_minimum_required(VERSION 3.5) project(engine) -find_package( PythonInterp 2.7 REQUIRED ) +find_package(PythonInterp 2.7 REQUIRED) include(CheckCCompilerFlag) +include(ExternalProject) # Source: https://stackoverflow.com/a/33266748/1127064 function(enable_c_compiler_flag_if_supported flag) string(FIND "${CMAKE_C_FLAGS}" "${flag}" flag_already_set) - if(flag_already_set EQUAL -1) + if (flag_already_set EQUAL -1) check_c_compiler_flag("${flag}" flag_supported) - if(flag_supported) + if (flag_supported) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flag}" PARENT_SCOPE) - endif() + endif () unset(flag_supported CACHE) - endif() + endif () endfunction() +# Build shaderc library for compiling glsl shaders +set(SHADERC_SYMLINKS + ${CMAKE_SOURCE_DIR}/lib/shaderc/third_party/glslang + ${CMAKE_SOURCE_DIR}/lib/shaderc/third_party/googletest + ${CMAKE_SOURCE_DIR}/lib/shaderc/third_party/spirv-headers + ${CMAKE_SOURCE_DIR}/lib/shaderc/third_party/spirv-tools) +add_custom_command( + OUTPUT ${SHADERC_SYMLINKS} + COMMAND ln -s ${CMAKE_SOURCE_DIR}/lib/glslang ${CMAKE_SOURCE_DIR}/lib/shaderc/third_party/glslang + COMMAND ln -s ${CMAKE_SOURCE_DIR}/lib/googletest ${CMAKE_SOURCE_DIR}/lib/shaderc/third_party/googletest + COMMAND ln -s ${CMAKE_SOURCE_DIR}/lib/spirv-headers ${CMAKE_SOURCE_DIR}/lib/shaderc/third_party/spirv-headers + COMMAND ln -s ${CMAKE_SOURCE_DIR}/lib/spirv-tools ${CMAKE_SOURCE_DIR}/lib/shaderc/third_party/spirv-tools +) +add_custom_target(shadercsymlinktarget + ALL + DEPENDS ${SHADERC_SYMLINKS}) +ExternalProject_Add(localshaderc + DEPENDS shadercsymlinktarget + PREFIX lib/shaderc + SOURCE_DIR ${CMAKE_SOURCE_DIR}/lib/shaderc/ + CONFIGURE_COMMAND cmake -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/lib/shaderc/ -DCMAKE_BUILD_TYPE=Debug ${CMAKE_SOURCE_DIR}/lib/shaderc + BUILD_COMMAND make -j4 + INSTALL_COMMAND make install + ) +link_directories(${CMAKE_BINARY_DIR}/lib/shaderc/lib/) +include_directories(${CMAKE_BINARY_DIR}/lib/shaderc/include) # VENDOR DUKTAPE set(VENDOR_DUKTAPE_SOURCES ${CMAKE_SOURCE_DIR}/lib/duktape/duktape.c ${CMAKE_SOURCE_DIR}/lib/duktape/duktape.h ${CMAKE_SOURCE_DIR}/lib/duktape/duk_config.h ${CMAKE_SOURCE_DIR}/lib/duktape/duk_source_meta.json) @@ -31,14 +58,14 @@ add_custom_target(localduktape set(VENDOR_GLAD_SOURCES ${CMAKE_SOURCE_DIR}/lib/glad/glad.c ${CMAKE_SOURCE_DIR}/lib/glad/glad.h) add_custom_command( - OUTPUT ${VENDOR_GLAD_SOURCES} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/vendor/glad/ - PRE_BUILD - COMMAND ${PYTHON_EXECUTABLE} -m glad --generator c --out-path ${CMAKE_SOURCE_DIR}/lib/glad/ --spec gl --omit-khrplatform --local-files + OUTPUT ${VENDOR_GLAD_SOURCES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/vendor/glad/ + PRE_BUILD + COMMAND ${PYTHON_EXECUTABLE} -m glad --generator c --out-path ${CMAKE_SOURCE_DIR}/lib/glad/ --spec gl --omit-khrplatform --local-files ) add_custom_target(localglad - ALL - DEPENDS ${VENDOR_GLAD_SOURCES}) + ALL + DEPENDS ${VENDOR_GLAD_SOURCES}) # lib/glad/glad.c @@ -46,39 +73,76 @@ add_custom_target(localglad # ALL SOURCES set(SOURCE_FILES - ${VENDOR_GLAD_SOURCES} ${VENDOR_DUKTAPE_SOURCES} + ${VENDOR_GLAD_SOURCES} ${VENDOR_DUKTAPE_SOURCES} src/engine/main.c + + # Scripting src/engine/scripting/interface.c src/engine/scripting/interface.h src/engine/scripting/script.h + + # Utilities src/engine/util/llist.c src/engine/util/llist.h src/engine/util/files.c src/engine/util/files.h + src/engine/util/dict.c src/engine/util/dict.h + src/engine/util/hashing/crc.c src/engine/util/hashing/crc.h + + # Engine source src/engine/graphics/quadmesh.c src/engine/graphics/quadmesh.h src/engine/graphics/shader.c src/engine/graphics/shader.h src/engine/graphics/texture.c src/engine/graphics/texture.h lib/stb/stb_image.h src/engine/graphics/style.c src/engine/graphics/style.h - lib/linmath/linmath.h src/engine/scripting/callbacks.c src/engine/scripting/callbacks.h src/engine/graphics/screen.c src/engine/graphics/screen.h) + lib/linmath/linmath.h + src/engine/scripting/callbacks.c src/engine/scripting/callbacks.h + src/engine/graphics/screen.c src/engine/graphics/screen.h + + # Vulkan + src/engine/graphics/vulkan/vulkan.c src/engine/graphics/vulkan/vulkan.h + src/engine/graphics/vulkan/wrappers.h + src/engine/graphics/vulkan/query.c src/engine/graphics/vulkan/query.h + src/engine/graphics/vulkan/debug.c src/engine/graphics/vulkan/debug.h + src/engine/graphics/vulkan/config.c src/engine/graphics/vulkan/config.h + src/engine/graphics/vulkan/queues.c src/engine/graphics/vulkan/queues.h + src/engine/graphics/vulkan/surface.c src/engine/graphics/vulkan/surface.h + src/engine/graphics/vulkan/window.c src/engine/graphics/vulkan/window.h + src/engine/graphics/vulkan/swapchain.c src/engine/graphics/vulkan/swapchain.h + src/engine/graphics/vulkan/shaders/shader.c src/engine/graphics/vulkan/shaders/shader.h + src/engine/graphics/vulkan/pipeline.c src/engine/graphics/vulkan/pipeline.h + src/engine/graphics/vulkan/framebuffer.c src/engine/graphics/vulkan/framebuffer.h + src/engine/graphics/vulkan/locking.c src/engine/graphics/vulkan/locking.h + src/engine/graphics/vulkan/memory/vbuffer.c src/engine/graphics/vulkan/memory/vbuffer.h + src/engine/graphics/vulkan/memory/memory.c src/engine/graphics/vulkan/memory/memory.h + src/engine/util/bits.h src/engine/graphics/vulkan/memory/buffer.h src/engine/graphics/vulkan/memory/buffer.c + + src/engine/entity/entity.c src/engine/entity/entity.h + src/engine/entity/definitions/triangle.c src/engine/entity/definitions/triangle.h + src/engine/entity/manager.c src/engine/entity/manager.h + src/engine/graphics/vulkan/commands/pool.c src/engine/graphics/vulkan/commands/pool.h src/engine/graphics/vulkan/commands/buffer.c src/engine/graphics/vulkan/commands/buffer.h src/engine/graphics/vulkan/commands/render.c src/engine/graphics/vulkan/commands/render.h) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + + -set(CMAKE_INCLUDE_CURRENT_DIR ON) +# COPY SOURCE OF GAME INTO BUILD DIR +add_custom_target( + game_client_code + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/src/game/ + ${CMAKE_CURRENT_BINARY_DIR}/) add_executable(engine ${SOURCE_FILES}) # VENDOR DUKTAPE -add_dependencies(engine localduktape localglad) +add_dependencies(engine localduktape localglad localshaderc game_client_code) # GL, GLFW, DL, M -target_link_libraries(engine dl m glfw GL) +target_link_libraries(engine + dl m glfw GL vulkan + shaderc_combined stdc++ pthread) # STANDARDS set_property(TARGET engine PROPERTY C_STANDARD 99) enable_c_compiler_flag_if_supported("-Wall") enable_c_compiler_flag_if_supported("-Wextra") enable_c_compiler_flag_if_supported("-pedantic") - -# COPY SOURCE OF GAME INTO BUILD DIR -add_custom_command( - TARGET engine POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/src/game/ - ${CMAKE_CURRENT_BINARY_DIR}/) diff --git a/README.md b/README.md index a8f1857..301f757 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ git pull --recursive-submodules | dl | N/A | Dynamically load libraries | | m | N/A | Standard math library | | GLFW3 | libglfw3-dev | GLFW 3 headers | -| GL | GPU drivers, N/A | OpenGL headers and libs | | Python 2 | python2 | Python 2 to build duktape | +| Ninja | ninja-build | Used by google's shaderc | ## Used Libraries diff --git a/lib/glslang b/lib/glslang new file mode 160000 index 0000000..1ca0f8e --- /dev/null +++ b/lib/glslang @@ -0,0 +1 @@ +Subproject commit 1ca0f8e8eb00b1a16286a9d46c150ffbf7a24aef diff --git a/lib/googletest b/lib/googletest new file mode 160000 index 0000000..34d5d22 --- /dev/null +++ b/lib/googletest @@ -0,0 +1 @@ +Subproject commit 34d5d22b6441fd7d91898a48097a18e29d137ace diff --git a/lib/shaderc b/lib/shaderc new file mode 160000 index 0000000..30af9f9 --- /dev/null +++ b/lib/shaderc @@ -0,0 +1 @@ +Subproject commit 30af9f9899aefd018669e81a5b8e605d14d40431 diff --git a/lib/spirv-headers b/lib/spirv-headers new file mode 160000 index 0000000..dcf23bd --- /dev/null +++ b/lib/spirv-headers @@ -0,0 +1 @@ +Subproject commit dcf23bdabacc3c54b83b1f9367e7a8adb27f8d87 diff --git a/lib/spirv-tools b/lib/spirv-tools new file mode 160000 index 0000000..40a6854 --- /dev/null +++ b/lib/spirv-tools @@ -0,0 +1 @@ +Subproject commit 40a68547dcda8b97d467a1d8264f626c63b50a60 diff --git a/src/engine/entity/definitions/triangle.c b/src/engine/entity/definitions/triangle.c new file mode 100644 index 0000000..a1fedee --- /dev/null +++ b/src/engine/entity/definitions/triangle.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "triangle.h" + + +vec2 *triangle_center_position; +buffer_t triangle_position_buffer, triangle_position_staging_buffer; + +void entity_triangle_update(entity_t *entity, void *metadata) +{ + (void) entity; + (void) metadata; + + vec2 position = { + (cursor_x - (vulkan_window_width_get() / 2.0f)) / vulkan_window_width_get(), + (cursor_y - (vulkan_window_height_get() / 2.0f)) / vulkan_window_height_get() + }; + + /** + * set triangle position + */ + memcpy(triangle_center_position, &position, sizeof(vec2)); + + + uint32_t cbuffer_id = vulkan_commands_cpool_cbuffer_id_take(cpool); + vulkan_commands_buffer_begin(cpool, cbuffer_id); + { + vulkan_memory_buffer_copy(&triangle_position_staging_buffer, &triangle_position_buffer, vulkan_commands_cpool_cbuffer_get(cpool, cbuffer_id)); + } + vulkan_commands_buffer_end( + cpool, cbuffer_id, + 0, NULL, NULL, + 0, NULL + ); +} + +void entity_triangle_free(entity_t *entity, void *metadata) +{ + (void) entity; + (void) metadata; +} + +void entity_triangle_draw(entity_t *e, VkCommandBuffer buffer) +{ + (void) e; + + VkBuffer vertexBuffers[] = {triangle_position_buffer.buffer}; + VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(buffer, 0, 1, vertexBuffers, offsets); + + vkCmdDraw(buffer, (uint32_t) triangle_position_buffer.num_elements, 1, 0, 0); +} + + +void entity_triangle_init(entity_t *entity, vulkan *v) +{ + // TODO: Do not allocate this here. This is game state logic + if (!vulkan_memory_vbuffer_allocate(v, &triangle_position_buffer, sizeof(vec2), 1, false)) { + printf("Failed allocate buffer\n"); + } + + if (!vulkan_memory_vbuffer_allocate(v, &triangle_position_staging_buffer, sizeof(vec2), 1, true)) { + printf("Failed allocate buffer\n"); + } + triangle_center_position = triangle_position_staging_buffer.mapped_memory; + + entity->update = (void (*)(struct entity_struct *)) entity_triangle_update; + entity->draw = (void (*)(struct entity_struct *, VkCommandBuffer)) (void (*)(struct entity_struct *)) entity_triangle_draw; + entity->free = (void (*)(struct entity_struct *)) entity_triangle_free; +} diff --git a/src/engine/entity/definitions/triangle.h b/src/engine/entity/definitions/triangle.h new file mode 100644 index 0000000..4dd5923 --- /dev/null +++ b/src/engine/entity/definitions/triangle.h @@ -0,0 +1,17 @@ +#ifndef ENGINE_TRIANGLE_H +#define ENGINE_TRIANGLE_H + + +#include +#include +#include + +void entity_triangle_init(entity_t *entity, vulkan *v); + +void entity_triangle_update(entity_t *entity, void *metadata); + +void entity_triangle_free(entity_t *entity, void *metadata); + +void entity_triangle_draw(entity_t *e, VkCommandBuffer buffer); + +#endif diff --git a/src/engine/entity/entity.c b/src/engine/entity/entity.c new file mode 100644 index 0000000..1ad8317 --- /dev/null +++ b/src/engine/entity/entity.c @@ -0,0 +1,47 @@ +#include "entity.h" + +void entity_init_empty(entity_t *e, void *metadata) +{ + (void) metadata; + if (!e) { + return; + } + + e->allocated = false; + + // Null out behavior + e->draw = NULL; + e->free = NULL; + e->update = NULL; + + if (!e->paramaters) { + // This entity needs a dictionary allocated for them. + e->paramaters = dict_init(16); + } else { + // This entity already had a dict allocated. Just reuse this by emptying it + dict_clear(e->paramaters); + } +} + +void entity_update(entity_t *entity, void *metadata) +{ + (void) metadata; + if (entity->update) { + entity->update(entity); + } +} + +void entity_free(entity_t *entity, void *metadata) +{ + (void) metadata; + if (entity->free) { + entity->free(entity); + } +} + +void entity_draw(entity_t *e, VkCommandBuffer metadata) +{ + if (e->draw) { + e->draw(e, metadata); + } +} diff --git a/src/engine/entity/entity.h b/src/engine/entity/entity.h new file mode 100644 index 0000000..8b74625 --- /dev/null +++ b/src/engine/entity/entity.h @@ -0,0 +1,66 @@ +#ifndef ENGINE_ENTITY_ENTITY_H +#define ENGINE_ENTITY_ENTITY_H + +#include +#include +#include +#include + +/** + * Abstract definition of an entity + */ +typedef struct entity_struct +{ + /** + * Unique ID for every entity + */ + size_t id; + + /** + * Misc paramaters that can't be accounted for + */ + dict *paramaters; + + /** + * If this entity is already being used by something + */ + bool allocated; + + void (*free)(struct entity_struct *); + + void (*update)(struct entity_struct *); + + void (*draw)(struct entity_struct *, VkCommandBuffer buffer); +} entity_t; + +/** + * Abstract definition of initialization of an entity + */ +typedef void (*entity_initializer_t)(entity_t *, void *); + +/** + * Consume and process an entity + */ +typedef void (*entity_consumer_t)(entity_t *, void *); + + +/** + * Initialize an empty entity + * @param e + */ +void entity_init_empty(entity_t *e, void *metadata); + + +void entity_update(entity_t *entity, void *metadata); + +void entity_free(entity_t *entity, void *metadata); + +/** + * Draw an entity to a command buffer + * + * @param e + * @param buffer + */ +void entity_draw(entity_t *e, VkCommandBuffer buffer); + +#endif diff --git a/src/engine/entity/manager.c b/src/engine/entity/manager.c new file mode 100644 index 0000000..ab106f8 --- /dev/null +++ b/src/engine/entity/manager.c @@ -0,0 +1,123 @@ +#include +#include "manager.h" + +struct +{ + size_t freed_entities; + + size_t next_entity_id; + size_t num_entities; + size_t next_allocation_index; + entity_t *entities; +} entity_pool; + + +bool entity_manager_init(size_t num_entites) +{ + entity_pool.freed_entities = 0; + + entity_pool.next_entity_id = 0; + entity_pool.num_entities = num_entites; + entity_pool.next_allocation_index = 0; + entity_pool.entities = calloc(sizeof(entity_t), num_entites); + + + // Pre-allocate null entities + entity_manager_for_each(entity_init_empty, NULL, false); + + return true; +} + +entity_t *entity_manager_take(entity_initializer_t initializer, void *metadata) +{ + entity_t *entity = NULL; + + if (entity_pool.freed_entities) { + // We have non-compacted entities. We need to linearly search rather + // than allocating a new entity + for (size_t i = 0; i < entity_pool.num_entities; i++) { + if (entity_pool.entities[i].allocated) { + continue; + } + + entity = &entity_pool.entities[i]; + break; + } + + if (!entity) { + printf("No entities were free despite freed_entities being marked as %zu\n", entity_pool.freed_entities); + return NULL; + } + + entity_pool.freed_entities -= 1; + } else { + // All of our entities are compactly allocated. We can take from the end + // of our preallocated set and avoid a linear search + if (entity_pool.num_entities < entity_pool.next_allocation_index) { + // TODO: Slab allocation? + printf("Ran out of entities to allocate\n"); + return NULL; + } + entity = &entity_pool.entities[entity_pool.next_allocation_index++]; + } + + entity->id = entity_pool.next_entity_id++; + entity->allocated = true; + + // Call initializer + initializer(entity, metadata); + + return entity; +} + + +void entity_manager_for_each(entity_consumer_t consumer, void *metadata, bool only_allocated) +{ + size_t remaining_freed_entities = entity_pool.freed_entities; + + for (size_t i = 0; i < entity_pool.num_entities; i++) { + + // If we only want to run against allocated entities stop + // as soon as we find a non allocated entity + if (only_allocated) { + if (!entity_pool.entities[i].allocated) { + + // We expected some freed entities. If we have already hit all of the freed entities + // that we knew about then we can finally die + if (!remaining_freed_entities) { + return; + } + + // Otherwise we need to mark that we've seen a freed entity and it skip this one + remaining_freed_entities -= 1; + continue; + } + } + + // Pass to consumer + consumer(&entity_pool.entities[i], metadata); + } +} + + +/** + * Return an entity to the unallocated pool + * @param entity + */ +void entity_manager_release(entity_t *entity) +{ + entity_free(entity, NULL); + entity_init_empty(entity, NULL); + + entity_pool.freed_entities += 1; +} + +void entity_manager_update() +{ + entity_manager_for_each(entity_update, NULL, true); +} + +void entity_manager_draw(VkCommandBuffer buffer) +{ + entity_manager_for_each((entity_consumer_t) entity_draw, buffer, true); +} diff --git a/src/engine/entity/manager.h b/src/engine/entity/manager.h new file mode 100644 index 0000000..58d027b --- /dev/null +++ b/src/engine/entity/manager.h @@ -0,0 +1,47 @@ +#ifndef ENGINE_ENTITY_MANAGER_H +#define ENGINE_ENTITY_MANAGER_H + +#include +#include +#include "entity.h" + +/** + * Wrap entity_manager_make with casts for simplicity + */ +#define entity_manager_make(initializer, metadata) entity_manager_take((entity_initializer_t) initializer, (void*) metadata) + +/** + * Initialize the entity pool + * @param num_entites + * @return + */ +bool entity_manager_init(size_t num_entites); + +/** + * Find a free entity + * @param initializer - Initialization function + * @param metadata - Metadata required to create this entity + * @return + */ +entity_t *entity_manager_take(entity_initializer_t initializer, void *metadata); + +/** + * Return an entity to the unallocated pool + * @param entity + */ +void entity_manager_release(entity_t *entity); + +/** + * Run a function against every allocated entity + * @param consumer + * @param metadata - Data passed to the consumer in addition to the entity + * @param only_allocated + */ +void entity_manager_for_each(entity_consumer_t consumer, void *metadata, bool only_allocated); + + +void entity_manager_update(); + +void entity_manager_draw(VkCommandBuffer buffer); + +#endif diff --git a/src/engine/graphics/vulkan/commands/buffer.c b/src/engine/graphics/vulkan/commands/buffer.c new file mode 100644 index 0000000..50989e6 --- /dev/null +++ b/src/engine/graphics/vulkan/commands/buffer.c @@ -0,0 +1,63 @@ +#include +#include "buffer.h" +#include "pool.h" + + +bool vulkan_commands_buffer_begin(cbuffer_pool_t *pool, uint32_t cbuffer_id) +{ + VkCommandBuffer buffer = vulkan_commands_cpool_cbuffer_get(pool, cbuffer_id); + + VkCommandBufferBeginInfo command_buffer_begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + .pInheritanceInfo = NULL, + }; + + + vkResetCommandBuffer(buffer, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); + + if (vkBeginCommandBuffer(buffer, &command_buffer_begin_info) != VK_SUCCESS) { + printf("Failed to start recording on command buffer %u\n", cbuffer_id); + return false; + } + + return true; +} + + +void vulkan_commands_buffer_end( + cbuffer_pool_t *pool, uint32_t cbuffer_id, + uint32_t num_waiting_semaphores, VkSemaphore *waiting_semaphores, const VkPipelineStageFlags *waiting_semaphores_signaled_at, + uint32_t num_signaling_semaphores, VkSemaphore *signaling_semaphores +) +{ + VkQueue queue = pool->owners.queue; + VkCommandBuffer buffer = vulkan_commands_cpool_cbuffer_get(pool, cbuffer_id); + VkFence fence = vulkan_commands_cpool_fence_get(pool, cbuffer_id); + + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = num_waiting_semaphores, + .pWaitSemaphores = waiting_semaphores, + .pWaitDstStageMask = waiting_semaphores_signaled_at, + + + // Link the command buffer to the render stage + .commandBufferCount = 1, + .pCommandBuffers = &buffer, + + // After rendering signal these semphores + .signalSemaphoreCount = num_signaling_semaphores, + .pSignalSemaphores = signaling_semaphores + }; + + // Mark that we've started using this command buffer + vulkan_commands_cpool_cbuffer_id_mark(pool, cbuffer_id); + + vkEndCommandBuffer(buffer); + + // Submitting render job + if (vkQueueSubmit(queue, 1, &submit_info, fence) != VK_SUCCESS) { + printf("Failed to render!\n"); + } +} diff --git a/src/engine/graphics/vulkan/commands/buffer.h b/src/engine/graphics/vulkan/commands/buffer.h new file mode 100644 index 0000000..7f8b0eb --- /dev/null +++ b/src/engine/graphics/vulkan/commands/buffer.h @@ -0,0 +1,34 @@ +#ifndef ENGINE_GRAPHICS_VULKAN_COMMANDS_BUFFER_H +#define ENGINE_GRAPHICS_VULKAN_COMMANDS_BUFFER_H + +#include + + +/** + * Begin a command buffer + * + * @param pool + * @param cbuffer_id + * @return + */ +bool vulkan_commands_buffer_begin(cbuffer_pool_t *pool, uint32_t cbuffer_id); + +/** + * Submit a command buffer to the GPU on a given queue + * + * @param pool + * @param cbuffer_id + * @param num_waiting_semaphores + * @param waiting_semaphores + * @param waiting_semaphores_signaled_at + * @param num_signaling_semaphores + * @param signaling_semaphores + */ +void vulkan_commands_buffer_end( + cbuffer_pool_t *pool, uint32_t cbuffer_id, + uint32_t num_waiting_semaphores, VkSemaphore *waiting_semaphores, const VkPipelineStageFlags *waiting_semaphores_signaled_at, + uint32_t num_signaling_semaphores, VkSemaphore *signaling_semaphores +); + + +#endif diff --git a/src/engine/graphics/vulkan/commands/pool.c b/src/engine/graphics/vulkan/commands/pool.c new file mode 100644 index 0000000..7ba3f1e --- /dev/null +++ b/src/engine/graphics/vulkan/commands/pool.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include + +/** + * Allocate a VkCommandPool for a given device and queue + * + * @param device + * @param queue_family_index + * @return + */ +VkCommandPool vulkan_commands_cpool_commandpool_allocate(VkDevice device, uint32_t queue_family_index) +{ + VkCommandPool result; + VkCommandPoolCreateInfo command_pool_create_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = queue_family_index + }; + + + if (vkCreateCommandPool(device, &command_pool_create_info, NULL, &result) != VK_SUCCESS) { + printf("Failed to initialize command pool\n"); + return VK_NULL_HANDLE; + } + + return result; +} + +bool vulkan_commands_cpool_fences_allocate(VkDevice device, VkFence *fences, uint32_t size) +{ + const VkFenceCreateInfo fence_create_info = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .flags = 0, + .pNext = NULL + }; + + for (uint32_t i = 0; i < size; i++) { + if (vkCreateFence(device, &fence_create_info, NULL, &fences[i]) != VK_SUCCESS) { + printf("Failed to allocate fence for command buffer!\n"); + return false; + } + } + return true; +} + +bool vulkan_commands_cpool_buffers_allocate(VkDevice device, VkCommandPool pool, VkCommandBuffer *buffers, uint32_t size) +{ + const VkCommandBufferAllocateInfo allocation_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = size + }; + + if (vkAllocateCommandBuffers(device, &allocation_info, buffers) != VK_SUCCESS) { + printf("Failed to allocate command buffer for pool\n"); + return false; + } + + return true; +} + +cbuffer_pool_t *vulkan_commands_cpool_allocate(vulkan *v, uint32_t size) +{ + cbuffer_pool_t *pool = calloc(sizeof(cbuffer_pool_t), 1); + + pool->owners.device = v->devices.logical_device; + pool->owners.pool = vulkan_commands_cpool_commandpool_allocate(v->devices.logical_device, v->queues.main_rendering_queue_id); + pool->owners.queue = v->queues.rendering; + pool->size = size; + + pool->fences = calloc(sizeof(VkFence), size); + pool->already_submitted = calloc(sizeof(bool), size); // this all defaults to 0 which is the correct behavior + pool->buffers = calloc(sizeof(VkCommandBuffer), size); + + // Create resources on device + vulkan_commands_cpool_fences_allocate(v->devices.logical_device, pool->fences, size); + vulkan_commands_cpool_buffers_allocate(v->devices.logical_device, pool->owners.pool, pool->buffers, size); + + // We haven't use anything yet + pool->last_used = 0; + + return pool; +} + + +uint32_t vulkan_commands_cpool_cbuffer_id_take(cbuffer_pool_t *pool) +{ + VkDevice device = pool->owners.device; + // Wait for and locate an unused fence + while (true) { + uint32_t peaking = CBUFFER_POOL_NEXT_INDEX(pool->last_used, pool->size); + + // Check to see if we've come full circle around out command buffer + while (peaking != pool->last_used) { + VkFence fence = vulkan_commands_cpool_fence_get(pool, peaking); + VkCommandBuffer buffer = vulkan_commands_cpool_cbuffer_get(pool, peaking); + + + // We have found a good fence if it's never been submitted before OR if the fence has been signaled by the GPU + if (!pool->already_submitted[peaking] || vkGetFenceStatus(device, fence) == VK_SUCCESS) { + + // If this was already submitted we need to clean up the Fence and CommandBuffer object + if (pool->already_submitted[peaking]) { + vkResetFences(device, 1, &fence); + vkResetCommandBuffer(buffer, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); + } + + return peaking; + } + + peaking = CBUFFER_POOL_NEXT_INDEX(peaking, pool->size); + } + + // Block until we have a fence available + if (vkWaitForFences(device, pool->size, pool->fences, VK_FALSE, UINT64_MAX) != VK_SUCCESS) { + printf("Failed to block on fences"); + } + } +} + +VkCommandBuffer vulkan_commands_cpool_cbuffer_get(cbuffer_pool_t *pool, uint32_t cbuffer_id) +{ + // TODO: Locking & bounds checking + return pool->buffers[cbuffer_id]; +} + +VkFence vulkan_commands_cpool_fence_get(cbuffer_pool_t *pool, uint32_t cbuffer_id) +{ + // TODO: Locking & bounds checking + return pool->fences[cbuffer_id]; +} + +void vulkan_commands_cpool_cbuffer_id_mark(cbuffer_pool_t *pool, uint32_t cbuffer_id) +{ + pool->last_used = cbuffer_id; + pool->already_submitted[cbuffer_id] = true; +} diff --git a/src/engine/graphics/vulkan/commands/pool.h b/src/engine/graphics/vulkan/commands/pool.h new file mode 100644 index 0000000..9fbe3a1 --- /dev/null +++ b/src/engine/graphics/vulkan/commands/pool.h @@ -0,0 +1,75 @@ +#ifndef ENGINE_GRAPHICS_VULKAN_COMMANDS_POOL_H +#define ENGINE_GRAPHICS_VULKAN_COMMANDS_POOL_H + + +#include +#include + +#define CBUFFER_POOL_NEXT_INDEX(last_used, size) ((last_used + 1) % size) + +typedef struct +{ + struct + { + // The device we are allocated against + VkDevice device; + + // The CommandPool object we allocated + VkCommandPool pool; + + // The queue we intend to submit to + VkQueue queue; + } owners; + + uint32_t last_used; + + uint32_t size; + bool *already_submitted; + VkFence *fences; + VkCommandBuffer *buffers; + +} cbuffer_pool_t; + +// TODO: This is terrible. Find a better way to do DI +extern cbuffer_pool_t *cpool; + +/** + * Allocate a pool of command buffers + * @param v + * @param size + * @return + */ +cbuffer_pool_t *vulkan_commands_cpool_allocate(vulkan *v, uint32_t size); + +/** + * Find an unused VkCommandBuffer and VkFence pair. + * @param pool + * @return + */ +uint32_t vulkan_commands_cpool_cbuffer_id_take(cbuffer_pool_t *pool); + +/** + * Get a VkCommandBuffer by it's cbuffer_id + * + * @param pool + * @param cbuffer_id + * @return + */ +VkCommandBuffer vulkan_commands_cpool_cbuffer_get(cbuffer_pool_t *pool, uint32_t cbuffer_id); + +/** + * Get a VkFence by it's cbuffer_id + * @param pool + * @param cbuffer_id + * @return + */ +VkFence vulkan_commands_cpool_fence_get(cbuffer_pool_t *pool, uint32_t cbuffer_id); + +/** + * Mark that we've dirtied this CommandBuffer and Fence + * @param pool + * @return + */ +void vulkan_commands_cpool_cbuffer_id_mark(cbuffer_pool_t *pool, uint32_t cbuffer_id); + +#endif diff --git a/src/engine/graphics/vulkan/commands/render.c b/src/engine/graphics/vulkan/commands/render.c new file mode 100644 index 0000000..aa5a2b9 --- /dev/null +++ b/src/engine/graphics/vulkan/commands/render.c @@ -0,0 +1,30 @@ +#include "render.h" + +void vulkan_commands_render_begin(vulkan *v, cbuffer_pool_t *pool, uint32_t cbuffer_id, uint32_t swapchain_image_id) +{ + VkClearValue clear_color = { + .color.float32 = { + 0, 0, 0, 0 + } + }; + VkRenderPassBeginInfo render_pass_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderPass = v->pipelines.render_pass, + .framebuffer = v->swapchain.frame_buffers[swapchain_image_id], + .renderArea = { + .offset = {0, 0}, + .extent = v->swapchain.extent + }, + .clearValueCount = 1, + .pClearValues = &clear_color, + }; + + + vkCmdBeginRenderPass(pool->buffers[cbuffer_id], &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); +} + + +void vulkan_commands_render_end(cbuffer_pool_t *pool, uint32_t cbuffer_id) +{ + vkCmdEndRenderPass(pool->buffers[cbuffer_id]); +} diff --git a/src/engine/graphics/vulkan/commands/render.h b/src/engine/graphics/vulkan/commands/render.h new file mode 100644 index 0000000..f9ab510 --- /dev/null +++ b/src/engine/graphics/vulkan/commands/render.h @@ -0,0 +1,27 @@ +#ifndef ENGINE_GRAPHICS_VULKAN_COMMANDS_RENDER_H +#define ENGINE_GRAPHICS_VULKAN_COMMANDS_RENDER_H + +#include +#include +#include + + +/** + * Begin a swapchain command + * + * @param v + * @param pool + * @param cbuffer_id + * @param swapchain_image_id + */ +void vulkan_commands_render_begin(vulkan *v, cbuffer_pool_t *pool, uint32_t cbuffer_id, uint32_t swapchain_image_id); + +/** + * End a swapchain command + * + * @param pool + * @param cbuffer_id + */ +void vulkan_commands_render_end(cbuffer_pool_t *pool, uint32_t cbuffer_id); + +#endif diff --git a/src/engine/graphics/vulkan/config.c b/src/engine/graphics/vulkan/config.c new file mode 100644 index 0000000..3f5fce8 --- /dev/null +++ b/src/engine/graphics/vulkan/config.c @@ -0,0 +1,61 @@ +#include +#include "config.h" +#include "vulkan.h" +#include "debug.h" + +#define NUM_LOGICAL_EXTENSIONS 1 +const char *logical_extensions[NUM_LOGICAL_EXTENSIONS] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + + +bool vulkan_config_physical_device_is_suitable(VkPhysicalDevice device) { + VkPhysicalDeviceProperties properties; + VkPhysicalDeviceFeatures features; + vkGetPhysicalDeviceProperties(device, &properties); + vkGetPhysicalDeviceFeatures(device, &features); + + return properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && features.geometryShader; +} + +VkPhysicalDevice vulkan_config_pick_physical_device(vulkan *v) { + if (vkEnumeratePhysicalDevices(v->instance, &v->devices.num_devices, NULL) != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + + v->devices.devices = malloc(sizeof(VkPhysicalDevice) * v->devices.num_devices); + if (vkEnumeratePhysicalDevices(v->instance, &v->devices.num_devices, v->devices.devices) != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + + for (uint32_t i = 0; i < v->devices.num_devices; i++) { + if (vulkan_config_physical_device_is_suitable(v->devices.devices[i])) { + return v->devices.devices[i]; + } + } + + return VK_NULL_HANDLE; +} + + +bool vulkan_config_init(vulkan *v) { + // Request all GLFW extensions to be loaded up + v->required_configuration.num_extensions = v->g.num_extensions + 1; + v->required_configuration.extensions = malloc(sizeof(char *) * v->required_configuration.num_extensions); + for (size_t i = 0; i < v->g.num_extensions; i++) { + v->required_configuration.extensions[i] = v->g.extensions[i]; + } + + // Requrest for debugging extensions to be loaded up + v->required_configuration.extensions[v->g.num_extensions] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + + // request all validation layers + v->required_configuration.num_layers = vulkan_debug_num_validation_layers(); + v->required_configuration.layers = vulkan_debug_validation_layers(); + + + v->required_configuration.num_logical_extensions = NUM_LOGICAL_EXTENSIONS; + v->required_configuration.logical_extensions = logical_extensions; + + return true; +} diff --git a/src/engine/graphics/vulkan/config.h b/src/engine/graphics/vulkan/config.h new file mode 100644 index 0000000..476a4ea --- /dev/null +++ b/src/engine/graphics/vulkan/config.h @@ -0,0 +1,11 @@ +#ifndef ENGINE_CONFIG_H +#define ENGINE_CONFIG_H + +#include "vulkan.h" +#include + +VkPhysicalDevice vulkan_config_pick_physical_device(vulkan *v); + +bool vulkan_config_init(vulkan *v); + +#endif diff --git a/src/engine/graphics/vulkan/debug.c b/src/engine/graphics/vulkan/debug.c new file mode 100644 index 0000000..f086f1d --- /dev/null +++ b/src/engine/graphics/vulkan/debug.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include "vulkan.h" +#include "wrappers.h" + + +#define NUM_WANTED_VALIDATION_LAYERS ((uint32_t) 2) +const char *wanted_validation_layers[NUM_WANTED_VALIDATION_LAYERS] = { + "VK_LAYER_LUNARG_core_validation", + "VK_LAYER_LUNARG_standard_validation" + +}; + + +static VKAPI_ATTR VkBool32 VKAPI_CALL on_error( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT *data, + void *user_date +) +{ + // Ignore lack of usage + ((void) severity); + ((void) type); + ((void) data); + ((void) user_date); + + printf("Validation Message: %s\n", data->pMessage); + + return VK_FALSE; +} + + +uint32_t vulkan_debug_num_validation_layers() +{ + return NUM_WANTED_VALIDATION_LAYERS; +} + +const char **vulkan_debug_validation_layers() +{ + return wanted_validation_layers; +} + + +void vulkan_debug_init(vulkan *v) +{ + VkDebugUtilsMessengerCreateInfoEXT creation_request = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, + .pfnUserCallback = on_error, + .pUserData = NULL // Optional + }; + + wrap_vulkan_create_debug_utils_messenger_ext( + v->instance, + &creation_request, + NULL, + &v->debugging.debug_utils_messenger_callback + ); +} + +bool vulkan_debug_has_all_wanted_validation_layers(vulkan *v) +{ + + for (uint32_t wanted_layer_index = 0; wanted_layer_index < NUM_WANTED_VALIDATION_LAYERS; wanted_layer_index++) { + bool found = false; + + // Search array of available layers + for (uint32_t i = 0; !found && i < v->layers.num_properties; i++) { + found = strcmp(wanted_validation_layers[wanted_layer_index], v->layers.properties[i].layerName) == 0; + } + + if (!found) { + return false; + } + } + + return true; +} diff --git a/src/engine/graphics/vulkan/debug.h b/src/engine/graphics/vulkan/debug.h new file mode 100644 index 0000000..5a691e6 --- /dev/null +++ b/src/engine/graphics/vulkan/debug.h @@ -0,0 +1,24 @@ +#ifndef ENGINE_HDEBUG_H +#define ENGINE_HDEBUG_H + +#include +#include "vulkan.h" + +/** + * Check if all validation layers can be provided by vulkan on this system + * @param v + * @return + */ +bool vulkan_debug_has_all_wanted_validation_layers(vulkan *v); + +/** + * Set debugger callbacks into vulkan instance + * @param v + */ +void vulkan_debug_init(vulkan *v); + +uint32_t vulkan_debug_num_validation_layers(); + +const char **vulkan_debug_validation_layers(); + +#endif diff --git a/src/engine/graphics/vulkan/framebuffer.c b/src/engine/graphics/vulkan/framebuffer.c new file mode 100644 index 0000000..2edf3f2 --- /dev/null +++ b/src/engine/graphics/vulkan/framebuffer.c @@ -0,0 +1,34 @@ +#include +#include "framebuffer.h" + +bool vulkan_framebuffer_init(vulkan *v) +{ + v->swapchain.frame_buffers = malloc(sizeof(VkFramebuffer) * v->swapchain.num_images); + + VkFramebufferCreateInfo framebuffer_create_info = { + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = v->pipelines.render_pass, + .attachmentCount = 1, + .pAttachments = NULL, // configured later + .width = v->swapchain.extent.width, + .height = v->swapchain.extent.height, + .layers = 1 + }; + + for (size_t i = 0; i < v->swapchain.num_images; i++) { + framebuffer_create_info.pAttachments = &v->swapchain.image_views[i]; + VkResult creation_result = vkCreateFramebuffer( + v->devices.logical_device, + &framebuffer_create_info, + NULL, + &v->swapchain.frame_buffers[i] + ); + + if (creation_result != VK_SUCCESS) { + printf("Failed to create vulkan framebuffer for swapchain image %zu\n", i); + return false; + } + } + + return true; +} diff --git a/src/engine/graphics/vulkan/framebuffer.h b/src/engine/graphics/vulkan/framebuffer.h new file mode 100644 index 0000000..f72e6bb --- /dev/null +++ b/src/engine/graphics/vulkan/framebuffer.h @@ -0,0 +1,10 @@ +#ifndef ENGINE_FRAMEBUFFER_H +#define ENGINE_FRAMEBUFFER_H + +#include +#include "vulkan.h" + +bool vulkan_framebuffer_init(vulkan *v); + +#endif + diff --git a/src/engine/graphics/vulkan/locking.c b/src/engine/graphics/vulkan/locking.c new file mode 100644 index 0000000..be55bcb --- /dev/null +++ b/src/engine/graphics/vulkan/locking.c @@ -0,0 +1,28 @@ +#include "locking.h" + + +VkSemaphoreCreateInfo semaphore_create_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .flags = 0, + .pNext = NULL +}; + +VkSemaphore frame_buffer_image_available; +VkSemaphore rendering_finished; + +bool vulkan_locking_init(vulkan *v) +{ + VkDevice device = v->devices.logical_device; + return vkCreateSemaphore(device, &semaphore_create_info, NULL, &frame_buffer_image_available) == VK_SUCCESS && + vkCreateSemaphore(device, &semaphore_create_info, NULL, &rendering_finished) == VK_SUCCESS; +} + +VkSemaphore vulkan_locking_semphore_get_frame_buffer_image_available() +{ + return frame_buffer_image_available; +} + +VkSemaphore vulkan_locking_semphore_get_rendering_finished() +{ + return rendering_finished; +} diff --git a/src/engine/graphics/vulkan/locking.h b/src/engine/graphics/vulkan/locking.h new file mode 100644 index 0000000..d7a8fd5 --- /dev/null +++ b/src/engine/graphics/vulkan/locking.h @@ -0,0 +1,12 @@ +#ifndef ENGINE_LOCKING_H +#define ENGINE_LOCKING_H + +#include "vulkan.h" + + +bool vulkan_locking_init(vulkan *v); +VkSemaphore vulkan_locking_semphore_get_frame_buffer_image_available(); +VkSemaphore vulkan_locking_semphore_get_rendering_finished(); + +#endif + diff --git a/src/engine/graphics/vulkan/materials/material.c b/src/engine/graphics/vulkan/materials/material.c new file mode 100644 index 0000000..87a2271 --- /dev/null +++ b/src/engine/graphics/vulkan/materials/material.c @@ -0,0 +1,3 @@ +#include "material.h" + + diff --git a/src/engine/graphics/vulkan/materials/material.h b/src/engine/graphics/vulkan/materials/material.h new file mode 100644 index 0000000..f0bdead --- /dev/null +++ b/src/engine/graphics/vulkan/materials/material.h @@ -0,0 +1,22 @@ +#ifndef ENGINE_GRAPHICS_VULKAN_MATERIALS_MATERIAL_H +#define ENGINE_GRAPHICS_VULKAN_MATERIALS_MATERIAL_H + +#include +#include +#include + +typedef struct material_s +{ + VkPipeline pipeline; + buffer_t verticies; +} material_t; + +/** + * Setup material for rendering + * + * @param material + * @param buffer + */ +void vulkan_material_bind(material_t *material, VkCommandBuffer buffer); + +#endif diff --git a/src/engine/graphics/vulkan/memory/buffer.c b/src/engine/graphics/vulkan/memory/buffer.c new file mode 100644 index 0000000..20fb9ca --- /dev/null +++ b/src/engine/graphics/vulkan/memory/buffer.c @@ -0,0 +1,93 @@ +#include +#include +#include + + +bool vulkan_memory_buffer_allocate( + vulkan *v, + buffer_t *buffer, + VkMemoryPropertyFlags buffer_memory_property_flags, + VkBufferUsageFlags buffer_usage_flags, VkSharingMode buffer_sharing_mode, + uint32_t element_size, uint32_t num_elements, + bool map_memory_region +) +{ + + if (map_memory_region) { + // If we want to memory map this region it must be visible to the host!!! + buffer_memory_property_flags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + buffer_usage_flags |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + } else { + // If we dont mmap this region it must be possible to copy into this buffer + buffer_usage_flags |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + } + + const VkDeviceSize size = element_size * num_elements; + + // Clear out old buffer + memset(buffer, 0, sizeof(buffer_t)); + + // Engine metadata + buffer->element_size = element_size; + buffer->num_elements = num_elements; + + // Set up buffer dimension info + buffer->info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer->info.size = size; + buffer->info.usage = buffer_usage_flags; + buffer->info.sharingMode = buffer_sharing_mode; + + if (vkCreateBuffer(v->devices.logical_device, &buffer->info, NULL, &buffer->buffer) != VK_SUCCESS) { + return false; + } + + vkGetBufferMemoryRequirements(v->devices.logical_device, buffer->buffer, &buffer->required_memory); + + if (!vulkan_memory_allocate(v, buffer->required_memory.memoryTypeBits, buffer_memory_property_flags, buffer->required_memory.size, &buffer->memory)) { + return false; + } + + // Bind the memory to the new buffer + if (vkBindBufferMemory(v->devices.logical_device, buffer->buffer, buffer->memory, 0) != VK_SUCCESS) { + return false; + } + + if (map_memory_region) { + // Map memory location into trapped page (I guess?) + if (!vulkan_memory_map(v, buffer->memory, 0, buffer->info.size, &buffer->mapped_memory)) { + return false; + } + } + + return true; +} + + +void vulkan_memory_buffer_free(vulkan *v, buffer_t *buffer) +{ + if (!buffer || !buffer->memory) { + return; + } + + // If we have mapped memory, we need to unmap it first. + if (buffer->mapped_memory) { + vulkan_memory_unmap(v, buffer->memory, &buffer->mapped_memory); + } + + vulkan_memory_free(v, buffer->memory); + // Clear out old buffer + memset(buffer, 0, sizeof(buffer_t)); +} + +void vulkan_memory_buffer_copy( + buffer_t *from, buffer_t *to, + VkCommandBuffer buffer +) +{ + VkBufferCopy copy_request = { + .srcOffset = 0, + .dstOffset = 0, + .size = from->required_memory.size, + }; + vkCmdCopyBuffer(buffer, from->buffer, to->buffer, 1, ©_request); +} diff --git a/src/engine/graphics/vulkan/memory/buffer.h b/src/engine/graphics/vulkan/memory/buffer.h new file mode 100644 index 0000000..f050545 --- /dev/null +++ b/src/engine/graphics/vulkan/memory/buffer.h @@ -0,0 +1,60 @@ +#ifndef ENGINE_GRAPHICS_VULKAN_MEMORY_BUFFER_H +#define ENGINE_GRAPHICS_VULKAN_MEMORY_BUFFER_H + +#include +#include +#include + +typedef struct +{ + VkBufferCreateInfo info; + VkBuffer buffer; + VkMemoryRequirements required_memory; + VkDeviceMemory memory; + size_t element_size; + size_t num_elements; + void *mapped_memory; +} buffer_t; + + +/** + * Allocate a buffer + * + * @param v - Vulkan instance to attach to + * @param buffer - Buffer to create + * @param buffer_memory_property_flags - What interface requirements we have. (CPU can see? Coherent? etc) + * @param buffer_usage_flags - Mask provided by the GPU which describes where we can look for memory + * @param buffer_sharing_mode - Sharing mode + * @param element_size - Width of a single element within the buffer + * @param num_elements - Number of elements in the buffer + * @param map_memory_region - If we should map a segment of memory for this buffer + * @return + */ +bool vulkan_memory_buffer_allocate( + vulkan *v, + buffer_t *buffer, + VkMemoryPropertyFlags buffer_memory_property_flags, + VkBufferUsageFlags buffer_usage_flags, VkSharingMode buffer_sharing_mode, + uint32_t element_size, uint32_t num_elements, + bool map_memory_region +); + +/** + * Vulkan free a memory buffer from the GPU + * @param v + * @param buffer + */ +void vulkan_memory_buffer_free(vulkan *v, buffer_t *buffer); + +/** + * Schedule a buffer copy into the VkCommandBuffer + * @param from - Source buffer + * @param to - Destination buffer + * @param buffer - Command buffer + */ +void vulkan_memory_buffer_copy( + buffer_t *from, buffer_t *to, + VkCommandBuffer buffer +); + +#endif diff --git a/src/engine/graphics/vulkan/memory/memory.c b/src/engine/graphics/vulkan/memory/memory.c new file mode 100644 index 0000000..25b65a1 --- /dev/null +++ b/src/engine/graphics/vulkan/memory/memory.c @@ -0,0 +1,101 @@ +#include +#include "memory.h" + +vulkan_device_memory_info info; + +static inline bool vulkan_memory_properties_has_all_desired_features( + const VkMemoryPropertyFlags available, + const VkMemoryPropertyFlags desired +) +{ + return IS_EVERY_BIT_SET(available, desired); +} + +uint32_t vulkan_memory_type_find(uint32_t memory_type_mask, VkMemoryPropertyFlags properties) +{ + for (uint32_t i = 0; i < info.properties.memoryTypeCount; i++) { + const VkMemoryPropertyFlags memory_type_properties = info.properties.memoryTypes[i].propertyFlags; + + if (!IS_BIT_SET(memory_type_mask, i)) { + continue; + } + + if (!vulkan_memory_properties_has_all_desired_features(memory_type_properties, properties)) { + continue; + } + + return i; + } + + return UINT32_MAX; +} + +// TODO: Implement memory mapping, flushing mapped memory, and invalidating mapped memory caches. +// TODO: Move memory mapping out of vbuffer.c/h + +bool vulkan_memory_allocate( + const vulkan *const v, + const uint32_t memory_type_mask, + const VkMemoryPropertyFlags properties, + const VkDeviceSize size, + VkDeviceMemory *const memory +) +{ + + // If you don't store the memory address we refuse to allocate anything + if (!memory) { + return false; + } + + // Memory index that supports our variable filter flags + uint32_t memory_type_index = vulkan_memory_type_find(memory_type_mask, properties); + + if (memory_type_index == UINT32_MAX) { + return false; + } + + VkMemoryAllocateInfo memory_allocation_info = { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = size, + .memoryTypeIndex = memory_type_index + }; + + return vkAllocateMemory(v->devices.logical_device, &memory_allocation_info, NULL, memory) == VK_SUCCESS; +} + +bool vulkan_memory_map( + const vulkan *const v, + VkDeviceMemory memory, + VkDeviceSize offset, VkDeviceSize size, + void **ptr +) +{ + return vkMapMemory(v->devices.logical_device, memory, offset, size, 0, ptr) == VK_SUCCESS; +} + + + +void vulkan_memory_unmap( + const vulkan *const v, + VkDeviceMemory memory, + void **ptr +) +{ + vkUnmapMemory(v->devices.logical_device, memory); + *ptr = NULL; +} + + +void vulkan_memory_free( + const vulkan *v, + VkDeviceMemory memory +) +{ + vkFreeMemory(v->devices.logical_device, memory, NULL); +} + +bool vulkan_memory_init(vulkan *v) +{ + vkGetPhysicalDeviceMemoryProperties(v->devices.selected_device, &info.properties); + return true; +} diff --git a/src/engine/graphics/vulkan/memory/memory.h b/src/engine/graphics/vulkan/memory/memory.h new file mode 100644 index 0000000..45a1e69 --- /dev/null +++ b/src/engine/graphics/vulkan/memory/memory.h @@ -0,0 +1,40 @@ +#ifndef ENGINE_MEMORY_H +#define ENGINE_MEMORY_H + +#include +#include "src/engine/graphics/vulkan/vulkan.h" + +typedef struct +{ + VkPhysicalDeviceMemoryProperties properties; +} vulkan_device_memory_info; + +bool vulkan_memory_allocate( + const vulkan *v, + uint32_t memory_type_mask, + VkMemoryPropertyFlags properties, + VkDeviceSize size, + VkDeviceMemory *memory +); + +void vulkan_memory_free( + const vulkan *v, + VkDeviceMemory memory +); + +bool vulkan_memory_map( + const vulkan *v, + VkDeviceMemory memory, + VkDeviceSize offset, VkDeviceSize size, + void **ptr +); + +void vulkan_memory_unmap( + const vulkan *v, + VkDeviceMemory memory, + void **ptr +); + +bool vulkan_memory_init(vulkan *v); + +#endif diff --git a/src/engine/graphics/vulkan/memory/vbuffer.c b/src/engine/graphics/vulkan/memory/vbuffer.c new file mode 100644 index 0000000..d801928 --- /dev/null +++ b/src/engine/graphics/vulkan/memory/vbuffer.c @@ -0,0 +1,18 @@ +#include +#include +#include "vbuffer.h" + + +bool vulkan_memory_vbuffer_allocate( + vulkan *v, + buffer_t *buffer, + uint32_t element_size, + uint32_t num_elements, + bool map_memory_region +) +{ + // Memory is going to be used to store vertex data + const VkBufferUsageFlags usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + const VkSharingMode sharing = VK_SHARING_MODE_EXCLUSIVE; + return vulkan_memory_buffer_allocate(v, buffer, 0, usage, sharing, element_size, num_elements, map_memory_region); +} diff --git a/src/engine/graphics/vulkan/memory/vbuffer.h b/src/engine/graphics/vulkan/memory/vbuffer.h new file mode 100644 index 0000000..a5e123e --- /dev/null +++ b/src/engine/graphics/vulkan/memory/vbuffer.h @@ -0,0 +1,29 @@ +#ifndef ENGINE_GRAPHICS_VULKAN_MEMORY_VBUFFER_H +#define ENGINE_GRAPHICS_VULKAN_MEMORY_VBUFFER_H + + +#include +#include +#include +#include + + +/** + * Allocate a buffer for usage in a vertex buffer + * + * @param v + * @param buffer + * @param element_size + * @param num_elements + * @param map_memory_region + * @return + */ +bool vulkan_memory_vbuffer_allocate( + vulkan *v, + buffer_t *buffer, + uint32_t element_size, + uint32_t num_elements, + bool map_memory_region +); + +#endif diff --git a/src/engine/graphics/vulkan/pipeline.c b/src/engine/graphics/vulkan/pipeline.c new file mode 100644 index 0000000..e1b7783 --- /dev/null +++ b/src/engine/graphics/vulkan/pipeline.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include "pipeline.h" +#include "window.h" +#include "src/engine/graphics/vulkan/shaders/shader.h" + + +static VkViewport viewport = { + .x = 0.0f, + .y = 0.0f, + .width = 0, // This will be configured later in the code + .height = 0, // This will be configured later in the code + .minDepth = 0.0f, + .maxDepth = 1.0f +}; + +static VkRect2D scissor = { + .offset = {0, 0}, + .extent = { + .height = 0, + .width = 0 + } // This will be configured later in the code +}; + +static VkPipelineViewportStateCreateInfo viewport_state_creation_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .pViewports = &viewport, + .scissorCount = 1, + .pScissors = &scissor +}; + +static VkPipelineRasterizationStateCreateInfo rasterizer = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .lineWidth = 1.0f, + .cullMode = VK_CULL_MODE_BACK_BIT, + .frontFace = VK_FRONT_FACE_CLOCKWISE, + + + .depthBiasEnable = VK_FALSE, + .depthBiasConstantFactor = 0.0f, + .depthBiasClamp = 0.0f, + .depthBiasSlopeFactor = 0.0f, +}; + +static VkPipelineMultisampleStateCreateInfo multisampling = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .sampleShadingEnable = VK_FALSE, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .minSampleShading = 1.0f, + .pSampleMask = NULL, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE +}; + +static VkPipelineColorBlendAttachmentState color_blending_attachment_state = { + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | + VK_COLOR_COMPONENT_A_BIT, + .blendEnable = VK_FALSE, + .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, + .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, +}; + +static VkPipelineColorBlendStateCreateInfo color_blending = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_COPY, + .attachmentCount = 1, + .pAttachments = &color_blending_attachment_state, + .blendConstants = { + 0.0f, + 0.0f, + 0.0f, + 0.0f + } +}; + +const VkDynamicState dynamic_states[] = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_LINE_WIDTH, +}; + +bool vulkan_pipeline_render_pass_init(vulkan *v) +{ + + + VkSubpassDependency dependency = { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT + }; + + VkAttachmentDescription color_attachment = { + .samples = VK_SAMPLE_COUNT_1_BIT, + .format = v->swapchain.format.format, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR + }; + VkAttachmentReference reference = { + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + VkSubpassDescription subpass = { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + + .colorAttachmentCount = 1, + .pColorAttachments = &reference, + }; + + VkRenderPassCreateInfo render_pass_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + + .attachmentCount = 1, + .pAttachments = &color_attachment, + + .subpassCount = 1, + .pSubpasses = &subpass, + + .dependencyCount = 1, + .pDependencies = &dependency + }; + + + return vkCreateRenderPass( + v->devices.logical_device, + &render_pass_info, + NULL, + &v->pipelines.render_pass + ) == VK_SUCCESS; +} + +bool vulkan_pipeline_layout_init(vulkan *v) +{ + VkPipelineLayoutCreateInfo request = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = 0, + .pSetLayouts = NULL, + .pushConstantRangeCount = 0, + .pPushConstantRanges = NULL, + }; + + return vkCreatePipelineLayout(v->devices.logical_device, &request, NULL, &v->pipelines.layout) == VK_SUCCESS; +} + +bool vulkan_pipeline_graphics_init( + vulkan *v, + shader_group_t *group +) +{ + + VkGraphicsPipelineCreateInfo graphics_pipeline_create_info = { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .stageCount = group->num_shaders, + .pStages = group->stages, + + .pVertexInputState = &group->vertex_input_state_create_info, + .pInputAssemblyState = &group->input_assembly_state, + .pViewportState = &viewport_state_creation_info, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = NULL, // Optional + .pColorBlendState = &color_blending, + .pDynamicState = NULL, // &dynamic_state_create_info, // Optional + + .layout = v->pipelines.layout, + + .renderPass = v->pipelines.render_pass, + .subpass = 0, + + .basePipelineHandle = VK_NULL_HANDLE, + //.basePipelineIndex = -1, + }; + + return vkCreateGraphicsPipelines( + v->devices.logical_device, + VK_NULL_HANDLE, + 1, + &graphics_pipeline_create_info, + NULL, + &v->pipelines.graphics + ) == VK_SUCCESS; +} + +bool vulkan_pipeline_init(vulkan *v) +{ + + // configure viewport size + viewport.width = vulkan_window_width_get(); + viewport.height = vulkan_window_height_get(); + + // configure swapchain extent + scissor.extent = v->swapchain.extent; + + + shader_group_t *group = vulkan_shader_group_create(v->devices.logical_device); + + if (!vulkan_pipeline_layout_init(v)) { + printf("Failed to create pipeline layout\n"); + return false; + } + + if (!vulkan_pipeline_render_pass_init(v)) { + printf("Failed to initialize render pass\n"); + return false; + } + + if (!vulkan_pipeline_graphics_init(v, group)) { + printf("Failed to initialize graphics pipeline\n"); + return false; + } + + return true; +} diff --git a/src/engine/graphics/vulkan/pipeline.h b/src/engine/graphics/vulkan/pipeline.h new file mode 100644 index 0000000..7bf002d --- /dev/null +++ b/src/engine/graphics/vulkan/pipeline.h @@ -0,0 +1,9 @@ +#ifndef ENGINE_PIPELINE_H +#define ENGINE_PIPELINE_H + +#include +#include "vulkan.h" + +bool vulkan_pipeline_init(vulkan *v); + +#endif diff --git a/src/engine/graphics/vulkan/query.c b/src/engine/graphics/vulkan/query.c new file mode 100644 index 0000000..1e6deba --- /dev/null +++ b/src/engine/graphics/vulkan/query.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include "query.h" + + +/** + * Load hardware and rendering information from GLFW + */ +bool glfw_query(vulkan *v) +{ + v->g.extensions = glfwGetRequiredInstanceExtensions(&v->g.num_extensions); + return true; +} + +/** + * Load available extensions from vulkan + * @return + */ +bool vulkan_query_extensions(vulkan *v) +{ + if (vkEnumerateInstanceExtensionProperties(NULL, &v->extensions.num_properties, NULL) != VK_SUCCESS) { + return false; + } + + v->extensions.properties = malloc(sizeof(VkExtensionProperties) * v->extensions.num_properties); + + return vkEnumerateInstanceExtensionProperties(NULL, &v->extensions.num_properties, v->extensions.properties) == + VK_SUCCESS; +} + +bool vulkan_query_layers(vulkan *v) +{ + if (vkEnumerateInstanceLayerProperties(&v->layers.num_properties, NULL) != VK_SUCCESS) { + return false; + } + + v->layers.properties = malloc(sizeof(VkLayerProperties) * v->layers.num_properties); + + if (vkEnumerateInstanceLayerProperties(&v->layers.num_properties, v->layers.properties) != VK_SUCCESS) { + printf("Failed to get extension properties\n"); + return false; + } + + return true; +} + + +/** + * Query all hardware on the system + * @return + */ +bool vulkan_hardware_query(vulkan *v) +{ + return + glfw_query(v) && + vulkan_query_extensions(v) && + vulkan_query_layers(v); +} diff --git a/src/engine/graphics/vulkan/query.h b/src/engine/graphics/vulkan/query.h new file mode 100644 index 0000000..82e0d36 --- /dev/null +++ b/src/engine/graphics/vulkan/query.h @@ -0,0 +1,14 @@ +#ifndef ENGINE_HQUERY_H +#define ENGINE_HQUERY_H + +#include +#include + +/** + * Query all hardware from vulkan system + * @param v + * @return + */ +bool vulkan_hardware_query(vulkan *v); + +#endif diff --git a/src/engine/graphics/vulkan/queues.c b/src/engine/graphics/vulkan/queues.c new file mode 100644 index 0000000..667c78b --- /dev/null +++ b/src/engine/graphics/vulkan/queues.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include "queues.h" +#include "vulkan.h" + +typedef bool (*vulkan_queues_selection_criteria_t)(vulkan *v, VkPhysicalDevice device, uint32_t i, + VkQueueFamilyProperties *properties); + + +bool +vulkan_queues_selection_filter_is_presentable(vulkan *v, VkPhysicalDevice device, uint32_t i, + VkQueueFamilyProperties *properties) +{ + ((void) properties); + VkBool32 presentable = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, v->surface, &presentable); + return (bool) presentable; +} + +bool +vulkan_queues_selection_filter_is_graphics_queue(vulkan *v, VkPhysicalDevice device, uint32_t i, + VkQueueFamilyProperties *properties) +{ + + ((void) v); + ((void) device); + ((void) i); + return (bool) properties->queueFlags & VK_QUEUE_GRAPHICS_BIT; +} + +VkResult vulkan_queues_init_queue_family(vulkan *v, VkPhysicalDevice device, float priority) +{ + + + v->queues.num_queue_families = 2; + + // We don't need to double create the same queue. + // TODO: Hack. Make this handle a dynamic number of queues + if (v->queues.main_presentation_queue_id == v->queues.main_rendering_queue_id) { + v->queues.num_queue_families = 1; + v->queues.queue_families[0] = v->queues.main_presentation_queue_id; + } else { + v->queues.num_queue_families = 2; + v->queues.queue_families[0] = v->queues.main_presentation_queue_id; + v->queues.queue_families[1] = v->queues.main_rendering_queue_id; + } + + VkDeviceQueueCreateInfo queue_creations[] = { + { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = v->queues.main_rendering_queue_id, + .queueCount = 1, + .pQueuePriorities = &priority + }, + { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = v->queues.main_presentation_queue_id, + .queueCount = 1, + .pQueuePriorities = &priority + }, + }; + + VkPhysicalDeviceFeatures device_features; + memset(&device_features, 0, sizeof(VkPhysicalDeviceFeatures)); + + // Geometry shaders enabled. TODO: Refactor + device_features.geometryShader = VK_TRUE; + + VkDeviceCreateInfo request = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pQueueCreateInfos = queue_creations, + .queueCreateInfoCount = v->queues.num_queue_families, + .pEnabledFeatures = &device_features, + + // Device extensions + .enabledExtensionCount = v->required_configuration.num_logical_extensions, + .ppEnabledExtensionNames = v->required_configuration.logical_extensions, + + // Validation Layers + .enabledLayerCount = v->required_configuration.num_layers, + .ppEnabledLayerNames = v->required_configuration.layers + }; + + + return vkCreateDevice(device, &request, NULL, &v->devices.logical_device); +} + + +uint32_t vulkan_queues_find_appropriate_queue(vulkan *v, vulkan_queues_selection_criteria_t filter) +{ + for (uint32_t i = 0; i < v->queues.num_properties; i++) { + VkQueueFamilyProperties *properties = &v->queues.properties[i]; + + if (filter(v, v->devices.selected_device, i, properties)) { + return i; + } + } + return UINT32_MAX; +} + +void vulkan_queues_cleanup(vulkan *v) +{ + vkDestroyDevice(v->devices.logical_device, NULL); +} + +bool vulkan_queues_init(vulkan *v) +{ + VkPhysicalDevice device = v->devices.selected_device; + + // Find properties + vkGetPhysicalDeviceQueueFamilyProperties(device, &v->queues.num_properties, NULL); + v->queues.properties = malloc(sizeof(VkQueueFamilyProperties) * v->queues.num_properties); + vkGetPhysicalDeviceQueueFamilyProperties(device, &v->queues.num_properties, v->queues.properties); + + + v->queues.main_rendering_queue_id = vulkan_queues_find_appropriate_queue(v, + vulkan_queues_selection_filter_is_graphics_queue); + v->queues.main_presentation_queue_id = vulkan_queues_find_appropriate_queue(v, + vulkan_queues_selection_filter_is_presentable); + + if (v->queues.main_rendering_queue_id == UINT32_MAX || v->queues.main_presentation_queue_id == UINT32_MAX) { + return false; + } + + + if (vulkan_queues_init_queue_family(v, v->devices.selected_device, 1.0f) != VK_SUCCESS) { + printf("Failed to initialize queue family\n"); + return false; + } + + + // Load instance into vulkan structure + vkGetDeviceQueue(v->devices.logical_device, v->queues.main_rendering_queue_id, 0, &v->queues.rendering); + vkGetDeviceQueue(v->devices.logical_device, v->queues.main_presentation_queue_id, 0, &v->queues.presenting); + + return true; +} diff --git a/src/engine/graphics/vulkan/queues.h b/src/engine/graphics/vulkan/queues.h new file mode 100644 index 0000000..4770be6 --- /dev/null +++ b/src/engine/graphics/vulkan/queues.h @@ -0,0 +1,10 @@ +#ifndef ENGINE_QUEUES_H +#define ENGINE_QUEUES_H + +#include "vulkan.h" + +void vulkan_queues_cleanup(vulkan *v); + +bool vulkan_queues_init(vulkan *v); + +#endif diff --git a/src/engine/graphics/vulkan/shaders/shader.c b/src/engine/graphics/vulkan/shaders/shader.c new file mode 100644 index 0000000..0f056c9 --- /dev/null +++ b/src/engine/graphics/vulkan/shaders/shader.c @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include "shader.h" + +dict *compiled_shader_map; +llist *compiled; +shaderc_compiler_t compiler; + +void vulkan_shader_compiler_init() { + compiler = shaderc_compiler_initialize(); +} + +static inline bool vulkan_shader_shaderc_kind_to_stage_bit(shaderc_shader_kind kind, VkShaderStageFlagBits *bits) { + if (kind == shaderc_glsl_vertex_shader) { + *bits = VK_SHADER_STAGE_VERTEX_BIT; + return true; + } + + if (kind == shaderc_glsl_fragment_shader) { + *bits = VK_SHADER_STAGE_FRAGMENT_BIT; + return true; + } + + if (kind == shaderc_glsl_geometry_shader) { + *bits = VK_SHADER_STAGE_GEOMETRY_BIT; + return true; + } + + printf("Compiled unknown shaderc type\n"); + return false; +} + +/** + * Find the shader kind from the file name + * + * @param file_name + * @return the shader kind or shaderc_glsl_infer_from_source if we could not determine from the + */ +static inline shaderc_shader_kind vulkan_shader_shaderc_detect_kind(char *file_name) { +#define NUM_SHADER_KINDS 3 + + static const char *extensions[NUM_SHADER_KINDS] = { + "vert", + "frag", + "geom" + }; + static const shaderc_shader_kind kinds[NUM_SHADER_KINDS] = { + shaderc_glsl_vertex_shader, + shaderc_glsl_fragment_shader, + shaderc_glsl_geometry_shader + }; + + const char *file_extension = file_extract_extension(file_name); + + for (size_t i = 0; i < NUM_SHADER_KINDS; i++) { + if (strcmp(extensions[i], file_extension) == 0) { + return kinds[i]; + } + } + + return shaderc_glsl_infer_from_source; +} + +shader_group_t *vulkan_shader_group_create(VkDevice device) { +#define NUM_SHADERS 3 + shader_group_t *group = malloc(sizeof(shader_group_t)); + memset(group, 0, sizeof(shader_group_t)); + // Shader space + group->num_shaders = NUM_SHADERS; + group->shaders = malloc(sizeof(shader_t *) * group->num_shaders); + group->stages = malloc(sizeof(VkPipelineShaderStageCreateInfo) * group->num_shaders); + + // Vertex input attribute space + group->num_vertex_input_attribute_descriptions = 1; + group->vertex_input_attribute_descriptions = malloc(sizeof(VkVertexInputAttributeDescription) * group->num_vertex_input_attribute_descriptions); + + // Vertex description space + group->num_vertex_input_binding_descriptions = 1; + group->vertex_input_binding_descriptions = malloc(sizeof(VkVertexInputBindingDescription) * group->num_vertex_input_binding_descriptions); + + // Load all shaders for this group + group->shaders[0] = vulkan_shader_compile(device, VULKAN_SHADER_VERTEX_TEST); + group->shaders[1] = vulkan_shader_compile(device, VULKAN_SHADER_GEOMETRY_TEST); + group->shaders[2] = vulkan_shader_compile(device, VULKAN_SHADER_FRAGMENT_TEST); + + + // Input Assembly State. + // Define what kind of data we will be sending in + group->input_assembly_state.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + group->input_assembly_state.pNext = NULL; + group->input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + group->input_assembly_state.primitiveRestartEnable = VK_FALSE; + + + // Define the Vertex Input Attribute Description + group->vertex_input_attribute_descriptions[0].binding = 0; + group->vertex_input_attribute_descriptions[0].offset = 0; + group->vertex_input_attribute_descriptions[0].format = VK_FORMAT_R32G32_SFLOAT; + group->vertex_input_attribute_descriptions[0].location = 0; + + // Input binding descriptions + group->vertex_input_binding_descriptions[0].binding = 0; + group->vertex_input_binding_descriptions[0].stride = sizeof(vec2); + group->vertex_input_binding_descriptions[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + for (size_t i = 0; i < group->num_shaders; i++) { + group->stages[i] = group->shaders[i]->stage_info; + } + + // Vertex Input State Create Info + // - This is used to create and bind to the pipeline + group->vertex_input_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + group->vertex_input_state_create_info.vertexBindingDescriptionCount = group->num_vertex_input_binding_descriptions; + group->vertex_input_state_create_info.pVertexBindingDescriptions = group->vertex_input_binding_descriptions; + group->vertex_input_state_create_info.vertexAttributeDescriptionCount = group->num_vertex_input_attribute_descriptions; + group->vertex_input_state_create_info.pVertexAttributeDescriptions = group->vertex_input_attribute_descriptions; + + return group; +} + +shader_t *vulkan_shader_compile(VkDevice device, char *file_name) { + shader_t *resource; + + // Is this shader compiled already? + if (dict_get(compiled_shader_map, file_name, &resource, NULL)) { + return resource; + } + + shaderc_shader_kind kind = vulkan_shader_shaderc_detect_kind(file_name); + + resource = malloc(sizeof(shader_t)); + memset(resource, 0, sizeof(shader_t)); + resource->device = device; + resource->file_name = strdup(file_name); + resource->source = read_file(file_name); + resource->result = shaderc_compile_into_spv( + compiler, + resource->source, strlen(resource->source), + kind, + file_name, + "main", // TODO: Configurable shaders? + NULL // TODO: Add preprocessor directives + ); + + + // Was the compile successful? + if (shaderc_result_get_compilation_status(resource->result) != shaderc_compilation_status_success) { + printf("Failed to compile shader (%s): %s\n", file_name, shaderc_result_get_error_message(resource->result)); + return NULL; + } + + const char *binary = shaderc_result_get_bytes(resource->result); + resource->binary = (const uint32_t *) binary; + + VkShaderModuleCreateInfo request = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = shaderc_result_get_length(resource->result), + .pCode = resource->binary, + .pNext = NULL + }; + + if (vkCreateShaderModule(device, &request, NULL, &resource->module) != VK_SUCCESS) { + printf("Could not allocate shader on GPU: %s\n", file_name); + return NULL; + } + + + // Create pipeline stage info for vulkan configuration down the line + resource->stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + resource->stage_info.pName = "main"; + resource->stage_info.module = resource->module; + resource->stage_info.pNext = NULL; + resource->stage_info.pSpecializationInfo = NULL; + resource->stage_info.flags = 0; + + if (!vulkan_shader_shaderc_kind_to_stage_bit(kind, &resource->stage_info.stage)) { + return NULL; + } + + // Track shader + llist_add(&compiled, file_name, resource, sizeof(shader_t)); + + return resource; +} + + +bool vulkan_shader_init() { + compiled_shader_map = dict_new(); + + // Init the compiler library + vulkan_shader_compiler_init(); + + return true; +} diff --git a/src/engine/graphics/vulkan/shaders/shader.h b/src/engine/graphics/vulkan/shaders/shader.h new file mode 100644 index 0000000..556a8bf --- /dev/null +++ b/src/engine/graphics/vulkan/shaders/shader.h @@ -0,0 +1,77 @@ +#ifndef ENGINE_VULKAN_SHADERS_SHADER_H +#define ENGINE_VULKAN_SHADERS_SHADER_H + +#include +#include "src/engine/graphics/vulkan/vulkan.h" + +#define VULKAN_SHADER_FRAGMENT_TEST "assets/shaders/vt.frag" +#define VULKAN_SHADER_VERTEX_TEST "assets/shaders/vt.vert" +#define VULKAN_SHADER_GEOMETRY_TEST "assets/shaders/vt.geom" + +typedef struct { + /** + * Name of the file this shadr source came from + */ + char *file_name; + + /** + * Source code of the shader + */ + char *source; + + /** + * SPIR-V opcodes + */ + const uint32_t *binary; + + /** + * Device this shader was compiled onto + */ + VkDevice device; + + /** + * Shader stage configuration for pipelines + */ + VkPipelineShaderStageCreateInfo stage_info; + VkShaderModule module; + + /** + * Output of the shaderc compiler + */ + shaderc_compilation_result_t result; +} shader_t; + +typedef struct { + VkPipelineInputAssemblyStateCreateInfo input_assembly_state; + + // Vertex Attribute Descriptions + uint32_t num_vertex_input_attribute_descriptions; + VkVertexInputAttributeDescription *vertex_input_attribute_descriptions; + + // Vertex Binding Descriptions + uint32_t num_vertex_input_binding_descriptions; + VkVertexInputBindingDescription *vertex_input_binding_descriptions; + + uint32_t num_shaders; + shader_t **shaders; + + VkPipelineShaderStageCreateInfo *stages; + + VkPipelineVertexInputStateCreateInfo vertex_input_state_create_info; +} shader_group_t; + + +shader_group_t *vulkan_shader_group_create(VkDevice device); + +/** + * Compile a shader. + * + * @param device - Device to compile onto + * @param file_name - The file name of the shader + * @return + */ +shader_t *vulkan_shader_compile(VkDevice device, char *file_name); + +bool vulkan_shader_init(); + +#endif diff --git a/src/engine/graphics/vulkan/surface.c b/src/engine/graphics/vulkan/surface.c new file mode 100644 index 0000000..bdcf1bb --- /dev/null +++ b/src/engine/graphics/vulkan/surface.c @@ -0,0 +1,6 @@ +#include "surface.h" + +bool vulkan_surface_init(vulkan *v) +{ + return glfwCreateWindowSurface(v->instance, v->g.window, NULL, &v->surface) == VK_SUCCESS; +} diff --git a/src/engine/graphics/vulkan/surface.h b/src/engine/graphics/vulkan/surface.h new file mode 100644 index 0000000..e396fdd --- /dev/null +++ b/src/engine/graphics/vulkan/surface.h @@ -0,0 +1,8 @@ +#ifndef ENGINE_SURFACE_H +#define ENGINE_SURFACE_H + +#include "vulkan.h" + +bool vulkan_surface_init(vulkan *v); + +#endif diff --git a/src/engine/graphics/vulkan/swapchain.c b/src/engine/graphics/vulkan/swapchain.c new file mode 100644 index 0000000..c3fe522 --- /dev/null +++ b/src/engine/graphics/vulkan/swapchain.c @@ -0,0 +1,217 @@ +#include +#include "swapchain.h" +#include "window.h" +#include "locking.h" + +VkSurfaceFormatKHR vulkan_swapchain_select_format(vulkan *v) +{ + VkSurfaceFormatKHR best_format = { + .format = VK_FORMAT_B8G8R8A8_UNORM, + .colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR + }; + + if (v->swapchain.formats[0].format == VK_FORMAT_UNDEFINED) { + return best_format; + } else { + for (uint32_t i = 0; i < v->swapchain.num_formats; i++) { + VkSurfaceFormatKHR found_format = v->swapchain.formats[i]; + if (found_format.format == best_format.format && found_format.colorSpace == best_format.colorSpace) { + return found_format; + } + } + } + + return v->swapchain.formats[0]; +} + +VkPresentModeKHR vulkan_swapchain_select_presentation_mode(vulkan *v) +{ + VkPresentModeKHR mode_order[] = { + VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_FIFO_KHR + }; + + for (uint32_t mode_order_idx = 0; sizeof(mode_order) / sizeof(VkPresentModeKHR); mode_order_idx++) { + for (uint32_t i = 0; i < v->swapchain.num_modes; i++) { + if (v->swapchain.modes[i] == mode_order[mode_order_idx]) { + return v->swapchain.modes[i]; + } + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D vulkan_swapchain_choose_extent(vulkan *v) +{ + if (v->swapchain.capabilities.currentExtent.width != UINT32_MAX) { + return v->swapchain.capabilities.currentExtent; + } + + VkExtent2D minimum = v->swapchain.capabilities.minImageExtent; + VkExtent2D maximum = v->swapchain.capabilities.maxImageExtent; + + VkExtent2D extent = { + SCREEN_W, SCREEN_H + }; + + + // Clamp width and height between minimum and maximum + if (minimum.width > extent.width) { + extent.width = minimum.width; + } + if (maximum.width < extent.width) { + extent.width = maximum.width; + } + + + if (minimum.height > extent.height) { + extent.height = minimum.height; + } + if (maximum.height < extent.height) { + extent.height = maximum.height; + } + + return extent; +} + +uint32_t vulkan_swapchain_choose_image_count(vulkan *v) +{ + uint32_t count = v->swapchain.capabilities.minImageCount + 1; + if (v->swapchain.capabilities.maxImageCount > 0 && v->swapchain.capabilities.maxImageCount < count) { + count = v->swapchain.capabilities.maxImageCount; + } + return count; +} + +VkResult vulkan_swapchain_create(vulkan *v) +{ + v->swapchain.format = vulkan_swapchain_select_format(v); + v->swapchain.extent = vulkan_swapchain_choose_extent(v); + + VkSwapchainCreateInfoKHR request = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = v->surface, + .minImageCount = vulkan_swapchain_choose_image_count(v), + .imageFormat = v->swapchain.format.format, + .imageColorSpace = v->swapchain.format.colorSpace, + .imageExtent = v->swapchain.extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = NULL, + + .preTransform = v->swapchain.capabilities.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + + .presentMode = vulkan_swapchain_select_presentation_mode(v), + .clipped = VK_TRUE, + + .oldSwapchain = VK_NULL_HANDLE + }; + + if (v->queues.main_presentation_queue_id != v->queues.main_rendering_queue_id) { + request.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + request.queueFamilyIndexCount = v->queues.num_queue_families; + request.pQueueFamilyIndices = v->queues.queue_families; + } + + return vkCreateSwapchainKHR(v->devices.logical_device, &request, NULL, &v->swapchain.swapchain); +} + +VkResult vulkan_swapchan_get_images(vulkan *v) +{ + + vkGetSwapchainImagesKHR(v->devices.logical_device, v->swapchain.swapchain, &v->swapchain.num_images, NULL); + v->swapchain.images = malloc(sizeof(VkImage) * v->swapchain.num_images); + return vkGetSwapchainImagesKHR(v->devices.logical_device, v->swapchain.swapchain, &v->swapchain.num_images, + v->swapchain.images); +} + +bool vulkan_swapchain_make_image_views(vulkan *v) +{ + v->swapchain.image_views = malloc(sizeof(VkImageView) * v->swapchain.num_images); + for (uint32_t i = 0; i < v->swapchain.num_images; i++) { + VkImageViewCreateInfo request = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = v->swapchain.images[i], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = v->swapchain.format.format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1 + } + }; + + if (vkCreateImageView(v->devices.logical_device, &request, NULL, &v->swapchain.image_views[i]) != VK_SUCCESS) { + return false; + } + } + return true; +} + +bool vulkan_swapchain_init(vulkan *v) +{ + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(v->devices.selected_device, v->surface, &v->swapchain.capabilities); + vkGetPhysicalDeviceSurfaceFormatsKHR(v->devices.selected_device, v->surface, &v->swapchain.num_formats, NULL); + vkGetPhysicalDeviceSurfacePresentModesKHR(v->devices.selected_device, v->surface, &v->swapchain.num_modes, NULL); + + v->swapchain.formats = malloc(sizeof(VkSurfaceFormatKHR) * v->swapchain.num_formats); + v->swapchain.modes = malloc(sizeof(VkPresentModeKHR) * v->swapchain.num_modes); + + vkGetPhysicalDeviceSurfaceFormatsKHR(v->devices.selected_device, v->surface, &v->swapchain.num_formats, + v->swapchain.formats); + vkGetPhysicalDeviceSurfacePresentModesKHR(v->devices.selected_device, v->surface, &v->swapchain.num_modes, + v->swapchain.modes); + + + if (!v->swapchain.num_modes || !v->swapchain.num_formats) { + printf("Could not find a supported swapchain mode or format\n"); + return false; + } + + if (vulkan_swapchain_create(v) != VK_SUCCESS) { + printf("Failed to create swapchain instance from configuration\n"); + return false; + } + + if (vulkan_swapchan_get_images(v) != VK_SUCCESS) { + printf("Failed to load images out of swapchain\n"); + return false; + } + + if (!vulkan_swapchain_make_image_views(v)) { + printf("Failed to make image views\n"); + return false; + } + + return true; +} + +uint32_t vulkan_swapchain_aquire(vulkan *v) +{ + uint32_t image_index; + + vkAcquireNextImageKHR( + v->devices.logical_device, + v->swapchain.swapchain, + UINT64_MAX, + vulkan_locking_semphore_get_frame_buffer_image_available(), + VK_NULL_HANDLE, + &image_index + ); + + return image_index; +} diff --git a/src/engine/graphics/vulkan/swapchain.h b/src/engine/graphics/vulkan/swapchain.h new file mode 100644 index 0000000..5ea2f72 --- /dev/null +++ b/src/engine/graphics/vulkan/swapchain.h @@ -0,0 +1,10 @@ +#ifndef ENGINE_SWAPCHAIN_H +#define ENGINE_SWAPCHAIN_H + +#include "vulkan.h" + +bool vulkan_swapchain_init(vulkan *v); + +uint32_t vulkan_swapchain_aquire(vulkan *v); + +#endif diff --git a/src/engine/graphics/vulkan/vulkan.c b/src/engine/graphics/vulkan/vulkan.c new file mode 100644 index 0000000..e0b2a7c --- /dev/null +++ b/src/engine/graphics/vulkan/vulkan.c @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include +#include +#include "query.h" +#include "vulkan.h" +#include "config.h" +#include "debug.h" +#include "queues.h" +#include "window.h" +#include "surface.h" +#include "swapchain.h" +#include "pipeline.h" +#include "framebuffer.h" +#include "locking.h" + + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +vulkan v = { + .definition = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "Solid Snake", + .applicationVersion = VK_MAKE_VERSION(0, 0, 1), + .pEngineName = "Solid Engine", + .engineVersion = VK_MAKE_VERSION(0, 0, 1), + .apiVersion = VK_API_VERSION_1_0 + }, + .devices.selected_device = VK_NULL_HANDLE +}; + +// TODO: This is terrible. Find a better way to do DI +vulkan *vulkan_pointer = &v; + +cbuffer_pool_t *cpool; + +VkResult vulkan_create_instance() +{ + VkInstanceCreateInfo creation_request = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &v.definition + }; + + creation_request.ppEnabledLayerNames = v.required_configuration.layers; + creation_request.enabledLayerCount = v.required_configuration.num_layers; + + creation_request.ppEnabledExtensionNames = v.required_configuration.extensions; + creation_request.enabledExtensionCount = v.required_configuration.num_extensions; + + return vkCreateInstance(&creation_request, NULL, &v.instance); +} + +void vulkan_info_print() +{ + printf("Vulkan Initialized\n"); + + printf("Loaded Vulkan Extensions: %u\n", v.extensions.num_properties); + for (uint32_t i = 0; i < v.extensions.num_properties; i++) { + printf("\t%d - %s (%d)\n", i, v.extensions.properties[i].extensionName, v.extensions.properties[i].specVersion); + } + + printf("Loaded Vulkan Instance Layers: %u\n", v.layers.num_properties); + for (uint32_t i = 0; i < v.layers.num_properties; i++) { + printf("\t%d - %s (%d)\n", i, v.layers.properties[i].layerName, v.layers.properties[i].specVersion); + } + + printf("Using %u as the main graphics queue\n", v.queues.main_rendering_queue_id); +} + + +bool vulkan_init() +{ + + if (!vulkan_window_init(&v)) { + printf("Failed to create window!\n"); + return false; + } + + // Find hardware info + if (!vulkan_hardware_query(&v)) { + printf("Failed to query vulkan hardware!\n"); + return false; + } + + // Choose extensions and layers to use + vulkan_config_init(&v); + + // Create vulkan instance + if (vulkan_create_instance() != VK_SUCCESS) { + printf("Failed to create vulkan instance\n"); + return false; + } + + // Start the debugger + vulkan_debug_init(&v); + + // Select a device + if ((v.devices.selected_device = vulkan_config_pick_physical_device(&v)) == VK_NULL_HANDLE) { + printf("Failed to find suitable device!\n"); + return false; + } + + // Make drawable surface + if (!vulkan_surface_init(&v)) { + printf("Failed to create drawing surface\n"); + return false; + } + + // Find a queue with graphics pipeline support + if (!vulkan_queues_init(&v)) { + printf("Could not find graphics bit in chosen device!\n"); + return false; + } + + if (!vulkan_swapchain_init(&v)) { + return false; + } + + if (!vulkan_shader_init()) { + return false; + } + + + if (!vulkan_locking_init(&v)) { + printf("Could not create locks\n"); + return false; + } + + if (!vulkan_pipeline_init(&v)) { + return false; + } + + if (!vulkan_framebuffer_init(&v)) { + return false; + } + + if (!vulkan_memory_init(&v)) { + printf("Failed to init memory\n"); + return false; + } + + // TODO: We should NOT be passing the triangle position buffer in like this + if (!(cpool = vulkan_commands_cpool_allocate(&v, v.swapchain.num_images * 2))) { + printf("Failed to init command pool\n"); + } + + // Info to prove we have loaded everything + vulkan_info_print(); + + return true; +} + +void vulkan_render() +{ + + // Render starting configuration + VkSemaphore waiting_semaphore = vulkan_locking_semphore_get_frame_buffer_image_available(); + VkPipelineStageFlags waiting_semaphore_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + + // Render ending configuration + VkSemaphore signaling_semaphore = vulkan_locking_semphore_get_rendering_finished(); + + + // Get the next image to render onto + uint32_t swapchain_image_id = vulkan_swapchain_aquire(&v); + + // Get a free command buffer to use + uint32_t cbuffer_id = vulkan_commands_cpool_cbuffer_id_take(cpool); + + vulkan_commands_buffer_begin(cpool, cbuffer_id); + { + vulkan_commands_render_begin(&v, cpool, cbuffer_id, swapchain_image_id); + { + VkCommandBuffer buffer = vulkan_commands_cpool_cbuffer_get(cpool, cbuffer_id); + + // TODO: Bind pipeline in a better way + vkCmdBindPipeline(buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, v.pipelines.graphics); + + // Draw all entities into the command buffer + entity_manager_draw(buffer); + } + vulkan_commands_render_end(cpool, cbuffer_id); + } + vulkan_commands_buffer_end( + cpool, cbuffer_id, + 1, &waiting_semaphore, &waiting_semaphore_stage, + 1, &signaling_semaphore + ); + + + // Schedule this render job to be presented + VkPresentInfoKHR present_info = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + // Subscribe + .waitSemaphoreCount = 1, + .pWaitSemaphores = &signaling_semaphore, + + .swapchainCount = 1, + .pSwapchains = &v.swapchain.swapchain, + .pImageIndices = &swapchain_image_id, + + .pResults = NULL + }; + + vkQueuePresentKHR(v.queues.presenting, &present_info); +} + +void vulkan_update() +{ + vulkan_window_update(); +} + +void vulkan_cleanup() +{ + vulkan_window_cleanup(); +} diff --git a/src/engine/graphics/vulkan/vulkan.h b/src/engine/graphics/vulkan/vulkan.h new file mode 100644 index 0000000..83b4fb4 --- /dev/null +++ b/src/engine/graphics/vulkan/vulkan.h @@ -0,0 +1,136 @@ +#ifndef ENGINE_VGMODULE_H +#define ENGINE_VGMODULE_H + +#define GLFW_INCLUDE_VULKAN 1 + +#include +#include +#include + +typedef struct +{ + uint32_t num_extensions; + const char **extensions; + GLFWwindow *window; +} glfw; + + +typedef struct +{ + VkDebugUtilsMessengerEXT debug_utils_messenger_callback; +} vulkan_debugging; + + +typedef struct +{ + /** + * Info about available devices + */ + VkPhysicalDevice *devices; + uint32_t num_devices; + + /** + * The device we are currently rendering to + */ + VkPhysicalDevice selected_device; + VkDevice logical_device; +} vulkan_devices; + +typedef struct +{ + VkExtensionProperties *properties; + uint32_t num_properties; +} vulkan_extensions; + +typedef struct +{ + VkLayerProperties *properties; + uint32_t num_properties; + + const char **layers; + uint32_t num_layers; +} vulkan_layers; + + +typedef struct +{ + uint32_t num_properties; + VkQueueFamilyProperties *properties; + + uint32_t num_queue_families; + uint32_t queue_families[2]; + + uint32_t main_rendering_queue_id; + uint32_t main_presentation_queue_id; + VkQueue rendering; + VkQueue presenting; +} vulkan_queues; + +typedef struct +{ + uint32_t num_extensions; + const char **extensions; + uint32_t num_logical_extensions; + const char **logical_extensions; + uint32_t num_layers; + const char **layers; +} vulkan_required_configuration; + +typedef struct +{ + VkSurfaceFormatKHR format; + VkExtent2D extent; + VkSwapchainKHR swapchain; + + VkImage *images; + VkImageView *image_views; + uint32_t num_images; + + VkSurfaceCapabilitiesKHR capabilities; + + VkSurfaceFormatKHR *formats; + uint32_t num_formats; + + VkPresentModeKHR *modes; + uint32_t num_modes; + + VkFramebuffer *frame_buffers; +} vulkan_swapchain; + +typedef struct +{ + VkRenderPass render_pass; + VkPipelineLayout layout; + VkPipeline graphics; +} vulkan_pipelines; + +typedef struct +{ + VkApplicationInfo definition; + + glfw g; + VkInstance instance; + VkSurfaceKHR surface; + + vulkan_layers layers; + vulkan_extensions extensions; + vulkan_debugging debugging; + vulkan_devices devices; + vulkan_queues queues; + vulkan_swapchain swapchain; + vulkan_pipelines pipelines; + + vulkan_required_configuration required_configuration; +} vulkan; + +extern vulkan *vulkan_pointer; + +bool vulkan_init(); + +void vulkan_update(); + +void vulkan_render(); + +void vulkan_cleanup(); + +#endif diff --git a/src/engine/graphics/vulkan/window.c b/src/engine/graphics/vulkan/window.c new file mode 100644 index 0000000..2a58dd8 --- /dev/null +++ b/src/engine/graphics/vulkan/window.c @@ -0,0 +1,75 @@ +#include "window.h" + +#include +#include + +GLFWwindow *window; + +volatile float cursor_x; +volatile float cursor_y; + +void vulkan_on_mouse_move(GLFWwindow *window, double x, double y) +{ + (void) window; + cursor_x = (float) x; + cursor_y = (float) y; +} + + +bool vulkan_window_init(vulkan *v) +{ + glfwInit(); + + // Disable loading OpenGL and use GLFW for loading APIs + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = v->g.window = glfwCreateWindow( + vulkan_window_width_get(), vulkan_window_height_get(), + "Solid Engine", + NULL, + NULL + ); + + if (!v->g.window) { + printf("Failed to create window!\n"); + glfwTerminate(); + return false; + } + + // TODO: Move into it's own location + glfwSetCursorPosCallback(v->g.window, vulkan_on_mouse_move); + + glfwMakeContextCurrent(v->g.window); + + return true; +} + +int vulkan_window_width_get() +{ + return SCREEN_W; +} + +int vulkan_window_height_get() +{ + return SCREEN_H; +} + +void vulkan_window_update() +{ + glfwPollEvents(); +} + +void vulkan_window_cleanup() +{ + glfwDestroyWindow(window); + glfwTerminate(); +} + +bool vulkan_window_is_close_requested() +{ + if (!window) { + return true; + } + return (bool) glfwWindowShouldClose(window); +} diff --git a/src/engine/graphics/vulkan/window.h b/src/engine/graphics/vulkan/window.h new file mode 100644 index 0000000..ec179c3 --- /dev/null +++ b/src/engine/graphics/vulkan/window.h @@ -0,0 +1,28 @@ +#ifndef ENGINE_WINDOW_H +#define ENGINE_WINDOW_H + + +#include +#include "vulkan.h" + +#define SCREEN_W 400 +#define SCREEN_H 400 + +// this shouldn't be the way this is done +extern volatile float cursor_x; +extern volatile float cursor_y; + + +bool vulkan_window_init(vulkan *v); + +bool vulkan_window_is_close_requested(); + +void vulkan_window_update(); + +void vulkan_window_cleanup(); + +int vulkan_window_width_get(); + +int vulkan_window_height_get(); + +#endif diff --git a/src/engine/graphics/vulkan/wrappers.h b/src/engine/graphics/vulkan/wrappers.h new file mode 100644 index 0000000..7b6f5ce --- /dev/null +++ b/src/engine/graphics/vulkan/wrappers.h @@ -0,0 +1,53 @@ +#ifndef ENGINE_WRAPPERS_H +#define ENGINE_WRAPPERS_H + +#include + +/** + * Wrapper for vkCreateDebugUtilsMessengerEXT extension function + * @param instance + * @param pCreateInfo + * @param pAllocator + * @param pCallback + * @return + */ +static inline VkResult wrap_vulkan_create_debug_utils_messenger_ext( + VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT *pCreateInfo, + const VkAllocationCallbacks *pAllocator, + VkDebugUtilsMessengerEXT *pCallback +) +{ + const PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr( + instance, + "vkCreateDebugUtilsMessengerEXT" + ); + if (func != NULL) { + return func(instance, pCreateInfo, pAllocator, pCallback); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +/** + * Wrapper around vkDestroyDebugUtilsMessengerEXT + * @param instance + * @param callback + * @param pAllocator + */ +static inline void wrap_vulkan_destroy_debug_utils_messenger_ext( + VkInstance instance, + VkDebugUtilsMessengerEXT callback, + const VkAllocationCallbacks *pAllocator +) +{ + PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr( + instance, + "vkDestroyDebugUtilsMessengerEXT" + ); + if (func != NULL) { + func(instance, callback, pAllocator); + } +} + +#endif diff --git a/src/engine/main.c b/src/engine/main.c index a92ceb5..fd88e92 100644 --- a/src/engine/main.c +++ b/src/engine/main.c @@ -1,24 +1,30 @@ + #include "lib/glad/glad.h" #include +#include +#include +#include +#include +#include #include "src/engine/scripting/callbacks.h" #include "src/engine/scripting/script.h" #define SCREEN_W 400 #define SCREEN_H 400 -void update() -{ - static double start = 0; + + +void update() { + static double start = 0; const double current_time = glfwGetTime(); - const double delta = current_time - start; + const double delta = current_time - start; start = current_time; game.update(delta); } -void draw() -{ +void draw() { glClear(GL_COLOR_BUFFER_BIT); { game.draw(); @@ -27,8 +33,7 @@ void draw() } -void init() -{ +void init() { if (!init_interface()) { printf("Failed to load scripting context\n"); exit(1); @@ -45,47 +50,28 @@ void init() int main() { + if (!entity_manager_init(128)) { + printf("Failed to initialize the entity manager\n"); + return 1; + } - - glfwInit(); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - - GLFWwindow* window = glfwCreateWindow(SCREEN_W, SCREEN_H, "Solid Engine", NULL, NULL); - - if (!window) - { - printf("Failed to create window!\n"); - glfwTerminate(); - return 1; - } - - glfwMakeContextCurrent(window); - - if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) - { - printf("Failed to load open GL api\n"); + if (!vulkan_init()) { + printf("Failed to initialize graphics layer\n"); return 1; } + entity_t *entity = entity_manager_make(entity_triangle_init, vulkan_pointer); - // Screen is black - glClearColor(0.0, 0.0, 0.0, 0.0); - init(); // Init our code. - - REGISTER_GLFW_CALLBACKS(); + while (!vulkan_window_is_close_requested()) { + entity_manager_update(); + vulkan_update(); + vulkan_render(); + } - while (!glfwWindowShouldClose(window)) - { - update(); - draw(); - glfwSwapBuffers(window); - glfwPollEvents(); - } - glfwTerminate(); + entity_free(entity, NULL); + vulkan_cleanup(); return 0; } diff --git a/src/engine/util/bits.h b/src/engine/util/bits.h new file mode 100644 index 0000000..e86e9b8 --- /dev/null +++ b/src/engine/util/bits.h @@ -0,0 +1,7 @@ +#ifndef ENGINE_UTIL_BITS_H +#define ENGINE_UTIL_BITS_H + +#define IS_BIT_SET(number, index) (number & (1 << index)) +#define IS_EVERY_BIT_SET(number, mask) ((number & mask) == mask) + +#endif diff --git a/src/engine/util/dict.c b/src/engine/util/dict.c new file mode 100644 index 0000000..6810b93 --- /dev/null +++ b/src/engine/util/dict.c @@ -0,0 +1,81 @@ +#include "dict.h" +#include +#include + +#include +#include + +typedef size_t bucket_index_t; + +/** + * Find the bucket index for this hash map + * @param d - Dictionary object + * @param name - Name of the pair + * @param length - Length of the name + * @return + */ +static inline bucket_index_t dict_bucket_for(dict *d, const char *const name, const size_t length) +{ + const uint_fast32_t hash = hashing_crc32_make(name, length); + const size_t num_buckets = d->num_buckets; + return (bucket_index_t) (hash % num_buckets); +} + +static inline bool dict_bucket_empty(dict *d, bucket_index_t i) +{ + return llist_empty(&d->buckets[i]); +} + + +dict *dict_init(size_t num_buckets) +{ + const size_t size_of_buckets = sizeof(llist *) * num_buckets; + dict *d = malloc(sizeof(dict) + size_of_buckets); + d->num_buckets = num_buckets; + memset(d->buckets, 0, size_of_buckets); + return d; +} + +void dict_clear(dict *d) +{ + if (d) { + for (bucket_index_t i = 0; i < d->num_buckets; i++) { + llist_free(&d->buckets[i]); + } + } +} + +bool dict_get(dict *d, const char *const name, void *value, size_t *length) +{ + const size_t len_name = strlen(name); + bucket_index_t index = dict_bucket_for(d, name, len_name); + + if (dict_bucket_empty(d, index)) { + return false; + } + + return llist_retrive(&d->buckets[index], name, value, length); +} + +bool dict_get_string(dict *d, const char *const name, char **value) +{ + return dict_get(d, name, (void *) value, NULL); +} + +bool dict_has(dict *d, const char *const name) +{ + return dict_get(d, name, NULL, NULL); +} + +void dict_set(dict *d, const char *const name, void *value, size_t length) +{ + const size_t len_name = strlen(name); + bucket_index_t index = dict_bucket_for(d, name, len_name); + llist_add(&d->buckets[index], name, value, length); +} + +void dict_set_string(dict *d, const char *const name, char *value) +{ + dict_set(d, name, value, strlen(value)); +} + diff --git a/src/engine/util/dict.h b/src/engine/util/dict.h new file mode 100644 index 0000000..21197f7 --- /dev/null +++ b/src/engine/util/dict.h @@ -0,0 +1,66 @@ +#ifndef ENGINE_UTIL_DICT_H +#define ENGINE_UTIL_DICT_H + +#include + +#define DICT_DEFAULT_NUM_BUCKETS 256 + +#define dict_new() dict_init(DICT_DEFAULT_NUM_BUCKETS) + +typedef struct { + size_t num_buckets; + llist *buckets[]; +} dict; + +dict *dict_init(size_t num_buckets); + +void dict_clear(dict *d); + +/** + * Get a value from the dictionary + * + * @param d + * @param name + * @param value + * @param length + * @return + */ +bool dict_get(dict *d, const char *name, void *value, size_t *length); + + +/** + * Check to see if a dictionary has a KV pair + * @param d + * @param name + * @return + */ +bool dict_has(dict *d, const char *name); + +/** + * Set a dictionary KV pair + * @param d + * @param name + * @param value + * @param length + */ +void dict_set(dict *d, const char *name, void *value, size_t length); + +/** + * Set a KV pair of strings + * @param d + * @param name + * @param value + */ +void dict_set_string(dict *d, const char *name, char *value); + + +/** + * Get a string from a K + * @param d + * @param name + * @param value + * @return + */ +bool dict_get_string(dict *d, const char *name, char **value); + +#endif diff --git a/src/engine/util/files.c b/src/engine/util/files.c index 1271ec8..fe90ee2 100644 --- a/src/engine/util/files.c +++ b/src/engine/util/files.c @@ -7,70 +7,76 @@ #include #include -long int fsize(const char *filename) -{ - struct stat st; +long int fsize(const char *filename) { + struct stat st; - if (stat(filename, &st) == 0) - return st.st_size; + if (stat(filename, &st) == 0) + return st.st_size; - return -1; + return -1; } -char* read_file(const char* filename) -{ - long int reported_size = fsize(filename); - FILE *f; +char *read_file(const char *filename) { + long int reported_size = fsize(filename); + FILE *f; - if (reported_size == -1 || !(f = fopen(filename, "rb"))) { - return NULL; - } + if (reported_size == -1 || !(f = fopen(filename, "rb"))) { + return NULL; + } - const size_t file_size = (size_t) reported_size; - size_t data_left = file_size; - char *buffer = malloc(file_size + 1); - char *tmp = buffer; + const size_t file_size = (size_t) reported_size; + size_t data_left = file_size; + char *buffer = malloc(file_size + 1); + char *tmp = buffer; - while (data_left > 0) - { - const size_t len = fread((void *) tmp, sizeof(char), sizeof(buffer), f); - data_left -= len; - tmp += len; - } - buffer[file_size] = 0; + while (data_left > 0) { + const size_t len = fread((void *) tmp, sizeof(char), sizeof(buffer), f); + data_left -= len; + tmp += len; + } + buffer[file_size] = 0; - fclose(f); + fclose(f); - return buffer; + return buffer; } -llist* list_files(const char* folder_name, const char *ext) -{ - static char buff[128]; +llist *list_files(const char *folder_name, const char *ext) { + static char buff[128]; - DIR *dir; - struct dirent *ent; + DIR *dir; + struct dirent *ent; - size_t ext_len = ext ? strlen(ext) : 0; - llist* files = NULL; + size_t ext_len = ext ? strlen(ext) : 0; + llist *files = NULL; - if ((dir = opendir(folder_name)) != NULL) { - while ((ent = readdir(dir)) != NULL) { + if ((dir = opendir(folder_name)) != NULL) { + while ((ent = readdir(dir)) != NULL) { - // Filter extensions - // TODO: Make sure this condition will actually work. - if (ext && strcmp(ext, ent->d_name + (strlen(ent->d_name) - ext_len)) != 0) { - continue; - } + // Filter extensions + // TODO: Make sure this condition will actually work. + if (ext && strcmp(ext, ent->d_name + (strlen(ent->d_name) - ext_len)) != 0) { + continue; + } - memset(buff, 0, sizeof(buff)); - strcat(buff, folder_name); - strcat(buff, ent->d_name); + memset(buff, 0, sizeof(buff)); + strcat(buff, folder_name); + strcat(buff, ent->d_name); - llist_add(&files, buff, NULL, 0); - } - closedir(dir); - } + llist_add(&files, buff, NULL, 0); + } + closedir(dir); + } - return files; + return files; +} + +const char *file_extract_extension(const char *filename) { + const char *dot = strrchr(filename, '.'); + + if (!dot || dot == filename) { + return ""; + } + + return dot + 1; } diff --git a/src/engine/util/files.h b/src/engine/util/files.h index 7eb4e2b..a594031 100644 --- a/src/engine/util/files.h +++ b/src/engine/util/files.h @@ -25,6 +25,14 @@ char* read_file(const char* filename); llist* list_files(const char* folder_name, const char *ext); + +/** + * Get the file extension from file name + * @param filename + * @return + */ +const char *file_extract_extension(const char *filename); + /** * Native wrapper to read_file */ diff --git a/src/engine/util/hashing/crc.c b/src/engine/util/hashing/crc.c new file mode 100644 index 0000000..60726eb --- /dev/null +++ b/src/engine/util/hashing/crc.c @@ -0,0 +1,73 @@ +#include "crc.h" + + +/** + * CRC32 magic table. Copied from the person who copied from the person who copied from the person (ad infinum) + * https://referencesource.microsoft.com/#System/sys/System/IO/compression/Crc32Helper.cs,3b31978c7d7f7246,references + */ +uint_fast32_t table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, + 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, + 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, + 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, + 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, + 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, + 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, + 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, + 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, + 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, + 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, + 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, + 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, + 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, + 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, + 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, + 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, + 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, + 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, + 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, + 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, + 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, + 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, + 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, + 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, + 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, + 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, + 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, + 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, + 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, + 0x2d02ef8d +}; + +uint_fast32_t hashing_crc32_make(const char *const data, size_t length) { + uint_fast32_t hash = 0xffffffff; + + for (size_t i = 0; i < length; i++) { + const size_t table_lookup_index = (hash ^ data[i]) & 0xff; + const uint_fast32_t table_value = table[table_lookup_index]; + hash = (hash >> 8) ^ table_value; + } + + return hash; +} diff --git a/src/engine/util/hashing/crc.h b/src/engine/util/hashing/crc.h new file mode 100644 index 0000000..1a4260a --- /dev/null +++ b/src/engine/util/hashing/crc.h @@ -0,0 +1,18 @@ +#ifndef ENGINE_UTIL_HASHING_CRC_H +#define ENGINE_UTIL_HASHING_CRC_H + +#include +#include + + +/** + * Hash a string using CRC32. + * Algorithm from https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm + * + * @param data Pointer to data + * @param length The size of the data block + * @return Hash of the data + */ +uint_fast32_t hashing_crc32_make(const char *data, size_t length); + +#endif diff --git a/src/engine/util/llist.c b/src/engine/util/llist.c index a6e1f9d..86eae9e 100644 --- a/src/engine/util/llist.c +++ b/src/engine/util/llist.c @@ -7,25 +7,44 @@ #include #include -void *llist_get(llist **head, const char * const name) -{ - llist const * tmp = *head; - while (tmp) - { +llist *llist_get_internal(llist **head, const char *const name) { + llist *tmp = *head; + while (tmp) { if (strcmp(name, tmp->name) == 0) - return tmp->value; + return tmp; tmp = tmp->next; } return NULL; } -bool llist_has(llist **head, const char * const name) -{ +void *llist_get(llist **head, const char *const name) { + llist const *tmp = llist_get_internal(head, name); + if (tmp) { + return tmp->value; + } + return NULL; +} + +bool llist_retrive(llist **head, const char *const name, void **value, size_t *length) { + llist const *tmp = llist_get_internal(head, name); + if (tmp) { + if (length) { + *length = tmp->length; + } + if (value) { + *value = tmp->value; + } + return true; + } + return false; +} + + +bool llist_has(llist **head, const char *const name) { return llist_get(head, name) != NULL; } -void llist_add(llist **head, const char * const name, const void * const value, size_t size) -{ +void llist_add(llist **head, const char *const name, const void *const value, size_t size) { // Allocate llist *next = malloc(sizeof(llist)); @@ -33,6 +52,13 @@ void llist_add(llist **head, const char * const name, const void * const value, next->value = malloc(size); next->name = strdup(name); next->next = *head; + next->length = size; + next->last = NULL; + + // Set last value + if (*head) { + (*head)->last = next; + } // Copy data into container memcpy(next->value, value, size); @@ -40,10 +66,32 @@ void llist_add(llist **head, const char * const name, const void * const value, *head = next; } -void llist_free(llist **head) -{ - while (*head) - { +void llist_remove(llist **head, const char *const name) { + llist *item = llist_get_internal(head, name); + + if (!item) { + return; + } + + if (item->last) { + item->last->next = item->next; + } + + if (item->next) { + item->next->last = item->last; + } + + free(item->value); + free(item->name); + free(item); +} + +bool llist_empty(llist **head) { + return *head == NULL; +} + +void llist_free(llist **head) { + while (*head) { llist *curr = *head; llist *next = curr->next; free(curr->name); diff --git a/src/engine/util/llist.h b/src/engine/util/llist.h index 1f09bb7..c45a617 100644 --- a/src/engine/util/llist.h +++ b/src/engine/util/llist.h @@ -9,15 +9,26 @@ #include struct llist_t { + struct llist_t *last; struct llist_t *next; char *name; void *value; + size_t length; }; typedef struct llist_t llist; -void *llist_get(llist **head, const char * const name); -bool llist_has(llist **head, const char * const name); -void llist_add(llist **head, const char * const name, const void * const value, size_t size); +void *llist_get(llist **head, const char *const name); + +bool llist_retrive(llist **head, const char *const name, void **value, size_t *length); + +bool llist_has(llist **head, const char *const name); + +void llist_add(llist **head, const char *const name, const void *const value, size_t size); + +void llist_remove(llist **head, const char *const name); + +bool llist_empty(llist **head); + void llist_free(llist **head); #endif //ENGINE_LLIST_H diff --git a/src/game/assets/shaders/apple.frag b/src/game/assets/shaders/apple.frag index b14776a..305745c 100644 --- a/src/game/assets/shaders/apple.frag +++ b/src/game/assets/shaders/apple.frag @@ -1,6 +1,6 @@ #version 330 core -out vec4 FragColor; +layout(location = 0) out vec4 FragColor; void main() { diff --git a/src/game/assets/shaders/vt.frag b/src/game/assets/shaders/vt.frag new file mode 100644 index 0000000..7718cbd --- /dev/null +++ b/src/game/assets/shaders/vt.frag @@ -0,0 +1,8 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/src/game/assets/shaders/vt.geom b/src/game/assets/shaders/vt.geom new file mode 100644 index 0000000..3e39d10 --- /dev/null +++ b/src/game/assets/shaders/vt.geom @@ -0,0 +1,47 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + + +// Terrible but only resource: https://www.khronos.org/opengl/wiki/Geometry_Shader +layout (points) in; +layout (triangle_strip, max_vertices = 3) out; + +out gl_PerVertex { + vec4 gl_Position; +}; + +float triangle_radius = 0.5f; + +vec4 triangle_first(vec4 triangle_center_position) { + return triangle_center_position + vec4(0.0f, -triangle_radius, 0.0f, 0.0f); +} + +vec4 triangle_second(vec4 triangle_center_position) { + return triangle_center_position + vec4(triangle_radius, triangle_radius, 0.0f, 0.0f); +} + +vec4 triangle_third(vec4 triangle_center_position) { + return triangle_center_position + vec4(-triangle_radius, triangle_radius, 0.0f, 0.0f); +} + + +void main(void) +{ + + for(int i = 0; i < gl_in.length(); i++) + { + { + gl_Position = triangle_first(gl_in[i].gl_Position); + EmitVertex(); + + gl_Position = triangle_second(gl_in[i].gl_Position); + EmitVertex(); + + gl_Position = triangle_third(gl_in[i].gl_Position); + EmitVertex(); + } + EndPrimitive(); + } +} \ No newline at end of file diff --git a/src/game/assets/shaders/vt.vert b/src/game/assets/shaders/vt.vert new file mode 100644 index 0000000..cd651a7 --- /dev/null +++ b/src/game/assets/shaders/vt.vert @@ -0,0 +1,18 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +out gl_PerVertex { + vec4 gl_Position; +}; + +//vec2 positions[3] = vec2[]( +// vec2(0.0, -0.5), +// vec2(0.5, 0.5), +// vec2(-0.5, 0.5) +//); + +layout(location = 0) in vec2 triangle_center_pos; + +void main() { + gl_Position = vec4(triangle_center_pos, 0.0, 1.0); +} \ No newline at end of file