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" || token ==
"-clamp" || token ==
"-imfchan" || token ==
"-type") {
41 }
else if (token ==
"-mm") {
44 }
else if (token ==
"-o" || token ==
"-s" || token ==
"-t") {
48 }
else if (token ==
"-bm" || token ==
"-boost" || token ==
"-texres") {
54 if (!texturePath.empty()) {
62 [[nodiscard]] std::string
resolveTexturePath(
const std::string &textureBasePath,
const std::string &texturePath) {
63 if (texturePath.empty()) {
67 std::filesystem::path resolvedPath(texturePath);
68 if (resolvedPath.is_absolute()) {
69 resolvedPath = resolvedPath.filename();
72 if (textureBasePath.empty()) {
73 return resolvedPath.string();
76 return (std::filesystem::path(textureBasePath) / resolvedPath).string();
80 void VKAbstractModel::load(
VK_Window *targetWindow,
const std::string &modelPath,
const std::string &textureManifestPath,
const std::string &textureBasePath,
float scale) {
81 if (targetWindow ==
nullptr) {
84 if (modelPath.empty()) {
88 logVKAbstractModelStep(
"creation begin: " + modelPath,
true);
90 windowPtr = targetWindow;
91 if (!windowPtr->ensureRenderResources()) {
92 throw mxvk::Exception(
"VKAbstractModel::load failed because render resources are not ready");
95 obj.load(modelPath, scale);
96 obj.upload(windowPtr->getDevice(), windowPtr->getPhysicalDevice(), windowPtr->getCommandPool(), windowPtr->getGraphicsQueue());
97 computeBoundsAndScale();
98 logVKAbstractModelStep(
"mesh upload complete",
true);
101 if (!textureManifestPath.empty()) {
102 loadTextures(textureManifestPath, textureBasePath);
104 loadTexturesFromMTL(textureBasePath.empty() ? std::filesystem::path(modelPath).parent_path().string() : textureBasePath);
106 if (textures.empty()) {
107 createFallbackTexture();
108 logVKAbstractModelStep(
"using fallback texture",
true);
110 logVKAbstractModelStep(
"textures ready: " + std::to_string(textures.size()),
true);
112 createTextureSampler();
113 createDescriptorSetLayout();
114 createUniformBuffers();
115 createDescriptorPool();
116 createDescriptorSets();
118 logVKAbstractModelStep(
"creation complete",
true);
122 if (targetWindow ==
nullptr) {
123 throw mxvk::Exception(
"VKAbstractModel::load requires a valid window");
126 windowPtr = targetWindow;
127 if (!windowPtr->ensureRenderResources()) {
128 throw mxvk::Exception(
"VKAbstractModel::load failed because render resources are not ready");
131 obj = std::move(
model);
132 obj.upload(windowPtr->getDevice(), windowPtr->getPhysicalDevice(), windowPtr->getCommandPool(), windowPtr->getGraphicsQueue());
133 computeBoundsAndScale();
134 logVKAbstractModelStep(
"mesh upload complete (prepared)",
true);
137 if (!textureManifestPath.empty()) {
138 loadTextures(textureManifestPath, textureBasePath);
139 }
else if (!obj.mtlLibPath().empty()) {
140 loadTexturesFromMTL(textureBasePath.empty() ? std::filesystem::path(obj.mtlLibPath()).parent_path().string() : textureBasePath);
142 createFallbackTexture();
143 logVKAbstractModelStep(
"using fallback texture",
true);
145 if (textures.empty()) {
146 createFallbackTexture();
147 logVKAbstractModelStep(
"using fallback texture",
true);
149 logVKAbstractModelStep(
"textures ready: " + std::to_string(textures.size()),
true);
151 createTextureSampler();
152 createDescriptorSetLayout();
153 createUniformBuffers();
154 createDescriptorPool();
155 createDescriptorSets();
157 logVKAbstractModelStep(
"creation complete",
true);
161 if (targetWindow ==
nullptr) {
162 throw mxvk::Exception(
"VKAbstractModel::setShaders requires a valid window");
165 windowPtr = targetWindow;
166 vertexShaderPath = vertSpv;
167 fragmentShaderPath = fragSpv;
168 logVKAbstractModelStep(
"setShaders",
true);
173 if (backfaceCullingEnabled == enabled) {
177 backfaceCullingEnabled = enabled;
178 if (windowPtr !=
nullptr) {
184 if (alphaBlendingEnabled == enabled) {
188 alphaBlendingEnabled = enabled;
189 if (windowPtr !=
nullptr) {
195 if (colorAttachmentFormat == format) {
198 colorAttachmentFormat = format;
199 if (windowPtr !=
nullptr) {
205 if (imageIndex >= uniformBuffersMapped.size()) {
208 if (uniformBuffersMapped[imageIndex] ==
nullptr) {
216 if (windowPtr !=
nullptr) {
217 throw mxvk::Exception(
"VKAbstractModel::enableExtendedFragmentUniforms must be called before load");
219 extendedFragmentUniformsEnabled =
true;
223 if (imageIndex >= fragmentUniformBuffersMapped.size() || fragmentUniformBuffersMapped[imageIndex] ==
nullptr) {
232 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
235 if (pixels ==
nullptr || width <= 0 || height <= 0) {
239 const uint32_t uploadWidth =
static_cast<uint32_t
>(width);
240 const uint32_t uploadHeight =
static_cast<uint32_t
>(height);
241 const uint32_t srcRowBytes =
static_cast<uint32_t
>(pitch > 0 ? pitch : width * 4);
242 const uint32_t tightRowBytes = uploadWidth * 4U;
243 if (srcRowBytes < tightRowBytes) {
247 if (textures.empty()) {
248 createFallbackTexture();
249 createDescriptorSets();
252 TextureEntry &texture = textures[0];
253 if (texture.image == VK_NULL_HANDLE || texture.memory == VK_NULL_HANDLE || texture.view == VK_NULL_HANDLE) {
257 bool recreatedTexture =
false;
258 if (texture.width != uploadWidth || texture.height != uploadHeight) {
259 vkDeviceWaitIdle(windowPtr->getDevice());
262 destroyTextureCudaInterop(texture);
264 if (texture.view != VK_NULL_HANDLE) {
265 vkDestroyImageView(windowPtr->getDevice(), texture.view,
nullptr);
267 if (texture.image != VK_NULL_HANDLE) {
268 vkDestroyImage(windowPtr->getDevice(), texture.image,
nullptr);
270 if (texture.memory != VK_NULL_HANDLE) {
271 vkFreeMemory(windowPtr->getDevice(), texture.memory,
nullptr);
274 texture.view = VK_NULL_HANDLE;
275 texture.image = VK_NULL_HANDLE;
276 texture.memory = VK_NULL_HANDLE;
278 createTextureImage(uploadWidth, uploadHeight, texture);
280 texture.view = createImageView(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
281 texture.width = uploadWidth;
282 texture.height = uploadHeight;
283 recreatedTexture =
true;
285 createDescriptorSets();
289 if (updatePrimaryTextureCudaHost(texture, pixels, uploadWidth, uploadHeight, srcRowBytes)) {
294 const VkDeviceSize stagingSize =
static_cast<VkDeviceSize
>(tightRowBytes) * uploadHeight;
295 VkBuffer stagingBuffer = VK_NULL_HANDLE;
296 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
297 createBuffer(stagingSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
299 void *mapped =
nullptr;
300 const VkResult mapResult = vkMapMemory(windowPtr->getDevice(), stagingMemory, 0, stagingSize, 0, &mapped);
301 if (mapResult != VK_SUCCESS || mapped ==
nullptr) {
302 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
303 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
307 if (srcRowBytes == tightRowBytes) {
308 std::memcpy(mapped, pixels,
static_cast<size_t>(stagingSize));
310 const auto *src =
static_cast<const uint8_t *
>(pixels);
311 auto *dst =
static_cast<uint8_t *
>(mapped);
312 for (uint32_t y = 0; y < uploadHeight; ++y) {
313 const size_t srcOffset =
static_cast<size_t>(y) * srcRowBytes;
314 const size_t dstOffset =
static_cast<size_t>(y) * tightRowBytes;
315 std::memcpy(dst + dstOffset, src + srcOffset, tightRowBytes);
318 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
320 if (recreatedTexture) {
321 transitionImageLayout(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
323 transitionImageLayout(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
325 copyBufferToImage(stagingBuffer, texture.image, uploadWidth, uploadHeight);
326 transitionImageLayout(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
328 texture.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
331 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
332 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
337 if (cmd == VK_NULL_HANDLE || imageIndex >= uniformBuffers.size() || descriptorSets.empty()) {
341 const VkPipeline pipeline = (wireframe && pipelineWireframe != VK_NULL_HANDLE) ? pipelineWireframe : pipelineFill;
342 if (pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
346 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
347 if (extendedFragmentUniformsEnabled) {
348 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(
ModelFragmentPushConstants), &fragmentPushConstants);
351 const size_t textureCount = std::max<size_t>(1, textures.size());
352 for (
size_t i = 0; i < obj.subMeshCount(); ++i) {
353 const SubMesh &submesh = obj.subMesh(i);
354 const size_t textureIndex = std::min<size_t>(submesh.
textureIndex, textureCount - 1U);
355 const size_t setIndex =
static_cast<size_t>(imageIndex) * textureCount + textureIndex;
356 if (setIndex >= descriptorSets.size()) {
360 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[setIndex], 0,
nullptr);
362 obj.drawSubMesh(cmd, i);
367 if (cmd == VK_NULL_HANDLE || imageIndex >= uniformBuffers.size() || descriptorSets.empty()) {
371 const VkPipeline pipeline = (wireframe && pipelineWireframe != VK_NULL_HANDLE) ? pipelineWireframe : pipelineFill;
372 if (pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
376 const size_t textureCount = std::max<size_t>(1, textures.size());
377 textureIndex = std::min(textureIndex, textureCount - 1U);
378 const size_t setIndex =
static_cast<size_t>(imageIndex) * textureCount + textureIndex;
379 if (setIndex >= descriptorSets.size()) {
382 updateTextureDescriptor(descriptorSets[setIndex], textures[textureIndex].view);
384 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
386 if (!extendedFragmentUniformsEnabled) {
387 const ModelPushConstants pushConstants{
391 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(ModelPushConstants), &pushConstants);
393 if (extendedFragmentUniformsEnabled) {
394 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(
ModelFragmentPushConstants), &fragmentPushConstants);
396 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[setIndex], 0,
nullptr);
402 if (cmd == VK_NULL_HANDLE || textureView == VK_NULL_HANDLE || imageIndex >= uniformBuffers.size() || descriptorSets.empty()) {
406 const VkPipeline pipeline = (wireframe && pipelineWireframe != VK_NULL_HANDLE) ? pipelineWireframe : pipelineFill;
407 if (pipeline == VK_NULL_HANDLE || pipelineLayout == VK_NULL_HANDLE) {
411 const size_t textureCount = std::max<size_t>(1, textures.size());
412 const size_t setIndex =
static_cast<size_t>(imageIndex) * textureCount;
413 if (setIndex >= descriptorSets.size()) {
416 updateTextureDescriptor(descriptorSets[setIndex], textureView);
418 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
420 if (!extendedFragmentUniformsEnabled) {
421 const ModelPushConstants pushConstants{
425 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(ModelPushConstants), &pushConstants);
427 if (extendedFragmentUniformsEnabled) {
428 vkCmdPushConstants(cmd, pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(
ModelFragmentPushConstants), &fragmentPushConstants);
430 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[setIndex], 0,
nullptr);
435 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
442 logVKAbstractModelStep(
"resize begin",
true);
443 windowPtr = targetWindow;
445 destroyDescriptors();
447 createDescriptorSetLayout();
448 createUniformBuffers();
449 createDescriptorPool();
450 createDescriptorSets();
452 logVKAbstractModelStep(
"resize complete",
true);
456 if (targetWindow ==
nullptr || targetWindow->
getDevice() == VK_NULL_HANDLE) {
460 logVKAbstractModelStep(
"teardown begin",
true);
461 windowPtr = targetWindow;
463 destroyDescriptors();
465 obj.cleanup(windowPtr->getDevice());
467 logVKAbstractModelStep(
"teardown complete",
true);
470 void VKAbstractModel::computeBoundsAndScale() {
471 const auto &vertices = obj.
vertices();
472 if (vertices.empty()) {
473 modelCenterOffsetValue = glm::vec3(0.0f);
474 modelRenderScaleValue = 1.0f;
475 modelAxisExtentValue = glm::vec3(1.0f);
479 float minX = vertices.front().pos[0];
481 float minY = vertices.front().pos[1];
483 float minZ = vertices.front().pos[2];
486 for (
const VKVertex &v : vertices) {
487 minX = std::min(minX, v.pos[0]);
488 maxX = std::max(maxX, v.pos[0]);
489 minY = std::min(minY, v.pos[1]);
490 maxY = std::max(maxY, v.pos[1]);
491 minZ = std::min(minZ, v.pos[2]);
492 maxZ = std::max(maxZ, v.pos[2]);
495 modelCenterOffsetValue = glm::vec3(-0.5f * (minX + maxX), -0.5f * (minY + maxY), -0.5f * (minZ + maxZ));
497 modelAxisExtentValue = glm::vec3(maxX - minX, maxY - minY, maxZ - minZ);
498 const float maxExtent = std::max(modelAxisExtentValue.x, std::max(modelAxisExtentValue.y, modelAxisExtentValue.z));
499 modelRenderScaleValue = (maxExtent > 1e-6f) ? (2.5f / maxExtent) : 1.0f;
502 void VKAbstractModel::loadTextures(
const std::string &textureManifestPath,
const std::string &textureBasePath) {
503 std::ifstream file(textureManifestPath);
504 if (!file.is_open()) {
505 throw mxvk::Exception(
"Failed to open texture manifest: " + textureManifestPath);
508 std::vector<std::string> lines{};
510 while (std::getline(file, line)) {
511 const size_t begin = line.find_first_not_of(
" \t\r\n");
512 if (begin == std::string::npos) {
515 const size_t end = line.find_last_not_of(
" \t\r\n");
516 line = line.substr(begin, end - begin + 1);
517 if (line.empty() || line[0] ==
'#') {
520 lines.push_back(line);
523 bool isStructured =
false;
524 bool isMtlLike =
false;
525 for (
const std::string &ln : lines) {
526 std::istringstream stream(ln);
529 if (keyword ==
"submesh" || keyword ==
"texture_dir" || keyword ==
"material_lib" || keyword ==
"model") {
533 if (keyword ==
"newmtl") {
539 std::vector<std::string> imagePaths{};
540 const std::string prefix = textureBasePath;
543 int currentMaterialTexture = -1;
544 for (
const std::string &ln : lines) {
545 std::istringstream stream(ln);
548 if (keyword ==
"newmtl") {
549 imagePaths.emplace_back();
550 currentMaterialTexture =
static_cast<int>(imagePaths.size()) - 1;
551 }
else if (keyword ==
"map_Kd") {
553 if (!image.empty() && currentMaterialTexture >= 0) {
554 imagePaths[
static_cast<size_t>(currentMaterialTexture)] =
resolveTexturePath(prefix, image);
558 }
else if (isStructured) {
559 for (
const std::string &ln : lines) {
560 std::istringstream stream(ln);
563 if (keyword ==
"texture") {
565 if (stream >> image) {
571 for (
const std::string &ln : lines) {
576 if (imagePaths.empty()) {
580 for (
const std::string &path : imagePaths) {
583 createFallbackTexture();
589 if (surface ==
nullptr) {
591 createFallbackTexture();
595 const uint32_t width =
static_cast<uint32_t
>(surface->w);
596 const uint32_t height =
static_cast<uint32_t
>(surface->h);
601 createTextureImage(width, height, tex);
604 if (updatePrimaryTextureCudaHost(tex, surface->pixels, width, height,
static_cast<uint32_t
>(surface->pitch))) {
605 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
606 textures.push_back(tex);
607 SDL_DestroySurface(surface);
612 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
613 VkBuffer stagingBuffer = VK_NULL_HANDLE;
614 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
615 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
617 void *mapped =
nullptr;
618 vkMapMemory(windowPtr->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
619 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
620 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
623 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED;
625 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
627 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, uploadOldLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
628 copyBufferToImage(stagingBuffer, tex.image, width, height);
629 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
631 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
634 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
635 textures.push_back(tex);
637 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
638 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
639 SDL_DestroySurface(surface);
643 void VKAbstractModel::loadTexturesFromMTL(
const std::string &textureBasePath) {
644 bool foundTextureReference =
false;
645 for (
const MXMaterial &material : obj.materials()) {
646 if (material.map_kd.empty()) {
647 createFallbackTexture();
651 foundTextureReference =
true;
655 if (surface ==
nullptr) {
657 createFallbackTexture();
661 const uint32_t width =
static_cast<uint32_t
>(surface->w);
662 const uint32_t height =
static_cast<uint32_t
>(surface->h);
667 createTextureImage(width, height, tex);
670 if (updatePrimaryTextureCudaHost(tex, surface->pixels, width, height,
static_cast<uint32_t
>(surface->pitch))) {
671 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
672 textures.push_back(tex);
673 SDL_DestroySurface(surface);
678 const VkDeviceSize imageSize =
static_cast<VkDeviceSize
>(width) *
static_cast<VkDeviceSize
>(height) * 4U;
679 VkBuffer stagingBuffer = VK_NULL_HANDLE;
680 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
681 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
683 void *mapped =
nullptr;
684 vkMapMemory(windowPtr->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
685 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
686 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
689 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED;
691 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
693 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, uploadOldLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
694 copyBufferToImage(stagingBuffer, tex.image, width, height);
695 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
697 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
700 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
701 textures.push_back(tex);
703 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
704 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
705 SDL_DestroySurface(surface);
708 if (!foundTextureReference) {
713 void VKAbstractModel::createFallbackTexture() {
714 SDL_Surface *surface = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA32);
715 if (surface ==
nullptr) {
716 throw mxvk::Exception(
"VKAbstractModel failed to allocate fallback texture surface");
719 auto *pixel =
static_cast<uint32_t *
>(surface->pixels);
720 *pixel = 0xFFFFFFFFu;
725 createTextureImage(1, 1, tex);
728 if (updatePrimaryTextureCudaHost(tex, surface->pixels, 1, 1,
static_cast<uint32_t
>(surface->pitch))) {
729 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
730 textures.push_back(tex);
731 SDL_DestroySurface(surface);
736 const VkDeviceSize imageSize = 4;
737 VkBuffer stagingBuffer = VK_NULL_HANDLE;
738 VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
739 createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingMemory);
741 void *mapped =
nullptr;
742 vkMapMemory(windowPtr->getDevice(), stagingMemory, 0, imageSize, 0, &mapped);
743 std::memcpy(mapped, surface->pixels,
static_cast<size_t>(imageSize));
744 vkUnmapMemory(windowPtr->getDevice(), stagingMemory);
747 const VkImageLayout uploadOldLayout = (tex.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED;
749 const VkImageLayout uploadOldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
751 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, uploadOldLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
752 copyBufferToImage(stagingBuffer, tex.image, 1, 1);
753 transitionImageLayout(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
755 tex.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
757 tex.view = createImageView(tex.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
758 textures.push_back(tex);
760 vkDestroyBuffer(windowPtr->getDevice(), stagingBuffer,
nullptr);
761 vkFreeMemory(windowPtr->getDevice(), stagingMemory,
nullptr);
762 SDL_DestroySurface(surface);
765 void VKAbstractModel::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
const {
766 VkBufferCreateInfo bufferInfo{};
767 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
768 bufferInfo.size = size;
769 bufferInfo.usage = usage;
770 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
772 if (vkCreateBuffer(windowPtr->getDevice(), &bufferInfo,
nullptr, &buffer) != VK_SUCCESS) {
773 throw mxvk::Exception(
"VKAbstractModel failed to create buffer");
776 VkMemoryRequirements requirements{};
777 vkGetBufferMemoryRequirements(windowPtr->getDevice(), buffer, &requirements);
779 VkMemoryAllocateInfo allocInfo{};
780 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
781 allocInfo.allocationSize = requirements.size;
784 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
785 if (vkAllocateMemory(windowPtr->getDevice(), &allocInfo,
nullptr, &bufferMemory) != VK_SUCCESS) {
786 throw mxvk::Exception(
"VKAbstractModel failed to allocate buffer memory");
789 if (vkBindBufferMemory(windowPtr->getDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) {
790 throw mxvk::Exception(
"VKAbstractModel failed to bind buffer memory");
793 if (bufferMemory != VK_NULL_HANDLE) {
794 vkFreeMemory(windowPtr->getDevice(), bufferMemory,
nullptr);
795 bufferMemory = VK_NULL_HANDLE;
797 if (buffer != VK_NULL_HANDLE) {
798 vkDestroyBuffer(windowPtr->getDevice(), buffer,
nullptr);
799 buffer = VK_NULL_HANDLE;
805 uint32_t VKAbstractModel::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
const {
806 VkPhysicalDeviceMemoryProperties memProperties{};
807 vkGetPhysicalDeviceMemoryProperties(windowPtr->getPhysicalDevice(), &memProperties);
809 for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
810 const bool typeSupported = (typeFilter & (1u << i)) != 0u;
811 const bool propsSupported = (memProperties.memoryTypes[i].propertyFlags & properties) == properties;
812 if (typeSupported && propsSupported) {
817 throw mxvk::Exception(
"VKAbstractModel failed to find suitable memory type");
820 VkCommandBuffer VKAbstractModel::beginSingleTimeCommands()
const {
821 VkCommandBufferAllocateInfo allocInfo{};
822 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
823 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
824 allocInfo.commandPool = windowPtr->getCommandPool();
825 allocInfo.commandBufferCount = 1;
827 VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
828 if (vkAllocateCommandBuffers(windowPtr->getDevice(), &allocInfo, &commandBuffer) != VK_SUCCESS) {
829 throw mxvk::Exception(
"VKAbstractModel failed to allocate command buffer");
832 VkCommandBufferBeginInfo beginInfo{};
833 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
834 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
835 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
836 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
837 throw mxvk::Exception(
"VKAbstractModel failed to begin command buffer");
840 return commandBuffer;
843 void VKAbstractModel::endSingleTimeCommands(VkCommandBuffer commandBuffer)
const {
844 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
845 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
846 throw mxvk::Exception(
"VKAbstractModel failed to end command buffer");
849 VkSubmitInfo submitInfo{};
850 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
851 submitInfo.commandBufferCount = 1;
852 submitInfo.pCommandBuffers = &commandBuffer;
854 if (vkQueueSubmit(windowPtr->getGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
855 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
856 throw mxvk::Exception(
"VKAbstractModel failed to submit command buffer");
858 if (vkQueueWaitIdle(windowPtr->getGraphicsQueue()) != VK_SUCCESS) {
859 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
860 throw mxvk::Exception(
"VKAbstractModel failed to wait for queue idle");
863 vkFreeCommandBuffers(windowPtr->getDevice(), windowPtr->getCommandPool(), 1, &commandBuffer);
866 void VKAbstractModel::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &memory)
const {
867 VkImageCreateInfo imageInfo{};
868 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
869 imageInfo.imageType = VK_IMAGE_TYPE_2D;
870 imageInfo.extent.width = width;
871 imageInfo.extent.height = height;
872 imageInfo.extent.depth = 1;
873 imageInfo.mipLevels = 1;
874 imageInfo.arrayLayers = 1;
875 imageInfo.format = format;
876 imageInfo.tiling = tiling;
877 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
878 imageInfo.usage = usage;
879 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
880 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
882 if (vkCreateImage(windowPtr->getDevice(), &imageInfo,
nullptr, &image) != VK_SUCCESS) {
883 throw mxvk::Exception(
"VKAbstractModel failed to create image");
886 VkMemoryRequirements requirements{};
887 vkGetImageMemoryRequirements(windowPtr->getDevice(), image, &requirements);
889 VkMemoryAllocateInfo allocInfo{};
890 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
891 allocInfo.allocationSize = requirements.size;
894 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, properties);
895 if (vkAllocateMemory(windowPtr->getDevice(), &allocInfo,
nullptr, &memory) != VK_SUCCESS) {
896 throw mxvk::Exception(
"VKAbstractModel failed to allocate image memory");
899 if (vkBindImageMemory(windowPtr->getDevice(), image, memory, 0) != VK_SUCCESS) {
900 throw mxvk::Exception(
"VKAbstractModel failed to bind image memory");
903 if (memory != VK_NULL_HANDLE) {
904 vkFreeMemory(windowPtr->getDevice(), memory,
nullptr);
905 memory = VK_NULL_HANDLE;
907 if (image != VK_NULL_HANDLE) {
908 vkDestroyImage(windowPtr->getDevice(), image,
nullptr);
909 image = VK_NULL_HANDLE;
915 void VKAbstractModel::createTextureImage(uint32_t width, uint32_t height, TextureEntry &texture)
const {
918 createCudaExportableImage(width, height, texture);
920 }
catch (
const std::exception &ex) {
921 logVKAbstractModelStep(std::format(
"CUDA exportable model texture unavailable: {}; using standard Vulkan texture", ex.what()));
922 texture.cudaExportMemorySize = 0;
923 texture.cudaInteropEnabled =
false;
924 texture.cudaInteropUnavailableLogged =
true;
928 createImage(width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, texture.image, texture.memory);
929 texture.width = width;
930 texture.height = height;
932 texture.cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
937 void VKAbstractModel::destroyTextureCudaInterop(TextureEntry &texture)
const {
938 if (texture.cudaInteropEnabled || texture.cudaExternalMemory !=
nullptr || texture.cudaMipmappedArray !=
nullptr) {
941 if (texture.cudaMipmappedArray !=
nullptr) {
942 cudaFreeMipmappedArray(texture.cudaMipmappedArray);
943 texture.cudaMipmappedArray =
nullptr;
944 texture.cudaArray =
nullptr;
946 if (texture.cudaExternalMemory !=
nullptr) {
947 cudaDestroyExternalMemory(texture.cudaExternalMemory);
948 texture.cudaExternalMemory =
nullptr;
950 texture.cudaInteropEnabled =
false;
951 texture.cudaExportMemorySize = 0;
952 texture.cudaUploadLogged =
false;
953 texture.cudaWriteTransitionLogged =
false;
954 texture.cudaShaderTransitionLogged =
false;
955 texture.cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
958 void VKAbstractModel::createCudaExportableImage(uint32_t width, uint32_t height, TextureEntry &texture)
const {
959 logVKAbstractModelStep(std::format(
"CUDA interop init: requesting exportable model texture {}x{} RGBA8 optimal-tiled OPAQUE_FD", width, height));
961 VkExternalMemoryImageCreateInfo externalImageInfo{};
962 externalImageInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
963 externalImageInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
965 VkImageCreateInfo imageInfo{};
966 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
967 imageInfo.pNext = &externalImageInfo;
968 imageInfo.imageType = VK_IMAGE_TYPE_2D;
969 imageInfo.extent.width = width;
970 imageInfo.extent.height = height;
971 imageInfo.extent.depth = 1;
972 imageInfo.mipLevels = 1;
973 imageInfo.arrayLayers = 1;
974 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
975 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
976 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
977 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
978 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
979 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
981 if (vkCreateImage(windowPtr->getDevice(), &imageInfo,
nullptr, &texture.image) != VK_SUCCESS) {
982 throw mxvk::Exception(
"VKAbstractModel failed to create CUDA exportable texture image");
985 VkMemoryRequirements requirements{};
986 vkGetImageMemoryRequirements(windowPtr->getDevice(), texture.image, &requirements);
988 VkExportMemoryAllocateInfo exportMemoryInfo{};
989 exportMemoryInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO;
990 exportMemoryInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
992 VkMemoryAllocateInfo allocInfo{};
993 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
994 allocInfo.pNext = &exportMemoryInfo;
995 allocInfo.allocationSize = requirements.size;
998 allocInfo.memoryTypeIndex = findMemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
999 if (vkAllocateMemory(windowPtr->getDevice(), &allocInfo,
nullptr, &texture.memory) != VK_SUCCESS) {
1000 throw mxvk::Exception(
"VKAbstractModel failed to allocate CUDA exportable texture memory");
1002 if (vkBindImageMemory(windowPtr->getDevice(), texture.image, texture.memory, 0) != VK_SUCCESS) {
1003 throw mxvk::Exception(
"VKAbstractModel failed to bind CUDA exportable texture memory");
1006 if (texture.memory != VK_NULL_HANDLE) {
1007 vkFreeMemory(windowPtr->getDevice(), texture.memory,
nullptr);
1008 texture.memory = VK_NULL_HANDLE;
1010 if (texture.image != VK_NULL_HANDLE) {
1011 vkDestroyImage(windowPtr->getDevice(), texture.image,
nullptr);
1012 texture.image = VK_NULL_HANDLE;
1014 texture.cudaExportMemorySize = 0;
1018 texture.width = width;
1019 texture.height = height;
1020 texture.cudaExportMemorySize = requirements.size;
1021 texture.cudaInteropUnavailableLogged =
false;
1022 texture.cudaImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1023 logVKAbstractModelStep(std::format(
"CUDA interop init: exportable model texture allocated (memorySize={} bytes, memoryType={}); optimal image memory is imported as cudaArray, not wrapped as pitched GpuMat",
static_cast<unsigned long long>(requirements.size), allocInfo.memoryTypeIndex));
1026 bool VKAbstractModel::ensureTextureCudaInterop(TextureEntry &texture)
const {
1027 if (texture.cudaInteropEnabled) {
1030 if (windowPtr ==
nullptr || texture.memory == VK_NULL_HANDLE || texture.cudaExportMemorySize == 0) {
1031 if (!texture.cudaInteropUnavailableLogged) {
1032 logVKAbstractModelStep(
"CUDA interop init: model texture is not exportable; CPU staging fallback remains active");
1033 texture.cudaInteropUnavailableLogged =
true;
1037 if (vkGetMemoryFdKHR ==
nullptr) {
1038 if (!texture.cudaInteropUnavailableLogged) {
1040 texture.cudaInteropUnavailableLogged =
true;
1045 VkMemoryGetFdInfoKHR fdInfo{};
1046 fdInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
1047 fdInfo.memory = texture.memory;
1048 fdInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
1051 const VkResult fdResult = vkGetMemoryFdKHR(windowPtr->getDevice(), &fdInfo, &memoryFd);
1052 if (fdResult != VK_SUCCESS) {
1053 if (!texture.cudaInteropUnavailableLogged) {
1054 logVKAbstractModelStep(std::format(
"CUDA interop init: vkGetMemoryFdKHR failed for model texture ({})",
static_cast<int>(fdResult)));
1055 texture.cudaInteropUnavailableLogged =
true;
1061 cudaExternalMemoryHandleDesc externalMemoryDesc{};
1062 externalMemoryDesc.type = cudaExternalMemoryHandleTypeOpaqueFd;
1063 externalMemoryDesc.handle.fd = memoryFd;
1064 externalMemoryDesc.size = texture.cudaExportMemorySize;
1066 cudaError_t cudaResult = cudaImportExternalMemory(&texture.cudaExternalMemory, &externalMemoryDesc);
1067 if (cudaResult != cudaSuccess) {
1069 if (!texture.cudaInteropUnavailableLogged) {
1070 logVKAbstractModelStep(std::format(
"CUDA interop init: cudaImportExternalMemory failed for model texture: {}", cudaGetErrorString(cudaResult)));
1071 texture.cudaInteropUnavailableLogged =
true;
1073 texture.cudaExternalMemory =
nullptr;
1076 logVKAbstractModelStep(std::format(
"CUDA interop init: imported model texture external memory into CUDA ({} bytes)",
static_cast<unsigned long long>(texture.cudaExportMemorySize)));
1078 cudaExternalMemoryMipmappedArrayDesc arrayDesc{};
1079 arrayDesc.offset = 0;
1080 arrayDesc.formatDesc = cudaCreateChannelDesc<uchar4>();
1081 arrayDesc.extent = make_cudaExtent(
static_cast<size_t>(texture.width),
static_cast<size_t>(texture.height), 0);
1082 arrayDesc.flags = cudaArrayColorAttachment;
1083 arrayDesc.numLevels = 1;
1085 cudaResult = cudaExternalMemoryGetMappedMipmappedArray(&texture.cudaMipmappedArray, texture.cudaExternalMemory, &arrayDesc);
1086 if (cudaResult != cudaSuccess) {
1087 if (!texture.cudaInteropUnavailableLogged) {
1088 logVKAbstractModelStep(std::format(
"CUDA interop init: cudaExternalMemoryGetMappedMipmappedArray failed for model texture: {}", cudaGetErrorString(cudaResult)));
1089 texture.cudaInteropUnavailableLogged =
true;
1091 destroyTextureCudaInterop(texture);
1094 logVKAbstractModelStep(std::format(
"CUDA interop init: mapped model texture CUDA mipmapped array {}x{} uchar4", texture.width, texture.height));
1096 cudaResult = cudaGetMipmappedArrayLevel(&texture.cudaArray, texture.cudaMipmappedArray, 0);
1097 if (cudaResult != cudaSuccess) {
1098 if (!texture.cudaInteropUnavailableLogged) {
1099 logVKAbstractModelStep(std::format(
"CUDA interop init: cudaGetMipmappedArrayLevel failed for model texture: {}", cudaGetErrorString(cudaResult)));
1100 texture.cudaInteropUnavailableLogged =
true;
1102 destroyTextureCudaInterop(texture);
1106 texture.cudaInteropEnabled =
true;
1111 bool VKAbstractModel::transitionTextureForCudaWrite(TextureEntry &texture)
const {
1112 if (texture.cudaImageLayout == VK_IMAGE_LAYOUT_GENERAL) {
1116 const VkImageLayout oldLayout = (texture.cudaImageLayout == VK_IMAGE_LAYOUT_UNDEFINED) ? VK_IMAGE_LAYOUT_UNDEFINED : texture.cudaImageLayout;
1117 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1119 VkImageMemoryBarrier barrier{};
1120 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1121 barrier.oldLayout = oldLayout;
1122 barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
1123 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1124 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1125 barrier.image = texture.image;
1126 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1127 barrier.subresourceRange.baseMipLevel = 0;
1128 barrier.subresourceRange.levelCount = 1;
1129 barrier.subresourceRange.baseArrayLayer = 0;
1130 barrier.subresourceRange.layerCount = 1;
1131 barrier.srcAccessMask = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) ? VK_ACCESS_SHADER_READ_BIT : 0;
1132 barrier.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1134 const VkPipelineStageFlags srcStage = (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) ? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1135 vkCmdPipelineBarrier(commandBuffer, srcStage, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1136 endSingleTimeCommands(commandBuffer);
1138 texture.cudaImageLayout = VK_IMAGE_LAYOUT_GENERAL;
1139 if (!texture.cudaWriteTransitionLogged) {
1141 texture.cudaWriteTransitionLogged =
true;
1146 bool VKAbstractModel::transitionTextureForShaderRead(TextureEntry &texture)
const {
1147 if (texture.cudaImageLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1151 VkCommandBuffer commandBuffer = beginSingleTimeCommands();
1152 VkImageMemoryBarrier barrier{};
1153 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1154 barrier.oldLayout = texture.cudaImageLayout;
1155 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1156 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1157 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1158 barrier.image = texture.image;
1159 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1160 barrier.subresourceRange.baseMipLevel = 0;
1161 barrier.subresourceRange.levelCount = 1;
1162 barrier.subresourceRange.baseArrayLayer = 0;
1163 barrier.subresourceRange.layerCount = 1;
1164 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1165 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1167 vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1168 endSingleTimeCommands(commandBuffer);
1170 texture.cudaImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1171 if (!texture.cudaShaderTransitionLogged) {
1172 logVKAbstractModelStep(
"CUDA interop sync: model texture transitions GENERAL -> SHADER_READ_ONLY before sampling");
1173 texture.cudaShaderTransitionLogged =
true;
1178 void VKAbstractModel::recreatePrimaryTextureForCuda(TextureEntry &texture, uint32_t width, uint32_t height) {
1179 vkDeviceWaitIdle(windowPtr->getDevice());
1180 destroyTextureCudaInterop(texture);
1181 if (texture.view != VK_NULL_HANDLE) {
1182 vkDestroyImageView(windowPtr->getDevice(), texture.view,
nullptr);
1183 texture.view = VK_NULL_HANDLE;
1185 if (texture.image != VK_NULL_HANDLE) {
1186 vkDestroyImage(windowPtr->getDevice(), texture.image,
nullptr);
1187 texture.image = VK_NULL_HANDLE;
1189 if (texture.memory != VK_NULL_HANDLE) {
1190 vkFreeMemory(windowPtr->getDevice(), texture.memory,
nullptr);
1191 texture.memory = VK_NULL_HANDLE;
1194 createCudaExportableImage(width, height, texture);
1195 texture.view = createImageView(texture.image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
1197 createDescriptorSets();
1200 bool VKAbstractModel::updatePrimaryTextureCudaHost(TextureEntry &texture,
const void *pixels, uint32_t width, uint32_t height, uint32_t pitch)
const {
1201 if (pixels ==
nullptr || width == 0 || height == 0) {
1204 const uint32_t rowBytes = width * 4U;
1205 if (pitch < rowBytes || texture.width != width || texture.height != height) {
1208 if (!ensureTextureCudaInterop(texture) || !transitionTextureForCudaWrite(texture)) {
1212 if (!texture.cudaUploadLogged) {
1213 logVKAbstractModelStep(std::format(
"CUDA interop upload: copying {}x{} host RGBA pixels to optimal-tiled Vulkan model texture via cudaArray (source pitch={} bytes)", width, height, pitch));
1214 texture.cudaUploadLogged =
true;
1217 const cudaError_t cudaResult = cudaMemcpy2DToArray(texture.cudaArray, 0, 0, pixels, pitch,
static_cast<size_t>(rowBytes),
static_cast<size_t>(height), cudaMemcpyHostToDevice);
1218 if (cudaResult != cudaSuccess) {
1219 logVKAbstractModelStep(std::format(
"CUDA interop model host texture copy failed: {}", cudaGetErrorString(cudaResult)));
1223 return transitionTextureForShaderRead(texture);
1226 bool VKAbstractModel::updatePrimaryTextureCuda(
const cv::cuda::GpuMat &rgba, cv::cuda::Stream &stream) {
1227 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1230 if (rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols <= 0 || rgba.rows <= 0) {
1234 if (textures.empty()) {
1235 textures.push_back(TextureEntry{});
1238 TextureEntry &texture = textures[0];
1239 const uint32_t uploadWidth =
static_cast<uint32_t
>(rgba.cols);
1240 const uint32_t uploadHeight =
static_cast<uint32_t
>(rgba.rows);
1241 if (texture.image == VK_NULL_HANDLE || texture.memory == VK_NULL_HANDLE || texture.view == VK_NULL_HANDLE || texture.width != uploadWidth || texture.height != uploadHeight || texture.cudaExportMemorySize == 0) {
1243 recreatePrimaryTextureForCuda(texture, uploadWidth, uploadHeight);
1244 }
catch (
const std::exception &ex) {
1245 if (!texture.cudaInteropUnavailableLogged) {
1246 logVKAbstractModelStep(std::format(
"CUDA exportable model texture unavailable: {}; CPU staging fallback remains active", ex.what()));
1247 texture.cudaInteropUnavailableLogged =
true;
1253 if (!ensureTextureCudaInterop(texture) || !transitionTextureForCudaWrite(texture)) {
1257 cudaStream_t cudaStream = cuda_stream_handle(stream);
1258 if (!texture.cudaUploadLogged) {
1259 logVKAbstractModelStep(std::format(
"CUDA interop upload: copying {}x{} RGBA GpuMat to optimal-tiled Vulkan model texture via cudaArray (source pitch={} bytes, copy row bytes={})", rgba.cols, rgba.rows,
static_cast<unsigned long long>(rgba.step),
static_cast<unsigned long long>(
static_cast<size_t>(rgba.cols) * 4U)));
1260 texture.cudaUploadLogged =
true;
1263 cudaError_t cudaResult = cudaMemcpy2DToArrayAsync(texture.cudaArray, 0, 0, rgba.ptr(), rgba.step,
static_cast<size_t>(rgba.cols) * 4U,
static_cast<size_t>(rgba.rows), cudaMemcpyDeviceToDevice, cudaStream);
1264 if (cudaResult != cudaSuccess) {
1265 logVKAbstractModelStep(std::format(
"CUDA interop model texture copy failed: {}", cudaGetErrorString(cudaResult)));
1269 cudaResult = cudaStreamSynchronize(cudaStream);
1270 if (cudaResult != cudaSuccess) {
1271 logVKAbstractModelStep(std::format(
"CUDA interop model texture sync failed: {}", cudaGetErrorString(cudaResult)));
1275 return transitionTextureForShaderRead(texture);
1279 VkImageView VKAbstractModel::createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags)
const {
1280 VkImageViewCreateInfo viewInfo{};
1281 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1282 viewInfo.image = image;
1283 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1284 viewInfo.format = format;
1285 viewInfo.subresourceRange.aspectMask = aspectFlags;
1286 viewInfo.subresourceRange.baseMipLevel = 0;
1287 viewInfo.subresourceRange.levelCount = 1;
1288 viewInfo.subresourceRange.baseArrayLayer = 0;
1289 viewInfo.subresourceRange.layerCount = 1;
1291 VkImageView imageView = VK_NULL_HANDLE;
1292 if (vkCreateImageView(windowPtr->getDevice(), &viewInfo,
nullptr, &imageView) != VK_SUCCESS) {
1293 throw mxvk::Exception(
"VKAbstractModel failed to create image view");
1298 void VKAbstractModel::transitionImageLayout(VkImage image, VkFormat, VkImageLayout oldLayout, VkImageLayout newLayout)
const {
1299 VkCommandBuffer cmd = beginSingleTimeCommands();
1301 VkImageMemoryBarrier barrier{};
1302 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
1303 barrier.oldLayout = oldLayout;
1304 barrier.newLayout = newLayout;
1305 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1306 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
1307 barrier.image = image;
1308 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1309 barrier.subresourceRange.baseMipLevel = 0;
1310 barrier.subresourceRange.levelCount = 1;
1311 barrier.subresourceRange.baseArrayLayer = 0;
1312 barrier.subresourceRange.layerCount = 1;
1314 VkPipelineStageFlags sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1315 VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1317 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1318 barrier.srcAccessMask = 0;
1319 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1320 sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
1321 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1322 }
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
1323 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1324 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
1325 sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1326 destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1327 }
else if (oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1328 barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
1329 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1330 sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
1331 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1332 }
else if (oldLayout == VK_IMAGE_LAYOUT_GENERAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
1333 barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
1334 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
1335 sourceStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
1336 destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
1339 vkCmdPipelineBarrier(cmd, sourceStage, destinationStage, 0, 0,
nullptr, 0,
nullptr, 1, &barrier);
1341 endSingleTimeCommands(cmd);
1344 void VKAbstractModel::copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height)
const {
1345 VkCommandBuffer cmd = beginSingleTimeCommands();
1347 VkBufferImageCopy region{};
1348 region.bufferOffset = 0;
1349 region.bufferRowLength = 0;
1350 region.bufferImageHeight = 0;
1351 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1352 region.imageSubresource.mipLevel = 0;
1353 region.imageSubresource.baseArrayLayer = 0;
1354 region.imageSubresource.layerCount = 1;
1355 region.imageOffset = {0, 0, 0};
1356 region.imageExtent = {width, height, 1};
1358 vkCmdCopyBufferToImage(cmd, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
1359 endSingleTimeCommands(cmd);
1362 void VKAbstractModel::createTextureSampler() {
1363 if (textureSampler != VK_NULL_HANDLE) {
1367 VkPhysicalDeviceFeatures deviceFeatures{};
1368 vkGetPhysicalDeviceFeatures(windowPtr->getPhysicalDevice(), &deviceFeatures);
1369 VkPhysicalDeviceProperties deviceProperties{};
1370 vkGetPhysicalDeviceProperties(windowPtr->getPhysicalDevice(), &deviceProperties);
1371 const bool anisotropySupported = deviceFeatures.samplerAnisotropy == VK_TRUE;
1372 const float anisotropyLevel = anisotropySupported ? std::min(8.0f, deviceProperties.limits.maxSamplerAnisotropy) : 1.0f;
1374 VkSamplerCreateInfo samplerInfo{};
1375 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1376 samplerInfo.magFilter = VK_FILTER_LINEAR;
1377 samplerInfo.minFilter = VK_FILTER_LINEAR;
1378 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1379 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1380 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
1381 samplerInfo.anisotropyEnable = anisotropySupported ? VK_TRUE : VK_FALSE;
1382 samplerInfo.maxAnisotropy = anisotropyLevel;
1383 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
1384 samplerInfo.unnormalizedCoordinates = VK_FALSE;
1385 samplerInfo.compareEnable = VK_FALSE;
1386 samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
1387 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
1389 if (vkCreateSampler(windowPtr->getDevice(), &samplerInfo,
nullptr, &textureSampler) != VK_SUCCESS) {
1390 throw mxvk::Exception(
"VKAbstractModel failed to create texture sampler");
1394 void VKAbstractModel::createDescriptorSetLayout() {
1395 if (descriptorSetLayout != VK_NULL_HANDLE) {
1399 VkDescriptorSetLayoutBinding samplerBinding{};
1400 samplerBinding.binding = 0;
1401 samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1402 samplerBinding.descriptorCount = 1;
1403 samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
1405 VkDescriptorSetLayoutBinding fragmentBinding{};
1406 fragmentBinding.binding = 1;
1407 fragmentBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1408 fragmentBinding.descriptorCount = 1;
1409 fragmentBinding.stageFlags = extendedFragmentUniformsEnabled ? VK_SHADER_STAGE_FRAGMENT_BIT : VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
1411 VkDescriptorSetLayoutBinding modelBinding{};
1412 modelBinding.binding = 2;
1413 modelBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1414 modelBinding.descriptorCount = 1;
1415 modelBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
1417 const std::array<VkDescriptorSetLayoutBinding, 3> bindings = {samplerBinding, fragmentBinding, modelBinding};
1419 VkDescriptorSetLayoutCreateInfo layoutInfo{};
1420 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
1421 layoutInfo.bindingCount = extendedFragmentUniformsEnabled ?
static_cast<uint32_t
>(bindings.size()) : 2U;
1422 layoutInfo.pBindings = bindings.data();
1424 if (vkCreateDescriptorSetLayout(windowPtr->getDevice(), &layoutInfo,
nullptr, &descriptorSetLayout) != VK_SUCCESS) {
1425 throw mxvk::Exception(
"VKAbstractModel failed to create descriptor set layout");
1429 void VKAbstractModel::createUniformBuffers() {
1430 destroyUniformBuffers();
1432 const size_t frameCount = windowPtr->getSwapchainImageCount();
1433 if (frameCount == 0) {
1437 uniformBuffers.resize(frameCount, VK_NULL_HANDLE);
1438 uniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
1439 uniformBuffersMapped.resize(frameCount,
nullptr);
1440 if (extendedFragmentUniformsEnabled) {
1441 fragmentUniformBuffers.resize(frameCount, VK_NULL_HANDLE);
1442 fragmentUniformBufferMemory.resize(frameCount, VK_NULL_HANDLE);
1443 fragmentUniformBuffersMapped.resize(frameCount,
nullptr);
1446 for (
size_t i = 0; i < frameCount; ++i) {
1447 createBuffer(
sizeof(UniformBufferObject), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBufferMemory[i]);
1448 vkMapMemory(windowPtr->getDevice(), uniformBufferMemory[i], 0,
sizeof(UniformBufferObject), 0, &uniformBuffersMapped[i]);
1449 if (extendedFragmentUniformsEnabled) {
1450 createBuffer(
sizeof(ModelFragmentUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, fragmentUniformBuffers[i], fragmentUniformBufferMemory[i]);
1451 vkMapMemory(windowPtr->getDevice(), fragmentUniformBufferMemory[i], 0,
sizeof(ModelFragmentUniforms), 0, &fragmentUniformBuffersMapped[i]);
1456 void VKAbstractModel::destroyUniformBuffers() {
1457 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1458 uniformBuffers.clear();
1459 uniformBufferMemory.clear();
1460 uniformBuffersMapped.clear();
1461 fragmentUniformBuffers.clear();
1462 fragmentUniformBufferMemory.clear();
1463 fragmentUniformBuffersMapped.clear();
1467 for (
size_t i = 0; i < uniformBuffers.size(); ++i) {
1468 if (uniformBuffersMapped[i] !=
nullptr) {
1469 vkUnmapMemory(windowPtr->getDevice(), uniformBufferMemory[i]);
1470 uniformBuffersMapped[i] =
nullptr;
1472 if (uniformBuffers[i] != VK_NULL_HANDLE) {
1473 vkDestroyBuffer(windowPtr->getDevice(), uniformBuffers[i],
nullptr);
1475 if (uniformBufferMemory[i] != VK_NULL_HANDLE) {
1476 vkFreeMemory(windowPtr->getDevice(), uniformBufferMemory[i],
nullptr);
1480 uniformBuffers.clear();
1481 uniformBufferMemory.clear();
1482 uniformBuffersMapped.clear();
1484 for (
size_t i = 0; i < fragmentUniformBuffers.size(); ++i) {
1485 if (fragmentUniformBuffersMapped[i] !=
nullptr) {
1486 vkUnmapMemory(windowPtr->getDevice(), fragmentUniformBufferMemory[i]);
1488 if (fragmentUniformBuffers[i] != VK_NULL_HANDLE) {
1489 vkDestroyBuffer(windowPtr->getDevice(), fragmentUniformBuffers[i],
nullptr);
1491 if (fragmentUniformBufferMemory[i] != VK_NULL_HANDLE) {
1492 vkFreeMemory(windowPtr->getDevice(), fragmentUniformBufferMemory[i],
nullptr);
1495 fragmentUniformBuffers.clear();
1496 fragmentUniformBufferMemory.clear();
1497 fragmentUniformBuffersMapped.clear();
1500 void VKAbstractModel::createDescriptorPool() {
1501 const uint32_t textureCount = std::max<uint32_t>(1U,
static_cast<uint32_t
>(textures.size()));
1502 const uint32_t frameCount =
static_cast<uint32_t
>(windowPtr->getSwapchainImageCount());
1503 const uint32_t requiredSetCount = textureCount * frameCount;
1504 const uint32_t setCount = std::max(requiredSetCount, descriptorPoolSetCapacity);
1506 std::array<VkDescriptorPoolSize, 2> poolSizes{};
1507 poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1508 poolSizes[0].descriptorCount = setCount;
1509 poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1510 poolSizes[1].descriptorCount = extendedFragmentUniformsEnabled ? setCount * 2U : setCount;
1512 VkDescriptorPoolCreateInfo poolInfo{};
1513 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
1514 poolInfo.poolSizeCount =
static_cast<uint32_t
>(poolSizes.size());
1515 poolInfo.pPoolSizes = poolSizes.data();
1516 poolInfo.maxSets = setCount;
1517 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
1519 if (vkCreateDescriptorPool(windowPtr->getDevice(), &poolInfo,
nullptr, &descriptorPool) != VK_SUCCESS) {
1520 throw mxvk::Exception(
"VKAbstractModel failed to create descriptor pool");
1522 descriptorPoolSetCapacity = setCount;
1525 void VKAbstractModel::createDescriptorSets() {
1526 const size_t textureCount = std::max<size_t>(1, textures.size());
1527 const size_t frameCount = windowPtr->getSwapchainImageCount();
1528 const size_t setCount = textureCount * frameCount;
1530 if (descriptorSetLayout == VK_NULL_HANDLE || frameCount == 0 || uniformBuffers.size() < frameCount || textures.empty() || (extendedFragmentUniformsEnabled && fragmentUniformBuffers.size() < frameCount)) {
1534 if (descriptorPool == VK_NULL_HANDLE || descriptorPoolSetCapacity < setCount) {
1535 descriptorSets.clear();
1536 if (descriptorPool != VK_NULL_HANDLE) {
1537 vkDestroyDescriptorPool(windowPtr->getDevice(), descriptorPool,
nullptr);
1538 descriptorPool = VK_NULL_HANDLE;
1539 descriptorPoolSetCapacity = 0;
1541 createDescriptorPool();
1544 const bool needsAllocation = descriptorSets.size() != setCount || std::any_of(descriptorSets.begin(), descriptorSets.end(), [](VkDescriptorSet set) { return set == VK_NULL_HANDLE; });
1546 if (needsAllocation) {
1547 std::vector<VkDescriptorSetLayout> layouts(setCount, descriptorSetLayout);
1549 VkDescriptorSetAllocateInfo allocInfo{};
1550 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
1551 allocInfo.descriptorPool = descriptorPool;
1552 allocInfo.descriptorSetCount =
static_cast<uint32_t
>(setCount);
1553 allocInfo.pSetLayouts = layouts.data();
1555 descriptorSets.assign(setCount, VK_NULL_HANDLE);
1556 const VkResult allocateResult = vkAllocateDescriptorSets(windowPtr->getDevice(), &allocInfo, descriptorSets.data());
1557 if (allocateResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocateResult == VK_ERROR_FRAGMENTED_POOL) {
1558 vkDestroyDescriptorPool(windowPtr->getDevice(), descriptorPool,
nullptr);
1559 descriptorPool = VK_NULL_HANDLE;
1560 descriptorPoolSetCapacity =
static_cast<uint32_t
>(std::max<size_t>(setCount * 2U, 1U));
1561 descriptorSets.clear();
1562 createDescriptorPool();
1563 allocInfo.descriptorPool = descriptorPool;
1564 descriptorSets.assign(setCount, VK_NULL_HANDLE);
1565 if (vkAllocateDescriptorSets(windowPtr->getDevice(), &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
1566 throw mxvk::Exception(
"VKAbstractModel failed to allocate descriptor sets");
1568 }
else if (allocateResult != VK_SUCCESS) {
1569 throw mxvk::Exception(
"VKAbstractModel failed to allocate descriptor sets");
1573 for (
size_t frame = 0; frame < frameCount; ++frame) {
1574 VkDescriptorBufferInfo bufferInfo{};
1575 bufferInfo.buffer = uniformBuffers[frame];
1576 bufferInfo.offset = 0;
1577 bufferInfo.range =
sizeof(UniformBufferObject);
1578 VkDescriptorBufferInfo fragmentBufferInfo{};
1579 if (extendedFragmentUniformsEnabled) {
1580 fragmentBufferInfo.buffer = fragmentUniformBuffers[frame];
1581 fragmentBufferInfo.offset = 0;
1582 fragmentBufferInfo.range =
sizeof(ModelFragmentUniforms);
1585 for (
size_t tex = 0; tex < textureCount; ++tex) {
1586 const size_t setIndex = frame * textureCount + tex;
1587 const TextureEntry &entry = textures[tex];
1589 VkDescriptorImageInfo imageInfo{};
1590 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
1591 imageInfo.imageView = entry.view;
1592 imageInfo.sampler = textureSampler;
1594 std::array<VkWriteDescriptorSet, 3> writes{};
1595 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1596 writes[0].dstSet = descriptorSets[setIndex];
1597 writes[0].dstBinding = 0;
1598 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
1599 writes[0].descriptorCount = 1;
1600 writes[0].pImageInfo = &imageInfo;
1602 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1603 writes[1].dstSet = descriptorSets[setIndex];
1604 writes[1].dstBinding = 1;
1605 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1606 writes[1].descriptorCount = 1;
1607 writes[1].pBufferInfo = extendedFragmentUniformsEnabled ? &fragmentBufferInfo : &bufferInfo;
1609 if (extendedFragmentUniformsEnabled) {
1610 writes[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
1611 writes[2].dstSet = descriptorSets[setIndex];
1612 writes[2].dstBinding = 2;
1613 writes[2].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
1614 writes[2].descriptorCount = 1;
1615 writes[2].pBufferInfo = &bufferInfo;
1618 const uint32_t writeCount = extendedFragmentUniformsEnabled ?
static_cast<uint32_t
>(writes.size()) : 2U;
1619 vkUpdateDescriptorSets(windowPtr->getDevice(), writeCount, writes.data(), 0,
nullptr);
1624 void VKAbstractModel::updateTextureDescriptor(VkDescriptorSet descriptorSet, VkImageView imageView)
const {
1625 if (descriptorSet == VK_NULL_HANDLE || imageView == VK_NULL_HANDLE || textureSampler == VK_NULL_HANDLE) {
1628 const VkDescriptorImageInfo imageInfo{
1629 .sampler = textureSampler,
1630 .imageView = imageView,
1631 .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
1633 const VkWriteDescriptorSet write{
1634 .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
1635 .dstSet = descriptorSet,
1637 .descriptorCount = 1,
1638 .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
1639 .pImageInfo = &imageInfo,
1641 vkUpdateDescriptorSets(windowPtr->getDevice(), 1, &write, 0,
nullptr);
1644 void VKAbstractModel::createPipelines() {
1647 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1650 if (descriptorSetLayout == VK_NULL_HANDLE) {
1653 if (vertexShaderPath.empty() || fragmentShaderPath.empty()) {
1656 if (windowPtr->getSwapchainFormat() == VK_FORMAT_UNDEFINED) {
1660 const std::vector<char> vertBytes =
mxvk::load_spv(vertexShaderPath);
1661 const std::vector<char> fragBytes =
mxvk::load_spv(fragmentShaderPath);
1664 VkShaderModule fragModule = VK_NULL_HANDLE;
1669 VkPipelineShaderStageCreateInfo vertStage{};
1670 vertStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1671 vertStage.stage = VK_SHADER_STAGE_VERTEX_BIT;
1672 vertStage.module = vertModule;
1673 vertStage.pName =
"main";
1675 VkPipelineShaderStageCreateInfo fragStage{};
1676 fragStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
1677 fragStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
1678 fragStage.module = fragModule;
1679 fragStage.pName =
"main";
1681 const std::array<VkPipelineShaderStageCreateInfo, 2> stages = {vertStage, fragStage};
1683 VkVertexInputBindingDescription binding{};
1684 binding.binding = 0;
1685 binding.stride =
sizeof(VKVertex);
1686 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
1688 std::array<VkVertexInputAttributeDescription, 3> attrs{};
1689 attrs[0].binding = 0;
1690 attrs[0].location = 0;
1691 attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
1692 attrs[0].offset = offsetof(VKVertex, pos);
1693 attrs[1].binding = 0;
1694 attrs[1].location = 1;
1695 attrs[1].format = VK_FORMAT_R32G32_SFLOAT;
1696 attrs[1].offset = offsetof(VKVertex, texCoord);
1697 attrs[2].binding = 0;
1698 attrs[2].location = 2;
1699 attrs[2].format = VK_FORMAT_R32G32B32_SFLOAT;
1700 attrs[2].offset = offsetof(VKVertex, normal);
1702 VkPipelineVertexInputStateCreateInfo vertexInput{};
1703 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
1704 vertexInput.vertexBindingDescriptionCount = 1;
1705 vertexInput.pVertexBindingDescriptions = &binding;
1706 vertexInput.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attrs.size());
1707 vertexInput.pVertexAttributeDescriptions = attrs.data();
1709 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
1710 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
1711 inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1712 inputAssembly.primitiveRestartEnable = VK_FALSE;
1714 const std::array<VkDynamicState, 2> dynamicStates = {
1715 VK_DYNAMIC_STATE_VIEWPORT,
1716 VK_DYNAMIC_STATE_SCISSOR,
1718 VkPipelineDynamicStateCreateInfo dynamicInfo{};
1719 dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
1720 dynamicInfo.dynamicStateCount =
static_cast<uint32_t
>(dynamicStates.size());
1721 dynamicInfo.pDynamicStates = dynamicStates.data();
1723 VkPipelineViewportStateCreateInfo viewportState{};
1724 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
1725 viewportState.viewportCount = 1;
1726 viewportState.scissorCount = 1;
1728 VkPipelineRasterizationStateCreateInfo rasterizer{};
1729 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
1730 rasterizer.depthClampEnable = VK_FALSE;
1731 rasterizer.rasterizerDiscardEnable = VK_FALSE;
1732 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
1733 rasterizer.cullMode = backfaceCullingEnabled ? VK_CULL_MODE_BACK_BIT : VK_CULL_MODE_NONE;
1734 rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
1735 rasterizer.depthBiasEnable = VK_FALSE;
1736 rasterizer.lineWidth = 1.0f;
1738 VkPipelineMultisampleStateCreateInfo multisample{};
1739 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
1740 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
1741 multisample.sampleShadingEnable = VK_FALSE;
1743 VkPipelineDepthStencilStateCreateInfo depthStencil{};
1744 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
1745 depthStencil.depthTestEnable = VK_TRUE;
1746 depthStencil.depthWriteEnable = alphaBlendingEnabled ? VK_FALSE : VK_TRUE;
1747 depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
1749 VkPipelineColorBlendAttachmentState blendAttachment{};
1750 blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
1751 blendAttachment.blendEnable = alphaBlendingEnabled ? VK_TRUE : VK_FALSE;
1752 blendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
1753 blendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1754 blendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
1755 blendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
1756 blendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1757 blendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
1759 VkPipelineColorBlendStateCreateInfo colorBlend{};
1760 colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
1761 colorBlend.logicOpEnable = VK_FALSE;
1762 colorBlend.attachmentCount = 1;
1763 colorBlend.pAttachments = &blendAttachment;
1765 VkPipelineLayoutCreateInfo layoutInfo{};
1766 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
1767 layoutInfo.setLayoutCount = 1;
1768 layoutInfo.pSetLayouts = &descriptorSetLayout;
1769 const VkPushConstantRange vertexPushConstantRange{
1770 .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
1772 .size =
sizeof(ModelPushConstants),
1774 const VkPushConstantRange fragmentPushConstantRange{
1775 .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
1777 .size =
sizeof(ModelFragmentPushConstants),
1779 layoutInfo.pushConstantRangeCount = 1U;
1780 layoutInfo.pPushConstantRanges = extendedFragmentUniformsEnabled ? &fragmentPushConstantRange : &vertexPushConstantRange;
1782 if (vkCreatePipelineLayout(windowPtr->getDevice(), &layoutInfo,
nullptr, &pipelineLayout) != VK_SUCCESS) {
1783 throw mxvk::Exception(
"VKAbstractModel failed to create pipeline layout");
1786 const VkFormat colorFormat = colorAttachmentFormat != VK_FORMAT_UNDEFINED ? colorAttachmentFormat : windowPtr->getSwapchainFormat();
1787 const VkFormat depthFormat = windowPtr->getDepthFormat();
1788 VkPipelineRenderingCreateInfo renderingInfo{};
1789 renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
1790 renderingInfo.colorAttachmentCount = 1;
1791 renderingInfo.pColorAttachmentFormats = &colorFormat;
1792 if (depthFormat != VK_FORMAT_UNDEFINED) {
1793 renderingInfo.depthAttachmentFormat = depthFormat;
1796 VkGraphicsPipelineCreateInfo pipelineInfo{};
1797 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
1798 pipelineInfo.pNext = &renderingInfo;
1799 pipelineInfo.stageCount =
static_cast<uint32_t
>(stages.size());
1800 pipelineInfo.pStages = stages.data();
1801 pipelineInfo.pVertexInputState = &vertexInput;
1802 pipelineInfo.pInputAssemblyState = &inputAssembly;
1803 pipelineInfo.pViewportState = &viewportState;
1804 pipelineInfo.pRasterizationState = &rasterizer;
1805 pipelineInfo.pMultisampleState = &multisample;
1806 pipelineInfo.pDepthStencilState = &depthStencil;
1807 pipelineInfo.pColorBlendState = &colorBlend;
1808 pipelineInfo.pDynamicState = &dynamicInfo;
1809 pipelineInfo.layout = pipelineLayout;
1810 pipelineInfo.renderPass = VK_NULL_HANDLE;
1811 pipelineInfo.subpass = 0;
1813 if (vkCreateGraphicsPipelines(windowPtr->getDevice(), windowPtr->getPipelineCache(), 1, &pipelineInfo,
nullptr, &pipelineFill) != VK_SUCCESS) {
1814 throw mxvk::Exception(
"VKAbstractModel failed to create fill pipeline");
1820 pipelineWireframe = VK_NULL_HANDLE;
1822 if (fragModule != VK_NULL_HANDLE) {
1823 vkDestroyShaderModule(windowPtr->getDevice(), fragModule,
nullptr);
1825 vkDestroyShaderModule(windowPtr->getDevice(), vertModule,
nullptr);
1829 vkDestroyShaderModule(windowPtr->getDevice(), fragModule,
nullptr);
1830 vkDestroyShaderModule(windowPtr->getDevice(), vertModule,
nullptr);
1833 void VKAbstractModel::destroyPipelines() {
1834 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1835 pipelineFill = VK_NULL_HANDLE;
1836 pipelineWireframe = VK_NULL_HANDLE;
1837 pipelineLayout = VK_NULL_HANDLE;
1841 if (pipelineFill != VK_NULL_HANDLE) {
1843 vkDestroyPipeline(windowPtr->getDevice(), pipelineFill,
nullptr);
1844 pipelineFill = VK_NULL_HANDLE;
1846 if (pipelineWireframe != VK_NULL_HANDLE) {
1848 vkDestroyPipeline(windowPtr->getDevice(), pipelineWireframe,
nullptr);
1849 pipelineWireframe = VK_NULL_HANDLE;
1851 if (pipelineLayout != VK_NULL_HANDLE) {
1853 vkDestroyPipelineLayout(windowPtr->getDevice(), pipelineLayout,
nullptr);
1854 pipelineLayout = VK_NULL_HANDLE;
1858 void VKAbstractModel::destroyDescriptors() {
1859 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1860 descriptorSets.clear();
1861 descriptorPool = VK_NULL_HANDLE;
1862 descriptorSetLayout = VK_NULL_HANDLE;
1863 destroyUniformBuffers();
1867 descriptorSets.clear();
1868 if (descriptorPool != VK_NULL_HANDLE) {
1870 vkDestroyDescriptorPool(windowPtr->getDevice(), descriptorPool,
nullptr);
1871 descriptorPool = VK_NULL_HANDLE;
1873 if (descriptorSetLayout != VK_NULL_HANDLE) {
1875 vkDestroyDescriptorSetLayout(windowPtr->getDevice(), descriptorSetLayout,
nullptr);
1876 descriptorSetLayout = VK_NULL_HANDLE;
1879 destroyUniformBuffers();
1882 void VKAbstractModel::destroyTextures() {
1883 if (windowPtr ==
nullptr || windowPtr->getDevice() == VK_NULL_HANDLE) {
1885 textureSampler = VK_NULL_HANDLE;
1889 for (TextureEntry &tex : textures) {
1891 destroyTextureCudaInterop(tex);
1893 if (tex.view != VK_NULL_HANDLE) {
1895 vkDestroyImageView(windowPtr->getDevice(), tex.view,
nullptr);
1897 if (tex.image != VK_NULL_HANDLE) {
1899 vkDestroyImage(windowPtr->getDevice(), tex.image,
nullptr);
1901 if (tex.memory != VK_NULL_HANDLE) {
1903 vkFreeMemory(windowPtr->getDevice(), tex.memory,
nullptr);
1908 if (textureSampler != VK_NULL_HANDLE) {
1910 vkDestroySampler(windowPtr->getDevice(), textureSampler,
nullptr);
1911 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.
void setColorAttachmentFormat(VkFormat format)
Override the dynamic-rendering color format for this model.
void resize(VK_Window *window)
Rebuild swapchain-dependent resources after resize.
void renderWithPushConstants(VkCommandBuffer cmd, uint32_t imageIndex, size_t textureIndex, const UniformBufferObject &ubo, bool wireframe=false)
Record one draw using push constants for per-draw transforms and an explicit texture slot.
void render(VkCommandBuffer cmd, uint32_t imageIndex, bool wireframe=false) const
Record draw commands for this model.
void renderWithExternalTexture(VkCommandBuffer cmd, uint32_t imageIndex, VkImageView textureView, const UniformBufferObject &ubo, bool wireframe=false)
Render using a non-owning shader-readable image view as texture slot zero.
void enableExtendedFragmentUniforms()
Use binding 1 for fragment uniforms and binding 2 for model transforms.
bool updatePrimaryTexture(const void *pixels, int width, int height, int pitch=0)
Upload raw RGBA pixels into the primary model texture.
void setFragmentPushConstants(const ModelFragmentPushConstants &constants)
Set sprite-compatible push constants used by custom fragment shaders.
Main Vulkan window wrapper for MXVK.
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.