Pipelines, materials, and render submission
CE::Rendering::Pipelines::Definition describes owned program source paths, position3/UV2 or
position3/UV2/color4 vertex layout, triangle/strip topology, fixed blend/depth/cull
intent, and a public parameter contract. CE::Rendering::Materials::Definition references a
specific shared pipeline generation and supplies defaults, including retained
image handles and zero-based texture-unit requests. Neither definition contains
GLSL uniform names. Backend mappings are supplied to explicit OpenGL builders.
CE::Rendering::Resources::Pipeline is a backend base with a validated definition snapshot and no value
copying/slicing. Its backend subclass must retain the linked executable program;
compiled stages remain transient. CE::Rendering::Resources::Material copies its
CE::Rendering::Materials::Definition on construction.
Definition/default access is read-only. Building another pipeline/material
instance preserves the old logical generation while its readers retain it.
CE::Assets::Materials::Manager publishes complete immutable materials, including
their pipeline generation, through explicit application-supplied builders. Indexed
recipes use CE::Assets::Shaders::Manager and the optional provider-owned
CE::Assets::Shaders::Builder. Both paths preserve immutable generations. CE::Rendering::Frame packets
retain those generations for low-level renderer playback.
API domains
Portable resource interfaces live in CE::Rendering::Resources. Geometry
owns coordinates, vertex layouts and uploaded buffers; Pipelines owns fixed-state
definitions and pass constraints; Materials owns retained material construction
inputs. Parameters, Sampling and Submission describe copied draw values,
immutable sampling and CPU packet resolution. Concrete native implementations
belong to CE::Rendering::OpenGL.
The shader asset family preserves a separate distinction between recipes and their retained results:
API |
Role |
|---|---|
Owned CPU sources, identities, literals, contracts and backend payloads. |
|
|
Immutable catalogue entries pairing a recipe with its executable or material generation; a material entry also retains its selected program entry. |
|
Executable-program access and retained material resources used for drawing. |
|
Concrete linked OpenGL resources and validated native playback. |
Parameters and ownership
Parameter values own scalars, vectors, matrices, and their map keys.
CE::Rendering::Parameters::ImageBinding owns a shared const image handle,
a binding-unit request and an optional retained const sampler override.
CE::Rendering::Sampling::Sampler owns immutable sampling options and exposes
the backend’s effective anisotropy. Each pass and draw supplies copied
CE::Rendering::Parameters::Set values; material defaults belong to the immutable
material snapshot. Resolution returns a new owned set, never a view into the
caller’s inputs.
Source |
Ownership |
Precedence |
|---|---|---|
Contract default |
Pipeline generation |
Lowest |
Pass custom values |
Pass submission |
Overrides contract defaults |
Material defaults/resources |
Material generation |
Overrides pass custom values |
Draw custom values |
Draw submission |
Highest |
Projection/view/model/alpha/scale semantics come from
CE::Rendering::Parameters::PassSemantics and DrawSemantics.
The contract explicitly opts into each semantic; an effect does not inherit
sprite uniforms automatically. Custom layers cannot override semantic keys.
Custom values support float/int/uint/bool, float vec2/vec3/vec4, mat4, and sampler2D.
Matrix scope is deliberately limited to the existing mat4 transforms.
Contracts reject duplicate/empty keys, invalid types, duplicate or mistyped engine semantics, and invalid defaults. Each source layer rejects unknown keys, wrong types, semantic overrides, and null images, even if a later value would hide the problem. After all layers resolve, required missing values and colliding sampler units fail. Optional values with no source/default are absent from the result. The native builder requires a reset/default for an active optional custom uniform. An inactive optional uniform produces no upload. Missing active engine semantics reject an incomplete packet. Binding rewrites every active contracted uniform, so an absent optional value cannot inherit a previous draw’s value.
Independent binding
Geometry selects only its vertex array/buffer. Image binding accepts a zero-based unit for each request; OpenGL converts it to GL_TEXTURE0 + unit and checks the current context’s unit limit, sampled once during upload rather than queried on every draw. Image upload uses unit zero as temporary setup and stores no unit. Asset submission selects the requested key/unit explicitly. Two material recipes can retain the same image with different unit requests without mutating each other’s recipe or the cached image.
Immutable sampling
CE::Rendering::Sampling::Options selects nearest/linear minification and magnification, no/nearest/
linear mipmap filtering, clamp/repeat/mirrored-repeat wrapping on each axis and
disabled/maximum-supported anisotropy. Engine defaults are linear filtering,
linear mipmaps, clamp-to-edge and maximum-supported anisotropy. OpenGL image
creation applies those defaults and generates a complete mip chain. Font-atlas
creation keeps its separate no-mipmap policy. Unsupported anisotropy resolves to
an effective value of one; the uploaded sampler exposes the effective value.
CE::Rendering::Resources::Provider::create_sampler(options) is an optional capability. Its default
rejects rather than silently dropping overrides. OpenGL creates immutable sampler
objects on the loading owner and weakly reuses live matching options. An
CE::Rendering::Parameters::ImageBinding without an override uses the image’s default policy. With an override,
the packet retains it independently of image pixels. An FX material’s named sampling
policy fills a missing binding sampler; an explicit submitted sampler wins.
Ordinary indexed materials need no sampling declaration or image knowledge.
OpenGL validates sampler/image domains, unit bounds and mip completeness before binding. A draw using default image sampling unbinds a preceding sampler object; state from another draw cannot leak into it. Samplers retire through the native resource lifetime just like textures. Shared images and previously published frames remain unchanged when another binding selects different sampling. For exact nearest sampling, disable anisotropy explicitly. Grid UVs still address cell edges; sampling support does not supply atlas padding or prevent every adjacent-cell bleed.
Frame and recipe integration
OpenGL pipeline builders validate explicit mappings/reflection, required/optional
values, copied-value uploads, and sampler domains/units. CE::Rendering::OpenGL::Pipeline::draw() applies
complete fixed state after validating pass constraints, geometry, and parameters.
Frame packets use that path.
Linked attributes match position3 at location zero and UV2 at location one. The
colored layout additionally maps float RGBA at location two, with an explicit
CE::Rendering::OpenGL::PipelineBindings::color_attribute name. Inactive inputs may be omitted;
an active color input is rejected for the uncolored layout. CE::Rendering::Geometry::Vertex2D keeps its
existing storage; CE::Rendering::Geometry::Vertex2DColor is a separate immutable upload format. Pipeline
and geometry layouts must match. Shaders interpret color values consistently with
their selected straight/premultiplied blend mode; upload does not convert them.
Geometry exposes immutable CPU-readable layout/topology/count metadata. Pipeline
validation checks complete primitives and bounded ranges before publication;
native drawing additionally checks vertex-array/program domain identity and the live current
context. Program/image native domains are checked as well.
Common validation is CPU-only and reads stable inputs. Pipeline construction checks
source paths for emptiness, supported layout/topology/state and parameter contracts;
it does not read files, link or reflect a program. Material construction validates a
partial custom-default layer, so required values and sampler collisions are checked
when resolving a complete draw. CE::Rendering::Parameters::validate_values() permits missing keys
and colliding units in a partial layer; CE::Rendering::Parameters::validate_resolved() requires
required keys and distinct final sampler units, but does not prove that engine values
actually came from a camera. Neither checks image domains, backend unit limits,
numeric finiteness or alpha/scale ranges. Backend binding supplies its additional
validation. A validation/resolution failure leaves caller inputs and immutable recipes
unchanged.
Rectangular clipping
DrawStyle2D::clip resolves into a copied optional DrawPacket2D::clip. A
CE::Rendering::ClipRegion2D retains top-left logical edges and positive logical viewport dimensions.
Edges must be finite and ordered; equal edges mean empty. CE::Rendering::intersect_clip_rects
combines nested rectangles in the same logical coordinate space. Clipping does not
transform with the draw’s model matrix or change authored order.
Playback maps the retained extent to its current framebuffer. CE::Rendering::resolve_clip_region
clamps to the logical viewport and rounds nonempty edges outwards (floor left/top,
ceil right/bottom). Rectangles outside the viewport, zero-area rectangles and a
zero-sized framebuffer produce no pixels. This is integer rectangular clipping;
it does not promise subpixel masks or rotated clips. Resize/content-scale changes
use the playback framebuffer and the retained logical extent without reading live UI
state. A changed logical layout belongs to a newly published frame.
OpenGL converts top-left pixel edges to its bottom-left scissor box after validating
the complete draw. Empty clips issue no draw; absent clips disable scissor. Full
frame clears also disable scissor, so a preceding clipped packet cannot restrict
the next clear. The renderer viewport supplies the framebuffer extent.
Direct clipped CE::Rendering::OpenGL::Pipeline::draw() calls also supply that extent explicitly.
CE::Assets::Shaders::ProgramCache publishes executable program handles for explicit
program access; CE::Rendering::DrawStyle2D stores an immutable material handle.
CE::Assets::Materials::Manager::load() retains an existing key; reload() builds a full
candidate outside cache locks, then replaces one material after success. Builders
own their typed definitions/backend mappings and validate all dependent resources.
A throw or null candidate retains the previous complete material; retained readers
keep the old material/pipeline/image generation. Provider teardown clears materials
before other resource caches, with the same loading-owner/domain exclusion.
The demo loads indexed main:text and main:images recipes and reloads them on
the platform owner.
Frame preparation resolves asset/text submissions into owned packets; playback only
consumes geometry, material, ranges, and copied parameters. A failed demo reload keeps
the previous generation and reports its error in the overlay.
Graphics manifests 2.0 also produce owned CPU recipes through explicit index selection. Shader/material documents contain no default images; sprites and tilesets can select a material independently of their texture. The manifest guide owns the document format; the loader guide owns catalogue ordering, preserve-existing loads and incremental replacement.
Use architecture validation for native state/reload, retained-packet and fixture checks.
Native bootstrap and binding
The OpenGL provider implements CE::Assets::Shaders::Builder without exposing GL types to
common loading. It validates opaque bindings.opengl payloads and links the owned
prepared source bytes. It constructs materials from the catalogue’s retained program
recipe/executable pair. These optional APIs coexist with the manual builders below.
CE::Rendering::OpenGL::ResourceProvider::build_pipeline(definition, bindings) links the definition’s
sources and constructs a native pipeline after validating reflection. PipelineBindings
maps public parameter keys to uniform names; its attribute-name mapping is also
backend-owned. build_material(recipe) checks that its pipeline and default images
belong to this provider’s live native domain. Both builders require the platform
owner with its rendering context current. Common resource/definition headers remain
free of GL names and types.
The native pipeline owns the linked OpenGL program and immutable definition. Native stages are marked for deletion after attachment and detached after linking, including a failed link, so a retained executable does not retain its compilation stages. Failure before linking is covered by the program/stage cleanup guards.
Every contract key needs an explicit unique uniform mapping. Required uniforms must be active; optional ones may be absent after optimization. Active uniform types must match exactly. The initial API rejects uniform arrays and block storage, and requires every active uniform to appear in the contract. This prevents an undeclared mutable program value from silently affecting another draw.
CE::Rendering::Resources::Material::resolve() produces copied values from engine
semantics and custom layers. Pass those values to
CE::Rendering::OpenGL::Pipeline::bind_parameters() on the platform owner. The
binder fills omitted active optional uniforms from contract defaults or the mapping’s
missing_value, then validates the complete set, including fallback sampler collisions.
It checks all image domains/units before changing the program or texture bindings.
This method applies parameter/resource values only. The native pipeline’s draw()
validates the complete geometry/state/parameter request, reapplies blend equations
and factors, depth enable/function/write mask, cull enable/face, and CCW front-face
winding, then binds parameters and geometry and draws. Every draw sets all supported
state, so adjacent draws/passes cannot inherit another pipeline’s policy. Depth
clears explicitly enable depth writes before clearing; the next draw reapplies its
own mask.
PassConstraints2D restricts any selected pipeline state without overriding it. Default 2D passes require disabled depth; set or reset that constraint explicitly for a depth pass or a mixed-policy pass. Opaque blending uses one/zero factors; straight alpha uses source-alpha/one-minus-source-alpha RGB with one/one-minus-source-alpha alpha; premultiplied alpha uses one/one-minus-source-alpha; additive uses source-alpha/one RGB and one/one alpha. All use additive blend equations.
Recording-GLAD scenarios cover a native two-image effect, optional uniform reset after another draw, inactive optional uniforms without sprite roles, reflection/type/storage failures, invalid attributes/unlinked programs, and sampler domain/unit failures before any bind. These sources use synthetic IDs and restore all replaced entry points; they do not replace real-context acceptance.
Manual material construction
TGUI and RmlUi deliberately keep manual recipes. The demo’s
TGUI builder and
RmlUi builder show adding typed
definitions and wiring their bindings explicitly. On the OpenGL loading owner,
with its context current, a textured TGUI material can be built as follows
(native is a CE::Rendering::OpenGL::ResourceProvider&, root is the asset root):
CE::Rendering::Pipelines::Definition definition;
definition.vertex_layout = CE::Rendering::Geometry::Layout2D::Position3UV2Color4;
definition.topology = CE::Rendering::Geometry::Topology::Triangles;
definition.state = {CE::Rendering::Pipelines::Blend::StraightAlpha,
CE::Rendering::Pipelines::Depth::Disabled, false,
CE::Rendering::Pipelines::Cull::None};
definition.program_sources = {root / "graphics/shaders/tgui.vert",
root / "graphics/shaders/tgui-textured.frag"};
definition.parameters = {{"projection", CE::Rendering::Parameters::Type::Mat4, true,
CE::Rendering::Parameters::Semantic::Projection},
{"image", CE::Rendering::Parameters::Type::Sampler2D}};
CE::Rendering::OpenGL::PipelineBindings bindings{{{"projection", "projectionMatrix"},
{"image", "mytexture"}}};
auto material = native.build_material({native.build_pipeline(std::move(definition), bindings), {}});
Supply that handle as Materials::textured to the UI uploader; its per-draw image
binding supplies the texture and sampling without putting an image in the recipe.
The solid material omits the image parameter and uses tgui-solid.frag. RmlUi uses
the same construction pattern with premultiplied-alpha blending. Required parameter
keys belong in the typed definition, and their uniform mappings belong in bindings;
adding one requires both and a matching shader uniform.
A future indexed UI migration targets TGUI first. If RmlUi also migrates, retain this manual example and an independently usable manual consumer. Other shader files are not indexed merely because they exist; their purposes and native behavior remain unverified.
CPU submission
CE::Rendering::DrawPacket2D retains geometry, an immutable material generation, and copied resolved
parameters. Asset submission helpers select sprite/tile ranges, whole graphics,
and laid-out font glyphs before rendering, without binding native resources.
CE::Rendering::Submission::ImageParameter2D selects a public sampler key and unit explicitly. Text layout is
const and returns glyph placements; deprecated BitmapFont bank selection remains typed.
Resolved glyph packets retain the geometry/atlas even after the Font object is released.
CE::Rendering::PassWriter validates individual packets or a whole glyph group before publication
and assigns stable authored order. CE::Rendering::Frame keeps reusable vector capacity and
releases packet/pass handles on recycle. The OpenGL renderer checks its native
pipeline domain, then calls the validated fixed-state draw path in authored order.
Whole-image graphics, tiles, tile-animation values and text use
CE::Rendering::Submission CPU helpers.
Their asset/playback contract defines
metadata and glyph units, direct-construction limits and image-key insertion.
Draw2D/iDraw/DrawInfo and the font formatting pointer contract are retired. Deprecated
BitmapFont keeps immutable width metrics and typed normal/alternate-bank layout; callers
place and rotate text through CE::Rendering::DrawStyle2D::model_matrix. Legacy AsciiFont uses a supplied
font file and an ASCII atlas. The Unicode service
provides retained shaped glyph pages through its CE::Text::Rendering::Generation submission overload.
Opt-in positioned text runs prepare
placements once and submit contiguous atlas-page ranges with a whole-text transform.
They preserve order, clipping, sampling and retained generations. General packet
merging and reorder-safe sorting remain separate extensions; the renderer still
plays authored packets in order.
See BitmapFont deprecation.
Frame storage and writers
Frame writers borrow a slot; they do not lock it or publish it across threads.
The runtime’s frame boundary owns that transfer.
Only active passes are visible through passes(), whose span borrows the slot and
is invalidated by outer-vector growth. A pass writer uses an index that survives
that growth, but references from parameters()/constraints() do not. Keep every
writer within one exclusive preparation phase, before publication or recycling.
Each add() validates before inserting. The group overload validates all packets
and reserves capacity before appending any of them. Validation/allocation failure
leaves that pass’s earlier draws intact; it does not roll back an entire frame or
other application side effects. Writer destruction performs no commit or rollback.
Recycle a partially prepared frame before reuse. begin_pass() owns its values and
copies matrices; a pass’s constraints are immutable during its preparation through
this writer API. Authored pass/draw order is the playback order regardless of the
packet’s order_sensitive metadata.
Recycle only after playback or supersession excludes readers, on the resource release owner required by the backend. It releases active packet/pass handles and keeps vector capacity for later preparation. Native render/presentation failures can leave partial output or changed native state; CPU validation is not a transaction over backend execution.
Camera observations
CE::Rendering::CameraBase is unsynchronized mutable CPU state. Setters and matrix references belong
to one owner; a revision is a change counter, not an atomic publication mechanism.
Copy matrices into a pass before sharing them with playback. Changed framebuffer
size, view or perspective advances the revision once, while equal configurations
leave it unchanged. View equality is exact per component; view matrices are not
checked for finiteness or invertibility.
Framebuffer dimensions are pixels. Negative dimensions throw before changing the
camera; zero dimensions are retained while projection clamps each axis to at least
one. CE::Rendering::Camera2D uses GLM orthographic bounds [0, width] and [0, height], Y-up, with
near/far 0/1. CE::Rendering::Camera3D uses vertical FOV in degrees and framebuffer aspect; finite
0 < FOV < 180 and 0 < near < far are required, with plane distances in view units.
Invalid perspective values leave the old configuration unchanged. Neither camera
selects renderer depth policy or converts logical window coordinates to framebuffer
pixels; that choice belongs to the consumer.