mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-13 06:38:31 +09:00
[Feat] (Program): implement GL_ARB_gl_spirv - glShaderBinary, glSpecializeShader and the SPIR_V_BINARY state, feeding the module into the ordinary compile pipeline
This commit is contained in:
@@ -1681,6 +1681,120 @@ namespace MobileGL {
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// The execution model an application-supplied module's entry point must carry for
|
||||
// the shader object it was handed to. glShaderBinary attaches a module to a shader
|
||||
// of a fixed type, and ARB_gl_spirv requires the specialized entry point to match.
|
||||
SpvExecutionModel ExecutionModelForShaderType(GLenum shaderType) {
|
||||
switch (shaderType) {
|
||||
case GL_VERTEX_SHADER:
|
||||
return SpvExecutionModelVertex;
|
||||
case GL_TESS_CONTROL_SHADER:
|
||||
return SpvExecutionModelTessellationControl;
|
||||
case GL_TESS_EVALUATION_SHADER:
|
||||
return SpvExecutionModelTessellationEvaluation;
|
||||
case GL_GEOMETRY_SHADER:
|
||||
return SpvExecutionModelGeometry;
|
||||
case GL_COMPUTE_SHADER:
|
||||
return SpvExecutionModelGLCompute;
|
||||
case GL_FRAGMENT_SHADER:
|
||||
default:
|
||||
return SpvExecutionModelFragment;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Result<void> ShaderCompiler::ValidateSpirvModule(const Vector<Uint32>& spirv) {
|
||||
ResultInfo r;
|
||||
r.errc = -6;
|
||||
if (spirv.size() < 5) {
|
||||
r.log = "Error: [ARB_gl_spirv] the module is too short to be SPIR-V.";
|
||||
return std::unexpected(r);
|
||||
}
|
||||
// 0x07230203 is SPIR-V's magic number. A module in the other byte order is a
|
||||
// legal SPIR-V file but NOT one glShaderBinary accepts: ARB_gl_spirv fixes the
|
||||
// word order to the host's.
|
||||
if (spirv[0] != 0x07230203u) {
|
||||
r.log = "Error: [ARB_gl_spirv] the module does not begin with the SPIR-V magic number.";
|
||||
return std::unexpected(r);
|
||||
}
|
||||
|
||||
PrepareSpirvValidation();
|
||||
spvtools::SpirvTools tools(SPV_ENV_OPENGL_4_5);
|
||||
String diagnostics;
|
||||
tools.SetMessageConsumer([&diagnostics](spv_message_level_t, const char*, const spv_position_t&,
|
||||
const char* message) {
|
||||
if (!diagnostics.empty()) diagnostics += "\n";
|
||||
diagnostics += message ? message : "";
|
||||
});
|
||||
if (!tools.Validate(spirv.data(), spirv.size())) {
|
||||
r.log = "Error: [ARB_gl_spirv] the module failed SPIR-V validation:\n" + diagnostics;
|
||||
return std::unexpected(r);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Result<String> ShaderCompiler::SpecializeAndDecompileSpirvModule(const Vector<Uint32>& spirv,
|
||||
GLenum shaderType,
|
||||
const String& entryPoint,
|
||||
const Vector<Uint32>& constantIds,
|
||||
const Vector<Uint32>& constantValues) {
|
||||
SpvcSession session(spirv, SessionUsageBit::Transpile);
|
||||
|
||||
Uint32 unknownConstantId = 0;
|
||||
if (!session.SetSpecializationConstants(constantIds, constantValues, unknownConstantId)) {
|
||||
ResultInfo r;
|
||||
r.errc = -7;
|
||||
r.log = "Error: [ARB_gl_spirv] constant index " + std::to_string(unknownConstantId) +
|
||||
" is not a specialization constant of this module.";
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
spvc_compiler_options options;
|
||||
if (session.CreateOptions(&options) != SPVC_SUCCESS) {
|
||||
ResultInfo r;
|
||||
r.errc = -9;
|
||||
r.log = "Error: [ARB_gl_spirv] could not create SPIRV-Cross options for the module.";
|
||||
return std::unexpected(r);
|
||||
}
|
||||
// DESKTOP 4.60, not the ESSL 3.20 DecompileShader emits: this source goes back in
|
||||
// at the FRONT of the pipeline, to be parsed by glslang exactly like an
|
||||
// application's own GLSL, and every one of MobileGL's source-level passes is
|
||||
// written against the desktop dialect. The ESSL hop happens later and unchanged,
|
||||
// out of the SPIR-V this re-parse produces.
|
||||
spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 460);
|
||||
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_FALSE);
|
||||
// Vulkan semantics OFF is what makes this a GL source: descriptor sets collapse
|
||||
// onto GL binding points, push constants become a uniform block, and - the point
|
||||
// of the specialization pass above - every specialization constant is folded in
|
||||
// as a literal instead of re-emitted as layout(constant_id = N).
|
||||
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE);
|
||||
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_SEPARATE_SHADER_OBJECTS, SPVC_TRUE);
|
||||
session.SetOptions(options);
|
||||
|
||||
const char* emitted = nullptr;
|
||||
session.Compile(&emitted);
|
||||
if (!emitted) {
|
||||
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);
|
||||
}
|
||||
|
||||
Result<String> ShaderCompiler::DecompileShader(SpvcSession& session) {
|
||||
spvc_compiler_options options;
|
||||
session.CreateOptions(&options);
|
||||
|
||||
@@ -405,6 +405,45 @@ namespace MobileGL {
|
||||
bool enableSpirvValidation = false);
|
||||
static Result<String> DecompileShader(SpvcSession& session);
|
||||
|
||||
// ---- GL_ARB_gl_spirv ----
|
||||
// Turn an APPLICATION-supplied SPIR-V module into the desktop GLSL the ordinary
|
||||
// compile pipeline consumes.
|
||||
//
|
||||
// Why a round trip rather than handing the module straight to the backends. SPIR-V
|
||||
// is not where MobileGL's pipeline STARTS: a program's whole GL-visible surface -
|
||||
// every glGetActiveUniform, every uniform location, every block index, the
|
||||
// transform-feedback layout, the default-block UBO routing - is reflected out of
|
||||
// glslang's TProgram at link (ProgramLinkTask::SnapshotGlslangReflection), and
|
||||
// glslang can only build one from a GLSL parse. Injecting the module at
|
||||
// ProgramSpirvTask instead would skip the link entirely and leave every one of
|
||||
// those queries answering nothing. Decompiling puts the application's module at
|
||||
// the head of the SAME pipeline, so reflection, the relaxed default-block
|
||||
// lowering, both backends and every memo tier work on it unchanged.
|
||||
//
|
||||
// What it costs, stated plainly: names. A module stripped of OpName (which
|
||||
// ARB_gl_spirv permits, and the conformance suite deliberately does) comes back
|
||||
// with SPIRV-Cross's generated identifiers rather than with none, so the
|
||||
// *_MAX_LENGTH queries answer those instead of 1.
|
||||
//
|
||||
// `entryPoint` selects among several OpEntryPoint of this stage's execution
|
||||
// model; an empty string means "whichever one is there". The specialization
|
||||
// constants glSpecializeShader supplied are applied in the same pass - SPIRV-Cross
|
||||
// folds each into the emitted source as a literal once Vulkan semantics are off,
|
||||
// which is exactly what "specialize, then compile" means for a GLSL consumer.
|
||||
//
|
||||
// `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);
|
||||
|
||||
// spirv-val over an application-supplied module, against the environment MobileGL
|
||||
// parses and emits under. glShaderBinary is where a malformed module has to be
|
||||
// caught: past it the words reach SPIRV-Cross, which is not a validator.
|
||||
static Result<void> ValidateSpirvModule(const Vector<Uint32>& spirv);
|
||||
|
||||
// Parses one trivial shader in each configuration the production path can
|
||||
// reach, on the calling thread, so the built-in symbol tables those
|
||||
// configurations need are already cached before any worker asks for one.
|
||||
|
||||
@@ -496,6 +496,51 @@ namespace MobileGL {
|
||||
SPVC_CHK_RETURN
|
||||
}
|
||||
|
||||
spvc_result SpvcSession::SetEntryPoint(const char* name, SpvExecutionModel model) {
|
||||
if (compiler == nullptr || name == nullptr || *name == '\0') return SPVC_SUCCESS;
|
||||
return spvc_compiler_set_entry_point(compiler, name, model);
|
||||
}
|
||||
|
||||
Bool SpvcSession::SetSpecializationConstants(const Vector<Uint32>& constantIds,
|
||||
const Vector<Uint32>& constantValues,
|
||||
Uint32& outUnknownConstantId) {
|
||||
if (constantIds.empty()) return true;
|
||||
if (compiler == nullptr) return false;
|
||||
|
||||
const spvc_specialization_constant* declared = nullptr;
|
||||
SizeT declaredCount = 0;
|
||||
if (spvc_compiler_get_specialization_constants(compiler, &declared, &declaredCount) != SPVC_SUCCESS) {
|
||||
outUnknownConstantId = constantIds.front();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (SizeT i = 0; i < constantIds.size(); ++i) {
|
||||
const Uint32 wantedId = constantIds[i];
|
||||
spvc_constant handle = nullptr;
|
||||
for (SizeT j = 0; j < declaredCount; ++j) {
|
||||
if (declared[j].constant_id != wantedId) continue;
|
||||
handle = spvc_compiler_get_constant_handle(compiler, declared[j].id);
|
||||
break;
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
// ARB_gl_spirv: "INVALID_VALUE is generated if any value in pConstantIndex
|
||||
// refers to a specialization constant that does not exist in the shader
|
||||
// module". Reported rather than skipped - a silently ignored id would let
|
||||
// the shader specialize to something the application never asked for.
|
||||
outUnknownConstantId = wantedId;
|
||||
return false;
|
||||
}
|
||||
// The GL side hands over a flat GLuint per constant and ARB_gl_spirv says it
|
||||
// is "interpreted according to the type of the specialization constant", so
|
||||
// the 32-bit PATTERN is what has to be stored, not a converted number.
|
||||
// spvc_constant_set_scalar_u32 writes exactly that pattern into the constant's
|
||||
// scalar union, which SPIRV-Cross then reads back as whatever the constant's
|
||||
// declared type is - the reinterpretation the extension asks for, for free.
|
||||
spvc_constant_set_scalar_u32(handle, 0, 0, constantValues[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
spvc_result SpvcSession::Compile(const char** result) {
|
||||
if (!(usage & SessionUsageBit::Transpile)) return SPVC_ERROR_INVALID_ARGUMENT;
|
||||
SPVC_CHK_INIT
|
||||
|
||||
@@ -160,6 +160,21 @@ namespace MobileGL {
|
||||
// A block carrying only ONE of the two is left exactly as it is: those really do
|
||||
// constrain the accesses the shader makes, and the driver is entitled to know.
|
||||
spvc_result RelaxReadWriteExclusiveStorageBuffers();
|
||||
// ---- GL_ARB_gl_spirv: an APPLICATION-supplied module, not one MobileGL emitted ----
|
||||
// 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.
|
||||
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
|
||||
// constant's own scalar type, exactly as ARB_gl_spirv specifies ("the value is
|
||||
// interpreted as the type of the specialization constant"). Returns false and
|
||||
// sets `outUnknownConstantId` when an id the caller passed is not a
|
||||
// specialization constant of this module, which the extension makes
|
||||
// GL_INVALID_VALUE.
|
||||
Bool SetSpecializationConstants(const Vector<Uint32>& constantIds,
|
||||
const Vector<Uint32>& constantValues,
|
||||
Uint32& outUnknownConstantId);
|
||||
spvc_result Compile(const char** result);
|
||||
const SpvcMetadata& GetMetadata() const;
|
||||
const char* GetLastErrorString() const;
|
||||
|
||||
Reference in New Issue
Block a user