From 4e42179ef3ff805b041acac0c710af5f9a53ab0d Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sat, 30 Nov 2024 23:14:01 -0500 Subject: [PATCH] Support choosing display mode --- src/anaglyph.wgsl | 14 ++++- src/app.rs | 2 +- src/window/game.rs | 63 ++++++++++++++++++---- src/window/game_screen.rs | 107 +++++++++++++++++++++++--------------- 4 files changed, 131 insertions(+), 55 deletions(-) diff --git a/src/anaglyph.wgsl b/src/anaglyph.wgsl index 0483665..a2e87dd 100644 --- a/src/anaglyph.wgsl +++ b/src/anaglyph.wgsl @@ -49,7 +49,19 @@ struct Colors { var colors: Colors; @fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { +fn fs_lefteye(in: VertexOutput) -> @location(0) vec4 { + let brt = textureSample(u_texture, u_sampler, in.tex_coords); + return colors.left * brt[0]; +} + +@fragment +fn fs_righteye(in: VertexOutput) -> @location(0) vec4 { + let brt = textureSample(u_texture, u_sampler, in.tex_coords); + return colors.right * brt[1]; +} + +@fragment +fn fs_anaglyph(in: VertexOutput) -> @location(0) vec4 { let brt = textureSample(u_texture, u_sampler, in.tex_coords); return colors.left * brt[0] + colors.right * brt[1]; } diff --git a/src/app.rs b/src/app.rs index d03615c..19aa2b6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -196,7 +196,7 @@ impl Viewport { ); let ctx = Context::default(); - let mut fonts = FontDefinitions::empty(); + let mut fonts = FontDefinitions::default(); fonts.font_data.insert( "Selawik".into(), FontData::from_static(include_bytes!("../assets/selawik.ttf")), diff --git a/src/window/game.rs b/src/window/game.rs index 0ec7885..d20b371 100644 --- a/src/window/game.rs +++ b/src/window/game.rs @@ -8,12 +8,16 @@ use egui::{ }; use winit::event_loop::EventLoopProxy; -use super::{game_screen::GameScreen, AppWindow}; +use super::{ + game_screen::{DisplayMode, GameScreen}, + AppWindow, +}; pub struct GameWindow { client: EmulatorClient, proxy: EventLoopProxy, sim_id: SimId, + display_mode: DisplayMode, screen: Option, } @@ -23,6 +27,7 @@ impl GameWindow { client, proxy, sim_id, + display_mode: DisplayMode::Anaglyph, screen: None, } } @@ -61,21 +66,44 @@ impl GameWindow { } }); ui.menu_button("Video", |ui| { - let current_dims = ctx.input(|i| i.viewport().inner_rect.unwrap()); - let current_dims = current_dims.max - current_dims.min; + ui.menu_button("Screen Size", |ui| { + let current_dims = ctx.input(|i| i.viewport().inner_rect.unwrap()); + let current_dims = current_dims.max - current_dims.min; - for scale in 1..=4 { - let label = format!("x{scale}"); - let scale = scale as f32; - let dims = (384.0 * scale, 224.0 * scale + 22.0).into(); + for scale in 1..=4 { + let label = format!("x{scale}"); + let scale = scale as f32; + let dims = (384.0 * scale, 224.0 * scale + 22.0).into(); + if ui + .selectable_button((current_dims - dims).length() < 1.0, label) + .clicked() + { + ctx.send_viewport_cmd(ViewportCommand::InnerSize(dims)); + ui.close_menu(); + } + } + }); + + ui.menu_button("Display Mode", |ui| { if ui - .selectable_button((current_dims - dims).length() < 1.0, label) + .selectable_option(&mut self.display_mode, DisplayMode::Anaglyph, "Anaglyph") .clicked() { - ctx.send_viewport_cmd(ViewportCommand::InnerSize(dims)); ui.close_menu(); } - } + if ui + .selectable_option(&mut self.display_mode, DisplayMode::LeftEye, "Left Eye") + .clicked() + { + ui.close_menu(); + } + if ui + .selectable_option(&mut self.display_mode, DisplayMode::RightEye, "Right Eye") + .clicked() + { + ui.close_menu(); + } + }); }); ui.menu_button("Audio", |ui| { let p1_enabled = self.client.is_audio_enabled(SimId::Player1); @@ -146,7 +174,8 @@ impl AppWindow for GameWindow { }); let frame = Frame::central_panel(&ctx.style()).fill(Color32::BLACK); CentralPanel::default().frame(frame).show(ctx, |ui| { - if let Some(screen) = self.screen.as_ref() { + if let Some(screen) = self.screen.as_mut() { + screen.display_mode = self.display_mode; ui.add(screen); } }); @@ -168,6 +197,18 @@ impl AppWindow for GameWindow { trait UiExt { fn selectable_button(&mut self, selected: bool, text: impl Into) -> Response; + fn selectable_option( + &mut self, + current_value: &mut T, + selected_value: T, + text: impl Into, + ) -> Response { + let response = self.selectable_button(*current_value == selected_value, text); + if response.clicked() { + *current_value = selected_value; + } + response + } } impl UiExt for Ui { diff --git a/src/window/game_screen.rs b/src/window/game_screen.rs index c70c9bc..b18caa7 100644 --- a/src/window/game_screen.rs +++ b/src/window/game_screen.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use egui::Widget; use wgpu::{util::DeviceExt as _, BindGroup, BindGroupLayout, RenderPipeline}; @@ -7,6 +7,7 @@ use crate::graphics::TextureSink; pub struct GameScreen { bind_group: Arc, + pub display_mode: DisplayMode, } impl GameScreen { @@ -52,50 +53,58 @@ impl GameScreen { bind_group_layouts: &[&bind_group_layout], push_constant_ranges: &[], }); - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("render pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: wgpu::TextureFormat::Bgra8Unorm, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - polygon_mode: wgpu::PolygonMode::Fill, - unclipped_depth: false, - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); + + let create_render_pipeline = |entry_point: &str| { + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("render pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point, + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Bgra8Unorm, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }) + }; + + let mut render_pipelines = HashMap::new(); + render_pipelines.insert(DisplayMode::Anaglyph, create_render_pipeline("fs_anaglyph")); + render_pipelines.insert(DisplayMode::LeftEye, create_render_pipeline("fs_lefteye")); + render_pipelines.insert(DisplayMode::RightEye, create_render_pipeline("fs_righteye")); render_state .renderer .write() .callback_resources .insert(SharedGameScreenResources { - pipeline: render_pipeline, + render_pipelines, bind_group_layout, }); } @@ -144,19 +153,21 @@ impl GameScreen { ( Self { bind_group: Arc::new(bind_group), + display_mode: DisplayMode::Anaglyph, }, sink, ) } } -impl Widget for &GameScreen { +impl Widget for &mut GameScreen { fn ui(self, ui: &mut egui::Ui) -> egui::Response { let response = ui.allocate_rect(ui.clip_rect(), egui::Sense::hover()); let callback = egui_wgpu::Callback::new_paint_callback( response.rect, GameScreenCallback { bind_group: self.bind_group.clone(), + display_mode: self.display_mode, }, ); ui.painter().add(callback); @@ -166,6 +177,7 @@ impl Widget for &GameScreen { struct GameScreenCallback { bind_group: Arc, + display_mode: DisplayMode, } impl egui_wgpu::CallbackTrait for GameScreenCallback { @@ -186,7 +198,11 @@ impl egui_wgpu::CallbackTrait for GameScreenCallback { let h = height.min(width / aspect_ratio); let x = left + (width - w) / 2.0; let y = top + (height - h) / 2.0; - render_pass.set_pipeline(&resources.pipeline); + let pipeline = resources + .render_pipelines + .get(&self.display_mode) + .unwrap_or_else(|| panic!("Unrecognized display mode {:?}", self.display_mode)); + render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, &self.bind_group, &[]); render_pass.set_viewport(x, y, w, h, 0.0, 1.0); render_pass.draw(0..6, 0..1); @@ -194,7 +210,7 @@ impl egui_wgpu::CallbackTrait for GameScreenCallback { } struct SharedGameScreenResources { - pipeline: RenderPipeline, + render_pipelines: HashMap, bind_group_layout: BindGroupLayout, } @@ -204,3 +220,10 @@ struct Colors { left: [f32; 4], right: [f32; 4], } + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum DisplayMode { + Anaglyph, + LeftEye, + RightEye, +}