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"))]
pub struct GpuPrewarm {
pub gpu_pool: GpuDevicePool,
pub dev_id: usize,
pub renderer: vello::Renderer,
}
#[cfg(not(target_arch = "wasm32"))]
pub struct GpuDeviceReady {
pub gpu_pool: GpuDevicePool,
pub dev_id: usize,
}
#[cfg(not(target_arch = "wasm32"))]
pub fn prewarm_device() -> Result<GpuDeviceReady, SurfaceError> {
let t_total = std::time::Instant::now();
let mut gpu_pool = GpuDevicePool::new();
gpu_pool.instance = build_dcomp_instance();
eprintln!("[render-hub] prewarm_device: instance: {:?}", t_total.elapsed());
let t_dev = std::time::Instant::now();
let dev_id = pollster::block_on(gpu_pool.device(None))
.ok_or_else(|| SurfaceError::InitFailed("prewarm: no compatible device".into()))?;
eprintln!("[render-hub] prewarm_device: adapter + device: {:?}", t_dev.elapsed());
eprintln!("[render-hub] prewarm_device: TOTAL: {:?}", t_total.elapsed());
Ok(GpuDeviceReady { gpu_pool, dev_id })
}
#[cfg(not(target_arch = "wasm32"))]
pub fn build_renderer(device: &wgpu::Device) -> Result<vello::Renderer, SurfaceError> {
let t_renderer = std::time::Instant::now();
let n_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let aa = vello::AaSupport { area: true, msaa8: false, msaa16: false };
let renderer = vello::Renderer::new(
device,
vello::RendererOptions {
use_cpu: false,
antialiasing_support: aa,
num_init_threads: std::num::NonZeroUsize::new(n_threads),
pipeline_cache: None,
},
)
.map_err(|e| SurfaceError::InitFailed(format!("build_renderer: {e}")))?;
eprintln!("[render-hub] build_renderer: {:?}", t_renderer.elapsed());
Ok(renderer)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn prewarm_vello_gpu() -> Result<GpuPrewarm, SurfaceError> {
let ready = prewarm_device()?;
let renderer = build_renderer(&ready.gpu_pool.devices[ready.dev_id].device)?;
Ok(GpuPrewarm {
gpu_pool: ready.gpu_pool,
dev_id: ready.dev_id,
renderer,
})
}
#[cfg(not(target_arch = "wasm32"))]
pub fn build_surface_from_device(
ready: GpuDeviceReady,
handle: &RawHandle,
size: SurfaceSize,
) -> Result<(GpuDevicePool, vello::util::RenderSurface<'static>, usize), SurfaceError> {
let pair = extract_handle_pair(handle, RenderBackend::VelloGpu)?;
let t_total = std::time::Instant::now();
let GpuDeviceReady { gpu_pool, dev_id } = ready;
let target = WinitSurfaceTarget { window: pair.0, display: pair.1 };
use vello::util::RenderSurface;
let surface_raw: wgpu::Surface<'_> = gpu_pool
.instance
.create_surface(wgpu::SurfaceTarget::from(target))
.map_err(|e| SurfaceError::InitFailed(format!("build_surface: create_surface: {e}")))?;
{
let dh = &gpu_pool.devices[dev_id];
if !dh.adapter().is_surface_supported(&surface_raw) {
return Err(SurfaceError::InitFailed(
"build_surface: pre-warmed adapter does not support this HWND".into(),
));
}
}
let (alpha_mode, swap_format) = {
let dh = &gpu_pool.devices[dev_id];
let caps = surface_raw.get_capabilities(dh.adapter());
let am = if caps.alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
wgpu::CompositeAlphaMode::PreMultiplied
} else {
wgpu::CompositeAlphaMode::Auto
};
let fmt = caps.formats.iter()
.find(|f| matches!(f, wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm))
.copied().unwrap_or(caps.formats[0]);
(am, fmt)
};
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: swap_format,
width: size.width.max(1),
height: size.height.max(1),
present_mode: PresentMode::Fifo,
desired_maximum_frame_latency: 2,
alpha_mode,
view_formats: vec![],
};
{
let device = &gpu_pool.devices[dev_id].device;
surface_raw.configure(device, &config);
}
let (target_texture, target_view) = {
let device = &gpu_pool.devices[dev_id].device;
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("uzor_target_texture"),
size: wgpu::Extent3d {
width: config.width, height: config.height, depth_or_array_layers: 1,
},
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 view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
};
let blitter = {
let device = &gpu_pool.devices[dev_id].device;
wgpu::util::TextureBlitter::new(device, swap_format)
};
let surface_with_lifetime: RenderSurface<'_> = RenderSurface {
surface: surface_raw, config, dev_id,
format: swap_format, target_texture, target_view, blitter,
};
let surface: RenderSurface<'static> = unsafe { std::mem::transmute(surface_with_lifetime) };
eprintln!("[render-hub] build_surface_from_device: {:?}", t_total.elapsed());
Ok((gpu_pool, surface, dev_id))
}
#[cfg(not(target_arch = "wasm32"))]
fn finalize_gpu_surface(
prewarm: GpuPrewarm,
pair: &SendSyncHandlePair,
size: SurfaceSize,
) -> Result<(GpuDevicePool, vello::util::RenderSurface<'static>, usize, vello::Renderer), SurfaceError> {
let t_total = std::time::Instant::now();
let GpuPrewarm { gpu_pool, dev_id, renderer } = prewarm;
let target = WinitSurfaceTarget { window: pair.0, display: pair.1 };
use vello::util::RenderSurface;
let surface_raw: wgpu::Surface<'_> = gpu_pool
.instance
.create_surface(wgpu::SurfaceTarget::from(target))
.map_err(|e| SurfaceError::InitFailed(format!("finalize: create_surface: {e}")))?;
{
let dh = &gpu_pool.devices[dev_id];
if !dh.adapter().is_surface_supported(&surface_raw) {
return Err(SurfaceError::InitFailed(
"finalize: pre-warmed adapter does not support this HWND surface — fallback path needed".into()
));
}
}
let (alpha_mode, swap_format) = {
let dh = &gpu_pool.devices[dev_id];
let caps = surface_raw.get_capabilities(dh.adapter());
let am = if caps.alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
wgpu::CompositeAlphaMode::PreMultiplied
} else {
wgpu::CompositeAlphaMode::Auto
};
let fmt = caps.formats.iter()
.find(|f| matches!(f, wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm))
.copied().unwrap_or(caps.formats[0]);
(am, fmt)
};
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: swap_format,
width: size.width.max(1),
height: size.height.max(1),
present_mode: PresentMode::Fifo,
desired_maximum_frame_latency: 2,
alpha_mode,
view_formats: vec![],
};
{
let device = &gpu_pool.devices[dev_id].device;
surface_raw.configure(device, &config);
}
let (target_texture, target_view) = {
let device = &gpu_pool.devices[dev_id].device;
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("uzor_target_texture"),
size: wgpu::Extent3d {
width: config.width, height: config.height, depth_or_array_layers: 1,
},
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 view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
};
let blitter = {
let device = &gpu_pool.devices[dev_id].device;
wgpu::util::TextureBlitter::new(device, swap_format)
};
let surface_with_lifetime: RenderSurface<'_> = RenderSurface {
surface: surface_raw, config, dev_id,
format: swap_format, target_texture, target_view, blitter,
};
let surface: RenderSurface<'static> = unsafe { std::mem::transmute(surface_with_lifetime) };
eprintln!("[render-hub] finalize_gpu_surface: {:?}", t_total.elapsed());
Ok((gpu_pool, surface, dev_id, renderer))
}
#[cfg(not(target_arch = "wasm32"))]
fn build_dcomp_instance() -> wgpu::Instance {
#[cfg(target_os = "windows")]
{
let backends = wgpu::Backends::DX12;
let flags = wgpu::InstanceFlags::from_build_config().with_env();
let memory_budget_thresholds = wgpu::MemoryBudgetThresholds::default();
let backend_options = wgpu::BackendOptions {
dx12: wgpu::Dx12BackendOptions {
presentation_system: wgpu::Dx12SwapchainKind::DxgiFromVisual,
..Default::default()
},
..Default::default()
};
return wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends,
flags,
memory_budget_thresholds,
backend_options,
});
}
#[cfg(not(target_os = "windows"))]
{
let backends = wgpu::Backends::from_env().unwrap_or_default();
let flags = wgpu::InstanceFlags::from_build_config().with_env();
let memory_budget_thresholds = wgpu::MemoryBudgetThresholds::default();
let backend_options = wgpu::BackendOptions::from_env_or_default();
wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends,
flags,
memory_budget_thresholds,
backend_options,
})
}
}
#[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 t_init = std::time::Instant::now();
let mut gpu_pool = GpuDevicePool::new();
gpu_pool.instance = build_dcomp_instance();
eprintln!("[render-hub] build_dcomp_instance: {:?}", t_init.elapsed());
use vello::util::RenderSurface;
let t_surf = std::time::Instant::now();
let surface_raw: wgpu::Surface<'_> = gpu_pool
.instance
.create_surface(wgpu::SurfaceTarget::from(target))
.map_err(|e| SurfaceError::InitFailed(format!("{backend:?}: create_surface: {e}")))?;
eprintln!("[render-hub] instance.create_surface: {:?}", t_surf.elapsed());
let t_dev = std::time::Instant::now();
let dev_id = pollster::block_on(gpu_pool.device(Some(&surface_raw)))
.ok_or_else(|| SurfaceError::InitFailed(format!("{backend:?}: no compatible device")))?;
eprintln!("[render-hub] request_adapter + device: {:?}", t_dev.elapsed());
let (alpha_mode, swap_format, alpha_modes_log, formats_log, adapter_name) = {
let dh = &gpu_pool.devices[dev_id];
let caps = surface_raw.get_capabilities(dh.adapter());
let am = if caps.alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
wgpu::CompositeAlphaMode::PreMultiplied
} else {
wgpu::CompositeAlphaMode::Auto
};
let fmt = caps
.formats
.iter()
.find(|f| matches!(f, wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm))
.copied()
.unwrap_or(caps.formats[0]);
(am, fmt, caps.alpha_modes.clone(), caps.formats.clone(), dh.adapter().get_info().name.clone())
};
eprintln!(
"[render-hub] alpha_modes={:?} formats={:?} adapter={:?} picked alpha_mode={:?} picked format={:?}",
alpha_modes_log, formats_log, adapter_name, alpha_mode, swap_format,
);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: swap_format,
width: size.width.max(1),
height: size.height.max(1),
present_mode: PresentMode::Fifo,
desired_maximum_frame_latency: 2,
alpha_mode,
view_formats: vec![],
};
{
let device = &gpu_pool.devices[dev_id].device;
surface_raw.configure(device, &config);
}
eprintln!("[render-hub] surface configured ONCE with alpha_mode={:?}", alpha_mode);
use std::io::Write;
let _ = std::io::stderr().flush();
let t_blitter = std::time::Instant::now();
let (target_texture, target_view) = {
let device = &gpu_pool.devices[dev_id].device;
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("uzor_target_texture"),
size: wgpu::Extent3d {
width: config.width,
height: config.height,
depth_or_array_layers: 1,
},
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 view = tex.create_view(&wgpu::TextureViewDescriptor::default());
(tex, view)
};
let blitter = {
let device = &gpu_pool.devices[dev_id].device;
wgpu::util::TextureBlitter::new(device, swap_format)
};
eprintln!("[render-hub] target_texture + blitter: {:?}", t_blitter.elapsed());
let _ = std::io::stderr().flush();
let surface_with_lifetime: RenderSurface<'_> = RenderSurface {
surface: surface_raw,
config,
dev_id,
format: swap_format,
target_texture,
target_view,
blitter,
};
let surface: RenderSurface<'static> =
unsafe { std::mem::transmute(surface_with_lifetime) };
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 VelloGpuSurfaceFactory {
pub fn create_render_state_with_prewarm(
&self,
prewarm: GpuPrewarm,
handle: &RawHandle,
size: SurfaceSize,
) -> Result<WindowRenderState, SurfaceError> {
let pair = extract_handle_pair(handle, RenderBackend::VelloGpu)?;
let (gpu_pool, surface, dev_id, renderer) = finalize_gpu_surface(prewarm, pair, size)?;
Ok(WindowRenderState::new_gpu(gpu_pool, surface, renderer, dev_id))
}
}
#[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 t_renderer = std::time::Instant::now();
let n_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let aa = AaSupport { area: true, msaa8: false, msaa16: false };
let renderer = Renderer::new(
device,
RendererOptions {
use_cpu: false,
antialiasing_support: aa,
num_init_threads: std::num::NonZeroUsize::new(n_threads),
pipeline_cache: None,
},
)
.map_err(|e| SurfaceError::InitFailed(e.to_string()))?;
eprintln!("[render-hub] vello Renderer::new ({} threads, area-only AA): {:?}", n_threads, t_renderer.elapsed());
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));
}
}