SleakEngine 1.0.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 if (m_pbrMaterialResourcesCreated) {
1074 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
1075 if (m_pbrMaterialCBMapped[i]) {
1076 vkUnmapMemory(device, m_pbrMaterialCBMemory[i]);
1077 m_pbrMaterialCBMapped[i] = nullptr;
1078 }
1079 if (m_pbrMaterialCBBuffers[i]) {
1080 vkDestroyBuffer(device, m_pbrMaterialCBBuffers[i], nullptr);
1081 m_pbrMaterialCBBuffers[i] = VK_NULL_HANDLE;
1082 }
1083 if (m_pbrMaterialCBMemory[i]) {
1084 vkFreeMemory(device, m_pbrMaterialCBMemory[i], nullptr);
1085 m_pbrMaterialCBMemory[i] = VK_NULL_HANDLE;
1086 }
1087 }
1088 m_pbrMaterialResourcesCreated = false;
1089 }
1090 if (m_pbrMaterialPool) {
1091 vkDestroyDescriptorPool(device, m_pbrMaterialPool, nullptr);
1092 m_pbrMaterialPool = VK_NULL_HANDLE;
1093 }
1094 if (m_pbrMaterialDSL) {
1095 vkDestroyDescriptorSetLayout(device, m_pbrMaterialDSL, nullptr);
1096 m_pbrMaterialDSL = VK_NULL_HANDLE;
1097 }
1098 if (m_gbufferGeomLayout) {
1099 vkDestroyPipelineLayout(device, m_gbufferGeomLayout, nullptr);
1100 m_gbufferGeomLayout = VK_NULL_HANDLE;
1101 }
1102
1103 // IBL resources
1104 CleanupIBLResources();
1105
1106 // SSAO + SSR + Bloom + HDR scene (owned by the deferred pipeline)
1107 CleanupSSAOResources();
1108 CleanupSSRResources();
1109 CleanupBloomResources();
1110
1111 m_gbufferResourcesCreated = false;
1112 m_inGeometryPass = false;
1113 m_inForwardTransparentPass = false;
1114}
1115
1116/// Binds the GBuffer pipeline and marks the geometry pass active.
1117/// Called by RenderCommandQueue when deferred is active.
1119 if (!bFrameStarted || !m_gbufferResourcesCreated) return;
1120 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, m_gbufferPipeline);
1121 // This binds the default GBuffer pipeline (96-byte Vertex stride). Without
1122 // the reset, the next custom-format draw's BindVertexBuffer sees its format
1123 // already active, skips the pipeline switch, and feeds the compact vertices
1124 // to a 96-byte stride.
1125 m_activeCustomFormat = 0;
1126}
1127
1128/// Runs the deferred lighting pass, reading the GBuffer and writing the HDR scene image.
1130 if (!bFrameStarted || !m_gbufferResourcesCreated) return;
1131
1132 // 1. End GBuffer render pass — transitions color RTs → SHADER_READ_ONLY,
1133 // depth → DEPTH_STENCIL_READ_ONLY via finalLayout in CreateGBufferRenderPass
1134 vkCmdEndRenderPass(command);
1135 m_inGeometryPass = false;
1136 m_activeCustomFormat = 0; // geometry pass is over; pipeline state doesn't survive across render passes
1137
1138 // 2. Run SSAO (raw + bilateral blur) using GBuffer normal + depth.
1139 // This writes to m_ssaoBlurImage which the lighting pass binding 7 reads.
1140 RenderSSAOPasses();
1141
1142 // 3. Update GBuffer sampler descriptor sets for current frame
1143 UpdateGBufferDescriptors();
1144
1145 // 4. Begin lighting render pass
1146 VkClearValue clearVal{};
1147 clearVal.color = {0.0f, 0.0f, 0.0f, 1.0f};
1148
1149 VkRenderPassBeginInfo rpBegin{};
1150 rpBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1151 rpBegin.renderPass = m_lightingRenderPass;
1152 rpBegin.framebuffer = m_lightingFramebuffers[CurrentFrameIndex];
1153 rpBegin.renderArea.offset = {0, 0};
1154 rpBegin.renderArea.extent = scExtent;
1155 rpBegin.clearValueCount = 1;
1156 rpBegin.pClearValues = &clearVal;
1157
1158 vkCmdBeginRenderPass(command, &rpBegin, VK_SUBPASS_CONTENTS_INLINE);
1159
1160 // 4. Set viewport and scissor
1161 VkViewport viewport{};
1162 viewport.x = 0.0f;
1163 viewport.y = 0.0f;
1164 viewport.width = static_cast<float>(scExtent.width);
1165 viewport.height = static_cast<float>(scExtent.height);
1166 viewport.minDepth = 0.0f;
1167 viewport.maxDepth = 1.0f;
1168 vkCmdSetViewport(command, 0, 1, &viewport);
1169
1170 VkRect2D scissor{};
1171 scissor.offset = {0, 0};
1172 scissor.extent = scExtent;
1173 vkCmdSetScissor(command, 0, 1, &scissor);
1174
1175 // 5. Bind lighting pipeline
1176 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, m_lightingPipeline);
1177
1178 // 6. Bind descriptor sets:
1179 // set 0: GBuffer samplers (RTs + shadow maps)
1180 // set 1: DeferredCB (InvViewProj + screen size)
1181 // set 2: LightUBO (directional light + shadow params + fog)
1182 // set 3: IBL (irradiance + prefilter + BRDF LUT + settings)
1183 VkDescriptorSet lightingSets[4] = {
1184 m_gbufferSamplerSets[currentFrame],
1185 m_deferredCBSets[currentFrame],
1186 m_lightUBODescriptorSets[currentFrame],
1187 m_iblSets[currentFrame]
1188 };
1189 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1190 m_lightingPipelineLayout, 0, 4,
1191 lightingSets, 0, nullptr);
1192
1193 // 7. Fullscreen triangle draw (3 vertices, no VBO)
1194 vkCmdDraw(command, 3, 1, 0, 0);
1195
1196 // 8. End lighting render pass
1197 vkCmdEndRenderPass(command);
1198
1199 // 9. Transition depth back to DEPTH_STENCIL_ATTACHMENT_OPTIMAL for forward pass
1200 VkImageMemoryBarrier depthBarrier{};
1201 depthBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1202 depthBarrier.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
1203 depthBarrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
1204 depthBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1205 depthBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1206 depthBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
1207 depthBarrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
1208 VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
1209 depthBarrier.image = depthImage;
1210 depthBarrier.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
1211
1212 vkCmdPipelineBarrier(command,
1213 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
1214 VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
1215 0, 0, nullptr, 0, nullptr, 1, &depthBarrier);
1216}
1217
1218/// Begins the forward transparent render pass over the HDR scene image.
1220 if (!bFrameStarted || !m_gbufferResourcesCreated) return;
1221
1222 VkRenderPassBeginInfo rpBegin{};
1223 rpBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
1224 rpBegin.renderPass = m_forwardRenderPass;
1225 rpBegin.framebuffer = m_forwardFramebuffers[CurrentFrameIndex];
1226 rpBegin.renderArea.offset = {0, 0};
1227 rpBegin.renderArea.extent = scExtent;
1228 rpBegin.clearValueCount = 0; // LOAD_OP — no clear needed
1229
1230 vkCmdBeginRenderPass(command, &rpBegin, VK_SUBPASS_CONTENTS_INLINE);
1231
1232 VkViewport viewport{};
1233 viewport.x = 0.0f;
1234 viewport.y = 0.0f;
1235 viewport.width = static_cast<float>(scExtent.width);
1236 viewport.height = static_cast<float>(scExtent.height);
1237 viewport.minDepth = 0.0f;
1238 viewport.maxDepth = 1.0f;
1239 vkCmdSetViewport(command, 0, 1, &viewport);
1240
1241 VkRect2D scissor{};
1242 scissor.offset = {0, 0};
1243 scissor.extent = scExtent;
1244 vkCmdSetScissor(command, 0, 1, &scissor);
1245
1246 // Default to the forward pipeline; a custom vertex format with a
1247 // transparent variant swaps to it when its vertex buffer is bound.
1248 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
1249
1250 // Bind descriptor sets 0-3 (same as normal forward pass)
1251 if (m_textureDescriptorsWritten && CurrentFrameIndex < descriptorSets.size()) {
1252 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1253 pipelineLay, 0, 1,
1254 &descriptorSets[CurrentFrameIndex], 0, nullptr);
1255 }
1256 if (m_boneUBOCreated) {
1257 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1258 pipelineLay, 1, 1,
1259 &boneDescriptorSets[currentFrame], 0, nullptr);
1260 }
1261 if (m_lightUBOCreated) {
1262 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1263 pipelineLay, 2, 1,
1264 &m_lightUBODescriptorSets[currentFrame], 0, nullptr);
1265 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1266 pipelineLay, 3, 1,
1267 &m_shadowSamplerDescriptorSets[currentFrame], 0, nullptr);
1268 }
1269
1270 m_inForwardTransparentPass = true;
1271 m_forwardPassOpen = true;
1272 m_activeCustomFormat = 0; // new render pass; cached pipeline state is stale
1273}
1274
1275/// Marks the forward transparent pass ended; EndRender closes the actual render pass.
1277 m_inForwardTransparentPass = false;
1278 // m_forwardPassOpen stays true — the RP remains open until EndRender
1279}
1280
1281/// Copies deferred CB data into the current frame's UBO and snapshots the camera matrices.
1282void VulkanRenderer::UpdateDeferredCB(const void* data, uint32_t size) {
1283 if (!m_deferredCBCreated || !data) return;
1284 uint32_t copySize = std::min(size, static_cast<uint32_t>(sizeof(DeferredCBData)));
1285 memcpy(m_deferredCBMapped[currentFrame], data, copySize);
1286
1287 // Snapshot current camera View / Projection for SSAO/SSR/TAA UBO population.
1290 memcpy(m_cachedView, &V(0, 0), sizeof(m_cachedView));
1291 memcpy(m_cachedProjection, &P(0, 0), sizeof(m_cachedProjection));
1292
1293 // Snapshot InvViewProj (first mat4 of DeferredCBData) so SSAO/SSR reuse the
1294 // exact inverse the lighting pass uses to reconstruct world from depth.
1295 if (copySize >= sizeof(m_cachedInvViewProj))
1296 memcpy(m_cachedInvViewProj, data, sizeof(m_cachedInvViewProj));
1297
1298 // Compute sub-pixel Halton jitter for this frame (UV space).
1299 // Applied to WVP push constants in BindConstantBuffer during the geometry pass
1300 // so each frame samples a slightly different sub-pixel location — the temporal
1301 // accumulation in TAA then converges to full-resolution anti-aliased output.
1302 if (m_taaResourcesCreated && m_taaEnabled && scExtent.width > 0) {
1303 const uint32_t haltonIdx = static_cast<uint32_t>((m_taaFrameIdx % 8) + 1);
1304 m_taaJitter[0] = (HaltonSeq(haltonIdx, 2) - 0.5f) / static_cast<float>(scExtent.width);
1305 m_taaJitter[1] = (HaltonSeq(haltonIdx, 3) - 0.5f) / static_cast<float>(scExtent.height);
1306 } else {
1307 m_taaJitter[0] = m_taaJitter[1] = 0.0f;
1308 }
1309}
1310
1311} // namespace RenderEngine
1312} // 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