mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
- listed locations are masked to 8/16 bits or sign-extended from them at the top of main - each input becomes a Private copy seeded once; access chains are retyped, arrays go per element
705 lines
58 KiB
C++
705 lines
58 KiB
C++
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h
|
|
// 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
|
|
|
|
#pragma once
|
|
#include <Includes.h>
|
|
#include "SpvcSession.h"
|
|
#include "glslang/TVarEntryInfo.h"
|
|
#include "glslang/TMglGlslIoResolver.h"
|
|
#include "NarrowIntegerVertexInputs.h"
|
|
|
|
#include <map>
|
|
#include <set>
|
|
|
|
namespace MobileGL {
|
|
namespace MG_Util {
|
|
namespace ShaderTranspiler {
|
|
// A GLSL float literal for a tessellation level, for the pass-through tessellation
|
|
// control stage both backends synthesize when a program has an evaluation stage and no
|
|
// control stage. Shared so the two generators cannot disagree about what a level means.
|
|
//
|
|
// GL 4.6 core 11.2.2 discards a patch only when a relevant OUTER level is <= 0; every
|
|
// other value is CLAMPED into [1, MAX_TESS_GEN_LEVEL]. So "draw nothing" is reserved
|
|
// for the values that really mean it, and everything else has to survive the trip
|
|
// through text: a shortest-round-trip spelling, because a fixed-decimal one flushes
|
|
// small positive levels to zero, and always with a '.' or an exponent, because a bare
|
|
// digit sequence is an INT literal and `gl_TessLevelOuter[0] = 1;` does not compile.
|
|
String TessellationLevelLiteral(Float value);
|
|
|
|
class ShaderCompiler {
|
|
public:
|
|
static Result<SharedPtr<glslang::TShader>> CompileShader(const ShaderAttrib& attrib);
|
|
static Result<SharedPtr<glslang::TProgram>> LinkProgram(const ProgramAttrib& attrib);
|
|
static Result<Vector<Vector<unsigned>>> GetSpirvBinaryFromProgram(const ProgramBinaryAttrib& attrib);
|
|
// `nativeFloat64` is the caller's FINAL verdict, not a capability read: true means
|
|
// the two fp64 passes at the tail of the chain are skipped and real doubles reach
|
|
// the driver. False - which is DirectGLES always, every mobile device, and the
|
|
// no-backend default - runs the chain exactly as it always has. It is the ONE
|
|
// argument of this function that changes the output bytes, which is why it is
|
|
// also a field of the L1 memo's key.
|
|
//
|
|
// Production sets it in ProgramSpirvTask::GenerateSpirv, which takes the verdict
|
|
// for the WHOLE program (CompileEnv::ConsumesFloat64Natively() minus the
|
|
// 64-bit-vertex-input exception) before touching any module. Do not re-derive it
|
|
// per module: the global UBO is one buffer every stage reads.
|
|
static bool SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
|
bool validateOutput = true, bool enableSpirvValidation = false,
|
|
bool nativeFloat64 = false);
|
|
// Demotes DrawIndex/BaseInstance/BaseVertex builtins to plain Private globals
|
|
// (mg_DrawID/mg_BaseInstance/mg_BaseVertex) so SPIRV-Cross can emit ESSL.
|
|
// Only for backends without native draw-parameter support (DirectGLES).
|
|
static bool LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Demotes the gl_ViewportIndex OUTPUT builtin to a plain Private global named
|
|
// mg_ViewportIndex, so SPIRV-Cross emits an ordinary declaration instead of a bare
|
|
// gl_ViewportIndex that ESSL has no core spelling for. Multi-viewport routing is
|
|
// lost (everything lands in viewport 0) but the stage compiles and the program
|
|
// runs, instead of every draw made with it becoming a silent no-op. Only for the
|
|
// DirectGLES transpile path on a driver WITHOUT GL_OES_viewport_array; gl_Layer is
|
|
// deliberately left alone, being core in ESSL 3.20 geometry shaders.
|
|
static bool LowerViewportIndexForEssl(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Moves a geometry stage's emits behind its memory writes (DeferGeometryEmitsPass), for
|
|
// an ES driver that drops storage writes made after EmitVertex(). `outDeferred` says
|
|
// whether the module was rewritten; a module the pass declines comes back unchanged.
|
|
static bool DeferGeometryEmitsForEssl(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
|
bool& outDeferred, bool enableSpirvValidation = false);
|
|
// Whether the module declares an output decorated BuiltIn ViewportIndex, i.e.
|
|
// whether the pass above has anything to do. The gate that keeps every other
|
|
// stage off an optimizer round trip it does not need.
|
|
static bool DeclaresViewportIndexBuiltin(const Vector<Uint32>& binary);
|
|
// Clamps the Sample image-operand of every multisample fetch to the sample count
|
|
// the BACKEND can really deliver for that image's category, which on Adreno and
|
|
// Mali is 1 for integer formats while the frontend advertises the GL-mandated
|
|
// floor of 4. Without it a `texelFetch(usampler2DMS, coord, 3)` reads past the
|
|
// end of a one-sample allocation. Pass the backend-real per-category ceilings and
|
|
// the advertised maximum (GL_Getter's GetAdvertisedMaxSamples); a category that
|
|
// already reaches the advertised value is left alone. DirectGLES transpile path
|
|
// only. See ClampMultisampleFetchPass, which also says what
|
|
// deferDirectUnsignedFetches hands over to the caller's runtime clamp.
|
|
static bool ClampMultisampleFetchesForEssl(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary, Int32 maxColorSamples,
|
|
Int32 maxIntegerSamples, Int32 maxDepthSamples,
|
|
Int32 advertisedMaxSamples,
|
|
bool enableSpirvValidation = false,
|
|
bool deferDirectUnsignedFetches = false);
|
|
// Whether the module declares any multisampled image type, i.e. whether the pass
|
|
// above has anything to do. The gate that keeps every other stage off an
|
|
// optimizer round trip it does not need.
|
|
static bool DeclaresMultisampledImage(const Vector<Uint32>& binary);
|
|
// Both gate questions above answered from ONE parse. Every armed gate costs a
|
|
// BuildModule per shader stage, and on a driver where both are armed (Mali: no
|
|
// GL_OES_viewport_array AND integer multisample squeezed to 1) the separate
|
|
// probes made compile-heavy workloads measurably slower - ReservedNames-class
|
|
// CTS cases paid ~10%. Callers with more than one armed gate use this instead.
|
|
// The image-format widening deliberately does NOT ride this probe, even though it
|
|
// is a module question of exactly the same shape. It is armed on every driver, so
|
|
// a gate answered from the module would put a BuildModule on every stage of every
|
|
// program - and the frontend's uniform reflection can answer it for free
|
|
// (PrgramImpl::ImageFormatBakeInputs::declaresWidenableImageFormat).
|
|
struct SpirvGateFeatures {
|
|
Bool WritesViewportIndexOutput = false;
|
|
Bool DeclaresMultisampledImage = false;
|
|
};
|
|
static SpirvGateFeatures ProbeSpirvGateFeatures(const Vector<Uint32>& binary);
|
|
// Replaces an ARRAY vertex input with one input per element at consecutive
|
|
// locations, seeding a Private copy of the array so indexed reads still work.
|
|
// GLSL ES has no array vertex inputs and SPIRV-Cross refuses the whole module
|
|
// rather than emulating them, so without this the stage never reaches the
|
|
// driver. Only for the DirectGLES transpile path.
|
|
static bool SplitArrayVertexInputsForEssl(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Replaces the named interface BLOCKS with one variable per member, named
|
|
// "<Block>_<member>", shadowing the block itself so the body is untouched. The
|
|
// Adreno ES driver silently captures NOTHING for a transform-feedback varying
|
|
// named as a block member, so a capture list that names one has to be respelled
|
|
// - and the declaration with it. `flattenedBlockNames` reports which blocks
|
|
// this stage actually rewrote, which is what the capture list must follow.
|
|
// Only for the DirectGLES transpile path.
|
|
static bool FlattenXfbInterfaceBlocksForEssl(const Vector<Uint32>& inputBinary,
|
|
const std::set<String>& blockNames,
|
|
std::set<String>& flattenedBlockNames,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// The capture request "StageData.attrib[0]" as the pass above renamed it,
|
|
// "StageData_attrib[0]", or false when it does not name a member of a block
|
|
// that was flattened.
|
|
static bool RewriteXfbCaptureNameForFlattenedBlock(const String& captureName,
|
|
const std::set<String>& flattenedBlockNames,
|
|
String& outName);
|
|
// Adds to `collidingBlockNames` every inter-stage interface block this stage
|
|
// declares in BOTH directions at once (`in FOO {...}; out FOO {...}`, which
|
|
// desktop GLSL allows because its input and output block namespaces are
|
|
// separate), and to `declaredNames` every name the module spells. The gate for
|
|
// UniquifyIoBlockNamesForEssl below, and the source of the name set a
|
|
// replacement has to avoid. Reads the module; never rewrites it.
|
|
static void ProbeIoBlockNamesForEssl(const Vector<Uint32>& binary,
|
|
std::set<String>& collidingBlockNames,
|
|
std::set<String>& declaredNames);
|
|
// Renames inter-stage interface BLOCK types so the collision the probe above
|
|
// found gets one spelling per producing stage. `inputBlockRenames` applies to
|
|
// blocks this stage consumes and `outputBlockRenames` to blocks it produces,
|
|
// both planned program-wide by the caller so a producer and its consumer keep
|
|
// matching; `renamedBlockNames` reports the original names this stage actually
|
|
// rewrote. SPIRV-Cross re-emits two same-named blocks verbatim and the Mali ES
|
|
// driver then loses the output block's payload. Only for the DirectGLES
|
|
// transpile path. See UniquifyIoBlockNamesPass.
|
|
static bool UniquifyIoBlockNamesForEssl(const Vector<Uint32>& inputBinary,
|
|
const std::map<String, String>& inputBlockRenames,
|
|
const std::map<String, String>& outputBlockRenames,
|
|
std::set<String>& renamedBlockNames,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Drops the Location (and Component) decoration from inter-stage interface
|
|
// BLOCK variables, so SPIRV-Cross emits them unqualified and ES matches them
|
|
// by block name plus member sequence. `stripInputBlocks` covers the blocks
|
|
// this stage consumes and `stripOutputBlocks` the ones it produces - armed
|
|
// separately because an interface whose other end is in a DIFFERENT program
|
|
// must keep the location that matches it there. `strippedAny` reports whether
|
|
// this stage actually had one. The Mali ES driver loses the payload of a
|
|
// located block across any tessellation or geometry boundary; only for the
|
|
// DirectGLES transpile path, and only when the driver POST says so. See
|
|
// StripIoBlockLocationsPass.
|
|
static bool StripIoBlockLocationsForEssl(const Vector<Uint32>& inputBinary, bool stripInputBlocks,
|
|
bool stripOutputBlocks, bool& strippedAny,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Drops RelaxedPrecision member decorations from uniform-block structs so
|
|
// SPIRV-Cross prints the same (highp) member precision in every stage; ES
|
|
// drivers reject cross-stage uniform blocks whose member precisions differ.
|
|
// Only for the DirectGLES transpile path.
|
|
static bool StripUboMemberRelaxedPrecisionForEssl(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Removes NoPerspective decorations so SPIRV-Cross emits plain (smooth) ESSL varyings.
|
|
// DirectGLES fallback only, for devices lacking GL_NV_shader_noperspective_interpolation
|
|
// (SPIRV-Cross would otherwise require that extension and the driver would reject it).
|
|
static bool StripNoPerspectiveForEssl(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Emulates noperspective (screen-linear) interpolation via gl_Position.w / gl_FragCoord.w
|
|
// so no NV extension is needed; strips what it cannot emulate. DirectGLES fallback for
|
|
// devices lacking GL_NV_shader_noperspective_interpolation. See EmulateNoPerspectivePass.
|
|
static bool EmulateNoPerspectiveForEssl(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Makes every index into a fragment-output array a constant integral
|
|
// expression, which is what GLSL ES requires and SPIR-V does not. Runs the
|
|
// stock folding chain first (loop unrolling folds the loop-derived indices
|
|
// real shaders use), and lowers whatever is left - a genuinely dynamic index -
|
|
// to a switch over the array's range. DirectGLES transpile path only: the
|
|
// original module is legal for Vulkan, and no other stage is constrained this
|
|
// way. Copies the input through untouched when no fragment output is indexed
|
|
// dynamically, which is every shader but a handful.
|
|
// See LegalizeFragmentOutputIndexPass.
|
|
static bool LegalizeFragmentOutputIndexingForEssl(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Makes every index into an ARRAY OF SHADER STORAGE BLOCKS or an ARRAY OF IMAGE
|
|
// UNIFORMS a constant integral expression. Desktop GL allows any
|
|
// dynamically-uniform index in either; ES keeps the ES 3.1
|
|
// constant-expression rule for both and the drivers refuse the whole stage
|
|
// ("indexing into an SSBO array using a non-constant expression is not
|
|
// permitted" on Qualcomm, "image arrays indexed with non-constant expressions
|
|
// are forbidden in GLSL ES" on llvmpipe), which loses the program while the
|
|
// frontend still reports GL_LINK_STATUS = TRUE. Same two halves as the
|
|
// fragment-output legalization: fold the loop-derived indices, then lower
|
|
// whatever is genuinely dynamic to a switch over the array's range. SAMPLER
|
|
// arrays are out of scope - ESSL 3.20 4.1.7 permits them a dynamically-uniform
|
|
// index. DirectGLES transpile path only - Vulkan has no such restriction and
|
|
// must keep seeing one descriptor array. Copies the input through untouched
|
|
// when no such array is indexed dynamically, which is every shader but a
|
|
// handful. See LegalizeResourceArrayIndexPass.
|
|
static bool LegalizeResourceArrayIndexingForEssl(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Collapses each synthesized gl_AtomicCounterBlock_<N> into one uint array at
|
|
// offset 0, re-indexing every counter access to the element that used to sit at
|
|
// its byte offset. glslang preserves the application's layout(offset = N) as the
|
|
// member's Offset decoration, no std140/std430 layout can express a first member
|
|
// at a non-zero offset, and GLSL ES has no member layout(offset=) - so SPIRV-Cross
|
|
// throws and takes the whole stage with it. DirectGLES transpile path only.
|
|
// Copies the input through untouched when every counter block is already packed
|
|
// naturally, which is every shader that omits the offset qualifier. See
|
|
// FlattenAtomicCounterBlockPass.
|
|
static bool FlattenAtomicCounterBlockOffsetsForEssl(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Rebases loads of the InstanceIndex builtin to (InstanceIndex - BaseInstance) so
|
|
// shaders see GL's zero-based gl_InstanceID. Vertex shaders only; DirectVulkan
|
|
// backend only (glslang's relaxed mode aliases gl_InstanceID to gl_InstanceIndex,
|
|
// which wrongly includes baseInstance).
|
|
// GL_TEXTURE_RECTANGLE emulated on a plain 2D texture, for every backend:
|
|
// divides the coordinate of each normalized-coordinate lookup by the texture
|
|
// size and rewrites the image type to 2D. See NormalizeRectCoordinatesPass for
|
|
// what it declines and why.
|
|
static bool LowerRectImages(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// GL_TEXTURE_1D_ARRAY storage images rewritten to the 2D-array shape the texture
|
|
// is actually stored in on ES, with the layer moved from the coordinate's second
|
|
// component to its third - and, when the module performs an image ATOMIC on one,
|
|
// the non-arrayed GL_TEXTURE_1D storage image to the 2D shape with its coordinate
|
|
// widened to (u, 0), which is the one 1D shape SPIRV-Cross does not widen itself.
|
|
// DirectGLES transpile path only - Vulkan binds a real VK_IMAGE_VIEW_TYPE_1D(_ARRAY)
|
|
// and must see the module unchanged. Copies the input through untouched when the
|
|
// module declares no such image, which is every shader but a handful. See
|
|
// Lower1DArrayImagesPass for what it declines and why.
|
|
static bool Lower1DArrayImagesForEssl(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// The SAMPLED-image counterpart. SPIRV-Cross widens a 1D sampler's COORDINATE for
|
|
// ES and prints the OFFSET and GRADIENT operands with their original 1D arity, so
|
|
// textureOffset / textureLodOffset / texelFetchOffset / textureGrad on a
|
|
// sampler1D(Array) come out with no ESSL overload ("no matching overloaded
|
|
// function found") and the stage is lost. Rewrites the type to 2D and widens
|
|
// coordinate, offset and gradients together. DirectGLES transpile path only -
|
|
// Vulkan has 1D images natively. Copies the input through untouched unless the
|
|
// module actually carries such an operand on a 1D sampler, so a shader that only
|
|
// samples or fetches keeps SPIRV-Cross's own correct emission. See
|
|
// Lower1DSampledImagesPass for what it declines and why.
|
|
static bool Lower1DSampledImagesForEssl(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Gives each format-less storage image the format bound to its image unit, so
|
|
// the emitted ESSL can carry the format layout qualifier GLSL ES requires of
|
|
// every image and desktop GLSL lets a writeonly declaration omit. `glFormatByName`
|
|
// maps uniform name to the glBindImageTexture format of the unit it addresses.
|
|
// DirectGLES transpile path only - Vulkan takes an Unknown-format storage image
|
|
// natively. See BakeImageFormatsPass for what it declines and why.
|
|
static bool BakeImageFormatsForEssl(const Vector<Uint32>& inputBinary,
|
|
const UnorderedMap<String, Uint>& glFormatByName,
|
|
Vector<uint32_t>& outputBinary, bool enableSpirvValidation = false);
|
|
// Whether the module declares a storage image with no format qualifier at all,
|
|
// i.e. whether BakeImageFormatsForEssl could change anything. One module parse,
|
|
// so the ~every shader that declares none pays no optimizer run.
|
|
static bool DeclaresFormatlessStorageImage(const Vector<Uint32>& binary);
|
|
// Whether the GL internal format's image-format spelling is one GLSL ES has in
|
|
// core. False both for a format ES only reaches through GL_NV_image_formats and
|
|
// for one with no image-format spelling at all, so a caller that has to decide
|
|
// whether to emit the extension directive can ask this one question.
|
|
static bool GLInternalFormatIsCoreEsslImageFormat(Uint glInternalFormat);
|
|
// The ESSL layout-qualifier spelling of a GL internal format ("r8ui", "rgba32f"),
|
|
// empty when the format has no image-format spelling at all.
|
|
static String EsslImageFormatSpelling(Uint glInternalFormat);
|
|
// Whether SPIRV-Cross will print that format when it targets ESSL. It throws on
|
|
// the ones it calls desktop-only - taking the whole stage with it - so a caller
|
|
// must not ask BakeImageFormatsForEssl for those, and completes them in the
|
|
// emitted text instead.
|
|
static bool SpirvCrossCanPrintEsslImageFormat(Uint glInternalFormat);
|
|
// Re-declares every storage image whose DECLARED format GLSL ES cannot spell in
|
|
// the core format that carries it exactly, and masks each access back to the
|
|
// channels the original format has. The 26 formats outside the ES core set have no
|
|
// legal ESSL spelling on any tested driver (none exposes GL_NV_image_formats), and
|
|
// a format-less declaration is rejected too, so the stage is otherwise lost
|
|
// whatever this backend emits. DirectGLES transpile path only - Vulkan takes the
|
|
// declared format natively. See WidenImageFormatsPass for the table, for the nine
|
|
// formats it deliberately does NOT widen, and for why the texture storage and the
|
|
// glBindImageTexture argument have to move with it.
|
|
// `onlyFormatsSpirvCrossRefusesToPrint` narrows it to the formats that have no
|
|
// ESSL route even WITH GL_NV_image_formats, because SPIRV-Cross throws for them
|
|
// rather than printing a token - which is the whole set a driver that advertises
|
|
// the extension still needs. See WidenImageFormatsPass.
|
|
static bool WidenImageFormatsForEssl(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
|
bool onlyFormatsSpirvCrossRefusesToPrint = false,
|
|
bool enableSpirvValidation = false);
|
|
// Whether the module declares a storage image WidenImageFormatsForEssl would
|
|
// widen, under the same mode the run would use. Costs its own module parse, so
|
|
// the transpile path does NOT gate on this - it answers the question from the
|
|
// frontend's uniform reflection instead, for the reason on SpirvGateFeatures.
|
|
// Here for tests and for callers that already hold nothing but the binary.
|
|
static bool DeclaresWidenableImageFormat(const Vector<Uint32>& binary,
|
|
bool onlyFormatsSpirvCrossRefusesToPrint = false);
|
|
// The core-ESSL GL internal format that carries `glInternalFormat` exactly, or 0
|
|
// when it needs no widening or cannot be widened exactly. The single source of
|
|
// truth for all three layers of the emulation: this one answers the shader, and
|
|
// DirectGLES asks it again for the texture storage and the image bind, so the two
|
|
// sides cannot drift.
|
|
static Uint WidenedCoreEsslImageFormat(Uint glInternalFormat);
|
|
// Channels a GL image internal format really has (1-4), 0 when it is not one of
|
|
// the forty image formats.
|
|
static Uint ImageFormatChannelCount(Uint glInternalFormat);
|
|
// Whether the carrier holds the format's channels as the INTEGER CODES of a
|
|
// normalized value, and the largest code each channel can hold. See
|
|
// WidenImageFormatsPass::NormalizedImageCarrierCodes - DirectGLES needs it for
|
|
// both halves of the transfer, which no longer share the frontend format's
|
|
// component class with the ES storage.
|
|
static bool NormalizedImageCarrierCodes(Uint glInternalFormat, Uint32 (&outChannelMax)[4],
|
|
bool& outSignedNormalized);
|
|
// The single-channel core format a non-core BUFFER image is SPLIT into, or 0. See
|
|
// WidenImageFormatsPass::SplitCoreEsslBufferImageFormat - DirectGLES asks it for
|
|
// glTexBuffer's internal format and for glBindImageTexture's.
|
|
static Uint SplitCoreEsslBufferImageFormat(Uint glInternalFormat);
|
|
static bool RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Builds the non-indexed-draw variant of a vertex shader: every gl_BaseVertex
|
|
// read becomes zero, which is what GL defines for a command carrying no
|
|
// baseVertex parameter while Vulkan's builtin would report firstVertex.
|
|
// See ZeroBaseVertexPass.
|
|
static bool ZeroBaseVertexForVulkan(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Builds the vertex-shader variant that widens narrow integer attribute data the way
|
|
// GL does: the inputs at the listed locations are zero- or sign-extended from 8/16
|
|
// bits right after they are read. A module with no listed integer input comes back
|
|
// unchanged. See ExtendNarrowIntegerVertexInputsPass.
|
|
static bool ExtendNarrowIntegerVertexInputsForVulkan(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
const NarrowIntegerVertexInputs& inputs,
|
|
bool enableSpirvValidation = false);
|
|
// Drops `layout(pixel_center_integer)` from a fragment module, which the Vulkan
|
|
// environment forbids, and shifts every gl_FragCoord.xy read by -0.5 to keep its
|
|
// values. See LowerPixelCenterIntegerPass.
|
|
static bool LowerPixelCenterIntegerForVulkan(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Builds the variant of a pre-rasterization module that a non-layered framebuffer
|
|
// draws with: every gl_Layer store writes 0, since GL ignores gl_Layer there. A
|
|
// module that writes no Layer comes back unchanged. See ZeroLayerOutputPass.
|
|
static bool ZeroLayerOutputForVulkan(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Builds the variant of a fragment module that a draw without multisample fragment
|
|
// operations (single-sample framebuffer, or MULTISAMPLE disabled) uses: every
|
|
// gl_SampleMask store writes all-ones, since GL ignores the mask there. A module
|
|
// that writes no SampleMask comes back unchanged. See FullCoverageSampleMaskPass.
|
|
static bool FullCoverageSampleMaskForVulkan(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Replaces compute gl_NumSubgroups loads with ceil(workgroup invocations /
|
|
// gl_SubgroupSize). DirectVulkan only; this repairs drivers whose builtin
|
|
// disagrees with the subgroup IDs the same dispatch emits (Adreno reports 1
|
|
// while emitting IDs 0..7). The ceil() partition is only spec-guaranteed
|
|
// under VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT, which
|
|
// the caller requests whenever it is legal for the workgroup shape; see
|
|
// DeriveNumSubgroupsPass.
|
|
static bool DeriveNumSubgroupsForVulkan(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Lowers every GL_KHR_shader_subgroup construct in a compute module onto a
|
|
// 32-lane virtual subgroup built from workgroup-shared memory - what either
|
|
// backend runs on a driver with NO native subgroup support (or under
|
|
// MOBILEGL_SUBGROUP_EMULATION=ForceOn); the module is generic SPIR-V, consumed by
|
|
// DirectVulkan and transpiled to ESSL by DirectGLES alike.
|
|
// maxWorkgroupScratchBytes bounds the shared
|
|
// scratch the lowering may add (pass the device's
|
|
// maxComputeSharedMemorySize; 0 falls back to the 16384-byte Vulkan
|
|
// minimum). atomicScratchReads reads the scratch back through atomic loads,
|
|
// for a driver whose plain shared-memory loads go stale across barrier()
|
|
// (SelfTest::SharedMemoryLoadsGoStale). See EmulateSubgroupsPass.
|
|
static bool EmulateSubgroupsForVulkan(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
|
Uint32 maxWorkgroupScratchBytes,
|
|
bool enableSpirvValidation = false,
|
|
bool atomicScratchReads = false);
|
|
// Grows iterationRP's under-declared gl_SubgroupID-indexed scratch to the
|
|
// subgroup count the device actually partitions into, fingerprint-gated to
|
|
// that pack's reduction idiom; every other module - and every device whose
|
|
// width the pack already assumed - passes through byte-identical.
|
|
// maxWorkgroupScratchBytes bounds the growth (pass the device's
|
|
// maxComputeSharedMemorySize; 0 falls back to the 16384-byte Vulkan
|
|
// minimum). See FixIterationRPSubgroupScratchPass.
|
|
static bool FixIterationRPSubgroupScratchForVulkan(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
Uint32 nativeSubgroupSize,
|
|
Uint32 maxWorkgroupScratchBytes,
|
|
bool enableSpirvValidation = false);
|
|
// Inserts the missing workgroup rendezvous between Program 203's two
|
|
// prefixSumCache reductions. Fingerprint-gated to the iterationRP shape;
|
|
// unrelated and already-repaired modules pass through byte-identical.
|
|
static bool FixIterationRPBarrierForVulkan(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Re-declares 64-bit float vertex inputs as their 32-bit unsigned word pair
|
|
// (double -> uvec2, dvec2 -> uvec4) and bitcasts them back to double at entry, so no
|
|
// VK_FORMAT_R64*_SFLOAT is needed - lavapipe advertises none of them for vertex
|
|
// buffers. Vertex stage, DirectVulkan only; pairs with the Float64 case in
|
|
// VertexInputStateFactory::ToVkVertexFormat.
|
|
static bool PackDoubleVertexInputsForVulkan(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Adds the Invariant decoration to every Position builtin output. GL apps
|
|
// routinely rely on cross-program position invariance for multi-pass
|
|
// equality depth tests (e.g. GEQUAL re-draws of the same geometry), and
|
|
// mobile drivers that optimize per-pipeline break that without the
|
|
// decoration. DirectVulkan only.
|
|
static bool DecoratePositionInvariantForVulkan(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Replaces the declared format of float storage images with Unknown and adds the
|
|
// matching SPIR-V capabilities. DirectVulkan uses this only when both Vulkan
|
|
// shaderStorageImage*WithoutFormat features are enabled, allowing the
|
|
// glBindImageTexture format to select the descriptor view at runtime. Integer
|
|
// storage images deliberately keep their declared format for GL-compatible bit
|
|
// reinterpretation paths (for example, R32F storage accessed as r32ui).
|
|
static bool UseUnformattedFloatStorageImagesForVulkan(const Vector<Uint32>& inputBinary,
|
|
Vector<uint32_t>& outputBinary,
|
|
bool enableSpirvValidation = false);
|
|
// Rewrites every 64-bit float in the module to a 32-bit one, preserving every
|
|
// block offset and stride exactly (see DemoteFloat64Pass). Already part of
|
|
// SanitizeAndOptimizeBinary, which is where production reaches it; exposed
|
|
// separately so a test can drive the demotion on its own.
|
|
static bool DemoteFloat64ToFloat32(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary,
|
|
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.
|
|
// 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
|
|
// caught: past it the words reach SPIRV-Cross, which is not a validator.
|
|
static Result<void> ValidateSpirvModule(const Vector<Uint32>& spirv);
|
|
|
|
// Parses trivial #version 460 and #version 330 core shaders on the calling
|
|
// thread, so those two built-in symbol tables are already cached before any
|
|
// worker asks for one. Every other declared version builds its own on first use.
|
|
//
|
|
// Why it matters: glslang builds a built-in TSymbolTable per distinct
|
|
// (version, spvVersion, profile, source) combination, and does it under a
|
|
// process-wide lock held for the whole build. Without this, the first
|
|
// parallel compiles of a shaderpack load all pile up behind that lock and
|
|
// show no speedup at all - which is easy to misread as asynchronous
|
|
// compilation not working. Call once, from the GL thread, right after
|
|
// glslang::InitializeProcess(). Idempotent and cheap on repeat.
|
|
//
|
|
// Only worth its cost when compiles can actually run in parallel, so the GL
|
|
// frontend calls it only when asynchronous compilation is enabled: a
|
|
// synchronous build would pay for three throwaway parses at every
|
|
// eglInitialize to prewarm tables the first real compile builds anyway.
|
|
static void PrewarmBuiltins();
|
|
// Clears the "already prewarmed" latch. MUST be called wherever
|
|
// glslang::FinalizeProcess() is, and for the same reason: finalizing deletes
|
|
// the cached built-in tables the latch is asserting the existence of. Without
|
|
// it, the second eglInitialize of a process comes back up unwarmed and with
|
|
// no way left to warm it.
|
|
static void ResetPrewarmLatch();
|
|
|
|
// Validation is an explicit immutable option of each compiler operation. The
|
|
// program-link task snapshots MOBILEGL_ENABLE_SPIRV_VALIDATION before it can run
|
|
// on a worker; standalone callers pass true directly. A failure logs the VUID and
|
|
// bumps the latch below WITHOUT changing a wrapper's return value, so validating
|
|
// and shipping configurations preserve identical rendering control flow.
|
|
|
|
// Makes validator table lifetime safe before an external final-module validator
|
|
// runs. This has no configuration state; callers invoke it only for an enabled
|
|
// task-local validation option.
|
|
static void PrepareSpirvValidation();
|
|
|
|
// The test-lane enforcement signal: total validation failures observed this
|
|
// process. Tests snapshot it, run the operation under scrutiny, and assert
|
|
// on the delta. NoteSpirvValidationFailure is for validation done outside
|
|
// this file (ProgramFactory::ValidateTransformedSpirv); it returns the new
|
|
// total.
|
|
static Uint64 SpirvValidationFailureCount();
|
|
static Uint64 NoteSpirvValidationFailure();
|
|
|
|
// True when the module declares any buffer-backed image type - an OpTypeImage with
|
|
// Dim = Buffer. That is the samplerBuffer / isamplerBuffer / usamplerBuffer
|
|
// family and equally the imageBuffer / iimageBuffer / uimageBuffer one: SPIRV-Cross
|
|
// requires GL_EXT_texture_buffer for both, from the same branch, so both are
|
|
// uncompilable on a driver without buffer textures and both belong here.
|
|
// 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);
|
|
// Does this module carry the Xfb execution mode - i.e. would a
|
|
// vkCmdBeginTransformFeedbackEXT against a pipeline whose last pre-rasterization
|
|
// stage is this module satisfy VUID-vkCmdBeginTransformFeedbackEXT-None-04128?
|
|
// Asked of the FINAL bytes, so it answers for whatever the backend transform
|
|
// chain actually produced rather than for what it was asked to produce.
|
|
static Bool ModuleDeclaresTransformFeedback(const Vector<Uint32>& spirv);
|
|
// Does this module declare TessellationPointSize or GeometryPointSize - i.e. does
|
|
// it need VkPhysicalDeviceFeatures::shaderTessellationAndGeometryPointSize before
|
|
// a pipeline built from it is legal usage (VUID-RuntimeSpirv-PointSize-06439)?
|
|
// glslang emits either capability from any access to the PointSize built-in in a
|
|
// tessellation or geometry stage, which desktop GL treats as an ordinary
|
|
// per-vertex output, so a program that is perfectly legal in GL can need a Vulkan
|
|
// feature the device does not have. Callers only ask when the feature is OFF, so
|
|
// the module parse costs nothing on a device that has it.
|
|
static Bool ModuleDeclaresTessellationOrGeometryPointSize(const Vector<Uint32>& spirv);
|
|
|
|
// ---- gl_PointSize demotion for devices without the capability above ----
|
|
// The name of the demoted program's LAST capture-capable stage's point-size
|
|
// carrier. It is the contract three parties meet at: the demotion pass names
|
|
// the variable, DirectVulkan's XfbCaptureDecoratePass binds a "gl_PointSize"
|
|
// capture to it instead of mirroring the (no longer accessed) built-in, and
|
|
// DirectGLES respells the driver-side glTransformFeedbackVaryings request
|
|
// with it. Deliberately NOT containing the substring "gl_PointSize":
|
|
// DirectGLES's extension-request gate is a text search for that token over
|
|
// the emitted ESSL, and a carrier name embedding it would re-arm the decline
|
|
// this demotion exists to retire.
|
|
static constexpr const char* POINT_SIZE_CAPTURE_CARRIER_NAME = "mg_PointSizeCapture";
|
|
|
|
// What the program-scoped demotion left behind. `demoted` false with an empty
|
|
// detail means the program never needed it (no tessellation/geometry stage
|
|
// accesses the built-in, or the device hosts it); false WITH a detail means a
|
|
// module shape the pass cannot express - the modules are byte-identical and
|
|
// the existing decline paths (Espryt's missing-extension compile failure,
|
|
// Magma's pointSizeCapabilityUnsupported refusal) stay in charge of it.
|
|
struct PointSizeDemotionOutcome {
|
|
Bool demoted = false;
|
|
String declineDetail;
|
|
};
|
|
|
|
// Demotes gl_PointSize across a WHOLE program's pre-rasterization chain into
|
|
// ordinary float varyings at one shared free location, so a device that
|
|
// advertises neither ES tessellation/geometry_point_size extension nor
|
|
// Vulkan's shaderTessellationAndGeometryPointSize can still run programs
|
|
// whose tessellation/geometry stages merely CARRY the value (transform
|
|
// feedback and gl_in[].gl_PointSize reads). Runs after
|
|
// SanitizeAndOptimizeBinary, on the final shared modules both backends
|
|
// consume, and is atomic per program: every stage is rewritten or none is,
|
|
// because a consumer whose producer kept the built-in would read garbage.
|
|
// `demoteTessellation` / `demoteGeometry` are the env verdicts (the device
|
|
// LACKS that capability); the per-program half of the decision - whether any
|
|
// module actually declares TessellationPointSize / GeometryPointSize - is
|
|
// probed here. `captureRequestsPointSize` forces the capture-capable last
|
|
// stage to declare its carrier even when it never writes the built-in, so a
|
|
// by-name capture always has something to bind to. Returns false only when
|
|
// the optimizer itself failed (modules untouched); a shape decline is
|
|
// reported through `outcome` and also leaves the modules untouched. See
|
|
// DemotePointSizePass for the per-module rewrite and its honest residue.
|
|
//
|
|
// Two declines are PROGRAM-shaped and therefore live here rather than in the
|
|
// pass: a carrier that would land past the minimum-spec varying budget, and
|
|
// an evaluation stage reading gl_in point size with NO control stage - the
|
|
// synthesized pass-through control stage both backends stand in that gap
|
|
// forwards gl_Position alone, so the input carrier would strand the value and
|
|
// trip the backends' own "reads a located input" refusal against a name the
|
|
// application never wrote.
|
|
static Bool DemoteTessellationGeometryPointSizeForProgram(
|
|
Vector<Vector<Uint32>>& modules, const Vector<GLenum>& shaderTypes, Bool demoteTessellation,
|
|
Bool demoteGeometry, Bool captureRequestsPointSize, PointSizeDemotionOutcome& outcome,
|
|
bool validateOutput = true, bool enableSpirvValidation = false);
|
|
|
|
// True when the module still declares a 64-bit float type. After
|
|
// SanitizeAndOptimizeBinary that can only mean DemoteFloat64Pass declined the
|
|
// module (see its header for the two operations that make it decline), which is
|
|
// what the backends report: no mobile driver can build such a module.
|
|
static Bool ModuleDeclaresFloat64(const Vector<Uint32>& spirv);
|
|
|
|
// True when the module is a VERTEX stage that declares a 64-bit float INPUT
|
|
// variable - `in double`, `in dvec2`, `in dmat3` and so on.
|
|
//
|
|
// Asked only on a backend with native fp64, and it is what keeps that backend's
|
|
// vertex path consistent. No backend here can FETCH 64 bits (VK_FORMAT_R64*_SFLOAT
|
|
// is optional and lavapipe advertises none of them), and the format is chosen from
|
|
// the VAO attribute, which does not know what the shader declared - so a module
|
|
// that keeps a Float64 input would be fed a narrowed float32 stream, or a packed
|
|
// uint pair with no matching format. Such a module is demoted WHOLE instead, which
|
|
// is exactly what every other backend does to it.
|
|
static Bool ModuleDeclaresFloat64VertexInput(const Vector<Uint32>& spirv);
|
|
|
|
// True when the module declares an Input variable carrying a Location - i.e. a
|
|
// user-defined varying or a per-patch input, as opposed to a built-in.
|
|
//
|
|
// Asked of a TESSELLATION EVALUATION stage that has no control stage, to decide
|
|
// whether the pass-through control stage GL 4.6 core 11.2.2 describes can stand
|
|
// in for the missing one. That stage forwards gl_Position and nothing else, so a
|
|
// located input - which the vertex stage feeds today and which would stop
|
|
// arriving once a control stage sat in between - means the program has to be
|
|
// declined rather than fed an undefined varying. Same rule, same reasoning, as
|
|
// DirectVulkan's ReflectPassthroughTessControlNeed, which asks SPIRV-Reflect the
|
|
// identical question for the identical decision.
|
|
static Bool ModuleReadsLocatedInput(const Vector<Uint32>& spirv);
|
|
};
|
|
|
|
// The explicit layout(location = N) qualifiers this shader's DEFAULT-BLOCK uniforms
|
|
// declared, keyed the way glslang's own reflection will later spell them.
|
|
//
|
|
// They cannot be read back off the parsed module, and that is not an oversight of
|
|
// this function: MobileGL parses every shader as a Vulkan client under relaxed
|
|
// rules, which sweeps plain uniforms into MGL_GLOBAL_UBO - where a location
|
|
// qualifier has no meaning - and DROPS the qualifier on the way past
|
|
// (ParseHelper.cpp vkRelaxedRemapUniformVariable). What this reads is the snapshot
|
|
// glslang takes at that exact site, handed over through TIntermediate; the GL
|
|
// location assigner in ProgramLinkTask::DoReflection is the only party left that
|
|
// can honour the number.
|
|
//
|
|
// Keyed by declared name (no "[0]" suffix), plus one synthesized key per outer
|
|
// index of an array-of-arrays - see the note in the implementation for why
|
|
// reflection needs those spelled out. A uniform declared in several stages must
|
|
// agree, which the caller enforces across stages.
|
|
UnorderedMap<String, Int> CollectExplicitUniformLocations(const glslang::TShader& shader);
|
|
|
|
// `source` run through glslang's PREPROCESSOR only (inactive #if branches gone, macros
|
|
// expanded), under the same preamble a parse would use, into `out`. For questions about
|
|
// the text a parse actually sees - which of two #if-alternative declarations is live.
|
|
// False (and `out` untouched) when glslang refuses it.
|
|
Bool PreprocessForInspection(GLenum shaderType, const String& source, String& out);
|
|
} // namespace ShaderTranspiler
|
|
} // namespace MG_Util
|
|
} // namespace MobileGL
|