[Feat] (Espryt): forward fragment shading rate to the driver extension

This commit is contained in:
2026-10-12 08:03:29 -04:00
parent 6d27499f3c
commit db23b9d551
4 changed files with 164 additions and 0 deletions
@@ -842,6 +842,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
capabilities.SupportsDrawIndirect,
capabilities.SupportsDrawIndirect && capabilities.SupportsBaseInstance,
capabilities.SupportsTextureView, capabilities.SupportsTextureCubeMapArray);
// 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;
if (capabilities.SupportsFragmentShadingRate) {
extensions.push_back(E_GL_EXT_fragment_shading_rate);
if (capabilities.SupportsFragmentShadingRatePrimitive) {
extensions.push_back(E_GL_EXT_fragment_shading_rate_primitive);
}
}
}
} // namespace
@@ -1895,6 +1904,25 @@ namespace MobileGL::MG_Backend::DirectGLES {
// describe behaviour this backend does not implement.
m_dynamicParameters.LayerProvokingVertex = m_GLESCapabilities.LayerProvokingVertex;
m_dynamicParameters.ViewportIndexProvokingVertex = m_GLESCapabilities.ViewportIndexProvokingVertex;
// GL_EXT_fragment_shading_rate is a straight pass-through: the pipeline rate and the
// combiner ops are forwarded per draw (SyncRenderState), gl_ShadingRateEXT and
// gl_PrimitiveShadingRateEXT are emitted into the ESSL as the driver's own built-ins, and
// the device answers every query. The attachment is not forwarded yet, so it is not
// claimed, whatever the driver offers.
m_dynamicParameters.SupportsFragmentShadingRate = m_GLESCapabilities.SupportsFragmentShadingRate;
m_dynamicParameters.SupportsPrimitiveFragmentShadingRate =
m_GLESCapabilities.SupportsFragmentShadingRatePrimitive;
m_dynamicParameters.SupportsAttachmentFragmentShadingRate = false;
m_dynamicParameters.FragmentShadingRateNonTrivialCombiners =
m_GLESCapabilities.FragmentShadingRateNonTrivialCombiners;
m_dynamicParameters.FragmentShadingRateWithShaderDepthStencilWrites =
m_GLESCapabilities.FragmentShadingRateWithShaderDepthStencilWrites;
m_dynamicParameters.FragmentShadingRateWithSampleMask = m_GLESCapabilities.FragmentShadingRateWithSampleMask;
m_dynamicParameters.FragmentShadingRatePrimitiveWithMultiViewport =
m_GLESCapabilities.FragmentShadingRatePrimitiveWithMultiViewport;
for (SizeT i = 0; i < std::size(m_dynamicParameters.FragmentShadingRateMasks); ++i) {
m_dynamicParameters.FragmentShadingRateMasks[i] = m_GLESCapabilities.FragmentShadingRateMasks[i];
}
m_dynamicParameters.MaxViewportWidth = m_GLESCapabilities.MaxViewportWidth;
m_dynamicParameters.MaxViewportHeight = m_GLESCapabilities.MaxViewportHeight;
m_dynamicParameters.ViewportBoundsRangeMin = m_GLESCapabilities.ViewportBoundsRangeMin;
@@ -4177,6 +4177,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
}
if (tailSpanDirty && g_GLESCapabilities.SupportsFragmentShadingRate) { // GL_EXT_fragment_shading_rate
// The frontend stores the packed rate and op indices; the driver takes the enums.
// Only forwarded where the driver has the extension, which is also the only place
// the frontend advertised it - so a non-default value cannot exist anywhere else.
if (forceFullPush || parameters.ShadingRate != g_syncedRenderStateParameters.ShadingRate) {
const GLenum rate = MGUnpackShadingRate(parameters.ShadingRate);
g_GLESFuncs.glShadingRateEXT(rate != GL_NONE ? rate : GL_SHADING_RATE_1X1_PIXELS_EXT);
}
if (forceFullPush ||
parameters.ShadingRateCombinerOp0 != g_syncedRenderStateParameters.ShadingRateCombinerOp0 ||
parameters.ShadingRateCombinerOp1 != g_syncedRenderStateParameters.ShadingRateCombinerOp1) {
g_GLESFuncs.glShadingRateCombinerOpsEXT(MGShadingRateCombinerOpEnum(parameters.ShadingRateCombinerOp0),
MGShadingRateCombinerOpEnum(parameters.ShadingRateCombinerOp1));
}
}
g_syncedRenderStateVersion = currentRenderStateVersion;
// Byte copy, not member copy: it also clones the frontend struct's padding bytes,
// which is what lets the span memcmps above answer "unchanged" exactly instead of
@@ -641,6 +641,11 @@ namespace MobileGL::MG_Util::BackendLoader {
// absent on plenty of drivers and neither absence is an error.
INIT_GLES_FUNC_OPTIONAL(glTextureViewEXT)
INIT_GLES_FUNC_OPTIONAL(glTextureViewOES)
// GL_EXT_fragment_shading_rate: extension-only, so absence is not an error.
INIT_GLES_FUNC_OPTIONAL(glShadingRateEXT)
INIT_GLES_FUNC_OPTIONAL(glShadingRateCombinerOpsEXT)
INIT_GLES_FUNC_OPTIONAL(glGetFragmentShadingRatesEXT)
INIT_GLES_FUNC_OPTIONAL(glFramebufferShadingRateEXT)
INIT_GLES_FUNC_OPTIONAL(glTexBufferEXT)
INIT_GLES_FUNC_OPTIONAL(glTexBufferOES)
INIT_GLES_FUNC_OPTIONAL(glTexBufferRangeEXT)
@@ -1021,6 +1026,66 @@ namespace MobileGL::MG_Util::BackendLoader {
}
}
// Reads what the driver answers for GL_EXT_fragment_shading_rate once the extension strings have
// been matched. The per-sample-count rate lists are folded into masks of the packed encoding
// ((log2 w << 2) | log2 h), which is what crosses to the frontend.
static void FillInFragmentShadingRateCapabilities(MG_External::GLESCapabilities& caps,
const MG_External::GLESFunctionsTable& glesFuncs) {
const auto getBoolean = [&](GLenum pname) {
GLboolean value = GL_FALSE;
glesFuncs.glGetBooleanv(pname, &value);
return value == GL_TRUE;
};
caps.FragmentShadingRateNonTrivialCombiners =
getBoolean(GL_FRAGMENT_SHADING_RATE_NON_TRIVIAL_COMBINERS_SUPPORTED_EXT);
caps.FragmentShadingRateWithShaderDepthStencilWrites =
getBoolean(GL_FRAGMENT_SHADING_RATE_WITH_SHADER_DEPTH_STENCIL_WRITES_SUPPORTED_EXT);
caps.FragmentShadingRateWithSampleMask = getBoolean(GL_FRAGMENT_SHADING_RATE_WITH_SAMPLE_MASK_SUPPORTED_EXT);
if (caps.SupportsFragmentShadingRatePrimitive) {
caps.FragmentShadingRatePrimitiveWithMultiViewport =
getBoolean(GL_FRAGMENT_SHADING_RATE_PRIMITIVE_RATE_WITH_MULTI_VIEWPORT_SUPPORTED_EXT);
}
if (caps.SupportsFragmentShadingRateAttachment) {
caps.FragmentShadingRateAttachmentWithDefaultFramebuffer =
getBoolean(GL_FRAGMENT_SHADING_RATE_ATTACHMENT_WITH_DEFAULT_FRAMEBUFFER_SUPPORTED_EXT);
glesFuncs.glGetIntegerv(GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT,
&caps.MinFragmentShadingRateAttachmentTexelWidth);
glesFuncs.glGetIntegerv(GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT,
&caps.MinFragmentShadingRateAttachmentTexelHeight);
glesFuncs.glGetIntegerv(GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT,
&caps.MaxFragmentShadingRateAttachmentTexelWidth);
glesFuncs.glGetIntegerv(GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT,
&caps.MaxFragmentShadingRateAttachmentTexelHeight);
glesFuncs.glGetIntegerv(GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_ASPECT_RATIO_EXT,
&caps.MaxFragmentShadingRateAttachmentTexelAspectRatio);
glesFuncs.glGetIntegerv(GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_LAYERS_EXT,
&caps.MaxFragmentShadingRateAttachmentLayers);
}
// Table X.1 in enum order, 1X1 .. 4X4, as packed rates.
static constexpr Uint8 kPackedByEnumOffset[] = {0x0, 0x1, 0x4, 0x5, 0x2, 0x8, 0x9, 0x6, 0xa};
for (SizeT i = 0; i < std::size(caps.FragmentShadingRateMasks); ++i) {
GLenum rates[16] = {};
GLsizei count = 0;
glesFuncs.glGetFragmentShadingRatesEXT(static_cast<GLsizei>(1u << i), 16, &count, rates);
Uint16 mask = 0;
for (GLsizei r = 0; r < count && r < 16; ++r) {
const GLenum offset = rates[r] - GL_SHADING_RATE_1X1_PIXELS_EXT;
if (offset < std::size(kPackedByEnumOffset)) mask |= Uint16(1u << kPackedByEnumOffset[offset]);
}
caps.FragmentShadingRateMasks[i] = mask;
}
// The queries above are best-effort: a pname a driver does not know only leaves its default.
while (glesFuncs.glGetError() != GL_NO_ERROR) {
}
MGLOG_I("GL_EXT_fragment_shading_rate: primitive=%d attachment=%d nonTrivialCombiners=%d rates(1x)=0x%04x "
"rates(4x)=0x%04x attachment texel %dx%d..%dx%d",
caps.SupportsFragmentShadingRatePrimitive, caps.SupportsFragmentShadingRateAttachment,
caps.FragmentShadingRateNonTrivialCombiners, caps.FragmentShadingRateMasks[0],
caps.FragmentShadingRateMasks[2], caps.MinFragmentShadingRateAttachmentTexelWidth,
caps.MinFragmentShadingRateAttachmentTexelHeight, caps.MaxFragmentShadingRateAttachmentTexelWidth,
caps.MaxFragmentShadingRateAttachmentTexelHeight);
}
Bool FillInGLESCapabilities(MG_External::GLESCapabilities& caps, const MG_External::GLESFunctionsTable& glesFuncs) {
if (!glesFuncs.glGetString || !glesFuncs.glGetIntegerv) {
MGLOG_E("Required GLES functions are not loaded, cannot query capabilities");
@@ -1097,6 +1162,15 @@ namespace MobileGL::MG_Util::BackendLoader {
if (std::strcmp(extension, "GL_OES_texture_view") == 0) {
hasOesTextureView = true;
}
if (std::strcmp(extension, "GL_EXT_fragment_shading_rate") == 0) {
caps.SupportsFragmentShadingRate = true;
}
if (std::strcmp(extension, "GL_EXT_fragment_shading_rate_primitive") == 0) {
caps.SupportsFragmentShadingRatePrimitive = true;
}
if (std::strcmp(extension, "GL_EXT_fragment_shading_rate_attachment") == 0) {
caps.SupportsFragmentShadingRateAttachment = true;
}
if (std::strcmp(extension, "GL_EXT_texture_buffer") == 0) {
hasExtTextureBuffer = true;
}
@@ -1183,6 +1257,20 @@ namespace MobileGL::MG_Util::BackendLoader {
MGLOG_I("MOBILEGL_DISABLE_TEXTURE_VIEW is set; reporting no EXT/OES_texture_view support");
caps.SupportsTextureView = false;
}
// Each companion builds on the base extension, and each needs the entry points it brings;
// the strings alone never count (see AcquireGLESFunctions on stub pointers).
caps.SupportsFragmentShadingRate = caps.SupportsFragmentShadingRate &&
glesFuncs.glShadingRateEXT != nullptr &&
glesFuncs.glShadingRateCombinerOpsEXT != nullptr &&
glesFuncs.glGetFragmentShadingRatesEXT != nullptr;
caps.SupportsFragmentShadingRatePrimitive =
caps.SupportsFragmentShadingRate && caps.SupportsFragmentShadingRatePrimitive;
caps.SupportsFragmentShadingRateAttachment = caps.SupportsFragmentShadingRate &&
caps.SupportsFragmentShadingRateAttachment &&
glesFuncs.glFramebufferShadingRateEXT != nullptr;
if (caps.SupportsFragmentShadingRate) {
FillInFragmentShadingRateCapabilities(caps, glesFuncs);
}
// Core from ES 3.2 on, so an extension string is not required there; below 3.2 the
// extension is, and the pointer still has to have resolved either way.
const Bool esAtLeast32 = caps.GLESVersion.Major > 3 ||
@@ -619,6 +619,13 @@ namespace MobileGL {
GL_FUNC_TYPEDEF(void, glTextureViewOES, GLuint texture, GLenum target, GLuint origtexture,
GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer,
GLuint numlayers)
// GL_EXT_fragment_shading_rate (+ _attachment). Extension-only in every ES version.
GL_FUNC_TYPEDEF(void, glShadingRateEXT, GLenum rate)
GL_FUNC_TYPEDEF(void, glShadingRateCombinerOpsEXT, GLenum combinerOp0, GLenum combinerOp1)
GL_FUNC_TYPEDEF(void, glGetFragmentShadingRatesEXT, GLsizei samples, GLsizei maxCount, GLsizei* count,
GLenum* shadingRates)
GL_FUNC_TYPEDEF(void, glFramebufferShadingRateEXT, GLenum target, GLenum attachment, GLuint texture,
GLint baseLayer, GLsizei numLayers, GLsizei texelWidth, GLsizei texelHeight)
GL_FUNC_TYPEDEF(void, glTexBufferEXT, GLenum target, GLenum internalformat, GLuint buffer)
GL_FUNC_TYPEDEF(void, glTexBufferOES, GLenum target, GLenum internalformat, GLuint buffer)
GL_FUNC_TYPEDEF(void, glTexBufferRangeEXT, GLenum target, GLenum internalformat, GLuint buffer,
@@ -1040,6 +1047,10 @@ namespace MobileGL {
GL_FUNC_DECL(glTexBufferRange)
GL_FUNC_DECL(glTextureViewEXT)
GL_FUNC_DECL(glTextureViewOES)
GL_FUNC_DECL(glShadingRateEXT)
GL_FUNC_DECL(glShadingRateCombinerOpsEXT)
GL_FUNC_DECL(glGetFragmentShadingRatesEXT)
GL_FUNC_DECL(glFramebufferShadingRateEXT)
GL_FUNC_DECL(glTexBufferEXT)
GL_FUNC_DECL(glTexBufferOES)
GL_FUNC_DECL(glTexBufferRangeEXT)
@@ -1111,6 +1122,27 @@ namespace MobileGL {
// through the other). Gate on this, never on the entry points: eglGetProcAddress
// hands back live-looking stubs (see AcquireGLESFunctions).
Bool SupportsTextureView = false;
// GL_EXT_fragment_shading_rate and its _primitive / _attachment companions, with what the
// driver answers for them. Extension-only in every ES version; each flag also requires
// the entry points that extension brings, and _primitive / _attachment require the base
// one. The values below are meaningful only when their flag is set.
Bool SupportsFragmentShadingRate = false;
Bool SupportsFragmentShadingRatePrimitive = false;
Bool SupportsFragmentShadingRateAttachment = false;
Bool FragmentShadingRateNonTrivialCombiners = false;
Bool FragmentShadingRateWithShaderDepthStencilWrites = false;
Bool FragmentShadingRateWithSampleMask = false;
Bool FragmentShadingRatePrimitiveWithMultiViewport = false;
Bool FragmentShadingRateAttachmentWithDefaultFramebuffer = false;
// glGetFragmentShadingRatesEXT for 1, 2, 4 .. 64 samples, as a mask of packed rates
// (see MG_Backend::DynamicBackendParameters::FragmentShadingRateMasks).
Uint16 FragmentShadingRateMasks[7] = {};
Int MinFragmentShadingRateAttachmentTexelWidth = 0;
Int MinFragmentShadingRateAttachmentTexelHeight = 0;
Int MaxFragmentShadingRateAttachmentTexelWidth = 0;
Int MaxFragmentShadingRateAttachmentTexelHeight = 0;
Int MaxFragmentShadingRateAttachmentTexelAspectRatio = 0;
Int MaxFragmentShadingRateAttachmentLayers = 0;
// Which spelling of buffer-texture support the host driver has. Desktop GL makes buffer
// textures core from 3.1 on, so the frontend advertises them unconditionally and an app
// may call glTexBuffer at any time; ES only gained them in 3.2, and before that only