diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 324850bc..3438de0d 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -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; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 4cadf37c..dc80437c 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -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 diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 9006fd07..89b6d382 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -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(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 || diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index e890a5f1..3e151ac4 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -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