diff --git a/CHANGELOG.md b/CHANGELOG.md index fb839a8..9acc30b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 interface. - `Mesh::new_with_tolerance`, which allows to control the tolerance of line segment approximations. [#100] +- `Game::cursor_icon`, which allows customization of the mouse cursor icon. ### Changed - `Mesh::stroke` now takes an `f32` as `line_width` instead of a `u16`. diff --git a/Cargo.toml b/Cargo.toml index 09b80b6..d194547 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,11 +20,11 @@ features = ["opengl", "debug"] [features] default = [] -opengl = ["gfx", "gfx_core", "glutin", "gfx_device_gl", "gfx_window_glutin", "gfx_glyph", "gfx_winit"] -vulkan = ["wgpu", "wgpu/vulkan", "wgpu_glyph"] -metal = ["wgpu", "wgpu/metal", "wgpu_glyph"] -dx11 = ["wgpu", "wgpu/dx11", "wgpu_glyph"] -dx12 = ["wgpu", "wgpu/dx12", "wgpu_glyph"] +opengl = ["gfx", "gfx_core", "glutin", "gfx_device_gl", "gfx_glyph"] +vulkan = ["wgpu", "wgpu_glyph", "zerocopy", "futures"] +metal = ["wgpu", "wgpu_glyph", "zerocopy", "futures"] +dx11 = ["wgpu", "wgpu_glyph", "zerocopy", "futures"] +dx12 = ["wgpu", "wgpu_glyph", "zerocopy", "futures"] debug = [] [dependencies] @@ -35,19 +35,20 @@ stretch = "0.2" twox-hash = "1.3" lyon_tessellation = "0.13" gilrs = "0.7" +winit = "0.22" # gfx (OpenGL) gfx = { version = "0.18", optional = true } gfx_core = { version = "0.9", optional = true } -glutin = { version = "0.20", optional = true } gfx_device_gl = { version = "0.16", optional = true } -gfx_window_glutin = { version = "0.30", optional = true } gfx_glyph = { version = "0.15", optional = true } -gfx_winit = { package = "winit", version = "0.19", optional = true } +glutin = { version = "0.24", optional = true } # wgpu (Vulkan, Metal, D3D) -wgpu = { version = "0.2", optional = true, git = "https://github.com/gfx-rs/wgpu-rs", rev = "5522c912f7e2f4f33a1167fb0c8ee4549f066dcf" } -wgpu_glyph = { version = "0.3", optional = true, git = "https://github.com/hecrj/wgpu_glyph", rev = "0577e4d2be6b035a14aa0c5d82b143aaf26c1bd3" } +wgpu = { version = "0.5", optional = true } +wgpu_glyph = { version = "0.8", optional = true } +zerocopy = { version = "0.3", optional = true } +futures = { version = "0.3", optional = true } [dev-dependencies] rand = "0.6" diff --git a/build.rs b/build.rs index 929b179..5d66a60 100644 --- a/build.rs +++ b/build.rs @@ -3,7 +3,7 @@ feature = "vulkan", feature = "metal", feature = "dx11", - feature = "dx12" + feature = "dx12", )))] compile_error!( "You need to enable a graphics backend feature. \ diff --git a/src/debug/null.rs b/src/debug/null.rs index 6ce928c..6c4d03c 100644 --- a/src/debug/null.rs +++ b/src/debug/null.rs @@ -1,12 +1,10 @@ use crate::graphics; // Null debug implementation -#[cfg(not(debug_assertions))] #[allow(missing_debug_implementations)] #[allow(missing_docs)] pub struct Debug {} -#[cfg(not(debug_assertions))] impl Debug { pub(crate) fn new(_gpu: &mut graphics::Gpu) -> Self { Self {} diff --git a/src/game.rs b/src/game.rs index 442f7fc..df38292 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,6 +1,9 @@ -use crate::graphics::window; -use crate::graphics::{Frame, Window, WindowSettings}; -use crate::input::{self, gamepad, keyboard, mouse, Input}; +mod r#loop; + +pub(crate) use r#loop::Loop; + +use crate::graphics::{CursorIcon, Frame, Window, WindowSettings}; +use crate::input::{keyboard, Input}; use crate::load::{LoadingScreen, Task}; use crate::{Debug, Result, Timer}; @@ -121,6 +124,13 @@ pub trait Game { /// [`Window`]: graphics/struct.Window.html fn update(&mut self, _window: &Window) {} + /// Defines the cursor icon of the window. + /// + /// By default, it returns platform-dependent default cursor. + fn cursor_icon(&self) -> CursorIcon { + CursorIcon::Default + } + /// Displays debug information. /// /// This method is called after [`draw`] once per frame when debug has been @@ -171,155 +181,8 @@ pub trait Game { /// [`WindowSettings`]: graphics/struct.WindowSettings.html fn run(window_settings: WindowSettings) -> Result<()> where - Self: Sized + 'static, + Self: 'static + Sized, { - // Set up window - let event_loop = &mut window::EventLoop::new(); - let window = &mut Window::new(window_settings, &event_loop)?; - let mut debug = Debug::new(window.gpu()); - - // Load game - debug.loading_started(); - let mut loading_screen = Self::LoadingScreen::new(window.gpu())?; - let game = &mut loading_screen.run(Self::load(window), window)?; - let input = &mut Self::Input::new(); - let mut gamepads = gamepad::Tracker::new(); - debug.loading_finished(); - - // Game loop - let mut timer = Timer::new(Self::TICKS_PER_SECOND); - let mut alive = true; - - while alive && !game.is_finished() { - debug.frame_started(); - timer.update(); - - while timer.tick() { - interact( - game, - input, - &mut debug, - window, - event_loop, - gamepads.as_mut(), - &mut alive, - ); - - debug.update_started(); - game.update(window); - debug.update_finished(); - } - - if !timer.has_ticked() { - interact( - game, - input, - &mut debug, - window, - event_loop, - gamepads.as_mut(), - &mut alive, - ); - } - - debug.draw_started(); - game.draw(&mut window.frame(), &timer); - debug.draw_finished(); - - if debug.is_enabled() { - debug.debug_started(); - game.debug(input, &mut window.frame(), &mut debug); - debug.debug_finished(); - } - - window.swap_buffers(); - debug.frame_finished(); - } - - Ok(()) - } -} - -fn interact( - game: &mut G, - input: &mut G::Input, - debug: &mut Debug, - window: &mut Window, - event_loop: &mut window::EventLoop, - gamepads: Option<&mut gamepad::Tracker>, - alive: &mut bool, -) { - debug.interact_started(); - - event_loop.poll(|event| { - process_window_event(game, input, debug, window, alive, event) - }); - - process_gamepad_events(gamepads, input); - - game.interact(input, window); - input.clear(); - - debug.interact_finished(); -} - -pub(crate) fn process_window_event( - game: &mut G, - input: &mut I, - debug: &mut Debug, - window: &mut Window, - alive: &mut bool, - event: window::Event, -) { - match event { - window::Event::Input(input_event) => { - input.update(input_event); - - #[cfg(any(debug_assertions, feature = "debug"))] - match input_event { - input::Event::Keyboard(keyboard::Event::Input { - state: input::ButtonState::Released, - key_code, - }) if Some(key_code) == G::DEBUG_KEY => { - debug.toggle(); - } - _ => {} - } - } - window::Event::CursorMoved(logical_position) => { - let position = logical_position.to_physical(window.dpi()); - - input.update(input::Event::Mouse(mouse::Event::CursorMoved { - x: position.x as f32, - y: position.y as f32, - })); - } - window::Event::Moved(logical_position) => { - let position = logical_position.to_physical(window.dpi()); - - input.update(input::Event::Window(input::window::Event::Moved { - x: position.x as f32, - y: position.y as f32, - })) - } - window::Event::CloseRequested => { - if game.on_close_request() { - *alive = false; - } - } - window::Event::Resized(new_size) => { - window.resize(new_size); - } - }; -} - -pub(crate) fn process_gamepad_events( - gamepads: Option<&mut gamepad::Tracker>, - input: &mut I, -) { - if let Some(tracker) = gamepads { - while let Some((id, event, time)) = tracker.next_event() { - input.update(input::Event::Gamepad { id, event, time }); - } + >::run(window_settings) } } diff --git a/src/game/loop.rs b/src/game/loop.rs new file mode 100644 index 0000000..85beaba --- /dev/null +++ b/src/game/loop.rs @@ -0,0 +1,247 @@ +use crate::debug::Debug; +use crate::graphics::window::winit; +use crate::graphics::{Window, WindowSettings}; +use crate::input::{self, gamepad, keyboard, mouse, window, Input}; +use crate::load::{Join, LoadingScreen, Task}; +use crate::{Result, Timer}; +use std::convert::TryInto; + +pub trait Loop { + type Attributes; + + fn new( + configuration: Self::Attributes, + game: &mut Game, + window: &Window, + ) -> Self; + + fn load(window: &Window) -> Task; + + fn on_input(&mut self, input: &mut Game::Input, event: input::Event) { + input.update(event); + } + + fn after_draw( + &mut self, + _game: &mut Game, + _input: &mut Game::Input, + _window: &mut Window, + _debug: &mut Debug, + ) { + } + + fn run(window_settings: WindowSettings) -> Result<()> + where + Self: 'static + Sized, + Game: 'static, + Game::Input: 'static, + { + // Window creation + let event_loop = winit::event_loop::EventLoop::new(); + let mut window = Window::new(window_settings, &event_loop)?; + let mut debug = Debug::new(window.gpu()); + + // Loading + debug.loading_started(); + let (mut game, configuration) = { + let mut loading_screen = Game::LoadingScreen::new(window.gpu())?; + + loading_screen.run( + (Game::load(&window), Self::load(&window)).join(), + &mut window, + )? + }; + + let mut game_loop = Self::new(configuration, &mut game, &mut window); + let mut input = Game::Input::new(); + let mut gamepads = gamepad::Tracker::new(); + debug.loading_finished(); + + let mut timer = Timer::new(Game::TICKS_PER_SECOND); + + // Initialization + debug.frame_started(); + timer.update(); + + event_loop.run(move |event, _, control_flow| match event { + winit::event::Event::NewEvents(_) => { + debug.interact_started(); + } + winit::event::Event::MainEventsCleared => { + if let Some(tracker) = &mut gamepads { + while let Some((id, event, time)) = tracker.next_event() { + game_loop.on_input( + &mut input, + input::Event::Gamepad { id, event, time }, + ); + } + } + + game.interact(&mut input, &mut window); + input.clear(); + debug.interact_finished(); + + if timer.tick() { + debug.update_started(); + game.update(&window); + debug.update_finished(); + } + + window.request_redraw(); + + if game.is_finished() { + *control_flow = winit::event_loop::ControlFlow::Exit; + } + } + winit::event::Event::RedrawRequested { .. } => { + debug.draw_started(); + game.draw(&mut window.frame(), &timer); + debug.draw_finished(); + + game_loop.after_draw( + &mut game, + &mut input, + &mut window, + &mut debug, + ); + + if debug.is_enabled() { + debug.debug_started(); + game.debug(&input, &mut window.frame(), &mut debug); + debug.debug_finished(); + } + + window.swap_buffers(); + debug.frame_finished(); + + debug.frame_started(); + window.request_redraw(); + timer.update(); + } + winit::event::Event::WindowEvent { event, .. } => match event { + winit::event::WindowEvent::CloseRequested => { + if game.on_close_request() { + *control_flow = winit::event_loop::ControlFlow::Exit; + } + } + winit::event::WindowEvent::Resized(logical_size) => { + window.resize(logical_size); + } + _ => { + match event { + winit::event::WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode, + state: winit::event::ElementState::Released, + .. + }, + .. + } if Game::DEBUG_KEY.is_some() => { + if virtual_keycode == Game::DEBUG_KEY { + debug.toggle(); + } + } + _ => {} + } + + if let Some(input_event) = try_into_input_event(event) { + game_loop.on_input(&mut input, input_event); + } + } + }, + _ => {} + }); + } +} + +fn try_into_input_event( + event: winit::event::WindowEvent<'_>, +) -> Option { + match event { + winit::event::WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + state, + virtual_keycode: Some(key_code), + .. + }, + .. + } => Some(input::Event::Keyboard(keyboard::Event::Input { + state, + key_code, + })), + winit::event::WindowEvent::ReceivedCharacter(codepoint) => { + Some(input::Event::Keyboard(keyboard::Event::TextEntered { + character: codepoint, + })) + } + winit::event::WindowEvent::MouseInput { state, button, .. } => { + Some(input::Event::Mouse(mouse::Event::Input { state, button })) + } + winit::event::WindowEvent::MouseWheel { delta, .. } => match delta { + winit::event::MouseScrollDelta::LineDelta(x, y) => { + Some(input::Event::Mouse(mouse::Event::WheelScrolled { + delta_x: x, + delta_y: y, + })) + } + _ => None, + }, + winit::event::WindowEvent::CursorMoved { position, .. } => { + Some(input::Event::Mouse(mouse::Event::CursorMoved { + x: position.x as f32, + y: position.y as f32, + })) + } + winit::event::WindowEvent::CursorEntered { .. } => { + Some(input::Event::Mouse(mouse::Event::CursorEntered)) + } + winit::event::WindowEvent::CursorLeft { .. } => { + Some(input::Event::Mouse(mouse::Event::CursorLeft)) + } + winit::event::WindowEvent::Focused(focus) => Some(if focus == true { + input::Event::Window(window::Event::Focused) + } else { + input::Event::Window(window::Event::Unfocused) + }), + winit::event::WindowEvent::Moved(position) => { + Some(input::Event::Window(window::Event::Moved { + x: position.x as f32, + y: position.y as f32, + })) + } + _ => None, + } +} + +pub struct Default {} + +impl Loop for Default +where + Game: 'static, +{ + type Attributes = (); + + fn new( + _attributes: Self::Attributes, + _game: &mut Game, + _window: &Window, + ) -> Self { + Self {} + } + + fn load(_window: &Window) -> Task { + Task::succeed(|| ()) + } + + fn after_draw( + &mut self, + game: &mut Game, + _input: &mut Game::Input, + window: &mut Window, + _debug: &mut Debug, + ) { + window.update_cursor(game.cursor_icon().try_into().ok()); + } +} diff --git a/src/graphics.rs b/src/graphics.rs index 349999b..f2dcb05 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -94,14 +94,14 @@ use backend_gfx as gpu; feature = "vulkan", feature = "metal", feature = "dx11", - feature = "dx12" + feature = "dx12", ))] mod backend_wgpu; #[cfg(any( feature = "vulkan", feature = "metal", feature = "dx11", - feature = "dx12" + feature = "dx12", ))] use backend_wgpu as gpu; @@ -141,4 +141,4 @@ pub use text::{HorizontalAlignment, Text, VerticalAlignment}; pub use texture_array::TextureArray; pub use transformation::Transformation; pub use vector::Vector; -pub use window::{Frame, Settings as WindowSettings, Window}; +pub use window::{CursorIcon, Frame, Settings as WindowSettings, Window}; diff --git a/src/graphics/backend_gfx/font.rs b/src/graphics/backend_gfx/font.rs index d7a5e00..8d53a04 100644 --- a/src/graphics/backend_gfx/font.rs +++ b/src/graphics/backend_gfx/font.rs @@ -2,7 +2,7 @@ use gfx_device_gl as gl; use gfx_glyph::GlyphCruncher; use crate::graphics::gpu::{TargetView, Transformation}; -use crate::graphics::{HorizontalAlignment, Text, VerticalAlignment}; +use crate::graphics::{HorizontalAlignment, Text, Vector, VerticalAlignment}; pub struct Font { glyphs: gfx_glyph::GlyphBrush<'static, gl::Resources, gl::Factory>, @@ -46,7 +46,10 @@ impl Font { self.glyphs .use_queue() - .transform(transformation) + .transform( + Transformation::nonuniform_scale(Vector::new(1.0, -1.0)) + * transformation, + ) .draw(encoder, &typed_target) .expect("Font draw"); } diff --git a/src/graphics/backend_gfx/mod.rs b/src/graphics/backend_gfx/mod.rs index c62ab60..98ef7ef 100644 --- a/src/graphics/backend_gfx/mod.rs +++ b/src/graphics/backend_gfx/mod.rs @@ -8,7 +8,7 @@ mod types; pub use font::Font; pub use quad::Quad; -pub use surface::{winit, Surface}; +pub use surface::Surface; pub use texture::Texture; pub use triangle::Vertex; pub use types::TargetView; @@ -40,8 +40,8 @@ pub struct Gpu { impl Gpu { pub(super) fn for_window( - builder: winit::WindowBuilder, - events_loop: &winit::EventsLoop, + builder: winit::window::WindowBuilder, + events_loop: &winit::event_loop::EventLoop<()>, ) -> Result<(Gpu, Surface)> { let (surface, device, mut factory) = Surface::new(builder, events_loop)?; diff --git a/src/graphics/backend_gfx/shader/quad.vert b/src/graphics/backend_gfx/shader/quad.vert index da2f62b..919d8e9 100644 --- a/src/graphics/backend_gfx/shader/quad.vert +++ b/src/graphics/backend_gfx/shader/quad.vert @@ -14,13 +14,6 @@ layout (std140) uniform Globals { out vec2 v_Uv; flat out uint v_Layer; -const mat4 INVERT_Y_AXIS = mat4( - vec4(1.0, 0.0, 0.0, 0.0), - vec4(0.0, -1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) -); - void main() { v_Uv = a_Pos * a_Src.zw + a_Src.xy; v_Layer = t_Layer; @@ -32,7 +25,7 @@ void main() { vec4(a_Translation, 0.0, 1.0) ); - vec4 position = INVERT_Y_AXIS * u_MVP * instance_transform * vec4(a_Pos, 0.0, 1.0); + vec4 position = u_MVP * instance_transform * vec4(a_Pos, 0.0, 1.0); gl_Position = position; } diff --git a/src/graphics/backend_gfx/shader/triangle.vert b/src/graphics/backend_gfx/shader/triangle.vert index 9e93563..ac12fa0 100644 --- a/src/graphics/backend_gfx/shader/triangle.vert +++ b/src/graphics/backend_gfx/shader/triangle.vert @@ -9,15 +9,8 @@ layout (std140) uniform Globals { out vec4 v_Color; -const mat4 INVERT_Y_AXIS = mat4( - vec4(1.0, 0.0, 0.0, 0.0), - vec4(0.0, -1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(0.0, 0.0, 0.0, 1.0) -); - void main() { v_Color = a_Color; - gl_Position = INVERT_Y_AXIS * u_MVP * vec4(a_Pos, 0.0, 1.0); + gl_Position = u_MVP * vec4(a_Pos, 0.0, 1.0); } diff --git a/src/graphics/backend_gfx/surface.rs b/src/graphics/backend_gfx/surface.rs index 53eaa89..c310425 100644 --- a/src/graphics/backend_gfx/surface.rs +++ b/src/graphics/backend_gfx/surface.rs @@ -1,18 +1,17 @@ use gfx_device_gl as gl; -pub use gfx_winit as winit; use super::{format, Gpu, TargetView}; use crate::{Error, Result}; pub struct Surface { - context: glutin::WindowedContext, + context: glutin::WindowedContext, target: TargetView, } impl Surface { pub(super) fn new( - builder: winit::WindowBuilder, - events_loop: &winit::EventsLoop, + builder: winit::window::WindowBuilder, + event_loop: &winit::event_loop::EventLoop<()>, ) -> Result<(Self, gl::Device, gl::Factory)> { let gl_builder = glutin::ContextBuilder::new() .with_gl(glutin::GlRequest::Latest) @@ -22,20 +21,19 @@ impl Surface { .with_pixel_format(24, 8) .with_vsync(true); - let (context, device, factory, target, _depth) = - gfx_window_glutin::init_raw( - builder, - gl_builder, - &events_loop, - format::COLOR, - format::DEPTH, - ) - .map_err(|error| Error::WindowCreation(error.to_string()))?; + let (context, device, factory, target, _depth) = init_raw( + builder, + gl_builder, + &event_loop, + format::COLOR, + format::DEPTH, + ) + .map_err(|error| Error::WindowCreation(error.to_string()))?; Ok((Self { context, target }, device, factory)) } - pub fn window(&self) -> &winit::Window { + pub fn window(&self) -> &winit::window::Window { self.context.window() } @@ -43,12 +41,16 @@ impl Surface { &self.target } - pub fn resize(&mut self, _gpu: &mut Gpu, size: winit::dpi::PhysicalSize) { + pub fn resize( + &mut self, + _gpu: &mut Gpu, + size: winit::dpi::PhysicalSize, + ) { self.context.resize(size); let dimensions = self.target.get_dimensions(); - if let Some((target, _depth)) = gfx_window_glutin::update_views_raw( + if let Some((target, _depth)) = update_views_raw( &self.context, dimensions, format::COLOR, @@ -58,9 +60,114 @@ impl Surface { } } + pub fn request_redraw(&mut self) { + self.context.window().request_redraw(); + } + pub fn swap_buffers(&mut self, gpu: &mut Gpu) { gpu.flush(); self.context.swap_buffers().expect("Buffer swap"); gpu.cleanup(); } } + +fn init_raw( + window: glutin::window::WindowBuilder, + context: glutin::ContextBuilder<'_, glutin::NotCurrent>, + events_loop: &glutin::event_loop::EventLoop<()>, + color_format: gfx::format::Format, + ds_format: gfx::format::Format, +) -> std::result::Result< + ( + glutin::WindowedContext, + gl::Device, + gl::Factory, + gfx::handle::RawRenderTargetView, + gfx::handle::RawDepthStencilView, + ), + glutin::CreationError, +> { + let window = { + let color_total_bits = color_format.0.get_total_bits(); + let alpha_bits = color_format.0.get_alpha_stencil_bits(); + let depth_total_bits = ds_format.0.get_total_bits(); + let stencil_bits = ds_format.0.get_alpha_stencil_bits(); + + context + .with_depth_buffer(depth_total_bits - stencil_bits) + .with_stencil_buffer(stencil_bits) + .with_pixel_format(color_total_bits - alpha_bits, alpha_bits) + .with_srgb(color_format.1 == gfx::format::ChannelType::Srgb) + .build_windowed(window, events_loop)? + }; + + let (window, device, factory, color_view, ds_view) = + init_existing_raw(window, color_format, ds_format); + + Ok((window, device, factory, color_view, ds_view)) +} + +fn init_existing_raw( + window: glutin::WindowedContext, + color_format: gfx::format::Format, + ds_format: gfx::format::Format, +) -> ( + glutin::WindowedContext, + gl::Device, + gl::Factory, + gfx::handle::RawRenderTargetView, + gfx::handle::RawDepthStencilView, +) { + #[allow(unsafe_code)] + let window = unsafe { window.make_current().unwrap() }; + + let (device, factory) = gl::create(|s| { + window.get_proc_address(s) as *const std::os::raw::c_void + }); + + // create the main color/depth targets + let dim = get_window_dimensions(&window); + let (color_view, ds_view) = + gl::create_main_targets_raw(dim, color_format.0, ds_format.0); + + // done + (window, device, factory, color_view, ds_view) +} + +pub fn update_views_raw( + window: &glutin::WindowedContext, + old_dimensions: gfx::texture::Dimensions, + color_format: gfx::format::Format, + ds_format: gfx::format::Format, +) -> Option<( + gfx::handle::RawRenderTargetView, + gfx::handle::RawDepthStencilView, +)> { + let dim = get_window_dimensions(window); + + if dim != old_dimensions { + Some(gl::create_main_targets_raw( + dim, + color_format.0, + ds_format.0, + )) + } else { + None + } +} + +fn get_window_dimensions( + ctx: &glutin::WindowedContext, +) -> gfx::texture::Dimensions { + let window = ctx.window(); + + let (width, height) = { + let size = window.inner_size(); + (size.width as _, size.height as _) + }; + + let aa = ctx.get_pixel_format().multisampling.unwrap_or(0) + as gfx::texture::NumSamples; + + (width, height, 1, aa.into()) +} diff --git a/src/graphics/backend_wgpu/font.rs b/src/graphics/backend_wgpu/font.rs index b8ca7a7..6b11949 100644 --- a/src/graphics/backend_wgpu/font.rs +++ b/src/graphics/backend_wgpu/font.rs @@ -13,6 +13,7 @@ impl Font { pub fn from_bytes(device: &mut wgpu::Device, bytes: &'static [u8]) -> Font { Font { glyphs: wgpu_glyph::GlyphBrushBuilder::using_font_bytes(bytes) + .expect("Load font") .texture_filter_method(wgpu::FilterMode::Nearest) .build(device, wgpu::TextureFormat::Bgra8UnormSrgb), } diff --git a/src/graphics/backend_wgpu/mod.rs b/src/graphics/backend_wgpu/mod.rs index e9600b9..56b3e86 100644 --- a/src/graphics/backend_wgpu/mod.rs +++ b/src/graphics/backend_wgpu/mod.rs @@ -7,7 +7,7 @@ mod types; pub use font::Font; pub use quad::Quad; -pub use surface::{winit, Surface}; +pub use surface::Surface; pub use texture::Texture; pub use triangle::Vertex; pub use types::TargetView; @@ -19,6 +19,7 @@ use crate::{Error, Result}; #[allow(missing_docs)] pub struct Gpu { device: wgpu::Device, + queue: wgpu::Queue, quad_pipeline: quad::Pipeline, triangle_pipeline: triangle::Pipeline, encoder: wgpu::CommandEncoder, @@ -26,38 +27,50 @@ pub struct Gpu { impl Gpu { pub(super) fn for_window( - builder: winit::WindowBuilder, - events_loop: &winit::EventsLoop, + builder: winit::window::WindowBuilder, + event_loop: &winit::event_loop::EventLoop<()>, ) -> Result<(Gpu, Surface)> { - let instance = wgpu::Instance::new(); + let window = builder + .build(event_loop) + .map_err(|error| Error::WindowCreation(error.to_string()))?; - let adapter = instance.get_adapter(&wgpu::AdapterDescriptor { - power_preference: wgpu::PowerPreference::HighPerformance, + let (mut device, queue) = futures::executor::block_on(async { + let adapter = wgpu::Adapter::request( + &wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: None, + }, + wgpu::BackendBit::all(), + ) + .await + .expect("Request adapter"); + + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + limits: wgpu::Limits::default(), + }) + .await; + + (device, queue) }); - let mut device = adapter.request_device(&wgpu::DeviceDescriptor { - extensions: wgpu::Extensions { - anisotropic_filtering: false, - }, - limits: wgpu::Limits::default(), - }); + let surface = Surface::new(window, &device); let quad_pipeline = quad::Pipeline::new(&mut device); let triangle_pipeline = triangle::Pipeline::new(&mut device); - let window = builder - .build(events_loop) - .map_err(|error| Error::WindowCreation(error.to_string()))?; - let surface = Surface::new(window, &instance, &device); - let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - todo: 0, + label: Some("coffee::backend encoder"), }); Ok(( Gpu { device, + queue, quad_pipeline, triangle_pipeline, encoder, @@ -67,7 +80,7 @@ impl Gpu { } pub(super) fn clear(&mut self, view: &TargetView, color: Color) { - let [r, g, b, a]: [f32; 4] = color.into_linear(); + let [r, g, b, a] = color.into_linear(); let _ = self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { @@ -75,7 +88,12 @@ impl Gpu { resolve_target: None, load_op: wgpu::LoadOp::Clear, store_op: wgpu::StoreOp::Store, - clear_color: wgpu::Color { r, g, b, a }, + clear_color: wgpu::Color { + r: r as f64, + g: g as f64, + b: b as f64, + a: a as f64, + }, }], depth_stencil_attachment: None, }); @@ -85,14 +103,19 @@ impl Gpu { &mut self, image: &image::DynamicImage, ) -> Texture { - Texture::new(&mut self.device, &self.quad_pipeline, image) + Texture::new(&mut self.device, &self.queue, &self.quad_pipeline, image) } pub(super) fn upload_texture_array( &mut self, layers: &[image::DynamicImage], ) -> Texture { - Texture::new_array(&mut self.device, &self.quad_pipeline, layers) + Texture::new_array( + &mut self.device, + &self.queue, + &self.quad_pipeline, + layers, + ) } pub(super) fn create_drawable_texture( @@ -102,6 +125,7 @@ impl Gpu { ) -> texture::Drawable { texture::Drawable::new( &mut self.device, + &self.queue, &self.quad_pipeline, width, height, @@ -113,12 +137,14 @@ impl Gpu { drawable: &texture::Drawable, ) -> image::DynamicImage { let new_encoder = self.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { todo: 0 }, + &wgpu::CommandEncoderDescriptor { + label: Some("coffee::backend encoder"), + }, ); let encoder = std::mem::replace(&mut self.encoder, new_encoder); - drawable.read_pixels(&mut self.device, encoder) + drawable.read_pixels(&mut self.device, &self.queue, encoder) } pub(super) fn upload_font(&mut self, bytes: &'static [u8]) -> Font { diff --git a/src/graphics/backend_wgpu/quad.rs b/src/graphics/backend_wgpu/quad.rs index cdb4262..ae9939b 100644 --- a/src/graphics/backend_wgpu/quad.rs +++ b/src/graphics/backend_wgpu/quad.rs @@ -1,6 +1,7 @@ use std::mem; use crate::graphics::{self, Transformation}; +use zerocopy::AsBytes; pub struct Pipeline { pipeline: wgpu::RenderPipeline, @@ -23,36 +24,36 @@ impl Pipeline { mipmap_filter: wgpu::FilterMode::Nearest, lod_min_clamp: -100.0, lod_max_clamp: 100.0, - compare_function: wgpu::CompareFunction::Always, + compare: wgpu::CompareFunction::Always, }); let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("coffee::backend::quad constants"), bindings: &[ - wgpu::BindGroupLayoutBinding { + wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::VERTEX, - ty: wgpu::BindingType::UniformBuffer, + ty: wgpu::BindingType::UniformBuffer { dynamic: false }, }, - wgpu::BindGroupLayoutBinding { + wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Sampler, + ty: wgpu::BindingType::Sampler { comparison: false }, }, ], }); let matrix: [f32; 16] = Transformation::identity().into(); - let transform_buffer = device - .create_buffer_mapped( - 16, - wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::TRANSFER_DST, - ) - .fill_from_slice(&matrix[..]); + let transform_buffer = device.create_buffer_with_data( + matrix.as_bytes(), + wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + ); let constant_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("coffee::backend::quad constants"), layout: &constant_layout, bindings: &[ wgpu::Binding { @@ -71,10 +72,15 @@ impl Pipeline { let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - bindings: &[wgpu::BindGroupLayoutBinding { + label: Some("coffee::backend::quad texture"), + bindings: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::SampledTexture, + ty: wgpu::BindingType::SampledTexture { + multisampled: false, + dimension: wgpu::TextureViewDimension::D2Array, + component_type: wgpu::TextureComponentType::Float, + }, }], }); @@ -83,29 +89,36 @@ impl Pipeline { bind_group_layouts: &[&constant_layout, &texture_layout], }); - let vs_module = - device.create_shader_module(include_bytes!("shader/quad.vert.spv")); - let fs_module = - device.create_shader_module(include_bytes!("shader/quad.frag.spv")); + let vs = include_bytes!("shader/quad.vert.spv"); + let vs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&vs[..])) + .expect("Read quad vertex shader as SPIR-V"), + ); + + let fs = include_bytes!("shader/quad.frag.spv"); + let fs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&fs[..])) + .expect("Read quad fragment shader as SPIR-V"), + ); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { layout: &layout, - vertex_stage: wgpu::PipelineStageDescriptor { + vertex_stage: wgpu::ProgrammableStageDescriptor { module: &vs_module, entry_point: "main", }, - fragment_stage: Some(wgpu::PipelineStageDescriptor { + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { module: &fs_module, entry_point: "main", }), - rasterization_state: wgpu::RasterizationStateDescriptor { + rasterization_state: Some(wgpu::RasterizationStateDescriptor { front_face: wgpu::FrontFace::Cw, cull_mode: wgpu::CullMode::None, depth_bias: 0, depth_bias_slope_scale: 0.0, depth_bias_clamp: 0.0, - }, + }), primitive_topology: wgpu::PrimitiveTopology::TriangleList, color_states: &[wgpu::ColorStateDescriptor { format: wgpu::TextureFormat::Bgra8UnormSrgb, @@ -122,58 +135,65 @@ impl Pipeline { write_mask: wgpu::ColorWrite::ALL, }], depth_stencil_state: None, - index_format: wgpu::IndexFormat::Uint16, - vertex_buffers: &[ - wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u64, - step_mode: wgpu::InputStepMode::Vertex, - attributes: &[wgpu::VertexAttributeDescriptor { - shader_location: 0, - format: wgpu::VertexFormat::Float2, - offset: 0, - }], - }, - wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u64, - step_mode: wgpu::InputStepMode::Instance, - attributes: &[ - wgpu::VertexAttributeDescriptor { - shader_location: 1, - format: wgpu::VertexFormat::Float4, - offset: 0, - }, - wgpu::VertexAttributeDescriptor { - shader_location: 2, - format: wgpu::VertexFormat::Float2, - offset: 4 * 4, - }, - wgpu::VertexAttributeDescriptor { - shader_location: 3, + vertex_state: wgpu::VertexStateDescriptor { + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[ + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as u64, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[wgpu::VertexAttributeDescriptor { + shader_location: 0, format: wgpu::VertexFormat::Float2, - offset: 4 * (4 + 2), - }, - wgpu::VertexAttributeDescriptor { - shader_location: 4, - format: wgpu::VertexFormat::Uint, - offset: 4 * (4 + 2 + 2), - }, - ], - }, - ], + offset: 0, + }], + }, + wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as u64, + step_mode: wgpu::InputStepMode::Instance, + attributes: &[ + wgpu::VertexAttributeDescriptor { + shader_location: 1, + format: wgpu::VertexFormat::Float4, + offset: 0, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 2, + format: wgpu::VertexFormat::Float2, + offset: 4 * 4, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 3, + format: wgpu::VertexFormat::Float2, + offset: 4 * (4 + 2), + }, + wgpu::VertexAttributeDescriptor { + shader_location: 4, + format: wgpu::VertexFormat::Uint, + offset: 4 * (4 + 2 + 2), + }, + ], + }, + ], + }, sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, }); - let vertices = device - .create_buffer_mapped(QUAD_VERTS.len(), wgpu::BufferUsage::VERTEX) - .fill_from_slice(&QUAD_VERTS); + let vertices = device.create_buffer_with_data( + QUAD_VERTS.as_bytes(), + wgpu::BufferUsage::VERTEX, + ); - let indices = device - .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX) - .fill_from_slice(&QUAD_INDICES); + let indices = device.create_buffer_with_data( + QUAD_INDICES.as_bytes(), + wgpu::BufferUsage::INDEX, + ); let instances = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("coffee::backend::quad instances"), size: mem::size_of::() as u64 * Quad::MAX as u64, - usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::TRANSFER_DST, + usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, }); Pipeline { @@ -193,6 +213,7 @@ impl Pipeline { view: &wgpu::TextureView, ) -> TextureBinding { let binding = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("coffee::backend::quad texture"), layout: &self.texture_layout, bindings: &[wgpu::Binding { binding: 0, @@ -214,9 +235,10 @@ impl Pipeline { ) { let matrix: [f32; 16] = transformation.clone().into(); - let transform_buffer = device - .create_buffer_mapped(16, wgpu::BufferUsage::TRANSFER_SRC) - .fill_from_slice(&matrix[..]); + let transform_buffer = device.create_buffer_with_data( + matrix.as_bytes(), + wgpu::BufferUsage::COPY_SRC, + ); encoder.copy_buffer_to_buffer( &transform_buffer, @@ -233,9 +255,10 @@ impl Pipeline { let end = (i + Quad::MAX).min(total); let amount = end - i; - let instance_buffer = device - .create_buffer_mapped(amount, wgpu::BufferUsage::TRANSFER_SRC) - .fill_from_slice(&instances[i..end]); + let instance_buffer = device.create_buffer_with_data( + instances[i..end].as_bytes(), + wgpu::BufferUsage::COPY_SRC, + ); encoder.copy_buffer_to_buffer( &instance_buffer, @@ -244,6 +267,7 @@ impl Pipeline { 0, (mem::size_of::() * amount) as u64, ); + { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { @@ -267,11 +291,9 @@ impl Pipeline { render_pass.set_pipeline(&self.pipeline); render_pass.set_bind_group(0, &self.constants, &[]); render_pass.set_bind_group(1, &texture.0, &[]); - render_pass.set_index_buffer(&self.indices, 0); - render_pass.set_vertex_buffers(&[ - (&self.vertices, 0), - (&self.instances, 0), - ]); + render_pass.set_index_buffer(&self.indices, 0, 0); + render_pass.set_vertex_buffer(0, &self.vertices, 0, 0); + render_pass.set_vertex_buffer(1, &self.instances, 0, 0); render_pass.draw_indexed( 0..QUAD_INDICES.len() as u32, @@ -285,7 +307,8 @@ impl Pipeline { } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, AsBytes)] +#[repr(C)] pub struct Vertex { _position: [f32; 2], } @@ -307,7 +330,8 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, AsBytes)] +#[repr(C)] pub struct Quad { source: [f32; 4], scale: [f32; 2], diff --git a/src/graphics/backend_wgpu/surface.rs b/src/graphics/backend_wgpu/surface.rs index 4aed71c..d823781 100644 --- a/src/graphics/backend_wgpu/surface.rs +++ b/src/graphics/backend_wgpu/surface.rs @@ -1,140 +1,105 @@ -use std::rc::Rc; - use super::{Gpu, TargetView}; -pub use wgpu::winit; pub struct Surface { - window: winit::Window, + window: winit::window::Window, surface: wgpu::Surface, swap_chain: wgpu::SwapChain, extent: wgpu::Extent3d, - buffer: wgpu::Texture, - target: TargetView, + output: Option, } impl Surface { pub fn new( - window: winit::Window, - instance: &wgpu::Instance, + window: winit::window::Window, device: &wgpu::Device, ) -> Surface { - let surface = instance.create_surface(&window); - - let size = window - .get_inner_size() - // TODO: Find out when and why the "inner size" might not be available - // and do something smarter here. - .unwrap_or(winit::dpi::LogicalSize { - width: 1280.0, - height: 1024.0, - }) - .to_physical(window.get_hidpi_factor()); + let surface = wgpu::Surface::create(&window); + let size = window.inner_size(); - let (swap_chain, extent, buffer, target) = - new_swap_chain(device, &surface, size); + let (swap_chain, extent) = new_swap_chain(device, &surface, size); Surface { window, surface, swap_chain, extent, - buffer, - target, + output: None, } } - pub fn window(&self) -> &winit::Window { + pub fn window(&self) -> &winit::window::Window { &self.window } - pub fn target(&self) -> &TargetView { - &self.target + pub fn target(&mut self) -> &TargetView { + if self.output.is_none() { + let output = self + .swap_chain + .get_next_texture() + .expect("Get next texture"); + + self.output = Some(output); + } + + &self.output.as_ref().unwrap().view } - pub fn resize(&mut self, gpu: &mut Gpu, size: winit::dpi::PhysicalSize) { - let (swap_chain, extent, buffer, target) = + pub fn resize( + &mut self, + gpu: &mut Gpu, + size: winit::dpi::PhysicalSize, + ) { + let (swap_chain, extent) = new_swap_chain(&gpu.device, &self.surface, size); self.swap_chain = swap_chain; self.extent = extent; - self.buffer = buffer; - self.target = target; + self.output = None; } pub fn swap_buffers(&mut self, gpu: &mut Gpu) { - let output = self.swap_chain.get_next_texture(); - let new_encoder = gpu.device.create_command_encoder( - &wgpu::CommandEncoderDescriptor { todo: 0 }, + &wgpu::CommandEncoderDescriptor { + label: Some("coffee::backend::surface blit"), + }, ); // We swap the current decoder by a new one here, so we can finish the // current frame - let mut encoder = std::mem::replace(&mut gpu.encoder, new_encoder); - - encoder.copy_texture_to_texture( - wgpu::TextureCopyView { - texture: &self.buffer, - array_layer: 0, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }, - wgpu::TextureCopyView { - texture: &output.texture, - array_layer: 0, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }, - self.extent, - ); + let encoder = std::mem::replace(&mut gpu.encoder, new_encoder); - gpu.device.get_queue().submit(&[encoder.finish()]); + gpu.queue.submit(&[encoder.finish()]); + + self.output = None; + } + + pub fn request_redraw(&mut self) { + self.window.request_redraw(); } } fn new_swap_chain( device: &wgpu::Device, surface: &wgpu::Surface, - size: winit::dpi::PhysicalSize, -) -> (wgpu::SwapChain, wgpu::Extent3d, wgpu::Texture, TargetView) { + size: winit::dpi::PhysicalSize, +) -> (wgpu::SwapChain, wgpu::Extent3d) { let swap_chain = device.create_swap_chain( surface, &wgpu::SwapChainDescriptor { usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT - | wgpu::TextureUsage::TRANSFER_DST, - format: wgpu::TextureFormat::Bgra8Unorm, - width: size.width.round() as u32, - height: size.height.round() as u32, - present_mode: wgpu::PresentMode::Vsync, + | wgpu::TextureUsage::COPY_DST, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, }, ); let extent = wgpu::Extent3d { - width: size.width.round() as u32, - height: size.height.round() as u32, + width: size.width, + height: size.height, depth: 1, }; - let buffer = device.create_texture(&wgpu::TextureDescriptor { - size: extent, - dimension: wgpu::TextureDimension::D2, - array_layer_count: 1, - mip_level_count: 1, - sample_count: 1, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT - | wgpu::TextureUsage::TRANSFER_SRC, - }); - - let target = Rc::new(buffer.create_default_view()); - - (swap_chain, extent, buffer, target) + (swap_chain, extent) } diff --git a/src/graphics/backend_wgpu/texture.rs b/src/graphics/backend_wgpu/texture.rs index 58aa3d6..d8bff02 100644 --- a/src/graphics/backend_wgpu/texture.rs +++ b/src/graphics/backend_wgpu/texture.rs @@ -8,7 +8,7 @@ use crate::graphics::Transformation; #[derive(Clone)] pub struct Texture { raw: Rc, - view: TargetView, + view: Rc, binding: Rc, width: u16, height: u16, @@ -28,6 +28,7 @@ impl fmt::Debug for Texture { impl Texture { pub(super) fn new( device: &mut wgpu::Device, + queue: &wgpu::Queue, pipeline: &Pipeline, image: &image::DynamicImage, ) -> Texture { @@ -37,11 +38,12 @@ impl Texture { let (texture, view, binding) = create_texture_array( device, + queue, pipeline, - width, - height, + u32::from(width), + u32::from(height), Some(&[&bgra.into_raw()[..]]), - wgpu::TextureUsage::TRANSFER_DST | wgpu::TextureUsage::SAMPLED, + wgpu::TextureUsage::COPY_DST | wgpu::TextureUsage::SAMPLED, ); Texture { @@ -56,6 +58,7 @@ impl Texture { pub(super) fn new_array( device: &mut wgpu::Device, + queue: &wgpu::Queue, pipeline: &Pipeline, layers: &[image::DynamicImage], ) -> Texture { @@ -70,11 +73,12 @@ impl Texture { let (texture, view, binding) = create_texture_array( device, + queue, pipeline, - width, - height, + u32::from(width), + u32::from(height), Some(&raw_layers[..]), - wgpu::TextureUsage::TRANSFER_DST | wgpu::TextureUsage::SAMPLED, + wgpu::TextureUsage::COPY_DST | wgpu::TextureUsage::SAMPLED, ); Texture { @@ -112,19 +116,21 @@ pub struct Drawable { impl Drawable { pub fn new( device: &mut wgpu::Device, + queue: &wgpu::Queue, pipeline: &Pipeline, width: u16, height: u16, ) -> Drawable { let (texture, view, binding) = create_texture_array( device, + queue, pipeline, - width, - height, + u32::from(width), + u32::from(height), None, wgpu::TextureUsage::OUTPUT_ATTACHMENT | wgpu::TextureUsage::SAMPLED - | wgpu::TextureUsage::TRANSFER_SRC, + | wgpu::TextureUsage::COPY_SRC, ); let texture = Texture { @@ -150,6 +156,7 @@ impl Drawable { pub fn read_pixels( &self, device: &mut wgpu::Device, + queue: &wgpu::Queue, mut encoder: wgpu::CommandEncoder, ) -> image::DynamicImage { let texture = self.texture(); @@ -157,9 +164,10 @@ impl Drawable { let buffer_size = 4 * texture.width() as u64 * texture.height() as u64; let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("coffee::backend::texture pixels"), size: buffer_size, - usage: wgpu::BufferUsage::TRANSFER_DST - | wgpu::BufferUsage::TRANSFER_SRC + usage: wgpu::BufferUsage::COPY_DST + | wgpu::BufferUsage::COPY_SRC | wgpu::BufferUsage::MAP_READ, }); @@ -168,47 +176,31 @@ impl Drawable { texture: &texture.raw, mip_level: 0, array_layer: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, + origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, }, wgpu::BufferCopyView { buffer: &buffer, offset: 0, - row_pitch: 4 * texture.width() as u32, - image_height: texture.height() as u32, + bytes_per_row: 4 * u32::from(texture.width()), + rows_per_image: u32::from(texture.height()), }, wgpu::Extent3d { - width: texture.width() as u32, - height: texture.height() as u32, + width: u32::from(texture.width()), + height: u32::from(texture.height()), depth: 1, }, ); - device.get_queue().submit(&[encoder.finish()]); + queue.submit(&[encoder.finish()]); - use std::cell::RefCell; + use futures::executor::block_on; - let pixels: Rc>>> = Rc::new(RefCell::new(None)); - let write = pixels.clone(); + let result = block_on(buffer.map_read(0, buffer_size)); - buffer.map_read_async(0, buffer_size, move |result| { - match result { - Ok(mapping) => { - *write.borrow_mut() = Some(mapping.data.to_vec()); - } - Err(_) => { - *write.borrow_mut() = Some(vec![]); - } - }; - }); - - device.poll(true); - - let data = pixels.borrow(); - let bgra = data.clone().unwrap(); + let bgra = match result { + Ok(mapping) => mapping.as_slice().to_vec(), + Err(_) => vec![], + }; image::DynamicImage::ImageBgra8( image::ImageBuffer::from_raw( @@ -228,21 +220,23 @@ impl Drawable { // Helpers fn create_texture_array( device: &mut wgpu::Device, + queue: &wgpu::Queue, pipeline: &Pipeline, - width: u16, - height: u16, + width: u32, + height: u32, layers: Option<&[&[u8]]>, usage: wgpu::TextureUsage, ) -> (wgpu::Texture, wgpu::TextureView, quad::TextureBinding) { let extent = wgpu::Extent3d { - width: width as u32, - height: height as u32, + width: width, + height: height, depth: 1, }; let layer_count = layers.map(|l| l.len()).unwrap_or(1) as u32; let texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("coffee::backend::texture array"), size: extent, array_layer_count: layer_count, mip_level_count: 1, @@ -259,45 +253,40 @@ fn create_texture_array( layers.iter().cloned().flatten().cloned().collect(); let temp_buf = device - .create_buffer_mapped(slice.len(), wgpu::BufferUsage::TRANSFER_SRC) - .fill_from_slice(&slice[..]); + .create_buffer_with_data(&slice[..], wgpu::BufferUsage::COPY_SRC); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - todo: 0, + label: Some("coffee::backend::texture upload"), }); encoder.copy_buffer_to_texture( wgpu::BufferCopyView { buffer: &temp_buf, offset: 0, - row_pitch: 4 * width as u32, - image_height: height as u32, + bytes_per_row: 4 * width, + rows_per_image: height, }, wgpu::TextureCopyView { texture: &texture, array_layer: 0, mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, + origin: wgpu::Origin3d { x: 0, y: 0, z: 0 }, }, extent, ); - device.get_queue().submit(&[encoder.finish()]); + queue.submit(&[encoder.finish()]); } let view = texture.create_view(&wgpu::TextureViewDescriptor { format: wgpu::TextureFormat::Bgra8UnormSrgb, dimension: wgpu::TextureViewDimension::D2Array, - aspect: wgpu::TextureAspectFlags::COLOR, + aspect: wgpu::TextureAspect::All, base_mip_level: 0, level_count: 1, base_array_layer: 0, - array_count: layer_count, + array_layer_count: layer_count, }); let binding = pipeline.create_texture_binding(device, &view); diff --git a/src/graphics/backend_wgpu/triangle.rs b/src/graphics/backend_wgpu/triangle.rs index db9532e..e697613 100644 --- a/src/graphics/backend_wgpu/triangle.rs +++ b/src/graphics/backend_wgpu/triangle.rs @@ -1,6 +1,7 @@ use std::mem; use crate::graphics::Transformation; +use zerocopy::AsBytes; pub struct Pipeline { pipeline: wgpu::RenderPipeline, @@ -17,24 +18,24 @@ impl Pipeline { pub fn new(device: &mut wgpu::Device) -> Pipeline { let transform_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - bindings: &[wgpu::BindGroupLayoutBinding { + label: Some("coffee::backend::triangle transform"), + bindings: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::VERTEX, - ty: wgpu::BindingType::UniformBuffer, + ty: wgpu::BindingType::UniformBuffer { dynamic: false }, }], }); let matrix: [f32; 16] = Transformation::identity().into(); - let transform_buffer = device - .create_buffer_mapped( - 16, - wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::TRANSFER_DST, - ) - .fill_from_slice(&matrix[..]); + let transform_buffer = device.create_buffer_with_data( + matrix.as_bytes(), + wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + ); let constant_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("coffee::backend::triangle constants"), layout: &transform_layout, bindings: &[wgpu::Binding { binding: 0, @@ -50,29 +51,36 @@ impl Pipeline { bind_group_layouts: &[&transform_layout], }); - let vs_module = device - .create_shader_module(include_bytes!("shader/triangle.vert.spv")); - let fs_module = device - .create_shader_module(include_bytes!("shader/triangle.frag.spv")); + let vs = include_bytes!("shader/triangle.vert.spv"); + let vs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&vs[..])) + .expect("Read triangle vertex shader as SPIR-V"), + ); + + let fs = include_bytes!("shader/triangle.frag.spv"); + let fs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&fs[..])) + .expect("Read triangle fragment shader as SPIR-V"), + ); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { layout: &layout, - vertex_stage: wgpu::PipelineStageDescriptor { + vertex_stage: wgpu::ProgrammableStageDescriptor { module: &vs_module, entry_point: "main", }, - fragment_stage: Some(wgpu::PipelineStageDescriptor { + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { module: &fs_module, entry_point: "main", }), - rasterization_state: wgpu::RasterizationStateDescriptor { + rasterization_state: Some(wgpu::RasterizationStateDescriptor { front_face: wgpu::FrontFace::Ccw, cull_mode: wgpu::CullMode::None, depth_bias: 0, depth_bias_slope_scale: 0.0, depth_bias_clamp: 0.0, - }, + }), primitive_topology: wgpu::PrimitiveTopology::TriangleList, color_states: &[wgpu::ColorStateDescriptor { format: wgpu::TextureFormat::Bgra8UnormSrgb, @@ -89,36 +97,42 @@ impl Pipeline { write_mask: wgpu::ColorWrite::ALL, }], depth_stencil_state: None, - index_format: wgpu::IndexFormat::Uint32, - vertex_buffers: &[wgpu::VertexBufferDescriptor { - stride: mem::size_of::() as u64, - step_mode: wgpu::InputStepMode::Vertex, - attributes: &[ - wgpu::VertexAttributeDescriptor { - shader_location: 0, - format: wgpu::VertexFormat::Float2, - offset: 0, - }, - wgpu::VertexAttributeDescriptor { - shader_location: 1, - format: wgpu::VertexFormat::Float4, - offset: 4 * 2, - }, - ], - }], + vertex_state: wgpu::VertexStateDescriptor { + index_format: wgpu::IndexFormat::Uint32, + vertex_buffers: &[wgpu::VertexBufferDescriptor { + stride: mem::size_of::() as u64, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[ + wgpu::VertexAttributeDescriptor { + shader_location: 0, + format: wgpu::VertexFormat::Float2, + offset: 0, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 1, + format: wgpu::VertexFormat::Float4, + offset: 4 * 2, + }, + ], + }], + }, sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, }); let vertices = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("coffee::backend::triangle vertices"), size: mem::size_of::() as u64 * Self::INITIAL_BUFFER_SIZE as u64, - usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::TRANSFER_DST, + usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, }); let indices = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("coffee::backend::triangle indices"), size: mem::size_of::() as u64 * Self::INITIAL_BUFFER_SIZE as u64, - usage: wgpu::BufferUsage::INDEX | wgpu::BufferUsage::TRANSFER_DST, + usage: wgpu::BufferUsage::INDEX | wgpu::BufferUsage::COPY_DST, }); Pipeline { @@ -146,9 +160,10 @@ impl Pipeline { let matrix: [f32; 16] = transformation.clone().into(); - let transform_buffer = device - .create_buffer_mapped(16, wgpu::BufferUsage::TRANSFER_SRC) - .fill_from_slice(&matrix[..]); + let transform_buffer = device.create_buffer_with_data( + matrix.as_bytes(), + wgpu::BufferUsage::COPY_SRC, + ); encoder.copy_buffer_to_buffer( &transform_buffer, @@ -164,33 +179,29 @@ impl Pipeline { let new_size = vertices.len().max(indices.len()) as u32; self.vertices = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("coffee::backend::triangle vertices"), size: mem::size_of::() as u64 * new_size as u64, - usage: wgpu::BufferUsage::VERTEX - | wgpu::BufferUsage::TRANSFER_DST, + usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, }); self.indices = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("coffee::backend::triangle indices"), size: mem::size_of::() as u64 * new_size as u64, - usage: wgpu::BufferUsage::INDEX - | wgpu::BufferUsage::TRANSFER_DST, + usage: wgpu::BufferUsage::INDEX | wgpu::BufferUsage::COPY_DST, }); self.buffer_size = new_size; } - let vertex_buffer = device - .create_buffer_mapped( - vertices.len(), - wgpu::BufferUsage::TRANSFER_SRC, - ) - .fill_from_slice(vertices); + let vertex_buffer = device.create_buffer_with_data( + vertices.as_bytes(), + wgpu::BufferUsage::COPY_SRC, + ); - let index_buffer = device - .create_buffer_mapped( - indices.len(), - wgpu::BufferUsage::TRANSFER_SRC, - ) - .fill_from_slice(indices); + let index_buffer = device.create_buffer_with_data( + indices.as_bytes(), + wgpu::BufferUsage::COPY_SRC, + ); encoder.copy_buffer_to_buffer( &vertex_buffer, @@ -230,15 +241,16 @@ impl Pipeline { render_pass.set_pipeline(&self.pipeline); render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_index_buffer(&self.indices, 0); - render_pass.set_vertex_buffers(&[(&self.vertices, 0)]); + render_pass.set_index_buffer(&self.indices, 0, 0); + render_pass.set_vertex_buffer(0, &self.vertices, 0, 0); render_pass.draw_indexed(0..indices.len() as u32, 0, 0..1); } } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, AsBytes)] +#[repr(C)] pub struct Vertex { _position: [f32; 2], _color: [f32; 4], diff --git a/src/graphics/backend_wgpu/types.rs b/src/graphics/backend_wgpu/types.rs index d5717fa..55c89e9 100644 --- a/src/graphics/backend_wgpu/types.rs +++ b/src/graphics/backend_wgpu/types.rs @@ -1,3 +1 @@ -use std::rc::Rc; - -pub type TargetView = Rc; +pub type TargetView = wgpu::TextureView; diff --git a/src/graphics/canvas.rs b/src/graphics/canvas.rs index f715411..14793d4 100644 --- a/src/graphics/canvas.rs +++ b/src/graphics/canvas.rs @@ -49,14 +49,14 @@ impl Canvas { /// /// [`Canvas`]: struct.Canvas.html /// [`Target`]: struct.Target.html - pub fn as_target<'a>(&mut self, gpu: &'a mut Gpu) -> Target<'a> { + pub fn as_target<'a>(&'a mut self, gpu: &'a mut Gpu) -> Target<'a> { let texture = self.drawable.texture(); Target::with_transformation( gpu, - self.drawable.target().clone(), - texture.width() as f32, - texture.height() as f32, + self.drawable.target(), + f32::from(texture.width()), + f32::from(texture.height()), texture::Drawable::render_transformation(), ) } diff --git a/src/graphics/target.rs b/src/graphics/target.rs index ee146a4..f8b349f 100644 --- a/src/graphics/target.rs +++ b/src/graphics/target.rs @@ -15,17 +15,17 @@ use crate::graphics::{Color, Transformation}; /// [`Canvas`]: struct.Canvas.html pub struct Target<'a> { gpu: &'a mut Gpu, - view: TargetView, + view: &'a TargetView, transformation: Transformation, } impl<'a> Target<'a> { pub(super) fn new( - gpu: &mut Gpu, - view: TargetView, + gpu: &'a mut Gpu, + view: &'a TargetView, width: f32, height: f32, - ) -> Target<'_> { + ) -> Self { Target { gpu, view, @@ -34,12 +34,12 @@ impl<'a> Target<'a> { } pub(super) fn with_transformation( - gpu: &mut Gpu, - view: TargetView, + gpu: &'a mut Gpu, + view: &'a TargetView, width: f32, height: f32, transformation: Transformation, - ) -> Target<'_> { + ) -> Self { let mut target = Self::new(gpu, view, width, height); target.transformation = transformation * target.transformation; target @@ -82,7 +82,7 @@ impl<'a> Target<'a> { pub fn transform(&mut self, transformation: Transformation) -> Target<'_> { Target { gpu: self.gpu, - view: self.view.clone(), + view: self.view, transformation: self.transformation * transformation, } } diff --git a/src/graphics/transformation.rs b/src/graphics/transformation.rs index 617d4e4..84f4112 100644 --- a/src/graphics/transformation.rs +++ b/src/graphics/transformation.rs @@ -27,7 +27,7 @@ impl Transformation { pub fn orthographic(width: f32, height: f32) -> Transformation { Transformation(nalgebra::Matrix3::new( 2.0 / width, 0.0, -1.0, - 0.0, 2.0 / height, -1.0, + 0.0, -2.0 / height, 1.0, 0.0, 0.0, 1.0 )) } diff --git a/src/graphics/window.rs b/src/graphics/window.rs index 7e5db8e..995b74c 100644 --- a/src/graphics/window.rs +++ b/src/graphics/window.rs @@ -1,10 +1,10 @@ -mod event; +mod cursor_icon; mod frame; mod settings; -pub(crate) use crate::graphics::gpu::winit; -pub(crate) use event::{Event, EventLoop}; +pub(crate) use winit; +pub use cursor_icon::CursorIcon; pub use frame::Frame; pub use settings::Settings; @@ -22,49 +22,27 @@ pub struct Window { width: f32, height: f32, is_fullscreen: bool, + cursor_icon: Option, } impl Window { pub(crate) fn new( - mut settings: Settings, - event_loop: &EventLoop, + settings: Settings, + event_loop: &winit::event_loop::EventLoop<()>, ) -> Result { - let (mut width, mut height) = settings.size; - - // Try to revert DPI - let dpi = event_loop.raw().get_primary_monitor().get_hidpi_factor(); - - width = (width as f64 / dpi).round() as u32; - height = (height as f64 / dpi).round() as u32; - - settings.size = (width, height); - + let (width, height) = settings.size; let is_fullscreen = settings.fullscreen; - let (gpu, surface) = Gpu::for_window( - settings.into_builder(event_loop.raw()), - event_loop.raw(), - )?; - - let window = surface.window(); - - let (width, height) = window - .get_inner_size() - .map(|inner_size| { - let dpi = window.get_hidpi_factor(); - ( - (inner_size.width * dpi) as f32, - (inner_size.height * dpi) as f32, - ) - }) - .unwrap_or((width as f32, height as f32)); + let (gpu, surface) = + Gpu::for_window(settings.into_builder(event_loop), event_loop)?; Ok(Window { is_fullscreen, gpu, surface, - width, - height, + width: width as f32, + height: height as f32, + cursor_icon: Some(winit::window::CursorIcon::Default), }) } @@ -89,10 +67,11 @@ impl Window { let monitor = if self.is_fullscreen { None } else { - Some(window.get_primary_monitor()) + Some(window.primary_monitor()) }; - window.set_fullscreen(monitor); + window + .set_fullscreen(monitor.map(winit::window::Fullscreen::Borderless)); self.is_fullscreen = !self.is_fullscreen; } @@ -111,26 +90,34 @@ impl Window { self.height } - pub(crate) fn dpi(&self) -> f64 { - self.surface.window().get_hidpi_factor() - } - pub(crate) fn swap_buffers(&mut self) { self.surface.swap_buffers(&mut self.gpu); } - pub(crate) fn resize(&mut self, new_size: winit::dpi::LogicalSize) { - let dpi = self.surface.window().get_hidpi_factor(); - let physical_size = new_size.to_physical(dpi); + pub(crate) fn request_redraw(&mut self) { + self.surface.request_redraw(); + } - self.surface.resize(&mut self.gpu, physical_size); + pub(crate) fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + self.surface.resize(&mut self.gpu, new_size); - self.width = physical_size.width as f32; - self.height = physical_size.height as f32; + self.width = new_size.width as f32; + self.height = new_size.height as f32; } - pub(crate) fn update_cursor(&mut self, new_cursor: winit::MouseCursor) { - self.surface.window().set_cursor(new_cursor); + pub(crate) fn update_cursor( + &mut self, + new_cursor: Option, + ) { + if self.cursor_icon != new_cursor { + if let Some(cursor_icon) = new_cursor { + self.surface.window().set_cursor_icon(cursor_icon); + } + self.surface + .window() + .set_cursor_visible(new_cursor.is_some()); + self.cursor_icon = new_cursor; + } } } diff --git a/src/graphics/window/cursor_icon.rs b/src/graphics/window/cursor_icon.rs new file mode 100644 index 0000000..786d84c --- /dev/null +++ b/src/graphics/window/cursor_icon.rs @@ -0,0 +1,39 @@ +use crate::graphics::window::winit; +use std::convert::TryFrom; + +/// Describes the appearance of the mouse cursor. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum CursorIcon { + /// The platform-dependent default cursor. + Default, + /// A simple crosshair. + Crosshair, + /// A hand (often used to indicate links in web browsers). + Hand, + /// Hides the cursor. + Hidden, + /// Indicates something is to be moved. + Move, +} + +impl Default for CursorIcon { + fn default() -> Self { + Self::Default + } +} + +impl TryFrom for winit::window::CursorIcon { + type Error = (); + + fn try_from( + cursor_icon: CursorIcon, + ) -> Result { + match cursor_icon { + CursorIcon::Default => Ok(winit::window::CursorIcon::Default), + CursorIcon::Crosshair => Ok(winit::window::CursorIcon::Crosshair), + CursorIcon::Hand => Ok(winit::window::CursorIcon::Hand), + CursorIcon::Hidden => Err(()), + CursorIcon::Move => Ok(winit::window::CursorIcon::Move), + } + } +} diff --git a/src/graphics/window/frame.rs b/src/graphics/window/frame.rs index 54e549c..f2bc719 100644 --- a/src/graphics/window/frame.rs +++ b/src/graphics/window/frame.rs @@ -48,11 +48,17 @@ impl<'a> Frame<'a> { /// /// [`Target`]: struct.Target.html pub fn as_target(&mut self) -> Target<'_> { - let view = self.window.surface.target().clone(); - let width = self.window.width; - let height = self.window.height; + let Window { + surface, + gpu, + width, + height, + .. + } = &mut self.window; - Target::new(self.window.gpu(), view, width, height) + let view = surface.target(); + + Target::new(gpu, view, *width, *height) } /// Clear the frame with the given [`Color`]. diff --git a/src/graphics/window/settings.rs b/src/graphics/window/settings.rs index 83f92ff..4aebb1b 100644 --- a/src/graphics/window/settings.rs +++ b/src/graphics/window/settings.rs @@ -22,22 +22,22 @@ pub struct Settings { impl Settings { pub(super) fn into_builder( self, - events_loop: &winit::EventsLoop, - ) -> winit::WindowBuilder { + events_loop: &winit::event_loop::EventLoop<()>, + ) -> winit::window::WindowBuilder { let monitor = if self.fullscreen { - Some(events_loop.get_primary_monitor()) + Some(events_loop.primary_monitor()) } else { None }; - winit::WindowBuilder::new() + winit::window::WindowBuilder::new() .with_title(self.title) - .with_dimensions(winit::dpi::LogicalSize { - width: self.size.0 as f64, - height: self.size.1 as f64, + .with_inner_size(winit::dpi::PhysicalSize { + width: self.size.0, + height: self.size.1, }) .with_resizable(self.resizable) - .with_fullscreen(monitor) + .with_fullscreen(monitor.map(winit::window::Fullscreen::Borderless)) .with_maximized(self.maximized) } } diff --git a/src/input.rs b/src/input.rs index 4fa9099..838b164 100644 --- a/src/input.rs +++ b/src/input.rs @@ -8,7 +8,7 @@ pub mod window; mod event; mod keyboard_and_mouse; -pub use crate::graphics::window::winit::ElementState as ButtonState; +pub use crate::graphics::window::winit::event::ElementState as ButtonState; pub use event::Event; pub use keyboard::Keyboard; pub use keyboard_and_mouse::KeyboardAndMouse; diff --git a/src/input/keyboard.rs b/src/input/keyboard.rs index 94b4307..897ba9d 100644 --- a/src/input/keyboard.rs +++ b/src/input/keyboard.rs @@ -2,7 +2,7 @@ mod event; -pub use crate::graphics::window::winit::VirtualKeyCode as KeyCode; +pub use crate::graphics::window::winit::event::VirtualKeyCode as KeyCode; pub use event::Event; use super::{ButtonState, Event as InputEvent, Input}; diff --git a/src/input/mouse.rs b/src/input/mouse.rs index 576fb40..dd79199 100644 --- a/src/input/mouse.rs +++ b/src/input/mouse.rs @@ -3,7 +3,7 @@ mod event; mod wheel_movement; -pub use crate::graphics::window::winit::MouseButton as Button; +pub use crate::graphics::window::winit::event::MouseButton as Button; pub use event::Event; pub use wheel_movement::WheelMovement; diff --git a/src/load/task.rs b/src/load/task.rs index a3b6918..e0fe2af 100644 --- a/src/load/task.rs +++ b/src/load/task.rs @@ -388,7 +388,7 @@ impl Progress { /// /// [`Task`]: struct.Task.html pub fn percentage(&self) -> f32 { - (self.completed_work() as f32 / self.total_work.max(1) as f32 * 100.0) + self.completed_work() as f32 / self.total_work.max(1) as f32 * 100.0 } /// Returns the title of the current [`Task::stage`], if there is one. @@ -425,8 +425,8 @@ impl Join for (Task, Task) { Task::sequence( loader_a.total_work() + loader_b.total_work(), move |task| { - ((loader_a.function)(task) - .and_then(|a| (loader_b.function)(task).map(|b| (a, b)))) + (loader_a.function)(task) + .and_then(|a| (loader_b.function)(task).map(|b| (a, b))) }, ) } diff --git a/src/ui.rs b/src/ui.rs index 7e02b81..45a9337 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -173,12 +173,13 @@ pub type Panel<'a, Message> = widget::Panel<'a, Message, Renderer>; /// [`Renderer`]: struct.Renderer.html pub type Element<'a, Message> = self::core::Element<'a, Message, Renderer>; -use crate::game; -use crate::graphics::{window, Point, Window, WindowSettings}; -use crate::input::{self, gamepad, mouse, Input as _}; -use crate::load::{Join, LoadingScreen}; +use crate::game::{self, Loop as _}; +use crate::graphics::{Point, Window, WindowSettings}; +use crate::input::{self, mouse, Input as _}; +use crate::load::Task; use crate::ui::core::{Event, Interface, MouseCursor, Renderer as _}; -use crate::{Debug, Game, Result, Timer}; +use crate::{Debug, Game, Result}; +use std::convert::TryInto; /// The user interface of your game. /// @@ -270,142 +271,40 @@ pub trait UserInterface: Game { where Self: 'static + Sized, { - // Set up window - let event_loop = &mut window::EventLoop::new(); - let window = &mut Window::new(window_settings, &event_loop)?; - let mut debug = Debug::new(window.gpu()); - - // Load game - debug.loading_started(); - let mut loading_screen = Self::LoadingScreen::new(window.gpu())?; - let load = ( - Self::load(window), - Self::Renderer::load(Self::configuration()), - ) - .join(); - let (game, renderer) = &mut loading_screen.run(load, window)?; - let input = &mut Input::new(); - let mut gamepads = gamepad::Tracker::new(); - debug.loading_finished(); - - // Game loop - let mut timer = Timer::new(Self::TICKS_PER_SECOND); - let mut alive = true; - let messages = &mut Vec::new(); - let mut mouse_cursor = MouseCursor::OutOfBounds; - let mut ui_cache = - Interface::compute(game.layout(window), &renderer).cache(); - - while alive && !game.is_finished() { - debug.frame_started(); - timer.update(); - - while timer.tick() { - interact( - game, - input, - &mut debug, - window, - event_loop, - gamepads.as_mut(), - &mut alive, - ); - - debug.update_started(); - game.update(window); - debug.update_finished(); - } - - if !timer.has_ticked() { - interact( - game, - input, - &mut debug, - window, - event_loop, - gamepads.as_mut(), - &mut alive, - ); - } - - debug.draw_started(); - game.draw(&mut window.frame(), &timer); - debug.draw_finished(); - - debug.ui_started(); - let mut interface = Interface::compute_with_cache( - game.layout(window), - &renderer, - ui_cache, - ); - - let cursor_position = input.cursor_position; - input.ui_events.drain(..).for_each(|event| { - interface.on_event(event, cursor_position, messages) - }); - - let new_cursor = interface.draw( - renderer, - &mut window.frame(), - input.cursor_position, - ); - - ui_cache = interface.cache(); - - if new_cursor != mouse_cursor { - if new_cursor == MouseCursor::OutOfBounds { - input.update(input::Event::Mouse( - mouse::Event::CursorReturned, - )); - } else if mouse_cursor == MouseCursor::OutOfBounds { - input - .update(input::Event::Mouse(mouse::Event::CursorTaken)); - } - - window.update_cursor(new_cursor.into()); - mouse_cursor = new_cursor; - } - - for message in messages.drain(..) { - game.react(message, window); - } - debug.ui_finished(); - - if debug.is_enabled() { - debug.debug_started(); - game.debug( - &mut input.game_input, - &mut window.frame(), - &mut debug, - ); - debug.debug_finished(); - } - - window.swap_buffers(); - debug.frame_finished(); - } - - Ok(()) + Loop::::run(window_settings) } } -struct Input { - game_input: I, +struct Loop { + renderer: UI::Renderer, + messages: Vec, + mouse_cursor: MouseCursor, + cache: Option, cursor_position: Point, - ui_events: Vec, + events: Vec, } -impl input::Input for Input { - fn new() -> Input { - Input { - game_input: I::new(), +impl game::Loop for Loop { + type Attributes = UI::Renderer; + + fn new(renderer: UI::Renderer, game: &mut UI, window: &Window) -> Self { + let cache = Interface::compute(game.layout(window), &renderer).cache(); + Loop { + renderer, + messages: Vec::new(), + mouse_cursor: MouseCursor::OutOfBounds, + cache: Some(cache), cursor_position: Point::new(0.0, 0.0), - ui_events: Vec::new(), + events: Vec::new(), } } - fn update(&mut self, event: input::Event) { - self.game_input.update(event); + fn load(_window: &Window) -> Task { + UI::Renderer::load(UI::configuration()) + } + + fn on_input(&mut self, input: &mut UI::Input, event: input::Event) { + input.update(event); match event { input::Event::Mouse(mouse::Event::CursorMoved { x, y }) => { @@ -415,34 +314,59 @@ impl input::Input for Input { }; if let Some(ui_event) = Event::from_input(event) { - self.ui_events.push(ui_event); + self.events.push(ui_event); } } - fn clear(&mut self) { - self.game_input.clear(); - } -} - -fn interact( - game: &mut G, - input: &mut Input, - debug: &mut Debug, - window: &mut Window, - event_loop: &mut window::EventLoop, - gamepads: Option<&mut gamepad::Tracker>, - alive: &mut bool, -) { - debug.interact_started(); - - event_loop.poll(|event| { - game::process_window_event(game, input, debug, window, alive, event) - }); - - game::process_gamepad_events(gamepads, input); + fn after_draw( + &mut self, + ui: &mut UI, + input: &mut UI::Input, + window: &mut Window, + debug: &mut Debug, + ) { + debug.ui_started(); + let mut interface = Interface::compute_with_cache( + ui.layout(window), + &self.renderer, + self.cache.take().unwrap(), + ); + + let cursor_position = self.cursor_position; + let messages = &mut self.messages; + + self.events.drain(..).for_each(|event| { + interface.on_event(event, cursor_position, messages) + }); + + let new_cursor = interface.draw( + &mut self.renderer, + &mut window.frame(), + cursor_position, + ); + + self.cache = Some(interface.cache()); + + if new_cursor != self.mouse_cursor { + if new_cursor == MouseCursor::OutOfBounds { + input.update(input::Event::Mouse(mouse::Event::CursorReturned)); + } else if self.mouse_cursor == MouseCursor::OutOfBounds { + input.update(input::Event::Mouse(mouse::Event::CursorTaken)); + } - game.interact(&mut input.game_input, window); - input.clear(); + self.mouse_cursor = new_cursor; + } + // Use the game cursor if cursor is not on a UI element, use the mouse cursor otherwise + if self.mouse_cursor == MouseCursor::OutOfBounds { + let game_cursor = ui.cursor_icon(); + window.update_cursor(game_cursor.try_into().ok()); + } else { + window.update_cursor(Some(self.mouse_cursor.into())); + } - debug.interact_finished(); + for message in messages.drain(..) { + ui.react(message, window); + } + debug.ui_finished(); + } } diff --git a/src/ui/core.rs b/src/ui/core.rs index fb7c584..b88c104 100644 --- a/src/ui/core.rs +++ b/src/ui/core.rs @@ -22,7 +22,7 @@ pub use stretch::{geometry::Size, number::Number}; pub use element::Element; pub use event::Event; pub use hasher::Hasher; -pub(crate) use interface::Interface; +pub(crate) use interface::{Cache, Interface}; pub use layout::Layout; pub use mouse_cursor::MouseCursor; pub use node::Node; diff --git a/src/ui/core/mouse_cursor.rs b/src/ui/core/mouse_cursor.rs index c0c9cfd..4b99ee6 100644 --- a/src/ui/core/mouse_cursor.rs +++ b/src/ui/core/mouse_cursor.rs @@ -23,15 +23,15 @@ pub enum MouseCursor { } #[doc(hidden)] -impl From for winit::MouseCursor { - fn from(mouse_cursor: MouseCursor) -> winit::MouseCursor { +impl From for winit::window::CursorIcon { + fn from(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { match mouse_cursor { - MouseCursor::OutOfBounds => winit::MouseCursor::Default, - MouseCursor::Idle => winit::MouseCursor::Default, - MouseCursor::Pointer => winit::MouseCursor::Hand, - MouseCursor::Working => winit::MouseCursor::Progress, - MouseCursor::Grab => winit::MouseCursor::Grab, - MouseCursor::Grabbing => winit::MouseCursor::Grabbing, + MouseCursor::OutOfBounds => winit::window::CursorIcon::Default, + MouseCursor::Idle => winit::window::CursorIcon::Default, + MouseCursor::Pointer => winit::window::CursorIcon::Hand, + MouseCursor::Working => winit::window::CursorIcon::Progress, + MouseCursor::Grab => winit::window::CursorIcon::Grab, + MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, } } }