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