SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
VulkanDescriptors.cpp
Go to the documentation of this file.
3
4#include <Core/Window.hpp>
5#include <array>
6#include <cstring>
7#include <vector>
8#include "Core/Logger.hpp"
9
10namespace Sleak {
11 namespace RenderEngine {
12
13/// Creates the texture, bone UBO, light UBO, and shadow sampler descriptor set layouts.
14bool VulkanRenderer::CreateDescriptorSetLayout() {
15 // Set 0: texture sampler
16 VkDescriptorSetLayoutBinding samplerBinding{};
17 samplerBinding.binding = 0;
18 samplerBinding.descriptorType =
19 VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
20 samplerBinding.descriptorCount = 1;
21 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
22 samplerBinding.pImmutableSamplers = nullptr;
23
24 VkDescriptorSetLayoutCreateInfo layoutInfo{};
25 layoutInfo.sType =
26 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
27 layoutInfo.bindingCount = 1;
28 layoutInfo.pBindings = &samplerBinding;
29
30 if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr,
31 &descriptorSetLayout) != VK_SUCCESS)
32 SLEAK_RETURN_ERR("Failed to create descriptor set layout!");
33
34 // Set 1: bone UBO (for skeletal animation)
35 VkDescriptorSetLayoutBinding boneUBOBinding{};
36 boneUBOBinding.binding = 0;
37 boneUBOBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
38 boneUBOBinding.descriptorCount = 1;
39 boneUBOBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
40 boneUBOBinding.pImmutableSamplers = nullptr;
41
42 VkDescriptorSetLayoutCreateInfo boneLayoutInfo{};
43 boneLayoutInfo.sType =
44 VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
45 boneLayoutInfo.bindingCount = 1;
46 boneLayoutInfo.pBindings = &boneUBOBinding;
47
48 if (vkCreateDescriptorSetLayout(device, &boneLayoutInfo, nullptr,
49 &boneDescriptorSetLayout) != VK_SUCCESS)
50 SLEAK_RETURN_ERR("Failed to create bone descriptor set layout!");
51
52 // Set 2: light/shadow UBO
53 VkDescriptorSetLayoutBinding lightUBOBinding{};
54 lightUBOBinding.binding = 0;
55 lightUBOBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
56 lightUBOBinding.descriptorCount = 1;
57 lightUBOBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
58 lightUBOBinding.pImmutableSamplers = nullptr;
59
60 VkDescriptorSetLayoutCreateInfo lightUBOLayoutInfo{};
61 lightUBOLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
62 lightUBOLayoutInfo.bindingCount = 1;
63 lightUBOLayoutInfo.pBindings = &lightUBOBinding;
64
65 if (vkCreateDescriptorSetLayout(device, &lightUBOLayoutInfo, nullptr,
66 &m_lightUBODescriptorSetLayout) != VK_SUCCESS)
67 SLEAK_RETURN_ERR("Failed to create light UBO descriptor set layout!");
68
69 // Set 3: shadow map samplers — binding 0 = compare sampler (PCF),
70 // binding 1 = raw sampler (PCSS blocker search)
71 std::array<VkDescriptorSetLayoutBinding, 2> shadowSamplerBindings{};
72 shadowSamplerBindings[0].binding = 0;
73 shadowSamplerBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
74 shadowSamplerBindings[0].descriptorCount = 1;
75 shadowSamplerBindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
76 shadowSamplerBindings[0].pImmutableSamplers = nullptr;
77
78 shadowSamplerBindings[1].binding = 1;
79 shadowSamplerBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
80 shadowSamplerBindings[1].descriptorCount = 1;
81 shadowSamplerBindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
82 shadowSamplerBindings[1].pImmutableSamplers = nullptr;
83
84 VkDescriptorSetLayoutCreateInfo shadowSamplerLayoutInfo{};
85 shadowSamplerLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
86 shadowSamplerLayoutInfo.bindingCount = static_cast<uint32_t>(shadowSamplerBindings.size());
87 shadowSamplerLayoutInfo.pBindings = shadowSamplerBindings.data();
88
89 if (vkCreateDescriptorSetLayout(device, &shadowSamplerLayoutInfo, nullptr,
90 &m_shadowSamplerDescriptorSetLayout) != VK_SUCCESS)
91 SLEAK_RETURN_ERR("Failed to create shadow sampler descriptor set layout!");
92
93 return true;
94}
95
96
97/// Creates the descriptor pool backing the per-texture descriptor sets.
98bool VulkanRenderer::CreateDescriptorPool() {
99 uint32_t imageCount =
100 static_cast<uint32_t>(swapChainImages.size());
101
102 // Allow up to 128 textures, each needing imageCount descriptor sets
103 static constexpr uint32_t MAX_TEXTURES = 1024;
104 uint32_t totalSets = imageCount * MAX_TEXTURES;
105
106 VkDescriptorPoolSize poolSize{};
107 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
108 poolSize.descriptorCount = totalSets;
109
110 VkDescriptorPoolCreateInfo poolInfo{};
111 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
112 poolInfo.poolSizeCount = 1;
113 poolInfo.pPoolSizes = &poolSize;
114 poolInfo.maxSets = totalSets;
115
116 if (vkCreateDescriptorPool(device, &poolInfo, nullptr,
117 &descriptorPool) != VK_SUCCESS)
118 SLEAK_RETURN_ERR("Failed to create descriptor pool!");
119
120 return true;
121}
122
123
124/// Allocates one texture descriptor set per swapchain image.
125bool VulkanRenderer::AllocateDescriptorSets() {
126 uint32_t imageCount =
127 static_cast<uint32_t>(swapChainImages.size());
128
129 std::vector<VkDescriptorSetLayout> layouts(imageCount,
130 descriptorSetLayout);
131
132 VkDescriptorSetAllocateInfo allocInfo{};
133 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
134 allocInfo.descriptorPool = descriptorPool;
135 allocInfo.descriptorSetCount = imageCount;
136 allocInfo.pSetLayouts = layouts.data();
137
138 descriptorSets.resize(imageCount);
139 if (vkAllocateDescriptorSets(device, &allocInfo,
140 descriptorSets.data()) != VK_SUCCESS)
141 SLEAK_RETURN_ERR("Failed to allocate descriptor sets!");
142
143 return true;
144}
145
146
147/// Creates the fallback 1x1 white texture and writes it into the global descriptor sets.
148bool VulkanRenderer::CreateDefaultTexture() {
149 // Create a 1x1 white pixel texture as fallback so descriptor sets
150 // are always valid, even when no user texture is loaded.
151 uint32_t whitePixel = 0xFFFFFFFF; // RGBA(255,255,255,255)
152 m_defaultTexture = new VulkanTexture(device, physicalDevice, commands,
153 graphicsQueue);
154 if (!m_defaultTexture->LoadFromMemory(&whitePixel, 1, 1,
156 delete m_defaultTexture;
157 m_defaultTexture = nullptr;
158 return false;
159 }
160
161 // Allocate per-texture descriptor sets for the default texture
162 WriteTextureDescriptors(m_defaultTexture);
163
164 // Also write the default texture to the global descriptor sets
165 // (used as initial binding in BeginRender)
166 for (size_t i = 0; i < descriptorSets.size(); i++) {
167 VkDescriptorImageInfo imageInfo{};
168 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
169 imageInfo.imageView = m_defaultTexture->GetImageView();
170 imageInfo.sampler = m_defaultTexture->GetSampler();
171
172 VkWriteDescriptorSet descriptorWrite{};
173 descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
174 descriptorWrite.dstSet = descriptorSets[i];
175 descriptorWrite.dstBinding = 0;
176 descriptorWrite.dstArrayElement = 0;
177 descriptorWrite.descriptorType =
178 VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
179 descriptorWrite.descriptorCount = 1;
180 descriptorWrite.pImageInfo = &imageInfo;
181
182 vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
183 }
184 m_textureDescriptorsWritten = true;
185
186 return true;
187}
188
189
190/// Allocates and writes a per-texture descriptor set for the given texture.
191void VulkanRenderer::WriteTextureDescriptors(VulkanTexture* texture) {
192 if (!texture || texture->GetImageView() == VK_NULL_HANDLE ||
193 texture->GetSampler() == VK_NULL_HANDLE)
194 return;
195
196 uint32_t imageCount = static_cast<uint32_t>(swapChainImages.size());
197
198 // Allocate per-texture descriptor sets (one per swapchain image)
199 std::vector<VkDescriptorSetLayout> layouts(imageCount, descriptorSetLayout);
200
201 VkDescriptorSetAllocateInfo allocInfo{};
202 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
203 allocInfo.descriptorPool = descriptorPool;
204 allocInfo.descriptorSetCount = imageCount;
205 allocInfo.pSetLayouts = layouts.data();
206
207 std::vector<VkDescriptorSet> sets(imageCount);
208 if (vkAllocateDescriptorSets(device, &allocInfo, sets.data()) != VK_SUCCESS) {
209 SLEAK_ERROR("VulkanRenderer: Failed to allocate descriptor sets for texture");
210 return;
211 }
212
213 // Write the texture's imageView/sampler to each descriptor set
214 for (size_t i = 0; i < sets.size(); i++) {
215 VkDescriptorImageInfo imageInfo{};
216 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
217 imageInfo.imageView = texture->GetImageView();
218 imageInfo.sampler = texture->GetSampler();
219
220 VkWriteDescriptorSet descriptorWrite{};
221 descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
222 descriptorWrite.dstSet = sets[i];
223 descriptorWrite.dstBinding = 0;
224 descriptorWrite.dstArrayElement = 0;
225 descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
226 descriptorWrite.descriptorCount = 1;
227 descriptorWrite.pImageInfo = &imageInfo;
228
229 vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
230 }
231
232 texture->SetDescriptorSets(std::move(sets));
233}
234
235/// Initializes ImGui and its Vulkan backend against the active render pass.
237 if (!device || !instance || !graphicsQueue)
238 return false;
239
240 // In deferred mode, ImGui renders inside the bloom COMPOSITE pass (swapchain
241 // target, post-tonemap). Forward pass can't host ImGui because its output
242 // is linear HDR and it ends in SHADER_READ_ONLY_OPTIMAL for bloom sampling.
243 VkRenderPass imguiRenderPass = VK_NULL_HANDLE;
244 if (m_gbufferResourcesCreated && m_deferredEnabled && m_bloomCompositeRenderPass != VK_NULL_HANDLE) {
245 imguiRenderPass = m_bloomCompositeRenderPass;
246 } else if (m_gbufferResourcesCreated && m_deferredEnabled && m_forwardRenderPass != VK_NULL_HANDLE) {
247 imguiRenderPass = m_forwardRenderPass;
248 } else {
249 imguiRenderPass = renderPass;
250 }
251
252 if (!imguiRenderPass)
253 return false;
254
255 // ImGui backend allocates split SAMPLED_IMAGE + SAMPLER sets, not COMBINED_IMAGE_SAMPLER
256 VkDescriptorPoolSize poolSizes[] = {
257 {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 64},
258 {VK_DESCRIPTOR_TYPE_SAMPLER, IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE},
259 };
260
261 VkDescriptorPoolCreateInfo poolInfo{};
262 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
263 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
264 poolInfo.maxSets = 64 + IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE;
265 poolInfo.poolSizeCount = 2;
266 poolInfo.pPoolSizes = poolSizes;
267
268 if (vkCreateDescriptorPool(device, &poolInfo, nullptr,
269 &imguiDescriptorPool) != VK_SUCCESS) {
270 SLEAK_ERROR("Failed to create ImGUI descriptor pool!");
271 return false;
272 }
273
274 IMGUI_CHECKVERSION();
275 ImGui::CreateContext();
276 ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
277 ImGui::StyleColorsDark();
278
279 if (!ImGui_ImplSDL3_InitForVulkan(sdlWindow->GetSDLWindow()))
280 return false;
281
282 ImGui_ImplVulkan_InitInfo initInfo{};
283 initInfo.Instance = instance;
284 initInfo.PhysicalDevice = physicalDevice;
285 initInfo.Device = device;
286 initInfo.QueueFamily = QueueIDs.GraphicsIndex;
287 initInfo.Queue = graphicsQueue;
288 initInfo.DescriptorPool = imguiDescriptorPool;
289 initInfo.MinImageCount = 2;
290 initInfo.ImageCount =
291 static_cast<uint32_t>(swapChainImages.size());
292 initInfo.PipelineInfoMain.MSAASamples = (m_gbufferResourcesCreated && m_deferredEnabled)
293 ? VK_SAMPLE_COUNT_1_BIT : m_msaaSamples;
294 initInfo.PipelineInfoMain.RenderPass = imguiRenderPass;
295 initInfo.PipelineInfoMain.Subpass = 0;
296
297 if (!ImGui_ImplVulkan_Init(&initInfo))
298 return false;
299
300 bImInitialized = true;
301 return true;
302}
303
304/// Creates the per-frame bone UBO buffers and their descriptor sets.
305bool VulkanRenderer::CreateBoneUBOResources() {
306 if (m_boneUBOCreated) return true;
307
308 static constexpr uint32_t MAX_BONES = 256;
309 static constexpr VkDeviceSize boneUBOSize = MAX_BONES * 64; // 256 mat4 = 16384 bytes
310
311 // Create per-frame UBO buffers (host-visible, coherent for fast CPU writes)
312 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
313 VkBufferCreateInfo bufferInfo{};
314 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
315 bufferInfo.size = boneUBOSize;
316 bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
317 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
318
319 if (vkCreateBuffer(device, &bufferInfo, nullptr, &boneUBOBuffers[i]) != VK_SUCCESS) {
320 SLEAK_ERROR("Failed to create bone UBO buffer!");
321 return false;
322 }
323
324 VkMemoryRequirements memReqs;
325 vkGetBufferMemoryRequirements(device, boneUBOBuffers[i], &memReqs);
326
327 VkMemoryAllocateInfo allocInfo{};
328 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
329 allocInfo.allocationSize = memReqs.size;
330 allocInfo.memoryTypeIndex = FindMemoryType(memReqs.memoryTypeBits,
331 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
332
333 if (vkAllocateMemory(device, &allocInfo, nullptr, &boneUBOMemory[i]) != VK_SUCCESS) {
334 SLEAK_ERROR("Failed to allocate bone UBO memory!");
335 return false;
336 }
337
338 vkBindBufferMemory(device, boneUBOBuffers[i], boneUBOMemory[i], 0);
339 vkMapMemory(device, boneUBOMemory[i], 0, boneUBOSize, 0, &boneUBOMapped[i]);
340
341 // Initialize with identity matrices
342 auto* matrices = static_cast<float*>(boneUBOMapped[i]);
343 for (uint32_t b = 0; b < MAX_BONES; ++b) {
344 // Identity matrix in column-major order
345 for (int c = 0; c < 16; ++c)
346 matrices[b * 16 + c] = (c % 5 == 0) ? 1.0f : 0.0f;
347 }
348 }
349
350 // Create descriptor pool for bone UBO
351 VkDescriptorPoolSize poolSize{};
352 poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
353 poolSize.descriptorCount = MAX_FRAMES_IN_FLIGHT;
354
355 VkDescriptorPoolCreateInfo poolInfo{};
356 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
357 poolInfo.poolSizeCount = 1;
358 poolInfo.pPoolSizes = &poolSize;
359 poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT;
360
361 if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &boneDescriptorPool) != VK_SUCCESS) {
362 SLEAK_ERROR("Failed to create bone descriptor pool!");
363 return false;
364 }
365
366 // Allocate descriptor sets
367 std::array<VkDescriptorSetLayout, MAX_FRAMES_IN_FLIGHT> layouts;
368 layouts.fill(boneDescriptorSetLayout);
369
370 VkDescriptorSetAllocateInfo dsAllocInfo{};
371 dsAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
372 dsAllocInfo.descriptorPool = boneDescriptorPool;
373 dsAllocInfo.descriptorSetCount = MAX_FRAMES_IN_FLIGHT;
374 dsAllocInfo.pSetLayouts = layouts.data();
375
376 if (vkAllocateDescriptorSets(device, &dsAllocInfo, boneDescriptorSets.data()) != VK_SUCCESS) {
377 SLEAK_ERROR("Failed to allocate bone descriptor sets!");
378 return false;
379 }
380
381 // Write descriptor sets pointing to UBO buffers
382 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
383 VkDescriptorBufferInfo bufInfo{};
384 bufInfo.buffer = boneUBOBuffers[i];
385 bufInfo.offset = 0;
386 bufInfo.range = boneUBOSize;
387
388 VkWriteDescriptorSet descriptorWrite{};
389 descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
390 descriptorWrite.dstSet = boneDescriptorSets[i];
391 descriptorWrite.dstBinding = 0;
392 descriptorWrite.dstArrayElement = 0;
393 descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
394 descriptorWrite.descriptorCount = 1;
395 descriptorWrite.pBufferInfo = &bufInfo;
396
397 vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);
398 }
399
400 m_boneUBOCreated = true;
401 SLEAK_INFO("VulkanRenderer: Bone UBO resources created ({} bytes per frame)", boneUBOSize);
402 return true;
403}
404
405
406/// Destroys the bone UBO buffers, memory, and descriptor pool.
407void VulkanRenderer::CleanupBoneUBOResources() {
408 if (!m_boneUBOCreated) return;
409
410 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
411 if (boneUBOMapped[i]) {
412 vkUnmapMemory(device, boneUBOMemory[i]);
413 boneUBOMapped[i] = nullptr;
414 }
415 if (boneUBOBuffers[i]) {
416 vkDestroyBuffer(device, boneUBOBuffers[i], nullptr);
417 boneUBOBuffers[i] = VK_NULL_HANDLE;
418 }
419 if (boneUBOMemory[i]) {
420 vkFreeMemory(device, boneUBOMemory[i], nullptr);
421 boneUBOMemory[i] = VK_NULL_HANDLE;
422 }
423 }
424 if (boneDescriptorPool) {
425 vkDestroyDescriptorPool(device, boneDescriptorPool, nullptr);
426 boneDescriptorPool = VK_NULL_HANDLE;
427 }
428 m_boneUBOCreated = false;
429}
430
431/// Copies bone matrices into the current frame's UBO and binds its descriptor set.
433 if (!bFrameStarted) return;
434 if (!buffer) return;
435
436 // Lazily create bone UBO resources on first use
437 if (!m_boneUBOCreated) {
438 if (!CreateBoneUBOResources()) return;
439 }
440
441 auto* vkBuf = static_cast<VulkanBuffer*>(buffer.get());
442 if (!vkBuf) return;
443
444 void* data = vkBuf->GetData();
445 if (!data) return;
446
447 uint32_t size = static_cast<uint32_t>(vkBuf->GetSize());
448 static constexpr uint32_t MAX_BONE_UBO_SIZE = 256 * 64; // MAX_BONES * sizeof(mat4)
449 if (size > MAX_BONE_UBO_SIZE) size = MAX_BONE_UBO_SIZE;
450
451 // Copy bone matrices to mapped UBO (use currentFrame, not CurrentFrameIndex
452 // which is the swapchain image index and can exceed MAX_FRAMES_IN_FLIGHT)
453 memcpy(boneUBOMapped[currentFrame], data, size);
454
455 // Bind bone descriptor set at set index 1.
456 // In the GBuffer geometry pass the active layout is m_gbufferGeomLayout;
457 // outside it pipelineLay is active. The layout used here must match the
458 // pipeline that will draw, because Vulkan invalidates sets when layouts
459 // are incompatible at lower-numbered sets (set 0 differs between the two).
460 VkPipelineLayout boneBindLayout = (m_inGeometryPass && m_gbufferGeomLayout != VK_NULL_HANDLE)
461 ? m_gbufferGeomLayout : pipelineLay;
462 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
463 boneBindLayout, 1, 1,
464 &boneDescriptorSets[currentFrame],
465 0, nullptr);
466}
467
468/// Creates the per-frame PBR material descriptor set (set 0 in GBuffer pass):
469/// bindings 0-5 are combined image samplers, binding 6 is the params UBO.
470/// Also creates m_gbufferGeomLayout used by the GBuffer pipeline.
471bool VulkanRenderer::CreatePBRMaterialResources() {
472 if (m_pbrMaterialResourcesCreated) return true;
473
474 // --- Descriptor Set Layout ---
475 std::array<VkDescriptorSetLayoutBinding, 7> bindings{};
476 for (uint32_t i = 0; i < 6; ++i) {
477 bindings[i].binding = i;
478 bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
479 bindings[i].descriptorCount = 1;
480 bindings[i].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
481 }
482 bindings[6].binding = 6;
483 bindings[6].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
484 bindings[6].descriptorCount = 1;
485 bindings[6].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
486
487 VkDescriptorSetLayoutCreateInfo dslInfo{};
488 dslInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
489 dslInfo.bindingCount = 7;
490 dslInfo.pBindings = bindings.data();
491 if (vkCreateDescriptorSetLayout(device, &dslInfo, nullptr, &m_pbrMaterialDSL) != VK_SUCCESS) {
492 SLEAK_ERROR("PBR: Failed to create PBR material DSL!");
493 return false;
494 }
495
496 // --- Descriptor Pool: samplers + UBOs, PBR_SET_COUNT ring sets ---
497 std::array<VkDescriptorPoolSize, 2> poolSizes{};
498 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
499 poolSizes[0].descriptorCount = 6 * PBR_SET_COUNT;
500 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
501 poolSizes[1].descriptorCount = 1 * PBR_SET_COUNT;
502
503 VkDescriptorPoolCreateInfo poolInfo{};
504 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
505 poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
506 poolInfo.pPoolSizes = poolSizes.data();
507 poolInfo.maxSets = PBR_SET_COUNT;
508 if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &m_pbrMaterialPool) != VK_SUCCESS) {
509 SLEAK_ERROR("PBR: Failed to create PBR material pool!");
510 return false;
511 }
512
513 // --- Allocate the full ring of descriptor sets ---
514 std::array<VkDescriptorSetLayout, PBR_SET_COUNT> layouts;
515 layouts.fill(m_pbrMaterialDSL);
516 VkDescriptorSetAllocateInfo allocInfo{};
517 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
518 allocInfo.descriptorPool = m_pbrMaterialPool;
519 allocInfo.descriptorSetCount = PBR_SET_COUNT;
520 allocInfo.pSetLayouts = layouts.data();
521 if (vkAllocateDescriptorSets(device, &allocInfo, m_pbrMaterialSets.data()) != VK_SUCCESS) {
522 SLEAK_ERROR("PBR: Failed to allocate PBR material descriptor sets!");
523 return false;
524 }
525
526 // --- Per-frame material params UBO: PBR_SETS_PER_FRAME slots, offset-addressed ---
527 VkPhysicalDeviceProperties props{};
528 vkGetPhysicalDeviceProperties(physicalDevice, &props);
529 VkDeviceSize minAlign = props.limits.minUniformBufferOffsetAlignment;
530 VkDeviceSize stride = sizeof(PBRMaterialParams);
531 if (minAlign > 0)
532 stride = ((stride + minAlign - 1) / minAlign) * minAlign;
533 m_pbrMaterialUBOStride = stride;
534 const VkDeviceSize uboSize = stride * PBR_SETS_PER_FRAME;
535 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
536 VkBufferCreateInfo bufInfo{};
537 bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
538 bufInfo.size = uboSize;
539 bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
540 bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
541 if (vkCreateBuffer(device, &bufInfo, nullptr, &m_pbrMaterialCBBuffers[i]) != VK_SUCCESS) {
542 SLEAK_ERROR("PBR: Failed to create material UBO buffer {}!", i);
543 return false;
544 }
545
546 VkMemoryRequirements memReqs;
547 vkGetBufferMemoryRequirements(device, m_pbrMaterialCBBuffers[i], &memReqs);
548 VkMemoryAllocateInfo memInfo{};
549 memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
550 memInfo.allocationSize = memReqs.size;
551 memInfo.memoryTypeIndex = FindMemoryType(memReqs.memoryTypeBits,
552 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
553 if (vkAllocateMemory(device, &memInfo, nullptr, &m_pbrMaterialCBMemory[i]) != VK_SUCCESS) {
554 SLEAK_ERROR("PBR: Failed to allocate material UBO memory {}!", i);
555 return false;
556 }
557 vkBindBufferMemory(device, m_pbrMaterialCBBuffers[i], m_pbrMaterialCBMemory[i], 0);
558 vkMapMemory(device, m_pbrMaterialCBMemory[i], 0, uboSize, 0, &m_pbrMaterialCBMapped[i]);
559 }
560
561 // --- GBuffer geometry pipeline layout ---
562 // Set 0: PBR material DSL, Set 1: bone DSL, Set 2: lightUBO DSL, Set 3: shadow sampler DSL
563 // Push constants: VK_SHADER_STAGE_VERTEX_BIT, offset=0, size=128 (WVP + World)
564 std::array<VkDescriptorSetLayout, 4> geomSetLayouts = {
565 m_pbrMaterialDSL,
566 boneDescriptorSetLayout,
567 m_lightUBODescriptorSetLayout,
568 m_shadowSamplerDescriptorSetLayout
569 };
570 VkPushConstantRange pcRange{};
571 pcRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
572 pcRange.offset = 0;
573 pcRange.size = 128;
574
575 VkPipelineLayoutCreateInfo layoutInfo{};
576 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
577 layoutInfo.setLayoutCount = static_cast<uint32_t>(geomSetLayouts.size());
578 layoutInfo.pSetLayouts = geomSetLayouts.data();
579 layoutInfo.pushConstantRangeCount = 1;
580 layoutInfo.pPushConstantRanges = &pcRange;
581 if (vkCreatePipelineLayout(device, &layoutInfo, nullptr, &m_gbufferGeomLayout) != VK_SUCCESS) {
582 SLEAK_ERROR("PBR: Failed to create GBuffer geometry pipeline layout!");
583 return false;
584 }
585
586 m_pbrMaterialResourcesCreated = true;
587 SLEAK_INFO("VulkanRenderer: PBR material resources created");
588 return true;
589}
590
591/// Writes a material's textures and params into its ring slot and binds it at set 0.
593 if (!bFrameStarted || !m_pbrMaterialResourcesCreated || !material) return;
594
595 // Switch to the default GBuffer pipeline and drop the active custom format
596 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, m_gbufferPipeline);
597 m_activeCustomFormat = 0;
598
599 // Claim this material's own ring slot (own set + own UBO region) so the
600 // set is never rewritten while already bound by a prior draw this frame.
601 uint32_t slot = m_pbrMaterialSlot[currentFrame];
602 if (slot >= PBR_SETS_PER_FRAME) slot = PBR_SETS_PER_FRAME - 1; // clamp (rare)
603 const uint32_t setIdx = currentFrame * PBR_SETS_PER_FRAME + slot;
604 const VkDeviceSize uboOffset = slot * m_pbrMaterialUBOStride;
605
606 // Build PBRMaterialParams from Material properties
607 PBRMaterialParams params{};
608 auto dc = material->GetDiffuseColor();
609 params.albedoFactorR = dc.GetR() / 255.0f;
610 params.albedoFactorG = dc.GetG() / 255.0f;
611 params.albedoFactorB = dc.GetB() / 255.0f;
612 params.albedoFactorA = material->GetOpacity();
613 params.metallicFactor = material->GetMetallic();
614 params.roughnessFactor = material->GetRoughness();
615 params.aoFactor = material->GetAO();
616 params.normalIntensity = material->GetNormalIntensity();
617 auto ec = material->GetEmissiveColor();
618 params.emissiveR = ec.GetR() / 255.0f;
619 params.emissiveG = ec.GetG() / 255.0f;
620 params.emissiveB = ec.GetB() / 255.0f;
621 params.emissiveIntensity = material->GetEmissiveIntensity();
622 auto tiling = material->GetTiling();
623 params.tilingX = tiling.GetX();
624 params.tilingY = tiling.GetY();
625 auto offset = material->GetOffset();
626 params.offsetX = offset.GetX();
627 params.offsetY = offset.GetY();
628 params.hasNormalMap = material->HasNormalTexture() ? 1u : 0u;
629 params.hasMetallicMap = material->HasMetallicTexture() ? 1u : 0u;
630 params.hasRoughnessMap = material->HasRoughnessTexture() ? 1u : 0u;
631 params.hasAOMap = material->HasAOTexture() ? 1u : 0u;
632 params.hasEmissiveMap = material->HasEmissiveTexture() ? 1u : 0u;
633
634 if (m_pbrMaterialCBMapped[currentFrame])
635 memcpy(static_cast<char*>(m_pbrMaterialCBMapped[currentFrame]) + uboOffset,
636 &params, sizeof(params));
637
638 // Resolve textures — fall back to the default white 1×1 texture if absent
639 auto resolveView = [&](Texture* tex) -> VkImageView {
640 auto* vt = tex ? static_cast<VulkanTexture*>(tex) : nullptr;
641 if (!vt && m_defaultTexture) vt = m_defaultTexture;
642 return vt ? vt->GetImageView() : VK_NULL_HANDLE;
643 };
644 auto resolveSampler = [&](Texture* tex) -> VkSampler {
645 auto* vt = tex ? static_cast<VulkanTexture*>(tex) : nullptr;
646 if (!vt && m_defaultTexture) vt = m_defaultTexture;
647 return vt ? vt->GetSampler() : VK_NULL_HANDLE;
648 };
649
650 Texture* textures[6] = {
651 material->GetDiffuseTexture(),
652 material->HasNormalTexture() ? material->GetNormalTexture() : nullptr,
653 material->HasMetallicTexture() ? material->GetMetallicTexture() : nullptr,
654 material->HasRoughnessTexture() ? material->GetRoughnessTexture() : nullptr,
655 material->HasAOTexture() ? material->GetAOTexture() : nullptr,
656 material->HasEmissiveTexture() ? material->GetEmissiveTexture() : nullptr,
657 };
658
659 std::array<VkDescriptorImageInfo, 6> imageInfos{};
660 for (uint32_t i = 0; i < 6; ++i) {
661 imageInfos[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
662 imageInfos[i].imageView = resolveView(textures[i]);
663 imageInfos[i].sampler = resolveSampler(textures[i]);
664 }
665
666 VkDescriptorBufferInfo bufInfo{};
667 bufInfo.buffer = m_pbrMaterialCBBuffers[currentFrame];
668 bufInfo.offset = uboOffset;
669 bufInfo.range = sizeof(PBRMaterialParams);
670
671 std::array<VkWriteDescriptorSet, 7> writes{};
672 for (uint32_t i = 0; i < 6; ++i) {
673 writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
674 writes[i].dstSet = m_pbrMaterialSets[setIdx];
675 writes[i].dstBinding = i;
676 writes[i].dstArrayElement = 0;
677 writes[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
678 writes[i].descriptorCount = 1;
679 writes[i].pImageInfo = &imageInfos[i];
680 }
681 writes[6].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
682 writes[6].dstSet = m_pbrMaterialSets[setIdx];
683 writes[6].dstBinding = 6;
684 writes[6].dstArrayElement = 0;
685 writes[6].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
686 writes[6].descriptorCount = 1;
687 writes[6].pBufferInfo = &bufInfo;
688
689 vkUpdateDescriptorSets(device, 7, writes.data(), 0, nullptr);
690
691 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
692 m_gbufferGeomLayout, 0, 1,
693 &m_pbrMaterialSets[setIdx], 0, nullptr);
694
695 // Advance the ring for the next material this frame.
696 m_pbrMaterialSlot[currentFrame] = slot + 1;
697}
698
699}
700}
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_RETURN_ERR(...)
Definition Logger.hpp:25
#define SLEAK_INFO(...)
Definition Logger.hpp:20
Math::Color GetEmissiveColor() const
Definition Material.cpp:315
Texture * GetRoughnessTexture() const
Definition Material.cpp:203
Texture * GetMetallicTexture() const
Definition Material.cpp:222
bool HasNormalTexture() const
Definition Material.cpp:172
Texture * GetNormalTexture() const
Definition Material.cpp:167
Texture * GetDiffuseTexture() const
Definition Material.cpp:149
bool HasEmissiveTexture() const
Definition Material.cpp:264
bool HasAOTexture() const
Definition Material.cpp:245
float GetOpacity() const
Definition Material.cpp:357
Texture * GetEmissiveTexture() const
Definition Material.cpp:258
float GetAO() const
Definition Material.cpp:339
bool HasRoughnessTexture() const
Definition Material.cpp:209
Math::Vector2D GetTiling() const
Definition Material.cpp:375
Math::Color GetDiffuseColor() const
Definition Material.cpp:285
Math::Vector2D GetOffset() const
Definition Material.cpp:389
Texture * GetAOTexture() const
Definition Material.cpp:241
float GetNormalIntensity() const
Definition Material.cpp:345
float GetRoughness() const
Definition Material.cpp:335
float GetMetallic() const
Definition Material.cpp:329
float GetEmissiveIntensity() const
Definition Material.cpp:351
bool HasMetallicTexture() const
Definition Material.cpp:228
T * get() const
Definition RefPtr.hpp:170
VMA-backed Vulkan buffer with staging uploads, batched copies, and a size-bucketed recycling pool.
virtual bool CreateImGUI() override
Initializes ImGui and its Vulkan backend against the active render pass.
virtual void BindPBRMaterial(Sleak::Material *material) override
Writes a material's textures and params into its ring slot and binds it at set 0.
virtual void BindBoneBuffer(RefPtr< BufferBase > buffer) override
Copies bone matrices into the current frame's UBO and binds its descriptor set.
Vulkan 2D texture: image + view + sampler, with per-swapchain-image descriptor sets.
Backend-facing rendering layer shared by the four graphics backends.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
static constexpr int MAX_BONES
Definition Skeleton.hpp:12