diff --git a/bracket-terminal/examples/webgpu_minimal.rs b/bracket-terminal/examples/webgpu_minimal.rs new file mode 100644 index 0000000..dc79e86 --- /dev/null +++ b/bracket-terminal/examples/webgpu_minimal.rs @@ -0,0 +1,103 @@ +use bracket_terminal::prelude::*; + +bracket_terminal::add_wasm_support!(); + +struct State { + player_x: i32, + player_y: i32, + glow: bool, + last_key: Option, +} + +impl State { + fn new() -> Self { + Self { + player_x: 40, + player_y: 25, + glow: false, + last_key: None, + } + } + + fn clamp_player(&mut self) { + self.player_x = self.player_x.clamp(1, 78); + self.player_y = self.player_y.clamp(4, 48); + } +} + +impl GameState for State { + fn tick(&mut self, ctx: &mut BTerm) { + if let Some(key) = ctx.key { + self.last_key = Some(key); + match key { + VirtualKeyCode::Left => self.player_x -= 1, + VirtualKeyCode::Right => self.player_x += 1, + VirtualKeyCode::Up => self.player_y -= 1, + VirtualKeyCode::Down => self.player_y += 1, + VirtualKeyCode::Space => self.glow = !self.glow, + VirtualKeyCode::Escape => ctx.quitting = true, + _ => {} + } + } + self.clamp_player(); + + let bg = if self.glow { + RGB::from_u8(8, 22, 40) + } else { + RGB::from_u8(0, 0, 0) + }; + let marker_fg = if self.glow { + RGB::named(YELLOW) + } else { + RGB::named(WHITE) + }; + let marker_bg = if self.glow { + RGB::from_u8(20, 60, 100) + } else { + RGB::named(BLACK) + }; + + ctx.cls_bg(bg); + ctx.draw_box(0, 0, 79, 49, RGB::named(CYAN), bg); + ctx.print_color(2, 1, RGB::named(WHITE), bg, "WebGPU Minimal Example"); + ctx.print_color( + 2, + 2, + RGB::named(GRAY), + bg, + "Arrow keys move, Space toggles glow, Esc exits", + ); + + ctx.print_color( + 2, + 46, + RGB::named(GREEN), + bg, + format!("Mouse tile: {}, {}", ctx.mouse_pos.0, ctx.mouse_pos.1), + ); + ctx.print_color( + 2, + 47, + RGB::named(MAGENTA), + bg, + format!("Last key: {:?}", self.last_key), + ); + ctx.print_color( + 2, + 48, + RGB::named(YELLOW), + bg, + format!("FPS: {:.1} Frame: {:.2} ms", ctx.fps, ctx.frame_time_ms), + ); + + ctx.print_color(self.player_x, self.player_y, marker_fg, marker_bg, "@"); + } +} + +fn main() -> BError { + let context = BTermBuilder::simple80x50() + .with_title("Bracket Terminal - WebGPU Minimal") + .build()?; + + main_loop(context, State::new()) +} diff --git a/bracket-terminal/src/hal/mod.rs b/bracket-terminal/src/hal/mod.rs index ab8f66c..0e540b8 100755 --- a/bracket-terminal/src/hal/mod.rs +++ b/bracket-terminal/src/hal/mod.rs @@ -56,9 +56,15 @@ pub use dummy::*; #[cfg(any(feature = "opengl", feature = "webgpu"))] mod scaler; +#[cfg(all(not(feature = "opengl"), feature = "webgpu"))] +type ActivePlatformGL = PlatformGL<'static>; + +#[cfg(not(all(not(feature = "opengl"), feature = "webgpu")))] +type ActivePlatformGL = PlatformGL; + /// Provides a base abstract platform for BTerm to run on, with specialized content. pub struct BTermPlatform { - pub platform: PlatformGL, + pub platform: ActivePlatformGL, } #[allow(dead_code)] diff --git a/bracket-terminal/src/hal/webgpu/backend.rs b/bracket-terminal/src/hal/webgpu/backend.rs index 59800fe..74b3383 100644 --- a/bracket-terminal/src/hal/webgpu/backend.rs +++ b/bracket-terminal/src/hal/webgpu/backend.rs @@ -5,7 +5,7 @@ use lazy_static::*; use parking_lot::Mutex; lazy_static! { - pub static ref BACKEND: Mutex = Mutex::new(PlatformGL { + pub static ref BACKEND: Mutex> = Mutex::new(PlatformGL { context_wrapper: None, wgpu: None, resize_scaling: false, diff --git a/bracket-terminal/src/hal/webgpu/backing/fancy_console_backing.rs b/bracket-terminal/src/hal/webgpu/backing/fancy_console_backing.rs index d147498..f831052 100644 --- a/bracket-terminal/src/hal/webgpu/backing/fancy_console_backing.rs +++ b/bracket-terminal/src/hal/webgpu/backing/fancy_console_backing.rs @@ -38,28 +38,29 @@ impl FancyConsoleBackend { wgpu.device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, - bind_group_layouts: &[font.bind_group_layout.as_ref().unwrap()], - push_constant_ranges: &[], + bind_group_layouts: &[Some(font.bind_group_layout.as_ref().unwrap())], + immediate_size: 0, }); let render_pipeline = wgpu .device .create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, - multiview: None, layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { module: &shader.0, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[vao.descriptor()], + compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &shader.0, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format: wgpu.config.format, blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], + compilation_options: wgpu::PipelineCompilationOptions::default(), }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, @@ -77,6 +78,8 @@ impl FancyConsoleBackend { mask: !0, alpha_to_coverage_enabled: false, }, + multiview_mask: None, + cache: None, }); FancyConsoleBackend { @@ -126,7 +129,7 @@ impl FancyConsoleBackend { offset_x: f32, offset_y: f32, scale: f32, - scale_center: (i32, i32), + _scale_center: (i32, i32), tiles: &[FlexiTile], font_scaler: FontScaler, screen_scaler: &ScreenScaler, @@ -253,12 +256,16 @@ impl FancyConsoleBackend { color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: wgpu.backing_buffer.view(), resolve_target: None, + depth_slice: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, }); render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, font.bind_group.as_ref().unwrap(), &[]); diff --git a/bracket-terminal/src/hal/webgpu/backing/index_array_helper.rs b/bracket-terminal/src/hal/webgpu/backing/index_array_helper.rs index 57294b3..35e3bd8 100644 --- a/bracket-terminal/src/hal/webgpu/backing/index_array_helper.rs +++ b/bracket-terminal/src/hal/webgpu/backing/index_array_helper.rs @@ -28,9 +28,7 @@ impl IndexBuffer { /// Calls WGPU's "create_buffer_init" path to copy the index buffer /// from local memory to GPU memory. pub fn build(&mut self, wgpu: &WgpuLink) { - if let Some(buf) = &mut self.buffer { - std::mem::drop(buf); - } + self.buffer = None; self.buffer = Some( wgpu.device .create_buffer_init(&wgpu::util::BufferInitDescriptor { @@ -47,7 +45,7 @@ impl IndexBuffer { } /// Maps the index buffer into a slice, suitable for render submission. - pub fn slice(&self) -> wgpu::BufferSlice { + pub fn slice(&self) -> wgpu::BufferSlice<'_> { self.buffer.as_ref().unwrap().slice(..) } } diff --git a/bracket-terminal/src/hal/webgpu/backing/simple_console_backing.rs b/bracket-terminal/src/hal/webgpu/backing/simple_console_backing.rs index 4c079c7..219586f 100644 --- a/bracket-terminal/src/hal/webgpu/backing/simple_console_backing.rs +++ b/bracket-terminal/src/hal/webgpu/backing/simple_console_backing.rs @@ -48,28 +48,29 @@ impl SimpleConsoleBackend { wgpu.device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, - bind_group_layouts: &[font.bind_group_layout.as_ref().unwrap()], - push_constant_ranges: &[], + bind_group_layouts: &[Some(font.bind_group_layout.as_ref().unwrap())], + immediate_size: 0, }); let render_pipeline = wgpu .device .create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, - multiview: None, layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { module: &shader.0, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[vao.descriptor()], + compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &shader.0, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format: wgpu.config.format, blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], + compilation_options: wgpu::PipelineCompilationOptions::default(), }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, @@ -87,6 +88,8 @@ impl SimpleConsoleBackend { mask: !0, alpha_to_coverage_enabled: false, }, + multiview_mask: None, + cache: None, }); // Build the result @@ -150,7 +153,7 @@ impl SimpleConsoleBackend { offset_x: f32, offset_y: f32, scale: f32, - scale_center: (i32, i32), + _scale_center: (i32, i32), needs_resize: bool, font_scaler: FontScaler, screen_scaler: &ScreenScaler, @@ -277,6 +280,7 @@ impl SimpleConsoleBackend { color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: wgpu.backing_buffer.view(), resolve_target: None, + depth_slice: None, ops: wgpu::Operations { /*load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, @@ -285,10 +289,13 @@ impl SimpleConsoleBackend { a: 1.0, }),*/ load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, }); render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, font.bind_group.as_ref().unwrap(), &[]); diff --git a/bracket-terminal/src/hal/webgpu/backing/sparse_console_backing.rs b/bracket-terminal/src/hal/webgpu/backing/sparse_console_backing.rs index 2338903..242cd12 100644 --- a/bracket-terminal/src/hal/webgpu/backing/sparse_console_backing.rs +++ b/bracket-terminal/src/hal/webgpu/backing/sparse_console_backing.rs @@ -33,28 +33,29 @@ impl SparseConsoleBackend { wgpu.device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, - bind_group_layouts: &[font.bind_group_layout.as_ref().unwrap()], - push_constant_ranges: &[], + bind_group_layouts: &[Some(font.bind_group_layout.as_ref().unwrap())], + immediate_size: 0, }); let render_pipeline = wgpu .device .create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, - multiview: None, layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { module: &shader.0, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[vao.descriptor()], + compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &shader.0, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format: wgpu.config.format, blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], + compilation_options: wgpu::PipelineCompilationOptions::default(), }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, @@ -72,6 +73,8 @@ impl SparseConsoleBackend { mask: !0, alpha_to_coverage_enabled: false, }, + multiview_mask: None, + cache: None, }); SparseConsoleBackend { @@ -116,7 +119,7 @@ impl SparseConsoleBackend { offset_x: f32, offset_y: f32, scale: f32, - scale_center: (i32, i32), + _scale_center: (i32, i32), tiles: &Vec, font_scaler: FontScaler, screen_scaler: &ScreenScaler, @@ -229,12 +232,16 @@ impl SparseConsoleBackend { color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: wgpu.backing_buffer.view(), resolve_target: None, + depth_slice: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, }); render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, font.bind_group.as_ref().unwrap(), &[]); diff --git a/bracket-terminal/src/hal/webgpu/backing/sprite_console_backing.rs b/bracket-terminal/src/hal/webgpu/backing/sprite_console_backing.rs index add9a29..ec85d66 100644 --- a/bracket-terminal/src/hal/webgpu/backing/sprite_console_backing.rs +++ b/bracket-terminal/src/hal/webgpu/backing/sprite_console_backing.rs @@ -30,28 +30,29 @@ impl SpriteConsoleBackend { wgpu.device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, - bind_group_layouts: &[font.bind_group_layout.as_ref().unwrap()], - push_constant_ranges: &[], + bind_group_layouts: &[Some(font.bind_group_layout.as_ref().unwrap())], + immediate_size: 0, }); let render_pipeline = wgpu .device .create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, - multiview: None, layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { module: &shader.0, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[vao.descriptor()], + compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &shader.0, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format: wgpu.config.format, blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], + compilation_options: wgpu::PipelineCompilationOptions::default(), }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, @@ -69,6 +70,8 @@ impl SpriteConsoleBackend { mask: !0, alpha_to_coverage_enabled: false, }, + multiview_mask: None, + cache: None, }); SpriteConsoleBackend { @@ -223,6 +226,7 @@ impl SpriteConsoleBackend { color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: wgpu.backing_buffer.view(), resolve_target: None, + depth_slice: None, ops: wgpu::Operations { /*load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, @@ -231,10 +235,13 @@ impl SpriteConsoleBackend { a: 1.0, }),*/ load: wgpu::LoadOp::Load, - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, }); render_pass.set_pipeline(&self.render_pipeline); render_pass.set_bind_group(0, font.bind_group.as_ref().unwrap(), &[]); diff --git a/bracket-terminal/src/hal/webgpu/backing/vertex_array_helper.rs b/bracket-terminal/src/hal/webgpu/backing/vertex_array_helper.rs index 8b58244..4d8ef24 100644 --- a/bracket-terminal/src/hal/webgpu/backing/vertex_array_helper.rs +++ b/bracket-terminal/src/hal/webgpu/backing/vertex_array_helper.rs @@ -61,7 +61,7 @@ where } /// Create a vertex buffer descriptor for pipelines. - pub fn descriptor(&self) -> wgpu::VertexBufferLayout { + pub fn descriptor(&self) -> wgpu::VertexBufferLayout<'_> { wgpu::VertexBufferLayout { array_stride: self.total_size, step_mode: wgpu::VertexStepMode::Vertex, @@ -72,9 +72,7 @@ where /// If a previous buffer exists, drop it. /// Map the backing store to a new vertex array. pub fn build(&mut self, wgpu: &WgpuLink) { - if let Some(buf) = &mut self.buffer { - std::mem::drop(buf); - } + self.buffer = None; self.buffer = Some( wgpu.device .create_buffer_init(&wgpu::util::BufferInitDescriptor { @@ -86,7 +84,7 @@ where } /// Maps the vertex buffer to a render-friendly slice. - pub fn slice(&self) -> wgpu::BufferSlice { + pub fn slice(&self) -> wgpu::BufferSlice<'_> { self.buffer.as_ref().unwrap().slice(..) } } diff --git a/bracket-terminal/src/hal/webgpu/font.rs b/bracket-terminal/src/hal/webgpu/font.rs index 4c8396a..bff96b1 100644 --- a/bracket-terminal/src/hal/webgpu/font.rs +++ b/bracket-terminal/src/hal/webgpu/font.rs @@ -2,7 +2,6 @@ use crate::BResult; use bracket_color::prelude::RGB; use bracket_embedding::prelude::EMBED; -use image::GenericImageView; use wgpu::{BindGroup, BindGroupLayout, Sampler, TextureView}; use super::WgpuLink; @@ -120,26 +119,27 @@ impl Font { depth_or_array_layers: 1, }; let tex = wgpu.device.create_texture(&wgpu::TextureDescriptor { + label: None, size: texture_size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - label: None, + view_formats: &[], }); wgpu.queue.write_texture( - wgpu::ImageCopyTexture { + wgpu::TexelCopyTextureInfo { texture: &tex, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, }, &data, - wgpu::ImageDataLayout { + wgpu::TexelCopyBufferLayout { offset: 0, - bytes_per_row: std::num::NonZeroU32::new(4 * self.width), - rows_per_image: std::num::NonZeroU32::new(self.height), + bytes_per_row: Some(4 * self.width), + rows_per_image: Some(self.height), }, texture_size, ); @@ -152,7 +152,9 @@ impl Font { address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::MipmapFilterMode::Nearest, + lod_min_clamp: 0.0, + lod_max_clamp: 32.0, ..Default::default() }); diff --git a/bracket-terminal/src/hal/webgpu/framebuffer.rs b/bracket-terminal/src/hal/webgpu/framebuffer.rs index 877d75c..0883f64 100644 --- a/bracket-terminal/src/hal/webgpu/framebuffer.rs +++ b/bracket-terminal/src/hal/webgpu/framebuffer.rs @@ -25,6 +25,7 @@ impl Framebuffer { usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], }); let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); let sampler = device.create_sampler(&wgpu::SamplerDescriptor { @@ -33,12 +34,12 @@ impl Framebuffer { address_mode_w: wgpu::AddressMode::Repeat, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, - lod_min_clamp: -100.0, - lod_max_clamp: 100.0, + mipmap_filter: wgpu::MipmapFilterMode::Linear, + lod_min_clamp: 0.0, + lod_max_clamp: 32.0, compare: None, label: None, - anisotropy_clamp: None, + anisotropy_clamp: 1, ..Default::default() }); diff --git a/bracket-terminal/src/hal/webgpu/init.rs b/bracket-terminal/src/hal/webgpu/init.rs index b48c9b5..c37f276 100644 --- a/bracket-terminal/src/hal/webgpu/init.rs +++ b/bracket-terminal/src/hal/webgpu/init.rs @@ -5,8 +5,9 @@ use crate::{ BResult, gamestate::BTerm, hal::Framebuffer, hal::scaler::ScreenScaler, prelude::BACKEND_INTERNAL, }; +use std::sync::Arc; use wgpu::{Adapter, Device, Instance, Queue, Surface, SurfaceConfiguration}; -use winit::{dpi::LogicalSize, event_loop::EventLoop, window::Window}; +use winit::{event_loop::EventLoop, window::Window}; pub fn init_raw( width_pixels: u32, @@ -21,10 +22,11 @@ pub fn init_raw( .with_min_inner_size(scaler.new_window_size()) .with_inner_size(scaler.new_window_size()); - let window = el.create_window(wb)?; + #[allow(deprecated)] + let window = Arc::new(el.create_window(wb)?); let (instance, surface, adapter, device, queue, config) = - pollster::block_on(init_adapter(&window)); + pollster::block_on(init_adapter(window.clone()))?; // Shaders let mut shaders: Vec = Vec::new(); @@ -56,7 +58,7 @@ pub fn init_raw( scaler.change_logical_size(width_pixels, height_pixels, initial_dpi_factor as f32); let backing_buffer = Framebuffer::new( &device, - surface.get_supported_formats(&adapter)[0], + config.format, scaler.logical_size.0, scaler.logical_size.1, ); @@ -105,52 +107,45 @@ pub fn init_raw( } async fn init_adapter( - window: &Window, -) -> ( + window: Arc, +) -> BResult<( Instance, - Surface, + Surface<'static>, Adapter, Device, Queue, SurfaceConfiguration, -) { +)> { let size = window.inner_size(); // The instance is a handle to our GPU // Backends::all => Vulkan + Metal + DX12 + Browser WebGPU - let instance = wgpu::Instance::new(wgpu::Backends::all()); - let surface = unsafe { instance.create_surface(window) }; + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle()); + let surface = instance.create_surface(window)?; let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::default(), compatible_surface: Some(&surface), force_fallback_adapter: false, }) - .await - .unwrap(); + .await?; let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), - label: None, - }, - None, // Trace path - ) - .await - .unwrap(); + .request_device(&wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), + memory_hints: wgpu::MemoryHints::Performance, + trace: wgpu::Trace::Off, + }) + .await?; //println!("{:?}", adapter.get_info()); - - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface.get_supported_formats(&adapter)[0], - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Fifo, - }; + let config = surface + .get_default_config(&adapter, size.width.max(1), size.height.max(1)) + .ok_or_else(|| "Failed to create default webgpu surface configuration".to_string())?; surface.configure(&device, &config); - (instance, surface, adapter, device, queue, config) + Ok((instance, surface, adapter, device, queue, config)) } diff --git a/bracket-terminal/src/hal/webgpu/mainloop.rs b/bracket-terminal/src/hal/webgpu/mainloop.rs index 5e01b30..2cf74d2 100644 --- a/bracket-terminal/src/hal/webgpu/mainloop.rs +++ b/bracket-terminal/src/hal/webgpu/mainloop.rs @@ -17,7 +17,12 @@ use bracket_geometry::prelude::Point; use std::mem::size_of; use std::{rc::Rc, time::Instant}; use wgpu::TextureViewDescriptor; -use winit::{dpi::PhysicalSize, event::*, event_loop::ControlFlow}; +use winit::{ + dpi::PhysicalSize, + event::*, + event_loop::ControlFlow, + keyboard::{KeyCode, PhysicalKey}, +}; const TICK_TYPE: ControlFlow = ControlFlow::Poll; @@ -27,6 +32,145 @@ struct ResizeEvent { send_event: bool, } +// Translate winit 0.30 key codes into the existing engine's VirtualKeyCode values. +fn map_keycode(code: KeyCode) -> Option { + use crate::hal::VirtualKeyCode as V; + use KeyCode as W; + + Some(match code { + W::Digit0 => V::Key0, + W::Digit1 => V::Key1, + W::Digit2 => V::Key2, + W::Digit3 => V::Key3, + W::Digit4 => V::Key4, + W::Digit5 => V::Key5, + W::Digit6 => V::Key6, + W::Digit7 => V::Key7, + W::Digit8 => V::Key8, + W::Digit9 => V::Key9, + W::KeyA => V::A, + W::KeyB => V::B, + W::KeyC => V::C, + W::KeyD => V::D, + W::KeyE => V::E, + W::KeyF => V::F, + W::KeyG => V::G, + W::KeyH => V::H, + W::KeyI => V::I, + W::KeyJ => V::J, + W::KeyK => V::K, + W::KeyL => V::L, + W::KeyM => V::M, + W::KeyN => V::N, + W::KeyO => V::O, + W::KeyP => V::P, + W::KeyQ => V::Q, + W::KeyR => V::R, + W::KeyS => V::S, + W::KeyT => V::T, + W::KeyU => V::U, + W::KeyV => V::V, + W::KeyW => V::W, + W::KeyX => V::X, + W::KeyY => V::Y, + W::KeyZ => V::Z, + W::Escape => V::Escape, + W::F1 => V::F1, + W::F2 => V::F2, + W::F3 => V::F3, + W::F4 => V::F4, + W::F5 => V::F5, + W::F6 => V::F6, + W::F7 => V::F7, + W::F8 => V::F8, + W::F9 => V::F9, + W::F10 => V::F10, + W::F11 => V::F11, + W::F12 => V::F12, + W::PrintScreen => V::Snapshot, + W::ScrollLock => V::Scroll, + W::Pause => V::Pause, + W::Insert => V::Insert, + W::Home => V::Home, + W::Delete => V::Delete, + W::End => V::End, + W::PageDown => V::PageDown, + W::PageUp => V::PageUp, + W::NumLock => V::Numlock, + W::Numpad0 => V::Numpad0, + W::Numpad1 => V::Numpad1, + W::Numpad2 => V::Numpad2, + W::Numpad3 => V::Numpad3, + W::Numpad4 => V::Numpad4, + W::Numpad5 => V::Numpad5, + W::Numpad6 => V::Numpad6, + W::Numpad7 => V::Numpad7, + W::Numpad8 => V::Numpad8, + W::Numpad9 => V::Numpad9, + W::NumpadDecimal => V::Decimal, + W::NumpadDivide => V::Divide, + W::NumpadMultiply => V::Multiply, + W::NumpadSubtract => V::Subtract, + W::NumpadAdd => V::Add, + W::NumpadEnter => V::NumpadEnter, + W::NumpadEqual => V::NumpadEquals, + W::NumpadComma => V::NumpadComma, + W::ArrowLeft => V::Left, + W::ArrowUp => V::Up, + W::ArrowRight => V::Right, + W::ArrowDown => V::Down, + W::Backspace => V::Back, + W::Enter => V::Return, + W::Space => V::Space, + W::Tab => V::Tab, + W::ShiftLeft => V::LShift, + W::ShiftRight => V::RShift, + W::ControlLeft => V::LControl, + W::ControlRight => V::RControl, + W::AltLeft => V::LAlt, + W::AltRight => V::RAlt, + W::BracketLeft => V::LBracket, + W::BracketRight => V::RBracket, + W::Minus => V::Minus, + W::Equal => V::Equals, + W::Comma => V::Comma, + W::Period => V::Period, + W::Semicolon => V::Semicolon, + W::Slash => V::Slash, + W::Backslash => V::Backslash, + W::Quote => V::Apostrophe, + W::Backquote => V::Grave, + _ => return None, + }) +} + +#[cfg(test)] +mod tests { + use super::map_keycode; + use crate::hal::VirtualKeyCode; + use winit::keyboard::KeyCode; + + #[test] + fn maps_representative_keys() { + assert_eq!(map_keycode(KeyCode::KeyA), Some(VirtualKeyCode::A)); + assert_eq!(map_keycode(KeyCode::ArrowLeft), Some(VirtualKeyCode::Left)); + assert_eq!(map_keycode(KeyCode::Numpad8), Some(VirtualKeyCode::Numpad8)); + assert_eq!( + map_keycode(KeyCode::PrintScreen), + Some(VirtualKeyCode::Snapshot) + ); + assert_eq!( + map_keycode(KeyCode::NumpadEnter), + Some(VirtualKeyCode::NumpadEnter) + ); + } + + #[test] + fn returns_none_for_unmapped_keys() { + assert_eq!(map_keycode(KeyCode::SuperLeft), None); + } +} + pub fn main_loop(mut bterm: BTerm, mut gamestate: GS) -> BResult<()> { let now = Instant::now(); let mut prev_seconds = now.elapsed().as_secs(); @@ -71,16 +215,18 @@ pub fn main_loop(mut bterm: BTerm, mut gamestate: GS) -> BResult< let spin_sleeper = spin_sleep::SpinSleeper::default(); let my_window_id = window.id(); - el.run(move |event, _, control_flow| { + #[allow(deprecated)] + el.run(move |event, target| { let wait_time = BACKEND.lock().frame_sleep_time.unwrap_or(33); // Hoisted to reduce locks - *control_flow = TICK_TYPE; + target.set_control_flow(TICK_TYPE); if bterm.quitting { - *control_flow = ControlFlow::Exit; + target.exit(); + return; } - match &event { - Event::RedrawEventsCleared => { + match event { + Event::AboutToWait => { let frame_timer = Instant::now(); if window.inner_size().width == 0 || window.inner_size().height == 0 { return; @@ -111,27 +257,23 @@ pub fn main_loop(mut bterm: BTerm, mut gamestate: GS) -> BResult< &now, &mut backing_flip, ); - //wc.swap_buffers().unwrap(); - // Moved from new events, which doesn't make sense clear_input_state(&mut bterm); } // Wait for an appropriate amount of time let time_since_last_frame = frame_timer.elapsed().as_millis() as u64; if time_since_last_frame < wait_time { + #[cfg(feature = "low_cpu")] let delay = u64::min(33, wait_time - time_since_last_frame); #[cfg(feature = "low_cpu")] spin_sleeper.sleep(std::time::Duration::from_millis(delay)); } } Event::WindowEvent { event, window_id } => { - // Fast return for other windows - if *window_id != my_window_id { - //println!("Dropped event from other window"); + if window_id != my_window_id { return; } - // Handle Window Events match event { WindowEvent::Moved(physical_position) => { bterm.on_event(BEvent::Moved { @@ -140,8 +282,6 @@ pub fn main_loop(mut bterm: BTerm, mut gamestate: GS) -> BResult< let scale_factor = window.scale_factor(); let physical_size = window.inner_size(); - //wc.resize(physical_size); - //on_resize(&mut bterm, physical_size, scale_factor, true).unwrap(); queued_resize_event = Some(ResizeEvent { physical_size, dpi_scale_factor: scale_factor, @@ -151,8 +291,6 @@ pub fn main_loop(mut bterm: BTerm, mut gamestate: GS) -> BResult< WindowEvent::Resized(_physical_size) => { let scale_factor = window.scale_factor(); let physical_size = window.inner_size(); - //wc.resize(physical_size); - //on_resize(&mut bterm, physical_size, scale_factor, true).unwrap(); queued_resize_event = Some(ResizeEvent { physical_size, dpi_scale_factor: scale_factor, @@ -160,18 +298,14 @@ pub fn main_loop(mut bterm: BTerm, mut gamestate: GS) -> BResult< }); } WindowEvent::CloseRequested => { - // If not using events, just close. Otherwise, push the event if !INPUT.lock().use_events { - *control_flow = ControlFlow::Exit; + target.exit(); } else { bterm.on_event(BEvent::CloseRequested); } } - WindowEvent::ReceivedCharacter(char) => { - bterm.on_event(BEvent::Character { c: *char }); - } WindowEvent::Focused(focused) => { - bterm.on_event(BEvent::Focused { focused: *focused }); + bterm.on_event(BEvent::Focused { focused }); } WindowEvent::CursorMoved { position: pos, .. } => { bterm.on_mouse_position(pos.x, pos.y); @@ -180,19 +314,20 @@ pub fn main_loop(mut bterm: BTerm, mut gamestate: GS) -> BResult< WindowEvent::CursorLeft { .. } => bterm.on_event(BEvent::CursorLeft), WindowEvent::MouseInput { button, state, .. } => { - let button = match button { + let button = match &button { MouseButton::Left => 0, MouseButton::Right => 1, MouseButton::Middle => 2, - MouseButton::Other(num) => 3 + *num as usize, + MouseButton::Back => 3, + MouseButton::Forward => 4, + MouseButton::Other(num) => 5 + *num as usize, }; - bterm.on_mouse_button(button, *state == ElementState::Pressed); + bterm.on_mouse_button(button, state == ElementState::Pressed); } - WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + WindowEvent::ScaleFactorChanged { .. } => { let scale_factor = window.scale_factor(); let physical_size = window.inner_size(); - //wc.resize(physical_size); on_resize( &mut bterm, physical_size, @@ -202,33 +337,39 @@ pub fn main_loop(mut bterm: BTerm, mut gamestate: GS) -> BResult< ) .unwrap(); bterm.on_event(BEvent::ScaleFactorChanged { - new_size: Point::new(new_inner_size.width, new_inner_size.height), + new_size: Point::new(physical_size.width, physical_size.height), dpi_scale_factor: scale_factor as f32, }) } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_keycode), - state, - scancode, - .. - }, - .. - } => bterm.on_key(*virtual_keycode, *scancode, *state == ElementState::Pressed), + WindowEvent::KeyboardInput { event, .. } => { + if let Some(text) = event.text { + for c in text.chars() { + bterm.on_event(BEvent::Character { c }); + } + } + + if let PhysicalKey::Code(code) = event.physical_key { + if let Some(key) = map_keycode(code) { + bterm.on_key(key, key as u32, event.state == ElementState::Pressed); + } + } + } WindowEvent::ModifiersChanged(modifiers) => { - bterm.shift = modifiers.shift(); - bterm.alt = modifiers.alt(); - bterm.control = modifiers.ctrl(); + let state = modifiers.state(); + bterm.shift = state.shift_key(); + bterm.alt = state.alt_key(); + bterm.control = state.control_key(); } _ => {} } } _ => {} } - }); + })?; + + Ok(()) } fn largest_active_font() -> (u32, u32) { @@ -274,7 +415,7 @@ fn on_resize( } // WGPU resizing - if let Some(mut wgpu) = be.wgpu.as_mut() { + if let Some(wgpu) = be.wgpu.as_mut() { backing_flip.update_buffer_with_gutter(wgpu, l, r, t, b); wgpu.config.width = physical_size.width; wgpu.config.height = physical_size.height; @@ -296,13 +437,8 @@ fn on_resize( // Backing buffer resizing let w = be.screen_scaler.available_width; let h = be.screen_scaler.available_height; - let mut wgpu = be.wgpu.as_mut().unwrap(); - wgpu.backing_buffer = Framebuffer::new( - &wgpu.device, - wgpu.surface.get_supported_formats(&wgpu.adapter)[0], - w, - h, - ); + let wgpu = be.wgpu.as_mut().unwrap(); + wgpu.backing_buffer = Framebuffer::new(&wgpu.device, wgpu.config.format, w, h); let num_consoles = bit.consoles.len(); for i in 0..num_consoles { @@ -366,8 +502,22 @@ fn tock( // backing buffer/post-process { let mut be = BACKEND.lock(); + let screenshot_request = be.request_screenshot.clone(); + let mut clear_screenshot_request = false; if let Some(wgpu) = be.wgpu.as_ref() { - if let Ok(current_tex) = wgpu.surface.get_current_texture() { + let (current_tex, reconfigure) = match wgpu.surface.get_current_texture() { + wgpu::CurrentSurfaceTexture::Success(t) => (Some(t), false), + wgpu::CurrentSurfaceTexture::Suboptimal(t) => (Some(t), true), + wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => { + wgpu.surface.configure(&wgpu.device, &wgpu.config); + (None, false) + } + wgpu::CurrentSurfaceTexture::Timeout + | wgpu::CurrentSurfaceTexture::Occluded + | wgpu::CurrentSurfaceTexture::Validation => (None, false), + }; + + if let Some(current_tex) = current_tex { backing_flip.update_uniform( wgpu, bterm.post_scanlines, @@ -377,15 +527,21 @@ fn tock( let target = current_tex .texture .create_view(&TextureViewDescriptor::default()); - if backing_flip.render(&wgpu, &target).is_ok() { - if let Some(filename) = &be.request_screenshot { - take_screenshot(filename, &wgpu, bterm, &wgpu.backing_buffer.texture); + if backing_flip.render(wgpu, &target).is_ok() { + if let Some(filename) = &screenshot_request { + take_screenshot(filename, wgpu, bterm, &wgpu.backing_buffer.texture); } - be.request_screenshot = None; + clear_screenshot_request = true; current_tex.present(); } + if reconfigure { + wgpu.surface.configure(&wgpu.device, &wgpu.config); + } } } + if clear_screenshot_request { + be.request_screenshot = None; + } } } @@ -401,7 +557,7 @@ pub(crate) fn rebuild_consoles() { let cons = &mut bi.consoles[i]; match c { ConsoleBacking::Simple { backing } => { - let mut sc = cons + let sc = cons .console .as_any_mut() .downcast_mut::() @@ -426,7 +582,7 @@ pub(crate) fn rebuild_consoles() { } } ConsoleBacking::Sparse { backing } => { - let mut sc = bi.consoles[i] + let sc = bi.consoles[i] .console .as_any_mut() .downcast_mut::() @@ -451,7 +607,7 @@ pub(crate) fn rebuild_consoles() { } } ConsoleBacking::Fancy { backing } => { - let mut fc = bi.consoles[i] + let fc = bi.consoles[i] .console .as_any_mut() .downcast_mut::() @@ -477,7 +633,7 @@ pub(crate) fn rebuild_consoles() { } ConsoleBacking::Sprite { backing } => { let ss = bi.sprite_sheets.clone(); - let mut sc = bi.consoles[i] + let sc = bi.consoles[i] .console .as_any_mut() .downcast_mut::() @@ -577,7 +733,7 @@ pub(crate) fn check_console_backing() { } } -fn clear_screen_pass() -> Result<(), wgpu::SurfaceError> { +fn clear_screen_pass() -> BResult<()> { let mut be = BACKEND.lock(); if let Some(wgpu) = be.wgpu.as_mut() { let mut encoder = wgpu @@ -590,6 +746,7 @@ fn clear_screen_pass() -> Result<(), wgpu::SurfaceError> { label: Some("Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: wgpu.backing_buffer.view(), + depth_slice: None, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { @@ -598,10 +755,13 @@ fn clear_screen_pass() -> Result<(), wgpu::SurfaceError> { b: 0.0, a: 1.0, }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, }); } @@ -610,14 +770,11 @@ fn clear_screen_pass() -> Result<(), wgpu::SurfaceError> { Ok(()) } else { - Err(wgpu::SurfaceError::OutOfMemory) + Err("WebGPU backend not initialized".into()) } } -fn take_screenshot(filename: &str, wgpu: &WgpuLink, bterm: &BTerm, texture: &wgpu::Texture) { - use std::fs::File; - use std::io::Write; - +fn take_screenshot(_filename: &str, wgpu: &WgpuLink, bterm: &BTerm, texture: &wgpu::Texture) { let w = (bterm.width_pixels as f32) as usize; let h = (bterm.height_pixels as f32) as usize; //println!("Taking screenshot {} = {}x{}", filename, w, h); @@ -643,14 +800,11 @@ fn take_screenshot(filename: &str, wgpu: &WgpuLink, bterm: &BTerm, texture: &wgp //println!("Copying texture to buffer"); encoder.copy_texture_to_buffer( texture.as_image_copy(), - wgpu::ImageCopyBuffer { + wgpu::TexelCopyBufferInfo { buffer: &output_buffer, - layout: wgpu::ImageDataLayout { + layout: wgpu::TexelCopyBufferLayout { offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new(buffer_dimensions.padded_bytes_per_row as u32) - .unwrap(), - ), + bytes_per_row: Some(buffer_dimensions.padded_bytes_per_row as u32), rows_per_image: None, }, }, @@ -696,7 +850,6 @@ fn take_screenshot(filename: &str, wgpu: &WgpuLink, bterm: &BTerm, texture: &wgp struct BufferDimensions { width: usize, height: usize, - unpadded_bytes_per_row: usize, padded_bytes_per_row: usize, } @@ -710,7 +863,6 @@ impl BufferDimensions { Self { width, height, - unpadded_bytes_per_row, padded_bytes_per_row, } } diff --git a/bracket-terminal/src/hal/webgpu/mod.rs b/bracket-terminal/src/hal/webgpu/mod.rs index f5f5790..102f67b 100644 --- a/bracket-terminal/src/hal/webgpu/mod.rs +++ b/bracket-terminal/src/hal/webgpu/mod.rs @@ -1,5 +1,9 @@ //! Provides wgpu support back-end. +#[path = "../dummy/keycodes.rs"] +mod keycodes; +pub use keycodes::VirtualKeyCode; + mod platform; pub use platform::*; mod init; @@ -12,8 +16,6 @@ mod backend; pub use backend::*; mod mainloop; pub use mainloop::*; -pub use winit::keyboard::KeyCode; -pub use winit::keyboard::KeyCode as VirtualKeyCode; mod backing; pub(crate) use backing::*; mod framebuffer; diff --git a/bracket-terminal/src/hal/webgpu/platform.rs b/bracket-terminal/src/hal/webgpu/platform.rs index abe1c87..56ffbc4 100644 --- a/bracket-terminal/src/hal/webgpu/platform.rs +++ b/bracket-terminal/src/hal/webgpu/platform.rs @@ -2,6 +2,7 @@ use super::Framebuffer; use crate::hal::scaler::{ScreenScaler, default_gutter_size}; +use std::sync::Arc; use wgpu::{Adapter, Device, Instance, Queue, Surface, SurfaceConfiguration}; use winit::{event_loop::EventLoop, window::Window}; @@ -39,7 +40,7 @@ unsafe impl<'a> Sync for PlatformGL<'a> {} pub struct WrappedContext { pub el: EventLoop<()>, - pub window: Window, + pub window: Arc, } pub struct InitHints { diff --git a/bracket-terminal/src/hal/webgpu/quadrender.rs b/bracket-terminal/src/hal/webgpu/quadrender.rs index eddac1a..fa83560 100644 --- a/bracket-terminal/src/hal/webgpu/quadrender.rs +++ b/bracket-terminal/src/hal/webgpu/quadrender.rs @@ -73,28 +73,29 @@ impl QuadRender { wgpu.device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, - bind_group_layouts: &[&texture_bind_group_layout, &uniform_layout], - push_constant_ranges: &[], + bind_group_layouts: &[Some(&texture_bind_group_layout), Some(&uniform_layout)], + immediate_size: 0, }); let render_pipeline = wgpu .device .create_render_pipeline(&wgpu::RenderPipelineDescriptor { - multiview: None, label: None, layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { module: &shader.0, - entry_point: "vs_main", + entry_point: Some("vs_main"), buffers: &[quad_buffer.descriptor()], + compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &shader.0, - entry_point: "fs_main", + entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format: wgpu.config.format, blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], + compilation_options: wgpu::PipelineCompilationOptions::default(), }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, @@ -112,6 +113,8 @@ impl QuadRender { mask: !0, alpha_to_coverage_enabled: false, }, + multiview_mask: None, + cache: None, }); let uniform = QuadUniform::empty(); @@ -198,8 +201,9 @@ impl QuadRender { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &target, + view: target, resolve_target: None, + depth_slice: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, @@ -207,10 +211,13 @@ impl QuadRender { b: 0.0, a: 1.0, }), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, }); render_pass.set_pipeline(&self.pipeline); render_pass.set_bind_group(0, &bind_group, &[]); diff --git a/bracket-terminal/src/lib.rs b/bracket-terminal/src/lib.rs index f44418c..dd33037 100755 --- a/bracket-terminal/src/lib.rs +++ b/bracket-terminal/src/lib.rs @@ -12,6 +12,7 @@ pub mod rex; pub use bracket_embedding::prelude::{EMBED, embedded_resource, link_resource}; pub type BResult = anyhow::Result>; +#[allow(unused_imports)] pub(crate) use input::clear_input_state; pub type FontCharType = u16; pub use consoles::console;