From 10ff5e2b18d4a3c9f4b13841d903940bfe094059 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 15 Aug 2026 06:30:44 -0400 Subject: [PATCH] [Fix, Test] (DirectGLES, DirectVulkan): advertise indirect draw capabilities accurately - advertise GL_ARB_draw_indirect when supported - gate GL_ARB_base_instance on complete non-zero firstInstance semantics - synchronize Driver POST reporting - add capability and extension-advertisement regression tests --- .../DirectGLES/BackendObject_DirectGLES.cpp | 41 +++++-- .../DirectGLES/BackendObject_DirectGLES.h | 7 +- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 5 +- .../BackendObject_DirectVulkan.cpp | 22 +++- .../DirectVulkan/BackendObject_DirectVulkan.h | 7 +- .../DirectVulkan/Renderer/VulkanRenderer.h | 6 + .../BackendLoader/BackendLoaderTest.cpp | 103 +++++++++++++++++- .../Program/ParallelShaderCompileTest.cpp | 8 +- .../MG_Util/BackendLoaders/OpenGL/Loader.cpp | 3 + .../MG_Util/BackendLoaders/OpenGL/Loader.h | 4 + MobileGL/MG_Util/SelfTest/DriverPost.cpp | 11 +- 11 files changed, 180 insertions(+), 37 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 042e2445..f8a3de4b 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -712,9 +712,9 @@ namespace MobileGL::MG_Backend::DirectGLES { { .TargetGLVersion = {4, 0, 0}, // GL target version .TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version - // Baseline advertisement (no timer queries / anisotropy yet); reconciled - // once the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions. - .Extensions = BuildAdvertisedExtensions(false, false), + // Baseline advertisement (no runtime capabilities yet); reconciled once + // the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions. + .Extensions = BuildAdvertisedExtensions(false, false, false, false), .IsCompatibilityProfile = false // Is Compatibility Profile }, .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability @@ -734,9 +734,11 @@ namespace MobileGL::MG_Backend::DirectGLES { // thread can only observe the extension string after the // advertisement for its context has settled; rebuilding the whole // list keeps the re-run after a context recreation idempotent. - void UpdateAdvertisedCapabilityExtensions(Bool anisotropicFilteringSupported) { - MutableRendererInfo().RendererGLInfo.Extensions = - BuildAdvertisedExtensions(AreTimerQueriesSupported(), anisotropicFilteringSupported); + void UpdateAdvertisedCapabilityExtensions(const MG_External::GLESCapabilities& capabilities) { + MutableRendererInfo().RendererGLInfo.Extensions = BuildAdvertisedExtensions( + AreTimerQueriesSupported(), capabilities.SupportsTextureFilterAnisotropy, + capabilities.SupportsDrawIndirect, + capabilities.SupportsDrawIndirect && capabilities.SupportsBaseInstance); } } // namespace @@ -779,11 +781,11 @@ namespace MobileGL::MG_Backend::DirectGLES { return false; } DirectGLES::SetGLESCapabilities(m_GLESCapabilities); - // Now that g_GLESCapabilities knows about GL_EXT_disjoint_timer_query and - // GL_EXT_texture_filter_anisotropic, reconcile the advertisement (see the comment on - // UpdateAdvertisedCapabilityExtensions for why it cannot happen when the extension - // list is first built). - UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities.SupportsTextureFilterAnisotropy); + // Now that g_GLESCapabilities knows the host extensions, entry points, and ES version, + // reconcile every runtime-gated advertisement (see the comment on + // UpdateAdvertisedCapabilityExtensions for why this cannot happen when the list is first + // built). + UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities); UpdateDynamicBackendParameters(); PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities()); PrintFormatCapabilities(GetFormatCapabilities()); @@ -924,7 +926,9 @@ namespace MobileGL::MG_Backend::DirectGLES { return MutableRendererInfo(); } - Vector BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported) { + Vector BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported, + Bool drawIndirectSupported, + Bool nonZeroIndirectBaseInstanceSupported) { Vector extensions = { V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, 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, @@ -955,6 +959,19 @@ namespace MobileGL::MG_Backend::DirectGLES { // extension explicitly permits. It is also the only thing that // exposes glProgramParameteri before GL 4.1. E_GL_ARB_get_program_binary}; + // Minecraft 26.3 checks this prerequisite before it even considers + // GL_ARB_multi_draw_indirect. ES 3.1 supplies both single-draw entry points; the loader + // folds the version and pointer checks into SupportsDrawIndirect. + if (drawIndirectSupported) { + extensions.push_back(E_GL_ARB_draw_indirect); + } + // ARB_base_instance also defines the last word of an indirect command. Direct calls are + // emulated on every Espryt device, but without host GL_EXT_base_instance a native indirect + // draw cannot shift divisor attributes by a GPU-authored non-zero value, so do not promise + // that incomplete case. + if (drawIndirectSupported && nonZeroIndirectBaseInstanceSupported) { + extensions.push_back(E_GL_ARB_base_instance); + } // GL_KHR_parallel_shader_compile is MobileGL's own capability, not the host ES // driver's: the compiler threads are MobileGL's, and glCompileShader/glLinkProgram // are serviced entirely inside the frontend. Whether the device driver advertises diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h index e107ef72..8aa4fdba 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h @@ -67,9 +67,12 @@ namespace MobileGL::MG_Backend::DirectGLES { const RendererInfo& GetRendererIdentity(); // The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS)) - // for a device whose timer queries / anisotropic filtering are (or are not) usable. + // for a device whose timer queries / anisotropic filtering / native indirect draws / + // non-zero indirect baseInstance semantics are (or are not) usable. // The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside. - Vector BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported); + Vector BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported, + Bool drawIndirectSupported, + Bool nonZeroIndirectBaseInstanceSupported); // Format: , OpenGL ES . — the exact string an // initialized backend returns from GetBackendAPIVersionString (and that ends up diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 8c247a38..a7ebf22a 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -3061,10 +3061,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } static Bool SupportsNativeIndirectDraws() { - const auto& version = g_GLESCapabilities.GLESVersion; - const Bool esVersionOk = version.Major > 3 || (version.Major == 3 && version.Minor >= 1); - return esVersionOk && g_GLESFuncs.glDrawElementsIndirect != nullptr && - g_GLESFuncs.glDrawArraysIndirect != nullptr; + return g_GLESCapabilities.SupportsDrawIndirect; } // Runs an (indexed) indirect multi-draw. When a GL_DRAW_INDIRECT_BUFFER is bound the draws diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index de4753d2..04e679fe 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -497,20 +497,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { .ExtraVendor = Nullopt, .RendererGLInfo = {.TargetGLVersion = {4, 0, 0}, .TargetGLSLVersion = {4, 6, 0}, - // Baseline advertisement (no shader subgroup, no timer queries); a - // live backend reconciles its copy in UpdateAdvertisedExtensions. - .Extensions = BuildAdvertisedExtensions(false, false, false), + // Baseline advertisement (no runtime-gated capabilities); a live + // backend reconciles its copy in UpdateAdvertisedExtensions. + .Extensions = BuildAdvertisedExtensions(false, false, false, false), .IsCompatibilityProfile = false}, .StaticBackendCapability = {.AllowVSOnlyPrograms = false}}; return rendererInfo; } Vector BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, - Bool anisotropicFilteringSupported) { + Bool anisotropicFilteringSupported, + Bool nonZeroIndirectBaseInstanceSupported) { Vector extensions = { V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, 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_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_ARB_draw_indirect, + 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_texture_storage_multisample, E_GL_ARB_texture_multisample, E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access, E_GL_ARB_shader_draw_parameters, @@ -530,6 +532,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { // extension explicitly permits. It is also the only thing that // exposes glProgramParameteri before GL 4.1. E_GL_ARB_get_program_binary}; + // Vulkan's drawIndirectFirstInstance feature is optional. Direct base-instance calls work + // without it, but ARB_base_instance also promises non-zero firstInstance in GPU indirect + // commands; the renderer supplies true only when that word is legal and gl_InstanceID can + // be rebased to OpenGL's zero-based semantics. + if (nonZeroIndirectBaseInstanceSupported) { + extensions.push_back(E_GL_ARB_base_instance); + } if (shaderSubgroupSupported && !MG_Config::Features.DisableSubgroup) { extensions.push_back(E_GL_KHR_shader_subgroup); } @@ -690,7 +699,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // the whole list keeps re-runs idempotent. m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions( m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(), - pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported()); + pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported(), + pVulkanRenderer && pVulkanRenderer->IsNonZeroIndirectBaseInstanceSupported()); } void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() { diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h index 8f81f6bc..c64d57cb 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h @@ -62,8 +62,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // POST screen shows. // Static identity of the Magma renderer (renderer/backend names, target GL/GLSL - // versions, ExtraVendor) with the baseline extension advertisement (no shader - // subgroup, no timer queries). A live backend copies this in its constructor and + // versions, ExtraVendor) with the baseline extension advertisement (no runtime-gated + // capabilities). A live backend copies this in its constructor and // reconciles the Extensions in UpdateAdvertisedExtensions once real capabilities // exist; callers that need the advertised list for a known capability set must // use BuildAdvertisedExtensions instead. @@ -74,7 +74,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass // the detected device support (passing an already-gated value is harmless). Vector BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, - Bool anisotropicFilteringSupported); + Bool anisotropicFilteringSupported, + Bool nonZeroIndirectBaseInstanceSupported); // Format: , Vulkan , Driver — the exact // string an initialized backend returns from GetBackendAPIVersionString (and that diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index b8843d6b..c2593185 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -310,6 +310,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { // The samplerAnisotropy device feature was granted, so GL_TEXTURE_MAX_ANISOTROPY_EXT is // honored rather than accepted-and-ignored. Bool IsSamplerAnisotropySupported() const { return m_samplerAnisotropyFeatureEnabled; } + // ARB_base_instance extends indirect command records with a non-zero firstInstance and + // requires gl_InstanceID to remain zero-based. Vulkan needs both features to honor that + // complete contract: one legalizes the command word, the other enables the shader rebase. + Bool IsNonZeroIndirectBaseInstanceSupported() const { + return m_drawIndirectFirstInstanceFeatureEnabled && m_shaderDrawParametersFeatureEnabled; + } // Ensures the frame command buffer is recording (same lazy pattern as // SetupDraw) and writes a bottom-of-pipe timestamp into the current // frame's pool. Null when unsupported or the pool is exhausted. diff --git a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp index 8ddcc142..cbd801db 100644 --- a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp +++ b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp @@ -725,22 +725,57 @@ TEST(TextureAnisotropyCapabilities, ExtensionIsAdvertisedOnlyWhenTheHostDriverSu return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end(); }; - const auto without = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false); + const auto without = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false); EXPECT_FALSE(contains(without, MobileGL::E_GL_EXT_texture_filter_anisotropic)); EXPECT_FALSE(contains(without, MobileGL::E_GL_ARB_texture_filter_anisotropic)); - const auto with = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, true); + const auto with = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, true, false, false); EXPECT_TRUE(contains(with, MobileGL::E_GL_EXT_texture_filter_anisotropic)); EXPECT_TRUE(contains(with, MobileGL::E_GL_ARB_texture_filter_anisotropic)); // Same rule on the Vulkan backend, where the gate is the samplerAnisotropy device feature. - const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false); + const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false); EXPECT_FALSE(contains(vkWithout, MobileGL::E_GL_EXT_texture_filter_anisotropic)); - const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, true); + const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, true, false); EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_EXT_texture_filter_anisotropic)); EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_ARB_texture_filter_anisotropic)); } +// Minecraft 26.3 checks ARB_draw_indirect before it considers the already-advertised +// ARB_multi_draw_indirect, then separately requires ARB_base_instance before enabling its terrain +// indirect path. Pin both strings and, just as importantly, the non-zero firstInstance gate. +TEST(IndirectDrawAdvertisement, MatchesEachBackendsUsableCommandSemantics) { + const auto contains = [](const MobileGL::Vector& extensions, + MobileGL::GLExtension wanted) { + return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end(); + }; + + const auto esWithoutIndirect = + MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false); + EXPECT_FALSE(contains(esWithoutIndirect, MobileGL::E_GL_ARB_draw_indirect)); + EXPECT_FALSE(contains(esWithoutIndirect, MobileGL::E_GL_ARB_base_instance)); + + const auto esWithoutBaseInstance = + MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, true, false); + EXPECT_TRUE(contains(esWithoutBaseInstance, MobileGL::E_GL_ARB_draw_indirect)); + EXPECT_FALSE(contains(esWithoutBaseInstance, MobileGL::E_GL_ARB_base_instance)); + + const auto esWithBoth = + MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, true, true); + EXPECT_TRUE(contains(esWithBoth, MobileGL::E_GL_ARB_draw_indirect)); + EXPECT_TRUE(contains(esWithBoth, MobileGL::E_GL_ARB_base_instance)); + + const auto vkWithoutBaseInstance = + MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false); + EXPECT_TRUE(contains(vkWithoutBaseInstance, MobileGL::E_GL_ARB_draw_indirect)); + EXPECT_FALSE(contains(vkWithoutBaseInstance, MobileGL::E_GL_ARB_base_instance)); + + const auto vkWithBoth = + MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, true); + EXPECT_TRUE(contains(vkWithBoth, MobileGL::E_GL_ARB_draw_indirect)); + EXPECT_TRUE(contains(vkWithBoth, MobileGL::E_GL_ARB_base_instance)); +} + TEST(TextureAnisotropyCapabilities, MaxAnisotropyIsQueriedOnlyWhenTheExtensionIsPresent) { ResetFakeDriver(); g_fake.maxVertexSsboBlocks = 0; @@ -836,3 +871,63 @@ TEST(MultiDrawCapabilities, ExtensionWithoutResolvedPointerIsNotSupport) { EXPECT_FALSE(caps.SupportsMultiDrawIndirect); EXPECT_FALSE(caps.SupportsMultiDrawElementsBaseVertex); } + +TEST(DrawIndirectCapabilities, RequiresEs31AndBothCoreEntryPoints) { + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + auto funcs = MakeFakeGLESFunctions(); + funcs.glDrawElementsIndirect = [](GLenum, GLenum, const void*) {}; + + MobileGL::MG_External::GLESCapabilities supportedCaps; + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(supportedCaps, funcs)); + EXPECT_TRUE(supportedCaps.SupportsDrawIndirect); + + // The same pointers on an ES 3.0 context are not core entry points and cannot back the + // desktop extension contract. + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + g_fake.glesMinorVersion = 0; + MobileGL::MG_External::GLESCapabilities es30Caps; + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(es30Caps, funcs)); + EXPECT_FALSE(es30Caps.SupportsDrawIndirect); + + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + const auto missingElements = MakeFakeGLESFunctions(); + MobileGL::MG_External::GLESCapabilities missingEntryPointCaps; + ASSERT_TRUE( + MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(missingEntryPointCaps, missingElements)); + EXPECT_FALSE(missingEntryPointCaps.SupportsDrawIndirect); +} + +TEST(BaseInstanceCapabilities, RequiresTheExtensionAndAllThreeEntryPoints) { + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + auto funcs = MakeFakeGLESFunctions(); + funcs.glDrawArraysInstancedBaseInstanceEXT = [](GLenum, GLint, GLsizei, GLsizei, GLuint) {}; + funcs.glDrawElementsInstancedBaseInstanceEXT = + [](GLenum, GLsizei, GLenum, const void*, GLsizei, GLuint) {}; + funcs.glDrawElementsInstancedBaseVertexBaseInstanceEXT = + [](GLenum, GLsizei, GLenum, const void*, GLsizei, GLint, GLuint) {}; + + // Resolved stubs alone must never make the capability true. + MobileGL::MG_External::GLESCapabilities pointersOnlyCaps; + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(pointersOnlyCaps, funcs)); + EXPECT_FALSE(pointersOnlyCaps.SupportsBaseInstance); + + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + g_fake.extensions.emplace_back("GL_EXT_base_instance"); + MobileGL::MG_External::GLESCapabilities supportedCaps; + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(supportedCaps, funcs)); + EXPECT_TRUE(supportedCaps.SupportsBaseInstance); + + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + g_fake.extensions.emplace_back("GL_EXT_base_instance"); + funcs.glDrawElementsInstancedBaseInstanceEXT = nullptr; + MobileGL::MG_External::GLESCapabilities missingEntryPointCaps; + ASSERT_TRUE( + MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(missingEntryPointCaps, funcs)); + EXPECT_FALSE(missingEntryPointCaps.SupportsBaseInstance); +} diff --git a/MobileGL/MG_Test/Program/ParallelShaderCompileTest.cpp b/MobileGL/MG_Test/Program/ParallelShaderCompileTest.cpp index f3410593..94a7d0e8 100644 --- a/MobileGL/MG_Test/Program/ParallelShaderCompileTest.cpp +++ b/MobileGL/MG_Test/Program/ParallelShaderCompileTest.cpp @@ -516,17 +516,17 @@ TEST_F(ParallelShaderCompileTest, MaxShaderCompilerThreadsIgnoresTheCurrentBudge TEST_F(ParallelShaderCompileTest, BothBackendsAdvertiseTheExtensionIffAsyncIsEnabled) { { const AsyncModeScope async(true); - EXPECT_TRUE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false), + EXPECT_TRUE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false), E_GL_KHR_parallel_shader_compile)); - EXPECT_TRUE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false), + EXPECT_TRUE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false), E_GL_KHR_parallel_shader_compile)); } { const AsyncModeScope async(false); - EXPECT_FALSE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false), + EXPECT_FALSE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false), E_GL_KHR_parallel_shader_compile)) << "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading"; - EXPECT_FALSE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false), + EXPECT_FALSE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false), E_GL_KHR_parallel_shader_compile)) << "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading"; } diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 9e28dca1..7f0c8a23 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -955,6 +955,8 @@ namespace MobileGL::MG_Util::BackendLoader { (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2); const Bool esAtLeast31 = caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 1); + caps.SupportsDrawIndirect = esAtLeast31 && glesFuncs.glDrawArraysIndirect != nullptr && + glesFuncs.glDrawElementsIndirect != nullptr; caps.SupportsDrawElementsBaseVertex = (esAtLeast32 || hasDrawElementsBaseVertexExtension) && glesFuncs.glDrawElementsBaseVertex != nullptr; caps.SupportsComputeShader = esAtLeast31 && glesFuncs.glDispatchCompute != nullptr && @@ -976,6 +978,7 @@ namespace MobileGL::MG_Util::BackendLoader { MGLOG_I(" indexed glColorMaski: %s", caps.SupportsIndexedColorMask ? "yes" : "no"); MGLOG_I(" dual-source blend (EXT_blend_func_extended): %s", caps.SupportsDualSourceBlend ? "yes" : "no"); + MGLOG_I(" draw indirect (ES 3.1 core): %s", caps.SupportsDrawIndirect ? "yes" : "no"); MGLOG_I(" multi-draw indirect (EXT_multi_draw_indirect): %s", caps.SupportsMultiDrawIndirect ? "yes" : "no"); MGLOG_I(" multi-draw base vertex (EXT/OES_draw_elements_base_vertex + EXT_multi_draw_arrays): %s", diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index e69d0798..64f6cef2 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -1149,6 +1149,10 @@ namespace MobileGL { // GLES 3.2 core or GL_OES_shader_multisample_interpolation exposes // interpolateAtOffset and the three fragment-offset limit queries. Bool SupportsShaderMultisampleInterpolation = false; + // ES 3.1+ exposes glDrawArraysIndirect / glDrawElementsIndirect in core. Keep the + // version and both entry-point checks together so extension advertisement and the + // DirectGLES dispatch path cannot disagree on whether native indirect draws exist. + Bool SupportsDrawIndirect = false; // GL_EXT_multi_draw_indirect is present AND glMultiDrawArraysIndirectEXT / // glMultiDrawElementsIndirectEXT both resolved. Multi-draw is not core in any ES // version, and eglGetProcAddress may return a live-looking stub on drivers without diff --git a/MobileGL/MG_Util/SelfTest/DriverPost.cpp b/MobileGL/MG_Util/SelfTest/DriverPost.cpp index 4ca15d10..4aeeb2c0 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPost.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverPost.cpp @@ -1168,7 +1168,9 @@ namespace MobileGL::MG_Util::SelfTest { backendApiVersionString = MG_Backend::DirectGLES::FormatBackendAPIVersionString( summary.caps.GLESRendererString, summary.caps.GLESVersion.Major, summary.caps.GLESVersion.Minor); advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectGLES::BuildAdvertisedExtensions( - summary.caps.SupportsDisjointTimerQuery, summary.caps.SupportsTextureFilterAnisotropy)); + summary.caps.SupportsDisjointTimerQuery, summary.caps.SupportsTextureFilterAnisotropy, + summary.caps.SupportsDrawIndirect, + summary.caps.SupportsDrawIndirect && summary.caps.SupportsBaseInstance)); } AppendMobileGLReportedRows(builder, MG_Backend::DirectGLES::GetRendererIdentity(), backendApiVersionString, advertisedExtensions); @@ -1461,6 +1463,8 @@ namespace MobileGL::MG_Util::SelfTest { Bool shaderSubgroupUsable = false; Bool timerQueriesSupported = false; Bool samplerAnisotropySupported = false; + Bool drawIndirectFirstInstanceSupported = false; + Bool shaderDrawParametersSupported = false; }; } // namespace @@ -1744,6 +1748,7 @@ namespace MobileGL::MG_Util::SelfTest { VkPhysicalDeviceFeatures features{}; vkGetPhysicalDeviceFeaturesFn(physicalDevice, &features); summary.samplerAnisotropySupported = features.samplerAnisotropy == VK_TRUE; + summary.drawIndirectFirstInstanceSupported = features.drawIndirectFirstInstance == VK_TRUE; if (features.multiDrawIndirect == VK_TRUE) { builder.Pass("multiDrawIndirect", "indirect multi-draw batches run as single native commands"); } else { @@ -1910,6 +1915,7 @@ namespace MobileGL::MG_Util::SelfTest { builder.Warn("shaderDrawParameters", "unavailable; shaders using gl_DrawID/gl_BaseInstance will not work"); } + summary.shaderDrawParametersSupported = shaderDrawParameters; Bool provokingVertexLast = false; Bool transformFeedbackPreservesProvokingVertex = false; @@ -2108,7 +2114,8 @@ namespace MobileGL::MG_Util::SelfTest { backendApiVersionString = MG_Backend::DirectVulkan::FormatBackendAPIVersionString( summary.deviceName, summary.apiVersionString, summary.driverVersionString); advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectVulkan::BuildAdvertisedExtensions( - summary.shaderSubgroupUsable, summary.timerQueriesSupported, summary.samplerAnisotropySupported)); + summary.shaderSubgroupUsable, summary.timerQueriesSupported, summary.samplerAnisotropySupported, + summary.drawIndirectFirstInstanceSupported && summary.shaderDrawParametersSupported)); } AppendMobileGLReportedRows(builder, MG_Backend::DirectVulkan::GetRendererIdentity(), backendApiVersionString, advertisedExtensions);