[Refactor] (MG_Backend/DirectVulkan/ProgramFactory): refactor uniform reflection

This commit is contained in:
2026-04-03 16:45:11 +08:00
parent 1b2a7989af
commit 4a3a44924a
6 changed files with 208 additions and 359 deletions
@@ -8,6 +8,7 @@
#include "ProgramFactory.h"
#include "MG_Util/ShaderTranspiler/SpvcSession.h"
#include "MG_Util/ShaderTranspiler/Types.h"
#include <cstring>
#include <spirv-tools/libspirv.h>
@@ -23,6 +24,8 @@
namespace MobileGL::MG_Backend::DirectVulkan {
namespace {
using ShaderObject = MG_State::GLState::ShaderObject;
using SpvcSession = MG_Util::ShaderTranspiler::SpvcSession;
using SessionUsageBit = MG_Util::ShaderTranspiler::SessionUsageBit;
struct PositionTargetInfo {
Uint32 variableId = 0;
@@ -323,10 +326,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
} // namespace
ProgramFactory::~ProgramFactory() {
DestroyLayoutCache();
}
VkShaderStageFlagBits ProgramFactory::ToVkStage(ShaderStage stage) {
switch (stage) {
case ShaderStage::Vertex:
@@ -355,24 +354,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint)));
}
XXHASH_VERIFY(XXH64_update(m_hashState, &flags, sizeof(CompileOptionFlags)));
HashType hash = XXH64_digest(m_hashState);
return hash;
}
ProgramFactory::HashType ProgramFactory::ComputeLayoutHash(const MG_State::GLState::ProgramObject& program) const {
XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion));
const auto& spirvs = program.GetGeneratedSpirv();
for (const auto& spv : spirvs) {
XXHASH_VERIFY(XXH64_update(m_hashState, spv.data(), spv.size() * sizeof(Uint)));
}
// Include UBO block bindings in hash so different binding configurations produce different entries
const Uint32 blockCount = static_cast<Uint32>(program.GetActiveUniformBlocksCount());
XXHASH_VERIFY(XXH64_update(m_hashState, &blockCount, sizeof(blockCount)));
for (Uint32 i = 0; i < blockCount; ++i) {
const Uint32 binding = program.GetUniformBlockBinding(i);
XXHASH_VERIFY(XXH64_update(m_hashState, &binding, sizeof(binding)));
}
return XXH64_digest(m_hashState);
HashType hash = XXH64_digest(m_hashState);
return hash;
}
TextureTarget ProgramFactory::UniformTypeToTextureTarget(GLenum glType) {
@@ -426,205 +418,117 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
Bool ProgramFactory::ReflectBindingKinds(const MG_State::GLState::ProgramObject& program,
Vector<DescriptorBindingKind>& outKinds) const {
outKinds.assign(m_maxBindings, DescriptorBindingKind::None);
void ProgramFactory::ReflectLayout(const MG_State::GLState::ProgramObject& program,
VkProgramObject& entry) const {
// Initialize layout vectors
entry.bindingKinds.assign(m_maxBindings, DescriptorBindingKind::None);
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;
}
spvc_context context = nullptr;
spvc_parsed_ir ir = nullptr;
spvc_compiler compiler = nullptr;
spvc_resources resources = nullptr;
SpvcSession session(module, SessionUsageBit::Reflection);
if (spvc_context_create(&context) != SPVC_SUCCESS) {
return false;
}
const spvc_result parseResult = spvc_context_parse_spirv(context, module.data(), module.size(), &ir);
if (parseResult != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
const spvc_result compilerResult =
spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler);
if (compilerResult != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
const auto applyBindings = [&](spvc_resource_type resourceType, DescriptorBindingKind kind) {
const spvc_reflected_resource* list = nullptr;
size_t count = 0;
if (spvc_resources_get_resource_list_for_type(resources, resourceType, &list, &count) != SPVC_SUCCESS) {
return;
// 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;
}
for (size_t i = 0; i < count; ++i) {
const Uint32 binding =
spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding);
if (binding >= m_maxBindings) {
continue;
}
if (kind == DescriptorBindingKind::CombinedImageSampler) {
outKinds[binding] = DescriptorBindingKind::CombinedImageSampler;
} else if (outKinds[binding] == DescriptorBindingKind::None) {
outKinds[binding] = kind;
if (entry.bindingKinds[binding] == DescriptorBindingKind::None) {
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) {
entry.globalUboBinding = static_cast<Int>(binding);
}
}
// 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) {
continue;
}
// Sampler always wins over UBO for a binding slot
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;
}
applyBindings(SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, DescriptorBindingKind::UniformBufferDynamic);
applyBindings(SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, DescriptorBindingKind::CombinedImageSampler);
spvc_context_destroy(context);
entry.samplerUniformLocationByBinding[binding] = location;
entry.samplerTextureTargetByBinding[binding] =
UniformTypeToTextureTarget(program.GetUniformType(static_cast<Uint>(location)));
}
}
return true;
}
Bool ProgramFactory::ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program,
VkProgramLayout& layout) const {
layout.samplerUniformLocationByBinding.assign(m_maxBindings, -1);
layout.samplerTextureTargetByBinding.assign(m_maxBindings, TextureTarget::Texture2D);
const auto& spirv = program.GetGeneratedSpirv();
for (const auto& module : spirv) {
if (module.empty()) {
// Build Vulkan descriptor set layout and pipeline layout from reflected binding kinds
Vector<VkDescriptorSetLayoutBinding> bindings;
bindings.reserve(m_maxBindings);
for (Uint32 binding = 0; binding < m_maxBindings; ++binding) {
const auto kind = entry.bindingKinds[binding];
if (kind == DescriptorBindingKind::None) {
continue;
}
spvc_context context = nullptr;
spvc_parsed_ir ir = nullptr;
spvc_compiler compiler = nullptr;
spvc_resources resources = nullptr;
if (spvc_context_create(&context) != SPVC_SUCCESS) {
return false;
VkDescriptorSetLayoutBinding layoutBinding{};
layoutBinding.binding = binding;
layoutBinding.descriptorCount = 1;
layoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;
layoutBinding.pImmutableSamplers = nullptr;
if (kind == DescriptorBindingKind::UniformBufferDynamic) {
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
entry.dynamicBindings.push_back(binding);
} else {
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
}
if (spvc_context_parse_spirv(context, module.data(), module.size(), &ir) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP,
&compiler) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
const spvc_reflected_resource* list = nullptr;
size_t count = 0;
if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, &list, &count) ==
SPVC_SUCCESS) {
for (size_t i = 0; i < count; ++i) {
const Uint32 binding =
spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding);
if (binding >= m_maxBindings) {
continue;
}
String uniformName = list[i].name ? list[i].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;
}
layout.samplerUniformLocationByBinding[binding] = location;
layout.samplerTextureTargetByBinding[binding] =
UniformTypeToTextureTarget(program.GetUniformType(static_cast<Uint>(location)));
}
}
spvc_context_destroy(context);
bindings.push_back(layoutBinding);
}
return true;
VkDescriptorSetLayoutCreateInfo setLayoutInfo{};
setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
setLayoutInfo.bindingCount = static_cast<Uint32>(bindings.size());
setLayoutInfo.pBindings = bindings.data();
VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &entry.descriptorSetLayout),
"ProgramFactory::ReflectLayout, vkCreateDescriptorSetLayout");
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &entry.descriptorSetLayout;
VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &entry.pipelineLayout),
"ProgramFactory::ReflectLayout, vkCreatePipelineLayout");
}
Bool ProgramFactory::ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program,
VkProgramLayout& layout) const {
layout.globalUboBinding = -1;
const auto& spirv = program.GetGeneratedSpirv();
for (const auto& module : spirv) {
if (module.empty()) {
continue;
}
spvc_context context = nullptr;
spvc_parsed_ir ir = nullptr;
spvc_compiler compiler = nullptr;
spvc_resources resources = nullptr;
if (spvc_context_create(&context) != SPVC_SUCCESS) {
return false;
}
if (spvc_context_parse_spirv(context, module.data(), module.size(), &ir) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP,
&compiler) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) {
spvc_context_destroy(context);
continue;
}
const spvc_reflected_resource* list = nullptr;
size_t count = 0;
if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count) ==
SPVC_SUCCESS) {
for (size_t i = 0; i < count; ++i) {
const char* name = list[i].name ? list[i].name : "";
if (std::strstr(name, MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) == nullptr) {
continue;
}
const Uint32 binding =
spvc_compiler_get_decoration(compiler, list[i].id, SpvDecorationBinding);
if (binding < m_maxBindings) {
layout.globalUboBinding = static_cast<Int>(binding);
}
break;
}
}
spvc_context_destroy(context);
if (layout.globalUboBinding >= 0) {
break;
}
}
return true;
}
Vector<VkPipelineShaderStageCreateInfo>& ProgramFactory::GetOrCreatePipelineShaderStages(
const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram(
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) {
auto hash = ComputeHash(program, flags);
auto it = m_cache.find(hash);
if (it != m_cache.end()) {
return it->second.stages;
return it->second;
}
auto& entry = m_cache[hash];
@@ -664,88 +568,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
entry.stages.push_back(stage);
}
return entry.stages;
}
// Reflect and create layout as part of the program object
ReflectLayout(program, entry);
const ProgramFactory::VkProgramLayout* ProgramFactory::GetOrCreateProgramLayout(
const MG_State::GLState::ProgramObject& program) {
const HashType hash = ComputeLayoutHash(program);
auto it = m_layoutCache.find(hash);
if (it != m_layoutCache.end()) {
return &it->second;
}
VkProgramLayout layout{};
layout.hash = hash;
if (!ReflectBindingKinds(program, layout.bindingKinds)) {
MGLOG_E("ProgramFactory::GetOrCreateProgramLayout failed: reflection failed");
return nullptr;
}
if (!ReflectSamplerBindings(program, layout)) {
MGLOG_E("ProgramFactory::GetOrCreateProgramLayout failed: sampler reflection failed");
return nullptr;
}
if (!ReflectGlobalUboBinding(program, layout)) {
MGLOG_E("ProgramFactory::GetOrCreateProgramLayout failed: global UBO reflection failed");
return nullptr;
}
Vector<VkDescriptorSetLayoutBinding> bindings;
bindings.reserve(m_maxBindings);
for (Uint32 binding = 0; binding < m_maxBindings; ++binding) {
const auto kind = layout.bindingKinds[binding];
if (kind == DescriptorBindingKind::None) {
continue;
}
VkDescriptorSetLayoutBinding layoutBinding{};
layoutBinding.binding = binding;
layoutBinding.descriptorCount = 1;
layoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;
layoutBinding.pImmutableSamplers = nullptr;
if (kind == DescriptorBindingKind::UniformBufferDynamic) {
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
layout.dynamicBindings.push_back(binding);
} else {
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
}
bindings.push_back(layoutBinding);
}
VkDescriptorSetLayoutCreateInfo setLayoutInfo{};
setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
setLayoutInfo.bindingCount = static_cast<Uint32>(bindings.size());
setLayoutInfo.pBindings = bindings.data();
VK_VERIFY(vkCreateDescriptorSetLayout(m_device, &setLayoutInfo, nullptr, &layout.descriptorSetLayout),
"ProgramFactory::GetOrCreateProgramLayout, vkCreateDescriptorSetLayout");
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &layout.descriptorSetLayout;
VK_VERIFY(vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &layout.pipelineLayout),
"ProgramFactory::GetOrCreateProgramLayout, vkCreatePipelineLayout");
auto [insertIt, _] = m_layoutCache.emplace(hash, std::move(layout));
return &insertIt->second;
}
VkPipelineLayout ProgramFactory::GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program) {
const auto* layout = GetOrCreateProgramLayout(program);
return layout ? layout->pipelineLayout : VK_NULL_HANDLE;
}
void ProgramFactory::DestroyLayoutCache() {
for (auto& [_, layout] : m_layoutCache) {
if (layout.pipelineLayout != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(m_device, layout.pipelineLayout, nullptr);
layout.pipelineLayout = VK_NULL_HANDLE;
}
if (layout.descriptorSetLayout != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(m_device, layout.descriptorSetLayout, nullptr);
layout.descriptorSetLayout = VK_NULL_HANDLE;
}
}
m_layoutCache.clear();
return entry;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -39,6 +39,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
HashType hash = 0;
Vector<VkPipelineShaderStageCreateInfo> stages;
Vector<VkShaderModule> modules;
// Layout data (previously in separate VkProgramLayout)
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
Vector<DescriptorBindingKind> bindingKinds;
Vector<Uint32> dynamicBindings;
Vector<Int> samplerUniformLocationByBinding;
Vector<TextureTarget> samplerTextureTargetByBinding;
Int globalUboBinding = -1;
static inline VkDevice s_device = VK_NULL_HANDLE;
VkProgramObject() = default;
@@ -48,76 +58,86 @@ namespace MobileGL::MG_Backend::DirectVulkan {
hash = other.hash;
stages = std::move(other.stages);
modules = std::move(other.modules);
descriptorSetLayout = other.descriptorSetLayout;
pipelineLayout = other.pipelineLayout;
bindingKinds = std::move(other.bindingKinds);
dynamicBindings = std::move(other.dynamicBindings);
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
globalUboBinding = other.globalUboBinding;
other.hash = 0;
other.descriptorSetLayout = VK_NULL_HANDLE;
other.pipelineLayout = VK_NULL_HANDLE;
other.globalUboBinding = -1;
}
VkProgramObject& operator=(VkProgramObject&& other) noexcept {
if (this == &other) {
return *this;
}
DestroyModules();
stages.clear();
Destroy();
hash = other.hash;
stages = std::move(other.stages);
modules = std::move(other.modules);
descriptorSetLayout = other.descriptorSetLayout;
pipelineLayout = other.pipelineLayout;
bindingKinds = std::move(other.bindingKinds);
dynamicBindings = std::move(other.dynamicBindings);
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
globalUboBinding = other.globalUboBinding;
other.hash = 0;
other.descriptorSetLayout = VK_NULL_HANDLE;
other.pipelineLayout = VK_NULL_HANDLE;
other.globalUboBinding = -1;
return *this;
}
~VkProgramObject() {
DestroyModules();
stages.clear();
Destroy();
}
private:
void DestroyModules() {
for (auto module : modules) {
if (module != VK_NULL_HANDLE && s_device != VK_NULL_HANDLE) {
vkDestroyShaderModule(s_device, module, nullptr);
void Destroy() {
if (s_device != VK_NULL_HANDLE) {
if (pipelineLayout != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(s_device, pipelineLayout, nullptr);
pipelineLayout = VK_NULL_HANDLE;
}
if (descriptorSetLayout != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(s_device, descriptorSetLayout, nullptr);
descriptorSetLayout = VK_NULL_HANDLE;
}
for (auto module : modules) {
if (module != VK_NULL_HANDLE) {
vkDestroyShaderModule(s_device, module, nullptr);
}
}
}
modules.clear();
stages.clear();
}
};
struct VkProgramLayout {
HashType hash = 0;
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
Vector<DescriptorBindingKind> bindingKinds;
Vector<Uint32> dynamicBindings;
Vector<Int> samplerUniformLocationByBinding;
Vector<TextureTarget> samplerTextureTargetByBinding;
Int globalUboBinding = -1;
};
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings = 16)
: m_device(device), m_config(config), m_maxBindings(maxBindings) {
VkProgramObject::s_device = device;
}
~ProgramFactory();
~ProgramFactory() = default;
ProgramFactory(const ProgramFactory&) = delete;
HashType ComputeHash(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) const;
Vector<VkPipelineShaderStageCreateInfo>& GetOrCreatePipelineShaderStages(
const VkProgramObject& GetOrCreateProgram(
const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags);
const VkProgramLayout* GetOrCreateProgramLayout(const MG_State::GLState::ProgramObject& program);
VkPipelineLayout GetOrCreatePipelineLayout(const MG_State::GLState::ProgramObject& program);
static VkShaderStageFlagBits ToVkStage(ShaderStage stage);
private:
HashType ComputeLayoutHash(const MG_State::GLState::ProgramObject& program) const;
static TextureTarget UniformTypeToTextureTarget(GLenum glType);
Bool ReflectBindingKinds(const MG_State::GLState::ProgramObject& program,
Vector<DescriptorBindingKind>& outKinds) const;
Bool ReflectSamplerBindings(const MG_State::GLState::ProgramObject& program, VkProgramLayout& layout) const;
Bool ReflectGlobalUboBinding(const MG_State::GLState::ProgramObject& program, VkProgramLayout& layout) const;
void DestroyLayoutCache();
void ReflectLayout(const MG_State::GLState::ProgramObject& program, VkProgramObject& entry) const;
VkDevice m_device = VK_NULL_HANDLE;
Uint32 m_maxBindings = 0;
UnorderedMap<HashType, VkProgramObject> m_cache;
UnorderedMap<HashType, VkProgramLayout> m_layoutCache;
const VulkanRendererConfig& m_config;
static inline XXH64_state_t* m_hashState = XXH64_createState();
};
@@ -158,18 +158,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Bool UniformManager::ResolveSamplerDescriptor(VkCommandBuffer commandBuffer,
const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramLayout& layout,
Uint32 binding, VkDescriptorImageInfo& outImageInfo) const {
const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
Uint32 binding, VkDescriptorImageInfo& outImageInfo) const {
(void)commandBuffer;
MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveSamplerDescriptor: texture manager is null");
MOBILEGL_ASSERT(m_samplerManager != nullptr, "ResolveSamplerDescriptor: sampler manager is null");
SharedPtr<MG_State::GLState::ITextureObject> texture;
if (!ResolveSamplerTexture(program, layout, binding, texture)) {
if (!ResolveSamplerTexture(program, programObj, binding, texture)) {
return false;
}
const Int location = layout.samplerUniformLocationByBinding[binding];
const Int location = programObj.samplerUniformLocationByBinding[binding];
const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
const auto samplerOverride = textureUnit.GetSamplerObject();
@@ -236,14 +236,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Bool UniformManager::ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramLayout& layout, Uint32 binding,
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) const {
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) const {
outTexture.reset();
if (!MG_State::pGLContext || binding >= layout.samplerUniformLocationByBinding.size()) {
if (!MG_State::pGLContext || binding >= programObj.samplerUniformLocationByBinding.size()) {
return false;
}
const Int location = layout.samplerUniformLocationByBinding[binding];
const Int location = programObj.samplerUniformLocationByBinding[binding];
if (location < 0) {
return false;
}
@@ -254,29 +254,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
auto& textureUnit = MG_State::pGLContext->GetTextureUnitObject(unit);
const TextureTarget preferredTarget = layout.samplerTextureTargetByBinding[binding];
const TextureTarget preferredTarget = programObj.samplerTextureTargetByBinding[binding];
outTexture = textureUnit.GetBindingSlot(preferredTarget).GetBoundObject();
return outTexture != nullptr;
}
Bool UniformManager::CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
Vector<MG_State::GLState::ITextureObject*>& outTextures) {
const ProgramFactory::VkProgramObject& programObj,
Vector<MG_State::GLState::ITextureObject*>& outTextures) {
outTextures.clear();
MOBILEGL_ASSERT(m_programFactory != nullptr, "CollectSampledTextures: program factory is null");
const auto* layout = m_programFactory->GetOrCreateProgramLayout(program);
if (layout == nullptr) {
return false;
}
const Uint32 bindingCount =
std::min<Uint32>(m_maxBindings, static_cast<Uint32>(layout->bindingKinds.size()));
std::min<Uint32>(m_maxBindings, static_cast<Uint32>(programObj.bindingKinds.size()));
for (Uint32 binding = 0; binding < bindingCount; ++binding) {
if (layout->bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) {
if (programObj.bindingKinds[binding] != ProgramFactory::DescriptorBindingKind::CombinedImageSampler) {
continue;
}
SharedPtr<MG_State::GLState::ITextureObject> texture;
if (!ResolveSamplerTexture(program, *layout, binding, texture) || !texture) {
if (!ResolveSamplerTexture(program, programObj, binding, texture) || !texture) {
continue;
}
@@ -406,13 +402,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex, const ProgramFactory::VkProgramLayout& layout, VkDescriptorSet& outDescriptorSet) {
VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet) {
auto& frame = m_frames[frameIndex];
auto& bucket = frame.descriptorPools[frame.activeDescriptorPoolIndex];
VkDescriptorSetAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorSetCount = 1;
allocInfo.pSetLayouts = &layout.descriptorSetLayout;
allocInfo.pSetLayouts = &programObj.descriptorSetLayout;
allocInfo.descriptorPool = bucket.handle;
VkResult result = vkAllocateDescriptorSets(m_device, &allocInfo, &outDescriptorSet);
@@ -426,14 +422,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Bool UniformManager::BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
const MG_State::GLState::ProgramObject& program,
Uint32 frameIndex,
const SamplerBindingOverride* samplerBindingOverride) {
MOBILEGL_ASSERT(m_programFactory != nullptr, "BindProgramUniformBuffers: program factory is null");
const auto* layout = m_programFactory->GetOrCreateProgramLayout(program);
MOBILEGL_ASSERT(layout != nullptr,
"UniformDescriptorBinder::BindProgramUniformBuffers: program layout is null");
const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
Uint32 frameIndex,
const SamplerBindingOverride* samplerBindingOverride) {
auto& frame = m_frames[frameIndex];
if (frame.descriptorPools.empty()) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame descriptor pools are invalid");
@@ -444,13 +436,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, *layout, descriptorSet);
VkResult allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, descriptorSet);
if (allocResult == VK_ERROR_OUT_OF_POOL_MEMORY || allocResult == VK_ERROR_FRAGMENTED_POOL) {
if (!GrowFrameDescriptorPool(frame, frameIndex)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor pool growth failed");
return false;
}
allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, *layout, descriptorSet);
allocResult = AllocateDescriptorSetsFromActivePool(frameIndex, programObj, descriptorSet);
}
if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: vkAllocateDescriptorSets returned %d",
@@ -476,12 +468,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<Uint32> dynamicOffsets;
bufferInfos.reserve(m_maxBindings);
imageInfos.reserve(m_maxBindings);
dynamicOffsets.reserve(layout->dynamicBindings.size());
dynamicOffsets.reserve(programObj.dynamicBindings.size());
const Uint32 bindingCount =
std::min<Uint32>(m_maxBindings, static_cast<Uint32>(layout->bindingKinds.size()));
std::min<Uint32>(m_maxBindings, static_cast<Uint32>(programObj.bindingKinds.size()));
for (Uint32 binding = 0; binding < bindingCount; ++binding) {
const auto kind = layout->bindingKinds[binding];
const auto kind = programObj.bindingKinds[binding];
if (kind == ProgramFactory::DescriptorBindingKind::None) {
continue;
}
@@ -497,7 +489,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const void* payload = bindingData[binding];
VkDeviceSize payloadSize = bindingSizes[binding];
if (payload == nullptr || payloadSize == 0) {
if (layout->globalUboBinding == static_cast<Int>(binding)) {
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) {
@@ -510,7 +502,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
BufferSlice slice{};
if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, payload, payloadSize,
m_minDynamicOffsetAlignment, slice)) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u",
MOBILEGL_ASSERT(false, "UniformDescriptorBinder::BindProgramUniformBuffers failed: UBO upload failed on binding %u",
binding);
return false;
}
@@ -534,7 +526,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
samplerBindingOverride->sampler != nullptr) {
hasImage = ResolveSamplerDescriptorOverride(*samplerBindingOverride, imageInfo);
} else {
hasImage = ResolveSamplerDescriptor(commandBuffer, program, *layout, binding, imageInfo);
hasImage = ResolveSamplerDescriptor(commandBuffer, program, programObj, binding, imageInfo);
}
if (!hasImage) {
MGLOG_E("UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u has no valid texture descriptor",
@@ -557,7 +549,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
vkUpdateDescriptorSets(m_device, static_cast<Uint32>(writes.size()), writes.data(), 0, nullptr);
}
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, layout->pipelineLayout, 0, 1,
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, programObj.pipelineLayout, 0, 1,
&descriptorSet, static_cast<Uint32>(dynamicOffsets.size()), dynamicOffsets.data());
return true;
}
@@ -39,9 +39,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void BeginFrame(Uint32 frameIndex);
Bool CollectSampledTextures(const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
Vector<MG_State::GLState::ITextureObject*>& outTextures);
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
const MG_State::GLState::ProgramObject& program, Uint32 frameIndex,
const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramObject& programObj,
Uint32 frameIndex,
const SamplerBindingOverride* samplerBindingOverride = nullptr);
private:
@@ -59,10 +62,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
};
Bool ResolveSamplerTexture(const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramLayout& layout, Uint32 binding,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
SharedPtr<MG_State::GLState::ITextureObject>& outTexture) const;
Bool ResolveSamplerDescriptor(VkCommandBuffer commandBuffer, const MG_State::GLState::ProgramObject& program,
const ProgramFactory::VkProgramLayout& layout, Uint32 binding,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
VkDescriptorImageInfo& outImageInfo) const;
Bool ResolveSamplerDescriptorOverride(const SamplerBindingOverride& samplerBindingOverride,
VkDescriptorImageInfo& outImageInfo) const;
@@ -71,7 +74,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool CreateDescriptorPool(Uint32 maxSets, VkDescriptorPool& outPool) const;
Bool GrowFrameDescriptorPool(FrameResources& frame, Uint32 frameIndex);
VkResult AllocateDescriptorSetsFromActivePool(
Uint32 frameIndex, const ProgramFactory::VkProgramLayout& layout, VkDescriptorSet& outDescriptorSet);
Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet);
VkDevice m_device = VK_NULL_HANDLE;
VkBufferManager* m_bufferManager = nullptr;
@@ -472,7 +472,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(),
sourceData->data(), static_cast<VkDeviceSize>(sourceSize), 16,
slice)) {
MGLOG_E("UploadAndBindVertexStreams skipped: failed to upload transient binding %zu", binding);
MOBILEGL_ASSERT(false, "UploadAndBindVertexStreams skipped: failed to upload transient binding %zu", binding);
return false;
}
if (!transientThisFrame) {
@@ -534,7 +534,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(),
indexData->data() + pIndexBufferView->indexByteOffset,
static_cast<VkDeviceSize>(indexDataSizeBytes), indexSize, slice)) {
MGLOG_E("DrawElements skipped: failed to prepare transient index buffer");
MOBILEGL_ASSERT(false, "DrawElements skipped: failed to prepare transient index buffer");
return false;
}
if (!transientThisFrame) {
@@ -667,11 +667,11 @@ void main() {
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
};
ProgramFactory::CompileOptionFlags transformFlags = 0;
auto& stages = m_programFactory->GetOrCreatePipelineShaderStages(*m_blitResources.program, transformFlags);
const auto& programObj = m_programFactory->GetOrCreateProgram(*m_blitResources.program, transformFlags);
PipelineFactory::PipelineCreatePayload payload{
.programHash = m_programFactory->ComputeHash(*m_blitResources.program, transformFlags),
.programHash = programObj.hash,
.vertexInputHash = 0,
.pipelineLayout = m_programFactory->GetOrCreatePipelineLayout(*m_blitResources.program),
.pipelineLayout = programObj.pipelineLayout,
.renderPass = renderPassEntry.renderPass,
.subpass = 0,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
@@ -687,7 +687,7 @@ void main() {
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
.stages = &stages,
.stages = &programObj.stages,
.vertexInputState = &kEmptyVertexInputState
};
return m_pipelineFactory->GetOrCreatePipeline(payload);
@@ -700,16 +700,14 @@ void main() {
const RenderPassEntry& renderPassEntry) {
ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
Bool invertClockwise = transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip;
auto& stages = m_programFactory->GetOrCreatePipelineShaderStages(program, transformFlags);
if (stages.empty()) {
const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags);
if (programObj.stages.empty()) {
MGLOG_D("GetOrCreatePipeline skipped: program has no shader stages");
return VK_NULL_HANDLE;
}
const Uint64 programHash = m_programFactory->ComputeHash(program, transformFlags);
auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao);
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
auto pipelineLayout = m_programFactory->GetOrCreatePipelineLayout(program);
auto cullFaceEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::CullFace);
auto depthTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DepthTest);
BlendFactor srcRGB = BlendFactor::One;
@@ -720,9 +718,9 @@ void main() {
auto mask = MG_State::pGLContext->GetColorMask();
PipelineFactory::PipelineCreatePayload payload {
.programHash = programHash,
.programHash = programObj.hash,
.vertexInputHash = vertexInputHash,
.pipelineLayout = pipelineLayout,
.pipelineLayout = programObj.pipelineLayout,
.renderPass = renderPassEntry.renderPass,
.subpass = 0,
.topology = MG_Util::ConvertPrimitiveModeToVkEnum(mode),
@@ -743,7 +741,7 @@ void main() {
(mask.g() ? VK_COLOR_COMPONENT_G_BIT : 0u) |
(mask.b() ? VK_COLOR_COMPONENT_B_BIT : 0u) |
(mask.a() ? VK_COLOR_COMPONENT_A_BIT : 0u) ),
.stages = &stages,
.stages = &programObj.stages,
.vertexInputState = &vis.state
};
return m_pipelineFactory->GetOrCreatePipeline(payload);
@@ -756,6 +754,8 @@ void main() {
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
const auto& vao = *MG_State::pGLContext->GetBoundVertexArray();
const auto& program = *MG_State::pGLContext->GetCurrentProgram();
ProgramFactory::CompileOptionFlags transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags);
// Begin command recording if not yet
if (!frame.isCommandRecording) {
@@ -770,7 +770,7 @@ void main() {
// without draws in between to give it a chance to materialize such clear.
// Deal with this situation here.
Vector<MG_State::GLState::ITextureObject*> sampledTextures;
Bool hasSampledTextures = m_uniformManager->CollectSampledTextures(program, sampledTextures);
Bool hasSampledTextures = m_uniformManager->CollectSampledTextures(program, programObj, sampledTextures);
MOBILEGL_ASSERT(hasSampledTextures, "%s: CollectSampledTextures failed", __func__);
MGLOG_D("SetupDraw: program=%u drawFbo=%u sampledTextureCount=%zu activeRenderPass=%s",
program.GetExternalIndex(), drawFbo ? drawFbo->GetExternalIndex() : 0u, sampledTextures.size(),
@@ -856,7 +856,7 @@ void main() {
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program,
m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj,
m_frameContext.GetCurrentFrameIndex());
auto vtxUploadOk = UploadAndBindVertexBuffers(frame.commandBuffer, vao);
@@ -1086,8 +1086,10 @@ void main() {
.sampler = (filter == GL_LINEAR ? m_blitResources.linearSampler.get()
: m_blitResources.nearestSampler.get()),
};
ProgramFactory::CompileOptionFlags blitTransformFlags = 0;
const auto& blitProgramObj = m_programFactory->GetOrCreateProgram(*m_blitResources.program, blitTransformFlags);
const Bool bound = m_uniformManager->BindProgramUniformBuffers(
frame.commandBuffer, *m_blitResources.program, m_frameContext.GetCurrentFrameIndex(),
frame.commandBuffer, *m_blitResources.program, blitProgramObj, m_frameContext.GetCurrentFrameIndex(),
&samplerBindingOverride);
MOBILEGL_ASSERT(bound, "TryBlitToDefaultFramebufferWithShader: BindProgramUniformBuffers failed");
vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0);
@@ -179,7 +179,14 @@ namespace MobileGL {
for (uint32_t i = 0; i < count; ++i) {
if (bindings[i]->descriptor_type == SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER) {
InterfaceVariable var;
var.name = bindings[i]->name;
// Use the block/type name (e.g. "MGL_GLOBAL_UBO") rather than
// the variable name, which may be empty or meaningless for UBOs.
// This is consistent with ParseMetaData() which uses type_description->type_name.
if (bindings[i]->type_description && bindings[i]->type_description->type_name) {
var.name = bindings[i]->type_description->type_name;
} else {
var.name = bindings[i]->name;
}
var.location = bindings[i]->binding;
variables.push_back(var);
}