Skip to main content

RenderBackend

Trait RenderBackend 

Source
pub trait RenderBackend: SceneControl + Send {
Show 73 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 top_content_inset(&self) -> 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§

Source

fn window_closed(&mut self) -> bool

Window / input lifecycle.

Source

fn capture_cursor(&mut self)

Confine the cursor to the window.

Source

fn take_input(&mut self) -> RenderInput

Take the input sampled since the last call.

Source

fn wait_idle(&self)

Block until the GPU has drained every submitted frame.

Source

fn draw_frame(&mut self, params: FrameParams<'_>) -> RenderResult<()>

Per-frame drive. See FrameParams for the inputs.

Source

fn update_view(&mut self, matrix: [[f32; 4]; 4])

Push the camera’s view matrix, column-major.

Source

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.

Source

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.

Source

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.

Source

fn update_skinned_pose( &mut self, skinned_index: usize, matrices: &[[[f32; 4]; 4]], )

Push one skinned slot’s joint matrices for this frame.

Source

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.

Source

fn update_texture_slot( &mut self, slot: usize, image: &TextureImage, ) -> RenderResult<()>

Replace a texture slot’s image after a streaming upload.

Source

fn evict_mesh( &mut self, draw_idx: usize, retire_frame: u64, ) -> Result<(), String>

Mesh streaming.

Source

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.

Source

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).

Source

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.

Source

fn remove_chunk_mesh( &mut self, draw_idx: usize, retire_frame: u64, ) -> Result<(), String>

Free a streamed chunk’s geometry, retiring it after retire_frame.

Source

fn set_chunk_model( &mut self, draw_idx: usize, model: [[f32; 4]; 4], ) -> Result<(), String>

Move a streamed chunk by replacing its placement matrix.

Provided Methods§

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

fn top_content_inset(&self) -> f32

Height of the window chrome overlapping the top of the render surface, in the logical units logical_size reports. Non-zero only where the content view runs under a transparent title bar (macOS), which leaves the OS window buttons floating over the frame’s top-left corner. UI that must stay clear of them starts below this; the frame itself still covers the whole window.

Default 0.0: a window whose content already begins below its chrome.

Source

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.

Source

fn set_ui_cursor_hidden(&mut self, hidden: bool)

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).

Source

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.

Source

fn set_menu_mode(&mut self, on: bool)

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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 crate::bake::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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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".

Implementors§