use uzor::layout::window::RawHandle;
use crate::{RenderBackend, RenderSurfaceFactory, SurfaceError, SurfaceSize, WindowRenderState};
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Mutex;
#[cfg(not(target_arch = "wasm32"))]
use vello::{AaSupport, Renderer, RendererOptions};
#[cfg(not(target_arch = "wasm32"))]
use crate::factory::GpuDevicePool;
#[cfg(not(target_arch = "wasm32"))]
use vello::wgpu::PresentMode;
#[cfg(not(target_arch = "wasm32"))]
use winit::raw_window_handle::{RawWindowHandle, RawDisplayHandle};
#[cfg(not(target_arch = "wasm32"))]
use uzor::layout::window::SoftwarePresenter;
#[cfg(not(target_arch = "wasm32"))]
use uzor_window_desktop::SendSyncHandlePair;
#[cfg(not(target_arch = "wasm32"))]
struct WinitSurfaceTarget {
window: RawWindowHandle,
display: RawDisplayHandle,
}
#[cfg(not(target_arch = "wasm32"))]
unsafe impl Send for WinitSurfaceTarget {}
#[cfg(not(target_arch = "wasm32"))]
unsafe impl Sync for WinitSurfaceTarget {}
#[cfg(not(target_arch = "wasm32"))]
impl winit::raw_window_handle::HasWindowHandle for WinitSurfaceTarget {
fn window_handle(
&self,
) -> Result<winit::raw_window_handle::WindowHandle<'_>, winit::raw_window_handle::HandleError>
{
Ok(unsafe { winit::raw_window_handle::WindowHandle::borrow_raw(self.window) })
}
}
#[cfg(not(target_arch = "wasm32"))]
impl winit::raw_window_handle::HasDisplayHandle for WinitSurfaceTarget {
fn display_handle(
&self,
) -> Result<winit::raw_window_handle::DisplayHandle<'_>, winit::raw_window_handle::HandleError>
{
Ok(unsafe { winit::raw_window_handle::DisplayHandle::borrow_raw(self.display) })
}
}
#[cfg(not(target_arch = "wasm32"))]
fn init_gpu_surface(
pair: &SendSyncHandlePair,
size: SurfaceSize,
backend: RenderBackend,
) -> Result<(GpuDevicePool, vello::util::RenderSurface<'static>, usize), SurfaceError> {
let target = WinitSurfaceTarget {
window: pair.0,
display: pair.1,
};
let mut gpu_pool = GpuDevicePool::new();
let surface_with_lifetime = pollster::block_on(gpu_pool.create_surface(
target,
size.width,
size.height,
PresentMode::AutoNoVsync,
))
.map_err(|e| SurfaceError::InitFailed(format!("{backend:?}: {e}")))?;
let dev_id = surface_with_lifetime.dev_id;
let mut surface: vello::util::RenderSurface<'static> =
unsafe { std::mem::transmute(surface_with_lifetime) };
{
let device = &gpu_pool.devices[dev_id].device;
let old = &surface.target_texture;
let extent = old.size();
let new_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("uzor_target_texture"),
size: extent,
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;
}
Ok((gpu_pool, surface, dev_id))
}
#[cfg(not(target_arch = "wasm32"))]
fn extract_handle_pair<'a>(
handle: &'a RawHandle,
backend: RenderBackend,
) -> Result<&'a SendSyncHandlePair, SurfaceError> {
let RawHandle::RawWindowHandle(any) = handle else {
return Err(SurfaceError::HandleMismatch(backend));
};
any.downcast_ref::<SendSyncHandlePair>().ok_or_else(|| {
SurfaceError::InitFailed(
"expected SendSyncHandlePair inside RawHandle — \
use WinitWindowProvider to obtain the handle"
.into(),
)
})
}
#[cfg(not(target_arch = "wasm32"))]
pub struct VelloGpuSurfaceFactory;
#[cfg(not(target_arch = "wasm32"))]
impl VelloGpuSurfaceFactory {
pub fn new() -> Self {
Self
}
}
#[cfg(not(target_arch = "wasm32"))]
impl Default for VelloGpuSurfaceFactory {
fn default() -> Self {
Self::new()
}
}
#[cfg(not(target_arch = "wasm32"))]
impl RenderSurfaceFactory for VelloGpuSurfaceFactory {
fn create_render_state(
&self,
handle: &RawHandle,
backend: RenderBackend,
size: SurfaceSize,
) -> Result<WindowRenderState, SurfaceError> {
if !matches!(backend, RenderBackend::VelloGpu) {
return Err(SurfaceError::UnsupportedBackend(backend));
}
let pair = extract_handle_pair(handle, backend)?;
let (gpu_pool, surface, dev_id) = init_gpu_surface(pair, size, backend)?;
let device = &gpu_pool.devices[dev_id].device;
let renderer = Renderer::new(
device,
RendererOptions {
use_cpu: false,
antialiasing_support: AaSupport::all(),
num_init_threads: std::num::NonZeroUsize::new(1),
pipeline_cache: None,
},
)
.map_err(|e| SurfaceError::InitFailed(e.to_string()))?;
Ok(WindowRenderState::new_gpu(gpu_pool, surface, renderer, dev_id))
}
fn supports(&self, handle: &RawHandle, backend: RenderBackend) -> bool {
matches!(backend, RenderBackend::VelloGpu)
&& matches!(handle, RawHandle::RawWindowHandle(_))
}
}
#[cfg(not(target_arch = "wasm32"))]
pub struct TinySkiaSurfaceFactory {
presenter: Mutex<Option<Box<dyn SoftwarePresenter>>>,
}
#[cfg(not(target_arch = "wasm32"))]
impl TinySkiaSurfaceFactory {
pub fn new() -> Self {
Self { presenter: Mutex::new(None) }
}
pub fn with_presenter(presenter: Box<dyn SoftwarePresenter>) -> Self {
Self { presenter: Mutex::new(Some(presenter)) }
}
}
#[cfg(not(target_arch = "wasm32"))]
impl Default for TinySkiaSurfaceFactory {
fn default() -> Self {
Self::new()
}
}
#[cfg(not(target_arch = "wasm32"))]
impl RenderSurfaceFactory for TinySkiaSurfaceFactory {
fn create_render_state(
&self,
handle: &RawHandle,
backend: RenderBackend,
size: SurfaceSize,
) -> Result<WindowRenderState, SurfaceError> {
if !matches!(backend, RenderBackend::TinySkia) {
return Err(SurfaceError::UnsupportedBackend(backend));
}
if let Some(presenter) = self
.presenter
.lock()
.unwrap_or_else(|p| p.into_inner())
.take()
{
return Ok(WindowRenderState::new_cpu(size.width, size.height, presenter));
}
let pair = extract_handle_pair(handle, backend)?;
let (gpu_pool, surface, dev_id) = init_gpu_surface(pair, size, backend)?;
Ok(WindowRenderState::new_tiny_skia_gpu(gpu_pool, surface, dev_id))
}
fn supports(&self, _handle: &RawHandle, backend: RenderBackend) -> bool {
matches!(backend, RenderBackend::TinySkia)
}
}
#[cfg(not(target_arch = "wasm32"))]
pub struct VelloCpuSurfaceFactory {
pub dpr: f64,
presenter: Mutex<Option<Box<dyn SoftwarePresenter>>>,
}
#[cfg(not(target_arch = "wasm32"))]
impl VelloCpuSurfaceFactory {
pub fn new(dpr: f64) -> Self {
Self { dpr, presenter: Mutex::new(None) }
}
pub fn with_presenter(dpr: f64, presenter: Box<dyn SoftwarePresenter>) -> Self {
Self { dpr, presenter: Mutex::new(Some(presenter)) }
}
}
#[cfg(not(target_arch = "wasm32"))]
impl Default for VelloCpuSurfaceFactory {
fn default() -> Self {
Self::new(1.0)
}
}
#[cfg(not(target_arch = "wasm32"))]
impl RenderSurfaceFactory for VelloCpuSurfaceFactory {
fn create_render_state(
&self,
handle: &RawHandle,
backend: RenderBackend,
size: SurfaceSize,
) -> Result<WindowRenderState, SurfaceError> {
if !matches!(backend, RenderBackend::VelloCpu) {
return Err(SurfaceError::UnsupportedBackend(backend));
}
if let Some(presenter) = self
.presenter
.lock()
.unwrap_or_else(|p| p.into_inner())
.take()
{
return Ok(WindowRenderState::new_vello_cpu(self.dpr, presenter));
}
let pair = extract_handle_pair(handle, backend)?;
let (gpu_pool, surface, dev_id) = init_gpu_surface(pair, size, backend)?;
Ok(WindowRenderState::new_vello_cpu_gpu(gpu_pool, surface, dev_id, self.dpr))
}
fn supports(&self, _handle: &RawHandle, backend: RenderBackend) -> bool {
matches!(backend, RenderBackend::VelloCpu)
}
}
#[cfg(not(target_arch = "wasm32"))]
pub struct VelloHybridSurfaceFactory {
pub dpr: f64,
}
#[cfg(not(target_arch = "wasm32"))]
impl VelloHybridSurfaceFactory {
pub fn new(dpr: f64) -> Self {
Self { dpr }
}
}
#[cfg(not(target_arch = "wasm32"))]
impl Default for VelloHybridSurfaceFactory {
fn default() -> Self {
Self::new(1.0)
}
}
#[cfg(not(target_arch = "wasm32"))]
impl RenderSurfaceFactory for VelloHybridSurfaceFactory {
fn create_render_state(
&self,
handle: &RawHandle,
backend: RenderBackend,
size: SurfaceSize,
) -> Result<WindowRenderState, SurfaceError> {
if !matches!(backend, RenderBackend::VelloHybrid) {
return Err(SurfaceError::UnsupportedBackend(backend));
}
let pair = extract_handle_pair(handle, backend)?;
let (gpu_pool, surface, dev_id) = init_gpu_surface(pair, size, backend)?;
Ok(WindowRenderState::new_vello_hybrid(gpu_pool, surface, dev_id, self.dpr))
}
fn supports(&self, handle: &RawHandle, backend: RenderBackend) -> bool {
matches!(backend, RenderBackend::VelloHybrid)
&& matches!(handle, RawHandle::RawWindowHandle(_))
}
}
#[cfg(not(target_arch = "wasm32"))]
pub struct WgpuInstancedSurfaceFactory;
#[cfg(not(target_arch = "wasm32"))]
impl WgpuInstancedSurfaceFactory {
pub fn new() -> Self {
Self
}
}
#[cfg(not(target_arch = "wasm32"))]
impl Default for WgpuInstancedSurfaceFactory {
fn default() -> Self {
Self::new()
}
}
#[cfg(not(target_arch = "wasm32"))]
impl RenderSurfaceFactory for WgpuInstancedSurfaceFactory {
fn create_render_state(
&self,
handle: &RawHandle,
backend: RenderBackend,
size: SurfaceSize,
) -> Result<WindowRenderState, SurfaceError> {
if !matches!(backend, RenderBackend::InstancedWgpu) {
return Err(SurfaceError::UnsupportedBackend(backend));
}
let pair = extract_handle_pair(handle, backend)?;
let (gpu_pool, surface, dev_id) = init_gpu_surface(pair, size, backend)?;
Ok(WindowRenderState::new_wgpu_instanced(gpu_pool, surface, dev_id))
}
fn supports(&self, handle: &RawHandle, backend: RenderBackend) -> bool {
matches!(backend, RenderBackend::InstancedWgpu)
&& matches!(handle, RawHandle::RawWindowHandle(_))
}
}
pub struct Canvas2dSurfaceFactory;
impl Canvas2dSurfaceFactory {
pub fn new() -> Self {
Self
}
}
impl Default for Canvas2dSurfaceFactory {
fn default() -> Self {
Self::new()
}
}
#[cfg(target_arch = "wasm32")]
impl RenderSurfaceFactory for Canvas2dSurfaceFactory {
fn create_render_state(
&self,
handle: &RawHandle,
backend: RenderBackend,
_size: SurfaceSize,
) -> Result<WindowRenderState, SurfaceError> {
if !matches!(backend, RenderBackend::Canvas2d) {
return Err(SurfaceError::UnsupportedBackend(backend));
}
let RawHandle::Canvas(any) = handle else {
return Err(SurfaceError::HandleMismatch(backend));
};
let canvas = any
.downcast_ref::<uzor_window_web::SendSyncCanvas>()
.ok_or_else(|| {
SurfaceError::InitFailed(
"expected SendSyncCanvas in RawHandle::Canvas — use WebWindowProvider".into(),
)
})?
.0
.clone();
let raw_ctx = canvas
.get_context("2d")
.map_err(|e| {
SurfaceError::InitFailed(format!("canvas.getContext(\"2d\") failed: {e:?}"))
})?
.ok_or_else(|| SurfaceError::InitFailed("canvas.getContext(\"2d\") returned null".into()))?;
use wasm_bindgen::JsCast as _;
let ctx2d = raw_ctx
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.map_err(|_| {
SurfaceError::InitFailed(
"getContext(\"2d\") object is not CanvasRenderingContext2d".into(),
)
})?;
let dpr = web_sys::window()
.map(|w| w.device_pixel_ratio())
.unwrap_or(1.0);
let render_ctx = uzor_render_canvas2d::Canvas2dRenderContext::new(ctx2d, dpr);
Ok(WindowRenderState::new_canvas2d(canvas, render_ctx))
}
fn supports(&self, handle: &RawHandle, backend: RenderBackend) -> bool {
matches!(backend, RenderBackend::Canvas2d) && matches!(handle, RawHandle::Canvas(_))
}
}
#[cfg(not(target_arch = "wasm32"))]
impl RenderSurfaceFactory for Canvas2dSurfaceFactory {
fn create_render_state(
&self,
_handle: &RawHandle,
backend: RenderBackend,
_size: SurfaceSize,
) -> Result<WindowRenderState, SurfaceError> {
Err(SurfaceError::UnsupportedBackend(backend))
}
fn supports(&self, _handle: &RawHandle, _backend: RenderBackend) -> bool {
false
}
}
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
mod tests {
use super::*;
fn canvas_handle() -> RawHandle {
RawHandle::Canvas(Box::new(42u32))
}
fn raw_window_handle_dummy() -> RawHandle {
RawHandle::Canvas(Box::new(99u32))
}
#[test]
fn vello_gpu_supports_correct_pair() {
let f = VelloGpuSurfaceFactory::new();
let handle = RawHandle::RawWindowHandle(Box::new(42u32));
assert!(f.supports(&handle, RenderBackend::VelloGpu));
}
#[test]
fn vello_gpu_rejects_wrong_backend() {
let f = VelloGpuSurfaceFactory::new();
let handle = RawHandle::RawWindowHandle(Box::new(42u32));
assert!(!f.supports(&handle, RenderBackend::TinySkia));
assert!(!f.supports(&handle, RenderBackend::VelloCpu));
}
#[test]
fn vello_gpu_rejects_canvas_handle() {
let f = VelloGpuSurfaceFactory::new();
assert!(!f.supports(&canvas_handle(), RenderBackend::VelloGpu));
}
#[test]
fn tiny_skia_supports_any_handle() {
let f = TinySkiaSurfaceFactory::new();
assert!(f.supports(&canvas_handle(), RenderBackend::TinySkia));
assert!(f.supports(&raw_window_handle_dummy(), RenderBackend::TinySkia));
}
#[test]
fn tiny_skia_rejects_wrong_backend() {
let f = TinySkiaSurfaceFactory::new();
assert!(!f.supports(&canvas_handle(), RenderBackend::VelloGpu));
assert!(!f.supports(&canvas_handle(), RenderBackend::VelloCpu));
}
#[test]
fn vello_cpu_supports_any_handle() {
let f = VelloCpuSurfaceFactory::default();
assert!(f.supports(&canvas_handle(), RenderBackend::VelloCpu));
assert!(f.supports(&raw_window_handle_dummy(), RenderBackend::VelloCpu));
}
#[test]
fn vello_cpu_rejects_wrong_backend() {
let f = VelloCpuSurfaceFactory::default();
assert!(!f.supports(&canvas_handle(), RenderBackend::VelloGpu));
assert!(!f.supports(&canvas_handle(), RenderBackend::TinySkia));
}
#[test]
fn vello_hybrid_supports_raw_window_handle() {
let f = VelloHybridSurfaceFactory::default();
let handle = RawHandle::RawWindowHandle(Box::new(42u32));
assert!(f.supports(&handle, RenderBackend::VelloHybrid));
}
#[test]
fn vello_hybrid_rejects_canvas_handle() {
let f = VelloHybridSurfaceFactory::default();
assert!(!f.supports(&canvas_handle(), RenderBackend::VelloHybrid));
}
#[test]
fn vello_hybrid_rejects_wrong_backend() {
let f = VelloHybridSurfaceFactory::default();
let handle = RawHandle::RawWindowHandle(Box::new(42u32));
assert!(!f.supports(&handle, RenderBackend::VelloGpu));
assert!(!f.supports(&handle, RenderBackend::TinySkia));
}
#[test]
fn wgpu_instanced_supports_raw_window_handle() {
let f = WgpuInstancedSurfaceFactory::new();
let handle = RawHandle::RawWindowHandle(Box::new(42u32));
assert!(f.supports(&handle, RenderBackend::InstancedWgpu));
}
#[test]
fn wgpu_instanced_rejects_canvas_handle() {
let f = WgpuInstancedSurfaceFactory::new();
assert!(!f.supports(&canvas_handle(), RenderBackend::InstancedWgpu));
}
#[test]
fn wgpu_instanced_rejects_wrong_backend() {
let f = WgpuInstancedSurfaceFactory::new();
let handle = RawHandle::RawWindowHandle(Box::new(42u32));
assert!(!f.supports(&handle, RenderBackend::VelloGpu));
assert!(!f.supports(&handle, RenderBackend::TinySkia));
}
#[test]
fn canvas2d_never_supports() {
let f = Canvas2dSurfaceFactory::new();
assert!(!f.supports(&canvas_handle(), RenderBackend::VelloGpu));
assert!(!f.supports(&canvas_handle(), RenderBackend::TinySkia));
let handle = RawHandle::RawWindowHandle(Box::new(42u32));
assert!(!f.supports(&handle, RenderBackend::VelloGpu));
}
}