From 16fee9d4bb6fbeff573c42a7e633a8310c688009 Mon Sep 17 00:00:00 2001 From: jackthepunished Date: Sat, 21 Mar 2026 05:38:30 +0300 Subject: [PATCH] fix(vulkan): resolve startup hang and enable dynamic rendering pipelines The application was freezing ("not responding") on startup due to three synchronization issues in the Vulkan frame loop: 1. Fence reset was called immediately after wait, before any GPU work was submitted - causing the next frame's wait to block forever. Moved reset_fences() to just before submit(). 2. Swapchain semaphores were indexed by frame-in-flight rather than by acquired image index, causing semaphore reuse conflicts when the swapchain image count (3) exceeded frames-in-flight (2). Changed to image-index-based semaphore management. 3. VulkanDevice::end_frame() reset its internal fence but never re-signaled it, causing begin_frame() to deadlock on the next iteration. Added an empty queue submit to signal the fence. Additionally, all graphics pipelines were created with VkRenderPass objects but the rendering code uses vkCmdBeginRendering (Vulkan 1.3 dynamic rendering), making every draw call invalid and producing a blue screen. Fixed by: - Adding color_attachment_formats / depth_attachment_format fields to GraphicsPipelineDesc for dynamic rendering compatibility - Using VkPipelineRenderingCreateInfo (pNext chain) when render_pass is nullptr in VulkanPipeline creation - Migrating all 6 pipeline definitions (geometry, lighting, composite, shadow, SSAO, SSAO blur) to use format-based dynamic rendering Other fixes included in this changeset: - Fixed Texture::create() calls (static factory, not member function) - Added early camera descriptor set binding after UBO creation - Guarded command list early-return to always call end_frame() - Locale fix for MinGW path conversion on Turkish Windows Made-with: Cursor --- assets/shaders/ssao.vert | 11 -- assets/shaders/ssao_blur.frag | 13 +- engine/assets/shader_compiler.cpp | 34 +++- engine/assets/shader_compiler.hpp | 4 +- engine/renderer/deferred_renderer.cpp | 27 ++-- engine/renderer/ssao.cpp | 225 -------------------------- engine/renderer/ssao.hpp | 69 -------- engine/rhi/rhi_device.cpp | 26 ++- engine/rhi/rhi_pipeline.hpp | 5 + engine/rhi/vulkan/vk_device.cpp | 7 + engine/rhi/vulkan/vk_pipeline.cpp | 28 +++- game/src/application.cpp | 40 +++-- game/src/application.hpp | 3 +- game/src/main.cpp | 12 ++ 14 files changed, 154 insertions(+), 350 deletions(-) delete mode 100644 assets/shaders/ssao.vert delete mode 100644 engine/renderer/ssao.cpp delete mode 100644 engine/renderer/ssao.hpp diff --git a/assets/shaders/ssao.vert b/assets/shaders/ssao.vert deleted file mode 100644 index 838e898..0000000 --- a/assets/shaders/ssao.vert +++ /dev/null @@ -1,11 +0,0 @@ -#version 410 core - -layout(location = 0) in vec3 a_position; -layout(location = 1) in vec2 a_texcoord; - -out vec2 v_texcoord; - -void main() { - v_texcoord = a_texcoord; - gl_Position = vec4(a_position, 1.0); -} diff --git a/assets/shaders/ssao_blur.frag b/assets/shaders/ssao_blur.frag index 453373f..46f4580 100644 --- a/assets/shaders/ssao_blur.frag +++ b/assets/shaders/ssao_blur.frag @@ -1,16 +1,15 @@ -#version 410 core +#version 450 core -out float g_occlusion; +layout(location = 0) in vec2 v_texcoord; +layout(location = 0) out float g_occlusion; -in vec2 v_texcoord; - -uniform sampler2D u_ssao_input; +layout(set = 0, binding = 0) uniform sampler2D u_ssao_input; void main() { float result = 0.0; vec2 texel_size = 1.0 / vec2(textureSize(u_ssao_input, 0)); - // 4x4 Box Blur + // 4x4 Box Blur (Simple) for (int x = -2; x < 2; ++x) { for (int y = -2; y < 2; ++y) { vec2 offset = vec2(float(x), float(y)) * texel_size; @@ -18,5 +17,5 @@ void main() { } } - g_occlusion = result / (4.0 * 4.0); + g_occlusion = result / 16.0; } diff --git a/engine/assets/shader_compiler.cpp b/engine/assets/shader_compiler.cpp index 39b7324..428c21e 100644 --- a/engine/assets/shader_compiler.cpp +++ b/engine/assets/shader_compiler.cpp @@ -5,12 +5,35 @@ #include #include +#ifdef _WIN32 +#include +#endif + #ifdef HZ_VULKAN_BACKEND #include #endif namespace hz { +// Safe path-to-string conversion that avoids MinGW's broken narrow locale +// conversion (which throws "Illegal byte sequence" on Turkish Windows etc.) +static std::string path_to_string(const std::filesystem::path& p) { +#ifdef _WIN32 + // On Windows, go through wide string -> narrow via WideCharToMultiByte CP_UTF8 + const auto& ws = p.native(); // returns const wstring& on Windows + if (ws.empty()) + return {}; + int size_needed = WideCharToMultiByte(CP_UTF8, 0, ws.data(), static_cast(ws.size()), + nullptr, 0, nullptr, nullptr); + std::string result(static_cast(size_needed), '\0'); + WideCharToMultiByte(CP_UTF8, 0, ws.data(), static_cast(ws.size()), result.data(), + size_needed, nullptr, nullptr); + return result; +#else + return p.string(); +#endif +} + namespace { #ifdef HZ_VULKAN_BACKEND @@ -65,7 +88,7 @@ class ShaderIncluder : public shaderc::CompileOptions::IncluderInterface { // Read the file std::ifstream file(resolved_path, std::ios::binary | std::ios::ate); if (!file) { - std::string error_msg = "Could not open include file: " + resolved_path.string(); + std::string error_msg = "Could not open include file: " + path_to_string(resolved_path); auto* error_data = new std::string(std::move(error_msg)); result->source_name = ""; result->source_name_length = 0; @@ -82,7 +105,7 @@ class ShaderIncluder : public shaderc::CompileOptions::IncluderInterface { content->resize(static_cast(size)); file.read(content->data(), size); - auto* name = new std::string(resolved_path.string()); + auto* name = new std::string(path_to_string(resolved_path)); result->source_name = name->c_str(); result->source_name_length = name->size(); @@ -232,7 +255,7 @@ ShaderCompileResult ShaderCompiler::compile_file(const std::filesystem::path& pa // Read the file std::ifstream file(path, std::ios::binary | std::ios::ate); if (!file) { - result.error_message = "Could not open shader file: " + path.string(); + result.error_message = "Could not open shader file: " + path_to_string(path); HZ_LOG_ERROR("{}", result.error_message); return result; } @@ -248,7 +271,8 @@ ShaderCompileResult ShaderCompiler::compile_file(const std::filesystem::path& pa if (stage == rhi::ShaderStage::None) { stage = infer_stage_from_extension(path); if (stage == rhi::ShaderStage::None) { - result.error_message = "Could not infer shader stage from extension: " + path.string(); + result.error_message = + "Could not infer shader stage from extension: " + path_to_string(path); HZ_LOG_ERROR("{}", result.error_message); return result; } @@ -257,7 +281,7 @@ ShaderCompileResult ShaderCompiler::compile_file(const std::filesystem::path& pa // Set up compilation options ShaderCompileOptions options; options.source = source; - options.filename = path.string(); + options.filename = path_to_string(path); options.stage = stage; // Add common include paths diff --git a/engine/assets/shader_compiler.hpp b/engine/assets/shader_compiler.hpp index 338027b..db0896d 100644 --- a/engine/assets/shader_compiler.hpp +++ b/engine/assets/shader_compiler.hpp @@ -23,8 +23,8 @@ namespace hz { * @brief Options for shader compilation */ struct ShaderCompileOptions { - std::string_view source; ///< GLSL source code - std::string_view filename; ///< Filename for error messages (can be empty) + std::string_view source; ///< GLSL source code + std::string filename; ///< Filename for error messages (can be empty) rhi::ShaderStage stage{rhi::ShaderStage::None}; /// Macro definitions: {name, value} pairs diff --git a/engine/renderer/deferred_renderer.cpp b/engine/renderer/deferred_renderer.cpp index be6ec1d..57d5800 100644 --- a/engine/renderer/deferred_renderer.cpp +++ b/engine/renderer/deferred_renderer.cpp @@ -356,13 +356,9 @@ void SSAOPass::create(rhi::Device& device, u32 w, u32 h, const SSAOConfig& cfg, pl_desc.set_layouts = {&camera_layout, &gbuffer_layout, descriptor_layout.get()}; pipeline_layout = device.create_pipeline_layout(pl_desc); - // Create Pipeline (using temp RenderPass for compatibility) - auto rp_desc = rhi::RenderPassDesc::simple(rhi::Format::R8_UNORM); - auto temp_rp = device.create_render_pass(rp_desc); - rhi::GraphicsPipelineDesc pipe_desc{}; pipe_desc.layout = pipeline_layout.get(); - pipe_desc.render_pass = temp_rp.get(); + pipe_desc.color_attachment_formats = {rhi::Format::R8_UNORM}; rhi::VertexInputLayout item_layout; item_layout.bindings.push_back({0, 5 * sizeof(float), rhi::VertexInputRate::Vertex}); @@ -406,13 +402,9 @@ void SSAOPass::create(rhi::Device& device, u32 w, u32 h, const SSAOConfig& cfg, pl_desc.set_layouts = {blur_descriptor_layout.get()}; blur_layout = device.create_pipeline_layout(pl_desc); - // Pipeline - auto rp_desc = rhi::RenderPassDesc::simple(rhi::Format::R8_UNORM); - auto temp_rp = device.create_render_pass(rp_desc); - rhi::GraphicsPipelineDesc pipe_desc{}; pipe_desc.layout = blur_layout.get(); - pipe_desc.render_pass = temp_rp.get(); + pipe_desc.color_attachment_formats = {rhi::Format::R8_UNORM}; auto vs = device.create_shader_from_file("assets/shaders/deferred/vk_fullscreen.vert", rhi::ShaderStage::Vertex, "FullscreenVert"); @@ -1303,7 +1295,10 @@ void DeferredRenderer::create_pipelines() { desc.blend = rhi::BlendState::disabled(4); desc.multisample = {}; desc.layout = m_geometry_layout.get(); - desc.render_pass = m_geometry_pass.get(); + desc.color_attachment_formats = { + rhi::Format::RGBA16_FLOAT, rhi::Format::RGBA16_FLOAT, + rhi::Format::RGBA16_FLOAT, rhi::Format::RG16_FLOAT}; + desc.depth_attachment_format = rhi::Format::D32_FLOAT; m_geometry_pipeline = m_device.create_graphics_pipeline(desc); } @@ -1321,7 +1316,7 @@ void DeferredRenderer::create_pipelines() { desc.blend = rhi::BlendState::disabled(1); desc.multisample = {}; desc.layout = m_lighting_layout.get(); - desc.render_pass = m_lighting_pass.get(); + desc.color_attachment_formats = {rhi::Format::RGBA16_FLOAT}; m_lighting_pipeline = m_device.create_graphics_pipeline(desc); } @@ -1339,7 +1334,7 @@ void DeferredRenderer::create_pipelines() { desc.blend = rhi::BlendState::disabled(1); desc.multisample = {}; desc.layout = m_composite_layout.get(); - desc.render_pass = m_composite_pass.get(); + desc.color_attachment_formats = {m_swapchain.format()}; m_composite_pipeline = m_device.create_graphics_pipeline(desc); } @@ -1356,7 +1351,7 @@ void DeferredRenderer::create_pipelines() { desc.blend = rhi::BlendState::disabled(0); desc.multisample = {}; desc.layout = m_shadow_layout.get(); - desc.render_pass = m_shadow_pass.get(); + desc.depth_attachment_format = rhi::Format::D32_FLOAT; m_shadow_pipeline = m_device.create_graphics_pipeline(desc); } @@ -1387,6 +1382,10 @@ void DeferredRenderer::create_pipelines() { m_device.create_uniform_buffer(sizeof(glm::vec4) * 2 + sizeof(glm::uvec4), "LightUBO"); m_shadow_ubo = m_device.create_uniform_buffer(sizeof(DeferredShadowUBO), "ShadowUBO"); + if (m_camera_set && m_camera_ubo) { + m_camera_set->write_buffer(0, *m_camera_ubo); + } + // Create samplers { rhi::SamplerDesc desc; diff --git a/engine/renderer/ssao.cpp b/engine/renderer/ssao.cpp deleted file mode 100644 index 76990e5..0000000 --- a/engine/renderer/ssao.cpp +++ /dev/null @@ -1,225 +0,0 @@ -#include "ssao.hpp" - -#include "engine/core/log.hpp" -#include "engine/renderer/opengl/shader.hpp" -#include "engine/renderer/renderer.hpp" // For shader management if needed - -#include - -#include - -namespace hz { - -SSAO::~SSAO() { - destroy(); -} - -void SSAO::create(u32 width, u32 height, const SSAOConfig& cfg) { - config = cfg; - resize(width, height); - generate_kernel(); - generate_noise(); -} - -void SSAO::destroy() { - if (m_fbo) { - glDeleteFramebuffers(1, &m_fbo); - glDeleteTextures(1, &m_color_texture); - m_fbo = 0; - m_color_texture = 0; - } - if (m_blur_fbo) { - glDeleteFramebuffers(1, &m_blur_fbo); - glDeleteTextures(1, &m_blur_texture); - m_blur_fbo = 0; - m_blur_texture = 0; - } - if (m_noise_texture) { - glDeleteTextures(1, &m_noise_texture); - m_noise_texture = 0; - } -} - -void SSAO::resize(u32 width, u32 height) { - m_width = static_cast(width * config.resolution_scale); - m_height = static_cast(height * config.resolution_scale); - init_framebuffers(m_width, m_height); -} - -void SSAO::init_framebuffers(u32 width, u32 height) { - // Cleanup old FBOs if they exist (handling resize) - if (m_fbo) { - glDeleteFramebuffers(1, &m_fbo); - glDeleteTextures(1, &m_color_texture); - } - if (m_blur_fbo) { - glDeleteFramebuffers(1, &m_blur_fbo); - glDeleteTextures(1, &m_blur_texture); - } - - // SSAO FBO - glGenFramebuffers(1, &m_fbo); - glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); - - glGenTextures(1, &m_color_texture); - glBindTexture(GL_TEXTURE_2D, m_color_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, static_cast(width), static_cast(height), - 0, GL_RED, GL_FLOAT, nullptr); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_color_texture, 0); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - HZ_ENGINE_ERROR("SSAO FBO Incomplete"); - - // Blur FBO - glGenFramebuffers(1, &m_blur_fbo); - glBindFramebuffer(GL_FRAMEBUFFER, m_blur_fbo); - - glGenTextures(1, &m_blur_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, static_cast(width), static_cast(height), - 0, GL_RED, GL_FLOAT, nullptr); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_blur_texture, 0); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - HZ_ENGINE_ERROR("SSAO Blur FBO Incomplete"); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -void SSAO::generate_kernel() { - std::uniform_real_distribution random_floats(0.0, 1.0); - std::default_random_engine generator; - - m_kernel.clear(); - for (int i = 0; i < config.kernel_size; ++i) { - glm::vec3 sample(random_floats(generator) * 2.0 - 1.0, random_floats(generator) * 2.0 - 1.0, - random_floats(generator) // Hemisphere Z > 0 - ); - sample = glm::normalize(sample); - sample *= random_floats(generator); // Randomize length - - // Scale samples to cluster near origin (center of kernel) - float scale = float(i) / float(config.kernel_size); - scale = 0.1f + (scale * scale) * 0.9f; // Lerp: 0.1 -> 1.0 - sample *= scale; - - m_kernel.push_back(sample); - } -} - -void SSAO::generate_noise() { - std::uniform_real_distribution random_floats(0.0, 1.0); - std::default_random_engine generator; - - m_noise.clear(); - for (unsigned int i = 0; i < 16; i++) { - glm::vec3 noise(random_floats(generator) * 2.0 - 1.0, random_floats(generator) * 2.0 - 1.0, - 0.0f); - m_noise.push_back(noise); - } - - if (m_noise_texture) - glDeleteTextures(1, &m_noise_texture); - - glGenTextures(1, &m_noise_texture); - glBindTexture(GL_TEXTURE_2D, m_noise_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, 4, 4, 0, GL_RGB, GL_FLOAT, &m_noise[0]); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Repeat noise - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); -} - -void SSAO::render(u32 g_position, u32 g_normal, const glm::mat4& projection, - const gl::Shader& ssao_shader, const gl::Shader& blur_shader) { - if (!config.enabled) - return; - - // ------------------------------------------------------------------------ - // 1. SSAO Generation Pass - // ------------------------------------------------------------------------ - glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); - glClear(GL_COLOR_BUFFER_BIT); // Clear SSAO texture - - ssao_shader.bind(); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, g_position); // Depth texture (used for Pos reconstruction) - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_2D, g_normal); - glActiveTexture(GL_TEXTURE0 + 2); - glBindTexture(GL_TEXTURE_2D, m_noise_texture); - - // Uniforms - ssao_shader.set_int("u_g_depth", 0); - ssao_shader.set_int("u_g_normal", 1); - ssao_shader.set_int("u_tex_noise", 2); - - ssao_shader.set_mat4("u_projection", projection); - ssao_shader.set_mat4("u_inverse_projection", glm::inverse(projection)); - - for (int i = 0; i < config.kernel_size; ++i) { - ssao_shader.set_vec3("u_samples[" + std::to_string(i) + "]", m_kernel[i]); - } - - ssao_shader.set_float("u_radius", config.radius); - ssao_shader.set_float("u_bias", config.bias); - ssao_shader.set_vec2("u_noise_scale", glm::vec2(m_width / 4.0f, m_height / 4.0f)); - ssao_shader.set_int("u_kernel_size", config.kernel_size); - - render_quad(); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // ------------------------------------------------------------------------ - // 2. Blur Pass - // ------------------------------------------------------------------------ - glBindFramebuffer(GL_FRAMEBUFFER, m_blur_fbo); - glClear(GL_COLOR_BUFFER_BIT); - - blur_shader.bind(); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, m_color_texture); - blur_shader.set_int("u_ssao_input", 0); - - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_2D, g_position); // Depth texture - blur_shader.set_int("u_g_depth", 1); - - render_quad(); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -void SSAO::render_quad() { - if (m_quad_vao == 0) { - float quadVertices[] = { - // positions // texCoords - -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, - 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, - }; - // Setup plane VAO - glGenVertexArrays(1, &m_quad_vao); - glGenBuffers(1, &m_quad_vbo); - glBindVertexArray(m_quad_vao); - glBindBuffer(GL_ARRAY_BUFFER, m_quad_vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), - (void*)(3 * sizeof(float))); - } - glBindVertexArray(m_quad_vao); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glBindVertexArray(0); -} - -} // namespace hz diff --git a/engine/renderer/ssao.hpp b/engine/renderer/ssao.hpp deleted file mode 100644 index be820fd..0000000 --- a/engine/renderer/ssao.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include "engine/core/types.hpp" -#include "engine/renderer/opengl/shader.hpp" - -#include -#include - -#include - -namespace hz { - -struct SSAOConfig { - bool enabled{true}; - int kernel_size{64}; - float radius{0.5f}; - float bias{0.025f}; - float power{2.0f}; // Contrast power - float resolution_scale{0.5f}; // Render at half resolution for performance -}; - -class SSAO { -public: - SSAO() = default; - ~SSAO(); - - void create(u32 width, u32 height, const SSAOConfig& config = {}); - void destroy(); - - void resize(u32 width, u32 height); - - // Returns the texture ID of the blurred SSAO result - [[nodiscard]] u32 get_output_texture() const { return m_blur_texture; } - - // Main render function - void render(u32 g_position, u32 g_normal, const glm::mat4& projection, - const gl::Shader& ssao_shader, const gl::Shader& blur_shader); - - SSAOConfig config; - -private: - void generate_kernel(); - void generate_noise(); - void init_framebuffers(u32 width, u32 height); - void render_quad(); // Helper for quad rendering - - u32 m_fbo{0}; - u32 m_color_texture{0}; // Raw SSAO output - - u32 m_blur_fbo{0}; - u32 m_blur_texture{0}; // Blurred SSAO output - - u32 m_noise_texture{0}; - std::vector m_kernel; - std::vector m_noise; - - u32 m_quad_vao{0}; - u32 m_quad_vbo{0}; - - u32 m_width{0}; - u32 m_height{0}; - - // Shader programs (loaded from AssetManager or Renderer) - // For now we assume they are managed externally or identifiers stored here - // Note: In this architecture, we might want to store Shader instance or ID. - // We will use standard shader loading in implementation. -}; - -} // namespace hz diff --git a/engine/rhi/rhi_device.cpp b/engine/rhi/rhi_device.cpp index 552ebd5..4f5f378 100644 --- a/engine/rhi/rhi_device.cpp +++ b/engine/rhi/rhi_device.cpp @@ -3,8 +3,29 @@ #include "engine/assets/shader_compiler.hpp" #include "engine/core/log.hpp" +#ifdef _WIN32 +#include +#endif + namespace hz::rhi { +// Safe path-to-string conversion (same as in shader_compiler.cpp) +static std::string path_to_string(const std::filesystem::path& p) { +#ifdef _WIN32 + const auto& ws = p.native(); + if (ws.empty()) + return {}; + int size_needed = WideCharToMultiByte(CP_UTF8, 0, ws.data(), static_cast(ws.size()), + nullptr, 0, nullptr, nullptr); + std::string result(static_cast(size_needed), '\0'); + WideCharToMultiByte(CP_UTF8, 0, ws.data(), static_cast(ws.size()), result.data(), + size_needed, nullptr, nullptr); + return result; +#else + return p.string(); +#endif +} + std::unique_ptr Device::create_shader_from_glsl(std::string_view source, ShaderStage stage, const char* debug_name) { @@ -34,12 +55,13 @@ std::unique_ptr Device::create_shader_from_file(const std::filesys ShaderCompileResult result = ShaderCompiler::compile_file(path, stage); if (!result.success) { - HZ_LOG_ERROR("Failed to compile shader file '{}': {}", path.string(), result.error_message); + HZ_LOG_ERROR("Failed to compile shader file '{}': {}", path_to_string(path), + result.error_message); return nullptr; } // Use filename as debug name if not provided - std::string name = debug_name ? debug_name : path.filename().string(); + std::string name = debug_name ? debug_name : path_to_string(path.filename()); return create_shader_module({std::span(result.spirv), stage, "main", name.c_str()}); } diff --git a/engine/rhi/rhi_pipeline.hpp b/engine/rhi/rhi_pipeline.hpp index 7544982..bcba27c 100644 --- a/engine/rhi/rhi_pipeline.hpp +++ b/engine/rhi/rhi_pipeline.hpp @@ -550,6 +550,11 @@ struct GraphicsPipelineDesc { const RenderPass* render_pass{nullptr}; u32 subpass{0}; + // Dynamic rendering (VK_KHR_dynamic_rendering): used when render_pass is nullptr. + // Specify the attachment formats so the pipeline is compatible with vkCmdBeginRendering. + std::vector color_attachment_formats; + Format depth_attachment_format{Format::Unknown}; + const char* debug_name{nullptr}; }; diff --git a/engine/rhi/vulkan/vk_device.cpp b/engine/rhi/vulkan/vk_device.cpp index afe1515..72c6f2b 100644 --- a/engine/rhi/vulkan/vk_device.cpp +++ b/engine/rhi/vulkan/vk_device.cpp @@ -690,6 +690,13 @@ void VulkanDevice::end_frame() { // Reset fence for next use vkResetFences(m_device, 1, &frame.in_flight_fence); + // Signal the frame fence after all previously queued graphics work completes. + // This keeps begin_frame()/end_frame() synchronization valid even if callers + // submit their own per-frame fences through the higher-level RHI API. + VkSubmitInfo empty_submit{}; + empty_submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + VK_CHECK(vkQueueSubmit(m_graphics_queue, 1, &empty_submit, frame.in_flight_fence)); + // Advance frame m_current_frame = (m_current_frame + 1) % MAX_FRAMES_IN_FLIGHT; ++m_frame_number; diff --git a/engine/rhi/vulkan/vk_pipeline.cpp b/engine/rhi/vulkan/vk_pipeline.cpp index bc612b0..6f511e5 100644 --- a/engine/rhi/vulkan/vk_pipeline.cpp +++ b/engine/rhi/vulkan/vk_pipeline.cpp @@ -424,6 +424,10 @@ VulkanPipeline::VulkanPipeline(VulkanDevice& device, const GraphicsPipelineDesc& dynamic_state.dynamicStateCount = static_cast(dynamic_states.size()); dynamic_state.pDynamicStates = dynamic_states.empty() ? nullptr : dynamic_states.data(); + // Dynamic rendering support (VK_KHR_dynamic_rendering / Vulkan 1.3) + VkPipelineRenderingCreateInfo rendering_info{}; + std::vector vk_color_formats; + // Create pipeline VkGraphicsPipelineCreateInfo pipeline_info{}; pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; @@ -438,9 +442,27 @@ VulkanPipeline::VulkanPipeline(VulkanDevice& device, const GraphicsPipelineDesc& pipeline_info.pColorBlendState = &color_blend; pipeline_info.pDynamicState = dynamic_states.empty() ? nullptr : &dynamic_state; pipeline_info.layout = m_layout->layout(); - pipeline_info.renderPass = - static_cast(desc.render_pass)->render_pass(); - pipeline_info.subpass = desc.subpass; + + if (desc.render_pass) { + pipeline_info.renderPass = + static_cast(desc.render_pass)->render_pass(); + pipeline_info.subpass = desc.subpass; + } else { + for (auto fmt : desc.color_attachment_formats) { + vk_color_formats.push_back(to_vk_format(fmt)); + } + rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO; + rendering_info.colorAttachmentCount = static_cast(vk_color_formats.size()); + rendering_info.pColorAttachmentFormats = vk_color_formats.data(); + rendering_info.depthAttachmentFormat = + desc.depth_attachment_format != Format::Unknown + ? to_vk_format(desc.depth_attachment_format) + : VK_FORMAT_UNDEFINED; + rendering_info.stencilAttachmentFormat = VK_FORMAT_UNDEFINED; + + pipeline_info.renderPass = VK_NULL_HANDLE; + pipeline_info.pNext = &rendering_info; + } VkResult result = vkCreateGraphicsPipelines(m_device.device(), VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &m_pipeline); diff --git a/game/src/application.cpp b/game/src/application.cpp index 42608ce..83a5229 100644 --- a/game/src/application.cpp +++ b/game/src/application.cpp @@ -97,9 +97,13 @@ bool Application::init_renderer() { // 4. Initialize Synchronization primitives for (unsigned int i = 0; i < APP_MAX_FRAMES_IN_FLIGHT; ++i) { m_image_available_sems[i] = m_device->create_semaphore(); - m_render_finished_sems[i] = m_device->create_semaphore(); m_frame_fences[i] = m_device->create_fence(true); // Signaled initially } + m_render_finished_sems.clear(); + m_render_finished_sems.reserve(m_swapchain->image_count()); + for (hz::u32 i = 0; i < m_swapchain->image_count(); ++i) { + m_render_finished_sems.push_back(m_device->create_semaphore()); + } return true; } @@ -157,8 +161,7 @@ void Application::load_assets() { // Albedo (White) { hz::u8 data[4] = {200, 200, 200, 255}; - hz::Texture tex; - tex.create(1, 1, hz::TextureFormat::RGBA8, data); + hz::Texture tex = hz::Texture::create(1, 1, hz::TextureFormat::RGBA8, data); tex.upload_to_gpu(*m_device); m_albedo_handle = m_assets->register_texture(std::move(tex), "default_albedo"); } @@ -166,8 +169,7 @@ void Application::load_assets() { // Normal (Flat Z+) { hz::u8 data[4] = {128, 128, 255, 255}; - hz::Texture tex; - tex.create(1, 1, hz::TextureFormat::RGBA8, data); + hz::Texture tex = hz::Texture::create(1, 1, hz::TextureFormat::RGBA8, data); tex.upload_to_gpu(*m_device); m_normal_handle = m_assets->register_texture(std::move(tex), "default_normal"); } @@ -175,8 +177,7 @@ void Application::load_assets() { // ARM (AO=1, Roughness=0.8, Metallic=0.0) -> Plastic-like { hz::u8 data[4] = {255, 204, 0, 255}; - hz::Texture tex; - tex.create(1, 1, hz::TextureFormat::RGBA8, data); + hz::Texture tex = hz::Texture::create(1, 1, hz::TextureFormat::RGBA8, data); tex.upload_to_gpu(*m_device); m_arm_handle = m_assets->register_texture(std::move(tex), "default_arm"); } @@ -339,7 +340,6 @@ void Application::on_render([[maybe_unused]] float alpha) { hz::rhi::Fence* fences[] = {m_frame_fences[m_current_frame].get()}; m_device->wait_fences(fences); - m_device->reset_fences(fences); // Camera & Light setup std::vector point_lights; @@ -370,15 +370,29 @@ void Application::on_render([[maybe_unused]] float alpha) { m_device->wait_idle(); m_swapchain->resize(width, height); m_renderer->resize(width, height); + m_render_finished_sems.clear(); + m_render_finished_sems.reserve(m_swapchain->image_count()); + for (hz::u32 i = 0; i < m_swapchain->image_count(); ++i) { + m_render_finished_sems.push_back(m_device->create_semaphore()); + } } m_device->end_frame(); return; } + const hz::u32 image_index = m_swapchain->current_image_index(); + if (image_index >= m_render_finished_sems.size() || !m_render_finished_sems[image_index]) { + HZ_LOG_ERROR("Invalid swapchain image index for render semaphore: {}", image_index); + m_device->end_frame(); + return; + } + // 2. Get Command List auto cmd = m_device->create_command_list(hz::rhi::QueueType::Graphics); - if (!cmd) + if (!cmd) { + m_device->end_frame(); return; + } cmd->begin(); @@ -453,6 +467,9 @@ void Application::on_render([[maybe_unused]] float alpha) { m_renderer->end_geometry_pass(*cmd); + // === SSAO Pass === + m_renderer->execute_ssao_pass(*cmd, camera); + // === Lighting Pass === m_renderer->execute_lighting_pass(*cmd, camera, point_lights, spot_lights, sun_dir, sun_color); @@ -478,16 +495,17 @@ void Application::on_render([[maybe_unused]] float alpha) { submit_info.command_lists = {&cmd_ptr, 1}; hz::rhi::Semaphore* wait_sems[] = {m_image_available_sems[m_current_frame].get()}; - hz::rhi::Semaphore* signal_sems[] = {m_render_finished_sems[m_current_frame].get()}; + hz::rhi::Semaphore* signal_sems[] = {m_render_finished_sems[image_index].get()}; submit_info.wait_semaphores = wait_sems; submit_info.signal_semaphores = signal_sems; submit_info.signal_fence = m_frame_fences[m_current_frame].get(); + m_device->reset_fences(fences); m_device->submit(hz::rhi::QueueType::Graphics, {&submit_info, 1}); // 4. Present - hz::rhi::Semaphore* present_wait_sems[] = {m_render_finished_sems[m_current_frame].get()}; + hz::rhi::Semaphore* present_wait_sems[] = {m_render_finished_sems[image_index].get()}; m_swapchain->present(present_wait_sems); m_device->end_frame(); diff --git a/game/src/application.hpp b/game/src/application.hpp index a85e49d..2a58484 100644 --- a/game/src/application.hpp +++ b/game/src/application.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -120,7 +121,7 @@ class Application { unsigned int m_current_frame = 0; std::array, 2> m_image_available_sems; - std::array, 2> m_render_finished_sems; + std::vector> m_render_finished_sems; std::array, 2> m_frame_fences; // Previous frame data for TAA diff --git a/game/src/main.cpp b/game/src/main.cpp index 7e543f3..f00197b 100644 --- a/game/src/main.cpp +++ b/game/src/main.cpp @@ -8,11 +8,23 @@ #include "application.hpp" +#include #include +#include #include int main() { + // Fix for non-ASCII system locales (e.g., Turkish Windows OEM code page 857): + // MinGW's std::filesystem::path::string() uses the C locale for narrow char conversion, + // which can throw "Illegal byte sequence" if the locale doesn't support the characters. + std::setlocale(LC_ALL, "C"); + try { + std::locale::global(std::locale("C")); + } catch (...) { + // Fall back silently if locale setting fails + } + try { game::Application app;