From 8e074b9f4e01a1174fe19d10f89f4153c0dde344 Mon Sep 17 00:00:00 2001 From: David Chavez Date: Tue, 11 Jul 2023 00:02:46 +0200 Subject: [PATCH] feature(wgpu): WGPU renderer (#31) --- Cargo.toml | 11 +- src/gui.rs | 19 +-- src/gui/glium_renderer.rs | 31 ++--- src/gui/wgpu_renderer.rs | 235 ++++++++++++++++---------------------- src/gui/windows.rs | 2 +- 5 files changed, 136 insertions(+), 162 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bf2af80..5a4945a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ name = "helix" crate-type = ["lib", "staticlib"] [features] -default = ["f3dex2", "opengl_renderer"] +default = ["f3dex2", "wgpu_renderer"] # Graphics Features f3dex2 = ["fast3d/f3dex2"] @@ -31,7 +31,7 @@ env_logger = "0.10.0" gilrs = { version = "0.10.2", features = ["serde", "serde-serialize"] } glium = { version = "0.32.1", optional = true } imgui = { version = "0.11.0", features = ["docking"] } -imgui-wgpu = { git = "https://github.com/retrofoundry/imgui-wgpu-rs.git", branch = "helix", optional = true } +imgui-wgpu = { git = "https://github.com/Yatekii/imgui-wgpu-rs.git", optional = true } imgui-winit-support = "0.11.0" imgui-glium-renderer = { version = "0.11.0", optional = true } winit = { version = "0.27.5", features = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] } @@ -44,9 +44,10 @@ 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.1", default-features = false } -fast3d-glium-renderer = { version = "0.3.1", optional = true } -fast3d-wgpu-renderer = { version = "0.3.1", optional = true } +fast3d = { version = "0.4.0", default-features = false } +fast3d-glium-renderer = { version = "0.4.0", optional = true } +fast3d-wgpu-renderer = { version = "0.4.0", optional = true } +rustc-hash = "1.1.0" [patch.crates-io] #fast3d = { path = "../../fast3d-rs/fast3d" } diff --git a/src/gui.rs b/src/gui.rs index 1cb3b29..9f055e6 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -40,6 +40,12 @@ impl EventLoopWrapper { } } +impl Default for EventLoopWrapper { + fn default() -> Self { + Self::new() + } +} + pub struct Gui<'a> { // imgui imgui: imgui::Context, @@ -210,7 +216,7 @@ impl<'a> Gui<'a> { pub fn process_draw_lists(&mut self, commands: usize) -> anyhow::Result<()> { // Set RDP output dimensions - let size = self.gfx_renderer.window_size(); + let size = self.gfx_renderer.content_size(); let dimensions = OutputDimensions { width: size.width, height: size.height, @@ -224,11 +230,7 @@ impl<'a> Gui<'a> { // 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 + // Draw the UI let ui = self.imgui.new_frame(); ui.main_menu_bar(|| (self.draw_menu_callback)(ui)); (self.draw_windows_callback)(ui); @@ -238,9 +240,10 @@ impl<'a> Gui<'a> { self.gfx_renderer.prepare_render(&mut self.platform, ui); } + // Render RCPOutput and ImGui content let draw_data = self.imgui.render(); self.gfx_renderer - .draw_imgui_content(&mut frame, draw_data)?; + .draw_content(&mut frame, &mut self.rcp_output, draw_data)?; // Clear the draw calls self.rcp_output.clear_draw_calls(); @@ -262,7 +265,7 @@ type OnDraw = unsafe extern "C" fn(ui: &imgui::Ui); #[no_mangle] pub extern "C" fn GUICreateEventLoop() -> Box { - let event_loop = EventLoopWrapper::new(); + let event_loop = EventLoopWrapper::default(); Box::new(event_loop) } diff --git a/src/gui/glium_renderer.rs b/src/gui/glium_renderer.rs index 833b182..586a10d 100644 --- a/src/gui/glium_renderer.rs +++ b/src/gui/glium_renderer.rs @@ -1,5 +1,6 @@ use crate::gui::{EventLoopWrapper, Frame}; use fast3d::output::RCPOutput; +use fast3d::rdp::OutputDimensions; use fast3d_glium_renderer::glium_device::GliumGraphicsDevice; pub struct Renderer<'a> { @@ -31,10 +32,14 @@ impl<'a> Renderer<'a> { // Create the renderer let renderer = imgui_glium_renderer::Renderer::init(imgui, &display)?; + // Create graphics device + let size = display.gl_window().window().inner_size(); + let graphics_device = GliumGraphicsDevice::new([size.width, size.height]); + Ok(Self { display, renderer, - graphics_device: GliumGraphicsDevice::default(), + graphics_device, }) } @@ -80,15 +85,21 @@ impl<'a> Renderer<'a> { // Rendering Functions - pub fn window_size(&self) -> winit::dpi::PhysicalSize { + pub fn content_size(&self) -> winit::dpi::PhysicalSize { self.display.gl_window().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.display .gl_window() .resize(glutin::dpi::PhysicalSize::new(width, height)); + self.graphics_device.resize([width, height]); } pub fn get_current_texture(&self) -> Option { @@ -96,10 +107,11 @@ impl<'a> Renderer<'a> { Some(frame) } - pub fn process_rcp_output( + pub fn draw_content( &mut self, frame: &mut Frame, rcp_output: &mut RCPOutput, + imgui_draw_data: &imgui::DrawData, ) -> anyhow::Result<()> { // Prepare the context device self.graphics_device.start_frame(frame); @@ -107,21 +119,12 @@ impl<'a> Renderer<'a> { // Process the RCP output self.render_game(frame, rcp_output)?; - // Finish rendering - self.graphics_device.end_frame(); + // Render the ImGui content + self.renderer.render(frame, imgui_draw_data)?; 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(()) diff --git a/src/gui/wgpu_renderer.rs b/src/gui/wgpu_renderer.rs index f7a37d5..ea1f5d6 100644 --- a/src/gui/wgpu_renderer.rs +++ b/src/gui/wgpu_renderer.rs @@ -1,7 +1,31 @@ use crate::gui::{EventLoopWrapper, Frame}; use fast3d::output::RCPOutput; + use fast3d_wgpu_renderer::wgpu_device::WgpuGraphicsDevice; -use std::marker::PhantomData; + +const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + +fn create_depth_texture( + config: &wgpu::SurfaceConfiguration, + device: &wgpu::Device, +) -> wgpu::TextureView { + let depth_texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: DEPTH_FORMAT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + label: None, + view_formats: &[], + }); + + depth_texture.create_view(&wgpu::TextureViewDescriptor::default()) +} pub struct Renderer<'a> { window: winit::window::Window, @@ -9,10 +33,9 @@ pub struct Renderer<'a> { device: wgpu::Device, queue: wgpu::Queue, surface_config: wgpu::SurfaceConfiguration, + depth_texture: wgpu::TextureView, renderer: imgui_wgpu::Renderer, - graphics_device: WgpuGraphicsDevice, - current_frame_texture: Option, - phantom: PhantomData<&'a ()>, + graphics_device: WgpuGraphicsDevice<'a>, } impl<'a> Renderer<'a> { @@ -37,7 +60,7 @@ impl<'a> Renderer<'a> { .with_resizable(true) .build(&event_loop_wrapper.event_loop)?; - let size = window.outer_size(); + let size = window.inner_size(); let surface = unsafe { instance.create_surface(&window) }?; @@ -66,22 +89,24 @@ impl<'a> Renderer<'a> { 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_config.format = wgpu::TextureFormat::Bgra8Unorm; surface.configure(&device, &surface_config); + // Create the depth texture + let depth_texture = create_depth_texture(&surface_config, &device); + // Create Renderer let renderer_config = imgui_wgpu::RendererConfig { texture_format: surface_config.format, + fragment_shader_entry_point: Some("fs_main_srgb"), ..Default::default() }; let renderer = imgui_wgpu::Renderer::new(imgui, &device, &queue, renderer_config); // Create graphics device - let graphics_device = WgpuGraphicsDevice::new(&surface_config, &device); + let graphics_device = WgpuGraphicsDevice::new(&device, [size.width, size.height]); Ok(Self { window, @@ -89,10 +114,9 @@ impl<'a> Renderer<'a> { device, queue, surface_config, + depth_texture, renderer, graphics_device, - current_frame_texture: None, - phantom: PhantomData, }) } @@ -138,7 +162,7 @@ impl<'a> Renderer<'a> { // Rendering Functions - pub fn window_size(&self) -> winit::dpi::PhysicalSize { + pub fn content_size(&self) -> winit::dpi::PhysicalSize { self.window.inner_size() } @@ -153,8 +177,8 @@ impl<'a> Renderer<'a> { 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); + self.depth_texture = create_depth_texture(&self.surface_config, &self.device); + self.graphics_device.resize([width, height]); } pub fn get_current_texture(&mut self) -> Option { @@ -166,47 +190,67 @@ impl<'a> Renderer<'a> { } }; - self.current_frame_texture = Some( - frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()), - ); - Some(frame) } - pub fn process_rcp_output( + pub fn draw_content( &mut self, - _frame: &mut Frame, + frame: &mut Frame, rcp_output: &mut RCPOutput, + imgui_draw_data: &imgui::DrawData, ) -> anyhow::Result<()> { + let frame_texture = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + // Prepare the context device self.graphics_device.update_frame_count(); - // Setup encoder that the RDP will use + // Process the RCP output + self.graphics_device.process_rcp_output( + &self.device, + &self.queue, + self.surface_config.format, + rcp_output, + ); + let mut encoder: wgpu::CommandEncoder = self.device .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Game Draw Command Encoder"), + label: Some("Game Render Pass Command Encoder"), }); - // Process the RCP output - self.render_game(&mut encoder, rcp_output)?; + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Game Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &frame_texture, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), + store: true, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_texture, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), + }); + + // Draw the RCP output + self.graphics_device.draw(&mut rpass); + } - // Finish game encoding and submit + // Finish 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]; + let fb_width = imgui_draw_data.display_size[0] * imgui_draw_data.framebuffer_scale[0]; + let fb_height = imgui_draw_data.display_size[1] * imgui_draw_data.framebuffer_scale[1]; if fb_width as u32 == u32::MAX || fb_height as u32 == u32::MAX { return Ok(()); } @@ -214,26 +258,29 @@ impl<'a> Renderer<'a> { let mut encoder: wgpu::CommandEncoder = self.device .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("ImGui Command Encoder"), + label: Some("ImGui Render Pass 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)?; + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("ImGui Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &frame_texture, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + // Render the ImGui content + self.renderer + .render(imgui_draw_data, &self.queue, &self.device, &mut rpass)?; + } - drop(rpass); + // Finish encoding and submit self.queue.submit(Some(encoder.finish())); Ok(()) @@ -241,86 +288,6 @@ impl<'a> Renderer<'a> { 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 fd4010d..c0e33b4 100644 --- a/src/gui/windows.rs +++ b/src/gui/windows.rs @@ -122,5 +122,5 @@ pub extern "C" fn GUIShowProfilerWindow(ui: &Ui, gui: Option<&mut Gui>, opened: return; } - ui.show_profiler_window(opened as &mut bool, gui); + ui.show_profiler_window(opened, gui); }