[Fix] (MG_Backend/DirectVulkan+DirectGLES, MG_State): cap per-stage GL_MAX_TEXTURE_IMAGE_UNITS to 32

Adreno/Qualcomm report a huge maxPerStageDescriptorSampledImages, and the
per-stage texture-unit limits were clamped only to the combined array capacity
(TextureState::MAX_TEXTURE_IMAGE_UNITS = 192). glGetIntegerv thus advertised 192
for GL_MAX_TEXTURE_IMAGE_UNITS, but host code treats it as an array bound:
Minecraft's Blaze3D GlStateManager.TEXTURES[] holds 128 entries and Iris iterates
[0, GL_MAX_TEXTURE_IMAGE_UNITS) over it in CompositeRenderer.renderAll, throwing
ArrayIndexOutOfBoundsException: Index 128 out of bounds for length 128.

Introduce MAX_PER_STAGE_TEXTURE_IMAGE_UNITS = 32 (desktop-driver value) and clamp
the per-stage sampler limits to it in both backends (DirectGLES previously did not
clamp at all), keeping the combined limit at the array capacity. Update SanityTest.
This commit is contained in:
2026-07-10 21:53:04 -04:00
parent 5e8106114f
commit f892f609c2
4 changed files with 42 additions and 10 deletions
@@ -977,10 +977,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_dynamicParameters.MaxIntegerSamples = m_GLESCapabilities.MaxIntegerSamples;
m_dynamicParameters.MaxSamples = m_GLESCapabilities.MaxSamples;
m_dynamicParameters.MaxSampleMaskWords = m_GLESCapabilities.MaxSampleMaskWords;
m_dynamicParameters.MaxTextureImageUnits = m_GLESCapabilities.MaxTextureImageUnits;
m_dynamicParameters.MaxVertexTextureImageUnits = m_GLESCapabilities.MaxVertexTextureImageUnits;
m_dynamicParameters.MaxComputeTextureImageUnits = m_GLESCapabilities.MaxComputeTextureImageUnits;
m_dynamicParameters.MaxCombinedTextureImageUnits = m_GLESCapabilities.MaxCombinedTextureImageUnits;
// Clamp the advertised sampler limits the same way the DirectVulkan backend does: per-stage
// GL_MAX_TEXTURE_IMAGE_UNITS must never exceed host-side fixed arrays sized off it (e.g.
// Minecraft's 128-entry Blaze3D GlStateManager.TEXTURES[], iterated by Iris), and the combined
// limit must stay within our texture-unit state array capacity.
m_dynamicParameters.MaxTextureImageUnits =
std::min(m_GLESCapabilities.MaxTextureImageUnits,
static_cast<Int>(MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS));
m_dynamicParameters.MaxVertexTextureImageUnits =
std::min(m_GLESCapabilities.MaxVertexTextureImageUnits,
static_cast<Int>(MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS));
m_dynamicParameters.MaxComputeTextureImageUnits =
std::min(m_GLESCapabilities.MaxComputeTextureImageUnits,
static_cast<Int>(MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS));
m_dynamicParameters.MaxCombinedTextureImageUnits =
std::min(m_GLESCapabilities.MaxCombinedTextureImageUnits,
static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS));
// Never advertise more attributes than the state layer can store: the current-value array and
// the Uint32 attribute masks the draw path passes around are both bounded by MAX_VERTEX_ATTRIBS.
m_dynamicParameters.MaxVertexAttribs =
@@ -718,11 +718,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_dynamicParameters.MaxSampleMaskWords = m_vulkanCaps.MaxSampleMaskWords;
const Int maxSupportedTextureUnits =
static_cast<Int>(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
m_dynamicParameters.MaxTextureImageUnits = std::min(m_vulkanCaps.MaxTextureImageUnits, maxSupportedTextureUnits);
// GL_MAX_TEXTURE_IMAGE_UNITS is a *per-stage* sampler limit. Adreno/Qualcomm report a huge
// maxPerStageDescriptorSampledImages (descriptor-indexing scale), so clamping it only to our
// combined array capacity (192) still advertises 192 per stage. Host code treats this value as
// an array bound: Minecraft's Blaze3D GlStateManager.TEXTURES[] holds 128 entries and Iris
// iterates [0, GL_MAX_TEXTURE_IMAGE_UNITS) over it (CompositeRenderer.renderAll), so any value
// > 128 throws ArrayIndexOutOfBoundsException. Match desktop drivers (32) for the per-stage
// limits while keeping the combined limit at our texture-unit array capacity.
constexpr Int maxPerStageTextureUnits =
static_cast<Int>(MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS);
m_dynamicParameters.MaxTextureImageUnits =
std::min(m_vulkanCaps.MaxTextureImageUnits, maxPerStageTextureUnits);
m_dynamicParameters.MaxVertexTextureImageUnits =
std::min(m_vulkanCaps.MaxVertexTextureImageUnits, maxSupportedTextureUnits);
std::min(m_vulkanCaps.MaxVertexTextureImageUnits, maxPerStageTextureUnits);
m_dynamicParameters.MaxComputeTextureImageUnits =
std::min(m_vulkanCaps.MaxComputeTextureImageUnits, maxSupportedTextureUnits);
std::min(m_vulkanCaps.MaxComputeTextureImageUnits, maxPerStageTextureUnits);
m_dynamicParameters.MaxCombinedTextureImageUnits =
std::min(m_vulkanCaps.MaxCombinedTextureImageUnits, maxSupportedTextureUnits);
// Never advertise more attributes than the state layer can store: the current-value array and
@@ -37,7 +37,13 @@ namespace MobileGL::MG_State::GLState {
class TextureState {
public:
// Capacity of the combined texture-unit state arrays (indexed by glActiveTexture unit).
static constexpr int MAX_TEXTURE_IMAGE_UNITS = 192;
// Per-stage sampler limit advertised through GL_MAX_TEXTURE_IMAGE_UNITS /
// GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS. Held at the desktop-driver value (32) so it never exceeds
// host-side fixed arrays sized off this query -- e.g. Minecraft's 128-entry Blaze3D
// GlStateManager.TEXTURES[], which Iris iterates over in CompositeRenderer.renderAll.
static constexpr int MAX_PER_STAGE_TEXTURE_IMAGE_UNITS = 32;
TextureState();
void GenerateNames(Uint number, Vector<Uint>& textures);
+7 -3
View File
@@ -311,9 +311,13 @@ TEST(DirectVulkanSanity, ClampsAdvertisedTextureAndDrawBufferLimitsToFrontendSta
backend.ApplyVulkanCapabilitiesForTesting(highCaps);
const auto& highParams = backend.GetDynamicParameters();
EXPECT_EQ(highParams.MaxTextureImageUnits, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
EXPECT_EQ(highParams.MaxVertexTextureImageUnits, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
EXPECT_EQ(highParams.MaxComputeTextureImageUnits, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
// Per-stage sampler limits clamp to the desktop-conventional per-stage cap (kept well under
// Blaze3D's 128-entry TEXTURES[] so Iris' unbind loop cannot index out of bounds); the combined
// limit clamps to the full texture-unit array capacity.
EXPECT_EQ(highParams.MaxTextureImageUnits, MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS);
EXPECT_EQ(highParams.MaxVertexTextureImageUnits, MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS);
EXPECT_EQ(highParams.MaxComputeTextureImageUnits, MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS);
EXPECT_LE(highParams.MaxTextureImageUnits, 128);
EXPECT_EQ(highParams.MaxCombinedTextureImageUnits, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS);
EXPECT_EQ(highParams.MaxDrawBuffers, MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS);
EXPECT_EQ(highParams.MaxColorAttachments, MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS);