[Feat] (GL): advertise the 4.x-core extension batch and ARB_tessellation_shader where the device has tessellation

This commit is contained in:
2026-10-12 08:09:24 -04:00
parent 2eae028938
commit 4c9ce56124
11 changed files with 208 additions and 8 deletions
+9
View File
@@ -309,6 +309,15 @@ namespace MobileGL {
void (*SetSwapInterval)(Int interval);
};
// Device features that decide whether an extension string may be advertised, beyond the
// per-backend positional flags of BuildAdvertisedExtensions. Each is the PROBED driver
// capability (an ES version / extension on DirectGLES, a VkPhysicalDeviceFeatures bit on
// DirectVulkan), never a vendor guess. Defaults describe the no-device case.
struct AdvertisedFeatureSupport {
// ES 3.2 or GL_EXT_tessellation_shader; VkPhysicalDeviceFeatures::tessellationShader.
Bool TessellationShader = false;
};
// Coarse GPU vendor identity for gating device-specific quirks. Detected from the
// Vulkan physical-device vendorID or the GLES GL_VENDOR/GL_RENDERER strings; stays
// Unknown when detection is inconclusive, in which case auto-gated quirks stay off.
@@ -841,7 +841,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
AreTimerQueriesSupported(), capabilities.SupportsTextureFilterAnisotropy,
capabilities.SupportsDrawIndirect,
capabilities.SupportsDrawIndirect && capabilities.SupportsBaseInstance,
capabilities.SupportsTextureView, capabilities.SupportsTextureCubeMapArray);
capabilities.SupportsTextureView, capabilities.SupportsTextureCubeMapArray,
AdvertisedFeatureSupport{.TessellationShader = capabilities.SupportsTessellationShader});
// Exactly what InitCapabilities forwards into the dynamic parameters: the base
// extension and _primitive when the driver has them, _attachment not yet.
auto& extensions = MutableRendererInfo().RendererGLInfo.Extensions;
@@ -1383,7 +1384,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported,
Bool drawIndirectSupported,
Bool nonZeroIndirectBaseInstanceSupported,
Bool textureViewSupported, Bool cubeMapArraySupported) {
Bool textureViewSupported, Bool cubeMapArraySupported,
const AdvertisedFeatureSupport& features) {
Vector<GLExtension> extensions = {
// The version tokens have to reach the version the backend actually claims:
// TargetGLVersion is {4,6,0}, and a list that stopped at OpenGL40 told an
@@ -1494,10 +1496,33 @@ namespace MobileGL::MG_Backend::DirectGLES {
// glDepthRangeArrayv / glDepthRangeIndexed / glGetFloati_v / glGetDoublei_v, over the
// 16 viewports GL_MAX_VIEWPORTS reports and the per-viewport routing emulation.
E_GL_ARB_viewport_array,
// Core in the 4.x versions this backend already claims, and named so a context of an
// older requested version reaches the same, already-implemented surface (the conformance
// suite gates its GL 3.x runs of these on the string alone):
// - transform feedback objects, pause/resume and glDrawTransformFeedback (4.0);
E_GL_ARB_transform_feedback2,
// - multiple capture buffers per stream, gl_SkipComponents / gl_NextBuffer, the
// indexed queries and glDrawTransformFeedbackStream (4.0);
E_GL_ARB_transform_feedback3,
// - GL_RGB10_A2UI (3.3), GL_RGB9_E5 (3.0) and the integer colour formats (3.0);
E_GL_ARB_texture_rgb10_a2ui, E_GL_EXT_texture_shared_exponent, E_GL_EXT_texture_integer,
// - the eleven pipeline-statistics query targets (4.6). Accepted by glBeginQuery and
// answered with GL_QUERY_COUNTER_BITS = 0, the spec's own "this counter is not
// instrumented" signal (see IsPipelineStatisticsQueryTarget in GL_Query.cpp);
E_GL_ARB_pipeline_statistics_query,
// - mix() over integer and boolean vectors in GLSL below 4.50. The frontend compiles it
// to OpSelect, which the backend prints back as a core boolean mix.
E_GL_EXT_shader_integer_mix,
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
// extension explicitly permits. It is also the only thing that
// exposes glProgramParameteri before GL 4.1.
E_GL_ARB_get_program_binary};
// Tessellation is core from GL 4.0, but on the host it is ES 3.2 or
// GL_EXT_tessellation_shader, so the string follows the driver. GL_Query also reads it to
// open the two tessellation pipeline-statistics targets.
if (features.TessellationShader) {
extensions.push_back(E_GL_ARB_tessellation_shader);
}
// 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.
@@ -114,12 +114,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The full OpenGL extension list Espryt advertises (glGetString(GL_EXTENSIONS))
// for a device whose timer queries / anisotropic filtering / native indirect draws /
// non-zero indirect baseInstance semantics / EXT-OES texture views are (or are not) usable.
// The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied inside.
// non-zero indirect baseInstance semantics / EXT-OES texture views are (or are not) usable,
// plus the feature set in `features`. The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied
// inside.
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported,
Bool drawIndirectSupported,
Bool nonZeroIndirectBaseInstanceSupported,
Bool textureViewSupported, Bool cubeMapArraySupported);
Bool textureViewSupported, Bool cubeMapArraySupported,
const AdvertisedFeatureSupport& features = {});
// Format: <OpenGL ES Renderer>, OpenGL ES <Major>.<Minor> — the exact string an
// initialized backend returns from GetBackendAPIVersionString (and that ends up
@@ -792,7 +792,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported,
Bool nonZeroIndirectBaseInstanceSupported,
Bool cubeMapArraySupported) {
Bool cubeMapArraySupported,
const AdvertisedFeatureSupport& features) {
Vector<GLExtension> extensions = {
// The version tokens have to reach the version the backend actually claims:
// TargetGLVersion is {4,6,0}, and a list that stopped at OpenGL40 told an
@@ -898,10 +899,32 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// glDepthRangeArrayv / glDepthRangeIndexed / glGetFloati_v / glGetDoublei_v, over the
// 16 viewports GL_MAX_VIEWPORTS reports.
E_GL_ARB_viewport_array,
// Core in the 4.x versions this backend already claims, and named so a context of an
// older requested version reaches the same, already-implemented surface (the conformance
// suite gates its GL 3.x runs of these on the string alone):
// - transform feedback objects, pause/resume and glDrawTransformFeedback (4.0);
E_GL_ARB_transform_feedback2,
// - multiple capture buffers per stream, gl_SkipComponents / gl_NextBuffer, the
// indexed queries and glDrawTransformFeedbackStream (4.0);
E_GL_ARB_transform_feedback3,
// - GL_RGB10_A2UI (3.3), GL_RGB9_E5 (3.0) and the integer colour formats (3.0);
E_GL_ARB_texture_rgb10_a2ui, E_GL_EXT_texture_shared_exponent, E_GL_EXT_texture_integer,
// - the eleven pipeline-statistics query targets (4.6). Accepted by glBeginQuery and
// answered with GL_QUERY_COUNTER_BITS = 0, the spec's own "this counter is not
// instrumented" signal (see IsPipelineStatisticsQueryTarget in GL_Query.cpp);
E_GL_ARB_pipeline_statistics_query,
// - mix() over integer and boolean vectors in GLSL below 4.50. The frontend compiles it
// to OpSelect, which SPIR-V consumes directly.
E_GL_EXT_shader_integer_mix,
// Advertised with GL_NUM_PROGRAM_BINARY_FORMATS = 0, which the
// extension explicitly permits. It is also the only thing that
// exposes glProgramParameteri before GL 4.1.
E_GL_ARB_get_program_binary};
// Follows the tessellationShader device feature; GL_Query also reads the string to open
// the two tessellation pipeline-statistics targets.
if (features.TessellationShader) {
extensions.push_back(E_GL_ARB_tessellation_shader);
}
// 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
@@ -1082,7 +1105,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
subgroupSupportAdvertised, pVulkanRenderer && pVulkanRenderer->IsTimerQuerySupported(),
pVulkanRenderer && pVulkanRenderer->IsSamplerAnisotropySupported(),
pVulkanRenderer && pVulkanRenderer->IsNonZeroIndirectBaseInstanceSupported(),
m_vulkanCaps.SupportsImageCubeArray);
m_vulkanCaps.SupportsImageCubeArray,
AdvertisedFeatureSupport{.TessellationShader = m_vulkanCaps.SupportsTessellationShader});
// GL_EXT_fragment_shading_rate rides on VK_KHR_fragment_shading_rate's pipeline rate, and
// its _primitive companion on the primitive rate; both only when the renderer enabled them.
// _attachment is not advertised: no framebuffer carries a rate attachment to this backend.
@@ -115,7 +115,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported,
Bool nonZeroIndirectBaseInstanceSupported,
Bool cubeMapArraySupported);
Bool cubeMapArraySupported,
const AdvertisedFeatureSupport& features = {});
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
// string an initialized backend returns from GetBackendAPIVersionString (and that
@@ -1244,6 +1244,78 @@ TEST(CorePlumbingAdvertisement, IsUnconditionalAndIdenticalOnBothBackends) {
}
}
// Strings for functionality that is core in the 4.x versions MobileGL claims, named so contexts of
// an older requested version (the conformance suite's GL 3.x runs gate on the string alone) reach
// the same surface. Unconditional on both backends; only the tessellation string follows a probed
// device capability.
TEST(CoreSurfaceAdvertisement, BatchIsUnconditionalAndTessellationFollowsTheDevice) {
const auto contains = [](const MobileGL::Vector<MobileGL::GLExtension>& extensions,
MobileGL::GLExtension wanted) {
return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end();
};
const MobileGL::GLExtension expected[] = {
MobileGL::E_GL_ARB_transform_feedback2, MobileGL::E_GL_ARB_transform_feedback3,
MobileGL::E_GL_ARB_texture_rgb10_a2ui, MobileGL::E_GL_EXT_texture_shared_exponent,
MobileGL::E_GL_EXT_texture_integer, MobileGL::E_GL_ARB_pipeline_statistics_query,
MobileGL::E_GL_EXT_shader_integer_mix};
const auto es = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false,
false);
const auto vk = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false);
for (const auto extension : expected) {
EXPECT_TRUE(contains(es, extension)) << "DirectGLES does not advertise extension " << extension;
EXPECT_TRUE(contains(vk, extension)) << "DirectVulkan does not advertise extension " << extension;
}
// Withheld until shaders can actually use the gl_FragCoord layout qualifiers it adds.
EXPECT_FALSE(contains(es, MobileGL::E_GL_ARB_fragment_coord_conventions));
EXPECT_FALSE(contains(vk, MobileGL::E_GL_ARB_fragment_coord_conventions));
EXPECT_FALSE(contains(es, MobileGL::E_GL_ARB_tessellation_shader));
EXPECT_FALSE(contains(vk, MobileGL::E_GL_ARB_tessellation_shader));
const MobileGL::MG_Backend::AdvertisedFeatureSupport tessellation{.TessellationShader = true};
EXPECT_TRUE(contains(MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false,
false, false, tessellation),
MobileGL::E_GL_ARB_tessellation_shader));
EXPECT_TRUE(contains(MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false,
false, tessellation),
MobileGL::E_GL_ARB_tessellation_shader));
}
// ES 3.2 has tessellation in core; ES 3.1 only through GL_EXT_tessellation_shader. Either way the
// patch-size entry point has to have resolved.
TEST(TessellationCapabilities, FollowTheVersionOrTheExtension) {
const auto funcs = MakeFakeGLESFunctions();
auto withPatch = funcs;
withPatch.glPatchParameteri = [](GLenum, GLint) {};
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
MobileGL::MG_External::GLESCapabilities es31;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(es31, withPatch));
EXPECT_FALSE(es31.SupportsTessellationShader);
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
g_fake.extensions.emplace_back("GL_EXT_tessellation_shader");
MobileGL::MG_External::GLESCapabilities es31Ext;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(es31Ext, withPatch));
EXPECT_TRUE(es31Ext.SupportsTessellationShader);
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
g_fake.glesMinorVersion = 2;
MobileGL::MG_External::GLESCapabilities es32NoEntryPoint;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(es32NoEntryPoint, funcs));
EXPECT_FALSE(es32NoEntryPoint.SupportsTessellationShader);
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
g_fake.glesMinorVersion = 2;
MobileGL::MG_External::GLESCapabilities es32;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(es32, withPatch));
EXPECT_TRUE(es32.SupportsTessellationShader);
}
// 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.
+51
View File
@@ -1571,6 +1571,57 @@ void main() { mgColor = vec4(float(gl_MaxCullDistances)); }
MG_State::pGLContext = Move(previousContext);
}
// GL_EXT_shader_integer_mix (KHR-GL4x.shader_integer_mix.*): a 3.30 shader that requires the
// extension must see its macro and must be able to mix integer and boolean vectors under a
// boolean selector. Both backends carry the result: SPIR-V lowers the call to OpSelect, which
// ESSL 3.10+ and GLSL 4.50 print back as a core boolean mix.
TEST(ShaderCompilerSanity, ExtShaderIntegerMixIsUsableAt330) {
using namespace MobileGL;
auto previousContext = MG_State::pGLContext.Take();
auto previousBackend = Move(MG_Backend::pActiveBackendObject);
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
MG_Backend::pActiveBackendObject = MakeUnique<DynamicParameterBackend>(MG_Backend::DynamicBackendParameters{});
const auto env = MG_Util::ShaderTranspiler::CaptureCompileEnv();
const auto compile = [&env](GLenum stage, const String& source) {
return MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({
.shaderType = stage,
.sourceStr = source,
.env = env.get(),
});
};
const String defineProbe = R"(#version 330
#extension GL_EXT_shader_integer_mix: require
#if !defined GL_EXT_shader_integer_mix
# error GL_EXT_shader_integer_mix is not defined
#elif GL_EXT_shader_integer_mix != 1
# error GL_EXT_shader_integer_mix is not equal to 1
#endif
void main(void) { gl_Position = vec4(0); }
)";
auto define = compile(GL_VERTEX_SHADER, defineProbe);
EXPECT_TRUE(define) << (define ? String() : define.error().log);
const String mixes = R"(#version 330
#extension GL_EXT_shader_integer_mix: enable
uniform ivec4 a; uniform ivec4 b; uniform uvec4 c; uniform uvec4 d; uniform bvec4 sel;
flat out ivec4 i; flat out uvec4 u; flat out ivec4 bo;
void main(void) {
i = mix(a, b, sel);
u = mix(c, d, sel);
bo = ivec4(mix(bvec4(a), bvec4(b), sel));
gl_Position = vec4(0);
}
)";
auto mixed = compile(GL_VERTEX_SHADER, mixes);
EXPECT_TRUE(mixed) << (mixed ? String() : mixed.error().log);
MG_Backend::pActiveBackendObject = Move(previousBackend);
MG_State::pGLContext = Move(previousContext);
}
TEST(GetterSanity, ReportsKhrSubgroupDynamicParameters) {
using namespace MobileGL;
@@ -1123,6 +1123,7 @@ namespace MobileGL::MG_Util::BackendLoader {
// Combined with the three entry points below; DirectGLES emulates baseInstance when this
// comes out false, so a stub pointer counting as support would silently break the draws.
Bool hasBaseInstanceExtension = false;
Bool hasTessellationShaderExtension = false;
for (GLint i = 0; i < extCount; ++i) {
const char* extension = (const char*)glesFuncs.glGetStringi(GL_EXTENSIONS, i);
if (extension) {
@@ -1180,6 +1181,9 @@ namespace MobileGL::MG_Util::BackendLoader {
if (std::strcmp(extension, "GL_EXT_base_instance") == 0) {
hasBaseInstanceExtension = true;
}
if (std::strcmp(extension, "GL_EXT_tessellation_shader") == 0) {
hasTessellationShaderExtension = true;
}
if (std::strcmp(extension, "GL_EXT_disjoint_timer_query") == 0) {
caps.SupportsDisjointTimerQuery = true;
}
@@ -1281,6 +1285,8 @@ namespace MobileGL::MG_Util::BackendLoader {
glesFuncs.glDrawElementsIndirect != nullptr;
caps.SupportsDrawElementsBaseVertex = (esAtLeast32 || hasDrawElementsBaseVertexExtension) &&
glesFuncs.glDrawElementsBaseVertex != nullptr;
caps.SupportsTessellationShader = (esAtLeast32 || (esAtLeast31 && hasTessellationShaderExtension)) &&
glesFuncs.glPatchParameteri != nullptr;
caps.SupportsComputeShader = esAtLeast31 && glesFuncs.glDispatchCompute != nullptr &&
glesFuncs.glMemoryBarrier != nullptr &&
glesFuncs.glCreateShader != nullptr &&
@@ -1308,6 +1314,8 @@ namespace MobileGL::MG_Util::BackendLoader {
MGLOG_I(" draw elements base vertex (ES 3.2 core or EXT/OES_draw_elements_base_vertex): %s",
caps.SupportsDrawElementsBaseVertex ? "yes" : "no");
MGLOG_I(" compute shaders (ES 3.1 core): %s", caps.SupportsComputeShader ? "yes" : "no");
MGLOG_I(" tessellation shaders (ES 3.2 core or EXT_tessellation_shader): %s",
caps.SupportsTessellationShader ? "yes" : "no");
MGLOG_I(" base instance (EXT_base_instance; emulated by attribute offsets when absent): %s",
caps.SupportsBaseInstance ? "yes" : "no");
MGLOG_I(" clip distances (EXT_clip_cull_distance): %s", caps.SupportsClipDistance ? "yes" : "no");
@@ -1114,6 +1114,9 @@ namespace MobileGL {
Bool SupportsTextureBorderClamp = false;
// GL_TEXTURE_CUBE_MAP_ARRAY: ES 3.2 core, or EXT/OES_texture_cube_map_array before it.
Bool SupportsTextureCubeMapArray = false;
// Tessellation stages: ES 3.2 core, or GL_EXT_tessellation_shader on 3.1, with
// glPatchParameteri resolved either way.
Bool SupportsTessellationShader = false;
// GL_EXT_texture_view / GL_OES_texture_view: two ES texture names sharing one
// storage, i.e. the only way DirectGLES can answer glTextureView at all. ES never
// made this core - not even in 3.2 - so unlike every other capability here there is
@@ -259,6 +259,7 @@ namespace MobileGL::MG_Util::BackendLoader {
supportedFeatures.vertexPipelineStoresAndAtomics == VK_TRUE;
caps.SupportsFragmentStoresAndAtomics = supportedFeatures.fragmentStoresAndAtomics == VK_TRUE;
caps.SupportsGeometryShader = supportedFeatures.geometryShader == VK_TRUE;
caps.SupportsTessellationShader = supportedFeatures.tessellationShader == VK_TRUE;
caps.SupportsShaderClipDistance = supportedFeatures.shaderClipDistance == VK_TRUE;
caps.SupportsShaderCullDistance = supportedFeatures.shaderCullDistance == VK_TRUE;
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(p.limits.maxStorageBufferRange);
@@ -372,6 +373,7 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.SupportsVertexPipelineStoresAndAtomics = false;
caps.SupportsFragmentStoresAndAtomics = false;
caps.SupportsGeometryShader = false;
caps.SupportsTessellationShader = false;
caps.SupportsShaderClipDistance = false;
caps.SupportsShaderCullDistance = false;
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(properties.limits.maxStorageBufferRange);
@@ -117,6 +117,9 @@ namespace MobileGL {
Bool SupportsVertexPipelineStoresAndAtomics = false;
Bool SupportsFragmentStoresAndAtomics = false;
Bool SupportsGeometryShader = false;
// VkPhysicalDeviceFeatures::tessellationShader, which VulkanRenderer enables wherever
// the physical device has it.
Bool SupportsTessellationShader = false;
// VkPhysicalDeviceFeatures::shaderClipDistance. maxClipDistances is a LIMIT and is
// reported whatever the feature says, so the limit alone does not mean a module may
// declare ClipDistance - VulkanRenderer enables the feature only where the physical