mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-13 14:48:32 +09:00
[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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user