[Fix, Test] (MG_Util, MG_Backend/DirectGLES, MG_Test, MG_IntegrationTest): detect buffer-texture support, emit the directive the driver advertises, and name the capability when it is missing

This commit is contained in:
2026-08-12 01:24:40 -04:00
parent 97facf777b
commit a0bf4a83bc
16 changed files with 791 additions and 2 deletions
@@ -1151,6 +1151,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_dynamicParameters.MaxComputeUniformBlocks = m_GLESCapabilities.MaxComputeUniformBlocks;
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_GLESCapabilities.MaxComputeWorkGroupInvocations;
m_dynamicParameters.MaxShaderStorageBufferBindings = m_GLESCapabilities.MaxShaderStorageBufferBindings;
// 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
// which). Reporting 0 instead was considered and rejected: MobileGL advertises an OpenGL
// 4.x context, where buffer textures are core and the limit has a spec minimum of 65536,
// so 0 is not a legal answer and applications are not written to survive it. GL offers no
// way to say "this core feature is missing", so the honesty is carried outside the limit:
// FillInGLESCapabilities logs the tier, glTexBuffer and the program build each name the
// missing capability at MGLOG_I, and the driver POST carries a "Buffer textures" row that
// FAILs on this tier.
m_dynamicParameters.MaxTextureBufferSize = m_GLESCapabilities.MaxTextureBufferSize;
m_dynamicParameters.TextureBufferOffsetAlignment = m_GLESCapabilities.TextureBufferOffsetAlignment;
m_dynamicParameters.MaxUniformBufferBindings = m_GLESCapabilities.MaxUniformBufferBindings;
@@ -7233,6 +7233,30 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glGetQueryObjectui64vEXT;
}
Bool AreBufferTexturesSupported() {
// The tier already folds in the resolved-pointer requirement (see FillInGLESCapabilities),
// but the pointer is re-checked here because the tier is only meaningful once the
// capabilities have been filled in, and callers may run before that.
return g_GLESCapabilities.TextureBufferSupport !=
MG_External::GLESCapabilities::TextureBufferTier::None &&
g_GLESFuncs.glTexBuffer != nullptr;
}
const char* GetBufferTextureTierName() {
using Tier = MG_External::GLESCapabilities::TextureBufferTier;
switch (g_GLESCapabilities.TextureBufferSupport) {
case Tier::CoreEs32:
return "core (ES 3.2)";
case Tier::ExtensionEXT:
return "GL_EXT_texture_buffer";
case Tier::ExtensionOES:
return "GL_OES_texture_buffer";
case Tier::None:
default:
return "unsupported";
}
}
BackendQueryHandle BeginTimeElapsedQuery() {
// Query objects can only be created on the thread that owns the ES
// context (MC's F3 profiler queries on the render thread, which
@@ -117,6 +117,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
// capability read needs no current ES context, and it stays false until
// the ES capabilities have been filled in.
Bool AreTimerQueriesSupported();
// True when the host ES driver can back a GL_TEXTURE_BUFFER at all - ES 3.2 core, or
// EXT/OES_texture_buffer, with glTexBuffer resolved. Desktop GL has had buffer textures as
// core since 3.1, so the frontend advertises them unconditionally and an app may call
// glTexBuffer whenever it likes; this is the only thing standing between that call and a
// null entry point. False also means every shader declaring a samplerBuffer is
// uncompilable on this driver, which the program build reports by name.
Bool AreBufferTexturesSupported();
// Human-readable name of the buffer-texture tier for diagnostics and the driver POST:
// "core (ES 3.2)", "GL_EXT_texture_buffer", "GL_OES_texture_buffer" or "unsupported".
const char* GetBufferTextureTierName();
// GL timer-query objects, backed by GL_EXT_disjoint_timer_query. The
// creators return null (the frontend then falls back to an immediately
// available zero result) when the calling thread does not own the ES
@@ -2779,6 +2779,29 @@ namespace MobileGL::MG_Backend::DirectGLES {
&glType, TextureTarget::TextureBuffer);
if (needsRegeneration) {
// Desktop GL has had buffer textures core since 3.1 and MobileGL advertises a
// 4.x context, so glTexBuffer is a legal call the app may make on any driver -
// but ES only gained them in 3.2, and g_GLESFuncs.glTexBuffer is simply null
// below that without EXT/OES_texture_buffer. Calling it was an unconditional
// null dereference. There is no conformant way to refuse the call (it is valid
// in the context MobileGL claims), so the texture is left unbacked and the
// reason is stated once per respecify at a level that survives the shipped
// INFO build - MGLOG_E is compiled out there, which is exactly how this class
// of defect stays invisible.
if (!AreBufferTexturesSupported()) {
if (m_bufferTextureUnsupportedReported) {
break;
}
m_bufferTextureUnsupportedReported = true;
MGLOG_I("Texture buffer %u cannot be backed: this ES driver has no buffer "
"textures (%s). Every draw sampling it will read zero and every "
"shader declaring a samplerBuffer will fail to compile. MobileGL "
"still advertises GL_MAX_TEXTURE_BUFFER_SIZE = %d because an "
"OpenGL 4.x context may not report 0.",
stateTextureObject->GetExternalIndex(), GetBufferTextureTierName(),
g_GLESCapabilities.MaxTextureBufferSize);
break;
}
MGLOG_D("Texture state changed significantly or not initialized, regenerating texture buffer with "
"ID: %u, buffer ID: %u, buffer size: %zu, format: %s",
m_backendTextureId, backendId, buffer->GetSize(),
@@ -4297,6 +4320,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
String source;
auto& spirvCode = shaderSpirvs[index];
// A samplerBuffer is core in the OpenGL 3.1+ context MobileGL advertises but needs
// ES 3.2 or EXT/OES_texture_buffer on the host. Without it SPIRV-Cross emits
// `#extension GL_EXT_texture_buffer : require` and the driver rejects both that
// and the isamplerBuffer keyword - the program never links and every draw using it
// becomes a silent no-op. Say so here, naming the stage, instead of leaving a
// driver info log the shipped INFO build compiles out (MGLOG_E is inactive there).
// Gated on the capability so the module walk never runs on a healthy driver.
if (!AreBufferTexturesSupported() &&
MG_Util::ShaderTranspiler::ShaderCompiler::ModuleDeclaresBufferTextureSampler(spirvCode)) {
MGLOG_I("Program %u stage %s samples a buffer texture, which this ES driver "
"cannot provide (%s). The shader will not compile and the program will "
"not link; every draw using it is a no-op.",
m_backendProgramId,
MG_Util::ConvertGLEnumToString(glShaderType).c_str(),
GetBufferTextureTierName());
m_backendProgramUsable = false;
g_GLESFuncs.glDeleteShader(backendShaderId);
continue;
}
// ESSL cannot express gl_DrawID/gl_BaseInstance/gl_BaseVertex; demote them to
// plain globals (mg_*) before handing the module to SPIRV-Cross.
Vector<unsigned int> loweredSpirv;
@@ -4399,6 +4442,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
source = result;
// First in the chain because it is the only header-level rewrite: it edits
// #extension directives and never the body, so it is independent of every pass
// below and running it early keeps the directive block correct for
// ForceSupporterOutput, which scans for the last #extension line to decide where
// its precision statements go.
source = RetargetTextureBufferExtension(std::move(source),
g_GLESCapabilities.TextureBufferSupport);
source = RebindImageUniformsToFrontendUnits(std::move(source), stateProgramObject);
// Wedged between those two on purpose:
// * AFTER RebindImageUniformsToFrontendUnits, so the binding it copies onto
@@ -626,6 +626,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool m_isInitialized = false;
Bool m_imageBindableStorageRequired = false;
Bool m_backendStorageImmutable = false;
// Latches the "this driver has no buffer textures" report to once per texture. The
// report is emitted from the respecify path, which bails before recording the state
// it was asked to apply - so without the latch the texture stays permanently dirty
// and every draw of every frame logs the same line.
Bool m_bufferTextureUnsupportedReported = false;
StateTextureBasicInfo m_prevTextureInfo;
// Frontend content version at the last completed mipmap sync. The per-draw
// clean probe compares this before rebuilding shape info and scanning
+63
View File
@@ -435,6 +435,69 @@ namespace MobileGL::MG_Backend::DirectGLES {
return result;
}
String RetargetTextureBufferExtension(String glslCode,
MG_External::GLESCapabilities::TextureBufferTier tier) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
// SPIRV-Cross hardcodes the EXT spelling: CompilerGLSL::type_to_glsl emits
// require_extension_internal("GL_EXT_texture_buffer") for any Dim=Buffer image
// whenever it targets ESSL below 320, with no OES alternative and no way to
// configure it. GL_OES_texture_buffer is functionally identical but is a separate
// directive, and `#extension <name> : require` on a name the driver does not
// advertise is a hard compile error - so on an OES-only driver the emitted shader
// fails to compile for the sake of one token.
//
// Deliberately a directive rewrite and nothing more. The alternative - teaching the
// SPIR-V to stop asking for the extension - is not available: the requirement is
// synthesized by SPIRV-Cross from the image type itself, not carried in the module,
// so there is nothing upstream to strip. Everything about the shader body that
// actually uses the buffer texture is identical between the two extensions.
using Tier = MG_External::GLESCapabilities::TextureBufferTier;
if (tier != Tier::ExtensionOES) {
return glslCode;
}
static constexpr const char* kExtName = "GL_EXT_texture_buffer";
static constexpr const char* kOesName = "GL_OES_texture_buffer";
constexpr SizeT kExtNameLength = 21; // strlen("GL_EXT_texture_buffer")
static_assert(sizeof("GL_EXT_texture_buffer") - 1 == kExtNameLength, "name length drifted");
static_assert(sizeof("GL_OES_texture_buffer") - 1 == kExtNameLength,
"the two spellings must be the same length for the in-place replace");
// Only rewrite the name where it is the subject of an #extension directive. The same
// token can legitimately appear in a comment SPIRV-Cross carried through, and a
// shader that merely mentions the string must not be edited.
SizeT searchFrom = 0;
while (true) {
const SizeT hit = glslCode.find(kExtName, searchFrom);
if (hit == String::npos) {
break;
}
searchFrom = hit + kExtNameLength;
// Walk back to the start of the line and require that it is an #extension
// directive, allowing whitespace between '#' and the keyword.
SizeT lineStart = glslCode.rfind('\n', hit);
lineStart = (lineStart == String::npos) ? 0 : lineStart + 1;
SizeT cursor = lineStart;
while (cursor < hit && std::isspace(static_cast<unsigned char>(glslCode[cursor]))) {
++cursor;
}
if (cursor >= hit || glslCode[cursor] != '#') {
continue;
}
++cursor;
while (cursor < hit && std::isspace(static_cast<unsigned char>(glslCode[cursor]))) {
++cursor;
}
if (glslCode.compare(cursor, 9, "extension") != 0) {
continue;
}
glslCode.replace(hit, kExtNameLength, kOesName);
}
return glslCode;
}
String RemoveLayoutBinding(const String& glslCode) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
+7
View File
@@ -130,6 +130,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
// drawBufferCount <= 1, i.e. for everything but a framebuffer that actually
// enables several draw buffers, so the ordinary single-target shader is untouched.
String BroadcastLegacyFragColor(String glslCode, GLenum shaderType, Uint drawBufferCount);
// SPIRV-Cross emits `#extension GL_EXT_texture_buffer : require` for every buffer-texture
// sampler when it targets ESSL below 320, and offers no way to ask for the OES spelling.
// On a driver that advertises only GL_OES_texture_buffer that directive is a compile
// error, so the name is retargeted in the emitted source. A no-op on every other tier:
// ES 3.2 needs no directive at all and an EXT driver already has the right one.
String RetargetTextureBufferExtension(String glslCode,
MG_External::GLESCapabilities::TextureBufferTier tier);
String RemoveLayoutBinding(const String& glslCode);
// Prefix of the writeonly half a read+write image uniform is split into (see
// SplitReadWriteImageUniforms); the suffix is the image's own name.
@@ -67,6 +67,7 @@ add_executable(MobileGLIntegrationTest
Scenarios/SsboDeclarationFormScenario.cpp
Scenarios/Glsl420DeclarationScenario.cpp
Scenarios/FragmentOutputArrayIndexScenario.cpp
Scenarios/BufferTextureScenario.cpp
)
target_include_directories(MobileGLIntegrationTest PRIVATE
@@ -0,0 +1,170 @@
// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/BufferTextureScenario.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
//
// Scenario - A BUFFER TEXTURE IS SAMPLED FROM THE VERTEX STAGE, AND TRACKS ITS BUFFER.
//
// Buffer textures are core in OpenGL 3.1 and MobileGL advertises a 4.x context, so an
// application may build geometry out of one without asking whether the host can. Minecraft
// 26.3 does exactly that: its cloud layer has no vertex attributes at all, only gl_VertexID
// and texelFetch on a GL_R8I buffer texture. Nothing covered that path end to end on either
// backend - the frontend unit tests stop at glTexBuffer's state, and no scenario ever drew
// with the result - which is how DirectGLES came to emit `#extension GL_EXT_texture_buffer :
// require` unconditionally, compile nothing on a host without the extension, and lose the
// whole cloud layer with no diagnostic anywhere.
//
// Two claims, in the order they can break:
// 1. a vertex-stage texelFetch on an R8I buffer texture reads the byte the application put
// in the buffer (the shape of the real workload: no attributes, index from gl_VertexID);
// 2. a later glBufferSubData is visible to the next draw WITHOUT re-specifying the texture.
// glTexBuffer attaches storage, it does not copy: the texture is a live view of the
// buffer, so a backend that only refreshes the view when the texture's own state changes
// must still show the new bytes. DirectGLES' respecify gate is keyed on the texture info
// and deliberately does not include the buffer's contents, so this is the assertion that
// says that is safe rather than merely untested.
//
// NOTE ON A HOST WITHOUT BUFFER TEXTURES: this scenario is expected to FAIL there, and that is
// the honest outcome - MobileGL keeps advertising GL_MAX_TEXTURE_BUFFER_SIZE (an OpenGL 4.x
// context may not answer 0), so there is no capability an application, or this test, could
// branch on. The driver POST's "Buffer textures" row is where that verdict is stated.
#include <cstdint>
#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 {
// No vertex attributes: the quad's corners come from gl_VertexID, exactly like the
// workload this exists for. The texel is fetched in the VERTEX stage - the stage where
// buffer-texture support is scarcest across ES drivers - and carried flat so every
// fragment of the quad reports the same byte and the readback is exact.
constexpr const char* kVS = R"(#version 330 core
uniform isamplerBuffer uFaces;
flat out int vFace;
void main() {
vec2 corner = vec2((gl_VertexID & 1) == 0 ? -1.0 : 1.0,
(gl_VertexID & 2) == 0 ? -1.0 : 1.0);
vFace = texelFetch(uFaces, 0).r;
gl_Position = vec4(corner, 0.0, 1.0);
}
)";
// 1/255 steps survive an RGBA8 round trip exactly, so the readback byte IS the value
// the vertex shader fetched.
constexpr const char* kFS = R"(#version 330 core
flat in int vFace;
out vec4 o_color;
void main() { o_color = vec4(float(vFace) / 255.0, 0.0, 0.0, 1.0); }
)";
class BufferTextureScenario : public ScenarioTest {};
// Draws the full-viewport quad and returns the red byte every fragment was painted with,
// or -1 if the quad did not come out uniform (which would mean the flat varying, not the
// fetch, is what this test is measuring).
int PaintedValue(unsigned int program, int width, int height) {
ClearTo(0.0f, 0.0f, 0.0f, 1.0f);
GLuint vao = 0;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glUseProgram(program);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
glDeleteVertexArrays(1, &vao);
const Image image = ReadPixels(width, height);
if (image.Empty()) {
return -1;
}
const int first = image.At(0, 0).r;
for (int y = 0; y < image.Height(); ++y) {
for (int x = 0; x < image.Width(); ++x) {
if (image.At(x, y).r != first) {
return -1;
}
}
}
return first;
}
} // namespace
TEST_F(BufferTextureScenario, VertexStageTexelFetchReadsTheBufferAndTracksItsUpdates) {
if (!Ready()) return;
HeadlessGL& gl = Gl();
std::string error;
const unsigned int program = CompileProgram(kVS, kFS, &error);
ASSERT_NE(program, 0u) << error;
// GL_R8I is the format the real workload uses. Signed, so the values stay well inside
// [0, 127] to keep the readback arithmetic honest.
constexpr signed char kInitial = 37;
constexpr signed char kUpdated = 91;
std::vector<signed char> texels(64, 0);
texels[0] = kInitial;
GLuint buffer = 0;
glGenBuffers(1, &buffer);
glBindBuffer(GL_TEXTURE_BUFFER, buffer);
glBufferData(GL_TEXTURE_BUFFER, static_cast<GLsizeiptr>(texels.size()), texels.data(),
GL_DYNAMIC_DRAW);
GLuint texture = 0;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_BUFFER, texture);
glTexBuffer(GL_TEXTURE_BUFFER, GL_R8I, buffer);
ASSERT_EQ(FirstGLError(), 0u) << "glTexBuffer(GL_R8I) was refused";
ColorFbo target = MakeColorFbo(64, 64);
ASSERT_NE(target.fbo, 0u) << "could not create the render target";
BindFbo(target);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER, texture);
glUseProgram(program);
const GLint location = glGetUniformLocation(program, "uFaces");
ASSERT_NE(location, -1) << "the buffer sampler was optimized away or never reflected";
glUniform1i(location, 0);
EXPECT_EQ(PaintedValue(program, target.width, target.height), static_cast<int>(kInitial))
<< "a vertex-stage texelFetch on an R8I buffer texture did not read the byte the "
"application stored (a uniform -1 here means the quad was not uniform at all)";
// The texture is a VIEW of the buffer: no glTexBuffer call follows, and none should be
// needed for the new bytes to be visible.
glBindBuffer(GL_TEXTURE_BUFFER, buffer);
glBufferSubData(GL_TEXTURE_BUFFER, 0, 1, &kUpdated);
ASSERT_EQ(FirstGLError(), 0u) << "glBufferSubData on the texture's buffer was refused";
EXPECT_EQ(PaintedValue(program, target.width, target.height), static_cast<int>(kUpdated))
<< "the buffer texture kept showing the old contents after glBufferSubData; the "
"texture must track its buffer without being re-specified";
BindDefaultFramebuffer();
DestroyColorFbo(target);
glUseProgram(0);
glDeleteProgram(program);
glDeleteTextures(1, &texture);
glDeleteBuffers(1, &buffer);
glViewport(0, 0, gl.Width(), gl.Height());
EXPECT_EQ(FirstGLError(), 0u);
}
} // namespace MGITest
@@ -253,3 +253,90 @@ void main()
)";
EXPECT_EQ(SplitReadWriteImageUniforms(source), source);
}
// ---------------------------------------------------------------------------------------
// RetargetTextureBufferExtension
//
// Buffer textures are core in the OpenGL 3.1+ context MobileGL advertises, but in ES they
// only became core in 3.2; below that they need EXT_texture_buffer or OES_texture_buffer.
// SPIRV-Cross hardcodes the EXT spelling for every Dim=Buffer image it emits below ESSL 320
// and offers no way to ask for the other one, so on a driver that advertises only the OES
// name the `: require` is a hard compile error over a single token.
// ---------------------------------------------------------------------------------------
using Tier = MobileGL::MG_External::GLESCapabilities::TextureBufferTier;
using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RetargetTextureBufferExtension;
namespace {
// What SPIRV-Cross actually emits for `uniform isamplerBuffer CloudFaces;` at ESSL 310 -
// the shape that empties Minecraft 26.3's cloud layer on a driver without the extension.
const String kBufferTextureShader = R"(#version 310 es
#extension GL_EXT_texture_buffer : require
precision highp float;
uniform highp isamplerBuffer CloudFaces;
layout(location = 0) out highp vec4 mg_FragColor;
void main()
{
mg_FragColor = vec4(texelFetch(CloudFaces, gl_VertexID).r);
}
)";
} // namespace
TEST(RetargetTextureBufferExtensionTest, OesOnlyDriverGetsTheOesDirective) {
const String out = RetargetTextureBufferExtension(kBufferTextureShader, Tier::ExtensionOES);
EXPECT_TRUE(Contains(out, "#extension GL_OES_texture_buffer : require"))
<< "the OES driver's own spelling must reach the directive:\n" << out;
EXPECT_FALSE(Contains(out, "GL_EXT_texture_buffer"))
<< "the EXT spelling this driver does not advertise must be gone:\n" << out;
// Only the directive changes; the declaration and the fetch are identical between the two
// extensions and must not be touched.
EXPECT_TRUE(Contains(out, "uniform highp isamplerBuffer CloudFaces;"));
EXPECT_TRUE(Contains(out, "texelFetch(CloudFaces, gl_VertexID)"));
}
TEST(RetargetTextureBufferExtensionTest, ExtDriverKeepsWhatSpirvCrossEmitted) {
EXPECT_EQ(RetargetTextureBufferExtension(kBufferTextureShader, Tier::ExtensionEXT),
kBufferTextureShader);
}
// ES 3.2 needs no directive at all, and SPIRV-Cross emits none at ESSL 320 - but a shader
// that arrived with one anyway must not be rewritten to a name the pass was not asked for.
TEST(RetargetTextureBufferExtensionTest, CoreAndUnsupportedTiersAreNoOps) {
EXPECT_EQ(RetargetTextureBufferExtension(kBufferTextureShader, Tier::CoreEs32),
kBufferTextureShader);
EXPECT_EQ(RetargetTextureBufferExtension(kBufferTextureShader, Tier::None),
kBufferTextureShader);
}
// The name is only the subject of a rewrite where it is the subject of an #extension
// directive. A shader that merely mentions it - in a comment SPIRV-Cross carried through, or
// in an identifier - is not an extension request and must come out byte-identical.
TEST(RetargetTextureBufferExtensionTest, OnlyExtensionDirectivesAreRewritten) {
const String source = R"(#version 310 es
// GL_EXT_texture_buffer is what this shader would need
precision highp float;
uniform highp float GL_EXT_texture_buffer_lookalike;
layout(location = 0) out highp vec4 mg_FragColor;
void main()
{
mg_FragColor = vec4(GL_EXT_texture_buffer_lookalike);
}
)";
EXPECT_EQ(RetargetTextureBufferExtension(source, Tier::ExtensionOES), source);
}
// Whitespace between '#' and the keyword is legal in GLSL, and a shader carrying several
// extension directives must have exactly the one retargeted.
TEST(RetargetTextureBufferExtensionTest, SpacedDirectiveIsRewrittenAndNeighboursAreLeftAlone) {
const String source = R"(#version 310 es
# extension GL_EXT_texture_buffer : require
#extension GL_EXT_shader_io_blocks : require
precision highp float;
void main() {}
)";
const String out = RetargetTextureBufferExtension(source, Tier::ExtensionOES);
EXPECT_TRUE(Contains(out, "# extension GL_OES_texture_buffer : require")) << out;
EXPECT_TRUE(Contains(out, "#extension GL_EXT_shader_io_blocks : require"))
<< "an unrelated extension must survive untouched:\n" << out;
EXPECT_EQ(CountOf(out, "GL_OES_texture_buffer"), 1u);
}
@@ -3324,3 +3324,160 @@ void main() {
EXPECT_FALSE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw))
<< "only fragment outputs carry the constant-index rule:\n" << DisassembleSpirv(raw);
}
// ---------------------------------------------------------------------------------------
// Buffer-texture samplers (samplerBuffer / isamplerBuffer / usamplerBuffer)
//
// Buffer textures are core in OpenGL 3.1 and MobileGL advertises a 4.x context, so an
// application may sample one without asking. On the ES side they only became core in 3.2,
// and SPIRV-Cross emits `#extension GL_EXT_texture_buffer : require` for any Dim=Buffer
// image it renders below ESSL 320. On a driver with neither EXT_ nor OES_texture_buffer that
// directive - and the isamplerBuffer keyword behind it - fail to compile, the program never
// links, and every draw using it is a silent no-op. DirectGLES asks the detector below so it
// can name that as the missing capability it is, instead of leaving a driver info log the
// shipped INFO build compiles out.
// ---------------------------------------------------------------------------------------
namespace {
// Compiles `source` for `stage` and returns the module, or fails the calling test.
Vector<Uint32> BuildSpirvForStage(const String& source, GLenum stage) {
using namespace MG_Util::ShaderTranspiler;
ShaderAttrib attrib{.shaderType = stage, .sourceStr = source};
auto res = ShaderCompiler::CompileShader(attrib);
if (!res) {
ADD_FAILURE() << "compile errc: " << res.error().errc << "\nlog: " << res.error().log;
return {};
}
ProgramAttrib programAttrib{.shaders = {res.value()}};
auto program_res = ShaderCompiler::LinkProgram(programAttrib);
if (!program_res) {
ADD_FAILURE() << "link errc: " << program_res.error().errc << "\nlog: " << program_res.error().log;
return {};
}
ProgramBinaryAttrib binaryAttrib{.shaderTypes = {stage}, .program = *program_res.value()};
auto bin_res = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib);
if (!bin_res) {
ADD_FAILURE() << "spirv errc: " << bin_res.error().errc << "\nlog: " << bin_res.error().log;
return {};
}
if (bin_res.value().size() != 1u) {
ADD_FAILURE() << "expected exactly one module, got " << bin_res.value().size();
return {};
}
return bin_res.value()[0];
}
} // namespace
// The shape of Minecraft 26.3's cloud vertex shader: no vertex attributes at all, the whole
// geometry read out of a GL_R8I buffer texture indexed by gl_VertexID.
TEST_F(ProgramUtilTest, BufferTextureSamplerIsDetectedInTheModule) {
using namespace MG_Util::ShaderTranspiler;
String vs = R"(#version 330 core
uniform isamplerBuffer CloudFaces;
out vec4 vColor;
void main() {
int face = texelFetch(CloudFaces, gl_VertexID).r;
vColor = vec4(float(face) / 255.0);
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
)";
const Vector<Uint32> spirv = BuildSpirvForStage(vs, GL_VERTEX_SHADER);
ASSERT_FALSE(spirv.empty());
EXPECT_TRUE(ShaderCompiler::ModuleDeclaresBufferTextureSampler(spirv))
<< "an isamplerBuffer must be recognised as a buffer texture";
}
// The float and unsigned spellings lower to the same Dim=Buffer image with a different
// sampled type, so all three have to be caught by the same check.
TEST_F(ProgramUtilTest, FloatAndUnsignedBufferSamplersAreDetectedToo) {
using namespace MG_Util::ShaderTranspiler;
String floatFs = R"(#version 330 core
uniform samplerBuffer Data;
out vec4 fragColor;
void main() { fragColor = texelFetch(Data, 3); }
)";
const Vector<Uint32> floatSpirv = BuildSpirvForStage(floatFs, GL_FRAGMENT_SHADER);
ASSERT_FALSE(floatSpirv.empty());
EXPECT_TRUE(ShaderCompiler::ModuleDeclaresBufferTextureSampler(floatSpirv));
String uintFs = R"(#version 330 core
uniform usamplerBuffer Data;
out vec4 fragColor;
void main() { fragColor = vec4(texelFetch(Data, 3)); }
)";
const Vector<Uint32> uintSpirv = BuildSpirvForStage(uintFs, GL_FRAGMENT_SHADER);
ASSERT_FALSE(uintSpirv.empty());
EXPECT_TRUE(ShaderCompiler::ModuleDeclaresBufferTextureSampler(uintSpirv));
}
// The negative control that keeps the detector from turning into "declares any sampler":
// an ordinary sampler2D must not put a program on the unsupported path on a driver that is
// perfectly able to run it.
TEST_F(ProgramUtilTest, OrdinaryTextureSamplersAreNotBufferTextures) {
using namespace MG_Util::ShaderTranspiler;
String fs = R"(#version 330 core
uniform sampler2D Albedo;
uniform isampler2D Ids;
in vec2 vUv;
out vec4 fragColor;
void main() { fragColor = texture(Albedo, vUv) + vec4(texelFetch(Ids, ivec2(0), 0)); }
)";
const Vector<Uint32> spirv = BuildSpirvForStage(fs, GL_FRAGMENT_SHADER);
ASSERT_FALSE(spirv.empty());
EXPECT_FALSE(ShaderCompiler::ModuleDeclaresBufferTextureSampler(spirv))
<< "only Dim=Buffer images are buffer textures";
}
// Pins the SPIRV-Cross behaviour the whole defect rests on: below ESSL 320 it synthesizes an
// EXT_texture_buffer requirement from the image type itself. There is nothing in the module
// to strip - which is why the OES driver is served by retargeting the emitted directive
// (RetargetTextureBufferExtension) rather than by rewriting the SPIR-V.
TEST_F(ProgramUtilTest, BufferTextureSamplerEmitsTheExtDirectiveInEssl) {
using namespace MG_Util::ShaderTranspiler;
String fs = R"(#version 330 core
uniform isamplerBuffer Data;
out vec4 fragColor;
void main() { fragColor = vec4(texelFetch(Data, 3)); }
)";
const Vector<Uint32> spirv = BuildSpirvForStage(fs, GL_FRAGMENT_SHADER);
ASSERT_FALSE(spirv.empty());
// Emitted at ESSL 310 the way DirectGLES does on an ES 3.1 host (ShaderCompiler's own
// DecompileShader helper hardcodes 320, where the question does not arise). This is the
// version the defect lives at: the emulator SDK's ANGLE is ES 3.1 with neither extension.
auto emitAt = [&spirv](unsigned version) -> String {
SpvcSession session(spirv, SessionUsageBit::Transpile);
spvc_compiler_options options;
session.CreateOptions(&options);
spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, version);
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_TRUE);
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE);
session.SetOptions(options);
const char* result = nullptr;
session.Compile(&result);
return result ? String(result) : String();
};
const String essl310 = emitAt(310);
ASSERT_FALSE(essl310.empty()) << "ESSL 310 emission failed outright";
EXPECT_NE(essl310.find("isamplerBuffer"), String::npos)
<< "the buffer sampler must survive to ESSL:\n" << essl310;
EXPECT_NE(essl310.find("GL_EXT_texture_buffer"), String::npos)
<< "SPIRV-Cross requires EXT_texture_buffer below ESSL 320, and hardcodes that spelling - "
"which is the whole reason an OES-only driver needs the emitted directive retargeted:\n"
<< essl310;
// At 320 buffer textures are ES core, so there is no directive to get wrong. This half is
// what makes the ES 3.2 tier a Pass with nothing to do rather than a silent dependency.
const String essl320 = emitAt(320);
ASSERT_FALSE(essl320.empty()) << "ESSL 320 emission failed outright";
EXPECT_NE(essl320.find("isamplerBuffer"), String::npos) << essl320;
EXPECT_EQ(essl320.find("GL_EXT_texture_buffer"), String::npos)
<< "ES 3.2 has buffer textures in core; requiring the extension there would be wrong:\n"
<< essl320;
}
@@ -841,6 +841,9 @@ namespace MobileGL::MG_Util::BackendLoader {
Bool hasMultiDrawIndirectExtension = false;
Bool hasDrawElementsBaseVertexExtension = false;
Bool hasMultiDrawArraysExtension = false;
// Resolved into caps.TextureBufferSupport below, once the ES version is also known.
Bool hasExtTextureBuffer = false;
Bool hasOesTextureBuffer = false;
for (GLint i = 0; i < extCount; ++i) {
const char* extension = (const char*)glesFuncs.glGetStringi(GL_EXTENSIONS, i);
if (extension) {
@@ -874,6 +877,12 @@ namespace MobileGL::MG_Util::BackendLoader {
std::strcmp(extension, "GL_OES_texture_cube_map_array") == 0) {
caps.SupportsTextureCubeMapArray = true;
}
if (std::strcmp(extension, "GL_EXT_texture_buffer") == 0) {
hasExtTextureBuffer = true;
}
if (std::strcmp(extension, "GL_OES_texture_buffer") == 0) {
hasOesTextureBuffer = true;
}
if (std::strcmp(extension, "GL_EXT_base_instance") == 0) {
caps.SupportsBaseInstance = true;
}
@@ -1050,7 +1059,12 @@ namespace MobileGL::MG_Util::BackendLoader {
glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_UNIFORM_BLOCKS, &maxComputeUniformBlocks);
glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &maxComputeWorkGroupInvocations);
glesFuncs.glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxShaderStorageBufferBindings);
glesFuncs.glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTextureBufferSize);
// 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,
// so on a driver without them it raises GL_INVALID_ENUM, leaves the local at MobileGL's own
// floor, and - because nothing drains the queue until the alignment probe far below - lets
// that error be misattributed to any query in between. It is queried in the guarded block
// that resolves caps.TextureBufferSupport instead.
glesFuncs.glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings);
glesFuncs.glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize);
glesFuncs.glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits);
@@ -1101,6 +1115,62 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.SupportsTextureBorderClamp = true;
caps.SupportsTextureCubeMapArray = true;
}
// Buffer-texture tier. Core from ES 3.2 on; below that the EXT spelling is preferred over
// the OES one purely because SPIRV-Cross emits GL_EXT_texture_buffer natively, so a driver
// with both needs no directive retargeting. The entry point has to have resolved either
// way - the extension string alone is not support (see the multi-draw note above).
{
const Bool textureBufferIsCore =
caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2);
using Tier = MG_External::GLESCapabilities::TextureBufferTier;
if (glesFuncs.glTexBuffer == nullptr) {
caps.TextureBufferSupport = Tier::None;
} else if (textureBufferIsCore) {
caps.TextureBufferSupport = Tier::CoreEs32;
} else if (hasExtTextureBuffer) {
caps.TextureBufferSupport = Tier::ExtensionEXT;
} else if (hasOesTextureBuffer) {
caps.TextureBufferSupport = Tier::ExtensionOES;
} else {
caps.TextureBufferSupport = Tier::None;
}
if (caps.TextureBufferSupport != Tier::None) {
// Drain first: an error left by any earlier probe would otherwise read as this
// query having failed, and the value would be discarded as a non-answer.
if (glesFuncs.glGetError) {
while (glesFuncs.glGetError() != GL_NO_ERROR) {
}
}
glesFuncs.glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTextureBufferSize);
if (glesFuncs.glGetError) {
Bool queryFailed = false;
while (glesFuncs.glGetError() != GL_NO_ERROR) {
queryFailed = true;
}
caps.MaxTextureBufferSizeIsDriverReported = !queryFailed;
} else {
caps.MaxTextureBufferSizeIsDriverReported = true;
}
}
// On the None tier the local keeps MobileGL's floor and
// MaxTextureBufferSizeIsDriverReported stays false. The floor, not 0, is what the
// frontend goes on advertising: MobileGL reports an OpenGL 4.x context, where buffer
// textures are core and GL_MAX_TEXTURE_BUFFER_SIZE has a spec minimum of 65536, so 0
// would be an illegal answer that no conformant app is prepared to read (several
// divide by it or size an allocation with it). The dishonesty is contained by making
// the missing capability loud instead - at capability init here, at glTexBuffer, at
// program build, and as its own driver POST row - because GL offers no way to say
// "buffer textures exist but cannot work".
if (caps.TextureBufferSupport == Tier::None) {
MGLOG_I(" Buffer textures: UNSUPPORTED (ES %d.%d core needs 3.2, and neither "
"GL_EXT_texture_buffer nor GL_OES_texture_buffer is present). Any shader "
"sampling a samplerBuffer will fail to compile, and MobileGL keeps "
"advertising GL_MAX_TEXTURE_BUFFER_SIZE = %d because a GL 4.x context may "
"not report 0.",
caps.GLESVersion.Major, caps.GLESVersion.Minor, maxTextureBufferSize);
}
}
if (caps.SupportsTextureFilterAnisotropy) {
GLfloat maxTextureMaxAnisotropy = 1.0f;
glesFuncs.glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxTextureMaxAnisotropy);
@@ -1215,7 +1285,8 @@ namespace MobileGL::MG_Util::BackendLoader {
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);
MGLOG_I(" GL_MAX_TEXTURE_BUFFER_SIZE: %d", caps.MaxTextureBufferSize);
MGLOG_I(" GL_MAX_TEXTURE_BUFFER_SIZE: %d%s", caps.MaxTextureBufferSize,
caps.MaxTextureBufferSizeIsDriverReported ? "" : " (MobileGL floor - the driver has no buffer textures to ask)");
MGLOG_I(" GL_MAX_UNIFORM_BUFFER_BINDINGS: %d", caps.MaxUniformBufferBindings);
MGLOG_I(" GL_MAX_UNIFORM_BLOCK_SIZE: %d", caps.MaxUniformBlockSize);
MGLOG_I(" GL_MAX_IMAGE_UNITS: %d", caps.MaxImageUnits);
@@ -1057,6 +1057,27 @@ namespace MobileGL {
Bool SupportsTextureBorderClamp = false;
// GL_TEXTURE_CUBE_MAP_ARRAY: ES 3.2 core, or EXT/OES_texture_cube_map_array before it.
Bool SupportsTextureCubeMapArray = false;
// Which spelling of buffer-texture support the host driver has. Desktop GL makes buffer
// textures core from 3.1 on, so the frontend advertises them unconditionally and an app
// may call glTexBuffer at any time; ES only gained them in 3.2, and before that only
// through EXT/OES_texture_buffer. The two extensions are functionally identical but
// their ESSL directives are NOT interchangeable, and SPIRV-Cross hardcodes the EXT
// spelling whenever it emits ESSL below 320 for a Dim=Buffer image - so a driver that
// ships only the OES spelling needs the emitted directive retargeted, and a driver with
// neither cannot compile such a shader at all. Gate on this, never on the entry point:
// eglGetProcAddress hands back live-looking stubs (see AcquireGLESFunctions).
enum class TextureBufferTier : Uint8 {
None = 0, // no core support and neither extension; glTexBuffer is unusable
CoreEs32, // ES >= 3.2, buffer textures are core and ESSL 320 needs no directive
ExtensionEXT, // GL_EXT_texture_buffer; ESSL below 320 must say GL_EXT_texture_buffer
ExtensionOES, // GL_OES_texture_buffer; ESSL below 320 must say GL_OES_texture_buffer
};
TextureBufferTier TextureBufferSupport = TextureBufferTier::None;
// GL_MAX_TEXTURE_BUFFER_SIZE actually came back from the driver. False means the value
// below is MobileGL's own floor, not a driver answer: the pname is only legal once
// buffer textures exist, and querying it on a driver without them raises
// GL_INVALID_ENUM and leaves the default untouched.
Bool MaxTextureBufferSizeIsDriverReported = false;
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT of the host driver; only queried when the
// extension above is present, and left at 1.0 (no anisotropy) otherwise.
Float MaxTextureMaxAnisotropy = 1.0f;
+74
View File
@@ -422,6 +422,57 @@ namespace MobileGL::MG_Util::SelfTest {
"map array texture gets no driver storage at all, so sampling one reads nothing "
"and rendering to one does not reach the screen");
}
// FAIL, not WARN: buffer textures are CORE in OpenGL 3.1 and MobileGL advertises a 4.x
// context, so an application may use one without asking - and nothing degrades
// gracefully when they are absent. The texture gets no driver storage (glTexBuffer does
// not exist), and, worse, every shader declaring a samplerBuffer fails to compile
// outright, because SPIRV-Cross emits `#extension GL_EXT_texture_buffer : require` for
// it below ESSL 320. The program then never links and every draw using it silently
// draws nothing - which is how Minecraft 26.3, whose cloud layer is built entirely from
// gl_VertexID plus texelFetch on a GL_R8I buffer texture, loses its clouds.
// The limit is stated on every tier because it is the one number an application can
// read, and on the None tier it is knowingly a fiction (see below).
{
using Tier = MG_External::GLESCapabilities::TextureBufferTier;
const Int advertisedLimit = caps.MaxTextureBufferSize;
switch (caps.TextureBufferSupport) {
case Tier::CoreEs32:
builder.Pass("Buffer textures",
format("core in ES 3.2; GL_MAX_TEXTURE_BUFFER_SIZE = {} is the "
"driver's own answer, and ESSL 320 needs no #extension "
"directive to declare a samplerBuffer",
advertisedLimit));
break;
case Tier::ExtensionEXT:
builder.Pass("Buffer textures",
format("GL_EXT_texture_buffer; GL_MAX_TEXTURE_BUFFER_SIZE = {} is "
"the driver's own answer, and the directive SPIRV-Cross "
"emits (GL_EXT_texture_buffer) is the one this driver wants",
advertisedLimit));
break;
case Tier::ExtensionOES:
builder.Pass("Buffer textures",
format("GL_OES_texture_buffer; GL_MAX_TEXTURE_BUFFER_SIZE = {} is "
"the driver's own answer. SPIRV-Cross hardcodes the EXT "
"spelling, so MobileGL retargets the emitted #extension "
"directive to the OES one this driver advertises",
advertisedLimit));
break;
case Tier::None:
default:
builder.Fail("Buffer textures",
format("not supported (pre-ES 3.2 without GL_EXT/OES_texture_buffer); "
"glTexBuffer does not exist, so a buffer texture gets no storage, "
"and any shader declaring a samplerBuffer fails to compile and "
"leaves its program unlinked - every draw using it is a silent "
"no-op. MobileGL still reports GL_MAX_TEXTURE_BUFFER_SIZE = {}: "
"the value is a floor it cannot honour, kept because an OpenGL "
"4.x context may not answer 0 and GL has no way to say that a "
"core feature is missing",
advertisedLimit));
break;
}
}
// Reported rather than probed: this one cannot come out any other way. OpenGL ES has no
// double-precision vertex format and ESSL has no fp64 type, so there is no driver and no
// extension that could make it work - the row exists so the loss is named at startup
@@ -1728,6 +1779,29 @@ namespace MobileGL::MG_Util::SelfTest {
} else {
builder.Warn("dualSrcBlend", "unsupported; GL_SRC1_* dual-source blend factors hard-fail at draw");
}
// The Magma counterpart of the GLES "Buffer textures" row, so the two sections can be
// read side by side. Vulkan has no optional-feature bit here: a uniform texel buffer is
// core, and maxTexelBufferElements has a spec floor of 65536 - exactly the GL 3.1 floor
// for GL_MAX_TEXTURE_BUFFER_SIZE - so this backend can always back a buffer texture and
// the row exists to state the limit MobileGL derives its advertisement from, not to
// report a risk. A driver below the floor would be non-conformant, hence the Warn.
{
const Uint32 maxTexelBufferElements = properties.limits.maxTexelBufferElements;
constexpr Uint32 kGL31MinTextureBufferSize = 65536;
if (maxTexelBufferElements >= kGL31MinTextureBufferSize) {
builder.Pass("maxTexelBufferElements",
format("{}; uniform texel buffers are core in Vulkan, so buffer textures "
"need no extension and MobileGL advertises "
"GL_MAX_TEXTURE_BUFFER_SIZE from this limit",
maxTexelBufferElements));
} else {
builder.Warn("maxTexelBufferElements",
format("{} (< {}); below the OpenGL 3.1 floor for "
"GL_MAX_TEXTURE_BUFFER_SIZE, so a conformant application may "
"create a buffer texture larger than this driver can view",
maxTexelBufferElements, kGL31MinTextureBufferSize));
}
}
{
VkImageFormatProperties sliceProbe{};
const Bool sliceCapable =
@@ -30,6 +30,8 @@
#include "SpirvPasses/LegalizeFragmentOutputIndexPass.h"
#include "spirv-tools/libspirv.h"
#include "spirv-tools/optimizer.hpp"
#include "source/opt/build_module.h"
#include "source/opt/ir_context.h"
#include "ShaderSourceProcessor.h"
#include <MG_Backend/BackendObjects.h>
@@ -538,6 +540,32 @@ namespace MobileGL {
return g_spirvValidationFailures.load(std::memory_order_relaxed);
}
Bool ShaderCompiler::ModuleDeclaresBufferTextureSampler(const Vector<Uint32>& spirv) {
// Callers gate this on the driver LACKING buffer textures, so the module build
// here only ever happens on a degraded driver that is about to fail the compile
// anyway - it is not on the healthy path.
std::unique_ptr<spvtools::opt::IRContext> context = spvtools::BuildModule(
SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleDeclaresBufferTextureSampler"),
spirv.data(), spirv.size());
if (!context) {
// Unparseable here means unusable downstream too; let the ordinary transpile
// path produce the error rather than inventing a capability verdict from it.
return false;
}
for (const spvtools::opt::Instruction& type : context->types_values()) {
if (type.opcode() != spv::Op::OpTypeImage) {
continue;
}
// OpTypeImage operands: Sampled Type, Dim, Depth, Arrayed, MS, Sampled, Format.
// Dim is operand 1; SpvDimBuffer is what samplerBuffer/isamplerBuffer/
// usamplerBuffer all lower to, whatever their sampled type.
if (static_cast<spv::Dim>(type.GetSingleWordInOperand(1)) == spv::Dim::Buffer) {
return true;
}
}
return false;
}
bool ShaderCompiler::SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary,
Vector<uint32_t>& outputBinary) {
using namespace spvtools;
@@ -133,6 +133,16 @@ namespace MobileGL {
// total.
static Uint64 SpirvValidationFailureCount();
static Uint64 NoteSpirvValidationFailure();
// True when the module declares any buffer-texture sampler - a samplerBuffer,
// isamplerBuffer or usamplerBuffer, i.e. an OpTypeImage with Dim = Buffer.
// DirectGLES asks before handing the transpiled ESSL to the driver: buffer
// textures are core in the OpenGL 3.1+ context MobileGL advertises but need
// ES 3.2 or EXT/OES_texture_buffer on the host, and on a driver without them
// SPIRV-Cross's `#extension ... : require` makes the shader uncompilable. The
// check exists so that failure can be reported as the missing capability it is,
// naming the shader, rather than as a driver info log nobody sees.
static Bool ModuleDeclaresBufferTextureSampler(const Vector<Uint32>& spirv);
};
} // namespace ShaderTranspiler
} // namespace MG_Util