From 709f78867049600224cda809551acebd3200e680 Mon Sep 17 00:00:00 2001 From: David Chavez Date: Tue, 4 Jul 2023 16:46:19 +0200 Subject: [PATCH] chore(gui): reuse implementations (#30) --- Cargo.toml | 6 +- cpp/helix.c | 4 +- include/helix/internal.h | 4 +- src/gui.rs | 326 ++++++++++++++++++++++- src/gui/glium_renderer.rs | 187 +++++++++++++ src/gui/gui_glium.rs | 412 ----------------------------- src/gui/gui_wgpu.rs | 542 -------------------------------------- src/gui/wgpu_renderer.rs | 326 +++++++++++++++++++++++ src/gui/windows.rs | 7 +- src/lib.rs | 2 +- 10 files changed, 844 insertions(+), 972 deletions(-) create mode 100644 src/gui/glium_renderer.rs delete mode 100644 src/gui/gui_glium.rs delete mode 100644 src/gui/gui_wgpu.rs create mode 100644 src/gui/wgpu_renderer.rs diff --git a/Cargo.toml b/Cargo.toml index 12177d4..bf2af80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,9 +44,9 @@ glutin = { version = "0.29.1", optional = true } wgpu = { version = "0.16", optional = true } spin_sleep = "1.1.1" arie = "0.2.0" -fast3d = { version = "0.3.0", default-features = false } -fast3d-glium-renderer = { version = "0.3.0", optional = true } -fast3d-wgpu-renderer = { version = "0.3.0", optional = true } +fast3d = { version = "0.3.1", default-features = false } +fast3d-glium-renderer = { version = "0.3.1", optional = true } +fast3d-wgpu-renderer = { version = "0.3.1", optional = true } [patch.crates-io] #fast3d = { path = "../../fast3d-rs/fast3d" } diff --git a/cpp/helix.c b/cpp/helix.c index 76fc6d9..d4e8ce2 100644 --- a/cpp/helix.c +++ b/cpp/helix.c @@ -40,11 +40,11 @@ void HLXDisplaySetup(const char* title, void (*draw_menu)(void*), void (*draw_wi } void HLXDisplayStartFrame() { - _frame = GUIStartFrame(_gui, _event_loop); + GUIStartFrame(_gui, _event_loop); } void HLXDisplayProcessDrawLists(u64* commands) { - GUIDrawLists(_gui, _frame, commands); + GUIDrawLists(_gui, commands); } void HLXDisplayEndFrame() { diff --git a/include/helix/internal.h b/include/helix/internal.h index 38cdf5a..68d3598 100644 --- a/include/helix/internal.h +++ b/include/helix/internal.h @@ -31,8 +31,8 @@ void AudioPlayerQueueBuffer(void* player, const uint8_t* buf, size_t len); // GUI void* GUICreateEventLoop(void); void* GUICreate(const char* title, void* event_loop, void (*draw_menu_callback)(void*), void (*draw_windows_callback)(void*), void* gamepad_manager); -void* GUIStartFrame(void* gui, void* event_loop); -void GUIDrawLists(void* gui, void* frame, uint64_t* commands); +void GUIStartFrame(void* gui, void* event_loop); +void GUIDrawLists(void* gui, uint64_t* commands); void GUIEndFrame(void* gui); f32 GUIGetAspectRatio(void* gui); diff --git a/src/gui.rs b/src/gui.rs index 2dad455..1cb3b29 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,7 +1,325 @@ -#[cfg(feature = "wgpu_renderer")] -pub mod gui_wgpu; +use crate::gamepad::manager::GamepadManager; +use fast3d::output::RCPOutput; +use fast3d::rcp::RCP; +use fast3d::rdp::OutputDimensions; +use winit::platform::run_return::EventLoopExtRunReturn; + +pub mod windows; #[cfg(feature = "opengl_renderer")] -pub mod gui_glium; +mod glium_renderer; +#[cfg(feature = "opengl_renderer")] +pub use glium_renderer::Renderer; +#[cfg(feature = "opengl_renderer")] +pub type Frame = glium::Frame; -pub mod windows; +#[cfg(feature = "wgpu_renderer")] +mod wgpu_renderer; +#[cfg(feature = "wgpu_renderer")] +pub use wgpu_renderer::Renderer; +#[cfg(feature = "wgpu_renderer")] +pub type Frame = wgpu::SurfaceTexture; + +/// Represents the state of the UI. +pub struct UIState { + last_frame_time: std::time::Instant, + last_cursor: Option, +} + +/// Wrapper around winit's event loop to allow for +/// the creation of the imgui context. +pub struct EventLoopWrapper { + event_loop: winit::event_loop::EventLoop<()>, +} + +impl EventLoopWrapper { + pub fn new() -> Self { + Self { + event_loop: winit::event_loop::EventLoop::new(), + } + } +} + +pub struct Gui<'a> { + // imgui + imgui: imgui::Context, + platform: imgui_winit_support::WinitPlatform, + + // ui state + ui_state: UIState, + + // draw callbacks + draw_menu_callback: Box, + draw_windows_callback: Box, + + // gamepad + gamepad_manager: Option<&'a mut GamepadManager>, + + // game renderer + rcp: RCP, + rcp_output: RCPOutput, + gfx_renderer: Renderer<'a>, +} + +impl<'a> Gui<'a> { + pub fn new( + title: &str, + event_loop_wrapper: &EventLoopWrapper, + draw_menu: D, + draw_windows: W, + gamepad_manager: Option<&'a mut GamepadManager>, + ) -> anyhow::Result + where + D: Fn(&imgui::Ui) + 'static, + W: Fn(&imgui::Ui) + 'static, + { + // Setup ImGui + let mut imgui = imgui::Context::create(); + + // Create the imgui + winit platform + let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui); + + // Setup Dear ImGui style + imgui.set_ini_filename(None); + + // Imgui Setup fonts + let hidpi_factor = platform.hidpi_factor(); + let font_size = (13.0 * hidpi_factor) as f32; + imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; + + imgui + .fonts() + .add_font(&[imgui::FontSource::DefaultFontData { + config: Some(imgui::FontConfig { + oversample_h: 1, + pixel_snap_h: true, + size_pixels: font_size, + ..Default::default() + }), + }]); + + // Setup Renderer + let (width, height) = (800, 600); + let renderer = Renderer::new(width, height, title, event_loop_wrapper, &mut imgui)?; + renderer.attach_window(&mut platform, &mut imgui); + + // Initial UI state + let last_frame_time = std::time::Instant::now(); + + Ok(Self { + imgui, + platform, + ui_state: UIState { + last_frame_time, + last_cursor: None, + }, + draw_menu_callback: Box::new(draw_menu), + draw_windows_callback: Box::new(draw_windows), + gamepad_manager, + rcp: RCP::new(), + rcp_output: RCPOutput::new(), + gfx_renderer: renderer, + }) + } + + fn handle_events(&mut self, event_loop_wrapper: &mut EventLoopWrapper) { + event_loop_wrapper + .event_loop + .run_return(|event, _, control_flow| { + match event { + winit::event::Event::MainEventsCleared => control_flow.set_exit(), + winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::CloseRequested, + .. + } => std::process::exit(0), + winit::event::Event::WindowEvent { + event: + winit::event::WindowEvent::Resized(size) + | winit::event::WindowEvent::ScaleFactorChanged { + new_inner_size: &mut size, + .. + }, + .. + } => { + self.gfx_renderer.resize(size.width, size.height); + + // TODO: Fix resizing on OpenGL + #[cfg(feature = "wgpu_renderer")] + self.gfx_renderer + .handle_event(&mut self.platform, &mut self.imgui, &event); + } + winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::ModifiersChanged(modifiers), + .. + } => { + if let Some(gamepad_manager) = self.gamepad_manager.as_mut() { + gamepad_manager.handle_modifiers_changed(modifiers); + } + + self.gfx_renderer + .handle_event(&mut self.platform, &mut self.imgui, &event); + } + winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::KeyboardInput { input, .. }, + .. + } => { + if let Some(gamepad_manager) = self.gamepad_manager.as_mut() { + gamepad_manager.handle_keyboard_input(input); + } + + self.gfx_renderer + .handle_event(&mut self.platform, &mut self.imgui, &event); + } + event => { + self.gfx_renderer + .handle_event(&mut self.platform, &mut self.imgui, &event) + } + } + }); + } + + fn sync_frame_rate(&mut self) { + const FRAME_INTERVAL_MS: u64 = 1000 / 30; + + let frame_duration = self.ui_state.last_frame_time.elapsed(); + if frame_duration < std::time::Duration::from_millis(FRAME_INTERVAL_MS) { + let sleep_duration = + std::time::Duration::from_millis(FRAME_INTERVAL_MS) - frame_duration; + spin_sleep::sleep(sleep_duration); + } + + let now = std::time::Instant::now(); + + self.imgui + .io_mut() + .update_delta_time(now - self.ui_state.last_frame_time); + + self.ui_state.last_frame_time = now; + } + + pub fn start_frame(&mut self, event_loop_wrapper: &mut EventLoopWrapper) -> anyhow::Result<()> { + // Handle events + self.handle_events(event_loop_wrapper); + + // Prepare for drawing + self.gfx_renderer + .prepare_frame(&mut self.platform, &mut self.imgui)?; + + Ok(()) + } + + pub fn process_draw_lists(&mut self, commands: usize) -> anyhow::Result<()> { + // Set RDP output dimensions + let size = self.gfx_renderer.window_size(); + let dimensions = OutputDimensions { + width: size.width, + height: size.height, + aspect_ratio: size.width as f32 / size.height as f32, + }; + self.rcp.rdp.output_dimensions = dimensions; + + // Run the RCP + self.rcp.run(&mut self.rcp_output, commands); + + // Grab the frame + let mut frame = self.gfx_renderer.get_current_texture().unwrap(); + + // Render RCP output + self.gfx_renderer + .process_rcp_output(&mut frame, &mut self.rcp_output)?; + + // Render ImGui on top of any game content + let ui = self.imgui.new_frame(); + ui.main_menu_bar(|| (self.draw_menu_callback)(ui)); + (self.draw_windows_callback)(ui); + + if self.ui_state.last_cursor != ui.mouse_cursor() { + self.ui_state.last_cursor = ui.mouse_cursor(); + self.gfx_renderer.prepare_render(&mut self.platform, ui); + } + + let draw_data = self.imgui.render(); + self.gfx_renderer + .draw_imgui_content(&mut frame, draw_data)?; + + // Clear the draw calls + self.rcp_output.clear_draw_calls(); + + // Swap buffers + self.gfx_renderer.finish_render(frame)?; + + Ok(()) + } + + pub fn end_frame(&mut self) { + self.sync_frame_rate(); + } +} + +// MARK: - C API + +type OnDraw = unsafe extern "C" fn(ui: &imgui::Ui); + +#[no_mangle] +pub extern "C" fn GUICreateEventLoop() -> Box { + let event_loop = EventLoopWrapper::new(); + Box::new(event_loop) +} + +#[no_mangle] +pub unsafe extern "C" fn GUICreate<'a>( + title_raw: *const i8, + event_loop: Option<&'a mut EventLoopWrapper>, + draw_menu: Option, + draw_windows: Option, + gamepad_manager: Option<&'a mut GamepadManager>, +) -> Box> { + let title_str: &std::ffi::CStr = unsafe { std::ffi::CStr::from_ptr(title_raw) }; + let title: &str = std::str::from_utf8(title_str.to_bytes()).unwrap(); + + let event_loop = event_loop.unwrap(); + let gui = Gui::new( + title, + event_loop, + move |ui| unsafe { + if let Some(draw_menu) = draw_menu { + draw_menu(ui); + } + }, + move |ui| unsafe { + if let Some(draw_windows) = draw_windows { + draw_windows(ui); + } + }, + gamepad_manager, + ) + .unwrap(); + + Box::new(gui) +} + +#[no_mangle] +pub extern "C" fn GUIStartFrame(gui: Option<&mut Gui>, event_loop: Option<&mut EventLoopWrapper>) { + let gui = gui.unwrap(); + let event_loop = event_loop.unwrap(); + gui.start_frame(event_loop).unwrap(); +} + +#[no_mangle] +pub extern "C" fn GUIDrawLists(gui: Option<&mut Gui>, commands: u64) { + let gui = gui.unwrap(); + gui.process_draw_lists(commands.try_into().unwrap()) + .unwrap(); +} + +#[no_mangle] +pub extern "C" fn GUIEndFrame(gui: Option<&mut Gui>) { + let gui = gui.unwrap(); + gui.end_frame(); +} + +#[no_mangle] +pub extern "C" fn GUIGetAspectRatio(gui: Option<&mut Gui>) -> f32 { + let gui = gui.unwrap(); + gui.rcp.rdp.output_dimensions.aspect_ratio +} diff --git a/src/gui/glium_renderer.rs b/src/gui/glium_renderer.rs new file mode 100644 index 0000000..833b182 --- /dev/null +++ b/src/gui/glium_renderer.rs @@ -0,0 +1,187 @@ +use crate::gui::{EventLoopWrapper, Frame}; +use fast3d::output::RCPOutput; +use fast3d_glium_renderer::glium_device::GliumGraphicsDevice; + +pub struct Renderer<'a> { + display: glium::Display, + renderer: imgui_glium_renderer::Renderer, + graphics_device: GliumGraphicsDevice<'a>, +} + +impl<'a> Renderer<'a> { + pub fn new( + width: i32, + height: i32, + title: &str, + event_loop_wrapper: &EventLoopWrapper, + imgui: &mut imgui::Context, + ) -> anyhow::Result { + // Create the window + let build = glutin::window::WindowBuilder::new() + .with_title(title) + .with_inner_size(glutin::dpi::LogicalSize::new(width, height)); + + let context = glutin::ContextBuilder::new() + .with_depth_buffer(24) + .with_gl(glutin::GlRequest::Latest) + .with_vsync(true); + + let display = glium::Display::new(build, context, &event_loop_wrapper.event_loop)?; + + // Create the renderer + let renderer = imgui_glium_renderer::Renderer::init(imgui, &display)?; + + Ok(Self { + display, + renderer, + graphics_device: GliumGraphicsDevice::default(), + }) + } + + // Platform Functions + + pub fn attach_window( + &self, + platform: &mut imgui_winit_support::WinitPlatform, + imgui: &mut imgui::Context, + ) { + platform.attach_window( + imgui.io_mut(), + self.display.gl_window().window(), + imgui_winit_support::HiDpiMode::Default, + ); + } + + pub fn handle_event( + &mut self, + platform: &mut imgui_winit_support::WinitPlatform, + imgui: &mut imgui::Context, + event: &winit::event::Event, + ) { + platform.handle_event(imgui.io_mut(), self.display.gl_window().window(), event); + } + + pub fn prepare_frame( + &self, + platform: &mut imgui_winit_support::WinitPlatform, + imgui: &mut imgui::Context, + ) -> anyhow::Result<()> { + platform.prepare_frame(imgui.io_mut(), self.display.gl_window().window())?; + Ok(()) + } + + pub fn prepare_render( + &self, + platform: &mut imgui_winit_support::WinitPlatform, + ui: &mut imgui::Ui, + ) { + platform.prepare_render(ui, self.display.gl_window().window()); + } + + // Rendering Functions + + pub fn window_size(&self) -> winit::dpi::PhysicalSize { + self.display.gl_window().window().inner_size() + } + + pub fn resize(&mut self, width: u32, height: u32) { + log::trace!("Resizing to {:?}x{:?}", width, height); + self.display + .gl_window() + .resize(glutin::dpi::PhysicalSize::new(width, height)); + } + + pub fn get_current_texture(&self) -> Option { + let frame = self.display.draw(); + Some(frame) + } + + pub fn process_rcp_output( + &mut self, + frame: &mut Frame, + rcp_output: &mut RCPOutput, + ) -> anyhow::Result<()> { + // Prepare the context device + self.graphics_device.start_frame(frame); + + // Process the RCP output + self.render_game(frame, rcp_output)?; + + // Finish rendering + self.graphics_device.end_frame(); + + Ok(()) + } + + pub fn draw_imgui_content( + &mut self, + frame: &mut Frame, + draw_data: &imgui::DrawData, + ) -> anyhow::Result<()> { + self.renderer.render(frame, draw_data)?; + Ok(()) + } + + pub fn finish_render(&mut self, frame: Frame) -> anyhow::Result<()> { + frame.finish()?; + Ok(()) + } + + // MARK: - Helpers + + fn render_game(&mut self, frame: &mut Frame, rcp_output: &mut RCPOutput) -> anyhow::Result<()> { + // omit the last draw call, because we know we that's an extra from the last flush + // for draw_call in &self.rcp_output.draw_calls[..self.rcp_output.draw_calls.len() - 1] { + for draw_call in rcp_output + .draw_calls + .iter() + .take(rcp_output.draw_calls.len() - 1) + { + assert!(!draw_call.vbo.vbo.is_empty()); + + self.graphics_device.set_cull_mode(draw_call.cull_mode); + + self.graphics_device + .set_depth_stencil_params(draw_call.stencil); + + self.graphics_device.set_blend_state(draw_call.blend_state); + self.graphics_device.set_viewport(&draw_call.viewport); + self.graphics_device.set_scissor(draw_call.scissor); + + self.graphics_device.select_program( + &self.display, + draw_call.shader_id, + draw_call.shader_config, + ); + + // loop through textures and bind them + for (index, hash) in draw_call.texture_indices.iter().enumerate() { + if let Some(hash) = hash { + let texture = rcp_output.texture_cache.get_mut(*hash).unwrap(); + self.graphics_device + .bind_texture(&self.display, index, texture); + } + } + + // loop through samplers and bind them + for (index, sampler) in draw_call.samplers.iter().enumerate() { + if let Some(sampler) = sampler { + self.graphics_device.bind_sampler(index, sampler); + } + } + + // draw triangles + self.graphics_device.draw_triangles( + &self.display, + frame, + draw_call.projection_matrix, + &draw_call.fog, + &draw_call.vbo.vbo, + draw_call.vbo.num_tris, + &draw_call.uniforms, + ); + } + + Ok(()) + } +} diff --git a/src/gui/gui_glium.rs b/src/gui/gui_glium.rs deleted file mode 100644 index b8e4b9e..0000000 --- a/src/gui/gui_glium.rs +++ /dev/null @@ -1,412 +0,0 @@ -use anyhow::Result; - -use glium::Frame; -use glutin::{event_loop::EventLoop, GlRequest}; -use imgui::{Context, FontSource, Ui}; -use imgui_glium_renderer::Renderer; - -use winit::event::{Event, WindowEvent}; - -use std::str; -use std::time::Duration; -use std::{ffi::CStr, result::Result::Ok, time::Instant}; -use winit::platform::run_return::EventLoopExtRunReturn; - -use crate::gamepad::manager::GamepadManager; -use fast3d::{output::RCPOutput, rcp::RCP, rdp::OutputDimensions}; - -use fast3d_glium_renderer::glium_device::GliumGraphicsDevice; - -pub struct Gui<'a> { - // window - pub display: glium::Display, - - // render - renderer: Renderer, - platform: imgui_winit_support::WinitPlatform, - - // imgui - imgui: Context, - - // ui state - ui_state: UIState, - - // draw callbacks - draw_menu_callback: Box, - draw_windows_callback: Box, - - // gamepad - gamepad_manager: Option<&'a mut GamepadManager>, - - // game renderer - rcp: RCP, - pub rcp_output: RCPOutput, - graphics_device: GliumGraphicsDevice<'a>, -} - -pub struct UIState { - last_frame_time: Instant, -} - -pub struct EventLoopWrapper { - event_loop: EventLoop<()>, -} - -impl<'a> Gui<'a> { - pub fn new( - title: &str, - event_loop_wrapper: &EventLoopWrapper, - draw_menu: D, - draw_windows: W, - gamepad_manager: Option<&'a mut GamepadManager>, - ) -> Result - where - D: Fn(&Ui) + 'static, - W: Fn(&Ui) + 'static, - { - let (width, height) = (800, 600); - - // Create the window - - let build = glutin::window::WindowBuilder::new() - .with_title(title) - .with_inner_size(glutin::dpi::LogicalSize::new(width, height)); - - let context = glutin::ContextBuilder::new() - .with_depth_buffer(24) - .with_gl(GlRequest::Latest) - .with_vsync(true); - - let display = glium::Display::new(build, context, &event_loop_wrapper.event_loop)?; - - // Setup ImGui - let mut imgui = Context::create(); - - // Create the egui + winit platform - let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui); - platform.attach_window( - imgui.io_mut(), - display.gl_window().window(), - imgui_winit_support::HiDpiMode::Default, - ); - - // Setup Dear ImGui style - imgui.set_ini_filename(None); - - // Setup fonts - let hidpi_factor = platform.hidpi_factor(); - let font_size = (13.0 * hidpi_factor) as f32; - imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; - - imgui.fonts().add_font(&[FontSource::DefaultFontData { - config: Some(imgui::FontConfig { - oversample_h: 1, - pixel_snap_h: true, - size_pixels: font_size, - ..Default::default() - }), - }]); - - // Setup Renderer - let renderer = imgui_glium_renderer::Renderer::init(&mut imgui, &display)?; - - // Initial UI state - let last_frame_time = Instant::now(); - - Ok(Self { - display, - renderer, - platform, - imgui, - ui_state: UIState { last_frame_time }, - draw_menu_callback: Box::new(draw_menu), - draw_windows_callback: Box::new(draw_windows), - gamepad_manager, - rcp: RCP::default(), - rcp_output: RCPOutput::default(), - graphics_device: GliumGraphicsDevice::default(), - }) - } - - fn handle_events(&mut self, event_loop_wrapper: &mut EventLoopWrapper) { - event_loop_wrapper - .event_loop - .run_return(|event, _, control_flow| match event { - Event::MainEventsCleared => control_flow.set_exit(), - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - std::process::exit(0); - } - Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - let gl_window = self.display.gl_window(); - gl_window.resize(size); - } - Event::WindowEvent { - event: WindowEvent::ModifiersChanged(modifiers), - .. - } => { - if let Some(gamepad_manager) = self.gamepad_manager.as_mut() { - gamepad_manager.handle_modifiers_changed(modifiers); - } - - // Forward the event over to Dear ImGui - let gl_window = self.display.gl_window(); - self.platform - .handle_event(self.imgui.io_mut(), gl_window.window(), &event); - } - Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, - .. - } => { - if let Some(gamepad_manager) = self.gamepad_manager.as_mut() { - gamepad_manager.handle_keyboard_input(input); - } - - // Forward the event over to Dear ImGui - let gl_window = self.display.gl_window(); - self.platform - .handle_event(self.imgui.io_mut(), gl_window.window(), &event); - } - event => { - let gl_window = self.display.gl_window(); - - self.platform - .handle_event(self.imgui.io_mut(), gl_window.window(), &event); - } - }); - } - - fn sync_frame_rate(&mut self) { - const FRAME_INTERVAL_MS: u64 = 1000 / 30; - - let frame_duration = self.ui_state.last_frame_time.elapsed(); - if frame_duration < Duration::from_millis(FRAME_INTERVAL_MS) { - let sleep_duration = Duration::from_millis(FRAME_INTERVAL_MS) - frame_duration; - spin_sleep::sleep(sleep_duration); - } - - let now = Instant::now(); - - self.imgui - .io_mut() - .update_delta_time(now - self.ui_state.last_frame_time); - - self.ui_state.last_frame_time = now; - } - - pub fn start_frame(&mut self, event_loop_wrapper: &mut EventLoopWrapper) -> Result { - // Handle events - self.handle_events(event_loop_wrapper); - - // Grab current window size and store them - let size = self.display.gl_window().window().inner_size(); - let dimensions = OutputDimensions { - width: size.width, - height: size.height, - aspect_ratio: size.width as f32 / size.height as f32, - }; - self.rcp.rdp.output_dimensions = dimensions; - - // Get the ImGui context and begin drawing the frame - let gl_window = self.display.gl_window(); - self.platform - .prepare_frame(self.imgui.io_mut(), gl_window.window())?; - - // Setup for drawing - let target = self.display.draw(); - - Ok(target) - } - - fn render(&mut self, target: &mut Frame) -> Result<()> { - // Begin drawing UI - let ui = self.imgui.new_frame(); - ui.main_menu_bar(|| { - (self.draw_menu_callback)(ui); - }); - - // Draw windows - (self.draw_windows_callback)(ui); - - // Setup for drawing - let gl_window = self.display.gl_window(); - - // Render ImGui on top of any drawn content - self.platform.prepare_render(ui, gl_window.window()); - let draw_data = self.imgui.render(); - - self.renderer.render(target, draw_data)?; - - Ok(()) - } - - fn render_game(&mut self, target: &mut Frame) -> Result<()> { - // omit the last draw call, because we know we that's an extra from the last flush - // for draw_call in &self.rcp_output.draw_calls[..self.rcp_output.draw_calls.len() - 1] { - for draw_call in self - .rcp_output - .draw_calls - .iter() - .take(self.rcp_output.draw_calls.len() - 1) - { - assert!(!draw_call.vbo.vbo.is_empty()); - - self.graphics_device.set_cull_mode(draw_call.cull_mode); - - self.graphics_device - .set_depth_stencil_params(draw_call.stencil); - - self.graphics_device.set_blend_state(draw_call.blend_state); - self.graphics_device.set_viewport(&draw_call.viewport); - self.graphics_device.set_scissor(draw_call.scissor); - - self.graphics_device.select_program( - &self.display, - draw_call.shader_id, - draw_call.shader_config, - ); - - // loop through textures and bind them - for (index, hash) in draw_call.texture_indices.iter().enumerate() { - if let Some(hash) = hash { - let texture = self.rcp_output.texture_cache.get_mut(*hash).unwrap(); - self.graphics_device - .bind_texture(&self.display, index, texture); - } - } - - // loop through samplers and bind them - for (index, sampler) in draw_call.samplers.iter().enumerate() { - if let Some(sampler) = sampler { - self.graphics_device.bind_sampler(index, sampler); - } - } - - // draw triangles - self.graphics_device.draw_triangles( - &self.display, - target, - draw_call.projection_matrix, - &draw_call.fog, - &draw_call.vbo.vbo, - draw_call.vbo.num_tris, - &draw_call.uniforms, - ); - } - - Ok(()) - } - - pub fn create_event_loop() -> EventLoopWrapper { - let event_loop = EventLoop::new(); - EventLoopWrapper { event_loop } - } - - pub fn draw_lists(&mut self, mut frame: Frame, commands: usize) -> Result<()> { - // Prepare the context device - self.graphics_device.start_frame(&mut frame); - - // Run the RCP - self.rcp.run(&mut self.rcp_output, commands); - self.render_game(&mut frame)?; - - // Finish rendering - self.graphics_device.end_frame(); - - // Render ImGui on top of any drawn content - self.render(&mut frame)?; - - // Clear the draw calls - self.rcp_output.clear_draw_calls(); - - // Swap buffers - frame.finish()?; - - Ok(()) - } - - pub fn end_frame(&mut self) -> Result<()> { - self.sync_frame_rate(); - Ok(()) - } -} - -// MARK: - C API - -type OnDraw = unsafe extern "C" fn(ui: &Ui); - -#[no_mangle] -pub extern "C" fn GUICreateEventLoop() -> Box { - let event_loop = Gui::create_event_loop(); - Box::new(event_loop) -} - -#[no_mangle] -pub unsafe extern "C" fn GUICreate<'a>( - title_raw: *const i8, - event_loop: Option<&'a mut EventLoopWrapper>, - draw_menu: Option, - draw_windows: Option, - gamepad_manager: Option<&'a mut GamepadManager>, -) -> Box> { - let title_str: &CStr = unsafe { CStr::from_ptr(title_raw) }; - let title: &str = str::from_utf8(title_str.to_bytes()).unwrap(); - - let event_loop = event_loop.unwrap(); - let gui = Gui::new( - title, - event_loop, - move |ui| unsafe { - if let Some(draw_menu) = draw_menu { - draw_menu(ui); - } - }, - move |ui| unsafe { - if let Some(draw_windows) = draw_windows { - draw_windows(ui); - } - }, - gamepad_manager, - ) - .unwrap(); - - Box::new(gui) -} - -#[no_mangle] -pub extern "C" fn GUIStartFrame( - gui: Option<&mut Gui>, - event_loop: Option<&mut EventLoopWrapper>, -) -> Box> { - let gui = gui.unwrap(); - let event_loop = event_loop.unwrap(); - match gui.start_frame(event_loop) { - Ok(frame) => Box::new(Some(frame)), - Err(_) => Box::new(None), - } -} - -#[no_mangle] -pub extern "C" fn GUIDrawLists(gui: Option<&mut Gui>, frame: Option>, commands: u64) { - let gui = gui.unwrap(); - let frame = frame.unwrap(); - gui.draw_lists(*frame, commands.try_into().unwrap()) - .unwrap(); -} - -#[no_mangle] -pub extern "C" fn GUIEndFrame(gui: Option<&mut Gui>) { - let gui = gui.unwrap(); - gui.end_frame().unwrap(); -} - -#[no_mangle] -pub extern "C" fn GUIGetAspectRatio(gui: Option<&mut Gui>) -> f32 { - let gui = gui.unwrap(); - gui.rcp.rdp.output_dimensions.aspect_ratio -} diff --git a/src/gui/gui_wgpu.rs b/src/gui/gui_wgpu.rs deleted file mode 100644 index 961d52c..0000000 --- a/src/gui/gui_wgpu.rs +++ /dev/null @@ -1,542 +0,0 @@ -use anyhow::Result; -use imgui::{Context, FontSource, MouseCursor, Ui}; -use imgui_wgpu::{Renderer, RendererConfig}; -use imgui_winit_support::winit::{ - event::{Event, WindowEvent}, - event_loop::EventLoop, - window::{Window, WindowBuilder}, -}; -use log::trace; -use wgpu::{SurfaceConfiguration, SurfaceTexture}; - -use std::{ - ffi::CStr, - result::Result::Ok, - str, - time::{Duration, Instant}, -}; -use winit::{dpi::LogicalSize, platform::run_return::EventLoopExtRunReturn}; - -use crate::gamepad::manager::GamepadManager; -use fast3d::{output::RCPOutput, rcp::RCP, rdp::OutputDimensions}; - -use fast3d_wgpu_renderer::wgpu_device::WgpuGraphicsDevice; - -pub struct Gui<'a> { - // window - pub window: Window, - - // render - surface: wgpu::Surface, - device: wgpu::Device, - queue: wgpu::Queue, - surface_config: SurfaceConfiguration, - renderer: Renderer, - platform: imgui_winit_support::WinitPlatform, - - // imgui - imgui: Context, - - // ui state - ui_state: UIState, - - // draw callbacks - draw_menu_callback: Box, - draw_windows_callback: Box, - - // gamepad - gamepad_manager: Option<&'a mut GamepadManager>, - - // game renderer - rcp: RCP, - pub rcp_output: RCPOutput, - graphics_device: WgpuGraphicsDevice, -} - -pub struct UIState { - last_frame_time: Instant, - last_cursor: Option, -} - -pub struct EventLoopWrapper { - event_loop: EventLoop<()>, -} - -impl<'a> Gui<'a> { - pub fn new( - title: &str, - event_loop_wrapper: &EventLoopWrapper, - draw_menu: D, - draw_windows: W, - gamepad_manager: Option<&'a mut GamepadManager>, - ) -> Result - where - D: Fn(&Ui) + 'static, - W: Fn(&Ui) + 'static, - { - let (width, height) = (800, 600); - - // Setup WGPU instance - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); - - // Create the window - let (window, size, surface) = { - let window = WindowBuilder::new() - .with_title(title) - .with_inner_size(LogicalSize::new(width, height)) - .with_resizable(true) - .build(&event_loop_wrapper.event_loop)?; - - let size = window.outer_size(); - - let surface = unsafe { instance.create_surface(&window) }?; - - (window, size, surface) - }; - - // Create the WGPU adapter - let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: Some(&surface), - force_fallback_adapter: false, - })) - .ok_or(anyhow::anyhow!("Failed to find an appropriate adapter"))?; - - // Create the WGPU device - let (device, queue) = pollster::block_on(adapter.request_device( - &wgpu::DeviceDescriptor { - features: wgpu::Features::empty(), - limits: wgpu::Limits::default(), - label: None, - }, - None, - ))?; - - // Create the swapchain - let mut surface_config = surface - .get_default_config(&adapter, size.width, size.height) - .ok_or(anyhow::anyhow!("Failed to get default surface config"))?; - - let surface_view_format = surface_config.format.add_srgb_suffix(); - surface_config.view_formats.push(surface_view_format); - - surface.configure(&device, &surface_config); - - // Setup ImGui - let mut imgui = Context::create(); - imgui.io_mut().config_flags |= - imgui::ConfigFlags::VIEWPORTS_ENABLE | imgui::ConfigFlags::NO_MOUSE_CURSOR_CHANGE; - - // Create the egui + winit platform - let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui); - platform.attach_window( - imgui.io_mut(), - &window, - imgui_winit_support::HiDpiMode::Default, - ); - - // Setup Dear ImGui style - imgui.set_ini_filename(None); - - // Setup fonts - let hidpi_factor = window.scale_factor(); - let font_size = (13.0 * hidpi_factor) as f32; - imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; - - imgui.fonts().add_font(&[FontSource::DefaultFontData { - config: Some(imgui::FontConfig { - oversample_h: 1, - pixel_snap_h: true, - size_pixels: font_size, - ..Default::default() - }), - }]); - - // Setup Renderer - let renderer_config = RendererConfig { - texture_format: surface_config.format, - ..Default::default() - }; - - let renderer = Renderer::new(&mut imgui, &device, &queue, renderer_config); - - // Create graphics device - let graphics_device = WgpuGraphicsDevice::new(&surface_config, &device); - - // Initial UI state - let last_frame_time = Instant::now(); - let last_cursor = None; - - Ok(Self { - window, - surface, - device, - queue, - surface_config, - renderer, - platform, - imgui, - ui_state: UIState { - last_frame_time, - last_cursor, - }, - draw_menu_callback: Box::new(draw_menu), - draw_windows_callback: Box::new(draw_windows), - gamepad_manager, - rcp: RCP::default(), - rcp_output: RCPOutput::default(), - graphics_device, - }) - } - - fn handle_events(&mut self, event_loop_wrapper: &mut EventLoopWrapper) { - event_loop_wrapper - .event_loop - .run_return(|event, _, control_flow| { - control_flow.set_poll(); - - match event { - Event::WindowEvent { - event: - WindowEvent::Resized(size) - | WindowEvent::ScaleFactorChanged { - new_inner_size: &mut size, - .. - }, - .. - } => { - // there's a bug where at first the size is u32::MAX so we just ignore it - if size.width == u32::MAX || size.height == u32::MAX { - return; - } - - trace!("Resizing to {:?}", size); - self.surface_config.width = size.width.max(1); - self.surface_config.height = size.height.max(1); - self.surface.configure(&self.device, &self.surface_config); - self.graphics_device - .resize(&self.surface_config, &self.device); - } - Event::WindowEvent { - event: WindowEvent::ModifiersChanged(modifiers), - .. - } => { - if let Some(gamepad_manager) = self.gamepad_manager.as_mut() { - gamepad_manager.handle_modifiers_changed(modifiers); - } - } - Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, - .. - } => { - if let Some(gamepad_manager) = self.gamepad_manager.as_mut() { - gamepad_manager.handle_keyboard_input(input); - } - } - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - std::process::exit(0); - } - Event::MainEventsCleared => control_flow.set_exit(), - _ => (), - } - - self.platform - .handle_event(self.imgui.io_mut(), &self.window, &event); - }); - } - - fn sync_frame_rate(&mut self) { - const FRAME_INTERVAL_MS: u64 = 1000 / 30; - - let frame_duration = self.ui_state.last_frame_time.elapsed(); - if frame_duration < Duration::from_millis(FRAME_INTERVAL_MS) { - let sleep_duration = Duration::from_millis(FRAME_INTERVAL_MS) - frame_duration; - spin_sleep::sleep(sleep_duration); - } - - let now = Instant::now(); - - self.imgui - .io_mut() - .update_delta_time(now - self.ui_state.last_frame_time); - - self.ui_state.last_frame_time = now; - } - - pub fn start_frame( - &mut self, - event_loop_wrapper: &mut EventLoopWrapper, - ) -> Result> { - // Handle events - self.handle_events(event_loop_wrapper); - - // Start the frame - let frame = match self.surface.get_current_texture() { - Ok(frame) => frame, - Err(e) => { - trace!("Dropped frame due to error: {:?}", e); - return Ok(None); - } - }; - - self.platform - .prepare_frame(self.imgui.io_mut(), &self.window)?; - - Ok(Some(frame)) - } - - pub fn create_event_loop() -> EventLoopWrapper { - let event_loop = EventLoop::new(); - EventLoopWrapper { event_loop } - } - - pub fn draw_lists(&mut self, frame: SurfaceTexture, commands: usize) -> Result<()> { - // Start frame - let ui = self.imgui.new_frame(); - - // Draw client menu bar - ui.main_menu_bar(|| { - (self.draw_menu_callback)(ui); - }); - - // Set RDP output dimensions - let size = self.window.inner_size(); - let dimensions = OutputDimensions { - width: size.width, - height: size.height, - aspect_ratio: size.width as f32 / size.height as f32, - }; - self.rcp.rdp.output_dimensions = dimensions; - - // Texture we'll be drawing game and ImGui to - let view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - { - // Prepare the context device - self.graphics_device.update_frame_count(); - - // Run the RCP - self.rcp.run(&mut self.rcp_output, commands); - - // Setup encoder that the RDP will use - let mut encoder: wgpu::CommandEncoder = - self.device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Game Draw Command Encoder"), - }); - - // Draw the RCP output - // omit the last draw call, because we know we that's an extra from the last flush - for (index, draw_call) in self - .rcp_output - .draw_calls - .iter() - .take(self.rcp_output.draw_calls.len() - 1) - .enumerate() - { - assert!(!draw_call.vbo.vbo.is_empty()); - - self.graphics_device - .update_current_height(draw_call.viewport.w as i32); - - self.graphics_device.select_program( - &self.device, - draw_call.shader_id, - draw_call.shader_config, - ); - - // loop through textures and bind them - for (index, hash) in draw_call.texture_indices.iter().enumerate() { - if let Some(hash) = hash { - let texture = self.rcp_output.texture_cache.get_mut(*hash).unwrap(); - self.graphics_device.bind_texture( - &self.device, - &self.queue, - index, - texture, - ); - } - } - - // loop through samplers and bind them - for (index, sampler) in draw_call.samplers.iter().enumerate() { - if let Some(sampler) = sampler { - self.graphics_device - .bind_sampler(&self.device, index, sampler); - } - } - - // set uniforms - self.graphics_device.update_uniforms( - &self.queue, - draw_call.projection_matrix, - &draw_call.fog, - &draw_call.uniforms, - ); - - // create pipeline - let (texture_bind_group_layout, pipeline) = self.graphics_device.create_pipeline( - &self.device, - self.surface_config.format, - draw_call.blend_state, - draw_call.cull_mode, - draw_call.stencil, - ); - - // render triangles to texture - self.graphics_device.draw_triangles( - index, - &view, - &self.device, - &self.queue, - &mut encoder, - &pipeline, - &texture_bind_group_layout, - &draw_call.viewport, - draw_call.scissor, - &draw_call.vbo.vbo, - draw_call.vbo.num_tris, - ); - } - - // Draw client windows - (self.draw_windows_callback)(ui); - - // Reset state - self.rcp_output.clear_draw_calls(); - - // Finish game encoding and submit - self.queue.submit(Some(encoder.finish())); - } - - // Draw ImGui to view - let mut encoder: wgpu::CommandEncoder = - self.device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("ImGui Command Encoder"), - }); - - if self.ui_state.last_cursor != ui.mouse_cursor() { - self.ui_state.last_cursor = ui.mouse_cursor(); - self.platform.prepare_render(ui, &self.window); - } - - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("ImGui Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - })], - depth_stencil_attachment: None, - }); - - self.renderer - .render(self.imgui.render(), &self.queue, &self.device, &mut rpass)?; - - drop(rpass); - - self.queue.submit(Some(encoder.finish())); - - frame.present(); - - Ok(()) - } - - pub fn end_frame(&mut self) -> Result<()> { - self.sync_frame_rate(); - Ok(()) - } -} - -// MARK: - C API - -type OnDraw = unsafe extern "C" fn(ui: &Ui); - -#[no_mangle] -pub extern "C" fn GUICreateEventLoop() -> Box { - let event_loop = Gui::create_event_loop(); - Box::new(event_loop) -} - -#[no_mangle] -pub unsafe extern "C" fn GUICreate<'a>( - title_raw: *const i8, - event_loop: Option<&'a mut EventLoopWrapper>, - draw_menu: Option, - draw_windows: Option, - gamepad_manager: Option<&'a mut GamepadManager>, -) -> Box> { - let title_str: &CStr = unsafe { CStr::from_ptr(title_raw) }; - let title: &str = str::from_utf8(title_str.to_bytes()).unwrap(); - - let event_loop = event_loop.unwrap(); - let gui = Gui::new( - title, - event_loop, - move |ui| unsafe { - if let Some(draw_menu) = draw_menu { - draw_menu(ui); - } - }, - move |ui| unsafe { - if let Some(draw_windows) = draw_windows { - draw_windows(ui); - } - }, - gamepad_manager, - ) - .unwrap(); - - Box::new(gui) -} - -#[no_mangle] -pub extern "C" fn GUIStartFrame( - gui: Option<&mut Gui>, - event_loop: Option<&mut EventLoopWrapper>, -) -> Box> { - let gui = gui.unwrap(); - let event_loop = event_loop.unwrap(); - - match gui.start_frame(event_loop) { - Ok(frame) => Box::new(frame), - Err(_) => Box::new(None), - } -} - -#[no_mangle] -pub extern "C" fn GUIDrawLists( - gui: Option<&mut Gui>, - current_frame: Box>, - commands: u64, -) { - let gui = gui.unwrap(); - let current_frame = current_frame.unwrap(); - - gui.draw_lists(current_frame, commands.try_into().unwrap()) - .unwrap(); -} - -#[no_mangle] -pub extern "C" fn GUIEndFrame(gui: Option<&mut Gui>) { - let gui = gui.unwrap(); - gui.end_frame().unwrap(); -} - -#[no_mangle] -pub extern "C" fn GUIGetAspectRatio(gui: Option<&mut Gui>) -> f32 { - let gui = gui.unwrap(); - gui.rcp.rdp.output_dimensions.aspect_ratio -} diff --git a/src/gui/wgpu_renderer.rs b/src/gui/wgpu_renderer.rs new file mode 100644 index 0000000..f7a37d5 --- /dev/null +++ b/src/gui/wgpu_renderer.rs @@ -0,0 +1,326 @@ +use crate::gui::{EventLoopWrapper, Frame}; +use fast3d::output::RCPOutput; +use fast3d_wgpu_renderer::wgpu_device::WgpuGraphicsDevice; +use std::marker::PhantomData; + +pub struct Renderer<'a> { + window: winit::window::Window, + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + surface_config: wgpu::SurfaceConfiguration, + renderer: imgui_wgpu::Renderer, + graphics_device: WgpuGraphicsDevice, + current_frame_texture: Option, + phantom: PhantomData<&'a ()>, +} + +impl<'a> Renderer<'a> { + pub fn new( + width: i32, + height: i32, + title: &str, + event_loop_wrapper: &EventLoopWrapper, + imgui: &mut imgui::Context, + ) -> anyhow::Result { + // Setup WGPU instance + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }); + + // Create the window + let (window, size, surface) = { + let window = winit::window::WindowBuilder::new() + .with_title(title) + .with_inner_size(winit::dpi::LogicalSize::new(width, height)) + .with_resizable(true) + .build(&event_loop_wrapper.event_loop)?; + + let size = window.outer_size(); + + let surface = unsafe { instance.create_surface(&window) }?; + + (window, size, surface) + }; + + // Create the WGPU adapter + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + })) + .ok_or(anyhow::anyhow!("Failed to find an appropriate adapter"))?; + + // Create the WGPU device + let (device, queue) = pollster::block_on(adapter.request_device( + &wgpu::DeviceDescriptor { + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + label: None, + }, + None, + ))?; + + // Create the swapchain + let mut surface_config = surface + .get_default_config(&adapter, size.width, size.height) + .ok_or(anyhow::anyhow!("Failed to get default surface config"))?; + + let surface_view_format = surface_config.format.add_srgb_suffix(); + surface_config.view_formats.push(surface_view_format); + + surface.configure(&device, &surface_config); + + // Create Renderer + let renderer_config = imgui_wgpu::RendererConfig { + texture_format: surface_config.format, + ..Default::default() + }; + + let renderer = imgui_wgpu::Renderer::new(imgui, &device, &queue, renderer_config); + + // Create graphics device + let graphics_device = WgpuGraphicsDevice::new(&surface_config, &device); + + Ok(Self { + window, + surface, + device, + queue, + surface_config, + renderer, + graphics_device, + current_frame_texture: None, + phantom: PhantomData, + }) + } + + // Platform Functions + + pub fn attach_window( + &self, + platform: &mut imgui_winit_support::WinitPlatform, + imgui: &mut imgui::Context, + ) { + platform.attach_window( + imgui.io_mut(), + &self.window, + imgui_winit_support::HiDpiMode::Default, + ); + } + + pub fn handle_event( + &mut self, + platform: &mut imgui_winit_support::WinitPlatform, + imgui: &mut imgui::Context, + event: &winit::event::Event, + ) { + platform.handle_event(imgui.io_mut(), &self.window, event); + } + + pub fn prepare_frame( + &self, + platform: &mut imgui_winit_support::WinitPlatform, + imgui: &mut imgui::Context, + ) -> anyhow::Result<()> { + platform.prepare_frame(imgui.io_mut(), &self.window)?; + Ok(()) + } + + pub fn prepare_render( + &self, + platform: &mut imgui_winit_support::WinitPlatform, + ui: &mut imgui::Ui, + ) { + platform.prepare_render(ui, &self.window); + } + + // Rendering Functions + + pub fn window_size(&self) -> winit::dpi::PhysicalSize { + self.window.inner_size() + } + + pub fn resize(&mut self, width: u32, height: u32) { + // there's a bug where at first the size is u32::MAX so we just ignore it + if width == u32::MAX || height == u32::MAX { + return; + } + + log::trace!("Resizing to {:?}x{:?}", width, height); + + self.surface_config.width = width.max(1); + self.surface_config.height = height.max(1); + self.surface.configure(&self.device, &self.surface_config); + self.graphics_device + .resize(&self.surface_config, &self.device); + } + + pub fn get_current_texture(&mut self) -> Option { + let frame = match self.surface.get_current_texture() { + Ok(frame) => frame, + Err(e) => { + log::trace!("Dropped frame due to error: {:?}", e); + return None; + } + }; + + self.current_frame_texture = Some( + frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()), + ); + + Some(frame) + } + + pub fn process_rcp_output( + &mut self, + _frame: &mut Frame, + rcp_output: &mut RCPOutput, + ) -> anyhow::Result<()> { + // Prepare the context device + self.graphics_device.update_frame_count(); + + // Setup encoder that the RDP will use + let mut encoder: wgpu::CommandEncoder = + self.device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Game Draw Command Encoder"), + }); + + // Process the RCP output + self.render_game(&mut encoder, rcp_output)?; + + // Finish game encoding and submit + self.queue.submit(Some(encoder.finish())); + + Ok(()) + } + + pub fn draw_imgui_content( + &mut self, + _frame: &mut Frame, + draw_data: &imgui::DrawData, + ) -> anyhow::Result<()> { + // due to bug in macos or imgui-wgpu, we need to check for wrong texture size + let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0]; + let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1]; + if fb_width as u32 == u32::MAX || fb_height as u32 == u32::MAX { + return Ok(()); + } + + let mut encoder: wgpu::CommandEncoder = + self.device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("ImGui Command Encoder"), + }); + + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("ImGui Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &self.current_frame_texture.as_ref().unwrap(), + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + self.renderer + .render(draw_data, &self.queue, &self.device, &mut rpass)?; + + drop(rpass); + self.queue.submit(Some(encoder.finish())); + + Ok(()) + } + + pub fn finish_render(&mut self, frame: Frame) -> anyhow::Result<()> { + frame.present(); + self.current_frame_texture = None; + Ok(()) + } + + // MARK: - Helpers + + fn render_game( + &mut self, + encoder: &mut wgpu::CommandEncoder, + rcp_output: &mut RCPOutput, + ) -> anyhow::Result<()> { + // omit the last draw call, because we know we that's an extra from the last flush + // for draw_call in &self.rcp_output.draw_calls[..self.rcp_output.draw_calls.len() - 1] { + for (index, draw_call) in rcp_output + .draw_calls + .iter() + .take(rcp_output.draw_calls.len() - 1) + .enumerate() + { + assert!(!draw_call.vbo.vbo.is_empty()); + + self.graphics_device + .update_current_height(draw_call.viewport.w as i32); + + self.graphics_device.select_program( + &self.device, + draw_call.shader_id, + draw_call.shader_config, + ); + + // loop through textures and bind them + for (index, hash) in draw_call.texture_indices.iter().enumerate() { + if let Some(hash) = hash { + let texture = rcp_output.texture_cache.get_mut(*hash).unwrap(); + self.graphics_device + .bind_texture(&self.device, &self.queue, index, texture); + } + } + + // loop through samplers and bind them + for (index, sampler) in draw_call.samplers.iter().enumerate() { + if let Some(sampler) = sampler { + self.graphics_device + .bind_sampler(&self.device, index, sampler); + } + } + + // set uniforms + self.graphics_device.update_uniforms( + &self.queue, + draw_call.projection_matrix, + &draw_call.fog, + &draw_call.uniforms, + ); + + // create pipeline + let (texture_bind_group_layout, pipeline) = self.graphics_device.create_pipeline( + &self.device, + self.surface_config.format, + draw_call.blend_state, + draw_call.cull_mode, + draw_call.stencil, + ); + + // render triangles to texture + self.graphics_device.draw_triangles( + index, + &self.current_frame_texture.as_ref().unwrap(), + &self.device, + &self.queue, + encoder, + &pipeline, + &texture_bind_group_layout, + &draw_call.viewport, + draw_call.scissor, + &draw_call.vbo.vbo, + draw_call.vbo.num_tris, + ); + } + + Ok(()) + } +} diff --git a/src/gui/windows.rs b/src/gui/windows.rs index badf1a3..fd4010d 100644 --- a/src/gui/windows.rs +++ b/src/gui/windows.rs @@ -1,13 +1,8 @@ use imgui::{CollapsingHeader, Ui}; +use crate::gui::Gui; use fast3d::gbi::utils::{geometry_mode_uses_fog, geometry_mode_uses_lighting}; -#[cfg(feature = "opengl_renderer")] -use super::gui_glium::Gui; - -#[cfg(feature = "wgpu_renderer")] -use super::gui_wgpu::Gui; - pub trait HelixWindows { fn show_profiler_window(&self, opened: &mut bool, gui: &mut Gui); } diff --git a/src/lib.rs b/src/lib.rs index dcc0e2d..c591074 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ pub mod speech; pub use arie; // Check for invalid feature combinations -#[cfg(all(feature = "opengl", feature = "wgpu"))] +#[cfg(all(feature = "opengl_renderer", feature = "wgpu_renderer"))] compile_error!("Cannot enable both OpenGL and WGPU rendering"); pub fn init() {