mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 05:08:31 +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:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user