use crate::auto_exposure::AutoExposureSettings;
use crate::backend_init::{BackendInit, ShaderBytes, SwapchainConfig};
use crate::error::{RenderError, RenderResult};
use crate::input::RenderInput;
use crate::keymap::KeyMap;
use crate::mesh_payload::{SkinnedVertex, Vertex};
use crate::profile::RenderStats;
use crate::render_types::{
LineVertex, MaterialUniforms, PostProcessTunables, SkinnedDrawObject, TextDrawCall,
};
use crate::rt_reflections::RtReflectionSettings;
use crate::scene_flow::SceneControl;
use crate::ssao::SsaoSettings;
use crate::ssgi::SsgiSettings;
use crate::ssr::SsrSettings;
use crate::volumetric_fog::FogSettings;
use alloc::string::String;
use alloc::string::ToString;
use alloc::vec::Vec;
#[derive(Clone, Copy)]
pub struct FrameParams<'a> {
pub elapsed: f32,
pub fov_y_radians: f32,
pub near: f32,
pub far: f32,
pub cam_pos: [f32; 3],
pub text_calls: &'a [TextDrawCall],
pub lines: &'a [LineVertex],
pub world_hidden: bool,
pub view_mode: concinnity_core::gfx::view_modes::ViewMode,
pub show: concinnity_core::gfx::view_modes::ShowFlags,
}
#[derive(Clone, Copy)]
pub struct ChunkMesh<'a> {
pub verts: &'a [Vertex],
pub idxs: &'a [u16],
pub model: [[f32; 4]; 4],
pub texture_slot: usize,
pub normal_map_slot: usize,
pub material: MaterialUniforms,
pub frame: u64,
}
pub struct DrawGeometryUpdate {
pub draw_idx: usize,
pub vertices: Vec<Vertex>,
pub indices: Vec<u16>,
pub lod_alternates: Vec<(f32, Vec<u16>)>,
}
pub struct SkinnedDrawGeometryUpdate {
pub skinned_index: usize,
pub vertices: Vec<SkinnedVertex>,
pub indices: Vec<u16>,
}
pub struct SkinnedSlotLayout {
pub skinned_index: usize,
pub vertex_base: u32,
pub vertex_count: usize,
pub index_count: usize,
}
pub struct QualitySettings {
pub taa: bool,
pub ssao: Option<SsaoSettings>,
pub ssr: Option<SsrSettings>,
pub rt_reflections: Option<RtReflectionSettings>,
pub ssgi: Option<SsgiSettings>,
pub reflection_blur_scale: u32,
pub auto_exposure: Option<AutoExposureSettings>,
pub auto_exposure_bias_ev: f32,
}
#[derive(Clone, Copy, Debug)]
pub struct DeviceCapabilities {
pub ray_tracing: bool,
pub selectable_upscaler: bool,
pub reuses_build_slots: bool,
pub rewrites_draws: bool,
}
impl DeviceCapabilities {
pub const ALL: Self = Self {
ray_tracing: true,
selectable_upscaler: true,
reuses_build_slots: true,
rewrites_draws: true,
};
}
impl Default for DeviceCapabilities {
fn default() -> Self {
Self::ALL
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GpuVendor {
Apple,
Nvidia,
Amd,
Intel,
Other,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GpuTier {
Unknown,
Integrated,
EntryDiscrete,
MidDiscrete,
HighDiscrete,
}
#[derive(Clone, Copy, Debug)]
pub struct GpuProfile {
pub vendor: GpuVendor,
pub tier: GpuTier,
pub memory_budget_bytes: u64,
pub unified_memory: bool,
pub discrete: bool,
}
impl GpuProfile {
pub const UNKNOWN: Self = Self {
vendor: GpuVendor::Other,
tier: GpuTier::Unknown,
memory_budget_bytes: 0,
unified_memory: false,
discrete: false,
};
}
impl Default for GpuProfile {
fn default() -> Self {
Self::UNKNOWN
}
}
pub struct GpuClassInput {
pub vendor: GpuVendor,
pub memory_budget_bytes: u64,
pub discrete: bool,
pub apple_family: u8,
}
pub fn apple_family_from_device_name(name: &str) -> u8 {
let Some(rest) = name.strip_prefix("Apple M") else {
return 0;
};
let digits: String = rest.chars().take_while(char::is_ascii_digit).collect();
match digits.parse::<u8>() {
Ok(n) if n >= 1 => n.saturating_add(6),
_ => 0,
}
}
pub fn classify_tier(input: &GpuClassInput) -> GpuTier {
const GB: u64 = 1 << 30;
if input.vendor == GpuVendor::Apple && input.apple_family >= 7 {
return match input.apple_family {
7 => GpuTier::EntryDiscrete, 8 => GpuTier::MidDiscrete, _ => GpuTier::HighDiscrete, };
}
if !input.discrete {
return GpuTier::Integrated;
}
match input.memory_budget_bytes {
0 => GpuTier::Unknown,
b if b >= 12 * GB => GpuTier::HighDiscrete,
b if b >= 6 * GB => GpuTier::MidDiscrete,
_ => GpuTier::EntryDiscrete,
}
}
pub trait RenderBackend: SceneControl + Send {
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 upload_skinned_morphs(
&mut self,
_morphs: Vec<Option<alloc::sync::Arc<crate::mesh_payload::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 evict_texture_slot(&mut self, slot: usize) -> Result<(), String>;
fn update_texture_slot(
&mut self,
slot: usize,
image: &crate::build::texture::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 seed_mesh_streaming(
&mut self,
vtx_offset: u64,
vtx_bytes: u64,
idx_offset: u64,
idx_bytes: u64,
) {
let _ = (vtx_offset, vtx_bytes, idx_offset, idx_bytes);
}
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: crate::draw_slot::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>;
fn capabilities(&self) -> DeviceCapabilities {
DeviceCapabilities::ALL
}
fn gpu_profile(&self) -> GpuProfile {
GpuProfile::UNKNOWN
}
fn logical_size(&self) -> (f32, f32) {
(0.0, 0.0)
}
fn render_stats(&self) -> RenderStats {
RenderStats::default()
}
fn set_ui_cursor_hidden(&mut self, hidden: bool) {
let _ = hidden;
}
fn cursor_outside_window(&self) -> bool {
false
}
fn set_menu_mode(&mut self, on: bool) {
let _ = on;
}
fn set_camera_capture(&mut self, capture: bool) {
let _ = capture;
}
fn set_reflection_probes(&mut self, probes: &[crate::reflection_probe::ProbePlacement]) {
let _ = probes;
}
fn set_vsync(&mut self, on: bool) {
let _ = on;
}
fn set_window_mode(&mut self, mode: crate::components::WindowMode) {
let _ = mode;
}
fn set_window_size(&mut self, width: u32, height: u32) {
let _ = (width, height);
}
fn display_modes(&self) -> Vec<crate::display_mode::DisplayMode> {
Vec::new()
}
fn current_display_mode(&self) -> Option<crate::display_mode::DisplayMode> {
None
}
fn set_display_mode(&mut self, mode: crate::display_mode::DisplayMode) {
let _ = mode;
}
fn update_post_process(&mut self, tunables: PostProcessTunables) {
let _ = tunables;
}
fn set_ambient_intensity(&mut self, value: f32) {
let _ = value;
}
fn update_directional_lights(&mut self, lights: &[crate::components::DirectionalLight]) {
let _ = lights;
}
fn set_keymap(&mut self, keymap: &KeyMap) {
let _ = keymap;
}
fn apply_quality_settings(&mut self, settings: QualitySettings) {
let _ = settings;
}
fn set_shadow_update(&mut self, update: crate::components::ShadowUpdate) {
let _ = update;
}
fn set_shadow_distance(&mut self, distance: u32) {
let _ = distance;
}
fn set_shadow_cascades(&mut self, count: u32) {
let _ = count;
}
fn update_quality_params(&mut self, settings: QualitySettings) {
let _ = settings;
}
fn shader_reload_flag(&self) -> Option<alloc::sync::Arc<core::sync::atomic::AtomicBool>> {
None
}
fn update_color_lut(&mut self, size: u32, data: &[u8]) -> Result<(), String> {
let _ = (size, data);
Ok(())
}
fn draw_geometry_size(&self, draw_idx: usize) -> Option<(usize, usize)> {
let _ = draw_idx;
None
}
fn draw_lod_index_counts(&self, draw_idx: usize) -> Option<Vec<usize>> {
let _ = draw_idx;
None
}
fn rebuild_static_geometry(&mut self, changes: Vec<DrawGeometryUpdate>) -> RenderResult<()> {
let _ = changes;
Ok(())
}
fn update_skinned_mesh_geometry(
&mut self,
skinned_index: usize,
vertex_base: u32,
verts: &[SkinnedVertex],
idxs: &[u16],
) -> Result<(), String> {
let _ = (skinned_index, vertex_base, verts, idxs);
Ok(())
}
fn rebuild_skinned_geometry(
&mut self,
changes: Vec<SkinnedDrawGeometryUpdate>,
) -> Result<Vec<SkinnedSlotLayout>, String> {
let _ = changes;
Ok(Vec::new())
}
fn update_skinned_skeleton(
&mut self,
skinned_index: usize,
new_joint_count: usize,
) -> Result<(), String> {
let _ = (skinned_index, new_joint_count);
Ok(())
}
fn update_mesh_geometry(
&mut self,
draw_idx: usize,
verts: &[Vertex],
idxs: &[u16],
lod_alternates: &[(f32, Vec<u16>)],
) -> Result<(), String> {
let _ = (draw_idx, verts, idxs, lod_alternates);
Ok(())
}
fn update_environment_map(&mut self, payload: &[u8]) -> RenderResult<()> {
let _ = payload;
Ok(())
}
fn update_fog_settings(&mut self, settings: Option<FogSettings>) {
let _ = settings;
}
fn screenshot(&mut self, path: &str) -> Result<String, String> {
let _ = path;
Err("screenshot capture not supported on this backend".to_string())
}
fn clone_static_draw_object(
&mut self,
src_draw_idx: usize,
model: [[f32; 4]; 4],
dst: crate::draw_slot::SlotAlloc,
) -> Result<(), String> {
let _ = (src_draw_idx, model, dst);
Err("clone_static_draw_object: not implemented on this backend".to_string())
}
fn set_draw_material(
&mut self,
draw_idx: usize,
material: MaterialUniforms,
texture_slot: usize,
normal_map_slot: usize,
) {
let _ = (draw_idx, material, texture_slot, normal_map_slot);
}
fn set_draw_cull_distance(&mut self, draw_idx: usize, cull_distance: f32) {
let _ = (draw_idx, cull_distance);
}
fn add_decal(&mut self, record: crate::decal::DecalRecord) -> Result<usize, String> {
let _ = record;
Err("add_decal: not implemented on this backend".to_string())
}
fn remove_decal(&mut self, decal_id: usize) -> Result<(), String> {
let _ = decal_id;
Err("remove_decal: not implemented on this backend".to_string())
}
fn add_emitter(
&mut self,
record: crate::particles::ParticleEmitterRecord,
) -> Result<usize, String> {
let _ = record;
Err("add_emitter: not implemented on this backend".to_string())
}
fn remove_emitter(&mut self, emitter_id: usize) -> Result<(), String> {
let _ = emitter_id;
Err("remove_emitter: not implemented on this backend".to_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> {
let _ = (vert_bytes, frag_bytes, shadow_bytes, vert_instanced_bytes);
Err("update_world_shader_pipelines: not implemented on this backend".to_string())
}
fn install_world_shader(&mut self, bucket: u32, shader: ShaderBytes<'_>) -> RenderResult<()> {
let _ = (bucket, shader);
Ok(())
}
fn evict_world_shader(&mut self, bucket: u32) {
let _ = bucket;
}
fn hot_swap_config(&self) -> Option<SwapchainConfig> {
None
}
fn reload_world(&mut self, init: BackendInit<'_>) -> RenderResult<()> {
let _ = init;
Err(RenderError::Other(
"reload_world: not supported on this backend".to_string(),
))
}
}
#[cfg(test)]
pub(crate) mod test_stub {
use super::*;
pub(crate) struct StubBackend;
impl SceneControl for StubBackend {
fn update_visibility(&mut self, _draw_idx: usize, _visible: bool) {}
fn set_fade(&mut self, _fade: f32) {}
}
impl RenderBackend for StubBackend {
fn window_closed(&mut self) -> bool {
false
}
fn capture_cursor(&mut self) {}
fn take_input(&mut self) -> RenderInput {
RenderInput::default()
}
fn wait_idle(&self) {}
fn draw_frame(&mut self, _params: FrameParams<'_>) -> RenderResult<()> {
Ok(())
}
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<()> {
Ok(())
}
fn update_skinned_pose(&mut self, _skinned_index: usize, _matrices: &[[[f32; 4]; 4]]) {}
fn evict_texture_slot(&mut self, _slot: usize) -> Result<(), String> {
Ok(())
}
fn update_texture_slot(
&mut self,
_slot: usize,
_image: &crate::build::texture::TextureImage,
) -> RenderResult<()> {
Ok(())
}
fn evict_mesh(&mut self, _draw_idx: usize, _retire_frame: u64) -> Result<(), String> {
Ok(())
}
fn upload_mesh(
&mut self,
_draw_idx: usize,
_verts: &[Vertex],
_idxs: &[u16],
_frame: u64,
) -> RenderResult<()> {
Ok(())
}
fn setup_chunk_streaming(
&mut self,
_chunk_vtx_bytes: usize,
_chunk_idx_bytes: usize,
_texture_slot: usize,
_normal_map_slot: usize,
) -> RenderResult<()> {
Ok(())
}
fn add_chunk_mesh(
&mut self,
_mesh: ChunkMesh<'_>,
_dst: crate::draw_slot::SlotAlloc,
) -> RenderResult<()> {
Ok(())
}
fn remove_chunk_mesh(
&mut self,
_draw_idx: usize,
_retire_frame: u64,
) -> Result<(), String> {
Ok(())
}
fn set_chunk_model(
&mut self,
_draw_idx: usize,
_model: [[f32; 4]; 4],
) -> Result<(), String> {
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
const GB: u64 = 1 << 30;
fn input(
vendor: GpuVendor,
memory_budget_bytes: u64,
discrete: bool,
apple_family: u8,
) -> GpuClassInput {
GpuClassInput {
vendor,
memory_budget_bytes,
discrete,
apple_family,
}
}
#[test]
fn unknown_profile_is_the_conservative_default() {
let p = GpuProfile::default();
assert_eq!(p.tier, GpuTier::Unknown);
assert_eq!(p.vendor, GpuVendor::Other);
assert_eq!(p.memory_budget_bytes, 0);
assert!(GpuTier::Unknown < GpuTier::Integrated);
assert!(GpuTier::Integrated < GpuTier::EntryDiscrete);
assert!(GpuTier::EntryDiscrete < GpuTier::MidDiscrete);
assert!(GpuTier::MidDiscrete < GpuTier::HighDiscrete);
}
#[test]
fn apple_family_reads_the_generation_out_of_the_device_name() {
assert_eq!(apple_family_from_device_name("Apple M1"), 7);
assert_eq!(apple_family_from_device_name("Apple M2 Max"), 8);
assert_eq!(apple_family_from_device_name("Apple M3 Pro"), 9);
assert_eq!(apple_family_from_device_name("Apple M4 Ultra"), 10);
assert!(apple_family_from_device_name("Apple M9") > 9);
}
#[test]
fn non_apple_device_names_report_no_family() {
for name in [
"NVIDIA GeForce RTX 4090",
"AMD Radeon RX 7900 XTX",
"Intel(R) Arc(tm) A770",
"Apple A17 Pro",
"Apple M",
"Apple MX",
"",
] {
assert_eq!(apple_family_from_device_name(name), 0, "{name}");
}
}
#[test]
fn apple_family_from_a_name_reaches_the_same_tier_metal_does() {
let family = apple_family_from_device_name("Apple M2 Max");
assert_eq!(
classify_tier(&input(GpuVendor::Apple, 32 * GB, false, family)),
GpuTier::MidDiscrete
);
assert_eq!(
classify_tier(&input(GpuVendor::Apple, 32 * GB, false, 0)),
GpuTier::Integrated
);
}
#[test]
fn apple_silicon_classifies_by_generation() {
assert_eq!(
classify_tier(&input(GpuVendor::Apple, 16 * GB, false, 7)),
GpuTier::EntryDiscrete );
assert_eq!(
classify_tier(&input(GpuVendor::Apple, 24 * GB, false, 8)),
GpuTier::MidDiscrete );
assert_eq!(
classify_tier(&input(GpuVendor::Apple, 48 * GB, false, 9)),
GpuTier::HighDiscrete );
assert_eq!(
classify_tier(&input(GpuVendor::Apple, 64 * GB, false, 10)),
GpuTier::HighDiscrete );
}
#[test]
fn discrete_gpu_classifies_by_vram() {
assert_eq!(
classify_tier(&input(GpuVendor::Nvidia, 24 * GB, true, 0)),
GpuTier::HighDiscrete
);
assert_eq!(
classify_tier(&input(GpuVendor::Amd, 8 * GB, true, 0)),
GpuTier::MidDiscrete
);
assert_eq!(
classify_tier(&input(GpuVendor::Nvidia, 4 * GB, true, 0)),
GpuTier::EntryDiscrete
);
assert_eq!(
classify_tier(&input(GpuVendor::Amd, 0, true, 0)),
GpuTier::Unknown
);
}
#[test]
fn integrated_gpu_is_the_lowest_tier() {
assert_eq!(
classify_tier(&input(GpuVendor::Intel, 0, false, 0)),
GpuTier::Integrated
);
}
#[test]
fn vram_bucket_boundaries() {
assert_eq!(
classify_tier(&input(GpuVendor::Nvidia, 12 * GB, true, 0)),
GpuTier::HighDiscrete
);
assert_eq!(
classify_tier(&input(GpuVendor::Nvidia, 12 * GB - 1, true, 0)),
GpuTier::MidDiscrete
);
assert_eq!(
classify_tier(&input(GpuVendor::Nvidia, 6 * GB, true, 0)),
GpuTier::MidDiscrete
);
assert_eq!(
classify_tier(&input(GpuVendor::Nvidia, 6 * GB - 1, true, 0)),
GpuTier::EntryDiscrete
);
}
pub(crate) use super::test_stub::StubBackend;
const IDENTITY: [[f32; 4]; 4] = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
fn stub_quality() -> QualitySettings {
QualitySettings {
taa: false,
ssao: None,
ssr: None,
rt_reflections: None,
ssgi: None,
reflection_blur_scale: 1,
auto_exposure: None,
auto_exposure_bias_ev: 0.0,
}
}
#[test]
fn default_query_methods_report_conservative_values() {
let backend = StubBackend;
assert!(backend.capabilities().ray_tracing);
assert_eq!(backend.gpu_profile().tier, GpuTier::Unknown);
assert_eq!(backend.gpu_profile().vendor, GpuVendor::Other);
assert_eq!(backend.gpu_profile().memory_budget_bytes, 0);
assert_eq!(backend.logical_size(), (0.0, 0.0));
assert_eq!(backend.render_stats(), RenderStats::default());
assert!(!backend.cursor_outside_window());
assert!(backend.display_modes().is_empty());
assert!(backend.current_display_mode().is_none());
assert!(backend.shader_reload_flag().is_none());
assert!(backend.hot_swap_config().is_none());
assert!(backend.draw_geometry_size(0).is_none());
assert!(backend.draw_lod_index_counts(0).is_none());
}
#[test]
fn default_mutators_are_noops_and_fallible_hooks_report_defaults() {
let mut backend = StubBackend;
backend.reveal_skinned_instance(0, IDENTITY);
backend.retire_skinned_draw_object(0);
backend.update_skinned_models(&[(0, IDENTITY)]);
backend.seed_mesh_streaming(0, 0, 0, 0);
backend.set_ui_cursor_hidden(true);
backend.set_menu_mode(true);
backend.set_camera_capture(true);
backend.set_reflection_probes(&[]);
backend.set_vsync(true);
backend.set_window_mode(crate::components::WindowMode::Fullscreen);
backend.set_window_size(1280, 720);
backend.set_display_mode(crate::display_mode::DisplayMode {
width: 1920,
height: 1080,
refresh_hz: 60,
});
backend.update_post_process(PostProcessTunables::DEFAULT);
backend.set_ambient_intensity(1.0);
backend.set_keymap(&KeyMap::default());
backend.apply_quality_settings(stub_quality());
backend.update_quality_params(stub_quality());
backend.set_shadow_update(crate::components::ShadowUpdate::EveryFrame);
backend.set_shadow_distance(200);
backend.set_shadow_cascades(3);
backend.update_fog_settings(None);
backend.update_directional_lights(&[]);
backend.set_draw_material(0, MaterialUniforms::DEFAULT, 0, 0);
backend.set_draw_cull_distance(0, 50.0);
assert!(backend.update_color_lut(2, &[0u8; 32]).is_ok());
assert!(backend.rebuild_static_geometry(vec![]).is_ok());
assert!(backend.update_skinned_mesh_geometry(0, 0, &[], &[]).is_ok());
assert!(backend.rebuild_skinned_geometry(vec![]).unwrap().is_empty());
assert!(backend.update_skinned_skeleton(0, 0).is_ok());
assert!(backend.update_mesh_geometry(0, &[], &[], &[]).is_ok());
assert!(backend.update_environment_map(&[]).is_ok());
assert!(backend.screenshot("unused.png").is_err());
assert!(
backend
.clone_static_draw_object(0, IDENTITY, crate::draw_slot::SlotAlloc::Append(0))
.is_err()
);
assert!(backend.add_decal(stub_decal()).is_err());
assert!(backend.remove_decal(0).is_err());
assert!(backend.add_emitter(stub_emitter()).is_err());
assert!(backend.remove_emitter(0).is_err());
assert!(
backend
.update_world_shader_pipelines(None, None, None, None)
.is_err()
);
}
fn empty_backend_init(window: &crate::components::Window) -> BackendInit<'_> {
BackendInit::minimal(window, alloc::vec::Vec::new())
}
#[test]
fn default_reload_world_is_unsupported() {
let mut backend = StubBackend;
let window = crate::components::Window::default();
assert!(backend.reload_world(empty_backend_init(&window)).is_err());
}
fn stub_decal() -> crate::decal::DecalRecord {
crate::decal::DecalRecord {
model: IDENTITY,
inv_model: IDENTITY,
texture_slot: 0,
tint: [1.0; 4],
}
}
fn stub_emitter() -> crate::particles::ParticleEmitterRecord {
crate::particles::ParticleEmitterRecord {
texture_slot: 0,
position: [0.0; 3],
direction: [0.0, 1.0, 0.0],
spread_cos: 1.0,
speed_min: 0.0,
speed_max: 1.0,
lifetime_min: 0.0,
lifetime_max: 1.0,
gravity: [0.0, -9.8, 0.0],
spawn_rate: 1.0,
max_particles: 1,
size_start: 1.0,
size_end: 1.0,
color_start: [1.0; 4],
color_end: [1.0; 4],
}
}
}