From dd52f0381a0d6f974b987d494cdd44faf6e2a7fb Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 9 Jun 2026 04:11:03 +0800 Subject: [PATCH] [Fix] (MG_Backend/DirectVulkan): fix Voxy subgroup and indirect draw sync - Implement Vulkan subgroup capability querying and expose KHR subgroup getter values. - Fix DirectVulkan memory barriers so GL_COMMAND_BARRIER_BIT makes generated indirect draw commands visible. - Keep Voxy on the DirectVulkan gpu_shader_int64 quad decode path while filtering unsupported optional int64 usage on backends that do not advertise it. - Add MG_Test coverage for subgroup getters, Voxy subgroup/int64 shader probes, command barrier mapping, and indirect draw command layout. - Check for whether driver supports shader subgroup operation, disable on demand, and provide env var `MOBILEGL_DISABLE_SUBGROUP` to explicitly disable subgroup features --- MobileGL/MG_Backend/BackendObject.h | 4 + .../BackendObject_DirectVulkan.cpp | 121 ++++++++++--- .../DirectVulkan/BackendObject_DirectVulkan.h | 4 + .../DirectVulkan/Renderer/VulkanRenderer.cpp | 33 ++-- .../DirectVulkan/Renderer/VulkanRenderer.h | 2 + MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp | 41 +++-- .../MG_Impl/GLImpl/Texture/GL_Texture.cpp | 2 +- MobileGL/MG_Test/Program/ProgramTest.cpp | 76 ++++++++ MobileGL/MG_Test/SanityTest.cpp | 166 ++++++++++++++++++ MobileGL/MG_Test/Texture/TextureTest.cpp | 33 ++++ .../MG_Util/BackendLoaders/Vulkan/Loader.cpp | 48 +++++ .../MG_Util/BackendLoaders/Vulkan/Loader.h | 5 + .../ShaderSourceProcessor.cpp | 104 +++++++++++ 13 files changed, 595 insertions(+), 44 deletions(-) diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 75841ef3..265cafcf 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -113,6 +113,10 @@ namespace MobileGL { struct DynamicBackendParameters { SizeT UniformBufferOffsetAlignment = 256; SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024; + Uint32 SubgroupSize = 0; + Uint32 SubgroupSupportedStages = 0; + Uint32 SubgroupSupportedFeatures = 0; + Bool SubgroupQuadOperationsInAllStages = false; }; enum class WindowBackend { diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index aad30621..ff59df3a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -19,6 +19,27 @@ namespace MobileGL::MG_Backend::DirectVulkan { BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default; + BackendObject_DirectVulkan::BackendObject_DirectVulkan(): + m_rendererInfo{ + .RendererName = "Magma", + .BackendName = "Direct (Vulkan)", + .ExtraVendor = Nullopt, + .RendererGLInfo = + { + .TargetGLVersion = {3, 3, 0}, + .TargetGLSLVersion = {4, 6, 0}, + .Extensions = {V_OpenGL30, V_OpenGL31, V_OpenGL32, + 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, E_GL_ARB_framebuffer_object, + E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, + E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, + E_GL_ARB_texture_storage, E_GL_ARB_direct_state_access, + E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64}, + .IsCompatibilityProfile = false + }, + .StaticBackendCapability = {.AllowVSOnlyPrograms = false}} {} + Bool BackendObject_DirectVulkan::InitWindowSurface() { if (!m_windowHandle.Handle) { MGLOG_E("Cannot initialize DirectVulkan window surface: native window handle is null"); @@ -47,8 +68,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } - MG_Util::BackendLoader::FillInVulkanCapabilities(m_vulkanCaps, pVulkanRenderer->GetPhysicalDevice().properties); + const auto& physicalDevice = pVulkanRenderer->GetPhysicalDevice(); + if (!MG_Util::BackendLoader::QueryVulkanCapabilities(m_vulkanCaps, pVulkanRenderer->GetInstance(), + physicalDevice.handle)) { + MGLOG_W("DirectVulkan: failed to query extended Vulkan capabilities, using basic properties"); + MG_Util::BackendLoader::FillInVulkanCapabilities(m_vulkanCaps, physicalDevice.properties); + } UpdateDynamicBackendParameters(); + UpdateAdvertisedExtensions(); return true; } @@ -113,27 +140,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const RendererInfo& BackendObject_DirectVulkan::GetRendererInfo() const { - static RendererInfo RendererInfo = { - .RendererName = "Magma", // Renderer Name - .BackendName = "Direct (Vulkan)", // Backend Name - .ExtraVendor = Nullopt, // Extra vendor - .RendererGLInfo = - { - .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, 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, E_GL_ARB_framebuffer_object, - E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, - E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, - E_GL_ARB_texture_storage, E_GL_ARB_direct_state_access, - E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader_int64}, - .IsCompatibilityProfile = false // Is Compatibility Profile - }, - .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability - }; - return RendererInfo; + return m_rendererInfo; } String BackendObject_DirectVulkan::GetBackendAPIVersionString() const { @@ -214,11 +221,81 @@ namespace MobileGL::MG_Backend::DirectVulkan { return m_dynamicParameters; } + void BackendObject_DirectVulkan::ApplyVulkanCapabilitiesForTesting( + const MG_External::VulkanCapabilities& capabilities) { + m_vulkanCaps = capabilities; + UpdateDynamicBackendParameters(); + UpdateAdvertisedExtensions(); + } + + void BackendObject_DirectVulkan::UpdateAdvertisedExtensions() { + auto& extensions = m_rendererInfo.RendererGLInfo.Extensions; + extensions.erase(std::remove(extensions.begin(), extensions.end(), E_GL_KHR_shader_subgroup), + extensions.end()); + + if (m_vulkanCaps.SupportsShaderSubgroup) { + extensions.push_back(E_GL_KHR_shader_subgroup); + } + } + void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() { + const auto mapShaderStages = [](Uint32 vkStages) { + Uint32 glStages = 0; + if ((vkStages & VK_SHADER_STAGE_VERTEX_BIT) != 0) glStages |= GL_VERTEX_SHADER_BIT; + if ((vkStages & VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT) != 0) glStages |= GL_TESS_CONTROL_SHADER_BIT; + if ((vkStages & VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) != 0) { + glStages |= GL_TESS_EVALUATION_SHADER_BIT; + } + if ((vkStages & VK_SHADER_STAGE_GEOMETRY_BIT) != 0) glStages |= GL_GEOMETRY_SHADER_BIT; + if ((vkStages & VK_SHADER_STAGE_FRAGMENT_BIT) != 0) glStages |= GL_FRAGMENT_SHADER_BIT; + if ((vkStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0) glStages |= GL_COMPUTE_SHADER_BIT; + return glStages; + }; + + const auto mapSubgroupFeatures = [](Uint32 vkFeatures) { + Uint32 glFeatures = 0; + if ((vkFeatures & VK_SUBGROUP_FEATURE_BASIC_BIT) != 0) { + glFeatures |= GL_SUBGROUP_FEATURE_BASIC_BIT_KHR; + } + if ((vkFeatures & VK_SUBGROUP_FEATURE_VOTE_BIT) != 0) { + glFeatures |= GL_SUBGROUP_FEATURE_VOTE_BIT_KHR; + } + if ((vkFeatures & VK_SUBGROUP_FEATURE_ARITHMETIC_BIT) != 0) { + glFeatures |= GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR; + } + if ((vkFeatures & VK_SUBGROUP_FEATURE_BALLOT_BIT) != 0) { + glFeatures |= GL_SUBGROUP_FEATURE_BALLOT_BIT_KHR; + } + if ((vkFeatures & VK_SUBGROUP_FEATURE_SHUFFLE_BIT) != 0) { + glFeatures |= GL_SUBGROUP_FEATURE_SHUFFLE_BIT_KHR; + } + if ((vkFeatures & VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT) != 0) { + glFeatures |= GL_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT_KHR; + } + if ((vkFeatures & VK_SUBGROUP_FEATURE_CLUSTERED_BIT) != 0) { + glFeatures |= GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR; + } + if ((vkFeatures & VK_SUBGROUP_FEATURE_QUAD_BIT) != 0) { + glFeatures |= GL_SUBGROUP_FEATURE_QUAD_BIT_KHR; + } + return glFeatures; + }; + static constexpr SizeT kMaxAdvertisedShaderStorageBlockSize = 512ull * 1024ull * 1024ull; m_dynamicParameters.UniformBufferOffsetAlignment = m_vulkanCaps.UniformBufferOffsetAlignment; m_dynamicParameters.MaxShaderStorageBlockSize = std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize); + if (m_vulkanCaps.SupportsShaderSubgroup) { + m_dynamicParameters.SubgroupSize = m_vulkanCaps.SubgroupSize; + m_dynamicParameters.SubgroupSupportedStages = mapShaderStages(m_vulkanCaps.SubgroupSupportedStages); + m_dynamicParameters.SubgroupSupportedFeatures = mapSubgroupFeatures(m_vulkanCaps.SubgroupSupportedOperations); + m_dynamicParameters.SubgroupQuadOperationsInAllStages = m_vulkanCaps.SubgroupQuadOperationsInAllStages; + } else { + m_dynamicParameters.SubgroupSize = 0; + m_dynamicParameters.SubgroupSupportedStages = 0; + m_dynamicParameters.SubgroupSupportedFeatures = 0; + m_dynamicParameters.SubgroupQuadOperationsInAllStages = false; + } if (m_dynamicParameters.MaxShaderStorageBlockSize != m_vulkanCaps.MaxShaderStorageBlockSize) { MGLOG_I("DirectVulkan: clamped GL_MAX_SHADER_STORAGE_BLOCK_SIZE from %zu to %zu", m_vulkanCaps.MaxShaderStorageBlockSize, diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h index 74e045fa..7058cb1a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h @@ -14,6 +14,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { class BackendObject_DirectVulkan : public BackendObject { public: + BackendObject_DirectVulkan(); ~BackendObject_DirectVulkan() override; void Initialize() override; @@ -30,12 +31,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { const GlobalBackendFunctionsTable& GetBackendFunctions() const override; const DynamicBackendParameters& GetDynamicParameters() const override; BackendType GetBackendType() const override; + void ApplyVulkanCapabilitiesForTesting(const MG_External::VulkanCapabilities& capabilities); private: + void UpdateAdvertisedExtensions(); void UpdateDynamicBackendParameters(); Bool m_initialized = false; DynamicBackendParameters m_dynamicParameters; MG_External::VulkanCapabilities m_vulkanCaps; + RendererInfo m_rendererInfo; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index a4614dd7..276730b8 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -2920,16 +2920,7 @@ void main() { vkCmdDispatchIndirect(frame.commandBuffer, slice.buffer, slice.offset + static_cast(indirect)); } - 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 VulkanRenderer::BuildMemoryBarrierForGlBarriers(GLbitfield barriers) { VkMemoryBarrier memoryBarrier{}; memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; memoryBarrier.srcAccessMask = @@ -2945,6 +2936,24 @@ void main() { 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; + if ((barriers & GL_COMMAND_BARRIER_BIT) != 0) { + memoryBarrier.dstAccessMask |= VK_ACCESS_INDIRECT_COMMAND_READ_BIT; + } + return memoryBarrier; + } + + 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 = BuildMemoryBarrierForGlBarriers(barriers); + 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, @@ -5331,6 +5340,10 @@ void main() { return m_physicalDevice; } + VkInstance VulkanRenderer::GetInstance() const { + return m_instance; + } + Bool VulkanRenderer::IsDrawIndirectCountExtensionEnabled() const { return m_drawIndirectCountExtensionEnabled; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index d50e0dab..4b80756e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -143,6 +143,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ); void DispatchComputeIndirect(GLintptr indirect); void MemoryBarrier(GLbitfield barriers); + static VkMemoryBarrier BuildMemoryBarrierForGlBarriers(GLbitfield barriers); void DrawArrays(const DrawCmd& payload); void DrawElements(const DrawIndexedCmd& payload); void MultiDrawElements(const MultiDrawIndexedCmd& payloads); @@ -151,6 +152,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void Present(); const PhysicalDevice& GetPhysicalDevice() const; + VkInstance GetInstance() const; Bool IsDrawIndirectCountExtensionEnabled() const; void RecreateSwapchain(); diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index e23af76d..37d1c753 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -76,12 +76,12 @@ namespace MobileGL::MG_Impl::GLImpl { MGLOG_D("shadingLanguageVersion: %s", shadingLanguageVersion.c_str()); return (const GLubyte*)shadingLanguageVersion.c_str(); case GL_EXTENSIONS: - if (extensionsString.empty()) { - for (auto& ext : rendererInfo.RendererGLInfo.Extensions) { - extensionsString += MG_Util::ConvertGLExtToString(ext); + extensionsString.clear(); + for (auto& ext : rendererInfo.RendererGLInfo.Extensions) { + if (!extensionsString.empty()) { extensionsString += " "; } - extensionsString.pop_back(); + extensionsString += MG_Util::ConvertGLExtToString(ext); } return (const GLubyte*)extensionsString.c_str(); default: @@ -108,13 +108,10 @@ namespace MobileGL::MG_Impl::GLImpl { } static Vector extStrings; - static Bool initialized = false; - if (!initialized) { - extStrings.reserve(exts.size()); - for (const auto& ext : exts) { - extStrings.emplace_back(MG_Util::ConvertGLExtToString(ext)); - } - initialized = true; + extStrings.clear(); + extStrings.reserve(exts.size()); + for (const auto& ext : exts) { + extStrings.emplace_back(MG_Util::ConvertGLExtToString(ext)); } return (const GLubyte*)extStrings[index].c_str(); @@ -174,6 +171,15 @@ namespace MobileGL::MG_Impl::GLImpl { *data = static_cast(MG_Backend::DynamicBackendParameters{}.MaxShaderStorageBlockSize); } return; + case GL_SUBGROUP_SIZE_KHR: + case GL_SUBGROUP_SUPPORTED_STAGES_KHR: + case GL_SUBGROUP_SUPPORTED_FEATURES_KHR: + case GL_SUBGROUP_QUAD_ALL_STAGES_KHR: { + GLint params = 0; + GetIntegerv(pname, ¶ms); + *data = static_cast(params); + return; + } default: *data = 0; MG_State::pGLContext->RecordError( @@ -200,6 +206,7 @@ namespace MobileGL::MG_Impl::GLImpl { return; } const auto& rendererInfo = activeBackendObject->GetRendererInfo(); + const auto& dynamicParameters = activeBackendObject->GetDynamicParameters(); switch (pname) { case GL_ACTIVE_TEXTURE: @@ -277,6 +284,18 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_COMPRESSED_TEXTURE_FORMATS: *params = 0; // TODO break; + case GL_SUBGROUP_SIZE_KHR: + *params = static_cast(dynamicParameters.SubgroupSize); + break; + case GL_SUBGROUP_SUPPORTED_STAGES_KHR: + *params = static_cast(dynamicParameters.SubgroupSupportedStages); + break; + case GL_SUBGROUP_SUPPORTED_FEATURES_KHR: + *params = static_cast(dynamicParameters.SubgroupSupportedFeatures); + break; + case GL_SUBGROUP_QUAD_ALL_STAGES_KHR: + *params = dynamicParameters.SubgroupQuadOperationsInAllStages ? GL_TRUE : GL_FALSE; + break; case GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS: *params = 16; // TODO: use backend value break; diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index 9fbd2c63..831a942f 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -365,7 +365,7 @@ namespace MobileGL::MG_Impl::GLImpl { const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureInternalFormat, texturePixelDataType); const SizeT srcRowSize = static_cast(width) * internalBpp; - const SizeT srcStride = (srcRowSize + unpackParams.Alignment - 1) & ~(unpackParams.Alignment - 1); + const SizeT srcStride = srcRowSize; const SizeT destRowSize = static_cast(texelSize.x()) * internalBpp; if (xoffset + width > static_cast(texelSize.x()) || diff --git a/MobileGL/MG_Test/Program/ProgramTest.cpp b/MobileGL/MG_Test/Program/ProgramTest.cpp index c074c7e3..37c24df6 100644 --- a/MobileGL/MG_Test/Program/ProgramTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramTest.cpp @@ -13,6 +13,8 @@ #include "Includes.h" #include "Init.h" #include "MG_Backend/DirectVulkan/DirectVulkanResourceState.h" +#include "MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h" +#include "MG_Backend/BackendObjects.h" #include "MG_Impl/GLImpl/Getter/GL_Getter.h" #include "MG_Impl/GLImpl/Program/GL_Program.h" #include "MG_State/GLState/Core.h" @@ -118,6 +120,80 @@ TEST_F(ProgramTest, CompileFragment) { CompileShader(fs); } +TEST_F(ProgramTest, CompileVoxySubgroupProbeShader) { + char infoLog[1024] = ""; + const char* csSrc = R"(#version 430 +#extension GL_KHR_shader_subgroup_basic : require +#extension GL_KHR_shader_subgroup_arithmetic : require +layout(local_size_x=32) in; +void main() { + uint a = subgroupExclusiveAdd(gl_LocalInvocationIndex); +} +)"; + + GLuint cs = CreateShader(GL_COMPUTE_SHADER); + ShaderSource(cs, 1, &csSrc, nullptr); + CompileShader(cs); + + GLint compileStatus = GL_FALSE; + GetShaderiv(cs, GL_COMPILE_STATUS, &compileStatus); + GetShaderInfoLog(cs, sizeof(infoLog), nullptr, infoLog); + EXPECT_EQ(compileStatus, GL_TRUE) << infoLog; +} + +TEST_F(ProgramTest, CompileVoxyGpuShaderInt64QuadDecode) { + auto previousBackend = Move(MG_Backend::pActiveBackendObject); + MG_Backend::pActiveBackendObject = MakeUnique(); + + char infoLog[2048] = ""; + const char* vsSrc = R"(#version 460 core +#extension GL_ARB_gpu_shader_int64 : enable + +#ifdef GL_ARB_gpu_shader_int64 +#define Quad uint64_t +#define Eu32(data, amountBits, shift) (uint((data)>>(shift))&((1u<<(amountBits))-1)) + +vec3 extractPos(uint64_t quad) { + return vec3(Eu32(quad, 5, 21), Eu32(quad, 5, 16), Eu32(quad, 5, 11)); +} + +uint extractStateId(uint64_t quad) { + return Eu32(quad, 16, 26); +} + +uint extractBiomeId(uint64_t quad) { + return Eu32(quad, 9, 46); +} +#else +#error GL_ARB_gpu_shader_int64 should select Voxy native quad decode path +#endif + +layout(std430, binding = 1) readonly buffer QuadBuffer { + Quad quadData[]; +}; + +layout(location = 0) flat out uvec4 interData; + +void main() { + uint64_t quad = quadData[uint(gl_VertexID) >> 2]; + vec3 pos = extractPos(quad); + interData = uvec4(extractStateId(quad), extractBiomeId(quad), uint(pos.x), uint(pos.y)); + gl_Position = vec4(pos * (1.0 / 32.0), 1.0); +} +)"; + + GLuint vs = CreateShader(GL_VERTEX_SHADER); + ShaderSource(vs, 1, &vsSrc, nullptr); + CompileShader(vs); + + GLint compileStatus = GL_FALSE; + GetShaderiv(vs, GL_COMPILE_STATUS, &compileStatus); + GetShaderInfoLog(vs, sizeof(infoLog), nullptr, infoLog); + EXPECT_EQ(compileStatus, GL_TRUE) << infoLog; + + MG_Backend::pActiveBackendObject = Move(previousBackend); +} + TEST_F(ProgramTest, ShaderSourceKeepsOriginalTextAfterCompile) { const char* part0 = R"(#define HIGHP_OR_DEFAULT highp attribute vec4 Position; diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index 88a5c5d1..5825444a 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -21,9 +21,44 @@ #include #include #include +#include +#include +#include #include namespace { + class DynamicParameterBackend final : public MobileGL::MG_Backend::BackendObject { + public: + explicit DynamicParameterBackend(MobileGL::MG_Backend::DynamicBackendParameters params): + m_params(params) {} + + void Initialize() override {} + MobileGL::Bool InitCapabilities() override { return true; } + MobileGL::Bool InitWindowSurface() override { return true; } + const MobileGL::RendererInfo& GetRendererInfo() const override { return m_info; } + MobileGL::String GetBackendAPIVersionString() const override { return "test"; } + const MobileGL::MG_Backend::GlobalBackendFunctionsTable& GetBackendFunctions() const override { + return m_functions; + } + const MobileGL::MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override { + return m_params; + } + MobileGL::BackendType GetBackendType() const override { return MobileGL::BackendType::Unknown; } + + private: + MobileGL::MG_Backend::DynamicBackendParameters m_params; + MobileGL::MG_Backend::GlobalBackendFunctionsTable m_functions{}; + MobileGL::RendererInfo m_info{ + .RendererName = "Test", + .BackendName = "DynamicParameterBackend", + .ExtraVendor = MobileGL::Nullopt, + .RendererGLInfo = {.TargetGLVersion = {3, 3, 0}, + .TargetGLSLVersion = {4, 6, 0}, + .Extensions = {}, + .IsCompatibilityProfile = false}, + .StaticBackendCapability = {.AllowVSOnlyPrograms = false}}; + }; + void SetEnvVar(const char* name, const char* value) { #if defined(_WIN32) _putenv_s(name, value); @@ -87,6 +122,137 @@ TEST(DirectVulkanSanity, AdvertisesVoxyRequiredRenderingExtensionsWithoutRaising extensions.end()); } +TEST(DirectVulkanSanity, AdvertisesSubgroupOnlyWhenVulkanReportsUsableSupport) { + using namespace MobileGL; + + MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend; + + MG_External::VulkanCapabilities unsupportedCaps; + unsupportedCaps.SupportsShaderSubgroup = false; + unsupportedCaps.SubgroupSize = 32; + unsupportedCaps.SubgroupSupportedStages = VK_SHADER_STAGE_COMPUTE_BIT; + unsupportedCaps.SubgroupSupportedOperations = VK_SUBGROUP_FEATURE_BASIC_BIT; + backend.ApplyVulkanCapabilitiesForTesting(unsupportedCaps); + + const auto& unsupportedExtensions = backend.GetRendererInfo().RendererGLInfo.Extensions; + EXPECT_EQ(std::find(unsupportedExtensions.begin(), unsupportedExtensions.end(), E_GL_KHR_shader_subgroup), + unsupportedExtensions.end()); + EXPECT_EQ(backend.GetDynamicParameters().SubgroupSize, 0u); + EXPECT_EQ(backend.GetDynamicParameters().SubgroupSupportedStages, 0u); + EXPECT_EQ(backend.GetDynamicParameters().SubgroupSupportedFeatures, 0u); + + MG_External::VulkanCapabilities supportedCaps; + supportedCaps.SupportsShaderSubgroup = true; + supportedCaps.SubgroupSize = 32; + supportedCaps.SubgroupSupportedStages = VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT; + supportedCaps.SubgroupSupportedOperations = + VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT | VK_SUBGROUP_FEATURE_QUAD_BIT; + supportedCaps.SubgroupQuadOperationsInAllStages = true; + backend.ApplyVulkanCapabilitiesForTesting(supportedCaps); + + const auto& supportedExtensions = backend.GetRendererInfo().RendererGLInfo.Extensions; + EXPECT_NE(std::find(supportedExtensions.begin(), supportedExtensions.end(), E_GL_KHR_shader_subgroup), + supportedExtensions.end()); + EXPECT_EQ(backend.GetDynamicParameters().SubgroupSize, 32u); + EXPECT_EQ(backend.GetDynamicParameters().SubgroupSupportedStages, + static_cast(GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT)); + EXPECT_EQ(backend.GetDynamicParameters().SubgroupSupportedFeatures, + static_cast(GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | + GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR | + GL_SUBGROUP_FEATURE_QUAD_BIT_KHR)); + EXPECT_TRUE(backend.GetDynamicParameters().SubgroupQuadOperationsInAllStages); +} + +TEST(DirectVulkanSanity, KeepsOptionalGpuShaderInt64BranchForVoxyQuadDecode) { + using namespace MobileGL; + + MG_Backend::pActiveBackendObject = MakeUnique(); + String source = R"(#version 460 core +#extension GL_ARB_gpu_shader_int64 : enable +#ifdef GL_ARB_gpu_shader_int64 +uint getLowBits(uint64_t v) { + return uint(v & uint64_t(0xffu)); +} +#else +#error int64 branch should be enabled for DirectVulkan +#endif +void main() { + gl_Position = vec4(float(getLowBits(uint64_t(0x2au)))); +} +)"; + + MG_Util::ShaderTranspiler::PreprocessShaderSource(ShaderStage::Vertex, source); + EXPECT_NE(source.find("#extension GL_ARB_gpu_shader_int64"), String::npos); + EXPECT_NE(source.find("GL_ARB_gpu_shader_int64"), String::npos); + + auto shaderResult = MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({ + .shaderType = GL_VERTEX_SHADER, + .sourceStr = source, + .flags = MG_Util::ShaderTranspiler::ShaderCompileBits::CompileForOpenGL, + }); + EXPECT_TRUE(shaderResult) << (shaderResult ? "" : shaderResult.error().log); + + MG_Backend::pActiveBackendObject.reset(); +} + +TEST(GetterSanity, ReportsKhrSubgroupDynamicParameters) { + using namespace MobileGL; + + MG_State::pGLContext = MakeUnique(); + + MG_Backend::DynamicBackendParameters params; + params.SubgroupSize = 32; + params.SubgroupSupportedStages = GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT; + params.SubgroupSupportedFeatures = GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | + GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR | + GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR | + GL_SUBGROUP_FEATURE_QUAD_BIT_KHR; + params.SubgroupQuadOperationsInAllStages = true; + MG_Backend::pActiveBackendObject = MakeUnique(params); + + GLint intValue = 0; + MG_Impl::GLImpl::GetIntegerv(GL_SUBGROUP_SIZE_KHR, &intValue); + EXPECT_EQ(intValue, 32); + MG_Impl::GLImpl::GetIntegerv(GL_SUBGROUP_SUPPORTED_STAGES_KHR, &intValue); + EXPECT_EQ(intValue, static_cast(params.SubgroupSupportedStages)); + MG_Impl::GLImpl::GetIntegerv(GL_SUBGROUP_SUPPORTED_FEATURES_KHR, &intValue); + EXPECT_EQ(intValue, static_cast(params.SubgroupSupportedFeatures)); + MG_Impl::GLImpl::GetIntegerv(GL_SUBGROUP_QUAD_ALL_STAGES_KHR, &intValue); + EXPECT_EQ(intValue, GL_TRUE); + + GLint64 int64Value = 0; + MG_Impl::GLImpl::GetInteger64v(GL_SUBGROUP_SUPPORTED_FEATURES_KHR, &int64Value); + EXPECT_EQ(int64Value, static_cast(params.SubgroupSupportedFeatures)); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + MG_Backend::pActiveBackendObject.reset(); + MG_State::pGLContext.reset(); +} + +TEST(DirectVulkanSanity, CommandMemoryBarrierMakesIndirectDrawCommandsVisible) { + using namespace MobileGL; + using namespace MobileGL::MG_Backend::DirectVulkan; + + const VkMemoryBarrier commandBarrier = + VulkanRenderer::BuildMemoryBarrierForGlBarriers(GL_COMMAND_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT); + EXPECT_NE(commandBarrier.dstAccessMask & VK_ACCESS_INDIRECT_COMMAND_READ_BIT, 0u); + + const VkMemoryBarrier storageOnlyBarrier = + VulkanRenderer::BuildMemoryBarrierForGlBarriers(GL_SHADER_STORAGE_BARRIER_BIT); + EXPECT_EQ(storageOnlyBarrier.dstAccessMask & VK_ACCESS_INDIRECT_COMMAND_READ_BIT, 0u); +} + +TEST(DirectVulkanSanity, DrawIndexedIndirectCommandMatchesGlAndVulkanLayout) { + using namespace MobileGL::MG_Backend::DirectVulkan; + + EXPECT_EQ(sizeof(DrawIndexedCmdParam), 20u); + EXPECT_EQ(offsetof(DrawIndexedCmdParam, indexCount), 0u); + EXPECT_EQ(offsetof(DrawIndexedCmdParam, instanceCount), 4u); + EXPECT_EQ(offsetof(DrawIndexedCmdParam, firstIndex), 8u); + EXPECT_EQ(offsetof(DrawIndexedCmdParam, vertexOffset), 12u); + EXPECT_EQ(offsetof(DrawIndexedCmdParam, firstInstance), 16u); +} + TEST(DirectVulkanSanity, UndefinedDepthStencilLayoutUsesDontCareForUnclearedAspects) { using namespace MobileGL; using namespace MobileGL::MG_Backend::DirectVulkan; diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index ccb8d493..61c16cce 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -11,6 +11,7 @@ #include "Includes.h" #include "Init.h" #include +#include #include #include #include @@ -66,6 +67,38 @@ TEST_F(TextureTest, TextureStorageAndSubImageModifyNamedObjectOnly) { EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } +TEST_F(TextureTest, BoundTexSubImage2DUsesCompactRowsAfterUnpackProcessing) { + GLuint texture = 0; + MG_Impl::GLImpl::GenTextures(1, &texture); + MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); + + const Uint8 initialPixels[2 * 16] = {}; + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 5, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, initialPixels); + + const Uint8 subImageWithGuard[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, + 101, 102, 103, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 201, 202, 203, 204, 205, 206, + }; + MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4); + MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 1, 0, 3, 2, GL_RGB, GL_UNSIGNED_BYTE, subImageWithGuard); + + const auto textureObject = MG_State::pGLContext->GetTextureObject(texture); + auto* mipmapObject = static_cast(textureObject.get()); + const auto* stored = static_cast( + mipmapObject->MapMipmapData(TextureUploadTarget::Texture2D, 0)); + + const Uint8 expected[] = { + 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, + 0, 0, 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 0, 0, 0, + }; + for (SizeT i = 0; i < sizeof(expected); ++i) { + EXPECT_EQ(stored[i], expected[i]) << "byte " << i; + } + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + TEST_F(TextureTest, GetTextureImageReadsNamedObjectWithoutBinding) { GLuint texture = 0; MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture); diff --git a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp index a5271759..68941de2 100644 --- a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp @@ -8,6 +8,9 @@ #include "Loader.h" +#include +#include + namespace MobileGL::MG_Util::BackendLoader { namespace { struct VulkanDynamicFunctions { @@ -33,6 +36,14 @@ namespace MobileGL::MG_Util::BackendLoader { return loaded; } + + Bool IsShaderSubgroupForcedDisabled() { + const char* value = std::getenv("MOBILEGL_DISABLE_SUBGROUP"); + if (!value) { + return false; + } + return std::strcmp(value, "true") == 0 || std::strcmp(value, "TRUE") == 0; + } } // namespace inline Version DecodeApiVersion(uint32_t version) { @@ -46,6 +57,12 @@ namespace MobileGL::MG_Util::BackendLoader { return oss.str(); } + inline Bool HasUsableShaderSubgroupSupport(const VkPhysicalDeviceSubgroupProperties& subgroupProps) { + return subgroupProps.subgroupSize > 0 && + (subgroupProps.supportedStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0 && + (subgroupProps.supportedOperations & VK_SUBGROUP_FEATURE_BASIC_BIT) != 0; + } + Bool QueryVulkanCapabilities(MobileGL::MG_External::VulkanCapabilities& caps, VkInstance instance, VkPhysicalDevice physicalDevice) { if (!physicalDevice) { @@ -69,8 +86,12 @@ namespace MobileGL::MG_Util::BackendLoader { return false; } + VkPhysicalDeviceSubgroupProperties subgroupProps{}; + subgroupProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES; + VkPhysicalDeviceProperties2 props2{}; props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + props2.pNext = &subgroupProps; if (vk.vkGetPhysicalDeviceProperties2) { vk.vkGetPhysicalDeviceProperties2(physicalDevice, &props2); } else { @@ -84,6 +105,28 @@ namespace MobileGL::MG_Util::BackendLoader { caps.DriverVersionString = DecodeDriverVersion(p.driverVersion); caps.UniformBufferOffsetAlignment = static_cast(p.limits.minUniformBufferOffsetAlignment); caps.MaxShaderStorageBlockSize = static_cast(p.limits.maxStorageBufferRange); + const Bool supportsShaderSubgroup = vk.vkGetPhysicalDeviceProperties2 && + HasUsableShaderSubgroupSupport(subgroupProps); + const Bool forceDisableShaderSubgroup = IsShaderSubgroupForcedDisabled(); + caps.SupportsShaderSubgroup = supportsShaderSubgroup && !forceDisableShaderSubgroup; + if (caps.SupportsShaderSubgroup) { + caps.SubgroupSize = subgroupProps.subgroupSize; + caps.SubgroupSupportedStages = subgroupProps.supportedStages; + caps.SubgroupSupportedOperations = subgroupProps.supportedOperations; + caps.SubgroupQuadOperationsInAllStages = subgroupProps.quadOperationsInAllStages == VK_TRUE; + } else { + caps.SubgroupSize = 0; + caps.SubgroupSupportedStages = 0; + caps.SubgroupSupportedOperations = 0; + caps.SubgroupQuadOperationsInAllStages = false; + } + + MGLOG_I("Vulkan shader subgroup support: detected=%s advertised=%s size=%u stages=0x%x operations=0x%x", + supportsShaderSubgroup ? "true" : "false", caps.SupportsShaderSubgroup ? "true" : "false", + subgroupProps.subgroupSize, subgroupProps.supportedStages, subgroupProps.supportedOperations); + if (supportsShaderSubgroup && forceDisableShaderSubgroup) { + MGLOG_W("Vulkan shader subgroup support forced off by MOBILEGL_DISABLE_SUBGROUP"); + } return true; } @@ -95,5 +138,10 @@ namespace MobileGL::MG_Util::BackendLoader { caps.DriverVersionString = DecodeDriverVersion(properties.driverVersion); caps.UniformBufferOffsetAlignment = static_cast(properties.limits.minUniformBufferOffsetAlignment); caps.MaxShaderStorageBlockSize = static_cast(properties.limits.maxStorageBufferRange); + caps.SupportsShaderSubgroup = false; + caps.SubgroupSize = 0; + caps.SubgroupSupportedStages = 0; + caps.SubgroupSupportedOperations = 0; + caps.SubgroupQuadOperationsInAllStages = false; } } // namespace MobileGL::MG_Util::BackendLoader diff --git a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h index 54c360a7..dae1620d 100644 --- a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h @@ -17,6 +17,11 @@ namespace MobileGL { String DriverVersionString; Int UniformBufferOffsetAlignment = 256; SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024; + Bool SupportsShaderSubgroup = false; + Uint32 SubgroupSize = 0; + Uint32 SubgroupSupportedStages = 0; + Uint32 SubgroupSupportedOperations = 0; + Bool SubgroupQuadOperationsInAllStages = false; }; } // namespace MG_External namespace MG_Util::BackendLoader { diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp index 4e13031b..e6c88619 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp @@ -8,7 +8,9 @@ #include "ShaderSourceProcessor.h" +#include #include +#include namespace { using MobileGL::SizeT; @@ -159,6 +161,106 @@ namespace { return lineEnd == MobileGL::String::npos ? source.size() : lineEnd + 1; } + bool IsExtensionAdvertised(MobileGL::GLExtension extension) { + const auto& activeBackendObject = MobileGL::MG_Backend::pActiveBackendObject; + if (!activeBackendObject) { + return true; + } + + const auto& extensions = activeBackendObject->GetRendererInfo().RendererGLInfo.Extensions; + return std::find(extensions.begin(), extensions.end(), extension) != extensions.end(); + } + + MobileGL::String TrimDirectiveToken(const MobileGL::String& token) { + SizeT start = 0; + while (start < token.size() && std::isspace(static_cast(token[start]))) { + start++; + } + + SizeT end = token.size(); + while (end > start && std::isspace(static_cast(token[end - 1]))) { + end--; + } + return token.substr(start, end - start); + } + + void FilterUnsupportedGpuShaderInt64(MobileGL::String& source) { + if (IsExtensionAdvertised(MobileGL::E_GL_ARB_gpu_shader_int64)) { + return; + } + + SizeT lineStart = 0; + while (lineStart < source.size()) { + SizeT lineEnd = source.find('\n', lineStart); + const bool hasLineBreak = lineEnd != MobileGL::String::npos; + if (!hasLineBreak) { + lineEnd = source.size(); + } + + const MobileGL::String line = source.substr(lineStart, lineEnd - lineStart); + SizeT probe = 0; + while (probe < line.size() && std::isspace(static_cast(line[probe]))) { + probe++; + } + + if (probe < line.size() && line[probe] == '#') { + probe++; + while (probe < line.size() && std::isspace(static_cast(line[probe]))) { + probe++; + } + + constexpr const char* extensionToken = "extension"; + constexpr SizeT extensionLen = 9; + const bool hasExtensionDirective = + probe + extensionLen <= line.size() && + line.compare(probe, extensionLen, extensionToken) == 0 && + (probe + extensionLen == line.size() || !IsIdentifierChar(line[probe + extensionLen])); + if (hasExtensionDirective) { + probe += extensionLen; + while (probe < line.size() && std::isspace(static_cast(line[probe]))) { + probe++; + } + + constexpr const char* int64Extension = "GL_ARB_gpu_shader_int64"; + constexpr SizeT int64ExtensionLen = 23; + const bool hasInt64Extension = + probe + int64ExtensionLen <= line.size() && + line.compare(probe, int64ExtensionLen, int64Extension) == 0 && + (probe + int64ExtensionLen == line.size() || + !IsIdentifierChar(line[probe + int64ExtensionLen])); + if (hasInt64Extension) { + probe += int64ExtensionLen; + while (probe < line.size() && std::isspace(static_cast(line[probe]))) { + probe++; + } + + if (probe < line.size() && line[probe] == ':') { + probe++; + const MobileGL::String behavior = TrimDirectiveToken(line.substr(probe)); + const SizeT replaceLen = lineEnd - lineStart + (hasLineBreak ? 1 : 0); + if (behavior == "require") { + const MobileGL::String replacement = + "#error GL_ARB_gpu_shader_int64 is not advertised by MobileGL\n"; + source.replace(lineStart, replaceLen, replacement); + lineStart += replacement.size(); + } else if (behavior == "enable" || behavior == "warn") { + source.replace(lineStart, replaceLen, "\n"); + lineStart++; + } else { + lineStart = lineEnd + (hasLineBreak ? 1 : 0); + } + continue; + } + } + } + } + + lineStart = lineEnd + (hasLineBreak ? 1 : 0); + } + + ReplaceIdentifier(source, "GL_ARB_gpu_shader_int64", "MG_DISABLED_GL_ARB_gpu_shader_int64"); + } + void ModernizeLegacyGLSL(MobileGL::ShaderStage stage, MobileGL::String& source) { RemoveDefineForIdentifier(source, "HIGHP_OR_DEFAULT"); RemoveDefineForIdentifier(source, "MEDIUMP_OR_DEFAULT"); @@ -278,6 +380,8 @@ namespace MobileGL { } } + FilterUnsupportedGpuShaderInt64(source); + // Some shader packs define helpers with built-in GLSL names such as round(), tanh(), or fma(). // These may pass OpenGL-style validation but fail when recompiled for Vulkan/SPIR-V generation. RenameBuiltinShadowingFunction(source, "round", "mg_round");