pub trait RenderBackend: SceneControl + Send {
Show 72 methods
// Required methods
fn window_closed(&mut self) -> bool;
fn capture_cursor(&mut self);
fn take_input(&mut self) -> RenderInput;
fn wait_idle(&self);
fn draw_frame(&mut self, params: FrameParams<'_>) -> RenderResult<()>;
fn update_view(&mut self, matrix: [[f32; 4]; 4]);
fn update_models(&mut self, updates: &[(u32, [[f32; 4]; 4])]);
fn retire_draw_object(&mut self, draw_idx: usize);
fn upload_skinned(
&mut self,
vertices: &[SkinnedVertex],
indices: &[u32],
draw_objects: Vec<SkinnedDrawObject>,
vert_bytes: &[u8],
frag_bytes: &[u8],
shadow_bytes: &[u8],
) -> RenderResult<()>;
fn update_skinned_pose(
&mut self,
skinned_index: usize,
matrices: &[[[f32; 4]; 4]],
);
fn evict_texture_slot(&mut self, slot: usize) -> Result<(), String>;
fn update_texture_slot(
&mut self,
slot: usize,
image: &TextureImage,
) -> RenderResult<()>;
fn evict_mesh(
&mut self,
draw_idx: usize,
retire_frame: u64,
) -> Result<(), String>;
fn upload_mesh(
&mut self,
draw_idx: usize,
verts: &[Vertex],
idxs: &[u16],
frame: u64,
) -> RenderResult<()>;
fn setup_chunk_streaming(
&mut self,
chunk_vtx_bytes: usize,
chunk_idx_bytes: usize,
texture_slot: usize,
normal_map_slot: usize,
) -> RenderResult<()>;
fn add_chunk_mesh(
&mut self,
mesh: ChunkMesh<'_>,
dst: SlotAlloc,
) -> RenderResult<()>;
fn remove_chunk_mesh(
&mut self,
draw_idx: usize,
retire_frame: u64,
) -> Result<(), String>;
fn set_chunk_model(
&mut self,
draw_idx: usize,
model: [[f32; 4]; 4],
) -> Result<(), String>;
// Provided methods
fn upload_skinned_morphs(
&mut self,
_morphs: Vec<Option<Arc<PayloadMorphs>>>,
) { ... }
fn update_morph_weights(&mut self, _skinned_index: usize, _weights: &[f32]) { ... }
fn reveal_skinned_instance(
&mut self,
_instance_index: usize,
_model: [[f32; 4]; 4],
) { ... }
fn retire_skinned_draw_object(&mut self, _skinned_index: usize) { ... }
fn update_skinned_models(&mut self, _updates: &[(u32, [[f32; 4]; 4])]) { ... }
fn seed_mesh_streaming(
&mut self,
vtx_offset: u64,
vtx_bytes: u64,
idx_offset: u64,
idx_bytes: u64,
) { ... }
fn capabilities(&self) -> DeviceCapabilities { ... }
fn gpu_profile(&self) -> GpuProfile { ... }
fn logical_size(&self) -> (f32, f32) { ... }
fn render_stats(&self) -> RenderStats { ... }
fn set_ui_cursor_hidden(&mut self, hidden: bool) { ... }
fn cursor_outside_window(&self) -> bool { ... }
fn set_menu_mode(&mut self, on: bool) { ... }
fn set_camera_capture(&mut self, capture: bool) { ... }
fn set_reflection_probes(&mut self, probes: &[ProbePlacement]) { ... }
fn set_vsync(&mut self, on: bool) { ... }
fn set_window_mode(&mut self, mode: WindowMode) { ... }
fn set_window_size(&mut self, width: u32, height: u32) { ... }
fn display_modes(&self) -> Vec<DisplayMode> { ... }
fn current_display_mode(&self) -> Option<DisplayMode> { ... }
fn set_display_mode(&mut self, mode: DisplayMode) { ... }
fn update_post_process(&mut self, tunables: PostProcessTunables) { ... }
fn set_ambient_intensity(&mut self, value: f32) { ... }
fn update_directional_lights(&mut self, lights: &[DirectionalLight]) { ... }
fn set_keymap(&mut self, keymap: &KeyMap) { ... }
fn apply_quality_settings(&mut self, settings: QualitySettings) { ... }
fn set_shadow_update(&mut self, update: ShadowUpdate) { ... }
fn set_shadow_distance(&mut self, distance: u32) { ... }
fn set_shadow_cascades(&mut self, count: u32) { ... }
fn update_quality_params(&mut self, settings: QualitySettings) { ... }
fn shader_reload_flag(&self) -> Option<Arc<AtomicBool>> { ... }
fn update_color_lut(&mut self, size: u32, data: &[u8]) -> Result<(), String> { ... }
fn draw_geometry_size(&self, draw_idx: usize) -> Option<(usize, usize)> { ... }
fn draw_lod_index_counts(&self, draw_idx: usize) -> Option<Vec<usize>> { ... }
fn rebuild_static_geometry(
&mut self,
changes: Vec<DrawGeometryUpdate>,
) -> RenderResult<()> { ... }
fn update_skinned_mesh_geometry(
&mut self,
skinned_index: usize,
vertex_base: u32,
verts: &[SkinnedVertex],
idxs: &[u16],
) -> Result<(), String> { ... }
fn rebuild_skinned_geometry(
&mut self,
changes: Vec<SkinnedDrawGeometryUpdate>,
) -> Result<Vec<SkinnedSlotLayout>, String> { ... }
fn update_skinned_skeleton(
&mut self,
skinned_index: usize,
new_joint_count: usize,
) -> Result<(), String> { ... }
fn update_mesh_geometry(
&mut self,
draw_idx: usize,
verts: &[Vertex],
idxs: &[u16],
lod_alternates: &[(f32, Vec<u16>)],
) -> Result<(), String> { ... }
fn update_environment_map(&mut self, payload: &[u8]) -> RenderResult<()> { ... }
fn update_fog_settings(&mut self, settings: Option<FogSettings>) { ... }
fn screenshot(&mut self, path: &str) -> Result<String, String> { ... }
fn clone_static_draw_object(
&mut self,
src_draw_idx: usize,
model: [[f32; 4]; 4],
dst: SlotAlloc,
) -> Result<(), String> { ... }
fn set_draw_material(
&mut self,
draw_idx: usize,
material: MaterialUniforms,
texture_slot: usize,
normal_map_slot: usize,
) { ... }
fn set_draw_cull_distance(&mut self, draw_idx: usize, cull_distance: f32) { ... }
fn add_decal(&mut self, record: DecalRecord) -> Result<usize, String> { ... }
fn remove_decal(&mut self, decal_id: usize) -> Result<(), String> { ... }
fn add_emitter(
&mut self,
record: ParticleEmitterRecord,
) -> Result<usize, String> { ... }
fn remove_emitter(&mut self, emitter_id: usize) -> Result<(), String> { ... }
fn update_world_shader_pipelines(
&mut self,
vert_bytes: Option<&[u8]>,
frag_bytes: Option<&[u8]>,
shadow_bytes: Option<&[u8]>,
vert_instanced_bytes: Option<&[u8]>,
) -> Result<(), String> { ... }
fn install_world_shader(
&mut self,
bucket: u32,
shader: ShaderBytes<'_>,
) -> RenderResult<()> { ... }
fn evict_world_shader(&mut self, bucket: u32) { ... }
fn hot_swap_config(&self) -> Option<SwapchainConfig> { ... }
fn reload_world(&mut self, init: BackendInit<'_>) -> RenderResult<()> { ... }
}Expand description
The set of operations GraphicsSystem performs on a graphics backend. Implementations are thin forwarders to the inherent methods on MtlContext / DxContext / VkContext.
The asset hot-reload mutators below (update_color_lut,
rebuild_*_geometry, clone_static_draw_object, etc.) are provided
methods that default to a no-op, so a backend implements only the reload
paths it actually supports.
Required Methods§
Sourcefn window_closed(&mut self) -> bool
fn window_closed(&mut self) -> bool
Window / input lifecycle.
Sourcefn capture_cursor(&mut self)
fn capture_cursor(&mut self)
Confine the cursor to the window.
Sourcefn take_input(&mut self) -> RenderInput
fn take_input(&mut self) -> RenderInput
Take the input sampled since the last call.
Sourcefn draw_frame(&mut self, params: FrameParams<'_>) -> RenderResult<()>
fn draw_frame(&mut self, params: FrameParams<'_>) -> RenderResult<()>
Per-frame drive. See FrameParams for the inputs.
Sourcefn update_view(&mut self, matrix: [[f32; 4]; 4])
fn update_view(&mut self, matrix: [[f32; 4]; 4])
Push the camera’s view matrix, column-major.
Sourcefn update_models(&mut self, updates: &[(u32, [[f32; 4]; 4])])
fn update_models(&mut self, updates: &[(u32, [[f32; 4]; 4])])
Push this frame’s changed model matrices, one (draw slot, matrix)
entry per moved draw object, applied in order. Batched so the trait is
crossed once per frame rather than once per entity; the caller sends
only slots whose matrix actually changed. An out-of-range slot is
ignored.
Sourcefn retire_draw_object(&mut self, draw_idx: usize)
fn retire_draw_object(&mut self, draw_idx: usize)
Retire a draw object: hide it from every pass (main, shadow, velocity)
and exclude it from the ray-tracing acceleration structure, so a
despawned entity’s slot leaves no ghost. The slot’s geometry buffers are
untouched; the engine’s draw-slot allocator returns the index to its
free list so a later clone_static_draw_object can recycle it. A no-op
if the index is out of range.
Sourcefn upload_skinned(
&mut self,
vertices: &[SkinnedVertex],
indices: &[u32],
draw_objects: Vec<SkinnedDrawObject>,
vert_bytes: &[u8],
frag_bytes: &[u8],
shadow_bytes: &[u8],
) -> RenderResult<()>
fn upload_skinned( &mut self, vertices: &[SkinnedVertex], indices: &[u32], draw_objects: Vec<SkinnedDrawObject>, vert_bytes: &[u8], frag_bytes: &[u8], shadow_bytes: &[u8], ) -> RenderResult<()>
Skinning. vert_bytes and shadow_bytes are Metal-only payloads;
DX/VK ignore them.
Sourcefn update_skinned_pose(
&mut self,
skinned_index: usize,
matrices: &[[[f32; 4]; 4]],
)
fn update_skinned_pose( &mut self, skinned_index: usize, matrices: &[[[f32; 4]; 4]], )
Push one skinned slot’s joint matrices for this frame.
Sourcefn evict_texture_slot(&mut self, slot: usize) -> Result<(), String>
fn evict_texture_slot(&mut self, slot: usize) -> Result<(), String>
Texture streaming. Albedo and normal maps share one handle-indexed pool, so every streamed texture (whatever its role) flows through these. The image carries its GPU format and mip chain: RGBA8 regenerates mips on upload, block-compressed formats upload their chain verbatim.
Sourcefn update_texture_slot(
&mut self,
slot: usize,
image: &TextureImage,
) -> RenderResult<()>
fn update_texture_slot( &mut self, slot: usize, image: &TextureImage, ) -> RenderResult<()>
Replace a texture slot’s image after a streaming upload.
Sourcefn evict_mesh(
&mut self,
draw_idx: usize,
retire_frame: u64,
) -> Result<(), String>
fn evict_mesh( &mut self, draw_idx: usize, retire_frame: u64, ) -> Result<(), String>
Mesh streaming.
Sourcefn upload_mesh(
&mut self,
draw_idx: usize,
verts: &[Vertex],
idxs: &[u16],
frame: u64,
) -> RenderResult<()>
fn upload_mesh( &mut self, draw_idx: usize, verts: &[Vertex], idxs: &[u16], frame: u64, ) -> RenderResult<()>
Upload a streamed mesh’s geometry into a draw slot.
Sourcefn setup_chunk_streaming(
&mut self,
chunk_vtx_bytes: usize,
chunk_idx_bytes: usize,
texture_slot: usize,
normal_map_slot: usize,
) -> RenderResult<()>
fn setup_chunk_streaming( &mut self, chunk_vtx_bytes: usize, chunk_idx_bytes: usize, texture_slot: usize, normal_map_slot: usize, ) -> RenderResult<()>
Voxel-world chunk streaming. texture_slot and normal_map_slot
are ignored by Metal (it binds chunk textures per draw).
Sourcefn add_chunk_mesh(
&mut self,
mesh: ChunkMesh<'_>,
dst: SlotAlloc,
) -> RenderResult<()>
fn add_chunk_mesh( &mut self, mesh: ChunkMesh<'_>, dst: SlotAlloc, ) -> RenderResult<()>
The destination draw slot comes from the engine’s allocator, like
clone_static_draw_object; the freed slot is likewise returned to it by
the caller of remove_chunk_mesh.
Provided Methods§
Sourcefn upload_skinned_morphs(&mut self, _morphs: Vec<Option<Arc<PayloadMorphs>>>)
fn upload_skinned_morphs(&mut self, _morphs: Vec<Option<Arc<PayloadMorphs>>>)
Attach morph-target data to the skinned draw objects, called once after
upload_skinned: morphs[i] belongs to draw object i (instance
copies share their template’s data via the Arc). Default no-op for a
backend without a morph deformation path.
Sourcefn update_morph_weights(&mut self, _skinned_index: usize, _weights: &[f32])
fn update_morph_weights(&mut self, _skinned_index: usize, _weights: &[f32])
Push a skinned object’s current morph-target weights, sampled by the animation system each frame. A no-op when the index is out of range or the object carries no morph targets.
Sourcefn reveal_skinned_instance(
&mut self,
_instance_index: usize,
_model: [[f32; 4]; 4],
)
fn reveal_skinned_instance( &mut self, _instance_index: usize, _model: [[f32; 4]; 4], )
Reveal the pre-reserved skinned instance at instance_index (a hidden
bind-pose copy expanded at load): show it at model and reset its
palette to bind so it does not flash a previous occupant’s pose. Which
instance to use is decided by the engine’s instance pool; the backend
only applies it. A no-op if the index is out of range.
Sourcefn retire_skinned_draw_object(&mut self, _skinned_index: usize)
fn retire_skinned_draw_object(&mut self, _skinned_index: usize)
Hide a live skinned instance. The engine’s instance pool returns the slot for reuse; the backend only hides it. A no-op if the index is out of range.
Sourcefn update_skinned_models(&mut self, _updates: &[(u32, [[f32; 4]; 4])])
fn update_skinned_models(&mut self, _updates: &[(u32, [[f32; 4]; 4])])
Push this frame’s changed skinned model-to-world matrices, one
(skinned index, matrix) entry per moved instance, applied in order
(a skinned object animates in place unless something moves it). Cheap:
the per-frame cull rebuild reads the object’s model directly, so this
just writes the fields. Out-of-range indices are ignored; default
no-op for a backend without movable skinned instances.
Sourcefn seed_mesh_streaming(
&mut self,
vtx_offset: u64,
vtx_bytes: u64,
idx_offset: u64,
idx_bytes: u64,
)
fn seed_mesh_streaming( &mut self, vtx_offset: u64, vtx_bytes: u64, idx_offset: u64, idx_bytes: u64, )
Seed the streamed-mesh sub-allocators with one reserved headroom block
(byte ranges in the shared vertex / index buffers) instead of the
per-mesh build-time regions. Used by the shrinkable-seed path: the
streamed geometry is no longer baked into the buffers at build time, so
the renderer hands the allocators one contiguous block sized to the
cap-many resident meshes rather than the whole streamed set. Implemented
on Metal + DirectX + Vulkan. Default no-op: a backend without the
shrinkable seed keeps freeing each mesh’s build-time region in
setup_mesh_streaming.
Sourcefn capabilities(&self) -> DeviceCapabilities
fn capabilities(&self) -> DeviceCapabilities
Device capability flags, queried from the GPU once the backend is built. Read by GraphicsSystem to gray out + disable settings rows the device cannot honor. Default: all capable, so a backend that does not report capabilities keeps every toggle live (the feature then no-ops with a warning on an incapable device, as before).
Sourcefn gpu_profile(&self) -> GpuProfile
fn gpu_profile(&self) -> GpuProfile
Coarse GPU performance profile, queried once the backend is built. Read at
init to pick default graphics quality on first launch. Default: UNKNOWN
(the conservative tier), so a backend that does not report a profile never
makes the resolver auto-select a high preset.
Sourcefn logical_size(&self) -> (f32, f32)
fn logical_size(&self) -> (f32, f32)
The overlay coordinate space: the window’s content size in logical,
DPI-independent units (points on macOS, client pixels on Windows, window
coordinates on Linux). Every backend reports the cursor in these same
units, so UI hit-testing, text layout, and the overlay shader’s divide to
NDC all share one space regardless of the backing scale. A backend
converts to attachment pixels only where a pixel rect is unavoidable,
through fullscreen::clip_rect_to_scissor.
Default (0.0, 0.0) for a headless backend with no window.
Sourcefn render_stats(&self) -> RenderStats
fn render_stats(&self) -> RenderStats
Per-frame draw-call / object counters. Default no-op so a backend that tracks none still satisfies the trait; all three shipping backends override it.
Show or hide the OS cursor for an in-engine UI cursor (e.g. a MainMenu), independent of camera capture. Edge-triggered by the backend, so calling it every frame with the same value is cheap. Default no-op: a backend without a free-mode cursor hide leaves the system cursor visible (DX / Vulkan today).
Sourcefn cursor_outside_window(&self) -> bool
fn cursor_outside_window(&self) -> bool
Whether the real cursor has left the window, so an in-engine UI cursor
should stop drawing (windowed / borderless). The backend confines the
cursor to the active screen while in fullscreen, so it reports false
there. Default false (inside): backends without window-bounds tracking
(DX / Vulkan today) always draw the in-engine cursor.
Tell the backend a togglable menu (a Screen toggled by an Escape KeyBinding) coexists with a captured camera. In this mode Escape routes to the ECS (so the menu shows/hides) instead of releasing the cursor inline, and a click never recaptures the cursor (it fires a UI action). Set once at setup. Default no-op: backends without dynamic capture (DX / Vulkan today) keep the static behavior.
Sourcefn set_camera_capture(&mut self, capture: bool)
fn set_camera_capture(&mut self, capture: bool)
Drive cursor capture from the menu state each frame: capture for camera control, release while a menu is open. Edge-triggered by the backend. Default no-op (DX / Vulkan): they keep their startup capture decision.
Sourcefn set_reflection_probes(&mut self, probes: &[ProbePlacement])
fn set_reflection_probes(&mut self, probes: &[ProbePlacement])
Supply the reflection-probe placements (from declared ReflectionProbe
assets, or empty to auto-seed from the scene bounds). The backend bakes a
cube per placement and samples the nearest for the specular reflection.
Pushed once after construction. Default no-op: backends without probe
support (DX / Vulkan today) keep the sky reflection.
Sourcefn set_vsync(&mut self, on: bool)
fn set_vsync(&mut self, on: bool)
Turn display sync (vsync) on or off at runtime, applied to presentation. Edge-triggered by the backend, so calling it with the unchanged value is cheap. Default no-op: a backend that only honors vsync at init ignores runtime changes.
Sourcefn set_window_mode(&mut self, mode: WindowMode)
fn set_window_mode(&mut self, mode: WindowMode)
Switch the window between windowed / borderless / fullscreen at runtime. The change flows through the backend’s normal resize path (no GPU rebuild beyond the resize it triggers). Default no-op for backends without a window (embedded / preview) or that don’t yet implement it.
Sourcefn set_window_size(&mut self, width: u32, height: u32)
fn set_window_size(&mut self, width: u32, height: u32)
Resize the window’s content area at runtime (meaningful in windowed mode). Drives the same resize path as a user-dragged resize. Default no-op for backends without a window or that don’t yet implement it.
Sourcefn display_modes(&self) -> Vec<DisplayMode>
fn display_modes(&self) -> Vec<DisplayMode>
The display modes (pixel resolution + refresh rate) the display this backend renders to supports, unshaped (the caller dedups + sorts). Default empty: a backend that cannot enumerate (or has no window) makes the Resolution row fall back to the static preset list.
Sourcefn current_display_mode(&self) -> Option<DisplayMode>
fn current_display_mode(&self) -> Option<DisplayMode>
The mode the display is currently running, if the backend can read it.
Shown by the Resolution row when the user has never chosen a mode (the
display keeps its desktop mode until one is chosen). Default None.
Sourcefn set_display_mode(&mut self, mode: DisplayMode)
fn set_display_mode(&mut self, mode: DisplayMode)
Select the display mode to hold while the window is in fullscreen. The backend applies it whenever the window is (or becomes) fullscreen and restores the display’s original mode when the window leaves fullscreen or shuts down; outside fullscreen the choice is only remembered. Default no-op: a backend without mode switching leaves the display alone.
Sourcefn update_post_process(&mut self, tunables: PostProcessTunables)
fn update_post_process(&mut self, tunables: PostProcessTunables)
Replace the live post-process tunables (bloom / exposure / vignette / LUT blend / FXAA). These are pushed to the bloom + composite shaders each frame, so a change takes effect on the next draw with no allocation or pipeline rebuild. Only the authored half travels here: the composite’s display-output flags belong to the display the backend negotiated with at init, so a push cannot disturb them. Default no-op: a backend that only reads the tunables at init ignores runtime changes.
Sourcefn set_ambient_intensity(&mut self, value: f32)
fn set_ambient_intensity(&mut self, value: f32)
Set the live ambient (IBL) light scale. Unlike the post-process params
above, ambient_intensity lives in the shared LightUniforms (uploaded
each frame by the main lighting pass), so it takes its own setter rather
than update_post_process. Default no-op: only Metal mutates it live
today; DirectX / Vulkan keep the init-time value (they read it at init).
Sourcefn update_directional_lights(&mut self, lights: &[DirectionalLight])
fn update_directional_lights(&mut self, lights: &[DirectionalLight])
Replace the live directional-light set (the sun). Unlike the local
lights, which ride a per-scene storage buffer sized once at init, the
directional slots are a fixed-size array in the shared LightUniforms,
so a new set is written in place: the backend re-packs the array and
re-caches whatever it derived from the first light at init (the cascade
shadow direction, the fog sun). Default no-op: a backend that only reads
the lights at init keeps the init-time sun.
Sourcefn set_keymap(&mut self, keymap: &KeyMap)
fn set_keymap(&mut self, keymap: &KeyMap)
Push the gameplay movement key map. The backend resolves each canonical
InputKey to its native key code and decodes physical key events through the
map (instead of hardcoded keys), so a settings-menu rebind takes effect on
the next key event. Pushed once after the backend is built and again on
each rebind. Default no-op: a backend without keymap decode keeps its
built-in defaults.
Sourcefn apply_quality_settings(&mut self, settings: QualitySettings)
fn apply_quality_settings(&mut self, settings: QualitySettings)
Apply a change to the quality-feature toggles (TAA / SSAO / SSR / RT reflections / SSGI / auto-exposure) live. Unlike the post-process params, these gate render passes whose GPU resources (pipelines, render targets, ray-tracing acceleration structures) are built once at init, so applying a change rebuilds the affected resources in place rather than flipping a uniform. Default no-op: a backend that only reads these at init ignores runtime changes (DirectX / Vulkan today), so the choice persists and takes effect at the next launch there.
Sourcefn set_shadow_update(&mut self, update: ShadowUpdate)
fn set_shadow_update(&mut self, update: ShadowUpdate)
Set the shadow cascade re-render cadence live. The cascade scheduler reads the policy at the start of each shadow pass, so a change takes effect on the next draw with no pipeline rebuild or allocation (unlike the shadow map resolution, which is sized once at init). Default no-op: a backend that only reads the cadence at init keeps the init-time value (DirectX / Vulkan today), so the choice persists and takes effect at the next launch there.
Sourcefn set_shadow_distance(&mut self, distance: u32)
fn set_shadow_distance(&mut self, distance: u32)
Set the shadow distance (world units the cascades cover, capped at the camera far plane) live. The per-frame cascade-split computation reads it each draw, so a change takes effect on the next frame with no allocation or rebuild (it sizes no GPU resource, unlike the shadow map resolution). Default no-op: a backend that only reads the distance at init keeps the init-time value (DirectX / Vulkan today), so the choice persists and takes effect at the next launch there.
Sourcefn set_shadow_cascades(&mut self, count: u32)
fn set_shadow_cascades(&mut self, count: u32)
Set the live shadow cascade count (1..=4). The cascade-split math + the
re-render schedule read it each frame and only the first count cascades
are projected, rendered, and sampled (the array capacity stays 4), so a
change takes effect on the next frame with no resize or rebuild. Default
no-op: a backend that only reads the count at init keeps the init-time
value (DirectX / Vulkan today), so the choice persists and takes effect at
the next launch there.
Sourcefn update_quality_params(&mut self, settings: QualitySettings)
fn update_quality_params(&mut self, settings: QualitySettings)
Update the live scalar sub-tunables of the SSAO / SSR / SSGI / auto-exposure
passes (radius, intensity, distance, EV bounds, adaptation speed). Unlike
apply_quality_settings, this rebuilds nothing: each backend re-reads these
values from its stored *Settings structs into a per-frame uniform every
draw, so mutating them takes effect on the next frame with no pipeline /
target rebuild and no TAA-history reset. Only the fields of a feature that is
currently on are honoured (its settings are present); a value for an off
feature is ignored here and applies when the feature next turns on. The
structural sub-knobs (gather resolution, ray / step counts) are NOT live and
still ride apply_quality_settings. Default no-op: a backend that reads
these only at init keeps the init-time values (DirectX / Vulkan today), so
the choice persists and takes effect at the next launch there.
Sourcefn shader_reload_flag(&self) -> Option<Arc<AtomicBool>>
fn shader_reload_flag(&self) -> Option<Arc<AtomicBool>>
Shared atomic flag the backend polls at frame start to trigger a
shader rebuild. Some only under cn debug on backends that ship
hot-reload (Metal today); None on production runs and on backends
that have not implemented hot-reload yet. The debug server reads this
to forward reload-shaders commands; the filesystem watcher writes
it directly. Default: None.
Sourcefn update_color_lut(&mut self, size: u32, data: &[u8]) -> Result<(), String>
fn update_color_lut(&mut self, size: u32, data: &[u8]) -> Result<(), String>
Replace the live colour-grading LUT with a fresh size³ RGBA8 payload.
Driven by asset hot-reload (cn debug only). Default no-op: backends
that have not implemented the swap leave the LUT bound at whatever
payload was uploaded at init.
Sourcefn draw_geometry_size(&self, draw_idx: usize) -> Option<(usize, usize)>
fn draw_geometry_size(&self, draw_idx: usize) -> Option<(usize, usize)>
(vertex_count, index_count) for the static draw at draw_idx, or
None when the index is out of range / the backend does not expose
the field. Used by asset hot-reload to detect size-changing
reloads before attempting Self::update_mesh_geometry, which
rejects size mismatches. Default returns None; backends that
implement the rebuild path also override this.
Sourcefn draw_lod_index_counts(&self, draw_idx: usize) -> Option<Vec<usize>>
fn draw_lod_index_counts(&self, draw_idx: usize) -> Option<Vec<usize>>
Per-LOD-alternate index counts for the static draw at draw_idx,
ordered from LOD1 upward (LOD0 is reported by
Self::draw_geometry_size). Returns None when the index is out of
range or the backend does not expose its LOD layout. Used by asset
hot-reload alongside Self::draw_geometry_size to detect
size-changing reloads: a .glb that re-exports with a different LOD
breakdown queues the entry for Self::rebuild_static_geometry
instead of Self::update_mesh_geometry’s in-place write.
Sourcefn rebuild_static_geometry(
&mut self,
changes: Vec<DrawGeometryUpdate>,
) -> RenderResult<()>
fn rebuild_static_geometry( &mut self, changes: Vec<DrawGeometryUpdate>, ) -> RenderResult<()>
Rebuild the shared static-mesh vertex + index buffers, replacing the
geometry of each DrawGeometryUpdate.draw_idx with the new
vertices / indices / LOD alternates. Draws not named in changes
keep their current geometry, copied byte-for-byte from the live
buffers. The slot’s vertex_count, index_count, and
lod_alternates index offsets are rewritten as the new buffers are
laid out. Driven by asset hot-reload (cn debug only) when a
size-changing .glb re-export means the existing
Self::update_mesh_geometry in-place write no longer fits.
wait_idle first; the rebuild swaps the GPU buffers wholesale.
Default no-op: backends that have not implemented the rebuild
return Ok(()) and the size-changing reload is logged + skipped at
the caller (the existing in-place path already errored on size
mismatch).
Sourcefn update_skinned_mesh_geometry(
&mut self,
skinned_index: usize,
vertex_base: u32,
verts: &[SkinnedVertex],
idxs: &[u16],
) -> Result<(), String>
fn update_skinned_mesh_geometry( &mut self, skinned_index: usize, vertex_base: u32, verts: &[SkinnedVertex], idxs: &[u16], ) -> Result<(), String>
Replace a SkinnedMesh draw slot’s vertex + index data in place.
Driven by asset hot-reload (cn debug only). Reuses the slot’s
existing vertex region + index region in the shared skinned vertex /
index buffers (created once by Self::upload_skinned), so the new
geometry must match the slot’s init-time vertex count + index count
and the new skeleton must keep the same joint count; pipelines stay
untouched, only the bytes change. vertex_base is the init-time
vertex offset (in vertex units) into the shared buffer; indices are
rebased onto it before writing. Default no-op.
Sourcefn rebuild_skinned_geometry(
&mut self,
changes: Vec<SkinnedDrawGeometryUpdate>,
) -> Result<Vec<SkinnedSlotLayout>, String>
fn rebuild_skinned_geometry( &mut self, changes: Vec<SkinnedDrawGeometryUpdate>, ) -> Result<Vec<SkinnedSlotLayout>, String>
Rebuild the shared skinned-mesh vertex + index buffers, replacing the
geometry of each SkinnedDrawGeometryUpdate.skinned_index with the
new vertices / indices. Slots not named in changes keep their
current geometry, copied byte-for-byte from the live buffers and
re-based onto the new vertex region they land in. Returns the
post-rebuild layout (one SkinnedSlotLayout per slot, in
skinned_index order) so the caller can refresh its source-map
vertex_base / vertex_count / index_count to point at the new
regions. Driven by asset hot-reload (cn debug only) when a
size-changing .glb re-export means the existing
Self::update_skinned_mesh_geometry in-place write no longer fits.
The backend wait_idles first; the rebuild swaps the GPU buffers
wholesale. The skinned pipelines, shadow + velocity + SSAO + SSR
variants, and skinned_draw_objects slot metadata
(texture_slot / normal_map_slot / material / joint_count)
all stay untouched; only the index_offset / index_count on each
SkinnedDrawObject (and the buffers themselves) move. Default no-op
(returns an empty layout vec): backends that have not implemented
the rebuild leave the size-changing reload as logged + skipped at
the caller, the same behaviour as before, since the in-place path
already errored on size mismatch.
Sourcefn update_skinned_skeleton(
&mut self,
skinned_index: usize,
new_joint_count: usize,
) -> Result<(), String>
fn update_skinned_skeleton( &mut self, skinned_index: usize, new_joint_count: usize, ) -> Result<(), String>
Update a skinned slot’s joint count and resize the backend’s per-slot
joint-matrix buffers to match. Driven by asset hot-reload (cn debug
only) when a re-imported .glb’s skeleton has a different joint
count than the slot was initialised with. Shrinking truncates the
per-slot Vec; growing seeds the new entries to identity so the slot
renders undeformed on the next update_skinned_pose. The skinned
shaders consume the joints buffer through a pointer (not a fixed-
size array) and use vertex-attribute-encoded joint indices, so no
pipeline or shader rebuild is required for a joint-count change;
only the CPU-side per-slot buffer and SkinnedDrawObject.joint_count
change. Default no-op: backends that have not implemented the resize
leave the skeleton-shape change logged + skipped at the caller.
Sourcefn update_mesh_geometry(
&mut self,
draw_idx: usize,
verts: &[Vertex],
idxs: &[u16],
lod_alternates: &[(f32, Vec<u16>)],
) -> Result<(), String>
fn update_mesh_geometry( &mut self, draw_idx: usize, verts: &[Vertex], idxs: &[u16], lod_alternates: &[(f32, Vec<u16>)], ) -> Result<(), String>
Replace a Mesh draw slot’s vertex + index data in place. Driven by
asset hot-reload (cn debug only). Reuses the slot’s existing offset
in the shared vertex / index buffers, so the new geometry must match
the slot’s init-time vertex count + index count; a size-changing
reload returns an error so the caller can queue
Self::rebuild_static_geometry instead, which repacks the shared
buffers. Each entry in
lod_alternates ((switch_distance, mesh-relative indices)) is
written to the matching slot’s pre-allocated LOD index region; the
number of LODs and each LOD’s index count must match the slot’s
init-time layout, otherwise the call returns an error so the caller
can queue Self::rebuild_static_geometry. switch_distance is
re-stored per LOD so a JSON-side tweak to lod_distances propagates
without a process restart. Default no-op.
Sourcefn update_environment_map(&mut self, payload: &[u8]) -> RenderResult<()>
fn update_environment_map(&mut self, payload: &[u8]) -> RenderResult<()>
Replace the live IBL environment map with a freshly precomputed payload.
payload is the serialised byte format emitted by
concinnity_core::build::environment_map::compile_environment_map_payload
(header + irradiance cube + prefilter mip chain), so init and hot-reload
share a single byte format. Driven by asset hot-reload (cn debug
only). Default no-op: backends that have not implemented the swap leave
the IBL cubes bound at whatever payload was uploaded at init.
Sourcefn update_fog_settings(&mut self, settings: Option<FogSettings>)
fn update_fog_settings(&mut self, settings: Option<FogSettings>)
Replace the live volumetric-fog settings, or disable the fog pass when
None. Driven by world.jsonl hot-reload (cn debug only). Default
no-op: backends that have not implemented the swap leave the fog pass
at whatever settings were resolved at init.
A backend that built its fog pipeline lazily based on the world’s
init-time VolumetricFog cannot enable the pass via this call when
the world started with no fog declared; re-enabling fog on a world
that did not declare it at startup requires a relaunch.
Sourcefn screenshot(&mut self, path: &str) -> Result<String, String>
fn screenshot(&mut self, path: &str) -> Result<String, String>
Capture the last presented frame to a PNG at path and return the saved
path. Driven by the cn debug WS screenshot command for headless
on-GPU render verification. Default Err: a backend without a capture
path reports it unsupported (all current backends override this).
Sourcefn clone_static_draw_object(
&mut self,
src_draw_idx: usize,
model: [[f32; 4]; 4],
dst: SlotAlloc,
) -> Result<(), String>
fn clone_static_draw_object( &mut self, src_draw_idx: usize, model: [[f32; 4]; 4], dst: SlotAlloc, ) -> Result<(), String>
Instantiate a runtime copy of an existing draw object at a new transform:
re-use the source slot’s geometry region (vertex_offset / vertex_count
/ index_offset / index_count / base_vertex / lod_alternates) and
copy its texture slots, material, and cull distance, swapping only the
model matrix. The new slot reuses one freed by retire_draw_object before
growing the draw-object vec. The destination slot comes from the
engine’s draw-slot allocator: Reuse overwrites a vacated entry,
Append grows the vec (the index always equals the current length,
which implementations debug-assert). Driven by runtime entity spawn
(SpawnRequest). The copy is non-cullable (sentinel AABB) and drawn
every frame, since the init-time BVH cannot refit to admit a slot added
at runtime; moving copies (the common case) opt out of the static BVH
exactly like streamed chunks and held items. Default no-op (returns
Err): backends without an implementation leave the spawn path
logged + skipped at the caller.
Sourcefn set_draw_material(
&mut self,
draw_idx: usize,
material: MaterialUniforms,
texture_slot: usize,
normal_map_slot: usize,
)
fn set_draw_material( &mut self, draw_idx: usize, material: MaterialUniforms, texture_slot: usize, normal_map_slot: usize, )
Rewrite a draw slot’s material parameters + texture/normal-map pool
indices in place. Driven by the editor’s live draw seam when a Prop edits
its material arg. Default no-op; a backend that implements it reports
DeviceCapabilities::rewrites_draws, which is what the caller gates on
rather than pushing an edit that would not land.
Sourcefn set_draw_cull_distance(&mut self, draw_idx: usize, cull_distance: f32)
fn set_draw_cull_distance(&mut self, draw_idx: usize, cull_distance: f32)
Rewrite a draw slot’s cull_distance in place. Driven by the editor’s
live draw seam when a Prop edits its cull_distance arg. Default no-op,
gated by the same DeviceCapabilities::rewrites_draws flag.
Sourcefn add_decal(&mut self, record: DecalRecord) -> Result<usize, String>
fn add_decal(&mut self, record: DecalRecord) -> Result<usize, String>
Append a projected-decal record at runtime, returning a stable slot
index the caller hands to Self::remove_decal later. Lets a
gameplay system stamp bullet holes, footprints, or other ad-hoc
decals after the world has built. Backends that have not implemented
the runtime path return Err; the caller logs and drops the request.
Sourcefn remove_decal(&mut self, decal_id: usize) -> Result<(), String>
fn remove_decal(&mut self, decal_id: usize) -> Result<(), String>
Tombstone a runtime decal slot. The id returned by
Self::add_decal becomes invalid; the next add may reuse it.
Default no-op-with-Err: backends without a runtime path leave the
remove logged + skipped at the caller.
Sourcefn add_emitter(
&mut self,
record: ParticleEmitterRecord,
) -> Result<usize, String>
fn add_emitter( &mut self, record: ParticleEmitterRecord, ) -> Result<usize, String>
Append a particle-emitter record at runtime, returning a stable slot index. The backend allocates the per-emitter GPU pool + atomic spawn counter (matching the init-time path) so the compute kernel can begin ticking on the next frame. Default no-op-with-Err.
Sourcefn remove_emitter(&mut self, emitter_id: usize) -> Result<(), String>
fn remove_emitter(&mut self, emitter_id: usize) -> Result<(), String>
Tombstone a runtime emitter slot and release its GPU pool + counter buffers (the GPU keeps them alive via its own refcount until any in-flight command buffer that referenced them completes). Default no-op-with-Err.
Sourcefn update_world_shader_pipelines(
&mut self,
vert_bytes: Option<&[u8]>,
frag_bytes: Option<&[u8]>,
shadow_bytes: Option<&[u8]>,
vert_instanced_bytes: Option<&[u8]>,
) -> Result<(), String>
fn update_world_shader_pipelines( &mut self, vert_bytes: Option<&[u8]>, frag_bytes: Option<&[u8]>, shadow_bytes: Option<&[u8]>, vert_instanced_bytes: Option<&[u8]>, ) -> Result<(), String>
Rebuild the live main / instanced / shadow render pipelines from
freshly compiled world-loaded shader stage bytes. Driven by asset
hot-reload (cn debug only) when one of the captured Shader
source files is saved or a debug-WS reload-assets command fires.
Each Some(bytes) replaces the matching live pipeline (and any
dependent state: bindless-texture argument encoder, cull pipeline,
instanced variant, shadow variant); None leaves the pipeline
untouched (e.g. a world without an instanced shader passes None
for the instanced slot). The backend should build every replacement
into a temporary first and only swap when every build succeeds;
mirrors the safety pattern in the Metal backend’s hot_reload so a
compile error never overwrites a live pipeline with a half-built
replacement. Default no-op (returns Err): backends without an
implementation leave the world-loaded shader reload logged + skipped
at the caller.
Skinned-mesh variants are out of scope here: their pipelines depend
on the world’s SkinnedMesh-injected library bytes that
Self::upload_skinned consumes and drops.
Sourcefn install_world_shader(
&mut self,
bucket: u32,
shader: ShaderBytes<'_>,
) -> RenderResult<()>
fn install_world_shader( &mut self, bucket: u32, shader: ShaderBytes<'_>, ) -> RenderResult<()>
Build the render pipeline for one shader bucket from its compiled stage
bytes, making draws that carry that bucket renderable. Called by the
streaming pump when a scene that exclusively owns the bucket’s Shader
pins: init skipped the build, so this is where the cost lands (behind
the loading screen, since the bucket counts as scene-resident content).
Bucket 0 is the world default program and is never installed this way.
Default no-op-with-Ok: a backend that renders every draw with the world default program has no per-bucket pipeline to build, and the bucket is resident as far as scene loading is concerned.
Sourcefn evict_world_shader(&mut self, bucket: u32)
fn evict_world_shader(&mut self, bucket: u32)
Release one shader bucket’s render pipeline, undoing
Self::install_world_shader. Called when the owning scene unpins;
draws carrying the bucket stop rendering until it is installed again.
Default no-op, for the same reason as above.
Sourcefn hot_swap_config(&self) -> Option<SwapchainConfig>
fn hot_swap_config(&self) -> Option<SwapchainConfig>
The swapchain-level configuration this live backend can hot-swap a world
onto, or None when the backend cannot reload a world in place (it must
be fully rebuilt instead). Read by GraphicsSystem when a transplanted
backend is handed a new world (the cn editor live SAVE): the swap reuses
the backend via Self::reload_world only when this equals the new
world’s BackendInit::swapchain_config; a None or a mismatch routes to a
full rebuild (recreating the window). Default None: DirectX / Vulkan
(and any backend without a real reload_world) always rebuild.
Sourcefn reload_world(&mut self, init: BackendInit<'_>) -> RenderResult<()>
fn reload_world(&mut self, init: BackendInit<'_>) -> RenderResult<()>
Re-upload a new world’s GPU content onto this already-constructed backend,
reusing the live device + window + swapchain instead of building a new one.
Driven by the cn editor live SAVE: after a structural edit recompiles the
blobs, GraphicsSystem transplants the running backend into the rebuilt
world and calls this so the edit applies without recreating the OS window
or re-initialising the GPU device. The backend waits for the GPU to idle,
drops the old world’s content resources, and rebuilds them from init on
the retained hardware. Only ever called when Self::hot_swap_config
reported a config matching init.swapchain_config(), so the swapchain
(pixel format / frames-in-flight / EDR) is guaranteed unchanged. Default
Err/unsupported: DirectX / Vulkan fall back to a full rebuild (no
regression; a real implementation is Windows-pending like the rest).
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".