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:
@@ -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
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
CompileFlags:
|
||||||
|
Add: [
|
||||||
|
"-ferror-limit=0",
|
||||||
|
"-Wall",
|
||||||
|
"-Wpedantic",
|
||||||
|
"-Werror",
|
||||||
|
"-Wno-gnu-statement-expression",
|
||||||
|
"-I./",
|
||||||
|
"-I./graphics",
|
||||||
|
"-I./utilites",
|
||||||
|
]
|
||||||
|
|
||||||
+56
@@ -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
|
||||||
@@ -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.
@@ -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.
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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 */
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
// }
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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, ©_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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,
|
||||||
|
©_region);
|
||||||
|
end_single_time_comands(context, command_buffer);
|
||||||
|
destroy_buffer(context, &staging_buffer);
|
||||||
|
|
||||||
|
transition_image_layout(context,
|
||||||
|
free_texture_image,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
STATUS_CHECK(create_image_view(context, free_texture_image, format, VK_IMAGE_ASPECT_COLOR_BIT));
|
||||||
|
|
||||||
|
free_texture_image->id_taken = IMAGE_TAKEN;
|
||||||
|
return RSE_ERROR_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
rse_err_t init_vulkan_images(struct graphics_context_t* context)
|
||||||
|
{
|
||||||
|
rse_err_t status = RSE_ERROR_NO_ERROR;
|
||||||
|
|
||||||
|
STATUS_CHECK(create_depth_resources(context));
|
||||||
|
STATUS_CHECK(create_color_resource(context));
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
rse_err_t texture_update_image(struct graphics_context_t* context,
|
||||||
|
const unsigned char* pixels,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
uint16_t texture_id,
|
||||||
|
VkFormat format)
|
||||||
|
{
|
||||||
|
rse_err_t status = RSE_ERROR_NO_ERROR;
|
||||||
|
VkDeviceSize staging_buffer_size = 0U;
|
||||||
|
VkBufferImageCopy copy_region = {0};
|
||||||
|
struct vulkan_buffer_t staging_buffer = {0};
|
||||||
|
VkExtent3D image_extent = {0};
|
||||||
|
VkCommandBuffer command_buffer = {0};
|
||||||
|
|
||||||
|
image_extent.width = width;
|
||||||
|
image_extent.height = height;
|
||||||
|
image_extent.depth = 1U;
|
||||||
|
|
||||||
|
STATUS_CHECK(format_to_pixel_size(format, &staging_buffer_size));
|
||||||
|
|
||||||
|
staging_buffer_size = staging_buffer_size * width * height;
|
||||||
|
|
||||||
|
transition_image_layout(context,
|
||||||
|
&context->texture_images[texture_id],
|
||||||
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||||
|
|
||||||
|
/* Copy pixel data to GPU memory */
|
||||||
|
STATUS_CHECK(
|
||||||
|
create_buffer(context,
|
||||||
|
staging_buffer_size,
|
||||||
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||||
|
VMA_MEMORY_USAGE_AUTO_PREFER_HOST,
|
||||||
|
VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
|
||||||
|
&staging_buffer));
|
||||||
|
|
||||||
|
memcpy(staging_buffer.allocation_info.pMappedData, pixels, staging_buffer_size);
|
||||||
|
|
||||||
|
copy_region.bufferOffset = 0U;
|
||||||
|
copy_region.bufferRowLength = 0U;
|
||||||
|
copy_region.bufferImageHeight = 0U;
|
||||||
|
copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
copy_region.imageSubresource.mipLevel = 0U;
|
||||||
|
copy_region.imageSubresource.baseArrayLayer = 0U;
|
||||||
|
copy_region.imageSubresource.layerCount = 1U;
|
||||||
|
copy_region.imageOffset.x = 0;
|
||||||
|
copy_region.imageOffset.y = 0;
|
||||||
|
copy_region.imageOffset.z = 0;
|
||||||
|
copy_region.imageExtent = image_extent;
|
||||||
|
|
||||||
|
STATUS_CHECK(begin_single_time_command(context, &command_buffer));
|
||||||
|
vkCmdCopyBufferToImage(command_buffer,
|
||||||
|
staging_buffer.buffer,
|
||||||
|
context->texture_images[texture_id].image,
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
|
1,
|
||||||
|
©_region);
|
||||||
|
end_single_time_comands(context, command_buffer);
|
||||||
|
destroy_buffer(context, &staging_buffer);
|
||||||
|
|
||||||
|
transition_image_layout(context,
|
||||||
|
&context->texture_images[texture_id],
|
||||||
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
|
||||||
|
return RSE_ERROR_NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
rse_err_t load_texture_from_bitmat(struct graphics_context_t* context,
|
||||||
|
const unsigned char* pixel_buffer,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
uint16_t* texture_id,
|
||||||
|
VkFormat color_format)
|
||||||
|
{
|
||||||
|
rse_err_t status = RSE_ERROR_NO_ERROR;
|
||||||
|
|
||||||
|
/* Create actual Vulkan Image */
|
||||||
|
STATUS_CHECK(create_textured_image(context, width, height, color_format, pixel_buffer, texture_id));
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
rse_err_t load_texture_from_file(struct graphics_context_t* context, const char* file_path, uint16_t* texture_id)
|
||||||
|
{
|
||||||
|
rse_err_t status = RSE_ERROR_NO_ERROR;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
size_t buffer_size = 0U;
|
||||||
|
unsigned char* pixel_buffer = NULL;
|
||||||
|
|
||||||
|
STATUS_CHECK(file_load_pixels(file_path, NULL, &buffer_size, &width, &height));
|
||||||
|
rse_malloc(pixel_buffer, buffer_size);
|
||||||
|
if (file_load_pixels(file_path, pixel_buffer, &buffer_size, &width, &height) != RSE_ERROR_NO_ERROR) {
|
||||||
|
status = RSE_ERROR_INTERNAL_ERROR;
|
||||||
|
|
||||||
|
goto mem_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (load_texture_from_bitmat(context, pixel_buffer, width, height, texture_id, VK_FORMAT_R8G8B8A8_SRGB) != RSE_ERROR_NO_ERROR) {
|
||||||
|
status = RSE_ERROR_INTERNAL_ERROR;
|
||||||
|
|
||||||
|
goto mem_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_free:
|
||||||
|
|
||||||
|
rse_free(pixel_buffer);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_textures(struct graphics_context_t* context)
|
||||||
|
{
|
||||||
|
size_t i = 0U;
|
||||||
|
|
||||||
|
for (i = 0U; i < RSE_MAX_IMAGE_COUNT; ++i) {
|
||||||
|
if (context->texture_images[i].id_taken == IMAGE_TAKEN) {
|
||||||
|
vkDestroyImageView(context->vulkan_handles.device, context->texture_images[i].image_view, NULL);
|
||||||
|
vmaDestroyImage(context->vulkan_handles.allocator,
|
||||||
|
context->texture_images[i].image,
|
||||||
|
context->texture_images[i].allocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vkDestroySampler(context->vulkan_handles.device, context->vulkan_handles.sampler, NULL);
|
||||||
|
vkDestroySampler(context->vulkan_handles.device, context->debug_overlay.sampler, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_depth_resource(struct graphics_context_t* context)
|
||||||
|
{
|
||||||
|
vkDestroyImageView(context->vulkan_handles.device, context->depth_image.image_view, NULL);
|
||||||
|
vmaDestroyImage(context->vulkan_handles.allocator, context->depth_image.image, context->depth_image.allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_color_resource(struct graphics_context_t* context)
|
||||||
|
{
|
||||||
|
vkDestroyImageView(context->vulkan_handles.device, context->color_image.image_view, NULL);
|
||||||
|
vmaDestroyImage(context->vulkan_handles.allocator, context->color_image.image, context->color_image.allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t image_exists(struct graphics_context_t* context, uint8_t image_id)
|
||||||
|
{
|
||||||
|
return context->texture_images[image_id].id_taken == IMAGE_TAKEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t get_textures_count(struct graphics_context_t* context)
|
||||||
|
{
|
||||||
|
size_t count = 0U;
|
||||||
|
size_t i = 0U;
|
||||||
|
|
||||||
|
for (i = 0U; i < RSE_MAX_IMAGE_COUNT; ++i) {
|
||||||
|
if (context->texture_images[i].id_taken == IMAGE_TAKEN) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
rse_err_t find_depth_format(struct graphics_context_t* context, VkFormat* found_format)
|
||||||
|
{
|
||||||
|
#define formats_count 3
|
||||||
|
rse_err_t status;
|
||||||
|
VkFormat formats[formats_count] = {VK_FORMAT_D32_SFLOAT};
|
||||||
|
|
||||||
|
STATUS_CHECK(find_supported_format(context,
|
||||||
|
formats,
|
||||||
|
formats_count,
|
||||||
|
VK_IMAGE_TILING_OPTIMAL,
|
||||||
|
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT,
|
||||||
|
found_format));
|
||||||
|
|
||||||
|
return status;
|
||||||
|
#undef formats_count
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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_ */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
@@ -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')
|
||||||
|
|
||||||
@@ -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)
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 713 KiB |
@@ -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 */
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user