mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
[Fix] (BackendLoader, DirectGLES, DirectVulkan): derive the per-stage shader storage block limits from the backend
This commit is contained in:
@@ -340,6 +340,22 @@ namespace MobileGL {
|
||||
Int MaxVertexAttribs = 16;
|
||||
Int MaxComputeShaderStorageBlocks = 8;
|
||||
Int MaxCombinedShaderStorageBlocks = 32;
|
||||
// Per-stage GL_MAX_*_SHADER_STORAGE_BLOCKS. Zero is a legal answer for the four
|
||||
// non-compute, non-fragment stages and these defaults are the spec minimums, not
|
||||
// placeholders: GL 4.6 table 23.64 and ES 3.2 table 21.44 both set the minimum for
|
||||
// vertex, tessellation control, tessellation evaluation and geometry at 0, and only
|
||||
// fragment (8 in GL, 4 in ES) and compute are guaranteed to have any. Every real ARM
|
||||
// GLES driver takes that allowance - a Mali-G925 reports 0 for all four - so a
|
||||
// backend that cannot honour a graphics-stage storage block MUST report 0 here
|
||||
// rather than a hopeful number. Advertising a non-zero count the driver will refuse
|
||||
// does not make the block work; it only moves the failure from an honest
|
||||
// "unsupported" at query time to a backend link error the frontend never surfaces,
|
||||
// after which every draw with that program silently renders nothing.
|
||||
Int MaxVertexShaderStorageBlocks = 0;
|
||||
Int MaxTessControlShaderStorageBlocks = 0;
|
||||
Int MaxTessEvaluationShaderStorageBlocks = 0;
|
||||
Int MaxGeometryShaderStorageBlocks = 0;
|
||||
Int MaxFragmentShaderStorageBlocks = 8;
|
||||
Int MaxComputeUniformBlocks = 12;
|
||||
Int MaxComputeWorkGroupInvocations = 128;
|
||||
Int MaxShaderStorageBufferBindings = 8;
|
||||
|
||||
@@ -1251,9 +1251,31 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
static_cast<Int>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS));
|
||||
m_dynamicParameters.MaxComputeShaderStorageBlocks = m_GLESCapabilities.MaxComputeShaderStorageBlocks;
|
||||
m_dynamicParameters.MaxCombinedShaderStorageBlocks = m_GLESCapabilities.MaxCombinedShaderStorageBlocks;
|
||||
// Per-stage storage-block counts, forwarded from the host driver rather than invented.
|
||||
// A stage the driver cannot serve reports 0, which is a legal answer everywhere these
|
||||
// limits appear (GL 4.6 table 23.64, ES 3.2 table 21.44 - the minimum is 0 for every
|
||||
// graphics stage except fragment) and is the only answer that lets an application take
|
||||
// its own fallback instead of building a program the driver will refuse to link. The
|
||||
// stage limit cannot exceed the combined limit or the number of binding points there
|
||||
// are to bind buffers to, so clamp to both.
|
||||
const auto clampStageStorageBlocks = [this](Int stageLimit) {
|
||||
return std::min({std::max(stageLimit, 0), std::max(m_dynamicParameters.MaxCombinedShaderStorageBlocks, 0),
|
||||
std::max(m_dynamicParameters.MaxShaderStorageBufferBindings, 0)});
|
||||
};
|
||||
m_dynamicParameters.MaxShaderStorageBufferBindings = m_GLESCapabilities.MaxShaderStorageBufferBindings;
|
||||
m_dynamicParameters.MaxVertexShaderStorageBlocks =
|
||||
clampStageStorageBlocks(m_GLESCapabilities.MaxVertexShaderStorageBlocks);
|
||||
m_dynamicParameters.MaxTessControlShaderStorageBlocks =
|
||||
clampStageStorageBlocks(m_GLESCapabilities.MaxTessControlShaderStorageBlocks);
|
||||
m_dynamicParameters.MaxTessEvaluationShaderStorageBlocks =
|
||||
clampStageStorageBlocks(m_GLESCapabilities.MaxTessEvaluationShaderStorageBlocks);
|
||||
m_dynamicParameters.MaxGeometryShaderStorageBlocks =
|
||||
clampStageStorageBlocks(m_GLESCapabilities.MaxGeometryShaderStorageBlocks);
|
||||
m_dynamicParameters.MaxFragmentShaderStorageBlocks =
|
||||
clampStageStorageBlocks(m_GLESCapabilities.MaxFragmentShaderStorageBlocks);
|
||||
m_dynamicParameters.MaxComputeUniformBlocks = m_GLESCapabilities.MaxComputeUniformBlocks;
|
||||
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_GLESCapabilities.MaxComputeWorkGroupInvocations;
|
||||
m_dynamicParameters.MaxShaderStorageBufferBindings = m_GLESCapabilities.MaxShaderStorageBufferBindings;
|
||||
// (MaxShaderStorageBufferBindings is assigned above, before the per-stage clamp reads it.)
|
||||
// This is the number glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE) hands the application, and
|
||||
// on a host without buffer textures it is knowingly a floor MobileGL cannot honour rather
|
||||
// than a driver answer (m_GLESCapabilities.MaxTextureBufferSizeIsDriverReported says
|
||||
|
||||
@@ -847,6 +847,38 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_dynamicParameters.MaxShaderStorageBufferBindings =
|
||||
clampLimit("GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS", m_vulkanCaps.MaxShaderStorageBufferBindings,
|
||||
kMaxAdvertisedBufferBlocks);
|
||||
// Per-stage GL_MAX_*_SHADER_STORAGE_BLOCKS. Vulkan has one descriptor limit for every
|
||||
// stage (maxPerStageDescriptorStorageBuffers, which is what MaxComputeShaderStorageBlocks
|
||||
// carries), so the stage limits differ only by whether the stage can have blocks at all.
|
||||
//
|
||||
// Deliberately NOT gated on vertexPipelineStoresAndAtomics, unlike the per-stage image
|
||||
// uniforms below. That gate reads as the obvious one and is wrong here in practice: a
|
||||
// Mali-G925-Immortalis reports vertexPipelineStoresAndAtomics=false (supported AND
|
||||
// enabled) and yet runs all 433 KHR-GL43.constant_expressions.*_tess_* cases correctly
|
||||
// through this backend - those write their result through a storage block declared in a
|
||||
// tessellation stage. Gating would report 0 and turn 433 passing cases into
|
||||
// "unsupported", removing function that demonstrably works.
|
||||
//
|
||||
// The asymmetry with DirectGLES is real and is the point. There, 0 prevents a program
|
||||
// the driver refuses outright at link time; the honest limit converts a silent
|
||||
// wrong-render into a capability an application can route around. Here there is no such
|
||||
// failure to prevent, so the limit stays at what the device can address. If a Vulkan
|
||||
// device is ever found that genuinely rejects such a pipeline, the gate belongs at
|
||||
// pipeline creation where the rejection is observable, not on a feature bit this driver
|
||||
// reports inaccurately.
|
||||
{
|
||||
const Int maxPerStageStorageBlocks =
|
||||
std::min(std::max(m_dynamicParameters.MaxComputeShaderStorageBlocks, 0),
|
||||
std::min(std::max(m_dynamicParameters.MaxCombinedShaderStorageBlocks, 0),
|
||||
std::max(m_dynamicParameters.MaxShaderStorageBufferBindings, 0)));
|
||||
m_dynamicParameters.MaxVertexShaderStorageBlocks = maxPerStageStorageBlocks;
|
||||
m_dynamicParameters.MaxTessControlShaderStorageBlocks = maxPerStageStorageBlocks;
|
||||
m_dynamicParameters.MaxTessEvaluationShaderStorageBlocks = maxPerStageStorageBlocks;
|
||||
// The one hard capability in the set: no geometry stage means no blocks in it.
|
||||
m_dynamicParameters.MaxGeometryShaderStorageBlocks =
|
||||
m_vulkanCaps.SupportsGeometryShader ? maxPerStageStorageBlocks : 0;
|
||||
m_dynamicParameters.MaxFragmentShaderStorageBlocks = maxPerStageStorageBlocks;
|
||||
}
|
||||
m_dynamicParameters.MaxTextureBufferSize = clampLimit(
|
||||
"GL_MAX_TEXTURE_BUFFER_SIZE", m_vulkanCaps.MaxTextureBufferSize, kMaxAdvertisedTextureBufferSize);
|
||||
m_dynamicParameters.TextureBufferOffsetAlignment = m_vulkanCaps.TextureBufferOffsetAlignment;
|
||||
|
||||
@@ -692,8 +692,12 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
!f.glUnmapBuffer || !f.glMemoryBarrier || !f.glCreateShader || !f.glCreateProgram) {
|
||||
return false;
|
||||
}
|
||||
GLint maxVertexSsboBlocks = 0;
|
||||
f.glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &maxVertexSsboBlocks);
|
||||
// Read from caps, not re-queried: the per-stage limits are resolved (and their query
|
||||
// errors drained) before this probe runs, so asking the driver again would be a second
|
||||
// round trip that can disagree with the number MobileGL actually advertises - and, on
|
||||
// the early return below, would leave its own GL_INVALID_ENUM in the queue for the
|
||||
// application's first glGetError to find.
|
||||
const GLint maxVertexSsboBlocks = caps.MaxVertexShaderStorageBlocks;
|
||||
if (maxVertexSsboBlocks < 1) {
|
||||
// The native indirect machinery cannot read the command buffer from the vertex
|
||||
// stage on this driver anyway; assume conforming zero-based gl_InstanceID.
|
||||
@@ -1036,6 +1040,15 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
GLint maxVertexAttribs = 16;
|
||||
GLint maxComputeShaderStorageBlocks = 8;
|
||||
GLint maxCombinedShaderStorageBlocks = 32;
|
||||
// ES 3.2 table 21.44 minimums. Zero for the four graphics stages below fragment is not a
|
||||
// placeholder - it is what the spec permits and what ARM's GLES driver actually reports,
|
||||
// so a probe that never runs (pre-ES 3.2, unsupported pname) leaves behind the truthful
|
||||
// answer rather than an optimistic one.
|
||||
GLint maxVertexShaderStorageBlocks = 0;
|
||||
GLint maxTessControlShaderStorageBlocks = 0;
|
||||
GLint maxTessEvaluationShaderStorageBlocks = 0;
|
||||
GLint maxGeometryShaderStorageBlocks = 0;
|
||||
GLint maxFragmentShaderStorageBlocks = 4;
|
||||
GLint maxComputeUniformBlocks = 12;
|
||||
GLint maxComputeWorkGroupInvocations = 128;
|
||||
GLint maxShaderStorageBufferBindings = 8;
|
||||
@@ -1127,6 +1140,59 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2)) {
|
||||
glesFuncs.glGetIntegerv(GL_MAX_GEOMETRY_IMAGE_UNIFORMS, &maxGeometryImageUniforms);
|
||||
}
|
||||
// Per-stage storage-block counts. Deliberately NOT batched with the unconditional probes
|
||||
// above, for the reason GL_MAX_TEXTURE_BUFFER_SIZE is not: the vertex and fragment pnames
|
||||
// are ES 3.1, but the tessellation and geometry ones only exist from ES 3.2 on (or under
|
||||
// EXT_tessellation_shader / EXT_geometry_shader), so on an older context they raise
|
||||
// GL_INVALID_ENUM, leave the local untouched, and - with nothing draining the queue until
|
||||
// some later probe - let that error be misattributed to an unrelated query in between, or
|
||||
// leak into the application's first glGetError.
|
||||
//
|
||||
// A stage whose probe does not run keeps the spec minimum, which for all four graphics
|
||||
// stages is 0. That is the honest answer: DirectGLES emits ESSL 3.10 on an ES 3.1 context,
|
||||
// where those stages do not exist at all.
|
||||
{
|
||||
const auto drainErrors = [&glesFuncs]() {
|
||||
Bool hadError = false;
|
||||
if (glesFuncs.glGetError) {
|
||||
while (glesFuncs.glGetError() != GL_NO_ERROR) hadError = true;
|
||||
}
|
||||
return hadError;
|
||||
};
|
||||
|
||||
// Isolate from errors raised by the preceding probes so the drain below reports on
|
||||
// these queries only.
|
||||
drainErrors();
|
||||
glesFuncs.glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &maxVertexShaderStorageBlocks);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &maxFragmentShaderStorageBlocks);
|
||||
if (drainErrors()) {
|
||||
MGLOG_W("Per-stage shader storage block query failed for the vertex/fragment "
|
||||
"stages; assuming the ES minimums (vertex 0, fragment 4)");
|
||||
maxVertexShaderStorageBlocks = 0;
|
||||
maxFragmentShaderStorageBlocks = 4;
|
||||
}
|
||||
if (esAtLeast32) {
|
||||
glesFuncs.glGetIntegerv(GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS,
|
||||
&maxTessControlShaderStorageBlocks);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS,
|
||||
&maxTessEvaluationShaderStorageBlocks);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS, &maxGeometryShaderStorageBlocks);
|
||||
if (drainErrors()) {
|
||||
MGLOG_W("Per-stage shader storage block query failed for the tessellation/"
|
||||
"geometry stages; assuming the ES minimum of 0");
|
||||
maxTessControlShaderStorageBlocks = 0;
|
||||
maxTessEvaluationShaderStorageBlocks = 0;
|
||||
maxGeometryShaderStorageBlocks = 0;
|
||||
}
|
||||
}
|
||||
// A driver is free to report a negative or nonsensical count into an untouched
|
||||
// out-param; clamp before anything downstream treats it as a capacity.
|
||||
maxVertexShaderStorageBlocks = std::max(maxVertexShaderStorageBlocks, 0);
|
||||
maxTessControlShaderStorageBlocks = std::max(maxTessControlShaderStorageBlocks, 0);
|
||||
maxTessEvaluationShaderStorageBlocks = std::max(maxTessEvaluationShaderStorageBlocks, 0);
|
||||
maxGeometryShaderStorageBlocks = std::max(maxGeometryShaderStorageBlocks, 0);
|
||||
maxFragmentShaderStorageBlocks = std::max(maxFragmentShaderStorageBlocks, 0);
|
||||
}
|
||||
glesFuncs.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_CLIP_DISTANCES, &maxClipDistances);
|
||||
@@ -1271,6 +1337,11 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.MaxVertexAttribs = maxVertexAttribs;
|
||||
caps.MaxComputeShaderStorageBlocks = maxComputeShaderStorageBlocks;
|
||||
caps.MaxCombinedShaderStorageBlocks = maxCombinedShaderStorageBlocks;
|
||||
caps.MaxVertexShaderStorageBlocks = maxVertexShaderStorageBlocks;
|
||||
caps.MaxTessControlShaderStorageBlocks = maxTessControlShaderStorageBlocks;
|
||||
caps.MaxTessEvaluationShaderStorageBlocks = maxTessEvaluationShaderStorageBlocks;
|
||||
caps.MaxGeometryShaderStorageBlocks = maxGeometryShaderStorageBlocks;
|
||||
caps.MaxFragmentShaderStorageBlocks = maxFragmentShaderStorageBlocks;
|
||||
caps.MaxComputeUniformBlocks = maxComputeUniformBlocks;
|
||||
caps.MaxComputeWorkGroupInvocations = maxComputeWorkGroupInvocations;
|
||||
caps.MaxShaderStorageBufferBindings = maxShaderStorageBufferBindings;
|
||||
@@ -1348,6 +1419,14 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
MGLOG_I(" GL_MAX_VERTEX_ATTRIBS: %d", caps.MaxVertexAttribs);
|
||||
MGLOG_I(" GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS: %d", caps.MaxComputeShaderStorageBlocks);
|
||||
MGLOG_I(" GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS: %d", caps.MaxCombinedShaderStorageBlocks);
|
||||
// Worth a line each: a zero here is what stops an application's storage block from ever
|
||||
// working in that stage, and reading it back from an artifact is the difference between
|
||||
// "MobileGL dropped my draw" and "this driver has no SSBOs outside compute".
|
||||
MGLOG_I(" GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS: %d", caps.MaxVertexShaderStorageBlocks);
|
||||
MGLOG_I(" GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS: %d", caps.MaxTessControlShaderStorageBlocks);
|
||||
MGLOG_I(" GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS: %d", caps.MaxTessEvaluationShaderStorageBlocks);
|
||||
MGLOG_I(" GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS: %d", caps.MaxGeometryShaderStorageBlocks);
|
||||
MGLOG_I(" GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS: %d", caps.MaxFragmentShaderStorageBlocks);
|
||||
MGLOG_I(" GL_MAX_COMPUTE_UNIFORM_BLOCKS: %d", caps.MaxComputeUniformBlocks);
|
||||
MGLOG_I(" GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS: %d", caps.MaxComputeWorkGroupInvocations);
|
||||
MGLOG_I(" GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS: %d", caps.MaxShaderStorageBufferBindings);
|
||||
|
||||
@@ -1248,6 +1248,17 @@ namespace MobileGL {
|
||||
Int MaxVertexAttribs = 16;
|
||||
Int MaxComputeShaderStorageBlocks = 8;
|
||||
Int MaxCombinedShaderStorageBlocks = 32;
|
||||
// Per-stage GL_MAX_*_SHADER_STORAGE_BLOCKS as the host GLES driver reports them.
|
||||
// The defaults are the ES 3.2 minimums (table 21.44): 0 for every graphics stage
|
||||
// except fragment, which is 4. ES only gained the tessellation and geometry pnames
|
||||
// in 3.2 (or with EXT_tessellation_shader / EXT_geometry_shader), so those two are
|
||||
// queried behind a support check and left at the default otherwise - see
|
||||
// FillInGLESCapabilities.
|
||||
Int MaxVertexShaderStorageBlocks = 0;
|
||||
Int MaxTessControlShaderStorageBlocks = 0;
|
||||
Int MaxTessEvaluationShaderStorageBlocks = 0;
|
||||
Int MaxGeometryShaderStorageBlocks = 0;
|
||||
Int MaxFragmentShaderStorageBlocks = 4;
|
||||
Int MaxComputeUniformBlocks = 12;
|
||||
Int MaxComputeWorkGroupInvocations = 128;
|
||||
Int MaxShaderStorageBufferBindings = 8;
|
||||
|
||||
Reference in New Issue
Block a user