SleakEngine 0.1.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
VulkanDeferred.cpp
Go to the documentation of this file.
4
6#include <algorithm>
7#include <array>
8#include <cstring>
9#include <vector>
10#include "Core/Logger.hpp"
11#include "Camera/Camera.hpp"
12#include "Math/Matrix.hpp"
13
14namespace Sleak {
15 namespace RenderEngine {
16
17/// GBuffer attachment formats: RT0 AlbedoAO, RT1 NormalRough, RT2 MetalEmit.
18const VkFormat VulkanRenderer::m_gbufferFormats[VulkanRenderer::GBUFFER_COUNT] = {
19 VK_FORMAT_R8G8B8A8_UNORM, // RT0: AlbedoAO
20 VK_FORMAT_R16G16B16A16_SFLOAT, // RT1: NormalRough
21 VK_FORMAT_R16G16B16A16_SFLOAT, // RT2: MetalEmit (HDR emissive needs float)
22};
23
24/// Creates the GBuffer render pass with its three color attachments and depth.
25bool VulkanRenderer::CreateGBufferRenderPass() {
26 // Attachment 0-2: GBuffer color RTs (CLEAR → SHADER_READ_ONLY)
27 VkAttachmentDescription colorAtts[GBUFFER_COUNT] = {};
28 VkAttachmentReference colorRefs[GBUFFER_COUNT] = {};
29
30 for (uint32_t i = 0; i < GBUFFER_COUNT; ++i) {
31 colorAtts[i].format = m_gbufferFormats[i];
32 colorAtts[i].samples = VK_SAMPLE_COUNT_1_BIT;
33 colorAtts[i].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
34 colorAtts[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
35 colorAtts[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
36 colorAtts[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
37 colorAtts[i].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
38 colorAtts[i].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
39
40 colorRefs[i].attachment = i;
41 colorRefs[i].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
42 }
43
44 // Attachment 3: Depth (CLEAR → DEPTH_STENCIL_READ_ONLY)
45 VkAttachmentDescription depthAtt{};
46 depthAtt.format = depthFormat;
47 depthAtt.samples = VK_SAMPLE_COUNT_1_BIT;
48 depthAtt.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
49 depthAtt.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
50 depthAtt.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
51 depthAtt.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
52 depthAtt.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
53 depthAtt.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
54
55 VkAttachmentReference depthRef{};
56 depthRef.attachment = GBUFFER_COUNT; // depth slot follows the color RTs
57 depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
58
59 VkSubpassDescription subpass{};
60 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
61 subpass.colorAttachmentCount = GBUFFER_COUNT;
62 subpass.pColorAttachments = colorRefs;
63 subpass.pDepthStencilAttachment = &depthRef;
64
65 // Two external dependencies:
66 // 1. External → subpass (color attachment write)
67 // 2. Subpass → external (shader read in lighting pass)
68 std::array<VkSubpassDependency, 2> deps{};
69
70 deps[0].srcSubpass = VK_SUBPASS_EXTERNAL;
71 deps[0].dstSubpass = 0;
72 deps[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
73 deps[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
74 VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
75 deps[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
76 deps[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
77 VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
78 deps[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
79
80 deps[1].srcSubpass = 0;
81 deps[1].dstSubpass = VK_SUBPASS_EXTERNAL;
82 deps[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
83 VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
84 deps[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
85 deps[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
86 VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
87 deps[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
88 deps[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
89
90 std::array<VkAttachmentDescription, GBUFFER_COUNT + 1> attachments;
91 for (uint32_t i = 0; i < GBUFFER_COUNT; ++i) attachments[i] = colorAtts[i];
92 attachments[GBUFFER_COUNT] = depthAtt;
93
94 VkRenderPassCreateInfo rpInfo{};
95 rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
96 rpInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
97 rpInfo.pAttachments = attachments.data();
98 rpInfo.subpassCount = 1;
99 rpInfo.pSubpasses = &subpass;
100 rpInfo.dependencyCount = static_cast<uint32_t>(deps.size());
101 rpInfo.pDependencies = deps.data();
102
103 return vkCreateRenderPass(device, &rpInfo, nullptr, &m_gbufferRenderPass) == VK_SUCCESS;
104}
105
106/// Creates the GBuffer framebuffer binding the GBuffer images and depth.
107bool VulkanRenderer::CreateGBufferFramebuffer() {
108 std::array<VkImageView, GBUFFER_COUNT + 1> views;
109 for (uint32_t i = 0; i < GBUFFER_COUNT; ++i) views[i] = m_gbufferViews[i];
110 views[GBUFFER_COUNT] = depthImageView;
111
112 VkFramebufferCreateInfo fbInfo{};
113 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
114 fbInfo.renderPass = m_gbufferRenderPass;
115 fbInfo.attachmentCount = static_cast<uint32_t>(views.size());
116 fbInfo.pAttachments = views.data();
117 fbInfo.width = scExtent.width;
118 fbInfo.height = scExtent.height;
119 fbInfo.layers = 1;
120
121 return vkCreateFramebuffer(device, &fbInfo, nullptr, &m_gbufferFramebuffer) == VK_SUCCESS;
122}
123
124/// Compiles the GBuffer shaders and creates the geometry pipeline, reusing pipelineLay.
125bool VulkanRenderer::CreateGBufferPipeline() {
126 m_gbufferShader = new VulkanShader(device);
127 if (!m_gbufferShader->compile("assets/shaders/gbuffer.vert.spv",
128 "assets/shaders/gbuffer.frag.spv")) {
129 SLEAK_ERROR("GBuffer: Failed to compile gbuffer shaders!");
130 delete m_gbufferShader;
131 m_gbufferShader = nullptr;
132 return false;
133 }
134
135 VkPipelineShaderStageCreateInfo shaderStages[] = {
136 m_gbufferShader->GetVertexInfo(),
137 m_gbufferShader->GetFragInfo()
138 };
139
140 // Dynamic state
141 std::vector<VkDynamicState> dynamicStates = {
142 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
143 VkPipelineDynamicStateCreateInfo dynamicState{};
144 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
145 dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
146 dynamicState.pDynamicStates = dynamicStates.data();
147
148 // Vertex input — identical to main pipeline
149 VkVertexInputBindingDescription bindingDesc{};
150 bindingDesc.binding = 0;
151 bindingDesc.stride = sizeof(Vertex);
152 bindingDesc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
153
154 std::array<VkVertexInputAttributeDescription, 7> attrDescs{};
155 attrDescs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, px)};
156 attrDescs[1] = {1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, nx)};
157 attrDescs[2] = {2, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, tx)};
158 attrDescs[3] = {3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, r)};
159 attrDescs[4] = {4, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, u)};
160 attrDescs[5] = {5, 0, VK_FORMAT_R32G32B32A32_SINT, offsetof(Vertex, boneIDs)};
161 attrDescs[6] = {6, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, boneWeights)};
162
163 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
164 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
165 vertexInputInfo.vertexBindingDescriptionCount = 1;
166 vertexInputInfo.pVertexBindingDescriptions = &bindingDesc;
167 vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attrDescs.size());
168 vertexInputInfo.pVertexAttributeDescriptions = attrDescs.data();
169
170 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
171 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
172 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
173 inputAssembly.primitiveRestartEnable = VK_FALSE;
174
175 VkPipelineViewportStateCreateInfo viewportState{};
176 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
177 viewportState.viewportCount = 1;
178 viewportState.scissorCount = 1;
179
180 VkPipelineRasterizationStateCreateInfo rasterizer{};
181 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
182 rasterizer.depthClampEnable = VK_FALSE;
183 rasterizer.rasterizerDiscardEnable = VK_FALSE;
184 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
185 rasterizer.lineWidth = 1.0f;
186 rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
187 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
188 rasterizer.depthBiasEnable = VK_FALSE;
189
190 VkPipelineMultisampleStateCreateInfo msaa{};
191 msaa.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
192 msaa.sampleShadingEnable = VK_FALSE;
193 msaa.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; // GBuffer is always 1 sample
194
195 VkPipelineDepthStencilStateCreateInfo depthStencil{};
196 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
197 depthStencil.depthTestEnable = VK_TRUE;
198 depthStencil.depthWriteEnable = VK_TRUE;
199 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
200 depthStencil.depthBoundsTestEnable = VK_FALSE;
201 depthStencil.stencilTestEnable = VK_FALSE;
202
203 // GBUFFER_COUNT color blend attachments — opaque, no blending
204 VkPipelineColorBlendAttachmentState opaqueBlend{};
205 opaqueBlend.blendEnable = VK_FALSE;
206 opaqueBlend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
207 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
208
209 std::array<VkPipelineColorBlendAttachmentState, GBUFFER_COUNT> colorBlendAtts;
210 colorBlendAtts.fill(opaqueBlend);
211
212 VkPipelineColorBlendStateCreateInfo colorBlendInfo{};
213 colorBlendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
214 colorBlendInfo.logicOpEnable = VK_FALSE;
215 colorBlendInfo.attachmentCount = static_cast<uint32_t>(colorBlendAtts.size());
216 colorBlendInfo.pAttachments = colorBlendAtts.data();
217
218 // Use the dedicated GBuffer geometry layout (PBR material DSL at set 0)
219 m_gbufferPipelineLayout = m_gbufferGeomLayout;
220
221 VkGraphicsPipelineCreateInfo pipelineInfo{};
222 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
223 pipelineInfo.stageCount = 2;
224 pipelineInfo.pStages = shaderStages;
225 pipelineInfo.pVertexInputState = &vertexInputInfo;
226 pipelineInfo.pInputAssemblyState = &inputAssembly;
227 pipelineInfo.pViewportState = &viewportState;
228 pipelineInfo.pRasterizationState = &rasterizer;
229 pipelineInfo.pMultisampleState = &msaa;
230 pipelineInfo.pDepthStencilState = &depthStencil;
231 pipelineInfo.pColorBlendState = &colorBlendInfo;
232 pipelineInfo.pDynamicState = &dynamicState;
233 pipelineInfo.layout = m_gbufferPipelineLayout;
234 pipelineInfo.renderPass = m_gbufferRenderPass;
235 pipelineInfo.subpass = 0;
236 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
237 pipelineInfo.basePipelineIndex = -1;
238
239 VkResult result = vkCreateGraphicsPipelines(
240 device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &m_gbufferPipeline);
241 if (result != VK_SUCCESS) {
242 SLEAK_ERROR("GBuffer: Failed to create GBuffer pipeline!");
243 return false;
244 }
245
246 SLEAK_INFO("VulkanRenderer: GBuffer pipeline created");
247 return true;
248}
249
250/// Compiles the skinned GBuffer shaders so skinned meshes write into the GBuffer.
251bool VulkanRenderer::CreateSkinnedGbufferPipeline() {
252 // Load skinned vert + gbuffer frag (SPIR-V already on disk)
253 VulkanShader* sh = new VulkanShader(device);
254 if (!sh->compile("assets/shaders/skinned_shader.vert.spv",
255 "assets/shaders/gbuffer.frag.spv")) {
256 SLEAK_ERROR("GBuffer: Failed to compile skinned gbuffer shaders!");
257 delete sh;
258 return false;
259 }
260
261 VkPipelineShaderStageCreateInfo stages[] = {
262 sh->GetVertexInfo(), sh->GetFragInfo()
263 };
264
265 std::vector<VkDynamicState> dynStates = {
266 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
267 VkPipelineDynamicStateCreateInfo dynState{};
268 dynState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
269 dynState.dynamicStateCount = static_cast<uint32_t>(dynStates.size());
270 dynState.pDynamicStates = dynStates.data();
271
272 // 7-attribute vertex layout (identical to static GBuffer pipeline)
273 VkVertexInputBindingDescription bindDesc{};
274 bindDesc.binding = 0;
275 bindDesc.stride = sizeof(Vertex);
276 bindDesc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
277
278 std::array<VkVertexInputAttributeDescription, 7> attrs{};
279 attrs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, px)};
280 attrs[1] = {1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, nx)};
281 attrs[2] = {2, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, tx)};
282 attrs[3] = {3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, r)};
283 attrs[4] = {4, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, u)};
284 attrs[5] = {5, 0, VK_FORMAT_R32G32B32A32_SINT, offsetof(Vertex, boneIDs)};
285 attrs[6] = {6, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, boneWeights)};
286
287 VkPipelineVertexInputStateCreateInfo vi{};
288 vi.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
289 vi.vertexBindingDescriptionCount = 1;
290 vi.pVertexBindingDescriptions = &bindDesc;
291 vi.vertexAttributeDescriptionCount = static_cast<uint32_t>(attrs.size());
292 vi.pVertexAttributeDescriptions = attrs.data();
293
294 VkPipelineInputAssemblyStateCreateInfo ia{};
295 ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
296 ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
297
298 VkPipelineViewportStateCreateInfo vps{};
299 vps.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
300 vps.viewportCount = 1;
301 vps.scissorCount = 1;
302
303 VkPipelineRasterizationStateCreateInfo rast{};
304 rast.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
305 rast.polygonMode = VK_POLYGON_MODE_FILL;
306 rast.lineWidth = 1.0f;
307 rast.cullMode = VK_CULL_MODE_BACK_BIT;
308 rast.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
309
310 VkPipelineMultisampleStateCreateInfo ms{};
311 ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
312 ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
313
314 VkPipelineDepthStencilStateCreateInfo ds{};
315 ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
316 ds.depthTestEnable = VK_TRUE;
317 ds.depthWriteEnable = VK_TRUE;
318 ds.depthCompareOp = VK_COMPARE_OP_LESS;
319
320 VkPipelineColorBlendAttachmentState opaqueBlend{};
321 opaqueBlend.blendEnable = VK_FALSE;
322 opaqueBlend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
323 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
324
325 std::array<VkPipelineColorBlendAttachmentState, GBUFFER_COUNT> blendAtts;
326 blendAtts.fill(opaqueBlend);
327
328 VkPipelineColorBlendStateCreateInfo cb{};
329 cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
330 cb.attachmentCount = static_cast<uint32_t>(blendAtts.size());
331 cb.pAttachments = blendAtts.data();
332
333 VkGraphicsPipelineCreateInfo pi{};
334 pi.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
335 pi.stageCount = 2;
336 pi.pStages = stages;
337 pi.pVertexInputState = &vi;
338 pi.pInputAssemblyState = &ia;
339 pi.pViewportState = &vps;
340 pi.pRasterizationState = &rast;
341 pi.pMultisampleState = &ms;
342 pi.pDepthStencilState = &ds;
343 pi.pColorBlendState = &cb;
344 pi.pDynamicState = &dynState;
345 pi.layout = m_gbufferGeomLayout;
346 pi.renderPass = m_gbufferRenderPass;
347 pi.subpass = 0;
348 pi.basePipelineHandle = VK_NULL_HANDLE;
349 pi.basePipelineIndex = -1;
350
351 VkResult res = vkCreateGraphicsPipelines(
352 device, VK_NULL_HANDLE, 1, &pi, nullptr, &m_skinnedGbufferPipeline);
353 delete sh;
354 if (res != VK_SUCCESS) {
355 SLEAK_ERROR("GBuffer: Failed to create skinned GBuffer pipeline!");
356 return false;
357 }
358
359 SLEAK_INFO("VulkanRenderer: Skinned GBuffer pipeline created");
360 return true;
361}
362
363/// Creates the deferred lighting render pass with a single color attachment.
364bool VulkanRenderer::CreateLightingRenderPass() {
365 VkAttachmentDescription colorAtt{};
366 // Target the HDR scene color image (R16G16B16A16_SFLOAT) so the lighting
367 // pass outputs linear HDR. The composite pass later tonemaps + bloom-blends.
368 colorAtt.format = m_hdrSceneFormat;
369 colorAtt.samples = VK_SAMPLE_COUNT_1_BIT;
370 colorAtt.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
371 colorAtt.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
372 colorAtt.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
373 colorAtt.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
374 colorAtt.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
375 colorAtt.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
376
377 VkAttachmentReference colorRef{};
378 colorRef.attachment = 0;
379 colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
380
381 VkSubpassDescription subpass{};
382 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
383 subpass.colorAttachmentCount = 1;
384 subpass.pColorAttachments = &colorRef;
385
386 std::array<VkSubpassDependency, 2> deps{};
387 deps[0].srcSubpass = VK_SUBPASS_EXTERNAL;
388 deps[0].dstSubpass = 0;
389 deps[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
390 deps[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
391 deps[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
392 deps[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
393 deps[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
394
395 deps[1].srcSubpass = 0;
396 deps[1].dstSubpass = VK_SUBPASS_EXTERNAL;
397 deps[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
398 deps[1].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
399 deps[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
400 deps[1].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
401 deps[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
402
403 VkRenderPassCreateInfo rpInfo{};
404 rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
405 rpInfo.attachmentCount = 1;
406 rpInfo.pAttachments = &colorAtt;
407 rpInfo.subpassCount = 1;
408 rpInfo.pSubpasses = &subpass;
409 rpInfo.dependencyCount = static_cast<uint32_t>(deps.size());
410 rpInfo.pDependencies = deps.data();
411
412 return vkCreateRenderPass(device, &rpInfo, nullptr, &m_lightingRenderPass) == VK_SUCCESS;
413}
414
415/// Creates one lighting pass framebuffer per swapchain image, all aliasing the HDR target.
416bool VulkanRenderer::CreateLightingFramebuffers() {
417 if (m_hdrSceneView == VK_NULL_HANDLE) {
418 SLEAK_ERROR("GBuffer: HDR scene image view is null when creating lighting framebuffers!");
419 return false;
420 }
421
422 VkFramebufferCreateInfo fbInfo{};
423 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
424 fbInfo.renderPass = m_lightingRenderPass;
425 fbInfo.attachmentCount = 1;
426 fbInfo.pAttachments = &m_hdrSceneView;
427 fbInfo.width = scExtent.width;
428 fbInfo.height = scExtent.height;
429 fbInfo.layers = 1;
430
431 m_lightingFramebuffers.resize(swapChainImageViews.size());
432 for (size_t i = 0; i < swapChainImageViews.size(); ++i) {
433 if (vkCreateFramebuffer(device, &fbInfo, nullptr, &m_lightingFramebuffers[i]) != VK_SUCCESS) {
434 SLEAK_ERROR("GBuffer: Failed to create lighting framebuffer {}!", i);
435 return false;
436 }
437 }
438 return true;
439}
440
441/// Compiles the lighting shaders and creates the fullscreen lighting pipeline.
442bool VulkanRenderer::CreateLightingPipeline() {
443 m_lightingShader = new VulkanShader(device);
444 if (!m_lightingShader->compile("assets/shaders/lighting_pass.vert.spv",
445 "assets/shaders/lighting_pass.frag.spv")) {
446 SLEAK_ERROR("GBuffer: Failed to compile lighting pass shaders!");
447 delete m_lightingShader;
448 m_lightingShader = nullptr;
449 return false;
450 }
451
452 VkPipelineShaderStageCreateInfo shaderStages[] = {
453 m_lightingShader->GetVertexInfo(),
454 m_lightingShader->GetFragInfo()
455 };
456
457 // No vertex input — fullscreen triangle from gl_VertexIndex
458 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
459 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
460
461 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
462 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
463 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
464 inputAssembly.primitiveRestartEnable = VK_FALSE;
465
466 std::vector<VkDynamicState> dynamicStates = {
467 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
468 VkPipelineDynamicStateCreateInfo dynamicState{};
469 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
470 dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
471 dynamicState.pDynamicStates = dynamicStates.data();
472
473 VkPipelineViewportStateCreateInfo viewportState{};
474 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
475 viewportState.viewportCount = 1;
476 viewportState.scissorCount = 1;
477
478 VkPipelineRasterizationStateCreateInfo rasterizer{};
479 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
480 rasterizer.depthClampEnable = VK_FALSE;
481 rasterizer.rasterizerDiscardEnable = VK_FALSE;
482 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
483 rasterizer.lineWidth = 1.0f;
484 rasterizer.cullMode = VK_CULL_MODE_NONE;
485 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
486 rasterizer.depthBiasEnable = VK_FALSE;
487
488 VkPipelineMultisampleStateCreateInfo msaa{};
489 msaa.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
490 msaa.sampleShadingEnable = VK_FALSE;
491 msaa.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
492
493 // Depth test OFF, depth write OFF
494 VkPipelineDepthStencilStateCreateInfo depthStencil{};
495 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
496 depthStencil.depthTestEnable = VK_FALSE;
497 depthStencil.depthWriteEnable = VK_FALSE;
498 depthStencil.stencilTestEnable = VK_FALSE;
499
500 VkPipelineColorBlendAttachmentState colorBlendAtt{};
501 colorBlendAtt.blendEnable = VK_FALSE;
502 colorBlendAtt.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
503 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
504
505 VkPipelineColorBlendStateCreateInfo colorBlendInfo{};
506 colorBlendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
507 colorBlendInfo.logicOpEnable = VK_FALSE;
508 colorBlendInfo.attachmentCount = 1;
509 colorBlendInfo.pAttachments = &colorBlendAtt;
510
511 // Build lighting pipeline layout:
512 // Set 0: m_gbufferSamplerDSL (7 combined image samplers: GBuffer RTs + shadow maps)
513 // Set 1: m_deferredCBDSL (1 UBO: InvViewProj + screen size)
514 // Set 2: m_lightUBODescriptorSetLayout (1 UBO: directional light + shadow + fog)
515 // Set 3: m_iblDSL (3 samplerCubes + 1 sampler2D + 1 UBO: IBL)
516 std::array<VkDescriptorSetLayout, 4> setLayouts = {
517 m_gbufferSamplerDSL,
518 m_deferredCBDSL,
519 m_lightUBODescriptorSetLayout,
520 m_iblDSL
521 };
522
523 VkPipelineLayoutCreateInfo layoutInfo{};
524 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
525 layoutInfo.setLayoutCount = static_cast<uint32_t>(setLayouts.size());
526 layoutInfo.pSetLayouts = setLayouts.data();
527
528 if (vkCreatePipelineLayout(device, &layoutInfo, nullptr, &m_lightingPipelineLayout) != VK_SUCCESS) {
529 SLEAK_ERROR("GBuffer: Failed to create lighting pipeline layout!");
530 return false;
531 }
532
533 VkGraphicsPipelineCreateInfo pipelineInfo{};
534 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
535 pipelineInfo.stageCount = 2;
536 pipelineInfo.pStages = shaderStages;
537 pipelineInfo.pVertexInputState = &vertexInputInfo;
538 pipelineInfo.pInputAssemblyState = &inputAssembly;
539 pipelineInfo.pViewportState = &viewportState;
540 pipelineInfo.pRasterizationState = &rasterizer;
541 pipelineInfo.pMultisampleState = &msaa;
542 pipelineInfo.pDepthStencilState = &depthStencil;
543 pipelineInfo.pColorBlendState = &colorBlendInfo;
544 pipelineInfo.pDynamicState = &dynamicState;
545 pipelineInfo.layout = m_lightingPipelineLayout;
546 pipelineInfo.renderPass = m_lightingRenderPass;
547 pipelineInfo.subpass = 0;
548 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
549 pipelineInfo.basePipelineIndex = -1;
550
551 VkResult result = vkCreateGraphicsPipelines(
552 device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &m_lightingPipeline);
553 if (result != VK_SUCCESS) {
554 SLEAK_ERROR("GBuffer: Failed to create lighting pipeline!");
555 return false;
556 }
557
558 SLEAK_INFO("VulkanRenderer: Lighting pass pipeline created");
559 return true;
560}
561
562/// Creates the forward transparent render pass writing into the HDR scene image.
563bool VulkanRenderer::CreateForwardRenderPass() {
564 VkAttachmentDescription colorAtt{};
565 colorAtt.format = m_hdrSceneFormat;
566 colorAtt.samples = VK_SAMPLE_COUNT_1_BIT;
567 colorAtt.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
568 colorAtt.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
569 colorAtt.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
570 colorAtt.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
571 colorAtt.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
572 colorAtt.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
573
574 VkAttachmentDescription depthAtt{};
575 depthAtt.format = depthFormat;
576 depthAtt.samples = VK_SAMPLE_COUNT_1_BIT;
577 depthAtt.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
578 depthAtt.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
579 depthAtt.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
580 depthAtt.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
581 depthAtt.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
582 depthAtt.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
583
584 VkAttachmentReference colorRef{};
585 colorRef.attachment = 0;
586 colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
587
588 VkAttachmentReference depthRef{};
589 depthRef.attachment = 1;
590 depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
591
592 VkSubpassDescription subpass{};
593 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
594 subpass.colorAttachmentCount = 1;
595 subpass.pColorAttachments = &colorRef;
596 subpass.pDepthStencilAttachment = &depthRef;
597
598 VkSubpassDependency dep{};
599 dep.srcSubpass = VK_SUBPASS_EXTERNAL;
600 dep.dstSubpass = 0;
601 dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
602 dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
603 VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
604 dep.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
605 dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
606 VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
607
608 std::array<VkAttachmentDescription, 2> attachments = {colorAtt, depthAtt};
609
610 VkRenderPassCreateInfo rpInfo{};
611 rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
612 rpInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
613 rpInfo.pAttachments = attachments.data();
614 rpInfo.subpassCount = 1;
615 rpInfo.pSubpasses = &subpass;
616 rpInfo.dependencyCount = 1;
617 rpInfo.pDependencies = &dep;
618
619 return vkCreateRenderPass(device, &rpInfo, nullptr, &m_forwardRenderPass) == VK_SUCCESS;
620}
621
622/// Creates one forward transparent framebuffer per swapchain image, all aliasing the HDR target.
623bool VulkanRenderer::CreateForwardFramebuffers() {
624 if (m_hdrSceneView == VK_NULL_HANDLE) {
625 SLEAK_ERROR("GBuffer: HDR scene image view is null when creating forward framebuffers!");
626 return false;
627 }
628
629 m_forwardFramebuffers.resize(swapChainImageViews.size());
630
631 for (size_t i = 0; i < swapChainImageViews.size(); ++i) {
632 std::array<VkImageView, 2> views = {m_hdrSceneView, depthImageView};
633
634 VkFramebufferCreateInfo fbInfo{};
635 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
636 fbInfo.renderPass = m_forwardRenderPass;
637 fbInfo.attachmentCount = static_cast<uint32_t>(views.size());
638 fbInfo.pAttachments = views.data();
639 fbInfo.width = scExtent.width;
640 fbInfo.height = scExtent.height;
641 fbInfo.layers = 1;
642
643 if (vkCreateFramebuffer(device, &fbInfo, nullptr, &m_forwardFramebuffers[i]) != VK_SUCCESS) {
644 SLEAK_ERROR("GBuffer: Failed to create forward framebuffer {}!", i);
645 return false;
646 }
647 }
648 return true;
649}
650
651/// Creates the GBuffer sampler descriptor set layout, pool, and per-frame sets.
652bool VulkanRenderer::CreateGBufferDescriptorSets() {
653 // Create GBuffer sampler (nearest for encoded data reads in lighting pass)
654 VkSamplerCreateInfo samplerInfo{};
655 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
656 samplerInfo.magFilter = VK_FILTER_NEAREST;
657 samplerInfo.minFilter = VK_FILTER_NEAREST;
658 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
659 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
660 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
661 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
662 samplerInfo.minLod = 0.0f;
663 samplerInfo.maxLod = 1.0f;
664
665 if (vkCreateSampler(device, &samplerInfo, nullptr, &m_gbufferSampler) != VK_SUCCESS) {
666 SLEAK_ERROR("GBuffer: Failed to create gbuffer sampler!");
667 return false;
668 }
669
670 // Depth sampler — nearest, no comparison (we read raw depth to reconstruct position)
671 VkSamplerCreateInfo depthSamplerInfo{};
672 depthSamplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
673 depthSamplerInfo.magFilter = VK_FILTER_NEAREST;
674 depthSamplerInfo.minFilter = VK_FILTER_NEAREST;
675 depthSamplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
676 depthSamplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
677 depthSamplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
678 depthSamplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
679 depthSamplerInfo.minLod = 0.0f;
680 depthSamplerInfo.maxLod = 1.0f;
681
682 if (vkCreateSampler(device, &depthSamplerInfo, nullptr, &m_depthSampler) != VK_SUCCESS) {
683 SLEAK_ERROR("GBuffer: Failed to create depth sampler!");
684 return false;
685 }
686
687 // DSL for set 0 of lighting pass:
688 // binding 0: RT0, 1: RT1, 2: RT2, 3: depth,
689 // binding 4: shadow compare sampler (hardware PCF),
690 // binding 5: shadow raw sampler (PCSS blocker search)
691 // binding 6: screen-space AO (R8, bilateral blurred)
692 // (world position is reconstructed from depth binding 3 + InvViewProj)
693 std::array<VkDescriptorSetLayoutBinding, 7> bindings{};
694 for (uint32_t b = 0; b < 7; ++b) {
695 bindings[b].binding = b;
696 bindings[b].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
697 bindings[b].descriptorCount = 1;
698 bindings[b].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
699 }
700
701 VkDescriptorSetLayoutCreateInfo dslInfo{};
702 dslInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
703 dslInfo.bindingCount = static_cast<uint32_t>(bindings.size());
704 dslInfo.pBindings = bindings.data();
705
706 if (vkCreateDescriptorSetLayout(device, &dslInfo, nullptr, &m_gbufferSamplerDSL) != VK_SUCCESS) {
707 SLEAK_ERROR("GBuffer: Failed to create gbuffer sampler DSL!");
708 return false;
709 }
710
711 // Pool: 7 samplers × MAX_FRAMES_IN_FLIGHT sets
712 VkDescriptorPoolSize poolSize{};
713 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
714 poolSize.descriptorCount = 7 * MAX_FRAMES_IN_FLIGHT;
715
716 VkDescriptorPoolCreateInfo poolInfo{};
717 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
718 poolInfo.poolSizeCount = 1;
719 poolInfo.pPoolSizes = &poolSize;
720 poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT;
721
722 if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &m_gbufferSamplerPool) != VK_SUCCESS) {
723 SLEAK_ERROR("GBuffer: Failed to create gbuffer sampler pool!");
724 return false;
725 }
726
727 std::array<VkDescriptorSetLayout, MAX_FRAMES_IN_FLIGHT> layouts;
728 layouts.fill(m_gbufferSamplerDSL);
729
730 VkDescriptorSetAllocateInfo allocInfo{};
731 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
732 allocInfo.descriptorPool = m_gbufferSamplerPool;
733 allocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT;
734 allocInfo.pSetLayouts = layouts.data();
735
736 if (vkAllocateDescriptorSets(device, &allocInfo, m_gbufferSamplerSets.data()) != VK_SUCCESS) {
737 SLEAK_ERROR("GBuffer: Failed to allocate gbuffer sampler descriptor sets!");
738 return false;
739 }
740
741 // Initial write — no rendering is in flight yet, so update all frames.
742 // After this, per-frame updates happen in UpdateGBufferDescriptors().
743 for (uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; ++f) {
744 uint32_t saved = currentFrame;
745 currentFrame = f;
746 UpdateGBufferDescriptors();
747 currentFrame = saved;
748 }
749
750 return true;
751}
752
753/// Writes the GBuffer, depth, and shadow images into the sampler descriptor sets before the lighting pass.
754void VulkanRenderer::UpdateGBufferDescriptors() {
755 // Only update the descriptor set for the current frame slot.
756 // BeginRender() already waited on this frame's fence, so its
757 // descriptor set is safe to update. Updating other slots would
758 // race with the GPU still consuming them.
759 uint32_t f = currentFrame;
760 std::array<VkDescriptorImageInfo, 7> imageInfos{};
761
762 // RT0..RT2 (AlbedoAO, NormalRough, MetalEmit)
763 for (uint32_t i = 0; i < GBUFFER_COUNT; ++i) {
764 imageInfos[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
765 imageInfos[i].imageView = m_gbufferViews[i];
766 imageInfos[i].sampler = m_gbufferSampler;
767 }
768
769 // Depth (binding 3) — used to reconstruct world position
770 imageInfos[3].imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
771 imageInfos[3].imageView = depthImageView;
772 imageInfos[3].sampler = m_depthSampler;
773
774 // Shadow map (binding 4) — compare sampler for hardware PCF.
775 // Depth images must use DEPTH_STENCIL_READ_ONLY_OPTIMAL (not SHADER_READ_ONLY_OPTIMAL)
776 // when accessed as a sampler; using the wrong layout causes VK_ERROR_DEVICE_LOST.
777 // Fall back to the default 1x1 white texture when no shadow map is available.
778 if (m_shadowImageView) {
779 imageInfos[4].imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
780 imageInfos[4].imageView = m_shadowImageView;
781 imageInfos[4].sampler = m_shadowSampler ? m_shadowSampler
782 : (m_defaultTexture ? m_defaultTexture->GetSampler() : VK_NULL_HANDLE);
783 } else if (m_defaultTexture) {
784 imageInfos[4].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
785 imageInfos[4].imageView = m_defaultTexture->GetImageView();
786 imageInfos[4].sampler = m_defaultTexture->GetSampler();
787 }
788
789 // Shadow map raw (binding 5) — non-compare sampler for PCSS blocker search
790 if (m_shadowImageView) {
791 imageInfos[5].imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
792 imageInfos[5].imageView = m_shadowImageView;
793 imageInfos[5].sampler = m_shadowRawSampler ? m_shadowRawSampler
794 : (m_defaultTexture ? m_defaultTexture->GetSampler() : VK_NULL_HANDLE);
795 } else if (m_defaultTexture) {
796 imageInfos[5].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
797 imageInfos[5].imageView = m_defaultTexture->GetImageView();
798 imageInfos[5].sampler = m_defaultTexture->GetSampler();
799 }
800
801 // SSAO (binding 6) — fallback to default white texture when SSAO isn't ready,
802 // so the lighting shader multiplies by 1.0 (no occlusion) as a safe default.
803 if (m_ssaoBlurView != VK_NULL_HANDLE && m_ssaoSampler != VK_NULL_HANDLE) {
804 imageInfos[6].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
805 imageInfos[6].imageView = m_ssaoBlurView;
806 imageInfos[6].sampler = m_ssaoSampler;
807 } else if (m_defaultTexture) {
808 imageInfos[6].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
809 imageInfos[6].imageView = m_defaultTexture->GetImageView();
810 imageInfos[6].sampler = m_defaultTexture->GetSampler();
811 }
812
813 std::array<VkWriteDescriptorSet, 7> writes{};
814 for (uint32_t b = 0; b < 7; ++b) {
815 writes[b].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
816 writes[b].dstSet = m_gbufferSamplerSets[f];
817 writes[b].dstBinding = b;
818 writes[b].dstArrayElement = 0;
819 writes[b].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
820 writes[b].descriptorCount = 1;
821 writes[b].pImageInfo = &imageInfos[b];
822 }
823 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()),
824 writes.data(), 0, nullptr);
825}
826
827/// Creates the per-frame deferred constant buffer holding InvViewProj and screen size.
828bool VulkanRenderer::CreateDeferredCBResources() {
829 if (m_deferredCBCreated) return true;
830
831 static constexpr VkDeviceSize uboSize = sizeof(DeferredCBData);
832
833 // Per-frame UBO buffers
834 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
835 VkBufferCreateInfo bufInfo{};
836 bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
837 bufInfo.size = uboSize;
838 bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
839 bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
840
841 if (vkCreateBuffer(device, &bufInfo, nullptr, &m_deferredCBBuffers[i]) != VK_SUCCESS) {
842 SLEAK_ERROR("GBuffer: Failed to create deferred CB buffer!");
843 return false;
844 }
845
846 VkMemoryRequirements memReqs;
847 vkGetBufferMemoryRequirements(device, m_deferredCBBuffers[i], &memReqs);
848
849 VkMemoryAllocateInfo allocInfo{};
850 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
851 allocInfo.allocationSize = memReqs.size;
852 allocInfo.memoryTypeIndex = FindMemoryType(memReqs.memoryTypeBits,
853 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
854
855 if (vkAllocateMemory(device, &allocInfo, nullptr, &m_deferredCBMemory[i]) != VK_SUCCESS) {
856 SLEAK_ERROR("GBuffer: Failed to allocate deferred CB memory!");
857 return false;
858 }
859
860 vkBindBufferMemory(device, m_deferredCBBuffers[i], m_deferredCBMemory[i], 0);
861 vkMapMemory(device, m_deferredCBMemory[i], 0, uboSize, 0, &m_deferredCBMapped[i]);
862 memset(m_deferredCBMapped[i], 0, uboSize);
863 }
864
865 // DSL: binding 0 = uniform buffer
866 VkDescriptorSetLayoutBinding uboBinding{};
867 uboBinding.binding = 0;
868 uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
869 uboBinding.descriptorCount = 1;
870 uboBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
871
872 VkDescriptorSetLayoutCreateInfo dslInfo{};
873 dslInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
874 dslInfo.bindingCount = 1;
875 dslInfo.pBindings = &uboBinding;
876
877 if (vkCreateDescriptorSetLayout(device, &dslInfo, nullptr, &m_deferredCBDSL) != VK_SUCCESS) {
878 SLEAK_ERROR("GBuffer: Failed to create deferred CB DSL!");
879 return false;
880 }
881
882 VkDescriptorPoolSize poolSize{};
883 poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
884 poolSize.descriptorCount = MAX_FRAMES_IN_FLIGHT;
885
886 VkDescriptorPoolCreateInfo poolInfo{};
887 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
888 poolInfo.poolSizeCount = 1;
889 poolInfo.pPoolSizes = &poolSize;
890 poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT;
891
892 if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &m_deferredCBPool) != VK_SUCCESS) {
893 SLEAK_ERROR("GBuffer: Failed to create deferred CB pool!");
894 return false;
895 }
896
897 std::array<VkDescriptorSetLayout, MAX_FRAMES_IN_FLIGHT> layouts;
898 layouts.fill(m_deferredCBDSL);
899
900 VkDescriptorSetAllocateInfo dsAllocInfo{};
901 dsAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
902 dsAllocInfo.descriptorPool = m_deferredCBPool;
903 dsAllocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT;
904 dsAllocInfo.pSetLayouts = layouts.data();
905
906 if (vkAllocateDescriptorSets(device, &dsAllocInfo, m_deferredCBSets.data()) != VK_SUCCESS) {
907 SLEAK_ERROR("GBuffer: Failed to allocate deferred CB descriptor sets!");
908 return false;
909 }
910
911 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
912 VkDescriptorBufferInfo bufInfo{};
913 bufInfo.buffer = m_deferredCBBuffers[i];
914 bufInfo.offset = 0;
915 bufInfo.range = uboSize;
916
917 VkWriteDescriptorSet write{};
918 write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
919 write.dstSet = m_deferredCBSets[i];
920 write.dstBinding = 0;
921 write.dstArrayElement = 0;
922 write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
923 write.descriptorCount = 1;
924 write.pBufferInfo = &bufInfo;
925
926 vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
927 }
928
929 m_deferredCBCreated = true;
930 return true;
931}
932
933/// Destroys all GBuffer, lighting, and forward transparent pass resources.
934void VulkanRenderer::CleanupGBufferResources() {
935 if (!m_gbufferResourcesCreated) return;
936
937 // If a frame is currently being recorded (command buffer is open), wait for
938 // the GPU to finish all pending work before destroying resources that may be
939 // referenced by the in-flight command buffer.
940 if (bFrameStarted) {
941 SLEAK_WARN("CleanupGBufferResources called while frame recording is active — forcing device idle");
942 vkDeviceWaitIdle(device);
943 bFrameStarted = false;
944 }
945
946 // GBuffer pipeline
947 if (m_gbufferPipeline) {
948 vkDestroyPipeline(device, m_gbufferPipeline, nullptr);
949 m_gbufferPipeline = VK_NULL_HANDLE;
950 }
951 // Every cached variant is built against a render pass destroyed below
952 // (GBuffer or forward); drop them all and let the next draw rebuild.
953 DestroyCustomFormatPipelines();
954 if (m_skinnedGbufferPipeline) {
955 vkDestroyPipeline(device, m_skinnedGbufferPipeline, nullptr);
956 m_skinnedGbufferPipeline = VK_NULL_HANDLE;
957 }
958 // m_gbufferPipelineLayout aliases m_gbufferGeomLayout — cleaned up below
959 m_gbufferPipelineLayout = VK_NULL_HANDLE;
960 delete m_gbufferShader;
961 m_gbufferShader = nullptr;
962
963 // Lighting pipeline
964 if (m_lightingPipeline) {
965 vkDestroyPipeline(device, m_lightingPipeline, nullptr);
966 m_lightingPipeline = VK_NULL_HANDLE;
967 }
968 if (m_lightingPipelineLayout) {
969 vkDestroyPipelineLayout(device, m_lightingPipelineLayout, nullptr);
970 m_lightingPipelineLayout = VK_NULL_HANDLE;
971 }
972 delete m_lightingShader;
973 m_lightingShader = nullptr;
974
975 // Lighting framebuffers
976 for (auto& fb : m_lightingFramebuffers) {
977 if (fb) vkDestroyFramebuffer(device, fb, nullptr);
978 }
979 m_lightingFramebuffers.clear();
980
981 // Lighting render pass
982 if (m_lightingRenderPass) {
983 vkDestroyRenderPass(device, m_lightingRenderPass, nullptr);
984 m_lightingRenderPass = VK_NULL_HANDLE;
985 }
986
987 // GBuffer framebuffer
988 if (m_gbufferFramebuffer) {
989 vkDestroyFramebuffer(device, m_gbufferFramebuffer, nullptr);
990 m_gbufferFramebuffer = VK_NULL_HANDLE;
991 }
992
993 // GBuffer render pass
994 if (m_gbufferRenderPass) {
995 vkDestroyRenderPass(device, m_gbufferRenderPass, nullptr);
996 m_gbufferRenderPass = VK_NULL_HANDLE;
997 }
998
999 // Forward framebuffers
1000 for (auto& fb : m_forwardFramebuffers) {
1001 if (fb) vkDestroyFramebuffer(device, fb, nullptr);
1002 }
1003 m_forwardFramebuffers.clear();
1004
1005 // Forward render pass
1006 if (m_forwardRenderPass) {
1007 vkDestroyRenderPass(device, m_forwardRenderPass, nullptr);
1008 m_forwardRenderPass = VK_NULL_HANDLE;
1009 }
1010
1011 // GBuffer sampler descriptor resources
1012 if (m_gbufferSamplerPool) {
1013 vkDestroyDescriptorPool(device, m_gbufferSamplerPool, nullptr);
1014 m_gbufferSamplerPool = VK_NULL_HANDLE;
1015 }
1016 if (m_gbufferSamplerDSL) {
1017 vkDestroyDescriptorSetLayout(device, m_gbufferSamplerDSL, nullptr);
1018 m_gbufferSamplerDSL = VK_NULL_HANDLE;
1019 }
1020 if (m_gbufferSampler) {
1021 vkDestroySampler(device, m_gbufferSampler, nullptr);
1022 m_gbufferSampler = VK_NULL_HANDLE;
1023 }
1024 if (m_depthSampler) {
1025 vkDestroySampler(device, m_depthSampler, nullptr);
1026 m_depthSampler = VK_NULL_HANDLE;
1027 }
1028
1029 // Deferred CB resources
1030 if (m_deferredCBCreated) {
1031 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
1032 if (m_deferredCBMapped[i]) {
1033 vkUnmapMemory(device, m_deferredCBMemory[i]);
1034 m_deferredCBMapped[i] = nullptr;
1035 }
1036 if (m_deferredCBBuffers[i]) {
1037 vkDestroyBuffer(device, m_deferredCBBuffers[i], nullptr);
1038 m_deferredCBBuffers[i] = VK_NULL_HANDLE;
1039 }
1040 if (m_deferredCBMemory[i]) {
1041 vkFreeMemory(device, m_deferredCBMemory[i], nullptr);
1042 m_deferredCBMemory[i] = VK_NULL_HANDLE;
1043 }
1044 }
1045 m_deferredCBCreated = false;
1046 }
1047 if (m_deferredCBPool) {
1048 vkDestroyDescriptorPool(device, m_deferredCBPool, nullptr);
1049 m_deferredCBPool = VK_NULL_HANDLE;
1050 }
1051 if (m_deferredCBDSL) {
1052 vkDestroyDescriptorSetLayout(device, m_deferredCBDSL, nullptr);
1053 m_deferredCBDSL = VK_NULL_HANDLE;
1054 }
1055
1056 // GBuffer images
1057 for (uint32_t i = 0; i < GBUFFER_COUNT; ++i) {
1058 if (m_gbufferViews[i]) {
1059 vkDestroyImageView(device, m_gbufferViews[i], nullptr);
1060 m_gbufferViews[i] = VK_NULL_HANDLE;
1061 }
1062 if (m_gbufferImages[i]) {
1063 vkDestroyImage(device, m_gbufferImages[i], nullptr);
1064 m_gbufferImages[i] = VK_NULL_HANDLE;
1065 }
1066 if (m_gbufferMemory[i]) {
1067 vkFreeMemory(device, m_gbufferMemory[i], nullptr);
1068 m_gbufferMemory[i] = VK_NULL_HANDLE;
1069 }
1070 }
1071
1072 // PBR material resources (GBuffer set 0)
1073 DestroyPBRMaterialChunks();
1074 m_pbrMaterialResourcesCreated = false;
1075 if (m_pbrMaterialDSL) {
1076 vkDestroyDescriptorSetLayout(device, m_pbrMaterialDSL, nullptr);
1077 m_pbrMaterialDSL = VK_NULL_HANDLE;
1078 }
1079 if (m_gbufferGeomLayout) {
1080 vkDestroyPipelineLayout(device, m_gbufferGeomLayout, nullptr);
1081 m_gbufferGeomLayout = VK_NULL_HANDLE;
1082 }
1083
1084 // IBL resources
1085 CleanupIBLResources();
1086
1087 // SSAO + SSR + Bloom + HDR scene (owned by the deferred pipeline)
1088 CleanupSSAOResources();
1089 CleanupSSRResources();
1090 CleanupBloomResources();
1091
1092 m_gbufferResourcesCreated = false;
1093 m_inGeometryPass = false;
1094 m_inForwardTransparentPass = false;
1095}
1096
1097/// Binds the GBuffer pipeline and marks the geometry pass active.
1098/// Called by RenderCommandQueue when deferred is active.
1100 if (!bFrameStarted || !m_gbufferResourcesCreated) return;
1101 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, m_gbufferPipeline);
1102 // This binds the default GBuffer pipeline (96-byte Vertex stride). Without
1103 // the reset, the next custom-format draw's BindVertexBuffer sees its format
1104 // already active, skips the pipeline switch, and feeds the compact vertices
1105 // to a 96-byte stride.
1106 m_activeCustomFormat = 0;
1107}
1108
1109/// Runs the deferred lighting pass, reading the GBuffer and writing the HDR scene image.
1111 if (!bFrameStarted || !m_gbufferResourcesCreated) return;
1112
1113 // 1. End GBuffer render pass — transitions color RTs → SHADER_READ_ONLY,
1114 // depth → DEPTH_STENCIL_READ_ONLY via finalLayout in CreateGBufferRenderPass
1115 vkCmdEndRenderPass(command);
1116 m_inGeometryPass = false;
1117 m_activeCustomFormat = 0; // geometry pass is over; pipeline state doesn't survive across render passes
1118
1119 // 2. Run SSAO (raw + bilateral blur) using GBuffer normal + depth.
1120 // This writes to m_ssaoBlurImage which the lighting pass binding 7 reads.
1121 RenderSSAOPasses();
1122
1123 // 3. Update GBuffer sampler descriptor sets for current frame
1124 UpdateGBufferDescriptors();
1125
1126 // 4. Begin lighting render pass
1127 VkClearValue clearVal{};
1128 clearVal.color = {0.0f, 0.0f, 0.0f, 1.0f};
1129
1130 VkRenderPassBeginInfo rpBegin{};
1131 rpBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1132 rpBegin.renderPass = m_lightingRenderPass;
1133 rpBegin.framebuffer = m_lightingFramebuffers[CurrentFrameIndex];
1134 rpBegin.renderArea.offset = {0, 0};
1135 rpBegin.renderArea.extent = scExtent;
1136 rpBegin.clearValueCount = 1;
1137 rpBegin.pClearValues = &clearVal;
1138
1139 vkCmdBeginRenderPass(command, &rpBegin, VK_SUBPASS_CONTENTS_INLINE);
1140
1141 // 4. Set viewport and scissor
1142 VkViewport viewport{};
1143 viewport.x = 0.0f;
1144 viewport.y = 0.0f;
1145 viewport.width = static_cast<float>(scExtent.width);
1146 viewport.height = static_cast<float>(scExtent.height);
1147 viewport.minDepth = 0.0f;
1148 viewport.maxDepth = 1.0f;
1149 vkCmdSetViewport(command, 0, 1, &viewport);
1150
1151 VkRect2D scissor{};
1152 scissor.offset = {0, 0};
1153 scissor.extent = scExtent;
1154 vkCmdSetScissor(command, 0, 1, &scissor);
1155
1156 // 5. Bind lighting pipeline
1157 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, m_lightingPipeline);
1158
1159 // 6. Bind descriptor sets:
1160 // set 0: GBuffer samplers (RTs + shadow maps)
1161 // set 1: DeferredCB (InvViewProj + screen size)
1162 // set 2: LightUBO (directional light + shadow params + fog)
1163 // set 3: IBL (irradiance + prefilter + BRDF LUT + settings)
1164 VkDescriptorSet lightingSets[4] = {
1165 m_gbufferSamplerSets[currentFrame],
1166 m_deferredCBSets[currentFrame],
1167 m_lightUBODescriptorSets[currentFrame],
1168 m_iblSets[currentFrame]
1169 };
1170 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1171 m_lightingPipelineLayout, 0, 4,
1172 lightingSets, 0, nullptr);
1173
1174 // 7. Fullscreen triangle draw (3 vertices, no VBO)
1175 vkCmdDraw(command, 3, 1, 0, 0);
1176
1177 // 8. End lighting render pass
1178 vkCmdEndRenderPass(command);
1179
1180 // 9. Transition depth back to DEPTH_STENCIL_ATTACHMENT_OPTIMAL for forward pass
1181 VkImageMemoryBarrier depthBarrier{};
1182 depthBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1183 depthBarrier.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
1184 depthBarrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1185 depthBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1186 depthBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1187 depthBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
1188 depthBarrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
1189 VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
1190 depthBarrier.image = depthImage;
1191 depthBarrier.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
1192
1193 vkCmdPipelineBarrier(command,
1194 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
1195 VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
1196 0, 0, nullptr, 0, nullptr, 1, &depthBarrier);
1197}
1198
1199/// Begins the forward transparent render pass over the HDR scene image.
1201 if (!bFrameStarted || !m_gbufferResourcesCreated) return;
1202
1203 VkRenderPassBeginInfo rpBegin{};
1204 rpBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1205 rpBegin.renderPass = m_forwardRenderPass;
1206 rpBegin.framebuffer = m_forwardFramebuffers[CurrentFrameIndex];
1207 rpBegin.renderArea.offset = {0, 0};
1208 rpBegin.renderArea.extent = scExtent;
1209 rpBegin.clearValueCount = 0; // LOAD_OP — no clear needed
1210
1211 vkCmdBeginRenderPass(command, &rpBegin, VK_SUBPASS_CONTENTS_INLINE);
1212
1213 VkViewport viewport{};
1214 viewport.x = 0.0f;
1215 viewport.y = 0.0f;
1216 viewport.width = static_cast<float>(scExtent.width);
1217 viewport.height = static_cast<float>(scExtent.height);
1218 viewport.minDepth = 0.0f;
1219 viewport.maxDepth = 1.0f;
1220 vkCmdSetViewport(command, 0, 1, &viewport);
1221
1222 VkRect2D scissor{};
1223 scissor.offset = {0, 0};
1224 scissor.extent = scExtent;
1225 vkCmdSetScissor(command, 0, 1, &scissor);
1226
1227 // Default to the forward pipeline; a custom vertex format with a
1228 // transparent variant swaps to it when its vertex buffer is bound.
1229 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
1230
1231 // Bind descriptor sets 0-3 (same as normal forward pass)
1232 if (m_textureDescriptorsWritten && CurrentFrameIndex < descriptorSets.size()) {
1233 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1234 pipelineLay, 0, 1,
1235 &descriptorSets[CurrentFrameIndex], 0, nullptr);
1236 }
1237 if (m_boneUBOCreated) {
1238 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1239 pipelineLay, 1, 1,
1240 &boneDescriptorSets[currentFrame], 0, nullptr);
1241 }
1242 if (m_lightUBOCreated) {
1243 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1244 pipelineLay, 2, 1,
1245 &m_lightUBODescriptorSets[currentFrame], 0, nullptr);
1246 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1247 pipelineLay, 3, 1,
1248 &m_shadowSamplerDescriptorSets[currentFrame], 0, nullptr);
1249 }
1250
1251 m_inForwardTransparentPass = true;
1252 m_forwardPassOpen = true;
1253 m_activeCustomFormat = 0; // new render pass; cached pipeline state is stale
1254}
1255
1256/// Marks the forward transparent pass ended; EndRender closes the actual render pass.
1258 m_inForwardTransparentPass = false;
1259 // m_forwardPassOpen stays true — the RP remains open until EndRender
1260}
1261
1262/// Copies deferred CB data into the current frame's UBO and snapshots the camera matrices.
1263void VulkanRenderer::UpdateDeferredCB(const void* data, uint32_t size) {
1264 if (!m_deferredCBCreated || !data) return;
1265 uint32_t copySize = std::min(size, static_cast<uint32_t>(sizeof(DeferredCBData)));
1266 memcpy(m_deferredCBMapped[currentFrame], data, copySize);
1267
1268 // Snapshot current camera View / Projection for SSAO/SSR/TAA UBO population.
1271 memcpy(m_cachedView, &V(0, 0), sizeof(m_cachedView));
1272 memcpy(m_cachedProjection, &P(0, 0), sizeof(m_cachedProjection));
1273
1274 // Snapshot InvViewProj (first mat4 of DeferredCBData) so SSAO/SSR reuse the
1275 // exact inverse the lighting pass uses to reconstruct world from depth.
1276 if (copySize >= sizeof(m_cachedInvViewProj))
1277 memcpy(m_cachedInvViewProj, data, sizeof(m_cachedInvViewProj));
1278
1279 // Compute sub-pixel Halton jitter for this frame (UV space).
1280 // Applied to WVP push constants in BindConstantBuffer during the geometry pass
1281 // so each frame samples a slightly different sub-pixel location — the temporal
1282 // accumulation in TAA then converges to full-resolution anti-aliased output.
1283 if (m_taaResourcesCreated && m_taaEnabled && scExtent.width > 0) {
1284 const uint32_t haltonIdx = static_cast<uint32_t>((m_taaFrameIdx % 8) + 1);
1285 m_taaJitter[0] = (HaltonSeq(haltonIdx, 2) - 0.5f) / static_cast<float>(scExtent.width);
1286 m_taaJitter[1] = (HaltonSeq(haltonIdx, 3) - 0.5f) / static_cast<float>(scExtent.height);
1287 } else {
1288 m_taaJitter[0] = m_taaJitter[1] = 0.0f;
1289 }
1290}
1291
1292} // namespace RenderEngine
1293} // namespace Sleak
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_INFO(...)
Definition Logger.hpp:20
#define SLEAK_WARN(...)
Definition Logger.hpp:21
static const Math::Matrix4 & GetMainProjectionMatrix()
Definition Camera.hpp:107
static const Math::Matrix4 & GetMainViewMatrix()
Definition Camera.hpp:103
virtual void ExecuteDeferredLightingPass() override
Runs the deferred lighting pass, reading the GBuffer and writing the HDR scene image.
virtual void EndForwardTransparentPass() override
Marks the forward transparent pass ended; EndRender closes the actual render pass.
virtual void UpdateDeferredCB(const void *data, uint32_t size) override
Copies deferred CB data into the current frame's UBO and snapshots the camera matrices.
virtual void BeginForwardTransparentPass() override
Begins the forward transparent render pass over the HDR scene image.
virtual void BindGBufferShader() override
Matrix< float, 4, 4 > Matrix4
Definition Matrix.hpp:413
Backend-facing rendering layer shared by the four graphics backends.
float HaltonSeq(int index, int base)
Halton low-discrepancy sequence value for TAA sub-pixel jitter.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10