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()) {
407 updateTextureDescriptor(descriptorSets[setIndex],
408 textures[textureIndex].view);
410 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
412 if (!extendedFragmentUniformsEnabled) {
413 const ModelPushConstants pushConstants{
417 vkCmdPushConstants(cmd,
419 VK_SHADER_STAGE_VERTEX_BIT,
421 sizeof(ModelPushConstants),
424 if (extendedFragmentUniformsEnabled) {
425 vkCmdPushConstants(cmd,
427 VK_SHADER_STAGE_FRAGMENT_BIT,
430 &fragmentPushConstants);
432 vkCmdBindDescriptorSets(cmd,
433 VK_PIPELINE_BIND_POINT_GRAPHICS,
437 &descriptorSets[setIndex],
445 VkCommandBuffer cmd, uint32_t imageIndex, VkImageView textureView,
447 if (cmd == VK_NULL_HANDLE || textureView == VK_NULL_HANDLE ||
448 imageIndex >= uniformBuffers.size() || descriptorSets.empty()) {
452 const VkPipeline pipeline =
453 (wireframe && pipelineWireframe != VK_NULL_HANDLE)
456 if (pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
460 const size_t textureCount = std::max<size_t>(1, textures.size());
461 const size_t setIndex =
static_cast<size_t>(imageIndex) * textureCount;
462 if (setIndex >= descriptorSets.size()) {
465 updateTextureDescriptor(descriptorSets[setIndex], textureView);
467 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
469 if (!extendedFragmentUniformsEnabled) {
470 const ModelPushConstants pushConstants{
474 vkCmdPushConstants(cmd, pipelineLayout,
475 VK_SHADER_STAGE_VERTEX_BIT, 0,
476 sizeof(ModelPushConstants), &pushConstants);
478 if (extendedFragmentUniformsEnabled) {
479 vkCmdPushConstants(cmd, pipelineLayout,
480 VK_SHADER_STAGE_FRAGMENT_BIT, 0,
482 &fragmentPushConstants);
484 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
485 pipelineLayout, 0, 1,
486 &descriptorSets[setIndex], 0,
nullptr);
491 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
498 logVKAbstractModelStep(
"resize begin",
true);
499 windowPtr = targetWindow;
501 destroyDescriptors();
503 createDescriptorSetLayout();
504 createUniformBuffers();
505 createDescriptorPool();
506 createDescriptorSets();
508 logVKAbstractModelStep(
"resize complete",
true);
512 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
516 logVKAbstractModelStep(
"teardown begin",
true);
517 windowPtr = targetWindow;
519 destroyDescriptors();
521 obj.cleanup(windowPtr->getDevice());
523 logVKAbstractModelStep(
"teardown complete",
true);
526 void VKAbstractModel::computeBoundsAndScale() {
527 const auto &vertices = obj.
vertices();
528 if (vertices.empty()) {
529 modelCenterOffsetValue = glm::vec3(0.0f);
530 modelRenderScaleValue = 1.0f;
531 modelAxisExtentValue = glm::vec3(1.0f);
535 float minX = vertices.front().pos[0];
537 float minY = vertices.front().pos[1];
539 float minZ = vertices.front().pos[2];
542 for (
const VKVertex &v : vertices) {
543 minX = std::min(minX, v.pos[0]);
544 maxX = std::max(maxX, v.pos[0]);
545 minY = std::min(minY, v.pos[1]);
546 maxY = std::max(maxY, v.pos[1]);
547 minZ = std::min(minZ, v.pos[2]);
548 maxZ = std::max(maxZ, v.pos[2]);
551 modelCenterOffsetValue = glm::vec3(
552 -0.5f * (minX + maxX),
553 -0.5f * (minY + maxY),
554 -0.5f * (minZ + maxZ));
556 modelAxisExtentValue = glm::vec3(maxX - minX, maxY - minY, maxZ - minZ);
557 const float maxExtent = std::max(modelAxisExtentValue.x, std::max(modelAxisExtentValue.y, modelAxisExtentValue.z));
558 modelRenderScaleValue = (maxExtent > 1e-6f) ? (2.5f / maxExtent) : 1.0f;
561 void VKAbstractModel::loadTextures(
const std::string &textureManifestPath,
const std::string &textureBasePath) {
562 std::ifstream file(textureManifestPath);
563 if (!file.is_open()) {
564 throw mxvk::Exception(
"Failed to open texture manifest: " + textureManifestPath);
567 std::vector<std::string> lines{};
569 while (std::getline(file, line)) {
570 const size_t begin = line.find_first_not_of(
" \t\r\n");
571 if (begin == std::string::npos) {
574 const size_t end = line.find_last_not_of(
" \t\r\n");
575 line = line.substr(begin, end - begin + 1);
576 if (line.empty() || line[0] ==
'#') {
579 lines.push_back(line);
582 bool isStructured =
false;
583 bool isMtlLike =
false;
584 for (
const std::string &ln : lines) {
585 std::istringstream stream(ln);
588 if (keyword ==
"submesh" || keyword ==
"texture_dir" || keyword ==
"material_lib" || keyword ==
"model") {
592 if (keyword ==
"newmtl") {
598 std::vector<std::string> imagePaths{};
599 const std::string prefix = textureBasePath;
602 int currentMaterialTexture = -1;
603 for (
const std::string &ln : lines) {
604 std::istringstream stream(ln);
607 if (keyword ==
"newmtl") {
608 imagePaths.emplace_back();
609 currentMaterialTexture =
static_cast<int>(imagePaths.size()) - 1;
610 }
else if (keyword ==
"map_Kd") {
612 if (!image.empty() && currentMaterialTexture >= 0) {
613 imagePaths[
static_cast<size_t>(currentMaterialTexture)] =
resolveTexturePath(prefix, image);
617 }
else if (isStructured) {
618 for (
const std::string &ln : lines) {
619 std::istringstream stream(ln);
622 if (keyword ==
"texture") {
624 if (stream >> image) {
630 for (
const std::string &ln : lines) {
635 if (imagePaths.empty()) {
639 for (
const std::string &path : imagePaths) {
642 createFallbackTexture();
648 if (surface ==
nullptr) {
650 createFallbackTexture();
654 const uint32_t width =
static_cast<uint32_t
>(surface->w);
655 const uint32_t height =
static_cast<uint32_t
>(surface->h);
660 createTextureImage(width, height, tex);
663 if (updatePrimaryTextureCudaHost(tex, surface->pixels, width, height,
static_cast<uint32_t
>(surface->pitch))) {
664 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
665 textures.push_back(tex);
666 SDL_DestroySurface(surface);
671 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
672 VkBuffer stagingBuffer = VK_NULL_HANDLE;
673 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
674 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
675 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
676 stagingBuffer, stagingMemory);
678 void *mapped =
nullptr;
679 vkMapMemory(windowPtr->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
680 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
681 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
684 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL)
685 ? VK_IMAGE_LAYOUT_GENERAL
686 : VK_IMAGE_LAYOUT_UNDEFINED;
688 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
690 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM,
692 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
693 copyBufferToImage(stagingBuffer, tex.image, width, height);
694 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM,
695 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
696 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
698 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
701 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
702 textures.push_back(tex);
704 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
705 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
706 SDL_DestroySurface(surface);
710 void VKAbstractModel::loadTexturesFromMTL(
const std::string &textureBasePath) {
711 bool foundTextureReference =
false;
712 for (
const MXMaterial &material : obj.materials()) {
713 if (material.map_kd.empty()) {
714 createFallbackTexture();
718 foundTextureReference =
true;
722 if (surface ==
nullptr) {
724 createFallbackTexture();
728 const uint32_t width =
static_cast<uint32_t
>(surface->w);
729 const uint32_t height =
static_cast<uint32_t
>(surface->h);
734 createTextureImage(width, height, tex);
737 if (updatePrimaryTextureCudaHost(tex, surface->pixels, width, height,
static_cast<uint32_t
>(surface->pitch))) {
738 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
739 textures.push_back(tex);
740 SDL_DestroySurface(surface);
745 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
746 VkBuffer stagingBuffer = VK_NULL_HANDLE;
747 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
748 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
749 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
750 stagingBuffer, stagingMemory);
752 void *mapped =
nullptr;
753 vkMapMemory(windowPtr->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
754 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
755 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
758 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL)
759 ? VK_IMAGE_LAYOUT_GENERAL
760 : VK_IMAGE_LAYOUT_UNDEFINED;
762 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
764 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM,
766 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
767 copyBufferToImage(stagingBuffer, tex.image, width, height);
768 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM,
769 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
770 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
772 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
775 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
776 textures.push_back(tex);
778 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
779 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
780 SDL_DestroySurface(surface);
783 if (!foundTextureReference) {
788 void VKAbstractModel::createFallbackTexture() {
789 SDL_Surface *surface = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA32);
790 if (surface ==
nullptr) {
791 throw mxvk::Exception(
"VKAbstractModel failed to allocate fallback texture surface");
794 auto *pixel =
static_cast<uint32_t *
>(surface->pixels);
795 *pixel = 0xFFFFFFFFu;
800 createTextureImage(1, 1, tex);
803 if (updatePrimaryTextureCudaHost(tex, surface->pixels, 1, 1,
static_cast<uint32_t
>(surface->pitch))) {
804 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
805 textures.push_back(tex);
806 SDL_DestroySurface(surface);
811 const VkDeviceSize imageSize = 4;
812 VkBuffer stagingBuffer = VK_NULL_HANDLE;
813 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
814 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
815 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
816 stagingBuffer, stagingMemory);
818 void *mapped =
nullptr;
819 vkMapMemory(windowPtr->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
820 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
821 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
824 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL)
825 ? VK_IMAGE_LAYOUT_GENERAL
826 : VK_IMAGE_LAYOUT_UNDEFINED;
828 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
830 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM,
832 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
833 copyBufferToImage(stagingBuffer, tex.image, 1, 1);
834 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM,
835 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
836 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
838 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
840 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
841 textures.push_back(tex);
843 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
844 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
845 SDL_DestroySurface(surface);
848 void VKAbstractModel::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
849 VkMemoryPropertyFlags properties, VkBuffer &buffer,
850 VkDeviceMemory &bufferMemory)
const {
851 VkBufferCreateInfo bufferInfo{};
852 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
853 bufferInfo.size = size;
854 bufferInfo.usage = usage;
855 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
857 if (vkCreateBuffer(windowPtr->getDevice(), &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
858 throw mxvk::Exception(
"VKAbstractModel failed to create buffer");
861 VkMemoryRequirements requirements{};
862 vkGetBufferMemoryRequirements(windowPtr->getDevice(), buffer, &requirements);
864 VkMemoryAllocateInfo allocInfo{};
865 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
866 allocInfo.allocationSize = requirements.size;
869 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
870 if (vkAllocateMemory(windowPtr->getDevice(), &allocInfo,
nullptr, &bufferMemory) != VK_SUCCESS) {
871 throw mxvk::Exception(
"VKAbstractModel failed to allocate buffer memory");
874 if (vkBindBufferMemory(windowPtr->getDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) {
875 throw mxvk::Exception(
"VKAbstractModel failed to bind buffer memory");
878 if (bufferMemory != VK_NULL_HANDLE) {
879 vkFreeMemory(windowPtr->getDevice(), bufferMemory,
nullptr);
880 bufferMemory = VK_NULL_HANDLE;
882 if (buffer != VK_NULL_HANDLE) {
883 vkDestroyBuffer(windowPtr->getDevice(), buffer,
nullptr);
884 buffer = VK_NULL_HANDLE;
890 uint32_t VKAbstractModel::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
891 VkPhysicalDeviceMemoryProperties memProperties{};
892 vkGetPhysicalDeviceMemoryProperties(windowPtr->getPhysicalDevice(), &memProperties);
894 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
895 const bool typeSupported = (typeFilter & (1u << i)) != 0u;
896 const bool propsSupported =
897 (memProperties.memoryTypes[i].propertyFlags & properties) == properties;
898 if (typeSupported && propsSupported) {
903 throw mxvk::Exception(
"VKAbstractModel failed to find suitable memory type");
906 VkCommandBuffer VKAbstractModel::beginSingleTimeCommands()
const {
907 VkCommandBufferAllocateInfo allocInfo{};
908 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
909 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
910 allocInfo.commandPool = windowPtr->getCommandPool();
911 allocInfo.commandBufferCount = 1;
913 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
914 if (vkAllocateCommandBuffers(windowPtr->getDevice(), &allocInfo, &commandBuffer) != VK_SUCCESS) {
915 throw mxvk::Exception(
"VKAbstractModel failed to allocate command buffer");
918 VkCommandBufferBeginInfo beginInfo{};
919 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
920 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
921 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
922 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
923 throw mxvk::Exception(
"VKAbstractModel failed to begin command buffer");
926 return commandBuffer;
929 void VKAbstractModel::endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
930 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
931 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
932 throw mxvk::Exception(
"VKAbstractModel failed to end command buffer");
935 VkSubmitInfo submitInfo{};
936 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
937 submitInfo.commandBufferCount = 1;
938 submitInfo.pCommandBuffers = &commandBuffer;
940 if (vkQueueSubmit(windowPtr->getGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
941 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
942 throw mxvk::Exception(
"VKAbstractModel failed to submit command buffer");
944 if (vkQueueWaitIdle(windowPtr->getGraphicsQueue()) != VK_SUCCESS) {
945 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
946 throw mxvk::Exception(
"VKAbstractModel failed to wait for queue idle");
949 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
952 void VKAbstractModel::createImage(uint32_t width, uint32_t height, VkFormat format,
953 VkImageTiling tiling, VkImageUsageFlags usage,
954 VkMemoryPropertyFlags properties, VkImage &image,
955 VkDeviceMemory &memory)
const {
956 VkImageCreateInfo imageInfo{};
957 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
958 imageInfo.imageType = VK_IMAGE_TYPE_2D;
959 imageInfo.extent.width = width;
960 imageInfo.extent.height = height;
961 imageInfo.extent.depth = 1;
962 imageInfo.mipLevels = 1;
963 imageInfo.arrayLayers = 1;
964 imageInfo.format = format;
965 imageInfo.tiling = tiling;
966 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
967 imageInfo.usage = usage;
968 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
969 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
971 if (vkCreateImage(windowPtr->getDevice(), &imageInfo,
nullptr, &image) != VK_SUCCESS) {
972 throw mxvk::Exception(
"VKAbstractModel failed to create image");
975 VkMemoryRequirements requirements{};
976 vkGetImageMemoryRequirements(windowPtr->getDevice(), image, &requirements);
978 VkMemoryAllocateInfo allocInfo{};
979 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
980 allocInfo.allocationSize = requirements.size;
983 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
984 if (vkAllocateMemory(windowPtr->getDevice(), &allocInfo,
nullptr, &memory) != VK_SUCCESS) {
985 throw mxvk::Exception(
"VKAbstractModel failed to allocate image memory");
988 if (vkBindImageMemory(windowPtr->getDevice(), image, memory, 0) != VK_SUCCESS) {
989 throw mxvk::Exception(
"VKAbstractModel failed to bind image memory");
992 if (memory != VK_NULL_HANDLE) {
993 vkFreeMemory(windowPtr->getDevice(), memory,
nullptr);
994 memory = VK_NULL_HANDLE;
996 if (image != VK_NULL_HANDLE) {
997 vkDestroyImage(windowPtr->getDevice(), image,
nullptr);
998 image = VK_NULL_HANDLE;
1004 void VKAbstractModel::createTextureImage(uint32_t width, uint32_t height, TextureEntry &texture)
const {
1007 createCudaExportableImage(width, height, texture);
1009 }
catch (
const std::exception &ex) {
1011 "CUDA exportable model texture unavailable: {}; using standard Vulkan texture",
1013 texture.cudaExportMemorySize = 0;
1014 texture.cudaInteropEnabled =
false;
1015 texture.cudaInteropUnavailableLogged =
true;
1019 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM,
1020 VK_IMAGE_TILING_OPTIMAL,
1021 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
1022 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
1023 texture.image, texture.memory);
1024 texture.width = width;
1025 texture.height = height;
1027 texture.cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1032 void VKAbstractModel::destroyTextureCudaInterop(TextureEntry &texture)
const {
1033 if (texture.cudaInteropEnabled || texture.cudaExternalMemory !=
nullptr || texture.cudaMipmappedArray !=
nullptr) {
1036 if (texture.cudaMipmappedArray !=
nullptr) {
1037 cudaFreeMipmappedArray(texture.cudaMipmappedArray);
1038 texture.cudaMipmappedArray =
nullptr;
1039 texture.cudaArray =
nullptr;
1041 if (texture.cudaExternalMemory !=
nullptr) {
1042 cudaDestroyExternalMemory(texture.cudaExternalMemory);
1043 texture.cudaExternalMemory =
nullptr;
1045 texture.cudaInteropEnabled =
false;
1046 texture.cudaExportMemorySize = 0;
1047 texture.cudaUploadLogged =
false;
1048 texture.cudaWriteTransitionLogged =
false;
1049 texture.cudaShaderTransitionLogged =
false;
1050 texture.cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1053 void VKAbstractModel::createCudaExportableImage(uint32_t width, uint32_t height, TextureEntry &texture)
const {
1055 "CUDA interop init: requesting exportable model texture {}x{} RGBA8 optimal-tiled OPAQUE_FD",
1058 VkExternalMemoryImageCreateInfo externalImageInfo{};
1059 externalImageInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
1060 externalImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
1062 VkImageCreateInfo imageInfo{};
1063 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1064 imageInfo.pNext = &externalImageInfo;
1065 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1066 imageInfo.extent.width = width;
1067 imageInfo.extent.height = height;
1068 imageInfo.extent.depth = 1;
1069 imageInfo.mipLevels = 1;
1070 imageInfo.arrayLayers = 1;
1071 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
1072 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
1073 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1074 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
1075 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1076 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1078 if (vkCreateImage(windowPtr->getDevice(), &imageInfo,
nullptr, &texture.image) != VK_SUCCESS) {
1079 throw mxvk::Exception(
"VKAbstractModel failed to create CUDA exportable texture image");
1082 VkMemoryRequirements requirements{};
1083 vkGetImageMemoryRequirements(windowPtr->getDevice(), texture.image, &requirements);
1085 VkExportMemoryAllocateInfo exportMemoryInfo{};
1086 exportMemoryInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
1087 exportMemoryInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
1089 VkMemoryAllocateInfo allocInfo{};
1090 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1091 allocInfo.pNext = &exportMemoryInfo;
1092 allocInfo.allocationSize = requirements.size;
1095 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
1096 if (vkAllocateMemory(windowPtr->getDevice(), &allocInfo,
nullptr, &texture.memory) != VK_SUCCESS) {
1097 throw mxvk::Exception(
"VKAbstractModel failed to allocate CUDA exportable texture memory");
1099 if (vkBindImageMemory(windowPtr->getDevice(), texture.image, texture.memory, 0) != VK_SUCCESS) {
1100 throw mxvk::Exception(
"VKAbstractModel failed to bind CUDA exportable texture memory");
1103 if (texture.memory != VK_NULL_HANDLE) {
1104 vkFreeMemory(windowPtr->getDevice(), texture.memory,
nullptr);
1105 texture.memory = VK_NULL_HANDLE;
1107 if (texture.image != VK_NULL_HANDLE) {
1108 vkDestroyImage(windowPtr->getDevice(), texture.image,
nullptr);
1109 texture.image = VK_NULL_HANDLE;
1111 texture.cudaExportMemorySize = 0;
1115 texture.width = width;
1116 texture.height = height;
1117 texture.cudaExportMemorySize = requirements.size;
1118 texture.cudaInteropUnavailableLogged =
false;
1119 texture.cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1121 "CUDA interop init: exportable model texture allocated (memorySize={} bytes, memoryType={}); optimal image memory is imported as cudaArray, not wrapped as pitched GpuMat",
1122 static_cast<unsigned long long>(requirements.size), allocInfo.memoryTypeIndex));
1125 bool VKAbstractModel::ensureTextureCudaInterop(TextureEntry &texture)
const {
1126 if (texture.cudaInteropEnabled) {
1129 if (windowPtr ==
nullptr || texture.memory == VK_NULL_HANDLE || texture.cudaExportMemorySize == 0) {
1130 if (!texture.cudaInteropUnavailableLogged) {
1131 logVKAbstractModelStep(
"CUDA interop init: model texture is not exportable; CPU staging fallback remains active");
1132 texture.cudaInteropUnavailableLogged =
true;
1136 if (vkGetMemoryFdKHR ==
nullptr) {
1137 if (!texture.cudaInteropUnavailableLogged) {
1139 texture.cudaInteropUnavailableLogged =
true;
1144 VkMemoryGetFdInfoKHR fdInfo{};
1145 fdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
1146 fdInfo.memory = texture.memory;
1147 fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
1150 const VkResult fdResult = vkGetMemoryFdKHR(windowPtr->getDevice(), &fdInfo, &memoryFd);
1151 if (fdResult != VK_SUCCESS) {
1152 if (!texture.cudaInteropUnavailableLogged) {
1153 logVKAbstractModelStep(std::format(
"CUDA interop init: vkGetMemoryFdKHR failed for model texture ({})",
static_cast<int>(fdResult)));
1154 texture.cudaInteropUnavailableLogged =
true;
1160 cudaExternalMemoryHandleDesc externalMemoryDesc{};
1161 externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd;
1162 externalMemoryDesc.handle.fd = memoryFd;
1163 externalMemoryDesc.size = texture.cudaExportMemorySize;
1165 cudaError_t cudaResult = cudaImportExternalMemory(&texture.cudaExternalMemory, &externalMemoryDesc);
1166 if (cudaResult != cudaSuccess) {
1168 if (!texture.cudaInteropUnavailableLogged) {
1169 logVKAbstractModelStep(std::format(
"CUDA interop init: cudaImportExternalMemory failed for model texture: {}",
1170 cudaGetErrorString(cudaResult)));
1171 texture.cudaInteropUnavailableLogged =
true;
1173 texture.cudaExternalMemory =
nullptr;
1176 logVKAbstractModelStep(std::format(
"CUDA interop init: imported model texture external memory into CUDA ({} bytes)",
1177 static_cast<unsigned long long>(texture.cudaExportMemorySize)));
1179 cudaExternalMemoryMipmappedArrayDesc arrayDesc{};
1180 arrayDesc.offset = 0;
1181 arrayDesc.formatDesc = cudaCreateChannelDesc<uchar4>();
1182 arrayDesc.extent = make_cudaExtent(
static_cast<size_t>(texture.width),
static_cast<size_t>(texture.height), 0);
1183 arrayDesc.flags = cudaArrayColorAttachment;
1184 arrayDesc.numLevels = 1;
1186 cudaResult = cudaExternalMemoryGetMappedMipmappedArray(&texture.cudaMipmappedArray, texture.cudaExternalMemory, &arrayDesc);
1187 if (cudaResult != cudaSuccess) {
1188 if (!texture.cudaInteropUnavailableLogged) {
1189 logVKAbstractModelStep(std::format(
"CUDA interop init: cudaExternalMemoryGetMappedMipmappedArray failed for model texture: {}",
1190 cudaGetErrorString(cudaResult)));
1191 texture.cudaInteropUnavailableLogged =
true;
1193 destroyTextureCudaInterop(texture);
1196 logVKAbstractModelStep(std::format(
"CUDA interop init: mapped model texture CUDA mipmapped array {}x{} uchar4",
1197 texture.width, texture.height));
1199 cudaResult = cudaGetMipmappedArrayLevel(&texture.cudaArray, texture.cudaMipmappedArray, 0);
1200 if (cudaResult != cudaSuccess) {
1201 if (!texture.cudaInteropUnavailableLogged) {
1202 logVKAbstractModelStep(std::format(
"CUDA interop init: cudaGetMipmappedArrayLevel failed for model texture: {}",
1203 cudaGetErrorString(cudaResult)));
1204 texture.cudaInteropUnavailableLogged =
true;
1206 destroyTextureCudaInterop(texture);
1210 texture.cudaInteropEnabled =
true;
1215 bool VKAbstractModel::transitionTextureForCudaWrite(TextureEntry &texture)
const {
1216 if (texture.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) {
1220 const VkImageLayout oldLayout = (texture.cudaImageLayout == VK_IMAGE_LAYOUT_UNDEFINED)
1221 ? VK_IMAGE_LAYOUT_UNDEFINED
1222 : texture.cudaImageLayout;
1223 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1225 VkImageMemoryBarrier barrier{};
1226 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1227 barrier.oldLayout = oldLayout;
1228 barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
1229 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1230 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1231 barrier.image = texture.image;
1232 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1233 barrier.subresourceRange.baseMipLevel = 0;
1234 barrier.subresourceRange.levelCount = 1;
1235 barrier.subresourceRange.baseArrayLayer = 0;
1236 barrier.subresourceRange.layerCount = 1;
1237 barrier.srcAccessMask = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) ? VK_ACCESS_SHADER_READ_BIT : 0;
1238 barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1240 const VkPipelineStageFlags srcStage = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
1241 ? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
1242 : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1243 vkCmdPipelineBarrier(commandBuffer, srcStage, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
1244 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1245 endSingleTimeCommands(commandBuffer);
1247 texture.cudaImageLayout = VK_IMAGE_LAYOUT_GENERAL;
1248 if (!texture.cudaWriteTransitionLogged) {
1250 texture.cudaWriteTransitionLogged =
true;
1255 bool VKAbstractModel::transitionTextureForShaderRead(TextureEntry &texture)
const {
1256 if (texture.cudaImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1260 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1261 VkImageMemoryBarrier barrier{};
1262 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1263 barrier.oldLayout = texture.cudaImageLayout;
1264 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1265 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1266 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1267 barrier.image = texture.image;
1268 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1269 barrier.subresourceRange.baseMipLevel = 0;
1270 barrier.subresourceRange.levelCount = 1;
1271 barrier.subresourceRange.baseArrayLayer = 0;
1272 barrier.subresourceRange.layerCount = 1;
1273 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1274 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1276 vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
1277 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1278 endSingleTimeCommands(commandBuffer);
1280 texture.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1281 if (!texture.cudaShaderTransitionLogged) {
1282 logVKAbstractModelStep(
"CUDA interop sync: model texture transitions GENERAL -> SHADER_READ_ONLY before sampling");
1283 texture.cudaShaderTransitionLogged =
true;
1288 void VKAbstractModel::recreatePrimaryTextureForCuda(TextureEntry &texture, uint32_t width, uint32_t height) {
1289 vkDeviceWaitIdle(windowPtr->getDevice());
1290 destroyTextureCudaInterop(texture);
1291 if (texture.view != VK_NULL_HANDLE) {
1292 vkDestroyImageView(windowPtr->getDevice(), texture.view,
nullptr);
1293 texture.view = VK_NULL_HANDLE;
1295 if (texture.image != VK_NULL_HANDLE) {
1296 vkDestroyImage(windowPtr->getDevice(), texture.image,
nullptr);
1297 texture.image = VK_NULL_HANDLE;
1299 if (texture.memory != VK_NULL_HANDLE) {
1300 vkFreeMemory(windowPtr->getDevice(), texture.memory,
nullptr);
1301 texture.memory = VK_NULL_HANDLE;
1304 createCudaExportableImage(width, height, texture);
1305 texture.view = createImageView(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1307 createDescriptorSets();
1310 bool VKAbstractModel::updatePrimaryTextureCudaHost(TextureEntry &texture,
const void *pixels,
1311 uint32_t width, uint32_t height, uint32_t pitch)
const {
1312 if (pixels ==
nullptr || width == 0 || height == 0) {
1315 const uint32_t rowBytes = width * 4U;
1316 if (pitch < rowBytes || texture.width != width || texture.height != height) {
1319 if (!ensureTextureCudaInterop(texture) || !transitionTextureForCudaWrite(texture)) {
1323 if (!texture.cudaUploadLogged) {
1325 "CUDA interop upload: copying {}x{} host RGBA pixels to optimal-tiled Vulkan model texture via cudaArray (source pitch={} bytes)",
1326 width, height, pitch));
1327 texture.cudaUploadLogged =
true;
1330 const cudaError_t cudaResult = cudaMemcpy2DToArray(
1331 texture.cudaArray, 0, 0, pixels, pitch,
1332 static_cast<size_t>(rowBytes),
static_cast<size_t>(height),
1333 cudaMemcpyHostToDevice);
1334 if (cudaResult != cudaSuccess) {
1335 logVKAbstractModelStep(std::format(
"CUDA interop model host texture copy failed: {}", cudaGetErrorString(cudaResult)));
1339 return transitionTextureForShaderRead(texture);
1342 bool VKAbstractModel::updatePrimaryTextureCuda(
const cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream) {
1343 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1346 if (rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols <= 0 || rgba.rows <= 0) {
1350 if (textures.empty()) {
1351 textures.push_back(TextureEntry{});
1354 TextureEntry &texture = textures[0];
1355 const uint32_t uploadWidth =
static_cast<uint32_t
>(rgba.cols);
1356 const uint32_t uploadHeight =
static_cast<uint32_t
>(rgba.rows);
1357 if (texture.image == VK_NULL_HANDLE || texture.memory == VK_NULL_HANDLE ||
1358 texture.view == VK_NULL_HANDLE || texture.width != uploadWidth ||
1359 texture.height != uploadHeight || texture.cudaExportMemorySize == 0) {
1361 recreatePrimaryTextureForCuda(texture, uploadWidth, uploadHeight);
1362 }
catch (
const std::exception &ex) {
1363 if (!texture.cudaInteropUnavailableLogged) {
1364 logVKAbstractModelStep(std::format(
"CUDA exportable model texture unavailable: {}; CPU staging fallback remains active", ex.what()));
1365 texture.cudaInteropUnavailableLogged =
true;
1371 if (!ensureTextureCudaInterop(texture) || !transitionTextureForCudaWrite(texture)) {
1375 cudaStream_t cudaStream = cuda_stream_handle(stream);
1376 if (!texture.cudaUploadLogged) {
1378 "CUDA interop upload: copying {}x{} RGBA GpuMat to optimal-tiled Vulkan model texture via cudaArray (source pitch={} bytes, copy row bytes={})",
1379 rgba.cols, rgba.rows,
1380 static_cast<unsigned long long>(rgba.step),
1381 static_cast<unsigned long long>(
static_cast<size_t>(rgba.cols) * 4U)));
1382 texture.cudaUploadLogged =
true;
1385 cudaError_t cudaResult = cudaMemcpy2DToArrayAsync(
1386 texture.cudaArray, 0, 0, rgba.ptr(), rgba.step,
1387 static_cast<size_t>(rgba.cols) * 4U,
static_cast<size_t>(rgba.rows),
1388 cudaMemcpyDeviceToDevice, cudaStream);
1389 if (cudaResult != cudaSuccess) {
1390 logVKAbstractModelStep(std::format(
"CUDA interop model texture copy failed: {}", cudaGetErrorString(cudaResult)));
1394 cudaResult = cudaStreamSynchronize(cudaStream);
1395 if (cudaResult != cudaSuccess) {
1396 logVKAbstractModelStep(std::format(
"CUDA interop model texture sync failed: {}", cudaGetErrorString(cudaResult)));
1400 return transitionTextureForShaderRead(texture);
1404 VkImageView VKAbstractModel::createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags)
const {
1405 VkImageViewCreateInfo viewInfo{};
1406 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1407 viewInfo.image = image;
1408 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1409 viewInfo.format = format;
1410 viewInfo.subresourceRange.aspectMask = aspectFlags;
1411 viewInfo.subresourceRange.baseMipLevel = 0;
1412 viewInfo.subresourceRange.levelCount = 1;
1413 viewInfo.subresourceRange.baseArrayLayer = 0;
1414 viewInfo.subresourceRange.layerCount = 1;
1416 VkImageView imageView = VK_NULL_HANDLE;
1417 if (vkCreateImageView(windowPtr->getDevice(), &viewInfo,
nullptr, &imageView) != VK_SUCCESS) {
1418 throw mxvk::Exception(
"VKAbstractModel failed to create image view");
1423 void VKAbstractModel::transitionImageLayout(VkImage image, VkFormat, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
1424 VkCommandBuffer cmd = beginSingleTimeCommands();
1426 VkImageMemoryBarrier barrier{};
1427 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1428 barrier.oldLayout = oldLayout;
1429 barrier.newLayout = newLayout;
1430 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1431 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1432 barrier.image = image;
1433 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1434 barrier.subresourceRange.baseMipLevel = 0;
1435 barrier.subresourceRange.levelCount = 1;
1436 barrier.subresourceRange.baseArrayLayer = 0;
1437 barrier.subresourceRange.layerCount = 1;
1439 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1440 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1442 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1443 barrier.srcAccessMask = 0;
1444 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1445 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1446 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1447 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1448 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1449 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1450 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1451 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1452 }
else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1453 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
1454 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1455 sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1456 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1457 }
else if (oldLayout == VK_IMAGE_LAYOUT_GENERAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1458 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1459 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1460 sourceStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
1461 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1464 vkCmdPipelineBarrier(cmd,
1472 endSingleTimeCommands(cmd);
1475 void VKAbstractModel::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
1476 VkCommandBuffer cmd = beginSingleTimeCommands();
1478 VkBufferImageCopy region{};
1479 region.bufferOffset = 0;
1480 region.bufferRowLength = 0;
1481 region.bufferImageHeight = 0;
1482 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1483 region.imageSubresource.mipLevel = 0;
1484 region.imageSubresource.baseArrayLayer = 0;
1485 region.imageSubresource.layerCount = 1;
1486 region.imageOffset = {0, 0, 0};
1487 region.imageExtent = {width, height, 1};
1489 vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
1490 endSingleTimeCommands(cmd);
1493 void VKAbstractModel::createTextureSampler() {
1494 if (textureSampler != VK_NULL_HANDLE) {
1498 VkPhysicalDeviceFeatures deviceFeatures{};
1499 vkGetPhysicalDeviceFeatures(windowPtr->getPhysicalDevice(), &deviceFeatures);
1500 VkPhysicalDeviceProperties deviceProperties{};
1501 vkGetPhysicalDeviceProperties(windowPtr->getPhysicalDevice(), &deviceProperties);
1502 const bool anisotropySupported = deviceFeatures.samplerAnisotropy == VK_TRUE;
1503 const float anisotropyLevel = anisotropySupported
1504 ? std::min(8.0f, deviceProperties.limits.maxSamplerAnisotropy)
1507 VkSamplerCreateInfo samplerInfo{};
1508 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1509 samplerInfo.magFilter = VK_FILTER_LINEAR;
1510 samplerInfo.minFilter = VK_FILTER_LINEAR;
1511 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1512 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1513 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1514 samplerInfo.anisotropyEnable = anisotropySupported ? VK_TRUE : VK_FALSE;
1515 samplerInfo.maxAnisotropy = anisotropyLevel;
1516 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1517 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1518 samplerInfo.compareEnable = VK_FALSE;
1519 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1520 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1522 if (vkCreateSampler(windowPtr->getDevice(), &samplerInfo,
nullptr, &textureSampler) != VK_SUCCESS) {
1523 throw mxvk::Exception(
"VKAbstractModel failed to create texture sampler");
1527 void VKAbstractModel::createDescriptorSetLayout() {
1528 if (descriptorSetLayout != VK_NULL_HANDLE) {
1532 VkDescriptorSetLayoutBinding samplerBinding{};
1533 samplerBinding.binding = 0;
1534 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1535 samplerBinding.descriptorCount = 1;
1536 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
1538 VkDescriptorSetLayoutBinding fragmentBinding{};
1539 fragmentBinding.binding = 1;
1540 fragmentBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1541 fragmentBinding.descriptorCount = 1;
1542 fragmentBinding.stageFlags = extendedFragmentUniformsEnabled ? VK_SHADER_STAGE_FRAGMENT_BIT : VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
1544 VkDescriptorSetLayoutBinding modelBinding{};
1545 modelBinding.binding = 2;
1546 modelBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1547 modelBinding.descriptorCount = 1;
1548 modelBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1550 const std::array<VkDescriptorSetLayoutBinding, 3> bindings = {samplerBinding, fragmentBinding, modelBinding};
1552 VkDescriptorSetLayoutCreateInfo layoutInfo{};
1553 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1554 layoutInfo.bindingCount = extendedFragmentUniformsEnabled ?
static_cast<uint32_t
>(bindings.size()) : 2U;
1555 layoutInfo.pBindings = bindings.data();
1557 if (vkCreateDescriptorSetLayout(windowPtr->getDevice(), &layoutInfo,
nullptr, &descriptorSetLayout) != VK_SUCCESS) {
1558 throw mxvk::Exception(
"VKAbstractModel failed to create descriptor set layout");
1562 void VKAbstractModel::createUniformBuffers() {
1563 destroyUniformBuffers();
1565 const size_t frameCount = windowPtr->getSwapchainImageCount();
1566 if (frameCount == 0) {
1570 uniformBuffers.resize(frameCount, VK_NULL_HANDLE);
1571 uniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
1572 uniformBuffersMapped.resize(frameCount,
nullptr);
1573 if (extendedFragmentUniformsEnabled) {
1574 fragmentUniformBuffers.resize(frameCount, VK_NULL_HANDLE);
1575 fragmentUniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
1576 fragmentUniformBuffersMapped.resize(frameCount,
nullptr);
1579 for (
size_t i = 0; i < frameCount; ++i) {
1580 createBuffer(
sizeof(UniformBufferObject),
1581 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
1582 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1583 uniformBuffers[i], uniformBufferMemory[i]);
1584 vkMapMemory(windowPtr->getDevice(), uniformBufferMemory[i], 0,
sizeof(UniformBufferObject), 0, &uniformBuffersMapped[i]);
1585 if (extendedFragmentUniformsEnabled) {
1586 createBuffer(
sizeof(ModelFragmentUniforms),
1587 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
1588 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
1589 fragmentUniformBuffers[i], fragmentUniformBufferMemory[i]);
1590 vkMapMemory(windowPtr->getDevice(), fragmentUniformBufferMemory[i], 0,
sizeof(ModelFragmentUniforms), 0, &fragmentUniformBuffersMapped[i]);
1595 void VKAbstractModel::destroyUniformBuffers() {
1596 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1597 uniformBuffers.clear();
1598 uniformBufferMemory.clear();
1599 uniformBuffersMapped.clear();
1600 fragmentUniformBuffers.clear();
1601 fragmentUniformBufferMemory.clear();
1602 fragmentUniformBuffersMapped.clear();
1606 for (
size_t i = 0; i < uniformBuffers.size(); ++i) {
1607 if (uniformBuffersMapped[i] !=
nullptr) {
1608 vkUnmapMemory(windowPtr->getDevice(), uniformBufferMemory[i]);
1609 uniformBuffersMapped[i] =
nullptr;
1611 if (uniformBuffers[i] != VK_NULL_HANDLE) {
1612 vkDestroyBuffer(windowPtr->getDevice(), uniformBuffers[i],
nullptr);
1614 if (uniformBufferMemory[i] != VK_NULL_HANDLE) {
1615 vkFreeMemory(windowPtr->getDevice(), uniformBufferMemory[i],
nullptr);
1619 uniformBuffers.clear();
1620 uniformBufferMemory.clear();
1621 uniformBuffersMapped.clear();
1623 for (
size_t i = 0; i < fragmentUniformBuffers.size(); ++i) {
1624 if (fragmentUniformBuffersMapped[i] !=
nullptr) {
1625 vkUnmapMemory(windowPtr->getDevice(), fragmentUniformBufferMemory[i]);
1627 if (fragmentUniformBuffers[i] != VK_NULL_HANDLE) {
1628 vkDestroyBuffer(windowPtr->getDevice(), fragmentUniformBuffers[i],
nullptr);
1630 if (fragmentUniformBufferMemory[i] != VK_NULL_HANDLE) {
1631 vkFreeMemory(windowPtr->getDevice(), fragmentUniformBufferMemory[i],
nullptr);
1634 fragmentUniformBuffers.clear();
1635 fragmentUniformBufferMemory.clear();
1636 fragmentUniformBuffersMapped.clear();
1639 void VKAbstractModel::createDescriptorPool() {
1640 const uint32_t textureCount = std::max<uint32_t>(1U,
static_cast<uint32_t
>(textures.size()));
1641 const uint32_t frameCount =
static_cast<uint32_t
>(windowPtr->getSwapchainImageCount());
1642 const uint32_t requiredSetCount = textureCount * frameCount;
1643 const uint32_t setCount = std::max(requiredSetCount, descriptorPoolSetCapacity);
1645 std::array<VkDescriptorPoolSize, 2> poolSizes{};
1646 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1647 poolSizes[0].descriptorCount = setCount;
1648 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1649 poolSizes[1].descriptorCount = extendedFragmentUniformsEnabled ? setCount * 2U : setCount;
1651 VkDescriptorPoolCreateInfo poolInfo{};
1652 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1653 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
1654 poolInfo.pPoolSizes = poolSizes.data();
1655 poolInfo.maxSets = setCount;
1656 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1658 if (vkCreateDescriptorPool(windowPtr->getDevice(), &poolInfo,
nullptr, &descriptorPool) != VK_SUCCESS) {
1659 throw mxvk::Exception(
"VKAbstractModel failed to create descriptor pool");
1661 descriptorPoolSetCapacity = setCount;
1664 void VKAbstractModel::createDescriptorSets() {
1665 const size_t textureCount = std::max<size_t>(1, textures.size());
1666 const size_t frameCount = windowPtr->getSwapchainImageCount();
1667 const size_t setCount = textureCount * frameCount;
1669 if (descriptorSetLayout == VK_NULL_HANDLE || frameCount == 0 || uniformBuffers.size() < frameCount || textures.empty() ||
1670 (extendedFragmentUniformsEnabled && fragmentUniformBuffers.size() < frameCount)) {
1674 if (descriptorPool == VK_NULL_HANDLE || descriptorPoolSetCapacity < setCount) {
1675 descriptorSets.clear();
1676 if (descriptorPool != VK_NULL_HANDLE) {
1677 vkDestroyDescriptorPool(windowPtr->getDevice(), descriptorPool,
nullptr);
1678 descriptorPool = VK_NULL_HANDLE;
1679 descriptorPoolSetCapacity = 0;
1681 createDescriptorPool();
1684 const bool needsAllocation = descriptorSets.size() != setCount ||
1685 std::any_of(descriptorSets.begin(), descriptorSets.end(), [](VkDescriptorSet set) {
1686 return set == VK_NULL_HANDLE;
1689 if (needsAllocation) {
1690 std::vector<VkDescriptorSetLayout> layouts(setCount, descriptorSetLayout);
1692 VkDescriptorSetAllocateInfo allocInfo{};
1693 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1694 allocInfo.descriptorPool = descriptorPool;
1695 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(setCount);
1696 allocInfo.pSetLayouts = layouts.data();
1698 descriptorSets.assign(setCount, VK_NULL_HANDLE);
1699 const VkResult allocateResult = vkAllocateDescriptorSets(windowPtr->getDevice(), &allocInfo, descriptorSets.data());
1700 if (allocateResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocateResult == VK_ERROR_FRAGMENTED_POOL) {
1701 vkDestroyDescriptorPool(windowPtr->getDevice(), descriptorPool,
nullptr);
1702 descriptorPool = VK_NULL_HANDLE;
1703 descriptorPoolSetCapacity =
static_cast<uint32_t
>(std::max<size_t>(setCount * 2U, 1U));
1704 descriptorSets.clear();
1705 createDescriptorPool();
1706 allocInfo.descriptorPool = descriptorPool;
1707 descriptorSets.assign(setCount, VK_NULL_HANDLE);
1708 if (vkAllocateDescriptorSets(windowPtr->getDevice(), &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
1709 throw mxvk::Exception(
"VKAbstractModel failed to allocate descriptor sets");
1711 }
else if (allocateResult != VK_SUCCESS) {
1712 throw mxvk::Exception(
"VKAbstractModel failed to allocate descriptor sets");
1716 for (
size_t frame = 0; frame < frameCount; ++frame) {
1717 VkDescriptorBufferInfo bufferInfo{};
1718 bufferInfo.buffer = uniformBuffers[frame];
1719 bufferInfo.offset = 0;
1720 bufferInfo.range =
sizeof(UniformBufferObject);
1721 VkDescriptorBufferInfo fragmentBufferInfo{};
1722 if (extendedFragmentUniformsEnabled) {
1723 fragmentBufferInfo.buffer = fragmentUniformBuffers[frame];
1724 fragmentBufferInfo.offset = 0;
1725 fragmentBufferInfo.range =
sizeof(ModelFragmentUniforms);
1728 for (
size_t tex = 0; tex < textureCount; ++tex) {
1729 const size_t setIndex = frame * textureCount + tex;
1730 const TextureEntry &entry = textures[tex];
1732 VkDescriptorImageInfo imageInfo{};
1733 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1734 imageInfo.imageView = entry.view;
1735 imageInfo.sampler = textureSampler;
1737 std::array<VkWriteDescriptorSet, 3> writes{};
1738 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1739 writes[0].dstSet = descriptorSets[setIndex];
1740 writes[0].dstBinding = 0;
1741 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1742 writes[0].descriptorCount = 1;
1743 writes[0].pImageInfo = &imageInfo;
1745 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1746 writes[1].dstSet = descriptorSets[setIndex];
1747 writes[1].dstBinding = 1;
1748 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1749 writes[1].descriptorCount = 1;
1750 writes[1].pBufferInfo = extendedFragmentUniformsEnabled ? &fragmentBufferInfo : &bufferInfo;
1752 if (extendedFragmentUniformsEnabled) {
1753 writes[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1754 writes[2].dstSet = descriptorSets[setIndex];
1755 writes[2].dstBinding = 2;
1756 writes[2].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1757 writes[2].descriptorCount = 1;
1758 writes[2].pBufferInfo = &bufferInfo;
1761 const uint32_t writeCount = extendedFragmentUniformsEnabled ?
static_cast<uint32_t
>(writes.size()) : 2U;
1762 vkUpdateDescriptorSets(windowPtr->getDevice(), writeCount, writes.data(), 0,
nullptr);
1767 void VKAbstractModel::updateTextureDescriptor(
1768 VkDescriptorSet descriptorSet, VkImageView imageView)
const {
1769 if (descriptorSet == VK_NULL_HANDLE || imageView == VK_NULL_HANDLE ||
1770 textureSampler == VK_NULL_HANDLE) {
1773 const VkDescriptorImageInfo imageInfo{
1774 .sampler = textureSampler,
1775 .imageView = imageView,
1776 .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
1778 const VkWriteDescriptorSet write{
1779 .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
1780 .dstSet = descriptorSet,
1782 .descriptorCount = 1,
1783 .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
1784 .pImageInfo = &imageInfo,
1786 vkUpdateDescriptorSets(windowPtr->getDevice(), 1, &write, 0,
nullptr);
1789 void VKAbstractModel::createPipelines() {
1792 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1795 if (descriptorSetLayout == VK_NULL_HANDLE) {
1798 if (vertexShaderPath.empty() || fragmentShaderPath.empty()) {
1801 if (windowPtr->getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
1805 const std::vector<char> vertBytes =
mxvk::load_spv(vertexShaderPath);
1806 const std::vector<char> fragBytes =
mxvk::load_spv(fragmentShaderPath);
1809 VkShaderModule fragModule = VK_NULL_HANDLE;
1814 VkPipelineShaderStageCreateInfo vertStage{};
1815 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1816 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1817 vertStage.module = vertModule;
1818 vertStage.pName =
"main";
1820 VkPipelineShaderStageCreateInfo fragStage{};
1821 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1822 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1823 fragStage.module = fragModule;
1824 fragStage.pName =
"main";
1826 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
1828 VkVertexInputBindingDescription binding{};
1829 binding.binding = 0;
1830 binding.stride =
sizeof(VKVertex);
1831 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1833 std::array<VkVertexInputAttributeDescription, 3> attrs{};
1834 attrs[0].binding = 0;
1835 attrs[0].location = 0;
1836 attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
1837 attrs[0].offset = offsetof(VKVertex, pos);
1838 attrs[1].binding = 0;
1839 attrs[1].location = 1;
1840 attrs[1].format = VK_FORMAT_R32G32_SFLOAT;
1841 attrs[1].offset = offsetof(VKVertex, texCoord);
1842 attrs[2].binding = 0;
1843 attrs[2].location = 2;
1844 attrs[2].format = VK_FORMAT_R32G32B32_SFLOAT;
1845 attrs[2].offset = offsetof(VKVertex, normal);
1847 VkPipelineVertexInputStateCreateInfo vertexInput{};
1848 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1849 vertexInput.vertexBindingDescriptionCount = 1;
1850 vertexInput.pVertexBindingDescriptions = &binding;
1851 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrs.size());
1852 vertexInput.pVertexAttributeDescriptions = attrs.data();
1854 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1855 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1856 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1857 inputAssembly.primitiveRestartEnable = VK_FALSE;
1859 const std::array<VkDynamicState, 2> dynamicStates = {
1860 VK_DYNAMIC_STATE_VIEWPORT,
1861 VK_DYNAMIC_STATE_SCISSOR,
1863 VkPipelineDynamicStateCreateInfo dynamicInfo{};
1864 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1865 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1866 dynamicInfo.pDynamicStates = dynamicStates.data();
1868 VkPipelineViewportStateCreateInfo viewportState{};
1869 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1870 viewportState.viewportCount = 1;
1871 viewportState.scissorCount = 1;
1873 VkPipelineRasterizationStateCreateInfo rasterizer{};
1874 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1875 rasterizer.depthClampEnable = VK_FALSE;
1876 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1877 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1878 rasterizer.cullMode = backfaceCullingEnabled ? VK_CULL_MODE_BACK_BIT : VK_CULL_MODE_NONE;
1879 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
1880 rasterizer.depthBiasEnable = VK_FALSE;
1881 rasterizer.lineWidth = 1.0f;
1883 VkPipelineMultisampleStateCreateInfo multisample{};
1884 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1885 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1886 multisample.sampleShadingEnable = VK_FALSE;
1888 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1889 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1890 depthStencil.depthTestEnable = VK_TRUE;
1891 depthStencil.depthWriteEnable = alphaBlendingEnabled ? VK_FALSE : VK_TRUE;
1892 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1894 VkPipelineColorBlendAttachmentState blendAttachment{};
1895 blendAttachment.colorWriteMask =
1896 VK_COLOR_COMPONENT_R_BIT |
1897 VK_COLOR_COMPONENT_G_BIT |
1898 VK_COLOR_COMPONENT_B_BIT |
1899 VK_COLOR_COMPONENT_A_BIT;
1900 blendAttachment.blendEnable = alphaBlendingEnabled ? VK_TRUE : VK_FALSE;
1901 blendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1902 blendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1903 blendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
1904 blendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1905 blendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1906 blendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
1908 VkPipelineColorBlendStateCreateInfo colorBlend{};
1909 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1910 colorBlend.logicOpEnable = VK_FALSE;
1911 colorBlend.attachmentCount = 1;
1912 colorBlend.pAttachments = &blendAttachment;
1914 VkPipelineLayoutCreateInfo layoutInfo{};
1915 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1916 layoutInfo.setLayoutCount = 1;
1917 layoutInfo.pSetLayouts = &descriptorSetLayout;
1918 const VkPushConstantRange vertexPushConstantRange{
1919 .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
1921 .size =
sizeof(ModelPushConstants),
1923 const VkPushConstantRange fragmentPushConstantRange{
1924 .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
1926 .size =
sizeof(ModelFragmentPushConstants),
1928 layoutInfo.pushConstantRangeCount = 1U;
1929 layoutInfo.pPushConstantRanges = extendedFragmentUniformsEnabled
1930 ? &fragmentPushConstantRange
1931 : &vertexPushConstantRange;
1933 if (vkCreatePipelineLayout(windowPtr->getDevice(), &layoutInfo,
nullptr, &pipelineLayout) != VK_SUCCESS) {
1934 throw mxvk::Exception(
"VKAbstractModel failed to create pipeline layout");
1937 const VkFormat colorFormat = windowPtr->getSwapchainFormat();
1938 const VkFormat depthFormat = windowPtr->getDepthFormat();
1939 VkPipelineRenderingCreateInfo renderingInfo{};
1940 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1941 renderingInfo.colorAttachmentCount = 1;
1942 renderingInfo.pColorAttachmentFormats = &colorFormat;
1943 if (depthFormat != VK_FORMAT_UNDEFINED) {
1944 renderingInfo.depthAttachmentFormat = depthFormat;
1947 VkGraphicsPipelineCreateInfo pipelineInfo{};
1948 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1949 pipelineInfo.pNext = &renderingInfo;
1950 pipelineInfo.stageCount =
static_cast<uint32_t
>(stages.size());
1951 pipelineInfo.pStages = stages.data();
1952 pipelineInfo.pVertexInputState = &vertexInput;
1953 pipelineInfo.pInputAssemblyState = &inputAssembly;
1954 pipelineInfo.pViewportState = &viewportState;
1955 pipelineInfo.pRasterizationState = &rasterizer;
1956 pipelineInfo.pMultisampleState = &multisample;
1957 pipelineInfo.pDepthStencilState = &depthStencil;
1958 pipelineInfo.pColorBlendState = &colorBlend;
1959 pipelineInfo.pDynamicState = &dynamicInfo;
1960 pipelineInfo.layout = pipelineLayout;
1961 pipelineInfo.renderPass = VK_NULL_HANDLE;
1962 pipelineInfo.subpass = 0;
1964 if (vkCreateGraphicsPipelines(windowPtr->getDevice(), windowPtr->getPipelineCache(), 1, &pipelineInfo,
nullptr, &pipelineFill) != VK_SUCCESS) {
1965 throw mxvk::Exception(
"VKAbstractModel failed to create fill pipeline");
1971 pipelineWireframe = VK_NULL_HANDLE;
1973 if (fragModule != VK_NULL_HANDLE) {
1974 vkDestroyShaderModule(windowPtr->getDevice(), fragModule,
nullptr);
1976 vkDestroyShaderModule(windowPtr->getDevice(), vertModule,
nullptr);
1980 vkDestroyShaderModule(windowPtr->getDevice(), fragModule,
nullptr);
1981 vkDestroyShaderModule(windowPtr->getDevice(), vertModule,
nullptr);
1984 void VKAbstractModel::destroyPipelines() {
1985 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1986 pipelineFill = VK_NULL_HANDLE;
1987 pipelineWireframe = VK_NULL_HANDLE;
1988 pipelineLayout = VK_NULL_HANDLE;
1992 if (pipelineFill != VK_NULL_HANDLE) {
1994 vkDestroyPipeline(windowPtr->getDevice(), pipelineFill,
nullptr);
1995 pipelineFill = VK_NULL_HANDLE;
1997 if (pipelineWireframe != VK_NULL_HANDLE) {
1999 vkDestroyPipeline(windowPtr->getDevice(), pipelineWireframe,
nullptr);
2000 pipelineWireframe = VK_NULL_HANDLE;
2002 if (pipelineLayout != VK_NULL_HANDLE) {
2004 vkDestroyPipelineLayout(windowPtr->getDevice(), pipelineLayout,
nullptr);
2005 pipelineLayout = VK_NULL_HANDLE;
2009 void VKAbstractModel::destroyDescriptors() {
2010 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
2011 descriptorSets.clear();
2012 descriptorPool = VK_NULL_HANDLE;
2013 descriptorSetLayout = VK_NULL_HANDLE;
2014 destroyUniformBuffers();
2018 descriptorSets.clear();
2019 if (descriptorPool != VK_NULL_HANDLE) {
2021 vkDestroyDescriptorPool(windowPtr->getDevice(), descriptorPool,
nullptr);
2022 descriptorPool = VK_NULL_HANDLE;
2024 if (descriptorSetLayout != VK_NULL_HANDLE) {
2026 vkDestroyDescriptorSetLayout(windowPtr->getDevice(), descriptorSetLayout,
nullptr);
2027 descriptorSetLayout = VK_NULL_HANDLE;
2030 destroyUniformBuffers();
2033 void VKAbstractModel::destroyTextures() {
2034 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
2036 textureSampler = VK_NULL_HANDLE;
2040 for (TextureEntry &tex : textures) {
2042 destroyTextureCudaInterop(tex);
2044 if (tex.view != VK_NULL_HANDLE) {
2046 vkDestroyImageView(windowPtr->getDevice(), tex.view,
nullptr);
2048 if (tex.image != VK_NULL_HANDLE) {
2050 vkDestroyImage(windowPtr->getDevice(), tex.image,
nullptr);
2052 if (tex.memory != VK_NULL_HANDLE) {
2054 vkFreeMemory(windowPtr->getDevice(), tex.memory,
nullptr);
2059 if (textureSampler != VK_NULL_HANDLE) {
2061 vkDestroySampler(windowPtr->getDevice(), textureSampler,
nullptr);
2062 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 renderWithExternalTexture(VkCommandBuffer cmd, uint32_t imageIndex, VkImageView textureView, const UniformBufferObject &ubo, bool wireframe=false)
Render using a non-owning shader-readable image view as texture slot zero.
void enableExtendedFragmentUniforms()
Use binding 1 for fragment uniforms and binding 2 for model transforms.
bool updatePrimaryTexture(const void *pixels, int width, int height, int pitch=0)
Upload raw RGBA pixels into the primary model texture.
void setFragmentPushConstants(const ModelFragmentPushConstants &constants)
Set sprite-compatible push constants used by custom fragment shaders.
Main Vulkan window wrapper for MXVK.
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.