VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
vulkanRenderPipeline.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3//
4
5#ifdef VISUTWIN_HAS_VULKAN
6
9#include "vulkanShader.h"
10#include "vulkanUtils.h"
11
15#include "scene/mesh.h"
16#include "spdlog/spdlog.h"
17
18namespace visutwin::canvas
19{
20 VulkanRenderPipeline::VulkanRenderPipeline(VulkanGraphicsDevice* device)
21 : _device(device)
22 {
23 createLayouts();
24 }
25
26 VulkanRenderPipeline::~VulkanRenderPipeline()
27 {
28 VkDevice vk = _device->device();
29 for (auto& [key, pipeline] : _cache) {
30 vkDestroyPipeline(vk, pipeline, nullptr);
31 }
32 if (_pipelineLayout != VK_NULL_HANDLE)
33 vkDestroyPipelineLayout(vk, _pipelineLayout, nullptr);
34 if (_materialSetLayout != VK_NULL_HANDLE)
35 vkDestroyDescriptorSetLayout(vk, _materialSetLayout, nullptr);
36 if (_textureSetLayout != VK_NULL_HANDLE)
37 vkDestroyDescriptorSetLayout(vk, _textureSetLayout, nullptr);
38 }
39
40 void VulkanRenderPipeline::createLayouts()
41 {
42 VkDevice vk = _device->device();
43
44 // Set 0: Material UBO
45 VkDescriptorSetLayoutBinding materialBinding{};
46 materialBinding.binding = 0;
47 materialBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
48 materialBinding.descriptorCount = 1;
49 materialBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
50
51 VkDescriptorSetLayoutCreateInfo materialLayoutInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
52 materialLayoutInfo.bindingCount = 1;
53 materialLayoutInfo.pBindings = &materialBinding;
54 vkCreateDescriptorSetLayout(vk, &materialLayoutInfo, nullptr, &_materialSetLayout);
55
56 // Set 1: Texture samplers (6 slots)
57 std::array<VkDescriptorSetLayoutBinding, 6> texBindings{};
58 for (uint32_t i = 0; i < texBindings.size(); i++) {
59 texBindings[i].binding = i;
60 texBindings[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
61 texBindings[i].descriptorCount = 1;
62 texBindings[i].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
63 }
64
65 VkDescriptorSetLayoutCreateInfo textureLayoutInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
66 textureLayoutInfo.bindingCount = static_cast<uint32_t>(texBindings.size());
67 textureLayoutInfo.pBindings = texBindings.data();
68 vkCreateDescriptorSetLayout(vk, &textureLayoutInfo, nullptr, &_textureSetLayout);
69
70 // Push constants: 2 × mat4 = 128 bytes
71 VkPushConstantRange pushRange{};
72 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
73 pushRange.offset = 0;
74 pushRange.size = 128;
75
76 VkDescriptorSetLayout setLayouts[] = {_materialSetLayout, _textureSetLayout};
77
78 VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
79 layoutInfo.setLayoutCount = 2;
80 layoutInfo.pSetLayouts = setLayouts;
81 layoutInfo.pushConstantRangeCount = 1;
82 layoutInfo.pPushConstantRanges = &pushRange;
83 vkCreatePipelineLayout(vk, &layoutInfo, nullptr, &_pipelineLayout);
84 }
85
86 VkPipeline VulkanRenderPipeline::get(const Primitive& primitive,
87 const std::shared_ptr<VertexFormat>& vertexFormat,
88 const std::shared_ptr<VulkanShader>& shader,
89 const std::shared_ptr<BlendState>& blendState,
90 const std::shared_ptr<DepthState>& depthState,
91 CullMode cullMode,
92 VkFormat colorFormat,
93 VkFormat depthFormat)
94 {
95 // FNV-1a hash of pipeline state
96 uint64_t hash = 14695981039346656037ULL;
97 auto mix = [&](uint64_t v) { hash ^= v; hash *= 1099511628211ULL; };
98 mix(static_cast<uint64_t>(primitive.type));
99 mix(vertexFormat ? vertexFormat->renderingHash() : 0);
100 mix(shader ? static_cast<uint64_t>(shader->id()) : 0);
101 mix(blendState ? blendState->key() : 0);
102 mix(depthState ? depthState->key() : 0);
103 mix(static_cast<uint64_t>(cullMode));
104 mix(static_cast<uint64_t>(colorFormat));
105 mix(static_cast<uint64_t>(depthFormat));
106
107 auto it = _cache.find(hash);
108 if (it != _cache.end()) return it->second;
109
110 VkPipeline pipeline = create(primitive, vertexFormat, shader,
111 blendState, depthState, cullMode, colorFormat, depthFormat);
112 _cache[hash] = pipeline;
113 return pipeline;
114 }
115
116 VkPipeline VulkanRenderPipeline::create(const Primitive& primitive,
117 const std::shared_ptr<VertexFormat>& vertexFormat,
118 const std::shared_ptr<VulkanShader>& shader,
119 const std::shared_ptr<BlendState>& blendState,
120 const std::shared_ptr<DepthState>& depthState,
121 CullMode cullMode,
122 VkFormat colorFormat,
123 VkFormat depthFormat)
124 {
125 VkDevice vk = _device->device();
126
127 // --- Shader stages ---
128 std::vector<VkPipelineShaderStageCreateInfo> stages;
129 if (shader->vertexModule() != VK_NULL_HANDLE) {
130 VkPipelineShaderStageCreateInfo vert{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
131 vert.stage = VK_SHADER_STAGE_VERTEX_BIT;
132 vert.module = shader->vertexModule();
133 vert.pName = "main";
134 stages.push_back(vert);
135 }
136 if (shader->fragmentModule() != VK_NULL_HANDLE) {
137 VkPipelineShaderStageCreateInfo frag{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
138 frag.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
139 frag.module = shader->fragmentModule();
140 frag.pName = "main";
141 stages.push_back(frag);
142 }
143
144 // --- Vertex input ---
145 // Standard layout: pos(3f) + normal(3f) + uv0(2f) + tangent(4f) + uv1(2f) = 56 bytes
146 int stride = vertexFormat ? vertexFormat->size() : 56;
147
148 VkVertexInputBindingDescription binding{};
149 binding.binding = 0;
150 binding.stride = static_cast<uint32_t>(stride);
151 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
152
153 // Derive attribute descriptions from vertex format elements
154 std::vector<VkVertexInputAttributeDescription> attributes;
155 if (vertexFormat) {
156 for (int i = 0; i < vertexFormat->elementCount(); i++) {
157 auto& elem = vertexFormat->element(i);
158 VkVertexInputAttributeDescription attr{};
159 attr.location = static_cast<uint32_t>(i);
160 attr.binding = 0;
161 attr.offset = static_cast<uint32_t>(elem.offset);
162
163 // Map element type + component count to VkFormat
164 switch (elem.numComponents) {
165 case 1:
166 attr.format = (elem.dataType == TYPE_FLOAT32) ? VK_FORMAT_R32_SFLOAT : VK_FORMAT_R32_UINT;
167 break;
168 case 2:
169 attr.format = VK_FORMAT_R32G32_SFLOAT;
170 break;
171 case 3:
172 attr.format = VK_FORMAT_R32G32B32_SFLOAT;
173 break;
174 case 4:
175 attr.format = VK_FORMAT_R32G32B32A32_SFLOAT;
176 break;
177 default:
178 attr.format = VK_FORMAT_R32G32B32A32_SFLOAT;
179 break;
180 }
181 attributes.push_back(attr);
182 }
183 } else {
184 // Fallback: hardcoded standard vertex layout
185 attributes = {
186 {0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // position
187 {1, 0, VK_FORMAT_R32G32B32_SFLOAT, 12}, // normal
188 {2, 0, VK_FORMAT_R32G32_SFLOAT, 24}, // uv0
189 {3, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 32}, // tangent
190 {4, 0, VK_FORMAT_R32G32_SFLOAT, 48}, // uv1
191 };
192 }
193
194 VkPipelineVertexInputStateCreateInfo vertexInput{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
195 vertexInput.vertexBindingDescriptionCount = 1;
196 vertexInput.pVertexBindingDescriptions = &binding;
197 vertexInput.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributes.size());
198 vertexInput.pVertexAttributeDescriptions = attributes.data();
199
200 // --- Input assembly ---
201 VkPipelineInputAssemblyStateCreateInfo inputAssembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
202 inputAssembly.topology = vulkanMapPrimitiveType(primitive.type);
203 inputAssembly.primitiveRestartEnable = VK_FALSE;
204
205 // --- Viewport (dynamic) ---
206 VkPipelineViewportStateCreateInfo viewportState{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
207 viewportState.viewportCount = 1;
208 viewportState.scissorCount = 1;
209
210 // --- Rasterization ---
211 VkPipelineRasterizationStateCreateInfo rasterization{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
212 rasterization.depthClampEnable = VK_FALSE;
213 rasterization.rasterizerDiscardEnable = VK_FALSE;
214 rasterization.polygonMode = VK_POLYGON_MODE_FILL;
215 rasterization.cullMode = vulkanMapCullMode(cullMode);
216 rasterization.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
217 rasterization.depthBiasEnable = VK_FALSE;
218 rasterization.lineWidth = 1.0f;
219
220 // --- Multisample ---
221 VkPipelineMultisampleStateCreateInfo multisampling{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
222 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
223
224 // --- Depth/stencil ---
225 VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
226 if (depthState) {
227 depthStencil.depthTestEnable = depthState->depthTest() ? VK_TRUE : VK_FALSE;
228 depthStencil.depthWriteEnable = depthState->depthWrite() ? VK_TRUE : VK_FALSE;
229 } else {
230 depthStencil.depthTestEnable = VK_TRUE;
231 depthStencil.depthWriteEnable = VK_TRUE;
232 }
233 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
234 depthStencil.depthBoundsTestEnable = VK_FALSE;
235 depthStencil.stencilTestEnable = VK_FALSE;
236
237 // --- Color blend ---
238 VkPipelineColorBlendAttachmentState blendAttachment{};
239 if (blendState && blendState->enabled()) {
240 blendAttachment.blendEnable = VK_TRUE;
241 blendAttachment.srcColorBlendFactor = vulkanMapBlendFactor(blendState->colorSrcFactor());
242 blendAttachment.dstColorBlendFactor = vulkanMapBlendFactor(blendState->colorDstFactor());
243 blendAttachment.colorBlendOp = vulkanMapBlendOp(blendState->colorOp());
244 blendAttachment.srcAlphaBlendFactor = vulkanMapBlendFactor(blendState->alphaSrcFactor());
245 blendAttachment.dstAlphaBlendFactor = vulkanMapBlendFactor(blendState->alphaDstFactor());
246 blendAttachment.alphaBlendOp = vulkanMapBlendOp(blendState->alphaOp());
247 }
248 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
249 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
250
251 VkPipelineColorBlendStateCreateInfo colorBlend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
252 colorBlend.attachmentCount = 1;
253 colorBlend.pAttachments = &blendAttachment;
254
255 // --- Dynamic state ---
256 VkDynamicState dynamicStates[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
257 VkPipelineDynamicStateCreateInfo dynamicState{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
258 dynamicState.dynamicStateCount = 2;
259 dynamicState.pDynamicStates = dynamicStates;
260
261 // --- Dynamic rendering (Vulkan 1.3) ---
262 VkPipelineRenderingCreateInfo renderingInfo{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO};
263 renderingInfo.colorAttachmentCount = 1;
264 renderingInfo.pColorAttachmentFormats = &colorFormat;
265 renderingInfo.depthAttachmentFormat = depthFormat;
266
267 // --- Create pipeline ---
268 VkGraphicsPipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
269 pipelineInfo.pNext = &renderingInfo;
270 pipelineInfo.stageCount = static_cast<uint32_t>(stages.size());
271 pipelineInfo.pStages = stages.data();
272 pipelineInfo.pVertexInputState = &vertexInput;
273 pipelineInfo.pInputAssemblyState = &inputAssembly;
274 pipelineInfo.pViewportState = &viewportState;
275 pipelineInfo.pRasterizationState = &rasterization;
276 pipelineInfo.pMultisampleState = &multisampling;
277 pipelineInfo.pDepthStencilState = &depthStencil;
278 pipelineInfo.pColorBlendState = &colorBlend;
279 pipelineInfo.pDynamicState = &dynamicState;
280 pipelineInfo.layout = _pipelineLayout;
281 pipelineInfo.renderPass = VK_NULL_HANDLE; // dynamic rendering
282
283 VkPipeline pipeline = VK_NULL_HANDLE;
284 VkResult result = vkCreateGraphicsPipelines(vk, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline);
285 if (result != VK_SUCCESS) {
286 spdlog::error("Failed to create Vulkan graphics pipeline: {}", static_cast<int>(result));
287 }
288 return pipeline;
289 }
290}
291
292#endif // VISUTWIN_HAS_VULKAN