diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index ba8f8ae5..2f2354b6 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -72,6 +72,7 @@ namespace MobileGL { GLenum access, GLenum format); void (*GetIntegeri_v)(GLenum target, GLuint index, GLint* data); void (*GetInteger64i_v)(GLenum target, GLuint index, GLint64* data); + void (*GetProgramiv)(GLuint program, GLenum pname, GLint* params); void (*GetProgramInterfaceiv)(GLuint program, GLenum programInterface, GLenum pname, GLint* params); GLuint (*GetProgramResourceIndex)(GLuint program, GLenum programInterface, const GLchar* name); void (*GetProgramResourceName)(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 32a9b692..039b8ea1 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -173,6 +173,7 @@ namespace MobileGL::MG_Backend::DirectGLES { funcsTable.GL.BindImageTexture = BindImageTexture; funcsTable.GL.GetIntegeri_v = GetIntegeri_v; funcsTable.GL.GetInteger64i_v = GetInteger64i_v; + funcsTable.GL.GetProgramiv = GetProgramiv; funcsTable.GL.GetProgramInterfaceiv = GetProgramInterfaceiv; funcsTable.GL.GetProgramResourceIndex = GetProgramResourceIndex; funcsTable.GL.GetProgramResourceName = GetProgramResourceName; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 244b986f..e7d763f9 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1428,6 +1428,16 @@ namespace MobileGL::MG_Backend::DirectGLES { } } + void GetProgramiv(GLuint program, GLenum pname, GLint* params) { + if (!params) return; + GLuint backendProgramId = GetBackendProgramId(program); + if (!backendProgramId) { + params[0] = 0; + return; + } + g_GLESFuncs.glGetProgramiv(backendProgramId, pname, params); + } + void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params) { GLuint backendProgramId = GetBackendProgramId(program); if (!backendProgramId) return; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index 4d2dddf4..25aac7f8 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -64,6 +64,7 @@ namespace MobileGL::MG_Backend::DirectGLES { GLenum format); void GetIntegeri_v(GLenum target, GLuint index, GLint* data); void GetInteger64i_v(GLenum target, GLuint index, GLint64* data); + void GetProgramiv(GLuint program, GLenum pname, GLint* params); void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params); GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name); void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 934727a1..edc53040 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -116,7 +116,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { .TargetGLVersion = {3, 3, 0}, // Target OpenGL Version .TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version .Extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32, // OpenGL Extensions - V_OpenGL33, E_GL_ARB_draw_buffers_blend}, + V_OpenGL33, E_GL_ARB_draw_buffers_blend, + E_GL_ARB_compute_shader, + E_GL_ARB_shader_storage_buffer_object, + E_GL_ARB_shader_image_load_store, + E_GL_ARB_program_interface_query}, .IsCompatibilityProfile = false // Is Compatibility Profile }, .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability @@ -172,6 +176,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { funcsTable.GL.GenerateMipmap = GenerateMipmap; funcsTable.GL.ReadPixels = ReadPixels; funcsTable.GL.GetTexImage = GetTexImage; + funcsTable.GL.DispatchCompute = DispatchCompute; + funcsTable.GL.DispatchComputeIndirect = DispatchComputeIndirect; + funcsTable.GL.MemoryBarrier = MemoryBarrier; + funcsTable.GL.MemoryBarrierByRegion = MemoryBarrierByRegion; + funcsTable.GL.BindImageTexture = BindImageTexture; + funcsTable.GL.GetIntegeri_v = GetIntegeri_v; + funcsTable.GL.GetInteger64i_v = GetInteger64i_v; + funcsTable.GL.GetProgramiv = GetProgramiv; + funcsTable.GL.GetProgramInterfaceiv = GetProgramInterfaceiv; + funcsTable.GL.GetProgramResourceIndex = GetProgramResourceIndex; + funcsTable.GL.GetProgramResourceName = GetProgramResourceName; + funcsTable.GL.GetProgramResourceiv = GetProgramResourceiv; + funcsTable.GL.GetProgramResourceLocation = GetProgramResourceLocation; + funcsTable.GL.GetProgramResourceLocationIndex = GetProgramResourceLocationIndex; + funcsTable.GL.ShaderStorageBlockBinding = ShaderStorageBlockBinding; funcsTableInitialized = true; } return funcsTable; diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 7b8e11fa..fde6983c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -7,12 +7,210 @@ // End of Source File Header #include "DirectVulkan.h" +#include "DirectVulkanResourceState.h" #include "MG_State/GLState/Core.h" #include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h" +#include "MG_Util/Miscellany/IndexGenerator.h" +#include +#include namespace MobileGL::MG_Backend::DirectVulkan { UniquePtr pVulkanRenderer = nullptr; + namespace { + struct BufferVariableResource { + String name; + GLuint blockIndex = 0; + GLint offset = 0; + GLint size = 0; + }; + + struct StorageBlockResource { + String name; + GLuint binding = 0; + GLint dataSize = 0; + Vector activeVariables; + }; + + struct ProgramResourceCache { + Uint32 backendStateVersion = 0; + Vector storageBlocks; + Vector bufferVariables; + GLint computeWorkGroupSize[3] = {1, 1, 1}; + }; + + UnorderedMap g_programResourceCaches; + + String NormalizeDescriptorName(const SpvReflectDescriptorBinding& binding) { + const char* rawName = binding.name; + if (binding.type_description != nullptr && binding.type_description->type_name != nullptr) { + rawName = binding.type_description->type_name; + } + if (rawName == nullptr) { + return {}; + } + String name = rawName; + const auto arraySuffix = name.find("[0]"); + if (arraySuffix != String::npos) { + name = name.substr(0, arraySuffix); + } + return name; + } + + void AddBufferVariablesRecursive(const SpvReflectBlockVariable& variable, const String& prefix, + GLuint blockIndex, Vector& variables, + Vector& activeVariables) { + for (Uint32 memberIndex = 0; memberIndex < variable.member_count; ++memberIndex) { + const auto& member = variable.members[memberIndex]; + String name = prefix; + if (!name.empty()) { + name += "."; + } + name += member.name ? member.name : ""; + + if (member.member_count > 0) { + AddBufferVariablesRecursive(member, name, blockIndex, variables, activeVariables); + continue; + } + + BufferVariableResource resource{}; + resource.name = name; + resource.blockIndex = blockIndex; + resource.offset = static_cast(member.offset); + resource.size = static_cast(member.size); + const GLuint variableIndex = static_cast(variables.size()); + variables.push_back(resource); + activeVariables.push_back(variableIndex); + } + } + + ProgramResourceCache& GetProgramResourceCache(const MG_State::GLState::ProgramObject& program) { + auto& cache = g_programResourceCaches[program.GetExternalIndex()]; + const Uint32 backendStateVersion = program.GetBackendStateVersion(); + if (cache.backendStateVersion == backendStateVersion && + (!cache.storageBlocks.empty() || !cache.bufferVariables.empty())) { + return cache; + } + + cache = {}; + cache.backendStateVersion = backendStateVersion; + + Vector modules; + Vector validModules; + const auto& spirvs = program.GetGeneratedSpirv(); + for (const auto& spirv : spirvs) { + if (spirv.empty()) { + continue; + } + SpvReflectShaderModule module{}; + const SpvReflectResult result = + spvReflectCreateShaderModule(spirv.size() * sizeof(Uint), spirv.data(), &module); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + continue; + } + modules.push_back(module); + validModules.push_back(true); + } + + for (auto& module : modules) { + for (Uint32 entryIndex = 0; entryIndex < module.entry_point_count; ++entryIndex) { + const auto& entryPoint = module.entry_points[entryIndex]; + if ((entryPoint.shader_stage & SPV_REFLECT_SHADER_STAGE_COMPUTE_BIT) == 0) { + continue; + } + cache.computeWorkGroupSize[0] = static_cast(std::max(entryPoint.local_size.x, 1)); + cache.computeWorkGroupSize[1] = static_cast(std::max(entryPoint.local_size.y, 1)); + cache.computeWorkGroupSize[2] = static_cast(std::max(entryPoint.local_size.z, 1)); + } + + uint32_t bindingCount = 0; + SpvReflectResult result = spvReflectEnumerateDescriptorBindings(&module, &bindingCount, nullptr); + if (result != SPV_REFLECT_RESULT_SUCCESS || bindingCount == 0) { + continue; + } + Vector bindings(bindingCount); + result = spvReflectEnumerateDescriptorBindings(&module, &bindingCount, bindings.data()); + if (result != SPV_REFLECT_RESULT_SUCCESS) { + continue; + } + std::sort(bindings.begin(), bindings.end(), [](const auto* lhs, const auto* rhs) { + const String lhsName = lhs ? NormalizeDescriptorName(*lhs) : String(); + const String rhsName = rhs ? NormalizeDescriptorName(*rhs) : String(); + if (lhsName != rhsName) return lhsName < rhsName; + return lhs->binding < rhs->binding; + }); + for (const auto* binding : bindings) { + if (binding == nullptr || + binding->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) { + continue; + } + const String blockName = NormalizeDescriptorName(*binding); + if (blockName.empty()) { + continue; + } + const auto existing = std::find_if( + cache.storageBlocks.begin(), cache.storageBlocks.end(), + [&](const StorageBlockResource& block) { return block.name == blockName; }); + if (existing != cache.storageBlocks.end()) { + continue; + } + + StorageBlockResource block{}; + block.name = blockName; + block.dataSize = static_cast(binding->block.size); + const GLuint blockIndex = static_cast(cache.storageBlocks.size()); + AddBufferVariablesRecursive(binding->block, blockName, blockIndex, cache.bufferVariables, + block.activeVariables); + cache.storageBlocks.push_back(block); + } + } + + for (SizeT i = 0; i < modules.size(); ++i) { + if (validModules[i]) { + spvReflectDestroyShaderModule(&modules[i]); + } + } + return cache; + } + + MG_State::GLState::ProgramObject* TryGetDirectVulkanProgram(GLuint program) { + if (!MG_State::pGLContext || !MG_State::pGLContext->ValidateProgramName(program)) { + return nullptr; + } + auto& programObject = MG_State::pGLContext->GetProgramObject(program); + return programObject.get(); + } + + void CopyResourceName(const String& source, GLsizei bufSize, GLsizei* length, GLchar* name) { + const GLsizei writtenLength = static_cast(source.size()); + if (length) { + *length = writtenLength; + } + if (name && bufSize > 0) { + const GLsizei copyLength = std::min(bufSize - 1, writtenLength); + std::memcpy(name, source.data(), static_cast(copyLength)); + name[copyLength] = '\0'; + } + } + } // namespace + + GLuint GetShaderStorageBlockIndex(const MG_State::GLState::ProgramObject& program, const String& name) { + auto& cache = GetProgramResourceCache(program); + const auto it = std::find_if(cache.storageBlocks.begin(), cache.storageBlocks.end(), + [&](const StorageBlockResource& block) { return block.name == name; }); + return it == cache.storageBlocks.end() + ? GL_INVALID_INDEX + : static_cast(std::distance(cache.storageBlocks.begin(), it)); + } + + GLuint GetShaderStorageBlockBinding(const MG_State::GLState::ProgramObject& program, GLuint blockIndex) { + auto& cache = GetProgramResourceCache(program); + if (blockIndex >= cache.storageBlocks.size()) { + return 0; + } + return cache.storageBlocks[blockIndex].binding; + } + void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::ClearBufferfi called with null VulkanRenderer"); MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::ClearBufferfi called with null GL context"); @@ -71,6 +269,326 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::GenerateMipmap called with null GL context"); pVulkanRenderer->GenerateMipmap(target); } + + void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DispatchCompute called with null VulkanRenderer"); + MOBILEGL_ASSERT(MG_State::pGLContext, "DirectVulkan::DispatchCompute called with null GL context"); + pVulkanRenderer->DispatchCompute(numGroupsX, numGroupsY, numGroupsZ); + } + + void DispatchComputeIndirect(GLintptr indirect) { + MGLOG_W("DirectVulkan::DispatchComputeIndirect is not implemented yet (offset=%zu)", + static_cast(indirect)); + } + + void MemoryBarrier(GLbitfield barriers) { + MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MemoryBarrier called with null VulkanRenderer"); + pVulkanRenderer->MemoryBarrier(barriers); + } + + void MemoryBarrierByRegion(GLbitfield barriers) { + MemoryBarrier(barriers); + } + + void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, + GLenum format) { + (void)unit; + (void)texture; + (void)level; + (void)layered; + (void)layer; + (void)access; + (void)format; + } + + void GetIntegeri_v(GLenum target, GLuint index, GLint* data) { + if (!data) return; + switch (target) { + case GL_MAX_COMPUTE_WORK_GROUP_COUNT: + if (!pVulkanRenderer || index >= 3) { + *data = 0; + return; + } + *data = static_cast( + pVulkanRenderer->GetPhysicalDevice().properties.limits.maxComputeWorkGroupCount[index]); + return; + case GL_MAX_COMPUTE_WORK_GROUP_SIZE: + if (!pVulkanRenderer || index >= 3) { + *data = 0; + return; + } + *data = static_cast( + pVulkanRenderer->GetPhysicalDevice().properties.limits.maxComputeWorkGroupSize[index]); + return; + case GL_SHADER_STORAGE_BUFFER_BINDING: { + auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, index); + auto& obj = point.GetBoundObject(); + *data = obj ? static_cast(obj->GetExternalIndex()) : 0; + return; + } + case GL_SHADER_STORAGE_BUFFER_START: { + auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, index); + *data = static_cast(point.GetRange().start); + return; + } + case GL_SHADER_STORAGE_BUFFER_SIZE: { + auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, index); + auto& obj = point.GetBoundObject(); + if (!obj) { + *data = 0; + return; + } + const auto& range = point.GetRange(); + const auto start = std::min(range.start, obj->GetSize()); + const auto end = std::min(range.end, obj->GetSize()); + *data = static_cast(end - start); + return; + } + case GL_IMAGE_BINDING_NAME: + case GL_IMAGE_BINDING_LEVEL: + case GL_IMAGE_BINDING_LAYERED: + case GL_IMAGE_BINDING_LAYER: + case GL_IMAGE_BINDING_ACCESS: + case GL_IMAGE_BINDING_FORMAT: { + if (index >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) { + *data = 0; + return; + } + auto& imageBinding = MG_State::pGLContext->GetImageTextureBinding(static_cast(index)); + if (target == GL_IMAGE_BINDING_NAME) { + *data = imageBinding.Texture ? static_cast(imageBinding.Texture->GetExternalIndex()) : 0; + } else if (target == GL_IMAGE_BINDING_LEVEL) { + *data = imageBinding.Level; + } else if (target == GL_IMAGE_BINDING_LAYERED) { + *data = imageBinding.Layered; + } else if (target == GL_IMAGE_BINDING_LAYER) { + *data = imageBinding.Layer; + } else if (target == GL_IMAGE_BINDING_ACCESS) { + *data = static_cast(imageBinding.Access); + } else { + *data = static_cast(imageBinding.Format); + } + return; + } + default: + *data = 0; + return; + } + } + + void GetInteger64i_v(GLenum target, GLuint index, GLint64* data) { + if (!data) return; + switch (target) { + case GL_SHADER_STORAGE_BUFFER_START: { + auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, index); + *data = static_cast(point.GetRange().start); + return; + } + case GL_SHADER_STORAGE_BUFFER_SIZE: { + auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, index); + auto& obj = point.GetBoundObject(); + if (!obj) { + *data = 0; + return; + } + const auto& range = point.GetRange(); + const auto start = std::min(range.start, obj->GetSize()); + const auto end = std::min(range.end, obj->GetSize()); + *data = static_cast(end - start); + return; + } + default: + *data = 0; + return; + } + } + + void GetProgramiv(GLuint program, GLenum pname, GLint* params) { + if (!params) return; + auto* programObject = TryGetDirectVulkanProgram(program); + if (!programObject) { + params[0] = 0; + return; + } + switch (pname) { + case GL_COMPUTE_WORK_GROUP_SIZE: { + auto& cache = GetProgramResourceCache(*programObject); + params[0] = cache.computeWorkGroupSize[0]; + params[1] = cache.computeWorkGroupSize[1]; + params[2] = cache.computeWorkGroupSize[2]; + return; + } + default: + params[0] = 0; + return; + } + } + + void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params) { + if (!params) return; + auto* programObject = TryGetDirectVulkanProgram(program); + if (!programObject) return; + auto& cache = GetProgramResourceCache(*programObject); + if (programInterface == GL_SHADER_STORAGE_BLOCK) { + if (pname == GL_ACTIVE_RESOURCES) { + *params = static_cast(cache.storageBlocks.size()); + } else if (pname == GL_MAX_NAME_LENGTH) { + SizeT maxLength = 0; + for (const auto& block : cache.storageBlocks) maxLength = std::max(maxLength, block.name.size() + 1); + *params = static_cast(maxLength); + } else { + *params = 0; + } + return; + } + if (programInterface == GL_BUFFER_VARIABLE) { + if (pname == GL_ACTIVE_RESOURCES) { + *params = static_cast(cache.bufferVariables.size()); + } else if (pname == GL_MAX_NAME_LENGTH) { + SizeT maxLength = 0; + for (const auto& var : cache.bufferVariables) maxLength = std::max(maxLength, var.name.size() + 1); + *params = static_cast(maxLength); + } else { + *params = 0; + } + return; + } + *params = 0; + } + + GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name) { + if (!name) return GL_INVALID_INDEX; + auto* programObject = TryGetDirectVulkanProgram(program); + if (!programObject) return GL_INVALID_INDEX; + auto& cache = GetProgramResourceCache(*programObject); + if (programInterface == GL_SHADER_STORAGE_BLOCK) { + return GetShaderStorageBlockIndex(*programObject, name); + } + if (programInterface == GL_BUFFER_VARIABLE) { + const String resourceName = name; + const auto it = std::find_if(cache.bufferVariables.begin(), cache.bufferVariables.end(), + [&](const BufferVariableResource& var) { return var.name == resourceName; }); + return it == cache.bufferVariables.end() + ? GL_INVALID_INDEX + : static_cast(std::distance(cache.bufferVariables.begin(), it)); + } + return GL_INVALID_INDEX; + } + + void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, + GLsizei* length, GLchar* name) { + auto* programObject = TryGetDirectVulkanProgram(program); + if (!programObject) return; + auto& cache = GetProgramResourceCache(*programObject); + if (programInterface == GL_SHADER_STORAGE_BLOCK && index < cache.storageBlocks.size()) { + CopyResourceName(cache.storageBlocks[index].name, bufSize, length, name); + return; + } + if (programInterface == GL_BUFFER_VARIABLE && index < cache.bufferVariables.size()) { + CopyResourceName(cache.bufferVariables[index].name, bufSize, length, name); + return; + } + if (length) *length = 0; + if (name && bufSize > 0) name[0] = '\0'; + } + + void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, + const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params) { + auto* programObject = TryGetDirectVulkanProgram(program); + if (!programObject || !props || !params || bufSize <= 0) return; + auto& cache = GetProgramResourceCache(*programObject); + GLsizei written = 0; + auto writeValue = [&](GLint value) { + if (written < bufSize) { + params[written++] = value; + } + }; + + for (GLsizei propIndex = 0; propIndex < propCount; ++propIndex) { + const GLenum prop = props[propIndex]; + if (programInterface == GL_SHADER_STORAGE_BLOCK && index < cache.storageBlocks.size()) { + const auto& block = cache.storageBlocks[index]; + switch (prop) { + case GL_NAME_LENGTH: + writeValue(static_cast(block.name.size() + 1)); + break; + case GL_BUFFER_BINDING: + writeValue(static_cast(block.binding)); + break; + case GL_BUFFER_DATA_SIZE: + writeValue(block.dataSize); + break; + case GL_NUM_ACTIVE_VARIABLES: + writeValue(static_cast(block.activeVariables.size())); + break; + case GL_ACTIVE_VARIABLES: + for (const auto variable : block.activeVariables) writeValue(static_cast(variable)); + break; + default: + writeValue(0); + break; + } + } else if (programInterface == GL_BUFFER_VARIABLE && index < cache.bufferVariables.size()) { + const auto& var = cache.bufferVariables[index]; + switch (prop) { + case GL_NAME_LENGTH: + writeValue(static_cast(var.name.size() + 1)); + break; + case GL_TYPE: + writeValue(GL_FLOAT); + break; + case GL_ARRAY_SIZE: + writeValue(1); + break; + case GL_OFFSET: + writeValue(var.offset); + break; + case GL_BLOCK_INDEX: + writeValue(static_cast(var.blockIndex)); + break; + case GL_ARRAY_STRIDE: + case GL_MATRIX_STRIDE: + case GL_TOP_LEVEL_ARRAY_SIZE: + case GL_TOP_LEVEL_ARRAY_STRIDE: + case GL_IS_ROW_MAJOR: + writeValue(0); + break; + default: + writeValue(0); + break; + } + } else { + writeValue(0); + } + } + if (length) *length = written; + } + + GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name) { + auto* programObject = TryGetDirectVulkanProgram(program); + if (!programObject || !name) return -1; + if (programInterface == GL_UNIFORM) { + return programObject->GetUniformLocation(name); + } + return -1; + } + + GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name) { + (void)program; + (void)programInterface; + (void)name; + return -1; + } + + void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding) { + auto* programObject = TryGetDirectVulkanProgram(program); + if (!programObject) return; + auto& cache = GetProgramResourceCache(*programObject); + if (storageBlockIndex >= cache.storageBlocks.size()) { + return; + } + cache.storageBlocks[storageBlockIndex].binding = storageBlockBinding; + } void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) {} void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) {} diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h index b4c91a76..92e761c9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h @@ -49,6 +49,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); void GenerateMipmap(GLenum target); + void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ); + void DispatchComputeIndirect(GLintptr indirect); + void MemoryBarrier(GLbitfield barriers); + void MemoryBarrierByRegion(GLbitfield barriers); + void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, + GLenum format); + void GetIntegeri_v(GLenum target, GLuint index, GLint* data); + void GetInteger64i_v(GLenum target, GLuint index, GLint64* data); + void GetProgramiv(GLuint program, GLenum pname, GLint* params); + void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params); + GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name); + void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, + GLsizei* length, GLchar* name); + void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, + const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params); + GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name); + GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name); + void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding); void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels); void Present(); diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkanResourceState.h b/MobileGL/MG_Backend/DirectVulkan/DirectVulkanResourceState.h new file mode 100644 index 00000000..39074466 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkanResourceState.h @@ -0,0 +1,20 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/DirectVulkanResourceState.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once + +#include + +namespace MobileGL::MG_State::GLState { + class ProgramObject; +} + +namespace MobileGL::MG_Backend::DirectVulkan { + GLuint GetShaderStorageBlockIndex(const MG_State::GLState::ProgramObject& program, const String& name); + GLuint GetShaderStorageBlockBinding(const MG_State::GLState::ProgramObject& program, GLuint blockIndex); +} diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index 44c91cec..7210626a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -8,6 +8,7 @@ #include "ProgramFactory.h" +#include "MG_Backend/DirectVulkan/DirectVulkanResourceState.h" #include "MG_Util/ShaderTranspiler/SpvcSession.h" #include "MG_Util/ShaderTranspiler/Types.h" #include @@ -979,6 +980,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { return ProgramFactory::DescriptorBindingKind::CombinedImageSampler; case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: return ProgramFactory::DescriptorBindingKind::UniformTexelBuffer; + case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER: + return ProgramFactory::DescriptorBindingKind::StorageBuffer; + case SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_IMAGE: + return ProgramFactory::DescriptorBindingKind::StorageImage; default: MOBILEGL_ASSERT(false, "ProgramFactory: unsupported reflected descriptor type %d", static_cast(descriptorType)); @@ -1000,7 +1005,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { String name = rawName; if (kind == ProgramFactory::DescriptorBindingKind::CombinedImageSampler || - kind == ProgramFactory::DescriptorBindingKind::UniformTexelBuffer) { + kind == ProgramFactory::DescriptorBindingKind::UniformTexelBuffer || + kind == ProgramFactory::DescriptorBindingKind::StorageImage) { const auto arraySuffix = name.find("[0]"); if (arraySuffix != String::npos) { name = name.substr(0, arraySuffix); @@ -1207,47 +1213,77 @@ namespace MobileGL::MG_Backend::DirectVulkan { case GL_SAMPLER_1D: case GL_INT_SAMPLER_1D: case GL_UNSIGNED_INT_SAMPLER_1D: + case GL_IMAGE_1D: + case GL_INT_IMAGE_1D: + case GL_UNSIGNED_INT_IMAGE_1D: return TextureTarget::Texture1D; case GL_SAMPLER_3D: case GL_INT_SAMPLER_3D: case GL_UNSIGNED_INT_SAMPLER_3D: + case GL_IMAGE_3D: + case GL_INT_IMAGE_3D: + case GL_UNSIGNED_INT_IMAGE_3D: return TextureTarget::Texture3D; case GL_SAMPLER_CUBE: case GL_SAMPLER_CUBE_SHADOW: case GL_INT_SAMPLER_CUBE: case GL_UNSIGNED_INT_SAMPLER_CUBE: + case GL_IMAGE_CUBE: + case GL_INT_IMAGE_CUBE: + case GL_UNSIGNED_INT_IMAGE_CUBE: return TextureTarget::TextureCubeMap; case GL_SAMPLER_2D_MULTISAMPLE: case GL_INT_SAMPLER_2D_MULTISAMPLE: case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: + case GL_IMAGE_2D_MULTISAMPLE: + case GL_INT_IMAGE_2D_MULTISAMPLE: + case GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE: return TextureTarget::Texture2DMultisample; case GL_SAMPLER_BUFFER: case GL_INT_SAMPLER_BUFFER: case GL_UNSIGNED_INT_SAMPLER_BUFFER: + case GL_IMAGE_BUFFER: + case GL_INT_IMAGE_BUFFER: + case GL_UNSIGNED_INT_IMAGE_BUFFER: return TextureTarget::TextureBuffer; case GL_SAMPLER_1D_ARRAY: case GL_SAMPLER_1D_ARRAY_SHADOW: case GL_INT_SAMPLER_1D_ARRAY: case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: + case GL_IMAGE_1D_ARRAY: + case GL_INT_IMAGE_1D_ARRAY: + case GL_UNSIGNED_INT_IMAGE_1D_ARRAY: return TextureTarget::Texture1DArray; case GL_SAMPLER_2D_ARRAY: case GL_SAMPLER_2D_ARRAY_SHADOW: case GL_INT_SAMPLER_2D_ARRAY: case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: + case GL_IMAGE_2D_ARRAY: + case GL_INT_IMAGE_2D_ARRAY: + case GL_UNSIGNED_INT_IMAGE_2D_ARRAY: return TextureTarget::Texture2DArray; case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: + case GL_IMAGE_2D_MULTISAMPLE_ARRAY: + case GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY: + case GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY: return TextureTarget::Texture2DMultisampleArray; case GL_SAMPLER_2D_RECT: case GL_SAMPLER_2D_RECT_SHADOW: case GL_INT_SAMPLER_2D_RECT: case GL_UNSIGNED_INT_SAMPLER_2D_RECT: + case GL_IMAGE_2D_RECT: + case GL_INT_IMAGE_2D_RECT: + case GL_UNSIGNED_INT_IMAGE_2D_RECT: return TextureTarget::TextureRectangle; case GL_SAMPLER_2D: case GL_SAMPLER_2D_SHADOW: case GL_INT_SAMPLER_2D: case GL_UNSIGNED_INT_SAMPLER_2D: + case GL_IMAGE_2D: + case GL_INT_IMAGE_2D: + case GL_UNSIGNED_INT_IMAGE_2D: default: return TextureTarget::Texture2D; } @@ -1397,6 +1433,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { entry.samplerNameByBinding.assign(m_maxBindings, String()); entry.samplerUniformLocationByBinding.assign(m_maxBindings, -1); entry.samplerTextureTargetByBinding.assign(m_maxBindings, TextureTarget::Texture2D); + entry.storageBlockNameByBinding.assign(m_maxBindings, String()); + entry.storageBlockIndexByBinding.assign(m_maxBindings, -1); entry.globalUboBinding = -1; entry.dynamicBindings.clear(); @@ -1461,7 +1499,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { entry.uniformBlockIndexByBinding[binding] = static_cast(blockIndex); } - // Reflect sampled images and samplerBuffer uniforms. + // Reflect sampled images, storage images, samplerBuffer uniforms, and SSBOs. uint32_t reflectedBindingCount = 0; SpvReflectResult reflectResult = spvReflectEnumerateDescriptorBindings(&reflectModule, &reflectedBindingCount, nullptr); @@ -1485,7 +1523,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const auto descriptorKind = ReflectDescriptorTypeToBindingKind(sampler->descriptor_type); if (descriptorKind != DescriptorBindingKind::CombinedImageSampler && - descriptorKind != DescriptorBindingKind::UniformTexelBuffer) { + descriptorKind != DescriptorBindingKind::UniformTexelBuffer && + descriptorKind != DescriptorBindingKind::StorageImage && + descriptorKind != DescriptorBindingKind::StorageBuffer) { continue; } @@ -1495,33 +1535,47 @@ namespace MobileGL::MG_Backend::DirectVulkan { "ProgramFactory::ReflectLayout: sampler binding %u exceeds maxBindings=%u for '%s'", binding, m_maxBindings, uniformName.c_str()); - const Int location = program.GetUniformLocation(uniformName); - if (location < 0) { - continue; - } - MOBILEGL_ASSERT(entry.bindingKinds[binding] == DescriptorBindingKind::None || entry.bindingKinds[binding] == descriptorKind, - "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for sampler '%s'", + "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for resource '%s'", binding, uniformName.c_str()); entry.bindingKinds[binding] = descriptorKind; + if (descriptorKind == DescriptorBindingKind::StorageBuffer) { + const GLuint blockIndex = GetShaderStorageBlockIndex(program, uniformName); + if (blockIndex == GL_INVALID_INDEX) { + MGLOG_D("ProgramFactory::ReflectLayout: skipping inactive SSBO '%s' at binding %u", + uniformName.c_str(), binding); + entry.bindingKinds[binding] = DescriptorBindingKind::None; + continue; + } + entry.storageBlockNameByBinding[binding] = uniformName; + entry.storageBlockIndexByBinding[binding] = static_cast(blockIndex); + continue; + } + + const Int location = program.GetUniformLocation(uniformName); + if (location < 0) { + entry.bindingKinds[binding] = DescriptorBindingKind::None; + continue; + } + const TextureTarget target = UniformTypeToTextureTarget(program.GetUniformType(static_cast(location))); MOBILEGL_ASSERT(target != TextureTarget::Unknown, - "ProgramFactory::ReflectLayout: failed to resolve sampler target for '%s'", + "ProgramFactory::ReflectLayout: failed to resolve texture 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)", + "ProgramFactory::ReflectLayout: texture 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)", + "ProgramFactory::ReflectLayout: texture binding %u maps to conflicting texture targets (%d vs %d)", binding, static_cast(entry.samplerTextureTargetByBinding[binding]), static_cast(target)); MOBILEGL_ASSERT(entry.samplerNameByBinding[binding].empty() || entry.samplerNameByBinding[binding] == uniformName, - "ProgramFactory::ReflectLayout: sampler binding %u maps to conflicting names ('%s' vs '%s')", + "ProgramFactory::ReflectLayout: texture binding %u maps to conflicting names ('%s' vs '%s')", binding, entry.samplerNameByBinding[binding].c_str(), uniformName.c_str()); if (location >= 0) { @@ -1546,13 +1600,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkDescriptorSetLayoutBinding layoutBinding{}; layoutBinding.binding = binding; layoutBinding.descriptorCount = 1; - layoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; + layoutBinding.stageFlags = VK_SHADER_STAGE_ALL; layoutBinding.pImmutableSamplers = nullptr; if (kind == DescriptorBindingKind::UniformBufferDynamic) { layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; entry.dynamicBindings.push_back(binding); } else if (kind == DescriptorBindingKind::UniformTexelBuffer) { layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; + } else if (kind == DescriptorBindingKind::StorageBuffer) { + layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + } else if (kind == DescriptorBindingKind::StorageImage) { + layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; } else { layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h index 18ed3288..50cfec0f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -22,7 +22,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { None = 0, UniformBufferDynamic, CombinedImageSampler, - UniformTexelBuffer + UniformTexelBuffer, + StorageBuffer, + StorageImage }; enum class CompileOptionBit : Uint { @@ -52,6 +54,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector samplerNameByBinding; Vector samplerUniformLocationByBinding; Vector samplerTextureTargetByBinding; + Vector storageBlockNameByBinding; + Vector storageBlockIndexByBinding; Int globalUboBinding = -1; Uint32 activeVertexInputLocationMask = 0; Array vertexInputTypes{}; @@ -78,6 +82,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { samplerNameByBinding = std::move(other.samplerNameByBinding); samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding); samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding); + storageBlockNameByBinding = std::move(other.storageBlockNameByBinding); + storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding); globalUboBinding = other.globalUboBinding; activeVertexInputLocationMask = other.activeVertexInputLocationMask; vertexInputTypes = other.vertexInputTypes; @@ -112,6 +118,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { samplerNameByBinding = std::move(other.samplerNameByBinding); samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding); samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding); + storageBlockNameByBinding = std::move(other.storageBlockNameByBinding); + storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding); globalUboBinding = other.globalUboBinding; activeVertexInputLocationMask = other.activeVertexInputLocationMask; vertexInputTypes = other.vertexInputTypes; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index 41bdb05e..5457da79 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -8,6 +8,7 @@ #include "UniformManager.h" +#include "MG_Backend/DirectVulkan/DirectVulkanResourceState.h" #include "MG_State/GLState/Core.h" #include "MG_State/GLState/ProgramState/ProgramObject.h" #include "MG_State/GLState/TextureState/TextureObject2D.h" @@ -393,6 +394,113 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + Bool UniformManager::ResolveStorageBufferDescriptor(const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, + Uint32 binding, + VkDescriptorBufferInfo& outBufferInfo) const { + outBufferInfo = {}; + MOBILEGL_ASSERT(m_bufferManager != nullptr, "ResolveStorageBufferDescriptor: buffer manager is null"); + MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, "ResolveStorageBufferDescriptor: GL context is null"); + MOBILEGL_ASSERT(binding < programObj.storageBlockIndexByBinding.size(), + "ResolveStorageBufferDescriptor: binding %u out of range", binding); + + const Int blockIndex = programObj.storageBlockIndexByBinding[binding]; + MOBILEGL_ASSERT(blockIndex >= 0, "ResolveStorageBufferDescriptor: no SSBO block mapped to binding %u", + binding); + const GLuint frontendBinding = + GetShaderStorageBlockBinding(program, static_cast(blockIndex)); + const Uint32 bindingPointCount = + static_cast(MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::ShaderStorage)); + MOBILEGL_ASSERT(frontendBinding < bindingPointCount, + "ResolveStorageBufferDescriptor: frontend SSBO binding %u out of range for block '%s'", + frontendBinding, programObj.storageBlockNameByBinding[binding].c_str()); + + auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, frontendBinding); + const auto bufferObject = bindingPoint.GetBoundObject(); + if (bufferObject == nullptr) { + MGLOG_E("ResolveStorageBufferDescriptor: no SSBO bound at frontend binding %u for block '%s'", + frontendBinding, programObj.storageBlockNameByBinding[binding].c_str()); + return false; + } + + BufferSlice slice{}; + if (!m_bufferManager->SyncResidentBuffer(BufferKind::ShaderStorage, bufferObject, slice) || !slice.IsValid()) { + MGLOG_E("ResolveStorageBufferDescriptor: failed to sync GL buffer %u for block '%s'", + bufferObject->GetExternalIndex(), programObj.storageBlockNameByBinding[binding].c_str()); + return false; + } + + const auto range = bindingPoint.GetRange(); + const VkDeviceSize bufferSize = static_cast(bufferObject->GetSize()); + VkDeviceSize rangeStart = static_cast(std::min(range.start, bufferObject->GetSize())); + VkDeviceSize rangeEnd = static_cast(std::min(range.end, bufferObject->GetSize())); + if (rangeEnd <= rangeStart) { + rangeStart = 0; + rangeEnd = bufferSize; + } + if (rangeEnd <= rangeStart) { + MGLOG_E("ResolveStorageBufferDescriptor: empty SSBO range for block '%s'", + programObj.storageBlockNameByBinding[binding].c_str()); + return false; + } + + outBufferInfo.buffer = slice.buffer; + outBufferInfo.offset = slice.offset + rangeStart; + outBufferInfo.range = rangeEnd - rangeStart; + return true; + } + + Bool UniformManager::ResolveStorageImageDescriptor(VkCommandBuffer commandBuffer, + const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, + Uint32 binding, + VkDescriptorImageInfo& outImageInfo) const { + outImageInfo = {}; + MOBILEGL_ASSERT(m_textureManager != nullptr, "ResolveStorageImageDescriptor: texture manager is null"); + MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, "ResolveStorageImageDescriptor: GL context is null"); + MOBILEGL_ASSERT(binding < programObj.samplerUniformLocationByBinding.size(), + "ResolveStorageImageDescriptor: binding %u out of range", binding); + + const Int location = programObj.samplerUniformLocationByBinding[binding]; + if (location < 0) { + MGLOG_E("ResolveStorageImageDescriptor: storage image binding %u has no uniform location", binding); + return false; + } + const Int imageUnit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); + if (imageUnit < 0 || imageUnit >= MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) { + MGLOG_E("ResolveStorageImageDescriptor: image unit %d out of range for binding %u", + imageUnit, binding); + return false; + } + + auto& imageBinding = MG_State::pGLContext->GetImageTextureBinding(imageUnit); + if (imageBinding.Texture == nullptr) { + MGLOG_E("ResolveStorageImageDescriptor: image unit %d is unbound for binding %u", imageUnit, binding); + return false; + } + + const Bool ready = m_textureManager->TransitionTextureForStorageImage(commandBuffer, *imageBinding.Texture); + if (!ready) { + MGLOG_E("ResolveStorageImageDescriptor: failed to transition textureId=%d for image unit %d", + imageBinding.Texture->GetExternalIndex(), imageUnit); + return false; + } + auto* resource = m_textureManager->SyncTextureAndGetDescriptor(*imageBinding.Texture); + if (resource == nullptr) { + return false; + } + + const Uint32 mipLevel = static_cast(std::max(0, imageBinding.Level)); + VkImageView view = m_textureManager->GetOrCreateViewAtMipLevel(*imageBinding.Texture, mipLevel); + if (view == VK_NULL_HANDLE) { + view = resource->fullView; + } + outImageInfo.sampler = VK_NULL_HANDLE; + outImageInfo.imageView = view; + outImageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + return outImageInfo.imageView != VK_NULL_HANDLE; + } + SharedPtr UniformManager::GetFallbackTexture(TextureTarget target) const { MOBILEGL_ASSERT(target == TextureTarget::Texture2D || target == TextureTarget::TextureRectangle, "UniformManager::GetFallbackTexture: unsupported fallback target=%d", @@ -530,13 +638,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const Uint32 descriptorCount = static_cast(descriptorCount64); - VkDescriptorPoolSize poolSizes[3]{}; + VkDescriptorPoolSize poolSizes[5]{}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; poolSizes[0].descriptorCount = descriptorCount; poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; poolSizes[1].descriptorCount = descriptorCount; poolSizes[2].type = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; poolSizes[2].descriptorCount = descriptorCount; + poolSizes[3].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + poolSizes[3].descriptorCount = descriptorCount; + poolSizes[4].type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + poolSizes[4].descriptorCount = descriptorCount; VkDescriptorPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; @@ -649,6 +761,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj, Uint32 frameIndex, + VkPipelineBindPoint bindPoint, const SamplerBindingOverride* samplerBindingOverride) { auto& frame = m_frames[frameIndex]; if (frame.descriptorPools.empty()) { @@ -737,6 +850,31 @@ namespace MobileGL::MG_Backend::DirectVulkan { write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; write.pTexelBufferView = &texelBufferViews.back(); writes.push_back(write); + } else if (kind == ProgramFactory::DescriptorBindingKind::StorageBuffer) { + VkDescriptorBufferInfo bufferInfo{}; + if (!ResolveStorageBufferDescriptor(program, programObj, binding, bufferInfo)) { + MGLOG_E( + "UniformDescriptorBinder::BindProgramUniformBuffers failed: storage buffer binding %u has no valid descriptor", + binding); + return false; + } + + bufferInfos.push_back(bufferInfo); + write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + write.pBufferInfo = &bufferInfos.back(); + writes.push_back(write); + } else if (kind == ProgramFactory::DescriptorBindingKind::StorageImage) { + VkDescriptorImageInfo imageInfo{}; + if (!ResolveStorageImageDescriptor(commandBuffer, program, programObj, binding, imageInfo)) { + MGLOG_E( + "UniformDescriptorBinder::BindProgramUniformBuffers failed: storage image binding %u has no valid descriptor", + binding); + return false; + } + imageInfos.push_back(imageInfo); + write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + write.pImageInfo = &imageInfos.back(); + writes.push_back(write); } else { VkDescriptorImageInfo imageInfo{}; Bool hasImage = false; @@ -771,7 +909,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, programObj.pipelineLayout, 0, 1, + vkCmdBindDescriptorSets(commandBuffer, bindPoint, programObj.pipelineLayout, 0, 1, &descriptorSet, static_cast(dynamicOffsets.size()), dynamicOffsets.data()); return true; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index f2067cad..e38ff013 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -46,6 +46,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj, Uint32 frameIndex, + VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, const SamplerBindingOverride* samplerBindingOverride = nullptr); private: @@ -81,6 +82,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool ResolveTexelBufferDescriptor(const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 frameIndex, VkBufferView& outBufferView); + Bool ResolveStorageBufferDescriptor(const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, Uint32 binding, + VkDescriptorBufferInfo& outBufferInfo) const; + Bool ResolveStorageImageDescriptor(VkCommandBuffer commandBuffer, + const MG_State::GLState::ProgramObject& program, + const ProgramFactory::VkProgramObject& programObj, Uint32 binding, + VkDescriptorImageInfo& outImageInfo) const; Bool ResolveUniformBufferPayload(const MG_State::GLState::ProgramObject& program, const ProgramFactory::VkProgramObject& programObj, Uint32 binding, const void*& outData, VkDeviceSize& outSize) const; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index 3bb4e2cb..b60f1aa8 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -199,6 +199,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; case BufferKind::TextureBuffer: return VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT; + case BufferKind::ShaderStorage: + return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; default: return 0; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h index 32082393..83c14d2b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h @@ -20,6 +20,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Index, Uniform, TextureBuffer, + ShaderStorage, }; struct VkBufferManagerInitInfo { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 57c09542..382d4519 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -650,6 +650,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { return ok; } + Bool VkTextureManager::TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, + MG_State::GLState::ITextureObject& texture) { + TextureResource* resource = SyncTextureAndGetDescriptor(texture); + if (resource == nullptr) { + return false; + } + if (resource->layout == VK_IMAGE_LAYOUT_GENERAL) { + return true; + } + + VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + VkAccessFlags srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT; + GetImageTransitionSourceState(resource->layout, srcStageMask, srcAccessMask); + + const Bool ok = TransitionImageLayout(commandBuffer, resource->image, resource->layout, + VK_IMAGE_LAYOUT_GENERAL, srcStageMask, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, srcAccessMask, + VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + resource->aspect, 0, resource->mipLevels, resource->arrayLayers); + MOBILEGL_ASSERT(ok, "TransitionTextureForStorageImage: transition failed for textureId=%d", + texture.GetExternalIndex()); + return ok; + } + Bool VkTextureManager::TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout, VkImageLayout newLayout, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, @@ -846,8 +870,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { imageInfo.format = format; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VkFormatProperties formatProperties{}; + vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties); + const Bool supportsStorageImage = + (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && + (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0; imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + (supportsStorageImage ? VK_IMAGE_USAGE_STORAGE_BIT : 0) | ((aspect & VK_IMAGE_ASPECT_COLOR_BIT) ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) | ((aspect & VK_IMAGE_ASPECT_DEPTH_BIT || aspect & VK_IMAGE_ASPECT_STENCIL_BIT) ? diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 23fd86fc..64bd6d89 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -125,6 +125,7 @@ public: Uint32 writtenMipLevel, VkImageLayout newLayout); Bool TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture); + Bool TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture); static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout, VkImageLayout newLayout, VkPipelineStageFlags srcStageMask, diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 1ccc35b7..6068ec46 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -1282,6 +1282,7 @@ void main() { VK_VERIFY(vkDeviceWaitIdle(m_device)); DestroyDeferredDepthMipmapCleanup(); + DestroyComputePipelines(); m_pipelineFactory.reset(); ShutdownBlitResources(); @@ -2047,7 +2048,7 @@ void main() { }; const Bool bound = m_uniformManager->BindProgramUniformBuffers( frame.commandBuffer, *m_depthMipmapResources.program, programObj, - m_frameContext.GetCurrentFrameIndex(), &samplerBindingOverride); + m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_GRAPHICS, &samplerBindingOverride); MOBILEGL_ASSERT(bound, "GenerateDepthMipmapWithShader: BindProgramUniformBuffers failed"); vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0); vkCmdEndRenderPass(frame.commandBuffer); @@ -2548,6 +2549,73 @@ void main() { return true; } + void VulkanRenderer::DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) { + m_textureManager->CollectGarbage(); + auto& frame = m_frameContext.GetCurrent(); + const auto& program = *MG_State::pGLContext->GetCurrentProgram(); + ProgramFactory::CompileOptionFlags transformFlags = 0; + const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags); + + if (!frame.isCommandRecording) { + m_frameContext.BeginCommandRecording(); + m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); + } + + if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { + VkRenderPassManager::EndRenderPass(frame.commandBuffer); + } + + const VkPipeline pipeline = GetOrCreateComputePipeline(programObj); + if (pipeline == VK_NULL_HANDLE) { + MGLOG_E("DispatchCompute skipped: compute pipeline creation failed for program=%u", + program.GetExternalIndex()); + return; + } + + vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + const Bool boundUniforms = m_uniformManager->BindProgramUniformBuffers( + frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex(), + VK_PIPELINE_BIND_POINT_COMPUTE); + if (!boundUniforms) { + MGLOG_E("DispatchCompute skipped: BindProgramUniformBuffers failed"); + return; + } + + MGLOG_D("DirectVulkan: glDispatchCompute(%u, %u, %u)", numGroupsX, numGroupsY, numGroupsZ); + vkCmdDispatch(frame.commandBuffer, numGroupsX, numGroupsY, numGroupsZ); + } + + void VulkanRenderer::MemoryBarrier(GLbitfield barriers) { + auto& frame = m_frameContext.GetCurrent(); + if (!frame.isCommandRecording) { + m_frameContext.BeginCommandRecording(); + m_uniformManager->BeginFrame(m_frameContext.GetCurrentFrameIndex()); + } + if (VkRenderPassManager::GetActiveRenderPass() != nullptr) { + VkRenderPassManager::EndRenderPass(frame.commandBuffer); + } + + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memoryBarrier.srcAccessMask = + VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + memoryBarrier.dstAccessMask = + VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT | + VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + + MGLOG_D("DirectVulkan: glMemoryBarrier(0x%x)", static_cast(barriers)); + vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, + 1, &memoryBarrier, 0, nullptr, 0, nullptr); + } + void VulkanRenderer::Clear(GLbitfield mask) { m_clearManager->CollectGarbage(); auto* fbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject().get(); @@ -2898,7 +2966,7 @@ void main() { const auto& blitProgramObj = m_programFactory->GetOrCreateProgram(*m_blitResources.program, blitTransformFlags); const Bool bound = m_uniformManager->BindProgramUniformBuffers( frame.commandBuffer, *m_blitResources.program, blitProgramObj, m_frameContext.GetCurrentFrameIndex(), - &samplerBindingOverride); + VK_PIPELINE_BIND_POINT_GRAPHICS, &samplerBindingOverride); MOBILEGL_ASSERT(bound, "TryBlitToDefaultFramebufferWithShader: BindProgramUniformBuffers failed"); vkCmdDraw(frame.commandBuffer, 3, 1, 0, 0); return true; @@ -4278,6 +4346,7 @@ void main() { if (m_pipelineFactory) { m_pipelineFactory->DestroyAll(); } + DestroyComputePipelines(); if (m_frameContext.GetFrameCount() > 0) { m_frameContext.GetCurrent().isCommandRecording = false; m_frameContext.GetCurrent().hasCommandBufferRecorded = false; @@ -4353,4 +4422,44 @@ void main() { m_clearManager->PopPendingClear(pending.texture); } } + + void VulkanRenderer::DestroyComputePipelines() { + if (m_device != VK_NULL_HANDLE) { + for (const auto& [hash, pipeline] : m_computePipelines) { + (void)hash; + if (pipeline != VK_NULL_HANDLE) { + vkDestroyPipeline(m_device, pipeline, nullptr); + } + } + } + m_computePipelines.clear(); + } + + VkPipeline VulkanRenderer::GetOrCreateComputePipeline(const ProgramFactory::VkProgramObject& programObj) { + const auto it = m_computePipelines.find(programObj.hash); + if (it != m_computePipelines.end()) { + return it->second; + } + + const auto stageIt = std::find_if(programObj.stages.begin(), programObj.stages.end(), + [](const VkPipelineShaderStageCreateInfo& stage) { + return stage.stage == VK_SHADER_STAGE_COMPUTE_BIT; + }); + MOBILEGL_ASSERT(stageIt != programObj.stages.end(), + "GetOrCreateComputePipeline: program has no compute stage"); + if (stageIt == programObj.stages.end()) { + return VK_NULL_HANDLE; + } + + VkComputePipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + pipelineInfo.stage = *stageIt; + pipelineInfo.layout = programObj.pipelineLayout; + + VkPipeline pipeline = VK_NULL_HANDLE; + VK_VERIFY(vkCreateComputePipelines(m_device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline), + "GetOrCreateComputePipeline, vkCreateComputePipelines"); + m_computePipelines.emplace(programObj.hash, pipeline); + return pipeline; + } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 062cbee2..efb2443a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -125,6 +125,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); void GenerateMipmap(GLenum target); + void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ); + void MemoryBarrier(GLbitfield barriers); void DrawArrays(const DrawCmd& payload); void DrawElements(const DrawIndexedCmd& payload); void MultiDrawElements(const MultiDrawIndexedCmd& payloads); @@ -211,6 +213,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { FrameContext m_frameContext; UniquePtr m_pipelineFactory; + UnorderedMap m_computePipelines; UniquePtr m_programFactory; UniquePtr m_uniformManager; UniquePtr m_vertexInputStateFactory; @@ -241,6 +244,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { ProgramFactory::CompileOptionFlags transformFlags, const MG_State::GLState::VertexArrayObject& vao, const RenderPassEntry& renderPassEntry); + VkPipeline GetOrCreateComputePipeline(const ProgramFactory::VkProgramObject& programObj); + void DestroyComputePipelines(); Bool UploadAndBindVertexBuffers(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao); Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame, diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp index 6ed32999..eab97596 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp @@ -304,7 +304,23 @@ namespace MobileGL::MG_Impl::GLImpl { *params = programObject->GetActiveUniformBlocksMaxNameLength(); MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params); break; - case GL_COMPUTE_WORK_GROUP_SIZE: // GL >= 4.3 + case GL_COMPUTE_WORK_GROUP_SIZE: { // GL >= 4.3 + auto getProgramiv = MG_Backend::gBackendFunctionsTable.GL.GetProgramiv; + if (!getProgramiv) { + params[0] = 1; + params[1] = 1; + params[2] = 1; + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + "Backend does not support program integer queries.")); + return; + } + getProgramiv(program, pname, params); + MGLOG_D("%s: %s = (%d, %d, %d)", __func__, + MG_Util::ConvertGLEnumToString(pname).c_str(), params[0], params[1], params[2]); + break; + } case GL_PROGRAM_BINARY_LENGTH: