SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
VulkanSSR.cpp
Go to the documentation of this file.
2
3#include <Camera/Camera.hpp>
4#include <array>
5#include <cstring>
6#include <vector>
7#include "Core/Logger.hpp"
8
9namespace Sleak {
10 namespace RenderEngine {
11
12// ==================================================================
13// ==================== SSR (Screen-Space Reflections) ==============
14// ==================================================================
15// Full-resolution view-space ray march with binary search refinement.
16// Runs AFTER the forward pass (HDR scene must be lit and in
17// SHADER_READ_ONLY_OPTIMAL) and BEFORE the bloom threshold pass — the
18// composite pass then additively blends the SSR result into the HDR scene.
19
20/// Creates the SSR image, render pass, framebuffer, descriptors, UBOs, and pipeline.
21bool VulkanRenderer::CreateSSRResources() {
22 if (m_ssrResourcesCreated) return true;
23
24 // ---- 1. SSR image (full-res, R16G16B16A16 premultiplied) ----
25 {
26 VkImageCreateInfo ic{};
27 ic.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
28 ic.imageType = VK_IMAGE_TYPE_2D;
29 ic.extent.width = scExtent.width;
30 ic.extent.height = scExtent.height;
31 ic.extent.depth = 1;
32 ic.mipLevels = 1;
33 ic.arrayLayers = 1;
34 ic.format = m_ssrFormat;
35 ic.tiling = VK_IMAGE_TILING_OPTIMAL;
36 ic.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
37 // TRANSFER_DST enables vkCmdClearColorImage when SSR is disabled
38 // (the composite pass always samples this buffer regardless).
39 ic.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
40 | VK_IMAGE_USAGE_SAMPLED_BIT
41 | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
42 ic.samples = VK_SAMPLE_COUNT_1_BIT;
43 ic.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
44 if (vkCreateImage(device, &ic, nullptr, &m_ssrImage) != VK_SUCCESS) {
45 SLEAK_ERROR("SSR: vkCreateImage failed"); return false;
46 }
47 VkMemoryRequirements req;
48 vkGetImageMemoryRequirements(device, m_ssrImage, &req);
49 VkMemoryAllocateInfo alloc{};
50 alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
51 alloc.allocationSize = req.size;
52 alloc.memoryTypeIndex = FindMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
53 if (vkAllocateMemory(device, &alloc, nullptr, &m_ssrMemory) != VK_SUCCESS) {
54 SLEAK_ERROR("SSR: vkAllocateMemory failed ({}B, typeIdx={})", req.size, alloc.memoryTypeIndex); return false;
55 }
56 vkBindImageMemory(device, m_ssrImage, m_ssrMemory, 0);
57
58 VkImageViewCreateInfo vi{};
59 vi.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
60 vi.image = m_ssrImage;
61 vi.viewType = VK_IMAGE_VIEW_TYPE_2D;
62 vi.format = m_ssrFormat;
63 vi.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
64 if (vkCreateImageView(device, &vi, nullptr, &m_ssrView) != VK_SUCCESS) {
65 SLEAK_ERROR("SSR: vkCreateImageView failed"); return false;
66 }
67 }
68
69 // Linear-clamp sampler — bloom composite samples the SSR buffer.
70 {
71 VkSamplerCreateInfo ss{};
72 ss.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
73 ss.magFilter = VK_FILTER_LINEAR;
74 ss.minFilter = VK_FILTER_LINEAR;
75 ss.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
76 ss.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
77 ss.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
78 ss.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
79 ss.minLod = 0.0f;
80 ss.maxLod = 0.0f;
81 if (vkCreateSampler(device, &ss, nullptr, &m_ssrSampler) != VK_SUCCESS) {
82 SLEAK_ERROR("SSR: vkCreateSampler failed"); return false;
83 }
84 }
85
86 // ---- 2. Render pass (single color attachment) ----
87 {
88 VkAttachmentDescription att{};
89 att.format = m_ssrFormat;
90 att.samples = VK_SAMPLE_COUNT_1_BIT;
91 att.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // we write every pixel
92 att.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
93 att.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
94 att.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
95 att.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
96 att.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
97
98 VkAttachmentReference ref{};
99 ref.attachment = 0;
100 ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
101
102 VkSubpassDescription sp{};
103 sp.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
104 sp.colorAttachmentCount = 1;
105 sp.pColorAttachments = &ref;
106
107 // External dependencies mirror the SSAO render pass — we read from
108 // GBuffer/HDR samplers before the pass and the composite samples us
109 // after, so we bracket with shader-read-to-color-write / color-write-
110 // to-shader-read transitions.
111 std::array<VkSubpassDependency, 2> deps{};
112 deps[0].srcSubpass = VK_SUBPASS_EXTERNAL;
113 deps[0].dstSubpass = 0;
114 deps[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
115 deps[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
116 deps[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
117 deps[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
118 deps[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
119
120 deps[1].srcSubpass = 0;
121 deps[1].dstSubpass = VK_SUBPASS_EXTERNAL;
122 deps[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
123 deps[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
124 deps[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
125 deps[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
126 deps[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
127
128 VkRenderPassCreateInfo rp{};
129 rp.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
130 rp.attachmentCount = 1;
131 rp.pAttachments = &att;
132 rp.subpassCount = 1;
133 rp.pSubpasses = &sp;
134 rp.dependencyCount = static_cast<uint32_t>(deps.size());
135 rp.pDependencies = deps.data();
136 if (vkCreateRenderPass(device, &rp, nullptr, &m_ssrRenderPass) != VK_SUCCESS) {
137 SLEAK_ERROR("SSR: vkCreateRenderPass failed"); return false;
138 }
139 }
140
141 // ---- 3. Framebuffer (single view) ----
142 {
143 VkFramebufferCreateInfo fb{};
144 fb.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
145 fb.renderPass = m_ssrRenderPass;
146 fb.attachmentCount = 1;
147 fb.pAttachments = &m_ssrView;
148 fb.width = scExtent.width;
149 fb.height = scExtent.height;
150 fb.layers = 1;
151 if (vkCreateFramebuffer(device, &fb, nullptr, &m_ssrFramebuffer) != VK_SUCCESS) {
152 SLEAK_ERROR("SSR: vkCreateFramebuffer failed"); return false;
153 }
154 }
155
156 // ---- 4. Descriptor set layouts ----
157 // Set 0: 5 combined image samplers (gNormalRough, gDepth, gMetalEmit,
158 // gAlbedoAO, sceneHDR). World position reconstructed from depth.
159 {
160 std::array<VkDescriptorSetLayoutBinding, 5> binds{};
161 for (uint32_t i = 0; i < binds.size(); ++i) {
162 binds[i].binding = i;
163 binds[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
164 binds[i].descriptorCount = 1;
165 binds[i].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
166 }
167 VkDescriptorSetLayoutCreateInfo info{};
168 info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
169 info.bindingCount = static_cast<uint32_t>(binds.size());
170 info.pBindings = binds.data();
171 if (vkCreateDescriptorSetLayout(device, &info, nullptr, &m_ssrInputDSL) != VK_SUCCESS) {
172 SLEAK_ERROR("SSR: vkCreateDescriptorSetLayout (input) failed"); return false;
173 }
174 }
175 // Set 1: UBO.
176 {
177 VkDescriptorSetLayoutBinding b{};
178 b.binding = 0;
179 b.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
180 b.descriptorCount = 1;
181 b.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
182 VkDescriptorSetLayoutCreateInfo info{};
183 info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
184 info.bindingCount = 1;
185 info.pBindings = &b;
186 if (vkCreateDescriptorSetLayout(device, &info, nullptr, &m_ssrUboDSL) != VK_SUCCESS) {
187 SLEAK_ERROR("SSR: vkCreateDescriptorSetLayout (ubo) failed"); return false;
188 }
189 }
190
191 // ---- 5. Descriptor pool + sets ----
192 {
193 std::array<VkDescriptorPoolSize, 2> sizes{};
194 sizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
195 sizes[0].descriptorCount = 6 * MAX_FRAMES_IN_FLIGHT;
196 sizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
197 sizes[1].descriptorCount = 1 * MAX_FRAMES_IN_FLIGHT;
198
199 VkDescriptorPoolCreateInfo pool{};
200 pool.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
201 pool.poolSizeCount = static_cast<uint32_t>(sizes.size());
202 pool.pPoolSizes = sizes.data();
203 pool.maxSets = 2 * MAX_FRAMES_IN_FLIGHT;
204 if (vkCreateDescriptorPool(device, &pool, nullptr, &m_ssrPool) != VK_SUCCESS) {
205 SLEAK_ERROR("SSR: vkCreateDescriptorPool failed"); return false;
206 }
207
208 auto allocSets = [&](VkDescriptorSetLayout dsl, std::array<VkDescriptorSet, MAX_FRAMES_IN_FLIGHT>& out) -> bool {
209 std::array<VkDescriptorSetLayout, MAX_FRAMES_IN_FLIGHT> layouts;
210 layouts.fill(dsl);
211 VkDescriptorSetAllocateInfo a{};
212 a.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
213 a.descriptorPool = m_ssrPool;
214 a.descriptorSetCount = MAX_FRAMES_IN_FLIGHT;
215 a.pSetLayouts = layouts.data();
216 return vkAllocateDescriptorSets(device, &a, out.data()) == VK_SUCCESS;
217 };
218 if (!allocSets(m_ssrInputDSL, m_ssrInputSets)) {
219 SLEAK_ERROR("SSR: vkAllocateDescriptorSets (input) failed"); return false;
220 }
221 if (!allocSets(m_ssrUboDSL, m_ssrUboSets)) {
222 SLEAK_ERROR("SSR: vkAllocateDescriptorSets (ubo) failed"); return false;
223 }
224 }
225
226 // ---- 6. UBO buffers (host visible coherent) ----
227 {
228 static constexpr VkDeviceSize uboSize = sizeof(SSRParams);
229 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
230 VkBufferCreateInfo bi{};
231 bi.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
232 bi.size = uboSize;
233 bi.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
234 bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
235 if (vkCreateBuffer(device, &bi, nullptr, &m_ssrUboBuffers[i]) != VK_SUCCESS) {
236 SLEAK_ERROR("SSR: vkCreateBuffer UBO[{}] failed", i); return false;
237 }
238 VkMemoryRequirements req;
239 vkGetBufferMemoryRequirements(device, m_ssrUboBuffers[i], &req);
240 VkMemoryAllocateInfo alloc{};
241 alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
242 alloc.allocationSize = req.size;
243 alloc.memoryTypeIndex = FindMemoryType(req.memoryTypeBits,
244 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
245 if (vkAllocateMemory(device, &alloc, nullptr, &m_ssrUboMemory[i]) != VK_SUCCESS) {
246 SLEAK_ERROR("SSR: vkAllocateMemory UBO[{}] failed", i); return false;
247 }
248 vkBindBufferMemory(device, m_ssrUboBuffers[i], m_ssrUboMemory[i], 0);
249 if (vkMapMemory(device, m_ssrUboMemory[i], 0, uboSize, 0, &m_ssrUboMapped[i]) != VK_SUCCESS) return false;
250
251 // Bind UBO to set 1 immediately — input descriptors are written
252 // later by UpdateSSRDescriptors() once all source views exist.
253 VkDescriptorBufferInfo bufInfo{};
254 bufInfo.buffer = m_ssrUboBuffers[i];
255 bufInfo.offset = 0;
256 bufInfo.range = uboSize;
257
258 VkWriteDescriptorSet w{};
259 w.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
260 w.dstSet = m_ssrUboSets[i];
261 w.dstBinding = 0;
262 w.dstArrayElement = 0;
263 w.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
264 w.descriptorCount = 1;
265 w.pBufferInfo = &bufInfo;
266 vkUpdateDescriptorSets(device, 1, &w, 0, nullptr);
267 }
268 }
269
270 // ---- 7. Pipeline layout ----
271 {
272 std::array<VkDescriptorSetLayout, 2> layouts = { m_ssrInputDSL, m_ssrUboDSL };
273 VkPipelineLayoutCreateInfo pli{};
274 pli.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
275 pli.setLayoutCount = static_cast<uint32_t>(layouts.size());
276 pli.pSetLayouts = layouts.data();
277 if (vkCreatePipelineLayout(device, &pli, nullptr, &m_ssrPipelineLayout) != VK_SUCCESS) {
278 SLEAK_ERROR("SSR: vkCreatePipelineLayout failed"); return false;
279 }
280 }
281
282 // ---- 8. Pipeline ----
283 {
284 m_ssrShader = new VulkanShader(device);
285 if (!m_ssrShader->compile("assets/shaders/ssr.vert.spv",
286 "assets/shaders/ssr.frag.spv")) {
287 SLEAK_ERROR("SSR: failed to compile ssr shaders");
288 return false;
289 }
290
291 VkPipelineShaderStageCreateInfo stages[] = {
292 m_ssrShader->GetVertexInfo(),
293 m_ssrShader->GetFragInfo()
294 };
295
296 VkPipelineVertexInputStateCreateInfo vin{};
297 vin.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
298
299 VkPipelineInputAssemblyStateCreateInfo ia{};
300 ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
301 ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
302
303 std::vector<VkDynamicState> dynStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
304 VkPipelineDynamicStateCreateInfo ds{};
305 ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
306 ds.dynamicStateCount = static_cast<uint32_t>(dynStates.size());
307 ds.pDynamicStates = dynStates.data();
308
309 VkPipelineViewportStateCreateInfo vps{};
310 vps.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
311 vps.viewportCount = 1;
312 vps.scissorCount = 1;
313
314 VkPipelineRasterizationStateCreateInfo rs{};
315 rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
316 rs.polygonMode = VK_POLYGON_MODE_FILL;
317 rs.cullMode = VK_CULL_MODE_NONE;
318 rs.lineWidth = 1.0f;
319
320 VkPipelineMultisampleStateCreateInfo ms{};
321 ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
322 ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
323
324 VkPipelineDepthStencilStateCreateInfo dss{};
325 dss.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
326 // no depth test / write — full-screen post-process
327
328 VkPipelineColorBlendAttachmentState blendAtt{};
329 blendAtt.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
330 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
331
332 VkPipelineColorBlendStateCreateInfo cb{};
333 cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
334 cb.attachmentCount = 1;
335 cb.pAttachments = &blendAtt;
336
337 VkGraphicsPipelineCreateInfo gpi{};
338 gpi.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
339 gpi.stageCount = 2;
340 gpi.pStages = stages;
341 gpi.pVertexInputState = &vin;
342 gpi.pInputAssemblyState = &ia;
343 gpi.pViewportState = &vps;
344 gpi.pRasterizationState = &rs;
345 gpi.pMultisampleState = &ms;
346 gpi.pDepthStencilState = &dss;
347 gpi.pColorBlendState = &cb;
348 gpi.pDynamicState = &ds;
349 gpi.layout = m_ssrPipelineLayout;
350 gpi.renderPass = m_ssrRenderPass;
351 gpi.subpass = 0;
352
353 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &gpi, nullptr, &m_ssrPipeline) != VK_SUCCESS) {
354 SLEAK_ERROR("SSR: failed to create pipeline");
355 return false;
356 }
357 }
358
359 m_ssrResourcesCreated = true;
360 SLEAK_INFO("SSR resources created ({}x{})", scExtent.width, scExtent.height);
361 return true;
362}
363
364/// Destroys the SSR pipeline, framebuffer, descriptors, image, and sampler.
365void VulkanRenderer::CleanupSSRResources() {
366 if (!m_ssrResourcesCreated) return;
367
368 if (m_ssrPipeline) { vkDestroyPipeline(device, m_ssrPipeline, nullptr); m_ssrPipeline = VK_NULL_HANDLE; }
369 if (m_ssrPipelineLayout) { vkDestroyPipelineLayout(device, m_ssrPipelineLayout, nullptr); m_ssrPipelineLayout = VK_NULL_HANDLE; }
370 delete m_ssrShader; m_ssrShader = nullptr;
371
372 if (m_ssrFramebuffer) { vkDestroyFramebuffer(device, m_ssrFramebuffer, nullptr); m_ssrFramebuffer = VK_NULL_HANDLE; }
373 if (m_ssrRenderPass) { vkDestroyRenderPass(device, m_ssrRenderPass, nullptr); m_ssrRenderPass = VK_NULL_HANDLE; }
374
375 if (m_ssrPool) { vkDestroyDescriptorPool(device, m_ssrPool, nullptr); m_ssrPool = VK_NULL_HANDLE; }
376 if (m_ssrInputDSL) { vkDestroyDescriptorSetLayout(device, m_ssrInputDSL, nullptr); m_ssrInputDSL = VK_NULL_HANDLE; }
377 if (m_ssrUboDSL) { vkDestroyDescriptorSetLayout(device, m_ssrUboDSL, nullptr); m_ssrUboDSL = VK_NULL_HANDLE; }
378
379 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
380 if (m_ssrUboMapped[i]) { vkUnmapMemory(device, m_ssrUboMemory[i]); m_ssrUboMapped[i] = nullptr; }
381 if (m_ssrUboBuffers[i]) { vkDestroyBuffer(device, m_ssrUboBuffers[i], nullptr); m_ssrUboBuffers[i] = VK_NULL_HANDLE; }
382 if (m_ssrUboMemory[i]) { vkFreeMemory(device, m_ssrUboMemory[i], nullptr); m_ssrUboMemory[i] = VK_NULL_HANDLE; }
383 }
384
385 if (m_ssrView) { vkDestroyImageView(device, m_ssrView, nullptr); m_ssrView = VK_NULL_HANDLE; }
386 if (m_ssrImage) { vkDestroyImage(device, m_ssrImage, nullptr); m_ssrImage = VK_NULL_HANDLE; }
387 if (m_ssrMemory) { vkFreeMemory(device, m_ssrMemory, nullptr); m_ssrMemory = VK_NULL_HANDLE; }
388 if (m_ssrSampler) { vkDestroySampler(device, m_ssrSampler, nullptr); m_ssrSampler = VK_NULL_HANDLE; }
389
390 m_ssrResourcesCreated = false;
391}
392
393// ---- Update / render half (physically separated in the original monolith) ----
394
395/// Writes the GBuffer and HDR scene samplers into the SSR input descriptor sets.
396void VulkanRenderer::UpdateSSRDescriptors() {
397 if (!m_ssrResourcesCreated) return;
398
399 // Write the input samplers for every frame slot. Called during init, so
400 // no concurrent GPU access — safe to update both slots at once.
401 // Sampler slots: [0]=gNormalRough, [1]=gDepth, [2]=gMetalEmit,
402 // [3]=gAlbedoAO, [4]=sceneHDR. World pos from depth.
403 for (uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; ++f) {
404 std::array<VkDescriptorImageInfo, 5> infos{};
405
406 // gNormalRough = gbuffer[1]
407 infos[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
408 infos[0].imageView = m_gbufferViews[1];
409 infos[0].sampler = m_gbufferSampler ? m_gbufferSampler : m_ssrSampler;
410
411 // gDepth — READ_ONLY because shadow pass finalLayout is
412 // DEPTH_STENCIL_READ_ONLY, GBuffer pass final is READ_ONLY, but the
413 // forward pass exits at DEPTH_STENCIL_ATTACHMENT_OPTIMAL. SSR bracket
414 // transitions it to READ_ONLY before sampling (see RenderSSRPass).
415 infos[1].imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
416 infos[1].imageView = depthImageView;
417 infos[1].sampler = m_depthSampler ? m_depthSampler : m_ssrSampler;
418
419 // gMetalEmit = gbuffer[2]
420 infos[2].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
421 infos[2].imageView = m_gbufferViews[2];
422 infos[2].sampler = m_gbufferSampler ? m_gbufferSampler : m_ssrSampler;
423
424 // gAlbedoAO = gbuffer[0]
425 infos[3].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
426 infos[3].imageView = m_gbufferViews[0];
427 infos[3].sampler = m_gbufferSampler ? m_gbufferSampler : m_ssrSampler;
428
429 // sceneHDR — forward pass finalLayout is SHADER_READ_ONLY_OPTIMAL.
430 infos[4].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
431 infos[4].imageView = m_hdrSceneView;
432 infos[4].sampler = m_ssrSampler;
433
434 std::array<VkWriteDescriptorSet, 5> writes{};
435 for (uint32_t i = 0; i < writes.size(); ++i) {
436 writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
437 writes[i].dstSet = m_ssrInputSets[f];
438 writes[i].dstBinding = i;
439 writes[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
440 writes[i].descriptorCount = 1;
441 writes[i].pImageInfo = &infos[i];
442 }
443 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()),
444 writes.data(), 0, nullptr);
445 }
446}
447
448/// Fills the SSR UBO with the cached camera matrices, camera position, and ray march parameters.
449void VulkanRenderer::UpdateSSRUBO() {
450 if (!m_ssrResourcesCreated || !m_ssrUboMapped[currentFrame]) return;
451
452 SSRParams p{};
453 memcpy(p.View, m_cachedView, sizeof(p.View));
454 memcpy(p.Projection, m_cachedProjection, sizeof(p.Projection));
455 memcpy(p.InvViewProj, m_cachedInvViewProj, sizeof(p.InvViewProj));
456
457 const auto& camPos = Camera::GetMainCameraPosition();
458 p.CameraPos[0] = camPos.GetX();
459 p.CameraPos[1] = camPos.GetY();
460 p.CameraPos[2] = camPos.GetZ();
461 p.CameraPos[3] = 0.0f;
462
463 p.ScreenW = static_cast<float>(scExtent.width);
464 p.ScreenH = static_cast<float>(scExtent.height);
465
466 // Quality vs perf defaults — 32 coarse + 8 binary is the sweet spot
467 // for full-res UE-style SSR. Thickness in view-space *depth* units
468 // (post-divide), 0.02 catches near+mid hits without smearing through
469 // thin geometry.
470 p.MaxDistance = 20.0f;
471 p.Thickness = 0.5f;
472 p.NumSteps = 20;
473 p.NumBinarySteps = 6;
474 p.RoughnessThreshold = 0.9f;
475 p._pad = 0.0f;
476
477 memcpy(m_ssrUboMapped[currentFrame], &p, sizeof(p));
478}
479
480/// Ray marches screen-space reflections into the SSR buffer, or clears it when SSR is disabled.
481void VulkanRenderer::RenderSSRPass() {
482 if (!m_ssrResourcesCreated) return;
483
484 // Depth must be in DEPTH_STENCIL_READ_ONLY_OPTIMAL before SSR samples it.
485 // When TAA is enabled (and ran before SSR), it already issued this barrier.
486 // When TAA is disabled, do it here instead.
487 if (!m_taaResourcesCreated || !m_taaEnabled) {
488 VkImageMemoryBarrier depthBarrier{};
489 depthBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
490 depthBarrier.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
491 depthBarrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
492 depthBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
493 depthBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
494 depthBarrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
495 depthBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
496 depthBarrier.image = depthImage;
497 depthBarrier.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
498 vkCmdPipelineBarrier(command,
499 VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
500 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
501 0, 0, nullptr, 0, nullptr, 1, &depthBarrier);
502 }
503
504 // When SSR is disabled we still need the reflection buffer in a defined
505 // state for the composite pass. The cheapest way is a single render-pass
506 // begin that (with DONT_CARE load) transitions UNDEFINED -> SHADER_READ_ONLY
507 // via finalLayout — but we also need actual zeroed contents. Use a
508 // vkCmdClearColorImage instead (image is in UNDEFINED -> TRANSFER_DST ->
509 // SHADER_READ_ONLY). Since the image is re-defined each frame the
510 // UNDEFINED initial layout is fine.
511 if (!m_ssrEnabled) {
512 // Already primed to black SHADER_READ_ONLY — skip the redundant
513 // per-frame clear. Re-prime only if the enabled path dirtied it.
514 if (m_ssrFallbackPrimed) return;
515 VkImageMemoryBarrier toClear{};
516 toClear.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
517 toClear.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
518 toClear.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
519 toClear.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
520 toClear.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
521 toClear.srcAccessMask = 0;
522 toClear.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
523 toClear.image = m_ssrImage;
524 toClear.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
525 vkCmdPipelineBarrier(command,
526 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
527 0, 0, nullptr, 0, nullptr, 1, &toClear);
528
529 VkClearColorValue black{};
530 VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
531 vkCmdClearColorImage(command, m_ssrImage,
532 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
533 &black, 1, &range);
534
535 VkImageMemoryBarrier toRead = toClear;
536 toRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
537 toRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
538 toRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
539 toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
540 vkCmdPipelineBarrier(command,
541 VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
542 0, 0, nullptr, 0, nullptr, 1, &toRead);
543 m_ssrFallbackPrimed = true;
544 return;
545 }
546
547 // Enabled path dirties the SSR image; force a re-prime if SSR is later
548 // disabled so the composite doesn't sample stale reflections.
549 m_ssrFallbackPrimed = false;
550
551 UpdateSSRUBO();
552
553 VkRenderPassBeginInfo rp{};
554 rp.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
555 rp.renderPass = m_ssrRenderPass;
556 rp.framebuffer = m_ssrFramebuffer;
557 rp.renderArea.offset = {0, 0};
558 rp.renderArea.extent = scExtent;
559 rp.clearValueCount = 0; // DONT_CARE load — we write every pixel
560
561 vkCmdBeginRenderPass(command, &rp, VK_SUBPASS_CONTENTS_INLINE);
562 FillFullscreenViewportScissor(command, scExtent);
563 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, m_ssrPipeline);
564
565 VkDescriptorSet sets[2] = { m_ssrInputSets[currentFrame], m_ssrUboSets[currentFrame] };
566 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
567 m_ssrPipelineLayout, 0, 2, sets, 0, nullptr);
568 vkCmdDraw(command, 3, 1, 0, 0);
569 vkCmdEndRenderPass(command);
570}
571
572} // namespace RenderEngine
573} // namespace Sleak
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_INFO(...)
Definition Logger.hpp:20
static const Math::Vector3D & GetMainCameraPosition()
Definition Camera.hpp:111
Backend-facing rendering layer shared by the four graphics backends.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10