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