use alloc::collections::VecDeque;
use alloc::string::{String, ToString};
use core::time::Duration;
use std::sync::Arc;
use winit::application::ApplicationHandler;
use winit::event::{
ElementState, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::{Key, NamedKey};
use winit::platform::pump_events::EventLoopExtPumpEvents;
use winit::window::{Window, WindowId};
use super::{BackbufferPersistence, DisplayInfo, InputEvent, Surface, logical_from_physical};
use crate::cache::InspectCaches;
use crate::draw::texture::ColorFormat;
use crate::types::{Fixed, Rect};
pub struct WgpuState {
pub window: Arc<Window>,
pub instance: wgpu::Instance,
pub surface: wgpu::Surface<'static>,
pub adapter: wgpu::Adapter,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub config: wgpu::SurfaceConfiguration,
pub msaa: wgpu::Texture,
}
struct WgpuHandler {
title: String,
requested_size: (u32, u32),
state: Option<WgpuState>,
event_queue: VecDeque<InputEvent>,
last_cursor: (Fixed, Fixed),
pending_move: Option<(Fixed, Fixed)>,
}
impl WgpuHandler {
fn to_logical(&self, x: f64, y: f64) -> (Fixed, Fixed) {
let scale = self
.state
.as_ref()
.map(|s| s.window.scale_factor().round().max(1.0))
.unwrap_or(1.0);
(
Fixed::from((x / scale) as i32),
Fixed::from((y / scale) as i32),
)
}
}
impl ApplicationHandler for WgpuHandler {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.state.is_some() {
return;
}
let attrs = Window::default_attributes()
.with_title(self.title.clone())
.with_inner_size(winit::dpi::LogicalSize::new(
self.requested_size.0,
self.requested_size.1,
));
let window = Arc::new(
event_loop
.create_window(attrs)
.expect("winit create_window failed"),
);
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
let surface = instance
.create_surface(window.clone())
.expect("wgpu create_surface failed");
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
}))
.expect("wgpu request_adapter failed");
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("mirui-wgpu-device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_defaults().using_resolution(adapter.limits()),
memory_hints: wgpu::MemoryHints::Performance,
trace: wgpu::Trace::Off,
..Default::default()
}))
.expect("wgpu request_device failed");
let size = window.inner_size();
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps
.formats
.iter()
.copied()
.find(|f| !f.is_srgb())
.unwrap_or(surface_caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width.max(1),
height: size.height.max(1),
present_mode: if surface_caps
.present_modes
.contains(&wgpu::PresentMode::Mailbox)
{
wgpu::PresentMode::Mailbox
} else if surface_caps
.present_modes
.contains(&wgpu::PresentMode::Immediate)
{
wgpu::PresentMode::Immediate
} else {
wgpu::PresentMode::Fifo
},
alpha_mode: surface_caps.alpha_modes[0],
view_formats: alloc::vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &config);
let msaa = create_msaa(&device, &config);
self.state = Some(WgpuState {
window,
instance,
surface,
adapter,
device,
queue,
config,
msaa,
});
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
self.event_queue.push_back(InputEvent::Quit);
event_loop.exit();
}
WindowEvent::Resized(new_size) => {
if let Some(state) = self.state.as_mut() {
state.config.width = new_size.width.max(1);
state.config.height = new_size.height.max(1);
state.surface.configure(&state.device, &state.config);
state.msaa = create_msaa(&state.device, &state.config);
}
}
WindowEvent::CursorMoved { position, .. } => {
let (x, y) = self.to_logical(position.x, position.y);
self.last_cursor = (x, y);
self.pending_move = Some((x, y));
}
WindowEvent::MouseInput { state, button, .. } => {
if button != MouseButton::Left {
return;
}
let (x, y) = self.last_cursor;
let event = match state {
ElementState::Pressed => InputEvent::PointerDown { id: 0, x, y },
ElementState::Released => InputEvent::PointerUp { id: 0, x, y },
};
self.event_queue.push_back(event);
}
WindowEvent::MouseWheel { delta, .. } => {
const PX_PER_LINE: f32 = 16.0;
let scale = self
.state
.as_ref()
.map(|s| s.window.scale_factor().round().max(1.0))
.unwrap_or(1.0) as f32;
let (dx, dy) = match delta {
MouseScrollDelta::LineDelta(x, y) => (Fixed::from_f32(-x), Fixed::from_f32(y)),
MouseScrollDelta::PixelDelta(p) => {
let lx = (p.x as f32) / scale;
let ly = (p.y as f32) / scale;
(
Fixed::from_f32(-lx / PX_PER_LINE),
Fixed::from_f32(ly / PX_PER_LINE),
)
}
};
let (x, y) = self.last_cursor;
self.event_queue
.push_back(InputEvent::Wheel { dx, dy, x, y });
}
WindowEvent::Touch(touch) => {
let (x, y) = self.to_logical(touch.location.x, touch.location.y);
let id = (touch.id & 0xff) as u8;
let event = match touch.phase {
TouchPhase::Started => InputEvent::PointerDown { id, x, y },
TouchPhase::Moved => InputEvent::PointerMove { id, x, y },
TouchPhase::Ended | TouchPhase::Cancelled => InputEvent::PointerUp { id, x, y },
};
self.event_queue.push_back(event);
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key,
text,
state,
..
},
..
} => {
use crate::event::input::*;
if state != ElementState::Pressed {
return;
}
let code = match &logical_key {
Key::Named(NamedKey::Backspace) => Some(KEY_BACKSPACE),
Key::Named(NamedKey::Delete) => Some(KEY_DELETE),
Key::Named(NamedKey::ArrowLeft) => Some(KEY_LEFT),
Key::Named(NamedKey::ArrowRight) => Some(KEY_RIGHT),
Key::Named(NamedKey::Home) => Some(KEY_HOME),
Key::Named(NamedKey::End) => Some(KEY_END),
Key::Named(NamedKey::Enter) => Some(KEY_RETURN),
Key::Named(NamedKey::Escape) => {
self.event_queue.push_back(InputEvent::Quit);
event_loop.exit();
return;
}
_ => None,
};
if let Some(code) = code {
self.event_queue.push_back(InputEvent::Key {
code,
pressed: true,
});
}
if let Some(s) = text.as_ref() {
if let Some(ch) = s.chars().next() {
if !ch.is_control() {
self.event_queue.push_back(InputEvent::CharInput { ch });
}
}
}
}
_ => {}
}
}
}
pub struct WgpuSurface {
event_loop: EventLoop<()>,
handler: WgpuHandler,
pumped_this_frame: bool,
}
impl WgpuSurface {
pub fn state(&self) -> Option<&WgpuState> {
self.handler.state.as_ref()
}
pub fn state_mut(&mut self) -> Option<&mut WgpuState> {
self.handler.state.as_mut()
}
pub fn new(title: &str, width: u16, height: u16) -> Self {
let event_loop = EventLoop::new().expect("winit EventLoop::new failed");
event_loop.set_control_flow(ControlFlow::Poll);
let mut this = Self {
event_loop,
handler: WgpuHandler {
title: title.to_string(),
requested_size: (width as u32, height as u32),
state: None,
event_queue: VecDeque::new(),
last_cursor: (Fixed::ZERO, Fixed::ZERO),
pending_move: None,
},
pumped_this_frame: false,
};
let mut spins = 0;
while this.handler.state.is_none() && spins < 100 {
this.pump_once();
spins += 1;
}
if this.handler.state.is_none() {
panic!("WgpuSurface: winit failed to deliver resumed within {spins} pumps");
}
this
}
fn pump_once(&mut self) -> winit::platform::pump_events::PumpStatus {
self.event_loop
.pump_app_events(Some(Duration::ZERO), &mut self.handler)
}
}
fn create_msaa(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> wgpu::Texture {
device.create_texture(&wgpu::TextureDescriptor {
label: Some("mirui-wgpu-msaa"),
size: wgpu::Extent3d {
width: config.width,
height: config.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: crate::draw::wgpu_render::MSAA_SAMPLES,
dimension: wgpu::TextureDimension::D2,
format: config.format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
})
}
impl InspectCaches for WgpuSurface {}
impl Surface for WgpuSurface {
fn display_info(&self) -> DisplayInfo {
let state = self
.handler
.state
.as_ref()
.expect("WgpuSurface state must be initialised by new()");
let size = state.window.inner_size();
let scale_int = state.window.scale_factor().round().max(1.0) as u16;
let scale = Fixed::from(scale_int);
let (lw, lh) = logical_from_physical(size.width as u16, size.height as u16, scale);
DisplayInfo {
width: lw,
height: lh,
scale,
format: ColorFormat::RGBA8888,
}
}
fn flush(&mut self, _area: &Rect) {
self.pumped_this_frame = false;
}
fn poll_event(&mut self) -> Option<InputEvent> {
if let Some(e) = self.handler.event_queue.pop_front() {
return Some(e);
}
if self.pumped_this_frame {
return None;
}
self.pumped_this_frame = true;
self.pump_once();
if let Some((x, y)) = self.handler.pending_move.take() {
self.handler
.event_queue
.push_back(InputEvent::PointerMove { id: 0, x, y });
}
self.handler.event_queue.pop_front()
}
fn persistence(&self) -> BackbufferPersistence {
BackbufferPersistence::Transient
}
}