mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
[Fix, Test] (MG_Impl, MG_IntegrationTest): a block count may not exceed its binding points, and an atomic-counter buffer size must survive division by them
This commit is contained in:
@@ -174,6 +174,30 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return frontendCount;
|
||||
}
|
||||
|
||||
// A per-stage or combined BLOCK count is an amount of indexed binding points an
|
||||
// application will occupy, and GL 4.6 table 23.64 orders the two accordingly:
|
||||
// MAX_UNIFORM_BUFFER_BINDINGS >= MAX_COMBINED_UNIFORM_BLOCKS >= every per-stage count,
|
||||
// and the same for the shader-storage family. The two families are answered from
|
||||
// unrelated places here - frontend constants, backend dynamic parameters, and a few
|
||||
// hard-coded TODOs - so nothing kept them ordered, and a backend that reports Vulkan
|
||||
// descriptor-indexing counts advertised 256 compute uniform blocks over 36 binding
|
||||
// points. KHR-GL44.multi_bind.dispatch_bind_buffers_base reads the block count and binds
|
||||
// that many buffers in ONE glBindBuffersBase, which is then INVALID_OPERATION before it
|
||||
// binds anything. Clamping is the only direction available: the binding count is the
|
||||
// capacity of the state layer's indexed-binding array, not a number we may inflate.
|
||||
GLint ClampBlockCountToBindingPoints(GLint blockCount, BufferTarget bufferTarget) {
|
||||
const GLint bindingPoints = static_cast<GLint>(GetIndexedBufferQueryPointCount(bufferTarget));
|
||||
return std::min(std::max(blockCount, 0), bindingPoints);
|
||||
}
|
||||
|
||||
GLint ClampUniformBlockCount(GLint blockCount) {
|
||||
return ClampBlockCountToBindingPoints(blockCount, BufferTarget::Uniform);
|
||||
}
|
||||
|
||||
GLint ClampStorageBlockCount(GLint blockCount) {
|
||||
return ClampBlockCountToBindingPoints(blockCount, BufferTarget::ShaderStorage);
|
||||
}
|
||||
|
||||
bool TryDecodeDrawBufferQuery(GLenum pname, SizeT& drawBufferIndex) {
|
||||
if (pname == GL_DRAW_BUFFER) {
|
||||
drawBufferIndex = 0;
|
||||
@@ -1397,7 +1421,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = kFrontendMaxCombinedAtomicCounters;
|
||||
return;
|
||||
case GL_MAX_COMBINED_UNIFORM_BLOCKS:
|
||||
*params = kFrontendMaxCombinedUniformBlocks;
|
||||
*params = ClampUniformBlockCount(kFrontendMaxCombinedUniformBlocks);
|
||||
return;
|
||||
case GL_MAX_DUAL_SOURCE_DRAW_BUFFERS:
|
||||
*params = 1; // TODO
|
||||
@@ -1412,7 +1436,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = kFrontendMaxFragmentAtomicCounters;
|
||||
return;
|
||||
case GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS:
|
||||
*params = 16; // TODO
|
||||
*params = ClampStorageBlockCount(16); // TODO
|
||||
return;
|
||||
case GL_MAX_FRAGMENT_INPUT_COMPONENTS:
|
||||
*params = kFrontendMaxFragmentInputComponents;
|
||||
@@ -1429,13 +1453,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = kFrontendMaxFragmentUniformVectors;
|
||||
return;
|
||||
case GL_MAX_FRAGMENT_UNIFORM_BLOCKS:
|
||||
*params = kFrontendMaxFragmentUniformBlocks;
|
||||
*params = ClampUniformBlockCount(kFrontendMaxFragmentUniformBlocks);
|
||||
return;
|
||||
case GL_MAX_GEOMETRY_ATOMIC_COUNTERS:
|
||||
*params = kFrontendMaxGeometryAtomicCounters;
|
||||
return;
|
||||
case GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS:
|
||||
*params = 16; // TODO
|
||||
*params = ClampStorageBlockCount(16); // TODO
|
||||
return;
|
||||
case GL_MAX_GEOMETRY_INPUT_COMPONENTS:
|
||||
*params = kFrontendMaxGeometryInputComponents;
|
||||
@@ -1458,7 +1482,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = kFrontendMaxGeometryTotalOutputComponents;
|
||||
return;
|
||||
case GL_MAX_GEOMETRY_UNIFORM_BLOCKS:
|
||||
*params = kFrontendMaxGeometryUniformBlocks;
|
||||
*params = ClampUniformBlockCount(kFrontendMaxGeometryUniformBlocks);
|
||||
return;
|
||||
case GL_MAX_GEOMETRY_UNIFORM_COMPONENTS:
|
||||
*params = kFrontendMaxGeometryUniformComponents;
|
||||
@@ -1500,10 +1524,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = 0;
|
||||
return;
|
||||
case GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS:
|
||||
*params = 16; // TODO
|
||||
*params = ClampStorageBlockCount(16); // TODO
|
||||
return;
|
||||
case GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS:
|
||||
*params = 16; // TODO
|
||||
*params = ClampStorageBlockCount(16); // TODO
|
||||
return;
|
||||
case GL_MAX_TEXTURE_LOD_BIAS:
|
||||
*params = 15; // TODO
|
||||
@@ -1526,7 +1550,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
: MG_Backend::DynamicBackendParameters{}.MaxVertexImageUniforms;
|
||||
return;
|
||||
case GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS:
|
||||
*params = 16; // TODO
|
||||
*params = ClampStorageBlockCount(16); // TODO
|
||||
return;
|
||||
case GL_MAX_VERTEX_UNIFORM_COMPONENTS:
|
||||
*params = kFrontendMaxVertexUniformComponents;
|
||||
@@ -1538,7 +1562,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = kFrontendMaxVertexOutputComponents;
|
||||
return;
|
||||
case GL_MAX_VERTEX_UNIFORM_BLOCKS:
|
||||
*params = kFrontendMaxVertexUniformBlocks;
|
||||
*params = ClampUniformBlockCount(kFrontendMaxVertexUniformBlocks);
|
||||
return;
|
||||
case GL_NUM_COMPRESSED_TEXTURE_FORMATS:
|
||||
*params = 0; // compressed texture upload entrypoints are still unimplemented
|
||||
@@ -1938,13 +1962,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = dynamicParameters.SubgroupQuadOperationsInAllStages ? GL_TRUE : GL_FALSE;
|
||||
break;
|
||||
case GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS:
|
||||
*params = dynamicParameters.MaxComputeShaderStorageBlocks;
|
||||
*params = ClampStorageBlockCount(dynamicParameters.MaxComputeShaderStorageBlocks);
|
||||
break;
|
||||
case GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS:
|
||||
*params = dynamicParameters.MaxCombinedShaderStorageBlocks;
|
||||
*params = ClampStorageBlockCount(dynamicParameters.MaxCombinedShaderStorageBlocks);
|
||||
break;
|
||||
case GL_MAX_COMPUTE_UNIFORM_BLOCKS:
|
||||
*params = dynamicParameters.MaxComputeUniformBlocks;
|
||||
*params = ClampUniformBlockCount(dynamicParameters.MaxComputeUniformBlocks);
|
||||
break;
|
||||
case GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS:
|
||||
*params = dynamicParameters.MaxComputeTextureImageUnits;
|
||||
@@ -2074,7 +2098,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::AtomicCounter));
|
||||
break;
|
||||
case GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE:
|
||||
*params = kFrontendMaxAtomicCounterBufferSize;
|
||||
// The conformance suite splits this evenly across every advertised binding point and
|
||||
// binds all of them in one glBindBuffersRange
|
||||
// (KHR-GL44.multi_bind.functional_bind_buffers_range), so the pair has to divide:
|
||||
// 32 bytes over 36 binding points is a zero-sized range, which BindBufferRange
|
||||
// rejects with INVALID_VALUE before it binds anything. Floor the advertised size at
|
||||
// one counter per binding point.
|
||||
*params = std::max<GLint>(
|
||||
kFrontendMaxAtomicCounterBufferSize,
|
||||
static_cast<GLint>(GetIndexedBufferQueryPointCount(BufferTarget::AtomicCounter) * sizeof(GLuint)));
|
||||
break;
|
||||
case GL_MAX_TEXTURE_BUFFER_SIZE:
|
||||
*params = dynamicParameters.MaxTextureBufferSize;
|
||||
|
||||
@@ -94,6 +94,89 @@ namespace MGITest {
|
||||
}
|
||||
}
|
||||
|
||||
// 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";
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
Reference in New Issue
Block a user