use std::sync::Arc;
use denise::{BufferAge, InputEvent, InputSource, Pen, Rect, Size};
use denise_wgpu::Gpu;
use winit::window::Window;
use crate::Error;
pub struct GpuSurface {
window: Arc<Window>,
surface: wgpu::Surface<'static>,
config: wgpu::SurfaceConfiguration,
gpu: Gpu,
size: Size,
scale_factor: f32,
events: Vec<InputEvent>,
canvas: Option<Canvas>,
}
struct Canvas {
texture: wgpu::Texture,
view: wgpu::TextureView,
fresh: bool,
}
impl GpuSurface {
pub fn new(window: Arc<Window>) -> Result<Self, Error> {
let instance = wgpu::Instance::default();
let surface = instance
.create_surface(window.clone())
.map_err(|e| Error::Present(e.to_string()))?;
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
compatible_surface: Some(&surface),
..Default::default()
}))
.map_err(|_| Error::Gpu("no adapter can present to this window".into()))?;
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("denise window"),
..Default::default()
}))
.map_err(|e| Error::Gpu(e.to_string()))?;
let inner = window.inner_size();
let size = Size::new(inner.width, inner.height);
let mut config = surface
.get_default_config(&adapter, size.width.max(1), size.height.max(1))
.ok_or_else(|| Error::Gpu("the adapter cannot present to this window".into()))?;
let caps = surface.get_capabilities(&adapter);
if let Some(format) = caps.formats.iter().copied().find(|f| !f.is_srgb()) {
config.format = format;
}
config.present_mode = wgpu::PresentMode::AutoVsync;
config.usage |= wgpu::TextureUsages::COPY_DST;
surface.configure(&device, &config);
let gpu = Gpu::new(device, queue, config.format);
Ok(Self {
scale_factor: window.scale_factor() as f32,
window,
surface,
config,
gpu,
size,
events: Vec::new(),
canvas: None,
})
}
pub fn window(&self) -> &Arc<Window> {
&self.window
}
pub fn size(&self) -> Size {
self.size
}
pub fn scale_factor(&self) -> f32 {
self.scale_factor
}
pub fn resize(&mut self, size: Size, scale_factor: f32) {
if size == self.size && scale_factor == self.scale_factor {
return;
}
self.size = size;
self.scale_factor = scale_factor;
if size.is_empty() {
return;
}
self.config.width = size.width;
self.config.height = size.height;
self.surface.configure(self.gpu.device(), &self.config);
self.canvas = None;
}
pub(crate) fn push_event(&mut self, event: InputEvent) {
self.events.push(event);
}
pub fn paint(
&mut self,
damage: &[Rect],
draw: impl FnOnce(&mut Pen<'_>),
) -> Result<bool, Error> {
use wgpu::CurrentSurfaceTexture as Current;
if self.size.is_empty() {
return Ok(false);
}
let frame = match self.surface.get_current_texture() {
Current::Success(frame) | Current::Suboptimal(frame) => frame,
Current::Lost | Current::Outdated => {
self.surface.configure(self.gpu.device(), &self.config);
return Ok(false);
}
Current::Timeout | Current::Occluded => return Ok(false),
Current::Validation => {
return Err(Error::Gpu("the swapchain failed validation".into()));
}
};
let canvas = self.canvas.get_or_insert_with(|| {
let texture = self.gpu.device().create_texture(&wgpu::TextureDescriptor {
label: Some("denise canvas"),
size: wgpu::Extent3d {
width: self.size.width,
height: self.size.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: self.config.format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
Canvas {
texture,
view,
fresh: true,
}
});
let mut painter = self.gpu.painter(self.size);
draw(&mut Pen::new(&mut painter));
if canvas.fresh {
painter.finish(&canvas.view);
canvas.fresh = false;
} else {
painter.finish_onto(&canvas.texture, damage);
}
let mut encoder =
self.gpu
.device()
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("denise present"),
});
encoder.copy_texture_to_texture(
wgpu::TexelCopyTextureInfo {
texture: &canvas.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyTextureInfo {
texture: &frame.texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::Extent3d {
width: self.size.width,
height: self.size.height,
depth_or_array_layers: 1,
},
);
self.gpu.queue().submit([encoder.finish()]);
self.window.pre_present_notify();
self.gpu.queue().present(frame);
Ok(true)
}
pub fn age(&self) -> BufferAge {
match &self.canvas {
Some(canvas) if !canvas.fresh => BufferAge::Frames(1),
_ => BufferAge::Undefined,
}
}
}
impl InputSource for GpuSurface {
fn poll(&mut self, out: &mut Vec<InputEvent>) {
out.append(&mut self.events);
}
}