From 7e544c2b786cb3466fa6760445c7d1d313f94722 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 12 Oct 2026 08:02:39 -0400 Subject: [PATCH] [Feat] (Frontend): add GL_EXT_fragment_shading_rate state, entry points and getters --- MobileGL/MG_Backend/BackendObject.h | 31 ++++ .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 3 + MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp | 117 ++++++++++++ MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.h | 2 + .../GLImpl/RenderState/GL_RenderState.cpp | 64 +++++++ .../GLImpl/RenderState/GL_RenderState.h | 3 + MobileGL/MG_Impl/GetProcAddress.cpp | 6 +- MobileGL/MG_Pipe/DirtySurface.def | 2 + MobileGL/MG_Pipe/MGPipeRenderStateSpans.h | 5 +- MobileGL/MG_Pipe/MGPipeValueTypes.h | 59 ++++++ MobileGL/MG_Pipe/PipeFields.def | 12 +- MobileGL/MG_Pipe/generated/PipeSpanTable.inc | 5 +- MobileGL/MG_Pipe/generated/PipeWireLayout.inc | 18 ++ MobileGL/MG_State/GLState/Core.cpp | 20 ++ MobileGL/MG_State/GLState/Core.h | 5 + .../GLState/RenderState/RenderState.cpp | 29 +++ .../GLState/RenderState/RenderState.h | 7 + .../MG_Test/Pipe/RenderStateSpansTest.cpp | 3 + MobileGL/MG_Test/State/RenderStateTest.cpp | 174 ++++++++++++++++++ .../MGToStr/GLExtensionConverter.cpp | 3 + MobileGL/MG_Util/GLExtensions.h | 5 +- scripts/gen_pipe.py | 3 + 22 files changed, 567 insertions(+), 9 deletions(-) diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index df169dc9..754d7846 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -557,6 +557,37 @@ namespace MobileGL { Uint32 SubgroupSupportedFeatures = 0; Bool SubgroupQuadOperationsInAllStages = false; GpuVendorKind GpuVendor = GpuVendorKind::Unknown; + // GL_EXT_fragment_shading_rate and its two companions. Each is a statement about the + // device, so all of them default to "absent" and the extension strings are advertised + // only from these: DirectGLES fills them from the driver's own + // EXT_fragment_shading_rate(_primitive, _attachment), DirectVulkan from + // VK_KHR_fragment_shading_rate's pipeline / primitive / attachment features. + // SupportsFragmentShadingRate glShadingRateEXT and gl_ShadingRateEXT + // SupportsPrimitiveFragmentShadingRate gl_PrimitiveShadingRateEXT, combiner op 0 + // SupportsAttachmentFragmentShadingRate the R8UI rate attachment, combiner op 1 + Bool SupportsFragmentShadingRate = false; + Bool SupportsPrimitiveFragmentShadingRate = false; + Bool SupportsAttachmentFragmentShadingRate = false; + // The four GL_FRAGMENT_SHADING_RATE_*_SUPPORTED_EXT booleans, as the device answers them. + Bool FragmentShadingRateNonTrivialCombiners = false; + Bool FragmentShadingRateWithShaderDepthStencilWrites = false; + Bool FragmentShadingRateWithSampleMask = false; + Bool FragmentShadingRatePrimitiveWithMultiViewport = false; + Bool FragmentShadingRateAttachmentWithDefaultFramebuffer = false; + // What glGetFragmentShadingRatesEXT answers, one mask per sample count 1, 2, 4 .. 64 + // (index = log2(samples)): bit (log2(w) << 2 | log2(h)) is set when that rate is + // honoured exactly at that sample count, i.e. gl_ShadingRateEXT reads it back + // unclamped. Zero masks mean no rate at all, which is what a sample count the device + // cannot shade coarsely answers. + Uint16 FragmentShadingRateMasks[7] = {}; + // The GL_{MIN,MAX}_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_{WIDTH,HEIGHT}_EXT, + // _ASPECT_RATIO_EXT and _LAYERS_EXT limits. Meaningful only with the attachment. + Int MinFragmentShadingRateAttachmentTexelWidth = 0; + Int MinFragmentShadingRateAttachmentTexelHeight = 0; + Int MaxFragmentShadingRateAttachmentTexelWidth = 0; + Int MaxFragmentShadingRateAttachmentTexelHeight = 0; + Int MaxFragmentShadingRateAttachmentTexelAspectRatio = 0; + Int MaxFragmentShadingRateAttachmentLayers = 0; }; enum class WindowBackend { diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index cc00326f..2a364ce1 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -2035,6 +2035,9 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetPixelTransformParameterivEXT, GLenum targ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetPixelTransformParameterfvEXT, GLenum target, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetPixelTransformParameterfvEXT, target, pname, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameterfEXT, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PointParameterfEXT, pname, param) DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameterfvEXT, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PointParameterfvEXT, pname, params) +DECLARE_GL_FUNCTION_HEAD(void, GetFragmentShadingRatesEXT, GLsizei samples, GLsizei maxCount, GLsizei* count, GLenum* shadingRates) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetFragmentShadingRatesEXT, samples, maxCount, count, shadingRates) +DECLARE_GL_FUNCTION_HEAD(void, ShadingRateEXT, GLenum rate) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ShadingRateEXT, rate) +DECLARE_GL_FUNCTION_HEAD(void, ShadingRateCombinerOpsEXT, GLenum combinerOp0, GLenum combinerOp1) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ShadingRateCombinerOpsEXT, combinerOp0, combinerOp1) DECLARE_GL_FUNCTION_HEAD(void, PolygonOffsetClampEXT, GLfloat factor, GLfloat units, GLfloat clamp) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PolygonOffsetClamp, factor, units, clamp) DECLARE_GL_FUNCTION_HEAD(void, ProvokingVertexEXT, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProvokingVertex, mode) DECLARE_GL_FUNCTION_STUB_HEAD(void, RasterSamplesEXT, GLuint samples, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, RasterSamplesEXT, samples, fixedsamplelocations) diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index de7c6ee5..4fd30392 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -1632,6 +1632,121 @@ namespace MobileGL::MG_Impl::GLImpl { } } + // GL_EXT_fragment_shading_rate's state and limits (tables 23.9 / 23.53 of the extension). + // Returns false for any other pname. The tokens belong to the extension, so a context whose + // backend cannot back it answers them like any unknown pname, and the attachment limits the + // same way unless the attachment extension is advertised too. + static Bool TryGetFragmentShadingRateInteger(GLenum pname, GLint* params) { + Bool attachmentToken = false; + switch (pname) { + case GL_SHADING_RATE_EXT: + case GL_FRAGMENT_SHADING_RATE_NON_TRIVIAL_COMBINERS_SUPPORTED_EXT: + case GL_FRAGMENT_SHADING_RATE_WITH_SHADER_DEPTH_STENCIL_WRITES_SUPPORTED_EXT: + case GL_FRAGMENT_SHADING_RATE_WITH_SAMPLE_MASK_SUPPORTED_EXT: + case GL_FRAGMENT_SHADING_RATE_PRIMITIVE_RATE_WITH_MULTI_VIEWPORT_SUPPORTED_EXT: + break; + case GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT: + case GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT: + case GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT: + case GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT: + case GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_ASPECT_RATIO_EXT: + case GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_LAYERS_EXT: + case GL_FRAGMENT_SHADING_RATE_ATTACHMENT_WITH_DEFAULT_FRAMEBUFFER_SUPPORTED_EXT: + attachmentToken = true; + break; + default: + return false; + } + + const auto& caps = MG_Backend::pActiveBackendObject->GetDynamicParameters(); + if (!caps.SupportsFragmentShadingRate || (attachmentToken && !caps.SupportsAttachmentFragmentShadingRate)) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", "GetIntegerv", + std::format("Invalid enum: 0x{:X} (its fragment shading rate extension " + "is not supported)", + pname))); + return true; + } + + switch (pname) { + case GL_SHADING_RATE_EXT: + *params = static_cast(MGUnpackShadingRate(MG_State::pGLContext->GetShadingRate())); + return true; + case GL_FRAGMENT_SHADING_RATE_NON_TRIVIAL_COMBINERS_SUPPORTED_EXT: + *params = caps.FragmentShadingRateNonTrivialCombiners ? GL_TRUE : GL_FALSE; + return true; + case GL_FRAGMENT_SHADING_RATE_WITH_SHADER_DEPTH_STENCIL_WRITES_SUPPORTED_EXT: + *params = caps.FragmentShadingRateWithShaderDepthStencilWrites ? GL_TRUE : GL_FALSE; + return true; + case GL_FRAGMENT_SHADING_RATE_WITH_SAMPLE_MASK_SUPPORTED_EXT: + *params = caps.FragmentShadingRateWithSampleMask ? GL_TRUE : GL_FALSE; + return true; + case GL_FRAGMENT_SHADING_RATE_PRIMITIVE_RATE_WITH_MULTI_VIEWPORT_SUPPORTED_EXT: + *params = caps.FragmentShadingRatePrimitiveWithMultiViewport ? GL_TRUE : GL_FALSE; + return true; + case GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT: + *params = caps.MinFragmentShadingRateAttachmentTexelWidth; + return true; + case GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT: + *params = caps.MinFragmentShadingRateAttachmentTexelHeight; + return true; + case GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT: + *params = caps.MaxFragmentShadingRateAttachmentTexelWidth; + return true; + case GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT: + *params = caps.MaxFragmentShadingRateAttachmentTexelHeight; + return true; + case GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_ASPECT_RATIO_EXT: + *params = caps.MaxFragmentShadingRateAttachmentTexelAspectRatio; + return true; + case GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_LAYERS_EXT: + *params = caps.MaxFragmentShadingRateAttachmentLayers; + return true; + case GL_FRAGMENT_SHADING_RATE_ATTACHMENT_WITH_DEFAULT_FRAMEBUFFER_SUPPORTED_EXT: + *params = caps.FragmentShadingRateAttachmentWithDefaultFramebuffer ? GL_TRUE : GL_FALSE; + return true; + default: + return false; + } + } + + // GL_EXT_fragment_shading_rate 14.X.1. The rates come from the backend's per-sample-count + // mask, largest area first (the order the Vulkan query uses; the extension leaves it open). + // A sample count the device does not list - including any that is not a power of two - + // answers zero rates rather than an error: the only error the extension gives is a negative + // maxCount. + void GetFragmentShadingRatesEXT(GLsizei samples, GLsizei maxCount, GLsizei* count, GLenum* shadingRates) { + if (maxCount < 0) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "maxCount must not be negative")); + return; + } + const auto& caps = MG_Backend::pActiveBackendObject->GetDynamicParameters(); + Uint16 mask = 0; + if (caps.SupportsFragmentShadingRate) { + // GL counts a non-multisampled framebuffer as samples = 0 or 1; both are the 1x mask. + const GLsizei effectiveSamples = samples <= 1 ? 1 : samples; + for (SizeT i = 0; i < std::size(caps.FragmentShadingRateMasks); ++i) { + if (effectiveSamples == (GLsizei{1} << i)) { + mask = caps.FragmentShadingRateMasks[i]; + break; + } + } + } + // Largest area first, then the wider rate of an equal area. + static constexpr Uint8 kOrder[] = {0xa, 0x9, 0x6, 0x8, 0x5, 0x2, 0x4, 0x1, 0x0}; + GLsizei written = 0; + for (Uint8 packed : kOrder) { + if ((mask & (1u << packed)) == 0) continue; + if (written >= maxCount) break; + if (shadingRates != nullptr) shadingRates[written] = MGUnpackShadingRate(packed); + ++written; + } + if (count != nullptr) *count = written; + } + void GetIntegerv(GLenum pname, GLint* params) { MGLOG_D("glGetIntegerv, pname: %s", MG_Util::ConvertGLEnumToString(pname).c_str()); if (!params) { @@ -1648,6 +1763,8 @@ namespace MobileGL::MG_Impl::GLImpl { return; } + if (TryGetFragmentShadingRateInteger(pname, params)) return; + switch (pname) { case GL_ACTIVE_TEXTURE: *params = MG_State::pGLContext->GetActiveTextureUnit() + GL_TEXTURE0; diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.h b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.h index 608f8f64..eff54054 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.h +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.h @@ -17,6 +17,8 @@ namespace MobileGL::MG_Impl::GLImpl { void GetFloatv(GLenum pname, GLfloat* params); void GetDoublev(GLenum pname, GLdouble* params); void GetIntegerv(GLenum pname, GLint* params); + // GL_EXT_fragment_shading_rate + void GetFragmentShadingRatesEXT(GLsizei samples, GLsizei maxCount, GLsizei* count, GLenum* shadingRates); void GetInteger64v(GLenum pname, GLint64* params); void GetIntegeri_v(GLenum target, GLuint index, GLint* data); void GetFloati_v(GLenum target, GLuint index, GLfloat* data); diff --git a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp index 208824c3..cba9b5fb 100644 --- a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp +++ b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp @@ -9,6 +9,7 @@ #include "GL_RenderState.h" #include #include +#include #include #include #include @@ -372,6 +373,61 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->SetClipControl(origin, depth); } + // GL_EXT_fragment_shading_rate 14.X.1: the only error is a rate outside table X.1, and the + // state is left untouched by it. Whether the device can honour the rate is not an error - + // an unsupported rate is clamped at rasterization, which the backends leave to the driver. + void ShadingRateEXT_State(GLenum rate) { + const Uint8 packed = MGPackShadingRate(rate); + if (packed == kMGInvalidShadingRateEncoding) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, + "glShadingRateEXT rate is not a GL_SHADING_RATE_*_PIXELS_EXT value; got " + + MG_Util::ConvertGLEnumToString(rate) + ".")); + return; + } + MG_State::pGLContext->SetShadingRate(packed); + } + + // GL_EXT_fragment_shading_rate 14.X.4, in the spec's order: an op outside the five combiner + // enums is INVALID_ENUM; a non-trivial op (MIN, MAX, MUL) without + // FRAGMENT_SHADING_RATE_NON_TRIVIAL_COMBINERS_SUPPORTED_EXT, or a non-KEEP op whose input + // extension (_primitive for op 0, _attachment for op 1) is absent, is INVALID_OPERATION. + void ShadingRateCombinerOpsEXT_State(GLenum combinerOp0, GLenum combinerOp1) { + const Uint8 op0 = MGShadingRateCombinerOpIndex(combinerOp0); + const Uint8 op1 = MGShadingRateCombinerOpIndex(combinerOp1); + if (op0 == kMGInvalidShadingRateEncoding || op1 == kMGInvalidShadingRateEncoding) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique( + "MG_Impl/GLImpl", __func__, + "glShadingRateCombinerOpsEXT ops must be GL_FRAGMENT_SHADING_RATE_COMBINER_OP_*_EXT; got " + + MG_Util::ConvertGLEnumToString(combinerOp0) + ", " + + MG_Util::ConvertGLEnumToString(combinerOp1) + ".")); + return; + } + const auto& caps = MG_Backend::pActiveBackendObject->GetDynamicParameters(); + constexpr Uint8 kKeep = 0; + constexpr Uint8 kReplace = 1; + const auto isTrivial = [](Uint8 op) { return op == kKeep || op == kReplace; }; + const char* refusal = nullptr; + if (!caps.FragmentShadingRateNonTrivialCombiners && (!isTrivial(op0) || !isTrivial(op1))) { + refusal = "MIN, MAX and MUL need GL_FRAGMENT_SHADING_RATE_NON_TRIVIAL_COMBINERS_SUPPORTED_EXT"; + } else if (!caps.SupportsPrimitiveFragmentShadingRate && op0 != kKeep) { + refusal = "combinerOp0 must be KEEP without GL_EXT_fragment_shading_rate_primitive"; + } else if (!caps.SupportsAttachmentFragmentShadingRate && op1 != kKeep) { + refusal = "combinerOp1 must be KEEP without GL_EXT_fragment_shading_rate_attachment"; + } + if (refusal != nullptr) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, + String("glShadingRateCombinerOpsEXT: ") + refusal + ".")); + return; + } + MG_State::pGLContext->SetShadingRateCombinerOps(op0, op1); + } + void PolygonMode_State(GLenum face, GLenum mode) { // GL 3.3 core: separate front/back polygon modes were removed in 3.1, so the only legal // face is GL_FRONT_AND_BACK. GL_FRONT / GL_BACK must be rejected (some desktop drivers @@ -1069,6 +1125,14 @@ namespace MobileGL::MG_Impl::GLImpl { ClipControl_State(origin, depth); } + void ShadingRateEXT(GLenum rate) { + ShadingRateEXT_State(rate); + } + + void ShadingRateCombinerOpsEXT(GLenum combinerOp0, GLenum combinerOp1) { + ShadingRateCombinerOpsEXT_State(combinerOp0, combinerOp1); + } + void PolygonMode(GLenum face, GLenum mode) { PolygonMode_State(face, mode); } diff --git a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h index 50588a83..47a40be5 100644 --- a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h +++ b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h @@ -42,6 +42,9 @@ namespace MobileGL::MG_Impl::GLImpl { void PolygonOffset(GLfloat factor, GLfloat units); void PolygonOffsetClamp(GLfloat factor, GLfloat units, GLfloat clamp); void ClipControl(GLenum origin, GLenum depth); + // GL_EXT_fragment_shading_rate + void ShadingRateEXT(GLenum rate); + void ShadingRateCombinerOpsEXT(GLenum combinerOp0, GLenum combinerOp1); void PolygonMode(GLenum face, GLenum mode); void PointSize(GLfloat size); void PointParameterf(GLenum pname, GLfloat param); diff --git a/MobileGL/MG_Impl/GetProcAddress.cpp b/MobileGL/MG_Impl/GetProcAddress.cpp index e6434da4..7c4cddf1 100644 --- a/MobileGL/MG_Impl/GetProcAddress.cpp +++ b/MobileGL/MG_Impl/GetProcAddress.cpp @@ -1298,9 +1298,9 @@ namespace MobileGL::MG_Impl { GETPROC(glVertexArrayVertexAttribDivisorEXT, name); GETPROC(glDrawArraysInstancedEXT, name); GETPROC(glDrawElementsInstancedEXT, name); - // GETPROC(glGetFragmentShadingRatesEXT, name); - // GETPROC(glShadingRateEXT, name); - // GETPROC(glShadingRateCombinerOpsEXT, name); + GETPROC(glGetFragmentShadingRatesEXT, name); + GETPROC(glShadingRateEXT, name); + GETPROC(glShadingRateCombinerOpsEXT, name); // GETPROC(glFramebufferShadingRateEXT, name); // GETPROC(glDrawMeshTasksEXT, name); // GETPROC(glDrawMeshTasksIndirectEXT, name); diff --git a/MobileGL/MG_Pipe/DirtySurface.def b/MobileGL/MG_Pipe/DirtySurface.def index a803041d..3fe47bce 100644 --- a/MobileGL/MG_Pipe/DirtySurface.def +++ b/MobileGL/MG_Pipe/DirtySurface.def @@ -206,6 +206,8 @@ X(SetProvokingVertexMode, NEW_RENDER_STATE|NEW_PIPELINE_STATE) \ X(SetSampleCoverage, NEW_RENDER_STATE|NEW_PIPELINE_STATE) \ X(SetSampleMaskValue, NEW_RENDER_STATE|NEW_PIPELINE_STATE) \ + X(SetShadingRate, NEW_RENDER_STATE|NEW_PIPELINE_STATE) \ + X(SetShadingRateCombinerOps, NEW_RENDER_STATE|NEW_PIPELINE_STATE) \ /* SetStencilFunc writes Func (pipeline chunk P2/P3) AND Ref/ValueMask (dynamic */ \ /* D3/D4), and ++m_pipelineStateVersion is CONDITIONAL on Func moving - which is */ \ /* what keeps a glStencilFunc that moves only the reference from evicting a */ \ diff --git a/MobileGL/MG_Pipe/MGPipeRenderStateSpans.h b/MobileGL/MG_Pipe/MGPipeRenderStateSpans.h index d0453c9f..9fef1683 100644 --- a/MobileGL/MG_Pipe/MGPipeRenderStateSpans.h +++ b/MobileGL/MG_Pipe/MGPipeRenderStateSpans.h @@ -68,8 +68,9 @@ namespace MobileGL::MG_Pipe { offsetof(RenderStateParameters, BlendStates), // D2 dynamic: ClearColor, ClearDepth, ClearStencil, BlendColor, DepthRanges[16] offsetof(RenderStateParameters, ClearColor), - // P2 pipeline: SampleCoverageValue, SampleCoverageInvert, SampleMaskValue, - // MinSampleShadingValue, StencilStates[0].Func + // P2 pipeline: SampleCoverageValue, SampleCoverageInvert, ShadingRate, + // ShadingRateCombinerOp0/1, SampleMaskValue, MinSampleShadingValue, + // StencilStates[0].Func offsetof(RenderStateParameters, SampleCoverageValue), // D3 dynamic: StencilStates[0].{Ref, ValueMask, WriteMask} MGPipeRenderStateChunkDetail::kStencilFace0 + MGPipeRenderStateChunkDetail::kFaceDynamicBegin, diff --git a/MobileGL/MG_Pipe/MGPipeValueTypes.h b/MobileGL/MG_Pipe/MGPipeValueTypes.h index a6d50c4e..e5138fce 100644 --- a/MobileGL/MG_Pipe/MGPipeValueTypes.h +++ b/MobileGL/MG_Pipe/MGPipeValueTypes.h @@ -323,6 +323,17 @@ namespace MobileGL { Array DepthRanges{}; Float SampleCoverageValue = 1.0f; Bool SampleCoverageInvert = false; + // GL_EXT_fragment_shading_rate: the pipeline rate glShadingRateEXT sets and the two + // combiner operations of glShadingRateCombinerOpsEXT. Placed in the three alignment bytes + // between SampleCoverageInvert and SampleMaskValue, the way FramebufferSrgbEnabled and its + // two neighbours were, so sizeof and every existing offset stay put. All three are + // PIPELINE state (their setters call BumpVersions). The rate is stored in the packed form + // the extension defines for gl_ShadingRateEXT and the attachment texels - + // (log2(width) << 2) | log2(height), so 1x1 = 0 - and each op as its index in + // KEEP, REPLACE, MIN, MAX, MUL, which is also VkFragmentShadingRateCombinerOpKHR's order. + Uint8 ShadingRate = 0; + Uint8 ShadingRateCombinerOp0 = 0; + Uint8 ShadingRateCombinerOp1 = 0; Uint32 SampleMaskValue = 0xffffffffu; // glMinSampleShading (ARB_sample_shading / GL 4.0 core 14.3.1). The fraction of samples // that get their own independent shading when GL_SAMPLE_SHADING is enabled; the initial @@ -406,6 +417,54 @@ namespace MobileGL { Uint32 ClipDistanceEnabledMask = 0; }; + // GL_EXT_fragment_shading_rate's two encodings, shared by the entry points, the getters and + // both backends. A rate packs to (log2(width) << 2) | log2(height) - the value + // gl_ShadingRateEXT and an attachment texel carry - and a combiner op to its index in + // KEEP, REPLACE, MIN, MAX, MUL. kMGInvalidShadingRateEncoding is "not a value of the enum". + inline constexpr Uint8 kMGInvalidShadingRateEncoding = 0xffu; + + constexpr Uint8 MGPackShadingRate(GLenum rate) { + switch (rate) { + case GL_SHADING_RATE_1X1_PIXELS_EXT: return 0x0u; + case GL_SHADING_RATE_1X2_PIXELS_EXT: return 0x1u; + case GL_SHADING_RATE_1X4_PIXELS_EXT: return 0x2u; + case GL_SHADING_RATE_2X1_PIXELS_EXT: return 0x4u; + case GL_SHADING_RATE_2X2_PIXELS_EXT: return 0x5u; + case GL_SHADING_RATE_2X4_PIXELS_EXT: return 0x6u; + case GL_SHADING_RATE_4X1_PIXELS_EXT: return 0x8u; + case GL_SHADING_RATE_4X2_PIXELS_EXT: return 0x9u; + case GL_SHADING_RATE_4X4_PIXELS_EXT: return 0xau; + default: return kMGInvalidShadingRateEncoding; + } + } + + // The inverse; GL_NONE for an encoding no enum names (a 3 in either half, or > 0xa). + constexpr GLenum MGUnpackShadingRate(Uint8 packed) { + switch (packed) { + case 0x0u: return GL_SHADING_RATE_1X1_PIXELS_EXT; + case 0x1u: return GL_SHADING_RATE_1X2_PIXELS_EXT; + case 0x2u: return GL_SHADING_RATE_1X4_PIXELS_EXT; + case 0x4u: return GL_SHADING_RATE_2X1_PIXELS_EXT; + case 0x5u: return GL_SHADING_RATE_2X2_PIXELS_EXT; + case 0x6u: return GL_SHADING_RATE_2X4_PIXELS_EXT; + case 0x8u: return GL_SHADING_RATE_4X1_PIXELS_EXT; + case 0x9u: return GL_SHADING_RATE_4X2_PIXELS_EXT; + case 0xau: return GL_SHADING_RATE_4X4_PIXELS_EXT; + default: return GL_NONE; + } + } + + constexpr Uint8 MGShadingRateCombinerOpIndex(GLenum op) { + return (op >= GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT && + op <= GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_EXT) + ? static_cast(op - GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT) + : kMGInvalidShadingRateEncoding; + } + + constexpr GLenum MGShadingRateCombinerOpEnum(Uint8 index) { + return static_cast(GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT + index); + } + enum class SamplerFilterMode { Nearest, Linear, diff --git a/MobileGL/MG_Pipe/PipeFields.def b/MobileGL/MG_Pipe/PipeFields.def index 9c79ebe2..f25db0b4 100644 --- a/MobileGL/MG_Pipe/PipeFields.def +++ b/MobileGL/MG_Pipe/PipeFields.def @@ -323,7 +323,8 @@ F(ClipOrigin) F(ClipDepthMode) F(BlendStates) F(LogicOp) F(DepthTestEnabled) F(DepthFunc) \ F(DepthMask) F(ColorMasks) F(FramebufferSrgbEnabled) F(DepthClampEnabled) \ F(TextureCubeMapSeamlessEnabled) F(ClearColor) F(ClearDepth) F(ClearStencil) F(BlendColor) \ - F(DepthRanges) F(SampleCoverageValue) F(SampleCoverageInvert) F(SampleMaskValue) \ + F(DepthRanges) F(SampleCoverageValue) F(SampleCoverageInvert) F(ShadingRate) \ + F(ShadingRateCombinerOp0) F(ShadingRateCombinerOp1) F(SampleMaskValue) \ F(MinSampleShadingValue) F(StencilStates) F(CullFaceEnabled) F(CullFaceModeSetting) \ F(FrontFaceModeSetting) F(ProvokingVertexModeSetting) F(LineSmoothHint) F(PolygonSmoothHint) \ F(TextureCompressionHint) F(FragmentShaderDerivativeHint) F(PointFadeThresholdSize) \ @@ -389,7 +390,14 @@ F(SupportsShaderFloat64) F(SupportsFloat64VertexAttributes) F(SupportsTessellationPointSize) \ F(SupportsGeometryPointSize) F(MaxShaderStorageBlockSize) F(SubgroupSize) \ F(SubgroupSupportedStages) F(SubgroupSupportedFeatures) F(SubgroupQuadOperationsInAllStages) \ - F(GpuVendor) + F(GpuVendor) F(SupportsFragmentShadingRate) F(SupportsPrimitiveFragmentShadingRate) \ + F(SupportsAttachmentFragmentShadingRate) F(FragmentShadingRateNonTrivialCombiners) \ + F(FragmentShadingRateWithShaderDepthStencilWrites) F(FragmentShadingRateWithSampleMask) \ + F(FragmentShadingRatePrimitiveWithMultiViewport) \ + F(FragmentShadingRateAttachmentWithDefaultFramebuffer) F(FragmentShadingRateMasks) \ + F(MinFragmentShadingRateAttachmentTexelWidth) F(MinFragmentShadingRateAttachmentTexelHeight) \ + F(MaxFragmentShadingRateAttachmentTexelWidth) F(MaxFragmentShadingRateAttachmentTexelHeight) \ + F(MaxFragmentShadingRateAttachmentTexelAspectRatio) F(MaxFragmentShadingRateAttachmentLayers) #define MGP_FIELDS_MGHostSpan(F) \ F(Ptr) F(Seg) F(Size) F(Offset) diff --git a/MobileGL/MG_Pipe/generated/PipeSpanTable.inc b/MobileGL/MG_Pipe/generated/PipeSpanTable.inc index 37269fae..a0d032d7 100644 --- a/MobileGL/MG_Pipe/generated/PipeSpanTable.inc +++ b/MobileGL/MG_Pipe/generated/PipeSpanTable.inc @@ -50,6 +50,9 @@ inline constexpr const char* const kMGPipePipelineStateMembers[] = { "TextureCubeMapSeamlessEnabled", "SampleCoverageValue", "SampleCoverageInvert", + "ShadingRate", + "ShadingRateCombinerOp0", + "ShadingRateCombinerOp1", "SampleMaskValue", "MinSampleShadingValue", "StencilStates", @@ -81,7 +84,7 @@ inline constexpr const char* const kMGPipePipelineStateMembers[] = { "ProgramPointSizeEnabled", "ScissorTestEnabledMask", }; -inline constexpr SizeT kMGPipePipelineStateMemberCount = 44; +inline constexpr SizeT kMGPipePipelineStateMemberCount = 47; static_assert(kMGPipePipelineStateMemberCount == sizeof(kMGPipePipelineStateMembers) / sizeof(kMGPipePipelineStateMembers[0])); diff --git a/MobileGL/MG_Pipe/generated/PipeWireLayout.inc b/MobileGL/MG_Pipe/generated/PipeWireLayout.inc index e03f15fe..e9fa9628 100644 --- a/MobileGL/MG_Pipe/generated/PipeWireLayout.inc +++ b/MobileGL/MG_Pipe/generated/PipeWireLayout.inc @@ -492,6 +492,9 @@ inline constexpr WireLayoutMember kMGPipeWireLayoutMembers[] = { {"RenderStateParameters.DepthRanges", offsetof(RenderStateParameters, DepthRanges), sizeof(((RenderStateParameters*)nullptr)->DepthRanges)}, {"RenderStateParameters.SampleCoverageValue", offsetof(RenderStateParameters, SampleCoverageValue), sizeof(((RenderStateParameters*)nullptr)->SampleCoverageValue)}, {"RenderStateParameters.SampleCoverageInvert", offsetof(RenderStateParameters, SampleCoverageInvert), sizeof(((RenderStateParameters*)nullptr)->SampleCoverageInvert)}, + {"RenderStateParameters.ShadingRate", offsetof(RenderStateParameters, ShadingRate), sizeof(((RenderStateParameters*)nullptr)->ShadingRate)}, + {"RenderStateParameters.ShadingRateCombinerOp0", offsetof(RenderStateParameters, ShadingRateCombinerOp0), sizeof(((RenderStateParameters*)nullptr)->ShadingRateCombinerOp0)}, + {"RenderStateParameters.ShadingRateCombinerOp1", offsetof(RenderStateParameters, ShadingRateCombinerOp1), sizeof(((RenderStateParameters*)nullptr)->ShadingRateCombinerOp1)}, {"RenderStateParameters.SampleMaskValue", offsetof(RenderStateParameters, SampleMaskValue), sizeof(((RenderStateParameters*)nullptr)->SampleMaskValue)}, {"RenderStateParameters.MinSampleShadingValue", offsetof(RenderStateParameters, MinSampleShadingValue), sizeof(((RenderStateParameters*)nullptr)->MinSampleShadingValue)}, {"RenderStateParameters.StencilStates", offsetof(RenderStateParameters, StencilStates), sizeof(((RenderStateParameters*)nullptr)->StencilStates)}, @@ -661,6 +664,21 @@ inline constexpr WireLayoutMember kMGPipeWireLayoutMembers[] = { {"DynamicBackendParameters.SubgroupSupportedFeatures", offsetof(DynamicBackendParameters, SubgroupSupportedFeatures), sizeof(((DynamicBackendParameters*)nullptr)->SubgroupSupportedFeatures)}, {"DynamicBackendParameters.SubgroupQuadOperationsInAllStages", offsetof(DynamicBackendParameters, SubgroupQuadOperationsInAllStages), sizeof(((DynamicBackendParameters*)nullptr)->SubgroupQuadOperationsInAllStages)}, {"DynamicBackendParameters.GpuVendor", offsetof(DynamicBackendParameters, GpuVendor), sizeof(((DynamicBackendParameters*)nullptr)->GpuVendor)}, + {"DynamicBackendParameters.SupportsFragmentShadingRate", offsetof(DynamicBackendParameters, SupportsFragmentShadingRate), sizeof(((DynamicBackendParameters*)nullptr)->SupportsFragmentShadingRate)}, + {"DynamicBackendParameters.SupportsPrimitiveFragmentShadingRate", offsetof(DynamicBackendParameters, SupportsPrimitiveFragmentShadingRate), sizeof(((DynamicBackendParameters*)nullptr)->SupportsPrimitiveFragmentShadingRate)}, + {"DynamicBackendParameters.SupportsAttachmentFragmentShadingRate", offsetof(DynamicBackendParameters, SupportsAttachmentFragmentShadingRate), sizeof(((DynamicBackendParameters*)nullptr)->SupportsAttachmentFragmentShadingRate)}, + {"DynamicBackendParameters.FragmentShadingRateNonTrivialCombiners", offsetof(DynamicBackendParameters, FragmentShadingRateNonTrivialCombiners), sizeof(((DynamicBackendParameters*)nullptr)->FragmentShadingRateNonTrivialCombiners)}, + {"DynamicBackendParameters.FragmentShadingRateWithShaderDepthStencilWrites", offsetof(DynamicBackendParameters, FragmentShadingRateWithShaderDepthStencilWrites), sizeof(((DynamicBackendParameters*)nullptr)->FragmentShadingRateWithShaderDepthStencilWrites)}, + {"DynamicBackendParameters.FragmentShadingRateWithSampleMask", offsetof(DynamicBackendParameters, FragmentShadingRateWithSampleMask), sizeof(((DynamicBackendParameters*)nullptr)->FragmentShadingRateWithSampleMask)}, + {"DynamicBackendParameters.FragmentShadingRatePrimitiveWithMultiViewport", offsetof(DynamicBackendParameters, FragmentShadingRatePrimitiveWithMultiViewport), sizeof(((DynamicBackendParameters*)nullptr)->FragmentShadingRatePrimitiveWithMultiViewport)}, + {"DynamicBackendParameters.FragmentShadingRateAttachmentWithDefaultFramebuffer", offsetof(DynamicBackendParameters, FragmentShadingRateAttachmentWithDefaultFramebuffer), sizeof(((DynamicBackendParameters*)nullptr)->FragmentShadingRateAttachmentWithDefaultFramebuffer)}, + {"DynamicBackendParameters.FragmentShadingRateMasks", offsetof(DynamicBackendParameters, FragmentShadingRateMasks), sizeof(((DynamicBackendParameters*)nullptr)->FragmentShadingRateMasks)}, + {"DynamicBackendParameters.MinFragmentShadingRateAttachmentTexelWidth", offsetof(DynamicBackendParameters, MinFragmentShadingRateAttachmentTexelWidth), sizeof(((DynamicBackendParameters*)nullptr)->MinFragmentShadingRateAttachmentTexelWidth)}, + {"DynamicBackendParameters.MinFragmentShadingRateAttachmentTexelHeight", offsetof(DynamicBackendParameters, MinFragmentShadingRateAttachmentTexelHeight), sizeof(((DynamicBackendParameters*)nullptr)->MinFragmentShadingRateAttachmentTexelHeight)}, + {"DynamicBackendParameters.MaxFragmentShadingRateAttachmentTexelWidth", offsetof(DynamicBackendParameters, MaxFragmentShadingRateAttachmentTexelWidth), sizeof(((DynamicBackendParameters*)nullptr)->MaxFragmentShadingRateAttachmentTexelWidth)}, + {"DynamicBackendParameters.MaxFragmentShadingRateAttachmentTexelHeight", offsetof(DynamicBackendParameters, MaxFragmentShadingRateAttachmentTexelHeight), sizeof(((DynamicBackendParameters*)nullptr)->MaxFragmentShadingRateAttachmentTexelHeight)}, + {"DynamicBackendParameters.MaxFragmentShadingRateAttachmentTexelAspectRatio", offsetof(DynamicBackendParameters, MaxFragmentShadingRateAttachmentTexelAspectRatio), sizeof(((DynamicBackendParameters*)nullptr)->MaxFragmentShadingRateAttachmentTexelAspectRatio)}, + {"DynamicBackendParameters.MaxFragmentShadingRateAttachmentLayers", offsetof(DynamicBackendParameters, MaxFragmentShadingRateAttachmentLayers), sizeof(((DynamicBackendParameters*)nullptr)->MaxFragmentShadingRateAttachmentLayers)}, {"MGHostSpan", 0, sizeof(MGHostSpan)}, {"MGHostSpan.Ptr", offsetof(MGHostSpan, Ptr), sizeof(((MGHostSpan*)nullptr)->Ptr)}, {"MGHostSpan.Seg", offsetof(MGHostSpan, Seg), sizeof(((MGHostSpan*)nullptr)->Seg)}, diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index aab477e3..2bf9f466 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -1208,6 +1208,26 @@ namespace MobileGL::MG_State { return m_renderState.GetMinSampleShadingValue(); } + void GLContext::SetShadingRate(Uint8 rate) { + m_renderState.SetShadingRate(rate); + } + + Uint8 GLContext::GetShadingRate() const { + return m_renderState.GetShadingRate(); + } + + void GLContext::SetShadingRateCombinerOps(Uint8 op0, Uint8 op1) { + m_renderState.SetShadingRateCombinerOps(op0, op1); + } + + Uint8 GLContext::GetShadingRateCombinerOp0() const { + return m_renderState.GetShadingRateCombinerOp0(); + } + + Uint8 GLContext::GetShadingRateCombinerOp1() const { + return m_renderState.GetShadingRateCombinerOp1(); + } + void GLContext::SetPixelStoreParam(PixelStoreParam param, Int value) { m_renderState.SetPixelStoreParam(param, value); } diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 2786fdce..10e31391 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -435,6 +435,11 @@ namespace MobileGL { Uint32 GetSampleMaskValue() const; void SetMinSampleShadingValue(Float value); Float GetMinSampleShadingValue() const; + void SetShadingRate(Uint8 rate); + Uint8 GetShadingRate() const; + void SetShadingRateCombinerOps(Uint8 op0, Uint8 op1); + Uint8 GetShadingRateCombinerOp0() const; + Uint8 GetShadingRateCombinerOp1() const; void SetPixelStoreParam(PixelStoreParam param, Int value); Int GetPixelStoreParam(PixelStoreParam param) const; PixelStoreParameters GetPixelStoreParameters(Bool isUnpack) const; diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp index 2ccfab72..b939a5c8 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp @@ -837,6 +837,35 @@ namespace MobileGL { return m_parameters.MinSampleShadingValue; } + void RenderState::SetShadingRate(Uint8 rate) { + if (m_parameters.ShadingRate == rate) return; + + m_parameters.ShadingRate = rate; + // BumpVersions, not just ++m_version: the bytes sit in a pipeline chunk (they took + // its alignment padding), and the G7 rule makes every byte there pipeline state. + BumpVersions(); + } + + Uint8 RenderState::GetShadingRate() const { + return m_parameters.ShadingRate; + } + + void RenderState::SetShadingRateCombinerOps(Uint8 op0, Uint8 op1) { + if (m_parameters.ShadingRateCombinerOp0 == op0 && m_parameters.ShadingRateCombinerOp1 == op1) return; + + m_parameters.ShadingRateCombinerOp0 = op0; + m_parameters.ShadingRateCombinerOp1 = op1; + BumpVersions(); + } + + Uint8 RenderState::GetShadingRateCombinerOp0() const { + return m_parameters.ShadingRateCombinerOp0; + } + + Uint8 RenderState::GetShadingRateCombinerOp1() const { + return m_parameters.ShadingRateCombinerOp1; + } + // -------------------- Pixel Store -------------------- void RenderState::SetPixelStoreParam(PixelStoreParam param, Int value) { #define SET_PIXEL_STORE_PARAM(paramNameHead, paramNameTail, val) \ diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.h b/MobileGL/MG_State/GLState/RenderState/RenderState.h index 40c9b076..769d5e95 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.h +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.h @@ -132,6 +132,13 @@ namespace MobileGL { // glMinSampleShading. `value` is stored as given; the entry point clamps. void SetMinSampleShadingValue(Float value); Float GetMinSampleShadingValue() const; + // GL_EXT_fragment_shading_rate. `rate` is the packed (log2 w << 2 | log2 h) form and + // each op its KEEP..MUL index (see RenderStateParameters); the entry points validate. + void SetShadingRate(Uint8 rate); + Uint8 GetShadingRate() const; + void SetShadingRateCombinerOps(Uint8 op0, Uint8 op1); + Uint8 GetShadingRateCombinerOp0() const; + Uint8 GetShadingRateCombinerOp1() const; // Pixel Store void SetPixelStoreParam(PixelStoreParam param, Int value); diff --git a/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp b/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp index 77a7366b..23fe694c 100644 --- a/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp +++ b/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp @@ -518,6 +518,9 @@ namespace { check("SetSampleCoverage", [](RenderState& s) { s.SetSampleCoverage(0.375f, true); }); check("SetSampleMaskValue", [](RenderState& s) { s.SetSampleMaskValue(0x5a5au); }); check("SetMinSampleShadingValue", [](RenderState& s) { s.SetMinSampleShadingValue(0.625f); }); + // The fragment shading rate took P2's alignment padding, so both setters are pipeline. + check("SetShadingRate", [](RenderState& s) { s.SetShadingRate(0x6u); }); + check("SetShadingRateCombinerOps", [](RenderState& s) { s.SetShadingRateCombinerOps(1u, 4u); }); // ---- Faces and scissor ---- check("SetCullFaceMode", [](RenderState& s) { s.SetCullFaceMode(CullFaceMode::Front); }); diff --git a/MobileGL/MG_Test/State/RenderStateTest.cpp b/MobileGL/MG_Test/State/RenderStateTest.cpp index a8d23a2b..ff042f3c 100644 --- a/MobileGL/MG_Test/State/RenderStateTest.cpp +++ b/MobileGL/MG_Test/State/RenderStateTest.cpp @@ -28,6 +28,7 @@ #include +#include #include #include #include @@ -1058,3 +1059,176 @@ TEST_F(RenderStateTest, NumSpirVExtensionsIsQueryableAndZero) { EXPECT_EQ(count, 0); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } + +// ---- GL_EXT_fragment_shading_rate ------------------------------------------------------------ +// +// The state machine half of the extension: the pipeline rate and the combiner ops, their errors, +// and the queries a backend's probed capability decides. The capability comes from a stand-in +// backend, because no device stands behind this binary. +namespace { + class FragmentShadingRateBackend final : public MG_Backend::BackendObject { + public: + FragmentShadingRateBackend(Bool primitive, Bool attachment, Bool nonTrivial) { + m_params.SupportsFragmentShadingRate = true; + m_params.SupportsPrimitiveFragmentShadingRate = primitive; + m_params.SupportsAttachmentFragmentShadingRate = attachment; + m_params.FragmentShadingRateNonTrivialCombiners = nonTrivial; + // 1x: 1x1, 1x2, 2x1, 2x2, 4x4. 4x: 1x1, 2x2. Every other count: nothing. + m_params.FragmentShadingRateMasks[0] = (1u << 0x0) | (1u << 0x1) | (1u << 0x4) | (1u << 0x5) | (1u << 0xa); + m_params.FragmentShadingRateMasks[2] = (1u << 0x0) | (1u << 0x5); + m_params.MaxFragmentShadingRateAttachmentTexelWidth = 16; + } + + void Initialize() override {} + Bool InitCapabilities() override { return true; } + Bool InitWindowSurface() override { return true; } + const RendererInfo& GetRendererInfo() const override { + static RendererInfo info = {}; + return info; + } + String GetBackendAPIVersionString() const override { return {}; } + const MG_Backend::GlobalBackendFunctionsTable& GetBackendFunctions() const override { + static MG_Backend::GlobalBackendFunctionsTable table = {}; + return table; + } + const MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override { return m_params; } + BackendType GetBackendType() const override { return BackendType::Unknown; } + + private: + MG_Backend::DynamicBackendParameters m_params{}; + }; + + class ScopedFragmentShadingRateBackend { + public: + ScopedFragmentShadingRateBackend(Bool primitive, Bool attachment, Bool nonTrivial): + m_previous(Move(MG_Backend::pActiveBackendObject)) { + MG_Backend::pActiveBackendObject = MakeUnique(primitive, attachment, nonTrivial); + } + ~ScopedFragmentShadingRateBackend() { + // Leave the context at the extension's initial state for whichever test runs next. + MG_State::pGLContext->SetShadingRate(0); + MG_State::pGLContext->SetShadingRateCombinerOps(0, 0); + MG_Backend::pActiveBackendObject = Move(m_previous); + } + + private: + UniquePtr m_previous; + }; +} // namespace + +TEST_F(RenderStateTest, ShadingRateEncodingsRoundTrip) { + for (GLenum rate = GL_SHADING_RATE_1X1_PIXELS_EXT; rate <= GL_SHADING_RATE_4X4_PIXELS_EXT; ++rate) { + const Uint8 packed = MGPackShadingRate(rate); + ASSERT_NE(packed, kMGInvalidShadingRateEncoding) << std::hex << rate; + EXPECT_EQ(MGUnpackShadingRate(packed), rate); + } + // The packed form is the one gl_ShadingRateEXT carries: (log2 w << 2) | log2 h. + EXPECT_EQ(MGPackShadingRate(GL_SHADING_RATE_2X4_PIXELS_EXT), 0x6u); + EXPECT_EQ(MGPackShadingRate(GL_SHADING_RATE_4X1_PIXELS_EXT), 0x8u); + EXPECT_EQ(MGPackShadingRate(GL_SAMPLE_SHADING), kMGInvalidShadingRateEncoding); + EXPECT_EQ(MGUnpackShadingRate(0x3u), static_cast(GL_NONE)); + EXPECT_EQ(MGShadingRateCombinerOpIndex(GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_EXT), 4u); + EXPECT_EQ(MGShadingRateCombinerOpIndex(GL_SHADING_RATE_EXT), kMGInvalidShadingRateEncoding); +} + +TEST_F(RenderStateTest, ShadingRateTokensAreUnknownWithoutABackendThatBacksThem) { + GLint value = -1; + MG_Impl::GLImpl::GetIntegerv(GL_SHADING_RATE_EXT, &value); + ExpectSingleGlError(GL_INVALID_ENUM); + + GLsizei count = -1; + GLenum rates[4] = {}; + MG_Impl::GLImpl::GetFragmentShadingRatesEXT(1, 4, &count, rates); + EXPECT_EQ(count, 0); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + +TEST_F(RenderStateTest, ShadingRateRoundTripsAndRejectsANonRate) { + ScopedFragmentShadingRateBackend backend(/*primitive=*/true, /*attachment=*/false, /*nonTrivial=*/true); + + GLint value = 0; + MG_Impl::GLImpl::GetIntegerv(GL_SHADING_RATE_EXT, &value); + EXPECT_EQ(value, GL_SHADING_RATE_1X1_PIXELS_EXT) << "the initial rate is 1x1"; + + MG_Impl::GLImpl::ShadingRateEXT(GL_SHADING_RATE_2X4_PIXELS_EXT); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + EXPECT_EQ(MG_State::pGLContext->GetShadingRate(), 0x6u); + + MG_Impl::GLImpl::ShadingRateEXT(GL_SAMPLE_SHADING); + ExpectSingleGlError(GL_INVALID_ENUM); + MG_Impl::GLImpl::GetIntegerv(GL_SHADING_RATE_EXT, &value); + EXPECT_EQ(value, GL_SHADING_RATE_2X4_PIXELS_EXT) << "a rejected glShadingRateEXT must not change the state"; +} + +TEST_F(RenderStateTest, ShadingRateCombinerOpsFollowTheExtensionErrorOrder) { + { + ScopedFragmentShadingRateBackend backend(/*primitive=*/true, /*attachment=*/false, /*nonTrivial=*/false); + + MG_Impl::GLImpl::ShadingRateCombinerOpsEXT(GL_SHADING_RATE_EXT, + GL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_EXT); + ExpectSingleGlError(GL_INVALID_ENUM); + MG_Impl::GLImpl::ShadingRateCombinerOpsEXT(GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT, + GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT); + ExpectSingleGlError(GL_INVALID_ENUM); + // Trivial combiners only on this device. + MG_Impl::GLImpl::ShadingRateCombinerOpsEXT(GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN_EXT, + GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT); + ExpectSingleGlError(GL_INVALID_OPERATION); + // No attachment extension: op 1 must stay KEEP. + MG_Impl::GLImpl::ShadingRateCombinerOpsEXT(GL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_EXT, + GL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_EXT); + ExpectSingleGlError(GL_INVALID_OPERATION); + + MG_Impl::GLImpl::ShadingRateCombinerOpsEXT(GL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_EXT, + GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + EXPECT_EQ(MG_State::pGLContext->GetShadingRateCombinerOp0(), 1u); + EXPECT_EQ(MG_State::pGLContext->GetShadingRateCombinerOp1(), 0u); + } + { + ScopedFragmentShadingRateBackend backend(/*primitive=*/false, /*attachment=*/true, /*nonTrivial=*/true); + + // No primitive extension: op 0 must stay KEEP, whatever op 1 is. + MG_Impl::GLImpl::ShadingRateCombinerOpsEXT(GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_EXT, + GL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_EXT); + ExpectSingleGlError(GL_INVALID_OPERATION); + MG_Impl::GLImpl::ShadingRateCombinerOpsEXT(GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT, + GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_EXT); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + EXPECT_EQ(MG_State::pGLContext->GetShadingRateCombinerOp1(), 4u); + } +} + +TEST_F(RenderStateTest, FragmentShadingRatesListTheBackendMaskLargestFirst) { + ScopedFragmentShadingRateBackend backend(/*primitive=*/false, /*attachment=*/false, /*nonTrivial=*/false); + + GLsizei count = -1; + GLenum rates[16] = {}; + MG_Impl::GLImpl::GetFragmentShadingRatesEXT(1, 16, &count, rates); + ASSERT_EQ(count, 5); + EXPECT_EQ(rates[0], static_cast(GL_SHADING_RATE_4X4_PIXELS_EXT)); + EXPECT_EQ(rates[1], static_cast(GL_SHADING_RATE_2X2_PIXELS_EXT)); + EXPECT_EQ(rates[2], static_cast(GL_SHADING_RATE_2X1_PIXELS_EXT)); + EXPECT_EQ(rates[3], static_cast(GL_SHADING_RATE_1X2_PIXELS_EXT)); + EXPECT_EQ(rates[4], static_cast(GL_SHADING_RATE_1X1_PIXELS_EXT)); + + // maxCount bounds what is written and counted; samples selects the mask. + MG_Impl::GLImpl::GetFragmentShadingRatesEXT(4, 1, &count, rates); + EXPECT_EQ(count, 1); + EXPECT_EQ(rates[0], static_cast(GL_SHADING_RATE_2X2_PIXELS_EXT)); + MG_Impl::GLImpl::GetFragmentShadingRatesEXT(3, 16, &count, rates); + EXPECT_EQ(count, 0) << "a sample count the device does not list has no rates"; + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + MG_Impl::GLImpl::GetFragmentShadingRatesEXT(1, -1, &count, rates); + ExpectSingleGlError(GL_INVALID_VALUE); + + // The attachment limits belong to the attachment extension, absent here. + GLint limit = -1; + MG_Impl::GLImpl::GetIntegerv(GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT, &limit); + ExpectSingleGlError(GL_INVALID_ENUM); + GLboolean nonTrivial = GL_TRUE; + MG_Impl::GLImpl::GetBooleanv(GL_FRAGMENT_SHADING_RATE_NON_TRIVIAL_COMBINERS_SUPPORTED_EXT, &nonTrivial); + EXPECT_EQ(nonTrivial, GL_FALSE); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} diff --git a/MobileGL/MG_Util/Converters/MGToStr/GLExtensionConverter.cpp b/MobileGL/MG_Util/Converters/MGToStr/GLExtensionConverter.cpp index 741b7225..0234243e 100644 --- a/MobileGL/MG_Util/Converters/MGToStr/GLExtensionConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToStr/GLExtensionConverter.cpp @@ -778,6 +778,9 @@ namespace MobileGL { CASE(E_GL_EXT_framebuffer_blit_layers) CASE(E_GL_NV_uniform_buffer_std430_layout) CASE(E_GL_MESA_texture_const_bandwidth) + CASE(E_GL_EXT_fragment_shading_rate) + CASE(E_GL_EXT_fragment_shading_rate_primitive) + CASE(E_GL_EXT_fragment_shading_rate_attachment) } str.erase(0, 2); return str; diff --git a/MobileGL/MG_Util/GLExtensions.h b/MobileGL/MG_Util/GLExtensions.h index 321e5bff..c8b74023 100644 --- a/MobileGL/MG_Util/GLExtensions.h +++ b/MobileGL/MG_Util/GLExtensions.h @@ -769,6 +769,9 @@ namespace MobileGL { E_GL_EXT_texture_storage, E_GL_EXT_framebuffer_blit_layers, E_GL_NV_uniform_buffer_std430_layout, - E_GL_MESA_texture_const_bandwidth + E_GL_MESA_texture_const_bandwidth, + E_GL_EXT_fragment_shading_rate, + E_GL_EXT_fragment_shading_rate_primitive, + E_GL_EXT_fragment_shading_rate_attachment }; } // namespace MobileGL diff --git a/scripts/gen_pipe.py b/scripts/gen_pipe.py index bf9f98e9..cc758e5a 100644 --- a/scripts/gen_pipe.py +++ b/scripts/gen_pipe.py @@ -97,6 +97,9 @@ PIPELINE_STATE_MEMBERS = [ "TextureCubeMapSeamlessEnabled", "SampleCoverageValue", "SampleCoverageInvert", + "ShadingRate", + "ShadingRateCombinerOp0", + "ShadingRateCombinerOp1", "SampleMaskValue", "MinSampleShadingValue", "StencilStates",