30 std::cout <<
"mxvk_abstract_model: " << message <<
'\n';
35 std::string texturePath{};
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") {
42 }
else if (token ==
"-mm") {
45 }
else if (token ==
"-o" || token ==
"-s" || token ==
"-t") {
49 }
else if (token ==
"-bm" || token ==
"-boost" || token ==
"-texres") {
55 if (!texturePath.empty()) {
63 [[nodiscard]] std::string
resolveTexturePath(
const std::string &textureBasePath,
const std::string &texturePath) {
64 if (texturePath.empty()) {
68 std::filesystem::path resolvedPath(texturePath);
69 if (resolvedPath.is_absolute()) {
70 resolvedPath = resolvedPath.filename();
73 if (textureBasePath.empty()) {
74 return resolvedPath.string();
77 return (std::filesystem::path(textureBasePath) / resolvedPath).string();
82 const std::string &modelPath,
83 const std::string &textureManifestPath,
84 const std::string &textureBasePath,
86 if (targetWindow ==
nullptr) {
89 if (modelPath.empty()) {
93 logVKAbstractModelStep(
"creation begin: " + modelPath,
true);
95 windowPtr = targetWindow;
96 if (!windowPtr->ensureRenderResources()) {
97 throw mxvk::Exception(
"VKAbstractModel::load failed because render resources are not ready");
100 obj.load(modelPath, scale);
101 obj.upload(windowPtr->getDevice(), windowPtr->getPhysicalDevice(), windowPtr->getCommandPool(), windowPtr->getGraphicsQueue());
102 computeBoundsAndScale();
103 logVKAbstractModelStep(
"mesh upload complete",
true);
106 if (!textureManifestPath.empty()) {
107 loadTextures(textureManifestPath, textureBasePath);
109 loadTexturesFromMTL(textureBasePath.empty() ? std::filesystem::path(modelPath).parent_path().string() : textureBasePath);
111 if (textures.empty()) {
112 createFallbackTexture();
113 logVKAbstractModelStep(
"using fallback texture",
true);
115 logVKAbstractModelStep(
"textures ready: " + std::to_string(textures.size()),
true);
117 createTextureSampler();
118 createDescriptorSetLayout();
119 createUniformBuffers();
120 createDescriptorPool();
121 createDescriptorSets();
123 logVKAbstractModelStep(
"creation complete",
true);
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");
135 windowPtr = targetWindow;
136 if (!windowPtr->ensureRenderResources()) {
137 throw mxvk::Exception(
"VKAbstractModel::load failed because render resources are not ready");
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);
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);
151 createFallbackTexture();
152 logVKAbstractModelStep(
"using fallback texture",
true);
154 if (textures.empty()) {
155 createFallbackTexture();
156 logVKAbstractModelStep(
"using fallback texture",
true);
158 logVKAbstractModelStep(
"textures ready: " + std::to_string(textures.size()),
true);
160 createTextureSampler();
161 createDescriptorSetLayout();
162 createUniformBuffers();
163 createDescriptorPool();
164 createDescriptorSets();
166 logVKAbstractModelStep(
"creation complete",
true);
170 if (targetWindow ==
nullptr) {
171 throw mxvk::Exception(
"VKAbstractModel::setShaders requires a valid window");
174 windowPtr = targetWindow;
175 vertexShaderPath = vertSpv;
176 fragmentShaderPath = fragSpv;
177 logVKAbstractModelStep(
"setShaders",
true);
182 if (backfaceCullingEnabled == enabled) {
186 backfaceCullingEnabled = enabled;
187 if (windowPtr !=
nullptr) {
193 if (alphaBlendingEnabled == enabled) {
197 alphaBlendingEnabled = enabled;
198 if (windowPtr !=
nullptr) {
204 if (imageIndex >= uniformBuffersMapped.size()) {
207 if (uniformBuffersMapped[imageIndex] ==
nullptr) {
215 if (windowPtr !=
nullptr) {
216 throw mxvk::Exception(
"VKAbstractModel::enableExtendedFragmentUniforms must be called before load");
218 extendedFragmentUniformsEnabled =
true;
222 if (imageIndex >= fragmentUniformBuffersMapped.size() || fragmentUniformBuffersMapped[imageIndex] ==
nullptr) {
229 fragmentPushConstants = constants;
233 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
236 if (pixels ==
nullptr || width <= 0 || height <= 0) {
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) {
248 if (textures.empty()) {
249 createFallbackTexture();
250 createDescriptorSets();
253 TextureEntry &texture = textures[0];
254 if (texture.image == VK_NULL_HANDLE || texture.memory == VK_NULL_HANDLE || texture.view == VK_NULL_HANDLE) {
258 bool recreatedTexture =
false;
259 if (texture.width != uploadWidth || texture.height != uploadHeight) {
260 vkDeviceWaitIdle(windowPtr->getDevice());
263 destroyTextureCudaInterop(texture);
265 if (texture.view != VK_NULL_HANDLE) {
266 vkDestroyImageView(windowPtr->getDevice(), texture.view,
nullptr);
268 if (texture.image != VK_NULL_HANDLE) {
269 vkDestroyImage(windowPtr->getDevice(), texture.image,
nullptr);
271 if (texture.memory != VK_NULL_HANDLE) {
272 vkFreeMemory(windowPtr->getDevice(), texture.memory,
nullptr);
275 texture.view = VK_NULL_HANDLE;
276 texture.image = VK_NULL_HANDLE;
277 texture.memory = VK_NULL_HANDLE;
279 createTextureImage(uploadWidth, uploadHeight, texture);
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;
286 createDescriptorSets();
290 if (updatePrimaryTextureCudaHost(texture, pixels, uploadWidth, uploadHeight, srcRowBytes)) {
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);
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);
310 if (srcRowBytes == tightRowBytes) {
311 std::memcpy(mapped, pixels,
static_cast<size_t>(stagingSize));
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);
321 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
323 if (recreatedTexture) {
324 transitionImageLayout(texture.image, VK_FORMAT_R8G8B8A8_UNORM,
325 VK_IMAGE_LAYOUT_UNDEFINED,
326 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
328 transitionImageLayout(texture.image, VK_FORMAT_R8G8B8A8_UNORM,
329 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
330 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
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);
337 texture.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
340 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
341 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
346 if (cmd == VK_NULL_HANDLE || imageIndex >= uniformBuffers.size() || descriptorSets.empty()) {
350 const VkPipeline pipeline = (wireframe && pipelineWireframe != VK_NULL_HANDLE) ? pipelineWireframe : pipelineFill;
351 if (pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
355 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
356 if (extendedFragmentUniformsEnabled) {
357 vkCmdPushConstants(cmd,
359 VK_SHADER_STAGE_FRAGMENT_BIT,
362 &fragmentPushConstants);
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()) {
374 vkCmdBindDescriptorSets(cmd,
375 VK_PIPELINE_BIND_POINT_GRAPHICS,
379 &descriptorSets[setIndex],
383 obj.drawSubMesh(cmd, i);
392 if (cmd == VK_NULL_HANDLE || imageIndex >= uniformBuffers.size() || descriptorSets.empty()) {
396 const VkPipeline pipeline = (wireframe && pipelineWireframe != VK_NULL_HANDLE) ? pipelineWireframe : pipelineFill;
397 if (pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
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()) {
408 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
410 const ModelPushConstants pushConstants{
414 vkCmdPushConstants(cmd,
416 VK_SHADER_STAGE_VERTEX_BIT,
418 sizeof(ModelPushConstants),
420 if (extendedFragmentUniformsEnabled) {
421 vkCmdPushConstants(cmd,
423 VK_SHADER_STAGE_FRAGMENT_BIT,
426 &fragmentPushConstants);
428 vkCmdBindDescriptorSets(cmd,
429 VK_PIPELINE_BIND_POINT_GRAPHICS,
433 &descriptorSets[setIndex],
441 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
448 logVKAbstractModelStep(
"resize begin",
true);
449 windowPtr = targetWindow;
451 destroyDescriptors();
453 createDescriptorSetLayout();
454 createUniformBuffers();
455 createDescriptorPool();
456 createDescriptorSets();
458 logVKAbstractModelStep(
"resize complete",
true);
462 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
466 logVKAbstractModelStep(
"teardown begin",
true);
467 windowPtr = targetWindow;
469 destroyDescriptors();
471 obj.cleanup(windowPtr->getDevice());
473 logVKAbstractModelStep(
"teardown complete",
true);
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);
485 float minX = vertices.front().pos[0];
487 float minY = vertices.front().pos[1];
489 float minZ = vertices.front().pos[2];
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]);
501 modelCenterOffsetValue = glm::vec3(
502 -0.5f * (minX + maxX),
503 -0.5f * (minY + maxY),
504 -0.5f * (minZ + maxZ));
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;
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);
517 std::vector<std::string> lines{};
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) {
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] ==
'#') {
529 lines.push_back(line);
532 bool isStructured =
false;
533 bool isMtlLike =
false;
534 for (
const std::string &ln : lines) {
535 std::istringstream stream(ln);
538 if (keyword ==
"submesh" || keyword ==
"texture_dir" || keyword ==
"material_lib" || keyword ==
"model") {
542 if (keyword ==
"newmtl") {
548 std::vector<std::string> imagePaths{};
549 const std::string prefix = textureBasePath;
552 int currentMaterialTexture = -1;
553 for (
const std::string &ln : lines) {
554 std::istringstream stream(ln);
557 if (keyword ==
"newmtl") {
558 imagePaths.emplace_back();
559 currentMaterialTexture =
static_cast<int>(imagePaths.size()) - 1;
560 }
else if (keyword ==
"map_Kd") {
562 if (!image.empty() && currentMaterialTexture >= 0) {
563 imagePaths[
static_cast<size_t>(currentMaterialTexture)] =
resolveTexturePath(prefix, image);
567 }
else if (isStructured) {
568 for (
const std::string &ln : lines) {
569 std::istringstream stream(ln);
572 if (keyword ==
"texture") {
574 if (stream >> image) {
580 for (
const std::string &ln : lines) {
585 if (imagePaths.empty()) {
589 for (
const std::string &path : imagePaths) {
592 createFallbackTexture();
598 if (surface ==
nullptr) {
600 createFallbackTexture();
604 const uint32_t width =
static_cast<uint32_t
>(surface->w);
605 const uint32_t height =
static_cast<uint32_t
>(surface->h);
610 createTextureImage(width, height, tex);
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);
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);
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);
634 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL)
635 ? VK_IMAGE_LAYOUT_GENERAL
636 : VK_IMAGE_LAYOUT_UNDEFINED;
638 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
640 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM,
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);
648 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
651 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
652 textures.push_back(tex);
654 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
655 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
656 SDL_DestroySurface(surface);
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();
668 foundTextureReference =
true;
672 if (surface ==
nullptr) {
674 createFallbackTexture();
678 const uint32_t width =
static_cast<uint32_t
>(surface->w);
679 const uint32_t height =
static_cast<uint32_t
>(surface->h);
684 createTextureImage(width, height, tex);
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);
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);
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);
708 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL)
709 ? VK_IMAGE_LAYOUT_GENERAL
710 : VK_IMAGE_LAYOUT_UNDEFINED;
712 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
714 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM,
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);
722 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
725 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
726 textures.push_back(tex);
728 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
729 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
730 SDL_DestroySurface(surface);
733 if (!foundTextureReference) {
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");
744 auto *pixel =
static_cast<uint32_t *
>(surface->pixels);
745 *pixel = 0xFFFFFFFFu;
750 createTextureImage(1, 1, tex);
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);
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);
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);
774 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL)
775 ? VK_IMAGE_LAYOUT_GENERAL
776 : VK_IMAGE_LAYOUT_UNDEFINED;
778 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
780 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM,
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);
788 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
790 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
791 textures.push_back(tex);
793 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
794 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
795 SDL_DestroySurface(surface);
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;
807 if (vkCreateBuffer(windowPtr->getDevice(), &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
808 throw mxvk::Exception(
"VKAbstractModel failed to create buffer");
811 VkMemoryRequirements requirements{};
812 vkGetBufferMemoryRequirements(windowPtr->getDevice(), buffer, &requirements);
814 VkMemoryAllocateInfo allocInfo{};
815 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
816 allocInfo.allocationSize = requirements.size;
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");
824 if (vkBindBufferMemory(windowPtr->getDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) {
825 throw mxvk::Exception(
"VKAbstractModel failed to bind buffer memory");
828 if (bufferMemory != VK_NULL_HANDLE) {
829 vkFreeMemory(windowPtr->getDevice(), bufferMemory,
nullptr);
830 bufferMemory = VK_NULL_HANDLE;
832 if (buffer != VK_NULL_HANDLE) {
833 vkDestroyBuffer(windowPtr->getDevice(), buffer,
nullptr);
834 buffer = VK_NULL_HANDLE;
840 uint32_t VKAbstractModel::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
841 VkPhysicalDeviceMemoryProperties memProperties{};
842 vkGetPhysicalDeviceMemoryProperties(windowPtr->getPhysicalDevice(), &memProperties);
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) {
853 throw mxvk::Exception(
"VKAbstractModel failed to find suitable memory type");
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;
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");
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");
876 return commandBuffer;
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");
885 VkSubmitInfo submitInfo{};
886 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
887 submitInfo.commandBufferCount = 1;
888 submitInfo.pCommandBuffers = &commandBuffer;
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");
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");
899 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
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;
921 if (vkCreateImage(windowPtr->getDevice(), &imageInfo,
nullptr, &image) != VK_SUCCESS) {
922 throw mxvk::Exception(
"VKAbstractModel failed to create image");
925 VkMemoryRequirements requirements{};
926 vkGetImageMemoryRequirements(windowPtr->getDevice(), image, &requirements);
928 VkMemoryAllocateInfo allocInfo{};
929 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
930 allocInfo.allocationSize = requirements.size;
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");
938 if (vkBindImageMemory(windowPtr->getDevice(), image, memory, 0) != VK_SUCCESS) {
939 throw mxvk::Exception(
"VKAbstractModel failed to bind image memory");
942 if (memory != VK_NULL_HANDLE) {
943 vkFreeMemory(windowPtr->getDevice(), memory,
nullptr);
944 memory = VK_NULL_HANDLE;
946 if (image != VK_NULL_HANDLE) {
947 vkDestroyImage(windowPtr->getDevice(), image,
nullptr);
948 image = VK_NULL_HANDLE;
954 void VKAbstractModel::createTextureImage(uint32_t width, uint32_t height, TextureEntry &texture)
const {
957 createCudaExportableImage(width, height, texture);
959 }
catch (
const std::exception &ex) {
961 "CUDA exportable model texture unavailable: {}; using standard Vulkan texture",
963 texture.cudaExportMemorySize = 0;
964 texture.cudaInteropEnabled =
false;
965 texture.cudaInteropUnavailableLogged =
true;
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;
977 texture.cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
982 void VKAbstractModel::destroyTextureCudaInterop(TextureEntry &texture)
const {
983 if (texture.cudaInteropEnabled || texture.cudaExternalMemory !=
nullptr || texture.cudaMipmappedArray !=
nullptr) {
986 if (texture.cudaMipmappedArray !=
nullptr) {
987 cudaFreeMipmappedArray(texture.cudaMipmappedArray);
988 texture.cudaMipmappedArray =
nullptr;
989 texture.cudaArray =
nullptr;
991 if (texture.cudaExternalMemory !=
nullptr) {
992 cudaDestroyExternalMemory(texture.cudaExternalMemory);
993 texture.cudaExternalMemory =
nullptr;
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;
1003 void VKAbstractModel::createCudaExportableImage(uint32_t width, uint32_t height, TextureEntry &texture)
const {
1005 "CUDA interop init: requesting exportable model texture {}x{} RGBA8 optimal-tiled OPAQUE_FD",
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;
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;
1028 if (vkCreateImage(windowPtr->getDevice(), &imageInfo,
nullptr, &texture.image) != VK_SUCCESS) {
1029 throw mxvk::Exception(
"VKAbstractModel failed to create CUDA exportable texture image");
1032 VkMemoryRequirements requirements{};
1033 vkGetImageMemoryRequirements(windowPtr->getDevice(), texture.image, &requirements);
1035 VkExportMemoryAllocateInfo exportMemoryInfo{};
1036 exportMemoryInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
1037 exportMemoryInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
1039 VkMemoryAllocateInfo allocInfo{};
1040 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1041 allocInfo.pNext = &exportMemoryInfo;
1042 allocInfo.allocationSize = requirements.size;
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");
1049 if (vkBindImageMemory(windowPtr->getDevice(), texture.image, texture.memory, 0) != VK_SUCCESS) {
1050 throw mxvk::Exception(
"VKAbstractModel failed to bind CUDA exportable texture memory");
1053 if (texture.memory != VK_NULL_HANDLE) {
1054 vkFreeMemory(windowPtr->getDevice(), texture.memory,
nullptr);
1055 texture.memory = VK_NULL_HANDLE;
1057 if (texture.image != VK_NULL_HANDLE) {
1058 vkDestroyImage(windowPtr->getDevice(), texture.image,
nullptr);
1059 texture.image = VK_NULL_HANDLE;
1061 texture.cudaExportMemorySize = 0;
1065 texture.width = width;
1066 texture.height = height;
1067 texture.cudaExportMemorySize = requirements.size;
1068 texture.cudaInteropUnavailableLogged =
false;
1069 texture.cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
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));
1075 bool VKAbstractModel::ensureTextureCudaInterop(TextureEntry &texture)
const {
1076 if (texture.cudaInteropEnabled) {
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;
1086 if (vkGetMemoryFdKHR ==
nullptr) {
1087 if (!texture.cudaInteropUnavailableLogged) {
1089 texture.cudaInteropUnavailableLogged =
true;
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;
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;
1110 cudaExternalMemoryHandleDesc externalMemoryDesc{};
1111 externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd;
1112 externalMemoryDesc.handle.fd = memoryFd;
1113 externalMemoryDesc.size = texture.cudaExportMemorySize;
1115 cudaError_t cudaResult = cudaImportExternalMemory(&texture.cudaExternalMemory, &externalMemoryDesc);
1116 if (cudaResult != cudaSuccess) {
1118 if (!texture.cudaInteropUnavailableLogged) {
1119 logVKAbstractModelStep(std::format(
"CUDA interop init: cudaImportExternalMemory failed for model texture: {}",
1120 cudaGetErrorString(cudaResult)));
1121 texture.cudaInteropUnavailableLogged =
true;
1123 texture.cudaExternalMemory =
nullptr;
1126 logVKAbstractModelStep(std::format(
"CUDA interop init: imported model texture external memory into CUDA ({} bytes)",
1127 static_cast<unsigned long long>(texture.cudaExportMemorySize)));
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;
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;
1143 destroyTextureCudaInterop(texture);
1146 logVKAbstractModelStep(std::format(
"CUDA interop init: mapped model texture CUDA mipmapped array {}x{} uchar4",
1147 texture.width, texture.height));
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;
1156 destroyTextureCudaInterop(texture);
1160 texture.cudaInteropEnabled =
true;
1165 bool VKAbstractModel::transitionTextureForCudaWrite(TextureEntry &texture)
const {
1166 if (texture.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) {
1170 const VkImageLayout oldLayout = (texture.cudaImageLayout == VK_IMAGE_LAYOUT_UNDEFINED)
1171 ? VK_IMAGE_LAYOUT_UNDEFINED
1172 : texture.cudaImageLayout;
1173 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
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;
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);
1197 texture.cudaImageLayout = VK_IMAGE_LAYOUT_GENERAL;
1198 if (!texture.cudaWriteTransitionLogged) {
1200 texture.cudaWriteTransitionLogged =
true;
1205 bool VKAbstractModel::transitionTextureForShaderRead(TextureEntry &texture)
const {
1206 if (texture.cudaImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
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;
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);
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;
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;
1245 if (texture.image != VK_NULL_HANDLE) {
1246 vkDestroyImage(windowPtr->getDevice(), texture.image,
nullptr);
1247 texture.image = VK_NULL_HANDLE;
1249 if (texture.memory != VK_NULL_HANDLE) {
1250 vkFreeMemory(windowPtr->getDevice(), texture.memory,
nullptr);
1251 texture.memory = VK_NULL_HANDLE;
1254 createCudaExportableImage(width, height, texture);
1255 texture.view = createImageView(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1257 createDescriptorSets();
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) {
1265 const uint32_t rowBytes = width * 4U;
1266 if (pitch < rowBytes || texture.width != width || texture.height != height) {
1269 if (!ensureTextureCudaInterop(texture) || !transitionTextureForCudaWrite(texture)) {
1273 if (!texture.cudaUploadLogged) {
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;
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)));
1289 return transitionTextureForShaderRead(texture);
1292 bool VKAbstractModel::updatePrimaryTextureCuda(
const cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream) {
1293 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1296 if (rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols <= 0 || rgba.rows <= 0) {
1300 if (textures.empty()) {
1301 textures.push_back(TextureEntry{});
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) {
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;
1321 if (!ensureTextureCudaInterop(texture) || !transitionTextureForCudaWrite(texture)) {
1325 cudaStream_t cudaStream = cuda_stream_handle(stream);
1326 if (!texture.cudaUploadLogged) {
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;
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)));
1344 cudaResult = cudaStreamSynchronize(cudaStream);
1345 if (cudaResult != cudaSuccess) {
1346 logVKAbstractModelStep(std::format(
"CUDA interop model texture sync failed: {}", cudaGetErrorString(cudaResult)));
1350 return transitionTextureForShaderRead(texture);
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;
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");
1373 void VKAbstractModel::transitionImageLayout(VkImage image, VkFormat, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
1374 VkCommandBuffer cmd = beginSingleTimeCommands();
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;
1389 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1390 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
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;
1414 vkCmdPipelineBarrier(cmd,
1422 endSingleTimeCommands(cmd);
1425 void VKAbstractModel::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
1426 VkCommandBuffer cmd = beginSingleTimeCommands();
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};
1439 vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
1440 endSingleTimeCommands(cmd);
1443 void VKAbstractModel::createTextureSampler() {
1444 if (textureSampler != VK_NULL_HANDLE) {
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)
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;
1472 if (vkCreateSampler(windowPtr->getDevice(), &samplerInfo,
nullptr, &textureSampler) != VK_SUCCESS) {
1473 throw mxvk::Exception(
"VKAbstractModel failed to create texture sampler");
1477 void VKAbstractModel::createDescriptorSetLayout() {
1478 if (descriptorSetLayout != VK_NULL_HANDLE) {
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;
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;
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;
1500 const std::array<VkDescriptorSetLayoutBinding, 3> bindings = {samplerBinding, fragmentBinding, modelBinding};
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();
1507 if (vkCreateDescriptorSetLayout(windowPtr->getDevice(), &layoutInfo,
nullptr, &descriptorSetLayout) != VK_SUCCESS) {
1508 throw mxvk::Exception(
"VKAbstractModel failed to create descriptor set layout");
1512 void VKAbstractModel::createUniformBuffers() {
1513 destroyUniformBuffers();
1515 const size_t frameCount = windowPtr->getSwapchainImageCount();
1516 if (frameCount == 0) {
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);
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]);
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();
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;
1561 if (uniformBuffers[i] != VK_NULL_HANDLE) {
1562 vkDestroyBuffer(windowPtr->getDevice(), uniformBuffers[i],
nullptr);
1564 if (uniformBufferMemory[i] != VK_NULL_HANDLE) {
1565 vkFreeMemory(windowPtr->getDevice(), uniformBufferMemory[i],
nullptr);
1569 uniformBuffers.clear();
1570 uniformBufferMemory.clear();
1571 uniformBuffersMapped.clear();
1573 for (
size_t i = 0; i < fragmentUniformBuffers.size(); ++i) {
1574 if (fragmentUniformBuffersMapped[i] !=
nullptr) {
1575 vkUnmapMemory(windowPtr->getDevice(), fragmentUniformBufferMemory[i]);
1577 if (fragmentUniformBuffers[i] != VK_NULL_HANDLE) {
1578 vkDestroyBuffer(windowPtr->getDevice(), fragmentUniformBuffers[i],
nullptr);
1580 if (fragmentUniformBufferMemory[i] != VK_NULL_HANDLE) {
1581 vkFreeMemory(windowPtr->getDevice(), fragmentUniformBufferMemory[i],
nullptr);
1584 fragmentUniformBuffers.clear();
1585 fragmentUniformBufferMemory.clear();
1586 fragmentUniformBuffersMapped.clear();
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);
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;
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;
1608 if (vkCreateDescriptorPool(windowPtr->getDevice(), &poolInfo,
nullptr, &descriptorPool) != VK_SUCCESS) {
1609 throw mxvk::Exception(
"VKAbstractModel failed to create descriptor pool");
1611 descriptorPoolSetCapacity = setCount;
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;
1619 if (descriptorSetLayout == VK_NULL_HANDLE || frameCount == 0 || uniformBuffers.size() < frameCount || textures.empty() ||
1620 (extendedFragmentUniformsEnabled && fragmentUniformBuffers.size() < frameCount)) {
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;
1631 createDescriptorPool();
1634 const bool needsAllocation = descriptorSets.size() != setCount ||
1635 std::any_of(descriptorSets.begin(), descriptorSets.end(), [](VkDescriptorSet set) {
1636 return set == VK_NULL_HANDLE;
1639 if (needsAllocation) {
1640 std::vector<VkDescriptorSetLayout> layouts(setCount, descriptorSetLayout);
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();
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");
1661 }
else if (allocateResult != VK_SUCCESS) {
1662 throw mxvk::Exception(
"VKAbstractModel failed to allocate descriptor sets");
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);
1678 for (
size_t tex = 0; tex < textureCount; ++tex) {
1679 const size_t setIndex = frame * textureCount + tex;
1680 const TextureEntry &entry = textures[tex];
1682 VkDescriptorImageInfo imageInfo{};
1683 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1684 imageInfo.imageView = entry.view;
1685 imageInfo.sampler = textureSampler;
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;
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;
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;
1711 const uint32_t writeCount = extendedFragmentUniformsEnabled ?
static_cast<uint32_t
>(writes.size()) : 2U;
1712 vkUpdateDescriptorSets(windowPtr->getDevice(), writeCount, writes.data(), 0,
nullptr);
1717 void VKAbstractModel::createPipelines() {
1720 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1723 if (descriptorSetLayout == VK_NULL_HANDLE) {
1726 if (vertexShaderPath.empty() || fragmentShaderPath.empty()) {
1729 if (windowPtr->getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
1733 const std::vector<char> vertBytes =
mxvk::load_spv(vertexShaderPath);
1734 const std::vector<char> fragBytes =
mxvk::load_spv(fragmentShaderPath);
1737 VkShaderModule fragModule = VK_NULL_HANDLE;
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";
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";
1754 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
1756 VkVertexInputBindingDescription binding{};
1757 binding.binding = 0;
1758 binding.stride =
sizeof(VKVertex);
1759 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
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);
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();
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;
1787 const std::array<VkDynamicState, 2> dynamicStates = {
1788 VK_DYNAMIC_STATE_VIEWPORT,
1789 VK_DYNAMIC_STATE_SCISSOR,
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();
1796 VkPipelineViewportStateCreateInfo viewportState{};
1797 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1798 viewportState.viewportCount = 1;
1799 viewportState.scissorCount = 1;
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;
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;
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;
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;
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;
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,
1850 .size =
sizeof(ModelPushConstants),
1852 VkPushConstantRange{
1853 .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
1855 .size =
sizeof(ModelFragmentPushConstants),
1858 layoutInfo.pushConstantRangeCount = extendedFragmentUniformsEnabled ?
static_cast<uint32_t
>(pushConstantRanges.size()) : 1U;
1859 layoutInfo.pPushConstantRanges = pushConstantRanges.data();
1861 if (vkCreatePipelineLayout(windowPtr->getDevice(), &layoutInfo,
nullptr, &pipelineLayout) != VK_SUCCESS) {
1862 throw mxvk::Exception(
"VKAbstractModel failed to create pipeline layout");
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;
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;
1892 if (vkCreateGraphicsPipelines(windowPtr->getDevice(), windowPtr->getPipelineCache(), 1, &pipelineInfo,
nullptr, &pipelineFill) != VK_SUCCESS) {
1893 throw mxvk::Exception(
"VKAbstractModel failed to create fill pipeline");
1899 pipelineWireframe = VK_NULL_HANDLE;
1901 if (fragModule != VK_NULL_HANDLE) {
1902 vkDestroyShaderModule(windowPtr->getDevice(), fragModule,
nullptr);
1904 vkDestroyShaderModule(windowPtr->getDevice(), vertModule,
nullptr);
1908 vkDestroyShaderModule(windowPtr->getDevice(), fragModule,
nullptr);
1909 vkDestroyShaderModule(windowPtr->getDevice(), vertModule,
nullptr);
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;
1920 if (pipelineFill != VK_NULL_HANDLE) {
1922 vkDestroyPipeline(windowPtr->getDevice(), pipelineFill,
nullptr);
1923 pipelineFill = VK_NULL_HANDLE;
1925 if (pipelineWireframe != VK_NULL_HANDLE) {
1927 vkDestroyPipeline(windowPtr->getDevice(), pipelineWireframe,
nullptr);
1928 pipelineWireframe = VK_NULL_HANDLE;
1930 if (pipelineLayout != VK_NULL_HANDLE) {
1932 vkDestroyPipelineLayout(windowPtr->getDevice(), pipelineLayout,
nullptr);
1933 pipelineLayout = VK_NULL_HANDLE;
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();
1946 descriptorSets.clear();
1947 if (descriptorPool != VK_NULL_HANDLE) {
1949 vkDestroyDescriptorPool(windowPtr->getDevice(), descriptorPool,
nullptr);
1950 descriptorPool = VK_NULL_HANDLE;
1952 if (descriptorSetLayout != VK_NULL_HANDLE) {
1954 vkDestroyDescriptorSetLayout(windowPtr->getDevice(), descriptorSetLayout,
nullptr);
1955 descriptorSetLayout = VK_NULL_HANDLE;
1958 destroyUniformBuffers();
1961 void VKAbstractModel::destroyTextures() {
1962 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1964 textureSampler = VK_NULL_HANDLE;
1968 for (TextureEntry &tex : textures) {
1970 destroyTextureCudaInterop(tex);
1972 if (tex.view != VK_NULL_HANDLE) {
1974 vkDestroyImageView(windowPtr->getDevice(), tex.view,
nullptr);
1976 if (tex.image != VK_NULL_HANDLE) {
1978 vkDestroyImage(windowPtr->getDevice(), tex.image,
nullptr);
1980 if (tex.memory != VK_NULL_HANDLE) {
1982 vkFreeMemory(windowPtr->getDevice(), tex.memory,
nullptr);
1987 if (textureSampler != VK_NULL_HANDLE) {
1989 vkDestroySampler(windowPtr->getDevice(), textureSampler,
nullptr);
1990 textureSampler = VK_NULL_HANDLE;
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.
VkDevice getDevice() const noexcept
Get the Vulkan logical device handle.
High-level model wrapper integrated with MXVK dynamic rendering.
Small compatibility wrappers around OpenCV CUDA APIs.
PNG image loading and saving utilities via SDL3.
std::string parseMTLTexturePath(std::istream &stream)
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.
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.
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.
One indexed sub-range that can reference a dedicated texture slot.