MXVK Vulkan Framework 0.35.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_abstract_model.cpp
Go to the documentation of this file.
3
4#include <cstring>
5
7#include "mxvk/mxvk_png.hpp"
9
10#include <algorithm>
11#include <array>
12#include <filesystem>
13#include <fstream>
14#include <iostream>
15#include <sstream>
16#ifdef MXVK_CUDA
17#include <unistd.h>
18#endif
19
20namespace mxvk {
21
22 namespace {
24 glm::mat4 model{1.0f};
25 glm::vec4 fx{0.0f};
26 };
27
28 void logVKAbstractModelStep(const std::string &message, bool important = false) {
29 if (important) {
30 std::cout << "mxvk_abstract_model: " << message << '\n';
31 }
32 }
33
34 [[nodiscard]] std::string parseMTLTexturePath(std::istream &stream) {
35 std::string texturePath{};
36 std::string token{};
37 while (stream >> token) {
38 if (!token.empty() && token[0] == '-') {
39 if (token == "-blendu" || token == "-blendv" || token == "-cc" || token == "-clamp" || token == "-imfchan" || token == "-type") {
40 stream >> token;
41 } else if (token == "-mm") {
42 stream >> token;
43 stream >> token;
44 } else if (token == "-o" || token == "-s" || token == "-t") {
45 stream >> token;
46 stream >> token;
47 stream >> token;
48 } else if (token == "-bm" || token == "-boost" || token == "-texres") {
49 stream >> token;
50 }
51 continue;
52 }
53
54 if (!texturePath.empty()) {
55 texturePath += ' ';
56 }
57 texturePath += token;
58 }
59 return texturePath;
60 }
61
62 [[nodiscard]] std::string resolveTexturePath(const std::string &textureBasePath, const std::string &texturePath) {
63 if (texturePath.empty()) {
64 return {};
65 }
66
67 std::filesystem::path resolvedPath(texturePath);
68 if (resolvedPath.is_absolute()) {
69 resolvedPath = resolvedPath.filename();
70 }
71
72 if (textureBasePath.empty()) {
73 return resolvedPath.string();
74 }
75
76 return (std::filesystem::path(textureBasePath) / resolvedPath).string();
77 }
78 } // namespace
79
80 void VKAbstractModel::load(VK_Window *targetWindow, const std::string &modelPath, const std::string &textureManifestPath, const std::string &textureBasePath, float scale) {
81 if (targetWindow == nullptr) {
82 throw mxvk::Exception("VKAbstractModel::load requires a valid window");
83 }
84 if (modelPath.empty()) {
85 throw mxvk::Exception("VKAbstractModel::load modelPath is empty");
86 }
87
88 logVKAbstractModelStep("creation begin: " + modelPath, true);
89
90 windowPtr = targetWindow;
91 if (!windowPtr->ensureRenderResources()) {
92 throw mxvk::Exception("VKAbstractModel::load failed because render resources are not ready");
93 }
94
95 obj.load(modelPath, scale);
96 obj.upload(windowPtr->getDevice(), windowPtr->getPhysicalDevice(), windowPtr->getCommandPool(), windowPtr->getGraphicsQueue());
97 computeBoundsAndScale();
98 logVKAbstractModelStep("mesh upload complete", true);
99
100 textures.clear();
101 if (!textureManifestPath.empty()) {
102 loadTextures(textureManifestPath, textureBasePath);
103 } else {
104 loadTexturesFromMTL(textureBasePath.empty() ? std::filesystem::path(modelPath).parent_path().string() : textureBasePath);
105 }
106 if (textures.empty()) {
107 createFallbackTexture();
108 logVKAbstractModelStep("using fallback texture", true);
109 }
110 logVKAbstractModelStep("textures ready: " + std::to_string(textures.size()), true);
111
112 createTextureSampler();
113 createDescriptorSetLayout();
114 createUniformBuffers();
115 createDescriptorPool();
116 createDescriptorSets();
117 createPipelines();
118 logVKAbstractModelStep("creation complete", true);
119 }
120
121 void VKAbstractModel::load(VK_Window *targetWindow, MXModel &&model, const std::string &textureManifestPath, const std::string &textureBasePath, [[maybe_unused]] float scale) {
122 if (targetWindow == nullptr) {
123 throw mxvk::Exception("VKAbstractModel::load requires a valid window");
124 }
125
126 windowPtr = targetWindow;
127 if (!windowPtr->ensureRenderResources()) {
128 throw mxvk::Exception("VKAbstractModel::load failed because render resources are not ready");
129 }
130
131 obj = std::move(model);
132 obj.upload(windowPtr->getDevice(), windowPtr->getPhysicalDevice(), windowPtr->getCommandPool(), windowPtr->getGraphicsQueue());
133 computeBoundsAndScale();
134 logVKAbstractModelStep("mesh upload complete (prepared)", true);
135
136 textures.clear();
137 if (!textureManifestPath.empty()) {
138 loadTextures(textureManifestPath, textureBasePath);
139 } else if (!obj.mtlLibPath().empty()) {
140 loadTexturesFromMTL(textureBasePath.empty() ? std::filesystem::path(obj.mtlLibPath()).parent_path().string() : textureBasePath);
141 } else {
142 createFallbackTexture();
143 logVKAbstractModelStep("using fallback texture", true);
144 }
145 if (textures.empty()) {
146 createFallbackTexture();
147 logVKAbstractModelStep("using fallback texture", true);
148 }
149 logVKAbstractModelStep("textures ready: " + std::to_string(textures.size()), true);
150
151 createTextureSampler();
152 createDescriptorSetLayout();
153 createUniformBuffers();
154 createDescriptorPool();
155 createDescriptorSets();
156 createPipelines();
157 logVKAbstractModelStep("creation complete", true);
158 }
159
160 void VKAbstractModel::setShaders(VK_Window *targetWindow, const std::string &vertSpv, const std::string &fragSpv) {
161 if (targetWindow == nullptr) {
162 throw mxvk::Exception("VKAbstractModel::setShaders requires a valid window");
163 }
164
165 windowPtr = targetWindow;
166 vertexShaderPath = vertSpv;
167 fragmentShaderPath = fragSpv;
168 logVKAbstractModelStep("setShaders", true);
169 createPipelines();
170 }
171
173 if (backfaceCullingEnabled == enabled) {
174 return;
175 }
176
177 backfaceCullingEnabled = enabled;
178 if (windowPtr != nullptr) {
179 createPipelines();
180 }
181 }
182
184 if (alphaBlendingEnabled == enabled) {
185 return;
186 }
187
188 alphaBlendingEnabled = enabled;
189 if (windowPtr != nullptr) {
190 createPipelines();
191 }
192 }
193
195 if (colorAttachmentFormat == format) {
196 return;
197 }
198 colorAttachmentFormat = format;
199 if (windowPtr != nullptr) {
200 createPipelines();
201 }
202 }
203
204 void VKAbstractModel::updateUBO(uint32_t imageIndex, const UniformBufferObject &ubo) {
205 if (imageIndex >= uniformBuffersMapped.size()) {
206 return;
207 }
208 if (uniformBuffersMapped[imageIndex] == nullptr) {
209 return;
210 }
211
212 std::memcpy(uniformBuffersMapped[imageIndex], &ubo, sizeof(UniformBufferObject));
213 }
214
216 if (windowPtr != nullptr) {
217 throw mxvk::Exception("VKAbstractModel::enableExtendedFragmentUniforms must be called before load");
218 }
219 extendedFragmentUniformsEnabled = true;
220 }
221
222 void VKAbstractModel::updateFragmentUBO(uint32_t imageIndex, const ModelFragmentUniforms &uniforms) {
223 if (imageIndex >= fragmentUniformBuffersMapped.size() || fragmentUniformBuffersMapped[imageIndex] == nullptr) {
224 return;
225 }
226 std::memcpy(fragmentUniformBuffersMapped[imageIndex], &uniforms, sizeof(ModelFragmentUniforms));
227 }
228
229 void VKAbstractModel::setFragmentPushConstants(const ModelFragmentPushConstants &constants) { fragmentPushConstants = constants; }
230
231 bool VKAbstractModel::updatePrimaryTexture(const void *pixels, int width, int height, int pitch) {
232 if (windowPtr == nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
233 return false;
234 }
235 if (pixels == nullptr || width <= 0 || height <= 0) {
236 return false;
237 }
238
239 const uint32_t uploadWidth = static_cast<uint32_t>(width);
240 const uint32_t uploadHeight = static_cast<uint32_t>(height);
241 const uint32_t srcRowBytes = static_cast<uint32_t>(pitch > 0 ? pitch : width * 4);
242 const uint32_t tightRowBytes = uploadWidth * 4U;
243 if (srcRowBytes < tightRowBytes) {
244 return false;
245 }
246
247 if (textures.empty()) {
248 createFallbackTexture();
249 createDescriptorSets();
250 }
251
252 TextureEntry &texture = textures[0];
253 if (texture.image == VK_NULL_HANDLE || texture.memory == VK_NULL_HANDLE || texture.view == VK_NULL_HANDLE) {
254 return false;
255 }
256
257 bool recreatedTexture = false;
258 if (texture.width != uploadWidth || texture.height != uploadHeight) {
259 vkDeviceWaitIdle(windowPtr->getDevice());
260
261#ifdef MXVK_CUDA
262 destroyTextureCudaInterop(texture);
263#endif
264 if (texture.view != VK_NULL_HANDLE) {
265 vkDestroyImageView(windowPtr->getDevice(), texture.view, nullptr);
266 }
267 if (texture.image != VK_NULL_HANDLE) {
268 vkDestroyImage(windowPtr->getDevice(), texture.image, nullptr);
269 }
270 if (texture.memory != VK_NULL_HANDLE) {
271 vkFreeMemory(windowPtr->getDevice(), texture.memory, nullptr);
272 }
273
274 texture.view = VK_NULL_HANDLE;
275 texture.image = VK_NULL_HANDLE;
276 texture.memory = VK_NULL_HANDLE;
277
278 createTextureImage(uploadWidth, uploadHeight, texture);
279
280 texture.view = createImageView(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
281 texture.width = uploadWidth;
282 texture.height = uploadHeight;
283 recreatedTexture = true;
284
285 createDescriptorSets();
286 }
287
288#ifdef MXVK_CUDA
289 if (updatePrimaryTextureCudaHost(texture, pixels, uploadWidth, uploadHeight, srcRowBytes)) {
290 return true;
291 }
292#endif
293
294 const VkDeviceSize stagingSize = static_cast<VkDeviceSize>(tightRowBytes) * uploadHeight;
295 VkBuffer stagingBuffer = VK_NULL_HANDLE;
296 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
297 createBuffer(stagingSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
298
299 void *mapped = nullptr;
300 const VkResult mapResult = vkMapMemory(windowPtr->getDevice(), stagingMemory, 0, stagingSize, 0, &mapped);
301 if (mapResult != VK_SUCCESS || mapped == nullptr) {
302 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer, nullptr);
303 vkFreeMemory(windowPtr->getDevice(), stagingMemory, nullptr);
304 return false;
305 }
306
307 if (srcRowBytes == tightRowBytes) {
308 std::memcpy(mapped, pixels, static_cast<size_t>(stagingSize));
309 } else {
310 const auto *src = static_cast<const uint8_t *>(pixels);
311 auto *dst = static_cast<uint8_t *>(mapped);
312 for (uint32_t y = 0; y < uploadHeight; ++y) {
313 const size_t srcOffset = static_cast<size_t>(y) * srcRowBytes;
314 const size_t dstOffset = static_cast<size_t>(y) * tightRowBytes;
315 std::memcpy(dst + dstOffset, src + srcOffset, tightRowBytes);
316 }
317 }
318 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
319
320 if (recreatedTexture) {
321 transitionImageLayout(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
322 } else {
323 transitionImageLayout(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
324 }
325 copyBufferToImage(stagingBuffer, texture.image, uploadWidth, uploadHeight);
326 transitionImageLayout(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
327#ifdef MXVK_CUDA
328 texture.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
329#endif
330
331 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer, nullptr);
332 vkFreeMemory(windowPtr->getDevice(), stagingMemory, nullptr);
333 return true;
334 }
335
336 void VKAbstractModel::render(VkCommandBuffer cmd, uint32_t imageIndex, bool wireframe) const {
337 if (cmd == VK_NULL_HANDLE || imageIndex >= uniformBuffers.size() || descriptorSets.empty()) {
338 return;
339 }
340
341 const VkPipeline pipeline = (wireframe && pipelineWireframe != VK_NULL_HANDLE) ? pipelineWireframe : pipelineFill;
342 if (pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
343 return;
344 }
345
346 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
347 if (extendedFragmentUniformsEnabled) {
348 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ModelFragmentPushConstants), &fragmentPushConstants);
349 }
350
351 const size_t textureCount = std::max<size_t>(1, textures.size());
352 for (size_t i = 0; i < obj.subMeshCount(); ++i) {
353 const SubMesh &submesh = obj.subMesh(i);
354 const size_t textureIndex = std::min<size_t>(submesh.textureIndex, textureCount - 1U);
355 const size_t setIndex = static_cast<size_t>(imageIndex) * textureCount + textureIndex;
356 if (setIndex >= descriptorSets.size()) {
357 continue;
358 }
359
360 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[setIndex], 0, nullptr);
361
362 obj.drawSubMesh(cmd, i);
363 }
364 }
365
366 void VKAbstractModel::renderWithPushConstants(VkCommandBuffer cmd, uint32_t imageIndex, size_t textureIndex, const UniformBufferObject &ubo, bool wireframe) {
367 if (cmd == VK_NULL_HANDLE || imageIndex >= uniformBuffers.size() || descriptorSets.empty()) {
368 return;
369 }
370
371 const VkPipeline pipeline = (wireframe && pipelineWireframe != VK_NULL_HANDLE) ? pipelineWireframe : pipelineFill;
372 if (pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
373 return;
374 }
375
376 const size_t textureCount = std::max<size_t>(1, textures.size());
377 textureIndex = std::min(textureIndex, textureCount - 1U);
378 const size_t setIndex = static_cast<size_t>(imageIndex) * textureCount + textureIndex;
379 if (setIndex >= descriptorSets.size()) {
380 return;
381 }
382 updateTextureDescriptor(descriptorSets[setIndex], textures[textureIndex].view);
383
384 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
385 updateUBO(imageIndex, ubo);
386 if (!extendedFragmentUniformsEnabled) {
387 const ModelPushConstants pushConstants{
388 .model = ubo.model,
389 .fx = ubo.fx,
390 };
391 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(ModelPushConstants), &pushConstants);
392 }
393 if (extendedFragmentUniformsEnabled) {
394 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ModelFragmentPushConstants), &fragmentPushConstants);
395 }
396 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[setIndex], 0, nullptr);
397
398 obj.draw(cmd);
399 }
400
401 void VKAbstractModel::renderWithExternalTexture(VkCommandBuffer cmd, uint32_t imageIndex, VkImageView textureView, const UniformBufferObject &ubo, bool wireframe) {
402 if (cmd == VK_NULL_HANDLE || textureView == VK_NULL_HANDLE || imageIndex >= uniformBuffers.size() || descriptorSets.empty()) {
403 return;
404 }
405
406 const VkPipeline pipeline = (wireframe && pipelineWireframe != VK_NULL_HANDLE) ? pipelineWireframe : pipelineFill;
407 if (pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
408 return;
409 }
410
411 const size_t textureCount = std::max<size_t>(1, textures.size());
412 const size_t setIndex = static_cast<size_t>(imageIndex) * textureCount;
413 if (setIndex >= descriptorSets.size()) {
414 return;
415 }
416 updateTextureDescriptor(descriptorSets[setIndex], textureView);
417
418 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
419 updateUBO(imageIndex, ubo);
420 if (!extendedFragmentUniformsEnabled) {
421 const ModelPushConstants pushConstants{
422 .model = ubo.model,
423 .fx = ubo.fx,
424 };
425 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(ModelPushConstants), &pushConstants);
426 }
427 if (extendedFragmentUniformsEnabled) {
428 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ModelFragmentPushConstants), &fragmentPushConstants);
429 }
430 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[setIndex], 0, nullptr);
431 obj.draw(cmd);
432 }
433
434 void VKAbstractModel::resize(VK_Window *targetWindow) {
435 if (targetWindow == nullptr || targetWindow->getDevice() == VK_NULL_HANDLE) {
436 return;
437 }
438 if (!isLoaded()) {
439 return;
440 }
441
442 logVKAbstractModelStep("resize begin", true);
443 windowPtr = targetWindow;
444 destroyPipelines();
445 destroyDescriptors();
446
447 createDescriptorSetLayout();
448 createUniformBuffers();
449 createDescriptorPool();
450 createDescriptorSets();
451 createPipelines();
452 logVKAbstractModelStep("resize complete", true);
453 }
454
456 if (targetWindow == nullptr || targetWindow->getDevice() == VK_NULL_HANDLE) {
457 return;
458 }
459
460 logVKAbstractModelStep("teardown begin", true);
461 windowPtr = targetWindow;
462 destroyPipelines();
463 destroyDescriptors();
464 destroyTextures();
465 obj.cleanup(windowPtr->getDevice());
466 windowPtr = nullptr;
467 logVKAbstractModelStep("teardown complete", true);
468 }
469
470 void VKAbstractModel::computeBoundsAndScale() {
471 const auto &vertices = obj.vertices();
472 if (vertices.empty()) {
473 modelCenterOffsetValue = glm::vec3(0.0f);
474 modelRenderScaleValue = 1.0f;
475 modelAxisExtentValue = glm::vec3(1.0f);
476 return;
477 }
478
479 float minX = vertices.front().pos[0];
480 float maxX = minX;
481 float minY = vertices.front().pos[1];
482 float maxY = minY;
483 float minZ = vertices.front().pos[2];
484 float maxZ = minZ;
485
486 for (const VKVertex &v : vertices) {
487 minX = std::min(minX, v.pos[0]);
488 maxX = std::max(maxX, v.pos[0]);
489 minY = std::min(minY, v.pos[1]);
490 maxY = std::max(maxY, v.pos[1]);
491 minZ = std::min(minZ, v.pos[2]);
492 maxZ = std::max(maxZ, v.pos[2]);
493 }
494
495 modelCenterOffsetValue = glm::vec3(-0.5f * (minX + maxX), -0.5f * (minY + maxY), -0.5f * (minZ + maxZ));
496
497 modelAxisExtentValue = glm::vec3(maxX - minX, maxY - minY, maxZ - minZ);
498 const float maxExtent = std::max(modelAxisExtentValue.x, std::max(modelAxisExtentValue.y, modelAxisExtentValue.z));
499 modelRenderScaleValue = (maxExtent > 1e-6f) ? (2.5f / maxExtent) : 1.0f;
500 }
501
502 void VKAbstractModel::loadTextures(const std::string &textureManifestPath, const std::string &textureBasePath) {
503 std::ifstream file(textureManifestPath);
504 if (!file.is_open()) {
505 throw mxvk::Exception("Failed to open texture manifest: " + textureManifestPath);
506 }
507
508 std::vector<std::string> lines{};
509 std::string line;
510 while (std::getline(file, line)) {
511 const size_t begin = line.find_first_not_of(" \t\r\n");
512 if (begin == std::string::npos) {
513 continue;
514 }
515 const size_t end = line.find_last_not_of(" \t\r\n");
516 line = line.substr(begin, end - begin + 1);
517 if (line.empty() || line[0] == '#') {
518 continue;
519 }
520 lines.push_back(line);
521 }
522
523 bool isStructured = false;
524 bool isMtlLike = false;
525 for (const std::string &ln : lines) {
526 std::istringstream stream(ln);
527 std::string keyword;
528 stream >> keyword;
529 if (keyword == "submesh" || keyword == "texture_dir" || keyword == "material_lib" || keyword == "model") {
530 isStructured = true;
531 break;
532 }
533 if (keyword == "newmtl") {
534 isMtlLike = true;
535 break;
536 }
537 }
538
539 std::vector<std::string> imagePaths{};
540 const std::string prefix = textureBasePath;
541
542 if (isMtlLike) {
543 int currentMaterialTexture = -1;
544 for (const std::string &ln : lines) {
545 std::istringstream stream(ln);
546 std::string keyword;
547 stream >> keyword;
548 if (keyword == "newmtl") {
549 imagePaths.emplace_back();
550 currentMaterialTexture = static_cast<int>(imagePaths.size()) - 1;
551 } else if (keyword == "map_Kd") {
552 const std::string image = parseMTLTexturePath(stream);
553 if (!image.empty() && currentMaterialTexture >= 0) {
554 imagePaths[static_cast<size_t>(currentMaterialTexture)] = resolveTexturePath(prefix, image);
555 }
556 }
557 }
558 } else if (isStructured) {
559 for (const std::string &ln : lines) {
560 std::istringstream stream(ln);
561 std::string keyword;
562 stream >> keyword;
563 if (keyword == "texture") {
564 std::string image;
565 if (stream >> image) {
566 imagePaths.push_back(resolveTexturePath(prefix, image));
567 }
568 }
569 }
570 } else {
571 for (const std::string &ln : lines) {
572 imagePaths.push_back(resolveTexturePath(prefix, ln));
573 }
574 }
575
576 if (imagePaths.empty()) {
577 logVKAbstractModelStep("no texture candidates found in manifest [" + textureManifestPath + "]");
578 }
579
580 for (const std::string &path : imagePaths) {
581 if (path.empty()) {
582 logVKAbstractModelStep("material has no texture map; using fallback texture slot");
583 createFallbackTexture();
584 continue;
585 }
586
587 logVKAbstractModelStep("trying texture [" + path + "]");
588 SDL_Surface *surface = mxvk::LoadPNG(path.c_str());
589 if (surface == nullptr) {
590 logVKAbstractModelStep("texture not found [" + path + "]");
591 createFallbackTexture();
592 continue;
593 }
594
595 const uint32_t width = static_cast<uint32_t>(surface->w);
596 const uint32_t height = static_cast<uint32_t>(surface->h);
597
598 TextureEntry tex{};
599 tex.width = width;
600 tex.height = height;
601 createTextureImage(width, height, tex);
602
603#ifdef MXVK_CUDA
604 if (updatePrimaryTextureCudaHost(tex, surface->pixels, width, height, static_cast<uint32_t>(surface->pitch))) {
605 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
606 textures.push_back(tex);
607 SDL_DestroySurface(surface);
608 continue;
609 }
610#endif
611
612 const VkDeviceSize imageSize = static_cast<VkDeviceSize>(width) * static_cast<VkDeviceSize>(height) * 4U;
613 VkBuffer stagingBuffer = VK_NULL_HANDLE;
614 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
615 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
616
617 void *mapped = nullptr;
618 vkMapMemory(windowPtr->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
619 std::memcpy(mapped, surface->pixels, static_cast<size_t>(imageSize));
620 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
621
622#ifdef MXVK_CUDA
623 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED;
624#else
625 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
626#endif
627 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, uploadOldLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
628 copyBufferToImage(stagingBuffer, tex.image, width, height);
629 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
630#ifdef MXVK_CUDA
631 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
632#endif
633
634 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
635 textures.push_back(tex);
636
637 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer, nullptr);
638 vkFreeMemory(windowPtr->getDevice(), stagingMemory, nullptr);
639 SDL_DestroySurface(surface);
640 }
641 }
642
643 void VKAbstractModel::loadTexturesFromMTL(const std::string &textureBasePath) {
644 bool foundTextureReference = false;
645 for (const MXMaterial &material : obj.materials()) {
646 if (material.map_kd.empty()) {
647 createFallbackTexture();
648 continue;
649 }
650
651 foundTextureReference = true;
652 const std::string path = resolveTexturePath(textureBasePath, material.map_kd);
653 logVKAbstractModelStep("trying texture [" + path + "]");
654 SDL_Surface *surface = mxvk::LoadPNG(path.c_str());
655 if (surface == nullptr) {
656 logVKAbstractModelStep("texture not found [" + path + "]");
657 createFallbackTexture();
658 continue;
659 }
660
661 const uint32_t width = static_cast<uint32_t>(surface->w);
662 const uint32_t height = static_cast<uint32_t>(surface->h);
663
664 TextureEntry tex{};
665 tex.width = width;
666 tex.height = height;
667 createTextureImage(width, height, tex);
668
669#ifdef MXVK_CUDA
670 if (updatePrimaryTextureCudaHost(tex, surface->pixels, width, height, static_cast<uint32_t>(surface->pitch))) {
671 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
672 textures.push_back(tex);
673 SDL_DestroySurface(surface);
674 continue;
675 }
676#endif
677
678 const VkDeviceSize imageSize = static_cast<VkDeviceSize>(width) * static_cast<VkDeviceSize>(height) * 4U;
679 VkBuffer stagingBuffer = VK_NULL_HANDLE;
680 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
681 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
682
683 void *mapped = nullptr;
684 vkMapMemory(windowPtr->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
685 std::memcpy(mapped, surface->pixels, static_cast<size_t>(imageSize));
686 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
687
688#ifdef MXVK_CUDA
689 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED;
690#else
691 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
692#endif
693 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, uploadOldLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
694 copyBufferToImage(stagingBuffer, tex.image, width, height);
695 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
696#ifdef MXVK_CUDA
697 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
698#endif
699
700 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
701 textures.push_back(tex);
702
703 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer, nullptr);
704 vkFreeMemory(windowPtr->getDevice(), stagingMemory, nullptr);
705 SDL_DestroySurface(surface);
706 }
707
708 if (!foundTextureReference) {
709 logVKAbstractModelStep("no texture candidates found in MTL materials");
710 }
711 }
712
713 void VKAbstractModel::createFallbackTexture() {
714 SDL_Surface *surface = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA32);
715 if (surface == nullptr) {
716 throw mxvk::Exception("VKAbstractModel failed to allocate fallback texture surface");
717 }
718
719 auto *pixel = static_cast<uint32_t *>(surface->pixels);
720 *pixel = 0xFFFFFFFFu;
721
722 TextureEntry tex{};
723 tex.width = 1;
724 tex.height = 1;
725 createTextureImage(1, 1, tex);
726
727#ifdef MXVK_CUDA
728 if (updatePrimaryTextureCudaHost(tex, surface->pixels, 1, 1, static_cast<uint32_t>(surface->pitch))) {
729 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
730 textures.push_back(tex);
731 SDL_DestroySurface(surface);
732 return;
733 }
734#endif
735
736 const VkDeviceSize imageSize = 4;
737 VkBuffer stagingBuffer = VK_NULL_HANDLE;
738 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
739 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
740
741 void *mapped = nullptr;
742 vkMapMemory(windowPtr->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
743 std::memcpy(mapped, surface->pixels, static_cast<size_t>(imageSize));
744 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
745
746#ifdef MXVK_CUDA
747 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED;
748#else
749 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
750#endif
751 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, uploadOldLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
752 copyBufferToImage(stagingBuffer, tex.image, 1, 1);
753 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
754#ifdef MXVK_CUDA
755 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
756#endif
757 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
758 textures.push_back(tex);
759
760 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer, nullptr);
761 vkFreeMemory(windowPtr->getDevice(), stagingMemory, nullptr);
762 SDL_DestroySurface(surface);
763 }
764
765 void VKAbstractModel::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory) const {
766 VkBufferCreateInfo bufferInfo{};
767 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
768 bufferInfo.size = size;
769 bufferInfo.usage = usage;
770 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
771
772 if (vkCreateBuffer(windowPtr->getDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
773 throw mxvk::Exception("VKAbstractModel failed to create buffer");
774 }
775
776 VkMemoryRequirements requirements{};
777 vkGetBufferMemoryRequirements(windowPtr->getDevice(), buffer, &requirements);
778
779 VkMemoryAllocateInfo allocInfo{};
780 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
781 allocInfo.allocationSize = requirements.size;
782
783 try {
784 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
785 if (vkAllocateMemory(windowPtr->getDevice(), &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) {
786 throw mxvk::Exception("VKAbstractModel failed to allocate buffer memory");
787 }
788
789 if (vkBindBufferMemory(windowPtr->getDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) {
790 throw mxvk::Exception("VKAbstractModel failed to bind buffer memory");
791 }
792 } catch (...) {
793 if (bufferMemory != VK_NULL_HANDLE) {
794 vkFreeMemory(windowPtr->getDevice(), bufferMemory, nullptr);
795 bufferMemory = VK_NULL_HANDLE;
796 }
797 if (buffer != VK_NULL_HANDLE) {
798 vkDestroyBuffer(windowPtr->getDevice(), buffer, nullptr);
799 buffer = VK_NULL_HANDLE;
800 }
801 throw;
802 }
803 }
804
805 uint32_t VKAbstractModel::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const {
806 VkPhysicalDeviceMemoryProperties memProperties{};
807 vkGetPhysicalDeviceMemoryProperties(windowPtr->getPhysicalDevice(), &memProperties);
808
809 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
810 const bool typeSupported = (typeFilter & (1u << i)) != 0u;
811 const bool propsSupported = (memProperties.memoryTypes[i].propertyFlags & properties) == properties;
812 if (typeSupported && propsSupported) {
813 return i;
814 }
815 }
816
817 throw mxvk::Exception("VKAbstractModel failed to find suitable memory type");
818 }
819
820 VkCommandBuffer VKAbstractModel::beginSingleTimeCommands() const {
821 VkCommandBufferAllocateInfo allocInfo{};
822 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
823 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
824 allocInfo.commandPool = windowPtr->getCommandPool();
825 allocInfo.commandBufferCount = 1;
826
827 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
828 if (vkAllocateCommandBuffers(windowPtr->getDevice(), &allocInfo, &commandBuffer) != VK_SUCCESS) {
829 throw mxvk::Exception("VKAbstractModel failed to allocate command buffer");
830 }
831
832 VkCommandBufferBeginInfo beginInfo{};
833 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
834 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
835 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
836 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
837 throw mxvk::Exception("VKAbstractModel failed to begin command buffer");
838 }
839
840 return commandBuffer;
841 }
842
843 void VKAbstractModel::endSingleTimeCommands(VkCommandBuffer commandBuffer) const {
844 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
845 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
846 throw mxvk::Exception("VKAbstractModel failed to end command buffer");
847 }
848
849 VkSubmitInfo submitInfo{};
850 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
851 submitInfo.commandBufferCount = 1;
852 submitInfo.pCommandBuffers = &commandBuffer;
853
854 if (vkQueueSubmit(windowPtr->getGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
855 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
856 throw mxvk::Exception("VKAbstractModel failed to submit command buffer");
857 }
858 if (vkQueueWaitIdle(windowPtr->getGraphicsQueue()) != VK_SUCCESS) {
859 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
860 throw mxvk::Exception("VKAbstractModel failed to wait for queue idle");
861 }
862
863 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
864 }
865
866 void VKAbstractModel::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &memory) const {
867 VkImageCreateInfo imageInfo{};
868 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
869 imageInfo.imageType = VK_IMAGE_TYPE_2D;
870 imageInfo.extent.width = width;
871 imageInfo.extent.height = height;
872 imageInfo.extent.depth = 1;
873 imageInfo.mipLevels = 1;
874 imageInfo.arrayLayers = 1;
875 imageInfo.format = format;
876 imageInfo.tiling = tiling;
877 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
878 imageInfo.usage = usage;
879 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
880 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
881
882 if (vkCreateImage(windowPtr->getDevice(), &imageInfo, nullptr, &image) != VK_SUCCESS) {
883 throw mxvk::Exception("VKAbstractModel failed to create image");
884 }
885
886 VkMemoryRequirements requirements{};
887 vkGetImageMemoryRequirements(windowPtr->getDevice(), image, &requirements);
888
889 VkMemoryAllocateInfo allocInfo{};
890 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
891 allocInfo.allocationSize = requirements.size;
892
893 try {
894 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
895 if (vkAllocateMemory(windowPtr->getDevice(), &allocInfo, nullptr, &memory) != VK_SUCCESS) {
896 throw mxvk::Exception("VKAbstractModel failed to allocate image memory");
897 }
898
899 if (vkBindImageMemory(windowPtr->getDevice(), image, memory, 0) != VK_SUCCESS) {
900 throw mxvk::Exception("VKAbstractModel failed to bind image memory");
901 }
902 } catch (...) {
903 if (memory != VK_NULL_HANDLE) {
904 vkFreeMemory(windowPtr->getDevice(), memory, nullptr);
905 memory = VK_NULL_HANDLE;
906 }
907 if (image != VK_NULL_HANDLE) {
908 vkDestroyImage(windowPtr->getDevice(), image, nullptr);
909 image = VK_NULL_HANDLE;
910 }
911 throw;
912 }
913 }
914
915 void VKAbstractModel::createTextureImage(uint32_t width, uint32_t height, TextureEntry &texture) const {
916#ifdef MXVK_CUDA
917 try {
918 createCudaExportableImage(width, height, texture);
919 return;
920 } catch (const std::exception &ex) {
921 logVKAbstractModelStep(std::format("CUDA exportable model texture unavailable: {}; using standard Vulkan texture", ex.what()));
922 texture.cudaExportMemorySize = 0;
923 texture.cudaInteropEnabled = false;
924 texture.cudaInteropUnavailableLogged = true;
925 }
926#endif
927
928 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, texture.image, texture.memory);
929 texture.width = width;
930 texture.height = height;
931#ifdef MXVK_CUDA
932 texture.cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
933#endif
934 }
935
936#ifdef MXVK_CUDA
937 void VKAbstractModel::destroyTextureCudaInterop(TextureEntry &texture) const {
938 if (texture.cudaInteropEnabled || texture.cudaExternalMemory != nullptr || texture.cudaMipmappedArray != nullptr) {
939 logVKAbstractModelStep("CUDA interop: destroying imported model texture resources");
940 }
941 if (texture.cudaMipmappedArray != nullptr) {
942 cudaFreeMipmappedArray(texture.cudaMipmappedArray);
943 texture.cudaMipmappedArray = nullptr;
944 texture.cudaArray = nullptr;
945 }
946 if (texture.cudaExternalMemory != nullptr) {
947 cudaDestroyExternalMemory(texture.cudaExternalMemory);
948 texture.cudaExternalMemory = nullptr;
949 }
950 texture.cudaInteropEnabled = false;
951 texture.cudaExportMemorySize = 0;
952 texture.cudaUploadLogged = false;
953 texture.cudaWriteTransitionLogged = false;
954 texture.cudaShaderTransitionLogged = false;
955 texture.cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
956 }
957
958 void VKAbstractModel::createCudaExportableImage(uint32_t width, uint32_t height, TextureEntry &texture) const {
959 logVKAbstractModelStep(std::format("CUDA interop init: requesting exportable model texture {}x{} RGBA8 optimal-tiled OPAQUE_FD", width, height));
960
961 VkExternalMemoryImageCreateInfo externalImageInfo{};
962 externalImageInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
963 externalImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
964
965 VkImageCreateInfo imageInfo{};
966 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
967 imageInfo.pNext = &externalImageInfo;
968 imageInfo.imageType = VK_IMAGE_TYPE_2D;
969 imageInfo.extent.width = width;
970 imageInfo.extent.height = height;
971 imageInfo.extent.depth = 1;
972 imageInfo.mipLevels = 1;
973 imageInfo.arrayLayers = 1;
974 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
975 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
976 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
977 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
978 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
979 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
980
981 if (vkCreateImage(windowPtr->getDevice(), &imageInfo, nullptr, &texture.image) != VK_SUCCESS) {
982 throw mxvk::Exception("VKAbstractModel failed to create CUDA exportable texture image");
983 }
984
985 VkMemoryRequirements requirements{};
986 vkGetImageMemoryRequirements(windowPtr->getDevice(), texture.image, &requirements);
987
988 VkExportMemoryAllocateInfo exportMemoryInfo{};
989 exportMemoryInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
990 exportMemoryInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
991
992 VkMemoryAllocateInfo allocInfo{};
993 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
994 allocInfo.pNext = &exportMemoryInfo;
995 allocInfo.allocationSize = requirements.size;
996
997 try {
998 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
999 if (vkAllocateMemory(windowPtr->getDevice(), &allocInfo, nullptr, &texture.memory) != VK_SUCCESS) {
1000 throw mxvk::Exception("VKAbstractModel failed to allocate CUDA exportable texture memory");
1001 }
1002 if (vkBindImageMemory(windowPtr->getDevice(), texture.image, texture.memory, 0) != VK_SUCCESS) {
1003 throw mxvk::Exception("VKAbstractModel failed to bind CUDA exportable texture memory");
1004 }
1005 } catch (...) {
1006 if (texture.memory != VK_NULL_HANDLE) {
1007 vkFreeMemory(windowPtr->getDevice(), texture.memory, nullptr);
1008 texture.memory = VK_NULL_HANDLE;
1009 }
1010 if (texture.image != VK_NULL_HANDLE) {
1011 vkDestroyImage(windowPtr->getDevice(), texture.image, nullptr);
1012 texture.image = VK_NULL_HANDLE;
1013 }
1014 texture.cudaExportMemorySize = 0;
1015 throw;
1016 }
1017
1018 texture.width = width;
1019 texture.height = height;
1020 texture.cudaExportMemorySize = requirements.size;
1021 texture.cudaInteropUnavailableLogged = false;
1022 texture.cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1023 logVKAbstractModelStep(std::format("CUDA interop init: exportable model texture allocated (memorySize={} bytes, memoryType={}); optimal image memory is imported as cudaArray, not wrapped as pitched GpuMat", static_cast<unsigned long long>(requirements.size), allocInfo.memoryTypeIndex));
1024 }
1025
1026 bool VKAbstractModel::ensureTextureCudaInterop(TextureEntry &texture) const {
1027 if (texture.cudaInteropEnabled) {
1028 return true;
1029 }
1030 if (windowPtr == nullptr || texture.memory == VK_NULL_HANDLE || texture.cudaExportMemorySize == 0) {
1031 if (!texture.cudaInteropUnavailableLogged) {
1032 logVKAbstractModelStep("CUDA interop init: model texture is not exportable; CPU staging fallback remains active");
1033 texture.cudaInteropUnavailableLogged = true;
1034 }
1035 return false;
1036 }
1037 if (vkGetMemoryFdKHR == nullptr) {
1038 if (!texture.cudaInteropUnavailableLogged) {
1039 logVKAbstractModelStep("CUDA interop init: vkGetMemoryFdKHR was not loaded for model texture");
1040 texture.cudaInteropUnavailableLogged = true;
1041 }
1042 return false;
1043 }
1044
1045 VkMemoryGetFdInfoKHR fdInfo{};
1046 fdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
1047 fdInfo.memory = texture.memory;
1048 fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
1049
1050 int memoryFd = -1;
1051 const VkResult fdResult = vkGetMemoryFdKHR(windowPtr->getDevice(), &fdInfo, &memoryFd);
1052 if (fdResult != VK_SUCCESS) {
1053 if (!texture.cudaInteropUnavailableLogged) {
1054 logVKAbstractModelStep(std::format("CUDA interop init: vkGetMemoryFdKHR failed for model texture ({})", static_cast<int>(fdResult)));
1055 texture.cudaInteropUnavailableLogged = true;
1056 }
1057 return false;
1058 }
1059 logVKAbstractModelStep(std::format("CUDA interop init: exported model texture memory fd={}", memoryFd));
1060
1061 cudaExternalMemoryHandleDesc externalMemoryDesc{};
1062 externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd;
1063 externalMemoryDesc.handle.fd = memoryFd;
1064 externalMemoryDesc.size = texture.cudaExportMemorySize;
1065
1066 cudaError_t cudaResult = cudaImportExternalMemory(&texture.cudaExternalMemory, &externalMemoryDesc);
1067 if (cudaResult != cudaSuccess) {
1068 close(memoryFd);
1069 if (!texture.cudaInteropUnavailableLogged) {
1070 logVKAbstractModelStep(std::format("CUDA interop init: cudaImportExternalMemory failed for model texture: {}", cudaGetErrorString(cudaResult)));
1071 texture.cudaInteropUnavailableLogged = true;
1072 }
1073 texture.cudaExternalMemory = nullptr;
1074 return false;
1075 }
1076 logVKAbstractModelStep(std::format("CUDA interop init: imported model texture external memory into CUDA ({} bytes)", static_cast<unsigned long long>(texture.cudaExportMemorySize)));
1077
1078 cudaExternalMemoryMipmappedArrayDesc arrayDesc{};
1079 arrayDesc.offset = 0;
1080 arrayDesc.formatDesc = cudaCreateChannelDesc<uchar4>();
1081 arrayDesc.extent = make_cudaExtent(static_cast<size_t>(texture.width), static_cast<size_t>(texture.height), 0);
1082 arrayDesc.flags = cudaArrayColorAttachment;
1083 arrayDesc.numLevels = 1;
1084
1085 cudaResult = cudaExternalMemoryGetMappedMipmappedArray(&texture.cudaMipmappedArray, texture.cudaExternalMemory, &arrayDesc);
1086 if (cudaResult != cudaSuccess) {
1087 if (!texture.cudaInteropUnavailableLogged) {
1088 logVKAbstractModelStep(std::format("CUDA interop init: cudaExternalMemoryGetMappedMipmappedArray failed for model texture: {}", cudaGetErrorString(cudaResult)));
1089 texture.cudaInteropUnavailableLogged = true;
1090 }
1091 destroyTextureCudaInterop(texture);
1092 return false;
1093 }
1094 logVKAbstractModelStep(std::format("CUDA interop init: mapped model texture CUDA mipmapped array {}x{} uchar4", texture.width, texture.height));
1095
1096 cudaResult = cudaGetMipmappedArrayLevel(&texture.cudaArray, texture.cudaMipmappedArray, 0);
1097 if (cudaResult != cudaSuccess) {
1098 if (!texture.cudaInteropUnavailableLogged) {
1099 logVKAbstractModelStep(std::format("CUDA interop init: cudaGetMipmappedArrayLevel failed for model texture: {}", cudaGetErrorString(cudaResult)));
1100 texture.cudaInteropUnavailableLogged = true;
1101 }
1102 destroyTextureCudaInterop(texture);
1103 return false;
1104 }
1105
1106 texture.cudaInteropEnabled = true;
1107 logVKAbstractModelStep("CUDA interop init: direct CUDA-to-model-texture upload is ready");
1108 return true;
1109 }
1110
1111 bool VKAbstractModel::transitionTextureForCudaWrite(TextureEntry &texture) const {
1112 if (texture.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) {
1113 return true;
1114 }
1115
1116 const VkImageLayout oldLayout = (texture.cudaImageLayout == VK_IMAGE_LAYOUT_UNDEFINED) ? VK_IMAGE_LAYOUT_UNDEFINED : texture.cudaImageLayout;
1117 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1118
1119 VkImageMemoryBarrier barrier{};
1120 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1121 barrier.oldLayout = oldLayout;
1122 barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
1123 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1124 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1125 barrier.image = texture.image;
1126 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1127 barrier.subresourceRange.baseMipLevel = 0;
1128 barrier.subresourceRange.levelCount = 1;
1129 barrier.subresourceRange.baseArrayLayer = 0;
1130 barrier.subresourceRange.layerCount = 1;
1131 barrier.srcAccessMask = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) ? VK_ACCESS_SHADER_READ_BIT : 0;
1132 barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1133
1134 const VkPipelineStageFlags srcStage = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) ? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1135 vkCmdPipelineBarrier(commandBuffer, srcStage, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
1136 endSingleTimeCommands(commandBuffer);
1137
1138 texture.cudaImageLayout = VK_IMAGE_LAYOUT_GENERAL;
1139 if (!texture.cudaWriteTransitionLogged) {
1140 logVKAbstractModelStep("CUDA interop sync: model texture transitions to GENERAL before CUDA writes");
1141 texture.cudaWriteTransitionLogged = true;
1142 }
1143 return true;
1144 }
1145
1146 bool VKAbstractModel::transitionTextureForShaderRead(TextureEntry &texture) const {
1147 if (texture.cudaImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1148 return true;
1149 }
1150
1151 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1152 VkImageMemoryBarrier barrier{};
1153 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1154 barrier.oldLayout = texture.cudaImageLayout;
1155 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1156 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1157 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1158 barrier.image = texture.image;
1159 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1160 barrier.subresourceRange.baseMipLevel = 0;
1161 barrier.subresourceRange.levelCount = 1;
1162 barrier.subresourceRange.baseArrayLayer = 0;
1163 barrier.subresourceRange.layerCount = 1;
1164 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1165 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1166
1167 vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
1168 endSingleTimeCommands(commandBuffer);
1169
1170 texture.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1171 if (!texture.cudaShaderTransitionLogged) {
1172 logVKAbstractModelStep("CUDA interop sync: model texture transitions GENERAL -> SHADER_READ_ONLY before sampling");
1173 texture.cudaShaderTransitionLogged = true;
1174 }
1175 return true;
1176 }
1177
1178 void VKAbstractModel::recreatePrimaryTextureForCuda(TextureEntry &texture, uint32_t width, uint32_t height) {
1179 vkDeviceWaitIdle(windowPtr->getDevice());
1180 destroyTextureCudaInterop(texture);
1181 if (texture.view != VK_NULL_HANDLE) {
1182 vkDestroyImageView(windowPtr->getDevice(), texture.view, nullptr);
1183 texture.view = VK_NULL_HANDLE;
1184 }
1185 if (texture.image != VK_NULL_HANDLE) {
1186 vkDestroyImage(windowPtr->getDevice(), texture.image, nullptr);
1187 texture.image = VK_NULL_HANDLE;
1188 }
1189 if (texture.memory != VK_NULL_HANDLE) {
1190 vkFreeMemory(windowPtr->getDevice(), texture.memory, nullptr);
1191 texture.memory = VK_NULL_HANDLE;
1192 }
1193
1194 createCudaExportableImage(width, height, texture);
1195 texture.view = createImageView(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1196
1197 createDescriptorSets();
1198 }
1199
1200 bool VKAbstractModel::updatePrimaryTextureCudaHost(TextureEntry &texture, const void *pixels, uint32_t width, uint32_t height, uint32_t pitch) const {
1201 if (pixels == nullptr || width == 0 || height == 0) {
1202 return false;
1203 }
1204 const uint32_t rowBytes = width * 4U;
1205 if (pitch < rowBytes || texture.width != width || texture.height != height) {
1206 return false;
1207 }
1208 if (!ensureTextureCudaInterop(texture) || !transitionTextureForCudaWrite(texture)) {
1209 return false;
1210 }
1211
1212 if (!texture.cudaUploadLogged) {
1213 logVKAbstractModelStep(std::format("CUDA interop upload: copying {}x{} host RGBA pixels to optimal-tiled Vulkan model texture via cudaArray (source pitch={} bytes)", width, height, pitch));
1214 texture.cudaUploadLogged = true;
1215 }
1216
1217 const cudaError_t cudaResult = cudaMemcpy2DToArray(texture.cudaArray, 0, 0, pixels, pitch, static_cast<size_t>(rowBytes), static_cast<size_t>(height), cudaMemcpyHostToDevice);
1218 if (cudaResult != cudaSuccess) {
1219 logVKAbstractModelStep(std::format("CUDA interop model host texture copy failed: {}", cudaGetErrorString(cudaResult)));
1220 return false;
1221 }
1222
1223 return transitionTextureForShaderRead(texture);
1224 }
1225
1226 bool VKAbstractModel::updatePrimaryTextureCuda(const cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream) {
1227 if (windowPtr == nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1228 return false;
1229 }
1230 if (rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols <= 0 || rgba.rows <= 0) {
1231 return false;
1232 }
1233
1234 if (textures.empty()) {
1235 textures.push_back(TextureEntry{});
1236 }
1237
1238 TextureEntry &texture = textures[0];
1239 const uint32_t uploadWidth = static_cast<uint32_t>(rgba.cols);
1240 const uint32_t uploadHeight = static_cast<uint32_t>(rgba.rows);
1241 if (texture.image == VK_NULL_HANDLE || texture.memory == VK_NULL_HANDLE || texture.view == VK_NULL_HANDLE || texture.width != uploadWidth || texture.height != uploadHeight || texture.cudaExportMemorySize == 0) {
1242 try {
1243 recreatePrimaryTextureForCuda(texture, uploadWidth, uploadHeight);
1244 } catch (const std::exception &ex) {
1245 if (!texture.cudaInteropUnavailableLogged) {
1246 logVKAbstractModelStep(std::format("CUDA exportable model texture unavailable: {}; CPU staging fallback remains active", ex.what()));
1247 texture.cudaInteropUnavailableLogged = true;
1248 }
1249 return false;
1250 }
1251 }
1252
1253 if (!ensureTextureCudaInterop(texture) || !transitionTextureForCudaWrite(texture)) {
1254 return false;
1255 }
1256
1257 cudaStream_t cudaStream = cuda_stream_handle(stream);
1258 if (!texture.cudaUploadLogged) {
1259 logVKAbstractModelStep(std::format("CUDA interop upload: copying {}x{} RGBA GpuMat to optimal-tiled Vulkan model texture via cudaArray (source pitch={} bytes, copy row bytes={})", rgba.cols, rgba.rows, static_cast<unsigned long long>(rgba.step), static_cast<unsigned long long>(static_cast<size_t>(rgba.cols) * 4U)));
1260 texture.cudaUploadLogged = true;
1261 }
1262
1263 cudaError_t cudaResult = cudaMemcpy2DToArrayAsync(texture.cudaArray, 0, 0, rgba.ptr(), rgba.step, static_cast<size_t>(rgba.cols) * 4U, static_cast<size_t>(rgba.rows), cudaMemcpyDeviceToDevice, cudaStream);
1264 if (cudaResult != cudaSuccess) {
1265 logVKAbstractModelStep(std::format("CUDA interop model texture copy failed: {}", cudaGetErrorString(cudaResult)));
1266 return false;
1267 }
1268
1269 cudaResult = cudaStreamSynchronize(cudaStream);
1270 if (cudaResult != cudaSuccess) {
1271 logVKAbstractModelStep(std::format("CUDA interop model texture sync failed: {}", cudaGetErrorString(cudaResult)));
1272 return false;
1273 }
1274
1275 return transitionTextureForShaderRead(texture);
1276 }
1277#endif
1278
1279 VkImageView VKAbstractModel::createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags) const {
1280 VkImageViewCreateInfo viewInfo{};
1281 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1282 viewInfo.image = image;
1283 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1284 viewInfo.format = format;
1285 viewInfo.subresourceRange.aspectMask = aspectFlags;
1286 viewInfo.subresourceRange.baseMipLevel = 0;
1287 viewInfo.subresourceRange.levelCount = 1;
1288 viewInfo.subresourceRange.baseArrayLayer = 0;
1289 viewInfo.subresourceRange.layerCount = 1;
1290
1291 VkImageView imageView = VK_NULL_HANDLE;
1292 if (vkCreateImageView(windowPtr->getDevice(), &viewInfo, nullptr, &imageView) != VK_SUCCESS) {
1293 throw mxvk::Exception("VKAbstractModel failed to create image view");
1294 }
1295 return imageView;
1296 }
1297
1298 void VKAbstractModel::transitionImageLayout(VkImage image, VkFormat, VkImageLayout oldLayout, VkImageLayout newLayout) const {
1299 VkCommandBuffer cmd = beginSingleTimeCommands();
1300
1301 VkImageMemoryBarrier barrier{};
1302 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1303 barrier.oldLayout = oldLayout;
1304 barrier.newLayout = newLayout;
1305 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1306 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1307 barrier.image = image;
1308 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1309 barrier.subresourceRange.baseMipLevel = 0;
1310 barrier.subresourceRange.levelCount = 1;
1311 barrier.subresourceRange.baseArrayLayer = 0;
1312 barrier.subresourceRange.layerCount = 1;
1313
1314 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1315 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1316
1317 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1318 barrier.srcAccessMask = 0;
1319 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1320 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1321 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1322 } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1323 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1324 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1325 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1326 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1327 } else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1328 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
1329 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1330 sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1331 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1332 } else if (oldLayout == VK_IMAGE_LAYOUT_GENERAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1333 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1334 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1335 sourceStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
1336 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1337 }
1338
1339 vkCmdPipelineBarrier(cmd, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
1340
1341 endSingleTimeCommands(cmd);
1342 }
1343
1344 void VKAbstractModel::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) const {
1345 VkCommandBuffer cmd = beginSingleTimeCommands();
1346
1347 VkBufferImageCopy region{};
1348 region.bufferOffset = 0;
1349 region.bufferRowLength = 0;
1350 region.bufferImageHeight = 0;
1351 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1352 region.imageSubresource.mipLevel = 0;
1353 region.imageSubresource.baseArrayLayer = 0;
1354 region.imageSubresource.layerCount = 1;
1355 region.imageOffset = {0, 0, 0};
1356 region.imageExtent = {width, height, 1};
1357
1358 vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
1359 endSingleTimeCommands(cmd);
1360 }
1361
1362 void VKAbstractModel::createTextureSampler() {
1363 if (textureSampler != VK_NULL_HANDLE) {
1364 return;
1365 }
1366
1367 VkPhysicalDeviceFeatures deviceFeatures{};
1368 vkGetPhysicalDeviceFeatures(windowPtr->getPhysicalDevice(), &deviceFeatures);
1369 VkPhysicalDeviceProperties deviceProperties{};
1370 vkGetPhysicalDeviceProperties(windowPtr->getPhysicalDevice(), &deviceProperties);
1371 const bool anisotropySupported = deviceFeatures.samplerAnisotropy == VK_TRUE;
1372 const float anisotropyLevel = anisotropySupported ? std::min(8.0f, deviceProperties.limits.maxSamplerAnisotropy) : 1.0f;
1373
1374 VkSamplerCreateInfo samplerInfo{};
1375 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1376 samplerInfo.magFilter = VK_FILTER_LINEAR;
1377 samplerInfo.minFilter = VK_FILTER_LINEAR;
1378 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1379 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1380 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1381 samplerInfo.anisotropyEnable = anisotropySupported ? VK_TRUE : VK_FALSE;
1382 samplerInfo.maxAnisotropy = anisotropyLevel;
1383 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1384 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1385 samplerInfo.compareEnable = VK_FALSE;
1386 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1387 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1388
1389 if (vkCreateSampler(windowPtr->getDevice(), &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) {
1390 throw mxvk::Exception("VKAbstractModel failed to create texture sampler");
1391 }
1392 }
1393
1394 void VKAbstractModel::createDescriptorSetLayout() {
1395 if (descriptorSetLayout != VK_NULL_HANDLE) {
1396 return;
1397 }
1398
1399 VkDescriptorSetLayoutBinding samplerBinding{};
1400 samplerBinding.binding = 0;
1401 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1402 samplerBinding.descriptorCount = 1;
1403 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
1404
1405 VkDescriptorSetLayoutBinding fragmentBinding{};
1406 fragmentBinding.binding = 1;
1407 fragmentBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1408 fragmentBinding.descriptorCount = 1;
1409 fragmentBinding.stageFlags = extendedFragmentUniformsEnabled ? VK_SHADER_STAGE_FRAGMENT_BIT : VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
1410
1411 VkDescriptorSetLayoutBinding modelBinding{};
1412 modelBinding.binding = 2;
1413 modelBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1414 modelBinding.descriptorCount = 1;
1415 modelBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1416
1417 const std::array<VkDescriptorSetLayoutBinding, 3> bindings = {samplerBinding, fragmentBinding, modelBinding};
1418
1419 VkDescriptorSetLayoutCreateInfo layoutInfo{};
1420 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1421 layoutInfo.bindingCount = extendedFragmentUniformsEnabled ? static_cast<uint32_t>(bindings.size()) : 2U;
1422 layoutInfo.pBindings = bindings.data();
1423
1424 if (vkCreateDescriptorSetLayout(windowPtr->getDevice(), &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
1425 throw mxvk::Exception("VKAbstractModel failed to create descriptor set layout");
1426 }
1427 }
1428
1429 void VKAbstractModel::createUniformBuffers() {
1430 destroyUniformBuffers();
1431
1432 const size_t frameCount = windowPtr->getSwapchainImageCount();
1433 if (frameCount == 0) {
1434 return;
1435 }
1436
1437 uniformBuffers.resize(frameCount, VK_NULL_HANDLE);
1438 uniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
1439 uniformBuffersMapped.resize(frameCount, nullptr);
1440 if (extendedFragmentUniformsEnabled) {
1441 fragmentUniformBuffers.resize(frameCount, VK_NULL_HANDLE);
1442 fragmentUniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
1443 fragmentUniformBuffersMapped.resize(frameCount, nullptr);
1444 }
1445
1446 for (size_t i = 0; i < frameCount; ++i) {
1447 createBuffer(sizeof(UniformBufferObject), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBufferMemory[i]);
1448 vkMapMemory(windowPtr->getDevice(), uniformBufferMemory[i], 0, sizeof(UniformBufferObject), 0, &uniformBuffersMapped[i]);
1449 if (extendedFragmentUniformsEnabled) {
1450 createBuffer(sizeof(ModelFragmentUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, fragmentUniformBuffers[i], fragmentUniformBufferMemory[i]);
1451 vkMapMemory(windowPtr->getDevice(), fragmentUniformBufferMemory[i], 0, sizeof(ModelFragmentUniforms), 0, &fragmentUniformBuffersMapped[i]);
1452 }
1453 }
1454 }
1455
1456 void VKAbstractModel::destroyUniformBuffers() {
1457 if (windowPtr == nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1458 uniformBuffers.clear();
1459 uniformBufferMemory.clear();
1460 uniformBuffersMapped.clear();
1461 fragmentUniformBuffers.clear();
1462 fragmentUniformBufferMemory.clear();
1463 fragmentUniformBuffersMapped.clear();
1464 return;
1465 }
1466
1467 for (size_t i = 0; i < uniformBuffers.size(); ++i) {
1468 if (uniformBuffersMapped[i] != nullptr) {
1469 vkUnmapMemory(windowPtr->getDevice(), uniformBufferMemory[i]);
1470 uniformBuffersMapped[i] = nullptr;
1471 }
1472 if (uniformBuffers[i] != VK_NULL_HANDLE) {
1473 vkDestroyBuffer(windowPtr->getDevice(), uniformBuffers[i], nullptr);
1474 }
1475 if (uniformBufferMemory[i] != VK_NULL_HANDLE) {
1476 vkFreeMemory(windowPtr->getDevice(), uniformBufferMemory[i], nullptr);
1477 }
1478 }
1479
1480 uniformBuffers.clear();
1481 uniformBufferMemory.clear();
1482 uniformBuffersMapped.clear();
1483
1484 for (size_t i = 0; i < fragmentUniformBuffers.size(); ++i) {
1485 if (fragmentUniformBuffersMapped[i] != nullptr) {
1486 vkUnmapMemory(windowPtr->getDevice(), fragmentUniformBufferMemory[i]);
1487 }
1488 if (fragmentUniformBuffers[i] != VK_NULL_HANDLE) {
1489 vkDestroyBuffer(windowPtr->getDevice(), fragmentUniformBuffers[i], nullptr);
1490 }
1491 if (fragmentUniformBufferMemory[i] != VK_NULL_HANDLE) {
1492 vkFreeMemory(windowPtr->getDevice(), fragmentUniformBufferMemory[i], nullptr);
1493 }
1494 }
1495 fragmentUniformBuffers.clear();
1496 fragmentUniformBufferMemory.clear();
1497 fragmentUniformBuffersMapped.clear();
1498 }
1499
1500 void VKAbstractModel::createDescriptorPool() {
1501 const uint32_t textureCount = std::max<uint32_t>(1U, static_cast<uint32_t>(textures.size()));
1502 const uint32_t frameCount = static_cast<uint32_t>(windowPtr->getSwapchainImageCount());
1503 const uint32_t requiredSetCount = textureCount * frameCount;
1504 const uint32_t setCount = std::max(requiredSetCount, descriptorPoolSetCapacity);
1505
1506 std::array<VkDescriptorPoolSize, 2> poolSizes{};
1507 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1508 poolSizes[0].descriptorCount = setCount;
1509 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1510 poolSizes[1].descriptorCount = extendedFragmentUniformsEnabled ? setCount * 2U : setCount;
1511
1512 VkDescriptorPoolCreateInfo poolInfo{};
1513 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1514 poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
1515 poolInfo.pPoolSizes = poolSizes.data();
1516 poolInfo.maxSets = setCount;
1517 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1518
1519 if (vkCreateDescriptorPool(windowPtr->getDevice(), &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {
1520 throw mxvk::Exception("VKAbstractModel failed to create descriptor pool");
1521 }
1522 descriptorPoolSetCapacity = setCount;
1523 }
1524
1525 void VKAbstractModel::createDescriptorSets() {
1526 const size_t textureCount = std::max<size_t>(1, textures.size());
1527 const size_t frameCount = windowPtr->getSwapchainImageCount();
1528 const size_t setCount = textureCount * frameCount;
1529
1530 if (descriptorSetLayout == VK_NULL_HANDLE || frameCount == 0 || uniformBuffers.size() < frameCount || textures.empty() || (extendedFragmentUniformsEnabled && fragmentUniformBuffers.size() < frameCount)) {
1531 return;
1532 }
1533
1534 if (descriptorPool == VK_NULL_HANDLE || descriptorPoolSetCapacity < setCount) {
1535 descriptorSets.clear();
1536 if (descriptorPool != VK_NULL_HANDLE) {
1537 vkDestroyDescriptorPool(windowPtr->getDevice(), descriptorPool, nullptr);
1538 descriptorPool = VK_NULL_HANDLE;
1539 descriptorPoolSetCapacity = 0;
1540 }
1541 createDescriptorPool();
1542 }
1543
1544 const bool needsAllocation = descriptorSets.size() != setCount || std::any_of(descriptorSets.begin(), descriptorSets.end(), [](VkDescriptorSet set) { return set == VK_NULL_HANDLE; });
1545
1546 if (needsAllocation) {
1547 std::vector<VkDescriptorSetLayout> layouts(setCount, descriptorSetLayout);
1548
1549 VkDescriptorSetAllocateInfo allocInfo{};
1550 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1551 allocInfo.descriptorPool = descriptorPool;
1552 allocInfo.descriptorSetCount = static_cast<uint32_t>(setCount);
1553 allocInfo.pSetLayouts = layouts.data();
1554
1555 descriptorSets.assign(setCount, VK_NULL_HANDLE);
1556 const VkResult allocateResult = vkAllocateDescriptorSets(windowPtr->getDevice(), &allocInfo, descriptorSets.data());
1557 if (allocateResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocateResult == VK_ERROR_FRAGMENTED_POOL) {
1558 vkDestroyDescriptorPool(windowPtr->getDevice(), descriptorPool, nullptr);
1559 descriptorPool = VK_NULL_HANDLE;
1560 descriptorPoolSetCapacity = static_cast<uint32_t>(std::max<size_t>(setCount * 2U, 1U));
1561 descriptorSets.clear();
1562 createDescriptorPool();
1563 allocInfo.descriptorPool = descriptorPool;
1564 descriptorSets.assign(setCount, VK_NULL_HANDLE);
1565 if (vkAllocateDescriptorSets(windowPtr->getDevice(), &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
1566 throw mxvk::Exception("VKAbstractModel failed to allocate descriptor sets");
1567 }
1568 } else if (allocateResult != VK_SUCCESS) {
1569 throw mxvk::Exception("VKAbstractModel failed to allocate descriptor sets");
1570 }
1571 }
1572
1573 for (size_t frame = 0; frame < frameCount; ++frame) {
1574 VkDescriptorBufferInfo bufferInfo{};
1575 bufferInfo.buffer = uniformBuffers[frame];
1576 bufferInfo.offset = 0;
1577 bufferInfo.range = sizeof(UniformBufferObject);
1578 VkDescriptorBufferInfo fragmentBufferInfo{};
1579 if (extendedFragmentUniformsEnabled) {
1580 fragmentBufferInfo.buffer = fragmentUniformBuffers[frame];
1581 fragmentBufferInfo.offset = 0;
1582 fragmentBufferInfo.range = sizeof(ModelFragmentUniforms);
1583 }
1584
1585 for (size_t tex = 0; tex < textureCount; ++tex) {
1586 const size_t setIndex = frame * textureCount + tex;
1587 const TextureEntry &entry = textures[tex];
1588
1589 VkDescriptorImageInfo imageInfo{};
1590 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1591 imageInfo.imageView = entry.view;
1592 imageInfo.sampler = textureSampler;
1593
1594 std::array<VkWriteDescriptorSet, 3> writes{};
1595 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1596 writes[0].dstSet = descriptorSets[setIndex];
1597 writes[0].dstBinding = 0;
1598 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1599 writes[0].descriptorCount = 1;
1600 writes[0].pImageInfo = &imageInfo;
1601
1602 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1603 writes[1].dstSet = descriptorSets[setIndex];
1604 writes[1].dstBinding = 1;
1605 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1606 writes[1].descriptorCount = 1;
1607 writes[1].pBufferInfo = extendedFragmentUniformsEnabled ? &fragmentBufferInfo : &bufferInfo;
1608
1609 if (extendedFragmentUniformsEnabled) {
1610 writes[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1611 writes[2].dstSet = descriptorSets[setIndex];
1612 writes[2].dstBinding = 2;
1613 writes[2].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1614 writes[2].descriptorCount = 1;
1615 writes[2].pBufferInfo = &bufferInfo;
1616 }
1617
1618 const uint32_t writeCount = extendedFragmentUniformsEnabled ? static_cast<uint32_t>(writes.size()) : 2U;
1619 vkUpdateDescriptorSets(windowPtr->getDevice(), writeCount, writes.data(), 0, nullptr);
1620 }
1621 }
1622 }
1623
1624 void VKAbstractModel::updateTextureDescriptor(VkDescriptorSet descriptorSet, VkImageView imageView) const {
1625 if (descriptorSet == VK_NULL_HANDLE || imageView == VK_NULL_HANDLE || textureSampler == VK_NULL_HANDLE) {
1626 return;
1627 }
1628 const VkDescriptorImageInfo imageInfo{
1629 .sampler = textureSampler,
1630 .imageView = imageView,
1631 .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
1632 };
1633 const VkWriteDescriptorSet write{
1634 .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
1635 .dstSet = descriptorSet,
1636 .dstBinding = 0,
1637 .descriptorCount = 1,
1638 .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
1639 .pImageInfo = &imageInfo,
1640 };
1641 vkUpdateDescriptorSets(windowPtr->getDevice(), 1, &write, 0, nullptr);
1642 }
1643
1644 void VKAbstractModel::createPipelines() {
1645 destroyPipelines();
1646
1647 if (windowPtr == nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1648 return;
1649 }
1650 if (descriptorSetLayout == VK_NULL_HANDLE) {
1651 return;
1652 }
1653 if (vertexShaderPath.empty() || fragmentShaderPath.empty()) {
1654 return;
1655 }
1656 if (windowPtr->getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
1657 return;
1658 }
1659
1660 const std::vector<char> vertBytes = mxvk::load_spv(vertexShaderPath);
1661 const std::vector<char> fragBytes = mxvk::load_spv(fragmentShaderPath);
1662
1663 const VkShaderModule vertModule = mxvk::create_shader_module(windowPtr->getDevice(), vertBytes);
1664 VkShaderModule fragModule = VK_NULL_HANDLE;
1665
1666 try {
1667 fragModule = mxvk::create_shader_module(windowPtr->getDevice(), fragBytes);
1668
1669 VkPipelineShaderStageCreateInfo vertStage{};
1670 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1671 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1672 vertStage.module = vertModule;
1673 vertStage.pName = "main";
1674
1675 VkPipelineShaderStageCreateInfo fragStage{};
1676 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1677 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1678 fragStage.module = fragModule;
1679 fragStage.pName = "main";
1680
1681 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
1682
1683 VkVertexInputBindingDescription binding{};
1684 binding.binding = 0;
1685 binding.stride = sizeof(VKVertex);
1686 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1687
1688 std::array<VkVertexInputAttributeDescription, 3> attrs{};
1689 attrs[0].binding = 0;
1690 attrs[0].location = 0;
1691 attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
1692 attrs[0].offset = offsetof(VKVertex, pos);
1693 attrs[1].binding = 0;
1694 attrs[1].location = 1;
1695 attrs[1].format = VK_FORMAT_R32G32_SFLOAT;
1696 attrs[1].offset = offsetof(VKVertex, texCoord);
1697 attrs[2].binding = 0;
1698 attrs[2].location = 2;
1699 attrs[2].format = VK_FORMAT_R32G32B32_SFLOAT;
1700 attrs[2].offset = offsetof(VKVertex, normal);
1701
1702 VkPipelineVertexInputStateCreateInfo vertexInput{};
1703 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1704 vertexInput.vertexBindingDescriptionCount = 1;
1705 vertexInput.pVertexBindingDescriptions = &binding;
1706 vertexInput.vertexAttributeDescriptionCount = static_cast<uint32_t>(attrs.size());
1707 vertexInput.pVertexAttributeDescriptions = attrs.data();
1708
1709 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1710 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1711 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1712 inputAssembly.primitiveRestartEnable = VK_FALSE;
1713
1714 const std::array<VkDynamicState, 2> dynamicStates = {
1715 VK_DYNAMIC_STATE_VIEWPORT,
1716 VK_DYNAMIC_STATE_SCISSOR,
1717 };
1718 VkPipelineDynamicStateCreateInfo dynamicInfo{};
1719 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1720 dynamicInfo.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
1721 dynamicInfo.pDynamicStates = dynamicStates.data();
1722
1723 VkPipelineViewportStateCreateInfo viewportState{};
1724 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1725 viewportState.viewportCount = 1;
1726 viewportState.scissorCount = 1;
1727
1728 VkPipelineRasterizationStateCreateInfo rasterizer{};
1729 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1730 rasterizer.depthClampEnable = VK_FALSE;
1731 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1732 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1733 rasterizer.cullMode = backfaceCullingEnabled ? VK_CULL_MODE_BACK_BIT : VK_CULL_MODE_NONE;
1734 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
1735 rasterizer.depthBiasEnable = VK_FALSE;
1736 rasterizer.lineWidth = 1.0f;
1737
1738 VkPipelineMultisampleStateCreateInfo multisample{};
1739 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1740 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1741 multisample.sampleShadingEnable = VK_FALSE;
1742
1743 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1744 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1745 depthStencil.depthTestEnable = VK_TRUE;
1746 depthStencil.depthWriteEnable = alphaBlendingEnabled ? VK_FALSE : VK_TRUE;
1747 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1748
1749 VkPipelineColorBlendAttachmentState blendAttachment{};
1750 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1751 blendAttachment.blendEnable = alphaBlendingEnabled ? VK_TRUE : VK_FALSE;
1752 blendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1753 blendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1754 blendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
1755 blendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1756 blendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1757 blendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
1758
1759 VkPipelineColorBlendStateCreateInfo colorBlend{};
1760 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1761 colorBlend.logicOpEnable = VK_FALSE;
1762 colorBlend.attachmentCount = 1;
1763 colorBlend.pAttachments = &blendAttachment;
1764
1765 VkPipelineLayoutCreateInfo layoutInfo{};
1766 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1767 layoutInfo.setLayoutCount = 1;
1768 layoutInfo.pSetLayouts = &descriptorSetLayout;
1769 const VkPushConstantRange vertexPushConstantRange{
1770 .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
1771 .offset = 0,
1772 .size = sizeof(ModelPushConstants),
1773 };
1774 const VkPushConstantRange fragmentPushConstantRange{
1775 .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
1776 .offset = 0,
1777 .size = sizeof(ModelFragmentPushConstants),
1778 };
1779 layoutInfo.pushConstantRangeCount = 1U;
1780 layoutInfo.pPushConstantRanges = extendedFragmentUniformsEnabled ? &fragmentPushConstantRange : &vertexPushConstantRange;
1781
1782 if (vkCreatePipelineLayout(windowPtr->getDevice(), &layoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
1783 throw mxvk::Exception("VKAbstractModel failed to create pipeline layout");
1784 }
1785
1786 const VkFormat colorFormat = colorAttachmentFormat != VK_FORMAT_UNDEFINED ? colorAttachmentFormat : windowPtr->getSwapchainFormat();
1787 const VkFormat depthFormat = windowPtr->getDepthFormat();
1788 VkPipelineRenderingCreateInfo renderingInfo{};
1789 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1790 renderingInfo.colorAttachmentCount = 1;
1791 renderingInfo.pColorAttachmentFormats = &colorFormat;
1792 if (depthFormat != VK_FORMAT_UNDEFINED) {
1793 renderingInfo.depthAttachmentFormat = depthFormat;
1794 }
1795
1796 VkGraphicsPipelineCreateInfo pipelineInfo{};
1797 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1798 pipelineInfo.pNext = &renderingInfo;
1799 pipelineInfo.stageCount = static_cast<uint32_t>(stages.size());
1800 pipelineInfo.pStages = stages.data();
1801 pipelineInfo.pVertexInputState = &vertexInput;
1802 pipelineInfo.pInputAssemblyState = &inputAssembly;
1803 pipelineInfo.pViewportState = &viewportState;
1804 pipelineInfo.pRasterizationState = &rasterizer;
1805 pipelineInfo.pMultisampleState = &multisample;
1806 pipelineInfo.pDepthStencilState = &depthStencil;
1807 pipelineInfo.pColorBlendState = &colorBlend;
1808 pipelineInfo.pDynamicState = &dynamicInfo;
1809 pipelineInfo.layout = pipelineLayout;
1810 pipelineInfo.renderPass = VK_NULL_HANDLE;
1811 pipelineInfo.subpass = 0;
1812
1813 if (vkCreateGraphicsPipelines(windowPtr->getDevice(), windowPtr->getPipelineCache(), 1, &pipelineInfo, nullptr, &pipelineFill) != VK_SUCCESS) {
1814 throw mxvk::Exception("VKAbstractModel failed to create fill pipeline");
1815 }
1816
1817 // Keep wireframe pipeline disabled by default. The examples render with
1818 // filled geometry, and creating a line-mode pipeline requires matching
1819 // logical-device feature enablement (fillModeNonSolid).
1820 pipelineWireframe = VK_NULL_HANDLE;
1821 } catch (...) {
1822 if (fragModule != VK_NULL_HANDLE) {
1823 vkDestroyShaderModule(windowPtr->getDevice(), fragModule, nullptr);
1824 }
1825 vkDestroyShaderModule(windowPtr->getDevice(), vertModule, nullptr);
1826 throw;
1827 }
1828
1829 vkDestroyShaderModule(windowPtr->getDevice(), fragModule, nullptr);
1830 vkDestroyShaderModule(windowPtr->getDevice(), vertModule, nullptr);
1831 }
1832
1833 void VKAbstractModel::destroyPipelines() {
1834 if (windowPtr == nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1835 pipelineFill = VK_NULL_HANDLE;
1836 pipelineWireframe = VK_NULL_HANDLE;
1837 pipelineLayout = VK_NULL_HANDLE;
1838 return;
1839 }
1840
1841 if (pipelineFill != VK_NULL_HANDLE) {
1842 logVKAbstractModelStep("destroying fill pipeline");
1843 vkDestroyPipeline(windowPtr->getDevice(), pipelineFill, nullptr);
1844 pipelineFill = VK_NULL_HANDLE;
1845 }
1846 if (pipelineWireframe != VK_NULL_HANDLE) {
1847 logVKAbstractModelStep("destroying wireframe pipeline");
1848 vkDestroyPipeline(windowPtr->getDevice(), pipelineWireframe, nullptr);
1849 pipelineWireframe = VK_NULL_HANDLE;
1850 }
1851 if (pipelineLayout != VK_NULL_HANDLE) {
1852 logVKAbstractModelStep("destroying pipeline layout");
1853 vkDestroyPipelineLayout(windowPtr->getDevice(), pipelineLayout, nullptr);
1854 pipelineLayout = VK_NULL_HANDLE;
1855 }
1856 }
1857
1858 void VKAbstractModel::destroyDescriptors() {
1859 if (windowPtr == nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1860 descriptorSets.clear();
1861 descriptorPool = VK_NULL_HANDLE;
1862 descriptorSetLayout = VK_NULL_HANDLE;
1863 destroyUniformBuffers();
1864 return;
1865 }
1866
1867 descriptorSets.clear();
1868 if (descriptorPool != VK_NULL_HANDLE) {
1869 logVKAbstractModelStep("destroying descriptor pool");
1870 vkDestroyDescriptorPool(windowPtr->getDevice(), descriptorPool, nullptr);
1871 descriptorPool = VK_NULL_HANDLE;
1872 }
1873 if (descriptorSetLayout != VK_NULL_HANDLE) {
1874 logVKAbstractModelStep("destroying descriptor set layout");
1875 vkDestroyDescriptorSetLayout(windowPtr->getDevice(), descriptorSetLayout, nullptr);
1876 descriptorSetLayout = VK_NULL_HANDLE;
1877 }
1878
1879 destroyUniformBuffers();
1880 }
1881
1882 void VKAbstractModel::destroyTextures() {
1883 if (windowPtr == nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1884 textures.clear();
1885 textureSampler = VK_NULL_HANDLE;
1886 return;
1887 }
1888
1889 for (TextureEntry &tex : textures) {
1890#ifdef MXVK_CUDA
1891 destroyTextureCudaInterop(tex);
1892#endif
1893 if (tex.view != VK_NULL_HANDLE) {
1894 logVKAbstractModelStep("destroying texture image view");
1895 vkDestroyImageView(windowPtr->getDevice(), tex.view, nullptr);
1896 }
1897 if (tex.image != VK_NULL_HANDLE) {
1898 logVKAbstractModelStep("destroying texture image");
1899 vkDestroyImage(windowPtr->getDevice(), tex.image, nullptr);
1900 }
1901 if (tex.memory != VK_NULL_HANDLE) {
1902 logVKAbstractModelStep("freeing texture memory");
1903 vkFreeMemory(windowPtr->getDevice(), tex.memory, nullptr);
1904 }
1905 }
1906 textures.clear();
1907
1908 if (textureSampler != VK_NULL_HANDLE) {
1909 logVKAbstractModelStep("destroying texture sampler");
1910 vkDestroySampler(windowPtr->getDevice(), textureSampler, nullptr);
1911 textureSampler = VK_NULL_HANDLE;
1912 }
1913 }
1914
1915} // namespace mxvk
Loads OBJ/MXMOD meshes and uploads them to Vulkan buffers.
const std::vector< VKVertex > & vertices() const
void setAlphaBlending(bool enabled)
Enable or disable alpha blending for this model pipeline.
void setBackfaceCulling(bool enabled)
Enable or disable backface culling for this model pipeline.
void updateFragmentUBO(uint32_t imageIndex, const ModelFragmentUniforms &uniforms)
Update extended fragment uniforms for one swapchain image.
void updateUBO(uint32_t imageIndex, const UniformBufferObject &ubo)
Update one per-frame UBO payload.
void load(VK_Window *window, const std::string &modelPath, const std::string &textureManifestPath, const std::string &textureBasePath, float scale=1.0f)
Load mesh/texture resources and build Vulkan state.
void setShaders(VK_Window *window, const std::string &vertSpv, const std::string &fragSpv)
Configure custom shader paths and rebuild pipelines.
bool isLoaded() const
True once the model has been uploaded to GPU buffers.
void cleanup(VK_Window *window)
Destroy all owned Vulkan resources.
void setColorAttachmentFormat(VkFormat format)
Override the dynamic-rendering color format for this model.
void resize(VK_Window *window)
Rebuild swapchain-dependent resources after resize.
void renderWithPushConstants(VkCommandBuffer cmd, uint32_t imageIndex, size_t textureIndex, const UniformBufferObject &ubo, bool wireframe=false)
Record one draw using push constants for per-draw transforms and an explicit texture slot.
void render(VkCommandBuffer cmd, uint32_t imageIndex, bool wireframe=false) const
Record draw commands for this model.
void renderWithExternalTexture(VkCommandBuffer cmd, uint32_t imageIndex, VkImageView textureView, const UniformBufferObject &ubo, bool wireframe=false)
Render using a non-owning shader-readable image view as texture slot zero.
void enableExtendedFragmentUniforms()
Use binding 1 for fragment uniforms and binding 2 for model transforms.
bool updatePrimaryTexture(const void *pixels, int width, int height, int pitch=0)
Upload raw RGBA pixels into the primary model texture.
void setFragmentPushConstants(const ModelFragmentPushConstants &constants)
Set sprite-compatible push constants used by custom fragment shaders.
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:39
VkDevice getDevice() const noexcept
Get the Vulkan logical device handle.
Definition mxvk.hpp:198
High-level model wrapper integrated with MXVK dynamic rendering.
Small compatibility wrappers around OpenCV CUDA APIs.
PNG image loading and saving utilities via SDL3.
Definition model.py:1
void logVKAbstractModelStep(const std::string &message, bool important=false)
std::string resolveTexturePath(const std::string &textureBasePath, const std::string &texturePath)
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31
VkShaderModule create_shader_module(VkDevice device, const std::vector< char > &spv_bytes)
Create a shader module from SPIR-V bytecode.
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition mxvk_png.cpp:89
std::vector< char > load_spv(const std::string &path)
Load a SPIR-V file from disk.
Sprite-compatible fragment parameters for UV-based model effects.
Extended shader-viewer uniforms available to fragment shaders at binding 1.
One indexed sub-range that can reference a dedicated texture slot.
uint32_t textureIndex
Default transform UBO payload for model shaders.