mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Feat] (Frontend): add GL_EXT_fragment_shading_rate state, entry points and getters
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */ \
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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); });
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -97,6 +97,9 @@ PIPELINE_STATE_MEMBERS = [
|
||||
"TextureCubeMapSeamlessEnabled",
|
||||
"SampleCoverageValue",
|
||||
"SampleCoverageInvert",
|
||||
"ShadingRate",
|
||||
"ShadingRateCombinerOp0",
|
||||
"ShadingRateCombinerOp1",
|
||||
"SampleMaskValue",
|
||||
"MinSampleShadingValue",
|
||||
"StencilStates",
|
||||
|
||||
Reference in New Issue
Block a user