#![forbid(unsafe_code)]
mod cpu;
mod gpu;
use std::sync::Arc;
use winit::window::Window;
use roxlap_core::opticast::OpticastSettings;
use roxlap_core::sky::Sky;
use roxlap_core::sprite::SpriteLighting;
use roxlap_core::Camera;
use roxlap_scene::Scene;
pub use roxlap_formats::sprite::Sprite;
pub use roxlap_gpu::{GpuInitError, GpuRendererSettings};
use crate::cpu::CpuBackend;
use crate::gpu::GpuBackend;
pub struct SpriteInstanceDesc {
pub model: usize,
pub pos: [f32; 3],
}
pub struct SpriteSet {
pub models: Vec<Sprite>,
pub instances: Vec<SpriteInstanceDesc>,
pub carve_model: Option<usize>,
}
pub struct FrameParams<'a> {
pub settings: &'a OpticastSettings,
pub sky_color: u32,
pub sky: Option<&'a Sky>,
pub fog_color: u32,
pub fog_max_scan_dist: i32,
pub treat_z_max_as_air: bool,
pub gpu_mip_scan_dist: f32,
pub gpu_max_outer_steps: u32,
pub gpu_fov_y_rad: f32,
pub sprite_lighting: Option<&'a SpriteLighting<'a>>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Backend {
Cpu,
Gpu,
}
pub struct RenderOptions {
pub want_gpu: bool,
pub gpu: GpuRendererSettings,
pub clear_sky: u32,
pub cpu_max_grid_vsid: u32,
pub cpu_render_threads: usize,
}
impl Default for RenderOptions {
fn default() -> Self {
Self {
want_gpu: false,
gpu: GpuRendererSettings::default(),
clear_sky: 0x0099_b3d9,
cpu_max_grid_vsid: 32 * roxlap_scene::CHUNK_SIZE_XY,
cpu_render_threads: 4,
}
}
}
enum BackendImpl {
Cpu(CpuBackend),
Gpu(Box<GpuBackend>),
}
pub struct SceneRenderer {
inner: BackendImpl,
}
impl SceneRenderer {
#[must_use]
pub fn new(window: Arc<Window>, opts: &RenderOptions) -> Self {
if opts.want_gpu {
match GpuBackend::new(window.clone(), opts) {
Ok(g) => {
return Self {
inner: BackendImpl::Gpu(Box::new(g)),
};
}
Err(e) => {
eprintln!(
"roxlap-render: GPU init failed ({e}); falling back to the CPU renderer",
);
}
}
}
Self {
inner: BackendImpl::Cpu(CpuBackend::new(window, opts)),
}
}
#[must_use]
pub fn backend(&self) -> Backend {
match self.inner {
BackendImpl::Cpu(_) => Backend::Cpu,
BackendImpl::Gpu(_) => Backend::Gpu,
}
}
#[must_use]
pub fn adapter_info(&self) -> Option<&str> {
match &self.inner {
BackendImpl::Gpu(g) => Some(g.adapter_info()),
BackendImpl::Cpu(_) => None,
}
}
pub fn set_sky_panorama(&mut self, rgba: &[u8], w: u32, h: u32) {
if let BackendImpl::Gpu(g) = &mut self.inner {
g.set_sky_panorama(rgba, w, h);
}
}
pub fn resize(&mut self, width: u32, height: u32) {
match &mut self.inner {
BackendImpl::Cpu(c) => c.resize(width, height),
BackendImpl::Gpu(g) => g.resize(width, height),
}
}
pub fn render(&mut self, scene: &mut Scene, camera: &Camera, frame: &FrameParams) {
match &mut self.inner {
BackendImpl::Cpu(c) => c.render(scene, camera, frame),
BackendImpl::Gpu(g) => g.render(scene, camera, frame),
}
}
pub fn set_sprites(&mut self, set: &SpriteSet) {
match &mut self.inner {
BackendImpl::Cpu(c) => c.set_sprites(set),
BackendImpl::Gpu(g) => g.set_sprites(set),
}
}
pub fn carve_active_sprite(&mut self) -> u32 {
match &mut self.inner {
BackendImpl::Cpu(_) => 0,
BackendImpl::Gpu(g) => g.carve_active_sprite(),
}
}
pub fn request_capture(&mut self) {
if let BackendImpl::Cpu(c) = &mut self.inner {
c.request_capture();
}
}
pub fn take_capture(&mut self) -> Option<(Vec<u32>, u32, u32)> {
match &mut self.inner {
BackendImpl::Cpu(c) => c.take_capture(),
BackendImpl::Gpu(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn options_default_is_cpu_intent() {
let o = RenderOptions::default();
assert!(!o.want_gpu);
assert_eq!(o.clear_sky & 0xFF00_0000, 0, "clear_sky is 0x00RRGGBB");
}
}