mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
190 lines
9.8 KiB
C++
190 lines
9.8 KiB
C++
// 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;
|
|
}
|
|
|
|
// Whether the program's build for `record`'s link refused the seeds; a link not built yet
|
|
// is presumed to take them.
|
|
Bool SeedsRefused(MG_Pipe::MGPipeHandle program, const MG_Pipe::MGPipeShaderCsoRecord& record) {
|
|
auto* twin = ExistingProgramTwin(program);
|
|
return twin != nullptr && twin->NarrowIntegerSeeds().RefusedAt(record.Serial);
|
|
}
|
|
|
|
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));
|
|
}
|
|
// The twin is looked up only for a plan that needs seeds; most integer draws need none.
|
|
const IntegerAttributePlan plan = ResolveIntegerAttributePlan(inputs, data, true);
|
|
return plan.NeedsSeeds() && SeedsRefused(program, *record) ? plan.WithoutSeeds() : plan;
|
|
}
|
|
|
|
Bool DropRefusedNarrowIntegerSeeds(IntegerAttributePlan& plan, MG_Pipe::MGPipeHandle program) {
|
|
if (!plan.NeedsSeeds()) return false;
|
|
const MG_Pipe::MGPipeShaderCsoRecord* const record = PipeShaderCsoRecordForHandle(program);
|
|
if (record == nullptr || !SeedsRefused(program, *record)) return false;
|
|
plan = plan.WithoutSeeds();
|
|
return true;
|
|
}
|
|
|
|
void PrepareNarrowIntegerSeedBuild(NarrowIntegerSeedState& state, const MG_Pipe::MGPipeShaderCsoRecord& record) {
|
|
const IntegerVertexInputMasks inputs = IntegerVertexInputsOf(record);
|
|
state.PrepareBuild(record.Serial, (inputs.signedInputs | inputs.unsignedInputs) != 0,
|
|
record.Archive && !record.Archive->Link.xfbVaryings.empty());
|
|
}
|
|
|
|
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 of this link 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-initialized 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
|