[Feat] (Frontend): add GL_EXT_fragment_shading_rate state, entry points and getters

This commit is contained in:
2026-10-12 08:03:27 -04:00
parent ba6db60c09
commit 7e544c2b78
22 changed files with 567 additions and 9 deletions
+31
View File
@@ -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 {
@@ -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)
@@ -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<GenericErrorInfo>("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<GLint>(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<GenericErrorInfo>("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;
@@ -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);
@@ -9,6 +9,7 @@
#include "GL_RenderState.h"
#include <cmath>
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
#include <MG_Backend/BackendObjects.h>
#include <MG_State/GLState/Core.h>
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
#include <MG_Util/Converters/GLToMG/RenderStateEnumConverter.h>
@@ -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<GenericErrorInfo>("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<GenericErrorInfo>(
"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<GenericErrorInfo>("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);
}
@@ -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);
+3 -3
View File
@@ -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);
+2
View File
@@ -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 */ \
+3 -2
View File
@@ -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,
+59
View File
@@ -323,6 +323,17 @@ namespace MobileGL {
Array<FloatVec2, MAX_VIEWPORTS> 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<Uint8>(op - GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT)
: kMGInvalidShadingRateEncoding;
}
constexpr GLenum MGShadingRateCombinerOpEnum(Uint8 index) {
return static_cast<GLenum>(GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT + index);
}
enum class SamplerFilterMode {
Nearest,
Linear,
+10 -2
View File
@@ -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)
+4 -1
View File
@@ -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]));
@@ -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)},
+20
View File
@@ -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);
}
+5
View File
@@ -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;
@@ -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) \
@@ -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);
@@ -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); });
+174
View File
@@ -28,6 +28,7 @@
#include <limits>
#include <MG_Backend/BackendObjects.h>
#include <MG_Impl/GLImpl/Drawing/GL_Drawing.h>
#include <MG_Impl/GLImpl/Getter/GL_Getter.h>
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
@@ -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<FragmentShadingRateBackend>(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<MG_Backend::BackendObject> 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<GLenum>(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<GLenum>(GL_SHADING_RATE_4X4_PIXELS_EXT));
EXPECT_EQ(rates[1], static_cast<GLenum>(GL_SHADING_RATE_2X2_PIXELS_EXT));
EXPECT_EQ(rates[2], static_cast<GLenum>(GL_SHADING_RATE_2X1_PIXELS_EXT));
EXPECT_EQ(rates[3], static_cast<GLenum>(GL_SHADING_RATE_1X2_PIXELS_EXT));
EXPECT_EQ(rates[4], static_cast<GLenum>(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<GLenum>(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);
}
@@ -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;
+4 -1
View File
@@ -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
+3
View File
@@ -97,6 +97,9 @@ PIPELINE_STATE_MEMBERS = [
"TextureCubeMapSeamlessEnabled",
"SampleCoverageValue",
"SampleCoverageInvert",
"ShadingRate",
"ShadingRateCombinerOp0",
"ShadingRateCombinerOp1",
"SampleMaskValue",
"MinSampleShadingValue",
"StencilStates",