Files
MobileGL/MobileGL/MG_State/GLState/Core.h
T
BZLZHH 4ce808b9f2 [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.
2026-08-05 07:20:42 -04:00

437 lines
26 KiB
C++

// MobileGL - MobileGL/MG_State/GLState/Core.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 "ErrorState/Error.h"
#include "BufferState/BufferState.h"
#include "MG_State/GLState/RenderbufferState/RenderbufferObject.h"
#include "MG_State/GLState/RenderbufferState/RenderbufferState.h"
#include "RenderState/RenderState.h"
#include "ProgramState/ProgramState.h"
#include "ProgramState/ProgramPipelineObject.h"
#include "SamplerState/SamplerState.h"
#include "TextureState/TextureState.h"
#include "FramebufferState/FramebufferState.h"
#include "VertexArrayState/VertexArrayState.h"
#include "RenderbufferState/RenderbufferState.h"
namespace MobileGL {
namespace MG_State {
void Init();
namespace GLState {
struct CurrentVertexAttributeValue {
Array<Float, 4> floatValue{0.f, 0.f, 0.f, 1.f};
Array<Int32, 4> intValue{0, 0, 0, 1};
Array<Uint32, 4> uintValue{0u, 0u, 0u, 1u};
};
// Which of the three views above a shader input of a given GLSL type consumes.
enum class VertexAttribBaseType { Unsupported, Float, Int, Uint };
struct VertexAttribTypeInfo {
VertexAttribBaseType baseType = VertexAttribBaseType::Unsupported;
Uint componentCount = 0;
};
// Maps a shader vertex-input type (GL_FLOAT_VEC3, GL_INT_VEC2, ...) onto the current-value
// view that feeds it. Shared by every backend so that "a disabled array reads the current
// value" resolves identically regardless of which backend is active; each backend only
// translates the result into its own API call.
VertexAttribTypeInfo ClassifyVertexAttribType(GLenum glType);
// One indexed capture binding of a transform feedback object, as the by-name queries
// report it. An empty Buffer means the binding point is unbound.
struct NamedTransformFeedbackBinding {
SharedPtr<BufferObject> Buffer;
Range1D Range{};
Bool HasExplicitRange = false;
};
class GLContext {
public:
GLContext() = default;
// Error
void RecordError(ErrorCode code, UniquePtr<ErrorInfo> info);
Bool HasGLError() const;
Optional<const Error*> PeekNonGLError() const;
Optional<UniquePtr<Error>> PopNonGLError();
Bool HasNonGLError() const;
Optional<const Error*> PeekGLError() const;
Optional<UniquePtr<Error>> PopGLError();
void ClearErrors();
// Buffer
void GenBufferNames(Uint number, Vector<Uint>& buffers);
const SharedPtr<BufferObject>& GetBufferObject(Uint index);
BindingSlot<BufferObject>& GetBufferBindingSlot(BufferTarget target);
BindingSlotRange1D<BufferObject>& GetBufferBindingPoint(BufferTarget target, Uint index);
constexpr SizeT GetBufferBindingPointCount(BufferTarget target) const {
return m_bufferState.GetBindingPointCount(target);
}
void TouchBufferBindingPoint(BufferTarget target, Uint index) {
m_bufferState.TouchBindPoint(target, index);
}
SizeT GetTouchedBufferBindingPointCount(BufferTarget target) const {
return m_bufferState.GetTouchedBindPointCount(target);
}
const SharedPtr<BufferObject>& CreateBufferObject(Uint index);
void MarkBufferObjectForDeletion(Uint index);
Bool ValidateBufferName(Uint index) const;
Bool ValidateBufferObject(Uint index) const;
// VertexArray
void GenVertexArrayNames(Uint number, Vector<Uint>& vertexArrays);
const SharedPtr<VertexArrayObject>& GetVertexArrayObject(Uint index);
void BindVertexArray(Uint index);
const SharedPtr<VertexArrayObject>& CreateVertexArrayObject(Uint index);
void MarkVertexArrayForDeletion(Uint index);
Bool ValidateVertexArrayName(Uint index) const;
Bool ValidateVertexArrayObject(Uint index) const;
const SharedPtr<VertexArrayObject>& GetBoundVertexArray();
void SetCurrentVertexAttributeFloat(Uint index, const Array<Float, 4>& value);
void SetCurrentVertexAttributeInt(Uint index, const Array<Int32, 4>& value);
void SetCurrentVertexAttributeUint(Uint index, const Array<Uint32, 4>& value);
const CurrentVertexAttributeValue& GetCurrentVertexAttribute(Uint index) const;
// Texture
void GenTextureNames(Uint number, Vector<Uint>& textures);
const SharedPtr<ITextureObject>& GetTextureObject(Uint index);
// Per-target default texture object (name 0); see TextureState::GetDefaultTextureObject.
const SharedPtr<ITextureObject>& GetDefaultTextureObject(TextureTarget target) const;
const SharedPtr<ITextureObject>& CreateTextureObject(Uint index, TextureTarget target);
void MarkTextureObjectForDeletion(Uint index);
TextureUnit& GetTextureUnitObject(Int unit);
ImageTextureBinding& GetImageTextureBinding(Int unit);
const ImageTextureBinding& GetImageTextureBinding(Int unit) const;
void NoteTextureUnitTouched(Int unit) { m_textureState.NoteUnitTouched(unit); }
Int GetMaxTouchedTextureUnit() const { return m_textureState.GetMaxTouchedUnit(); }
// Monotonic counter bumped whenever a texture bind/unbind/delete changes which
// texture is bound at a unit; lets a backend skip re-resolving an unchanged
// per-draw sampled-texture set.
Uint64 GetTextureBindGeneration() const { return m_textureState.GetTextureBindGeneration(); }
void BumpTextureBindGeneration() { m_textureState.BumpTextureBindGeneration(); }
Bool ValidateTextureName(Uint index) const;
Bool ValidateTextureObject(Uint index) const;
Int GetActiveTextureUnit() const;
void SetActiveTextureUnit(Int unit);
// Program
Uint CreateProgram();
Uint CreateShader(ShaderStage stage);
void MarkProgramForDeletion(Uint index);
void MarkShaderForDeletion(Uint index);
// Frees a deletion-flagged shader's name once it lost its last GL-visible
// attachment (call after glDetachShader).
void ReleaseShaderNameIfOrphaned(Uint index);
Bool ValidateProgramName(Uint index) const;
Bool ValidateShaderName(Uint index) const;
const SharedPtr<ProgramObject>& GetProgramObject(Uint index);
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
// appears on first bind - while glCreateProgramPipelines makes it immediately.
void GenProgramPipelineNames(Uint number, Vector<Uint>& pipelines);
void CreateProgramPipelineObject(Uint index);
Bool ValidateProgramPipelineName(Uint index) const;
Bool IsProgramPipelineObject(Uint index) const;
void BindProgramPipelineObject(Uint index);
void MarkProgramPipelineForDeletion(Uint index);
const SharedPtr<ProgramPipelineObject>& GetProgramPipelineObject(Uint index) const;
Uint GetBoundProgramPipelineName() const { return m_boundProgramPipeline; }
const SharedPtr<ProgramPipelineObject>& GetBoundProgramPipeline() const;
// RenderState
Uint GetRenderStateParametersVersion() const;
const RenderStateParameters& GetRenderStateParameters() const;
void SetViewport(IntVec4 viewport); // x, y, width, height
const IntVec4& GetViewport() const; // x, y, width, height
void SetLineWidth(Float width);
Float GetLineWidth() const;
void SetPointSize(Float size);
Float GetPointSize() const;
void SetPatchVertices(Uint vertices);
Uint GetPatchVertices() const;
void SetPolygonOffset(Float factor, Float units);
Float GetPolygonOffsetFactor() const;
Float GetPolygonOffsetUnits() const;
void SetHint(GLenum target, GLenum mode);
GLenum GetHint(GLenum target) const;
void SetPointFadeThresholdSize(Float size);
Float GetPointFadeThresholdSize() const;
void SetPointSpriteCoordOrigin(GLenum origin);
GLenum GetPointSpriteCoordOrigin() const;
void SetClampReadColor(GLenum clamp);
GLenum GetClampReadColor() const;
void SetPolygonMode(GLenum front, GLenum back);
GLenum GetPolygonModeFront() const;
GLenum GetPolygonModeBack() const;
void SetPrimitiveRestartIndex(Uint32 index);
Uint32 GetPrimitiveRestartIndex() const;
void SetCapability(CapabilityInput cap, Bool enabled);
Bool IsCapabilityEnabled(CapabilityInput cap) const;
void SetCapabilityIndexed(CapabilityInput cap, Uint index, Bool enabled);
Bool IsCapabilityEnabledIndexed(CapabilityInput cap, Uint index) const;
void SetBlendFunc(BlendFactor srcRGB, BlendFactor dstRGB, BlendFactor srcAlpha, BlendFactor dstAlpha);
void GetBlendFunc(BlendFactor& srcRGB, BlendFactor& dstRGB, BlendFactor& srcAlpha,
BlendFactor& dstAlpha) const;
void SetBlendFuncIndexed(Uint index, BlendFactor srcRGB, BlendFactor dstRGB, BlendFactor srcAlpha,
BlendFactor dstAlpha);
void GetBlendFuncIndexed(Uint index, BlendFactor& srcRGB, BlendFactor& dstRGB, BlendFactor& srcAlpha,
BlendFactor& dstAlpha) const;
void SetBlendEquation(BlendEquation color, BlendEquation alpha);
void GetBlendEquation(BlendEquation& color, BlendEquation& alpha) const;
void SetBlendEquationIndexed(Uint index, BlendEquation color, BlendEquation alpha);
void GetBlendEquationIndexed(Uint index, BlendEquation& color, BlendEquation& alpha) const;
void SetLogicOp(LogicOperation logicOp);
LogicOperation GetLogicOp() const;
void SetDepthFunc(DepthTestFunc func);
DepthTestFunc GetDepthFunc() const;
void SetDepthMask(Bool flag);
Bool GetDepthMask() const;
void SetStencilFunc(StencilFace face, DepthTestFunc func, Int ref, Uint32 mask);
void SetStencilMask(StencilFace face, Uint32 mask);
void SetStencilOp(StencilFace face, StencilOperation fail, StencilOperation depthFail,
StencilOperation depthPass);
const StencilFaceState& GetStencilState(StencilFace face) const;
void SetColorMask(BoolVec4 mask);
BoolVec4 GetColorMask() const;
void SetColorMaskIndexed(Uint index, BoolVec4 mask);
BoolVec4 GetColorMaskIndexed(Uint index) const;
void SetClearColor(FloatVec4 color);
const FloatVec4& GetClearColor() const;
void SetClearDepth(Float depth);
Float GetClearDepth() const;
void SetClearStencil(Int stencil);
Uint32 GetClearStencil() const;
void SetBlendColor(FloatVec4 color);
const FloatVec4& GetBlendColor() const;
void SetDepthRange(FloatVec2 range);
const FloatVec2& GetDepthRange() const;
void SetSampleCoverage(Float value, Bool invert);
Float GetSampleCoverageValue() const;
Bool GetSampleCoverageInvert() const;
void SetSampleMaskValue(Uint32 mask);
Uint32 GetSampleMaskValue() const;
void SetPixelStoreParam(PixelStoreParam param, Int value);
Int GetPixelStoreParam(PixelStoreParam param) const;
PixelStoreParameters GetPixelStoreParameters(Bool isUnpack) const;
void SetCullFaceMode(CullFaceMode mode);
CullFaceMode GetCullFaceMode() const;
void SetFrontFaceMode(FrontFaceMode mode);
FrontFaceMode GetFrontFaceMode() const;
void SetProvokingVertexMode(ProvokingVertexMode mode);
ProvokingVertexMode GetProvokingVertexMode() const;
void SetScissorBox(IntVec4 box); // x, y, width, height
const IntVec4& GetScissorBox() const; // x, y, width, height
// Transform feedback. The fields below are the state of the transform
// feedback object currently bound to GL_TRANSFORM_FEEDBACK; see the object
// block further down for how a bind swaps them.
void BeginTransformFeedback(GLenum primitiveMode, const SharedPtr<ProgramObject>& program) {
m_transformFeedbackActive = true;
m_transformFeedbackPaused = false;
m_transformFeedbackPrimitiveMode = primitiveMode;
m_transformFeedbackProgram = program;
m_transformFeedbackGeneration = ++m_transformFeedbackNextGeneration;
m_transformFeedbackCapturedVertices = 0;
m_transformFeedbackInputPrimitives = 0;
}
void EndTransformFeedback() {
m_transformFeedbackActive = false;
m_transformFeedbackPaused = false;
m_transformFeedbackProgram.reset();
// What glDrawTransformFeedback on this object replays from now on.
auto& object = m_transformFeedbackObjects[m_boundTransformFeedback];
object.recordedVertices = m_transformFeedbackCapturedVertices;
object.hasCompletedSpan = true;
}
Bool IsTransformFeedbackActive() const { return m_transformFeedbackActive; }
Bool IsTransformFeedbackPaused() const { return m_transformFeedbackPaused; }
void SetTransformFeedbackPaused(Bool paused) { m_transformFeedbackPaused = paused; }
GLenum GetTransformFeedbackPrimitiveMode() const { return m_transformFeedbackPrimitiveMode; }
const SharedPtr<ProgramObject>& GetTransformFeedbackProgram() const {
return m_transformFeedbackProgram;
}
// Bumped on every BeginTransformFeedback; the backend uses it to
// distinguish "resume appending" from "fresh capture".
Uint64 GetTransformFeedbackGeneration() const { return m_transformFeedbackGeneration; }
// CPU-side primitive accounting for the transform feedback queries:
// every captured draw adds its primitive count (draws without a
// geometry stage write exactly what they generate).
void AddTransformFeedbackPrimitives(Uint64 primitives) {
m_transformFeedbackPrimitiveCounter += primitives;
}
Uint64 GetTransformFeedbackPrimitiveCounter() const { return m_transformFeedbackPrimitiveCounter; }
// Primitives a draw assembled while the capture was paused. GL counts those in
// PRIMITIVES_GENERATED, but a backend that answers the query with its own
// transform feedback counter cannot see them - nothing was being captured.
void AddTransformFeedbackPausedPrimitives(Uint64 primitives) {
m_transformFeedbackPausedPrimitiveCounter += primitives;
}
Uint64 GetTransformFeedbackPausedPrimitiveCounter() const {
return m_transformFeedbackPausedPrimitiveCounter;
}
// Vertices already captured since BeginTransformFeedback (drives the
// buffer-capacity clamp on the primitives-written accounting).
void AddTransformFeedbackCapturedVertices(Uint64 vertices) {
m_transformFeedbackCapturedVertices += vertices;
}
Uint64 GetTransformFeedbackCapturedVertices() const { return m_transformFeedbackCapturedVertices; }
// Raw assembled input primitives fed to the capture stage since Begin
// (pre-clamp; drives the GS strip capture-order fixup at EndTF).
void AddTransformFeedbackInputPrimitives(Uint64 primitives) {
m_transformFeedbackInputPrimitives += primitives;
}
Uint64 GetTransformFeedbackInputPrimitives() const { return m_transformFeedbackInputPrimitives; }
// Transform feedback objects (ARB_transform_feedback2 / GL 4.0 core).
// The capture state above and the indexed GL_TRANSFORM_FEEDBACK_BUFFER
// binding points are object state, but the context keeps exactly one live
// copy of both so that every existing reader - the backends' per-draw sync,
// the drawing and getter paths - needs no notion of which object owns them.
// A bind therefore saves the live copy into the outgoing object and restores
// the incoming one's. Object 0 is the default object and always exists.
static constexpr Uint MAX_TRANSFORM_FEEDBACK_BUFFERS = 4;
void GenTransformFeedbackNames(Uint number, Vector<Uint>& ids);
// A name glGenTransformFeedbacks handed out and glDeleteTransformFeedbacks
// has not taken back. Name 0 is always valid.
Bool ValidateTransformFeedbackName(Uint index) const;
// What glIsTransformFeedback reports: a generated name only becomes the name
// of an object once it has been bound at least once (GL 4.6 core 13.2.1).
Bool IsTransformFeedbackObject(Uint index) const;
void BindTransformFeedbackObject(Uint index);
void MarkTransformFeedbackObjectForDeletion(Uint index);
Uint GetBoundTransformFeedbackName() const { return m_boundTransformFeedback; }
// Vertices the object captured in its last completed span; the vertex count
// glDrawTransformFeedback replays.
Uint64 GetTransformFeedbackRecordedVertices(Uint index) const;
// Whether the object has ever completed a capture span. glDrawTransformFeedback
// on an object that has not is INVALID_OPERATION, which a zero vertex count
// cannot express: an empty completed span is legal and draws nothing.
Bool HasTransformFeedbackCompletedSpan(Uint index) const;
// The by-name (direct state access) view. A named object that happens to be the
// bound one is answered from the live copy, since that is where its state actually
// is until a bind swaps it out.
void CreateTransformFeedbackObject(Uint index);
Bool IsNamedTransformFeedbackActive(Uint index) const;
Bool IsNamedTransformFeedbackPaused(Uint index) const;
NamedTransformFeedbackBinding GetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex) const;
void SetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex,
const SharedPtr<BufferObject>& buffer, Range1D range,
Bool hasExplicitRange);
// Framebuffer
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
BindingSlot<FramebufferObject>& GetFramebufferBindingSlot(FramebufferTarget target);
const SharedPtr<FramebufferObject>& CreateFramebufferObject(Uint index);
void MarkFramebufferObjectForDeletion(Uint index);
Bool ValidateFramebufferName(Uint index) const;
Bool ValidateFramebufferObject(Uint index) const;
// Sampler
void GenSamplerNames(Uint number, Vector<Uint>& samplers);
const SharedPtr<SamplerObject>& GetSamplerObject(Uint index);
const SharedPtr<SamplerObject>& CreateSamplerObject(Uint index);
void MarkSamplerObjectForDeletion(Uint index);
Bool ValidateSamplerName(Uint index) const;
Bool ValidateSamplerObject(Uint index) const;
// Renderbuffer
void GenRenderbufferNames(Uint number, Vector<Uint>& renderbuffers);
const SharedPtr<RenderbufferObject>& GetRenderbufferObject(Uint index);
BindingSlot<RenderbufferObject>& GetRenderbufferBindingSlot(RenderbufferTarget target);
const SharedPtr<RenderbufferObject>& CreateRenderbufferObject(Uint index);
void MarkRenderbufferObjectForDeletion(Uint index);
Bool ValidateRenderbufferName(Uint index) const;
Bool ValidateRenderbufferObject(Uint index) const;
private:
// State Components
ErrorState m_errorState;
BufferState m_bufferState;
VertexArrayState m_vertexArrayState;
Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{};
Bool m_transformFeedbackActive = false;
Bool m_transformFeedbackPaused = false;
GLenum m_transformFeedbackPrimitiveMode = GL_POINTS;
SharedPtr<ProgramObject> m_transformFeedbackProgram;
Uint64 m_transformFeedbackGeneration = 0;
// Source of the per-span ids above; never rolls back with an object switch.
Uint64 m_transformFeedbackNextGeneration = 0;
// Not object state: the transform feedback queries snapshot it at BeginQuery
// and take the delta at EndQuery, which spans whatever objects were used.
Uint64 m_transformFeedbackPrimitiveCounter = 0;
Uint64 m_transformFeedbackPausedPrimitiveCounter = 0;
Uint64 m_transformFeedbackCapturedVertices = 0;
Uint64 m_transformFeedbackInputPrimitives = 0;
// Everything a transform feedback object owns while it is NOT the bound one.
struct TransformFeedbackObjectState {
struct SavedBufferBinding {
SharedPtr<BufferObject> buffer;
Range1D range;
Bool hasExplicitRange = false;
};
Array<SavedBufferBinding, MAX_TRANSFORM_FEEDBACK_BUFFERS> bindings;
Bool active = false;
Bool paused = false;
GLenum primitiveMode = GL_POINTS;
SharedPtr<ProgramObject> program;
Uint64 generation = 0;
Uint64 capturedVertices = 0;
Uint64 inputPrimitives = 0;
Uint64 recordedVertices = 0;
Bool hasCompletedSpan = false;
Bool everBound = false;
};
void SaveBoundTransformFeedbackState();
void RestoreBoundTransformFeedbackState();
// operator[] materialises an entry with the default state on first touch, so
// the default object (name 0) needs no seeding here.
UnorderedMap<Uint, TransformFeedbackObjectState> m_transformFeedbackObjects;
IndexGenerator<Uint> m_transformFeedbackNames;
Uint m_boundTransformFeedback = 0;
// Map membership IS object existence here: a pipeline has no stateful default
// object 0, so no everBound flag is needed.
UnorderedMap<Uint, SharedPtr<ProgramPipelineObject>> m_programPipelines;
IndexGenerator<Uint> m_programPipelineNames;
Uint m_boundProgramPipeline = 0;
TextureState m_textureState;
ProgramState m_programState;
RenderState m_renderState;
FramebufferState m_framebufferState;
SamplerState m_samplerState;
RenderbufferState m_renderbufferState;
};
} // namespace GLState
extern UniquePtr<GLState::GLContext>& pGLContext;
// True when relaxed GL semantics apply. Strict core rules are enforced only when the
// current EGL context explicitly requested a core profile (core bit in
// EGL_CONTEXT_OPENGL_PROFILE_MASK, or a >=3.1 version request without the compatibility
// bit) and MOBILEGL_RELAXED_SEMANTICS is off; no current context, legacy version
// requests, and the compatibility bit all relax.
Bool IsRelaxedSemanticsActive();
} // namespace MG_State
} // namespace MobileGL