use uzor_render_tiny_skia::TinySkiaCpuRenderContext;
use uzor_render_vello_cpu::VelloCpuRenderContext;
use uzor_render_vello_gpu::VelloGpuRenderContext;
use uzor_render_vello_hybrid::VelloHybridRenderContext;
use uzor_render_wgpu_instanced::{InstancedRenderContext, InstancedRenderer};
#[cfg(not(target_arch = "wasm32"))]
use uzor::layout::window::SoftwarePresenter;
use vello::util::{RenderContext as VelloRenderContext, RenderSurface};
use vello::{Renderer as VelloRenderer, Scene};
use crate::backend::RenderBackend;
#[cfg(target_arch = "wasm32")]
use uzor_render_canvas2d::Canvas2dRenderContext;
pub type GpuDevicePool = VelloRenderContext;
pub enum SurfaceMode {
Gpu {
gpu_pool: GpuDevicePool,
surface: RenderSurface<'static>,
dev_id: usize,
},
#[cfg(not(target_arch = "wasm32"))]
Software {
presenter: Box<dyn SoftwarePresenter>,
width: u32,
height: u32,
},
#[cfg(target_arch = "wasm32")]
Canvas2d {
canvas: web_sys::HtmlCanvasElement,
},
}
pub enum BackendContext<'a> {
VelloGpu(VelloGpuRenderContext<'a>),
VelloHybrid(VelloHybridRenderContext),
Instanced(InstancedRenderContext),
VelloCpu(VelloCpuRenderContext),
TinySkia(TinySkiaCpuRenderContext),
}
impl<'a> BackendContext<'a> {
pub fn vello_gpu(scene: &'a mut Scene, offset_x: f64, offset_y: f64) -> Self {
Self::VelloGpu(VelloGpuRenderContext::new(scene, offset_x, offset_y))
}
pub fn vello_hybrid(dpr: f64) -> Self {
Self::VelloHybrid(VelloHybridRenderContext::new(dpr))
}
pub fn instanced(screen_w: f32, screen_h: f32, offset_x: f32, offset_y: f32) -> Self {
Self::Instanced(InstancedRenderContext::new(screen_w, screen_h, offset_x, offset_y))
}
pub fn vello_cpu(dpr: f64) -> Self {
Self::VelloCpu(VelloCpuRenderContext::new(dpr))
}
pub fn tiny_skia(width: u32, height: u32, dpr: f64) -> Self {
Self::TinySkia(TinySkiaCpuRenderContext::new(width, height, dpr))
}
}
pub struct WindowRenderState {
pub(crate) surface: SurfaceMode,
pub(crate) vello_gpu_renderer: Option<VelloRenderer>,
pub(crate) vello_hybrid_renderer: Option<vello_hybrid::Renderer>,
pub(crate) instanced_renderer: Option<InstancedRenderer>,
pub(crate) vello_cpu_ctx: Option<VelloCpuRenderContext>,
pub(crate) tiny_skia_ctx: Option<TinySkiaCpuRenderContext>,
#[cfg(target_arch = "wasm32")]
pub(crate) canvas2d_ctx: Option<Canvas2dRenderContext>,
pub(crate) scene: Scene,
pub(crate) vello_hybrid_ctx: VelloHybridRenderContext,
pub(crate) active: RenderBackend,
}
impl WindowRenderState {
pub fn new_gpu(
gpu_pool: GpuDevicePool,
surface: RenderSurface<'static>,
renderer: VelloRenderer,
dev_id: usize,
) -> Self {
Self {
surface: SurfaceMode::Gpu { gpu_pool, surface, dev_id },
vello_gpu_renderer: Some(renderer),
vello_hybrid_renderer: None,
instanced_renderer: None,
vello_cpu_ctx: None,
tiny_skia_ctx: None,
#[cfg(target_arch = "wasm32")]
canvas2d_ctx: None,
scene: Scene::new(),
vello_hybrid_ctx: VelloHybridRenderContext::new(1.0),
active: RenderBackend::VelloGpu,
}
}
pub fn new_gpu_no_vello(
gpu_pool: GpuDevicePool,
surface: RenderSurface<'static>,
dev_id: usize,
active: RenderBackend,
dpr: f64,
) -> Self {
Self {
surface: SurfaceMode::Gpu { gpu_pool, surface, dev_id },
vello_gpu_renderer: None,
vello_hybrid_renderer: None,
instanced_renderer: None,
vello_cpu_ctx: None,
tiny_skia_ctx: None,
#[cfg(target_arch = "wasm32")]
canvas2d_ctx: None,
scene: Scene::new(),
vello_hybrid_ctx: VelloHybridRenderContext::new(dpr),
active,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new_cpu(width: u32, height: u32, presenter: Box<dyn SoftwarePresenter>) -> Self {
Self {
surface: SurfaceMode::Software { presenter, width, height },
vello_gpu_renderer: None,
vello_hybrid_renderer: None,
instanced_renderer: None,
vello_cpu_ctx: None,
tiny_skia_ctx: Some(TinySkiaCpuRenderContext::new(width, height, 1.0)),
scene: Scene::new(),
vello_hybrid_ctx: VelloHybridRenderContext::new(1.0),
active: RenderBackend::TinySkia,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new_vello_cpu(dpr: f64, presenter: Box<dyn SoftwarePresenter>) -> Self {
Self {
surface: SurfaceMode::Software { presenter, width: 0, height: 0 },
vello_gpu_renderer: None,
vello_hybrid_renderer: None,
instanced_renderer: None,
vello_cpu_ctx: Some(VelloCpuRenderContext::new(dpr)),
tiny_skia_ctx: None,
scene: Scene::new(),
vello_hybrid_ctx: VelloHybridRenderContext::new(dpr),
active: RenderBackend::VelloCpu,
}
}
}
#[cfg(not(target_arch = "wasm32"))]
fn recreate_target_with_cpu_usage(
surface: &mut RenderSurface<'static>,
device: &wgpu::Device,
) {
let old = &surface.target_texture;
let size = old.size();
let new_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("target_texture_cpu_swapchain"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::STORAGE_BINDING
| wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let new_view = new_texture.create_view(&wgpu::TextureViewDescriptor::default());
surface.target_texture = new_texture;
surface.target_view = new_view;
}
impl WindowRenderState {
pub fn new_tiny_skia_gpu(
gpu_pool: GpuDevicePool,
mut surface: RenderSurface<'static>,
dev_id: usize,
) -> Self {
let (w, h) = (surface.config.width.max(1), surface.config.height.max(1));
recreate_target_with_cpu_usage(&mut surface, &gpu_pool.devices[dev_id].device);
Self {
surface: SurfaceMode::Gpu { gpu_pool, surface, dev_id },
vello_gpu_renderer: None,
vello_hybrid_renderer: None,
instanced_renderer: None,
vello_cpu_ctx: None,
tiny_skia_ctx: Some(TinySkiaCpuRenderContext::new(w, h, 1.0)),
#[cfg(target_arch = "wasm32")]
canvas2d_ctx: None,
scene: Scene::new(),
vello_hybrid_ctx: VelloHybridRenderContext::new(1.0),
active: RenderBackend::TinySkia,
}
}
pub fn new_vello_cpu_gpu(
gpu_pool: GpuDevicePool,
mut surface: RenderSurface<'static>,
dev_id: usize,
dpr: f64,
) -> Self {
recreate_target_with_cpu_usage(&mut surface, &gpu_pool.devices[dev_id].device);
Self {
surface: SurfaceMode::Gpu { gpu_pool, surface, dev_id },
vello_gpu_renderer: None,
vello_hybrid_renderer: None,
instanced_renderer: None,
vello_cpu_ctx: Some(VelloCpuRenderContext::new(dpr)),
tiny_skia_ctx: None,
#[cfg(target_arch = "wasm32")]
canvas2d_ctx: None,
scene: Scene::new(),
vello_hybrid_ctx: VelloHybridRenderContext::new(dpr),
active: RenderBackend::VelloCpu,
}
}
pub fn new_vello_hybrid(
gpu_pool: GpuDevicePool,
surface: RenderSurface<'static>,
dev_id: usize,
dpr: f64,
) -> Self {
Self::new_gpu_no_vello(gpu_pool, surface, dev_id, RenderBackend::VelloHybrid, dpr)
}
pub fn new_wgpu_instanced(
gpu_pool: GpuDevicePool,
surface: RenderSurface<'static>,
dev_id: usize,
) -> Self {
Self::new_gpu_no_vello(gpu_pool, surface, dev_id, RenderBackend::InstancedWgpu, 1.0)
}
#[cfg(target_arch = "wasm32")]
pub fn new_canvas2d(
canvas: web_sys::HtmlCanvasElement,
ctx: Canvas2dRenderContext,
) -> Self {
Self {
surface: SurfaceMode::Canvas2d { canvas },
vello_gpu_renderer: None,
vello_hybrid_renderer: None,
instanced_renderer: None,
vello_cpu_ctx: None,
tiny_skia_ctx: None,
canvas2d_ctx: Some(ctx),
scene: Scene::new(),
vello_hybrid_ctx: VelloHybridRenderContext::new(1.0),
active: RenderBackend::Canvas2d,
}
}
pub fn backend(&self) -> RenderBackend {
self.active
}
#[cfg(not(target_arch = "wasm32"))]
pub fn gpu_handles(&self) -> Option<(&wgpu::Device, &wgpu::Queue, &vello::util::RenderSurface<'static>)> {
match &self.surface {
SurfaceMode::Gpu { gpu_pool, surface, dev_id } => {
let dh = gpu_pool.devices.get(*dev_id)?;
Some((&dh.device, &dh.queue, surface))
}
#[cfg(not(target_arch = "wasm32"))]
SurfaceMode::Software { .. } => None,
#[cfg(target_arch = "wasm32")]
SurfaceMode::Canvas2d { .. } => None,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn gpu_handles_mut(
&mut self,
) -> Option<(&wgpu::Device, &wgpu::Queue, &mut vello::util::RenderSurface<'static>)> {
match &mut self.surface {
SurfaceMode::Gpu { gpu_pool, surface, dev_id } => {
let dh = gpu_pool.devices.get(*dev_id)?;
Some((&dh.device, &dh.queue, surface))
}
#[cfg(not(target_arch = "wasm32"))]
SurfaceMode::Software { .. } => None,
#[cfg(target_arch = "wasm32")]
SurfaceMode::Canvas2d { .. } => None,
}
}
pub fn set_active(&mut self, backend: RenderBackend) {
self.active = backend;
self.ensure_backend_slot(backend);
}
#[cfg(not(target_arch = "wasm32"))]
pub fn ensure_backend_slot(&mut self, backend: RenderBackend) {
match backend {
RenderBackend::VelloGpu => {
if self.vello_gpu_renderer.is_none() {
if let Some((device, _, _)) = self.gpu_handles() {
match vello::Renderer::new(
device,
vello::RendererOptions {
use_cpu: false,
antialiasing_support: vello::AaSupport::all(),
num_init_threads: std::num::NonZeroUsize::new(1),
pipeline_cache: None,
},
) {
Ok(r) => self.vello_gpu_renderer = Some(r),
Err(e) => eprintln!("[render-hub] VelloGpu renderer init failed: {e}"),
}
}
}
}
RenderBackend::VelloCpu => {
if self.vello_cpu_ctx.is_none() {
self.vello_cpu_ctx = Some(VelloCpuRenderContext::new(1.0));
}
}
RenderBackend::TinySkia => {
if self.tiny_skia_ctx.is_none() {
let (w, h) = self.gpu_handles()
.map(|(_, _, s)| (s.config.width.max(1), s.config.height.max(1)))
.unwrap_or((1, 1));
self.tiny_skia_ctx = Some(TinySkiaCpuRenderContext::new(w, h, 1.0));
}
}
RenderBackend::VelloHybrid | RenderBackend::InstancedWgpu => {}
#[cfg(target_arch = "wasm32")]
RenderBackend::Canvas2d => {}
#[cfg(not(target_arch = "wasm32"))]
RenderBackend::Canvas2d => {}
}
}
pub fn scene_mut(&mut self) -> Option<&mut Scene> {
match self.active {
RenderBackend::VelloGpu | RenderBackend::VelloHybrid => Some(&mut self.scene),
_ => None,
}
}
pub fn scene(&self) -> Option<&Scene> {
match self.active {
RenderBackend::VelloGpu | RenderBackend::VelloHybrid => Some(&self.scene),
_ => None,
}
}
pub fn cpu_ctx_mut(&mut self) -> Option<&mut TinySkiaCpuRenderContext> {
self.tiny_skia_ctx.as_mut()
}
pub fn cpu_ctx(&self) -> Option<&TinySkiaCpuRenderContext> {
self.tiny_skia_ctx.as_ref()
}
pub fn vello_cpu_ctx_mut(&mut self) -> Option<&mut VelloCpuRenderContext> {
self.vello_cpu_ctx.as_mut()
}
pub fn vello_cpu_ctx(&self) -> Option<&VelloCpuRenderContext> {
self.vello_cpu_ctx.as_ref()
}
pub fn vello_hybrid_ctx_mut(&mut self) -> Option<&mut VelloHybridRenderContext> {
if matches!(self.active, RenderBackend::VelloHybrid) {
Some(&mut self.vello_hybrid_ctx)
} else {
None
}
}
#[cfg(target_arch = "wasm32")]
pub fn canvas2d_ctx_mut(&mut self) -> Option<&mut Canvas2dRenderContext> {
if matches!(self.active, RenderBackend::Canvas2d) {
self.canvas2d_ctx.as_mut()
} else {
None
}
}
#[cfg(target_arch = "wasm32")]
pub fn canvas2d_ctx(&self) -> Option<&Canvas2dRenderContext> {
if matches!(self.active, RenderBackend::Canvas2d) {
self.canvas2d_ctx.as_ref()
} else {
None
}
}
pub fn with_scene_render_context<R>(
&mut self,
scene: &mut Scene,
f: impl FnOnce(&mut dyn uzor::render::RenderContext) -> R,
) -> Option<R> {
match self.active {
RenderBackend::VelloGpu => {
let mut ctx = VelloGpuRenderContext::new(scene, 0.0, 0.0);
Some(f(&mut ctx))
}
RenderBackend::VelloHybrid => {
None
}
_ => None,
}
}
pub fn append_region_scene(&mut self, region_scene: &Scene) {
if matches!(self.active, RenderBackend::VelloGpu | RenderBackend::VelloHybrid) {
self.scene.append(region_scene, None);
}
}
pub fn with_render_context<R>(
&mut self,
f: impl FnOnce(&mut dyn uzor::render::RenderContext) -> R,
) -> Option<R> {
match self.active {
RenderBackend::VelloGpu => {
let mut ctx = VelloGpuRenderContext::new(&mut self.scene, 0.0, 0.0);
Some(f(&mut ctx))
}
RenderBackend::VelloHybrid => {
Some(f(&mut self.vello_hybrid_ctx))
}
RenderBackend::VelloCpu => {
let (w, h) = self.gpu_handles()
.map(|(_, _, s)| (s.config.width.max(1), s.config.height.max(1)))
.unwrap_or((1, 1));
self.vello_cpu_ctx.as_mut().map(|c| {
c.begin_frame(w, h);
f(c)
})
}
RenderBackend::TinySkia => {
let (w, h) = self.gpu_handles()
.map(|(_, _, s)| (s.config.width.max(1), s.config.height.max(1)))
.unwrap_or((1, 1));
self.tiny_skia_ctx.as_mut().map(|c| {
if c.width() != w || c.height() != h {
c.resize(w, h);
}
f(c)
})
}
RenderBackend::InstancedWgpu => None,
#[cfg(target_arch = "wasm32")]
RenderBackend::Canvas2d => {
self.canvas2d_ctx.as_mut().map(|c| f(c))
}
#[cfg(not(target_arch = "wasm32"))]
RenderBackend::Canvas2d => None,
}
}
pub fn resize_surface(&mut self, width: u32, height: u32) {
if width == 0 || height == 0 {
return;
}
match &mut self.surface {
SurfaceMode::Gpu { gpu_pool, surface, dev_id } => {
gpu_pool.resize_surface(surface, width, height);
if matches!(self.active, RenderBackend::VelloHybrid) {
self.vello_hybrid_renderer = None;
}
let device = &gpu_pool.devices[*dev_id].device;
recreate_target_with_cpu_usage(surface, device);
}
#[cfg(not(target_arch = "wasm32"))]
SurfaceMode::Software { presenter, width: w, height: h } => {
presenter.resize(width, height);
*w = width;
*h = height;
if let Some(ref mut ts) = self.tiny_skia_ctx {
ts.resize(width, height);
}
}
#[cfg(target_arch = "wasm32")]
SurfaceMode::Canvas2d { .. } => {
}
}
}
pub fn begin_frame(&mut self) {
match self.active {
RenderBackend::VelloGpu => self.scene.reset(),
RenderBackend::VelloHybrid => {
if let SurfaceMode::Gpu { ref surface, .. } = self.surface {
self.vello_hybrid_ctx
.begin_frame(surface.config.width, surface.config.height);
}
}
RenderBackend::VelloCpu
| RenderBackend::TinySkia
| RenderBackend::InstancedWgpu => {
}
RenderBackend::Canvas2d => {
}
}
}
}