mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Feat] (MG_State, MG_Impl, MG_Backend): let a bound program pipeline actually draw
The pipeline object bookkeeping landed already - names, stage slots, queries - but nothing consumed it. Every draw asked the context for the current program, got null because a pipeline is used with program zero, and drew nothing; glCreateShaderProgramv was still a stub returning zero, so direct_state_access.program_pipelines_functional could not even build its stage programs and reported InternalError on both backends. glCreateShaderProgramv is written as the exact call sequence the spec defines it to be, with one deviation that matters: the link goes straight to ProgramObject::Link(false) rather than through LinkProgram, because LinkProgram injects a default fragment shader into a program that has none - correct for a whole program, wrong for a separable vertex-stage one whose fragment stage comes from the pipeline. glDetachShader defers removal to the next link, so the program keeps the shader object it was built from while correctly no longer reporting it attached. GL_PROGRAM_SEPARABLE joins glProgramParameteri and glGetProgramiv. Everything downstream of a draw - both backends, the uniform plumbing, the draw validation - is written against one linked program, so rather than teach all of it about stages, the pipeline is flattened: GetProgramForDraw() composites the stage programs' shaders into a single hidden program object and caches it against a signature of each stage program's lifetime id and link generation, so it is rebuilt exactly when a stage or a stage's link changes. The composite carries no GL name - it must not answer glIsProgram, and it must not consume a name the application could be handed. Uniform entry points get their own resolver rather than sharing that one: glUniform* addresses the pipeline's active program, not the composited draw program. GL_CURRENT_PROGRAM still reads the program in use, which is zero here. Fixes program_pipelines_functional on both backends.
This commit is contained in:
@@ -343,6 +343,55 @@ namespace MobileGL::MG_State {
|
||||
return m_programState.GetCurrentProgram();
|
||||
}
|
||||
|
||||
const SharedPtr<ProgramObject>& GLContext::GetProgramForDraw() {
|
||||
static const SharedPtr<ProgramObject> nullProgram = nullptr;
|
||||
const auto& currentProgram = m_programState.GetCurrentProgram();
|
||||
if (currentProgram) return currentProgram;
|
||||
if (m_boundProgramPipeline == 0) return nullProgram;
|
||||
const auto& pipeline = GetBoundProgramPipeline();
|
||||
if (!pipeline) return nullProgram;
|
||||
|
||||
const auto signature = pipeline->ComputeDrawProgramSignature();
|
||||
if (const auto& cached = pipeline->GetCachedDrawProgram(signature)) return cached;
|
||||
|
||||
// Everything downstream of here - the backends, the uniform plumbing, the draw
|
||||
// validation - is written against a single linked program, so the pipeline is
|
||||
// flattened into one. Each stage contributes only the shaders that serve it, so a
|
||||
// program bound to two stages is not pulled in twice and a program bound to a
|
||||
// stage it does not implement contributes nothing.
|
||||
// Deliberately not a named program: it is reachable only through the pipeline, it
|
||||
// must not answer glIsProgram, and it must not consume a name the application
|
||||
// could otherwise be handed. Backend registries key on the object, not the name.
|
||||
auto composite = MakeShared<ProgramObject>(0u);
|
||||
|
||||
Bool anyStage = false;
|
||||
for (SizeT stage = 0; stage < static_cast<SizeT>(ShaderStage::ShaderStageCount); ++stage) {
|
||||
const auto& stageProgram = pipeline->GetStageProgram(static_cast<ShaderStage>(stage));
|
||||
if (!stageProgram) continue;
|
||||
for (const auto& shader : stageProgram->GetAttachedShaders()) {
|
||||
if (!shader || static_cast<SizeT>(shader->GetShaderStage()) != stage) continue;
|
||||
composite->AttachShader(shader);
|
||||
anyStage = true;
|
||||
}
|
||||
}
|
||||
if (!anyStage) return nullProgram;
|
||||
// A pipeline with no fragment stage still rasterises, so the default fragment
|
||||
// shader is wanted here even though the separable stage programs never get one.
|
||||
composite->Link(true);
|
||||
pipeline->SetCachedDrawProgram(signature, Move(composite));
|
||||
return pipeline->GetCachedDrawProgram(signature);
|
||||
}
|
||||
|
||||
const SharedPtr<ProgramObject>& GLContext::GetProgramForUniform() {
|
||||
const auto& currentProgram = m_programState.GetCurrentProgram();
|
||||
if (currentProgram) return currentProgram;
|
||||
static const SharedPtr<ProgramObject> nullProgram = nullptr;
|
||||
if (m_boundProgramPipeline == 0) return nullProgram;
|
||||
const auto& pipeline = GetBoundProgramPipeline();
|
||||
if (!pipeline) return nullProgram;
|
||||
return pipeline->GetActiveProgram();
|
||||
}
|
||||
|
||||
// RenderState
|
||||
Uint GLContext::GetRenderStateParametersVersion() const {
|
||||
return m_renderState.GetVersion();
|
||||
|
||||
@@ -137,6 +137,12 @@ namespace MobileGL {
|
||||
const SharedPtr<ShaderObject>& GetShaderObject(Uint index);
|
||||
void UseProgram(Uint program);
|
||||
const SharedPtr<ProgramObject>& GetCurrentProgram();
|
||||
// What a draw or dispatch actually executes: the program in use, or - when
|
||||
// there is none - the bound pipeline's stages composited into one program.
|
||||
const SharedPtr<ProgramObject>& GetProgramForDraw();
|
||||
// What glUniform* addresses: the program in use, or the bound pipeline's
|
||||
// active program (GL 4.6 core 7.6.1).
|
||||
const SharedPtr<ProgramObject>& GetProgramForUniform();
|
||||
|
||||
// Program pipeline (GL_ARB_separate_shader_objects, GL 4.6 core 7.4). Like queries
|
||||
// and transform feedbacks, glGenProgramPipelines only RESERVES a name - the object
|
||||
|
||||
@@ -45,6 +45,13 @@ namespace MobileGL::MG_State::GLState {
|
||||
Vector<SharedPtr<ShaderObject>>& GetAttachedShaders();
|
||||
const Vector<SharedPtr<ShaderObject>>& GetAttachedShaders() const;
|
||||
const String& GetInfoLog() const { return m_infoLog; }
|
||||
// glCreateShaderProgramv folds the shader's compile log into the program's log, which
|
||||
// is the only place a caller can read it from once the shader name is gone.
|
||||
void AppendInfoLog(const String& text) {
|
||||
if (text.empty()) return;
|
||||
if (!m_infoLog.empty() && m_infoLog.back() != '\n') m_infoLog += '\n';
|
||||
m_infoLog += text;
|
||||
}
|
||||
Int GetUniformMaxLength() const { return m_uniformNameMaxLength; }
|
||||
Uint GetUniformCount() const { return m_activeUniformCount; }
|
||||
Uint GetMaxUniformLocation() const { return m_maxUniformLocation; }
|
||||
@@ -382,6 +389,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
// ARB_get_program_binary requires of it.
|
||||
Bool GetBinaryRetrievableHint() const { return m_binaryRetrievableHint; }
|
||||
void SetBinaryRetrievableHint(Bool hint) { m_binaryRetrievableHint = hint; }
|
||||
// GL_PROGRAM_SEPARABLE (GL_ARB_separate_shader_objects): the program may supply a
|
||||
// subset of the stages of a program pipeline. Only takes effect on the next link,
|
||||
// which is why it is plain state here rather than something Link() consults.
|
||||
Bool GetSeparable() const { return m_separable; }
|
||||
void SetSeparable(Bool separable) { m_separable = separable; }
|
||||
// glProgramBinary always fails here (there is no format it could accept) and the
|
||||
// spec then requires the program's LINK_STATUS to read FALSE.
|
||||
void MarkLinkFailedByProgramBinary() {
|
||||
@@ -605,6 +617,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
Bool m_deleteStatus = false;
|
||||
Bool m_linkStatus = false;
|
||||
Bool m_binaryRetrievableHint = false;
|
||||
Bool m_separable = false;
|
||||
Bool m_validateStatus = true;
|
||||
Uint32 m_backendStateVersion = 0;
|
||||
|
||||
|
||||
@@ -40,9 +40,40 @@ namespace MobileGL {
|
||||
|
||||
Uint GetExternalIndex() const { return m_externalIndex; }
|
||||
|
||||
// A draw sees one program, but a pipeline holds one program per stage. The
|
||||
// stages are composited into a single hidden program object, rebuilt whenever
|
||||
// the stage set - or any stage program's own link - changes. The signature is
|
||||
// what that "changes" means: a stage program's lifetime id pins the object and
|
||||
// its backend state version pins the link generation.
|
||||
using DrawProgramSignature =
|
||||
Array<Uint64, static_cast<SizeT>(ShaderStage::ShaderStageCount) * 2>;
|
||||
|
||||
DrawProgramSignature ComputeDrawProgramSignature() const {
|
||||
DrawProgramSignature signature{};
|
||||
for (SizeT stage = 0; stage < static_cast<SizeT>(ShaderStage::ShaderStageCount); ++stage) {
|
||||
const auto& program = m_stagePrograms[stage];
|
||||
if (!program) continue;
|
||||
signature[stage * 2] = program->GetLifetimeId();
|
||||
signature[stage * 2 + 1] = program->GetBackendStateVersion();
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
const SharedPtr<ProgramObject>& GetCachedDrawProgram(const DrawProgramSignature& signature) const {
|
||||
static const SharedPtr<ProgramObject> nullProgram = nullptr;
|
||||
if (!m_drawProgram || m_drawProgramSignature != signature) return nullProgram;
|
||||
return m_drawProgram;
|
||||
}
|
||||
void SetCachedDrawProgram(const DrawProgramSignature& signature, SharedPtr<ProgramObject> program) {
|
||||
m_drawProgramSignature = signature;
|
||||
m_drawProgram = Move(program);
|
||||
}
|
||||
|
||||
private:
|
||||
Array<SharedPtr<ProgramObject>, static_cast<SizeT>(ShaderStage::ShaderStageCount)> m_stagePrograms{};
|
||||
SharedPtr<ProgramObject> m_activeProgram;
|
||||
SharedPtr<ProgramObject> m_drawProgram;
|
||||
DrawProgramSignature m_drawProgramSignature{};
|
||||
String m_infoLog;
|
||||
const Uint m_externalIndex = 0;
|
||||
Bool m_validateStatus = false;
|
||||
|
||||
Reference in New Issue
Block a user