mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Feat, Fix, Test] (MG_State, MG_Impl, MG_Backend, MG_Test): give ARB_viewport_array real 16-element indexed state instead of eight stubs and a viewport-0 echo
This commit is contained in:
@@ -1600,7 +1600,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
!g_hasSyncedRenderState || std::memcmp(currentBytes + kBlendSpanEnd, syncedBytes + kBlendSpanEnd,
|
||||
sizeof(RenderStateParameters) - kBlendSpanEnd) != 0;
|
||||
|
||||
IntVec4 backendViewport = parameters.Viewport;
|
||||
IntVec4 backendViewport = MG_State::pGLContext->GetViewport();
|
||||
if (backendViewport.z() <= 0 || backendViewport.w() <= 0) {
|
||||
Int surfaceWidth = 0;
|
||||
Int surfaceHeight = 0;
|
||||
@@ -1633,11 +1633,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
SYNC_CAPABILITY(SampleMask, GL_SAMPLE_MASK);
|
||||
SYNC_CAPABILITY(PolygonOffsetFill, GL_POLYGON_OFFSET_FILL);
|
||||
SYNC_CAPABILITY(RasterizerDiscard, GL_RASTERIZER_DISCARD);
|
||||
SYNC_CAPABILITY(ScissorTest, GL_SCISSOR_TEST);
|
||||
SYNC_CAPABILITY(StencilTest, GL_STENCIL_TEST);
|
||||
SYNC_CAPABILITY(CullFace, GL_CULL_FACE);
|
||||
|
||||
#undef SYNC_CAPABILITY
|
||||
|
||||
// GL_SCISSOR_TEST is per-viewport enable state (ARB_viewport_array), so it is a
|
||||
// 16-bit mask and not a "<Name>Enabled" bool the macro above could key off. ES
|
||||
// has exactly one scissor rectangle and one scissor enable, so only bit 0 - the
|
||||
// index every ES draw rasterizes against - can be forwarded; a program that
|
||||
// enables the test for viewport 3 alone gets viewport 0's answer here. That is
|
||||
// the same limitation as the unemulated gl_ViewportIndex on this backend and is
|
||||
// why the multi-viewport half of KHR-GL43.viewport_array stays red on Espryt.
|
||||
{
|
||||
const Bool scissorTest = (parameters.ScissorTestEnabledMask & 1u) != 0;
|
||||
const Bool syncedScissorTest =
|
||||
(g_syncedRenderStateParameters.ScissorTestEnabledMask & 1u) != 0;
|
||||
if (forceFullPush || scissorTest != syncedScissorTest) {
|
||||
scissorTest ? g_GLESFuncs.glEnable(GL_SCISSOR_TEST) : g_GLESFuncs.glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tailSpanDirty && g_GLESCapabilities.SupportsClipDistance) {
|
||||
@@ -1864,8 +1879,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (forceFullPush || parameters.DepthMask != g_syncedRenderStateParameters.DepthMask) {
|
||||
g_GLESFuncs.glDepthMask(parameters.DepthMask ? GL_TRUE : GL_FALSE);
|
||||
}
|
||||
if (forceFullPush || parameters.DepthRange != g_syncedRenderStateParameters.DepthRange) {
|
||||
g_GLESFuncs.glDepthRangef(parameters.DepthRange.x(), parameters.DepthRange.y());
|
||||
if (forceFullPush || parameters.DepthRanges[0] != g_syncedRenderStateParameters.DepthRanges[0]) {
|
||||
g_GLESFuncs.glDepthRangef(parameters.DepthRanges[0].x(), parameters.DepthRanges[0].y());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2003,7 +2018,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// everything drawn with GL_SCISSOR_TEST enabled before the app's first glScissor
|
||||
// is clipped away - Minecraft 26.2 keeps only its unscissored sky and hand and
|
||||
// loses the terrain and the whole GUI.
|
||||
IntVec4 backendScissorBox = parameters.ScissorBox;
|
||||
IntVec4 backendScissorBox = parameters.ScissorBoxes[0];
|
||||
if (backendScissorBox.z() <= 0 || backendScissorBox.w() <= 0) {
|
||||
Int surfaceWidth = 0;
|
||||
Int surfaceHeight = 0;
|
||||
@@ -4762,7 +4777,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// restores the app state on exit, tracked via the render-state shadow.
|
||||
class ScopedScissorDisable {
|
||||
public:
|
||||
ScopedScissorDisable() : m_wasEnabled(RenderStateImpl::g_syncedRenderStateParameters.ScissorTestEnabled) {
|
||||
ScopedScissorDisable()
|
||||
: m_wasEnabled((RenderStateImpl::g_syncedRenderStateParameters.ScissorTestEnabledMask & 1u) != 0) {
|
||||
if (m_wasEnabled) g_GLESFuncs.glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
~ScopedScissorDisable() {
|
||||
|
||||
@@ -335,19 +335,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// Complete input inventory of ApplyDynamicDrawStateTail, one line per reader
|
||||
// (each accessor it replaces is a verified plain field read of the same
|
||||
// RenderStateParameters field - RenderState.cpp):
|
||||
// ApplyGLViewportState : Viewport, DepthRange, + extent/isDefaultFbo/preTransform
|
||||
// ApplyGLViewportState : Viewports[0], DepthRanges[0], + extent/isDefaultFbo/preTransform
|
||||
// ApplyBlendConstants : BlendColor
|
||||
// ApplyPolygonOffsetState : PolygonOffsetUnits, PolygonOffsetFactor
|
||||
// ApplyLineWidthState : LineWidth (see the caveat below)
|
||||
// ApplyStencilState : StencilStates[0..1].{ValueMask, WriteMask, Ref}
|
||||
// scissor rect : ScissorTestEnabled, ScissorBox,
|
||||
// scissor rect : ScissorTestEnabledMask bit 0, ScissorBoxes[0],
|
||||
// + extent/isDefaultFbo/preTransform
|
||||
// Caveat, unchanged from the version-only gate: ApplyLineWidthState also clamps
|
||||
// to the ACTIVE BACKEND OBJECT's aliased line-width range. Those are device
|
||||
// limits queried once at backend init and constant for the renderer's lifetime,
|
||||
// so they are not part of the key (the version gate never covered them either).
|
||||
struct DynamicTailKey {
|
||||
Int viewport[4] = {0, 0, 0, 0};
|
||||
Float viewport[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
Float depthRange[2] = {0.0f, 0.0f};
|
||||
Float blendColor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
Float polygonOffsetFactor = 0.0f;
|
||||
@@ -5342,12 +5342,14 @@ void main() {
|
||||
DynamicStateShadow::DynamicTailKey key;
|
||||
{
|
||||
const RenderStateParameters& p = MG_State::pGLContext->GetRenderStateParameters();
|
||||
key.viewport[0] = p.Viewport.x();
|
||||
key.viewport[1] = p.Viewport.y();
|
||||
key.viewport[2] = p.Viewport.z();
|
||||
key.viewport[3] = p.Viewport.w();
|
||||
key.depthRange[0] = p.DepthRange.x();
|
||||
key.depthRange[1] = p.DepthRange.y();
|
||||
// Viewport 0 and its depth range: ApplyGLViewportState reads exactly those two
|
||||
// (per-index state for indices > 0 is keyed separately, see multiViewportKey below).
|
||||
key.viewport[0] = p.Viewports[0].x();
|
||||
key.viewport[1] = p.Viewports[0].y();
|
||||
key.viewport[2] = p.Viewports[0].z();
|
||||
key.viewport[3] = p.Viewports[0].w();
|
||||
key.depthRange[0] = p.DepthRanges[0].x();
|
||||
key.depthRange[1] = p.DepthRanges[0].y();
|
||||
key.blendColor[0] = p.BlendColor.x();
|
||||
key.blendColor[1] = p.BlendColor.y();
|
||||
key.blendColor[2] = p.BlendColor.z();
|
||||
@@ -5362,11 +5364,11 @@ void main() {
|
||||
key.stencilWriteMask[face] = p.StencilStates[face].WriteMask;
|
||||
key.stencilRef[face] = p.StencilStates[face].Ref;
|
||||
}
|
||||
key.scissorEnabled = p.ScissorTestEnabled;
|
||||
key.scissorBox[0] = p.ScissorBox.x();
|
||||
key.scissorBox[1] = p.ScissorBox.y();
|
||||
key.scissorBox[2] = p.ScissorBox.z();
|
||||
key.scissorBox[3] = p.ScissorBox.w();
|
||||
key.scissorEnabled = (p.ScissorTestEnabledMask & 1u) != 0;
|
||||
key.scissorBox[0] = p.ScissorBoxes[0].x();
|
||||
key.scissorBox[1] = p.ScissorBoxes[0].y();
|
||||
key.scissorBox[2] = p.ScissorBoxes[0].z();
|
||||
key.scissorBox[3] = p.ScissorBoxes[0].w();
|
||||
key.extentX = extent.x();
|
||||
key.extentY = extent.y();
|
||||
key.preTransform = static_cast<Uint32>(preTransform);
|
||||
|
||||
@@ -969,14 +969,14 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL3dv, GLuint index, const GLdoub
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribL4dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribL4dv, index, v)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribLPointer, GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribLPointer, index, size, type, stride, pointer)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetVertexAttribLdv, GLuint index, GLenum pname, GLdouble* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetVertexAttribLdv, index, pname, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ViewportArrayv, GLuint first, GLsizei count, const GLfloat* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ViewportArrayv, first, count, v)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ViewportIndexedf, GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ViewportIndexedf, index, x, y, w, h)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ViewportIndexedfv, GLuint index, const GLfloat* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ViewportIndexedfv, index, v)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ScissorArrayv, GLuint first, GLsizei count, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ScissorArrayv, first, count, v)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ScissorIndexed, GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ScissorIndexed, index, left, bottom, width, height)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ScissorIndexedv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ScissorIndexedv, index, v)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DepthRangeArrayv, GLuint first, GLsizei count, const GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DepthRangeArrayv, first, count, v)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DepthRangeIndexed, GLuint index, GLdouble n, GLdouble f) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DepthRangeIndexed, index, n, f)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ViewportArrayv, GLuint first, GLsizei count, const GLfloat* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ViewportArrayv, first, count, v)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ViewportIndexedf, GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ViewportIndexedf, index, x, y, w, h)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ViewportIndexedfv, GLuint index, const GLfloat* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ViewportIndexedfv, index, v)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ScissorArrayv, GLuint first, GLsizei count, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ScissorArrayv, first, count, v)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ScissorIndexed, GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ScissorIndexed, index, left, bottom, width, height)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ScissorIndexedv, GLuint index, const GLint* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ScissorIndexedv, index, v)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DepthRangeArrayv, GLuint first, GLsizei count, const GLdouble* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DepthRangeArrayv, first, count, v)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DepthRangeIndexed, GLuint index, GLdouble n, GLdouble f) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DepthRangeIndexed, index, n, f)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetFloati_v, GLenum target, GLuint index, GLfloat* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetFloati_v, target, index, data)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetDoublei_v, GLenum target, GLuint index, GLdouble* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetDoublei_v, target, index, data)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, DrawArraysInstancedBaseInstance, GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawArraysInstancedBaseInstance, mode, first, count, instancecount, baseinstance)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <MG_State/GLState/ErrorState/ErrorInfo.h>
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
#include <MG_Util/Converters/GLToMG/BufferEnumConverter.h>
|
||||
#include <MG_Util/Converters/GLToMG/RenderStateEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/ErrorCodeConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||
@@ -27,6 +28,11 @@
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
// Declared rather than #included from GL_RenderState.h on purpose: that header also declares
|
||||
// a free function named BlendEquation, which would hide the ::MobileGL::BlendEquation enum
|
||||
// this file's blend-state queries name unqualified.
|
||||
GLboolean IsEnabledi(GLenum target, GLuint index);
|
||||
|
||||
namespace {
|
||||
enum class IndexedBufferQueryKind {
|
||||
Binding,
|
||||
@@ -339,26 +345,70 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return sampler ? static_cast<GLint>(sampler->GetExternalIndex()) : 0;
|
||||
}
|
||||
|
||||
// The ARB_viewport_array indexed rectangles. MobileGL keeps exactly one viewport, one
|
||||
// scissor box and one depth range, so every in-range index answers with that single
|
||||
// value - but it has to come from the frontend state the non-indexed getters read.
|
||||
// The generic path at the bottom of GetIntegeri_v is a raw backend passthrough that
|
||||
// has no case for these, so routing them through it returned zeros.
|
||||
// The ARB_viewport_array indexed rectangles. Each of these is genuinely per-viewport
|
||||
// frontend state (RenderStateParameters::Viewports / ScissorBoxes / DepthRanges), so the
|
||||
// indexed getters must read the indexed storage - the generic path at the bottom of
|
||||
// GetIntegeri_v is a raw backend passthrough that has no case for them and returned
|
||||
// zeros, and routing them to the NON-indexed getter (what this used to do) answered every
|
||||
// index with viewport 0's value, which is what
|
||||
// KHR-GL43.viewport_array.{viewport,scissor,depth_range}_api caught.
|
||||
Bool IsIndexedViewportQuery(GLenum target) {
|
||||
return target == GL_VIEWPORT || target == GL_SCISSOR_BOX || target == GL_DEPTH_RANGE;
|
||||
}
|
||||
|
||||
// ARB_viewport_array: `index` selects a viewport and MAX_VIEWPORTS bounds it.
|
||||
// Component count of an indexed viewport-array query, so every width of getter writes the
|
||||
// caller's whole buffer instead of just element 0 (GL 4.6 core 22.1).
|
||||
GLsizei IndexedViewportQueryComponents(GLenum target) {
|
||||
return target == GL_DEPTH_RANGE ? 2 : 4;
|
||||
}
|
||||
|
||||
// ARB_viewport_array: `index` selects a viewport and MAX_VIEWPORTS bounds it. The bound is
|
||||
// the frontend's own state width, which is also exactly what GL_MAX_VIEWPORTS reports -
|
||||
// taking it from the backend caps instead would let a device limit of 1 (a Vulkan device
|
||||
// without the multiViewport feature) make index 1 illegal even though the state exists.
|
||||
Bool ValidateViewportQueryIndex(GLuint index, const char* caller) {
|
||||
GLint maxViewports = 0;
|
||||
GetIntegerv(GL_MAX_VIEWPORTS, &maxViewports);
|
||||
if (index < static_cast<GLuint>(std::max(maxViewports, 1))) return true;
|
||||
if (index < RenderStateParameters::MAX_VIEWPORTS) return true;
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "Viewport index is out of range."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// The indexed viewport/scissor/depth-range state as floats, which is the widest lossless
|
||||
// shape MobileGL stores (the viewport really is float state; the scissor box is integral
|
||||
// and well inside float's exact range, and every depth range is in [0, 1]). Every indexed
|
||||
// getter width funnels through this so they can never disagree with each other.
|
||||
void ReadIndexedViewportStateFloat(GLenum target, GLuint index, GLfloat* out) {
|
||||
switch (target) {
|
||||
case GL_VIEWPORT: {
|
||||
const FloatVec4& viewport = MG_State::pGLContext->GetViewportIndexed(index);
|
||||
out[0] = viewport.x();
|
||||
out[1] = viewport.y();
|
||||
out[2] = viewport.z();
|
||||
out[3] = viewport.w();
|
||||
return;
|
||||
}
|
||||
case GL_SCISSOR_BOX: {
|
||||
const IntVec4& box = MG_State::pGLContext->GetScissorBoxIndexed(index);
|
||||
out[0] = static_cast<GLfloat>(box.x());
|
||||
out[1] = static_cast<GLfloat>(box.y());
|
||||
out[2] = static_cast<GLfloat>(box.z());
|
||||
out[3] = static_cast<GLfloat>(box.w());
|
||||
return;
|
||||
}
|
||||
case GL_DEPTH_RANGE: {
|
||||
const FloatVec2& range = MG_State::pGLContext->GetDepthRangeIndexed(index);
|
||||
out[0] = range.x();
|
||||
out[1] = range.y();
|
||||
return;
|
||||
}
|
||||
default:
|
||||
MOBILEGL_ASSERT(false, "ReadIndexedViewportStateFloat: unexpected target 0x%x",
|
||||
static_cast<Uint32>(target));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CopyIntsToBooleans(const GLint* src, SizeT count, GLboolean* dst) {
|
||||
for (SizeT i = 0; i < count; ++i) {
|
||||
dst[i] = src[i] ? GL_TRUE : GL_FALSE;
|
||||
@@ -629,6 +679,17 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
params[1] = dynamicParameters.ViewportBoundsRangeMax;
|
||||
return;
|
||||
}
|
||||
// Viewport 0's rectangle, verbatim. Falling through to the integer width below would
|
||||
// round the fractional rectangle a glViewportIndexedf(0, ...) is allowed to set, and
|
||||
// glGetFloatv(GL_VIEWPORT) is a lossless query of float state.
|
||||
case GL_VIEWPORT: {
|
||||
const FloatVec4& viewport = MG_State::pGLContext->GetViewportIndexed(0);
|
||||
params[0] = viewport.x();
|
||||
params[1] = viewport.y();
|
||||
params[2] = viewport.z();
|
||||
params[3] = viewport.w();
|
||||
return;
|
||||
}
|
||||
case GL_MIN_FRAGMENT_INTERPOLATION_OFFSET:
|
||||
case GL_MAX_FRAGMENT_INTERPOLATION_OFFSET:
|
||||
case GL_FRAGMENT_INTERPOLATION_OFFSET_BITS: {
|
||||
@@ -792,15 +853,32 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return;
|
||||
}
|
||||
|
||||
// GL 4.6 core 22.1: an indexed query answers EVERY indexed state, and GL_SCISSOR_TEST is
|
||||
// indexed by viewport just like GL_BLEND is by draw buffer. Without this the integer
|
||||
// width fell through to the backend passthrough and answered GL_INVALID_ENUM, which is
|
||||
// the sticky error KHR-GL43.viewport_array.queries trips over at its next error check.
|
||||
if (MG_Util::ConvertGLEnumToCapabilityInput(target) != CapabilityInput::Unknown) {
|
||||
*data = IsEnabledi(target, index);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
// ARB_viewport_array queries the indexed rectangles through glGetIntegeri_v as well
|
||||
// (gl4cMultiBindTests and the viewport_array group both do). The frontend keeps one
|
||||
// viewport and one scissor box, so every in-range index reports that one.
|
||||
// (gl4cMultiBindTests and the viewport_array group both do).
|
||||
case GL_VIEWPORT:
|
||||
case GL_SCISSOR_BOX:
|
||||
case GL_DEPTH_RANGE: {
|
||||
if (!ValidateViewportQueryIndex(index, __func__)) return;
|
||||
GetIntegerv(target, data);
|
||||
GLfloat values[4] = {};
|
||||
ReadIndexedViewportStateFloat(target, index, values);
|
||||
const GLsizei components = IndexedViewportQueryComponents(target);
|
||||
for (GLsizei i = 0; i < components; ++i) {
|
||||
// Round, not truncate: glGetIntegerv on floating-point state rounds to nearest
|
||||
// (GL 4.6 core 22.2), so a 255.875-wide viewport reads back as 256 and not 255.
|
||||
data[i] = static_cast<GLint>(std::lround(values[i]));
|
||||
}
|
||||
return;
|
||||
}
|
||||
// The vertex buffer binding points of the vertex array object that is bound. Indexed by
|
||||
// binding point, not by attribute (GL 4.6 core 10.3.1).
|
||||
case GL_VERTEX_BINDING_BUFFER:
|
||||
@@ -927,7 +1005,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
if (IsIndexedViewportQuery(target)) {
|
||||
if (!ValidateViewportQueryIndex(index, __func__)) return;
|
||||
GetFloatv(target, data);
|
||||
// Verbatim, NOT via the integer width: the viewport is float state and
|
||||
// KHR-GL43.viewport_array.viewport_api compares the read-back with ==, so a
|
||||
// glViewportIndexedf(i, 0.125f, ...) has to come back as 0.125f exactly.
|
||||
ReadIndexedViewportStateFloat(target, index, data);
|
||||
return;
|
||||
}
|
||||
GLint ints[4] = {};
|
||||
@@ -944,7 +1025,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
if (IsIndexedViewportQuery(target)) {
|
||||
if (!ValidateViewportQueryIndex(index, __func__)) return;
|
||||
GetDoublev(target, data);
|
||||
GLfloat values[4] = {};
|
||||
ReadIndexedViewportStateFloat(target, index, values);
|
||||
const GLsizei components = IndexedViewportQueryComponents(target);
|
||||
for (GLsizei i = 0; i < components; ++i) {
|
||||
data[i] = static_cast<GLdouble>(values[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
GLint ints[4] = {};
|
||||
@@ -1020,7 +1106,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// frontend-only value simply is not in the driver's table.
|
||||
GLint values[4] = {};
|
||||
GetIntegeri_v(target, index, values);
|
||||
*data = static_cast<GLint64>(values[0]);
|
||||
// The viewport-array rectangles are the only multi-component indexed state here; every
|
||||
// other pname is scalar, so widening element 0 alone would silently truncate them.
|
||||
const GLsizei components = IsIndexedViewportQuery(target) ? IndexedViewportQueryComponents(target) : 1;
|
||||
for (GLsizei i = 0; i < components; ++i) {
|
||||
data[i] = static_cast<GLint64>(values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void GetInteger64v(GLenum pname, GLint64* params) {
|
||||
@@ -2192,7 +2283,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
params[1] = dynamicParameters.MaxViewportHeight;
|
||||
break;
|
||||
case GL_MAX_VIEWPORTS:
|
||||
*params = dynamicParameters.MaxViewports;
|
||||
// The frontend's own state width, not the backend's device limit. GL 4.3 core
|
||||
// requires MAX_VIEWPORTS >= 16 and every indexed viewport entry point validates
|
||||
// against RenderStateParameters::MAX_VIEWPORTS, so reporting anything else would
|
||||
// either advertise viewports the state cannot hold or reject indices it can. A
|
||||
// Vulkan device without the multiViewport feature reports maxViewports == 1, which
|
||||
// limits what can be RASTERIZED to more than one rectangle (see the multiViewport
|
||||
// gate in VulkanRenderer), not what the GL state can hold; caps.MaxViewports keeps
|
||||
// carrying that device number for exactly that decision.
|
||||
*params = static_cast<GLint>(RenderStateParameters::MAX_VIEWPORTS);
|
||||
break;
|
||||
case GL_MINOR_VERSION:
|
||||
*params = rendererInfo.RendererGLInfo.TargetGLVersion.Minor;
|
||||
|
||||
@@ -20,28 +20,118 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return std::clamp(static_cast<Float>(value), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
static Bool ValidateIndexedBlendCapability(GLenum target, GLuint index, const char* functionName) {
|
||||
if (target != GL_BLEND) {
|
||||
// GL 4.6 core 17.3.2 and 22.1 give exactly two indexed capabilities: GL_BLEND, indexed by
|
||||
// draw buffer, and GL_SCISSOR_TEST, indexed by viewport. They have DIFFERENT bounds
|
||||
// (MAX_DRAW_BUFFERS vs MAX_VIEWPORTS), so the limit is picked per target rather than shared.
|
||||
static Bool ValidateIndexedCapability(GLenum target, GLuint index, const char* functionName) {
|
||||
GLuint limit = 0;
|
||||
const char* indexName = nullptr;
|
||||
switch (target) {
|
||||
case GL_BLEND:
|
||||
limit = MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS;
|
||||
indexName = "Buffer";
|
||||
break;
|
||||
case GL_SCISSOR_TEST:
|
||||
limit = RenderStateParameters::MAX_VIEWPORTS;
|
||||
indexName = "Viewport";
|
||||
break;
|
||||
default:
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidEnum,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"Only GL_BLEND is supported for indexed capability state."));
|
||||
"Only GL_BLEND and GL_SCISSOR_TEST are supported for indexed "
|
||||
"capability state."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) {
|
||||
if (index >= limit) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>(
|
||||
"MG_Impl/GLImpl", functionName,
|
||||
"Buffer index " + std::to_string(index) + " is out of range. Max supported is " +
|
||||
std::to_string(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS - 1) + "."));
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
String(indexName) + " index " + std::to_string(index) +
|
||||
" is out of range. Max supported is " + std::to_string(limit - 1) +
|
||||
"."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------ ARB_viewport_array parameter validation ------------------
|
||||
// All three families share the same two shapes, so they share the two checkers. GL 4.6 core
|
||||
// 13.6.1/17.3.2: an out-of-range index is GL_INVALID_VALUE, and so is a negative width or
|
||||
// height. `first + count == MAX_VIEWPORTS` is LEGAL - only strictly greater is an error,
|
||||
// which KHR-GL43.viewport_array.api_errors checks explicitly in both directions.
|
||||
static Bool ValidateViewportIndex(GLuint index, const char* functionName) {
|
||||
if (index < RenderStateParameters::MAX_VIEWPORTS) return true;
|
||||
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"Viewport index " + std::to_string(index) +
|
||||
" is out of range. Max supported is " +
|
||||
std::to_string(RenderStateParameters::MAX_VIEWPORTS - 1) + "."));
|
||||
return false;
|
||||
}
|
||||
|
||||
static Bool ValidateViewportRange(GLuint first, GLsizei count, const char* functionName) {
|
||||
if (count < 0) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "count must not be negative."));
|
||||
return false;
|
||||
}
|
||||
// Widened before adding: first is a GLuint and count a GLsizei, so `first + count` in
|
||||
// 32 bits can wrap past MAX_VIEWPORTS and let an out-of-range range through.
|
||||
const Uint64 last = static_cast<Uint64>(first) + static_cast<Uint64>(count);
|
||||
if (last > RenderStateParameters::MAX_VIEWPORTS) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"first (" + std::to_string(first) + ") + count (" +
|
||||
std::to_string(count) + ") exceeds GL_MAX_VIEWPORTS (" +
|
||||
std::to_string(RenderStateParameters::MAX_VIEWPORTS) + ")."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static Bool ValidateNonNegativeExtent(T width, T height, const char* functionName) {
|
||||
if (width >= T(0) && height >= T(0)) return true;
|
||||
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "Width and height must be non-negative."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// The array forms are all-or-nothing: one bad element rejects the whole call with a SINGLE
|
||||
// GL_INVALID_VALUE and leaves every rectangle untouched. api_errors relies on both halves -
|
||||
// it passes a full 16-element array with exactly one negative extent and then asserts the
|
||||
// error queue holds exactly one entry.
|
||||
template <typename T>
|
||||
static Bool ValidateArrayExtents(GLsizei count, const T* v, const char* functionName) {
|
||||
for (GLsizei i = 0; i < count; ++i) {
|
||||
if (v[i * 4 + 2] >= T(0) && v[i * 4 + 3] >= T(0)) continue;
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||
"Width and height must be non-negative (element " + std::to_string(i) +
|
||||
")."));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Bool ValidateNonNullArray(const void* v, const char* functionName) {
|
||||
if (v != nullptr) return true;
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName, "value pointer cannot be null."));
|
||||
return false;
|
||||
}
|
||||
|
||||
static Bool TryConvertBlendEquation(GLenum mode, const char* functionName,
|
||||
::MobileGL::BlendEquation& outEquation) {
|
||||
outEquation = MG_Util::ConvertGLEnumToBlendEquation(mode);
|
||||
@@ -93,16 +183,70 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
void Viewport_State(GLint x, GLint y, GLsizei width, GLsizei height) {
|
||||
if (width < 0 || height < 0) {
|
||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "Viewport_State",
|
||||
"Width abd height must be non-negative."));
|
||||
return;
|
||||
}
|
||||
if (!ValidateNonNegativeExtent(width, height, "Viewport_State")) return;
|
||||
|
||||
MG_State::pGLContext->SetViewport(IntVec4(x, y, width, height));
|
||||
}
|
||||
|
||||
// ------------------ ARB_viewport_array setters ------------------
|
||||
void ViewportArrayv_State(GLuint first, GLsizei count, const GLfloat* v) {
|
||||
if (!ValidateViewportRange(first, count, "ViewportArrayv_State")) return;
|
||||
if (count == 0) return;
|
||||
if (!ValidateNonNullArray(v, "ViewportArrayv_State")) return;
|
||||
if (!ValidateArrayExtents(count, v, "ViewportArrayv_State")) return;
|
||||
|
||||
for (GLsizei i = 0; i < count; ++i) {
|
||||
MG_State::pGLContext->SetViewportIndexed(first + static_cast<GLuint>(i),
|
||||
FloatVec4(v[i * 4 + 0], v[i * 4 + 1], v[i * 4 + 2], v[i * 4 + 3]));
|
||||
}
|
||||
}
|
||||
|
||||
void ViewportIndexedf_State(GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h) {
|
||||
if (!ValidateViewportIndex(index, "ViewportIndexedf_State")) return;
|
||||
if (!ValidateNonNegativeExtent(w, h, "ViewportIndexedf_State")) return;
|
||||
|
||||
MG_State::pGLContext->SetViewportIndexed(index, FloatVec4(x, y, w, h));
|
||||
}
|
||||
|
||||
void ScissorArrayv_State(GLuint first, GLsizei count, const GLint* v) {
|
||||
if (!ValidateViewportRange(first, count, "ScissorArrayv_State")) return;
|
||||
if (count == 0) return;
|
||||
if (!ValidateNonNullArray(v, "ScissorArrayv_State")) return;
|
||||
if (!ValidateArrayExtents(count, v, "ScissorArrayv_State")) return;
|
||||
|
||||
for (GLsizei i = 0; i < count; ++i) {
|
||||
MG_State::pGLContext->SetScissorBoxIndexed(first + static_cast<GLuint>(i),
|
||||
IntVec4(v[i * 4 + 0], v[i * 4 + 1], v[i * 4 + 2], v[i * 4 + 3]));
|
||||
}
|
||||
}
|
||||
|
||||
void ScissorIndexed_State(GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height) {
|
||||
if (!ValidateViewportIndex(index, "ScissorIndexed_State")) return;
|
||||
if (!ValidateNonNegativeExtent(width, height, "ScissorIndexed_State")) return;
|
||||
|
||||
MG_State::pGLContext->SetScissorBoxIndexed(index, IntVec4(left, bottom, width, height));
|
||||
}
|
||||
|
||||
void DepthRangeArrayv_State(GLuint first, GLsizei count, const GLdouble* v) {
|
||||
if (!ValidateViewportRange(first, count, "DepthRangeArrayv_State")) return;
|
||||
if (count == 0) return;
|
||||
if (!ValidateNonNullArray(v, "DepthRangeArrayv_State")) return;
|
||||
|
||||
for (GLsizei i = 0; i < count; ++i) {
|
||||
MG_State::pGLContext->SetDepthRangeIndexed(
|
||||
first + static_cast<GLuint>(i),
|
||||
FloatVec2(ClampUnitFloat(static_cast<GLfloat>(v[i * 2 + 0])),
|
||||
ClampUnitFloat(static_cast<GLfloat>(v[i * 2 + 1]))));
|
||||
}
|
||||
}
|
||||
|
||||
void DepthRangeIndexed_State(GLuint index, GLdouble n, GLdouble f) {
|
||||
if (!ValidateViewportIndex(index, "DepthRangeIndexed_State")) return;
|
||||
|
||||
MG_State::pGLContext->SetDepthRangeIndexed(
|
||||
index, FloatVec2(ClampUnitFloat(static_cast<GLfloat>(n)), ClampUnitFloat(static_cast<GLfloat>(f))));
|
||||
}
|
||||
|
||||
void StencilOpSeparate_State(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass) {
|
||||
Bool applyFront = false;
|
||||
Bool applyBack = false;
|
||||
@@ -175,12 +319,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
void Scissor_State(GLint x, GLint y, GLsizei width, GLsizei height) {
|
||||
if (width < 0 || height < 0) {
|
||||
MG_State::pGLContext->RecordError(ErrorCode::InvalidValue,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "Scissor_State",
|
||||
"Width abd height must be non-negative."));
|
||||
return;
|
||||
}
|
||||
if (!ValidateNonNegativeExtent(width, height, "Scissor_State")) return;
|
||||
|
||||
MG_State::pGLContext->SetScissorBox(IntVec4(x, y, width, height));
|
||||
}
|
||||
@@ -336,7 +475,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
GLboolean IsEnabledi_State(GLenum target, GLuint index) {
|
||||
if (!ValidateIndexedBlendCapability(target, index, "IsEnabledi_State")) {
|
||||
if (!ValidateIndexedCapability(target, index, "IsEnabledi_State")) {
|
||||
return GL_FALSE;
|
||||
}
|
||||
|
||||
@@ -392,7 +531,14 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
GLint values[4] = {};
|
||||
GetIntegeri_v(target, index, values);
|
||||
*data = values[0] != 0 ? GL_TRUE : GL_FALSE;
|
||||
// The ARB_viewport_array rectangles are the only multi-component indexed state that
|
||||
// reaches here; writing element 0 alone would leave the caller's other three untouched.
|
||||
const GLsizei components = target == GL_VIEWPORT || target == GL_SCISSOR_BOX
|
||||
? 4
|
||||
: (target == GL_DEPTH_RANGE ? 2 : 1);
|
||||
for (GLsizei i = 0; i < components; ++i) {
|
||||
data[i] = values[i] != 0 ? GL_TRUE : GL_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
GLboolean IsEnabled_State(GLenum cap) {
|
||||
@@ -725,7 +871,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
void Disablei_State(GLenum target, GLuint index) {
|
||||
if (!ValidateIndexedBlendCapability(target, index, "Disablei_State")) {
|
||||
if (!ValidateIndexedCapability(target, index, "Disablei_State")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -743,7 +889,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
void Enablei_State(GLenum target, GLuint index) {
|
||||
if (!ValidateIndexedBlendCapability(target, index, "Enablei_State")) {
|
||||
if (!ValidateIndexedCapability(target, index, "Enablei_State")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -797,6 +943,44 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
Viewport_State(x, y, width, height);
|
||||
}
|
||||
|
||||
void ViewportArrayv(GLuint first, GLsizei count, const GLfloat* v) {
|
||||
ViewportArrayv_State(first, count, v);
|
||||
}
|
||||
|
||||
void ViewportIndexedf(GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h) {
|
||||
ViewportIndexedf_State(index, x, y, w, h);
|
||||
}
|
||||
|
||||
void ViewportIndexedfv(GLuint index, const GLfloat* v) {
|
||||
// The index is validated before the pointer is touched: glViewportIndexedfv(MAX, nullptr)
|
||||
// must be one GL_INVALID_VALUE, not a null dereference.
|
||||
if (!ValidateViewportIndex(index, "ViewportIndexedfv")) return;
|
||||
if (!ValidateNonNullArray(v, "ViewportIndexedfv")) return;
|
||||
ViewportIndexedf_State(index, v[0], v[1], v[2], v[3]);
|
||||
}
|
||||
|
||||
void ScissorArrayv(GLuint first, GLsizei count, const GLint* v) {
|
||||
ScissorArrayv_State(first, count, v);
|
||||
}
|
||||
|
||||
void ScissorIndexed(GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height) {
|
||||
ScissorIndexed_State(index, left, bottom, width, height);
|
||||
}
|
||||
|
||||
void ScissorIndexedv(GLuint index, const GLint* v) {
|
||||
if (!ValidateViewportIndex(index, "ScissorIndexedv")) return;
|
||||
if (!ValidateNonNullArray(v, "ScissorIndexedv")) return;
|
||||
ScissorIndexed_State(index, v[0], v[1], v[2], v[3]);
|
||||
}
|
||||
|
||||
void DepthRangeArrayv(GLuint first, GLsizei count, const GLdouble* v) {
|
||||
DepthRangeArrayv_State(first, count, v);
|
||||
}
|
||||
|
||||
void DepthRangeIndexed(GLuint index, GLdouble n, GLdouble f) {
|
||||
DepthRangeIndexed_State(index, n, f);
|
||||
}
|
||||
|
||||
void StencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass) {
|
||||
StencilOpSeparate_State(face, sfail, dpfail, dppass);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void Enablei(GLenum target, GLuint index);
|
||||
void BlendFunc(GLenum sfactor, GLenum dfactor);
|
||||
void Viewport(GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
// ARB_viewport_array (core since GL 4.1). Every one of these addresses the same 16-element
|
||||
// indexed state the classic glViewport/glScissor/glDepthRange trio broadcasts to.
|
||||
void ViewportArrayv(GLuint first, GLsizei count, const GLfloat* v);
|
||||
void ViewportIndexedf(GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h);
|
||||
void ViewportIndexedfv(GLuint index, const GLfloat* v);
|
||||
void ScissorArrayv(GLuint first, GLsizei count, const GLint* v);
|
||||
void ScissorIndexed(GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height);
|
||||
void ScissorIndexedv(GLuint index, const GLint* v);
|
||||
void DepthRangeArrayv(GLuint first, GLsizei count, const GLdouble* v);
|
||||
void DepthRangeIndexed(GLuint index, GLdouble n, GLdouble f);
|
||||
void StencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass);
|
||||
void StencilOp(GLenum fail, GLenum zfail, GLenum zpass);
|
||||
void StencilMaskSeparate(GLenum face, GLuint mask);
|
||||
|
||||
@@ -712,10 +712,18 @@ namespace MobileGL::MG_State {
|
||||
m_renderState.SetViewport(viewport);
|
||||
}
|
||||
|
||||
const IntVec4& GLContext::GetViewport() const {
|
||||
IntVec4 GLContext::GetViewport() const {
|
||||
return m_renderState.GetViewport();
|
||||
}
|
||||
|
||||
void GLContext::SetViewportIndexed(Uint index, FloatVec4 viewport) {
|
||||
m_renderState.SetViewportIndexed(index, viewport);
|
||||
}
|
||||
|
||||
const FloatVec4& GLContext::GetViewportIndexed(Uint index) const {
|
||||
return m_renderState.GetViewportIndexed(index);
|
||||
}
|
||||
|
||||
void GLContext::SetLineWidth(Float width) {
|
||||
m_renderState.SetLineWidth(width);
|
||||
}
|
||||
@@ -953,6 +961,14 @@ namespace MobileGL::MG_State {
|
||||
return m_renderState.GetDepthRange();
|
||||
}
|
||||
|
||||
void GLContext::SetDepthRangeIndexed(Uint index, FloatVec2 range) {
|
||||
m_renderState.SetDepthRangeIndexed(index, range);
|
||||
}
|
||||
|
||||
const FloatVec2& GLContext::GetDepthRangeIndexed(Uint index) const {
|
||||
return m_renderState.GetDepthRangeIndexed(index);
|
||||
}
|
||||
|
||||
void GLContext::SetSampleCoverage(Float value, Bool invert) {
|
||||
m_renderState.SetSampleCoverage(value, invert);
|
||||
}
|
||||
@@ -1017,6 +1033,14 @@ namespace MobileGL::MG_State {
|
||||
return m_renderState.GetScissorBox();
|
||||
}
|
||||
|
||||
void GLContext::SetScissorBoxIndexed(Uint index, IntVec4 box) {
|
||||
m_renderState.SetScissorBoxIndexed(index, box);
|
||||
}
|
||||
|
||||
const IntVec4& GLContext::GetScissorBoxIndexed(Uint index) const {
|
||||
return m_renderState.GetScissorBoxIndexed(index);
|
||||
}
|
||||
|
||||
// Framebuffer
|
||||
void GLContext::GenFramebufferNames(Uint number, Vector<Uint>& framebuffers) {
|
||||
m_framebufferState.GenerateNames(number, framebuffers);
|
||||
|
||||
@@ -198,8 +198,10 @@ namespace MobileGL {
|
||||
// Only the pipeline-relevant subset - see RenderState::m_pipelineStateVersion.
|
||||
Uint GetPipelineStateVersion() const;
|
||||
const RenderStateParameters& GetRenderStateParameters() const;
|
||||
void SetViewport(IntVec4 viewport); // x, y, width, height
|
||||
const IntVec4& GetViewport() const; // x, y, width, height
|
||||
void SetViewport(IntVec4 viewport); // x, y, width, height; writes ALL viewports
|
||||
IntVec4 GetViewport() const; // x, y, width, height; viewport 0, rounded
|
||||
void SetViewportIndexed(Uint index, FloatVec4 viewport);
|
||||
const FloatVec4& GetViewportIndexed(Uint index) const;
|
||||
void SetLineWidth(Float width);
|
||||
Float GetLineWidth() const;
|
||||
void SetPointSize(Float size);
|
||||
@@ -260,8 +262,10 @@ namespace MobileGL {
|
||||
Uint32 GetClearStencil() const;
|
||||
void SetBlendColor(FloatVec4 color);
|
||||
const FloatVec4& GetBlendColor() const;
|
||||
void SetDepthRange(FloatVec2 range);
|
||||
void SetDepthRange(FloatVec2 range); // writes ALL viewports' depth ranges
|
||||
const FloatVec2& GetDepthRange() const;
|
||||
void SetDepthRangeIndexed(Uint index, FloatVec2 range);
|
||||
const FloatVec2& GetDepthRangeIndexed(Uint index) const;
|
||||
void SetSampleCoverage(Float value, Bool invert);
|
||||
Float GetSampleCoverageValue() const;
|
||||
Bool GetSampleCoverageInvert() const;
|
||||
@@ -276,8 +280,10 @@ namespace MobileGL {
|
||||
FrontFaceMode GetFrontFaceMode() const;
|
||||
void SetProvokingVertexMode(ProvokingVertexMode mode);
|
||||
ProvokingVertexMode GetProvokingVertexMode() const;
|
||||
void SetScissorBox(IntVec4 box); // x, y, width, height
|
||||
const IntVec4& GetScissorBox() const; // x, y, width, height
|
||||
void SetScissorBox(IntVec4 box); // x, y, width, height; writes ALL rectangles
|
||||
const IntVec4& GetScissorBox() const; // x, y, width, height; rectangle 0
|
||||
void SetScissorBoxIndexed(Uint index, IntVec4 box);
|
||||
const IntVec4& GetScissorBoxIndexed(Uint index) const;
|
||||
|
||||
// Transform feedback. The fields below are the state of the transform
|
||||
// feedback object currently bound to GL_TRANSFORM_FEEDBACK; see the object
|
||||
|
||||
@@ -25,6 +25,12 @@ namespace MobileGL {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Every viewport's scissor-test bit set, i.e. what glEnable(GL_SCISSOR_TEST) writes.
|
||||
constexpr Uint32 kAllViewportsMask =
|
||||
RenderStateParameters::MAX_VIEWPORTS >= 32
|
||||
? ~0u
|
||||
: (1u << RenderStateParameters::MAX_VIEWPORTS) - 1u;
|
||||
} // namespace
|
||||
|
||||
RenderState::RenderState() {
|
||||
@@ -32,6 +38,15 @@ namespace MobileGL {
|
||||
for (auto& mask : m_parameters.ColorMasks) {
|
||||
mask = BoolVec4(true, true, true, true);
|
||||
}
|
||||
// Every viewport's depth range starts at (0, 1) - GL 4.6 core table 23.4. The
|
||||
// viewport and scissor rectangles legitimately start all-zero here: their spec
|
||||
// initial value is the size of the window the context is first made current to,
|
||||
// which the frontend does not know yet, so an all-zero rectangle means "never
|
||||
// written" and the backends resolve it against the live surface (see
|
||||
// DirectGLES' SyncRenderState and VulkanRenderer's ApplyGLViewportState).
|
||||
for (auto& range : m_parameters.DepthRanges) {
|
||||
range = FloatVec2(0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
Uint RenderState::GetVersion() const {
|
||||
@@ -47,15 +62,47 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
// -------------------- Rasterization --------------------
|
||||
// ARB_viewport_array, "Additions to Chapter 2": Viewport(x, y, w, h) is equivalent to
|
||||
// ViewportIndexedf(i, x, y, w, h) for every i in [0, MAX_VIEWPORTS) - it is not a
|
||||
// synonym for "viewport 0".
|
||||
void RenderState::SetViewport(IntVec4 viewport) {
|
||||
if (m_parameters.Viewport == viewport) return;
|
||||
const FloatVec4 asFloat(static_cast<Float>(viewport.x()), static_cast<Float>(viewport.y()),
|
||||
static_cast<Float>(viewport.z()), static_cast<Float>(viewport.w()));
|
||||
Bool stateChanged = false;
|
||||
for (auto& stored : m_parameters.Viewports) {
|
||||
if (stored == asFloat) continue;
|
||||
stored = asFloat;
|
||||
stateChanged = true;
|
||||
}
|
||||
if (stateChanged) ++m_version;
|
||||
}
|
||||
|
||||
m_parameters.Viewport = viewport;
|
||||
IntVec4 RenderState::GetViewport() const {
|
||||
const FloatVec4& viewport = m_parameters.Viewports[0];
|
||||
// Round rather than truncate: glGetIntegerv on floating-point state rounds to
|
||||
// nearest (GL 4.6 core 22.2), and truncating a 63.5-wide viewport to 63 would
|
||||
// also hand the backends a rectangle one pixel short of what was asked for.
|
||||
return IntVec4(static_cast<Int>(std::lround(viewport.x())), static_cast<Int>(std::lround(viewport.y())),
|
||||
static_cast<Int>(std::lround(viewport.z())), static_cast<Int>(std::lround(viewport.w())));
|
||||
}
|
||||
|
||||
void RenderState::SetViewportIndexed(Uint index, FloatVec4 viewport) {
|
||||
if (index >= RenderStateParameters::MAX_VIEWPORTS) {
|
||||
MOBILEGL_ASSERT(false, "Viewport index out of range: %u", index);
|
||||
return;
|
||||
}
|
||||
if (m_parameters.Viewports[index] == viewport) return;
|
||||
|
||||
m_parameters.Viewports[index] = viewport;
|
||||
++m_version;
|
||||
}
|
||||
|
||||
const IntVec4& RenderState::GetViewport() const {
|
||||
return m_parameters.Viewport;
|
||||
const FloatVec4& RenderState::GetViewportIndexed(Uint index) const {
|
||||
if (index >= RenderStateParameters::MAX_VIEWPORTS) {
|
||||
MOBILEGL_ASSERT(false, "Viewport index out of range: %u", index);
|
||||
return m_parameters.Viewports[0];
|
||||
}
|
||||
return m_parameters.Viewports[index];
|
||||
}
|
||||
|
||||
void RenderState::SetLineWidth(Float width) {
|
||||
@@ -223,7 +270,6 @@ namespace MobileGL {
|
||||
SET_CAPABILITY(SampleAlphaToOne, enabled);
|
||||
SET_CAPABILITY(SampleCoverage, enabled);
|
||||
SET_CAPABILITY(SampleMask, enabled);
|
||||
SET_CAPABILITY(ScissorTest, enabled);
|
||||
SET_CAPABILITY(StencilTest, enabled);
|
||||
SET_CAPABILITY(ProgramPointSize, enabled);
|
||||
case CapabilityInput::Blend: {
|
||||
@@ -236,6 +282,17 @@ namespace MobileGL {
|
||||
if (stateChanged) BumpVersions();
|
||||
break;
|
||||
}
|
||||
// GL 4.6 core 17.3.2: the non-indexed Enable/Disable(SCISSOR_TEST) enables or
|
||||
// disables the test for ALL viewports, exactly like glViewport writes all
|
||||
// viewports. Anything narrower fails KHR-GL43.viewport_array.scissor_test_state_api,
|
||||
// whose "enable all" phase reads every index back through glIsEnabledi.
|
||||
case CapabilityInput::ScissorTest: {
|
||||
const Uint32 updated = enabled ? kAllViewportsMask : 0u;
|
||||
if (m_parameters.ScissorTestEnabledMask == updated) break;
|
||||
m_parameters.ScissorTestEnabledMask = updated;
|
||||
BumpVersions();
|
||||
break;
|
||||
}
|
||||
case CapabilityInput::ClipDistance0:
|
||||
case CapabilityInput::ClipDistance1:
|
||||
case CapabilityInput::ClipDistance2:
|
||||
@@ -287,11 +344,14 @@ namespace MobileGL {
|
||||
RETURN_CAPABILITY(SampleAlphaToOne);
|
||||
RETURN_CAPABILITY(SampleCoverage);
|
||||
RETURN_CAPABILITY(SampleMask);
|
||||
RETURN_CAPABILITY(ScissorTest);
|
||||
RETURN_CAPABILITY(StencilTest);
|
||||
RETURN_CAPABILITY(ProgramPointSize);
|
||||
case CapabilityInput::Blend:
|
||||
return m_parameters.BlendStates[0].Enabled;
|
||||
// The non-indexed query of an indexed capability answers for index 0
|
||||
// (GL 4.6 core 22.1), which is also the only bit either backend consumes today.
|
||||
case CapabilityInput::ScissorTest:
|
||||
return (m_parameters.ScissorTestEnabledMask & 1u) != 0;
|
||||
case CapabilityInput::ClipDistance0:
|
||||
case CapabilityInput::ClipDistance1:
|
||||
case CapabilityInput::ClipDistance2:
|
||||
@@ -307,13 +367,29 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
void RenderState::SetCapabilityIndexed(CapabilityInput cap, Uint index, Bool enabled) {
|
||||
// Only for BlendState currently. The GL entry points (glEnablei/glDisablei) already
|
||||
// reject every non-GL_BLEND target with GL_INVALID_ENUM before reaching here, so this
|
||||
// is a backstop - but it must stay a backstop: THROW_UNIMPL_EXCEPTION unwinds a C++
|
||||
// exception through the C GL ABI and terminates the process.
|
||||
// GL_BLEND (indexed by draw buffer) and GL_SCISSOR_TEST (indexed by viewport) are
|
||||
// the only indexed capabilities in GL 4.6 core. The GL entry points
|
||||
// (glEnablei/glDisablei) already reject every other target with GL_INVALID_ENUM
|
||||
// and every out-of-range index with GL_INVALID_VALUE before reaching here, so the
|
||||
// guards below are backstops - but they must stay backstops:
|
||||
// THROW_UNIMPL_EXCEPTION unwinds a C++ exception through the C GL ABI and
|
||||
// terminates the process.
|
||||
if (cap == CapabilityInput::ScissorTest) {
|
||||
if (index >= RenderStateParameters::MAX_VIEWPORTS) {
|
||||
MOBILEGL_ASSERT(false, "Scissor test capability index out of range: %u", index);
|
||||
return;
|
||||
}
|
||||
const Uint32 bit = 1u << index;
|
||||
const Uint32 updated = enabled ? (m_parameters.ScissorTestEnabledMask | bit)
|
||||
: (m_parameters.ScissorTestEnabledMask & ~bit);
|
||||
if (updated == m_parameters.ScissorTestEnabledMask) return;
|
||||
m_parameters.ScissorTestEnabledMask = updated;
|
||||
BumpVersions();
|
||||
return;
|
||||
}
|
||||
if (cap != CapabilityInput::Blend) {
|
||||
MGLOG_I("RenderState::SetCapabilityIndexed: indexed capability state exists only for "
|
||||
"GL_BLEND (cap=%d, index=%u); ignoring",
|
||||
"GL_BLEND and GL_SCISSOR_TEST (cap=%d, index=%u); ignoring",
|
||||
static_cast<int>(cap), index);
|
||||
return;
|
||||
}
|
||||
@@ -328,9 +404,17 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
Bool RenderState::IsCapabilityEnabledIndexed(CapabilityInput cap, Uint index) const {
|
||||
// Only for BlendState currently - same backstop reasoning as SetCapabilityIndexed:
|
||||
// glIsEnabledi has already answered GL_INVALID_ENUM/GL_FALSE for anything else, and a
|
||||
// query must never be able to terminate the process.
|
||||
// GL_BLEND and GL_SCISSOR_TEST only - same backstop reasoning as
|
||||
// SetCapabilityIndexed: glIsEnabledi has already answered
|
||||
// GL_INVALID_ENUM/GL_INVALID_VALUE for anything else, and a query must never be
|
||||
// able to terminate the process.
|
||||
if (cap == CapabilityInput::ScissorTest) {
|
||||
if (index >= RenderStateParameters::MAX_VIEWPORTS) {
|
||||
MOBILEGL_ASSERT(false, "Scissor test capability index out of range: %u", index);
|
||||
return false;
|
||||
}
|
||||
return (m_parameters.ScissorTestEnabledMask & (1u << index)) != 0;
|
||||
}
|
||||
if (cap != CapabilityInput::Blend) {
|
||||
MGLOG_I("RenderState::IsCapabilityEnabledIndexed: indexed capability state exists only "
|
||||
"for GL_BLEND (cap=%d, index=%u); reporting disabled",
|
||||
@@ -591,15 +675,39 @@ namespace MobileGL {
|
||||
return m_parameters.BlendColor;
|
||||
}
|
||||
|
||||
// Like Viewport: ARB_viewport_array makes DepthRange(n, f) the same as
|
||||
// DepthRangeIndexed(i, n, f) for every i.
|
||||
void RenderState::SetDepthRange(FloatVec2 range) {
|
||||
if (m_parameters.DepthRange == range) return;
|
||||
|
||||
m_parameters.DepthRange = range;
|
||||
++m_version;
|
||||
Bool stateChanged = false;
|
||||
for (auto& stored : m_parameters.DepthRanges) {
|
||||
if (stored == range) continue;
|
||||
stored = range;
|
||||
stateChanged = true;
|
||||
}
|
||||
if (stateChanged) ++m_version;
|
||||
}
|
||||
|
||||
const FloatVec2& RenderState::GetDepthRange() const {
|
||||
return m_parameters.DepthRange;
|
||||
return m_parameters.DepthRanges[0];
|
||||
}
|
||||
|
||||
void RenderState::SetDepthRangeIndexed(Uint index, FloatVec2 range) {
|
||||
if (index >= RenderStateParameters::MAX_VIEWPORTS) {
|
||||
MOBILEGL_ASSERT(false, "Depth range index out of range: %u", index);
|
||||
return;
|
||||
}
|
||||
if (m_parameters.DepthRanges[index] == range) return;
|
||||
|
||||
m_parameters.DepthRanges[index] = range;
|
||||
++m_version;
|
||||
}
|
||||
|
||||
const FloatVec2& RenderState::GetDepthRangeIndexed(Uint index) const {
|
||||
if (index >= RenderStateParameters::MAX_VIEWPORTS) {
|
||||
MOBILEGL_ASSERT(false, "Depth range index out of range: %u", index);
|
||||
return m_parameters.DepthRanges[0];
|
||||
}
|
||||
return m_parameters.DepthRanges[index];
|
||||
}
|
||||
|
||||
void RenderState::SetSampleCoverage(Float value, Bool invert) {
|
||||
@@ -726,15 +834,39 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
// --------------------- Scissor ---------------------
|
||||
// Like Viewport: ARB_viewport_array makes Scissor(x, y, w, h) the same as
|
||||
// ScissorIndexed(i, x, y, w, h) for every i.
|
||||
void RenderState::SetScissorBox(IntVec4 box) {
|
||||
if (m_parameters.ScissorBox == box) return;
|
||||
|
||||
m_parameters.ScissorBox = box;
|
||||
++m_version;
|
||||
Bool stateChanged = false;
|
||||
for (auto& stored : m_parameters.ScissorBoxes) {
|
||||
if (stored == box) continue;
|
||||
stored = box;
|
||||
stateChanged = true;
|
||||
}
|
||||
if (stateChanged) ++m_version;
|
||||
}
|
||||
|
||||
const IntVec4& RenderState::GetScissorBox() const {
|
||||
return m_parameters.ScissorBox;
|
||||
return m_parameters.ScissorBoxes[0];
|
||||
}
|
||||
|
||||
void RenderState::SetScissorBoxIndexed(Uint index, IntVec4 box) {
|
||||
if (index >= RenderStateParameters::MAX_VIEWPORTS) {
|
||||
MOBILEGL_ASSERT(false, "Scissor box index out of range: %u", index);
|
||||
return;
|
||||
}
|
||||
if (m_parameters.ScissorBoxes[index] == box) return;
|
||||
|
||||
m_parameters.ScissorBoxes[index] = box;
|
||||
++m_version;
|
||||
}
|
||||
|
||||
const IntVec4& RenderState::GetScissorBoxIndexed(Uint index) const {
|
||||
if (index >= RenderStateParameters::MAX_VIEWPORTS) {
|
||||
MOBILEGL_ASSERT(false, "Scissor box index out of range: %u", index);
|
||||
return m_parameters.ScissorBoxes[0];
|
||||
}
|
||||
return m_parameters.ScissorBoxes[index];
|
||||
}
|
||||
} // namespace GLState
|
||||
} // namespace MG_State
|
||||
|
||||
@@ -220,8 +220,21 @@ namespace MobileGL {
|
||||
};
|
||||
|
||||
struct RenderStateParameters {
|
||||
// ARB_viewport_array / GL 4.6 core 13.6.1: the viewport, the scissor rectangle, the depth
|
||||
// range and the scissor-test enable are all arrays indexed by gl_ViewportIndex, and the
|
||||
// spec floor for MAX_VIEWPORTS is 16. MobileGL advertises exactly 16 on both backends, so
|
||||
// this is also what GL_MAX_VIEWPORTS reports (see the backend loaders' caps.MaxViewports).
|
||||
static constexpr Uint MAX_VIEWPORTS = 16;
|
||||
|
||||
// Rasterization
|
||||
IntVec4 Viewport = IntVec4(0, 0, 0, 0); // x, y, width, height
|
||||
// The viewport rectangle is FLOAT state as of GL 4.1 - ViewportIndexedf writes fractional
|
||||
// values and GetFloati_v(GL_VIEWPORT) must hand them back bit-exact
|
||||
// (KHR-GL43.viewport_array.viewport_api compares with ==, no tolerance). glViewport's
|
||||
// integers are simply one way to write it. Index 0 is what a program that never assigns
|
||||
// gl_ViewportIndex rasterizes against, and what the classic glViewport /
|
||||
// glGetIntegerv(GL_VIEWPORT) pair addresses; both backends consume index 0 only, rounded
|
||||
// back to integers (GL_VIEWPORT_SUBPIXEL_BITS is 0, so rounding is the honest answer).
|
||||
Array<FloatVec4, MAX_VIEWPORTS> Viewports{}; // x, y, width, height
|
||||
Float LineWidth = 1.0f;
|
||||
Float PointSize = 1.0f;
|
||||
// GL_PATCH_VERTICES: how many vertices one tessellation patch consumes.
|
||||
@@ -247,7 +260,13 @@ namespace MobileGL {
|
||||
Float ClearDepth = 1.0f;
|
||||
Uint32 ClearStencil = 0;
|
||||
FloatVec4 BlendColor = FloatVec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
FloatVec2 DepthRange = FloatVec2(0.0f, 1.0f);
|
||||
// Per-viewport depth range (glDepthRangeIndexed / glDepthRangeArrayv). Every entry is
|
||||
// initialized to (0, 1) in RenderState's constructor - a default member initializer would
|
||||
// not survive the Array<> aggregate. Kept float rather than double: DepthRangeArrayv takes
|
||||
// GLdouble, but the value reaches the hardware as VkViewport::minDepth/maxDepth (float) on
|
||||
// Magma and glDepthRangef on Espryt, so a double store would only widen the readback and
|
||||
// then lose it again at the same place.
|
||||
Array<FloatVec2, MAX_VIEWPORTS> DepthRanges{};
|
||||
Float SampleCoverageValue = 1.0f;
|
||||
Bool SampleCoverageInvert = false;
|
||||
Uint32 SampleMaskValue = 0xffffffffu;
|
||||
@@ -299,10 +318,15 @@ namespace MobileGL {
|
||||
Bool SampleAlphaToOneEnabled = false;
|
||||
Bool SampleCoverageEnabled = false;
|
||||
Bool SampleMaskEnabled = false;
|
||||
Bool ScissorTestEnabled = false;
|
||||
Bool StencilTestEnabled = false;
|
||||
Bool ProgramPointSizeEnabled = false;
|
||||
IntVec4 ScissorBox = IntVec4(0, 0, 0, 0); // x, y, width, height
|
||||
// glEnable(GL_SCISSOR_TEST) enables the test for EVERY viewport, glEnablei for one
|
||||
// (GL 4.6 core 17.3.2), so this is 16 bits and not a bool. Bit 0 is what the classic
|
||||
// glIsEnabled(GL_SCISSOR_TEST) reports and what both backends currently consume. Unlike
|
||||
// ClipDistanceEnabledMask below it DOES bump the pipeline version, because DirectGLES
|
||||
// turns it into a real glEnable/glDisable.
|
||||
Uint32 ScissorTestEnabledMask = 0;
|
||||
Array<IntVec4, MAX_VIEWPORTS> ScissorBoxes{}; // x, y, width, height
|
||||
// glEnable(GL_CLIP_DISTANCE0 + i) for i in [0, 8), one bit each. A bitmask rather than
|
||||
// eight bools because every consumer wants the set, not an individual flag, and because
|
||||
// the SYNC_CAPABILITY/SET_CAPABILITY macros key off a "<Name>Enabled" field name that
|
||||
@@ -323,8 +347,14 @@ namespace MobileGL {
|
||||
const RenderStateParameters& GetAllParameters() const;
|
||||
|
||||
// Rasterization
|
||||
// ARB_viewport_array defines glViewport as ViewportIndexedf on EVERY index, so the
|
||||
// classic setter broadcasts; GetViewport answers for index 0 (rounded to the
|
||||
// integers glGetIntegerv(GL_VIEWPORT) and both backends want) and is BY VALUE for
|
||||
// that reason. The indexed pair is the verbatim float state.
|
||||
void SetViewport(IntVec4 viewport); // x, y, width, height
|
||||
const IntVec4& GetViewport() const; // x, y, width, height
|
||||
IntVec4 GetViewport() const; // x, y, width, height, viewport 0, rounded
|
||||
void SetViewportIndexed(Uint index, FloatVec4 viewport);
|
||||
const FloatVec4& GetViewportIndexed(Uint index) const;
|
||||
void SetLineWidth(Float width);
|
||||
Float GetLineWidth() const;
|
||||
void SetPointSize(Float size);
|
||||
@@ -400,8 +430,12 @@ namespace MobileGL {
|
||||
Uint32 GetClearStencil() const;
|
||||
void SetBlendColor(FloatVec4 color);
|
||||
const FloatVec4& GetBlendColor() const;
|
||||
// glDepthRange(f) writes every viewport's range (ARB_viewport_array); the indexed
|
||||
// pair is glDepthRangeIndexed / glDepthRangeArrayv. GetDepthRange answers index 0.
|
||||
void SetDepthRange(FloatVec2 range);
|
||||
const FloatVec2& GetDepthRange() const;
|
||||
void SetDepthRangeIndexed(Uint index, FloatVec2 range);
|
||||
const FloatVec2& GetDepthRangeIndexed(Uint index) const;
|
||||
void SetSampleCoverage(Float value, Bool invert);
|
||||
Float GetSampleCoverageValue() const;
|
||||
Bool GetSampleCoverageInvert() const;
|
||||
@@ -421,9 +455,12 @@ namespace MobileGL {
|
||||
void SetProvokingVertexMode(ProvokingVertexMode mode);
|
||||
ProvokingVertexMode GetProvokingVertexMode() const;
|
||||
|
||||
// Scissor
|
||||
// Scissor. glScissor writes every rectangle (ARB_viewport_array); GetScissorBox
|
||||
// answers for index 0.
|
||||
void SetScissorBox(IntVec4 box); // x, y, width, height
|
||||
const IntVec4& GetScissorBox() const; // x, y, width, height
|
||||
void SetScissorBoxIndexed(Uint index, IntVec4 box);
|
||||
const IntVec4& GetScissorBoxIndexed(Uint index) const;
|
||||
|
||||
private:
|
||||
// Bump both: any state change invalidates the draw snapshot, and this one also
|
||||
|
||||
@@ -6,11 +6,20 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// Indexed capability state (glEnablei/glDisablei/glIsEnabledi) exists only for GL_BLEND in this
|
||||
// stack. Every other capability must come back as GL_INVALID_ENUM per GL 4.6 sec. 17.3.3 - and,
|
||||
// far more importantly, must come back at all: RenderState::SetCapabilityIndexed and
|
||||
// IsCapabilityEnabledIndexed used to answer a non-blend capability with THROW_UNIMPL_EXCEPTION,
|
||||
// Indexed capability state (glEnablei/glDisablei/glIsEnabledi) exists for exactly two
|
||||
// capabilities: GL_BLEND, indexed by draw buffer, and GL_SCISSOR_TEST, indexed by viewport
|
||||
// (ARB_viewport_array). Every other capability must come back as GL_INVALID_ENUM per GL 4.6
|
||||
// sec. 17.3.3 - and, far more importantly, must come back at all: RenderState::SetCapabilityIndexed
|
||||
// and IsCapabilityEnabledIndexed used to answer a non-blend capability with THROW_UNIMPL_EXCEPTION,
|
||||
// which unwinds a C++ exception through the C GL ABI and terminates the process.
|
||||
//
|
||||
// The second half of this file is the ARB_viewport_array indexed rectangle state. Every one of
|
||||
// glViewportArrayv/glViewportIndexedf(v)/glScissorArrayv/glScissorIndexed(v)/glDepthRangeArrayv/
|
||||
// glDepthRangeIndexed was a MGLOG_W_ONCE stub that raised no error and stored nothing, and the
|
||||
// indexed getters answered EVERY index with viewport 0's value, so a set/get round trip silently
|
||||
// reported the initial state. The assertions below are deliberately state-shaped rather than
|
||||
// render-shaped: this IS the state machine, and the rendering half (gl_ViewportIndex routing) is
|
||||
// asserted separately in MG_IntegrationTest/Scenarios/ViewportArrayScenario.cpp.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -21,6 +30,7 @@
|
||||
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
||||
#include <MG_State/GLState/RenderState/RenderState.h>
|
||||
|
||||
using namespace MobileGL;
|
||||
|
||||
@@ -50,10 +60,11 @@ namespace {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(RenderStateTest, IndexedCapabilityTogglesRejectNonBlendCapabilities) {
|
||||
TEST_F(RenderStateTest, IndexedCapabilityTogglesRejectNonIndexedCapabilities) {
|
||||
// GL_CLIP_DISTANCE0 is a real capability, just not an indexed one - the shape an application or
|
||||
// a CTS negative test would hit.
|
||||
for (const GLenum cap : {GL_CLIP_DISTANCE0, GL_DEPTH_TEST, GL_SCISSOR_TEST}) {
|
||||
// a CTS negative test would hit. GL_SCISSOR_TEST used to be in this list and is not any more:
|
||||
// ARB_viewport_array makes it the second indexed capability (see the tests below).
|
||||
for (const GLenum cap : {GL_CLIP_DISTANCE0, GL_DEPTH_TEST, GL_STENCIL_TEST}) {
|
||||
MG_Impl::GLImpl::Enablei(cap, 0);
|
||||
ExpectSingleGlError(GL_INVALID_ENUM);
|
||||
|
||||
@@ -89,3 +100,462 @@ TEST_F(RenderStateTest, IndexedBlendTogglesStillWork) {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabledi(GL_BLEND, 1), GL_FALSE);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// ARB_viewport_array: indexed viewport / scissor / depth-range state
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
constexpr GLuint kMaxViewports = RenderStateParameters::MAX_VIEWPORTS;
|
||||
|
||||
Array<Array<GLfloat, 4>, kMaxViewports> ReadAllViewports() {
|
||||
Array<Array<GLfloat, 4>, kMaxViewports> out{};
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
MG_Impl::GLImpl::GetFloati_v(GL_VIEWPORT, i, out[i].data());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Array<Array<GLdouble, 2>, kMaxViewports> ReadAllDepthRanges() {
|
||||
Array<Array<GLdouble, 2>, kMaxViewports> out{};
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
MG_Impl::GLImpl::GetDoublei_v(GL_DEPTH_RANGE, i, out[i].data());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(RenderStateTest, ScissorTestIsIndexedByViewport) {
|
||||
// The exact shape of KHR-GL43.viewport_array.scissor_test_state_api's toggle loop: one index
|
||||
// is flipped and EVERY index is read back, so a broadcast masquerading as an indexed write
|
||||
// cannot pass.
|
||||
MG_Impl::GLImpl::Disable(GL_SCISSOR_TEST);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
|
||||
for (GLuint toggled = 0; toggled < kMaxViewports; ++toggled) {
|
||||
MG_Impl::GLImpl::Enablei(GL_SCISSOR_TEST, toggled);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "index " << toggled;
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabledi(GL_SCISSOR_TEST, i), i == toggled ? GL_TRUE : GL_FALSE)
|
||||
<< "enabled index " << toggled << ", read index " << i;
|
||||
}
|
||||
MG_Impl::GLImpl::Disablei(GL_SCISSOR_TEST, toggled);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabledi(GL_SCISSOR_TEST, toggled), GL_FALSE);
|
||||
}
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, NonIndexedScissorTestEnableWritesEveryViewport) {
|
||||
// GL 4.6 core 17.3.2: Enable/Disable(SCISSOR_TEST) is "for all viewports". Reading only
|
||||
// index 0 back would let a broadcast-less implementation through, so every index is checked.
|
||||
MG_Impl::GLImpl::Enable(GL_SCISSOR_TEST);
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabledi(GL_SCISSOR_TEST, i), GL_TRUE) << "index " << i;
|
||||
}
|
||||
// ... and the non-indexed query answers for viewport 0 (GL 4.6 core 22.1).
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabled(GL_SCISSOR_TEST), GL_TRUE);
|
||||
|
||||
MG_Impl::GLImpl::Disable(GL_SCISSOR_TEST);
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabledi(GL_SCISSOR_TEST, i), GL_FALSE) << "index " << i;
|
||||
}
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabled(GL_SCISSOR_TEST), GL_FALSE);
|
||||
|
||||
// An indexed enable on a NON-zero index must not move the non-indexed answer.
|
||||
MG_Impl::GLImpl::Enablei(GL_SCISSOR_TEST, 3);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabled(GL_SCISSOR_TEST), GL_FALSE);
|
||||
MG_Impl::GLImpl::Enablei(GL_SCISSOR_TEST, 0);
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabled(GL_SCISSOR_TEST), GL_TRUE);
|
||||
|
||||
MG_Impl::GLImpl::Disable(GL_SCISSOR_TEST);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, ScissorTestEnableRejectsAnOutOfRangeViewportIndex) {
|
||||
MG_Impl::GLImpl::Enablei(GL_SCISSOR_TEST, kMaxViewports);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
MG_Impl::GLImpl::Disablei(GL_SCISSOR_TEST, kMaxViewports);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
EXPECT_EQ(MG_Impl::GLImpl::IsEnabledi(GL_SCISSOR_TEST, kMaxViewports), GL_FALSE);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
// MAX_VIEWPORTS - 1 is the last LEGAL index and must stay silent.
|
||||
MG_Impl::GLImpl::Enablei(GL_SCISSOR_TEST, kMaxViewports - 1);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::Disablei(GL_SCISSOR_TEST, kMaxViewports - 1);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, MaxViewportsMatchesTheIndexedStateWidth) {
|
||||
// The advertised limit and the width of the state arrays are the same number by
|
||||
// construction; a divergence would make some index simultaneously legal to the CTS and
|
||||
// out of range to the setters.
|
||||
GLint maxViewports = 0;
|
||||
MG_Impl::GLImpl::GetIntegerv(GL_MAX_VIEWPORTS, &maxViewports);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
EXPECT_EQ(maxViewports, static_cast<GLint>(kMaxViewports));
|
||||
EXPECT_GE(maxViewports, 16) << "GL 4.3 core requires MAX_VIEWPORTS >= 16";
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, ViewportArrayvRoundTripsThroughEveryGetterWidth) {
|
||||
Array<GLfloat, kMaxViewports * 4> written{};
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
written[i * 4 + 0] = static_cast<GLfloat>(i) + 0.125f;
|
||||
written[i * 4 + 1] = static_cast<GLfloat>(i) + 0.25f;
|
||||
written[i * 4 + 2] = static_cast<GLfloat>(64 + i);
|
||||
written[i * 4 + 3] = static_cast<GLfloat>(32 + i);
|
||||
}
|
||||
MG_Impl::GLImpl::ViewportArrayv(0, kMaxViewports, written.data());
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
GLfloat asFloat[4] = {};
|
||||
MG_Impl::GLImpl::GetFloati_v(GL_VIEWPORT, i, asFloat);
|
||||
// Bit-exact: the fractional origin is the whole point of float viewport state, and the
|
||||
// CTS compares with == (0.125 and 0.25 are exact binary fractions, so this is fair).
|
||||
EXPECT_EQ(asFloat[0], written[i * 4 + 0]) << "index " << i << " must round-trip verbatim";
|
||||
EXPECT_EQ(asFloat[1], written[i * 4 + 1]) << "index " << i;
|
||||
EXPECT_EQ(asFloat[2], written[i * 4 + 2]) << "index " << i;
|
||||
EXPECT_EQ(asFloat[3], written[i * 4 + 3]) << "index " << i;
|
||||
|
||||
GLdouble asDouble[4] = {};
|
||||
MG_Impl::GLImpl::GetDoublei_v(GL_VIEWPORT, i, asDouble);
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
EXPECT_EQ(asDouble[c], static_cast<GLdouble>(written[i * 4 + c])) << "index " << i << " component " << c;
|
||||
}
|
||||
|
||||
// The integer widths round to nearest rather than truncate; the .5+ case is pinned by
|
||||
// ViewportRoundsRatherThanTruncatesForIntegerQueries below.
|
||||
GLint asInt[4] = {};
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_VIEWPORT, i, asInt);
|
||||
EXPECT_EQ(asInt[2], static_cast<GLint>(64 + i)) << "index " << i;
|
||||
EXPECT_EQ(asInt[3], static_cast<GLint>(32 + i)) << "index " << i;
|
||||
|
||||
GLint64 asInt64[4] = {};
|
||||
MG_Impl::GLImpl::GetInteger64i_v(GL_VIEWPORT, i, asInt64);
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
EXPECT_EQ(asInt64[c], static_cast<GLint64>(asInt[c])) << "index " << i << " component " << c;
|
||||
}
|
||||
|
||||
GLboolean asBool[4] = {};
|
||||
MG_Impl::GLImpl::GetBooleani_v(GL_VIEWPORT, i, asBool);
|
||||
EXPECT_EQ(asBool[2], GL_TRUE) << "index " << i << ": a non-zero width is GL_TRUE";
|
||||
}
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, ViewportRoundsRatherThanTruncatesForIntegerQueries) {
|
||||
MG_Impl::GLImpl::ViewportIndexedf(2, 0.0f, 0.0f, 255.875f, 63.5f);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
|
||||
GLint asInt[4] = {};
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_VIEWPORT, 2, asInt);
|
||||
EXPECT_EQ(asInt[2], 256);
|
||||
EXPECT_EQ(asInt[3], 64);
|
||||
|
||||
GLfloat asFloat[4] = {};
|
||||
MG_Impl::GLImpl::GetFloati_v(GL_VIEWPORT, 2, asFloat);
|
||||
EXPECT_EQ(asFloat[2], 255.875f) << "the integer query must not disturb the stored float";
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, ViewportIndexedWritesTouchExactlyOneIndex) {
|
||||
MG_Impl::GLImpl::Viewport(0, 0, 8, 8);
|
||||
const auto before = ReadAllViewports();
|
||||
|
||||
for (GLuint target = 0; target < kMaxViewports; ++target) {
|
||||
const GLfloat value[4] = {0.375f, 0.375f, 0.625f, 0.625f};
|
||||
// Alternate the two indexed entry points so both are covered by the isolation claim.
|
||||
if (target % 2 == 0) {
|
||||
MG_Impl::GLImpl::ViewportIndexedf(target, value[0], value[1], value[2], value[3]);
|
||||
} else {
|
||||
MG_Impl::GLImpl::ViewportIndexedfv(target, value);
|
||||
}
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
|
||||
const auto after = ReadAllViewports();
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
if (i == target) {
|
||||
EXPECT_EQ(after[i][0], value[0]) << "index " << i;
|
||||
EXPECT_EQ(after[i][2], value[2]) << "index " << i;
|
||||
} else {
|
||||
EXPECT_EQ(after[i], before[i]) << "write to " << target << " disturbed index " << i;
|
||||
}
|
||||
}
|
||||
MG_Impl::GLImpl::ViewportIndexedf(target, before[target][0], before[target][1], before[target][2],
|
||||
before[target][3]);
|
||||
}
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, ClassicViewportWritesEveryIndexAndIsVisibleThroughIndexZero) {
|
||||
// Both directions of the aliasing. ARB_viewport_array defines glViewport as ViewportIndexedf
|
||||
// on every index, and glGetIntegerv(GL_VIEWPORT) as viewport 0.
|
||||
MG_Impl::GLImpl::ViewportIndexedf(5, 1.0f, 2.0f, 3.0f, 4.0f);
|
||||
MG_Impl::GLImpl::Viewport(0, 0, 1, 1);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
GLfloat data[4] = {};
|
||||
MG_Impl::GLImpl::GetFloati_v(GL_VIEWPORT, i, data);
|
||||
EXPECT_EQ(data[0], 0.0f) << "index " << i;
|
||||
EXPECT_EQ(data[2], 1.0f) << "glViewport must overwrite index " << i;
|
||||
}
|
||||
|
||||
MG_Impl::GLImpl::ViewportIndexedf(0, 4.0f, 5.0f, 6.0f, 7.0f);
|
||||
GLint classic[4] = {};
|
||||
MG_Impl::GLImpl::GetIntegerv(GL_VIEWPORT, classic);
|
||||
EXPECT_EQ(classic[0], 4);
|
||||
EXPECT_EQ(classic[2], 6);
|
||||
GLfloat classicFloat[4] = {};
|
||||
MG_Impl::GLImpl::GetFloatv(GL_VIEWPORT, classicFloat);
|
||||
EXPECT_EQ(classicFloat[2], 6.0f);
|
||||
// Index 5 keeps its own value: writing index 0 is not a broadcast.
|
||||
GLfloat other[4] = {};
|
||||
MG_Impl::GLImpl::GetFloati_v(GL_VIEWPORT, 5, other);
|
||||
EXPECT_EQ(other[2], 1.0f);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, ScissorBoxRoundTripsPerIndexAndAliasesIndexZero) {
|
||||
Array<GLint, kMaxViewports * 4> written{};
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
written[i * 4 + 0] = static_cast<GLint>(i);
|
||||
written[i * 4 + 1] = static_cast<GLint>(i * 2);
|
||||
written[i * 4 + 2] = static_cast<GLint>(16 + i);
|
||||
written[i * 4 + 3] = static_cast<GLint>(8 + i);
|
||||
}
|
||||
MG_Impl::GLImpl::ScissorArrayv(0, kMaxViewports, written.data());
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
GLint readBack[4] = {};
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_SCISSOR_BOX, i, readBack);
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
EXPECT_EQ(readBack[c], written[i * 4 + c]) << "index " << i << " component " << c;
|
||||
}
|
||||
}
|
||||
|
||||
// Indexed writes stay indexed; both spellings.
|
||||
MG_Impl::GLImpl::ScissorIndexed(4, 4, 4, 8, 8);
|
||||
const GLint indexedV[4] = {9, 9, 12, 12};
|
||||
MG_Impl::GLImpl::ScissorIndexedv(7, indexedV);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
GLint probe[4] = {};
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_SCISSOR_BOX, 4, probe);
|
||||
EXPECT_EQ(probe[2], 8);
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_SCISSOR_BOX, 7, probe);
|
||||
EXPECT_EQ(probe[2], 12);
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_SCISSOR_BOX, 5, probe);
|
||||
EXPECT_EQ(probe[2], static_cast<GLint>(16 + 5)) << "index 5 must be untouched";
|
||||
|
||||
// glScissor writes every rectangle, and glGetIntegerv(GL_SCISSOR_BOX) reports rectangle 0.
|
||||
MG_Impl::GLImpl::Scissor(2, 3, 5, 6);
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_SCISSOR_BOX, i, probe);
|
||||
EXPECT_EQ(probe[0], 2) << "index " << i;
|
||||
EXPECT_EQ(probe[2], 5) << "index " << i;
|
||||
}
|
||||
GLint classic[4] = {};
|
||||
MG_Impl::GLImpl::GetIntegerv(GL_SCISSOR_BOX, classic);
|
||||
EXPECT_EQ(classic[2], 5);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, DepthRangeRoundTripsPerIndexAndAliasesIndexZero) {
|
||||
Array<GLdouble, kMaxViewports * 2> written{};
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
// Exact binary fractions, like the CTS uses: a float-backed store round-trips them.
|
||||
written[i * 2 + 0] = static_cast<GLdouble>(i) / 16.0;
|
||||
written[i * 2 + 1] = 1.0 - static_cast<GLdouble>(i) / 16.0;
|
||||
}
|
||||
MG_Impl::GLImpl::DepthRangeArrayv(0, kMaxViewports, written.data());
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
|
||||
const auto readBack = ReadAllDepthRanges();
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
EXPECT_EQ(readBack[i][0], written[i * 2 + 0]) << "index " << i;
|
||||
EXPECT_EQ(readBack[i][1], written[i * 2 + 1]) << "index " << i;
|
||||
}
|
||||
|
||||
MG_Impl::GLImpl::DepthRangeIndexed(9, 0.25, 0.75);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
GLdouble probe[2] = {};
|
||||
MG_Impl::GLImpl::GetDoublei_v(GL_DEPTH_RANGE, 9, probe);
|
||||
EXPECT_EQ(probe[0], 0.25);
|
||||
EXPECT_EQ(probe[1], 0.75);
|
||||
MG_Impl::GLImpl::GetDoublei_v(GL_DEPTH_RANGE, 8, probe);
|
||||
EXPECT_EQ(probe[0], 8.0 / 16.0) << "index 8 must be untouched";
|
||||
|
||||
GLfloat asFloat[2] = {};
|
||||
MG_Impl::GLImpl::GetFloati_v(GL_DEPTH_RANGE, 9, asFloat);
|
||||
EXPECT_EQ(asFloat[0], 0.25f);
|
||||
EXPECT_EQ(asFloat[1], 0.75f);
|
||||
|
||||
// glDepthRange writes every range; glGetDoublev(GL_DEPTH_RANGE) reports range 0.
|
||||
MG_Impl::GLImpl::DepthRange(0.0, 1.0);
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
MG_Impl::GLImpl::GetDoublei_v(GL_DEPTH_RANGE, i, probe);
|
||||
EXPECT_EQ(probe[0], 0.0) << "index " << i;
|
||||
EXPECT_EQ(probe[1], 1.0) << "index " << i;
|
||||
}
|
||||
MG_Impl::GLImpl::DepthRangeIndexed(0, 0.125, 0.875);
|
||||
GLdouble classic[2] = {};
|
||||
MG_Impl::GLImpl::GetDoublev(GL_DEPTH_RANGE, classic);
|
||||
EXPECT_EQ(classic[0], 0.125);
|
||||
EXPECT_EQ(classic[1], 0.875);
|
||||
MG_Impl::GLImpl::DepthRange(0.0, 1.0);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, IndexedRectangleSettersRejectAnOutOfRangeIndex) {
|
||||
const GLfloat viewport[4] = {0.0f, 0.0f, 1.0f, 1.0f};
|
||||
const GLint scissor[4] = {0, 0, 1, 1};
|
||||
|
||||
for (const GLuint index : {kMaxViewports, kMaxViewports + 1}) {
|
||||
MG_Impl::GLImpl::ViewportIndexedf(index, 0.0f, 0.0f, 1.0f, 1.0f);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::ViewportIndexedfv(index, viewport);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::ScissorIndexed(index, 0, 0, 1, 1);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::ScissorIndexedv(index, scissor);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::DepthRangeIndexed(index, 0.0, 1.0);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
}
|
||||
|
||||
// The last legal index must stay silent - api_errors checks both sides of the boundary.
|
||||
MG_Impl::GLImpl::ViewportIndexedf(kMaxViewports - 1, 0.0f, 0.0f, 1.0f, 1.0f);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::ScissorIndexed(kMaxViewports - 1, 0, 0, 1, 1);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::DepthRangeIndexed(kMaxViewports - 1, 0.0, 1.0);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, ArraySettersRejectAnOutOfRangeRangeButAcceptAnExactlyFullOne) {
|
||||
Array<GLfloat, kMaxViewports * 4> viewports{};
|
||||
Array<GLint, kMaxViewports * 4> scissors{};
|
||||
Array<GLdouble, kMaxViewports * 2> depths{};
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
viewports[i * 4 + 2] = 1.0f;
|
||||
viewports[i * 4 + 3] = 1.0f;
|
||||
scissors[i * 4 + 2] = 1;
|
||||
scissors[i * 4 + 3] = 1;
|
||||
depths[i * 2 + 1] = 1.0;
|
||||
}
|
||||
|
||||
// first == MAX_VIEWPORTS, and first + count > MAX_VIEWPORTS.
|
||||
MG_Impl::GLImpl::ViewportArrayv(kMaxViewports, 1, viewports.data());
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::ViewportArrayv(1, kMaxViewports, viewports.data());
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::ScissorArrayv(kMaxViewports, 1, scissors.data());
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::ScissorArrayv(1, kMaxViewports, scissors.data());
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::DepthRangeArrayv(kMaxViewports, 1, depths.data());
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::DepthRangeArrayv(1, kMaxViewports, depths.data());
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
// first + count == MAX_VIEWPORTS is LEGAL - the off-by-one an ">=" bound would get wrong,
|
||||
// and one KHR-GL43.viewport_array.api_errors asserts explicitly.
|
||||
MG_Impl::GLImpl::ViewportArrayv(1, kMaxViewports - 1, viewports.data());
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::ScissorArrayv(1, kMaxViewports - 1, scissors.data());
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::DepthRangeArrayv(1, kMaxViewports - 1, depths.data());
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
|
||||
// A negative count is GL_INVALID_VALUE and must not be read as a huge unsigned length.
|
||||
MG_Impl::GLImpl::ViewportArrayv(0, -1, viewports.data());
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::ScissorArrayv(0, -1, scissors.data());
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::DepthRangeArrayv(0, -1, depths.data());
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, NegativeExtentsAreRejectedWithoutDisturbingState) {
|
||||
MG_Impl::GLImpl::Viewport(0, 0, 4, 4);
|
||||
MG_Impl::GLImpl::Scissor(0, 0, 4, 4);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
|
||||
MG_Impl::GLImpl::Viewport(0, 0, -1, 1);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::Viewport(0, 0, 1, -1);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::Scissor(0, 0, -1, 1);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::Scissor(0, 0, 1, -1);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
for (GLuint index = 0; index < kMaxViewports; ++index) {
|
||||
MG_Impl::GLImpl::ViewportIndexedf(index, 0.0f, 0.0f, -1.0f, 1.0f);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::ViewportIndexedf(index, 0.0f, 0.0f, 1.0f, -1.0f);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
const GLfloat badW[4] = {0.0f, 0.0f, -1.0f, 1.0f};
|
||||
MG_Impl::GLImpl::ViewportIndexedfv(index, badW);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
MG_Impl::GLImpl::ScissorIndexed(index, 0, 0, -1, 1);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
const GLint badH[4] = {0, 0, 1, -1};
|
||||
MG_Impl::GLImpl::ScissorIndexedv(index, badH);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
// The array form must reject the WHOLE call for one bad element, exactly once, and
|
||||
// leave every rectangle alone - api_errors submits a full 16-element array with a
|
||||
// single negative extent and then requires the error queue to hold one entry.
|
||||
Array<GLfloat, kMaxViewports * 4> viewports{};
|
||||
Array<GLint, kMaxViewports * 4> scissors{};
|
||||
for (GLuint i = 0; i < kMaxViewports; ++i) {
|
||||
viewports[i * 4 + 2] = 1.0f;
|
||||
viewports[i * 4 + 3] = 1.0f;
|
||||
scissors[i * 4 + 2] = 1;
|
||||
scissors[i * 4 + 3] = 1;
|
||||
}
|
||||
viewports[index * 4 + 2] = -1.0f;
|
||||
scissors[index * 4 + 3] = -1;
|
||||
MG_Impl::GLImpl::ViewportArrayv(0, kMaxViewports, viewports.data());
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::ScissorArrayv(0, kMaxViewports, scissors.data());
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
}
|
||||
|
||||
// Nothing above may have landed.
|
||||
GLint viewport[4] = {};
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_VIEWPORT, 0, viewport);
|
||||
EXPECT_EQ(viewport[2], 4);
|
||||
EXPECT_EQ(viewport[3], 4);
|
||||
GLint scissor[4] = {};
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_SCISSOR_BOX, 0, scissor);
|
||||
EXPECT_EQ(scissor[2], 4);
|
||||
EXPECT_EQ(scissor[3], 4);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(RenderStateTest, IndexedRectangleQueriesRejectAnOutOfRangeIndex) {
|
||||
GLint ints[4] = {};
|
||||
GLfloat floats[4] = {};
|
||||
GLdouble doubles[4] = {};
|
||||
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_SCISSOR_BOX, kMaxViewports, ints);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::GetFloati_v(GL_VIEWPORT, kMaxViewports, floats);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
MG_Impl::GLImpl::GetDoublei_v(GL_DEPTH_RANGE, kMaxViewports, doubles);
|
||||
ExpectSingleGlError(GL_INVALID_VALUE);
|
||||
|
||||
MG_Impl::GLImpl::GetIntegeri_v(GL_SCISSOR_BOX, kMaxViewports - 1, ints);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::GetFloati_v(GL_VIEWPORT, kMaxViewports - 1, floats);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
MG_Impl::GLImpl::GetDoublei_v(GL_DEPTH_RANGE, kMaxViewports - 1, doubles);
|
||||
ExpectSingleGlError(GL_NO_ERROR);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user