Pipelines, materials, and render submission
Prepare complete draw packets from retained assets and copied game state, append them to an ordered frame, and let the runtime render and recycle that frame. Rendering introduces the interfaces; Ownership and Cleanup explains why packets can retain resources without keeping a closed graphics backend usable.
Task |
Path |
|---|---|
Draw ordinary sprites, images or text |
Default asset materials, selected during upload. |
Change one object’s material or shader parameters |
Custom sprite material example, sharing assets and material handles between objects. |
Load authored shader/material recipes |
Shader/material manifests through the asset loader. |
Render a toolkit UI |
Default UI materials, selected automatically by its uploader. |
Customize UI rendering |
Custom UI materials, using loaded material handles. |
Construct a material directly |
Manual material construction, on the graphics owner. |
Change filtering for one image binding |
Nearest-sampling example, retaining a sampler separately from image pixels. |
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::Context2D inserts the standard image sampler at unit zero by default.
Set its image to another ImageParameter2D for a custom key/unit, or to std::nullopt
for manual image selection where the asset helper permits it. 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.
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.
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.
Parameters and ownership
A packet retains the selected resource generations as well as its copied parameter values. Its material reaches the executable through a pipeline:
flowchart TB
accTitle: What a draw packet retains
accDescr: A packet retains geometry, a material and resolved values. The material retains a pipeline and its executable program; image and sampler handles are retained through resolved values and material defaults.
packet["Draw packet"] -->|retains| geometry["Geometry"]
packet -->|retains| material["Material"]
packet -->|owns| values["Resolved values"]
material -->|retains| pipeline["Pipeline"]
pipeline -->|retains| program["Executable program"]
values -->|retain| images["Images and samplers"]
material -->|defaults retain| images
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 uses the standard key/unit unless its context selects another. 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.
Choose nearest sampling for pixel art
On the provider’s platform/loading owner, with its graphics context current:
namespace Sampling = CE::Rendering::Sampling;
Sampling::Options options;
options.minification = Sampling::Filter::Nearest;
options.magnification = Sampling::Filter::Nearest;
options.mipmaps = Sampling::MipmapFilter::None;
options.anisotropy = Sampling::Anisotropy::Disabled;
auto nearest = provider.create_sampler(options);
style.parameters["image"] = CE::Rendering::Parameters::ImageBinding{image, 0, nearest};
Include <cheryl/assets/resources/sampler.h> and the draw/submission headers.
Here image is a retained image from that provider and style.material declares
its public sampler parameter as image. When this explicit binding supplies the
image, set context.image = std::nullopt to disable automatic insertion; inserting
the same draw-layer key twice rejects. The frame retains the sampler with the
binding. Other draws using the same pixels can keep their original filtering.
Nearest sampling still requires suitable atlas padding and placement to avoid
adjacent-cell bleed or unintended boundary pixels.
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.
Material replacement
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.
API domains
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.
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. |
Legacy submission
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.
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.
Default asset materials
Sprite and tileset loading, whole-image graphic creation, Unicode text upload and legacy AsciiFont loading retain compatible materials from the selected resource provider. Ordinary drawing requires no application shader files, uniform mappings or material factory. The provider constructs defaults on its upload owner; CPU frame preparation only reads the retained handles.
Profile |
Geometry and transforms |
|---|---|
|
|
|
Both sample the required public image parameter and apply projection, view,
model, scale and alpha semantics. Upload paths select straight alpha, disabled
depth/write and no culling. Explicit premultiplied material requests also scale
RGB during alpha fading. These profiles preserve the image’s immutable sampling:
color images keep their linear/mipmap defaults, while font atlases keep linear
filtering without mipmaps. Choose a separate sampler for pixel art.
An explicit DrawStyle2D::material overrides the asset’s retained selection.
Sprite/tileset manifest shader references select a loaded material instead of a
default. Graphic creation, Unicode upload and AsciiFont loading also accept an
explicit material handle; that bypasses default construction. These are ordinary
composition choices and require no asset subclass. Custom materials still need
compatible geometry, parameter and blend contracts. Low-level direct construction
can omit a material, in which case submission needs a per-draw selection.
OpenGL embeds the shader sources and bindings. Triangle and strip variants share their live program when their alpha convention matches, while keeping distinct pipelines. Provider caches are weak; retained assets and published packets own the live generations. A provider without built-in support rejects a required default during upload. Explicit selected handles continue to use their existing construction path.
Give one object a custom material
Two objects can share one sprite and one material while supplying different
shader parameters. This example draws two copies of First Game’s actor with
different color tints. Keep the sprite definition’s shader unset so other draws
can continue using its default material.
Under the application’s graphics/shaders/ directory, create tinted.vert:
#version 330 core
layout(location = 0) in vec3 in_Position;
layout(location = 1) in vec2 in_Texcoord;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform float in_Scale;
out vec2 texcoord;
void main() {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(in_Position * in_Scale, 1.0);
texcoord = in_Texcoord;
}
Create tinted.frag beside it:
#version 330 core
in vec2 texcoord;
uniform sampler2D imageTexture;
uniform vec4 tint;
uniform float in_Alpha;
out vec4 color;
void main() {
vec4 pixel = texture(imageTexture, texcoord);
color = vec4(pixel.rgb * tint.rgb, pixel.a * tint.a * in_Alpha);
}
Create graphics/shaders/tinted.json. Sprite cells use position3_uv2 triangle
strips; this differs from the colored triangles used by UI adapters. The public
parameter key tint maps to this program’s tint uniform:
{
"asset_class": "shader",
"version": "2.0",
"namespace": "game",
"programs": [{
"name": "tinted-program",
"stages": {
"vertex": "shaders/tinted.vert",
"fragment": "shaders/tinted.frag"
}
}],
"materials": [{
"name": "tinted",
"program": "game:tinted-program",
"vertex_layout": "position3_uv2",
"topology": "triangle_strip",
"state": {"blend": "straight_alpha", "depth": "disabled", "depth_write": false, "cull": "none"},
"parameters": [
{"key": "projection", "type": "mat4", "semantic": "projection"},
{"key": "view", "type": "mat4", "semantic": "view"},
{"key": "model", "type": "mat4", "semantic": "model"},
{"key": "scale", "type": "float", "semantic": "scale"},
{"key": "alpha", "type": "float", "semantic": "alpha"},
{"key": "image", "type": "sampler2d"},
{"key": "tint", "type": "vec4", "default": [1.0, 1.0, 1.0, 1.0]}
],
"bindings": {"opengl": {
"attributes": {"position": "in_Position", "texcoord": "in_Texcoord"},
"parameters": {
"projection": "projectionMatrix", "view": "viewMatrix", "model": "modelMatrix",
"scale": "in_Scale", "alpha": "in_Alpha", "image": "imageTexture", "tint": "tint"
}
}}
}]
}
Add that document to the existing graphics/graphics-manifests.json index:
{
"version": "2.0",
"manifests": ["definitions/sprites.json", "shaders/tinted.json"]
}
Keep the normal sprite-loading path from
First Game.
Include <cheryl/core/resources/asset-management/shader-asset-mgr.h>, the
draw/submission headers, <glm/gtc/matrix_transform.hpp>, <memory> and <stdexcept>.
Retain a game member:
std::shared_ptr<const CE::Rendering::Resources::Material> tinted_;
On platform during init_platform(), after loader.load_assets(engine_.resources()),
retrieve the uploaded material:
tinted_ = CE::Assets::Shaders::Manager::get().find_material("game:tinted");
if (!tinted_)
throw std::runtime_error("The tinted material is required");
In frame preparation, replace First Game’s actor draw with the following two
draws, keeping its actor_, pass and context. Each style supplies its own
transform and copied parameter values:
CE::Rendering::DrawStyle2D first;
first.material = tinted_;
first.scale = 4.0f;
first.model_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(160.0f, 100.0f, 0.0f));
first.parameters["tint"] = glm::vec4(1.0f, 0.25f, 0.25f, 1.0f);
pass.add(CE::Rendering::Submission::resolve_sprite(*actor_->sprite, actor_->cell, first, context));
auto second = first;
second.model_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(240.0f, 100.0f, 0.0f));
second.parameters["tint"] = glm::vec4(0.25f, 0.5f, 1.0f, 1.0f);
pass.add(CE::Rendering::Submission::resolve_sprite(*actor_->sprite, actor_->cell, second, context));
The first object appears red-tinted and the second blue-tinted. The sprite,
material and program remain immutable and shared; each packet owns its resolved
tint. The standard context inserts the sprite’s image automatically. Omitting a
draw’s tint uses the contract’s white default. Game logic can keep tint alongside
an object’s position and change it during update, then copy both into its next
draw style. No inheritance hook or material factory is required.
Reset tinted_ with the game’s sprite handles during platform-owned deinit_platform().
Earlier frames retain their selected material and parameters. A later catalogue
replacement does not change those packets or automatically replace this retained
game handle; adopt a new handle explicitly when changing material generations.
Default UI materials
TGUI and RmlUi uploaders select compatible materials when called without a
Materials argument:
pending = uploader.submit(platform, session.record());
Construct the uploader on the provider owner during initialization. The first
nonempty upload requests defaults on that owner, and subsequent uploads retain
and reuse them. Empty recordings need no material capability. TGUI selects
StraightAlpha; RmlUi selects PremultipliedAlpha. Applications need no material
factory or additional shader files for these defaults with OpenGL.
The generic contract is CE::Assets::Shaders::Builder::build_builtin_material(kind, blend),
accessed through provider.shader_asset_builder(). The library helper
CE::Assets::Shaders::build_builtin_material(provider, kind, blend) validates that
the provider supplies a nonnull result. Engine defines
CE::Rendering::Materials::Builtin2D; the graphics backend owns its realization:
Profile |
Contract |
|---|---|
|
Output vertex RGBA from |
|
Multiply sampled RGBA by vertex RGBA, with a required custom |
These UI profiles use the required Mat4 projection semantic named projection, disabled
depth/write/culling and the requested blend state. OpenGL embeds its shader
sources and uniform mappings and constructs them through its existing validated
recipe path. Its provider weakly caches programs by profile and materials by
profile/blend. The UI alpha variants share the live program without changing
each other’s state; uploader handles and published frames keep resources alive.
The cache adds no global resource owner or catalogue entries.
Construction requires the provider’s live domain, platform owner and current graphics context, including a warm cache lookup. A provider without a shader builder, or a builder without built-in support, rejects the default request. UI modules depend on the generic Engine contract; they do not link OpenGL.
Custom UI materials
To customize rendering, author and load
shader/material JSON recipes,
then pass their material handles to the uploader. No application material factory
is needed. For example, after loading game:ui-solid and game:ui-textured, select
them during initialization:
CE::UI::TGUI::Materials custom;
auto& catalogue = CE::Assets::Shaders::Manager::get();
custom.solid = catalogue.find_material("game:ui-solid");
custom.textured = catalogue.find_material("game:ui-textured");
if (!custom.solid || !custom.textured)
throw std::runtime_error("Custom UI materials are required");
custom.image_parameter = "image";
Include <cheryl/core/resources/asset-management/shader-asset-mgr.h>,
<cheryl/ui/tgui/scene.h> and <stdexcept>. Retain custom with the view and
pass it when publishing a recording:
pending = uploader.submit(platform, session.record(), custom);
RmlUi uses its own CE::UI::RmlUi::Materials with the same handle-selection
pattern. Overrides must match the adapter’s colored triangles, projection
semantic, image parameter and alpha/depth/culling contract. Choose the toolkit’s
blend state in the JSON; RmlUi’s sampled/vertex RGBA must stay premultiplied.
image_parameter and image_unit select the custom sampler key and binding unit.
Other custom values come from material defaults or Materials::pass_parameters.
The third argument bypasses default construction; explicitly passing empty or
incompatible handles fails for draws that use them. It does not trigger fallback.
Published packets retain the chosen generations through later catalogue changes.
Manual material construction
Direct typed construction remains available independently of UI defaults and
indexed loading. Use it when code must construct a recipe explicitly; the normal
custom UI path above can use JSON. 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.
Keep this manual API, example and its consumer coverage usable independently of automatic UI defaults. Other shader files are not indexed merely because they exist; their purposes and native behavior remain unverified.