mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (CMakeLists.txt, MG_Backend/DirectGLES, MG_Pipe, MG_Test/Backend, MG_Test/ShaderTranspiler, MG_Util/ShaderTranspiler): give integer attributes their GL value in Espryt
- every integer array reaches the driver with its input's signedness, at the pointer, zero-stride and client-memory call sites, so each fetch is one ES defines - 8/16-bit data of the other signedness is widened back by the program's runtime-width seeds, armed on the first draw that needs them and set as two uniforms per draw - capture programs with integer inputs are armed at their first build; arming is part of the ESSL translation key - a build whose seeds cannot be verified keeps the driver's own value for narrow data; one that fails to link is rebuilt without them - the program's integer input masks are memoized on its shader record
This commit is contained in:
@@ -529,6 +529,7 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_Backend/DirectGLES/ManagersTextures.cpp
|
||||
MobileGL/MG_Backend/DirectGLES/ManagersVertexArrays.cpp
|
||||
MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp
|
||||
MobileGL/MG_Backend/DirectGLES/NarrowIntegerSeeds.cpp
|
||||
|
||||
MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp
|
||||
MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp
|
||||
|
||||
@@ -398,12 +398,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// PrepareForDraw turns into BindBackendVAOId(0) exactly as a null frontend VAO did;
|
||||
// logging about it would fire on every draw of a session that legitimately has no
|
||||
// vertex-elements CSO yet.
|
||||
void SyncCurrentVAOFromRecords(BackendVertexArrayObject* vaoTwin) {
|
||||
void SyncCurrentVAOFromRecords(BackendVertexArrayObject* vaoTwin, const IntegerAttributePlan& plan) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (!vaoTwin) return;
|
||||
vaoTwin->SyncToBackendFromApplier();
|
||||
vaoTwin->SyncToBackendFromApplier(plan);
|
||||
}
|
||||
|
||||
// GL: a shader input whose generic attribute array is DISABLED reads that attribute's *current
|
||||
@@ -910,7 +910,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
BufferImpl::SyncNeccessaryBuffers(currentVAO, vaoTwin, vaoConfigVersion, syncBit & DrawSyncBit::IndexBuffer,
|
||||
syncBit & DrawSyncBit::IndirectBuffer);
|
||||
VertexArrayImpl::SyncCurrentVAOFromRecords(vaoTwin);
|
||||
// The draw's integer arrays and the program's narrow-integer seeds (NarrowIntegerSeeds.h).
|
||||
IntegerAttributePlan integerPlan = ResolveIntegerAttributePlanForDraw(MG_Pipe::MGPipeApplier().DrawProgram);
|
||||
VertexArrayImpl::SyncCurrentVAOFromRecords(vaoTwin, integerPlan);
|
||||
TextureImpl::SyncNeccessaryTextures(textureKeys);
|
||||
// A draw reads and writes through its image units too, so the unit bindings have to be
|
||||
// as current as the sampled ones. Gated (see the sweep): a program with no image binding
|
||||
@@ -927,7 +929,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// at all; the remaining uses of it below are the two callees' own, and both have a
|
||||
// handle arm of their own. P5e (pa) finished it: the pull itself is gone on that arm and
|
||||
// `currentProgram` is a null SharedPtr there, which this branch never reaches for.
|
||||
PrgramImpl::SyncCurrentProgramByHandle(MG_Pipe::MGPipeApplier().DrawProgram, true);
|
||||
PrgramImpl::SyncCurrentProgramByHandle(MG_Pipe::MGPipeApplier().DrawProgram, true, &integerPlan);
|
||||
if (DropRefusedNarrowIntegerSeeds(integerPlan, MG_Pipe::MGPipeApplier().DrawProgram))
|
||||
VertexArrayImpl::SyncCurrentVAOFromRecords(vaoTwin, integerPlan);
|
||||
RenderStateImpl::SyncRenderState();
|
||||
|
||||
BindCurrentFBO(FramebufferTarget::Draw);
|
||||
@@ -951,6 +955,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
BindCurrentTextures(textureKeys, currentProgram);
|
||||
BindCurrentProgramWithResources(currentProgram, textureKeys, MG_Pipe::MGPipeApplier().DrawProgram);
|
||||
PushNarrowIntegerWidthsForDraw(integerPlan, MG_Pipe::MGPipeApplier().DrawProgram);
|
||||
|
||||
// Last: opening the capture span needs the program current and the capture
|
||||
// buffers bound, and ES rejects most binding changes once it is open.
|
||||
|
||||
@@ -387,7 +387,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const MG_Pipe::MGPipeShaderCsoRecord* ResolveGlobalConstantsRecordForHandle(MG_Pipe::MGPipeHandle handle,
|
||||
Uint uboSize);
|
||||
|
||||
void SyncCurrentProgramByHandle(MG_Pipe::MGPipeHandle cso, Bool forDraw);
|
||||
// `plan` (a draw's) arms the program's narrow-integer seeds when it needs them.
|
||||
void SyncCurrentProgramByHandle(MG_Pipe::MGPipeHandle cso, Bool forDraw,
|
||||
const IntegerAttributePlan* plan = nullptr);
|
||||
} // namespace PrgramImpl
|
||||
|
||||
// Defined with the surface registry, below.
|
||||
|
||||
@@ -1434,7 +1434,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// and the clamp masks, the broadcast count, ImageUnitFormatsStillMatch and the three
|
||||
// patch clauses are unchanged - they are backend globals, fb's row and VALUE rows of the
|
||||
// residual block, none of which this family owns.
|
||||
void SyncCurrentProgramByHandle(MG_Pipe::MGPipeHandle cso, Bool forDraw) {
|
||||
void SyncCurrentProgramByHandle(MG_Pipe::MGPipeHandle cso, Bool forDraw, const IntegerAttributePlan* plan) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
@@ -1468,8 +1468,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_lastUsedBackendProgramId = 0;
|
||||
return;
|
||||
}
|
||||
// Sticky for the twin's lifetime: a draw that needs the seeds rebuilds the program once.
|
||||
if (plan != nullptr && plan->NeedsSeeds()) twin->NarrowIntegerSeeds().wanted = true;
|
||||
if (!twin->GetBackendProgramId() || twin->GetSyncedShaderCsoSerial() != record->Serial ||
|
||||
twin->GetSyncedBindingsSerial() != record->BindingsSerial ||
|
||||
twin->NarrowIntegerSeeds().Stale() || twin->GetSyncedBindingsSerial() != record->BindingsSerial ||
|
||||
twin->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask ||
|
||||
twin->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask ||
|
||||
twin->GetFragColorBroadcastCount() != g_fragColorBroadcastCount ||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectGLES/IntegerAttributePlan.h
|
||||
// Copyright (c) 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 <MG_Util/ShaderTranspiler/NarrowIntegerVertexInputs.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
using MG_Util::ShaderTranspiler::IntegerVertexInputMasks;
|
||||
|
||||
// What one draw does with its integer vertex arrays. ES defines an integer attribute's value
|
||||
// only when the array's type has the input's signedness, so every integer array feeding an
|
||||
// integer input of the other signedness is ISSUED with the input's signedness (retypeMask).
|
||||
// For 32-bit data that is the same bits, which is GL's value. For 8/16-bit data the driver
|
||||
// then extends by the input's rule, and the program's seeds (ExtendNarrowIntegerVertexInputs-
|
||||
// Pass's RuntimeWidths mode) widen the low 8 or 16 bits again by the data's rule: widths8 and
|
||||
// widths16 are the locations to widen, and they are the two uniforms the draw sets.
|
||||
struct IntegerAttributePlan {
|
||||
Uint32 retypeMask = 0;
|
||||
// Integer arrays feeding integer inputs: the only locations whose issued type is
|
||||
// observable, so a VAO keeps whatever it holds for every other location.
|
||||
Uint32 careMask = 0;
|
||||
Uint32 widths8 = 0;
|
||||
Uint32 widths16 = 0;
|
||||
|
||||
constexpr Bool NeedsSeeds() const { return (widths8 | widths16) != 0; }
|
||||
// The plan for a program without working seeds: a narrow array keeps the application's
|
||||
// type (the value the driver gives today) rather than a retype nothing widens back.
|
||||
constexpr IntegerAttributePlan WithoutSeeds() const {
|
||||
IntegerAttributePlan plan = *this;
|
||||
plan.retypeMask &= ~(widths8 | widths16);
|
||||
plan.widths8 = 0;
|
||||
plan.widths16 = 0;
|
||||
return plan;
|
||||
}
|
||||
constexpr Bool operator==(const IntegerAttributePlan&) const = default;
|
||||
};
|
||||
|
||||
// The locations whose integer array data is of each width and signedness.
|
||||
struct IntegerAttributeDataMasks {
|
||||
MG_Util::ShaderTranspiler::NarrowIntegerAttributeMasks narrow;
|
||||
Uint32 unsigned32 = 0;
|
||||
Uint32 signed32 = 0;
|
||||
|
||||
constexpr void Note(Uint32 location, DataType type) {
|
||||
if (location >= 32) return;
|
||||
const Uint32 bit = 1u << location;
|
||||
switch (type) {
|
||||
case DataType::Uint8:
|
||||
narrow.unsigned8 |= bit;
|
||||
break;
|
||||
case DataType::Uint16:
|
||||
narrow.unsigned16 |= bit;
|
||||
break;
|
||||
case DataType::Uint32:
|
||||
unsigned32 |= bit;
|
||||
break;
|
||||
case DataType::Int8:
|
||||
narrow.signed8 |= bit;
|
||||
break;
|
||||
case DataType::Int16:
|
||||
narrow.signed16 |= bit;
|
||||
break;
|
||||
case DataType::Int32:
|
||||
signed32 |= bit;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
constexpr Uint32 Unsigned() const { return narrow.unsigned8 | narrow.unsigned16 | unsigned32; }
|
||||
constexpr Uint32 Signed() const { return narrow.signed8 | narrow.signed16 | signed32; }
|
||||
};
|
||||
|
||||
// `seedsUsable` is false once the program's armed build could not give it working seeds.
|
||||
constexpr IntegerAttributePlan ResolveIntegerAttributePlan(const IntegerVertexInputMasks& inputs,
|
||||
const IntegerAttributeDataMasks& data,
|
||||
Bool seedsUsable) {
|
||||
IntegerAttributePlan plan;
|
||||
plan.careMask = (inputs.signedInputs | inputs.unsignedInputs) & (data.Unsigned() | data.Signed());
|
||||
plan.retypeMask = (inputs.signedInputs & data.Unsigned()) | (inputs.unsignedInputs & data.Signed());
|
||||
const auto narrow = MG_Util::ShaderTranspiler::ResolveNarrowIntegerVertexInputs(inputs, data.narrow);
|
||||
plan.widths8 = narrow.zeroExtend8 | narrow.signExtend8;
|
||||
plan.widths16 = narrow.zeroExtend16 | narrow.signExtend16;
|
||||
return seedsUsable ? plan : plan.WithoutSeeds();
|
||||
}
|
||||
|
||||
// The array type to issue: the application's, or its counterpart of the other signedness.
|
||||
constexpr GLenum IntegerAttribTypeForInput(GLenum applicationType, Bool retype) {
|
||||
if (!retype) return applicationType;
|
||||
switch (applicationType) {
|
||||
case GL_BYTE:
|
||||
return GL_UNSIGNED_BYTE;
|
||||
case GL_UNSIGNED_BYTE:
|
||||
return GL_BYTE;
|
||||
case GL_SHORT:
|
||||
return GL_UNSIGNED_SHORT;
|
||||
case GL_UNSIGNED_SHORT:
|
||||
return GL_SHORT;
|
||||
case GL_INT:
|
||||
return GL_UNSIGNED_INT;
|
||||
case GL_UNSIGNED_INT:
|
||||
return GL_INT;
|
||||
default:
|
||||
return applicationType;
|
||||
}
|
||||
}
|
||||
|
||||
// What a vertex array issues after this plan: the plan's choice wherever it can be observed,
|
||||
// what it already holds everywhere else, so a program switch that only changes which
|
||||
// locations are read does not re-issue any pointer.
|
||||
constexpr Uint32 HeldIntegerRetype(Uint32 held, const IntegerAttributePlan& plan) {
|
||||
return (held & ~plan.careMask) | (plan.retypeMask & plan.careMask);
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
@@ -374,6 +374,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (shaderType != GL_VERTEX_SHADER) {
|
||||
return source;
|
||||
}
|
||||
source = PromoteNarrowIntegerWidthGlobals(std::move(source));
|
||||
for (const char* name : {DRAW_ID_UNIFORM_NAME, BASE_VERTEX_UNIFORM_NAME}) {
|
||||
for (const char* declPrefix :
|
||||
{"highp int ", "mediump int ", "lowp int ", "int ", "highp uint ", "mediump uint ", "uint "}) {
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
// so a forward declaration would not do. Push-only, like everything else P4a adds to this
|
||||
// header, so the pull build's include graph is unchanged (D-P).
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
// P13 W5: the record arm's deaths (the slot tables' handle refusals below), in every build.
|
||||
#include <MG_Pipe/PipeSessionFail.h>
|
||||
#include <MG_Pipe/PipeSessionFail.h> // P13 W5: the slot tables' handle refusals, in every build
|
||||
#include "NarrowIntegerSeeds.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
|
||||
@@ -1230,9 +1230,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// above, so VertexArrayImpl::SyncCurrentVAOFromRecords calls this ENTRY directly
|
||||
// instead of passing a null SharedPtr into a function whose signature promises one.
|
||||
// CONTRACT-P5E §4.1's rule ("the object parameter is deleted from the transport
|
||||
// overload, not defaulted to null") is what this obeys. The body is unchanged and
|
||||
// was already record-only - it is this family's existence proof (scout S1 §2).
|
||||
void SyncToBackendFromApplier();
|
||||
// overload, not defaulted to null") is what this obeys. `plan`: IntegerAttributePlan.h.
|
||||
void SyncToBackendFromApplier(const IntegerAttributePlan& plan);
|
||||
// P14: the applier's buffer-set serials move whenever ANY VAO's set is published, so
|
||||
// switching between two VAOs moves them although neither twin's buffers changed. Called
|
||||
// before the draw's syncs: when the window this twin last emitted is the applier's
|
||||
@@ -1455,12 +1454,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// one monotone Uint64, no wrap, nothing to patch. This is the Track H re-key
|
||||
// ARCHITECTURE.md 9.5 counts.
|
||||
Uint64 m_syncedIndexSerial = 0;
|
||||
// Byte shift currently baked into the instanced arrays' offsets by the baseInstance
|
||||
// emulation (see SetPendingFetchBaseInstance). It is draw state, not VAO state, so it
|
||||
// is deliberately NOT covered by the config version: the frontend never bumps for it.
|
||||
// Kept here because it describes what was last EMITTED, which is what the next sync
|
||||
// has to correct.
|
||||
// Byte shift baked into the instanced arrays' offsets by the baseInstance emulation:
|
||||
// draw state, not VAO state, kept because the next sync has to correct what was EMITTED.
|
||||
Uint32 m_syncedFetchBaseInstance = 0;
|
||||
// The integer arrays last issued with the other signedness (HeldIntegerRetype).
|
||||
Uint32 m_syncedRetypeMask = 0;
|
||||
// BufferImpl::g_bufferBackendIdGeneration as of this twin's last emit. A
|
||||
// mismatch means some live buffer's driver id was re-minted since; the ids
|
||||
// baked into the driver VAO's attribute/element bindings may be dead even
|
||||
@@ -2847,6 +2845,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// shader failed to transpile or compile, or the link itself failed). Use()
|
||||
// must not leave the previously bound program current in that case.
|
||||
Bool IsBackendProgramUsable() const { return m_backendProgramUsable; }
|
||||
NarrowIntegerSeedState& NarrowIntegerSeeds() { return m_narrowIntegerSeeds; }
|
||||
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
||||
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
||||
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
|
||||
@@ -2971,7 +2970,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const std::map<String, String>& inputBlockRenames,
|
||||
const std::map<String, String>& outputBlockRenames, Bool stripInputBlockLocations,
|
||||
Bool stripOutputBlockLocations, Int atomicCounterEsslBindingTop,
|
||||
Bool enableSpirvValidation, String& outSource,
|
||||
Bool narrowIntegerSeedsArmed, Bool enableSpirvValidation, String& outSource,
|
||||
std::set<String>& outFlattenedXfbBlockNames,
|
||||
Vector<Int>& outAtomicCounterGlBindings, String& outError) const;
|
||||
|
||||
@@ -3030,6 +3029,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Vector<Int> m_formatlessImageUnits;
|
||||
Uint64 m_imageUnitFormatSignature = 0;
|
||||
SamplerPassMemo m_samplerPassMemo;
|
||||
NarrowIntegerSeedState m_narrowIntegerSeeds;
|
||||
};
|
||||
|
||||
extern Uint32 g_snormFallbackClampOutputMask;
|
||||
|
||||
@@ -888,8 +888,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const UnorderedMap<String, Int>& storageBlockBindingOverrides,
|
||||
const std::map<String, String>& inputBlockRenames, const std::map<String, String>& outputBlockRenames,
|
||||
const Bool stripInputBlockLocations, const Bool stripOutputBlockLocations,
|
||||
const Int atomicCounterEsslBindingTop, const Bool enableSpirvValidation, String& outSource,
|
||||
std::set<String>& outFlattenedXfbBlockNames, Vector<Int>& outAtomicCounterGlBindings,
|
||||
const Int atomicCounterEsslBindingTop, const Bool narrowIntegerSeedsArmed, const Bool enableSpirvValidation,
|
||||
String& outSource, std::set<String>& outFlattenedXfbBlockNames, Vector<Int>& outAtomicCounterGlBindings,
|
||||
String& outError) const {
|
||||
// ESSL cannot express gl_DrawID/gl_BaseInstance/gl_BaseVertex; demote them to
|
||||
// plain globals (mg_*) before handing the module to SPIRV-Cross.
|
||||
@@ -1050,24 +1050,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MG_Util::ShaderTranspiler::ShaderCompiler::SplitArrayVertexInputsForEssl(
|
||||
*effectiveSpirv, splitArrayInputSpirv, enableSpirvValidation) &&
|
||||
!splitArrayInputSpirv.empty() && splitArrayInputSpirv != *effectiveSpirv) {
|
||||
// Only when the pass ACTUALLY split something. The optimizer hands back a
|
||||
// re-serialised copy either way, and adopting that copy for every vertex
|
||||
// shader would put every one of them through a round trip they do not need
|
||||
// - which is not free: it cost the create-indirect retrace 0.15 SSIM the
|
||||
// first time this gate was missing (the original capture; the case is now
|
||||
// minecraft-1.21.1-neoforge-create-indirect-in-world-align1024).
|
||||
// Only when the pass ACTUALLY split something: the optimizer hands back a
|
||||
// re-serialised copy either way, and that round trip is not free - it cost the
|
||||
// minecraft-1.21.1-neoforge-create-indirect-in-world-align1024 retrace 0.15 SSIM.
|
||||
effectiveSpirv = &splitArrayInputSpirv;
|
||||
}
|
||||
|
||||
// Adopt the rewritten module only when THIS stage actually had one of the
|
||||
// blocks - the optimizer hands back a re-serialised copy either way, and taking
|
||||
// that copy for a module it did not rewrite is not free (it cost the
|
||||
// create-indirect retrace 0.15 SSIM when the array-input split first missed
|
||||
// this gate; the case is now
|
||||
// minecraft-1.21.1-neoforge-create-indirect-in-world-align1024). The report has to be
|
||||
// per stage, not cumulative: a fragment shader consuming the same block reports a
|
||||
// name the vertex stage already reported,
|
||||
// and its own rewrite must still be taken or the two stages stop matching.
|
||||
// An armed vertex stage's narrow-integer seeds (NarrowIntegerSeeds.h), after the split
|
||||
// and adopted only when the pass rewrote the module. Keyed by narrowIntegerSeedsArmed.
|
||||
Vector<unsigned int> narrowSeededSpirv;
|
||||
effectiveSpirv =
|
||||
SeedNarrowIntegerVertexInputsForEssl(narrowIntegerSeedsArmed && glShaderType == GL_VERTEX_SHADER,
|
||||
*effectiveSpirv, narrowSeededSpirv, enableSpirvValidation);
|
||||
|
||||
// Adopt the rewritten module only when THIS stage actually had one of the blocks, for
|
||||
// the array-input split's reason above. The report is per stage, not cumulative: a
|
||||
// fragment shader consuming a block the vertex stage reported must still take its own
|
||||
// rewrite or the two stages stop matching.
|
||||
Vector<unsigned int> flattenedXfbSpirv;
|
||||
if (!xfbCaptureBlockNames.empty()) {
|
||||
// Reported into a local first, and published only if the module is really
|
||||
@@ -1605,9 +1604,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
// Recorded before either early return below, so Use() can always name the GL
|
||||
// program a no-op draw belongs to - including the "linked but not drawable" exit.
|
||||
// Before either early return below, so Use() can name the GL program of a no-op draw.
|
||||
m_frontendProgramId = src.GetExternalIndex();
|
||||
BeginNarrowIntegerSeedBuild(m_narrowIntegerSeeds);
|
||||
|
||||
// GetSpirvStatus() as well as GetLinkStatus(): a program whose phase-B job was
|
||||
// cancelled (teardown) or whose optimizer run failed is fully linked and fully
|
||||
@@ -1636,9 +1635,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// this build current - the draw path compares the signature and rebuilds on a change.
|
||||
const auto& storageBlockBindingOverrides = src.GetShaderStorageBlockBindingOverrides();
|
||||
m_shaderStorageBlockBindingSignature = ComputeShaderStorageBlockBindingSignature(src);
|
||||
// Rebuilt by the transpile loop below, one entry per atomic-counter block it finds.
|
||||
// The top is snapshotted here so every stage of this program - and the draw path
|
||||
// reading it afterwards - resolves the same slot for the same GL binding.
|
||||
// Rebuilt by the transpile loop below, one entry per atomic-counter block; the top is
|
||||
// snapshotted so every stage and the draw path resolve one slot per GL binding.
|
||||
m_atomicCounterGlBindings.clear();
|
||||
m_atomicCounterEsslBindingTop = AtomicCounterEsslBindingTop();
|
||||
// Re-established by AttachPassthroughTessControlStage below when this program needs
|
||||
@@ -2045,6 +2043,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
esslKeyInputs.stripInputBlockLocations = stripInputBlockLocations;
|
||||
esslKeyInputs.stripOutputBlockLocations = stripOutputBlockLocations;
|
||||
esslKeyInputs.enableSpirvValidation = enableSpirvValidation;
|
||||
esslKeyInputs.narrowIntegerSeedsArmed = glShaderType == GL_VERTEX_SHADER && m_narrowIntegerSeeds.built;
|
||||
|
||||
auto& esslCache = MG_Util::ShaderTranspiler::GetEsslTranslationCache();
|
||||
MG_Util::ShaderTranspiler::TranslationCacheKey esslCacheKey;
|
||||
@@ -2070,9 +2069,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (!TranspileSpirvToEssl(spirvCode, glShaderType, xfbCaptureBlockNames, imageFormatBake,
|
||||
storageBlockBindingOverrides, inputBlockRenames, outputBlockRenames,
|
||||
stripInputBlockLocations, stripOutputBlockLocations,
|
||||
m_atomicCounterEsslBindingTop, enableSpirvValidation, source,
|
||||
stageFlattenedXfbBlockNames, stageAtomicCounterGlBindings,
|
||||
transpileError)) {
|
||||
m_atomicCounterEsslBindingTop, esslKeyInputs.narrowIntegerSeedsArmed,
|
||||
enableSpirvValidation, source, stageFlattenedXfbBlockNames,
|
||||
stageAtomicCounterGlBindings, transpileError)) {
|
||||
// MGLOG_E, unlatched, like the compile- and link-failure diagnostics
|
||||
// below: one line per failing stage is bounded by program count and
|
||||
// naming the stage is the entire diagnostic value. A stage that never
|
||||
@@ -2286,14 +2285,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
pos = source.find(findStr, pos);
|
||||
}
|
||||
|
||||
NoteNarrowIntegerSeedSource(m_narrowIntegerSeeds, source, glShaderType);
|
||||
const char* sourceCStr = source.c_str();
|
||||
MGLOG_D("Setting shader source for backend shader ID: %u\nsrc:\n%s", backendShaderId, sourceCStr);
|
||||
g_GLESFuncs.glShaderSource(backendShaderId, 1, &sourceCStr, nullptr);
|
||||
g_GLESFuncs.glCompileShader(backendShaderId);
|
||||
|
||||
// GL_FALSE, not GL_TRUE: an unwritten out-param must read as "compile failed"
|
||||
// and take the diagnostic path, never as a silent success that attaches an
|
||||
// uncompiled shader.
|
||||
// GL_FALSE, not GL_TRUE: an unwritten out-param must read as "compile failed", never
|
||||
// as a silent success that attaches an uncompiled shader.
|
||||
GLint compileStatus = GL_FALSE;
|
||||
g_GLESFuncs.glGetShaderiv(backendShaderId, GL_COMPILE_STATUS, &compileStatus);
|
||||
if (compileStatus == GL_FALSE) {
|
||||
@@ -2649,6 +2648,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_backendProgramUsable = false;
|
||||
return;
|
||||
}
|
||||
// A capture program with integer inputs is armed at its first build, so arming never
|
||||
// relinks a program in the middle of a capture (NarrowIntegerSeeds.h).
|
||||
const IntegerVertexInputMasks integerInputs = IntegerVertexInputsOf(*record);
|
||||
if (!record->Archive->Link.xfbVaryings.empty() &&
|
||||
(integerInputs.signedInputs | integerInputs.unsignedInputs))
|
||||
m_narrowIntegerSeeds.wanted = true;
|
||||
SyncToBackendFromSource(ProgramArchiveSource::FromRecord(cso, *record));
|
||||
// The two server-owned keys the draw path's clean condition reads instead of
|
||||
// GetLinkVersion()/GetImageUnitVersion(). BindingsSerial is the second because a
|
||||
@@ -2769,6 +2774,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
binding.lastAssignedMsSampleMax = 0;
|
||||
m_samplerUniformBindings.push_back(binding);
|
||||
}
|
||||
FinishNarrowIntegerSeedBuild(m_narrowIntegerSeeds, m_backendProgramId, m_backendProgramUsable);
|
||||
}
|
||||
|
||||
void BackendProgramObjectImpl::Use() {
|
||||
|
||||
@@ -422,7 +422,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// SyncZeroStrideAttribute on the wire views: the one spelling that can carry a resolved
|
||||
// stride of zero, which glVertexAttribPointer's zero means the opposite of.
|
||||
inline Bool SyncZeroStrideAttributeByHandle(Uint attribIndex, const MGPVertexAttribWire& attrib,
|
||||
const MG_Pipe::MGPVertexBuffer& binding) {
|
||||
const MG_Pipe::MGPVertexBuffer& binding, GLenum issuedType) {
|
||||
if (MG_Pipe::MGPipeHandleIsNull(binding.Res)) {
|
||||
MGLOG_W_ONCE("Zero-stride attribute %u has no bound buffer, skipping.", attribIndex);
|
||||
return false;
|
||||
@@ -438,9 +438,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)),
|
||||
attrib.Normalized ? GL_TRUE : GL_FALSE, 0);
|
||||
} else {
|
||||
g_GLESFuncs.glVertexAttribIFormat(attribIndex, static_cast<GLint>(attrib.Size),
|
||||
MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)),
|
||||
0);
|
||||
g_GLESFuncs.glVertexAttribIFormat(attribIndex, static_cast<GLint>(attrib.Size), issuedType, 0);
|
||||
}
|
||||
g_GLESFuncs.glVertexAttribBinding(attribIndex, attribIndex);
|
||||
// The resolved offset goes on the binding point, not into a relative offset (the
|
||||
@@ -506,7 +504,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// as the record of what was last
|
||||
// EMITTED, which is what the next
|
||||
// sync has to correct
|
||||
void BackendVertexArrayObject::SyncToBackendFromApplier() {
|
||||
void BackendVertexArrayObject::SyncToBackendFromApplier(const IntegerAttributePlan& plan) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
@@ -545,7 +543,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// same whichever side resolved it first - it is idempotent.
|
||||
const Uint32 fetchBaseInstance = BackendUsesNativeBaseInstance() ? 0u : st.VertexFetchBaseInstance;
|
||||
const Bool baseInstanceDirty = m_syncedFetchBaseInstance != fetchBaseInstance;
|
||||
const Bool emitAttributes = attributesDirty || baseInstanceDirty || m_hasConvertedAttribute;
|
||||
// The integer arrays to issue with the other signedness; only a pointer whose type flips
|
||||
// is re-issued, through the shadow below.
|
||||
const Uint32 retypeHeld = HeldIntegerRetype(m_syncedRetypeMask, plan);
|
||||
const Bool emitAttributes =
|
||||
attributesDirty || baseInstanceDirty || m_hasConvertedAttribute || retypeHeld != m_syncedRetypeMask;
|
||||
if (!emitAttributes && !indexBufferDirty) {
|
||||
return;
|
||||
}
|
||||
@@ -649,7 +651,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// The binding API rewrote this attribute's format and binding: whatever the
|
||||
// shadow said about its pointer no longer holds.
|
||||
m_hostPointer[attribIndex].valid = false;
|
||||
if (!SyncZeroStrideAttributeByHandle(attribIndex, attrib, *binding)) {
|
||||
const GLenum issuedType =
|
||||
IntegerAttribTypeForInput(MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)),
|
||||
attrib.IsInteger && ((retypeHeld >> attribIndex) & 1u));
|
||||
if (!SyncZeroStrideAttributeByHandle(attribIndex, attrib, *binding, issuedType)) {
|
||||
continue;
|
||||
}
|
||||
// No shift here on purpose: a zero stride never advances. Binding index ==
|
||||
@@ -674,7 +679,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
const SizeT fetchOffset = static_cast<SizeT>(attrib.Offset) +
|
||||
BaseInstanceByteShiftWire(attrib.Stride, divisor, fetchBaseInstance);
|
||||
const GLenum glType = MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type));
|
||||
const GLenum glType =
|
||||
IntegerAttribTypeForInput(MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)),
|
||||
attrib.IsInteger && ((retypeHeld >> attribIndex) & 1u));
|
||||
const GLint glSize = static_cast<GLint>(attrib.Size);
|
||||
|
||||
// The pointer the driver VAO already holds for this attribute - the buffer it
|
||||
@@ -759,6 +766,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
if (emitAttributes) {
|
||||
m_syncedFetchBaseInstance = fetchBaseInstance;
|
||||
m_syncedRetypeMask = retypeHeld;
|
||||
m_lastWalkWasPlain = walkWasPlain;
|
||||
}
|
||||
m_syncedBufferIdGeneration = currentBufferIdGeneration;
|
||||
@@ -923,7 +931,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
reinterpret_cast<const void*>(BaseInstanceByteShift(attrib, fetchBaseInstance)));
|
||||
} else {
|
||||
g_GLESFuncs.glVertexAttribIPointer(
|
||||
attribIndex, attrib.Size, MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
|
||||
attribIndex, attrib.Size,
|
||||
IntegerAttribTypeForInput(MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
|
||||
((m_syncedRetypeMask >> attribIndex) & 1u) != 0),
|
||||
static_cast<GLsizei>(stride),
|
||||
// See the pointer call above: the divisor'd
|
||||
// arrays' baseInstance shift is the pointer.
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectGLES/NarrowIntegerSeeds.cpp
|
||||
// Copyright (c) 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
|
||||
|
||||
#include "NarrowIntegerSeeds.h"
|
||||
|
||||
#include "DirectGLESInternal.h"
|
||||
|
||||
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
namespace {
|
||||
using MG_Util::ShaderTranspiler::kNarrowIntegerWidths16GlobalName;
|
||||
using MG_Util::ShaderTranspiler::kNarrowIntegerWidths8GlobalName;
|
||||
|
||||
// The twin a handle already has, without creating one: a program that was never built has
|
||||
// no seeds to refuse.
|
||||
PrgramImpl::BackendProgramObjectImpl* ExistingProgramTwin(MG_Pipe::MGPipeHandle program) {
|
||||
auto* slot = PrgramImpl::g_backendProgramObjects.FindByHandle(program);
|
||||
return slot != nullptr ? slot->get() : nullptr;
|
||||
}
|
||||
|
||||
String UniformDeclaration(const char* name) {
|
||||
return String("uniform highp uint ") + name + ";";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
IntegerVertexInputMasks IntegerVertexInputsOf(const MG_Pipe::MGPipeShaderCsoRecord& record) {
|
||||
auto& memo = record.BackendIntegerInputs;
|
||||
if (!memo.Valid || memo.Serial != record.Serial) {
|
||||
IntegerVertexInputMasks inputs;
|
||||
if (record.Archive) {
|
||||
// Indexed by location, an empty name being no active input: the same reading
|
||||
// SyncCurrentVertexAttributeValues makes of these two vectors.
|
||||
const auto& link = record.Archive->Link;
|
||||
const SizeT count = std::min<SizeT>(std::min(link.attribs.size(), link.attribTypes.size()), 32);
|
||||
for (SizeT location = 0; location < count; ++location) {
|
||||
if (!link.attribs[location].empty())
|
||||
inputs.Note(static_cast<Uint32>(location), link.attribTypes[location]);
|
||||
}
|
||||
}
|
||||
memo.SignedInputs = inputs.signedInputs;
|
||||
memo.UnsignedInputs = inputs.unsignedInputs;
|
||||
memo.Serial = record.Serial;
|
||||
memo.Valid = true;
|
||||
}
|
||||
IntegerVertexInputMasks inputs;
|
||||
inputs.signedInputs = memo.SignedInputs;
|
||||
inputs.unsignedInputs = memo.UnsignedInputs;
|
||||
return inputs;
|
||||
}
|
||||
|
||||
IntegerAttributePlan ResolveIntegerAttributePlanForDraw(MG_Pipe::MGPipeHandle program) {
|
||||
if (MG_Pipe::MGPipeHandleIsNull(program)) return {};
|
||||
const MG_Pipe::MGPipeShaderCsoRecord* const record = PipeShaderCsoRecordForHandle(program);
|
||||
if (record == nullptr || !record->Archive) return {};
|
||||
const IntegerVertexInputMasks inputs = IntegerVertexInputsOf(*record);
|
||||
Uint32 remaining = inputs.signedInputs | inputs.unsignedInputs;
|
||||
if (remaining == 0) return {};
|
||||
const auto* elements = VertexArrayImpl::BoundVertexElementsRecord(MG_Pipe::MGPipeApplier());
|
||||
if (elements == nullptr) return {};
|
||||
IntegerAttributeDataMasks data;
|
||||
for (; remaining != 0; remaining &= remaining - 1) {
|
||||
const Uint32 location = static_cast<Uint32>(std::countr_zero(remaining));
|
||||
const auto& attrib = elements->Attributes[location];
|
||||
if (attrib.Enabled && attrib.IsInteger && !attrib.IsLong)
|
||||
data.Note(location, static_cast<DataType>(attrib.Type));
|
||||
}
|
||||
auto* twin = ExistingProgramTwin(program);
|
||||
return ResolveIntegerAttributePlan(inputs, data, twin == nullptr || !twin->NarrowIntegerSeeds().Refused());
|
||||
}
|
||||
|
||||
Bool DropRefusedNarrowIntegerSeeds(IntegerAttributePlan& plan, MG_Pipe::MGPipeHandle program) {
|
||||
if (!plan.NeedsSeeds()) return false;
|
||||
auto* twin = ExistingProgramTwin(program);
|
||||
if (twin == nullptr || !twin->NarrowIntegerSeeds().Refused()) return false;
|
||||
plan = plan.WithoutSeeds();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PushNarrowIntegerWidthsForDraw(const IntegerAttributePlan& plan, MG_Pipe::MGPipeHandle program) {
|
||||
// Only the twin this draw's sync stashed, and only while it is the program Use() bound: a
|
||||
// uniform call with program 0, or on another program, is an error or a wrong write.
|
||||
if (PrgramImpl::g_currentDrawProgramHandle != program) return;
|
||||
auto* twin = PrgramImpl::g_currentDrawBackendProgram;
|
||||
if (twin == nullptr || !twin->IsBackendProgramUsable() ||
|
||||
PrgramImpl::g_lastUsedBackendProgramId != twin->GetBackendProgramId())
|
||||
return;
|
||||
NarrowIntegerSeedState& state = twin->NarrowIntegerSeeds();
|
||||
if (!state.available) return;
|
||||
if (state.pushed8 != plan.widths8) {
|
||||
g_GLESFuncs.glUniform1ui(state.widths8Location, plan.widths8);
|
||||
state.pushed8 = plan.widths8;
|
||||
}
|
||||
if (state.pushed16 != plan.widths16) {
|
||||
g_GLESFuncs.glUniform1ui(state.widths16Location, plan.widths16);
|
||||
state.pushed16 = plan.widths16;
|
||||
}
|
||||
}
|
||||
|
||||
void BeginNarrowIntegerSeedBuild(NarrowIntegerSeedState& state) {
|
||||
state.built = state.Target();
|
||||
state.declared = false;
|
||||
state.available = false;
|
||||
state.widths8Location = -1;
|
||||
state.widths16Location = -1;
|
||||
state.pushed8 = 0;
|
||||
state.pushed16 = 0;
|
||||
}
|
||||
|
||||
void NoteNarrowIntegerSeedSource(NarrowIntegerSeedState& state, const String& source, GLenum shaderType) {
|
||||
if (!state.built || shaderType != GL_VERTEX_SHADER) return;
|
||||
state.declared = source.find(UniformDeclaration(kNarrowIntegerWidths8GlobalName)) != String::npos &&
|
||||
source.find(UniformDeclaration(kNarrowIntegerWidths16GlobalName)) != String::npos;
|
||||
}
|
||||
|
||||
void FinishNarrowIntegerSeedBuild(NarrowIntegerSeedState& state, GLuint program, Bool programUsable) {
|
||||
if (!state.built) return;
|
||||
if (!programUsable) {
|
||||
// The seeds may be why the build failed; the next build goes without them.
|
||||
state.disabled = true;
|
||||
MGLOG_E_ONCE("DirectGLES: a program built with narrow integer seeds did not link; it is rebuilt "
|
||||
"without them, and its 8/16-bit integer attributes keep the driver's own value");
|
||||
return;
|
||||
}
|
||||
if (state.declared) {
|
||||
state.widths8Location = g_GLESFuncs.glGetUniformLocation(program, kNarrowIntegerWidths8GlobalName);
|
||||
state.widths16Location = g_GLESFuncs.glGetUniformLocation(program, kNarrowIntegerWidths16GlobalName);
|
||||
}
|
||||
state.available = state.widths8Location >= 0 && state.widths16Location >= 0;
|
||||
if (!state.available) {
|
||||
MGLOG_E_ONCE("DirectGLES: a program built with narrow integer seeds has no width uniforms (%s); its "
|
||||
"8/16-bit integer attributes keep the driver's own value",
|
||||
state.declared ? "the link dropped them" : "the vertex stage does not declare them");
|
||||
}
|
||||
}
|
||||
|
||||
const Vector<unsigned int>* SeedNarrowIntegerVertexInputsForEssl(Bool armed, const Vector<unsigned int>& spirv,
|
||||
Vector<unsigned int>& storage,
|
||||
Bool enableSpirvValidation) {
|
||||
if (!armed) return &spirv;
|
||||
Bool changed = false;
|
||||
if (!MG_Util::ShaderTranspiler::ShaderCompiler::ExtendNarrowIntegerVertexInputsForEssl(spirv, storage, changed,
|
||||
enableSpirvValidation)) {
|
||||
MGLOG_E_ONCE("DirectGLES: the narrow integer seed pass declined a vertex stage; it keeps the driver's "
|
||||
"own value for 8/16-bit integer attributes");
|
||||
return &spirv;
|
||||
}
|
||||
return changed && !storage.empty() ? &storage : &spirv;
|
||||
}
|
||||
|
||||
String PromoteNarrowIntegerWidthGlobals(String source) {
|
||||
for (const char* name : {kNarrowIntegerWidths8GlobalName, kNarrowIntegerWidths16GlobalName}) {
|
||||
// The zero-initialised Private global is printed with or without a precision qualifier;
|
||||
// only a declaration starting its own line is the global itself.
|
||||
for (const char* prefix : {"highp uint ", "mediump uint ", "lowp uint ", "uint "}) {
|
||||
Bool promoted = false;
|
||||
for (const char* initializer : {" = 0u;", ";"}) {
|
||||
const String declaration = String(prefix) + name + initializer;
|
||||
const SizeT pos = source.find(declaration);
|
||||
if (pos == String::npos || (pos != 0 && source[pos - 1] != '\n')) continue;
|
||||
source.replace(pos, declaration.size(), UniformDeclaration(name));
|
||||
promoted = true;
|
||||
break;
|
||||
}
|
||||
if (promoted) break;
|
||||
}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
@@ -0,0 +1,80 @@
|
||||
// MobileGL - MobileGL/MG_Backend/DirectGLES/NarrowIntegerSeeds.h
|
||||
// Copyright (c) 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 "IntegerAttributePlan.h"
|
||||
|
||||
#include <Includes.h>
|
||||
#include <MG_Pipe/MGPipeHandles.h>
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
|
||||
// The program half of IntegerAttributePlan: the two width uniforms a program carries once some
|
||||
// draw has fed it 8/16-bit data of the other signedness, and the per-draw plan that drives them.
|
||||
//
|
||||
// A program is built with the seeds only when it needs them ("armed"), so a program that never
|
||||
// meets such data keeps its ESSL byte for byte. Arming is sticky for the program object's
|
||||
// lifetime, so data alternating in signedness rebuilds it at most once and from then on moves two
|
||||
// uniforms. A program that captures transform feedback and has integer inputs is armed at its
|
||||
// first build, so arming never relinks a program in the middle of a capture.
|
||||
//
|
||||
// Every failure falls back to the values the driver gives today: a build whose ESSL lacks either
|
||||
// uniform, or whose link dropped one, is "refused" and its draws keep the application's type for
|
||||
// 8/16-bit data; a build that fails outright disables arming for the object, and it is rebuilt
|
||||
// without the seeds.
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
struct NarrowIntegerSeedState {
|
||||
Bool wanted = false;
|
||||
Bool disabled = false;
|
||||
// What the last build ran with and what it verified.
|
||||
Bool built = false;
|
||||
Bool declared = false;
|
||||
Bool available = false;
|
||||
Int widths8Location = -1;
|
||||
Int widths16Location = -1;
|
||||
// The values the linked program holds; a link resets them to 0.
|
||||
Uint32 pushed8 = 0;
|
||||
Uint32 pushed16 = 0;
|
||||
|
||||
Bool Target() const { return wanted && !disabled; }
|
||||
// The program's next build has to change its arming.
|
||||
Bool Stale() const { return Target() != built; }
|
||||
// An armed build that could not give the program working seeds.
|
||||
Bool Refused() const { return disabled || (built && !available); }
|
||||
};
|
||||
|
||||
// The program's 32-bit int and uint vertex inputs, memoized on the record.
|
||||
IntegerVertexInputMasks IntegerVertexInputsOf(const MG_Pipe::MGPipeShaderCsoRecord& record);
|
||||
|
||||
// The draw's plan, from the program's record and the bound vertex-elements record. All zero for
|
||||
// a program without integer inputs, which costs two loads.
|
||||
IntegerAttributePlan ResolveIntegerAttributePlanForDraw(MG_Pipe::MGPipeHandle program);
|
||||
// After the program sync: true when this draw's build refused the seeds the plan counted on,
|
||||
// in which case the plan has dropped them and the vertex array must be synced again.
|
||||
Bool DropRefusedNarrowIntegerSeeds(IntegerAttributePlan& plan, MG_Pipe::MGPipeHandle program);
|
||||
// After Use(): the two widths on the program the draw runs, when they differ from what it holds.
|
||||
void PushNarrowIntegerWidthsForDraw(const IntegerAttributePlan& plan, MG_Pipe::MGPipeHandle program);
|
||||
|
||||
// The build's three steps: reset at its start, note each stage's final ESSL, verify after the link.
|
||||
void BeginNarrowIntegerSeedBuild(NarrowIntegerSeedState& state);
|
||||
void NoteNarrowIntegerSeedSource(NarrowIntegerSeedState& state, const String& source, GLenum shaderType);
|
||||
void FinishNarrowIntegerSeedBuild(NarrowIntegerSeedState& state, GLuint program, Bool programUsable);
|
||||
|
||||
// The armed vertex stage's SPIR-V step (after the array-input split): `spirv` itself unless
|
||||
// the pass rewrote it into `storage`.
|
||||
const Vector<unsigned int>* SeedNarrowIntegerVertexInputsForEssl(Bool armed, const Vector<unsigned int>& spirv,
|
||||
Vector<unsigned int>& storage,
|
||||
Bool enableSpirvValidation);
|
||||
|
||||
// The two width globals of a seeded vertex stage, as `uniform highp uint`.
|
||||
String PromoteNarrowIntegerWidthGlobals(String source);
|
||||
|
||||
namespace VertexArrayImpl {
|
||||
const MG_Pipe::MGPipeVertexElementsRecord* BoundVertexElementsRecord(const MG_Pipe::MGPipeApplierState& st);
|
||||
} // namespace VertexArrayImpl
|
||||
} // namespace MobileGL::MG_Backend::DirectGLES
|
||||
@@ -405,6 +405,16 @@ namespace MobileGL::MG_Pipe {
|
||||
};
|
||||
mutable Array<BackendHashMemoEntry, 4> BackendHashMemo{};
|
||||
mutable Uint32 BackendHashMemoNext = 0;
|
||||
// A backend's own derivation from the archive: the locations of its 32-bit int and uint
|
||||
// vertex inputs, valid while Serial holds the value it was taken at. It dies with the
|
||||
// record like the memo above, and nothing in MG_Pipe reads it.
|
||||
struct BackendIntegerInputsMemo {
|
||||
Uint64 Serial = 0;
|
||||
Uint32 SignedInputs = 0;
|
||||
Uint32 UnsignedInputs = 0;
|
||||
Bool Valid = false;
|
||||
};
|
||||
mutable BackendIntegerInputsMemo BackendIntegerInputs{};
|
||||
};
|
||||
|
||||
// The vertex-elements CSO as the applier holds it: the unpacked blob, both views, plus
|
||||
|
||||
@@ -74,6 +74,17 @@ add_executable(SubsystemDepsTest SubsystemDepsTest.cpp)
|
||||
target_include_directories(SubsystemDepsTest PRIVATE ${MGL_ROOT}/include ${MGL_ROOT}/MobileGL)
|
||||
target_link_libraries(SubsystemDepsTest PRIVATE MobileGLTestMain ${LINK_LIBRARIES})
|
||||
gtest_discover_tests(SubsystemDepsTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
|
||||
# DirectGLES's narrow integer attributes: the per-draw plan, and an armed vertex stage's ESSL.
|
||||
add_executable(IntegerAttributePlanTest IntegerAttributePlanTest.cpp)
|
||||
target_include_directories(IntegerAttributePlanTest PRIVATE ${MGL_ROOT}/include ${MGL_ROOT}/MobileGL)
|
||||
target_link_libraries(IntegerAttributePlanTest PRIVATE MobileGLTestMain ${LINK_LIBRARIES})
|
||||
gtest_discover_tests(IntegerAttributePlanTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
|
||||
add_executable(NarrowIntegerEsslTest NarrowIntegerEsslTest.cpp)
|
||||
target_include_directories(NarrowIntegerEsslTest PRIVATE ${MGL_ROOT}/include ${MGL_ROOT}/MobileGL)
|
||||
target_link_libraries(NarrowIntegerEsslTest PRIVATE MobileGLTestMain glslang-default-resource-limits ${LINK_LIBRARIES})
|
||||
gtest_discover_tests(NarrowIntegerEsslTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
gtest_discover_tests(EsslShaderPassTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
gtest_discover_tests(BaseInstanceInjectionTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
gtest_discover_tests(ViewportIndexRoutingTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
// MobileGL - MobileGL/MG_Test/Backend/DirectGLES/IntegerAttributePlanTest.cpp
|
||||
// Copyright (c) 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
|
||||
//
|
||||
// What DirectGLES does with each (array data type, shader input type) pair. ES defines an integer
|
||||
// attribute only when the array has the input's signedness, so every integer array is issued with
|
||||
// it; 8/16-bit data of the other signedness also needs the program's seeds to put GL's value back,
|
||||
// and without working seeds such an array keeps the application's type instead.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <MG_Backend/DirectGLES/IntegerAttributePlan.h>
|
||||
|
||||
using namespace MobileGL;
|
||||
using MobileGL::MG_Backend::DirectGLES::HeldIntegerRetype;
|
||||
using MobileGL::MG_Backend::DirectGLES::IntegerAttribTypeForInput;
|
||||
using MobileGL::MG_Backend::DirectGLES::IntegerAttributeDataMasks;
|
||||
using MobileGL::MG_Backend::DirectGLES::IntegerAttributePlan;
|
||||
using MobileGL::MG_Backend::DirectGLES::IntegerVertexInputMasks;
|
||||
using MobileGL::MG_Backend::DirectGLES::ResolveIntegerAttributePlan;
|
||||
|
||||
namespace {
|
||||
struct DataCase {
|
||||
DataType type;
|
||||
GLenum glType;
|
||||
Uint32 bits;
|
||||
Bool isSigned;
|
||||
};
|
||||
constexpr DataCase kData[] = {
|
||||
{DataType::Int8, GL_BYTE, 8, true}, {DataType::Uint8, GL_UNSIGNED_BYTE, 8, false},
|
||||
{DataType::Int16, GL_SHORT, 16, true}, {DataType::Uint16, GL_UNSIGNED_SHORT, 16, false},
|
||||
{DataType::Int32, GL_INT, 32, true}, {DataType::Uint32, GL_UNSIGNED_INT, 32, false},
|
||||
};
|
||||
|
||||
Bool IsSignedGLType(GLenum type) {
|
||||
return type == GL_BYTE || type == GL_SHORT || type == GL_INT;
|
||||
}
|
||||
|
||||
IntegerAttributePlan PlanFor(DataType data, GLenum input, Uint32 location, Bool seedsUsable = true) {
|
||||
IntegerVertexInputMasks inputs;
|
||||
inputs.Note(location, input);
|
||||
IntegerAttributeDataMasks masks;
|
||||
masks.Note(location, data);
|
||||
return ResolveIntegerAttributePlan(inputs, masks, seedsUsable);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(IntegerAttributePlan, EveryIntegerArrayIsIssuedWithItsInputsSignedness) {
|
||||
constexpr Uint32 location = 3;
|
||||
constexpr Uint32 bit = 1u << location;
|
||||
for (const DataCase& data : kData) {
|
||||
for (const GLenum input : {GL_INT_VEC4, GL_UNSIGNED_INT_VEC2}) {
|
||||
const Bool signedInput = input == GL_INT_VEC4;
|
||||
const IntegerAttributePlan plan = PlanFor(data.type, input, location);
|
||||
const Bool mismatch = signedInput != data.isSigned;
|
||||
SCOPED_TRACE(testing::Message() << "data 0x" << std::hex << data.glType << " input 0x" << input);
|
||||
EXPECT_EQ(plan.careMask, bit);
|
||||
EXPECT_EQ(plan.retypeMask, mismatch ? bit : 0u);
|
||||
EXPECT_EQ(IsSignedGLType(IntegerAttribTypeForInput(data.glType, (plan.retypeMask & bit) != 0)),
|
||||
signedInput);
|
||||
// Only 8/16-bit data of the other signedness needs the seeds; 32-bit is the same bits.
|
||||
EXPECT_EQ(plan.widths8, mismatch && data.bits == 8 ? bit : 0u);
|
||||
EXPECT_EQ(plan.widths16, mismatch && data.bits == 16 ? bit : 0u);
|
||||
EXPECT_EQ(plan.widths8 & plan.widths16, 0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(IntegerAttributePlan, AFloatInputLeavesItsArrayAlone) {
|
||||
for (const DataCase& data : kData) {
|
||||
const IntegerAttributePlan plan = PlanFor(data.type, GL_FLOAT_VEC4, 5);
|
||||
EXPECT_EQ(plan, IntegerAttributePlan{}) << std::hex << data.glType;
|
||||
}
|
||||
// ...and an integer input fed float data has no integer array to issue.
|
||||
EXPECT_EQ(PlanFor(DataType::Float32, GL_INT_VEC4, 5), IntegerAttributePlan{});
|
||||
}
|
||||
|
||||
TEST(IntegerAttributePlan, WithoutWorkingSeedsANarrowArrayKeepsTheApplicationsType) {
|
||||
for (const DataCase& data : kData) {
|
||||
const GLenum input = data.isSigned ? GL_UNSIGNED_INT : GL_INT;
|
||||
const IntegerAttributePlan plan = PlanFor(data.type, input, 7, false);
|
||||
EXPECT_FALSE(plan.NeedsSeeds()) << std::hex << data.glType;
|
||||
EXPECT_EQ(plan.retypeMask, data.bits == 32 ? (1u << 7) : 0u) << std::hex << data.glType;
|
||||
EXPECT_EQ(plan, PlanFor(data.type, input, 7).WithoutSeeds());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(IntegerAttributePlan, LocationsStopAt31) {
|
||||
EXPECT_EQ(PlanFor(DataType::Uint8, GL_INT, 31).widths8, 1u << 31);
|
||||
EXPECT_EQ(PlanFor(DataType::Uint8, GL_INT, 32), IntegerAttributePlan{});
|
||||
}
|
||||
|
||||
TEST(IntegerAttributePlan, AVertexArrayKeepsWhatItHoldsWhereNothingCanSeeIt) {
|
||||
IntegerAttributePlan plan;
|
||||
plan.careMask = 0b0110u;
|
||||
plan.retypeMask = 0b0100u;
|
||||
// Bit 0 is held and unobservable: kept. Bit 1 is observable and wanted plain: cleared.
|
||||
EXPECT_EQ(HeldIntegerRetype(0b0011u, plan), 0b0101u);
|
||||
EXPECT_EQ(HeldIntegerRetype(0u, IntegerAttributePlan{}), 0u);
|
||||
}
|
||||
|
||||
TEST(IntegerAttributePlan, TheIssuedTypeOnlyEverSwapsSignedness) {
|
||||
for (const DataCase& data : kData) {
|
||||
EXPECT_EQ(IntegerAttribTypeForInput(data.glType, false), data.glType);
|
||||
const GLenum swapped = IntegerAttribTypeForInput(data.glType, true);
|
||||
EXPECT_NE(IsSignedGLType(swapped), data.isSigned);
|
||||
EXPECT_EQ(IntegerAttribTypeForInput(swapped, true), data.glType);
|
||||
}
|
||||
EXPECT_EQ(IntegerAttribTypeForInput(GL_FLOAT, true), static_cast<GLenum>(GL_FLOAT));
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// MobileGL - MobileGL/MG_Test/Backend/DirectGLES/NarrowIntegerEsslTest.cpp
|
||||
// Copyright (c) 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
|
||||
//
|
||||
// A vertex stage armed with the narrow-integer seeds, through the same steps the DirectGLES build
|
||||
// takes: the array-input split, the RuntimeWidths pass, ESSL 3.00 emission and the uniform
|
||||
// promotion. The two widths have to come out as `uniform highp uint` (anything else is read
|
||||
// uninitialised or at too low a precision) and the result has to be ESSL 3.00 a compiler accepts.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <MG_Backend/DirectGLES/DirectGLES.h>
|
||||
#include <MG_Backend/DirectGLES/Managers.h>
|
||||
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
||||
#include <MG_Util/ShaderTranspiler/SpvcSession.h>
|
||||
#include <MG_Util/ShaderTranspiler/Types.h>
|
||||
|
||||
#include "Init.h"
|
||||
|
||||
#include <glslang/Public/ResourceLimits.h>
|
||||
#include <glslang/Public/ShaderLang.h>
|
||||
|
||||
using namespace MobileGL;
|
||||
using MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms;
|
||||
using MobileGL::MG_Backend::DirectGLES::PromoteNarrowIntegerWidthGlobals;
|
||||
using MobileGL::MG_Util::ShaderTranspiler::kNarrowIntegerWidths16GlobalName;
|
||||
using MobileGL::MG_Util::ShaderTranspiler::kNarrowIntegerWidths8GlobalName;
|
||||
using MobileGL::MG_Util::ShaderTranspiler::SessionUsageBit;
|
||||
using MobileGL::MG_Util::ShaderTranspiler::ShaderCompiler;
|
||||
using MobileGL::MG_Util::ShaderTranspiler::SpvcSession;
|
||||
|
||||
namespace {
|
||||
constexpr const char* kVertex = R"(#version 330 core
|
||||
layout(location = 0) in vec2 pos;
|
||||
layout(location = 1) in ivec4 lightAndData;
|
||||
layout(location = 2) in uvec2 texc;
|
||||
layout(location = 3) in int packed[2];
|
||||
flat out ivec4 a;
|
||||
flat out uvec4 b;
|
||||
void main() {
|
||||
a = lightAndData + ivec4(packed[0], packed[1], 0, 0);
|
||||
b = uvec4(texc, 0u, 0u);
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
}
|
||||
)";
|
||||
constexpr const char* kFragment = R"(#version 330 core
|
||||
flat in ivec4 a;
|
||||
flat in uvec4 b;
|
||||
out vec4 color;
|
||||
void main() { color = vec4(a) + vec4(b); }
|
||||
)";
|
||||
|
||||
Vector<Uint32> CompileVertexModule() {
|
||||
using namespace MG_Util::ShaderTranspiler;
|
||||
Vector<SharedPtr<glslang::TShader>> shaders;
|
||||
for (const auto& [stage, source] :
|
||||
Vector<Pair<GLenum, const char*>>{{GL_VERTEX_SHADER, kVertex}, {GL_FRAGMENT_SHADER, kFragment}}) {
|
||||
ShaderAttrib attrib{.shaderType = stage, .sourceStr = source};
|
||||
auto shader = ShaderCompiler::CompileShader(attrib);
|
||||
EXPECT_TRUE(shader) << (shader ? String{} : shader.error().log);
|
||||
if (!shader) return {};
|
||||
shaders.push_back(shader.value());
|
||||
}
|
||||
ProgramAttrib programAttrib{.shaders = shaders};
|
||||
auto program = ShaderCompiler::LinkProgram(programAttrib);
|
||||
EXPECT_TRUE(program) << (program ? String{} : program.error().log);
|
||||
if (!program) return {};
|
||||
ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER},
|
||||
.program = *program.value()};
|
||||
auto binary = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib);
|
||||
EXPECT_TRUE(binary) << (binary ? String{} : binary.error().log);
|
||||
if (!binary || binary->size() != 2) return {};
|
||||
Vector<Uint32> module = binary.value()[0];
|
||||
EXPECT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(module, module, true, true));
|
||||
return module;
|
||||
}
|
||||
|
||||
String EmitEssl300(const Vector<Uint32>& spirv) {
|
||||
SpvcSession session(spirv, SessionUsageBit::Transpile);
|
||||
spvc_compiler_options options;
|
||||
session.CreateOptions(&options);
|
||||
spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 300);
|
||||
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_TRUE);
|
||||
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE);
|
||||
session.SetOptions(options);
|
||||
const char* result = nullptr;
|
||||
session.Compile(&result);
|
||||
EXPECT_NE(result, nullptr) << (session.GetLastErrorString() ? session.GetLastErrorString() : "");
|
||||
return result != nullptr ? String(result) : String{};
|
||||
}
|
||||
|
||||
String CompileErrorAsEssl300Vertex(const String& source) {
|
||||
glslang::TShader shader(EShLangVertex);
|
||||
const char* strings[] = {source.c_str()};
|
||||
shader.setStrings(strings, 1);
|
||||
if (shader.parse(GetDefaultResources(), 300, EEsProfile, false, false, EShMsgDefault)) return {};
|
||||
return shader.getInfoLog();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(NarrowIntegerEssl, ArmedVertexStageDeclaresBothWidthsAsHighpUniformsInValidEssl300) {
|
||||
MobileGL::Initialize();
|
||||
const Vector<Uint32> module = CompileVertexModule();
|
||||
ASSERT_FALSE(module.empty());
|
||||
|
||||
Vector<Uint32> split;
|
||||
ASSERT_TRUE(ShaderCompiler::SplitArrayVertexInputsForEssl(module, split, true));
|
||||
Vector<Uint32> seeded;
|
||||
Bool changed = false;
|
||||
ASSERT_TRUE(
|
||||
ShaderCompiler::ExtendNarrowIntegerVertexInputsForEssl(split.empty() ? module : split, seeded, changed, true));
|
||||
ASSERT_TRUE(changed);
|
||||
|
||||
const String emitted = EmitEssl300(seeded);
|
||||
ASSERT_FALSE(emitted.empty());
|
||||
const String essl = PromoteDrawParameterGlobalsToUniforms(emitted, GL_VERTEX_SHADER);
|
||||
for (const char* name : {kNarrowIntegerWidths8GlobalName, kNarrowIntegerWidths16GlobalName}) {
|
||||
EXPECT_NE(essl.find(String("uniform highp uint ") + name + ";"), String::npos) << name << "\n" << essl;
|
||||
// Exactly one declaration, and it is the uniform.
|
||||
EXPECT_EQ(essl.find(String("uint ") + name), essl.find(String("highp uint ") + name) + 6) << essl;
|
||||
}
|
||||
for (const char* bitfield : {"bitfieldExtract", "bitfieldInsert"})
|
||||
EXPECT_EQ(essl.find(bitfield), String::npos) << essl;
|
||||
EXPECT_EQ(CompileErrorAsEssl300Vertex(essl), String{}) << essl;
|
||||
}
|
||||
|
||||
TEST(NarrowIntegerEssl, OnlyALineStartingDeclarationIsPromoted) {
|
||||
const String promoted = PromoteNarrowIntegerWidthGlobals("#version 300 es\n"
|
||||
"uint mg_NarrowIntegerWidths8 = 0u;\n"
|
||||
"highp uint mg_NarrowIntegerWidths16;\n"
|
||||
"void main() { uint x = mg_NarrowIntegerWidths8; }\n");
|
||||
EXPECT_EQ(promoted, "#version 300 es\n"
|
||||
"uniform highp uint mg_NarrowIntegerWidths8;\n"
|
||||
"uniform highp uint mg_NarrowIntegerWidths16;\n"
|
||||
"void main() { uint x = mg_NarrowIntegerWidths8; }\n");
|
||||
// Already a uniform: untouched, and a mediump spelling is promoted to highp all the same.
|
||||
EXPECT_EQ(PromoteNarrowIntegerWidthGlobals(promoted), promoted);
|
||||
EXPECT_EQ(PromoteNarrowIntegerWidthGlobals("mediump uint mg_NarrowIntegerWidths16;\n"),
|
||||
"uniform highp uint mg_NarrowIntegerWidths16;\n");
|
||||
}
|
||||
@@ -1095,6 +1095,11 @@ TEST_F(TranslationCacheTest, L2KeyMovesWithEveryGateThatSteersTheEsslChain) {
|
||||
v.stripOutputBlockLocations = true;
|
||||
variants.emplace_back("stripOutputBlockLocations", BuildEsslTranslationKey(v));
|
||||
}
|
||||
{ // arms the narrow-integer seeds of a vertex stage (ExtendNarrowIntegerVertexInputsForEssl)
|
||||
EsslTranslationKeyInputs v = base;
|
||||
v.narrowIntegerSeedsArmed = true;
|
||||
variants.emplace_back("narrowIntegerSeedsArmed", BuildEsslTranslationKey(v));
|
||||
}
|
||||
{
|
||||
EsslTranslationKeyInputs v = base;
|
||||
v.enableSpirvValidation = true;
|
||||
|
||||
@@ -208,6 +208,7 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||
builder.StringMap(inputs.outputBlockRenames ? *inputs.outputBlockRenames : kEmptyRenames);
|
||||
builder.Value(static_cast<Uint8>(inputs.stripInputBlockLocations));
|
||||
builder.Value(static_cast<Uint8>(inputs.stripOutputBlockLocations));
|
||||
builder.Value(static_cast<Uint8>(inputs.narrowIntegerSeedsArmed));
|
||||
static const Vector<Uint32> kEmptyWords;
|
||||
builder.Words(inputs.spirv ? *inputs.spirv : kEmptyWords);
|
||||
return MakeTranslationCacheKey(builder);
|
||||
|
||||
@@ -670,6 +670,10 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
||||
// other end is in a different program must keep its location.
|
||||
Bool stripInputBlockLocations = false;
|
||||
Bool stripOutputBlockLocations = false;
|
||||
// Whether this (vertex) stage carries the narrow-integer seeds - a per-program arming, set
|
||||
// only once some draw fed the program 8/16-bit data of the other signedness, which changes
|
||||
// the emitted ESSL of the same module.
|
||||
Bool narrowIntegerSeedsArmed = false;
|
||||
|
||||
// The top of the reserved storage-block window atomic-counter blocks are moved into
|
||||
// (`top - N` for GL binding N). Derived from the driver's
|
||||
|
||||
Reference in New Issue
Block a user