SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
VulkanShadow.cpp
Go to the documentation of this file.
3
5#include <algorithm>
6#include <array>
7#include <cstring>
8#include <vector>
9#include "Core/Logger.hpp"
10
11namespace Sleak {
12 namespace RenderEngine {
13
14/// Creates the shadow depth image, sampler, render pass, and framebuffer.
15bool VulkanRenderer::CreateShadowResources() {
16 // 1. Create shadow depth image (2048x2048, D32_SFLOAT)
17 VkImageCreateInfo imageInfo{};
18 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
19 imageInfo.imageType = VK_IMAGE_TYPE_2D;
20 imageInfo.extent = {m_shadowMapResolution, m_shadowMapResolution, 1};
21 imageInfo.mipLevels = 1;
22 imageInfo.arrayLayers = 1;
23 imageInfo.format = VK_FORMAT_D32_SFLOAT;
24 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
25 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
26 imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |
27 VK_IMAGE_USAGE_SAMPLED_BIT;
28 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
29 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
30
31 if (vkCreateImage(device, &imageInfo, nullptr, &m_shadowImage) != VK_SUCCESS) {
32 SLEAK_ERROR("Failed to create shadow map image!");
33 return false;
34 }
35
36 VkMemoryRequirements memReqs;
37 vkGetImageMemoryRequirements(device, m_shadowImage, &memReqs);
38
39 VkMemoryAllocateInfo allocInfo{};
40 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
41 allocInfo.allocationSize = memReqs.size;
42 allocInfo.memoryTypeIndex = FindMemoryType(memReqs.memoryTypeBits,
43 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
44
45 if (vkAllocateMemory(device, &allocInfo, nullptr, &m_shadowImageMemory) != VK_SUCCESS) {
46 SLEAK_ERROR("Failed to allocate shadow map memory!");
47 return false;
48 }
49
50 vkBindImageMemory(device, m_shadowImage, m_shadowImageMemory, 0);
51
52 // 2. Create image view (DEPTH aspect)
53 VkImageViewCreateInfo viewInfo{};
54 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
55 viewInfo.image = m_shadowImage;
56 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
57 viewInfo.format = VK_FORMAT_D32_SFLOAT;
58 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
59 viewInfo.subresourceRange.baseMipLevel = 0;
60 viewInfo.subresourceRange.levelCount = 1;
61 viewInfo.subresourceRange.baseArrayLayer = 0;
62 viewInfo.subresourceRange.layerCount = 1;
63
64 if (vkCreateImageView(device, &viewInfo, nullptr, &m_shadowImageView) != VK_SUCCESS) {
65 SLEAK_ERROR("Failed to create shadow map image view!");
66 return false;
67 }
68
69 // 3. Create comparison sampler with bilinear filtering for smooth PCF
70 VkSamplerCreateInfo samplerInfo{};
71 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
72 samplerInfo.magFilter = VK_FILTER_LINEAR;
73 samplerInfo.minFilter = VK_FILTER_LINEAR;
74 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
75 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
76 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
77 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
78 samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
79 samplerInfo.compareEnable = VK_TRUE;
80 samplerInfo.compareOp = VK_COMPARE_OP_LESS;
81 samplerInfo.minLod = 0.0f;
82 samplerInfo.maxLod = 1.0f;
83 samplerInfo.anisotropyEnable = VK_FALSE;
84
85 if (vkCreateSampler(device, &samplerInfo, nullptr, &m_shadowSampler) != VK_SUCCESS) {
86 SLEAK_ERROR("Failed to create shadow map sampler!");
87 return false;
88 }
89
90 // 3b. Create non-comparison sampler for PCSS blocker search.
91 // The blocker pass needs raw depth values to average, so compareEnable is off
92 // and we switch to nearest filtering to sample individual texels cleanly.
93 VkSamplerCreateInfo rawSamplerInfo = samplerInfo;
94 rawSamplerInfo.compareEnable = VK_FALSE;
95 rawSamplerInfo.magFilter = VK_FILTER_NEAREST;
96 rawSamplerInfo.minFilter = VK_FILTER_NEAREST;
97
98 if (vkCreateSampler(device, &rawSamplerInfo, nullptr, &m_shadowRawSampler) != VK_SUCCESS) {
99 SLEAK_ERROR("Failed to create shadow map raw sampler!");
100 return false;
101 }
102
103 // 4. Create depth-only render pass
104 VkAttachmentDescription depthAttachment{};
105 depthAttachment.format = VK_FORMAT_D32_SFLOAT;
106 depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
107 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
108 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
109 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
110 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
111 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
112 depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
113
114 VkAttachmentReference depthRef{};
115 depthRef.attachment = 0;
116 depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
117
118 VkSubpassDescription subpass{};
119 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
120 subpass.colorAttachmentCount = 0;
121 subpass.pDepthStencilAttachment = &depthRef;
122
123 // Dependencies for layout transitions
124 std::array<VkSubpassDependency, 2> dependencies{};
125
126 dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
127 dependencies[0].dstSubpass = 0;
128 dependencies[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
129 dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
130 dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
131 dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
132 dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
133
134 dependencies[1].srcSubpass = 0;
135 dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
136 dependencies[1].srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
137 dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
138 dependencies[1].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
139 dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
140 dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
141
142 VkRenderPassCreateInfo renderPassInfo{};
143 renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
144 renderPassInfo.attachmentCount = 1;
145 renderPassInfo.pAttachments = &depthAttachment;
146 renderPassInfo.subpassCount = 1;
147 renderPassInfo.pSubpasses = &subpass;
148 renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
149 renderPassInfo.pDependencies = dependencies.data();
150
151 if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &m_shadowRenderPass) != VK_SUCCESS) {
152 SLEAK_ERROR("Failed to create shadow render pass!");
153 return false;
154 }
155
156 // 5. Create framebuffer
157 VkFramebufferCreateInfo fbInfo{};
158 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
159 fbInfo.renderPass = m_shadowRenderPass;
160 fbInfo.attachmentCount = 1;
161 fbInfo.pAttachments = &m_shadowImageView;
162 fbInfo.width = m_shadowMapResolution;
163 fbInfo.height = m_shadowMapResolution;
164 fbInfo.layers = 1;
165
166 if (vkCreateFramebuffer(device, &fbInfo, nullptr, &m_shadowFramebuffer) != VK_SUCCESS) {
167 SLEAK_ERROR("Failed to create shadow framebuffer!");
168 return false;
169 }
170
171 // 6. Transition shadow image to DEPTH_STENCIL_READ_ONLY_OPTIMAL so the
172 // descriptor is valid even before the first shadow pass runs.
173 // Depth images must use this layout (not SHADER_READ_ONLY_OPTIMAL)
174 // for sampler access; the wrong layout causes VK_ERROR_DEVICE_LOST.
175 {
176 VkCommandBufferAllocateInfo cmdAllocInfo{};
177 cmdAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
178 cmdAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
179 cmdAllocInfo.commandPool = commands;
180 cmdAllocInfo.commandBufferCount = 1;
181
182 VkCommandBuffer cmdBuf;
183 vkAllocateCommandBuffers(device, &cmdAllocInfo, &cmdBuf);
184
185 VkCommandBufferBeginInfo cmdBeginInfo{};
186 cmdBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
187 cmdBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
188 vkBeginCommandBuffer(cmdBuf, &cmdBeginInfo);
189
190 VkImageMemoryBarrier barrier{};
191 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
192 barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
193 barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
194 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
195 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
196 barrier.image = m_shadowImage;
197 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
198 barrier.subresourceRange.baseMipLevel = 0;
199 barrier.subresourceRange.levelCount = 1;
200 barrier.subresourceRange.baseArrayLayer = 0;
201 barrier.subresourceRange.layerCount = 1;
202 barrier.srcAccessMask = 0;
203 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
204
205 vkCmdPipelineBarrier(cmdBuf,
206 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
207 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
208 0, 0, nullptr, 0, nullptr, 1, &barrier);
209
210 vkEndCommandBuffer(cmdBuf);
211
212 VkSubmitInfo layoutSubmit{};
213 layoutSubmit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
214 layoutSubmit.commandBufferCount = 1;
215 layoutSubmit.pCommandBuffers = &cmdBuf;
216
217 VkFenceCreateInfo layoutFenceInfo{};
218 layoutFenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
219 VkFence layoutFence;
220 vkCreateFence(device, &layoutFenceInfo, nullptr, &layoutFence);
221 vkQueueSubmit(graphicsQueue, 1, &layoutSubmit, layoutFence);
222 vkWaitForFences(device, 1, &layoutFence, VK_TRUE, UINT64_MAX);
223 vkDestroyFence(device, layoutFence, nullptr);
224 vkFreeCommandBuffers(device, commands, 1, &cmdBuf);
225 }
226
227 // 7. Create shadow pipeline
228 if (!CreateShadowPipeline()) {
229 SLEAK_ERROR("Failed to create shadow pipeline!");
230 return false;
231 }
232
233 // Write shadow sampler to set 3 descriptors (UBO resources already created).
234 // Binding 0 uses the compare sampler for hardware PCF; binding 1 uses the
235 // raw sampler so the PCSS blocker search can read un-compared depth values.
236 if (m_lightUBOCreated && m_shadowImageView && m_shadowSampler && m_shadowRawSampler) {
237 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
238 // Depth images must use DEPTH_STENCIL_READ_ONLY_OPTIMAL for sampler access.
239 VkDescriptorImageInfo compareInfo{};
240 compareInfo.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
241 compareInfo.imageView = m_shadowImageView;
242 compareInfo.sampler = m_shadowSampler;
243
244 VkDescriptorImageInfo rawInfo{};
245 rawInfo.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
246 rawInfo.imageView = m_shadowImageView;
247 rawInfo.sampler = m_shadowRawSampler;
248
249 std::array<VkWriteDescriptorSet, 2> writes{};
250 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
251 writes[0].dstSet = m_shadowSamplerDescriptorSets[i];
252 writes[0].dstBinding = 0;
253 writes[0].dstArrayElement = 0;
254 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
255 writes[0].descriptorCount = 1;
256 writes[0].pImageInfo = &compareInfo;
257
258 writes[1] = writes[0];
259 writes[1].dstBinding = 1;
260 writes[1].pImageInfo = &rawInfo;
261
262 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()),
263 writes.data(), 0, nullptr);
264 }
265 }
266
268 SLEAK_INFO("VulkanRenderer: Shadow mapping resources created ({}x{} shadow map)",
270 return true;
271}
272
273/// Compiles the shadow depth shader and creates the shadow pass pipeline.
274bool VulkanRenderer::CreateShadowPipeline() {
275 m_shadowShader = new VulkanShader(device);
276 if (!m_shadowShader->compileVertexOnly("assets/shaders/shadow_depth.vert.spv")) {
277 SLEAK_ERROR("VulkanRenderer: Failed to compile shadow depth shader");
278 delete m_shadowShader;
279 m_shadowShader = nullptr;
280 return false;
281 }
282
283 // Vertex-only pipeline (no fragment shader)
284 VkPipelineShaderStageCreateInfo shaderStage = m_shadowShader->GetVertexInfo();
285
286 std::vector<VkDynamicState> dynamicStates = {
287 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
288
289 VkPipelineDynamicStateCreateInfo dynamicState{};
290 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
291 dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
292 dynamicState.pDynamicStates = dynamicStates.data();
293
294 // Same vertex layout as main pipeline
295 VkVertexInputBindingDescription bindingDescription{};
296 bindingDescription.binding = 0;
297 bindingDescription.stride = sizeof(Vertex);
298 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
299
300 std::array<VkVertexInputAttributeDescription, 7> attributeDescs{};
301 attributeDescs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, px)};
302 attributeDescs[1] = {1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, nx)};
303 attributeDescs[2] = {2, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, tx)};
304 attributeDescs[3] = {3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, r)};
305 attributeDescs[4] = {4, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, u)};
306 attributeDescs[5] = {5, 0, VK_FORMAT_R32G32B32A32_SINT, offsetof(Vertex, boneIDs)};
307 attributeDescs[6] = {6, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, boneWeights)};
308
309 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
310 vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
311 vertexInputInfo.vertexBindingDescriptionCount = 1;
312 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
313 vertexInputInfo.vertexAttributeDescriptionCount =
314 static_cast<uint32_t>(attributeDescs.size());
315 vertexInputInfo.pVertexAttributeDescriptions = attributeDescs.data();
316
317 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
318 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
319 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
320 inputAssembly.primitiveRestartEnable = VK_FALSE;
321
322 VkPipelineViewportStateCreateInfo viewportState{};
323 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
324 viewportState.viewportCount = 1;
325 viewportState.scissorCount = 1;
326
327 VkPipelineRasterizationStateCreateInfo rasterizer{};
328 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
329 rasterizer.depthClampEnable = VK_FALSE;
330 rasterizer.rasterizerDiscardEnable = VK_FALSE;
331 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
332 rasterizer.lineWidth = 1.0f;
333 rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; // same winding as main pass
334 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
335 rasterizer.depthBiasEnable = VK_TRUE;
336 rasterizer.depthBiasConstantFactor = 1.25f;
337 rasterizer.depthBiasSlopeFactor = 1.75f;
338 rasterizer.depthBiasClamp = 0.0f;
339
340 VkPipelineMultisampleStateCreateInfo msaa{};
341 msaa.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
342 msaa.sampleShadingEnable = VK_FALSE;
343 msaa.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
344
345 VkPipelineDepthStencilStateCreateInfo depthStencil{};
346 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
347 depthStencil.depthTestEnable = VK_TRUE;
348 depthStencil.depthWriteEnable = VK_TRUE;
349 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
350 depthStencil.depthBoundsTestEnable = VK_FALSE;
351 depthStencil.stencilTestEnable = VK_FALSE;
352
353 // No color blend (depth-only, no color attachment)
354 VkPipelineColorBlendStateCreateInfo colorBlendInfo{};
355 colorBlendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
356 colorBlendInfo.logicOpEnable = VK_FALSE;
357 colorBlendInfo.attachmentCount = 0;
358
359 VkGraphicsPipelineCreateInfo pipelineInfo{};
360 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
361 pipelineInfo.stageCount = 1; // Vertex-only
362 pipelineInfo.pStages = &shaderStage;
363 pipelineInfo.pVertexInputState = &vertexInputInfo;
364 pipelineInfo.pInputAssemblyState = &inputAssembly;
365 pipelineInfo.pViewportState = &viewportState;
366 pipelineInfo.pRasterizationState = &rasterizer;
367 pipelineInfo.pMultisampleState = &msaa;
368 pipelineInfo.pDepthStencilState = &depthStencil;
369 pipelineInfo.pColorBlendState = &colorBlendInfo;
370 pipelineInfo.pDynamicState = &dynamicState;
371 pipelineInfo.layout = pipelineLay;
372 pipelineInfo.renderPass = m_shadowRenderPass;
373 pipelineInfo.subpass = 0;
374 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
375 pipelineInfo.basePipelineIndex = -1;
376
377 VkResult result = vkCreateGraphicsPipelines(
378 device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &m_shadowPipeline);
379 if (result != VK_SUCCESS) {
380 SLEAK_ERROR("VulkanRenderer: Failed to create shadow pipeline!");
381 return false;
382 }
383
384 SLEAK_INFO("VulkanRenderer: Shadow pipeline created successfully");
385 return true;
386}
387
388/// Creates the per-frame light and shadow UBO buffers and descriptor sets.
389bool VulkanRenderer::CreateShadowLightUBOResources() {
390 static constexpr VkDeviceSize uboSize = sizeof(ShadowLightUBO);
391
392 // Create per-frame UBO buffers
393 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
394 VkBufferCreateInfo bufferInfo{};
395 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
396 bufferInfo.size = uboSize;
397 bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
398 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
399
400 if (vkCreateBuffer(device, &bufferInfo, nullptr, &m_lightUBOBuffers[i]) != VK_SUCCESS) {
401 SLEAK_ERROR("Failed to create light UBO buffer!");
402 return false;
403 }
404
405 VkMemoryRequirements memReqs;
406 vkGetBufferMemoryRequirements(device, m_lightUBOBuffers[i], &memReqs);
407
408 VkMemoryAllocateInfo allocInfo{};
409 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
410 allocInfo.allocationSize = memReqs.size;
411 allocInfo.memoryTypeIndex = FindMemoryType(memReqs.memoryTypeBits,
412 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
413
414 if (vkAllocateMemory(device, &allocInfo, nullptr, &m_lightUBOMemory[i]) != VK_SUCCESS) {
415 SLEAK_ERROR("Failed to allocate light UBO memory!");
416 return false;
417 }
418
419 vkBindBufferMemory(device, m_lightUBOBuffers[i], m_lightUBOMemory[i], 0);
420 vkMapMemory(device, m_lightUBOMemory[i], 0, uboSize, 0, &m_lightUBOMapped[i]);
421 memset(m_lightUBOMapped[i], 0, uboSize);
422 }
423
424 // Create descriptor pool for light UBO + shadow samplers.
425 // Two shadow samplers per frame now (compare + raw for PCSS blocker search).
426 std::array<VkDescriptorPoolSize, 2> poolSizes{};
427 poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
428 poolSizes[0].descriptorCount = MAX_FRAMES_IN_FLIGHT;
429 poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
430 poolSizes[1].descriptorCount = MAX_FRAMES_IN_FLIGHT * 2;
431
432 VkDescriptorPoolCreateInfo poolInfo{};
433 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
434 poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
435 poolInfo.pPoolSizes = poolSizes.data();
436 poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT * 2; // UBO sets + sampler sets
437
438 if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &m_lightUBODescriptorPool) != VK_SUCCESS) {
439 SLEAK_ERROR("Failed to create light UBO descriptor pool!");
440 return false;
441 }
442
443 // Allocate light UBO descriptor sets (set 2)
444 std::array<VkDescriptorSetLayout, MAX_FRAMES_IN_FLIGHT> uboLayouts;
445 uboLayouts.fill(m_lightUBODescriptorSetLayout);
446
447 VkDescriptorSetAllocateInfo uboAllocInfo{};
448 uboAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
449 uboAllocInfo.descriptorPool = m_lightUBODescriptorPool;
450 uboAllocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT;
451 uboAllocInfo.pSetLayouts = uboLayouts.data();
452
453 if (vkAllocateDescriptorSets(device, &uboAllocInfo, m_lightUBODescriptorSets.data()) != VK_SUCCESS) {
454 SLEAK_ERROR("Failed to allocate light UBO descriptor sets!");
455 return false;
456 }
457
458 // Write UBO descriptors
459 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
460 VkDescriptorBufferInfo bufInfo{};
461 bufInfo.buffer = m_lightUBOBuffers[i];
462 bufInfo.offset = 0;
463 bufInfo.range = uboSize;
464
465 VkWriteDescriptorSet write{};
466 write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
467 write.dstSet = m_lightUBODescriptorSets[i];
468 write.dstBinding = 0;
469 write.dstArrayElement = 0;
470 write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
471 write.descriptorCount = 1;
472 write.pBufferInfo = &bufInfo;
473
474 vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
475 }
476
477 // Allocate shadow sampler descriptor sets (set 3)
478 std::array<VkDescriptorSetLayout, MAX_FRAMES_IN_FLIGHT> samplerLayouts;
479 samplerLayouts.fill(m_shadowSamplerDescriptorSetLayout);
480
481 VkDescriptorSetAllocateInfo samplerAllocInfo{};
482 samplerAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
483 samplerAllocInfo.descriptorPool = m_lightUBODescriptorPool;
484 samplerAllocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT;
485 samplerAllocInfo.pSetLayouts = samplerLayouts.data();
486
487 if (vkAllocateDescriptorSets(device, &samplerAllocInfo, m_shadowSamplerDescriptorSets.data()) != VK_SUCCESS) {
488 SLEAK_ERROR("Failed to allocate shadow sampler descriptor sets!");
489 return false;
490 }
491
492 // Write default shadow sampler descriptors (using default texture as placeholder)
493 // These will be overwritten with actual shadow map when shadow resources are created.
494 // Both binding=0 (compare) and binding=1 (raw) must be populated or the layout
495 // is incomplete and first-frame sampling reads undefined memory.
496 if (m_defaultTexture) {
497 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
498 VkDescriptorImageInfo imageInfo{};
499 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
500 imageInfo.imageView = m_defaultTexture->GetImageView();
501 imageInfo.sampler = m_defaultTexture->GetSampler();
502
503 std::array<VkWriteDescriptorSet, 2> writes{};
504 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
505 writes[0].dstSet = m_shadowSamplerDescriptorSets[i];
506 writes[0].dstBinding = 0;
507 writes[0].dstArrayElement = 0;
508 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
509 writes[0].descriptorCount = 1;
510 writes[0].pImageInfo = &imageInfo;
511
512 writes[1] = writes[0];
513 writes[1].dstBinding = 1;
514
515 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()),
516 writes.data(), 0, nullptr);
517 }
518 }
519
520 m_lightUBOCreated = true;
521 SLEAK_INFO("VulkanRenderer: Light UBO and shadow sampler resources created");
522 return true;
523}
524
525/// Destroys the shadow map image, pipeline, render pass, and light UBO resources.
526void VulkanRenderer::CleanupShadowResources() {
527 if (m_shadowPipeline) {
528 vkDestroyPipeline(device, m_shadowPipeline, nullptr);
529 m_shadowPipeline = VK_NULL_HANDLE;
530 }
531 delete m_shadowShader;
532 m_shadowShader = nullptr;
533
534 if (m_shadowFramebuffer) {
535 vkDestroyFramebuffer(device, m_shadowFramebuffer, nullptr);
536 m_shadowFramebuffer = VK_NULL_HANDLE;
537 }
538 if (m_shadowRenderPass) {
539 vkDestroyRenderPass(device, m_shadowRenderPass, nullptr);
540 m_shadowRenderPass = VK_NULL_HANDLE;
541 }
542 if (m_shadowSampler) {
543 vkDestroySampler(device, m_shadowSampler, nullptr);
544 m_shadowSampler = VK_NULL_HANDLE;
545 }
546 if (m_shadowRawSampler) {
547 vkDestroySampler(device, m_shadowRawSampler, nullptr);
548 m_shadowRawSampler = VK_NULL_HANDLE;
549 }
550 if (m_shadowImageView) {
551 vkDestroyImageView(device, m_shadowImageView, nullptr);
552 m_shadowImageView = VK_NULL_HANDLE;
553 }
554 if (m_shadowImage) {
555 vkDestroyImage(device, m_shadowImage, nullptr);
556 m_shadowImage = VK_NULL_HANDLE;
557 }
558 if (m_shadowImageMemory) {
559 vkFreeMemory(device, m_shadowImageMemory, nullptr);
560 m_shadowImageMemory = VK_NULL_HANDLE;
561 }
562
563 // Cleanup light UBO resources
564 if (m_lightUBOCreated) {
565 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
566 if (m_lightUBOMapped[i]) {
567 vkUnmapMemory(device, m_lightUBOMemory[i]);
568 m_lightUBOMapped[i] = nullptr;
569 }
570 if (m_lightUBOBuffers[i]) {
571 vkDestroyBuffer(device, m_lightUBOBuffers[i], nullptr);
572 m_lightUBOBuffers[i] = VK_NULL_HANDLE;
573 }
574 if (m_lightUBOMemory[i]) {
575 vkFreeMemory(device, m_lightUBOMemory[i], nullptr);
576 m_lightUBOMemory[i] = VK_NULL_HANDLE;
577 }
578 }
579 m_lightUBOCreated = false;
580 }
581
582 if (m_lightUBODescriptorPool) {
583 vkDestroyDescriptorPool(device, m_lightUBODescriptorPool, nullptr);
584 m_lightUBODescriptorPool = VK_NULL_HANDLE;
585 }
586
588}
589
590/// Marks the shadow pass active and invalidates the push constant cache.
592 m_shadowPassActive = true;
593 m_shadowPCCacheValid = false;
594}
595
596/// Marks the shadow pass inactive.
598 m_shadowPassActive = false;
599}
600
601/// Copies light and shadow data into the current frame's mapped UBO.
602void VulkanRenderer::UpdateShadowLightUBO(const void* data, uint32_t size) {
603 if (!m_lightUBOCreated || !data) return;
604 uint32_t copySize = std::min(size, static_cast<uint32_t>(sizeof(ShadowLightUBO)));
605 memcpy(m_lightUBOMapped[currentFrame], data, copySize);
606}
607
608/// Stages the light view-projection matrix for commit at the next BeginRender.
609void VulkanRenderer::SetLightVP(const float* lightVP) {
610 // Stage only — commit happens at the next BeginRender. This keeps
611 // m_lightVP frozen for the duration of a frame so the shadow pass and
612 // the main pass agree on the transform (fixes per-frame shadow jitter
613 // caused by LightManager::UpdateAndBind mutating m_lightVP mid-frame,
614 // between the shadow pass and the main pass).
615 if (lightVP) {
616 memcpy(m_pendingLightVP, lightVP, sizeof(m_pendingLightVP));
617 m_hasPendingLightVP = true;
618 }
619}
620
621} // namespace RenderEngine
622} // namespace Sleak
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_INFO(...)
Definition Logger.hpp:20
void SetLightVP(const float *lightVP) override
Stages the light view-projection matrix for commit at the next BeginRender.
virtual void BeginShadowPass() override
Marks the shadow pass active and invalidates the push constant cache.
virtual void EndShadowPass() override
Marks the shadow pass inactive.
void UpdateShadowLightUBO(const void *data, uint32_t size) override
Copies light and shadow data into the current frame's mapped UBO.
Backend-facing rendering layer shared by the four graphics backends.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
Shadow-pass UBO: light/shadow parameters, fog, and extra fill lights (set 2, binding 0).