diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp index b2790ede..de4e9706 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp @@ -19,6 +19,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { XXHASH_VERIFY(XXH64_update(m_hashState, &payload.vertexInputHash, sizeof(payload.vertexInputHash))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.pipelineLayout, sizeof(payload.pipelineLayout))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.renderPass, sizeof(payload.renderPass))); + XXHASH_VERIFY(XXH64_update(m_hashState, &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.subpass, sizeof(payload.subpass))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.topology, sizeof(payload.topology))); XXHASH_VERIFY(XXH64_update(m_hashState, &payload.cullMode, sizeof(payload.cullMode))); @@ -61,6 +62,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(payload.vertexInputState != nullptr, "PipelineFactory: vertexInputState is null"); MOBILEGL_ASSERT(payload.pipelineLayout != VK_NULL_HANDLE, "PipelineFactory: pipelineLayout is null"); MOBILEGL_ASSERT(payload.renderPass != VK_NULL_HANDLE, "PipelineFactory: renderPass is null"); + MOBILEGL_ASSERT(payload.colorAttachmentCount <= 32, + "PipelineFactory: colorAttachmentCount=%u is unexpectedly large", + payload.colorAttachmentCount); + MGLOG_D("PipelineFactory::CreatePipeline: programHash=0x%llx vertexInputHash=0x%llx colorAttachmentCount=%u subpass=%u", + static_cast(payload.programHash), + static_cast(payload.vertexInputHash), + payload.colorAttachmentCount, + payload.subpass); static constexpr VkDynamicState kDynamicStates[] = { VK_DYNAMIC_STATE_VIEWPORT, @@ -95,18 +104,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { depthStencil.depthBoundsTestEnable = VK_FALSE; depthStencil.stencilTestEnable = VK_FALSE; - VkPipelineColorBlendAttachmentState colorAttach{}; - colorAttach.colorWriteMask = payload.colorWriteMask; - colorAttach.blendEnable = payload.blendEnable ? VK_TRUE : VK_FALSE; - colorAttach.srcColorBlendFactor = payload.srcColorBlendFactor; - colorAttach.dstColorBlendFactor = payload.dstColorBlendFactor; - colorAttach.colorBlendOp = VK_BLEND_OP_ADD; - colorAttach.srcAlphaBlendFactor = payload.srcAlphaBlendFactor; - colorAttach.dstAlphaBlendFactor = payload.dstAlphaBlendFactor; - colorAttach.alphaBlendOp = VK_BLEND_OP_ADD; + VkPipelineColorBlendAttachmentState colorAttachTemplate{}; + colorAttachTemplate.colorWriteMask = payload.colorWriteMask; + colorAttachTemplate.blendEnable = payload.blendEnable ? VK_TRUE : VK_FALSE; + colorAttachTemplate.srcColorBlendFactor = payload.srcColorBlendFactor; + colorAttachTemplate.dstColorBlendFactor = payload.dstColorBlendFactor; + colorAttachTemplate.colorBlendOp = VK_BLEND_OP_ADD; + colorAttachTemplate.srcAlphaBlendFactor = payload.srcAlphaBlendFactor; + colorAttachTemplate.dstAlphaBlendFactor = payload.dstAlphaBlendFactor; + colorAttachTemplate.alphaBlendOp = VK_BLEND_OP_ADD; + Vector colorAttachments(payload.colorAttachmentCount, + colorAttachTemplate); VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; - blend.attachmentCount = 1; - blend.pAttachments = &colorAttach; + blend.attachmentCount = payload.colorAttachmentCount; + blend.pAttachments = colorAttachments.empty() ? nullptr : colorAttachments.data(); VkGraphicsPipelineCreateInfo gpi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; gpi.stageCount = static_cast(payload.stages->size()); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h index 842aa10f..1c86e608 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h @@ -22,6 +22,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { HashType vertexInputHash = 0; VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; VkRenderPass renderPass = VK_NULL_HANDLE; + Uint32 colorAttachmentCount = 1; Uint32 subpass = 0; VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; VkCullModeFlags cullMode = VK_CULL_MODE_BACK_BIT; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index efdbffb9..41e8e60e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -27,6 +27,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { using SpvcSession = MG_Util::ShaderTranspiler::SpvcSession; using SessionUsageBit = MG_Util::ShaderTranspiler::SessionUsageBit; + struct DescriptorKey { + ProgramFactory::DescriptorBindingKind kind = ProgramFactory::DescriptorBindingKind::None; + String name; + + Bool operator==(const DescriptorKey& other) const { + return kind == other.kind && name == other.name; + } + }; + + struct DescriptorKeyHash { + SizeT operator()(const DescriptorKey& key) const noexcept { + return std::hash{}(key.name) ^ (static_cast(key.kind) << 1); + } + }; + struct PositionTargetInfo { Uint32 variableId = 0; Uint32 vectorTypeId = 0; @@ -324,6 +339,193 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (hasVertex) return ShaderStage::Vertex; return ShaderStage::Unknown; } + + ProgramFactory::DescriptorBindingKind ReflectDescriptorTypeToBindingKind(SpvReflectDescriptorType descriptorType) { + switch (descriptorType) { + case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER: + return ProgramFactory::DescriptorBindingKind::UniformBufferDynamic; + case SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: + case SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE: + return ProgramFactory::DescriptorBindingKind::CombinedImageSampler; + default: + MOBILEGL_ASSERT(false, "ProgramFactory: unsupported reflected descriptor type %d", + static_cast(descriptorType)); + return ProgramFactory::DescriptorBindingKind::None; + } + } + + String NormalizeDescriptorName(const SpvReflectDescriptorBinding& binding, + ProgramFactory::DescriptorBindingKind kind) { + const char* rawName = binding.name; + if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic && + binding.type_description != nullptr && binding.type_description->type_name != nullptr) { + rawName = binding.type_description->type_name; + } + + MOBILEGL_ASSERT(rawName != nullptr && rawName[0] != '\0', + "ProgramFactory: descriptor has empty name (spirvId=%u type=%d)", binding.spirv_id, + static_cast(binding.descriptor_type)); + + String name = rawName; + if (kind == ProgramFactory::DescriptorBindingKind::CombinedImageSampler) { + const auto arraySuffix = name.find("[0]"); + if (arraySuffix != String::npos) { + name = name.substr(0, arraySuffix); + } + } + return name; + } + + Bool RemapDescriptorBindingsForVulkan(const Vector>& inputModules, Uint32 maxBindings, + Vector>& outputModules) { + outputModules = inputModules; + + Vector reflectModules(outputModules.size()); + Vector reflectModuleValid(outputModules.size(), false); + UnorderedMap assignedBindings; + Uint32 nextBinding = 0; + + const auto destroyReflectModules = [&]() { + for (SizeT moduleIndex = 0; moduleIndex < reflectModules.size(); ++moduleIndex) { + if (!reflectModuleValid[moduleIndex]) { + continue; + } + spvReflectDestroyShaderModule(&reflectModules[moduleIndex]); + reflectModuleValid[moduleIndex] = false; + } + }; + + for (SizeT moduleIndex = 0; moduleIndex < outputModules.size(); ++moduleIndex) { + auto& moduleSpv = outputModules[moduleIndex]; + if (moduleSpv.empty()) { + continue; + } + + const SpvReflectResult createResult = + spvReflectCreateShaderModule(moduleSpv.size() * sizeof(Uint), moduleSpv.data(), + &reflectModules[moduleIndex]); + MOBILEGL_ASSERT(createResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory: failed to create reflection module for stage %zu (result=%d)", + moduleIndex, static_cast(createResult)); + if (createResult != SPV_REFLECT_RESULT_SUCCESS) { + destroyReflectModules(); + return false; + } + reflectModuleValid[moduleIndex] = true; + + uint32_t bindingCount = 0; + SpvReflectResult reflectResult = + spvReflectEnumerateDescriptorBindings(&reflectModules[moduleIndex], &bindingCount, nullptr); + MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory: failed to enumerate descriptor bindings for stage %zu (result=%d)", + moduleIndex, static_cast(reflectResult)); + if (reflectResult != SPV_REFLECT_RESULT_SUCCESS) { + destroyReflectModules(); + return false; + } + + Vector bindings(bindingCount); + if (bindingCount > 0) { + reflectResult = spvReflectEnumerateDescriptorBindings(&reflectModules[moduleIndex], &bindingCount, + bindings.data()); + MOBILEGL_ASSERT( + reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory: failed to fetch descriptor bindings for stage %zu (result=%d)", moduleIndex, + static_cast(reflectResult)); + if (reflectResult != SPV_REFLECT_RESULT_SUCCESS) { + destroyReflectModules(); + return false; + } + } + + std::sort(bindings.begin(), bindings.end(), [](const auto* lhs, const auto* rhs) { + if (lhs->set != rhs->set) { + return lhs->set < rhs->set; + } + if (lhs->binding != rhs->binding) { + return lhs->binding < rhs->binding; + } + return lhs->spirv_id < rhs->spirv_id; + }); + + for (auto* binding : bindings) { + MOBILEGL_ASSERT(binding != nullptr, "ProgramFactory: null descriptor binding reflection record"); + const auto kind = ReflectDescriptorTypeToBindingKind(binding->descriptor_type); + MOBILEGL_ASSERT(binding->count == 1, + "ProgramFactory: descriptor arrays are unsupported (name='%s' count=%u)", + binding->name ? binding->name : "", binding->count); + + DescriptorKey key{}; + key.kind = kind; + key.name = NormalizeDescriptorName(*binding, kind); + + Uint32 assignedBinding = 0; + const auto it = assignedBindings.find(key); + if (it == assignedBindings.end()) { + MOBILEGL_ASSERT(nextBinding < maxBindings, + "ProgramFactory: reflected descriptor count exceeded maxBindings (%u >= %u)", + nextBinding, maxBindings); + assignedBinding = nextBinding; + assignedBindings.emplace(key, assignedBinding); + ++nextBinding; + } else { + assignedBinding = it->second; + } + + if (binding->binding != assignedBinding || binding->set != 0) { + reflectResult = spvReflectChangeDescriptorBindingNumbers(&reflectModules[moduleIndex], binding, + assignedBinding, 0); + MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory: failed to remap descriptor '%s' in stage %zu (result=%d)", + key.name.c_str(), moduleIndex, static_cast(reflectResult)); + if (reflectResult != SPV_REFLECT_RESULT_SUCCESS) { + destroyReflectModules(); + return false; + } + } + } + } + + for (SizeT moduleIndex = 0; moduleIndex < outputModules.size(); ++moduleIndex) { + if (!reflectModuleValid[moduleIndex]) { + continue; + } + + const Uint32 codeSizeBytes = spvReflectGetCodeSize(&reflectModules[moduleIndex]); + MOBILEGL_ASSERT((codeSizeBytes % sizeof(Uint)) == 0, + "ProgramFactory: reflected SPIR-V size is not word aligned for stage %zu", + moduleIndex); + const Uint32* code = spvReflectGetCode(&reflectModules[moduleIndex]); + MOBILEGL_ASSERT(code != nullptr, "ProgramFactory: reflected SPIR-V code pointer is null for stage %zu", + moduleIndex); + outputModules[moduleIndex].assign(code, code + (codeSizeBytes / sizeof(Uint))); + } + + destroyReflectModules(); + return true; + } + + TextureTarget ReflectImageTraitsToTextureTarget(const SpvReflectImageTraits& imageTraits) { + switch (imageTraits.dim) { + case SpvDim1D: + return imageTraits.arrayed != 0 ? TextureTarget::Texture1DArray : TextureTarget::Texture1D; + case SpvDim2D: + if (imageTraits.ms != 0) { + return imageTraits.arrayed != 0 ? TextureTarget::Texture2DMultisampleArray + : TextureTarget::Texture2DMultisample; + } + return imageTraits.arrayed != 0 ? TextureTarget::Texture2DArray : TextureTarget::Texture2D; + case SpvDim3D: + return TextureTarget::Texture3D; + case SpvDimCube: + return imageTraits.arrayed != 0 ? TextureTarget::TextureCubeMapArray : TextureTarget::TextureCubeMap; + case SpvDimBuffer: + return TextureTarget::TextureBuffer; + default: + MOBILEGL_ASSERT(false, "ProgramFactory: unsupported sampler image dim %d", imageTraits.dim); + return TextureTarget::Unknown; + } + } } // namespace VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) { @@ -419,70 +621,137 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void ProgramFactory::ReflectLayout(const MG_State::GLState::ProgramObject& program, - VkProgramObject& entry) const { + const Vector>& spirv, VkProgramObject& entry) const { // Initialize layout vectors entry.bindingKinds.assign(m_maxBindings, DescriptorBindingKind::None); + entry.uniformBlockIndexByBinding.assign(m_maxBindings, -1); + entry.samplerNameByBinding.assign(m_maxBindings, String()); entry.samplerUniformLocationByBinding.assign(m_maxBindings, -1); entry.samplerTextureTargetByBinding.assign(m_maxBindings, TextureTarget::Texture2D); entry.globalUboBinding = -1; entry.dynamicBindings.clear(); // Use SpvcSession (Reflection mode) to reflect all SPIR-V modules in a single pass per module - const auto& spirv = program.GetGeneratedSpirv(); for (const auto& module : spirv) { if (module.empty()) { continue; } SpvcSession session(module, SessionUsageBit::Reflection); + SpvReflectShaderModule reflectModule{}; + const SpvReflectResult createReflectResult = + spvReflectCreateShaderModule(module.size() * sizeof(Uint), module.data(), &reflectModule); + MOBILEGL_ASSERT(createReflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectLayout: failed to create reflection module (result=%d)", + static_cast(createReflectResult)); // Reflect uniform buffers auto ubos = session.GetShaderInterface(SPVC_RESOURCE_TYPE_UNIFORM_BUFFER); for (const auto& ubo : ubos) { const Uint32 binding = ubo.location; // GetShaderInterface stores binding in location field - if (binding >= m_maxBindings) { - continue; - } - if (entry.bindingKinds[binding] == DescriptorBindingKind::None) { - entry.bindingKinds[binding] = DescriptorBindingKind::UniformBufferDynamic; - } + MOBILEGL_ASSERT(binding < m_maxBindings, + "ProgramFactory::ReflectLayout: UBO binding %u exceeds maxBindings=%u for '%s'", + binding, m_maxBindings, ubo.name.c_str()); + + MOBILEGL_ASSERT(entry.bindingKinds[binding] == DescriptorBindingKind::None || + entry.bindingKinds[binding] == DescriptorBindingKind::UniformBufferDynamic, + "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for UBO '%s'", + binding, ubo.name.c_str()); + entry.bindingKinds[binding] = DescriptorBindingKind::UniformBufferDynamic; // Check for global UBO - if (entry.globalUboBinding < 0 && - std::strstr(ubo.name.c_str(), MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) != nullptr) { + if (std::strstr(ubo.name.c_str(), MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) != nullptr) { + MOBILEGL_ASSERT(entry.globalUboBinding < 0 || entry.globalUboBinding == static_cast(binding), + "ProgramFactory::ReflectLayout: global UBO binding mismatch (%d vs %u)", + entry.globalUboBinding, binding); + MOBILEGL_ASSERT(entry.uniformBlockIndexByBinding[binding] < 0, + "ProgramFactory::ReflectLayout: global UBO shares binding %u with regular UBO index %d", + binding, entry.uniformBlockIndexByBinding[binding]); entry.globalUboBinding = static_cast(binding); + continue; } + + const Uint blockIndex = program.GetUniformBlockIndex(ubo.name.c_str()); + MOBILEGL_ASSERT(blockIndex != 0xFFFFFFFFu, + "ProgramFactory::ReflectLayout: failed to resolve uniform block '%s'", ubo.name.c_str()); + MOBILEGL_ASSERT(entry.globalUboBinding != static_cast(binding), + "ProgramFactory::ReflectLayout: regular UBO '%s' collides with global UBO binding %u", + ubo.name.c_str(), binding); + MOBILEGL_ASSERT(entry.uniformBlockIndexByBinding[binding] < 0 || + entry.uniformBlockIndexByBinding[binding] == static_cast(blockIndex), + "ProgramFactory::ReflectLayout: descriptor binding %u maps to conflicting UBO blocks (%d vs %u)", + binding, entry.uniformBlockIndexByBinding[binding], blockIndex); + entry.uniformBlockIndexByBinding[binding] = static_cast(blockIndex); } // Reflect sampled images - auto samplers = session.GetShaderInterface(SPVC_RESOURCE_TYPE_SAMPLED_IMAGE); - for (const auto& sampler : samplers) { - const Uint32 binding = sampler.location; // GetShaderInterface stores binding in location field - if (binding >= m_maxBindings) { + uint32_t reflectedBindingCount = 0; + SpvReflectResult reflectResult = + spvReflectEnumerateDescriptorBindings(&reflectModule, &reflectedBindingCount, nullptr); + MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectLayout: failed to enumerate descriptor bindings (result=%d)", + static_cast(reflectResult)); + + Vector reflectedBindings(reflectedBindingCount); + if (reflectedBindingCount > 0) { + reflectResult = spvReflectEnumerateDescriptorBindings(&reflectModule, &reflectedBindingCount, + reflectedBindings.data()); + MOBILEGL_ASSERT( + reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectLayout: failed to fetch descriptor bindings (result=%d)", + static_cast(reflectResult)); + } + + for (const auto* sampler : reflectedBindings) { + if (sampler == nullptr) { + continue; + } + if (sampler->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER && + sampler->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE) { continue; } - // Sampler always wins over UBO for a binding slot + const Uint32 binding = sampler->binding; + const String uniformName = NormalizeDescriptorName(*sampler, DescriptorBindingKind::CombinedImageSampler); + MOBILEGL_ASSERT(binding < m_maxBindings, + "ProgramFactory::ReflectLayout: sampler binding %u exceeds maxBindings=%u for '%s'", + binding, m_maxBindings, uniformName.c_str()); + + MOBILEGL_ASSERT(entry.bindingKinds[binding] == DescriptorBindingKind::None || + entry.bindingKinds[binding] == DescriptorBindingKind::CombinedImageSampler, + "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for sampler '%s'", + binding, uniformName.c_str()); entry.bindingKinds[binding] = DescriptorBindingKind::CombinedImageSampler; - // Resolve uniform location for this sampler - String uniformName = sampler.name; - Int location = program.GetUniformLocation(uniformName); - if (location < 0) { - const auto arraySuffix = uniformName.find("[0]"); - if (arraySuffix != String::npos) { - uniformName = uniformName.substr(0, arraySuffix); - location = program.GetUniformLocation(uniformName); - } - } - if (location < 0) { - continue; - } + const Int location = program.GetUniformLocation(uniformName); + const TextureTarget target = + location >= 0 ? UniformTypeToTextureTarget(program.GetUniformType(static_cast(location))) + : ReflectImageTraitsToTextureTarget(sampler->image); + MOBILEGL_ASSERT(target != TextureTarget::Unknown, + "ProgramFactory::ReflectLayout: failed to resolve sampler target for '%s'", + uniformName.c_str()); + MOBILEGL_ASSERT(entry.samplerUniformLocationByBinding[binding] < 0 || location < 0 || + entry.samplerUniformLocationByBinding[binding] == location, + "ProgramFactory::ReflectLayout: sampler binding %u maps to conflicting uniform locations (%d vs %d)", + binding, entry.samplerUniformLocationByBinding[binding], location); + MOBILEGL_ASSERT(entry.samplerUniformLocationByBinding[binding] < 0 || + entry.samplerTextureTargetByBinding[binding] == target, + "ProgramFactory::ReflectLayout: sampler binding %u maps to conflicting texture targets (%d vs %d)", + binding, static_cast(entry.samplerTextureTargetByBinding[binding]), + static_cast(target)); + MOBILEGL_ASSERT(entry.samplerNameByBinding[binding].empty() || + entry.samplerNameByBinding[binding] == uniformName, + "ProgramFactory::ReflectLayout: sampler binding %u maps to conflicting names ('%s' vs '%s')", + binding, entry.samplerNameByBinding[binding].c_str(), uniformName.c_str()); - entry.samplerUniformLocationByBinding[binding] = location; - entry.samplerTextureTargetByBinding[binding] = - UniformTypeToTextureTarget(program.GetUniformType(static_cast(location))); + if (location >= 0) { + entry.samplerUniformLocationByBinding[binding] = location; + } + entry.samplerNameByBinding[binding] = uniformName; + entry.samplerTextureTargetByBinding[binding] = target; } + + spvReflectDestroyShaderModule(&reflectModule); } // Build Vulkan descriptor set layout and pipeline layout from reflected binding kinds @@ -535,6 +804,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { entry.hash = hash; auto& shaders = program.GetAttachedShaders(); auto& spirv = program.GetGeneratedSpirv(); + Vector> moduleSpirvs(spirv.size()); const ShaderStage fixupStage = PickClipFixupStage(shaders); @@ -542,14 +812,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& spv = spirv[i]; if (spv.empty()) continue; - Vector moduleSpv; - // Apply position fixup if needed if (fixupStage != ShaderStage::Unknown && shaders[i] && shaders[i]->GetShaderStage() == fixupStage) { - TransformSpirvForVulkanPositionFix(spv, moduleSpv, flags); + TransformSpirvForVulkanPositionFix(spv, moduleSpirvs[i], flags); } else { - moduleSpv = spv; + moduleSpirvs[i] = spv; } + } + + const Bool remapOk = RemapDescriptorBindingsForVulkan(moduleSpirvs, m_maxBindings, moduleSpirvs); + MOBILEGL_ASSERT(remapOk, "ProgramFactory::GetOrCreateProgram: descriptor binding remap failed"); + + for (SizeT i = 0; i < shaders.size(); ++i) { + auto& moduleSpv = moduleSpirvs[i]; + if (moduleSpv.empty()) continue; VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; smci.codeSize = moduleSpv.size() * sizeof(Uint); @@ -569,7 +845,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } // Reflect and create layout as part of the program object - ReflectLayout(program, entry); + ReflectLayout(program, moduleSpirvs, entry); return entry; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h index 06fe897c..4ab42602 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -45,6 +45,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; Vector bindingKinds; Vector dynamicBindings; + Vector uniformBlockIndexByBinding; + Vector samplerNameByBinding; Vector samplerUniformLocationByBinding; Vector samplerTextureTargetByBinding; Int globalUboBinding = -1; @@ -62,6 +64,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { pipelineLayout = other.pipelineLayout; bindingKinds = std::move(other.bindingKinds); dynamicBindings = std::move(other.dynamicBindings); + uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding); + samplerNameByBinding = std::move(other.samplerNameByBinding); samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding); samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding); globalUboBinding = other.globalUboBinding; @@ -82,6 +86,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { pipelineLayout = other.pipelineLayout; bindingKinds = std::move(other.bindingKinds); dynamicBindings = std::move(other.dynamicBindings); + uniformBlockIndexByBinding = std::move(other.uniformBlockIndexByBinding); + samplerNameByBinding = std::move(other.samplerNameByBinding); samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding); samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding); globalUboBinding = other.globalUboBinding; @@ -133,7 +139,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { private: static TextureTarget UniformTypeToTextureTarget(GLenum glType); - void ReflectLayout(const MG_State::GLState::ProgramObject& program, VkProgramObject& entry) const; + void ReflectLayout(const MG_State::GLState::ProgramObject& program, const Vector>& spirv, + VkProgramObject& entry) const; VkDevice m_device = VK_NULL_HANDLE; Uint32 m_maxBindings = 0; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index 7f16d652..a3852041 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -53,6 +53,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } + static Int ResolveSamplerUnitIndex(const MG_State::GLState::ProgramObject& program, Int location, Uint32 binding) { + MOBILEGL_ASSERT(location >= -1, "ResolveSamplerUnitIndex: invalid sampler location for binding %u", binding); + if (location < 0) { + return 0; + } + const Int uniformUnit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); + MOBILEGL_ASSERT(uniformUnit >= -1, + "ResolveSamplerUnitIndex: invalid texture unit for binding %u location %d (unit=%d)", binding, + location, uniformUnit); + return uniformUnit >= 0 ? uniformUnit : 0; + } + Bool UniformManager::Initialize(VkDevice device, VkBufferManager* bufferManager, ProgramFactory* programFactory, VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount, @@ -164,28 +176,34 @@ namespace MobileGL::MG_Backend::DirectVulkan { (void)commandBuffer; MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptor: texture manager is null"); MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null"); + MOBILEGL_ASSERT(binding < programObj.samplerNameByBinding.size(), + "ResolveSamplerDescriptor: sampler binding %u name lookup out of range", binding); SharedPtr texture; - if (!ResolveSamplerTexture(program, programObj, binding, texture)) { - return false; - } + const Bool resolvedTexture = ResolveSamplerTexture(program, programObj, binding, texture); + MOBILEGL_ASSERT(resolvedTexture, + "ResolveSamplerDescriptor: failed to resolve sampler texture for binding %u ('%s')", binding, + programObj.samplerNameByBinding[binding].c_str()); const Int location = programObj.samplerUniformLocationByBinding[binding]; - const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); + const Int unit = ResolveSamplerUnitIndex(program, location, binding); auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); const auto samplerOverride = textureUnit.GetSamplerObject(); - if (!texture) { - return false; - } + MOBILEGL_ASSERT(texture != nullptr, + "ResolveSamplerDescriptor: sampler binding %u ('%s') resolved null texture (location=%d unit=%d target=%d)", + binding, programObj.samplerNameByBinding[binding].c_str(), location, unit, + static_cast(programObj.samplerTextureTargetByBinding[binding])); const MG_State::GLState::SamplerObject* samplerToUse = samplerOverride ? samplerOverride.get() : texture->GetSamplerObject().get(); - if (!samplerToUse) { - return false; - } + MOBILEGL_ASSERT(samplerToUse != nullptr, + "ResolveSamplerDescriptor: sampler binding %u ('%s') has no sampler object (textureId=%d location=%d unit=%d)", + binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(), location, + unit); VkTextureManager::TextureResource* resource = m_textureManager->SyncTextureAndGetDescriptor(*texture); - if (resource == nullptr) { - return false; - } + MOBILEGL_ASSERT(resource != nullptr, + "ResolveSamplerDescriptor: sampler binding %u ('%s') failed to create/sync texture resource (textureId=%d target=%d location=%d unit=%d)", + binding, programObj.samplerNameByBinding[binding].c_str(), texture->GetExternalIndex(), + static_cast(texture->GetTarget()), location, unit); if (!IsValidSampledImageLayout(resource->layout)) { auto drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); FramebufferAttachmentType attachmentType = FramebufferAttachmentType::None; @@ -218,14 +236,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { const SamplerBindingOverride& samplerBindingOverride, VkDescriptorImageInfo& outImageInfo) const { MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptorOverride: texture manager is null"); MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptorOverride: sampler manager is null"); - if (samplerBindingOverride.texture == nullptr || samplerBindingOverride.sampler == nullptr) { - return false; - } + MOBILEGL_ASSERT(samplerBindingOverride.texture != nullptr, + "ResolveSamplerDescriptorOverride: override texture is null for binding %u", + samplerBindingOverride.binding); + MOBILEGL_ASSERT(samplerBindingOverride.sampler != nullptr, + "ResolveSamplerDescriptorOverride: override sampler is null for binding %u", + samplerBindingOverride.binding); auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*samplerBindingOverride.texture); - if (resource == nullptr || !IsValidSampledImageLayout(resource->layout)) { - return false; - } + MOBILEGL_ASSERT(resource != nullptr, + "ResolveSamplerDescriptorOverride: failed to sync override texture resource for binding %u textureId=%d", + samplerBindingOverride.binding, samplerBindingOverride.texture->GetExternalIndex()); + MOBILEGL_ASSERT(IsValidSampledImageLayout(resource->layout), + "ResolveSamplerDescriptorOverride: invalid layout %d for binding %u textureId=%d", + static_cast(resource->layout), samplerBindingOverride.binding, + samplerBindingOverride.texture->GetExternalIndex()); outImageInfo = { .sampler = m_samplerManager->GetOrCreateSampler(*samplerBindingOverride.sampler), @@ -239,23 +264,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { const ProgramFactory::VkProgramObject& programObj, Uint32 binding, SharedPtr& outTexture) const { outTexture.reset(); - if (!MG_State::pGLContext || binding >= programObj.samplerUniformLocationByBinding.size()) { - return false; - } + MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, "ResolveSamplerTexture: GL context is null"); + MOBILEGL_ASSERT(binding < programObj.samplerUniformLocationByBinding.size(), + "ResolveSamplerTexture: sampler location binding %u out of range", binding); + MOBILEGL_ASSERT(binding < programObj.samplerTextureTargetByBinding.size(), + "ResolveSamplerTexture: sampler target binding %u out of range", binding); const Int location = programObj.samplerUniformLocationByBinding[binding]; - if (location < 0) { - return false; - } - - const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); - if (unit < 0) { - return false; - } + const Int unit = ResolveSamplerUnitIndex(program, location, binding); auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit); const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding]; outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject(); + MOBILEGL_ASSERT(outTexture != nullptr, + "ResolveSamplerTexture: no texture bound for sampler binding=%u location=%d unit=%d target=%d", + binding, location, unit, static_cast(preferredTarget)); return outTexture != nullptr; } @@ -284,62 +307,85 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - Bool UniformManager::GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, - Vector& outData, - Vector& outSizes) const { - outData.assign(m_maxBindings, nullptr); - outSizes.assign(m_maxBindings, 0); + Bool UniformManager::ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, Uint32 binding, + const void*& outData, VkDeviceSize& outSize) const { + outData = nullptr; + outSize = 0; - const Uint32 activeUniformBlockCount = static_cast(program.GetActiveUniformBlocksCount()); - const Uint32 uniformBindingPointCount = - static_cast(MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::Uniform)); + MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, "ResolveUniformBufferPayload: GL context is null"); + MOBILEGL_ASSERT(binding < programObj.bindingKinds.size(), + "ResolveUniformBufferPayload: binding %u out of range", binding); + MOBILEGL_ASSERT(programObj.bindingKinds[binding] == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic, + "ResolveUniformBufferPayload: binding %u is not a uniform buffer descriptor", binding); - for (Uint32 blockIndex = 0; blockIndex < activeUniformBlockCount; ++blockIndex) { - const Uint32 binding = program.GetUniformBlockBinding(blockIndex); - if (binding >= m_maxBindings) { - continue; - } - - VkDeviceSize blockSize = static_cast(program.GetUBOSizeAt(blockIndex)); - if (blockSize == 0) { - continue; - } - - if (binding >= uniformBindingPointCount) { - continue; - } - auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, binding); - const auto bufferObject = bindingPoint.GetBoundObject(); - if (!bufferObject) { - continue; - } - - const auto bufferData = bufferObject->GetDataReadOnly(); - if (!bufferData || bufferData->empty()) { - continue; - } - - const auto range = bindingPoint.GetRange(); - const VkDeviceSize bufferSize = static_cast(bufferObject->GetSize()); - VkDeviceSize rangeStart = static_cast(range.start); - VkDeviceSize rangeEnd = static_cast(range.end); - - if (rangeStart >= bufferSize) { - continue; - } - if (rangeEnd <= rangeStart || rangeEnd > bufferSize) { - rangeEnd = bufferSize; - } - - VkDeviceSize available = rangeEnd - rangeStart; - if (available == 0) { - continue; - } - - outData[binding] = bufferData->data() + static_cast(rangeStart); - outSizes[binding] = std::min(blockSize, available); + if (programObj.globalUboBinding == static_cast(binding)) { + outData = program.GetUBOData(); + outSize = static_cast(program.GetUBOSize()); + MOBILEGL_ASSERT(outData != nullptr, "ResolveUniformBufferPayload: global UBO data is null"); + MOBILEGL_ASSERT(outSize > 0, "ResolveUniformBufferPayload: global UBO size is zero"); + return outData != nullptr && outSize > 0; } + MOBILEGL_ASSERT(binding < programObj.uniformBlockIndexByBinding.size(), + "ResolveUniformBufferPayload: UBO mapping binding %u out of range", binding); + const Int blockIndex = programObj.uniformBlockIndexByBinding[binding]; + MOBILEGL_ASSERT(blockIndex >= 0, + "ResolveUniformBufferPayload: no uniform block mapped to descriptor binding %u", binding); + + const Uint32 activeUniformBlockCount = static_cast(program.GetActiveUniformBlocksCount()); + MOBILEGL_ASSERT(static_cast(blockIndex) < activeUniformBlockCount, + "ResolveUniformBufferPayload: uniform block index %d out of range (count=%u)", blockIndex, + activeUniformBlockCount); + + const Uint32 frontendBinding = program.GetUniformBlockBinding(static_cast(blockIndex)); + const Uint32 uniformBindingPointCount = + static_cast(MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::Uniform)); + MOBILEGL_ASSERT(frontendBinding < uniformBindingPointCount, + "ResolveUniformBufferPayload: frontend UBO binding %u out of range for block '%s'", + frontendBinding, program.GetUniformBlockName(static_cast(blockIndex)).c_str()); + + auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::Uniform, frontendBinding); + const auto bufferObject = bindingPoint.GetBoundObject(); + MOBILEGL_ASSERT(bufferObject != nullptr, + "ResolveUniformBufferPayload: no UBO bound at frontend binding %u for block '%s'", + frontendBinding, program.GetUniformBlockName(static_cast(blockIndex)).c_str()); + + const auto bufferData = bufferObject->GetDataReadOnly(); + MOBILEGL_ASSERT(bufferData != nullptr && !bufferData->empty(), + "ResolveUniformBufferPayload: bound UBO data is empty for block '%s'", + program.GetUniformBlockName(static_cast(blockIndex)).c_str()); + + const auto range = bindingPoint.GetRange(); + const VkDeviceSize bufferSize = static_cast(bufferObject->GetSize()); + const VkDeviceSize rangeStart = static_cast(range.start); + MOBILEGL_ASSERT(rangeStart < bufferSize, + "ResolveUniformBufferPayload: UBO range start %zu exceeds buffer size %zu for block '%s'", + static_cast(rangeStart), static_cast(bufferSize), + program.GetUniformBlockName(static_cast(blockIndex)).c_str()); + + VkDeviceSize rangeEnd = static_cast(range.end); + if (rangeEnd > bufferSize) { + rangeEnd = bufferSize; + } + MOBILEGL_ASSERT(rangeEnd > rangeStart, + "ResolveUniformBufferPayload: invalid UBO range [%zu, %zu) for block '%s'", + static_cast(rangeStart), static_cast(rangeEnd), + program.GetUniformBlockName(static_cast(blockIndex)).c_str()); + + const VkDeviceSize blockSize = static_cast(program.GetUBOSizeAt(static_cast(blockIndex))); + MOBILEGL_ASSERT(blockSize > 0, + "ResolveUniformBufferPayload: reflected UBO size is zero for block '%s'", + program.GetUniformBlockName(static_cast(blockIndex)).c_str()); + + const VkDeviceSize available = rangeEnd - rangeStart; + MOBILEGL_ASSERT(available >= blockSize, + "ResolveUniformBufferPayload: bound range %zu is smaller than UBO size %zu for block '%s'", + static_cast(available), static_cast(blockSize), + program.GetUniformBlockName(static_cast(blockIndex)).c_str()); + + outData = bufferData->data() + static_cast(rangeStart); + outSize = blockSize; return true; } @@ -450,13 +496,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } - Vector bindingData; - Vector bindingSizes; - if (!GatherBindingPayloads(program, bindingData, bindingSizes)) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: cannot gather UBO payloads"); - return false; - } - MOBILEGL_ASSERT(m_textureManager != nullptr, "BindProgramUniformBuffers: texture manager is null"); MOBILEGL_ASSERT(m_samplerManager != nullptr, "BindProgramUniformBuffers: sampler manager is null"); MOBILEGL_ASSERT(m_bufferManager != nullptr, "BindProgramUniformBuffers: buffer manager is null"); @@ -486,18 +525,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { write.descriptorCount = 1; if (kind == ProgramFactory::DescriptorBindingKind::UniformBufferDynamic) { - const void* payload = bindingData[binding]; - VkDeviceSize payloadSize = bindingSizes[binding]; - if (payload == nullptr || payloadSize == 0) { - if (programObj.globalUboBinding == static_cast(binding)) { - const void* globalUboData = program.GetUBOData(); - const VkDeviceSize globalUboSize = static_cast(program.GetUBOSize()); - if (globalUboData != nullptr && globalUboSize > 0) { - payload = globalUboData; - payloadSize = globalUboSize; - } - } - } + const void* payload = nullptr; + VkDeviceSize payloadSize = 0; + const Bool hasPayload = ResolveUniformBufferPayload(program, programObj, binding, payload, payloadSize); + MOBILEGL_ASSERT(hasPayload && payload != nullptr && payloadSize > 0, + "UniformDescriptorBinder::BindProgramUniformBuffers failed: missing UBO payload on binding %u", + binding); BufferSlice slice{}; if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, payload, payloadSize, @@ -528,16 +561,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { } else { hasImage = ResolveSamplerDescriptor(commandBuffer, program, programObj, binding, imageInfo); } - if (!hasImage) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has no valid texture descriptor", - binding); - return false; - } - if (imageInfo.sampler == VK_NULL_HANDLE || imageInfo.imageView == VK_NULL_HANDLE) { - MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has null sampler or imageView", - binding); - return false; - } + MOBILEGL_ASSERT(hasImage, + "UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has no valid texture descriptor", + binding); + MOBILEGL_ASSERT(imageInfo.sampler != VK_NULL_HANDLE && imageInfo.imageView != VK_NULL_HANDLE, + "UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has null sampler or imageView", + binding); imageInfos.push_back(imageInfo); write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write.pImageInfo = &imageInfos.back(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index 36d43997..79cb8f0b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -69,8 +69,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkDescriptorImageInfo& outImageInfo) const; Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride, VkDescriptorImageInfo& outImageInfo) const; - Bool GatherBindingPayloads(const MG_State::GLState::ProgramObject& program, Vector& outData, - Vector& outSizes) const; + Bool ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, Uint32 binding, + const void*& outData, VkDeviceSize& outSize) const; Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const; Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex); VkResult AllocateDescriptorSetsFromActivePool( diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index d67e1c59..a635502f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -200,7 +200,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto* texture2d = static_cast(texture); desc.flags = 0; - desc.format = + desc.format = isDefaultFbo ? + m_swapchainObject.GetSurfaceFormat().format : MG_Util::ConvertTextureInternalFormatToVkEnum( texture2d->GetFormat()); desc.samples = VK_SAMPLE_COUNT_1_BIT; @@ -242,6 +243,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { textureResources[i] = m_textureManager.SyncTextureAndGetDescriptor(*texture); MOBILEGL_ASSERT(textureResources[i], "GetOrCreateRenderPass: SyncTextureAndGetDescriptor failed at color attachment %d", i); + desc.format = textureResources[i]->format; trackedColorLayout = textureResources[i]->layout; trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { .target = TrackedAttachmentTarget::Texture, @@ -300,8 +302,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { trackedDepthLayout = depthTextureResource->layout; } depthAttachmentDescription.flags = 0; - depthAttachmentDescription.format = + depthAttachmentDescription.format = isDefaultFbo ? + m_swapchainObject.GetDepthStencilFormat() : MG_Util::ConvertTextureInternalFormatToVkEnum(texture.GetFormat()); + if (!isDefaultFbo) { + depthAttachmentDescription.format = depthTextureResource->format; + } depthAttachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT; depthAttachmentDescription.loadOp = clearDepth ? VK_ATTACHMENT_LOAD_OP_CLEAR : @@ -409,8 +415,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { Move(pendingClearAttachments), Move(trackedAttachmentLayouts), static_cast(attachmentViews.size()), + static_cast(colorAttachmentRefs.size()), extent, 1 }; + MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u extent=%dx%d", + static_cast(hash), + static_cast(compatibilityHash), + renderPassEntry.attachmentCount, + renderPassEntry.colorAttachmentCount, + extent.x(), + extent.y()); auto [insertedIt, _] = m_renderPasses.emplace(hash, Move(renderPassEntry)); return insertedIt->second; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index 073e6496..4ac091ed 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -46,6 +46,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector pendingClearAttachments; Vector trackedAttachmentLayouts; Uint32 attachmentCount = 0; + Uint32 colorAttachmentCount = 0; IntVec2 extent = {0, 0}; Uint32 subpass = 0; @@ -59,6 +60,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { std::swap(pendingClearAttachments, that.pendingClearAttachments); std::swap(trackedAttachmentLayouts, that.trackedAttachmentLayouts); std::swap(attachmentCount, that.attachmentCount); + std::swap(colorAttachmentCount, that.colorAttachmentCount); std::swap(extent, that.extent); std::swap(subpass, that.subpass); } @@ -70,6 +72,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Vector& pendingClearAttachments, const Vector& trackedAttachmentLayouts, Uint32 attachmentCount, + Uint32 colorAttachmentCount, IntVec2 extent, int subpass): hash(hash), renderPass(renderpass), @@ -78,6 +81,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { pendingClearAttachments(Move(pendingClearAttachments)), trackedAttachmentLayouts(Move(trackedAttachmentLayouts)), attachmentCount(attachmentCount), + colorAttachmentCount(colorAttachmentCount), extent(extent), subpass(subpass) {} diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 8068209f..cd9c7842 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -14,6 +14,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { static constexpr VkPipelineStageFlags kGraphicsSampledReadStages = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; + struct TextureFormatInfo { + VkFormat format = VK_FORMAT_UNDEFINED; + Bool expandRgbToRgba = false; + Uint32 componentByteCount = 0; + Array alphaBytes = {0, 0, 0, 0}; + }; + static Bool IsValidSampledImageLayout(VkImageLayout layout) { switch (layout) { case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: @@ -27,6 +34,82 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } + static TextureFormatInfo ResolveTextureFormatInfo(TextureInternalFormat format) { + switch (format) { + case TextureInternalFormat::RGB: + case TextureInternalFormat::RGB8: + return {VK_FORMAT_R8G8B8A8_UNORM, true, 1, {0xFF, 0x00, 0x00, 0x00}}; + case TextureInternalFormat::SRGB8: + return {VK_FORMAT_R8G8B8A8_SRGB, true, 1, {0xFF, 0x00, 0x00, 0x00}}; + case TextureInternalFormat::RGB8Snorm: + return {VK_FORMAT_R8G8B8A8_SNORM, true, 1, {0x7F, 0x00, 0x00, 0x00}}; + case TextureInternalFormat::RGB16: + return {VK_FORMAT_R16G16B16A16_UNORM, true, 2, {0xFF, 0xFF, 0x00, 0x00}}; + case TextureInternalFormat::RGB16Snorm: + return {VK_FORMAT_R16G16B16A16_SNORM, true, 2, {0xFF, 0x7F, 0x00, 0x00}}; + case TextureInternalFormat::RGB16F: + return {VK_FORMAT_R16G16B16A16_SFLOAT, true, 2, {0x00, 0x3C, 0x00, 0x00}}; + case TextureInternalFormat::RGB32F: + return {VK_FORMAT_R32G32B32A32_SFLOAT, true, 4, {0x00, 0x00, 0x80, 0x3F}}; + case TextureInternalFormat::RGB8I: + return {VK_FORMAT_R8G8B8A8_SINT, true, 1, {0x01, 0x00, 0x00, 0x00}}; + case TextureInternalFormat::RGB8UI: + return {VK_FORMAT_R8G8B8A8_UINT, true, 1, {0x01, 0x00, 0x00, 0x00}}; + case TextureInternalFormat::RGB16I: + return {VK_FORMAT_R16G16B16A16_SINT, true, 2, {0x01, 0x00, 0x00, 0x00}}; + case TextureInternalFormat::RGB16UI: + return {VK_FORMAT_R16G16B16A16_UINT, true, 2, {0x01, 0x00, 0x00, 0x00}}; + case TextureInternalFormat::RGB32I: + return {VK_FORMAT_R32G32B32A32_SINT, true, 4, {0x01, 0x00, 0x00, 0x00}}; + case TextureInternalFormat::RGB32UI: + return {VK_FORMAT_R32G32B32A32_UINT, true, 4, {0x01, 0x00, 0x00, 0x00}}; + default: + return {MG_Util::ConvertTextureInternalFormatToVkEnum(format), false, 0, {0, 0, 0, 0}}; + } + } + + static Bool ExpandRgbSourceToRgba(const void* source, SizeT sourceByteSize, const IntVec3& texelSize, + const TextureFormatInfo& formatInfo, Vector& outExpandedData) { + MOBILEGL_ASSERT(source != nullptr, "ExpandRgbSourceToRgba: source is null"); + MOBILEGL_ASSERT(formatInfo.expandRgbToRgba, "ExpandRgbSourceToRgba: format does not require RGB expansion"); + MOBILEGL_ASSERT(formatInfo.componentByteCount > 0, + "ExpandRgbSourceToRgba: invalid component size for expanded RGB format"); + + const SizeT depth = static_cast(std::max(texelSize.z(), 1)); + const SizeT pixelCount = static_cast(texelSize.x()) * static_cast(texelSize.y()) * depth; + MOBILEGL_ASSERT(pixelCount > 0, "ExpandRgbSourceToRgba: invalid texel size (%d, %d, %d)", + texelSize.x(), texelSize.y(), texelSize.z()); + MOBILEGL_ASSERT(sourceByteSize == pixelCount * formatInfo.componentByteCount * 3, + "ExpandRgbSourceToRgba: unexpected source byte size=%zu for pixelCount=%zu componentBytes=%u", + sourceByteSize, pixelCount, formatInfo.componentByteCount); + + outExpandedData.resize(pixelCount * formatInfo.componentByteCount * 4); + const auto* src = static_cast(source); + auto* dst = outExpandedData.data(); + const SizeT srcPixelSize = static_cast(formatInfo.componentByteCount) * 3; + const SizeT dstPixelSize = static_cast(formatInfo.componentByteCount) * 4; + for (SizeT pixel = 0; pixel < pixelCount; ++pixel) { + const SizeT srcOffset = pixel * srcPixelSize; + const SizeT dstOffset = pixel * dstPixelSize; + std::memcpy(dst + dstOffset, src + srcOffset, srcPixelSize); + std::memcpy(dst + dstOffset + srcPixelSize, formatInfo.alphaBytes.data(), formatInfo.componentByteCount); + } + return true; + } + + static VkComponentMapping ResolveSampledViewComponents(const MG_State::GLState::ITextureObject& texture) { + VkComponentMapping components{ + VK_COMPONENT_SWIZZLE_R, + VK_COMPONENT_SWIZZLE_G, + VK_COMPONENT_SWIZZLE_B, + VK_COMPONENT_SWIZZLE_A, + }; + if (ResolveTextureFormatInfo(texture.GetFormat()).expandRgbToRgba) { + components.a = VK_COMPONENT_SWIZZLE_ONE; + } + return components; + } + Bool VkTextureManager::Initialize(const InitInfo& initInfo) { Shutdown(); @@ -256,7 +339,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { TextureUploadTarget uploadTarget, const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels, TextureResource &resource) { - const VkFormat format = MG_Util::ConvertTextureInternalFormatToVkEnum(texture.GetFormat()); + const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat()); + const VkFormat format = formatInfo.format; if (format == VK_FORMAT_UNDEFINED) { MGLOG_D("%s: format == VK_FORMAT_UNDEFINED", __func__); return false; @@ -347,7 +431,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource.fullView = VK_NULL_HANDLE; } - resource.fullView = CreateImageView(resource.image, resource.format, resource.aspect, baseMipLevel, levelCount); + const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture); + resource.fullView = CreateImageView(resource.image, resource.format, resource.aspect, baseMipLevel, levelCount, + &sampledComponents); if (resource.fullView == VK_NULL_HANDLE) { return false; } @@ -359,12 +445,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { } VkImageView VkTextureManager::CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect, - Uint32 baseMipLevel, Uint32 levelCount) const { + Uint32 baseMipLevel, Uint32 levelCount, + const VkComponentMapping* components) const { VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewInfo.format = format; + viewInfo.components = components != nullptr ? + *components : + VkComponentMapping{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, + VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; viewInfo.subresourceRange.aspectMask = aspect; viewInfo.subresourceRange.baseMipLevel = baseMipLevel; viewInfo.subresourceRange.levelCount = levelCount; @@ -381,14 +472,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { TextureResource &outResource) { struct UploadItem { Uint32 level = 0; - SizeT byteSize = 0; + SizeT uploadByteSize = 0; IntVec3 texelSize = {0, 0, 0}; const void* source = nullptr; + Vector expandedData; VkDeviceSize offset = 0; }; Vector uploadItems; uploadItems.reserve(outResource.mipLevels); + const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(mipmapTexture.GetFormat()); VkDeviceSize stagingSize = 0; for (Uint32 level = 0; level < outResource.mipLevels; ++level) { @@ -409,8 +502,25 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } - uploadItems.push_back({level, byteSize, texelSize, source, stagingSize}); - stagingSize += static_cast(byteSize); + UploadItem uploadItem{}; + uploadItem.level = level; + uploadItem.texelSize = texelSize; + uploadItem.source = source; + uploadItem.offset = stagingSize; + uploadItem.uploadByteSize = byteSize; + if (formatInfo.expandRgbToRgba) { + const Bool expanded = ExpandRgbSourceToRgba(source, byteSize, texelSize, formatInfo, + uploadItem.expandedData); + MOBILEGL_ASSERT(expanded, + "UploadDirtyMipLevels: failed to expand RGB textureId=%d level=%u to RGBA staging data", + mipmapTexture.GetExternalIndex(), level); + uploadItem.uploadByteSize = uploadItem.expandedData.size(); + } + uploadItems.push_back(Move(uploadItem)); + if (!uploadItems.back().expandedData.empty()) { + uploadItems.back().source = uploadItems.back().expandedData.data(); + } + stagingSize += static_cast(uploadItems.back().uploadByteSize); } if (uploadItems.empty()) { @@ -435,7 +545,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void* mapped = nullptr; VK_VERIFY(vmaMapMemory(m_allocator, stagingAllocation, &mapped), "vmaMapMemory(staging texture)"); for (const auto& item : uploadItems) { - std::memcpy(static_cast(mapped) + item.offset, item.source, item.byteSize); + std::memcpy(static_cast(mapped) + item.offset, item.source, item.uploadByteSize); } vmaUnmapMemory(m_allocator, stagingAllocation); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 724b53e3..7f798fb8 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -119,7 +119,8 @@ private: TextureResource &resource); Bool SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource); VkImageView CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect, - Uint32 baseMipLevel, Uint32 levelCount) const; + Uint32 baseMipLevel, Uint32 levelCount, + const VkComponentMapping* components = nullptr) const; Bool UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture, TextureUploadTarget uploadTarget, TextureResource &outResource); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index d4cd3101..05effa90 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -635,6 +635,23 @@ void main() { "InitializeBlitResources: missing uSurfaceTransform"); MOBILEGL_ASSERT(m_blitResources.program->GetUBOSize() > 0, "InitializeBlitResources: blit program global UBO is empty"); + MOBILEGL_ASSERT(m_programFactory != nullptr, "InitializeBlitResources: program factory is null"); + + ProgramFactory::CompileOptionFlags blitTransformFlags = 0; + const auto& blitProgramObj = m_programFactory->GetOrCreateProgram(*m_blitResources.program, blitTransformFlags); + Bool foundBlitSamplerBinding = false; + for (Uint32 binding = 0; binding < blitProgramObj.samplerNameByBinding.size(); ++binding) { + if (blitProgramObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) { + continue; + } + if (blitProgramObj.samplerNameByBinding[binding] == "uSource") { + m_blitResources.samplerBinding = binding; + foundBlitSamplerBinding = true; + break; + } + } + MOBILEGL_ASSERT(foundBlitSamplerBinding, + "InitializeBlitResources: failed to resolve reflected binding for uSource"); auto createSampler = [](Uint externalIndex, SamplerFilterMode filter) { auto sampler = MakeShared(externalIndex); @@ -650,7 +667,6 @@ void main() { m_blitResources.nearestSampler = createSampler(kHiddenBlitNearestSamplerId, SamplerFilterMode::Nearest); m_blitResources.linearSampler = createSampler(kHiddenBlitLinearSamplerId, SamplerFilterMode::Linear); - m_blitResources.samplerBinding = 0; return true; } @@ -673,6 +689,7 @@ void main() { .vertexInputHash = 0, .pipelineLayout = programObj.pipelineLayout, .renderPass = renderPassEntry.renderPass, + .colorAttachmentCount = renderPassEntry.colorAttachmentCount, .subpass = 0, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .cullMode = VK_CULL_MODE_NONE, @@ -722,6 +739,7 @@ void main() { .vertexInputHash = vertexInputHash, .pipelineLayout = programObj.pipelineLayout, .renderPass = renderPassEntry.renderPass, + .colorAttachmentCount = renderPassEntry.colorAttachmentCount, .subpass = 0, .topology = MG_Util::ConvertPrimitiveModeToVkEnum(mode), .cullMode = cullFaceEnabled @@ -856,8 +874,12 @@ void main() { vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj, - m_frameContext.GetCurrentFrameIndex()); + const Bool boundUniforms = m_uniformManager->BindProgramUniformBuffers( + frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex()); + if (!boundUniforms) { + MGLOG_E("SetupDraw skipped: BindProgramUniformBuffers failed"); + return false; + } auto vtxUploadOk = UploadAndBindVertexBuffers(frame.commandBuffer, vao); MOBILEGL_ASSERT(vtxUploadOk, "SetupDraw skipped: failed to upload vertex buffers");