[Feat] (Backend, MGPipe): carry the six per-axis compute limits in DynamicBackendParameters so MGPCaps has every backend-owned indexed answer, and pin them against glGetIntegeri_v on both backends

- P-1: MGPCaps is DynamicBackendParameters by inclusion (plan B section 4.4.1), but that struct carried MaxComputeWorkGroupInvocations and no per-axis GL_MAX_COMPUTE_WORK_GROUP_COUNT / GL_MAX_COMPUTE_WORK_GROUP_SIZE - the six numbers that ARE the backend-owned indexed answers surviving the getter retirement (GL_Getter.cpp and CompileEnv.cpp ask GLFunctionsTable::GetIntegeri_v for exactly these, DirectVulkan answers them from VkPhysicalDeviceLimits), so the interface had a hole where its only genuine indexed carrier should be. DynamicBackendParameters now has MaxComputeWorkGroupCount[3] / MaxComputeWorkGroupSize[3] with the GL 4.3 minimums as the no-backend defaults; DirectGLES fills them from glGetIntegeri_v inside the loader's bracketed probe run (GLESCapabilities carries them, logged with the other limits) and DirectVulkan from maxComputeWorkGroupCount / maxComputeWorkGroupSize through the loader's SaturateToInt like every other limit. Raw driver answers, as the invocations limit is: the frontend floors them at the shared MIN_COMPUTE_WORK_GROUP_* minimums itself.
- The GetIntegeri_v table path is untouched, as is GL_Getter and CompileEnv behaviour: retiring the getter in favour of the caps is P0.5, and this only makes sure the caps have what P0.5 needs.
- PipeCalls.def's footer no longer claims that "only GL_COMPUTE_WORK_GROUP_SIZE is a real backend answer and it lives in MGPCaps": the six limits live in MGPCaps, and GL_COMPUTE_WORK_GROUP_SIZE is a frontend link artifact (ProgramObject::GetComputeLocalSize, what GL_Program.cpp answers from), which AdvertisedLimitsScenario.ComputeLocalSizeComesFromTheLinkedProgram already pins. The MGPCaps size assertion is a composition of sizeof(DynamicBackendParameters) and follows the struct.
- AdvertisedLimitsScenario.ComputeWorkGroupLimitsAreTheCapsBlocksAnswer pins, on both lanes: answerability, the GL 4.3 floors, vector/indexed agreement, INVALID_VALUE past axis 2, and - through the new Harness/BackendCapsPeek translation unit, which is the one place the module looks past the GL API - that max(caps, minimum) equals the live glGetIntegeri_v answer axis by axis. Shown live by halving each backend's caps copy: both lanes fail with "MGPCaps carries 512 but glGetIntegeri_v answers 1024". On Android the module links the shipping .so (hidden visibility), so the peek returns false there and only the GL-visible half runs. ComputeWorkGroupCapabilities.TakesEveryAxisFromTheIndexedQuery in BackendLoaderTest pins the DirectGLES loader half against the fake driver, per axis and above the initialisers.
- Verified: AdvertisedLimitsScenario 20/20 on DirectGLES and DirectVulkan (llvmpipe / lavapipe), BackendLoaderTest green.
This commit is contained in:
2026-09-05 21:19:00 -04:00
parent 1154f9a00d
commit e8ee7b1a88
15 changed files with 263 additions and 10 deletions
+13
View File
@@ -378,6 +378,19 @@ namespace MobileGL {
Int MaxFragmentShaderStorageBlocks = 8;
Int MaxComputeUniformBlocks = 12;
Int MaxComputeWorkGroupInvocations = 128;
// GL_MAX_COMPUTE_WORK_GROUP_COUNT / GL_MAX_COMPUTE_WORK_GROUP_SIZE, one value per
// axis. These six, with the invocations limit above, are the only indexed limits a
// backend genuinely OWNS - the device answers them (glGetIntegeri_v on DirectGLES,
// VkPhysicalDeviceLimits::maxComputeWorkGroupCount/Size on DirectVulkan) - and so
// the only ones that survive the retirement of the GetIntegeri_v table entry: they
// cross the MGPipe boundary inside MGPCaps, by inclusion of this struct (plan B
// section 4.4.1). Every other indexed pname names frontend state. RAW driver
// answers, like the invocations limit: GL_Getter and the compile environment floor
// them at the shared MIN_COMPUTE_WORK_GROUP_* minimums themselves. The defaults are
// the GL 4.3 core minimums (table 23.60) and describe the no-backend case, as
// MaxClipDistances' does.
Int MaxComputeWorkGroupCount[3] = {65535, 65535, 65535};
Int MaxComputeWorkGroupSize[3] = {1024, 1024, 64};
Int MaxShaderStorageBufferBindings = 8;
Int MaxTextureBufferSize = 65536;
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT; 1 means the offset is unconstrained.
@@ -1415,6 +1415,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
clampStageStorageBlocks(m_GLESCapabilities.MaxFragmentShaderStorageBlocks);
m_dynamicParameters.MaxComputeUniformBlocks = m_GLESCapabilities.MaxComputeUniformBlocks;
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_GLESCapabilities.MaxComputeWorkGroupInvocations;
// The six per-axis compute limits: the driver's raw glGetIntegeri_v answers, the same
// numbers GLFunctionsTable::GetIntegeri_v forwards live. Carried here so that MGPCaps has
// them once the table entry retires (plan B section 4.4.1); GL_Getter floors them.
for (SizeT axis = 0; axis < 3; ++axis) {
m_dynamicParameters.MaxComputeWorkGroupCount[axis] = m_GLESCapabilities.MaxComputeWorkGroupCount[axis];
m_dynamicParameters.MaxComputeWorkGroupSize[axis] = m_GLESCapabilities.MaxComputeWorkGroupSize[axis];
}
// (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
@@ -937,6 +937,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
clampLimit("GL_MAX_COMPUTE_UNIFORM_BLOCKS", m_vulkanCaps.MaxComputeUniformBlocks,
kMaxAdvertisedBufferBlocks);
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_vulkanCaps.MaxComputeWorkGroupInvocations;
// The six per-axis compute limits, from the same VkPhysicalDeviceLimits fields
// GLFunctionsTable::GetIntegeri_v (DirectVulkan.cpp) reads live. Carried here so that
// MGPCaps has them once the table entry retires (plan B section 4.4.1); GL_Getter floors
// them. Not clamped: unlike the block counts these are not amounts an application
// allocates, and the frontend already raises them to the GL minimum.
for (SizeT axis = 0; axis < 3; ++axis) {
m_dynamicParameters.MaxComputeWorkGroupCount[axis] = m_vulkanCaps.MaxComputeWorkGroupCount[axis];
m_dynamicParameters.MaxComputeWorkGroupSize[axis] = m_vulkanCaps.MaxComputeWorkGroupSize[axis];
}
m_dynamicParameters.MaxShaderStorageBufferBindings =
clampLimit("GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS", m_vulkanCaps.MaxShaderStorageBufferBindings,
kMaxAdvertisedBufferBlocks);
@@ -674,7 +674,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The two compute limits are the only indexed pnames a backend genuinely owns: they come
// from the physical device, and MG_Impl/GLImpl/Getter/GL_Getter.cpp asks for them here so it
// can raise the answer to the GL required minimum. Every other indexed pname names FRONTEND
// can raise the answer to the GL required minimum. The same six numbers are carried in
// DynamicBackendParameters::MaxComputeWorkGroupCount/Size (filled at capability init from
// the same limits), which is their MGPCaps carrier once this entry retires - the
// AdvertisedLimitsScenario pins the two against each other. Every other indexed pname names FRONTEND
// state (the indexed buffer bindings, the per-unit texture/sampler bindings, the image-unit
// bindings, the viewport rectangles, the indexed capabilities) and is answered there before
// the table is consulted, so the arms this function used to carry for
@@ -51,6 +51,7 @@ endif()
add_executable(MobileGLIntegrationTest
Main.cpp
Harness/HeadlessGL.cpp
Harness/BackendCapsPeek.cpp
Scenarios/OrientationScenario.cpp
Scenarios/CrossFrameBufferScenario.cpp
Scenarios/ResidentIndexScenario.cpp
@@ -0,0 +1,42 @@
// MobileGL - MobileGL/MG_IntegrationTest/Harness/BackendCapsPeek.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
#include "BackendCapsPeek.h"
#if !defined(__ANDROID__)
#include <MG_Backend/BackendObject.h>
namespace MobileGL::MG_Backend {
// Declared in MG_Backend/BackendObjects.h, which also pulls in both backends' headers
// and, through them, their loaders; the reference alone is all that is needed here.
extern UniquePtr<BackendObject>& pActiveBackendObject;
} // namespace MobileGL::MG_Backend
#endif
namespace MGITest {
bool PeekComputeWorkGroupCaps(int outCount[3], int outSize[3]) {
#if defined(__ANDROID__)
(void)outCount;
(void)outSize;
return false;
#else
const auto& backend = MobileGL::MG_Backend::pActiveBackendObject;
if (!backend) {
return false;
}
const MobileGL::MG_Backend::DynamicBackendParameters& caps = backend->GetDynamicParameters();
for (int axis = 0; axis < 3; ++axis) {
outCount[axis] = caps.MaxComputeWorkGroupCount[axis];
outSize[axis] = caps.MaxComputeWorkGroupSize[axis];
}
return true;
#endif
}
} // namespace MGITest
@@ -0,0 +1,29 @@
// MobileGL - MobileGL/MG_IntegrationTest/Harness/BackendCapsPeek.h
// 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 one place this module looks past the GL API into the active backend's caps block.
//
// It exists for exactly one assertion: that the six per-axis compute limits the MGPipe
// caps block carries (DynamicBackendParameters::MaxComputeWorkGroupCount/Size, plan B
// section 4.4.1) are the same numbers glGetIntegeri_v answers today, since P0.5 retires
// the getter in favour of the caps. A separate translation unit, because the scenario
// sources include the GL headers with prototypes and MobileGL's umbrella header is not
// meant to meet them in one file.
#pragma once
namespace MGITest {
// Copies the active backend's MaxComputeWorkGroupCount / MaxComputeWorkGroupSize into the
// two arrays and returns true. Returns false, touching nothing, where the caps block is
// out of reach: on Android this module links the SHIPPING libMobileGL.so, built
// -fvisibility=hidden, so no internal symbol resolves; on desktop it links MobileGL_s and
// the read is direct.
bool PeekComputeWorkGroupCaps(int outCount[3], int outSize[3]);
} // namespace MGITest
@@ -26,9 +26,11 @@
// quantities, so an entry that only fails on DirectVulkan is a translation bug and one that
// fails on both is a table bug.
#include <algorithm>
#include <string>
#include <vector>
#include "../Harness/BackendCapsPeek.h"
#include "../Harness/HeadlessGL.h"
#include "../Harness/ScenarioFixture.h"
@@ -564,5 +566,64 @@ void main() { g_data[gl_LocalInvocationIndex] = 1u; }
(void)FirstGLError();
}
// THE SIX COMPUTE LIMITS THAT OUTLIVE THE GETTER. GL_MAX_COMPUTE_WORK_GROUP_COUNT and
// GL_MAX_COMPUTE_WORK_GROUP_SIZE, three axes each, are the only indexed pnames the
// DEVICE answers rather than the frontend (glGetIntegeri_v on Espryt, VkPhysicalDevice-
// Limits on Magma), and therefore the only ones that have to cross the MGPipe boundary
// once GetIntegeri_v is retired (plan B section 4.4.6 / P0.5). They ride in MGPCaps by
// inclusion, as DynamicBackendParameters::MaxComputeWorkGroupCount/Size, filled by both
// backends at capability init. This case pins that the caps copy and the live getter
// answer are one number - the getter floors the backend's raw answer at the GL 4.3
// minimum, so the comparison is against the floored caps value - and pins the
// GL-visible half on every lane: answerability, the floors, vector/indexed agreement
// and the index bound. On a lane where the caps block is out of reach (Android links
// the shipping .so) only the GL-visible half runs.
TEST_F(AdvertisedLimitsScenario, ComputeWorkGroupLimitsAreTheCapsBlocksAnswer) {
struct Axis {
GLenum pname;
const char* name;
GLint minimum[3]; // GL 4.3 core table 23.60
};
const Axis axes[] = {
{GL_MAX_COMPUTE_WORK_GROUP_COUNT, "GL_MAX_COMPUTE_WORK_GROUP_COUNT", {65535, 65535, 65535}},
{GL_MAX_COMPUTE_WORK_GROUP_SIZE, "GL_MAX_COMPUTE_WORK_GROUP_SIZE", {1024, 1024, 64}},
};
int capsCount[3] = {0, 0, 0};
int capsSize[3] = {0, 0, 0};
const bool capsVisible = PeekComputeWorkGroupCaps(capsCount, capsSize);
for (const Axis& axis : axes) {
GLint indexed[3] = {-1, -1, -1};
for (GLuint i = 0; i < 3; ++i) {
glGetIntegeri_v(axis.pname, i, &indexed[i]);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << axis.name << "[" << i << "]";
EXPECT_GE(indexed[i], axis.minimum[i])
<< axis.name << "[" << i << "] = " << indexed[i]
<< " is below the GL 4.3 core table 23.60 minimum " << axis.minimum[i];
}
GLint vector[3] = {-1, -1, -1};
glGetIntegerv(axis.pname, vector);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << axis.name;
for (int i = 0; i < 3; ++i) {
EXPECT_EQ(vector[i], indexed[i])
<< axis.name << "[" << i << "]: the vector query and the indexed query disagree";
}
GLint outOfRange = -424242;
glGetIntegeri_v(axis.pname, 3, &outOfRange);
EXPECT_EQ(FirstGLError(), GLenum(GL_INVALID_VALUE))
<< axis.name << "[3]: an index past the three axes is INVALID_VALUE (GL 4.6 core 22.1)";
if (!capsVisible) continue;
const int* capsAxis = axis.pname == GL_MAX_COMPUTE_WORK_GROUP_COUNT ? capsCount : capsSize;
for (int i = 0; i < 3; ++i) {
EXPECT_EQ(std::max(capsAxis[i], axis.minimum[i]), indexed[i])
<< axis.name << "[" << i << "]: MGPCaps carries " << capsAxis[i]
<< " but glGetIntegeri_v answers " << indexed[i]
<< " - the caps block and the getter path must be one number, because P0.5 retires "
"the getter in favour of the caps";
}
}
}
} // namespace
} // namespace MGITest
+4 -1
View File
@@ -122,7 +122,10 @@ namespace MobileGL::MG_Pipe {
struct MGPCaps {
// The ~90 flat scalars the backends already publish, by inclusion rather than by
// restatement: a caps field added there must not need a second edit here.
// restatement: a caps field added there must not need a second edit here. This is
// also where the six per-axis compute limits (MaxComputeWorkGroupCount/Size) ride -
// the only indexed answers the device owns, and therefore the only ones that outlive
// the GetIntegeri_v table entry (see the PipeCalls.def footer).
DynamicBackendParameters Dynamic;
Uint64 CallMask; // MGPCapBit
// The two halves that are not flat PODs travel as blobs: the format capability
+15 -6
View File
@@ -140,9 +140,18 @@
X(SetSwapInterval, MGPSwapInterval, kCtxVerb, kOptional)
// clang-format on
// Explicitly NOT migrated (plan 4.4.6 / appendix A "explicit deletions"): GetIntegeri_v,
// GetInteger64i_v, GetProgramiv (only GL_COMPUTE_WORK_GROUP_SIZE is a real backend answer
// and it lives in MGPCaps), ShaderStorageBlockBinding (folded into MGPProgramDesc's
// reflection archive), set_pixel_unpack_state (no such state crosses the line - plan 4.6
// D5), a compressed-format concept, pipe_transfer, and the stage dimension of
// set_sampler_views (MobileGL's texture unit space is merged, not per stage - plan 4.4.3).
// Explicitly NOT migrated (plan 4.4.6 / appendix A "explicit deletions"):
// - GetIntegeri_v / GetInteger64i_v. The six backend-owned answers they carry -
// GL_MAX_COMPUTE_WORK_GROUP_COUNT and GL_MAX_COMPUTE_WORK_GROUP_SIZE, three axes each,
// the only indexed pnames the device rather than the frontend answers - live in MGPCaps
// as DynamicBackendParameters::MaxComputeWorkGroupCount / MaxComputeWorkGroupSize, filled
// by both backends at capability init (DirectGLES from glGetIntegeri_v, DirectVulkan from
// VkPhysicalDeviceLimits) and floored by the frontend. Every other indexed pname names
// frontend state and is answered before any table is consulted.
// - GetProgramiv. GL_COMPUTE_WORK_GROUP_SIZE is a FRONTEND link artifact
// (ProgramObject::GetComputeLocalSize, what GL_Program.cpp has always answered from), not
// a backend answer at all; nothing a backend knows about a program crosses this way.
// - ShaderStorageBlockBinding (folded into MGPProgramDesc's reflection archive),
// set_pixel_unpack_state (no such state crosses the line - plan 4.6 D5), a
// compressed-format concept, pipe_transfer, and the stage dimension of set_sampler_views
// (MobileGL's texture unit space is merged, not per stage - plan 4.4.3).
@@ -37,6 +37,11 @@ namespace {
std::size_t ioBlockDraws = 0;
// Behavior knobs, configured per test before running the probe.
GLint maxVertexSsboBlocks = 4;
// GL_MAX_COMPUTE_WORK_GROUP_COUNT / _SIZE per axis, answered through glGetIntegeri_v.
// Above the GL minimums and distinct per axis, so a loader that left an initialiser in
// place or copied one axis into another is caught.
GLint maxComputeWorkGroupCount[3] = {70001, 70002, 70003};
GLint maxComputeWorkGroupSize[3] = {1500, 1501, 100};
GLint glesMajorVersion = 3;
GLint glesMinorVersion = 1;
GLint maxVertexImageUniforms = 2;
@@ -460,8 +465,20 @@ namespace {
if (data == nullptr) return;
for (int i = 0; i < 4; ++i) data[i] = GL_TRUE;
};
funcs.glGetIntegeri_v = [](GLenum, GLuint, GLint* data) {
if (data != nullptr) *data = 0;
funcs.glGetIntegeri_v = [](GLenum pname, GLuint index, GLint* data) {
if (data == nullptr) return;
*data = 0;
if (index >= 3) return;
switch (pname) {
case GL_MAX_COMPUTE_WORK_GROUP_COUNT:
*data = g_fake.maxComputeWorkGroupCount[index];
break;
case GL_MAX_COMPUTE_WORK_GROUP_SIZE:
*data = g_fake.maxComputeWorkGroupSize[index];
break;
default:
break;
}
};
funcs.glGetProgramInfoLog = [](GLuint, GLsizei bufSize, GLsizei* length, GLchar* infoLog) {
if (infoLog != nullptr && bufSize > 0) infoLog[0] = '\0';
@@ -1551,3 +1568,23 @@ TEST(LocatedIoBlockProbe, ReportsTheDefectOnlyWhenTheUnlocatedControlCarriesTheP
EXPECT_FALSE(ProbeLocatedIoBlocksLosePayload(crippled).detected);
EXPECT_EQ(g_fake.ioBlockDraws, 0u) << "an entry-point-gated probe must not draw at all";
}
// The six per-axis compute limits are the backend-owned answers that cross the MGPipe boundary
// inside MGPCaps (DynamicBackendParameters::MaxComputeWorkGroupCount/Size), so the loader has
// to take EACH axis from glGetIntegeri_v rather than leave an initialiser - or one axis's
// answer - in the other slots. The integration side (AdvertisedLimitsScenario) pins the copy
// against the live getter on both backends; this pins the driver-to-caps step on its own.
TEST(ComputeWorkGroupCapabilities, TakesEveryAxisFromTheIndexedQuery) {
const auto funcs = MakeFakeGLESFunctions();
ResetFakeDriver();
MobileGL::MG_External::GLESCapabilities caps;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(caps, funcs));
for (int axis = 0; axis < 3; ++axis) {
EXPECT_EQ(caps.MaxComputeWorkGroupCount[axis], g_fake.maxComputeWorkGroupCount[axis]) << "axis " << axis;
EXPECT_EQ(caps.MaxComputeWorkGroupSize[axis], g_fake.maxComputeWorkGroupSize[axis]) << "axis " << axis;
}
// The initialisers are the GL 4.3 minimums and every fake answer is above them, so a
// value equal to its initialiser here would mean the query never ran.
EXPECT_GT(caps.MaxComputeWorkGroupCount[0], 65535);
EXPECT_GT(caps.MaxComputeWorkGroupSize[2], 64);
}
@@ -1145,6 +1145,8 @@ namespace MobileGL::MG_Util::BackendLoader {
GLint maxFragmentShaderStorageBlocks = 4;
GLint maxComputeUniformBlocks = 12;
GLint maxComputeWorkGroupInvocations = 128;
GLint maxComputeWorkGroupCount[3] = {65535, 65535, 65535};
GLint maxComputeWorkGroupSize[3] = {1024, 1024, 64};
GLint maxShaderStorageBufferBindings = 8;
GLint maxTextureBufferSize = 65536;
GLint maxUniformBufferBindings = 24;
@@ -1276,6 +1278,17 @@ namespace MobileGL::MG_Util::BackendLoader {
glesFuncs.glGetIntegerv(GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, &maxCombinedShaderStorageBlocks);
glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_UNIFORM_BLOCKS, &maxComputeUniformBlocks);
glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &maxComputeWorkGroupInvocations);
// The per-axis pair beside it, through the indexed query. ES 3.1 core like the
// invocations limit, so it sits inside the same bracketed run: a 3.0 context rejects
// it, the drain below swallows the error and the locals keep the GL 4.3 minimums.
// These are the six backend-owned indexed answers that cross the MGPipe boundary in
// MGPCaps (DynamicBackendParameters::MaxComputeWorkGroupCount/Size).
if (glesFuncs.glGetIntegeri_v) {
for (GLuint axis = 0; axis < 3; ++axis) {
glesFuncs.glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, axis, &maxComputeWorkGroupCount[axis]);
glesFuncs.glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, axis, &maxComputeWorkGroupSize[axis]);
}
}
glesFuncs.glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxShaderStorageBufferBindings);
// GL_MAX_TEXTURE_BUFFER_SIZE is deliberately NOT batched here: like
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT below, the pname only exists once buffer textures do,
@@ -1584,6 +1597,10 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.MaxFragmentShaderStorageBlocks = maxFragmentShaderStorageBlocks;
caps.MaxComputeUniformBlocks = maxComputeUniformBlocks;
caps.MaxComputeWorkGroupInvocations = maxComputeWorkGroupInvocations;
for (SizeT axis = 0; axis < 3; ++axis) {
caps.MaxComputeWorkGroupCount[axis] = maxComputeWorkGroupCount[axis];
caps.MaxComputeWorkGroupSize[axis] = maxComputeWorkGroupSize[axis];
}
caps.MaxShaderStorageBufferBindings = maxShaderStorageBufferBindings;
caps.MaxTextureBufferSize = maxTextureBufferSize;
// Through glesFuncs, like every other capability query here: a bare glGetIntegerv resolves
@@ -1681,6 +1698,10 @@ namespace MobileGL::MG_Util::BackendLoader {
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_COMPUTE_WORK_GROUP_COUNT: %d %d %d", caps.MaxComputeWorkGroupCount[0],
caps.MaxComputeWorkGroupCount[1], caps.MaxComputeWorkGroupCount[2]);
MGLOG_I(" GL_MAX_COMPUTE_WORK_GROUP_SIZE: %d %d %d", caps.MaxComputeWorkGroupSize[0],
caps.MaxComputeWorkGroupSize[1], caps.MaxComputeWorkGroupSize[2]);
MGLOG_I(" GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS: %d", caps.MaxShaderStorageBufferBindings);
// Three distinct states, and the suffix must not conflate them: a driver answer, a floor
// kept because there are no buffer textures to ask about, and a floor kept because the
@@ -1309,6 +1309,11 @@ namespace MobileGL {
Int MaxFragmentShaderStorageBlocks = 4;
Int MaxComputeUniformBlocks = 12;
Int MaxComputeWorkGroupInvocations = 128;
// GL_MAX_COMPUTE_WORK_GROUP_COUNT / _SIZE per axis, as the driver answers
// glGetIntegeri_v. Raw: the frontend floors them at the GL minimums itself. The
// initialisers are those minimums, for a context that rejects the query.
Int MaxComputeWorkGroupCount[3] = {65535, 65535, 65535};
Int MaxComputeWorkGroupSize[3] = {1024, 1024, 64};
Int MaxShaderStorageBufferBindings = 8;
Int MaxTextureBufferSize = 65536;
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT; 1 means the offset is unconstrained.
@@ -196,6 +196,10 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.MaxCombinedShaderStorageBlocks = SaturateToInt(p.limits.maxDescriptorSetStorageBuffers);
caps.MaxComputeUniformBlocks = SaturateToInt(p.limits.maxPerStageDescriptorUniformBuffers);
caps.MaxComputeWorkGroupInvocations = SaturateToInt(p.limits.maxComputeWorkGroupInvocations);
for (SizeT axis = 0; axis < 3; ++axis) {
caps.MaxComputeWorkGroupCount[axis] = SaturateToInt(p.limits.maxComputeWorkGroupCount[axis]);
caps.MaxComputeWorkGroupSize[axis] = SaturateToInt(p.limits.maxComputeWorkGroupSize[axis]);
}
caps.MaxShaderStorageBufferBindings = SaturateToInt(p.limits.maxDescriptorSetStorageBuffers);
caps.MaxTextureBufferSize = SaturateToInt(p.limits.maxTexelBufferElements);
caps.TextureBufferOffsetAlignment =
@@ -333,6 +337,10 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.MaxCombinedShaderStorageBlocks = SaturateToInt(properties.limits.maxDescriptorSetStorageBuffers);
caps.MaxComputeUniformBlocks = SaturateToInt(properties.limits.maxPerStageDescriptorUniformBuffers);
caps.MaxComputeWorkGroupInvocations = SaturateToInt(properties.limits.maxComputeWorkGroupInvocations);
for (SizeT axis = 0; axis < 3; ++axis) {
caps.MaxComputeWorkGroupCount[axis] = SaturateToInt(properties.limits.maxComputeWorkGroupCount[axis]);
caps.MaxComputeWorkGroupSize[axis] = SaturateToInt(properties.limits.maxComputeWorkGroupSize[axis]);
}
caps.MaxShaderStorageBufferBindings = SaturateToInt(properties.limits.maxDescriptorSetStorageBuffers);
caps.MaxTextureBufferSize = SaturateToInt(properties.limits.maxTexelBufferElements);
caps.TextureBufferOffsetAlignment =
@@ -56,6 +56,11 @@ namespace MobileGL {
Int MaxCombinedShaderStorageBlocks = 32;
Int MaxComputeUniformBlocks = 12;
Int MaxComputeWorkGroupInvocations = 128;
// VkPhysicalDeviceLimits::maxComputeWorkGroupCount / maxComputeWorkGroupSize per
// axis, saturated to Int like every other limit here. Raw: the frontend floors them
// at the GL minimums itself.
Int MaxComputeWorkGroupCount[3] = {65535, 65535, 65535};
Int MaxComputeWorkGroupSize[3] = {1024, 1024, 64};
Int MaxShaderStorageBufferBindings = 8;
Int MaxTextureBufferSize = 65536;
// GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT; 1 means the offset is unconstrained.