mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 05:38:31 +09:00
[Fix] (Review): bound copies by the requested level, reach every cube face, keep array layer counts, and give glSpecializeShader its spec error surface
This commit is contained in:
@@ -1702,6 +1702,54 @@ namespace MobileGL {
|
||||
return SpvExecutionModelFragment;
|
||||
}
|
||||
}
|
||||
// The decorated capture layout, as the equivalent glTransformFeedbackVaryings
|
||||
// request. GL 4.6 core 11.1.2.1 / ARB_transform_feedback3 give the name list two
|
||||
// pseudo-varyings that are exactly what a decoration layout needs: gl_NextBuffer
|
||||
// moves to the next capture buffer, and gl_SkipComponentsN (N in 1..4) advances the
|
||||
// cursor without capturing. Together they can express any offset/stride layout
|
||||
// whose offsets are component-aligned, which SPIR-V's are (Offset is in bytes and
|
||||
// xfb offsets are four-byte aligned by rule).
|
||||
Vector<String> BuildXfbVaryingRequest(const Vector<SpirvXfbCapture>& captures) {
|
||||
Vector<String> names;
|
||||
if (captures.empty()) return names;
|
||||
|
||||
auto emitSkip = [&names](Uint32 components) {
|
||||
while (components > 0) {
|
||||
const Uint32 step = std::min<Uint32>(components, 4);
|
||||
names.push_back("gl_SkipComponents" + std::to_string(step));
|
||||
components -= step;
|
||||
}
|
||||
};
|
||||
|
||||
Uint32 currentBuffer = captures.front().buffer;
|
||||
Uint32 cursorComponents = 0;
|
||||
Uint32 currentStride = 0;
|
||||
// Buffers below the first captured one still have to be stepped over, so the
|
||||
// Nth gl_NextBuffer really does land on buffer N.
|
||||
for (Uint32 buffer = 0; buffer < currentBuffer; ++buffer) {
|
||||
names.push_back("gl_NextBuffer");
|
||||
}
|
||||
for (const SpirvXfbCapture& capture : captures) {
|
||||
if (capture.buffer != currentBuffer) {
|
||||
// Pad the buffer being left out to its declared stride, so the record
|
||||
// size the module asked for survives.
|
||||
if (currentStride / 4 > cursorComponents) emitSkip(currentStride / 4 - cursorComponents);
|
||||
for (Uint32 buffer = currentBuffer; buffer < capture.buffer; ++buffer) {
|
||||
names.push_back("gl_NextBuffer");
|
||||
}
|
||||
currentBuffer = capture.buffer;
|
||||
cursorComponents = 0;
|
||||
currentStride = 0;
|
||||
}
|
||||
const Uint32 offsetComponents = capture.offset / 4;
|
||||
if (offsetComponents > cursorComponents) emitSkip(offsetComponents - cursorComponents);
|
||||
names.push_back(capture.name);
|
||||
cursorComponents = offsetComponents + capture.componentCount;
|
||||
currentStride = std::max(currentStride, capture.stride);
|
||||
}
|
||||
if (currentStride / 4 > cursorComponents) emitSkip(currentStride / 4 - cursorComponents);
|
||||
return names;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Result<void> ShaderCompiler::ValidateSpirvModule(const Vector<Uint32>& spirv) {
|
||||
@@ -1734,15 +1782,29 @@ namespace MobileGL {
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<String> ShaderCompiler::SpecializeAndDecompileSpirvModule(const Vector<Uint32>& spirv,
|
||||
GLenum shaderType,
|
||||
const String& entryPoint,
|
||||
const Vector<Uint32>& constantIds,
|
||||
const Vector<Uint32>& constantValues) {
|
||||
Result<ShaderCompiler::SpecializedModule> ShaderCompiler::SpecializeAndDecompileSpirvModule(
|
||||
const Vector<Uint32>& spirv, GLenum shaderType, const String& entryPoint,
|
||||
const Vector<Uint32>& constantIds, const Vector<Uint32>& constantValues,
|
||||
SpecializationFailure& outFailure) {
|
||||
outFailure = SpecializationFailure::None;
|
||||
|
||||
SpvcSession session(spirv, SessionUsageBit::Transpile);
|
||||
if (!session.IsTranspileReady()) {
|
||||
// SPIRV-Cross could not parse the module. glShaderBinary's spirv-val pass is a
|
||||
// validity check, not a parseability one, so this is reachable with a module
|
||||
// that validates - hence a diagnosis rather than the null dereference the
|
||||
// unchecked constructor used to walk into.
|
||||
outFailure = SpecializationFailure::ModuleRejected;
|
||||
ResultInfo r;
|
||||
r.errc = -11;
|
||||
r.log = "Error: [ARB_gl_spirv] the module could not be parsed:\n" +
|
||||
String(session.GetLastErrorString());
|
||||
return std::unexpected(r);
|
||||
}
|
||||
|
||||
Uint32 unknownConstantId = 0;
|
||||
if (!session.SetSpecializationConstants(constantIds, constantValues, unknownConstantId)) {
|
||||
outFailure = SpecializationFailure::UnknownConstantId;
|
||||
ResultInfo r;
|
||||
r.errc = -7;
|
||||
r.log = "Error: [ARB_gl_spirv] constant index " + std::to_string(unknownConstantId) +
|
||||
@@ -1750,19 +1812,32 @@ namespace MobileGL {
|
||||
return std::unexpected(r);
|
||||
}
|
||||
|
||||
if (!entryPoint.empty()) {
|
||||
if (session.SetEntryPoint(entryPoint.c_str(), ExecutionModelForShaderType(shaderType)) !=
|
||||
SPVC_SUCCESS) {
|
||||
ResultInfo r;
|
||||
r.errc = -8;
|
||||
r.log = "Error: [ARB_gl_spirv] the module has no entry point named '" + entryPoint +
|
||||
"' for this shader stage:\n" + String(session.GetLastErrorString());
|
||||
return std::unexpected(r);
|
||||
}
|
||||
// No `if (!entryPoint.empty())` guard any more. ARB_gl_spirv makes pEntryPoint the
|
||||
// name of the entry point to specialize, and no module carries one named ""; the
|
||||
// guard turned an empty name into "whichever entry point happens to be default",
|
||||
// which is neither what the application asked for nor an error it was told about.
|
||||
if (session.SetEntryPoint(entryPoint.c_str(), ExecutionModelForShaderType(shaderType)) !=
|
||||
SPVC_SUCCESS) {
|
||||
outFailure = SpecializationFailure::UnknownEntryPoint;
|
||||
ResultInfo r;
|
||||
r.errc = -8;
|
||||
r.log = "Error: [ARB_gl_spirv] the module has no entry point named '" + entryPoint +
|
||||
"' for this shader stage:\n" + String(session.GetLastErrorString());
|
||||
return std::unexpected(r);
|
||||
}
|
||||
|
||||
// Read the declared capture layout, then REMOVE the decorations that describe it.
|
||||
// Both halves matter: without the read a SPIR-V program captures nothing, and
|
||||
// without the strip the decorations round-trip through the emitted GLSL back into
|
||||
// the regenerated SPIR-V, where DirectGLES's ESSL hop refuses them outright and
|
||||
// loses the stage. See SpvcSession::StripTransformFeedbackDecorations.
|
||||
SpecializedModule specialized;
|
||||
specialized.xfbVaryings = BuildXfbVaryingRequest(session.ReflectTransformFeedbackCaptures());
|
||||
session.StripTransformFeedbackDecorations();
|
||||
|
||||
spvc_compiler_options options;
|
||||
if (session.CreateOptions(&options) != SPVC_SUCCESS) {
|
||||
outFailure = SpecializationFailure::ModuleRejected;
|
||||
ResultInfo r;
|
||||
r.errc = -9;
|
||||
r.log = "Error: [ARB_gl_spirv] could not create SPIRV-Cross options for the module.";
|
||||
@@ -1786,13 +1861,15 @@ namespace MobileGL {
|
||||
const char* emitted = nullptr;
|
||||
session.Compile(&emitted);
|
||||
if (!emitted) {
|
||||
outFailure = SpecializationFailure::ModuleRejected;
|
||||
ResultInfo r;
|
||||
r.errc = -10;
|
||||
r.log = "Error: [ARB_gl_spirv] could not translate the module to GLSL:\n" +
|
||||
String(session.GetLastErrorString());
|
||||
return std::unexpected(r);
|
||||
}
|
||||
return String(emitted);
|
||||
specialized.glsl = String(emitted);
|
||||
return specialized;
|
||||
}
|
||||
|
||||
Result<String> ShaderCompiler::DecompileShader(SpvcSession& session) {
|
||||
|
||||
@@ -434,10 +434,43 @@ namespace MobileGL {
|
||||
// `constantIds` and `constantValues` are the parallel arrays the entry point
|
||||
// takes. A constant id the module does not declare is GL_INVALID_VALUE per the
|
||||
// extension; it is reported through the error log rather than silently ignored.
|
||||
static Result<String> SpecializeAndDecompileSpirvModule(const Vector<Uint32>& spirv,
|
||||
GLenum shaderType, const String& entryPoint,
|
||||
const Vector<Uint32>& constantIds,
|
||||
const Vector<Uint32>& constantValues);
|
||||
// Why the caller needs a REASON and not just a failure: ARB_gl_spirv splits the
|
||||
// ways specialization can fail into two groups with different GL surfaces. A bad
|
||||
// entry-point name and a constant id the module does not declare are enumerated
|
||||
// errors - GL_INVALID_VALUE, and, being errors, they must leave the shader object
|
||||
// exactly as it was. Everything else (a module SPIRV-Cross cannot translate) is a
|
||||
// COMPILE failure, reported through COMPILE_STATUS and the info log like any other
|
||||
// glCompileShader outcome. Returning one undifferentiated error is what made both
|
||||
// groups look like the second.
|
||||
enum class SpecializationFailure {
|
||||
None,
|
||||
UnknownConstantId, // GL_INVALID_VALUE
|
||||
UnknownEntryPoint, // GL_INVALID_VALUE
|
||||
ModuleRejected, // COMPILE_STATUS false + info log
|
||||
};
|
||||
|
||||
// What a specialized module turns into: the GLSL the ordinary pipeline compiles,
|
||||
// plus the transform-feedback capture the module DECLARED, re-expressed as the
|
||||
// glTransformFeedbackVaryings request that produces the same layout.
|
||||
//
|
||||
// The re-expression is the whole design. ARB_gl_spirv makes XfbBuffer/XfbStride/
|
||||
// Offset decorations the only way a SPIR-V program declares capture, and MobileGL's
|
||||
// capture machinery - the frontend packer, DirectGLES's forwarding to the ES
|
||||
// driver, DirectVulkan's XfbCaptureDecoratePass - is driven entirely by a name
|
||||
// list. Translating the decorations into the equivalent name list (with
|
||||
// ARB_transform_feedback3's gl_NextBuffer / gl_SkipComponentsN spelling carrying
|
||||
// the buffer breaks and the gaps) hands a SPIR-V program to the machinery that
|
||||
// already exists, instead of teaching every consumer a second declaration form.
|
||||
struct SpecializedModule {
|
||||
String glsl;
|
||||
Vector<String> xfbVaryings;
|
||||
GLenum xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
};
|
||||
|
||||
static Result<SpecializedModule> SpecializeAndDecompileSpirvModule(
|
||||
const Vector<Uint32>& spirv, GLenum shaderType, const String& entryPoint,
|
||||
const Vector<Uint32>& constantIds, const Vector<Uint32>& constantValues,
|
||||
SpecializationFailure& outFailure);
|
||||
|
||||
// spirv-val over an application-supplied module, against the environment MobileGL
|
||||
// parses and emits under. glShaderBinary is where a malformed module has to be
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "SpvcSession.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
@@ -184,11 +186,29 @@ namespace MobileGL {
|
||||
const SpvId* p_spirv = spirv.data();
|
||||
size_t word_count = spirv.size();
|
||||
|
||||
spvc_context_create(&context);
|
||||
spvc_context_parse_spirv(context, p_spirv, word_count, &ir);
|
||||
spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP,
|
||||
&compiler);
|
||||
spvc_compiler_create_shader_resources(compiler, &resources);
|
||||
// Every step is checked, and each guards the next: the C API writes its
|
||||
// out-param only on success, so passing a failed step's null handle to the
|
||||
// step after it is a raw dereference (spvc_context_create_compiler does
|
||||
// `parsed_ir->parsed`, spvc_compiler_create_shader_resources does
|
||||
// `compiler->context`). IsTranspileReady() is how a caller asks whether this
|
||||
// sequence got all the way through.
|
||||
if (spvc_context_create(&context) != SPVC_SUCCESS) {
|
||||
context = nullptr;
|
||||
return;
|
||||
}
|
||||
if (spvc_context_parse_spirv(context, p_spirv, word_count, &ir) != SPVC_SUCCESS) {
|
||||
ir = nullptr;
|
||||
return;
|
||||
}
|
||||
if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir,
|
||||
SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler) != SPVC_SUCCESS) {
|
||||
compiler = nullptr;
|
||||
return;
|
||||
}
|
||||
if (spvc_compiler_create_shader_resources(compiler, &resources) != SPVC_SUCCESS) {
|
||||
resources = nullptr;
|
||||
return;
|
||||
}
|
||||
} else if (usage & SessionUsageBit::Reflection) {
|
||||
SpvReflectResult result = spvReflectCreateShaderModule(
|
||||
spirv.size() * sizeof(uint32_t), spirv.data(), &reflectModule);
|
||||
@@ -496,8 +516,144 @@ namespace MobileGL {
|
||||
SPVC_CHK_RETURN
|
||||
}
|
||||
|
||||
namespace {
|
||||
// How many 32-bit components a captured variable occupies, which is what the
|
||||
// gl_SkipComponentsN padding below is counted in. Matrices and arrays multiply.
|
||||
Uint32 XfbComponentCount(spvc_compiler compiler, spvc_type_id typeId) {
|
||||
const spvc_type type = spvc_compiler_get_type_handle(compiler, typeId);
|
||||
if (type == nullptr) return 0;
|
||||
Uint32 components = spvc_type_get_vector_size(type) * spvc_type_get_columns(type);
|
||||
const unsigned dimensions = spvc_type_get_num_array_dimensions(type);
|
||||
for (unsigned d = 0; d < dimensions; ++d) {
|
||||
const unsigned length = spvc_type_get_array_dimension(type, d);
|
||||
if (length != 0) components *= length;
|
||||
}
|
||||
// A double occupies two component slots per scalar (GL 4.6 core 11.1.2.1).
|
||||
const spvc_basetype base = spvc_type_get_basetype(type);
|
||||
if (base == SPVC_BASETYPE_FP64 || base == SPVC_BASETYPE_INT64 ||
|
||||
base == SPVC_BASETYPE_UINT64) {
|
||||
components *= 2;
|
||||
}
|
||||
return components;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Vector<SpirvXfbCapture> SpvcSession::ReflectTransformFeedbackCaptures() const {
|
||||
Vector<SpirvXfbCapture> captures;
|
||||
if (compiler == nullptr || resources == nullptr) return captures;
|
||||
|
||||
const spvc_reflected_resource* outputs = nullptr;
|
||||
SizeT outputCount = 0;
|
||||
if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STAGE_OUTPUT, &outputs,
|
||||
&outputCount) != SPVC_SUCCESS) {
|
||||
return captures;
|
||||
}
|
||||
|
||||
for (SizeT i = 0; i < outputCount; ++i) {
|
||||
const spvc_reflected_resource& output = outputs[i];
|
||||
// XfbBuffer/XfbStride sit on the VARIABLE; Offset sits on the variable for a
|
||||
// plain output and on each MEMBER for a block (which is how a redeclared
|
||||
// gl_PerVertex carries it).
|
||||
const Bool hasBuffer =
|
||||
spvc_compiler_has_decoration(compiler, output.id, SpvDecorationXfbBuffer) == SPVC_TRUE;
|
||||
const Uint32 buffer =
|
||||
hasBuffer ? spvc_compiler_get_decoration(compiler, output.id, SpvDecorationXfbBuffer) : 0u;
|
||||
const Uint32 stride =
|
||||
spvc_compiler_has_decoration(compiler, output.id, SpvDecorationXfbStride) == SPVC_TRUE
|
||||
? spvc_compiler_get_decoration(compiler, output.id, SpvDecorationXfbStride)
|
||||
: 0u;
|
||||
|
||||
const spvc_type type = spvc_compiler_get_type_handle(compiler, output.base_type_id);
|
||||
const unsigned memberCount =
|
||||
type != nullptr && spvc_type_get_basetype(type) == SPVC_BASETYPE_STRUCT
|
||||
? spvc_type_get_num_member_types(type)
|
||||
: 0u;
|
||||
|
||||
if (memberCount == 0) {
|
||||
if (spvc_compiler_has_decoration(compiler, output.id, SpvDecorationOffset) != SPVC_TRUE) {
|
||||
continue;
|
||||
}
|
||||
SpirvXfbCapture capture;
|
||||
capture.name = output.name ? output.name : "";
|
||||
capture.buffer = buffer;
|
||||
capture.stride = stride;
|
||||
capture.offset = spvc_compiler_get_decoration(compiler, output.id, SpvDecorationOffset);
|
||||
capture.componentCount = XfbComponentCount(compiler, output.type_id);
|
||||
if (!capture.name.empty()) captures.push_back(Move(capture));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (unsigned member = 0; member < memberCount; ++member) {
|
||||
if (spvc_compiler_has_member_decoration(compiler, output.base_type_id, member,
|
||||
SpvDecorationOffset) != SPVC_TRUE) {
|
||||
continue;
|
||||
}
|
||||
const char* memberName =
|
||||
spvc_compiler_get_member_name(compiler, output.base_type_id, member);
|
||||
if (memberName == nullptr || *memberName == '\0') continue;
|
||||
SpirvXfbCapture capture;
|
||||
// A redeclared built-in block contributes its members by their own names
|
||||
// ("gl_Position"), which is how GL's capture interface spells them; an
|
||||
// application block spells them "Block.member".
|
||||
const String blockName = output.name ? String(output.name) : String{};
|
||||
const Bool isBuiltInBlock = blockName.compare(0, 3, "gl_") == 0;
|
||||
capture.name = isBuiltInBlock || blockName.empty()
|
||||
? String(memberName)
|
||||
: blockName + "." + String(memberName);
|
||||
capture.buffer = buffer;
|
||||
capture.stride = stride;
|
||||
capture.offset = spvc_compiler_get_member_decoration(compiler, output.base_type_id, member,
|
||||
SpvDecorationOffset);
|
||||
capture.componentCount = XfbComponentCount(
|
||||
compiler, spvc_type_get_member_type(type, member));
|
||||
captures.push_back(Move(capture));
|
||||
}
|
||||
}
|
||||
|
||||
// Capture order IS buffer-then-offset order: that is the order the equivalent
|
||||
// glTransformFeedbackVaryings request has to name them in for the frontend's
|
||||
// packer to reproduce the declared layout.
|
||||
std::stable_sort(captures.begin(), captures.end(),
|
||||
[](const SpirvXfbCapture& a, const SpirvXfbCapture& b) {
|
||||
if (a.buffer != b.buffer) return a.buffer < b.buffer;
|
||||
return a.offset < b.offset;
|
||||
});
|
||||
return captures;
|
||||
}
|
||||
|
||||
void SpvcSession::StripTransformFeedbackDecorations() {
|
||||
if (compiler == nullptr || resources == nullptr) return;
|
||||
|
||||
const spvc_reflected_resource* outputs = nullptr;
|
||||
SizeT outputCount = 0;
|
||||
if (spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STAGE_OUTPUT, &outputs,
|
||||
&outputCount) != SPVC_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
for (SizeT i = 0; i < outputCount; ++i) {
|
||||
const spvc_reflected_resource& output = outputs[i];
|
||||
spvc_compiler_unset_decoration(compiler, output.id, SpvDecorationXfbBuffer);
|
||||
spvc_compiler_unset_decoration(compiler, output.id, SpvDecorationXfbStride);
|
||||
spvc_compiler_unset_decoration(compiler, output.id, SpvDecorationOffset);
|
||||
|
||||
const spvc_type type = spvc_compiler_get_type_handle(compiler, output.base_type_id);
|
||||
if (type == nullptr || spvc_type_get_basetype(type) != SPVC_BASETYPE_STRUCT) continue;
|
||||
const unsigned memberCount = spvc_type_get_num_member_types(type);
|
||||
for (unsigned member = 0; member < memberCount; ++member) {
|
||||
spvc_compiler_unset_member_decoration(compiler, output.base_type_id, member,
|
||||
SpvDecorationOffset);
|
||||
spvc_compiler_unset_member_decoration(compiler, output.base_type_id, member,
|
||||
SpvDecorationXfbBuffer);
|
||||
spvc_compiler_unset_member_decoration(compiler, output.base_type_id, member,
|
||||
SpvDecorationXfbStride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spvc_result SpvcSession::SetEntryPoint(const char* name, SpvExecutionModel model) {
|
||||
if (compiler == nullptr || name == nullptr || *name == '\0') return SPVC_SUCCESS;
|
||||
// A null compiler or a null/empty name is a FAILURE, not a silent success: the
|
||||
// caller is asking for a specific entry point and there is none to give it.
|
||||
if (compiler == nullptr || name == nullptr || *name == '\0') return SPVC_ERROR_INVALID_ARGUMENT;
|
||||
return spvc_compiler_set_entry_point(compiler, name, model);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,19 @@ namespace MobileGL {
|
||||
}
|
||||
};
|
||||
|
||||
// One output a SPIR-V module asked to have captured, as its Xfb decorations describe
|
||||
// it. ARB_gl_spirv makes these decorations the ONLY way a SPIR-V program declares
|
||||
// transform feedback - glTransformFeedbackVaryings has no effect on such a program -
|
||||
// so a module that carries them and an implementation that ignores them capture
|
||||
// nothing at all.
|
||||
struct SpirvXfbCapture {
|
||||
String name; // the GL interface name: "gl_Position", or "Block.member"
|
||||
Uint32 buffer = 0; // XfbBuffer on the declaring variable
|
||||
Uint32 offset = 0; // Offset on the variable or on the member
|
||||
Uint32 stride = 0; // XfbStride on the declaring variable
|
||||
Uint32 componentCount = 0; // how many 32-bit components the capture occupies
|
||||
};
|
||||
|
||||
enum class SessionUsageBit {
|
||||
Reflection = 1 << 0,
|
||||
Transpile = 1 << 1,
|
||||
@@ -164,6 +177,28 @@ namespace MobileGL {
|
||||
// Select which OpEntryPoint of `model` this session compiles. A module may carry
|
||||
// several of the same execution model, and glSpecializeShader names the one the
|
||||
// shader object stands for.
|
||||
// Whether the transpile constructor actually built a compiler. Every SPIRV-Cross
|
||||
// handle below is default-null and the C API leaves its out-params untouched on
|
||||
// failure, so a module SPIRV-Cross cannot parse used to leave `ir` null and then
|
||||
// have spvc_context_create_compiler dereference it - a raw null read that
|
||||
// SPVC_BEGIN_SAFE_SCOPE cannot catch. Only glShaderBinary feeds this class bytes
|
||||
// MobileGL did not generate itself, which is why the check earns its keep now.
|
||||
Bool IsTranspileReady() const { return compiler != nullptr && resources != nullptr; }
|
||||
// Read the module's transform-feedback layout out of its Xfb decorations, in
|
||||
// (buffer, offset) order. Empty when the module declares no capture.
|
||||
Vector<SpirvXfbCapture> ReflectTransformFeedbackCaptures() const;
|
||||
// Remove every Xfb decoration the reflection above just read.
|
||||
//
|
||||
// This is not tidying: the decorations must not survive into the GLSL this session
|
||||
// emits. SPIRV-Cross re-emits them as `layout(xfb_buffer = N, xfb_stride = M) out
|
||||
// gl_PerVertex { layout(xfb_offset = K) ... }`, glslang re-encodes that into the
|
||||
// regenerated SPIR-V, and the DirectGLES leg then transpiles THAT to ESSL - where
|
||||
// the same SPIRV-Cross throws "Need GL_ARB_enhanced_layouts for xfb_stride or
|
||||
// xfb_buffer" and the stage silently fails to build, leaving a program that links
|
||||
// clean and draws nothing. Stripping them and re-declaring the capture through
|
||||
// MobileGL's ordinary capture machinery (which both backends already implement)
|
||||
// routes a SPIR-V program down exactly the path a GLSL program takes.
|
||||
void StripTransformFeedbackDecorations();
|
||||
spvc_result SetEntryPoint(const char* name, SpvExecutionModel model);
|
||||
// Bake glSpecializeShader's values into the module's specialization constants.
|
||||
// Every value is a GLuint on the GL side and is reinterpreted according to the
|
||||
|
||||
@@ -176,11 +176,17 @@ namespace MobileGL {
|
||||
// memo keys. The conformance suite accepts a link-time rejection: its predicate is
|
||||
// compiledAndLinked(), which is the AND of the two.
|
||||
//
|
||||
// ONE enforcement point for all five kinds, on purpose. Before this, exactly one kind -
|
||||
// shader-storage blocks - was checked, by a bespoke lexical scan of the shader source, which
|
||||
// is why the storage sub-family was the one that passed while sampler, image, uniform-block
|
||||
// and atomic-counter bindings sailed past every ceiling. That scanner is retired; a second
|
||||
// enforcement point is a second thing to drift.
|
||||
// FIVE KINDS HERE, AND ONE OF THEM IS ALSO CHECKED EARLIER. Before this, exactly one kind -
|
||||
// shader-storage blocks - was checked at all, by a bespoke lexical scan of the shader source,
|
||||
// which is why the storage sub-family was the one that passed while sampler, image,
|
||||
// uniform-block and atomic-counter bindings sailed past every ceiling.
|
||||
//
|
||||
// That scan is deliberately KEPT (ShaderCompileTask.cpp's MaxShaderStorageBufferBindings
|
||||
// explains why: GLSL makes an over-range binding a COMPILE-time error, and the relaxed Vulkan
|
||||
// parse leaves the scan as the only place MobileGL can raise one). So the storage arm has two
|
||||
// enforcement points and the other four have this one. What keeps them from drifting is not
|
||||
// that there is only one site but that both read the SAME numbers - ResolveResourceBindingLimits
|
||||
// is the single derivation, and neither site computes a ceiling of its own.
|
||||
void TMglGlslIoResolver::CheckDeclaredBindingRange(const glslang::TType& type, const glslang::TString& name) {
|
||||
if (m_bindingLimits == nullptr || m_bindingViolation == nullptr) return;
|
||||
if (!m_bindingViolation->empty()) return; // first violation wins; the link is already lost
|
||||
@@ -229,10 +235,17 @@ namespace MobileGL {
|
||||
// The ARRAYED-INSTANCE rule: an array of N takes bindings base .. base + N - 1, and every
|
||||
// one of them has to fit. getCumulativeArraySize() folds a multi-dimensional array into
|
||||
// the count of leaf elements, which is exactly how many consecutive bindings GL hands out.
|
||||
// An unsized or implicitly-sized array reports 0; treat it as one binding rather than
|
||||
// guess, since it cannot be the shape the rule is about.
|
||||
//
|
||||
// isSizedArray() is MANDATORY, not defensive. glslang's TArraySizes::getCumulativeSize()
|
||||
// asserts `sizes.getDimSize(d) != UnsizedArraySize` ("this only makes sense in paths that
|
||||
// have a known array size"), so calling it on a run-time-sized array - the ordinary shape
|
||||
// of a storage block's trailing member, and legal on the block instance itself - aborts
|
||||
// the process inside mapIO's collect callback in any build with assertions live. The
|
||||
// repo defines no NDEBUG of its own, so a CMake Debug build is exactly such a build; the
|
||||
// "reports 0" behaviour the previous comment relied on is only what NDEBUG happens to do.
|
||||
// An unsized array occupies one binding here, which is also what GL means by it.
|
||||
long long elementCount = 1;
|
||||
if (type.isArray()) {
|
||||
if (type.isArray() && type.isSizedArray()) {
|
||||
const int cumulative = static_cast<int>(type.getCumulativeArraySize());
|
||||
if (cumulative > 1) elementCount = cumulative;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user