[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
This commit is contained in:
2026-08-15 06:30:44 -04:00
parent a6e52476f3
commit 10ff5e2b18
11 changed files with 180 additions and 37 deletions
@@ -712,9 +712,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
{ {
.TargetGLVersion = {4, 0, 0}, // GL target version .TargetGLVersion = {4, 0, 0}, // GL target version
.TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version .TargetGLSLVersion = {4, 6, 0}, // Target Shading Language Version
// Baseline advertisement (no timer queries / anisotropy yet); reconciled // Baseline advertisement (no runtime capabilities yet); reconciled once
// once the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions. // the ES capabilities exist, see UpdateAdvertisedCapabilityExtensions.
.Extensions = BuildAdvertisedExtensions(false, false), .Extensions = BuildAdvertisedExtensions(false, false, false, false),
.IsCompatibilityProfile = false // Is Compatibility Profile .IsCompatibilityProfile = false // Is Compatibility Profile
}, },
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability .StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
@@ -734,9 +734,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// thread can only observe the extension string after the // thread can only observe the extension string after the
// advertisement for its context has settled; rebuilding the whole // advertisement for its context has settled; rebuilding the whole
// list keeps the re-run after a context recreation idempotent. // list keeps the re-run after a context recreation idempotent.
void UpdateAdvertisedCapabilityExtensions(Bool anisotropicFilteringSupported) { void UpdateAdvertisedCapabilityExtensions(const MG_External::GLESCapabilities& capabilities) {
MutableRendererInfo().RendererGLInfo.Extensions = MutableRendererInfo().RendererGLInfo.Extensions = BuildAdvertisedExtensions(
BuildAdvertisedExtensions(AreTimerQueriesSupported(), anisotropicFilteringSupported); AreTimerQueriesSupported(), capabilities.SupportsTextureFilterAnisotropy,
capabilities.SupportsDrawIndirect,
capabilities.SupportsDrawIndirect && capabilities.SupportsBaseInstance);
} }
} // namespace } // namespace
@@ -779,11 +781,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false; return false;
} }
DirectGLES::SetGLESCapabilities(m_GLESCapabilities); DirectGLES::SetGLESCapabilities(m_GLESCapabilities);
// Now that g_GLESCapabilities knows about GL_EXT_disjoint_timer_query and // Now that g_GLESCapabilities knows the host extensions, entry points, and ES version,
// GL_EXT_texture_filter_anisotropic, reconcile the advertisement (see the comment on // reconcile every runtime-gated advertisement (see the comment on
// UpdateAdvertisedCapabilityExtensions for why it cannot happen when the extension // UpdateAdvertisedCapabilityExtensions for why this cannot happen when the list is first
// list is first built). // built).
UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities.SupportsTextureFilterAnisotropy); UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities);
UpdateDynamicBackendParameters(); UpdateDynamicBackendParameters();
PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities()); PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities());
PrintFormatCapabilities(GetFormatCapabilities()); PrintFormatCapabilities(GetFormatCapabilities());
@@ -924,7 +926,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
return MutableRendererInfo(); return MutableRendererInfo();
} }
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported) { Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported,
Bool drawIndirectSupported,
Bool nonZeroIndirectBaseInstanceSupported) {
Vector<GLExtension> extensions = { Vector<GLExtension> extensions = {
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend, 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_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 // extension explicitly permits. It is also the only thing that
// exposes glProgramParameteri before GL 4.1. // exposes glProgramParameteri before GL 4.1.
E_GL_ARB_get_program_binary}; 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 // 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 // driver's: the compiler threads are MobileGL's, and glCompileShader/glLinkProgram
// are serviced entirely inside the frontend. Whether the device driver advertises // are serviced entirely inside the frontend. Whether the device driver advertises
@@ -67,9 +67,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
const RendererInfo& GetRendererIdentity(); const RendererInfo& GetRendererIdentity();
// The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS)) // 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. // The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside.
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported); Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported,
Bool drawIndirectSupported,
Bool nonZeroIndirectBaseInstanceSupported);
// Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an // Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an
// initialized backend returns from GetBackendAPIVersionString (and that ends up // initialized backend returns from GetBackendAPIVersionString (and that ends up
@@ -3061,10 +3061,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
static Bool SupportsNativeIndirectDraws() { static Bool SupportsNativeIndirectDraws() {
const auto& version = g_GLESCapabilities.GLESVersion; return g_GLESCapabilities.SupportsDrawIndirect;
const Bool esVersionOk = version.Major > 3 || (version.Major == 3 && version.Minor >= 1);
return esVersionOk && g_GLESFuncs.glDrawElementsIndirect != nullptr &&
g_GLESFuncs.glDrawArraysIndirect != nullptr;
} }
// Runs an (indexed) indirect multi-draw. When a GL_DRAW_INDIRECT_BUFFER is bound the draws // Runs an (indexed) indirect multi-draw. When a GL_DRAW_INDIRECT_BUFFER is bound the draws
@@ -497,20 +497,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.ExtraVendor = Nullopt, .ExtraVendor = Nullopt,
.RendererGLInfo = {.TargetGLVersion = {4, 0, 0}, .RendererGLInfo = {.TargetGLVersion = {4, 0, 0},
.TargetGLSLVersion = {4, 6, 0}, .TargetGLSLVersion = {4, 6, 0},
// Baseline advertisement (no shader subgroup, no timer queries); a // Baseline advertisement (no runtime-gated capabilities); a live
// live backend reconciles its copy in UpdateAdvertisedExtensions. // backend reconciles its copy in UpdateAdvertisedExtensions.
.Extensions = BuildAdvertisedExtensions(false, false, false), .Extensions = BuildAdvertisedExtensions(false, false, false, false),
.IsCompatibilityProfile = false}, .IsCompatibilityProfile = false},
.StaticBackendCapability = {.AllowVSOnlyPrograms = false}}; .StaticBackendCapability = {.AllowVSOnlyPrograms = false}};
return rendererInfo; return rendererInfo;
} }
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported) { Bool anisotropicFilteringSupported,
Bool nonZeroIndirectBaseInstanceSupported) {
Vector<GLExtension> extensions = { Vector<GLExtension> extensions = {
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, E_GL_ARB_draw_buffers_blend, 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_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_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_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, 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 // extension explicitly permits. It is also the only thing that
// exposes glProgramParameteri before GL 4.1. // exposes glProgramParameteri before GL 4.1.
E_GL_ARB_get_program_binary}; 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) { if (shaderSubgroupSupported && !MG_Config::Features.DisableSubgroup) {
extensions.push_back(E_GL_KHR_shader_subgroup); extensions.push_back(E_GL_KHR_shader_subgroup);
} }
@@ -690,7 +699,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the whole list keeps re-runs idempotent. // the whole list keeps re-runs idempotent.
m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions( m_rendererInfo.RendererGLInfo.Extensions = BuildAdvertisedExtensions(
m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(), m_vulkanCaps.SupportsShaderSubgroup, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(),
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported()); pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported(),
pVulkanRenderer && pVulkanRenderer->IsNonZeroIndirectBaseInstanceSupported());
} }
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() { void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
@@ -62,8 +62,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// POST screen shows. // POST screen shows.
// Static identity of the Magma renderer (renderer/backend names, target GL/GLSL // Static identity of the Magma renderer (renderer/backend names, target GL/GLSL
// versions, ExtraVendor) with the baseline extension advertisement (no shader // versions, ExtraVendor) with the baseline extension advertisement (no runtime-gated
// subgroup, no timer queries). A live backend copies this in its constructor and // capabilities). A live backend copies this in its constructor and
// reconciles the Extensions in UpdateAdvertisedExtensions once real capabilities // reconciles the Extensions in UpdateAdvertisedExtensions once real capabilities
// exist; callers that need the advertised list for a known capability set must // exist; callers that need the advertised list for a known capability set must
// use BuildAdvertisedExtensions instead. // use BuildAdvertisedExtensions instead.
@@ -74,7 +74,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass // MOBILEGL_DISABLE_TIMERQUERY escape hatches are applied inside, so callers pass
// the detected device support (passing an already-gated value is harmless). // the detected device support (passing an already-gated value is harmless).
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported); Bool anisotropicFilteringSupported,
Bool nonZeroIndirectBaseInstanceSupported);
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact // Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
// string an initialized backend returns from GetBackendAPIVersionString (and that // string an initialized backend returns from GetBackendAPIVersionString (and that
@@ -310,6 +310,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The samplerAnisotropy device feature was granted, so GL_TEXTURE_MAX_ANISOTROPY_EXT is // The samplerAnisotropy device feature was granted, so GL_TEXTURE_MAX_ANISOTROPY_EXT is
// honored rather than accepted-and-ignored. // honored rather than accepted-and-ignored.
Bool IsSamplerAnisotropySupported() const { return m_samplerAnisotropyFeatureEnabled; } 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 // Ensures the frame command buffer is recording (same lazy pattern as
// SetupDraw) and writes a bottom-of-pipe timestamp into the current // SetupDraw) and writes a bottom-of-pipe timestamp into the current
// frame's pool. Null when unsupported or the pool is exhausted. // frame's pool. Null when unsupported or the pool is exhausted.
@@ -725,22 +725,57 @@ TEST(TextureAnisotropyCapabilities, ExtensionIsAdvertisedOnlyWhenTheHostDriverSu
return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end(); 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_EXT_texture_filter_anisotropic));
EXPECT_FALSE(contains(without, MobileGL::E_GL_ARB_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_EXT_texture_filter_anisotropic));
EXPECT_TRUE(contains(with, MobileGL::E_GL_ARB_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. // 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)); 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_EXT_texture_filter_anisotropic));
EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_ARB_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<MobileGL::GLExtension>& 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) { TEST(TextureAnisotropyCapabilities, MaxAnisotropyIsQueriedOnlyWhenTheExtensionIsPresent) {
ResetFakeDriver(); ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0; g_fake.maxVertexSsboBlocks = 0;
@@ -836,3 +871,63 @@ TEST(MultiDrawCapabilities, ExtensionWithoutResolvedPointerIsNotSupport) {
EXPECT_FALSE(caps.SupportsMultiDrawIndirect); EXPECT_FALSE(caps.SupportsMultiDrawIndirect);
EXPECT_FALSE(caps.SupportsMultiDrawElementsBaseVertex); 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);
}
@@ -516,17 +516,17 @@ TEST_F(ParallelShaderCompileTest, MaxShaderCompilerThreadsIgnoresTheCurrentBudge
TEST_F(ParallelShaderCompileTest, BothBackendsAdvertiseTheExtensionIffAsyncIsEnabled) { TEST_F(ParallelShaderCompileTest, BothBackendsAdvertiseTheExtensionIffAsyncIsEnabled) {
{ {
const AsyncModeScope async(true); 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)); 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)); E_GL_KHR_parallel_shader_compile));
} }
{ {
const AsyncModeScope async(false); 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)) E_GL_KHR_parallel_shader_compile))
<< "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading"; << "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)) E_GL_KHR_parallel_shader_compile))
<< "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading"; << "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading";
} }
@@ -955,6 +955,8 @@ namespace MobileGL::MG_Util::BackendLoader {
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2); (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2);
const Bool esAtLeast31 = caps.GLESVersion.Major > 3 || const Bool esAtLeast31 = caps.GLESVersion.Major > 3 ||
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 1); (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 1);
caps.SupportsDrawIndirect = esAtLeast31 && glesFuncs.glDrawArraysIndirect != nullptr &&
glesFuncs.glDrawElementsIndirect != nullptr;
caps.SupportsDrawElementsBaseVertex = (esAtLeast32 || hasDrawElementsBaseVertexExtension) && caps.SupportsDrawElementsBaseVertex = (esAtLeast32 || hasDrawElementsBaseVertexExtension) &&
glesFuncs.glDrawElementsBaseVertex != nullptr; glesFuncs.glDrawElementsBaseVertex != nullptr;
caps.SupportsComputeShader = esAtLeast31 && glesFuncs.glDispatchCompute != 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(" indexed glColorMaski: %s", caps.SupportsIndexedColorMask ? "yes" : "no");
MGLOG_I(" dual-source blend (EXT_blend_func_extended): %s", MGLOG_I(" dual-source blend (EXT_blend_func_extended): %s",
caps.SupportsDualSourceBlend ? "yes" : "no"); 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", MGLOG_I(" multi-draw indirect (EXT_multi_draw_indirect): %s",
caps.SupportsMultiDrawIndirect ? "yes" : "no"); caps.SupportsMultiDrawIndirect ? "yes" : "no");
MGLOG_I(" multi-draw base vertex (EXT/OES_draw_elements_base_vertex + EXT_multi_draw_arrays): %s", MGLOG_I(" multi-draw base vertex (EXT/OES_draw_elements_base_vertex + EXT_multi_draw_arrays): %s",
@@ -1149,6 +1149,10 @@ namespace MobileGL {
// GLES 3.2 core or GL_OES_shader_multisample_interpolation exposes // GLES 3.2 core or GL_OES_shader_multisample_interpolation exposes
// interpolateAtOffset and the three fragment-offset limit queries. // interpolateAtOffset and the three fragment-offset limit queries.
Bool SupportsShaderMultisampleInterpolation = false; 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 / // GL_EXT_multi_draw_indirect is present AND glMultiDrawArraysIndirectEXT /
// glMultiDrawElementsIndirectEXT both resolved. Multi-draw is not core in any ES // glMultiDrawElementsIndirectEXT both resolved. Multi-draw is not core in any ES
// version, and eglGetProcAddress may return a live-looking stub on drivers without // version, and eglGetProcAddress may return a live-looking stub on drivers without
+9 -2
View File
@@ -1168,7 +1168,9 @@ namespace MobileGL::MG_Util::SelfTest {
backendApiVersionString = MG_Backend::DirectGLES::FormatBackendAPIVersionString( backendApiVersionString = MG_Backend::DirectGLES::FormatBackendAPIVersionString(
summary.caps.GLESRendererString, summary.caps.GLESVersion.Major, summary.caps.GLESVersion.Minor); summary.caps.GLESRendererString, summary.caps.GLESVersion.Major, summary.caps.GLESVersion.Minor);
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectGLES::BuildAdvertisedExtensions( 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, AppendMobileGLReportedRows(builder, MG_Backend::DirectGLES::GetRendererIdentity(), backendApiVersionString,
advertisedExtensions); advertisedExtensions);
@@ -1461,6 +1463,8 @@ namespace MobileGL::MG_Util::SelfTest {
Bool shaderSubgroupUsable = false; Bool shaderSubgroupUsable = false;
Bool timerQueriesSupported = false; Bool timerQueriesSupported = false;
Bool samplerAnisotropySupported = false; Bool samplerAnisotropySupported = false;
Bool drawIndirectFirstInstanceSupported = false;
Bool shaderDrawParametersSupported = false;
}; };
} // namespace } // namespace
@@ -1744,6 +1748,7 @@ namespace MobileGL::MG_Util::SelfTest {
VkPhysicalDeviceFeatures features{}; VkPhysicalDeviceFeatures features{};
vkGetPhysicalDeviceFeaturesFn(physicalDevice, &features); vkGetPhysicalDeviceFeaturesFn(physicalDevice, &features);
summary.samplerAnisotropySupported = features.samplerAnisotropy == VK_TRUE; summary.samplerAnisotropySupported = features.samplerAnisotropy == VK_TRUE;
summary.drawIndirectFirstInstanceSupported = features.drawIndirectFirstInstance == VK_TRUE;
if (features.multiDrawIndirect == VK_TRUE) { if (features.multiDrawIndirect == VK_TRUE) {
builder.Pass("multiDrawIndirect", "indirect multi-draw batches run as single native commands"); builder.Pass("multiDrawIndirect", "indirect multi-draw batches run as single native commands");
} else { } else {
@@ -1910,6 +1915,7 @@ namespace MobileGL::MG_Util::SelfTest {
builder.Warn("shaderDrawParameters", builder.Warn("shaderDrawParameters",
"unavailable; shaders using gl_DrawID/gl_BaseInstance will not work"); "unavailable; shaders using gl_DrawID/gl_BaseInstance will not work");
} }
summary.shaderDrawParametersSupported = shaderDrawParameters;
Bool provokingVertexLast = false; Bool provokingVertexLast = false;
Bool transformFeedbackPreservesProvokingVertex = false; Bool transformFeedbackPreservesProvokingVertex = false;
@@ -2108,7 +2114,8 @@ namespace MobileGL::MG_Util::SelfTest {
backendApiVersionString = MG_Backend::DirectVulkan::FormatBackendAPIVersionString( backendApiVersionString = MG_Backend::DirectVulkan::FormatBackendAPIVersionString(
summary.deviceName, summary.apiVersionString, summary.driverVersionString); summary.deviceName, summary.apiVersionString, summary.driverVersionString);
advertisedExtensions = JoinAdvertisedExtensions(MG_Backend::DirectVulkan::BuildAdvertisedExtensions( 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, AppendMobileGLReportedRows(builder, MG_Backend::DirectVulkan::GetRendererIdentity(), backendApiVersionString,
advertisedExtensions); advertisedExtensions);