commit 493afb05e60c5493efa8f0d6bac281ab622b3fe1 Author: Piotr Krygier Date: Tue Jun 28 09:54:41 2022 +0200 Initial commit This is working repository now. I had to clean this up due to my f_ups, that made this simple repo around 200MB large. Signed-off-by: Piotr Krygier diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..29425d96 --- /dev/null +++ b/.clang-format @@ -0,0 +1,40 @@ +BasedOnStyle: Google +IndentWidth: 4 + +# Add missing namespace comment on the closing bracket +FixNamespaceComments: true + +# Do not indent everything in namespace +NamespaceIndentation: None + +# Disable single line functions +AllowShortFunctionsOnASingleLine: None + +# Braces configuration +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterClass: false + AfterControlStatement: Never + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: false + BeforeElse: false + +ColumnLimit: 120 + +# Pointer star next to type, not variable name +DerivePointerAlignment: false +PointerAlignment: Left + +# Function parameters Alignment +AlignAfterOpenBracket: Align +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +BinPackArguments: false +BinPackParameters: OnePerLine + diff --git a/.clangd b/.clangd new file mode 100644 index 00000000..a3e0cf88 --- /dev/null +++ b/.clangd @@ -0,0 +1,12 @@ +CompileFlags: + Add: [ + "-ferror-limit=0", + "-Wall", + "-Wpedantic", + "-Werror", + "-Wno-gnu-statement-expression", + "-I./", + "-I./graphics", + "-I./utilites", + ] + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..df555e03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Build and bin folder contents +build/* +bin/* + +# VSCode configurations +.vscode/ + +# Cache folder +.cache/ + +# Cmake cache files +CMakeCache.txt + +# Since we are using conan, we can remove CMakeFiles folder +CMakeFiles/ + +# Clang specific files +.clangd +.clangd/ +compile_commands.json + +# Environment preparation scripts +setup-env.sh diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..e85389e3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Piotr Krygier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NotoSansMono-Regular.ttf b/NotoSansMono-Regular.ttf new file mode 100644 index 00000000..159ca4b1 Binary files /dev/null and b/NotoSansMono-Regular.ttf differ diff --git a/README.md b/README.md new file mode 100644 index 00000000..c2d4b2bd --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Red Scarf Engine + +Graphics/Game engine written in Vulkan. More of a science project than the actual engine that will be used anywhere. + diff --git a/graphics/meson.build b/graphics/meson.build new file mode 100644 index 00000000..eec32728 --- /dev/null +++ b/graphics/meson.build @@ -0,0 +1,49 @@ +rse_graphics_sources = [ + 'src/descriptor_builder.c', + 'src/font_manager.c', + 'src/mesh_controller.c', + 'src/pipeline_builder.c', + 'src/renderer.c', + 'src/rse_graphics.c', + 'src/vma_port.cpp', + 'src/vulkan_base.c', + 'src/vulkan_buffers.c', + 'src/vulkan_commands.c', + 'src/vulkan_commons.c', + 'src/vulkan_image.c', + 'src/vulkan_render_pass.c', + 'src/vulkan_swapchain.c', + 'src/window.c', + ] + +vulkan_dep = dependency('vulkan', + version : '>=1.4.335') + +sdl3_dep = dependency('sdl3', + version : '>=3.4.0') + +freetype_dep = dependency('freetype2', + version : '>=22.1.16') +glm_dep = dependency('glm', + version : '>=1.0.3', + method : 'cmake') +vma_dep = dependency('VulkanMemoryAllocator', + version : '>=3.3.0', + modules : ['GPUOpen::VulkanMemoryAllocator']) + +rse_graphics_lib = shared_library( + 'rse_graphics', + rse_graphics_sources, + include_directories : [ + '../' + ], + dependencies : [vulkan_dep, + vma_dep, + sdl3_dep, + freetype_dep, + glm_dep, + stb_dep, + ], + link_with: rse_utilities_lib, + install : true + ) diff --git a/graphics/rse_graphics.h b/graphics/rse_graphics.h new file mode 100644 index 00000000..5c0c3c08 --- /dev/null +++ b/graphics/rse_graphics.h @@ -0,0 +1,31 @@ +#ifndef RSE_GRAPHICS_H +#define RSE_GRAPHICS_H + +#include "utilities/commons.h" + +struct rse_graphics_context_t; + +/** + * @brief Initializes graphics backend. This has to be called before any new mesh, texture or any other object is + * added + * + * @param context Graphics context handle. Must be NULL! + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t rse_graphics_init(struct rse_graphics_context_t** context); + +/** + * @brief Runs graphics engine. This has to be called after all objects are added to the scene + * + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +int rse_graphics_run(void* arg); + + +/** + * @brief Custom function for testing engine. TODO: Remove it whe releasing + * + */ +rse_err_t rse_graphics_test_function(struct rse_graphics_context_t* context); + +#endif /* RSE_GRAPHICS_H */ diff --git a/graphics/shaders/on_screen_text.frag b/graphics/shaders/on_screen_text.frag new file mode 100644 index 00000000..630280b5 --- /dev/null +++ b/graphics/shaders/on_screen_text.frag @@ -0,0 +1,8 @@ +#version 450 + +layout(set = 0, binding = 0) uniform sampler2D overlay_tex; +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 out_color; +void main() { + out_color = texture(overlay_tex, uv); +} diff --git a/graphics/shaders/on_screen_text.vert b/graphics/shaders/on_screen_text.vert new file mode 100644 index 00000000..5723ab38 --- /dev/null +++ b/graphics/shaders/on_screen_text.vert @@ -0,0 +1,13 @@ +#version 450 + +layout(location = 0) out vec2 uv; +vec2 positions[3] = vec2[]( + vec2(-1.0, -1.0), + vec2(3.0, -1.0), + vec2(-1.0, 3.0) +); +void main() { + uv = (positions[gl_VertexIndex] + 1.0) * 0.5; + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); +} + diff --git a/graphics/shaders/shader.vert b/graphics/shaders/shader.vert new file mode 100644 index 00000000..5fef9f39 --- /dev/null +++ b/graphics/shaders/shader.vert @@ -0,0 +1,79 @@ +#version 450 + +layout(binding = 0) uniform UniformBufferObject { + mat4 model; + mat4 view; + mat4 proj; +} ubo; + +#define MAX_INSTANCES 1024 + +struct InstanceData +{ + vec3 pos; + vec3 rot; + float scale; + uint texture_id; +}; + +layout(binding = 1) uniform InstanceUniformBuffer { + InstanceData instances[MAX_INSTANCES]; +}; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; + +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; +layout(location = 2) out uint outInstanceTextureId; + + +void main() +{ + InstanceData instance = instances[gl_InstanceIndex]; + mat4 gRotMat; + mat3 mx, my, mz; + + // rotate around x + float s = sin(instance.rot.x + 0); + float c = cos(instance.rot.x + 0); + + mx[0] = vec3(1, 0.0, 0.0); + mx[1] = vec3(0, c, -s); + mx[2] = vec3(0.0, s, c); + + // rotate around y + s = sin(instance.rot.y + 0); + c = cos(instance.rot.y + 0); + + my[0] = vec3(c, 0.0, s); + my[1] = vec3(0.0, 1.0, 0.0); + my[2] = vec3(-s, 0.0, c); + + // rot around z + s = sin(instance.rot.z + 0); + c = cos(instance.rot.z + 0); + + mz[0] = vec3(c, -s, 0.0); + mz[1] = vec3(s, c, 0.0); + mz[2] = vec3(0.0, 0.0, 1); + + mat3 rotMat = mz * my * mx; + + s = sin(instance.rot.y + 0); + c = cos(instance.rot.y + 0); + gRotMat[0] = vec4(c, 0.0, s, 0.0); + gRotMat[1] = vec4(0.0, 1.0, 0.0, 0.0); + gRotMat[2] = vec4(-s, 0.0, c, 0.0); + gRotMat[3] = vec4(0.0, 0.0, 0.0, 1.0); + + vec4 locPos = vec4(inPosition.xyz * rotMat, 1.0); + vec4 pos = vec4((locPos.xyz * instance.scale) + instance.pos, 1.0); + + gl_Position = ubo.proj * ubo.view * ubo.model * pos; + + fragColor = inColor; + fragTexCoord = inTexCoord; + outInstanceTextureId = instance.texture_id; +} diff --git a/graphics/shaders/solid_objects.frag b/graphics/shaders/solid_objects.frag new file mode 100644 index 00000000..0e823043 --- /dev/null +++ b/graphics/shaders/solid_objects.frag @@ -0,0 +1,15 @@ +#version 450 +#extension GL_EXT_nonuniform_qualifier : enable + +layout(set = 0, binding = 2) uniform sampler2D texSampler[]; + +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; +layout(location = 2) in flat uint instanceTextureId; + +layout(location = 0) out vec4 outColor; + +void main() { + vec4 texturePixels = texture(texSampler[instanceTextureId], fragTexCoord); + outColor = vec4(fragColor * texturePixels.rgb, texturePixels.a); +} diff --git a/graphics/src/descriptor_builder.c b/graphics/src/descriptor_builder.c new file mode 100644 index 00000000..499aaadc --- /dev/null +++ b/graphics/src/descriptor_builder.c @@ -0,0 +1,205 @@ +/** + * @file descriptor_builder.c + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief + * @version 0.1 + * @date 2025-03-14 + * + * @copyright Copyright (c) 2025 + * + */ + +#include "descriptor_builder.h" + +#include +#include +#include + +#include "src/graphics_context.h" +#include "src/vulkan_commons.h" +#include "utilities/commons.h" +#include "utilities/errors_common.h" +#include "vulkan/vulkan_core.h" + +#define POOL_MAX_SETS (32U) + +VkDescriptorSetLayoutBinding g_layout_bindings[DESCRIPTOR_MAX_SETS]; +static uint32_t g_layout_bindings_count = 0U; + +rse_err_t descriptor_pool_add_type(struct graphics_context_t* context, VkDescriptorType type, uint32_t count) +{ + context->descriptor_data.pool_sizes[context->descriptor_data.pool_sizes_count].type = type; + context->descriptor_data.pool_sizes[context->descriptor_data.pool_sizes_count].descriptorCount = count; + + context->descriptor_data.pool_sizes_count++; + return RSE_ERROR_NO_ERROR; +} + +rse_err_t descriptor_pool_initialize(struct graphics_context_t* context) +{ + VkDescriptorPoolCreateInfo create_info = {0}; + + create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + create_info.pNext = NULL; + create_info.flags = 0U; + create_info.maxSets = POOL_MAX_SETS; + create_info.poolSizeCount = context->descriptor_data.pool_sizes_count; + create_info.pPoolSizes = context->descriptor_data.pool_sizes; + + if (vkCreateDescriptorPool(context->vulkan_handles.device, + &create_info, + NULL, + &context->descriptor_data.descriptor_pool) != VK_SUCCESS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create descriptor pool"); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t descriptor_create_new_set(struct graphics_context_t* context, uint32_t* set_id) +{ + if (context->descriptor_data.descriptor_sets_count >= DESCRIPTOR_MAX_SETS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Reached maximum number of available descriptor sets"); + return RSE_ERROR_INTERNAL_ERROR; + } + + *set_id = context->descriptor_data.descriptor_sets_count++; + return RSE_ERROR_NO_ERROR; +} + +rse_err_t descriptor_add_layout(uint32_t binding, + VkDescriptorType type, + uint32_t descriptor_count, + VkShaderStageFlags shader_stages) +{ + g_layout_bindings[g_layout_bindings_count].binding = binding; + g_layout_bindings[g_layout_bindings_count].descriptorType = type; + g_layout_bindings[g_layout_bindings_count].descriptorCount = descriptor_count; + g_layout_bindings[g_layout_bindings_count].stageFlags = shader_stages; + + g_layout_bindings_count++; + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t descriptor_set_finish(struct graphics_context_t* context, uint32_t set_id) +{ + VkDescriptorSetLayoutCreateInfo create_info = {0}; + + create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + create_info.bindingCount = g_layout_bindings_count; + create_info.pBindings = g_layout_bindings; + + if (VK_SUCCESS != vkCreateDescriptorSetLayout(context->vulkan_handles.device, + &create_info, + NULL, + &context->descriptor_data.set_layouts[set_id])) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create descriptor sets layout"); + return RSE_ERROR_INTERNAL_ERROR; + } + + g_layout_bindings_count = 0U; + rse_memset(g_layout_bindings, 0, sizeof(VkDescriptorSetLayoutBinding) * DESCRIPTOR_MAX_SETS); + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t descriptor_build_sets(struct graphics_context_t* context) +{ + VkDescriptorSetAllocateInfo alloc_info = {0}; + + if (NULL == context) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Provided NULL context"); + + return RSE_ERROR_NULL_POINTER; + } + + if (context->descriptor_data.descriptor_pool == VK_NULL_HANDLE) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, + "Requested descriptor build, but descriptor pool has not been initialized"); + } + + /* Allocate descriptors from the pool */ + alloc_info.descriptorPool = context->descriptor_data.descriptor_pool; + alloc_info.descriptorSetCount = context->descriptor_data.descriptor_sets_count; + alloc_info.pSetLayouts = context->descriptor_data.set_layouts; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + + if (VK_SUCCESS != vkAllocateDescriptorSets(context->vulkan_handles.device, + &alloc_info, + context->descriptor_data.descriptor_sets)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create descriptor sets"); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t descriptor_attach_images(struct graphics_context_t* context, + uint32_t descriptor_set_id, + uint16_t* texture_ids, + size_t images_count, + VkSampler sampler, + uint32_t binding) +{ + size_t iter = 0U; + VkDescriptorImageInfo* image_infos = NULL; + VkWriteDescriptorSet write_set = {0}; + + rse_malloc(image_infos, sizeof(VkDescriptorImageInfo) * images_count); + for (iter = 0U; iter < images_count; ++iter) { + image_infos[iter].sampler = sampler; + image_infos[iter].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image_infos[iter].imageView = context->texture_images[texture_ids[iter]].image_view; + } + + write_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_set.pImageInfo = image_infos; + write_set.dstSet = context->descriptor_data.descriptor_sets[descriptor_set_id]; + write_set.dstBinding = binding; + write_set.dstArrayElement = 0U; + write_set.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; // TODO: Maybe not only samplers? + write_set.descriptorCount = images_count; + + vkUpdateDescriptorSets(context->vulkan_handles.device, 1U, &write_set, 0, NULL); + rse_free(image_infos); + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t descriptor_attach_buffer(struct graphics_context_t* context, + uint32_t descriptor_set_id, + struct vulkan_buffer_t* buffer, + uint32_t binding, + VkDescriptorType type) +{ + VkDescriptorBufferInfo buffer_info = {0}; + VkWriteDescriptorSet write_set = {0}; + + buffer_info.buffer = buffer->buffer; + buffer_info.offset = 0U; + buffer_info.range = buffer->allocated_size; + + write_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_set.pBufferInfo = &buffer_info; + write_set.dstSet = context->descriptor_data.descriptor_sets[descriptor_set_id]; + write_set.dstBinding = binding; + write_set.dstArrayElement = 0U; + write_set.descriptorType = type; + write_set.descriptorCount = 1; + + vkUpdateDescriptorSets(context->vulkan_handles.device, 1U, &write_set, 0, NULL); + + return RSE_ERROR_NO_ERROR; +} + +void destroy_descriptors(struct graphics_context_t* context) +{ + size_t iter = 0U; + + for (iter = 0U; iter < context->descriptor_data.descriptor_sets_count; ++iter) { + vkDestroyDescriptorSetLayout(context->vulkan_handles.device, context->descriptor_data.set_layouts[iter], NULL); + } + vkDestroyDescriptorPool(context->vulkan_handles.device, context->descriptor_data.descriptor_pool, NULL); +} diff --git a/graphics/src/descriptor_builder.h b/graphics/src/descriptor_builder.h new file mode 100644 index 00000000..35fd95cc --- /dev/null +++ b/graphics/src/descriptor_builder.h @@ -0,0 +1,68 @@ +/** + * @file descriptor_builder.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief + * @version 0.1 + * @date 2024-03-01 + * + * @copyright Copyright (c) 2024 + * + */ + +#ifndef DESCRIPTOR_BUILDER_H +#define DESCRIPTOR_BUILDER_H + +#include +#include + +#include "graphics_context.h" + +struct descriptor_set_handle_t; +struct descriptor_set_list_t; + +rse_err_t descriptor_pool_add_type(struct graphics_context_t* context, VkDescriptorType type, uint32_t count); + +rse_err_t descriptor_pool_initialize(struct graphics_context_t* context); + +rse_err_t descriptor_create_new_set(struct graphics_context_t* context, uint32_t* set_id); + +rse_err_t descriptor_add_layout(uint32_t binding, + VkDescriptorType type, + uint32_t descriptor_count, + VkShaderStageFlags shader_stages); + +rse_err_t descriptor_set_finish(struct graphics_context_t* context, uint32_t set_id); + +rse_err_t descriptor_build_sets(struct graphics_context_t* context); + +/** + * @brief Add descriptor set to provided list + * + * @param descriptor_set_handle Handle for given descriptor set + * @param binding Descriptors binding + * @param type Type of descriptors + * @param descriptor_count Number of descriptors in this set + * @param shader_stages In what stages this descriptors will be used + * @return rse_err_t RSE_ERROR_NO_ERROR on success, error code on failure + */ +rse_err_t descriptor_add_set(struct descriptor_set_handle_t* descriptor_set_handle, + uint32_t binding, + VkDescriptorType type, + uint32_t descriptor_count, + VkShaderStageFlags shader_stages); + +rse_err_t descriptor_attach_images(struct graphics_context_t* context, + uint32_t descriptor_set_id, + uint16_t* texture_ids, + size_t images_count, + VkSampler sampler, + uint32_t binding); + +rse_err_t descriptor_attach_buffer(struct graphics_context_t* context, + uint32_t descriptor_set_id, + struct vulkan_buffer_t* buffer, + uint32_t binding, + VkDescriptorType type); + +void destroy_descriptors(struct graphics_context_t* context); +#endif // !DESCRIPTOR_BUILDER_H diff --git a/graphics/src/font_manager.c b/graphics/src/font_manager.c new file mode 100644 index 00000000..0feeae56 --- /dev/null +++ b/graphics/src/font_manager.c @@ -0,0 +1,224 @@ +/** + * @file font_manager.c + * @author Piotr Krygier (piotrkrygier@everyonencancode.xyz) + * @brief Font manager. Load fonts from file and render it on the scene + * @version 0.1 + * @date 2025-10-08 + * + * @copyright Copyright (c) 2025 + * + */ + +#include "font_manager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "freetype/freetype.h" +#include "graphics_context.h" +#include "src/vulkan_commons.h" +#include "utilities/file_utils.h" +#include "vulkan_image.h" +#include "utilities/commons.h" +#include "utilities/errors_common.h" +#include "mesh_controller.h" + +#define FONTS_START_CHARACTER_CODE (33U) + +FT_Library g_font_library = NULL; +FT_Face g_font_faces[FONTS_MAX_COUNT] = {NULL}; +size_t g_font_free_index = 0U; + +rse_err_t fonts_init(void) +{ + FT_Error error = FT_Err_Ok; + + error = FT_Init_FreeType(&g_font_library); + + if (error != FT_Err_Ok) { + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize FreeType2. Error code: %d", error); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t fonts_load_from_file(struct graphics_context_t* context, const char* file_path, uint32_t* font_id) +{ + FT_Error error = FT_Err_Ok; + rse_err_t ret = RSE_ERROR_NO_ERROR; + char real_path[MAX_PATH_LENGTH] = {0}; + + assert(context != NULL); + assert(file_path != NULL); + assert(font_id != NULL); + assert(g_font_library != NULL); + assert(g_font_free_index < FONTS_MAX_COUNT); + + strncpy(real_path, file_path, MAX_PATH_LENGTH); + + if ((ret = file_append_full_path(real_path, MAX_PATH_LENGTH)) != RSE_ERROR_NO_ERROR) { + return ret; + } + + error = FT_New_Face(g_font_library, real_path, 0, &g_font_faces[g_font_free_index]); + if (error != FT_Err_Ok) { + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Failed to load new font face. Error code: %d", error); + return RSE_ERROR_INTERNAL_ERROR; + } + + *font_id = g_font_free_index++; + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t fonts_set_font_size(struct graphics_context_t* context, const uint32_t font_id, const uint32_t font_size) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + // size_t iter = 0U; + FT_UInt glyph_index = 0U; + FT_Error error = FT_Err_Ok; + FT_GlyphSlot glyph = NULL; + struct vertex_t vertices[4] = {0}; + uint16_t indices[] = {0, 1, 2, 2, 3, 0}; + struct sized_font_data_t* font = NULL; + + assert(context != NULL); + assert(font_id < FONTS_MAX_COUNT); + assert(g_font_faces[font_id] != NULL); + + error = FT_Set_Pixel_Sizes(g_font_faces[font_id], + 0, + font_size); + + if (error != FT_Err_Ok) { + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Failed to set font size. Error code: %d", error); + return RSE_ERROR_INTERNAL_ERROR; + } + + font = &context->fonts_data.fonts[font_id]; + font->font_size = font_size; + + // for (iter = 0U; iter < FONT_CHARACTERS_COUNT; ++iter) { + // glyph_index = iter + FONTS_START_CHARACTER_CODE; + glyph_index = 33 + FONTS_START_CHARACTER_CODE; + + error = FT_Load_Char(g_font_faces[font_id], glyph_index, FT_LOAD_RENDER); + if (error != FT_Err_Ok) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Failed to load glyph with id %d. Error code: %d", + glyph_index, + error); + // continue; + return RSE_ERROR_INTERNAL_ERROR; + } + + glyph = g_font_faces[font_id]->glyph; + STATUS_CHECK(load_texture_from_bitmat(context, + glyph->bitmap.buffer, + glyph->bitmap.width, + glyph->bitmap.rows, + &font->font_characters[33].texture_id, + VK_FORMAT_R8_UNORM)); + + vertices[0].pos[0] = 0.0 - glyph->bitmap.width; + vertices[0].pos[1] = glyph->bitmap.rows; + vertices[0].pos[2] = 0.0; + vertices[0].color[0] = 1.0; + vertices[0].color[1] = 1.0; + vertices[0].color[2] = 1.0; + vertices[0].tex_coords[0] = 0.0; + vertices[0].tex_coords[1] = 1.0; + + vertices[1].pos[0] = glyph->bitmap.width; + vertices[1].pos[1] = glyph->bitmap.rows; + vertices[1].pos[2] = 0.0; + vertices[1].color[0] = 1.0; + vertices[1].color[1] = 1.0; + vertices[1].color[2] = 1.0; + vertices[1].tex_coords[0] = 1.0; + vertices[1].tex_coords[1] = 1.0; + + vertices[2].pos[0] = glyph->bitmap.width; + vertices[2].pos[1] = 0.0 - glyph->bitmap.rows; + vertices[2].pos[2] = 0.0; + vertices[2].color[0] = 1.0; + vertices[2].color[1] = 1.0; + vertices[2].color[2] = 1.0; + vertices[2].tex_coords[0] = 1.0; + vertices[2].tex_coords[1] = 0.0; + + vertices[3].pos[0] = 0.0 - glyph->bitmap.width; + vertices[3].pos[1] = 0.0 - glyph->bitmap.rows; + vertices[3].pos[2] = 0.0; + vertices[3].color[0] = 1.0; + vertices[3].color[1] = 1.0; + vertices[3].color[2] = 1.0; + vertices[3].tex_coords[0] = 0.0; + vertices[3].tex_coords[1] = 0.0; + + STATUS_CHECK(create_mesh(context, vertices, indices, 4, 6, &font->font_characters[33].mesh_id)); + // } + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t print_debug_text(struct graphics_context_t* context, uint32_t font_id, char *text) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + (void)text; + + STATUS_CHECK(create_mesh_instance(context, context->fonts_data.fonts[font_id].font_characters[33].mesh_id, (struct instance_data_t){{0.0f, 0.0f, 2.4f}, {0.0f, 0.0f, 0.0}, 1.0f, context->fonts_data.fonts[font_id].font_characters[33].texture_id})); + + return status; +} +// rse_err_t print_debug_text(struct graphics_context_t* context, char* text) +// { +// rse_err_t status = RSE_ERROR_NO_ERROR; +// size_t width, height; +// size_t iter = 0U; +// int number_of_quads = 0; +// char buffer[99999] = {0}; +// float *v0, *v2 = NULL; +// +// int min_x, min_y, max_x, max_y = 0; +// int yy, xx; +// +// width = context->swapchain_data.swapchain_extent.width; +// height = context->swapchain_data.swapchain_extent.height; +// +// context->debug_overlay.pixels_size = width * height * sizeof(uint32_t); +// +// rse_memset(context->debug_overlay.pixels, 0, context->debug_overlay.pixels_size); +// +// number_of_quads = stb_easy_font_print(10.0, 20.0, text, NULL, (void*)buffer, sizeof(buffer)); +// +// for (iter = 0U; iter < number_of_quads * 4; ++iter) { +// v0 = (float*)&buffer[iter * 16 + 0]; +// v2 = (float*)&buffer[iter * 16 + 32]; +// +// min_x = v0[0]; +// min_y = v0[1]; +// max_x = v2[0]; +// max_y = v2[1]; +// +// for (yy = min_y; yy < max_y; ++yy) { +// for (xx = min_x; xx < max_x; ++xx) { +// if (xx >= 0 && yy >= 0 && xx < (int)width && yy < (int)height) { +// context->debug_overlay.pixels[yy * width + xx] = 0xFFFFFFFF; // RGBA white +// } +// } +// } +// } +// +// STATUS_CHECK(texture_update_image(context, (unsigned char*)context->debug_overlay.pixels, width, height, context->debug_overlay.texture_id, VK_FORMAT_R8G8B8A8_UNORM)); +// +// return RSE_ERROR_NO_ERROR; +// } diff --git a/graphics/src/font_manager.h b/graphics/src/font_manager.h new file mode 100644 index 00000000..7c876ad1 --- /dev/null +++ b/graphics/src/font_manager.h @@ -0,0 +1,23 @@ +/** + * @file font_manager.h + * @author Piotr Krygier (piotrkrygier@everyonencancode.xyz) + * @brief Font manager. Load fonts from file and render it on the scene + * @version 0.1 + * @date 2025-10-08 + * + * @copyright Copyright (c) 2025 + * + */ + +#ifndef FONT_MANAGER_H +#define FONT_MANAGER_H + +#include "utilities/commons.h" +#include "graphics_context.h" + +rse_err_t fonts_init(void); +rse_err_t fonts_load_from_file(struct graphics_context_t* context, const char* file_path, uint32_t* font_id); +rse_err_t fonts_set_font_size(struct graphics_context_t* context, const uint32_t font_id, const uint32_t font_size); +rse_err_t print_debug_text(struct graphics_context_t* context, uint32_t font_id, char *text); + +#endif /* FONT_MANAGER_H */ diff --git a/graphics/src/graphics_context.h b/graphics/src/graphics_context.h new file mode 100644 index 00000000..780c427d --- /dev/null +++ b/graphics/src/graphics_context.h @@ -0,0 +1,284 @@ +/** + * @file graphics_context.h + * @author Piotr Krygier (piotrkrygier@everyonencancode.xyz) + * @brief Graphics context for Red Scarf Engine + * @version 0.1 + * @date 2024-05-22 + * + * @copyright Copyright (c) 2024 + * + */ + +#ifndef GRAPHICS_CONTEXT_H +#define GRAPHICS_CONTEXT_H + +#include +#include +#include +#include +#include +#include + +#include "utilities/entity.h" +#include "utilities/stack.h" +#include "utilities/vector.h" +#include "vma/vk_mem_alloc.h" +#include "vulkan/vulkan_core.h" +#include "vulkan_commons.h" + +#define MAX_MESH_NUMBER 256U +/* FIXME: This is the total number of instances * meshes. It shouldn't be so low */ +#define MAX_INSTANCE_NUMBER (MAX_MESH_NUMBER * 256U) + +#define SWAPCHAIN_IMAGE_COUNT (3U) + +/** + * @brief Maximum number of textures that can be loaded + * + */ +#define RSE_MAX_IMAGE_COUNT 256 + +#define MAX_VULKAN_BUFFERS_COUNT (32U) + +#define MAX_PIPELINE_COUNT 16 +#define MAX_CREATE_INFOS (32U) +#define MAX_SHADER_STAGES 12U +#define MAX_SHADER_MODULES_COUNT (128U) +#define MAX_VERTEX_BINDINGS_COUNT (32U) +#define MAX_SHADER_ATTRIBUTES_COUNT (32U) +#define MAX_DYNAMIC_STATES_COUNT (32U) + +#define DESCRIPTOR_MAX_SETS (32U) +#define DESCRIPTOR_TYPE_ID_MAX_ENUM (21U) +#define RENDERER_MAX_COUNT MAX_PIPELINE_COUNT + +#define FONT_CHARACTERS_COUNT (94U) +#define FONTS_MAX_COUNT (32U) + +RSE_STACK_DEFINE(required_dynamic_states_t, VkDynamicState, 16U); + +struct graphics_context_t; + +enum QUEUE_FAMILY_INDEX { + QUEUE_FAMILY_INDEX_GRAPHICS, + QUEUE_FAMILY_INDEX_PRESENTATION, + QUEUE_FAMILY_INDEX_LAST_INDEX, +}; + +enum RENDER_TARGET { + RENDER_TARGET_3D, + RENDER_TARGET_2D, + RENDER_TARGET_COUNT +}; + +/** + * @brief Represents data within vertex or instance buffer. + * + */ +struct buffer_data_t +{ + size_t count; + size_t buffer_offset; +}; + +struct vulkan_buffers_t { + size_t vulkan_buffers_count; + struct vulkan_buffer_t vulkan_buffers[MAX_VULKAN_BUFFERS_COUNT]; +}; + +/** + * @brief Wrapper for Vulkan buffer. Holds allocation data; + * + */ +struct vulkan_image_t +{ + uint8_t id_taken; + size_t allocated_size; + VkImage image; + VkImageView image_view; + VmaAllocation allocation; + VmaAllocationInfo allocation_info; +}; + +RSE_VECTOR_DEFINE(instance_vector_t, struct instance_data_t); +/** + * @brief Mesh data, with unique id and dynamic arrays holding vertices and indices. + * One mesh can have multiple instances. + * + */ +struct mesh_t +{ + size_t vertex_count; + bool is_dirty; + size_t entities_count; + entity_t* entities; /* Which entities are connected to this mesh */ + size_t index_count; + size_t instances_count; + struct instance_vector_t instances_data; +}; + +struct shader_modules_t +{ + uint16_t shader_modules_count; + VkShaderModule shader_modules[MAX_SHADER_MODULES_COUNT]; + VkShaderStageFlagBits shaders_stages[MAX_SHADER_MODULES_COUNT]; +}; + +struct pipeline_infos_t +{ + size_t pipeline_create_infos_count; + VkGraphicsPipelineCreateInfo create_infos[MAX_CREATE_INFOS]; +}; + +struct pipeline_t +{ + VkPipeline pipeline; + uint8_t id; + /* Shader stages */ + size_t shader_stages_count; + VkPipelineShaderStageCreateInfo shader_stages[MAX_SHADER_STAGES]; + /* Input assembly */ + VkPipelineInputAssemblyStateCreateInfo input_assembly; + /* Viewport state */ + VkPipelineViewportStateCreateInfo viewport_state; + /* Rasterizer */ + VkPipelineRasterizationStateCreateInfo rasterizer; + /* Multisampling */ + VkPipelineMultisampleStateCreateInfo multisampling; + /* Color blending */ + VkPipelineColorBlendAttachmentState color_blend_attachment; + VkPipelineColorBlendStateCreateInfo color_blending; + /* Depth stencil */ + VkPipelineDepthStencilStateCreateInfo depth_stencil; + VkPipelineLayout pipeline_layout; + size_t vertex_bindings_count; + VkVertexInputBindingDescription vertex_bindings[MAX_VERTEX_BINDINGS_COUNT]; + size_t shader_attributes_count; + VkVertexInputAttributeDescription shader_attributes[MAX_SHADER_ATTRIBUTES_COUNT]; + size_t dynamic_states_count; + VkDynamicState dynamic_states[MAX_DYNAMIC_STATES_COUNT]; + struct required_dynamic_states_t required_dynamic_states; + struct descriptor_set_handle_t* descriptor_sets; +}; + +struct pipeline_internal_t +{ + uint32_t descriptor_set_ids[MAX_PIPELINE_COUNT]; + size_t pipelines_count; + size_t pipeline_layouts_count; + VkPipeline pipelines[MAX_PIPELINE_COUNT]; + VkPipelineLayout pipeline_layouts[MAX_PIPELINE_COUNT]; +}; + +struct descriptor_data_t +{ + uint32_t pool_sizes_count; + uint32_t descriptor_sets_count; + VkDescriptorPool descriptor_pool; + VkDescriptorPoolSize pool_sizes[DESCRIPTOR_TYPE_ID_MAX_ENUM]; + VkDescriptorSet descriptor_sets[DESCRIPTOR_MAX_SETS]; + VkDescriptorSetLayout set_layouts[DESCRIPTOR_MAX_SETS]; +}; + +struct render_target_t +{ + struct vulkan_buffer_t vertex_buffer; + struct vulkan_buffer_t index_buffer; + size_t instances_data_size; + struct vulkan_buffer_t instance_buffer; + struct vulkan_buffer_t draw_indirect_command_buffer; +}; + +struct swapchain_data_t +{ + VkSwapchainKHR swapchain; + VkImageView swapchain_image_views[SWAPCHAIN_IMAGE_COUNT]; + VkExtent2D swapchain_extent; + VkFramebuffer swapchain_framebuffers[SWAPCHAIN_IMAGE_COUNT]; + VkImage swapchain_images[SWAPCHAIN_IMAGE_COUNT]; +}; + +struct vulkan_handles +{ + VkSampler sampler; + VkPhysicalDevice physical_device; + VkDevice device; + VmaAllocator allocator; + VkSurfaceKHR surface; + VkInstance instance; + VkQueue graphics_queue; + VkQueue present_queue; + VkCommandPool command_pool; + VkCommandBuffer command_buffers[SWAP_BUFFER_COUNT]; + VkRenderPass render_pass; + VkSemaphore image_available_semaphores[SWAP_BUFFER_COUNT]; + VkSemaphore render_finished_semaphores[SWAP_BUFFER_COUNT]; + VkFence in_flight_fences[SWAP_BUFFER_COUNT]; +#ifndef NDEBUG + VkDebugUtilsMessengerEXT debug_messenger; +#endif // !NDEBUG +}; + +struct mesh_data_t { + uint32_t mesh_count; + struct mesh_t mesh_bucket[MAX_MESH_NUMBER]; +}; + +struct debug_overlay_t +{ + uint16_t texture_id; + uint32_t* pixels; + size_t pixels_size; + struct vulkan_image_t image_handle; + VkSampler sampler; +}; + +typedef rse_err_t(*renderer_function_t)(struct graphics_context_t* context, uint32_t renderer_id); + +struct renderer_t { + renderer_function_t render_function; +}; + +struct renderer_data_t { + size_t renderers_count; + struct renderer_t renderers[RENDERER_MAX_COUNT]; +}; + +struct font_character_data_t { + uint32_t mesh_id; + uint16_t texture_id; +}; + +struct sized_font_data_t { + uint8_t font_size; + struct font_character_data_t font_characters[FONT_CHARACTERS_COUNT]; +}; + +struct fonts_data_t { + struct sized_font_data_t fonts[FONTS_MAX_COUNT]; +}; + +struct graphics_context_t +{ + bool is_framebuffer_resized; + uint32_t queue_family_indices[QUEUE_FAMILY_INDEX_LAST_INDEX]; + uint32_t current_frame; + uint32_t swapchain_images_count; + SDL_Window* window_handle; + struct renderer_data_t renderer_data; + struct debug_overlay_t debug_overlay; + struct vulkan_handles vulkan_handles; + struct swapchain_data_t swapchain_data; + struct descriptor_data_t descriptor_data; + struct pipeline_internal_t pipelines_data; + struct render_target_t render_targets[RENDER_TARGET_COUNT]; + struct vulkan_buffers_t uniform_buffers; + struct vulkan_image_t texture_images[RSE_MAX_IMAGE_COUNT]; + struct vulkan_image_t depth_image; + struct vulkan_image_t color_image; + struct shader_modules_t shader_modules; + struct mesh_data_t mesh_data; + struct fonts_data_t fonts_data; +}; + +#endif /* GRAPHICS_CONTEXT_H */ diff --git a/graphics/src/mesh_controller.c b/graphics/src/mesh_controller.c new file mode 100644 index 00000000..a550d282 --- /dev/null +++ b/graphics/src/mesh_controller.c @@ -0,0 +1,59 @@ +/** + * @file mesh_controller.h + * @author Piotr Krygier (piotrkrygier@everyonencancode.xyz) + * @brief Graphics context for Red Scarf Engine + * @version 0.1 + * @date 2025-09-23 + * + * @copyright Copyright (c) 2025 + * + */ +#include +#include +#include + +#include "src/graphics_context.h" +#include "src/vulkan_commons.h" +#include "utilities/commons.h" +#include "utilities/entity.h" +#include "utilities/errors_common.h" +#include "vulkan_buffers.h" + +rse_err_t create_mesh(struct graphics_context_t* context, + struct vertex_t* vertices, + uint16_t* indices, + size_t vertices_count, + size_t indices_count, + uint32_t* mesh_id) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + + if (mesh_id == NULL) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Provided mesh id handle is NULL"); + return RSE_ERROR_NULL_POINTER; + } + + *mesh_id = context->mesh_data.mesh_count; + + if (context->mesh_data.mesh_count > MAX_MESH_NUMBER) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Could not allocate new mesh, reached maximum."); + return RSE_ERROR_INTERNAL_ERROR; + } + + STATUS_CHECK(add_vertices(context, *mesh_id, vertices_count, vertices, indices_count, indices)); + context->mesh_data.mesh_count++; + + return status; +} + +rse_err_t create_mesh_instance(struct graphics_context_t* context, + entity_t entity, + struct instance_data_t instance_data) +{ + if (context->mesh_data.mesh_bucket[entity].instances_count >= MAX_INSTANCE_NUMBER) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Could not allocate new mesh, reached maximum."); + return RSE_ERROR_INTERNAL_ERROR; + } + + return mesh_add_instance(context, entity, &instance_data); +} diff --git a/graphics/src/mesh_controller.h b/graphics/src/mesh_controller.h new file mode 100644 index 00000000..9e53ac7b --- /dev/null +++ b/graphics/src/mesh_controller.h @@ -0,0 +1,68 @@ +/** + * @file mesh_controller.h + * @author Piotr Krygier (piotr.krygier@meshsystems.com) + * @brief + * @version 0.1 + * @date 2023-03-29 + * + * @copyright Copyright (c) 2023 + * + */ + +#ifndef MESH_CONTROLLER_H +#define MESH_CONTROLLER_H + +#include "utilities/entity.h" +#include "vulkan_commons.h" +#include "graphics_context.h" +#include "utilities/commons.h" + +/** + * @brief Create a new mesh for provided vertices and indices. + * + * @param vertices vector of vertices + * @param indices indices for mesh + * @param vertices_num vertices count + * @param indices_num indices count + * @return uint16_t Mesh identifier. Can be useful for getting vertices, indices and instances + */ +rse_err_t create_mesh(struct graphics_context_t* context, struct vertex_t* vertices, + uint16_t* indices, + size_t vertices_count, + size_t indices_count, uint32_t* mesh_id); + +/** + * @brief Create a instance for the selected mesh. Instance can have its own location, rotation and scale + * + * @param context Graphics context + * @param entity Entity we wat to update + * @param instance_data New instance information + */ +rse_err_t create_mesh_instance(struct graphics_context_t* context, entity_t entity, + struct instance_data_t instance_data); + +/** + * @brief Get the vertex count for mesh with mesh_id ID + * + * @param mesh_id Mesh identifier + * @return size_t Number of vertices + */ +size_t get_vertices_count(struct graphics_context_t* context, uint16_t mesh_id); + +/** + * @brief Get the index count for mesh with mesh_id ID + * + * @param mesh_id Mesh identifier + * @return size_t Number of indices + */ +size_t get_indices_count(struct graphics_context_t* context, uint16_t mesh_id); + +/** + * @brief Get vertex offset in vertex buffer for selected mesh + * + * @param mesh_id Mesh identifies + * @return size_t Offset + */ +size_t get_vertex_offset(struct graphics_context_t* context, uint16_t mesh_id); + +#endif /* MESH_CONTROLLER_H */ diff --git a/graphics/src/pipeline_builder.c b/graphics/src/pipeline_builder.c new file mode 100644 index 00000000..1e813e7d --- /dev/null +++ b/graphics/src/pipeline_builder.c @@ -0,0 +1,466 @@ +/** + * @file pipeline_builder.c + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief + * @version 0.1 + * @date 2024-03-01 + * + * @copyright Copyright (c) 2024 + * + */ + +#include "pipeline_builder.h" + +#include +#include +#include + +#include "SDL3/SDL_log.h" +#include "src/graphics_context.h" +#include "utilities/commons.h" +#include "utilities/errors_common.h" +#include "utilities/file_utils.h" +#include "vulkan/vulkan_core.h" + +#define MAX_BINDLESS_RESOURCES 1000 + +rse_err_t shader_create_module(struct graphics_context_t* context, + uint16_t* shader_module_id, + const char* shader_path, + const VkShaderStageFlagBits shader_stage) +{ + char* buffer; + size_t buffer_size; + VkShaderModuleCreateInfo create_info = {0}; + + if (shader_module_id == NULL) { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "Please provide valid pointer to shader module ID\n"); + } + + /* Check if shader module is valid */ + if (shader_stage >= VK_SHADER_STAGE_FLAG_BITS_MAX_ENUM) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Invalid shader stage in pipeline"); + return RSE_ERROR_INTERNAL_ERROR; + } + + /* Read shader file */ + file_read(shader_path, &buffer_size, NULL); + rse_malloc(buffer, buffer_size); + file_read(shader_path, &buffer_size, buffer); + + create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + create_info.pNext = NULL; + create_info.flags = 0U; + create_info.codeSize = buffer_size; + create_info.pCode = (const uint32_t*)(buffer); + + if (context->shader_modules.shader_modules_count > MAX_SHADER_MODULES_COUNT) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Reached maximum number of available shader modules"); + + return RSE_ERROR_INTERNAL_ERROR; + } + + if (VK_SUCCESS != + vkCreateShaderModule(context->vulkan_handles.device, + &create_info, + NULL, + &context->shader_modules.shader_modules[context->shader_modules.shader_modules_count])) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create a shader"); + + return RSE_ERROR_INTERNAL_ERROR; + } + + context->shader_modules.shaders_stages[context->shader_modules.shader_modules_count] = shader_stage; + + *shader_module_id = context->shader_modules.shader_modules_count++; + rse_free(buffer); + + return RSE_ERROR_NO_ERROR; +} + +static rse_err_t pipeline_add_input_assembly(struct pipeline_t* pipeline) +{ + /* For now input assebly is hardcoded */ + pipeline->input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + pipeline->input_assembly.pNext = NULL; + pipeline->input_assembly.flags = 0U; + pipeline->input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + pipeline->input_assembly.primitiveRestartEnable = VK_FALSE; + + return RSE_ERROR_NO_ERROR; +} + +static rse_err_t pipeline_add_viewport_state(struct pipeline_t* pipeline) +{ + /* For now viewport is hardcoded */ + pipeline->viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + pipeline->viewport_state.pNext = NULL; + pipeline->viewport_state.flags = 0U; + pipeline->viewport_state.viewportCount = 1U; + pipeline->viewport_state.pViewports = NULL; /* This is set during rendering */ + pipeline->viewport_state.scissorCount = 1U; + pipeline->viewport_state.pScissors = NULL; /* This is set during rendering */ + + if (pipeline->viewport_state.pViewports == NULL || pipeline->viewport_state.viewportCount == 0U) { + RSE_STACK_PUSH(pipeline->required_dynamic_states, VK_DYNAMIC_STATE_VIEWPORT); + } + + if (pipeline->viewport_state.pScissors == NULL || pipeline->viewport_state.scissorCount == 0U) { + RSE_STACK_PUSH(pipeline->required_dynamic_states, VK_DYNAMIC_STATE_SCISSOR); + } + + return RSE_ERROR_NO_ERROR; +} + +static rse_err_t pipeline_add_rasterizer(struct pipeline_t* pipeline) +{ + /* For now rasterizer is hardcoded */ + pipeline->rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + pipeline->rasterizer.pNext = NULL; + pipeline->rasterizer.flags = 0U; + pipeline->rasterizer.depthClampEnable = VK_FALSE; /* VK_TRUE to play with shadow maps */ + pipeline->rasterizer.rasterizerDiscardEnable = VK_FALSE; /* Disable/Enable output to framebuffer*/ + pipeline->rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + pipeline->rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + pipeline->rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + pipeline->rasterizer.depthBiasEnable = VK_FALSE; + pipeline->rasterizer.depthBiasConstantFactor = 0.0f; + pipeline->rasterizer.depthBiasClamp = 0.0f; + pipeline->rasterizer.depthBiasSlopeFactor = 0.0f; + pipeline->rasterizer.lineWidth = 1.0f; + + return RSE_ERROR_NO_ERROR; +} + +static rse_err_t pipeline_add_multisampling(struct pipeline_t* pipeline) +{ + /* For now multisampling is hardcoded */ + pipeline->multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + pipeline->multisampling.pNext = NULL; + pipeline->multisampling.flags = 0U; + pipeline->multisampling.rasterizationSamples = VK_SAMPLE_COUNT_8_BIT; + pipeline->multisampling.sampleShadingEnable = VK_FALSE; + pipeline->multisampling.minSampleShading = 1.0f; + pipeline->multisampling.pSampleMask = NULL; + pipeline->multisampling.alphaToCoverageEnable = VK_FALSE; + pipeline->multisampling.alphaToOneEnable = VK_FALSE; + + return RSE_ERROR_NO_ERROR; +} + +static rse_err_t pipeline_add_color_blending(struct pipeline_t* pipeline) +{ + /* For now color blending is hardcoded */ + pipeline->color_blend_attachment.colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + pipeline->color_blend_attachment.blendEnable = VK_TRUE; + pipeline->color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + pipeline->color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + pipeline->color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; + pipeline->color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + pipeline->color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + pipeline->color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; + + pipeline->color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + pipeline->color_blending.pNext = NULL; + pipeline->color_blending.flags = 0U; + pipeline->color_blending.logicOpEnable = VK_FALSE; + pipeline->color_blending.logicOp = VK_LOGIC_OP_COPY; + pipeline->color_blending.attachmentCount = 1U; + pipeline->color_blending.pAttachments = &pipeline->color_blend_attachment; + pipeline->color_blending.blendConstants[0] = 0.0f; + pipeline->color_blending.blendConstants[1] = 0.0f; + pipeline->color_blending.blendConstants[2] = 0.0f; + pipeline->color_blending.blendConstants[3] = 0.0f; + + return RSE_ERROR_NO_ERROR; +} + +static rse_err_t pipeline_add_depth_stencil(struct pipeline_t* pipeline) +{ + /* For now depth stencil is hardcoded */ + pipeline->depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + pipeline->depth_stencil.pNext = NULL; + pipeline->depth_stencil.flags = 0U; + pipeline->depth_stencil.depthTestEnable = VK_TRUE; + pipeline->depth_stencil.depthWriteEnable = VK_TRUE; + pipeline->depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS; + pipeline->depth_stencil.depthBoundsTestEnable = VK_FALSE; + pipeline->depth_stencil.stencilTestEnable = VK_FALSE; + pipeline->depth_stencil.minDepthBounds = 0.0f; + pipeline->depth_stencil.maxDepthBounds = 1.0f; + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t pipeline_add_descriptor_sets(struct graphics_context_t* context, + struct pipeline_t* pipeline, + uint32_t descriptor_set_id) +{ + VkPipelineLayoutCreateInfo create_info = {0}; + create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + create_info.pNext = NULL; + create_info.flags = 0U; + create_info.setLayoutCount = 1; // FIXME: Make sure we can enable more than one layout + create_info.pSetLayouts = &context->descriptor_data.set_layouts[descriptor_set_id]; + create_info.pushConstantRangeCount = 0U; + create_info.pPushConstantRanges = NULL; + + if (VK_SUCCESS != + vkCreatePipelineLayout(context->vulkan_handles.device, &create_info, NULL, &pipeline->pipeline_layout)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create pipeline layout"); + return RSE_ERROR_INTERNAL_ERROR; + } + + context->pipelines_data.descriptor_set_ids[context->pipelines_data.pipelines_count] = descriptor_set_id; + context->pipelines_data.pipeline_layouts[context->pipelines_data.pipeline_layouts_count++] = + pipeline->pipeline_layout; + + return RSE_ERROR_NO_ERROR; +} + +/** + * @brief Clean up pipeline builder. This function should be called after all + * pipelines are built and no more pipelines are needed. It frees all resources + * used by pipeline builder. + * + * @param[in/out] pipelines List of pipelines to clean up + */ +static void pipeline_infos_cleanup(struct pipeline_infos_t* pipelines) +{ + size_t pipeline_count = pipelines->pipeline_create_infos_count; + + for (size_t i = 0; i < pipeline_count; i++) { + VkGraphicsPipelineCreateInfo* pipeline_info = &pipelines->create_infos[i]; + + rse_free((VkPipelineVertexInputStateCreateInfo*)pipeline_info->pVertexInputState); + rse_free((VkPipelineDynamicStateCreateInfo*)pipeline_info->pDynamicState); + } + + pipelines->pipeline_create_infos_count = 0; +} + +rse_err_t pipeline_add_dynamic_state(struct pipeline_t* pipeline, VkDynamicState dynamic_state) +{ + if (dynamic_state >= VK_DYNAMIC_STATE_MAX_ENUM) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Invalid dynamic state in pipeline"); + return RSE_ERROR_INTERNAL_ERROR; + } + + // TODO: This was "Push Front" when this was still a list. This might be relevant + pipeline->dynamic_states[pipeline->dynamic_states_count++] = dynamic_state; + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t pipeline_builder_init(struct pipeline_t* pipeline) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + + if (pipeline == NULL) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Provided NULL pointer"); + return RSE_ERROR_INTERNAL_ERROR; + } + + rse_memset(pipeline, 0, sizeof(struct pipeline_t)); + + return status; +} + +rse_err_t pipeline_add_shader_stage(struct graphics_context_t* context, + struct pipeline_t* pipeline, + const uint32_t shader_id) +{ + size_t i = 0; + rse_err_t status = RSE_ERROR_NO_ERROR; + + /* Check if we have enough space for new shader stages */ + if (pipeline->shader_stages_count + 1 > MAX_SHADER_STAGES) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Too many shader stages in pipeline"); + return RSE_ERROR_INTERNAL_ERROR; + } + + i = pipeline->shader_stages_count++; + + pipeline->shader_stages[i].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + pipeline->shader_stages[i].pNext = NULL; + pipeline->shader_stages[i].flags = 0U; + pipeline->shader_stages[i].stage = context->shader_modules.shaders_stages[shader_id]; + /* This is automatically updated during shader module creation */ + pipeline->shader_stages[i].module = context->shader_modules.shader_modules[shader_id]; + pipeline->shader_stages[i].pName = "main"; + pipeline->shader_stages[i].pSpecializationInfo = NULL; + + return status; +} + +rse_err_t pipeline_add_vertex_input_binding(struct pipeline_t* pipeline, + const uint8_t binding, + const uint8_t stride, + const VkVertexInputRate input_rate) +{ + VkVertexInputBindingDescription binding_pipeline_description = {0}; + if (input_rate >= VK_VERTEX_INPUT_RATE_MAX_ENUM) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Invalid vertex input rate"); + return RSE_ERROR_INTERNAL_ERROR; + } + + binding_pipeline_description.binding = binding; + binding_pipeline_description.stride = stride; + binding_pipeline_description.inputRate = input_rate; + + pipeline->vertex_bindings[pipeline->vertex_bindings_count++] = binding_pipeline_description; + return RSE_ERROR_NO_ERROR; +} + +rse_err_t pipeline_add_vertex_input_attribute(struct pipeline_t* pipeline, + const uint8_t binding, + const uint8_t location, + const VkFormat format, + const uint8_t offset) +{ + VkVertexInputAttributeDescription attribute_description = {0}; + if (binding >= 16U) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Invalid binding in pipeline attribute description"); + return RSE_ERROR_INTERNAL_ERROR; + } + if (format >= VK_FORMAT_MAX_ENUM) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Invalid format in pipeline attribute description"); + return RSE_ERROR_INTERNAL_ERROR; + } + + attribute_description.binding = binding; + attribute_description.location = location; + attribute_description.format = format; + attribute_description.offset = offset; + + pipeline->shader_attributes[pipeline->shader_attributes_count++] = attribute_description; + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t pipelines_build(struct graphics_context_t* context, struct pipeline_infos_t* pipelines_infos_list) +{ + assert(NULL != context); + assert(NULL != pipelines_infos_list); + + rse_err_t status = RSE_ERROR_NO_ERROR; + + if (VK_SUCCESS != vkCreateGraphicsPipelines(context->vulkan_handles.device, + VK_NULL_HANDLE, + pipelines_infos_list->pipeline_create_infos_count, + pipelines_infos_list->create_infos, + NULL, + &context->pipelines_data.pipelines[context->pipelines_data.pipelines_count])) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create graphics pipelines"); + status = RSE_ERROR_INTERNAL_ERROR; + } + + context->pipelines_data.pipelines_count += pipelines_infos_list->pipeline_create_infos_count; + pipeline_infos_cleanup(pipelines_infos_list); + rse_memset(pipelines_infos_list, 0, sizeof(struct pipeline_infos_t)); + + return status; +} + +rse_err_t add_pipeline(const struct graphics_context_t* context, + struct pipeline_t* pipeline, + struct pipeline_infos_t* pipelines_infos) +{ + /* Check for send and required dynamic states */ + size_t iter = 0U; + uint8_t dynamic_state_found = 0; + rse_err_t status = RSE_ERROR_NO_ERROR; + VkGraphicsPipelineCreateInfo pipeline_info = {0}; + VkPipelineVertexInputStateCreateInfo* vertex_bindings_ci = NULL; + VkPipelineDynamicStateCreateInfo* dynamic_states_ci = NULL; + + while (RSE_STACK_IS_EMPTY(pipeline->required_dynamic_states) == 0) { + dynamic_state_found = 0; + VkDynamicState dynamic_state = RSE_STACK_POP(pipeline->required_dynamic_states); + for (iter = 0U; iter < pipeline->dynamic_states_count; ++iter) { + if (pipeline->dynamic_states[iter] == dynamic_state) { + dynamic_state_found = 1; + break; + } + } + if (dynamic_state_found == 0) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Missing dynamic state in pipeline"); + return RSE_ERROR_INTERNAL_ERROR; + } + } + + /* Check for required shader stages */ + if (pipeline->shader_stages_count == 0) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Missing shader stages in pipeline. There must be at least one."); + return RSE_ERROR_INTERNAL_ERROR; + } + + /* Build constant pipeline. TODO: Some parts may be customizable */ + pipeline_add_input_assembly(pipeline); + pipeline_add_viewport_state(pipeline); + pipeline_add_rasterizer(pipeline); + pipeline_add_multisampling(pipeline); + pipeline_add_color_blending(pipeline); + pipeline_add_depth_stencil(pipeline); + + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_info.pNext = NULL; + pipeline_info.flags = 0U; + pipeline_info.stageCount = pipeline->shader_stages_count; + pipeline_info.pStages = pipeline->shader_stages; + + rse_malloc(vertex_bindings_ci, sizeof(VkPipelineVertexInputStateCreateInfo)); + vertex_bindings_ci->sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_bindings_ci->pNext = NULL; + vertex_bindings_ci->flags = 0U; + vertex_bindings_ci->vertexBindingDescriptionCount = pipeline->vertex_bindings_count; + vertex_bindings_ci->pVertexBindingDescriptions = pipeline->vertex_bindings; + vertex_bindings_ci->vertexAttributeDescriptionCount = pipeline->shader_attributes_count; + vertex_bindings_ci->pVertexAttributeDescriptions = pipeline->shader_attributes; + pipeline_info.pVertexInputState = vertex_bindings_ci; + + pipeline_info.pInputAssemblyState = &pipeline->input_assembly; + pipeline_info.pTessellationState = NULL; + pipeline_info.pViewportState = &pipeline->viewport_state; + pipeline_info.pRasterizationState = &pipeline->rasterizer; + pipeline_info.pMultisampleState = &pipeline->multisampling; + pipeline_info.pDepthStencilState = &pipeline->depth_stencil; + pipeline_info.pColorBlendState = &pipeline->color_blending; + + rse_malloc(dynamic_states_ci, sizeof(VkPipelineDynamicStateCreateInfo)); + dynamic_states_ci->sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_states_ci->pNext = NULL; + dynamic_states_ci->flags = 0U; + dynamic_states_ci->dynamicStateCount = pipeline->dynamic_states_count; + dynamic_states_ci->pDynamicStates = pipeline->dynamic_states; + pipeline_info.pDynamicState = dynamic_states_ci; + + pipeline_info.layout = pipeline->pipeline_layout; + pipeline_info.renderPass = context->vulkan_handles.render_pass; + pipeline_info.subpass = 0; + pipeline_info.basePipelineHandle = VK_NULL_HANDLE; // TODO: Add possiblity do derive from another pipeline + pipeline_info.basePipelineIndex = -1; + + pipelines_infos->create_infos[pipelines_infos->pipeline_create_infos_count++] = pipeline_info; + + return status; +} + +void destroy_pipelines(struct graphics_context_t* context) +{ + size_t i = 0; + + for (i = 0; i < context->pipelines_data.pipelines_count; i++) { + if (context->pipelines_data.pipeline_layouts[i] != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(context->vulkan_handles.device, context->pipelines_data.pipeline_layouts[i], NULL); + } + if (context->pipelines_data.pipelines[i] != VK_NULL_HANDLE) { + vkDestroyPipeline(context->vulkan_handles.device, context->pipelines_data.pipelines[i], NULL); + } + } + + for (i = 0; i < context->shader_modules.shader_modules_count; i++) { + vkDestroyShaderModule(context->vulkan_handles.device, context->shader_modules.shader_modules[i], NULL); + } +} diff --git a/graphics/src/pipeline_builder.h b/graphics/src/pipeline_builder.h new file mode 100644 index 00000000..63d3f0df --- /dev/null +++ b/graphics/src/pipeline_builder.h @@ -0,0 +1,125 @@ +/** + * @file pipeline_builder.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief Builder for vulkan pipeline + * @version 0.1 + * @date 2024-03-01 + * + * @copyright Copyright (c) 2024 + * + */ + +#ifndef PIPELINE_BUILDER_H +#define PIPELINE_BUILDER_H + +#include +#include +#include + +#include "graphics_context.h" +#include "utilities/commons.h" + +rse_err_t shader_create_module(struct graphics_context_t* context, + uint16_t* shader_module_id, + const char* shader_path, + const VkShaderStageFlagBits shader_stage); + +/** + * @brief Initialize pipeline builder + * + * @param[out] pipeline Pipeline to initialize. Must not be NULL + * @return RSE_SUCCESS on success, error code otherwise + */ +rse_err_t pipeline_builder_init(struct pipeline_t* pipeline); + +/** + * @brief Add shader stages to pipeline + * + * @param[in] context Graphics context handle + * @param[in] pipeline Pipeline to build + * @param[in] shader_id ID of the shader we want to add to pipeline + * @return RSE_SUCCESS on success, error code otherwise + */ +rse_err_t pipeline_add_shader_stage(struct graphics_context_t* context, + struct pipeline_t* pipeline, + const uint32_t shader_id); + +/** + * @brief Add vertex input binding to pipeline + * + * @param[in] pipeline Pipeline to build + * @param[in] binding Binding point for vertex data + * @param[in] stride Stride between vertex data elements in bytes + * @param[in] input_rate Input rate for vertex data + * @return RSE_SUCCESS on success, error code otherwise + */ +rse_err_t pipeline_add_vertex_input_binding(struct pipeline_t* pipeline, + const uint8_t binding, + const uint8_t stride, + const VkVertexInputRate input_rate); + +/** + * @brief Add vertex attribute to shader in pipeline + * + * @param[in] pipeline Pipeline to build + * @param[in] binding Binding point for vertex data + * @param[in] location Location of the attribute in shader + * @param[in] format Format of the attribute + * @param[in] offset Offset of the attribute in bytes + * @return RSE_SUCCESS on success, error code otherwise + */ +rse_err_t pipeline_add_vertex_input_attribute(struct pipeline_t* pipeline, + const uint8_t binding, + const uint8_t location, + const VkFormat format, + const uint8_t offset); + +/** + * @brief Add dynamic state to pipeline + * + * @param[in] pipeline Pipeline to build + * @param[in] dynamic_state Dynamic state to add + * @return RSE_SUCCESS on success, error code otherwise + */ +rse_err_t pipeline_add_dynamic_state(struct pipeline_t* pipeline, const VkDynamicState dynamic_state); + +/** + * @brief Create a Pipeline Layout + * + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_PIPELINE_LAYOUT_CREATION_FAILED + */ +rse_err_t pipeline_add_descriptor_sets(struct graphics_context_t* context, + struct pipeline_t* pipeline, + uint32_t descriptor_set_id); +/** + * @brief Build pipeline from added components. This function should be called after adding all necessary components. + * After call, pipeline is ready to be used and all added components are cleared, ready for next pipeline to be built. + * + * param[in/out] context Graphics context handle + * @param[in] pipeline Pipelines list to build + * @param[in] descriptor_set_handle Filled handle with descriptor sets + * @return RSE_SUCCESS on success, error code otherwise + */ +rse_err_t pipelines_build(struct graphics_context_t* context, struct pipeline_infos_t* pipelines); + +/** + * @brief Add pipeline to list of pipelines + * + * @param[in] context Graphics context handle + * @param[in] pipeline Pipeline to add + * @param[out] pipeline_infos Array of pipelines infos, where new info should be added + * @return RSE_SUCCESS on success, error code otherwise + */ +rse_err_t add_pipeline(const struct graphics_context_t* context, + struct pipeline_t* pipeline, + struct pipeline_infos_t* pipelines_infos); + +/** + * @brief Destroy all pipelines + * + * @param[in] context Graphics context handle + */ +void destroy_pipelines(struct graphics_context_t* context); + +#endif /* PIPELINE_BUILDER_H */ diff --git a/graphics/src/renderer.c b/graphics/src/renderer.c new file mode 100644 index 00000000..7d2ec70c --- /dev/null +++ b/graphics/src/renderer.c @@ -0,0 +1,41 @@ +/** + * @file renderer.c + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief + * @version 0.1 + * @date 2025-10-15 + * + * @copyright Copyright (c) 2025 + * + */ + +#include "renderer.h" + +#include +#include + +#include "src/graphics_context.h" +#include "utilities/commons.h" +#include "utilities/errors_common.h" + +rse_err_t renderer_get_new_id(struct graphics_context_t* context, uint32_t* id) +{ + if (context->renderer_data.renderers_count >= RENDERER_MAX_COUNT) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Reached maximum number of renderers"); + + return RSE_ERROR_INTERNAL_ERROR; + } + + *id = context->renderer_data.renderers_count++; + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t renderer_set_render_function(struct graphics_context_t* context, + uint32_t renderer_id, + renderer_function_t renderer_function) +{ + context->renderer_data.renderers[renderer_id].render_function = renderer_function; + + return RSE_ERROR_NO_ERROR; +} diff --git a/graphics/src/renderer.h b/graphics/src/renderer.h new file mode 100644 index 00000000..e5545705 --- /dev/null +++ b/graphics/src/renderer.h @@ -0,0 +1,24 @@ +/** + * @file renderer.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief + * @version 0.1 + * @date 2025-10-15 + * + * @copyright Copyright (c) 2025 + * + */ + +#ifndef RENDERER_H +#define RENDERER_H + +#include "src/graphics_context.h" +#include "utilities/commons.h" + +rse_err_t renderer_get_new_id(struct graphics_context_t* context, uint32_t* id); + +rse_err_t renderer_set_render_function(struct graphics_context_t* context, + uint32_t renderer_id, + renderer_function_t renderer_function); + +#endif /* !RENDERER_H */ diff --git a/graphics/src/rse_graphics.c b/graphics/src/rse_graphics.c new file mode 100644 index 00000000..7adaee16 --- /dev/null +++ b/graphics/src/rse_graphics.c @@ -0,0 +1,254 @@ +#include "rse_graphics.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "descriptor_builder.h" +#include "font_manager.h" +#include "graphics_context.h" +#include "mesh_controller.h" +#include "pipeline_builder.h" +#include "src/renderer.h" +#include "src/vulkan_buffers.h" +#include "utilities/commons.h" +#include "utilities/errors_common.h" +#include "vulkan_base.h" +#include "vulkan_commons.h" +#include "vulkan_image.h" +#include "window.h" + +struct rse_graphics_context_t +{ + struct graphics_context_t* context; +}; + +static rse_err_t render_static_mesh(struct graphics_context_t* context, uint32_t renderer_id) +{ + VkCommandBuffer command_buffer = context->vulkan_handles.command_buffers[context->current_frame]; + VkDeviceSize offsets[] = {0}; + + (void)renderer_id; + vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, context->pipelines_data.pipelines[0]); + vkCmdBindIndexBuffer(command_buffer, context->render_targets[RENDER_TARGET_3D].index_buffer.buffer, 0, VK_INDEX_TYPE_UINT16); + vkCmdBindVertexBuffers(command_buffer, 0, 1, &context->render_targets[RENDER_TARGET_3D].vertex_buffer.buffer, offsets); + /* FIXME: Currently we can only bind one descriptor set */ + vkCmdBindDescriptorSets(command_buffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + context->pipelines_data.pipeline_layouts[0], + 0, + 1, + &context->descriptor_data.descriptor_sets[0], + 0, + NULL); + + vkCmdDrawIndexedIndirect(command_buffer, + context->render_targets[RENDER_TARGET_3D].draw_indirect_command_buffer.buffer, + 0, + context->mesh_data.mesh_count, + sizeof(VkDrawIndexedIndirectCommand)); + + return RSE_ERROR_NO_ERROR; +} + +static void model_view_projection_update(struct graphics_context_t* context, const struct vulkan_buffer_t* buffer) +{ + struct uniform_buffer_object_t ubo = {0}; + vec3 eye = {0}; + vec3 center = {0}; + vec3 up = {0}; + vec3 rotation_vec = {1.0f, 0.0f, 0.0f}; + float fovy; + float z_near; + float z_far; + + eye[2] = -5.0f; + + up[1] = 1.0f; + + fovy = glm_rad(45.0f); + z_near = 0.1f; + z_far = 20.0f; + + glm_mat4_identity(ubo.model); + + glm_rotate(ubo.model, glm_rad(0.0f), rotation_vec); + glm_lookat(eye, center, up, ubo.view); + glm_perspective( + fovy, + context->swapchain_data.swapchain_extent.width / (float)context->swapchain_data.swapchain_extent.height, + z_near, + z_far, + ubo.proj); + memcpy(buffer->allocation_info.pMappedData, &ubo, sizeof(ubo)); +} + +rse_err_t rse_graphics_init(struct rse_graphics_context_t** context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + struct rse_graphics_context_t* ctx_ptr = NULL; + + if (*context != NULL) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Graphics already initialized!"); + return RSE_ERROR_ALREADY_INITIALIZED; + } + + rse_malloc(*context, sizeof(struct rse_graphics_context_t)); + ctx_ptr = *context; + rse_malloc(ctx_ptr->context, sizeof(struct graphics_context_t)); + rse_memset(ctx_ptr->context, 0, sizeof(struct graphics_context_t)); + + STATUS_CHECK(window_init(&ctx_ptr->context->window_handle, &ctx_ptr->context->is_framebuffer_resized)); + STATUS_CHECK(init_vulkan(ctx_ptr->context)); + + return status; +} + +rse_err_t rse_graphics_test_function(struct rse_graphics_context_t* rse_context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + struct graphics_context_t* context = rse_context->context; + uint16_t textures[2] = {0}; + struct pipeline_t pipeline; + uint32_t descriptor_set_handle = 0; + struct pipeline_infos_t pipeline_infos = {0}; + struct vulkan_buffer_t model_view_projection_buffer = {0}; + uint32_t mesh_id_1, mesh_id_2 = 0; + uint32_t font_id = 0; + uint32_t static_mesh_renderer = 0U; + uint16_t vertex_shader_id, fragment_shader_id; + + load_texture_from_file(context, "../../test_image.jpg", &textures[0]); + load_texture_from_file(context, "../../test_image_2.jpg", &textures[1]); + + STATUS_CHECK(fonts_init()); + STATUS_CHECK(fonts_load_from_file(context, "../../NotoSansMono-Regular.ttf", &font_id)); + STATUS_CHECK(fonts_set_font_size(context, font_id, 16)); + + // FIXME: Temporary array of vertices, for testing purposes + struct vertex_t vertices[] = {{{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}}; + + struct vertex_t vertices2[] = {{{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}}; + + // FIXME: Temporary array of vertices, for testing purposes + uint16_t indices[] = {0, 1, 2, 2, 3, 0}; + + create_mesh(context, vertices, indices, 4, 6, &mesh_id_1); + if (RSE_ERROR_NO_ERROR != + create_mesh_instance(context, + mesh_id_1, + (struct instance_data_t){{0.0f, 0.0f, 2.4f}, {0.0f, 0.0f, 0.0}, 1.0f, textures[1]})) { + exit(1); + } + + if (RSE_ERROR_NO_ERROR != + create_mesh_instance( + context, + mesh_id_1, + (struct instance_data_t){{-0.5f, 0.0f, -1.0f}, {0.0f, 0.0f, glm_rad(10.0f)}, 1.0f, textures[0]})) { + exit(1); + } + + create_mesh(context, vertices2, indices, 4, 6, &mesh_id_2); + + if (RSE_ERROR_NO_ERROR != + create_mesh_instance( + context, + mesh_id_2, + (struct instance_data_t){{1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, glm_rad(0.0f)}, 1.0f, textures[0]})) { + exit(1); + } + + create_uniform_buffer(context, &model_view_projection_buffer, sizeof(struct uniform_buffer_object_t)); + model_view_projection_update(context, &model_view_projection_buffer); + buffers_update(context); + + sampler_create(context, &context->vulkan_handles.sampler); + + descriptor_pool_add_type(context, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2); + descriptor_pool_add_type(context, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2); + descriptor_pool_initialize(context); + + /* Normal rendering */ + descriptor_create_new_set(context, &descriptor_set_handle); + descriptor_add_layout(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT); + descriptor_add_layout(1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT); + descriptor_add_layout(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, VK_SHADER_STAGE_FRAGMENT_BIT); + + descriptor_set_finish(context, descriptor_set_handle); + + descriptor_add_layout(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT); + descriptor_build_sets(context); + + descriptor_attach_buffer(context, + descriptor_set_handle, + &model_view_projection_buffer, + 0, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + descriptor_attach_buffer(context, + descriptor_set_handle, + &context->render_targets[RENDER_TARGET_3D].instance_buffer, + 1, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + + descriptor_attach_images(context, descriptor_set_handle, textures, 2, context->vulkan_handles.sampler, 2); + + /* Initialize pipeline */ + pipeline_builder_init(&pipeline); + + shader_create_module(context, &vertex_shader_id, "shader.vert.num", VK_SHADER_STAGE_VERTEX_BIT); + pipeline_add_shader_stage(context, &pipeline, vertex_shader_id); + shader_create_module(context, &fragment_shader_id, "solid_objects.frag.num", VK_SHADER_STAGE_FRAGMENT_BIT); + pipeline_add_shader_stage(context, &pipeline, fragment_shader_id); + + pipeline_add_vertex_input_binding(&pipeline, 0, sizeof(struct vertex_t), VK_VERTEX_INPUT_RATE_VERTEX); + + pipeline_add_vertex_input_attribute(&pipeline, 0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(struct vertex_t, pos)); + pipeline_add_vertex_input_attribute(&pipeline, 0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(struct vertex_t, color)); + pipeline_add_vertex_input_attribute(&pipeline, 0, 2, VK_FORMAT_R32G32_SFLOAT, offsetof(struct vertex_t, tex_coords)); + + pipeline_add_dynamic_state(&pipeline, VK_DYNAMIC_STATE_VIEWPORT); + pipeline_add_dynamic_state(&pipeline, VK_DYNAMIC_STATE_SCISSOR); + + pipeline_add_descriptor_sets(context, &pipeline, descriptor_set_handle); + add_pipeline(context, &pipeline, &pipeline_infos); + + pipelines_build(context, &pipeline_infos); + + renderer_get_new_id(context, &static_mesh_renderer); + + renderer_set_render_function(context, static_mesh_renderer, render_static_mesh); + + return RSE_ERROR_NO_ERROR; +} + +int rse_graphics_run(void* arg) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + struct rse_graphics_context_t* rse_context = (struct rse_graphics_context_t*)arg; + struct graphics_context_t* context = rse_context->context; + + STATUS_CHECK(run_vulkan(context)); + STATUS_CHECK(window_loop(context)); + + deinit_vulkan(context); + + window_terminate(context->window_handle); + rse_free(context->debug_overlay.pixels); + rse_free(context); + rse_free(rse_context); + + return status; +} diff --git a/graphics/src/vma_port.cpp b/graphics/src/vma_port.cpp new file mode 100644 index 00000000..4f0b3503 --- /dev/null +++ b/graphics/src/vma_port.cpp @@ -0,0 +1,29 @@ +/** + * @file vma_port.cpp + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief C port for vma + * @version 0.1 + * @date 2023-08-11 + * + * @copyright Copyright (c) 2023 + * + */ + +/* VMA allows C linkage, but it must be compiled as a CPP file. This file takes cares of it */ +#define VMA_IMPLEMENTATION +#ifdef __unix__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wpedantic" +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#pragma GCC diagnostic ignored "-Wswitch" +#pragma GCC diagnostic ignored "-Wparentheses" +#endif +#include "vma/vk_mem_alloc.h" +#ifdef __unix__ +#pragma GCC diagnostic pop +#endif diff --git a/graphics/src/vulkan_base.c b/graphics/src/vulkan_base.c new file mode 100644 index 00000000..c2d08042 --- /dev/null +++ b/graphics/src/vulkan_base.c @@ -0,0 +1,673 @@ +/** + * @file vulkanBase.cp + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief Base configuration for Vulkan API + * @version 0.1 + * @date 2022-05-13 + * + * @copyright Copyright (c) 2022 + * + */ + +#include "vulkan_base.h" + +#include +#include +#include +#include +#include + +#include "src/descriptor_builder.h" +#include "src/graphics_context.h" +#include "utilities/commons.h" +#include "utilities/errors_common.h" +#include "vulkan/vulkan_core.h" + +#include "vulkan_commons.h" +#include "vulkan_commands.h" +#include "vulkan_buffers.h" +#include "vulkan_image.h" +#include "vulkan_render_pass.h" +#include "vulkan_swapchain.h" +#include "pipeline_builder.h" + +#include +#include +#include + +#define APPLICATION_NAME "RedScarfEngine PoC" +#define ENGINE_NAME "RedScarf Engine" + +/** + * @brief Calback for debug messenger + * + * @param message_severity Message severity + * @param message_type Type of a message + * @param callback_data Contains all the callback related data in the VkDebugUtilsMessengerCallbackDataEXT structure + * @param user_data Data provided by the user + * @return VkBool32 Should always return VK_FALSE. The VK_TRUE value is reserved for use in layer development + */ +static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, + VkDebugUtilsMessageTypeFlagsEXT message_type, + const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) +{ + (void)message_severity; + (void)message_type; + (void)user_data; + + /* There is no guarantee, that the error message will have single severity flag set, so we can't use switch() + * here. We have to check flags and set severity to most important one */ + + if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s", callback_data->pMessage); + } else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "%s", callback_data->pMessage); + } else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "%s", callback_data->pMessage); + } else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { + SDL_LogTrace(SDL_LOG_CATEGORY_GPU, "%s", callback_data->pMessage); + } + + return VK_FALSE; +} + +/** + * @brief Proxy for vkCreateDebugUtilsMessengerEXT + * + * @param instance Vulkan instance object + * @param create_info VkDebugUtilsMessengerCreateInfoEXT structure + * @param allocator Memory allocator + * @param debug_messenger Debug messanger handle + * @return VkResult VK_SUCCESS on success + */ +static VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* create_info, + const VkAllocationCallbacks* allocator, + VkDebugUtilsMessengerEXT* debug_messenger) +{ + PFN_vkCreateDebugUtilsMessengerEXT func = NULL; + + func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != NULL) { + return func(instance, create_info, allocator, debug_messenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + + return VK_SUCCESS; +} + +/** + * @brief Setup debugging system for Vulkan API + * + * @param context Graphics context + * + */ +static rse_err_t setup_debug_messenger(VkInstance instance, VkDebugUtilsMessengerEXT* messenger) +{ + VkDebugUtilsMessengerCreateInfoEXT create_info = {0}; + + create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + create_info.pNext = NULL; + create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + create_info.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; + create_info.pfnUserCallback = debug_callback; + create_info.pUserData = NULL; // Optional + create_info.flags = 0U; + + if (CreateDebugUtilsMessengerEXT(instance, &create_info, NULL, messenger) != VK_SUCCESS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to setup debug messenger"); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +/** + * @brief Proxy for vkDestroyDebugUtilsMessengerEXT + * + * @param instance Vulkan instance object + * @param debug_messenger Debug messanger handle + * @param allocator Memory allocator + */ +static void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debug_messenger, + const VkAllocationCallbacks* allocator) +{ + PFN_vkDestroyDebugUtilsMessengerEXT func = + (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != NULL) { + func(instance, debug_messenger, allocator); + } +} + +/** + * @brief Create the Vulkan Instance object + * + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_INSTANCE_INIT_FAILED + */ +static rse_err_t create_instance(VkInstance* instance) +{ + size_t i = 0U; + size_t j = 0U; + uint8_t supported = 0U; + VkResult result = VK_SUCCESS; + uint32_t extensionCount = 0U; + uint32_t layersCount = 0U; + VkApplicationInfo application_info = {0}; + VkInstanceCreateInfo create_info = {0}; + VkLayerProperties* layer_properties; + VkExtensionProperties* extensions_properties; + + const char* enabled_instance_extensions_names[] = { + "VK_KHR_surface", + "VK_KHR_device_group_creation", +#ifdef __linux__ +#ifdef WAYLAND + "VK_KHR_wayland_surface", +#else + "VK_KHR_xlib_surface", +#endif /* WAYLAND */ +#elif defined(_WIN32) || defined(WIN32) + "VK_KHR_win32_surface", +#endif /* _WIN32 || WIN32 */ + "VK_EXT_debug_utils", + }; +#ifndef NDEBUG + uint32_t enabled_instance_extensions_count = 4; + uint32_t enabled_instance_layers_count = 1; + const char* enabled_instance_layers_names[] = { + "VK_LAYER_KHRONOS_validation", + }; +#else + uint32_t enabled_instance_extensions_count = 3; + uint32_t enabled_instance_layers_count = 0; + const char* enabled_instance_layers_names[] = {}; +#endif /* NDEBUG */ + + vkEnumerateInstanceLayerProperties(&layersCount, NULL); + rse_malloc(layer_properties, sizeof(VkLayerProperties) * layersCount); + vkEnumerateInstanceLayerProperties(&layersCount, layer_properties); + + vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL); + rse_malloc(extensions_properties, sizeof(VkExtensionProperties) * extensionCount); + vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensions_properties); + +#ifndef NDEBUG + SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Supported Layers: "); + for (i = 0; i < layersCount; i++) { + SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "\t-%s", layer_properties[i].layerName); + } + + SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Supported Extensions: "); + for (size_t i = 0; i < extensionCount; i++) { + SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "\t-%s", extensions_properties[i].extensionName); + } +#endif + + /* Check if selected layers are supported*/ + for (i = 0; i < enabled_instance_layers_count; ++i) { + for(j = 0; i < layersCount; ++j) { + if(strcmp(layer_properties[j].layerName, enabled_instance_layers_names[i]) == 0) { + supported = 1; + break; + } + } + if(supported == 0) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Selected layer is not supported: %s", layer_properties[j].layerName); + return RSE_ERROR_INTERNAL_ERROR; + } + supported = 0; + } + + /* Check if selected extensions are supported*/ + for (i = 0; i < enabled_instance_extensions_count; ++i) { + for(j = 0; j < extensionCount; ++j) { + if(strcmp(extensions_properties[j].extensionName, enabled_instance_extensions_names[i]) == 0) { + supported = 1; + break; + } + } + if(supported == 0) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Selected extension is not supported: %s", extensions_properties[j].extensionName); + return RSE_ERROR_INTERNAL_ERROR; + } + supported = 0; + } + + rse_free(layer_properties); + rse_free(extensions_properties); + + application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + application_info.pNext = NULL; + application_info.pApplicationName = APPLICATION_NAME; + application_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0); + application_info.pEngineName = ENGINE_NAME; + application_info.engineVersion = VK_MAKE_VERSION(0, 1, 0); + application_info.apiVersion = VK_API_VERSION_1_4; + + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + create_info.pNext = NULL; + create_info.flags = 0U; + create_info.pApplicationInfo = &application_info; + create_info.enabledLayerCount = enabled_instance_layers_count; + create_info.ppEnabledLayerNames = enabled_instance_layers_names; + create_info.enabledExtensionCount = enabled_instance_extensions_count; + create_info.ppEnabledExtensionNames = enabled_instance_extensions_names; + + result = vkCreateInstance(&create_info, NULL, instance); + if (VK_SUCCESS != result) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Vulkan instance initialization failed"); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +/** + * @brief Create a Surface, connection between Vulkan and actual window + * + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + */ +static rse_err_t create_surface(VkInstance instance, SDL_Window* window_handle, VkSurfaceKHR* surface) +{ + if (true != SDL_Vulkan_CreateSurface(window_handle, instance, NULL, surface)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create window surface. Error: %s", SDL_GetError()); + return RSE_ERROR_INTERNAL_ERROR; + } + return RSE_ERROR_NO_ERROR; +} + +/** + * @brief Choose physical device. Used later for vkDevice + * + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_NO_PHYSICAL_DEVICE_FOUND + * VULKAN_ERROR_NO_SUITABLE_PHYSICAL_DEVICE_FOUND + */ +static rse_err_t pick_physical_device(VkInstance instance, VkPhysicalDevice* physical_device) +{ + uint32_t physical_device_count = 0U; + VkPhysicalDevice* physical_devices = NULL; + VkPhysicalDeviceProperties device_properties = {0};; + VkPhysicalDeviceFeatures device_features = {0}; + + /* Get number of existing physical devices */ + vkEnumeratePhysicalDevices(instance, &physical_device_count, NULL); + + if (0 == physical_device_count) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "No physical graphical physical devices found in system"); + return RSE_ERROR_INTERNAL_ERROR; + } + + rse_malloc(physical_devices, sizeof(VkPhysicalDevice) * physical_device_count); + + vkEnumeratePhysicalDevices(instance, &physical_device_count, physical_devices); + + /* Check for suitability */ + for (size_t physicalDeviceIdx = 0U; physicalDeviceIdx < physical_device_count; ++physicalDeviceIdx) { + vkGetPhysicalDeviceProperties(physical_devices[physicalDeviceIdx], &device_properties); + vkGetPhysicalDeviceFeatures(physical_devices[physicalDeviceIdx], &device_features); + + /* Expect discrete graphics device type */ + if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU || + device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) { + /* Application MUST have geometry shader */ + if (device_features.geometryShader) { + *physical_device = physical_devices[physicalDeviceIdx]; + } + } + } + + if (VK_NULL_HANDLE == physical_device) { + /* No suitable device found */ + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "No suitable physical device found"); + return RSE_ERROR_INTERNAL_ERROR; + } + + rse_free(physical_devices); + + return RSE_ERROR_NO_ERROR; +} + +/** + * @brief Create vkDevice object + * + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_QUEUE_NOT_SUPPORTED + * VULKAN_ERROR_DEVICE_CREATION_FAILED + */ +static rse_err_t create_device(struct graphics_context_t* context) +{ + bool graphics_family_found = false; + bool presentation_familiy_found = false; + float* graphics_familiy_queue_priorities = NULL; + float* presentation_familiy_queue_priorities = NULL; + uint32_t device_queues_count = 0; + int64_t graphics_family_idx = 0; + uint32_t presentation_familiy_idx = 0; + uint32_t queue_families_property_count = 0U; + uint32_t enabled_device_extensions_count = 2; + VkBool32 bindless_supported = VK_FALSE; + VkDeviceCreateInfo device_create_info = {0}; + VkQueueFamilyProperties* queue_family_properties = NULL; + VkDeviceQueueCreateInfo device_queue_createinfos[2] = {0}; + VkPhysicalDeviceFeatures2 physical_features2 = {0}; + VkPhysicalDeviceDescriptorIndexingFeatures indexing_features = {0}; + const char* enabled_device_extensions_names[] = { + "VK_KHR_swapchain", + "VK_EXT_descriptor_indexing", + }; + + /* Enable descriptori indexing for bindless textures */ + indexing_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT; + + physical_features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + physical_features2.pNext = &indexing_features; + + vkGetPhysicalDeviceFeatures2(context->vulkan_handles.physical_device, &physical_features2); + + bindless_supported = indexing_features.descriptorBindingPartiallyBound && indexing_features.runtimeDescriptorArray; + if (bindless_supported == VK_FALSE) { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "Required features for bindless design not supported"); + return RSE_ERROR_INTERNAL_ERROR; + } + + /* Get information about supported queue families */ + vkGetPhysicalDeviceQueueFamilyProperties(context->vulkan_handles.physical_device, &queue_families_property_count, NULL); + rse_malloc(queue_family_properties, sizeof(VkQueueFamilyProperties) * queue_families_property_count); + vkGetPhysicalDeviceQueueFamilyProperties(context->vulkan_handles.physical_device, &queue_families_property_count, + queue_family_properties); + + /* Get queue families that support graphics operations AND have most + * queues. */ + { + size_t numberOfQueues = 0; + for (uint32_t familyIdx = 0; familyIdx < queue_families_property_count; ++familyIdx) { + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Checking queue: %d", familyIdx); + if (queue_family_properties[familyIdx].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if (queue_family_properties[familyIdx].queueCount > numberOfQueues) { + graphics_family_idx = familyIdx; + graphics_family_found = true; + SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Setting graphics family idx to %d", familyIdx); + numberOfQueues = queue_family_properties[familyIdx].queueCount; + device_queues_count++; + } + } + + /* Check for family with surface support */ + VkBool32 presentSupport = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(context->vulkan_handles.physical_device, familyIdx, context->vulkan_handles.surface, &presentSupport); + if (presentSupport) { + presentation_familiy_idx = familyIdx; + presentation_familiy_found = true; + /* If this is the same queue family as in graphics family, don't increase queue count */ + if (presentation_familiy_idx != graphics_family_idx) { + device_queues_count++; + } + } + + if (graphics_family_found && presentation_familiy_found) { + break; + } + } + } + + /* This is not magic number ;P. We want to support exact number of required queues flags */ + if (graphics_family_idx < 0) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Selected physical device does not support required queue capabilities"); + return RSE_ERROR_INTERNAL_ERROR; + } + + context->queue_family_indices[QUEUE_FAMILY_INDEX_GRAPHICS] = graphics_family_idx; + context->queue_family_indices[QUEUE_FAMILY_INDEX_PRESENTATION] = presentation_familiy_idx; + + rse_malloc(graphics_familiy_queue_priorities, sizeof(float) * queue_family_properties[graphics_family_idx].queueCount); + + for (size_t i = 0; i < queue_family_properties[graphics_family_idx].queueCount; i++) { + graphics_familiy_queue_priorities[i] = 1.0f; + } + + rse_malloc(presentation_familiy_queue_priorities, sizeof(float) * queue_family_properties[presentation_familiy_idx].queueCount); + for (size_t i = 0; i < queue_family_properties[presentation_familiy_idx].queueCount; i++) { + presentation_familiy_queue_priorities[i] = + 1.0f; + } + + device_queue_createinfos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + device_queue_createinfos[0].pNext = NULL; + device_queue_createinfos[0].flags = 0; + device_queue_createinfos[0].queueFamilyIndex = graphics_family_idx; + device_queue_createinfos[0].queueCount = queue_family_properties[graphics_family_idx].queueCount; + device_queue_createinfos[0].pQueuePriorities = graphics_familiy_queue_priorities; + + device_queue_createinfos[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + device_queue_createinfos[1].pNext = NULL; + device_queue_createinfos[1].flags = 0; + device_queue_createinfos[1].queueFamilyIndex = presentation_familiy_idx; + device_queue_createinfos[1].queueCount = queue_family_properties[presentation_familiy_idx].queueCount; + device_queue_createinfos[1].pQueuePriorities = presentation_familiy_queue_priorities; + + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_create_info.pNext = &physical_features2; + device_create_info.flags = 0U; + device_create_info.queueCreateInfoCount = device_queues_count; + device_create_info.pQueueCreateInfos = device_queue_createinfos; + device_create_info.enabledLayerCount = 0; + device_create_info.ppEnabledLayerNames = NULL; + device_create_info.enabledExtensionCount = enabled_device_extensions_count; + device_create_info.ppEnabledExtensionNames = enabled_device_extensions_names; + device_create_info.pEnabledFeatures = NULL; + + if (VK_SUCCESS != vkCreateDevice(context->vulkan_handles.physical_device, &device_create_info, NULL, &context->vulkan_handles.device)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create logical device"); + return RSE_ERROR_INTERNAL_ERROR; + } + + vkGetDeviceQueue(context->vulkan_handles.device, graphics_family_idx, 0, &context->vulkan_handles.graphics_queue); + vkGetDeviceQueue(context->vulkan_handles.device, presentation_familiy_idx, 0, &context->vulkan_handles.present_queue); + + rse_free(presentation_familiy_queue_priorities); + rse_free(graphics_familiy_queue_priorities); + rse_free(queue_family_properties); + + return RSE_ERROR_NO_ERROR; +} + +/** + * @brief Create a Memory Allocator object + * + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_ALLOCATOR_CREATION_FAILED + */ +static rse_err_t create_memory_allocator(struct graphics_context_t* context) +{ + VmaVulkanFunctions vulkan_functions = {0}; + vulkan_functions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr; + vulkan_functions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr; + + VmaAllocatorCreateInfo allocator_create_info = {0}; + allocator_create_info.vulkanApiVersion = VK_API_VERSION_1_3; + allocator_create_info.physicalDevice = context->vulkan_handles.physical_device; + allocator_create_info.device = context->vulkan_handles.device; + allocator_create_info.instance = context->vulkan_handles.instance; + allocator_create_info.pVulkanFunctions = &vulkan_functions; + + if (VK_SUCCESS != vmaCreateAllocator(&allocator_create_info, &context->vulkan_handles.allocator)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create Vulkan Memory Allocator"); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + + +/** + * @brief Create a Sync Objects + * + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_SYNCOBJCTS_CREATION_FAILED + */ +static rse_err_t create_sync_objects(struct graphics_context_t* context) +{ + VkSemaphoreCreateInfo semaphore_info = {0}; + semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fenceInfo = {0}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < SWAP_BUFFER_COUNT; ++i) { + if (VK_SUCCESS != vkCreateSemaphore(context->vulkan_handles.device, &semaphore_info, NULL, &context->vulkan_handles.image_available_semaphores[i]) || + VK_SUCCESS != vkCreateSemaphore(context->vulkan_handles.device, &semaphore_info, NULL, &context->vulkan_handles.render_finished_semaphores[i]) || + VK_SUCCESS != vkCreateFence(context->vulkan_handles.device, &fenceInfo, NULL, &context->vulkan_handles.in_flight_fences[i])) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create sync objects."); + return RSE_ERROR_INTERNAL_ERROR; + } + } + + return RSE_ERROR_NO_ERROR; +} + + +rse_err_t init_vulkan(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + + STATUS_CHECK(create_instance(&context->vulkan_handles.instance)); + STATUS_CHECK(setup_debug_messenger(context->vulkan_handles.instance, &context->vulkan_handles.debug_messenger)); + STATUS_CHECK(create_surface(context->vulkan_handles.instance, context->window_handle, &context->vulkan_handles.surface)); + STATUS_CHECK(pick_physical_device(context->vulkan_handles.instance, &context->vulkan_handles.physical_device)); + STATUS_CHECK(create_device(context)); + STATUS_CHECK(create_memory_allocator(context)); + STATUS_CHECK(create_render_pass(context)); + STATUS_CHECK(init_commands(context)); + STATUS_CHECK(create_buffers(context)); + STATUS_CHECK(init_vulkan_images(context)); + STATUS_CHECK(create_swapchain_and_framebuffers(context)); + + return status; +} + +rse_err_t run_vulkan(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + + STATUS_CHECK(create_sync_objects(context)); + + init_time(); + + return status; +} + +rse_err_t draw_frame(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + uint32_t image_index = 0U; + VkResult result = VK_FALSE; + VkSubmitInfo submit_info = {0}; + VkSemaphore wait_semaphores[1]; + VkPipelineStageFlags wait_stages[1]; + VkSemaphore signal_semaphores[1]; + VkPresentInfoKHR present_info = {0}; + VkSwapchainKHR swap_chains[1]; + + vkWaitForFences(context->vulkan_handles.device, 1, &context->vulkan_handles.in_flight_fences[context->current_frame], VK_TRUE, UINT64_MAX); + + STATUS_CHECK(buffers_update(context)); + + update_time(); + + result = vkAcquireNextImageKHR(context->vulkan_handles.device, context->swapchain_data.swapchain, UINT64_MAX, context->vulkan_handles.image_available_semaphores[context->current_frame], + VK_NULL_HANDLE, &image_index); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreate_swapchain(context); + return RSE_ERROR_NO_ERROR; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to acquire swapchain image"); + return RSE_ERROR_INTERNAL_ERROR; + } + + // update_uniform_buffers(context); + + /* Only reset the fence if we are submitting work */ + vkResetFences(context->vulkan_handles.device, 1, &context->vulkan_handles.in_flight_fences[context->current_frame]); + reset_command_buffer(context); + + record_command_buffer(context, image_index); + + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + wait_semaphores[0] = context->vulkan_handles.image_available_semaphores[context->current_frame]; + wait_stages[0] = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = wait_semaphores; + submit_info.pWaitDstStageMask = wait_stages; + + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &context->vulkan_handles.command_buffers[context->current_frame]; + + signal_semaphores[0] = context->vulkan_handles.render_finished_semaphores[context->current_frame]; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = signal_semaphores; + + if (vkQueueSubmit(context->vulkan_handles.graphics_queue, 1, &submit_info, context->vulkan_handles.in_flight_fences[context->current_frame]) != VK_SUCCESS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to submit draw command buffer!"); + return RSE_ERROR_INTERNAL_ERROR; + } + + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = &context->vulkan_handles.render_finished_semaphores[context->current_frame]; + + swap_chains[0] = context->swapchain_data.swapchain; + present_info.swapchainCount = 1; + present_info.pSwapchains = swap_chains; + + present_info.pImageIndices = &image_index; + + result = vkQueuePresentKHR(context->vulkan_handles.present_queue, &present_info); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || context->is_framebuffer_resized) { + context->is_framebuffer_resized = false; + recreate_swapchain(context); + } else if (result != VK_SUCCESS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to submit draw command buffer!"); + return RSE_ERROR_INTERNAL_ERROR; + } + + context->current_frame = (context->current_frame + 1) % SWAP_BUFFER_COUNT; + + return status; +} + +void deinit_vulkan(struct graphics_context_t* context) +{ + destroy_buffers(context); + for (size_t i = 0; i < SWAP_BUFFER_COUNT; ++i) { + vkDestroySemaphore(context->vulkan_handles.device, context->vulkan_handles.image_available_semaphores[i], NULL); + vkDestroySemaphore(context->vulkan_handles.device, context->vulkan_handles.render_finished_semaphores[i], NULL); + vkDestroyFence(context->vulkan_handles.device, context->vulkan_handles.in_flight_fences[i], NULL); + } + + destroy_textures(context); + + destroy_commands(context); + + destroy_render_pass(context); + cleanup_swapchain(context); + destroy_pipelines(context); + destroy_descriptors(context); + + vmaDestroyAllocator(context->vulkan_handles.allocator); + vkDestroyDevice(context->vulkan_handles.device, NULL); + DestroyDebugUtilsMessengerEXT(context->vulkan_handles.instance, context->vulkan_handles.debug_messenger, NULL); + vkDestroySurfaceKHR(context->vulkan_handles.instance, context->vulkan_handles.surface, NULL); + vkDestroyInstance(context->vulkan_handles.instance, NULL); +} diff --git a/graphics/src/vulkan_base.h b/graphics/src/vulkan_base.h new file mode 100644 index 00000000..47385247 --- /dev/null +++ b/graphics/src/vulkan_base.h @@ -0,0 +1,51 @@ +/** + * @file vulkan_base.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief Vulkan wrapper functions used by other parts of Graphics component + * @version 0.1 + * @date 2022-05-13 + * + * @copyright Copyright (c) 2022 + * + */ + +#ifndef GRAPHICS_VULKANBASE_H +#define GRAPHICS_VULKANBASE_H + +#include + +#include "utilities/commons.h" +#include "graphics_context.h" + +/** + * @brief Initialize Vulkan backend for buffers and stuff + * + * @param context Graphics context handle + * @return rse_err_t 0 on success. Status code on failure + */ +rse_err_t init_vulkan(struct graphics_context_t* context); + +/** + * @brief Initialize Vulkan backend with descriptors + * + * @param context Graphics context handle + * @return rse_err_t 0 on success. Status code on failure + */ +rse_err_t run_vulkan(struct graphics_context_t* context); + +/** + * @brief Deinitialize Vulkan backend + * + * @param context Graphics context handle + * */ +void deinit_vulkan(struct graphics_context_t* context); + +/** + * @brief Draw frame on the screen. + * + * @param context Graphics context + * @return rse_err_t + */ +rse_err_t draw_frame(struct graphics_context_t* context); + +#endif /* GRAPHICS_VULKANBASE_H */ diff --git a/graphics/src/vulkan_buffers.c b/graphics/src/vulkan_buffers.c new file mode 100644 index 00000000..c6201d4f --- /dev/null +++ b/graphics/src/vulkan_buffers.c @@ -0,0 +1,448 @@ +#include "vulkan_buffers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "src/graphics_context.h" +#include "src/vulkan_commons.h" +#include "utilities/commons.h" +#include "utilities/errors_common.h" +#include "utilities/vector.h" +#include "vulkan_commands.h" + +#define MAX_VERTEX_BUFFER_SIZE 33554432 /* 32 MB*/ +#define CMD_BUFFER_SIZE (MAX_INSTANCE_NUMBER * sizeof(VkDrawIndexedIndirectCommand)) + +/** + * @brief Copy one buffer's data to another + * + * @param src Source buffer + * @param dst Destination buffer + * @param size Size of buffer to copy + */ +static rse_err_t copy_buffer(struct graphics_context_t* context, + VkBuffer src, + VkBuffer dst, + VkDeviceSize size, + VkDeviceSize dest_offset) +{ + /* Vulkan buffers can only be copied using command buffers */ + rse_err_t status = RSE_ERROR_NO_ERROR; + VkBufferCopy copy_region = {0}; + VkCommandBuffer command_buffer = VK_NULL_HANDLE; + + STATUS_CHECK(begin_single_time_command(context, &command_buffer)); + + copy_region.srcOffset = 0U; + copy_region.dstOffset = dest_offset; + copy_region.size = size; + + vkCmdCopyBuffer(command_buffer, src, dst, 1, ©_region); + + STATUS_CHECK(end_single_time_comands(context, command_buffer)); + + return status; +} + +/** + * @brief Create a buffer holding all vertex data + * + * @param context Graphics context handle + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_BUFFER_CREATION_FAILED + * VULKAN_ERROR_VERTEX_BUFFER_MAPPING_FAILED + */ +static rse_err_t create_vertex_buffer(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + VkDeviceSize buffer_size; + + buffer_size = MAX_VERTEX_BUFFER_SIZE; + + /* Create Vertex Buffer*/ + STATUS_CHECK(create_buffer(context, + buffer_size, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0, /* Will not be mapped with vmaMapMemory */ + &context->render_targets[RENDER_TARGET_3D].vertex_buffer)); + + context->render_targets[RENDER_TARGET_3D].vertex_buffer.allocated_size = 0; + + return status; +} + +/** + * @brief Create an index buffer, connected with vertex buffer + * + * @param context Graphics context handle + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_BUFFER_CREATION_FAILED + */ +static rse_err_t create_index_buffer(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + VkDeviceSize buffer_size; + + buffer_size = MAX_VERTEX_BUFFER_SIZE; + + /* Create Index Buffer*/ + STATUS_CHECK(create_buffer(context, + buffer_size, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0, /* Will not be mapped with vmaMapMemory */ + &context->render_targets[RENDER_TARGET_3D].index_buffer)); + + context->render_targets[RENDER_TARGET_3D].index_buffer.allocated_size = 0; + return RSE_ERROR_NO_ERROR; +} + +/** + * @brief Create instance buffer alongside draw indirect command buffer + * + * @param[in/out] context Graphics context handle + * @return RSE_ERROR_NO_ERROR on success. + */ +static rse_err_t create_instance_buffers(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + VkDeviceSize buffer_size; + + buffer_size = sizeof(struct instance_data_t) * MAX_INSTANCE_NUMBER; + + STATUS_CHECK( + create_uniform_buffer(context, &context->render_targets[RENDER_TARGET_3D].instance_buffer, buffer_size)); + /* TODO: Move creation to somewhere else? */ + STATUS_CHECK(create_buffer(context, + CMD_BUFFER_SIZE, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, + 0, + &context->render_targets[RENDER_TARGET_3D].draw_indirect_command_buffer)); + + context->render_targets[RENDER_TARGET_3D].instance_buffer.allocated_size = 0; + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t create_uniform_buffer(struct graphics_context_t* context, + struct vulkan_buffer_t* buffer, + VkDeviceSize buffer_size) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + + STATUS_CHECK( + create_buffer(context, + buffer_size, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + buffer)); + + context->uniform_buffers.vulkan_buffers[context->uniform_buffers.vulkan_buffers_count++] = *buffer; + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t create_buffer(struct graphics_context_t* context, + const VkDeviceSize size, + VkBufferUsageFlags buffer_usage, + VmaMemoryUsage memory_usage, + const VmaAllocationCreateFlags allocation_flags, + struct vulkan_buffer_t* buffer) +{ + VkBufferCreateInfo vertex_buffer_info = {0}; + VmaAllocationCreateInfo create_info = {0}; + + vertex_buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + vertex_buffer_info.pNext = NULL; + vertex_buffer_info.flags = 0U; + vertex_buffer_info.size = size; + vertex_buffer_info.usage = buffer_usage; + vertex_buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + vertex_buffer_info.queueFamilyIndexCount = 0U; + vertex_buffer_info.pQueueFamilyIndices = NULL; + + create_info.flags = allocation_flags; + create_info.usage = memory_usage; + create_info.memoryTypeBits = 0U; + create_info.requiredFlags = 0U; + create_info.preferredFlags = 0U; + create_info.pool = VK_NULL_HANDLE; + create_info.pUserData = VK_NULL_HANDLE; + create_info.priority = 0.0f; + + if (VK_SUCCESS != vmaCreateBuffer(context->vulkan_handles.allocator, + &vertex_buffer_info, + &create_info, + &buffer->buffer, + &buffer->allocation, + &buffer->allocation_info)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create a vertex buffer"); + return RSE_ERROR_INTERNAL_ERROR; + } + + buffer->allocated_size = size; + + return RSE_ERROR_NO_ERROR; +} + +void destroy_buffer(struct graphics_context_t* context, struct vulkan_buffer_t* buffer) +{ + vmaDestroyBuffer(context->vulkan_handles.allocator, buffer->buffer, buffer->allocation); +} + +rse_err_t add_vertices(struct graphics_context_t* context, + uint16_t mesh_id, + size_t vertices_count, + const struct vertex_t* vertices, + size_t indices_count, + const uint16_t* indices) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + struct vulkan_buffer_t staging_buffer; + size_t vertices_size = sizeof(vertices[0]) * vertices_count; + size_t indices_size = sizeof(indices[0]) * indices_count; + size_t staging_buffer_size = vertices_size > indices_size ? vertices_size : indices_size; + + struct vulkan_buffer_t* vertex_buffer = &context->render_targets[RENDER_TARGET_3D].vertex_buffer; + struct vulkan_buffer_t* index_buffer = &context->render_targets[RENDER_TARGET_3D].index_buffer; + + /* Creating staging buffer*/ + STATUS_CHECK( + create_buffer(context, + staging_buffer_size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + &staging_buffer)); + + /* Fill staging buffer with vertex data */ + memcpy(staging_buffer.allocation_info.pMappedData, vertices, vertices_size); + copy_buffer(context, staging_buffer.buffer, vertex_buffer->buffer, vertices_size, vertex_buffer->allocated_size); + vertex_buffer->allocated_size += vertices_size; + + /* Fill staging buffer with index data */ + memcpy(staging_buffer.allocation_info.pMappedData, indices, indices_size); + copy_buffer(context, staging_buffer.buffer, index_buffer->buffer, indices_size, index_buffer->allocated_size); + index_buffer->allocated_size += indices_size; + + destroy_buffer(context, &staging_buffer); + + context->mesh_data.mesh_bucket[mesh_id].is_dirty = true; + context->mesh_data.mesh_bucket[mesh_id].vertex_count = vertices_count; + context->mesh_data.mesh_bucket[mesh_id].entities_count = 0; + context->mesh_data.mesh_bucket[mesh_id].instances_count = 0; + context->mesh_data.mesh_bucket[mesh_id].index_count = indices_count; + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t mesh_add_instance(struct graphics_context_t* context, uint32_t mesh_id, struct instance_data_t* instance_data) +{ + /* For now only store information about instances. Upload them to instance buffer later */ + RSE_VECTOR_PUSH_BACK(context->mesh_data.mesh_bucket[mesh_id].instances_data, *instance_data); + context->mesh_data.mesh_bucket[mesh_id].instances_count++; + context->mesh_data.mesh_bucket[mesh_id].is_dirty = true; + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t buffers_update(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + bool dirty_found = false; + char* instance_buffer_mapped = NULL; + size_t iter = 0U; + size_t buffer_offset = 0U; + VkDrawIndexedIndirectCommand* draw_indirect_commands_buffer = NULL; + VkDrawIndexedIndirectCommand* draw_indirect_command = NULL; + VkDeviceSize draw_indirect_commands_buffer_size = + sizeof(VkDrawIndexedIndirectCommand) * context->mesh_data.mesh_count; + struct vulkan_buffer_t staging_buffer; + struct mesh_t* mesh = NULL; + + rse_malloc(draw_indirect_commands_buffer, draw_indirect_commands_buffer_size); + for (iter = 0U; iter < context->mesh_data.mesh_count; ++iter) { + mesh = &context->mesh_data.mesh_bucket[iter]; + draw_indirect_command = &draw_indirect_commands_buffer[iter]; + /* TODO: I wonder if we save some time here actually by checking for dirty...*/ + if ((dirty_found == true) || (context->mesh_data.mesh_bucket[iter].is_dirty == true)) { + memcpy((char*)context->render_targets[RENDER_TARGET_3D].instance_buffer.allocation_info.pMappedData + + buffer_offset, + mesh->instances_data.data, + mesh->instances_count * sizeof(struct instance_data_t)); + dirty_found = true; + } + mesh->is_dirty = false; + buffer_offset += mesh->instances_count * sizeof(struct instance_data_t); + + if (iter == 0) { + draw_indirect_command->firstIndex = 0; + draw_indirect_command->firstInstance = 0; + draw_indirect_command->vertexOffset = 0; + } else { + draw_indirect_command->firstIndex = context->mesh_data.mesh_bucket[iter - 1].index_count; + draw_indirect_command->firstInstance = draw_indirect_commands_buffer[iter - 1].firstInstance + + draw_indirect_commands_buffer[iter - 1].instanceCount; + draw_indirect_command->vertexOffset = (context->mesh_data.mesh_bucket[iter - 1].vertex_count); + } + draw_indirect_command->indexCount = mesh->index_count; + draw_indirect_command->instanceCount = mesh->instances_count; + } + + /* Copy Draw indirect commands into buffer */ + if (dirty_found == true) { + STATUS_CHECK( + create_buffer(context, + draw_indirect_commands_buffer_size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + &staging_buffer)); + + /* Copy command buffer information about instances */ + memcpy(staging_buffer.allocation_info.pMappedData, + draw_indirect_commands_buffer, + draw_indirect_commands_buffer_size); + copy_buffer(context, + staging_buffer.buffer, + context->render_targets[RENDER_TARGET_3D].draw_indirect_command_buffer.buffer, + draw_indirect_commands_buffer_size, + 0); + + destroy_buffer(context, &staging_buffer); + } + + /* Update instance data uniform buffer */ + context->render_targets[RENDER_TARGET_3D].instance_buffer.allocated_size = buffer_offset; + buffer_offset = 0U; + instance_buffer_mapped = + (char*)context->render_targets[RENDER_TARGET_3D].instance_buffer.allocation_info.pMappedData; + for (iter = 0U; iter < context->mesh_data.mesh_count; ++iter) { + mesh = &context->mesh_data.mesh_bucket[iter]; + rse_memcpy(instance_buffer_mapped + buffer_offset, + mesh->instances_data.data, + mesh->instances_count * sizeof(struct instance_data_t)); + buffer_offset += mesh->instances_count * sizeof(struct instance_data_t); + } + + rse_free(draw_indirect_commands_buffer); + + return status; +} + +/** + * @brief Records commands for provided swapchain framebuffer + * + * @param command_buffer Command buffer, that will hold commands + * @param image_index Image index + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_RECORD_COMMAND_BUFFER_FAILED + */ +rse_err_t record_command_buffer(struct graphics_context_t* context, uint32_t image_index) +{ + size_t iter = 0U; + VkCommandBuffer command_buffer = context->vulkan_handles.command_buffers[context->current_frame]; + VkCommandBufferBeginInfo begin_info = {0}; + VkRenderPassBeginInfo render_pass_info = {0}; + VkViewport viewport = {0}; + VkRect2D scissor = {0}; + VkClearValue clear_values[2] = {0}; /* For color and depth stencil */ + + clear_values[0].color.float32[0] = 0.0f; + clear_values[0].color.float32[1] = 0.0f; + clear_values[0].color.float32[2] = 0.0f; + + clear_values[1].depthStencil.depth = 1.0f; + clear_values[1].depthStencil.stencil = 0.0f; + + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags = 0; + begin_info.pInheritanceInfo = NULL; + + if (vkBeginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to start recording command buffer"); + return RSE_ERROR_INTERNAL_ERROR; + } + + render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + render_pass_info.renderPass = context->vulkan_handles.render_pass; + render_pass_info.framebuffer = context->swapchain_data.swapchain_framebuffers[image_index]; + render_pass_info.renderArea.offset.x = 0; + render_pass_info.renderArea.offset.y = 0; + render_pass_info.renderArea.extent = context->swapchain_data.swapchain_extent; + render_pass_info.clearValueCount = 2; + render_pass_info.pClearValues = clear_values; + + vkCmdBeginRenderPass(command_buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = (float)(context->swapchain_data.swapchain_extent.width); + viewport.height = (float)(context->swapchain_data.swapchain_extent.height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(command_buffer, 0, 1, &viewport); + + scissor.offset.x = 0; + scissor.offset.y = 0; + scissor.extent = context->swapchain_data.swapchain_extent; + vkCmdSetScissor(command_buffer, 0, 1, &scissor); + + for (iter = 0U; iter < context->pipelines_data.pipelines_count; ++iter) { + context->renderer_data.renderers[iter].render_function(context, iter); + } + + vkCmdEndRenderPass(command_buffer); + + if (VK_SUCCESS != vkEndCommandBuffer(command_buffer)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to record command buffer. Validate your commands."); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t create_buffers(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + + STATUS_CHECK(create_vertex_buffer(context)); + STATUS_CHECK(create_index_buffer(context)); + STATUS_CHECK(create_instance_buffers(context)); + + return status; +} + +void reset_command_buffer(struct graphics_context_t* context) +{ + vkResetCommandBuffer(context->vulkan_handles.command_buffers[context->current_frame], + /*VkCommandBufferResetFlagBits*/ 0); +} + +void destroy_buffers(struct graphics_context_t* context) +{ + size_t iter = 0U; + + destroy_buffer(context, &context->render_targets[RENDER_TARGET_3D].vertex_buffer); + destroy_buffer(context, &context->render_targets[RENDER_TARGET_3D].index_buffer); + destroy_buffer(context, &context->render_targets[RENDER_TARGET_3D].draw_indirect_command_buffer); + + for (iter = 0U; iter < context->uniform_buffers.vulkan_buffers_count; ++iter) + { + vmaDestroyBuffer(context->vulkan_handles.allocator, context->uniform_buffers.vulkan_buffers[iter].buffer, context->uniform_buffers.vulkan_buffers[iter].allocation); + } + + context->uniform_buffers.vulkan_buffers_count = 0; + for (iter = 0; iter < context->mesh_data.mesh_count; ++iter) { + rse_free(context->mesh_data.mesh_bucket[iter].instances_data.data); + } +} diff --git a/graphics/src/vulkan_buffers.h b/graphics/src/vulkan_buffers.h new file mode 100644 index 00000000..1342e702 --- /dev/null +++ b/graphics/src/vulkan_buffers.h @@ -0,0 +1,104 @@ +#ifndef VULKAN_BUFFERS_H +#define VULKAN_BUFFERS_H + +#include +#include + +#include "graphics_context.h" +#include "utilities/commons.h" +#include "vulkan_commons.h" + +/** + * @brief Create buffers needed by vulkan pipeline + * + * @param context Graphics context handle + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t create_buffers(struct graphics_context_t* context); + +/** + * @brief Create a Uniform Buffers + * + * @param context Graphics context handle + * @param buffer Buffer handle to hold new buffer + * @return rse_err_t RSE_ERROR_NO_ERROR on success. + */ +rse_err_t create_uniform_buffer(struct graphics_context_t* context, struct vulkan_buffer_t* buffer, + VkDeviceSize buffer_size); + +/** + * @brief Add vertices and indices to vulkan buffers + * + * @param vertices_count Number of provided vertices + * @param vertices Vertices to add + * @param indices_count Number of provided indices + * @param indices Indices to add + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t add_vertices(struct graphics_context_t* context, uint16_t mesh_id, size_t vertices_count, + const struct vertex_t* vertices, size_t indices_count, const uint16_t* indices); + +/** + * @brief Add instance data to vulkan buffers + * + * @param mesh_id Mesh ID + * @param instance_data Instance data + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t mesh_add_instance(struct graphics_context_t* context, uint32_t mesh_id, + struct instance_data_t* instance_data); + +/** + * @brief Record commands for given image index + * + * @param vulkan_state + * @param imageIndex + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t record_command_buffer(struct graphics_context_t* context, uint32_t imageIndex); + +/** + * @brief Reset command buffer for current frame. + * + * @param vulkan_state + */ +void reset_command_buffer(struct graphics_context_t* context); + +/** + * @brief Destroy ALL previously allocated buffers + * + * @param vulkan_state + */ +void destroy_buffers(struct graphics_context_t* context); + +/** + * @brief Helper function. Creates a buffer object + * + * @param size Size of the target buffer + * @param buffer_usage Buffer usage flags + * @param memory_usage memory usage flags (mostly VMA_MEMORY_USAGE_AUTO) + * @param allocation_flags VMA allocation flags + * @param buffer Buffer, that will be allocated + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_COMMAND_BUFFER_ALLOCATION_FAILED + */ +rse_err_t create_buffer(struct graphics_context_t* context, const VkDeviceSize size, VkBufferUsageFlags buffer_usage, + VmaMemoryUsage memory_usage, const VmaAllocationCreateFlags allocation_flags, + struct vulkan_buffer_t* buffer); + +/** + * @brief Update vulkan buffers. Instance data and all that. This must be called before running vulkan application + * + * @param context Graphics context + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t buffers_update(struct graphics_context_t* context); + +/** + * @brief Destroy provided buffer + * + * @param buffer buffer to destroy + */ +void destroy_buffer(struct graphics_context_t* context, struct vulkan_buffer_t* buffer); + +#endif /* VULKAN_BUFFERS_H */ diff --git a/graphics/src/vulkan_commands.c b/graphics/src/vulkan_commands.c new file mode 100644 index 00000000..ef08e3d9 --- /dev/null +++ b/graphics/src/vulkan_commands.c @@ -0,0 +1,135 @@ +#include "vulkan_commands.h" +#include +#include + +#include "src/graphics_context.h" +#include "utilities/commons.h" +#include "vulkan_commons.h" +#include "utilities/errors_common.h" + +/** + * @brief Create Command Pool + * + * @param context Graphics context handle + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_COMMAND_POOL_CREATION_FAILED + */ +static rse_err_t create_command_pool(struct graphics_context_t* context) +{ + VkCommandPoolCreateInfo create_info; + + create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + create_info.pNext = NULL; + create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + create_info.queueFamilyIndex = context->queue_family_indices[QUEUE_FAMILY_INDEX_GRAPHICS]; + + if (VK_SUCCESS != vkCreateCommandPool(context->vulkan_handles.device, &create_info, NULL, &context->vulkan_handles.command_pool)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create command pool"); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +/** + * @brief Allocate Command Buffers + * + * @param context Graphics context handle + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_COMMAND_BUFFER_ALLOCATION_FAILED + */ +static rse_err_t allocate_command_buffers(struct graphics_context_t* context) +{ + VkCommandBufferAllocateInfo allocate_info; + + /* Allocate memory for command buffers */ + allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocate_info.pNext = NULL; + allocate_info.commandPool = context->vulkan_handles.command_pool; + allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocate_info.commandBufferCount = SWAP_BUFFER_COUNT; + + if (VK_SUCCESS != vkAllocateCommandBuffers(context->vulkan_handles.device, + &allocate_info, + context->vulkan_handles.command_buffers)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to allocate command buffers\n"); + return RSE_ERROR_INTERNAL_ERROR; + } + return RSE_ERROR_NO_ERROR; +} + +rse_err_t begin_single_time_command(struct graphics_context_t* context, VkCommandBuffer* command_buffer) +{ + VkCommandBufferBeginInfo begin_info = {0}; + VkCommandBufferAllocateInfo alloc_info = {0}; + + alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + alloc_info.pNext = NULL; + alloc_info.commandPool = context->vulkan_handles.command_pool; + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + alloc_info.commandBufferCount = 1; + + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.pNext = NULL; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + begin_info.pInheritanceInfo = NULL; + + if (vkAllocateCommandBuffers(context->vulkan_handles.device, &alloc_info, command_buffer) != VK_SUCCESS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to allocate new command buffer"); + return RSE_ERROR_INTERNAL_ERROR; + } + if (vkBeginCommandBuffer(*command_buffer, &begin_info) != VK_SUCCESS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to begin command buffer"); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t end_single_time_comands(struct graphics_context_t* context, VkCommandBuffer command_buffer) +{ + VkSubmitInfo submit_info = {0}; + + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.pNext = NULL; + submit_info.waitSemaphoreCount = 0; + submit_info.pWaitSemaphores = NULL; + submit_info.pWaitDstStageMask = NULL; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &command_buffer; + submit_info.signalSemaphoreCount = 0; + submit_info.pSignalSemaphores = NULL; + + if (vkEndCommandBuffer(command_buffer) != VK_SUCCESS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to end command buffer"); + return RSE_ERROR_INTERNAL_ERROR; + } + + if (vkQueueSubmit(context->vulkan_handles.graphics_queue, 1, &submit_info, VK_NULL_HANDLE) != VK_SUCCESS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed submit queue"); + return RSE_ERROR_INTERNAL_ERROR; + } + + if (vkQueueWaitIdle(context->vulkan_handles.graphics_queue) != VK_SUCCESS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to wait for graphics queue"); + return RSE_ERROR_INTERNAL_ERROR; + } + + vkFreeCommandBuffers(context->vulkan_handles.device, context->vulkan_handles.command_pool, 1, &command_buffer); + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t init_commands(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + STATUS_CHECK(create_command_pool(context)); + STATUS_CHECK(allocate_command_buffers(context)); + + return status; +} + +void destroy_commands(struct graphics_context_t* context) +{ + vkDestroyCommandPool(context->vulkan_handles.device, context->vulkan_handles.command_pool, NULL); +} diff --git a/graphics/src/vulkan_commands.h b/graphics/src/vulkan_commands.h new file mode 100644 index 00000000..53bb52e8 --- /dev/null +++ b/graphics/src/vulkan_commands.h @@ -0,0 +1,54 @@ +/** + * @file vulkan_commands.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief Vukan Commands allocation and execution + * @version 0.1 + * @date 2023-09-29 + * + * @copyright Copyright (c) 2023 + * + */ + +#ifndef VULKAN_COMMANDS_H +#define VULKAN_COMMANDS_H + +#include +#include + +#include "utilities/commons.h" +#include "graphics_context.h" + +/** + * @brief Initialize vulkan commands pools and sets + * + * @param context Graphics context handle + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t init_commands(struct graphics_context_t* context); + +/** + * @brief Destroy vulkan commands sets and pools + * + * @param context Graphics context handle + * + */ +void destroy_commands(struct graphics_context_t* context); + +/** + * @brief Helper function, creates command buffer for recording a command. Useful for copying buffer na images. + * + * @param context Graphics context handle + * @return RSE_ERROR_NO_ERROR on success + */ +rse_err_t begin_single_time_command(struct graphics_context_t* context, VkCommandBuffer* command_buffer); + +/** + * @brief Close command buffer and execute recorded command. + * + * @param context Graphics context handle + * @param command_buffer Command buffer for execution + * @return RSE_ERROR_NO_ERROR on success + */ +rse_err_t end_single_time_comands(struct graphics_context_t* context, VkCommandBuffer command_buffer); + +#endif /* VULKAN_COMMANDS_H */ diff --git a/graphics/src/vulkan_commons.c b/graphics/src/vulkan_commons.c new file mode 100644 index 00000000..69cc7e8f --- /dev/null +++ b/graphics/src/vulkan_commons.c @@ -0,0 +1,24 @@ +#include "vulkan_commons.h" + +#include + +time_t g_last_frame_time; +time_t g_this_frame_time; +float g_float_delta_time; + +void init_time(void) +{ + time(&g_last_frame_time); +} + +void update_time(void) +{ + g_last_frame_time = g_this_frame_time; + time(&g_this_frame_time); + g_float_delta_time = (float)difftime(g_this_frame_time, g_last_frame_time); +} + +float get_time_diff(void) +{ + return g_float_delta_time; +} diff --git a/graphics/src/vulkan_commons.h b/graphics/src/vulkan_commons.h new file mode 100644 index 00000000..9e2969b8 --- /dev/null +++ b/graphics/src/vulkan_commons.h @@ -0,0 +1,65 @@ +/** + * @file vulkan_global.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief Declaration of a structure holding Vulkan variables needed by different parts of the code + * @version 0.1 + * @date 2023-03-28 + * + * @copyright Copyright (c) 2023 + * + */ + +#ifndef VULKAN_GLOBAL_H +#define VULKAN_GLOBAL_H + +#include +#include +#include +#include + +#include + +#define SWAP_BUFFER_COUNT 3U + +/** + * @brief Represents single vertex on the screen + * + */ +struct vertex_t +{ + vec3 pos; + vec3 color; + vec2 tex_coords; +}; + +/** + * @brief Wrapper for Vulkan buffer. Holds allocation data; + * + */ +struct vulkan_buffer_t +{ + size_t allocated_size; + VkBuffer buffer; + VmaAllocation allocation; + VmaAllocationInfo allocation_info; +}; + +struct uniform_buffer_object_t +{ + alignas(16) mat4 model; + alignas(16) mat4 view; + alignas(16) mat4 proj; +}; + +struct instance_data_t { + alignas(16) vec3 pos; + alignas(16) vec3 rot; + alignas(4) float scale; + alignas(4) uint32_t texture_id; +}; + +void init_time(void); +void update_time(void); +float get_time_diff(void); + +#endif /* VULKAN_GLOBAL_H */ diff --git a/graphics/src/vulkan_image.c b/graphics/src/vulkan_image.c new file mode 100644 index 00000000..1055c18b --- /dev/null +++ b/graphics/src/vulkan_image.c @@ -0,0 +1,585 @@ +#include "vulkan_image.h" + +#include +#include + +#include "src/graphics_context.h" +#include "utilities/commons.h" +#include "utilities/errors_common.h" +#include "utilities/file_utils.h" +#include "vulkan/vulkan_core.h" +#include "vulkan_buffers.h" +#include "vulkan_commands.h" + +#include +#include +#include + +#define IMAGE_TAKEN 1U +#define IMAGE_FREE 0U + +rse_err_t sampler_create(struct graphics_context_t* context, VkSampler* sampler) +{ + VkSamplerCreateInfo create_info = {0}; + + create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + create_info.pNext = NULL; + create_info.flags = 0U; + create_info.magFilter = VK_FILTER_LINEAR; + create_info.minFilter = VK_FILTER_LINEAR; + create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + create_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + create_info.mipLodBias = 0.0f; + create_info.anisotropyEnable = VK_FALSE; // TODO: Enable this + create_info.maxAnisotropy = 1.0f; + create_info.compareEnable = VK_FALSE; + create_info.compareOp = VK_COMPARE_OP_ALWAYS; + create_info.minLod = 0.0f; + create_info.maxLod = 0.0f; + create_info.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + create_info.unnormalizedCoordinates = VK_FALSE; + + if (VK_SUCCESS != vkCreateSampler(context->vulkan_handles.device, &create_info, NULL, sampler)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create image view"); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +static rse_err_t find_supported_format(struct graphics_context_t* context, + const VkFormat* candidates, + size_t candidates_count, + VkImageTiling tiling, + VkFormatFeatureFlags features, + VkFormat* found_format) +{ + size_t i = 0U; + + for (i = 0U; i < candidates_count; ++i) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(context->vulkan_handles.physical_device, candidates[i], &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { + *found_format = candidates[i]; + return RSE_ERROR_NO_ERROR; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { + *found_format = candidates[i]; + return RSE_ERROR_NO_ERROR; + } + } + + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to find correct format"); + return RSE_ERROR_INTERNAL_ERROR; +} + +static rse_err_t create_image(struct graphics_context_t* context, + struct vulkan_image_t* image, + uint32_t width, + uint32_t height, + VkFormat format, + VkSampleCountFlagBits maa_samples, + VkImageUsageFlags usage, + VmaAllocationCreateFlags allocationFlags) +{ + VkResult result; + VkExtent3D image_extent = {0}; + VkImageCreateInfo image_info = {0}; + VmaAllocationCreateInfo alloc_create_info = {0}; + + image_extent.width = width; + image_extent.height = height; + image_extent.depth = 1U; + + image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_info.pNext = NULL; + image_info.flags = 0U; + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.format = format; + image_info.extent = image_extent; + image_info.mipLevels = 1U; + image_info.arrayLayers = 1U; + image_info.samples = maa_samples; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.usage = usage; + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; // TODO: Change if used by more than one + // queue family + image_info.queueFamilyIndexCount = 0U; + image_info.pQueueFamilyIndices = NULL; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + alloc_create_info.flags = allocationFlags; + alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO; + alloc_create_info.requiredFlags = 0U; + alloc_create_info.preferredFlags = 0U; + alloc_create_info.memoryTypeBits = 0U; + alloc_create_info.pool = NULL; + alloc_create_info.pUserData = NULL; + alloc_create_info.priority = 1.0f; + + result = vmaCreateImage(context->vulkan_handles.allocator, + &image_info, + &alloc_create_info, + &image->image, + &image->allocation, + &image->allocation_info); + if (VK_SUCCESS != result) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create image"); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +static rse_err_t create_image_view(struct graphics_context_t* context, + struct vulkan_image_t* image, + VkFormat format, + VkImageAspectFlags aspectFlags) +{ + VkImageViewCreateInfo image_view_create_info = {0}; + + image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + image_view_create_info.pNext = NULL; + image_view_create_info.flags = 0U; + image_view_create_info.image = image->image; + image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; /* 2D Texture */ + image_view_create_info.format = format; + image_view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + image_view_create_info.subresourceRange.aspectMask = aspectFlags; + image_view_create_info.subresourceRange.baseMipLevel = 0U; + image_view_create_info.subresourceRange.levelCount = 1U; + image_view_create_info.subresourceRange.baseArrayLayer = 0U; + image_view_create_info.subresourceRange.layerCount = 1U; + + if (VK_SUCCESS != + vkCreateImageView(context->vulkan_handles.device, &image_view_create_info, NULL, &image->image_view)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create swapchain image view"); + return RSE_ERROR_INTERNAL_ERROR; + } + return RSE_ERROR_NO_ERROR; +} + +rse_err_t create_color_resource(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + VkFormat color_format = IMAGE_FORMAT; + VkSurfaceCapabilitiesKHR physical_device_surface_capabilities; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(context->vulkan_handles.physical_device, + context->vulkan_handles.surface, + &physical_device_surface_capabilities); + + STATUS_CHECK(create_image(context, + &context->color_image, + physical_device_surface_capabilities.currentExtent.width, + physical_device_surface_capabilities.currentExtent.height, + color_format, + VK_SAMPLE_COUNT_8_BIT, + VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + 0U)); + STATUS_CHECK(create_image_view(context, &context->color_image, color_format, VK_IMAGE_ASPECT_COLOR_BIT)); + + return status; +} + +rse_err_t create_depth_resources(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + VkFormat depth_format = {0}; + VkSurfaceCapabilitiesKHR physical_device_surface_capabilities; + + STATUS_CHECK(find_depth_format(context, &depth_format)); + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(context->vulkan_handles.physical_device, + context->vulkan_handles.surface, + &physical_device_surface_capabilities); + + STATUS_CHECK(create_image(context, + &context->depth_image, + physical_device_surface_capabilities.currentExtent.width, + physical_device_surface_capabilities.currentExtent.height, + depth_format, + VK_SAMPLE_COUNT_8_BIT, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + 0U)); + STATUS_CHECK(create_image_view(context, &context->depth_image, depth_format, VK_IMAGE_ASPECT_DEPTH_BIT)); + + return status; +} + +/** + * @brief Translate vulkan format to pixel width + * + * @param[int] format Format to translate + * @param[out] format_size Format size in bytes to return + * @return RSE_ERROR_NO_ERROR on success, error code otherwise + */ +static rse_err_t format_to_pixel_size(VkFormat format, VkDeviceSize* format_size) +{ + switch (format) { + case VK_FORMAT_R8_SRGB: + case VK_FORMAT_R8_SNORM: + case VK_FORMAT_R8_UNORM: + case VK_FORMAT_R8_USCALED: + case VK_FORMAT_R8_SSCALED: + case VK_FORMAT_R8_UINT: + *format_size = 1; + break; + case VK_FORMAT_R8G8B8_UINT: + case VK_FORMAT_R8G8B8_UNORM: + *format_size = 3; + break; + case VK_FORMAT_R8G8B8A8_SRGB: + case VK_FORMAT_R8G8B8A8_UNORM: + *format_size = 4; + break; + + default: + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed translate vulkan format to pixel width"); + break; + } + + return RSE_ERROR_NO_ERROR; +} + +static rse_err_t transition_image_layout(struct graphics_context_t* context, + struct vulkan_image_t* image, + VkImageLayout old_layout, + VkImageLayout new_layout) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + VkImageSubresourceRange range = {0}; + VkImageMemoryBarrier image_barrier = {0}; + VkCommandBuffer command_buffer = {0}; + VkPipelineStageFlags source_stage = {0}; + VkPipelineStageFlags destination_stage = {0}; + + /* Put image into correct layout to copy pixels from buffer to image memory + */ + range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + range.baseMipLevel = 0; + range.levelCount = 1; + range.baseArrayLayer = 0; + range.layerCount = 1; + + image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + image_barrier.pNext = NULL; + image_barrier.oldLayout = old_layout; + image_barrier.newLayout = new_layout; + image_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + image_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + image_barrier.image = image->image; + image_barrier.subresourceRange = range; + + if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + image_barrier.srcAccessMask = 0; + image_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destination_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && + new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + image_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + image_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destination_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else { + return RSE_ERROR_INTERNAL_ERROR; + } + + STATUS_CHECK(begin_single_time_command(context, &command_buffer)); + vkCmdPipelineBarrier(command_buffer, source_stage, destination_stage, 0, 0, NULL, 0, NULL, 1, &image_barrier); + STATUS_CHECK(end_single_time_comands(context, command_buffer)); + + return RSE_ERROR_NO_ERROR; +} + +static rse_err_t create_textured_image(struct graphics_context_t* context, + uint32_t width, + uint32_t height, + VkFormat format, + const unsigned char* pixels, + uint16_t* texture_id) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + struct vulkan_buffer_t staging_buffer = {0}; + VkDeviceSize staging_buffer_size = 0U; + VkCommandBuffer command_buffer = {0}; + VkBufferImageCopy copy_region = {0}; + VkExtent3D image_extent = {0}; + struct vulkan_image_t* free_texture_image = NULL; + + image_extent.width = width; + image_extent.height = height; + image_extent.depth = 1U; + + *texture_id = 0U; + + while (free_texture_image == NULL && *texture_id < RSE_MAX_IMAGE_COUNT) { + if (context->texture_images[*texture_id].id_taken == IMAGE_FREE) { + free_texture_image = &context->texture_images[*texture_id]; + } else { + *texture_id += 1; + } + } + + /* Check if we found free image handle */ + if (free_texture_image == NULL) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to find free texture image handle"); + *texture_id = -1; + return RSE_ERROR_INTERNAL_ERROR; + } + + STATUS_CHECK(format_to_pixel_size(format, &staging_buffer_size)); + + staging_buffer_size = staging_buffer_size * width * height; + + STATUS_CHECK( + create_image(context, + free_texture_image, + width, + height, + format, + VK_SAMPLE_COUNT_1_BIT, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT)); + + transition_image_layout(context, + free_texture_image, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + /* Copy pixel data to GPU memory */ + STATUS_CHECK( + create_buffer(context, + staging_buffer_size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + &staging_buffer)); + + memcpy(staging_buffer.allocation_info.pMappedData, pixels, staging_buffer_size); + + copy_region.bufferOffset = 0U; + copy_region.bufferRowLength = 0U; + copy_region.bufferImageHeight = 0U; + copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy_region.imageSubresource.mipLevel = 0U; + copy_region.imageSubresource.baseArrayLayer = 0U; + copy_region.imageSubresource.layerCount = 1U; + copy_region.imageOffset.x = 0; + copy_region.imageOffset.y = 0; + copy_region.imageOffset.z = 0; + copy_region.imageExtent = image_extent; + + STATUS_CHECK(begin_single_time_command(context, &command_buffer)); + vkCmdCopyBufferToImage(command_buffer, + staging_buffer.buffer, + free_texture_image->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ©_region); + end_single_time_comands(context, command_buffer); + destroy_buffer(context, &staging_buffer); + + transition_image_layout(context, + free_texture_image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + STATUS_CHECK(create_image_view(context, free_texture_image, format, VK_IMAGE_ASPECT_COLOR_BIT)); + + free_texture_image->id_taken = IMAGE_TAKEN; + return RSE_ERROR_NO_ERROR; +} + +rse_err_t init_vulkan_images(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + + STATUS_CHECK(create_depth_resources(context)); + STATUS_CHECK(create_color_resource(context)); + + return status; +} + +rse_err_t texture_update_image(struct graphics_context_t* context, + const unsigned char* pixels, + int width, + int height, + uint16_t texture_id, + VkFormat format) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + VkDeviceSize staging_buffer_size = 0U; + VkBufferImageCopy copy_region = {0}; + struct vulkan_buffer_t staging_buffer = {0}; + VkExtent3D image_extent = {0}; + VkCommandBuffer command_buffer = {0}; + + image_extent.width = width; + image_extent.height = height; + image_extent.depth = 1U; + + STATUS_CHECK(format_to_pixel_size(format, &staging_buffer_size)); + + staging_buffer_size = staging_buffer_size * width * height; + + transition_image_layout(context, + &context->texture_images[texture_id], + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + /* Copy pixel data to GPU memory */ + STATUS_CHECK( + create_buffer(context, + staging_buffer_size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + &staging_buffer)); + + memcpy(staging_buffer.allocation_info.pMappedData, pixels, staging_buffer_size); + + copy_region.bufferOffset = 0U; + copy_region.bufferRowLength = 0U; + copy_region.bufferImageHeight = 0U; + copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy_region.imageSubresource.mipLevel = 0U; + copy_region.imageSubresource.baseArrayLayer = 0U; + copy_region.imageSubresource.layerCount = 1U; + copy_region.imageOffset.x = 0; + copy_region.imageOffset.y = 0; + copy_region.imageOffset.z = 0; + copy_region.imageExtent = image_extent; + + STATUS_CHECK(begin_single_time_command(context, &command_buffer)); + vkCmdCopyBufferToImage(command_buffer, + staging_buffer.buffer, + context->texture_images[texture_id].image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ©_region); + end_single_time_comands(context, command_buffer); + destroy_buffer(context, &staging_buffer); + + transition_image_layout(context, + &context->texture_images[texture_id], + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t load_texture_from_bitmat(struct graphics_context_t* context, + const unsigned char* pixel_buffer, + int width, + int height, + uint16_t* texture_id, + VkFormat color_format) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + + /* Create actual Vulkan Image */ + STATUS_CHECK(create_textured_image(context, width, height, color_format, pixel_buffer, texture_id)); + + return status; +} + +rse_err_t load_texture_from_file(struct graphics_context_t* context, const char* file_path, uint16_t* texture_id) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + int width = 0; + int height = 0; + size_t buffer_size = 0U; + unsigned char* pixel_buffer = NULL; + + STATUS_CHECK(file_load_pixels(file_path, NULL, &buffer_size, &width, &height)); + rse_malloc(pixel_buffer, buffer_size); + if (file_load_pixels(file_path, pixel_buffer, &buffer_size, &width, &height) != RSE_ERROR_NO_ERROR) { + status = RSE_ERROR_INTERNAL_ERROR; + + goto mem_free; + } + + if (load_texture_from_bitmat(context, pixel_buffer, width, height, texture_id, VK_FORMAT_R8G8B8A8_SRGB) != RSE_ERROR_NO_ERROR) { + status = RSE_ERROR_INTERNAL_ERROR; + + goto mem_free; + } + +mem_free: + + rse_free(pixel_buffer); + + return status; +} + +void destroy_textures(struct graphics_context_t* context) +{ + size_t i = 0U; + + for (i = 0U; i < RSE_MAX_IMAGE_COUNT; ++i) { + if (context->texture_images[i].id_taken == IMAGE_TAKEN) { + vkDestroyImageView(context->vulkan_handles.device, context->texture_images[i].image_view, NULL); + vmaDestroyImage(context->vulkan_handles.allocator, + context->texture_images[i].image, + context->texture_images[i].allocation); + } + } + + vkDestroySampler(context->vulkan_handles.device, context->vulkan_handles.sampler, NULL); + vkDestroySampler(context->vulkan_handles.device, context->debug_overlay.sampler, NULL); +} + +void destroy_depth_resource(struct graphics_context_t* context) +{ + vkDestroyImageView(context->vulkan_handles.device, context->depth_image.image_view, NULL); + vmaDestroyImage(context->vulkan_handles.allocator, context->depth_image.image, context->depth_image.allocation); +} + +void destroy_color_resource(struct graphics_context_t* context) +{ + vkDestroyImageView(context->vulkan_handles.device, context->color_image.image_view, NULL); + vmaDestroyImage(context->vulkan_handles.allocator, context->color_image.image, context->color_image.allocation); +} + +uint8_t image_exists(struct graphics_context_t* context, uint8_t image_id) +{ + return context->texture_images[image_id].id_taken == IMAGE_TAKEN; +} + +size_t get_textures_count(struct graphics_context_t* context) +{ + size_t count = 0U; + size_t i = 0U; + + for (i = 0U; i < RSE_MAX_IMAGE_COUNT; ++i) { + if (context->texture_images[i].id_taken == IMAGE_TAKEN) { + count++; + } + } + + return count; +} + +rse_err_t find_depth_format(struct graphics_context_t* context, VkFormat* found_format) +{ +#define formats_count 3 + rse_err_t status; + VkFormat formats[formats_count] = {VK_FORMAT_D32_SFLOAT}; + + STATUS_CHECK(find_supported_format(context, + formats, + formats_count, + VK_IMAGE_TILING_OPTIMAL, + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, + found_format)); + + return status; +#undef formats_count +} diff --git a/graphics/src/vulkan_image.h b/graphics/src/vulkan_image.h new file mode 100644 index 00000000..89fc1cc5 --- /dev/null +++ b/graphics/src/vulkan_image.h @@ -0,0 +1,101 @@ +/** + * @file vulkan_image.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief Library for loading and handling textures + * @version 0.1 + * @date 2023-09-27 + * + * @copyright Copyright (c) 2023 + * + */ + +#ifndef TEXTURE_H +#define TEXTURE_H + +#include + +#include "graphics_context.h" +#include "utilities/commons.h" +#include "utilities/file_utils.h" +#include "vulkan/vulkan_core.h" + +#define IMAGE_FORMAT VK_FORMAT_B8G8R8A8_SRGB; + +/** + * @brief Initialize vulkan with image views and stuff + * + * @param context Graphics context + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t init_vulkan_images(struct graphics_context_t* context); + +/** + * @brief Loads image from file, for later to be used as a texture + * + * @param context Graphics context handle + * @param file_path Path to texture file + * @param texture_id Texture ID will be set up here + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t load_texture_from_bitmat(struct graphics_context_t* context, const unsigned char* pixel_buffer, int width, + int height, uint16_t* texture_id, VkFormat color_format); + +/** + * @brief Loads image from file, for later to be used as a texture + * + * @param context Graphics context handle + * @param file_path Path to texture file + * @param texture_id Texture ID will be set up here + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t load_texture_from_file(struct graphics_context_t* context, const char* file_path, uint16_t* texture_id); + +rse_err_t texture_update_image(struct graphics_context_t* context, + const unsigned char* pixels, + int width, + int height, + uint16_t texture_id, + VkFormat format); + +/** + * @brief Destroy all loaded textures. Usefull for closing application or just clearing. + * + * @param context Graphics context handle + * + */ +void destroy_textures(struct graphics_context_t* context); + +/** + * @brief Checks if image exists for provided image ID. + * + * @param context Graphics context handle + * @param image_id Image ID + * @return uint8_t 1 on success, 0 on failure + */ +uint8_t image_exists(struct graphics_context_t* context, uint8_t image_id); + +/** + * @brief Returns number of loaded texture + * + * @param context Graphics context handle + * @return size_t Texture count + */ +size_t get_textures_count(struct graphics_context_t* context); + +rse_err_t find_depth_format(struct graphics_context_t* context, VkFormat* found_format); + +rse_err_t create_color_resource(struct graphics_context_t* context); +void destroy_color_resource(struct graphics_context_t* context); + +rse_err_t create_depth_resources(struct graphics_context_t* context); +void destroy_depth_resource(struct graphics_context_t* context); + +/** + * @brief Create a sampler object for texture sampling + * + * @param[in/out] context Graphics context to create sampler in + * @return RSE_ERROR_NO_ERROR on success, error code otherwise + */ +rse_err_t sampler_create(struct graphics_context_t* context, VkSampler* sampler); + +#endif /* TEXTURE_H */ diff --git a/graphics/src/vulkan_render_pass.c b/graphics/src/vulkan_render_pass.c new file mode 100644 index 00000000..8af5120e --- /dev/null +++ b/graphics/src/vulkan_render_pass.c @@ -0,0 +1,112 @@ +/** + * @file vulkan_render_pass.c + * @author Piotr Krygier (piotrkrygier@everyonecancode.xyz) + * @brief + * @version 0.1 + * @date 20-02-2025 + * + * @copyright Copyright (c) 2025 + * + */ + +#include "vulkan_render_pass.h" + +#include "vulkan_image.h" +#include "utilities/errors_common.h" + +rse_err_t create_render_pass(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + VkAttachmentDescription attachments[3] = {0}; + VkAttachmentDescription color_attachment = {0}; + VkAttachmentReference color_attachment_ref = {0}; + VkAttachmentDescription color_attachment_resolve = {0}; + VkAttachmentReference color_attachment_resolve_ref = {0}; + VkSubpassDescription subpass = {0}; + VkRenderPassCreateInfo renderpass_info = {0}; + VkAttachmentDescription depth_attachment = {0}; + VkAttachmentReference depth_attachment_ref = {0}; + VkSubpassDependency dependency = {0}; + + STATUS_CHECK(find_depth_format(context, &depth_attachment.format)); + + depth_attachment.samples = VK_SAMPLE_COUNT_8_BIT; + depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + depth_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depth_attachment.finalLayout = + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + depth_attachment_ref.attachment = 1; + depth_attachment_ref.layout = + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + + color_attachment.flags = 0U; + color_attachment.format = IMAGE_FORMAT; + color_attachment.samples = VK_SAMPLE_COUNT_8_BIT; + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + color_attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + color_attachment_ref.attachment = 0; + color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + color_attachment_resolve.format = IMAGE_FORMAT; + color_attachment_resolve.samples = VK_SAMPLE_COUNT_1_BIT; + color_attachment_resolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment_resolve.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color_attachment_resolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment_resolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color_attachment_resolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + color_attachment_resolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + color_attachment_resolve_ref.attachment = 2; + color_attachment_resolve_ref.layout = + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_ref; + subpass.pDepthStencilAttachment = &depth_attachment_ref; + subpass.pResolveAttachments = &color_attachment_resolve_ref; + + attachments[0] = color_attachment; + attachments[1] = depth_attachment; + attachments[2] = color_attachment_resolve; + + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.srcAccessMask = 0; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + renderpass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderpass_info.attachmentCount = 3; + renderpass_info.pAttachments = attachments; + renderpass_info.subpassCount = 1; + renderpass_info.pSubpasses = &subpass; + renderpass_info.dependencyCount = 1; + renderpass_info.pDependencies = &dependency; + + if (VK_SUCCESS != vkCreateRenderPass(context->vulkan_handles.device, &renderpass_info, + NULL, &context->vulkan_handles.render_pass)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create render pass"); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +void destroy_render_pass(struct graphics_context_t* context) +{ + vkDestroyRenderPass(context->vulkan_handles.device, context->vulkan_handles.render_pass, NULL); +} diff --git a/graphics/src/vulkan_render_pass.h b/graphics/src/vulkan_render_pass.h new file mode 100644 index 00000000..e9d9fdc8 --- /dev/null +++ b/graphics/src/vulkan_render_pass.h @@ -0,0 +1,33 @@ +/** + * @file vulkan_render_pass.h + * @author Piotr Krygier (piotrkrygier@everyonecancode.xyz) + * @brief + * @version 0.1 + * @date 20-02-2025 + * + * @copyright Copyright (c) 2025 + * + */ + +#ifndef VULKAN_RENDER_PASS_H_ +#define VULKAN_RENDER_PASS_H_ + +#include "utilities/commons.h" +#include "graphics_context.h" + +/** + * @brief Create a render pass + * + * @param context Graphics context + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t create_render_pass(struct graphics_context_t* context); + +/** + * @brief Destroy render pass + * + * @param context Graphics context + */ +void destroy_render_pass(struct graphics_context_t* context); + +#endif /* VULKAN_RENDER_PASS_H_ */ \ No newline at end of file diff --git a/graphics/src/vulkan_swapchain.c b/graphics/src/vulkan_swapchain.c new file mode 100644 index 00000000..955267a9 --- /dev/null +++ b/graphics/src/vulkan_swapchain.c @@ -0,0 +1,206 @@ +#include "vulkan_swapchain.h" + +#include +#include +#include + +#include "src/graphics_context.h" +#include "utilities/commons.h" +#include "utilities/errors_common.h" +#include "vulkan/vulkan_core.h" +#include "vulkan_commons.h" +#include "vulkan_image.h" + +/** + * @brief Create a Swapchain + * + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + * VULKAN_ERROR_SWAPCHAIN_CREATION_FAILED + */ +static rse_err_t create_swapchain(struct graphics_context_t* context) +{ + VkSwapchainCreateInfoKHR create_info; + VkSurfaceCapabilitiesKHR physical_device_surface_capabilities; + VkBool32 surfaceSupported; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(context->vulkan_handles.physical_device, + context->vulkan_handles.surface, + &physical_device_surface_capabilities); + + /* Store extent in global variable, for later use */ + context->swapchain_data.swapchain_extent = physical_device_surface_capabilities.currentExtent; + + /* Check if device supports surface for presentation */ + vkGetPhysicalDeviceSurfaceSupportKHR(context->vulkan_handles.physical_device, + context->queue_family_indices[0], + context->vulkan_handles.surface, + &surfaceSupported); + + create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + create_info.pNext = NULL; + create_info.flags = 0U; + create_info.surface = context->vulkan_handles.surface; + create_info.minImageCount = SWAP_BUFFER_COUNT; + create_info.imageFormat = IMAGE_FORMAT; + create_info.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + create_info.imageExtent = context->swapchain_data.swapchain_extent; + create_info.imageArrayLayers = 1U; /* For non-stereoscopic-3D applications, this value is 1. */ + create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; /* Using only one queue family, so this is ok + */ + create_info.queueFamilyIndexCount = 1U; + create_info.pQueueFamilyIndices = context->queue_family_indices; + create_info.preTransform = physical_device_surface_capabilities.currentTransform; + create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR; + create_info.clipped = VK_TRUE; + create_info.oldSwapchain = VK_NULL_HANDLE; + + if (VK_SUCCESS != + vkCreateSwapchainKHR(context->vulkan_handles.device, &create_info, NULL, &context->swapchain_data.swapchain)) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create swapchain"); + return RSE_ERROR_INTERNAL_ERROR; + } + + /* Obtain swapchain images */ + vkGetSwapchainImagesKHR(context->vulkan_handles.device, + context->swapchain_data.swapchain, + &context->swapchain_images_count, + NULL); + vkGetSwapchainImagesKHR(context->vulkan_handles.device, + context->swapchain_data.swapchain, + &context->swapchain_images_count, + context->swapchain_data.swapchain_images); + + return RSE_ERROR_NO_ERROR; +} + +/** + * @brief Create a Swapchain Image Views + * + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + */ +static rse_err_t create_swapchain_image_views(struct graphics_context_t* context) +{ + for (size_t i = 0; i < SWAPCHAIN_IMAGE_COUNT; i++) { + VkImageViewCreateInfo create_info; + + create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + create_info.pNext = NULL; + create_info.flags = 0U; + create_info.image = context->swapchain_data.swapchain_images[i]; + create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; /* 2D Texture */ + create_info.format = VK_FORMAT_B8G8R8A8_SRGB; + create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + create_info.subresourceRange.baseMipLevel = 0U; + create_info.subresourceRange.levelCount = 1U; + create_info.subresourceRange.baseArrayLayer = 0U; + create_info.subresourceRange.layerCount = 1U; + + if (VK_SUCCESS != vkCreateImageView(context->vulkan_handles.device, + &create_info, + NULL, + &context->swapchain_data.swapchain_image_views[i])) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create swapchain image view"); + return RSE_ERROR_INTERNAL_ERROR; + } + } + + return RSE_ERROR_NO_ERROR; +} + +/** + * @brief Create Frambuffers + * + * @return rse_err_t RSE_ERROR_NO_ERROR on success. Possible errors: + */ +static rse_err_t create_framebuffers(struct graphics_context_t* context) +{ + for (size_t i = 0; i < context->swapchain_images_count; i++) { + VkImageView attachments[] = {context->color_image.image_view, + context->depth_image.image_view, + context->swapchain_data.swapchain_image_views[i]}; + + VkFramebufferCreateInfo framebufferInfo = {0}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = context->vulkan_handles.render_pass; + framebufferInfo.attachmentCount = 3; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = context->swapchain_data.swapchain_extent.width; + framebufferInfo.height = context->swapchain_data.swapchain_extent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(context->vulkan_handles.device, + &framebufferInfo, + NULL, + &context->swapchain_data.swapchain_framebuffers[i]) != VK_SUCCESS) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create framebuffer"); + return RSE_ERROR_INTERNAL_ERROR; + } + } + + return RSE_ERROR_NO_ERROR; +} + +/** + * @brief Recreate swapchain + * + */ +void recreate_swapchain(struct graphics_context_t* context) +{ + SDL_Event event; + int width = 0; + int height = 0; + + SDL_GetWindowSizeInPixels(context->window_handle, &width, &height); + while (width == 0 || height == 0) { + SDL_GetWindowSizeInPixels(context->window_handle, &width, &height); + SDL_WaitEvent(&event); + } + + vkDeviceWaitIdle(context->vulkan_handles.device); + + cleanup_swapchain(context); + + create_swapchain(context); + create_swapchain_image_views(context); + create_color_resource(context); + create_depth_resources(context); + create_framebuffers(context); +} + +/** + * @brief Cleanup swapchain + * + */ +void cleanup_swapchain(struct graphics_context_t* context) +{ + size_t i; + + destroy_color_resource(context); + destroy_depth_resource(context); + for (i = 0; i < context->swapchain_images_count; i++) { + vkDestroyFramebuffer(context->vulkan_handles.device, context->swapchain_data.swapchain_framebuffers[i], NULL); + } + + for (i = 0; i < context->swapchain_images_count; i++) { + vkDestroyImageView(context->vulkan_handles.device, context->swapchain_data.swapchain_image_views[i], NULL); + } + + vkDestroySwapchainKHR(context->vulkan_handles.device, context->swapchain_data.swapchain, NULL); +} + +rse_err_t create_swapchain_and_framebuffers(struct graphics_context_t* context) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + + STATUS_CHECK(create_swapchain(context)); + STATUS_CHECK(create_swapchain_image_views(context)); + STATUS_CHECK(create_framebuffers(context)); + + return status; +} diff --git a/graphics/src/vulkan_swapchain.h b/graphics/src/vulkan_swapchain.h new file mode 100644 index 00000000..794a95d9 --- /dev/null +++ b/graphics/src/vulkan_swapchain.h @@ -0,0 +1,27 @@ +#ifndef VULKAN_SWAPCHAIN_H +#define VULKAN_SWAPCHAIN_H + +#include "utilities/commons.h" +#include "graphics_context.h" + +/** + * @brief Cleanup swapchain + * + */ +void cleanup_swapchain(struct graphics_context_t* context); + +/** + * @brief Recreates swapchain + * + */ +void recreate_swapchain(struct graphics_context_t* context); + +/** + * @brief Create a swapchain and framebuffers object + * + * @param context graphical context + * @return rse_err_t RSE_ERROR_NO_ERROR on success + */ +rse_err_t create_swapchain_and_framebuffers(struct graphics_context_t* context); + +#endif /* VULKAN_SWAPCHAIN_H */ diff --git a/graphics/src/window.c b/graphics/src/window.c new file mode 100644 index 00000000..175fbde4 --- /dev/null +++ b/graphics/src/window.c @@ -0,0 +1,65 @@ +/** + * @file window.cxx + * @author Piotr Krygier (everyonencancode@gmail.com) + * @brief + * @version 0.1 + * @date 2022-02-15 + * + * @copyright Copyright (c) 2022 + * + */ + +#include "window.h" + +#include +#include +#include +#include +#include +#include + +#include "utilities/commons.h" +#include "utilities/errors_common.h" +#include "vulkan/vulkan_core.h" +#include "vulkan_base.h" + +rse_err_t window_init(SDL_Window** window_handle, bool* is_framebuffer_resized) +{ + SDL_Init(SDL_INIT_VIDEO); // TODO: Move to initial phase of RSE initialization + + (void)is_framebuffer_resized;; // TODO: Actually use this parameter? + + *window_handle = SDL_CreateWindow("RedScarfEngine", 1024, 768, SDL_WINDOW_VULKAN); + + if (NULL == *window_handle) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Window context creation failed"); + return RSE_ERROR_INTERNAL_ERROR; + } + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t window_loop(struct graphics_context_t* context) +{ + uint8_t done = 0; + SDL_Event event; + + while (0 == done) { + while (SDL_PollEvent(&event)) { + if (SDL_EVENT_QUIT == event.type) { + done = 1; + } + } + + draw_frame(context); + } + + vkDeviceWaitIdle(context->vulkan_handles.device); + return RSE_ERROR_NO_ERROR; +} + +void window_terminate(SDL_Window* window_handle) +{ + SDL_DestroyWindow(window_handle); + SDL_QuitSubSystem(SDL_INIT_VIDEO); +} diff --git a/graphics/src/window.h b/graphics/src/window.h new file mode 100644 index 00000000..d7ee9499 --- /dev/null +++ b/graphics/src/window.h @@ -0,0 +1,50 @@ +/** + * @file window.h + * @author Piotr Krygier (everyonencancode@gmail.com) + * @brief Window control system + * @version 0.1 + * @date 2022-02-15 + * + * @copyright Copyright (c) 2022 + * + */ + +#ifndef WINDOW_H +#define WINDOW_H + +#include +#include + +/* Vulkan header MUST be included before glfw */ + +#include "utilities/commons.h" +#include "graphics_context.h" + +/** + * @brief Initialize window + * + * @param window_handle Pointer to window window_handle, that will be initialized + * @param is_framebuffer_resized Pointer to boolean, that will be set to true if framebuffer was resized + * @return rse_err_t WINDOW_SUCCESS on success. Possible errors: + * WINDOW_VULKAN_NOT_LOADED + * WINDOW_WINDOW_NOT_CREATED + * + */ +rse_err_t window_init(SDL_Window **window_handle, bool *is_framebuffer_resized); + +/** + * @brief Main window loop + * + * @param context Graphics context + * @return rse_err_t WINDOW_SUCCESS or error code + */ +rse_err_t window_loop(struct graphics_context_t* context); + +/** + * @brief Closes window and terminates GLFW + * + * @param window_handle Pointer to window window_handle + */ +void window_terminate(SDL_Window* window_handle); + +#endif /* WINDOW_H */ diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..279f0eee --- /dev/null +++ b/meson.build @@ -0,0 +1,12 @@ +project('RedScarfEngine', + ['c', 'cpp'], + version: '0.0.1', + default_options: [ + 'warning_level=3', + 'werror=true' + ]) + +subdir('utilities') +subdir('graphics') +subdir('red_scarf_engine') + diff --git a/red_scarf_engine/CMakeLists.txt b/red_scarf_engine/CMakeLists.txt new file mode 100644 index 00000000..bf138996 --- /dev/null +++ b/red_scarf_engine/CMakeLists.txt @@ -0,0 +1,10 @@ +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror") + +add_executable(${PROJECT_NAME} src/main.c) + +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_SOURCE_DIR}) + +target_link_libraries(${PROJECT_NAME} PRIVATE + graphics + utilities) \ No newline at end of file diff --git a/red_scarf_engine/meson.build b/red_scarf_engine/meson.build new file mode 100644 index 00000000..043f31fd --- /dev/null +++ b/red_scarf_engine/meson.build @@ -0,0 +1,18 @@ +red_scarf_engine_srcs = [ + 'src/main.c' + ] + +sdl3_dep = dependency('sdl3', + version : '>=3.4.0') + +executable( + 'RedScarfEngine', + red_scarf_engine_srcs, + include_directories : [ + '../' + ], + dependencies : [ + sdl3_dep, + ], + link_with: rse_graphics_lib, + ) diff --git a/red_scarf_engine/src/main.c b/red_scarf_engine/src/main.c new file mode 100644 index 00000000..a9cb6a6c --- /dev/null +++ b/red_scarf_engine/src/main.c @@ -0,0 +1,31 @@ +#include +#include +#include "graphics/rse_graphics.h" +#include "utilities/errors_common.h" +#include "SDL3/SDL_thread.h" + + +int main(int argc, char** argv) +{ + rse_err_t status = RSE_ERROR_NO_ERROR; + struct rse_graphics_context_t* context = NULL; + SDL_Thread* graphics_task = NULL; + (void)argc; + (void)argv; + + SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE); + + STATUS_CHECK(rse_graphics_init(&context)); + STATUS_CHECK(rse_graphics_test_function(context)); + + graphics_task = SDL_CreateThread(rse_graphics_run, "graphics_task", (void*) context); + + if (NULL == graphics_task) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to create graphics task"); + exit(1); + } + + SDL_WaitThread(graphics_task, NULL); + + return 0; +} diff --git a/test_image.jpg b/test_image.jpg new file mode 100644 index 00000000..6ff1f42d Binary files /dev/null and b/test_image.jpg differ diff --git a/test_image_2.jpg b/test_image_2.jpg new file mode 100644 index 00000000..b137b683 Binary files /dev/null and b/test_image_2.jpg differ diff --git a/utilities/commons.h b/utilities/commons.h new file mode 100644 index 00000000..50144658 --- /dev/null +++ b/utilities/commons.h @@ -0,0 +1,48 @@ +/** + * @file commons.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief Common functionality for all modules + * @version 0.1 + * @date 2023-10-05 + * + * @copyright Copyright (c) 2023 + * + */ + +#ifndef COMMONS_H +#define COMMONS_H + +#include +#include +#include + +/** + * @brief Macro for allocating memory and checking it on the same line + * + */ +#ifndef RSE_TEST +#define rse_malloc(ptr, size) \ + ptr = malloc(size); \ + if (ptr == NULL) { \ + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Memory allocation failure"); \ + exit(1); \ + } + +#define rse_free(ptr) free(ptr); + +#define rse_memcpy(dst, src, size) memcpy(dst, src, size); +#define rse_memset(ptr, value, size) memset(ptr, value, size); +#else +#define rse_malloc(ptr, size) ptr = NULL; +#define rse_free(ptr) +#define rse_memcpy(dst, src, size) +#define rse_memset +#endif + +/** + * @brief Error type for RSE + * + */ +typedef uint32_t rse_err_t; + +#endif /* COMMONS_H */ diff --git a/utilities/entity.h b/utilities/entity.h new file mode 100644 index 00000000..9c62da8d --- /dev/null +++ b/utilities/entity.h @@ -0,0 +1,26 @@ +/** + * @file entity.h + * @author Piotr Krygier (piotrkrygier@everyonencancode.xyz) + * @brief Entity from ECS + * @version 0.1 + * @date 2025-09-25 + * + * @copyright Copyright (c) 2025 + * + */ + +#ifndef ENTITY_H +#define ENTITY_H + +#include + +#define ENTITY_MAX_COUNT 65535 +#define COMPONENT_MAX_COUNT 65535 + +typedef uint32_t entity_t; + +entity_t entity_create(void); +void entity_free(entity_t entity); + +#endif // !ENTITY_H + diff --git a/utilities/errors_common.h b/utilities/errors_common.h new file mode 100644 index 00000000..3315d3c5 --- /dev/null +++ b/utilities/errors_common.h @@ -0,0 +1,34 @@ +/** + * @file errors_common.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief Common error messages, that can appear in any module + * @version 0.1 + * @date 2023-10-05 + * + * @copyright Copyright (c) 2023 + * + */ + +#ifndef ERRORS_COMMON_H + +/* Macro for checking status of function execution in initVulkan. Created to avoid writing boilerplate code */ +#define STATUS_CHECK(FUNC) \ + status = FUNC; \ + if (RSE_ERROR_NO_ERROR != status) { \ + return status; \ + } + +#define RSE_COMMON_MODULE_ID 0x0100U + +enum common_error_t +{ + RSE_ERROR_NO_ERROR = RSE_COMMON_MODULE_ID, + RSE_ERROR_NULL_POINTER, + RSE_ERROR_ALREADY_INITIALIZED, + RSE_COMMON_ERROR_MALLLOC_FAILED, + RSE_ERROR_INVALID_PARAM, + RSE_ERROR_INTERNAL_ERROR, +}; + +#define ERRORS_COMMON_H +#endif /* ERRORS_COMMON_H */ diff --git a/utilities/file_utils.h b/utilities/file_utils.h new file mode 100644 index 00000000..1365d81d --- /dev/null +++ b/utilities/file_utils.h @@ -0,0 +1,26 @@ +#ifndef FILE_UTILS_H +#define FILE_UTILS_H + +#include +#include "commons.h" + +#define MAX_PATH_LENGTH (4096U) + +/** + * @brief Reads file and puts its content into char buffer. If NULL is provided as buffer, than just bytes_count + * is set + * + * @param file_path Path to file + * @param bytes_count Returned bytes count + * @param buffer Returned filled buffer + */ +rse_err_t file_read(const char* file_path, size_t* bytes_count, char* buffer); + +rse_err_t file_write(const char* file_name, size_t bytes_count, char* buffer); + +rse_err_t file_load_pixels(const char* file_name, unsigned char* buffer, size_t* buffer_size, int* width, int* height); + +rse_err_t file_append_full_path(char* file_path, size_t max_buffer_size); + +#endif /* FILE_UTILS_H */ + diff --git a/utilities/meson.build b/utilities/meson.build new file mode 100644 index 00000000..3751eedf --- /dev/null +++ b/utilities/meson.build @@ -0,0 +1,23 @@ +rse_utilities_srcs = [ + 'src/file_utils.c' + ] + +cc = meson.get_compiler('c') + +sdl3_dep = dependency('sdl3', + version : '>=3.4.0') +stb_dep = dependency('stb', + version : '>=2.30.0') +m_dep = cc.find_library('m') + +rse_utilities_lib = shared_library( + 'rse_utilities', + rse_utilities_srcs, + include_directories : ['.'], + dependencies : [ + sdl3_dep, + stb_dep, + m_dep, + ], + install : true + ) diff --git a/utilities/src/entity.c b/utilities/src/entity.c new file mode 100644 index 00000000..867adb36 --- /dev/null +++ b/utilities/src/entity.c @@ -0,0 +1,34 @@ +/** + * @file entity.c + * @author Piotr Krygier (piotrkrygier@everyonencancode.xyz) + * @brief Entity from ECS + * @version 0.1 + * @date 2025-09-25 + * + * @copyright Copyright (c) 2025 + * + */ + +#include "utilities/entity.h" +#include + +static entity_t g_free_list[ENTITY_MAX_COUNT]; +static uint32_t g_free_count = 0U; +static uint32_t g_living_count = 0U; + +entity_t entity_create(void) +{ + entity_t e = 0U; + if (g_free_count > 0) { + e = g_free_list[g_free_count--]; + } else { + e = g_living_count++; + } + + return e; +} + +void entity_free(entity_t entity) +{ + g_free_list[g_free_count++] = entity; +} diff --git a/utilities/src/file_utils.c b/utilities/src/file_utils.c new file mode 100644 index 00000000..6f81e667 --- /dev/null +++ b/utilities/src/file_utils.c @@ -0,0 +1,242 @@ +#include "file_utils.h" + +#include +#include +#include + +#include "SDL3/SDL_log.h" +#include "commons.h" +#include "errors_common.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb/stb_image.h" + +#ifdef __linux__ +#include +#include +#endif /* ifndef __linux */ + +static rse_err_t get_curr_path(char* path) +{ + char* last_sep = NULL; + char* tmp_path = NULL; + size_t path_length = 0U; + ssize_t binary_path_length = 0U; + size_t directory_path_length = 0U; + + if (NULL == path) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to get path. Provided pointer is NULL"); + + return RSE_ERROR_NULL_POINTER; + } + + path_length = strlen(path); + if (path_length > MAX_PATH_LENGTH) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Max path length cannot be 0"); + + return RSE_ERROR_INVALID_PARAM; + } + + rse_memset(path, '\0', path_length); + rse_malloc(tmp_path, MAX_PATH_LENGTH); + +#ifdef __linux__ + binary_path_length = readlink("/proc/self/exe", tmp_path, MAX_PATH_LENGTH - 1); + if (binary_path_length == -1) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to read current binary path"); + + return RSE_ERROR_NULL_POINTER; + } + + tmp_path[binary_path_length] = '\0'; +#endif /* ifdef __linux__ */ + + last_sep = strrchr(tmp_path, '/') + 1; + + if (NULL == last_sep) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to get last separator in path"); + + return RSE_ERROR_INTERNAL_ERROR; + } + + directory_path_length = last_sep - tmp_path; + + if (directory_path_length > MAX_PATH_LENGTH) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Provided path buffer is too small"); + + return RSE_ERROR_INTERNAL_ERROR; + } + + strncpy(path, tmp_path, directory_path_length); + + free(tmp_path); + + return RSE_ERROR_NO_ERROR; +} + +rse_err_t file_append_full_path(char* file_path, size_t max_buffer_size) +{ + rse_err_t ret = RSE_ERROR_NO_ERROR; + size_t curr_path_length = 0U; + size_t file_path_length = 0U; + char curr_path[MAX_PATH_LENGTH] = {0}; + char temp_path[MAX_PATH_LENGTH] = {0}; + + file_path_length = strlen(file_path); + + if (file_path_length > max_buffer_size) { + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Provided max_buffer_size is smaller than the file path itself"); + + return RSE_ERROR_INVALID_PARAM; + } + + if ((ret = get_curr_path(curr_path)) != RSE_ERROR_NO_ERROR) { + return ret; + } + + curr_path_length = strlen(curr_path); + + if ((curr_path_length + file_path_length) > max_buffer_size) { + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, + "Provided max_buffer_size is to small to hold concatenated file path. %ld vs %ld", + (curr_path_length + file_path_length), + max_buffer_size); + } + + strncpy(temp_path, file_path, MAX_PATH_LENGTH); + + memset(file_path, 0, max_buffer_size); + + strncat(file_path, curr_path, max_buffer_size); + strncat(file_path, temp_path, max_buffer_size); + + return ret; +} + +rse_err_t file_load_pixels(const char* file_name, unsigned char* buffer, size_t* buffer_size, int* width, int* height) +{ + int channels = 0; + stbi_uc* pixel_buffer = NULL; + size_t image_buffer_size = 0U; + rse_err_t ret = RSE_ERROR_NO_ERROR; + char real_path[MAX_PATH_LENGTH] = {0}; + + if (buffer_size == NULL) { + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Provided NULL buffer_size"); + + return RSE_ERROR_INVALID_PARAM; + } + + if (width == NULL) { + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Provided NULL width"); + + return RSE_ERROR_INVALID_PARAM; + } + + if (height == NULL) { + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Provided NULL height"); + + return RSE_ERROR_INVALID_PARAM; + } + + strncpy(real_path, file_name, MAX_PATH_LENGTH); + if ((ret = file_append_full_path(real_path, MAX_PATH_LENGTH)) != RSE_ERROR_NO_ERROR) { + return ret; + } + + pixel_buffer = stbi_load(real_path, width, height, &channels, STBI_rgb_alpha); + + if (pixel_buffer == NULL) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to load texture from file: %s", file_name); + return RSE_ERROR_INTERNAL_ERROR; + } + + image_buffer_size = *width * *height * 4; // TODO: "4" is very hardcoded value. Change this + + if ((buffer != NULL) && (image_buffer_size > *buffer_size)) { + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, + "Provided buffer is to small to hold pixel data. %ld vs %ld", + *buffer_size, + image_buffer_size); + ret = RSE_ERROR_INTERNAL_ERROR; + + goto image_free; + } + + *buffer_size = image_buffer_size; + + if (buffer != NULL) { + memset(buffer, 0, *buffer_size); + memcpy(buffer, pixel_buffer, *buffer_size); + } + +image_free: + stbi_image_free(pixel_buffer); + + return ret; +} + +rse_err_t file_read(const char* file_name, size_t* bytes_count, char* buffer) +{ + rse_err_t ret = RSE_ERROR_NO_ERROR; + FILE* file = NULL; + char real_path[MAX_PATH_LENGTH] = {0}; + + strncpy(real_path, file_name, MAX_PATH_LENGTH); + if ((ret = file_append_full_path(real_path, MAX_PATH_LENGTH)) != RSE_ERROR_NO_ERROR) { + return ret; + } + + file = fopen(real_path, "rb"); + + if (file == NULL) { + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Failed to open file for read: %s", real_path); + ret = RSE_ERROR_INTERNAL_ERROR; + + goto file_close; + } + + fseek(file, 0, SEEK_END); + *bytes_count = ftell(file); + fseek(file, 0, SEEK_SET); + + if (buffer == NULL) { + goto file_close; + } + + fread(buffer, *bytes_count, 1, file); + if (ferror(file) != 0) { + perror("Error reading file"); + + ret = RSE_ERROR_INTERNAL_ERROR; + } + +file_close: + fclose(file); + + return ret; +} + +rse_err_t file_write(const char* file_name, size_t bytes_count, char* buffer) +{ + FILE* file = NULL; + char real_path[MAX_PATH_LENGTH] = {0}; + rse_err_t ret = RSE_ERROR_NO_ERROR; + + strncpy(real_path, file_name, MAX_PATH_LENGTH); + if ((ret = file_append_full_path(real_path, MAX_PATH_LENGTH)) != RSE_ERROR_NO_ERROR) { + return ret; + } + + file = fopen(real_path, "wb"); + + if (file == NULL) { + SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to open file for write: %s", file_name); + return RSE_ERROR_INTERNAL_ERROR; + } + + fwrite(buffer, bytes_count, 1, file); + fclose(file); + + return RSE_ERROR_NO_ERROR; +} diff --git a/utilities/stack.h b/utilities/stack.h new file mode 100644 index 00000000..85dac1b8 --- /dev/null +++ b/utilities/stack.h @@ -0,0 +1,45 @@ +/** + * @file stack.h + * @author Piotr Krygier (everyonecancode@gmail.com) + * @brief + * @version 0.1 + * @date 2024-05-01 + * + * @copyright Copyright (c) 2024 + * + */ + +#ifndef STACK_H +#define STACK_H + +#define RSE_STACK_DEFINE(name, type, size) \ + struct name { \ + type data[size]; \ + int top; \ + } + +#define RSE_STACK_PUSH(stack, value) \ + stack.data[++stack.top] = value + +#define RSE_STACK_POP(stack) \ + stack.data[stack.top--] + +#define RSE_STACK_TOP(stack) \ + stack.data[stack.top] + +#define RSE_STACK_IS_EMPTY(stack) \ + (stack.top == -1) + +#define RSE_STACK_IS_FULL(stack, size) \ + (stack.top == size - 1) + +#define RSE_STACK_INIT(stack) \ + (stack.top = -1) + +#define RSE_STACK_SIZE(stack) \ + (stack.top + 1) + +#define RSE_STACK_CLEAR(stack) \ + (stack.top = -1) + +#endif // STACK_H diff --git a/utilities/vector.h b/utilities/vector.h new file mode 100644 index 00000000..5ceaca10 --- /dev/null +++ b/utilities/vector.h @@ -0,0 +1,94 @@ +/** + * @file vector.h + * @author Piotr Krygier (piotrkrygier@everyonecancode.xyz) + * @brief C implementation of a vector, dynamic array + * @version 0.1 + * @date 2024-09-18 + * + * @copyright Copyright (c) 2025 + * + */ + +#ifndef VECTOR_H +#define VECTOR_H + +#include +#include + +#include "SDL3/SDL_log.h" +#include "utilities/commons.h" +#include "utilities/errors_common.h" + +#define VECTOR_CHUNK_SIZE (64U) +#define RSE_VECTOR_DEFINE(name, type) \ + struct name \ + { \ + type* data; \ + size_t size; \ + size_t _allocated_size; \ + } + +#define RSE_VECTOR_PUSH_BACK(vector, value) \ + { \ + if (vector.data == NULL) { \ + vector.size = 0; \ + vector._allocated_size = VECTOR_CHUNK_SIZE; \ + rse_malloc(vector.data, sizeof(*vector.data) * VECTOR_CHUNK_SIZE); \ + } \ + if (vector._allocated_size == vector.size) { \ + __typeof__((vector.data)) tmp_buffer = NULL; \ + rse_malloc(tmp_buffer, sizeof(*vector.data) * vector.size); \ + rse_memcpy(tmp_buffer, vector.data, sizeof(*vector.data) * vector.size); \ + rse_free(vector.data); \ + vector._allocated_size += VECTOR_CHUNK_SIZE; \ + rse_malloc(vector.data, vector._allocated_size * vector.size); \ + rse_memcpy(vector.data, tmp_buffer, sizeof(*vector.data) * vector.size); \ + rse_free(tmp_buffer); \ + } \ + vector.data[vector.size] = value; \ + vector.size++; \ + } + +#define RSE_VECTOR_INSERT_INTO(vector, idx, data) \ + { \ + if (vector._allocated_size == vector.size) { \ + __typeof__((vector.data)) tmp_buffer = NULL; \ + rse_malloc(tmp_buffer, sizeof(*vector.data) * vector.size); \ + rse_memcpy(tmp_buffer, vector.data, sizeof(*vector.data) * vector.size); \ + if (vector.data != NULL) { \ + rse_free(vector.data); \ + } \ + vector._allocated_size += VECTOR_CHUNK_SIZE; \ + rse_malloc(vector.data, vector._allocated_size * vector.size); \ + rse_memcpy(vector.data, tmp_buffer, sizeof(*vector.data) * vector.size); \ + rse_free(tmp_buffer); \ + } \ + vector->size++; \ + rse_memcpy(&vector->data[idx + 1], &vector->data[idx], sizeof(*vector->data) * (vector->size - idx)); \ + vector->data[idx] = data; \ + } + +#define RSE_VECTOR_REMOVE_ELEMENT(vector, idx) \ + { \ + if (vector->size <= idx) { \ + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Vector index out of bounds"); \ + exit(EXIT_FAILURE); \ + } \ + if (vector->size == 1) { \ + rse_free(vector->data); \ + vector->_allocated_size = 0; \ + } else { \ + rse_memcpy(&vector->data[idx], &vector->data[idx + 1], sizeof(vector->data) * (vector->size - idx - 1)); \ + } \ + vector->size--; \ + } + +struct vector_t +{ + void* data; + size_t size; + size_t _data_size; + size_t _allocated_size; +}; + +#endif // !VECTOR_H