SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
VulkanPipelines.cpp
Go to the documentation of this file.
2
4#include <array>
5#include <cstddef>
6#include <fstream>
7#include <vector>
8#include "Core/Logger.hpp"
9
10namespace Sleak {
11 namespace RenderEngine {
12
13/// Binds the skybox pipeline and its descriptor set for the current frame.
15 if (!bFrameStarted) return;
16 // Skybox only makes sense in forward context — not inside the GBuffer geometry pass
17 // where set 0 is a PBR material descriptor set incompatible with pipelineLay.
18 if (m_inGeometryPass) return;
19 if (skyboxPipeline == VK_NULL_HANDLE || !m_skyboxDescriptorsWritten)
20 return;
21
22 m_activeCustomFormat = 0; // prevent BindVertexBuffer from overriding this pipeline
23 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
24 skyboxPipeline);
25
26 if (CurrentFrameIndex < skyboxDescriptorSets.size()) {
27 vkCmdBindDescriptorSets(
28 command, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLay, 0, 1,
29 &skyboxDescriptorSets[CurrentFrameIndex], 0, nullptr);
30 }
31}
32
33
34/// Restores the previous pipeline and descriptor set after the skybox draw.
36 if (!bFrameStarted) return;
37
38 // Restore pipeline: inside geometry pass restore to the GBuffer pipeline;
39 // otherwise the main forward pipeline.
40 VkPipeline restoreTo =
41 (m_inGeometryPass && m_gbufferPipeline != VK_NULL_HANDLE)
42 ? m_gbufferPipeline : pipeline;
43 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, restoreTo);
44
45 // Rebind main texture descriptor sets.
46 // In the GBuffer geometry pass set 0 belongs to m_gbufferGeomLayout and is
47 // managed by BindPBRMaterial — do NOT overwrite it with the forward
48 // single-sampler descriptor set or use pipelineLay here.
49 if (!m_inGeometryPass && m_textureDescriptorsWritten &&
50 CurrentFrameIndex < descriptorSets.size()) {
51 vkCmdBindDescriptorSets(
52 command, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLay, 0, 1,
53 &descriptorSets[CurrentFrameIndex], 0, nullptr);
54 }
55}
56
57/// Creates the main forward graphics pipeline and its pipeline layout.
58bool VulkanRenderer::CreateGraphicsPipeline() {
59 VkResult result;
60
61 simpleShader = new VulkanShader(device);
62 bool isShader =
63 simpleShader->compile("assets/shaders/default_shader");
64
65 if (!isShader)
66 SLEAK_RETURN_ERR("Cannot compile shaders!")
67
68 VkPipelineShaderStageCreateInfo shaderStages[] = {
69 simpleShader->GetVertexInfo(), simpleShader->GetFragInfo()};
70
71 // Dynamic states
72 std::vector<VkDynamicState> dynamicStates = {
73 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
74
75 VkPipelineDynamicStateCreateInfo dynamicState{};
76 dynamicState.sType =
77 VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
78 dynamicState.dynamicStateCount =
79 static_cast<uint32_t>(dynamicStates.size());
80 dynamicState.pDynamicStates = dynamicStates.data();
81
82 // Vertex input — matches Sleak::Vertex (64 bytes)
83 VkVertexInputBindingDescription bindingDescription{};
84 bindingDescription.binding = 0;
85 bindingDescription.stride = sizeof(Vertex);
86 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
87
88 std::array<VkVertexInputAttributeDescription, 7> attributeDescs{};
89
90 // Position: float3 at offset 0
91 attributeDescs[0].binding = 0;
92 attributeDescs[0].location = 0;
93 attributeDescs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
94 attributeDescs[0].offset = offsetof(Vertex, px);
95
96 // Normal: float3 at offset 12
97 attributeDescs[1].binding = 0;
98 attributeDescs[1].location = 1;
99 attributeDescs[1].format = VK_FORMAT_R32G32B32_SFLOAT;
100 attributeDescs[1].offset = offsetof(Vertex, nx);
101
102 // Tangent: float4 at offset 24
103 attributeDescs[2].binding = 0;
104 attributeDescs[2].location = 2;
105 attributeDescs[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
106 attributeDescs[2].offset = offsetof(Vertex, tx);
107
108 // Color: float4 at offset 40
109 attributeDescs[3].binding = 0;
110 attributeDescs[3].location = 3;
111 attributeDescs[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
112 attributeDescs[3].offset = offsetof(Vertex, r);
113
114 // UV: float2 at offset 56
115 attributeDescs[4].binding = 0;
116 attributeDescs[4].location = 4;
117 attributeDescs[4].format = VK_FORMAT_R32G32_SFLOAT;
118 attributeDescs[4].offset = offsetof(Vertex, u);
119
120 // BoneIDs: int4
121 attributeDescs[5].binding = 0;
122 attributeDescs[5].location = 5;
123 attributeDescs[5].format = VK_FORMAT_R32G32B32A32_SINT;
124 attributeDescs[5].offset = offsetof(Vertex, boneIDs);
125
126 // BoneWeights: float4
127 attributeDescs[6].binding = 0;
128 attributeDescs[6].location = 6;
129 attributeDescs[6].format = VK_FORMAT_R32G32B32A32_SFLOAT;
130 attributeDescs[6].offset = offsetof(Vertex, boneWeights);
131
132 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
133 vertexInputInfo.sType =
134 VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
135 vertexInputInfo.vertexBindingDescriptionCount = 1;
136 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
137 vertexInputInfo.vertexAttributeDescriptionCount =
138 static_cast<uint32_t>(attributeDescs.size());
139 vertexInputInfo.pVertexAttributeDescriptions = attributeDescs.data();
140
141 // Input assembly
142 VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo{};
143 inputAssemblyInfo.sType =
144 VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
145 inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
146 inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
147
148 // Viewport state (dynamic)
149 VkPipelineViewportStateCreateInfo viewportInfo{};
150 viewportInfo.sType =
151 VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
152 viewportInfo.viewportCount = 1;
153 viewportInfo.scissorCount = 1;
154
155 // Rasterization
156 VkPipelineRasterizationStateCreateInfo rasterizer{};
157 rasterizer.sType =
158 VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
159 rasterizer.depthClampEnable = VK_FALSE;
160 rasterizer.rasterizerDiscardEnable = VK_FALSE;
161 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
162 rasterizer.lineWidth = 1.0f;
163 rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
164 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
165 rasterizer.depthBiasEnable = VK_FALSE;
166
167 // Multisampling
168 VkPipelineMultisampleStateCreateInfo msaa{};
169 msaa.sType =
170 VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
171 msaa.sampleShadingEnable = VK_FALSE;
172 msaa.rasterizationSamples = m_msaaSamples;
173
174 // Depth stencil
175 VkPipelineDepthStencilStateCreateInfo depthStencil{};
176 depthStencil.sType =
177 VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
178 depthStencil.depthTestEnable = VK_TRUE;
179 depthStencil.depthWriteEnable = VK_TRUE;
180 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
181 depthStencil.depthBoundsTestEnable = VK_FALSE;
182 depthStencil.stencilTestEnable = VK_FALSE;
183
184 // Color blending (fixed: R | G | B | A, not R | G | R | A)
185 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
186 colorBlendAttachment.blendEnable = VK_TRUE;
187 colorBlendAttachment.colorWriteMask =
188 VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
189 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
190 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
191 colorBlendAttachment.dstColorBlendFactor =
192 VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
193 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
194 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
195 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
196 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
197
198 VkPipelineColorBlendStateCreateInfo colorBlendInfo{};
199 colorBlendInfo.sType =
200 VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
201 colorBlendInfo.logicOpEnable = VK_FALSE;
202 colorBlendInfo.attachmentCount = 1;
203 colorBlendInfo.pAttachments = &colorBlendAttachment;
204
205 // Pipeline layout — push constants for WVP matrix + descriptor set
206 // for texture sampler
207 VkPushConstantRange pushConstantRange{};
208 pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
209 pushConstantRange.offset = 0;
210 pushConstantRange.size = 128; // sizeof(mat4) * 2 = 128 bytes (WVP + World)
211
212 // Four descriptor set layouts:
213 // set 0 = texture sampler, set 1 = bone UBO,
214 // set 2 = light/shadow UBO, set 3 = shadow map sampler
215 std::array<VkDescriptorSetLayout, 4> setLayouts = {
216 descriptorSetLayout, boneDescriptorSetLayout,
217 m_lightUBODescriptorSetLayout, m_shadowSamplerDescriptorSetLayout
218 };
219
220 VkPipelineLayoutCreateInfo layoutInfo{};
221 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
222 layoutInfo.setLayoutCount = static_cast<uint32_t>(setLayouts.size());
223 layoutInfo.pSetLayouts = setLayouts.data();
224 layoutInfo.pushConstantRangeCount = 1;
225 layoutInfo.pPushConstantRanges = &pushConstantRange;
226
227 result = vkCreatePipelineLayout(device, &layoutInfo, nullptr,
228 &pipelineLay);
229 if (result != VK_SUCCESS)
230 SLEAK_RETURN_ERR("Failed to create graphics pipeline layout!");
231
232 // Create pipeline
233 VkGraphicsPipelineCreateInfo pipelineInfo{};
234 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
235 pipelineInfo.stageCount = 2;
236 pipelineInfo.pStages = shaderStages;
237 pipelineInfo.pVertexInputState = &vertexInputInfo;
238 pipelineInfo.pInputAssemblyState = &inputAssemblyInfo;
239 pipelineInfo.pViewportState = &viewportInfo;
240 pipelineInfo.pRasterizationState = &rasterizer;
241 pipelineInfo.pMultisampleState = &msaa;
242 pipelineInfo.pDepthStencilState = &depthStencil;
243 pipelineInfo.pColorBlendState = &colorBlendInfo;
244 pipelineInfo.pDynamicState = &dynamicState;
245 pipelineInfo.layout = pipelineLay;
246 pipelineInfo.subpass = 0;
247 pipelineInfo.renderPass = (m_deferredEnabled && m_forwardRenderPass != VK_NULL_HANDLE)
248 ? m_forwardRenderPass : renderPass;
249 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
250 pipelineInfo.basePipelineIndex = -1;
251
252 result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1,
253 &pipelineInfo, nullptr, &pipeline);
254 if (result != VK_SUCCESS)
255 SLEAK_ERROR("Failed to create graphics pipeline!!");
256
257 return true;
258}
259
260
261/// Creates the main forward render pass with optional MSAA color and resolve attachments.
262bool VulkanRenderer::CreateRenderPass() {
263 const bool msaaEnabled = (m_msaaSamples != VK_SAMPLE_COUNT_1_BIT);
264
265 // Color attachment (multisampled when MSAA on)
266 VkAttachmentDescription colorAttachment{};
267 colorAttachment.format = scImageFormat;
268 colorAttachment.samples = m_msaaSamples;
269 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
270 colorAttachment.storeOp = msaaEnabled ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
271 colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
272 colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
273 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
274 colorAttachment.finalLayout = msaaEnabled ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
275
276 VkAttachmentReference colorRef{};
277 colorRef.attachment = 0;
278 colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
279
280 // Depth attachment (multisampled when MSAA on)
281 VkAttachmentDescription depthAttachment{};
282 depthAttachment.format = depthFormat;
283 depthAttachment.samples = m_msaaSamples;
284 depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
285 depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
286 depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
287 depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
288 depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
289 depthAttachment.finalLayout =
290 VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
291
292 VkAttachmentReference depthRef{};
293 depthRef.attachment = 1;
294 depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
295
296 // Resolve attachment (swapchain image, only when MSAA on)
297 VkAttachmentDescription resolveAttachment{};
298 VkAttachmentReference resolveRef{};
299 if (msaaEnabled) {
300 resolveAttachment.format = scImageFormat;
301 resolveAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
302 resolveAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
303 resolveAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
304 resolveAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
305 resolveAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
306 resolveAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
307 resolveAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
308
309 resolveRef.attachment = 2;
310 resolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
311 }
312
313 // Subpass
314 VkSubpassDescription subpass{};
315 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
316 subpass.colorAttachmentCount = 1;
317 subpass.pColorAttachments = &colorRef;
318 subpass.pDepthStencilAttachment = &depthRef;
319 subpass.pResolveAttachments = msaaEnabled ? &resolveRef : nullptr;
320
321 // Subpass dependency
322 VkSubpassDependency dependency{};
323 dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
324 dependency.dstSubpass = 0;
325 dependency.srcStageMask =
326 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
327 VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
328 dependency.srcAccessMask = 0;
329 dependency.dstStageMask =
330 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
331 VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
332 dependency.dstAccessMask =
333 VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
334 VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
335
336 // Create render pass
337 std::vector<VkAttachmentDescription> attachments = {
338 colorAttachment, depthAttachment};
339 if (msaaEnabled)
340 attachments.push_back(resolveAttachment);
341
342 VkRenderPassCreateInfo renderInfo{};
343 renderInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
344 renderInfo.attachmentCount =
345 static_cast<uint32_t>(attachments.size());
346 renderInfo.pAttachments = attachments.data();
347 renderInfo.subpassCount = 1;
348 renderInfo.pSubpasses = &subpass;
349 renderInfo.dependencyCount = 1;
350 renderInfo.pDependencies = &dependency;
351
352 if (vkCreateRenderPass(device, &renderInfo, nullptr, &renderPass) !=
353 VK_SUCCESS)
354 SLEAK_RETURN_ERR("Failed to create render pass!");
355
356 return true;
357}
358
359
360/// Creates one framebuffer per swapchain image for the main render pass.
361bool VulkanRenderer::CreateFrameBuffer() {
362 swapChainFramebuffers.resize(swapChainImageViews.size());
363 const bool msaaEnabled = (m_msaaSamples != VK_SAMPLE_COUNT_1_BIT);
364
365 for (size_t i = 0; i < swapChainImageViews.size(); i++) {
366 std::vector<VkImageView> attachments;
367 if (msaaEnabled) {
368 // MSAA color, depth, resolve (swapchain)
369 attachments = {m_msaaColorImageView, depthImageView, swapChainImageViews[i]};
370 } else {
371 // No MSAA: swapchain color, depth
372 attachments = {swapChainImageViews[i], depthImageView};
373 }
374
375 VkFramebufferCreateInfo info{};
376 info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
377 info.renderPass = renderPass;
378 info.attachmentCount =
379 static_cast<uint32_t>(attachments.size());
380 info.pAttachments = attachments.data();
381 info.layers = 1;
382 info.width = scExtent.width;
383 info.height = scExtent.height;
384
385 if (vkCreateFramebuffer(device, &info, nullptr,
386 &swapChainFramebuffers[i]) != VK_SUCCESS)
387 SLEAK_RETURN_ERR("Failed to create frame buffer!");
388 }
389 return true;
390}
391
392/// Compiles the skybox shaders and creates the skybox descriptor set and pipeline.
393bool VulkanRenderer::CreateSkyboxPipeline() {
394 // 1. Compile skybox shaders
395 skyboxShader = new VulkanShader(device);
396 if (!skyboxShader->compile("assets/shaders/skybox")) {
397 SLEAK_ERROR("VulkanRenderer: Failed to compile skybox shaders");
398 delete skyboxShader;
399 skyboxShader = nullptr;
400 return false;
401 }
402
403 // 2. Create skybox descriptor pool and sets (same layout as main)
404 uint32_t imageCount =
405 static_cast<uint32_t>(swapChainImages.size());
406
407 VkDescriptorPoolSize poolSize{};
408 poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
409 poolSize.descriptorCount = imageCount;
410
411 VkDescriptorPoolCreateInfo poolInfo{};
412 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
413 poolInfo.poolSizeCount = 1;
414 poolInfo.pPoolSizes = &poolSize;
415 poolInfo.maxSets = imageCount;
416
417 if (vkCreateDescriptorPool(device, &poolInfo, nullptr,
418 &skyboxDescriptorPool) != VK_SUCCESS) {
419 SLEAK_ERROR("VulkanRenderer: Failed to create skybox descriptor pool");
420 return false;
421 }
422
423 std::vector<VkDescriptorSetLayout> layouts(imageCount,
424 descriptorSetLayout);
425
426 VkDescriptorSetAllocateInfo allocInfo{};
427 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
428 allocInfo.descriptorPool = skyboxDescriptorPool;
429 allocInfo.descriptorSetCount = imageCount;
430 allocInfo.pSetLayouts = layouts.data();
431
432 skyboxDescriptorSets.resize(imageCount);
433 if (vkAllocateDescriptorSets(device, &allocInfo,
434 skyboxDescriptorSets.data()) !=
435 VK_SUCCESS) {
436 SLEAK_ERROR("VulkanRenderer: Failed to allocate skybox descriptor sets");
437 return false;
438 }
439
440 // 3. Create skybox pipeline (same as main but with skybox shaders
441 // and depth write disabled)
442 VkPipelineShaderStageCreateInfo shaderStages[] = {
443 skyboxShader->GetVertexInfo(), skyboxShader->GetFragInfo()};
444
445 std::vector<VkDynamicState> dynamicStates = {
446 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
447
448 VkPipelineDynamicStateCreateInfo dynamicState{};
449 dynamicState.sType =
450 VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
451 dynamicState.dynamicStateCount =
452 static_cast<uint32_t>(dynamicStates.size());
453 dynamicState.pDynamicStates = dynamicStates.data();
454
455 // Same vertex layout as main pipeline
456 VkVertexInputBindingDescription bindingDescription{};
457 bindingDescription.binding = 0;
458 bindingDescription.stride = sizeof(Vertex);
459 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
460
461 std::array<VkVertexInputAttributeDescription, 7> attributeDescs{};
462 attributeDescs[0].binding = 0;
463 attributeDescs[0].location = 0;
464 attributeDescs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
465 attributeDescs[0].offset = offsetof(Vertex, px);
466
467 attributeDescs[1].binding = 0;
468 attributeDescs[1].location = 1;
469 attributeDescs[1].format = VK_FORMAT_R32G32B32_SFLOAT;
470 attributeDescs[1].offset = offsetof(Vertex, nx);
471
472 attributeDescs[2].binding = 0;
473 attributeDescs[2].location = 2;
474 attributeDescs[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
475 attributeDescs[2].offset = offsetof(Vertex, tx);
476
477 attributeDescs[3].binding = 0;
478 attributeDescs[3].location = 3;
479 attributeDescs[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
480 attributeDescs[3].offset = offsetof(Vertex, r);
481
482 attributeDescs[4].binding = 0;
483 attributeDescs[4].location = 4;
484 attributeDescs[4].format = VK_FORMAT_R32G32_SFLOAT;
485 attributeDescs[4].offset = offsetof(Vertex, u);
486
487 attributeDescs[5].binding = 0;
488 attributeDescs[5].location = 5;
489 attributeDescs[5].format = VK_FORMAT_R32G32B32A32_SINT;
490 attributeDescs[5].offset = offsetof(Vertex, boneIDs);
491
492 attributeDescs[6].binding = 0;
493 attributeDescs[6].location = 6;
494 attributeDescs[6].format = VK_FORMAT_R32G32B32A32_SFLOAT;
495 attributeDescs[6].offset = offsetof(Vertex, boneWeights);
496
497 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
498 vertexInputInfo.sType =
499 VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
500 vertexInputInfo.vertexBindingDescriptionCount = 1;
501 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
502 vertexInputInfo.vertexAttributeDescriptionCount =
503 static_cast<uint32_t>(attributeDescs.size());
504 vertexInputInfo.pVertexAttributeDescriptions = attributeDescs.data();
505
506 VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo{};
507 inputAssemblyInfo.sType =
508 VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
509 inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
510 inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
511
512 VkPipelineViewportStateCreateInfo viewportInfo{};
513 viewportInfo.sType =
514 VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
515 viewportInfo.viewportCount = 1;
516 viewportInfo.scissorCount = 1;
517
518 VkPipelineRasterizationStateCreateInfo rasterizer{};
519 rasterizer.sType =
520 VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
521 rasterizer.depthClampEnable = VK_FALSE;
522 rasterizer.rasterizerDiscardEnable = VK_FALSE;
523 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
524 rasterizer.lineWidth = 1.0f;
525 rasterizer.cullMode = VK_CULL_MODE_NONE;
526 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
527 rasterizer.depthBiasEnable = VK_FALSE;
528
529 VkPipelineMultisampleStateCreateInfo msaa{};
530 msaa.sType =
531 VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
532 msaa.sampleShadingEnable = VK_FALSE;
533 msaa.rasterizationSamples = m_msaaSamples;
534
535 // Skybox: depth test enabled (LEQUAL), depth write DISABLED
536 VkPipelineDepthStencilStateCreateInfo depthStencil{};
537 depthStencil.sType =
538 VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
539 depthStencil.depthTestEnable = VK_TRUE;
540 depthStencil.depthWriteEnable = VK_FALSE;
541 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
542 depthStencil.depthBoundsTestEnable = VK_FALSE;
543 depthStencil.stencilTestEnable = VK_FALSE;
544
545 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
546 colorBlendAttachment.blendEnable = VK_FALSE;
547 colorBlendAttachment.colorWriteMask =
548 VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
549 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
550
551 VkPipelineColorBlendStateCreateInfo colorBlendInfo{};
552 colorBlendInfo.sType =
553 VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
554 colorBlendInfo.logicOpEnable = VK_FALSE;
555 colorBlendInfo.attachmentCount = 1;
556 colorBlendInfo.pAttachments = &colorBlendAttachment;
557
558 VkGraphicsPipelineCreateInfo pipelineInfo{};
559 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
560 pipelineInfo.stageCount = 2;
561 pipelineInfo.pStages = shaderStages;
562 pipelineInfo.pVertexInputState = &vertexInputInfo;
563 pipelineInfo.pInputAssemblyState = &inputAssemblyInfo;
564 pipelineInfo.pViewportState = &viewportInfo;
565 pipelineInfo.pRasterizationState = &rasterizer;
566 pipelineInfo.pMultisampleState = &msaa;
567 pipelineInfo.pDepthStencilState = &depthStencil;
568 pipelineInfo.pColorBlendState = &colorBlendInfo;
569 pipelineInfo.pDynamicState = &dynamicState;
570 pipelineInfo.layout = pipelineLay; // Reuse same pipeline layout
571 pipelineInfo.subpass = 0;
572 pipelineInfo.renderPass = (m_deferredEnabled && m_forwardRenderPass != VK_NULL_HANDLE)
573 ? m_forwardRenderPass : renderPass;
574 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
575 pipelineInfo.basePipelineIndex = -1;
576
577 VkResult result = vkCreateGraphicsPipelines(
578 device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &skyboxPipeline);
579 if (result != VK_SUCCESS) {
580 SLEAK_ERROR("VulkanRenderer: Failed to create skybox pipeline!");
581 return false;
582 }
583
584 SLEAK_INFO("VulkanRenderer: Skybox pipeline created successfully");
585 return true;
586}
587
588
589/// Compiles the debug line shaders and creates the line-list pipeline.
590bool VulkanRenderer::CreateDebugLinePipeline() {
591 if (debugLinePipeline != VK_NULL_HANDLE) return true;
592
593 debugLineShader = new VulkanShader(device);
594 if (!debugLineShader->compile("assets/shaders/debug_line")) {
595 SLEAK_ERROR("VulkanRenderer: Failed to compile debug line shaders");
596 delete debugLineShader;
597 debugLineShader = nullptr;
598 return false;
599 }
600
601 VkPipelineShaderStageCreateInfo shaderStages[] = {
602 debugLineShader->GetVertexInfo(), debugLineShader->GetFragInfo()};
603
604 std::vector<VkDynamicState> dynamicStates = {
605 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
606
607 VkPipelineDynamicStateCreateInfo dynamicState{};
608 dynamicState.sType =
609 VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
610 dynamicState.dynamicStateCount =
611 static_cast<uint32_t>(dynamicStates.size());
612 dynamicState.pDynamicStates = dynamicStates.data();
613
614 VkVertexInputBindingDescription bindingDescription{};
615 bindingDescription.binding = 0;
616 bindingDescription.stride = sizeof(Vertex);
617 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
618
619 std::array<VkVertexInputAttributeDescription, 7> attributeDescs{};
620 attributeDescs[0] = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, px)};
621 attributeDescs[1] = {1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, nx)};
622 attributeDescs[2] = {2, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, tx)};
623 attributeDescs[3] = {3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, r)};
624 attributeDescs[4] = {4, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, u)};
625 attributeDescs[5] = {5, 0, VK_FORMAT_R32G32B32A32_SINT, offsetof(Vertex, boneIDs)};
626 attributeDescs[6] = {6, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, boneWeights)};
627
628 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
629 vertexInputInfo.sType =
630 VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
631 vertexInputInfo.vertexBindingDescriptionCount = 1;
632 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
633 vertexInputInfo.vertexAttributeDescriptionCount =
634 static_cast<uint32_t>(attributeDescs.size());
635 vertexInputInfo.pVertexAttributeDescriptions = attributeDescs.data();
636
637 VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo{};
638 inputAssemblyInfo.sType =
639 VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
640 inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
641 inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
642
643 VkPipelineViewportStateCreateInfo viewportInfo{};
644 viewportInfo.sType =
645 VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
646 viewportInfo.viewportCount = 1;
647 viewportInfo.scissorCount = 1;
648
649 VkPipelineRasterizationStateCreateInfo rasterizer{};
650 rasterizer.sType =
651 VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
652 rasterizer.depthClampEnable = VK_FALSE;
653 rasterizer.rasterizerDiscardEnable = VK_FALSE;
654 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
655 rasterizer.lineWidth = 1.0f;
656 rasterizer.cullMode = VK_CULL_MODE_NONE;
657 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
658 rasterizer.depthBiasEnable = VK_FALSE;
659
660 VkPipelineMultisampleStateCreateInfo msaa{};
661 msaa.sType =
662 VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
663 msaa.sampleShadingEnable = VK_FALSE;
664 msaa.rasterizationSamples = m_msaaSamples;
665
666 VkPipelineDepthStencilStateCreateInfo depthStencil{};
667 depthStencil.sType =
668 VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
669 depthStencil.depthTestEnable = VK_TRUE;
670 depthStencil.depthWriteEnable = VK_TRUE;
671 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
672 depthStencil.depthBoundsTestEnable = VK_FALSE;
673 depthStencil.stencilTestEnable = VK_FALSE;
674
675 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
676 colorBlendAttachment.blendEnable = VK_FALSE;
677 colorBlendAttachment.colorWriteMask =
678 VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
679 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
680
681 VkPipelineColorBlendStateCreateInfo colorBlendInfo{};
682 colorBlendInfo.sType =
683 VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
684 colorBlendInfo.logicOpEnable = VK_FALSE;
685 colorBlendInfo.attachmentCount = 1;
686 colorBlendInfo.pAttachments = &colorBlendAttachment;
687
688 VkGraphicsPipelineCreateInfo pipelineInfo{};
689 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
690 pipelineInfo.stageCount = 2;
691 pipelineInfo.pStages = shaderStages;
692 pipelineInfo.pVertexInputState = &vertexInputInfo;
693 pipelineInfo.pInputAssemblyState = &inputAssemblyInfo;
694 pipelineInfo.pViewportState = &viewportInfo;
695 pipelineInfo.pRasterizationState = &rasterizer;
696 pipelineInfo.pMultisampleState = &msaa;
697 pipelineInfo.pDepthStencilState = &depthStencil;
698 pipelineInfo.pColorBlendState = &colorBlendInfo;
699 pipelineInfo.pDynamicState = &dynamicState;
700 pipelineInfo.layout = pipelineLay;
701 pipelineInfo.subpass = 0;
702 pipelineInfo.renderPass = (m_deferredEnabled && m_forwardRenderPass != VK_NULL_HANDLE)
703 ? m_forwardRenderPass : renderPass;
704 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
705 pipelineInfo.basePipelineIndex = -1;
706
707 VkResult result = vkCreateGraphicsPipelines(
708 device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &debugLinePipeline);
709 if (result != VK_SUCCESS) {
710 SLEAK_ERROR("VulkanRenderer: Failed to create debug line pipeline!");
711 return false;
712 }
713
714 SLEAK_INFO("VulkanRenderer: Debug line pipeline created successfully");
715 return true;
716}
717
718
719// ============================================================
720// Custom vertex format pipelines — vertex input and shader stems
721// come from VertexFormatRegistry, so no layout is hard-coded in the backend.
722// ============================================================
723
724/// Translates a registry attribute format into its Vulkan vertex format.
725static VkFormat ToVkVertexFormat(VertexAttribFormat format) {
726 switch (format) {
727 case VertexAttribFormat::Float1: return VK_FORMAT_R32_SFLOAT;
728 case VertexAttribFormat::Float2: return VK_FORMAT_R32G32_SFLOAT;
729 case VertexAttribFormat::Float3: return VK_FORMAT_R32G32B32_SFLOAT;
730 case VertexAttribFormat::Float4: return VK_FORMAT_R32G32B32A32_SFLOAT;
731 case VertexAttribFormat::UInt1: return VK_FORMAT_R32_UINT;
732 case VertexAttribFormat::Int1: return VK_FORMAT_R32_SINT;
733 }
734 return VK_FORMAT_R32G32B32_SFLOAT;
735}
736
737/// Creates any missing pipeline variant (main, shadow, GBuffer, transparent)
738/// for a registered vertex layout. Variants whose shader fails to load are
739/// marked absent so draws skip them without retrying every frame.
740bool VulkanRenderer::CreateCustomFormatPipelines(VertexFormatHandle format) {
741 const VertexLayoutDesc* desc = VertexFormatRegistry::Get(format);
742 if (!desc || desc->stride == 0 || desc->attributes.empty()) return false;
743
744 CustomFormatPipelines& pipes = m_customFormatPipelines[format];
745
746 // Vertex input state shared by all four variants
747 VkVertexInputBindingDescription bindingDescription{};
748 bindingDescription.binding = 0;
749 bindingDescription.stride = desc->stride;
750 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
751
752 std::vector<VkVertexInputAttributeDescription> attributeDescs;
753 attributeDescs.reserve(desc->attributes.size());
754 for (const auto& attr : desc->attributes) {
755 attributeDescs.push_back({attr.location, 0,
756 ToVkVertexFormat(attr.format), attr.offset});
757 }
758
759 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
760 vertexInputInfo.sType =
761 VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
762 vertexInputInfo.vertexBindingDescriptionCount = 1;
763 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
764 vertexInputInfo.vertexAttributeDescriptionCount =
765 static_cast<uint32_t>(attributeDescs.size());
766 vertexInputInfo.pVertexAttributeDescriptions = attributeDescs.data();
767
768 std::vector<VkDynamicState> dynamicStates = {
769 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
770
771 VkPipelineDynamicStateCreateInfo dynamicState{};
772 dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
773 dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
774 dynamicState.pDynamicStates = dynamicStates.data();
775
776 // Forward/main variant — opaque geometry in the forward pass
777 if (pipes.main == VK_NULL_HANDLE && !pipes.mainFailed) {
778 if (desc->shaderStem.empty()) {
779 SLEAK_ERROR("VulkanRenderer: Vertex format {} has no main shader stem", format);
780 pipes.mainFailed = true;
781 } else {
782 const std::string stemPath = "assets/shaders/" + desc->shaderStem;
783 auto* shader = new VulkanShader(device);
784 if (!shader->compile(stemPath)) {
785 SLEAK_ERROR("VulkanRenderer: Failed to compile '{}' for vertex format {}",
786 stemPath, format);
787 pipes.mainFailed = true;
788 delete shader;
789 } else {
790 VkPipelineShaderStageCreateInfo shaderStages[] = {
791 shader->GetVertexInfo(), shader->GetFragInfo()};
792
793 VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo{};
794 inputAssemblyInfo.sType =
795 VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
796 inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
797 inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
798
799 VkPipelineViewportStateCreateInfo viewportInfo{};
800 viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
801 viewportInfo.viewportCount = 1;
802 viewportInfo.scissorCount = 1;
803
804 VkPipelineRasterizationStateCreateInfo rasterizer{};
805 rasterizer.sType =
806 VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
807 rasterizer.depthClampEnable = VK_FALSE;
808 rasterizer.rasterizerDiscardEnable = VK_FALSE;
809 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
810 rasterizer.lineWidth = 1.0f;
811 rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
812 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
813 rasterizer.depthBiasEnable = VK_FALSE;
814
815 VkPipelineMultisampleStateCreateInfo msaa{};
816 msaa.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
817 msaa.sampleShadingEnable = VK_FALSE;
818 msaa.rasterizationSamples = m_msaaSamples;
819
820 VkPipelineDepthStencilStateCreateInfo depthStencil{};
821 depthStencil.sType =
822 VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
823 depthStencil.depthTestEnable = VK_TRUE;
824 depthStencil.depthWriteEnable = VK_TRUE;
825 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
826 depthStencil.depthBoundsTestEnable = VK_FALSE;
827 depthStencil.stencilTestEnable = VK_FALSE;
828
829 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
830 colorBlendAttachment.blendEnable = VK_TRUE;
831 colorBlendAttachment.colorWriteMask =
832 VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
833 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
834 colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
835 colorBlendAttachment.dstColorBlendFactor =
836 VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
837 colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
838 colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
839 colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
840 colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
841
842 VkPipelineColorBlendStateCreateInfo colorBlendInfo{};
843 colorBlendInfo.sType =
844 VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
845 colorBlendInfo.logicOpEnable = VK_FALSE;
846 colorBlendInfo.attachmentCount = 1;
847 colorBlendInfo.pAttachments = &colorBlendAttachment;
848
849 VkGraphicsPipelineCreateInfo pipelineInfo{};
850 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
851 pipelineInfo.stageCount = 2;
852 pipelineInfo.pStages = shaderStages;
853 pipelineInfo.pVertexInputState = &vertexInputInfo;
854 pipelineInfo.pInputAssemblyState = &inputAssemblyInfo;
855 pipelineInfo.pViewportState = &viewportInfo;
856 pipelineInfo.pRasterizationState = &rasterizer;
857 pipelineInfo.pMultisampleState = &msaa;
858 pipelineInfo.pDepthStencilState = &depthStencil;
859 pipelineInfo.pColorBlendState = &colorBlendInfo;
860 pipelineInfo.pDynamicState = &dynamicState;
861 pipelineInfo.layout = pipelineLay; // reuse main layout (same descriptor sets)
862 pipelineInfo.subpass = 0;
863 pipelineInfo.renderPass =
864 (m_deferredEnabled && m_forwardRenderPass != VK_NULL_HANDLE)
865 ? m_forwardRenderPass : renderPass;
866 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
867 pipelineInfo.basePipelineIndex = -1;
868
869 VkResult result = vkCreateGraphicsPipelines(
870 device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipes.main);
871 delete shader;
872
873 if (result != VK_SUCCESS) {
874 SLEAK_ERROR("VulkanRenderer: Failed to create main pipeline for vertex format {}",
875 format);
876 pipes.main = VK_NULL_HANDLE;
877 pipes.mainFailed = true;
878 } else {
879 SLEAK_INFO("VulkanRenderer: Custom format {} main pipeline created", format);
880 }
881 }
882 }
883 }
884
885 // Shadow variant — depth-only, rendered into the shadow map
886 if (pipes.shadow == VK_NULL_HANDLE && !pipes.shadowFailed &&
887 !desc->shadowShaderStem.empty() &&
888 m_shadowRenderPass != VK_NULL_HANDLE && m_shadowShader) {
889 const std::string shadowPath =
890 "assets/shaders/" + desc->shadowShaderStem + ".vert.spv";
891 auto* shadowShader = new VulkanShader(device);
892 if (!shadowShader->compileVertexOnly(shadowPath)) {
893 SLEAK_ERROR("VulkanRenderer: Failed to compile '{}' for vertex format {}",
894 shadowPath, format);
895 pipes.shadowFailed = true;
896 delete shadowShader;
897 } else {
898 VkPipelineShaderStageCreateInfo shaderStage = shadowShader->GetVertexInfo();
899
900 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
901 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
902 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
903 inputAssembly.primitiveRestartEnable = VK_FALSE;
904
905 VkPipelineViewportStateCreateInfo viewportState{};
906 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
907 viewportState.viewportCount = 1;
908 viewportState.scissorCount = 1;
909
910 VkPipelineRasterizationStateCreateInfo rasterizer{};
911 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
912 rasterizer.depthClampEnable = VK_FALSE;
913 rasterizer.rasterizerDiscardEnable = VK_FALSE;
914 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
915 rasterizer.lineWidth = 1.0f;
916 rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
917 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
918 rasterizer.depthBiasEnable = VK_TRUE;
919 rasterizer.depthBiasConstantFactor = 1.25f;
920 rasterizer.depthBiasSlopeFactor = 1.75f;
921 rasterizer.depthBiasClamp = 0.0f;
922
923 VkPipelineMultisampleStateCreateInfo msaa{};
924 msaa.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
925 msaa.sampleShadingEnable = VK_FALSE;
926 msaa.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
927
928 VkPipelineDepthStencilStateCreateInfo depthStencil{};
929 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
930 depthStencil.depthTestEnable = VK_TRUE;
931 depthStencil.depthWriteEnable = VK_TRUE;
932 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
933 depthStencil.depthBoundsTestEnable = VK_FALSE;
934 depthStencil.stencilTestEnable = VK_FALSE;
935
936 VkPipelineColorBlendStateCreateInfo colorBlendInfo{};
937 colorBlendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
938 colorBlendInfo.logicOpEnable = VK_FALSE;
939 colorBlendInfo.attachmentCount = 0;
940
941 VkGraphicsPipelineCreateInfo pipelineInfo{};
942 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
943 pipelineInfo.stageCount = 1;
944 pipelineInfo.pStages = &shaderStage;
945 pipelineInfo.pVertexInputState = &vertexInputInfo;
946 pipelineInfo.pInputAssemblyState = &inputAssembly;
947 pipelineInfo.pViewportState = &viewportState;
948 pipelineInfo.pRasterizationState = &rasterizer;
949 pipelineInfo.pMultisampleState = &msaa;
950 pipelineInfo.pDepthStencilState = &depthStencil;
951 pipelineInfo.pColorBlendState = &colorBlendInfo;
952 pipelineInfo.pDynamicState = &dynamicState;
953 pipelineInfo.layout = pipelineLay;
954 pipelineInfo.renderPass = m_shadowRenderPass;
955 pipelineInfo.subpass = 0;
956 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
957 pipelineInfo.basePipelineIndex = -1;
958
959 VkResult result = vkCreateGraphicsPipelines(
960 device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipes.shadow);
961 delete shadowShader;
962
963 if (result != VK_SUCCESS) {
964 SLEAK_ERROR("VulkanRenderer: Failed to create shadow pipeline for vertex format {}",
965 format);
966 pipes.shadow = VK_NULL_HANDLE;
967 pipes.shadowFailed = true;
968 } else {
969 SLEAK_INFO("VulkanRenderer: Custom format {} shadow pipeline created", format);
970 }
971 }
972 }
973
974 // GBuffer variant — deferred geometry pass, writes the GBuffer attachments
975 if (pipes.gbuffer == VK_NULL_HANDLE && !pipes.gbufferFailed &&
976 !desc->gbufferShaderStem.empty() && m_gbufferResourcesCreated &&
977 m_gbufferRenderPass != VK_NULL_HANDLE) {
978 const std::string gbufVertPath =
979 "assets/shaders/" + desc->gbufferShaderStem + ".vert.spv";
980 auto* gbufShader = new VulkanShader(device);
981 if (!gbufShader->compile(gbufVertPath, "assets/shaders/gbuffer.frag.spv")) {
982 SLEAK_ERROR("VulkanRenderer: Failed to compile '{}' for vertex format {}",
983 gbufVertPath, format);
984 pipes.gbufferFailed = true;
985 delete gbufShader;
986 } else {
987 VkPipelineShaderStageCreateInfo gbufStages[] = {
988 gbufShader->GetVertexInfo(), gbufShader->GetFragInfo()};
989
990 VkPipelineInputAssemblyStateCreateInfo ia{};
991 ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
992 ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
993 ia.primitiveRestartEnable = VK_FALSE;
994
995 VkPipelineViewportStateCreateInfo vp{};
996 vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
997 vp.viewportCount = 1;
998 vp.scissorCount = 1;
999
1000 VkPipelineRasterizationStateCreateInfo rs{};
1001 rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1002 rs.polygonMode = VK_POLYGON_MODE_FILL;
1003 rs.lineWidth = 1.0f;
1004 rs.cullMode = VK_CULL_MODE_BACK_BIT;
1005 rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1006
1007 VkPipelineMultisampleStateCreateInfo ms{};
1008 ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1009 ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1010
1011 VkPipelineDepthStencilStateCreateInfo ds{};
1012 ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1013 ds.depthTestEnable = VK_TRUE;
1014 ds.depthWriteEnable = VK_TRUE;
1015 ds.depthCompareOp = VK_COMPARE_OP_LESS;
1016
1017 VkPipelineColorBlendAttachmentState opaqueBlend{};
1018 opaqueBlend.blendEnable = VK_FALSE;
1019 opaqueBlend.colorWriteMask =
1020 VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
1021 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1022 std::array<VkPipelineColorBlendAttachmentState, GBUFFER_COUNT> gbAtts;
1023 gbAtts.fill(opaqueBlend);
1024
1025 VkPipelineColorBlendStateCreateInfo cb{};
1026 cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1027 cb.attachmentCount = static_cast<uint32_t>(gbAtts.size());
1028 cb.pAttachments = gbAtts.data();
1029
1030 VkGraphicsPipelineCreateInfo gbPipeInfo{};
1031 gbPipeInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1032 gbPipeInfo.stageCount = 2;
1033 gbPipeInfo.pStages = gbufStages;
1034 gbPipeInfo.pVertexInputState = &vertexInputInfo;
1035 gbPipeInfo.pInputAssemblyState = &ia;
1036 gbPipeInfo.pViewportState = &vp;
1037 gbPipeInfo.pRasterizationState = &rs;
1038 gbPipeInfo.pMultisampleState = &ms;
1039 gbPipeInfo.pDepthStencilState = &ds;
1040 gbPipeInfo.pColorBlendState = &cb;
1041 gbPipeInfo.pDynamicState = &dynamicState;
1042 // gbuffer.frag reads set 0 as m_pbrMaterialDSL, so the GBuffer
1043 // variant must use m_gbufferGeomLayout, never pipelineLay.
1044 gbPipeInfo.layout = m_gbufferGeomLayout;
1045 gbPipeInfo.renderPass = m_gbufferRenderPass;
1046 gbPipeInfo.subpass = 0;
1047
1048 VkResult gbResult = vkCreateGraphicsPipelines(
1049 device, VK_NULL_HANDLE, 1, &gbPipeInfo, nullptr, &pipes.gbuffer);
1050 delete gbufShader;
1051
1052 if (gbResult != VK_SUCCESS) {
1053 SLEAK_ERROR("VulkanRenderer: Failed to create GBuffer pipeline for vertex format {}",
1054 format);
1055 pipes.gbuffer = VK_NULL_HANDLE;
1056 pipes.gbufferFailed = true;
1057 } else {
1058 SLEAK_INFO("VulkanRenderer: Custom format {} GBuffer pipeline created", format);
1059 }
1060 }
1061 }
1062
1063 // Transparent variant — alpha-blended, two-sided, runs in the forward
1064 // transparent pass over the deferred scene image.
1065 if (pipes.transparent == VK_NULL_HANDLE && !pipes.transparentFailed &&
1066 !desc->transparentShaderStem.empty()) {
1067 const std::string transparentPath =
1068 "assets/shaders/" + desc->transparentShaderStem;
1069 auto* transparentShader = new VulkanShader(device);
1070 if (!transparentShader->compile(transparentPath)) {
1071 SLEAK_ERROR("VulkanRenderer: Failed to compile '{}' for vertex format {}",
1072 transparentPath, format);
1073 pipes.transparentFailed = true;
1074 delete transparentShader;
1075 } else {
1076 VkPipelineShaderStageCreateInfo trStages[] = {
1077 transparentShader->GetVertexInfo(),
1078 transparentShader->GetFragInfo()};
1079
1080 VkPipelineInputAssemblyStateCreateInfo ia{};
1081 ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1082 ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1083 ia.primitiveRestartEnable = VK_FALSE;
1084
1085 VkPipelineViewportStateCreateInfo vp{};
1086 vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1087 vp.viewportCount = 1;
1088 vp.scissorCount = 1;
1089
1090 VkPipelineRasterizationStateCreateInfo rs{};
1091 rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1092 rs.depthClampEnable = VK_FALSE;
1093 rs.rasterizerDiscardEnable = VK_FALSE;
1094 rs.polygonMode = VK_POLYGON_MODE_FILL;
1095 rs.lineWidth = 1.0f;
1096 // Transparent surfaces are viewed from both sides
1097 rs.cullMode = VK_CULL_MODE_NONE;
1098 rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1099 rs.depthBiasEnable = VK_FALSE;
1100
1101 VkPipelineMultisampleStateCreateInfo ms{};
1102 ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1103 ms.sampleShadingEnable = VK_FALSE;
1104 // The forward transparent pass runs on m_forwardRenderPass (1 sample)
1105 // when deferred is enabled; use the main render pass samples otherwise.
1106 ms.rasterizationSamples =
1107 (m_deferredEnabled && m_forwardRenderPass != VK_NULL_HANDLE)
1108 ? VK_SAMPLE_COUNT_1_BIT
1109 : m_msaaSamples;
1110
1111 VkPipelineDepthStencilStateCreateInfo ds{};
1112 ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1113 ds.depthTestEnable = VK_TRUE;
1114 ds.depthWriteEnable = VK_TRUE;
1115 ds.depthCompareOp = VK_COMPARE_OP_LESS;
1116 ds.depthBoundsTestEnable = VK_FALSE;
1117 ds.stencilTestEnable = VK_FALSE;
1118
1119 VkPipelineColorBlendAttachmentState blendAtt{};
1120 blendAtt.blendEnable = VK_TRUE;
1121 blendAtt.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1122 blendAtt.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1123 blendAtt.colorBlendOp = VK_BLEND_OP_ADD;
1124 blendAtt.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1125 blendAtt.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1126 blendAtt.alphaBlendOp = VK_BLEND_OP_ADD;
1127 blendAtt.colorWriteMask =
1128 VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
1129 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1130
1131 VkPipelineColorBlendStateCreateInfo cb{};
1132 cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1133 cb.logicOpEnable = VK_FALSE;
1134 cb.attachmentCount = 1;
1135 cb.pAttachments = &blendAtt;
1136
1137 VkGraphicsPipelineCreateInfo trPipeInfo{};
1138 trPipeInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1139 trPipeInfo.stageCount = 2;
1140 trPipeInfo.pStages = trStages;
1141 trPipeInfo.pVertexInputState = &vertexInputInfo;
1142 trPipeInfo.pInputAssemblyState = &ia;
1143 trPipeInfo.pViewportState = &vp;
1144 trPipeInfo.pRasterizationState = &rs;
1145 trPipeInfo.pMultisampleState = &ms;
1146 trPipeInfo.pDepthStencilState = &ds;
1147 trPipeInfo.pColorBlendState = &cb;
1148 trPipeInfo.pDynamicState = &dynamicState;
1149 trPipeInfo.layout = pipelineLay; // reuse main layout (same descriptor sets)
1150 trPipeInfo.subpass = 0;
1151 trPipeInfo.renderPass =
1152 (m_deferredEnabled && m_forwardRenderPass != VK_NULL_HANDLE)
1153 ? m_forwardRenderPass
1154 : renderPass;
1155 trPipeInfo.basePipelineHandle = VK_NULL_HANDLE;
1156 trPipeInfo.basePipelineIndex = -1;
1157
1158 VkResult trResult = vkCreateGraphicsPipelines(
1159 device, VK_NULL_HANDLE, 1, &trPipeInfo, nullptr, &pipes.transparent);
1160 delete transparentShader;
1161
1162 if (trResult != VK_SUCCESS) {
1163 SLEAK_ERROR("VulkanRenderer: Failed to create transparent pipeline for vertex format {}",
1164 format);
1165 pipes.transparent = VK_NULL_HANDLE;
1166 pipes.transparentFailed = true;
1167 } else {
1168 SLEAK_INFO("VulkanRenderer: Custom format {} transparent pipeline created", format);
1169 }
1170 }
1171 }
1172
1173 return pipes.main != VK_NULL_HANDLE;
1174}
1175
1176
1177/// Binds the custom-format pipeline matching the currently active render pass.
1179 if (!bFrameStarted) return;
1180 if (format == 0) return;
1181 if (m_activeCustomFormat == format) return;
1182 m_activeCustomFormat = format;
1183 m_customFormatUnbound = false;
1184
1185 if (!CreateCustomFormatPipelines(format)) {
1186 m_customFormatUnbound = true;
1187 return;
1188 }
1189 const CustomFormatPipelines& pipes = m_customFormatPipelines[format];
1190
1191 VkPipeline target;
1192 if (m_shadowPassActive) {
1193 target = pipes.shadow;
1194 } else if (m_inGeometryPass) {
1195 target = pipes.gbuffer;
1196 } else if (m_inForwardTransparentPass) {
1197 target = pipes.transparent;
1198 } else {
1199 target = pipes.main;
1200 }
1201
1202 // No variant for this pass: drop the draws rather than rasterize this
1203 // layout's vertices through a pipeline built for another one.
1204 if (target == VK_NULL_HANDLE) {
1205 m_customFormatUnbound = true;
1206 return;
1207 }
1208
1209 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, target);
1210}
1211
1212
1213/// Restores the previous pipeline and descriptor set after custom-format draws.
1215 if (!bFrameStarted) return;
1216 if (m_activeCustomFormat == 0) return;
1217 m_activeCustomFormat = 0;
1218 m_customFormatUnbound = false;
1219
1220 if (m_shadowPassActive) {
1221 if (m_shadowPipeline != VK_NULL_HANDLE) {
1222 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1223 m_shadowPipeline);
1224 }
1225 } else if (m_inGeometryPass && m_gbufferPipeline != VK_NULL_HANDLE) {
1226 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1227 m_gbufferPipeline);
1228 } else {
1229 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
1230 }
1231
1232 // Re-bind descriptors after pipeline change; set 0 inside the GBuffer
1233 // geometry pass belongs to m_gbufferGeomLayout and is owned by BindPBRMaterial.
1234 if (!m_inGeometryPass && m_textureDescriptorsWritten &&
1235 CurrentFrameIndex < descriptorSets.size()) {
1236 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1237 pipelineLay, 0, 1,
1238 &descriptorSets[CurrentFrameIndex], 0, nullptr);
1239 }
1240}
1241
1242
1243/// Destroys every cached custom-format pipeline and clears the map.
1244void VulkanRenderer::DestroyCustomFormatPipelines() {
1245 for (auto& entry : m_customFormatPipelines) {
1246 CustomFormatPipelines& pipes = entry.second;
1247 if (pipes.main) vkDestroyPipeline(device, pipes.main, nullptr);
1248 if (pipes.shadow) vkDestroyPipeline(device, pipes.shadow, nullptr);
1249 if (pipes.gbuffer) vkDestroyPipeline(device, pipes.gbuffer, nullptr);
1250 if (pipes.transparent)
1251 vkDestroyPipeline(device, pipes.transparent, nullptr);
1252 }
1253 m_customFormatPipelines.clear();
1254 m_activeCustomFormat = 0;
1255}
1256
1257
1258
1259/// Binds the debug line pipeline for the current frame.
1261 if (!bFrameStarted) return;
1262 if (debugLinePipeline == VK_NULL_HANDLE) {
1263 if (!CreateDebugLinePipeline()) return;
1264 }
1265 m_activeCustomFormat = 0; // prevent BindVertexBuffer from overriding this pipeline
1266 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1267 debugLinePipeline);
1268}
1269
1270
1271/// Restores the previous pipeline and descriptor set after debug line draws.
1273 if (!bFrameStarted) return;
1274
1275 // Restore pipeline: inside geometry pass restore to the GBuffer pipeline;
1276 // otherwise the main forward pipeline.
1277 VkPipeline restoreTo =
1278 (m_inGeometryPass && m_gbufferPipeline != VK_NULL_HANDLE)
1279 ? m_gbufferPipeline : pipeline;
1280 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, restoreTo);
1281
1282 // Rebind main texture descriptor sets.
1283 // In the GBuffer geometry pass set 0 belongs to m_gbufferGeomLayout and is
1284 // managed by BindPBRMaterial — do NOT overwrite it with the forward
1285 // single-sampler descriptor set or use pipelineLay here.
1286 if (!m_inGeometryPass && m_textureDescriptorsWritten &&
1287 CurrentFrameIndex < descriptorSets.size()) {
1288 vkCmdBindDescriptorSets(
1289 command, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLay, 0, 1,
1290 &descriptorSets[CurrentFrameIndex], 0, nullptr);
1291 }
1292}
1293
1294/// Compiles the skinned shaders and creates the forward skinned pipeline.
1295bool VulkanRenderer::CreateSkinnedPipeline() {
1296 // 1. Compile skinned shaders
1297 skinnedShader = new VulkanShader(device);
1298 if (!skinnedShader->compile("assets/shaders/skinned_shader")) {
1299 SLEAK_ERROR("VulkanRenderer: Failed to compile skinned shaders");
1300 delete skinnedShader;
1301 skinnedShader = nullptr;
1302 return false;
1303 }
1304
1305 // 2. Create pipeline (same as main but with skinned shaders)
1306 VkPipelineShaderStageCreateInfo shaderStages[] = {
1307 skinnedShader->GetVertexInfo(), skinnedShader->GetFragInfo()};
1308
1309 std::vector<VkDynamicState> dynamicStates = {
1310 VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
1311
1312 VkPipelineDynamicStateCreateInfo dynamicState{};
1313 dynamicState.sType =
1314 VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1315 dynamicState.dynamicStateCount =
1316 static_cast<uint32_t>(dynamicStates.size());
1317 dynamicState.pDynamicStates = dynamicStates.data();
1318
1319 // Same vertex layout as main pipeline (7 attributes including bone data)
1320 VkVertexInputBindingDescription bindingDescription{};
1321 bindingDescription.binding = 0;
1322 bindingDescription.stride = sizeof(Vertex);
1323 bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1324
1325 std::array<VkVertexInputAttributeDescription, 7> attributeDescs{};
1326 attributeDescs[0].binding = 0;
1327 attributeDescs[0].location = 0;
1328 attributeDescs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
1329 attributeDescs[0].offset = offsetof(Vertex, px);
1330
1331 attributeDescs[1].binding = 0;
1332 attributeDescs[1].location = 1;
1333 attributeDescs[1].format = VK_FORMAT_R32G32B32_SFLOAT;
1334 attributeDescs[1].offset = offsetof(Vertex, nx);
1335
1336 attributeDescs[2].binding = 0;
1337 attributeDescs[2].location = 2;
1338 attributeDescs[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1339 attributeDescs[2].offset = offsetof(Vertex, tx);
1340
1341 attributeDescs[3].binding = 0;
1342 attributeDescs[3].location = 3;
1343 attributeDescs[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1344 attributeDescs[3].offset = offsetof(Vertex, r);
1345
1346 attributeDescs[4].binding = 0;
1347 attributeDescs[4].location = 4;
1348 attributeDescs[4].format = VK_FORMAT_R32G32_SFLOAT;
1349 attributeDescs[4].offset = offsetof(Vertex, u);
1350
1351 attributeDescs[5].binding = 0;
1352 attributeDescs[5].location = 5;
1353 attributeDescs[5].format = VK_FORMAT_R32G32B32A32_SINT;
1354 attributeDescs[5].offset = offsetof(Vertex, boneIDs);
1355
1356 attributeDescs[6].binding = 0;
1357 attributeDescs[6].location = 6;
1358 attributeDescs[6].format = VK_FORMAT_R32G32B32A32_SFLOAT;
1359 attributeDescs[6].offset = offsetof(Vertex, boneWeights);
1360
1361 VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
1362 vertexInputInfo.sType =
1363 VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1364 vertexInputInfo.vertexBindingDescriptionCount = 1;
1365 vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
1366 vertexInputInfo.vertexAttributeDescriptionCount =
1367 static_cast<uint32_t>(attributeDescs.size());
1368 vertexInputInfo.pVertexAttributeDescriptions = attributeDescs.data();
1369
1370 VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo{};
1371 inputAssemblyInfo.sType =
1372 VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1373 inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1374 inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
1375
1376 VkPipelineViewportStateCreateInfo viewportInfo{};
1377 viewportInfo.sType =
1378 VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1379 viewportInfo.viewportCount = 1;
1380 viewportInfo.scissorCount = 1;
1381
1382 VkPipelineRasterizationStateCreateInfo rasterizer{};
1383 rasterizer.sType =
1384 VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1385 rasterizer.depthClampEnable = VK_FALSE;
1386 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1387 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1388 rasterizer.lineWidth = 1.0f;
1389 rasterizer.cullMode = VK_CULL_MODE_NONE;
1390 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
1391 rasterizer.depthBiasEnable = VK_FALSE;
1392
1393 VkPipelineMultisampleStateCreateInfo msaa{};
1394 msaa.sType =
1395 VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1396 msaa.sampleShadingEnable = VK_FALSE;
1397 // When deferred is enabled the skinned pipeline runs inside m_forwardRenderPass
1398 // which is always 1-sample (GBuffer outputs are resolved separately).
1399 // When forward-only, match the main render pass sample count.
1400 msaa.rasterizationSamples = (m_deferredEnabled && m_forwardRenderPass != VK_NULL_HANDLE)
1401 ? VK_SAMPLE_COUNT_1_BIT : m_msaaSamples;
1402
1403 // Skinned: depth test + depth write enabled (same as main pipeline)
1404 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1405 depthStencil.sType =
1406 VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1407 depthStencil.depthTestEnable = VK_TRUE;
1408 depthStencil.depthWriteEnable = VK_TRUE;
1409 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
1410 depthStencil.depthBoundsTestEnable = VK_FALSE;
1411 depthStencil.stencilTestEnable = VK_FALSE;
1412
1413 VkPipelineColorBlendAttachmentState colorBlendAttachment{};
1414 colorBlendAttachment.blendEnable = VK_FALSE;
1415 colorBlendAttachment.colorWriteMask =
1416 VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
1417 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1418
1419 VkPipelineColorBlendStateCreateInfo colorBlendInfo{};
1420 colorBlendInfo.sType =
1421 VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1422 colorBlendInfo.logicOpEnable = VK_FALSE;
1423 colorBlendInfo.attachmentCount = 1;
1424 colorBlendInfo.pAttachments = &colorBlendAttachment;
1425
1426 VkGraphicsPipelineCreateInfo pipelineInfo{};
1427 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1428 pipelineInfo.stageCount = 2;
1429 pipelineInfo.pStages = shaderStages;
1430 pipelineInfo.pVertexInputState = &vertexInputInfo;
1431 pipelineInfo.pInputAssemblyState = &inputAssemblyInfo;
1432 pipelineInfo.pViewportState = &viewportInfo;
1433 pipelineInfo.pRasterizationState = &rasterizer;
1434 pipelineInfo.pMultisampleState = &msaa;
1435 pipelineInfo.pDepthStencilState = &depthStencil;
1436 pipelineInfo.pColorBlendState = &colorBlendInfo;
1437 pipelineInfo.pDynamicState = &dynamicState;
1438 pipelineInfo.layout = pipelineLay; // Reuse same pipeline layout (set 0 + set 1)
1439 pipelineInfo.subpass = 0;
1440 pipelineInfo.renderPass = (m_deferredEnabled && m_forwardRenderPass != VK_NULL_HANDLE)
1441 ? m_forwardRenderPass : renderPass;
1442 pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
1443 pipelineInfo.basePipelineIndex = -1;
1444
1445 VkResult result = vkCreateGraphicsPipelines(
1446 device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &skinnedPipeline);
1447 if (result != VK_SUCCESS) {
1448 SLEAK_ERROR("VulkanRenderer: Failed to create skinned pipeline!");
1449 return false;
1450 }
1451
1452 SLEAK_INFO("VulkanRenderer: Skinned pipeline created successfully");
1453 return true;
1454}
1455
1456
1457/// Binds the skinned pipeline matching the currently active render pass.
1459 if (!bFrameStarted) return;
1460
1461 if (m_inGeometryPass) {
1462 // GBuffer pass — use the GBuffer-compatible skinned pipeline
1463 if (m_skinnedGbufferPipeline == VK_NULL_HANDLE)
1464 CreateSkinnedGbufferPipeline();
1465 if (m_skinnedGbufferPipeline != VK_NULL_HANDLE)
1466 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
1467 m_skinnedGbufferPipeline);
1468 return;
1469 }
1470
1471 // Forward pass — use the forward skinned pipeline
1472 if (skinnedPipeline == VK_NULL_HANDLE) {
1473 if (!CreateSkinnedPipeline()) return;
1474 }
1475 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, skinnedPipeline);
1476}
1477
1478
1479/// Restores the previous pipeline after skinned draws.
1481 if (!bFrameStarted) return;
1482
1483 if (m_inGeometryPass) {
1484 // Restore static GBuffer pipeline
1485 if (m_gbufferPipeline != VK_NULL_HANDLE)
1486 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, m_gbufferPipeline);
1487 return;
1488 }
1489
1490 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
1491}
1492
1493}
1494}
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_RETURN_ERR(...)
Definition Logger.hpp:25
#define SLEAK_INFO(...)
Definition Logger.hpp:20
virtual void EndDebugLinePass() override
Restores the previous pipeline and descriptor set after debug line draws.
virtual void EndSkinnedPass() override
Restores the previous pipeline after skinned draws.
virtual void EndCustomFormatPass() override
Restores the previous pipeline and descriptor set after custom-format draws.
virtual void EndSkyboxPass() override
Restores the previous pipeline and descriptor set after the skybox draw.
virtual void BeginDebugLinePass() override
Binds the debug line pipeline for the current frame.
virtual void BeginSkinnedPass() override
Binds the skinned pipeline matching the currently active render pass.
virtual void BeginSkyboxPass() override
Binds the skybox pipeline and its descriptor set for the current frame.
virtual void BeginCustomFormatPass(VertexFormatHandle format) override
Binds the custom-format pipeline matching the currently active render pass.
Vulkan vertex+fragment shader pair, loaded from precompiled SPIR-V modules.
VkPipelineShaderStageCreateInfo GetFragInfo()
VkPipelineShaderStageCreateInfo GetVertexInfo()
virtual bool compile(const std::string &shaderPath) override
Loads the combined-path convention's .vert.spv/.frag.spv pair.
static const VertexLayoutDesc * Get(VertexFormatHandle handle)
Backend-facing rendering layer shared by the four graphics backends.
static VkFormat ToVkVertexFormat(VertexAttribFormat format)
Translates a registry attribute format into its Vulkan vertex format.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
uint32_t VertexFormatHandle
std::string transparentShaderStem
std::vector< VertexAttribute > attributes