mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
383 lines
25 KiB
C++
383 lines
25 KiB
C++
// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/AdvertisedLimitsScenario.cpp
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
//
|
|
// "The limit we advertise is a promise, and an application will hold us to it."
|
|
//
|
|
// DirectVulkan copied Vulkan descriptor limits straight into the GL limit table. Those are not
|
|
// the same quantity: Adreno answers maxPerStageDescriptorUniformBuffers at descriptor-indexing
|
|
// scale, and GL_MAX_COMPUTE_UNIFORM_BLOCKS is a count an app will allocate. KHR-GL44.multi_bind
|
|
// .dispatch_bind_buffers_base does exactly that - createsO(limit) buffers and splices O(limit)
|
|
// UBO declarations into one compute shader - and spent ~14 s allocating before dying on
|
|
// std::bad_alloc. Its sibling dispatch_bind_buffers_range hard-codes 4 buffers and passes.
|
|
//
|
|
// Two failure modes, one table:
|
|
// - too LARGE: an unusable promise (the OOM above).
|
|
// - too SMALL or negative: a uint32 limit that lost its top bit on the way to a signed Int -
|
|
// UINT32_MAX arrived as -1, which every downstream std::min then accepted as "small enough".
|
|
// A conformant GL 4.x implementation may never advertise below the spec minimum either.
|
|
//
|
|
// Every bound below is checked on BOTH backends, because the loader casts are shared and the
|
|
// DirectGLES lane is the control: it takes its limits from a driver that already reports GL
|
|
// quantities, so an entry that only fails on DirectVulkan is a translation bug and one that
|
|
// fails on both is a table bug.
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "../Harness/HeadlessGL.h"
|
|
#include "../Harness/ScenarioFixture.h"
|
|
|
|
#ifdef GLAPI
|
|
#undef GLAPI
|
|
#endif
|
|
#define GL_GLEXT_PROTOTYPES
|
|
#include <GL/gl.h>
|
|
#include <GL/glcorearb.h>
|
|
#undef GL_GLEXT_PROTOTYPES
|
|
|
|
namespace MGITest {
|
|
namespace {
|
|
|
|
struct LimitBound {
|
|
GLenum pname;
|
|
const char* name;
|
|
// The GL 4.x required minimum. A value below this is a conformance failure in its own
|
|
// right, and is what a sign-flipped uint32 looks like.
|
|
int minimum;
|
|
// The largest value this implementation is willing to promise. Chosen well above every
|
|
// desktop driver's answer, so it can only catch a descriptor-scale number.
|
|
int ceiling;
|
|
};
|
|
|
|
const std::vector<LimitBound>& BufferLimitTable() {
|
|
static const std::vector<LimitBound> table = {
|
|
// 84 is the GL 4.5 core table 23.64 minimum, and also the width of the state
|
|
// layer's indexed-binding array - the two were made to coincide when the array
|
|
// was widened from 36, which had made the clamp in GL_Getter degenerate.
|
|
{GL_MAX_UNIFORM_BUFFER_BINDINGS, "GL_MAX_UNIFORM_BUFFER_BINDINGS", 84, 256},
|
|
// 14 uniform blocks on each of the FIVE graphics stages. The sum used to count
|
|
// three, and the two tessellation stages were simply missing from it.
|
|
{GL_MAX_COMBINED_UNIFORM_BLOCKS, "GL_MAX_COMBINED_UNIFORM_BLOCKS", 70, 256},
|
|
{GL_MAX_COMPUTE_UNIFORM_BLOCKS, "GL_MAX_COMPUTE_UNIFORM_BLOCKS", 12, 256},
|
|
{GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, "GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS", 8, 256},
|
|
{GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, "GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS", 8, 256},
|
|
{GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, "GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS", 8, 256},
|
|
{GL_MAX_TEXTURE_BUFFER_SIZE, "GL_MAX_TEXTURE_BUFFER_SIZE", 65536, 1 << 27},
|
|
{GL_MAX_UNIFORM_BLOCK_SIZE, "GL_MAX_UNIFORM_BLOCK_SIZE", 16384, 1 << 30},
|
|
// Already clamped before this campaign; in the table so a regression there is
|
|
// caught by the same case.
|
|
{GL_MAX_SHADER_STORAGE_BLOCK_SIZE, "GL_MAX_SHADER_STORAGE_BLOCK_SIZE", 1 << 24, 512 * 1024 * 1024},
|
|
{GL_MAX_TEXTURE_IMAGE_UNITS, "GL_MAX_TEXTURE_IMAGE_UNITS", 16, 32},
|
|
{GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS", 48, 192},
|
|
};
|
|
return table;
|
|
}
|
|
|
|
class AdvertisedLimitsScenario : public ScenarioTest {};
|
|
|
|
TEST_F(AdvertisedLimitsScenario, EveryBufferLimitIsWithinItsAdvertisedRange) {
|
|
for (const LimitBound& bound : BufferLimitTable()) {
|
|
GLint value = -424242;
|
|
glGetIntegerv(bound.pname, &value);
|
|
const unsigned int error = FirstGLError();
|
|
EXPECT_EQ(error, GLenum(GL_NO_ERROR))
|
|
<< bound.name << " is not answerable: " << GLErrorName(error);
|
|
if (error != GL_NO_ERROR) continue;
|
|
|
|
EXPECT_GE(value, bound.minimum)
|
|
<< bound.name << " = " << value << " is below the GL required minimum "
|
|
<< bound.minimum << " (a negative or tiny value here is a uint32 limit that lost "
|
|
"its top bit on the way to a signed Int)";
|
|
EXPECT_LE(value, bound.ceiling)
|
|
<< bound.name << " = " << value << " exceeds the ceiling " << bound.ceiling
|
|
<< " this implementation is willing to promise - an application that allocates "
|
|
"what we advertise will run out of memory";
|
|
}
|
|
}
|
|
|
|
// A per-stage block count is an amount of BINDING POINTS an application will use, so it
|
|
// can never exceed the number of binding points that exist. GL 4.6 Table 23.64 states the
|
|
// relation the other way round (MAX_UNIFORM_BUFFER_BINDINGS >= MAX_COMBINED_UNIFORM_BLOCKS
|
|
// >= every per-stage count), and DirectVulkan broke it by clamping the two families
|
|
// independently: a device reporting 256 compute uniform blocks and 84 uniform binding
|
|
// points passes both ceilings and still cannot serve
|
|
// KHR-GL44.multi_bind.dispatch_bind_buffers_base, which reads the block count and binds
|
|
// that many buffers in one glBindBuffersBase - INVALID_OPERATION before a single bind.
|
|
TEST_F(AdvertisedLimitsScenario, PerStageBlockCountsFitInTheirBindingPoints) {
|
|
struct Relation {
|
|
GLenum blocks;
|
|
const char* blocksName;
|
|
GLenum bindings;
|
|
const char* bindingsName;
|
|
};
|
|
const Relation relations[] = {
|
|
{GL_MAX_COMPUTE_UNIFORM_BLOCKS, "GL_MAX_COMPUTE_UNIFORM_BLOCKS", GL_MAX_UNIFORM_BUFFER_BINDINGS,
|
|
"GL_MAX_UNIFORM_BUFFER_BINDINGS"},
|
|
{GL_MAX_VERTEX_UNIFORM_BLOCKS, "GL_MAX_VERTEX_UNIFORM_BLOCKS", GL_MAX_UNIFORM_BUFFER_BINDINGS,
|
|
"GL_MAX_UNIFORM_BUFFER_BINDINGS"},
|
|
{GL_MAX_FRAGMENT_UNIFORM_BLOCKS, "GL_MAX_FRAGMENT_UNIFORM_BLOCKS", GL_MAX_UNIFORM_BUFFER_BINDINGS,
|
|
"GL_MAX_UNIFORM_BUFFER_BINDINGS"},
|
|
{GL_MAX_COMBINED_UNIFORM_BLOCKS, "GL_MAX_COMBINED_UNIFORM_BLOCKS", GL_MAX_UNIFORM_BUFFER_BINDINGS,
|
|
"GL_MAX_UNIFORM_BUFFER_BINDINGS"},
|
|
{GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, "GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS",
|
|
GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, "GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS"},
|
|
{GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, "GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS",
|
|
GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, "GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS"},
|
|
};
|
|
for (const Relation& relation : relations) {
|
|
GLint blocks = -1;
|
|
GLint bindings = -1;
|
|
glGetIntegerv(relation.blocks, &blocks);
|
|
glGetIntegerv(relation.bindings, &bindings);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << relation.blocksName;
|
|
EXPECT_LE(blocks, bindings)
|
|
<< relation.blocksName << " = " << blocks << " exceeds " << relation.bindingsName << " = "
|
|
<< bindings << "; a shader may declare more blocks than there are binding points to bind them to";
|
|
}
|
|
|
|
// THE MIDDLE TERM, which the relation quoted above always had and this case never
|
|
// checked. It is the one that actually broke: widening the binding-point array to 84
|
|
// raised what every PER-STAGE count clamps to, while the combined value was a
|
|
// five-stage sum of 70 - so a device reporting descriptor-indexing-scale uniform
|
|
// buffers (Adreno: maxPerStageDescriptorUniformBuffers = 16777216) advertised 84
|
|
// compute uniform blocks inside a combined limit of 70. Per-stage <= combined is
|
|
// exactly the assertion that says so, and it costs one glGetIntegerv per row.
|
|
struct StageAgainstCombined {
|
|
GLenum stage;
|
|
const char* stageName;
|
|
GLenum combined;
|
|
const char* combinedName;
|
|
};
|
|
const StageAgainstCombined stageRelations[] = {
|
|
{GL_MAX_COMPUTE_UNIFORM_BLOCKS, "GL_MAX_COMPUTE_UNIFORM_BLOCKS", GL_MAX_COMBINED_UNIFORM_BLOCKS,
|
|
"GL_MAX_COMBINED_UNIFORM_BLOCKS"},
|
|
{GL_MAX_VERTEX_UNIFORM_BLOCKS, "GL_MAX_VERTEX_UNIFORM_BLOCKS", GL_MAX_COMBINED_UNIFORM_BLOCKS,
|
|
"GL_MAX_COMBINED_UNIFORM_BLOCKS"},
|
|
{GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS, "GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS",
|
|
GL_MAX_COMBINED_UNIFORM_BLOCKS, "GL_MAX_COMBINED_UNIFORM_BLOCKS"},
|
|
{GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, "GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS",
|
|
GL_MAX_COMBINED_UNIFORM_BLOCKS, "GL_MAX_COMBINED_UNIFORM_BLOCKS"},
|
|
{GL_MAX_GEOMETRY_UNIFORM_BLOCKS, "GL_MAX_GEOMETRY_UNIFORM_BLOCKS", GL_MAX_COMBINED_UNIFORM_BLOCKS,
|
|
"GL_MAX_COMBINED_UNIFORM_BLOCKS"},
|
|
{GL_MAX_FRAGMENT_UNIFORM_BLOCKS, "GL_MAX_FRAGMENT_UNIFORM_BLOCKS", GL_MAX_COMBINED_UNIFORM_BLOCKS,
|
|
"GL_MAX_COMBINED_UNIFORM_BLOCKS"},
|
|
{GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, "GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS",
|
|
GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, "GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS"},
|
|
{GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, "GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS",
|
|
GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, "GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS"},
|
|
};
|
|
for (const StageAgainstCombined& relation : stageRelations) {
|
|
GLint stage = -1;
|
|
GLint combined = -1;
|
|
glGetIntegerv(relation.stage, &stage);
|
|
glGetIntegerv(relation.combined, &combined);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << relation.stageName;
|
|
EXPECT_LE(stage, combined)
|
|
<< relation.stageName << " = " << stage << " exceeds " << relation.combinedName << " = "
|
|
<< combined << "; GL 4.6 table 23.64 orders MAX_*_BUFFER_BINDINGS >= MAX_COMBINED_*_BLOCKS >= "
|
|
"every per-stage count, and a single-stage program may use its whole per-stage allowance";
|
|
}
|
|
}
|
|
|
|
// KHR-GL44.multi_bind.functional_bind_buffers_range sizes each of an indexed target's
|
|
// binding points at MAX_<target>_SIZE / MAX_<target>_BINDINGS and binds all of them in
|
|
// one glBindBuffersRange. That quotient has to be a legal BindBufferRange size, which
|
|
// makes the two limits of every indexed family a PAIR: advertise a size that does not
|
|
// survive division by the binding count and the call fails with INVALID_VALUE before any
|
|
// of it binds.
|
|
TEST_F(AdvertisedLimitsScenario, IndexedTargetSizeSurvivesDivisionByItsBindingCount) {
|
|
struct IndexedFamily {
|
|
GLenum maxSize;
|
|
const char* maxSizeName;
|
|
GLenum maxBindings;
|
|
const char* maxBindingsName;
|
|
GLint sizeGranularity; // BindBufferRange's size rule for the target
|
|
};
|
|
const IndexedFamily families[] = {
|
|
{GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE, "GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE",
|
|
GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS, "GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS", 1},
|
|
{GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS, "GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS",
|
|
GL_MAX_TRANSFORM_FEEDBACK_BUFFERS, "GL_MAX_TRANSFORM_FEEDBACK_BUFFERS", 4},
|
|
{GL_MAX_UNIFORM_BLOCK_SIZE, "GL_MAX_UNIFORM_BLOCK_SIZE", GL_MAX_UNIFORM_BUFFER_BINDINGS,
|
|
"GL_MAX_UNIFORM_BUFFER_BINDINGS", 1},
|
|
{GL_MAX_SHADER_STORAGE_BLOCK_SIZE, "GL_MAX_SHADER_STORAGE_BLOCK_SIZE",
|
|
GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, "GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS", 1},
|
|
};
|
|
for (const IndexedFamily& family : families) {
|
|
GLint maxSize = -1;
|
|
GLint maxBindings = -1;
|
|
glGetIntegerv(family.maxSize, &maxSize);
|
|
glGetIntegerv(family.maxBindings, &maxBindings);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << family.maxSizeName;
|
|
ASSERT_GT(maxBindings, 0) << family.maxBindingsName;
|
|
const GLint perBinding = maxSize / maxBindings;
|
|
EXPECT_GT(perBinding, 0)
|
|
<< family.maxSizeName << " (" << maxSize << ") / " << family.maxBindingsName << " ("
|
|
<< maxBindings << ") is zero, and BindBufferRange rejects a zero size";
|
|
EXPECT_EQ(perBinding % family.sizeGranularity, 0)
|
|
<< family.maxSizeName << " (" << maxSize << ") / " << family.maxBindingsName << " ("
|
|
<< maxBindings << ") = " << perBinding << " is not a multiple of the "
|
|
<< family.sizeGranularity << "-byte size granularity BindBufferRange requires for it";
|
|
}
|
|
}
|
|
|
|
// The OOM case in isolation, because it is the one with a known CTS victim and the one a
|
|
// future refactor is most likely to reintroduce by copying the Vulkan limit back.
|
|
TEST_F(AdvertisedLimitsScenario, ComputeUniformBlocksIsAnAmountAnApplicationCouldActuallyAllocate) {
|
|
GLint blocks = -1;
|
|
glGetIntegerv(GL_MAX_COMPUTE_UNIFORM_BLOCKS, &blocks);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
|
EXPECT_GE(blocks, 12);
|
|
EXPECT_LE(blocks, 256) << "KHR-GL44.multi_bind.dispatch_bind_buffers_base creates one GL buffer "
|
|
"and one UBO declaration per advertised block";
|
|
|
|
GLint blockSize = -1;
|
|
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &blockSize);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
|
EXPECT_GT(blockSize, 0);
|
|
// GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS is derived from the product of these two,
|
|
// so their product has to stay representable.
|
|
EXPECT_LE(static_cast<long long>(blocks) * blockSize,
|
|
static_cast<long long>(2147483647))
|
|
<< "blocks(" << blocks << ") * blockSize(" << blockSize << ") overflows the GLint the "
|
|
"derived component limits are computed in";
|
|
}
|
|
|
|
// The GL 4.5 core minimums that had no case in the getter at all, or that were still
|
|
// carrying an ES/GL3.3-tier number. Every one of these answered GL_INVALID_ENUM or a
|
|
// too-small value against a context advertising 4.6, and each is the FIRST call its
|
|
// conformance case makes - so the case died before it could measure anything.
|
|
//
|
|
// The cull pair is deliberately absent: zero is a legal answer there (a backend with no
|
|
// cull-distance route MUST report it), so it is checked for answerability only, below.
|
|
TEST_F(AdvertisedLimitsScenario, EveryGL45CoreMinimumIsMet) {
|
|
const std::vector<LimitBound> table = {
|
|
{GL_MAX_VARYING_VECTORS, "GL_MAX_VARYING_VECTORS", 15, 256},
|
|
{GL_MAX_VERTEX_UNIFORM_VECTORS, "GL_MAX_VERTEX_UNIFORM_VECTORS", 256, 1 << 20},
|
|
{GL_MAX_VARYING_COMPONENTS, "GL_MAX_VARYING_COMPONENTS", 60, 1 << 20},
|
|
// GL_MAX_VERTEX_STREAMS is deliberately absent. GL 4.5 requires 4 and MobileGL
|
|
// answers 1, which is a KNOWN non-conformance rather than an oversight: raising
|
|
// the number un-gates two transform-feedback CTS cases per package across
|
|
// KHR-GL40..GL46 that then fail, because no part of the shader pipeline supports
|
|
// layout(stream = N). See the GL_MAX_VERTEX_STREAMS case in GL_Getter.cpp. Adding
|
|
// a row here would pin a number the implementation cannot back.
|
|
{GL_MAX_GEOMETRY_SHADER_INVOCATIONS, "GL_MAX_GEOMETRY_SHADER_INVOCATIONS", 32, 256},
|
|
{GL_MAX_SUBROUTINES, "GL_MAX_SUBROUTINES", 256, 1 << 20},
|
|
{GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS, "GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS", 1024, 1 << 20},
|
|
{GL_MAX_TESS_CONTROL_INPUT_COMPONENTS, "GL_MAX_TESS_CONTROL_INPUT_COMPONENTS", 128, 1 << 16},
|
|
{GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS, "GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS", 128, 1 << 16},
|
|
{GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS, "GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS", 4096,
|
|
1 << 20},
|
|
{GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS, "GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS", 16, 256},
|
|
{GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS, "GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS", 1024, 1 << 20},
|
|
{GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS, "GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS", 14, 256},
|
|
{GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS, "GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS", 128, 1 << 16},
|
|
{GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS, "GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS", 128, 1 << 16},
|
|
{GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS, "GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS", 16, 256},
|
|
{GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS, "GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS", 1024,
|
|
1 << 20},
|
|
{GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, "GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS", 14, 256},
|
|
{GL_MAX_TESS_PATCH_COMPONENTS, "GL_MAX_TESS_PATCH_COMPONENTS", 120, 1 << 16},
|
|
{GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS, "GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS",
|
|
58368, 1 << 30},
|
|
{GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS,
|
|
"GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS", 58368, 1 << 30},
|
|
};
|
|
for (const LimitBound& bound : table) {
|
|
GLint value = -424242;
|
|
glGetIntegerv(bound.pname, &value);
|
|
const unsigned int error = FirstGLError();
|
|
EXPECT_EQ(error, GLenum(GL_NO_ERROR)) << bound.name << " is not answerable: " << GLErrorName(error);
|
|
if (error != GL_NO_ERROR) continue;
|
|
EXPECT_GE(value, bound.minimum) << bound.name << " = " << value << " is below the GL 4.5 minimum "
|
|
<< bound.minimum;
|
|
EXPECT_LE(value, bound.ceiling) << bound.name << " = " << value << " exceeds the ceiling "
|
|
<< bound.ceiling;
|
|
}
|
|
|
|
// ARB_cull_distance's pair. Zero is honest on a backend with no cull-distance route,
|
|
// so only answerability and the combined-limit ordering are checked here.
|
|
GLint cull = -1;
|
|
GLint clip = -1;
|
|
GLint combined = -1;
|
|
glGetIntegerv(GL_MAX_CULL_DISTANCES, &cull);
|
|
glGetIntegerv(GL_MAX_CLIP_DISTANCES, &clip);
|
|
glGetIntegerv(GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES, &combined);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the ARB_cull_distance queries must not error";
|
|
EXPECT_GE(cull, 0);
|
|
EXPECT_GE(combined, cull) << "GL 4.6 core 11.1.3.10: the combined limit is at least the cull one";
|
|
EXPECT_GE(combined, clip) << "GL 4.6 core 11.1.3.10: the combined limit is at least the clip one";
|
|
|
|
// GL_MAX_ELEMENT_INDEX is 64-bit state: the required 2^32-1 does not fit a GLint, so
|
|
// the wide query must answer it and the narrow one must saturate rather than wrap.
|
|
GLint64 elementIndex = -1;
|
|
glGetInteger64v(GL_MAX_ELEMENT_INDEX, &elementIndex);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
|
EXPECT_GE(elementIndex, static_cast<GLint64>(4294967295LL))
|
|
<< "GL 4.5 core table 23.55 sets the GL_MAX_ELEMENT_INDEX minimum at 2^32-1";
|
|
}
|
|
|
|
// ARB_viewport_array's own limits. They are advertised from three different places -
|
|
// GL_MAX_VIEWPORTS from the frontend's indexed state width, the bounds range and the
|
|
// subpixel bits from the backend caps table - and each backend fills that table from a
|
|
// different source, so all three are checked on both lanes.
|
|
//
|
|
// GL_VIEWPORT_BOUNDS_RANGE is the one that shipped wrong: GLES has no such query, the
|
|
// DirectGLES loader's glGetFloatv(GL_VIEWPORT_BOUNDS_RANGE) therefore raised
|
|
// GL_INVALID_ENUM and left the probe's zero-initialized array in place, and MobileGL
|
|
// advertised [0, 0] - a range that admits no viewport origin at all, and the check that
|
|
// kept KHR-GL43.viewport_array.queries red on Espryt after the indexed-state work.
|
|
TEST_F(AdvertisedLimitsScenario, ViewportArrayLimitsMeetTheirGL43Floors) {
|
|
GLint maxViewports = -1;
|
|
glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
|
EXPECT_GE(maxViewports, 16) << "GL 4.3 core table 23.53 sets the MAX_VIEWPORTS minimum at 16";
|
|
EXPECT_LE(maxViewports, 256) << "one viewport rectangle of indexed state is allocated per advertised "
|
|
"viewport, and the CTS sizes its arrays off this number";
|
|
|
|
GLfloat boundsRange[2] = {1.0f, -1.0f};
|
|
glGetFloatv(GL_VIEWPORT_BOUNDS_RANGE, boundsRange);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
|
EXPECT_LE(boundsRange[0], -32768.0f)
|
|
<< "GL 4.6 core table 23.60 sets the VIEWPORT_BOUNDS_RANGE minimum at [-32768, 32767]; got ["
|
|
<< boundsRange[0] << ", " << boundsRange[1] << "]";
|
|
EXPECT_GE(boundsRange[1], 32767.0f)
|
|
<< "GL 4.6 core table 23.60 sets the VIEWPORT_BOUNDS_RANGE minimum at [-32768, 32767]; got ["
|
|
<< boundsRange[0] << ", " << boundsRange[1] << "]";
|
|
|
|
// KNOWN INFIDELITY, pinned here rather than hidden. MobileGL reports the driver's own
|
|
// VIEWPORT_SUBPIXEL_BITS (4 on llvmpipe, i.e. 1/16-pixel viewport precision), but the
|
|
// float viewport rectangle glViewportIndexedf stores is snapped to integers on its
|
|
// way to both backends (ComputeGLViewport, DirectGLES SyncRenderState). The STATE
|
|
// round trip is exact - which is all KHR-GL43.viewport_array.viewport_api checks, and
|
|
// all this cluster set out to fix - so the gap is in rasterization only: a fractional
|
|
// viewport origin rasterizes as if it had been rounded. Nothing in the suite or in
|
|
// Minecraft sets one. Only the spec floor is asserted; tightening this to EQ(0) would
|
|
// mean advertising no subpixel precision at all, which is a separate decision about a
|
|
// limit MobileGL currently passes through from the driver.
|
|
GLint subpixelBits = -1;
|
|
glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &subpixelBits);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
|
EXPECT_GE(subpixelBits, 0) << "GL 4.6 core table 23.60: VIEWPORT_SUBPIXEL_BITS has a minimum of 0, and "
|
|
"a negative value is what a sign-flipped uint32 looks like";
|
|
|
|
GLint viewportDims[2] = {-1, -1};
|
|
glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewportDims);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
|
GLint maxRenderbufferSize = -1;
|
|
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxRenderbufferSize);
|
|
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
|
// GL 4.6 core 13.6.1: MAX_VIEWPORT_DIMS must be at least as large as the largest
|
|
// renderable surface, or a full-size framebuffer could not be fully viewported.
|
|
EXPECT_GE(viewportDims[0], maxRenderbufferSize);
|
|
EXPECT_GE(viewportDims[1], maxRenderbufferSize);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace MGITest
|