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;