1#![forbid(unsafe_code)]
17#![deny(missing_docs)]
18#![deny(clippy::all)]
19#![deny(clippy::pedantic)]
20#![allow(clippy::module_name_repetitions)]
21
22pub mod backend;
23#[cfg(feature = "charts")]
24pub mod chart;
25pub mod error;
26#[cfg(feature = "export")]
27pub mod export;
28pub mod holographic;
29#[cfg(feature = "images")]
30pub mod image;
31pub mod quilt;
32pub mod spatial;
33#[cfg(feature = "images")]
34pub mod texture_cache;
35#[cfg(feature = "gpu")]
36pub mod video;
37
38pub use backend::RenderBackend;
39pub use error::{RenderError, RenderResult};
40#[cfg(feature = "export")]
41pub use export::{ExportConfig, ExportFormat, SceneExporter};
42pub use holographic::{
43 HoloPlayInfo, HolographicRenderResult, HolographicRenderer, HolographicStats,
44};
45pub use quilt::{LookingGlassPreset, Quilt, QuiltRenderSettings, QuiltRenderTarget, QuiltView};
46pub use spatial::{Camera, HolographicConfig, Mat4, QuiltRenderInfo, Vec3};
47#[cfg(feature = "gpu")]
48pub use video::{
49 VideoFrameData, VideoTextureEntry, VideoTextureError, VideoTextureManager, VideoTextureResult,
50};
51
52use canvas_core::Scene;
53
54#[derive(Debug, Clone)]
56pub struct RendererConfig {
57 pub preferred_backend: BackendType,
59 pub target_fps: u32,
61 pub anti_aliasing: bool,
63 pub background_color: [f32; 4],
65}
66
67impl Default for RendererConfig {
68 fn default() -> Self {
69 Self {
70 preferred_backend: BackendType::WebGpu,
71 target_fps: 60,
72 anti_aliasing: true,
73 background_color: [1.0, 1.0, 1.0, 1.0], }
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum BackendType {
81 WebGpu,
83 WebGl2,
85 Canvas2D,
87}
88
89pub struct Renderer {
91 config: RendererConfig,
92 backend: Box<dyn RenderBackend>,
93 frame_count: u64,
94}
95
96impl Renderer {
97 pub fn new(config: RendererConfig) -> RenderResult<Self> {
103 let backend = Self::create_backend(&config)?;
104 Ok(Self::with_backend(backend, config))
105 }
106
107 #[must_use]
109 pub fn with_backend(backend: Box<dyn RenderBackend>, config: RendererConfig) -> Self {
110 Self {
111 config,
112 backend,
113 frame_count: 0,
114 }
115 }
116
117 fn create_backend(config: &RendererConfig) -> RenderResult<Box<dyn RenderBackend>> {
119 match config.preferred_backend {
120 BackendType::WebGpu => {
121 #[cfg(feature = "gpu")]
122 {
123 match backend::wgpu::WgpuBackend::new() {
124 Ok(b) => return Ok(Box::new(b)),
125 Err(e) => {
126 tracing::warn!("WebGPU unavailable, falling back: {}", e);
127 }
128 }
129 }
130 Self::create_backend(&RendererConfig {
132 preferred_backend: BackendType::WebGl2,
133 ..config.clone()
134 })
135 }
136 BackendType::WebGl2 => {
137 tracing::warn!("WebGL2 not yet implemented, falling back to 2D");
139 Self::create_backend(&RendererConfig {
140 preferred_backend: BackendType::Canvas2D,
141 ..config.clone()
142 })
143 }
144 BackendType::Canvas2D => Ok(Box::new(backend::canvas2d::Canvas2DBackend::new())),
145 }
146 }
147
148 pub fn render(&mut self, scene: &Scene) -> RenderResult<()> {
154 self.backend.render(scene)?;
155 self.frame_count += 1;
156 Ok(())
157 }
158
159 #[must_use]
161 pub fn frame_count(&self) -> u64 {
162 self.frame_count
163 }
164
165 #[must_use]
167 pub fn active_backend(&self) -> BackendType {
168 self.backend.backend_type()
169 }
170
171 #[must_use]
173 pub fn config(&self) -> &RendererConfig {
174 &self.config
175 }
176
177 pub fn resize(&mut self, width: u32, height: u32) -> RenderResult<()> {
183 self.backend.resize(width, height)
184 }
185}