[Fix] (MG_Backend/DirectVulkan): fix some uniform/sampler binding

This commit is contained in:
2026-05-04 22:48:50 +08:00
parent 212309083a
commit f61a7bb9c0
11 changed files with 650 additions and 174 deletions
@@ -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<unsigned long long>(payload.programHash),
static_cast<unsigned long long>(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<VkPipelineColorBlendAttachmentState> 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<Uint32>(payload.stages->size());
@@ -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;
@@ -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<String>{}(key.name) ^ (static_cast<SizeT>(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<Int>(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<Int>(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<Vector<Uint>>& inputModules, Uint32 maxBindings,
Vector<Vector<Uint>>& outputModules) {
outputModules = inputModules;
Vector<SpvReflectShaderModule> reflectModules(outputModules.size());
Vector<Bool> reflectModuleValid(outputModules.size(), false);
UnorderedMap<DescriptorKey, Uint32, DescriptorKeyHash> 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<Int>(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<Int>(reflectResult));
if (reflectResult != SPV_REFLECT_RESULT_SUCCESS) {
destroyReflectModules();
return false;
}
Vector<SpvReflectDescriptorBinding*> 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<Int>(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 : "<null>", 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<Int>(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<Vector<Uint>>& 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<Int>(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<Int>(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<Int>(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<Int>(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<Int>(blockIndex),
"ProgramFactory::ReflectLayout: descriptor binding %u maps to conflicting UBO blocks (%d vs %u)",
binding, entry.uniformBlockIndexByBinding[binding], blockIndex);
entry.uniformBlockIndexByBinding[binding] = static_cast<Int>(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<Int>(reflectResult));
Vector<SpvReflectDescriptorBinding*> 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<Int>(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<Uint>(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<Int>(entry.samplerTextureTargetByBinding[binding]),
static_cast<Int>(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<Uint>(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<Vector<Uint>> 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<Uint> 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;
}
@@ -45,6 +45,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
Vector<DescriptorBindingKind> bindingKinds;
Vector<Uint32> dynamicBindings;
Vector<Int> uniformBlockIndexByBinding;
Vector<String> samplerNameByBinding;
Vector<Int> samplerUniformLocationByBinding;
Vector<TextureTarget> 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<Vector<Uint>>& spirv,
VkProgramObject& entry) const;
VkDevice m_device = VK_NULL_HANDLE;
Uint32 m_maxBindings = 0;
@@ -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<Uint>(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<MG_State::GLState::ITextureObject> 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<Uint>(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<Int>(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<Int>(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<Int>(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<MG_State::GLState::ITextureObject>& 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<Uint>(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<Int>(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<const void*>& outData,
Vector<VkDeviceSize>& 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<Uint32>(program.GetActiveUniformBlocksCount());
const Uint32 uniformBindingPointCount =
static_cast<Uint32>(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<VkDeviceSize>(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<VkDeviceSize>(bufferObject->GetSize());
VkDeviceSize rangeStart = static_cast<VkDeviceSize>(range.start);
VkDeviceSize rangeEnd = static_cast<VkDeviceSize>(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<SizeT>(rangeStart);
outSizes[binding] = std::min(blockSize, available);
if (programObj.globalUboBinding == static_cast<Int>(binding)) {
outData = program.GetUBOData();
outSize = static_cast<VkDeviceSize>(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<Uint32>(program.GetActiveUniformBlocksCount());
MOBILEGL_ASSERT(static_cast<Uint32>(blockIndex) < activeUniformBlockCount,
"ResolveUniformBufferPayload: uniform block index %d out of range (count=%u)", blockIndex,
activeUniformBlockCount);
const Uint32 frontendBinding = program.GetUniformBlockBinding(static_cast<Uint32>(blockIndex));
const Uint32 uniformBindingPointCount =
static_cast<Uint32>(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<Uint32>(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<Uint32>(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<Uint32>(blockIndex)).c_str());
const auto range = bindingPoint.GetRange();
const VkDeviceSize bufferSize = static_cast<VkDeviceSize>(bufferObject->GetSize());
const VkDeviceSize rangeStart = static_cast<VkDeviceSize>(range.start);
MOBILEGL_ASSERT(rangeStart < bufferSize,
"ResolveUniformBufferPayload: UBO range start %zu exceeds buffer size %zu for block '%s'",
static_cast<SizeT>(rangeStart), static_cast<SizeT>(bufferSize),
program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
VkDeviceSize rangeEnd = static_cast<VkDeviceSize>(range.end);
if (rangeEnd > bufferSize) {
rangeEnd = bufferSize;
}
MOBILEGL_ASSERT(rangeEnd > rangeStart,
"ResolveUniformBufferPayload: invalid UBO range [%zu, %zu) for block '%s'",
static_cast<SizeT>(rangeStart), static_cast<SizeT>(rangeEnd),
program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
const VkDeviceSize blockSize = static_cast<VkDeviceSize>(program.GetUBOSizeAt(static_cast<Uint32>(blockIndex)));
MOBILEGL_ASSERT(blockSize > 0,
"ResolveUniformBufferPayload: reflected UBO size is zero for block '%s'",
program.GetUniformBlockName(static_cast<Uint32>(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<SizeT>(available), static_cast<SizeT>(blockSize),
program.GetUniformBlockName(static_cast<Uint32>(blockIndex)).c_str());
outData = bufferData->data() + static_cast<SizeT>(rangeStart);
outSize = blockSize;
return true;
}
@@ -450,13 +496,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false;
}
Vector<const void*> bindingData;
Vector<VkDeviceSize> 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<Int>(binding)) {
const void* globalUboData = program.GetUBOData();
const VkDeviceSize globalUboSize = static_cast<VkDeviceSize>(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();
@@ -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<const void*>& outData,
Vector<VkDeviceSize>& 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(
@@ -200,7 +200,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto* texture2d =
static_cast<MG_State::GLState::TextureObject2D*>(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<Uint32>(attachmentViews.size()),
static_cast<Uint32>(colorAttachmentRefs.size()),
extent,
1 };
MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u extent=%dx%d",
static_cast<unsigned long long>(hash),
static_cast<unsigned long long>(compatibilityHash),
renderPassEntry.attachmentCount,
renderPassEntry.colorAttachmentCount,
extent.x(),
extent.y());
auto [insertedIt, _] = m_renderPasses.emplace(hash, Move(renderPassEntry));
return insertedIt->second;
}
@@ -46,6 +46,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<PendingClearAttachmentInfo> pendingClearAttachments;
Vector<TrackedAttachmentLayoutInfo> 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<PendingClearAttachmentInfo>& pendingClearAttachments,
const Vector<TrackedAttachmentLayoutInfo>& 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)
{}
@@ -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<Uint8, 4> 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<Uint8>& 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<SizeT>(std::max(texelSize.z(), 1));
const SizeT pixelCount = static_cast<SizeT>(texelSize.x()) * static_cast<SizeT>(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<const Uint8*>(source);
auto* dst = outExpandedData.data();
const SizeT srcPixelSize = static_cast<SizeT>(formatInfo.componentByteCount) * 3;
const SizeT dstPixelSize = static_cast<SizeT>(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<Uint8> expandedData;
VkDeviceSize offset = 0;
};
Vector<UploadItem> 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<VkDeviceSize>(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<VkDeviceSize>(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<Uint8*>(mapped) + item.offset, item.source, item.byteSize);
std::memcpy(static_cast<Uint8*>(mapped) + item.offset, item.source, item.uploadByteSize);
}
vmaUnmapMemory(m_allocator, stagingAllocation);
@@ -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);
@@ -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<MG_State::GLState::SamplerObject>(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");