mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Fix] (MG_State, MG_Backend/DirectGLES): join both link phases on the draw and drain paths, and refuse to bind a program whose SPIR-V never arrived
This commit is contained in:
@@ -380,8 +380,14 @@ namespace MobileGL::MG_State {
|
||||
// inside the same draw when it finally touched an artifact, and cache under a
|
||||
// version the publish had already superseded. Settling here means every
|
||||
// version a backend reads during a draw describes the program it is drawing.
|
||||
// One null check in steady state.
|
||||
currentProgram->JoinLink();
|
||||
// Two null checks in steady state.
|
||||
//
|
||||
// BOTH phases, and that is not optional: the phase-B publish bumps those same
|
||||
// versions, so joining only phase A here would leave exactly the hazard this
|
||||
// site exists to close - a backend samples a version, then trips the phase-B
|
||||
// gate through GetGeneratedSpirv() deeper inside the same draw, and memoizes
|
||||
// under a version the publish has already superseded.
|
||||
currentProgram->JoinLinkAndSpirv();
|
||||
return currentProgram;
|
||||
}
|
||||
if (m_boundProgramPipeline == 0) return nullProgram;
|
||||
@@ -398,7 +404,7 @@ namespace MobileGL::MG_State {
|
||||
// programs. In steady state this is a null check per stage.
|
||||
for (SizeT stage = 0; stage < static_cast<SizeT>(ShaderStage::ShaderStageCount); ++stage) {
|
||||
const auto& stageProgram = pipeline->GetStageProgram(static_cast<ShaderStage>(stage));
|
||||
if (stageProgram) stageProgram->JoinLink();
|
||||
if (stageProgram) stageProgram->JoinLinkAndSpirv();
|
||||
}
|
||||
|
||||
const auto signature = pipeline->ComputeDrawProgramSignature();
|
||||
@@ -430,8 +436,9 @@ namespace MobileGL::MG_State {
|
||||
composite->Link(true);
|
||||
// P1 join site J2. The draw that asked for this program is the very next thing to
|
||||
// happen, so enqueueing the composite's link buys nothing and only moves the wait
|
||||
// to whichever backend accessor happens to touch its artifacts first.
|
||||
composite->JoinLink();
|
||||
// to whichever backend accessor happens to touch its artifacts first. Both phases,
|
||||
// for the same reason: the backend is about to read its SPIR-V.
|
||||
composite->JoinLinkAndSpirv();
|
||||
pipeline->SetCachedDrawProgram(signature, Move(composite));
|
||||
return pipeline->GetCachedDrawProgram(signature);
|
||||
}
|
||||
|
||||
@@ -111,9 +111,13 @@ namespace MobileGL::MG_State::GLState {
|
||||
// that can grow, and a reallocation underneath this loop would be a use-after-free
|
||||
// that only shows up on the one GL call that walks the whole table. The copy costs a
|
||||
// refcount bump on a path a mode switch takes at most once.
|
||||
// BOTH phases per program. This is the glMaxShaderCompilerThreadsKHR(0) path, whose
|
||||
// contract is that nothing is outstanding when it returns - a program left with its
|
||||
// SPIR-V job in flight would make the very next GL_COMPLETION_STATUS_KHR read GL_FALSE
|
||||
// in a mode the extension says cannot have anything pending.
|
||||
for (SizeT i = 0; i < m_programObjects.size(); ++i) {
|
||||
const SharedPtr<ProgramObject> program = m_programObjects[i];
|
||||
if (program) program->JoinLink();
|
||||
if (program) program->JoinLinkAndSpirv();
|
||||
}
|
||||
for (SizeT i = 0; i < m_shaderObjects.size(); ++i) {
|
||||
const SharedPtr<ShaderObject> shader = m_shaderObjects[i];
|
||||
@@ -122,7 +126,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
// The currently-used program is reachable through m_programObjects unless
|
||||
// glDeleteProgram already freed its slot while it stayed current. Nothing else holds
|
||||
// a GL-visible name for it, but a draw would still join it, so settle it here too.
|
||||
if (m_currentProgram) m_currentProgram->JoinLink();
|
||||
if (m_currentProgram) m_currentProgram->JoinLinkAndSpirv();
|
||||
}
|
||||
|
||||
void ProgramState::MarkShaderObjectForDeletion(Uint shader) {
|
||||
|
||||
Reference in New Issue
Block a user