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 <piotrkrygier@everyonecancode@xyz>
This commit is contained in:
Piotr Krygier
2022-06-28 09:54:41 +02:00
committed by Piotr Krygier
commit 493afb05e6
56 changed files with 5574 additions and 0 deletions
+40
View File
@@ -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
+12
View File
@@ -0,0 +1,12 @@
CompileFlags:
Add: [
"-ferror-limit=0",
"-Wall",
"-Wpedantic",
"-Werror",
"-Wno-gnu-statement-expression",
"-I./",
"-I./graphics",
"-I./utilites",
]
+56
View File
@@ -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
+21
View File
@@ -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.
Binary file not shown.
+4
View File
@@ -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.
+49
View File
@@ -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
)
+31
View File
@@ -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 */
+8
View File
@@ -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);
}
+13
View File
@@ -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);
}
+79
View File
@@ -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;
}
+15
View File
@@ -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);
}
+205
View File
@@ -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 <SDL3/SDL_log.h>
#include <stddef.h>
#include <stdint.h>
#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);
}
+68
View File
@@ -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 <stdint.h>
#include <vulkan/vulkan_core.h>
#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
+224
View File
@@ -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 <SDL3/SDL_log.h>
#include <assert.h>
#include <cglm/types.h>
#include <ft2build.h>
#include <freetype/freetype.h>
#include <freetype/fttypes.h>
#include <stdint.h>
#include <string.h>
#include <vulkan/vulkan_core.h>
#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;
// }
+23
View File
@@ -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 */
+284
View File
@@ -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 <SDL3/SDL.h>
#include <SDL3/SDL_render.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <vulkan/vulkan.h>
#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 */
+59
View File
@@ -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 <SDL3/SDL_log.h>
#include <stdbool.h>
#include <string.h>
#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);
}
+68
View File
@@ -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 */
+466
View File
@@ -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 <assert.h>
#include <stddef.h>
#include <stdint.h>
#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);
}
}
+125
View File
@@ -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 <stdint.h>
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_core.h>
#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 */
+41
View File
@@ -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 <SDL3/SDL_log.h>
#include <stdint.h>
#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;
}
+24
View File
@@ -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 */
+254
View File
@@ -0,0 +1,254 @@
#include "rse_graphics.h"
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_log.h>
#include <SDL3/SDL_render.h>
#include <cglm/util.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vulkan/vulkan_core.h>
#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;
}
+29
View File
@@ -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
+673
View File
@@ -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 <SDL3/SDL_error.h>
#include <SDL3/SDL_log.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_vulkan.h>
#include <vma/vk_mem_alloc.h>
#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 <stddef.h>
#include <stdlib.h>
#include <string.h>
#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);
}
+51
View File
@@ -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 <vulkan/vulkan.h>
#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 */
+448
View File
@@ -0,0 +1,448 @@
#include "vulkan_buffers.h"
#include <cglm/affine-pre.h>
#include <cglm/cam.h>
#include <cglm/cglm.h>
#include <cglm/mat4.h>
#include <cglm/util.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <vulkan/vulkan_core.h>
#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, &copy_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);
}
}
+104
View File
@@ -0,0 +1,104 @@
#ifndef VULKAN_BUFFERS_H
#define VULKAN_BUFFERS_H
#include <stdint.h>
#include <vma/vk_mem_alloc.h>
#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 */
+135
View File
@@ -0,0 +1,135 @@
#include "vulkan_commands.h"
#include <SDL3/SDL_log.h>
#include <vulkan/vulkan_core.h>
#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);
}
+54
View File
@@ -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 <vma/vk_mem_alloc.h>
#include <vulkan/vulkan_core.h>
#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 */
+24
View File
@@ -0,0 +1,24 @@
#include "vulkan_commons.h"
#include <time.h>
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;
}
+65
View File
@@ -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 <cglm/types.h>
#include <vulkan/vulkan.h>
#include <vma/vk_mem_alloc.h>
#include <cglm/cglm.h>
#include <stdalign.h>
#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 */
+585
View File
@@ -0,0 +1,585 @@
#include "vulkan_image.h"
#include <SDL3/SDL_log.h>
#include <stdint.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"
#include "vulkan_buffers.h"
#include "vulkan_commands.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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,
&copy_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,
&copy_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
}
+101
View File
@@ -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 <stdint.h>
#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 */
+112
View File
@@ -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);
}
+33
View File
@@ -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_ */
+206
View File
@@ -0,0 +1,206 @@
#include "vulkan_swapchain.h"
#include <SDL3/SDL_events.h>
#include <stdlib.h>
#include <string.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_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;
}
+27
View File
@@ -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 */
+65
View File
@@ -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 <SDL3/SDL_events.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_timer.h>
#include <SDL3/SDL_video.h>
#include <stdint.h>
#include <stdio.h>
#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);
}
+50
View File
@@ -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 <SDL3/SDL_main.h>
#include <vulkan/vulkan.h>
/* 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 */
+12
View File
@@ -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')
+10
View File
@@ -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)
+18
View File
@@ -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,
)
+31
View File
@@ -0,0 +1,31 @@
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_log.h>
#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;
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 713 KiB

+48
View File
@@ -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 <SDL3/SDL_log.h>
#include <stdint.h>
#include <stdlib.h>
/**
* @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 */
+26
View File
@@ -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 <stdint.h>
#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
+34
View File
@@ -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 */
+26
View File
@@ -0,0 +1,26 @@
#ifndef FILE_UTILS_H
#define FILE_UTILS_H
#include <stddef.h>
#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 */
+23
View File
@@ -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
)
+34
View File
@@ -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 <stdint.h>
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;
}
+242
View File
@@ -0,0 +1,242 @@
#include "file_utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SDL3/SDL_log.h"
#include "commons.h"
#include "errors_common.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb/stb_image.h"
#ifdef __linux__
#include <limits.h>
#include <unistd.h>
#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;
}
+45
View File
@@ -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
+94
View File
@@ -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 <stddef.h>
#include <stdlib.h>
#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