From 76ed4fe2d3f96c1c25905875a0d5dacc5ff7ed8a Mon Sep 17 00:00:00 2001 From: Micha White Date: Tue, 11 Oct 2022 21:42:10 -0400 Subject: Big performance improvement --- src/camera.rs | 1 + src/config.rs | 15 +++++++-- src/renderer.rs | 27 +++++++++++++--- src/texture.rs | 98 +++++++++++++++++++++++++-------------------------------- 4 files changed, 79 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/src/camera.rs b/src/camera.rs index 65d1d43..a9bfa9a 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -169,6 +169,7 @@ impl Camera { &self.bind_group } + #[profiling::function] pub(crate) fn refresh(&self, queue: &wgpu::Queue) { queue.write_buffer( &self.buffer, diff --git a/src/config.rs b/src/config.rs index 7eedb21..9376049 100644 --- a/src/config.rs +++ b/src/config.rs @@ -101,6 +101,14 @@ impl<'a> RenderWindowConfig<'a> { } } + fn alpha_mode(supported_modes: &[wgpu::CompositeAlphaMode]) -> wgpu::CompositeAlphaMode { + if supported_modes.contains(&wgpu::CompositeAlphaMode::PostMultiplied) { + wgpu::CompositeAlphaMode::PostMultiplied + } else { + wgpu::CompositeAlphaMode::Auto + } + } + /// Create a `WindowBuilder` from the configuration given. This window is /// initially invisible and must later be made visible. pub(crate) fn to_window(&self) -> WindowBuilder { @@ -154,10 +162,12 @@ impl<'a> RenderWindowConfig<'a> { /// Gets a surface configuration out of the config. pub(crate) fn to_surface_configuration( &self, - supported_modes: &[wgpu::PresentMode], + supported_present_modes: &[wgpu::PresentMode], + supported_alpha_modes: &[wgpu::CompositeAlphaMode], texture_format: wgpu::TextureFormat, ) -> wgpu::SurfaceConfiguration { - let present_mode = Self::present_mode(self.vsync, supported_modes); + let present_mode = Self::present_mode(self.vsync, supported_present_modes); + let alpha_mode = Self::alpha_mode(supported_alpha_modes); // configuration for the surface wgpu::SurfaceConfiguration { @@ -165,6 +175,7 @@ impl<'a> RenderWindowConfig<'a> { format: texture_format, width: self.default_width.get(), height: self.default_height.get(), + alpha_mode, present_mode, } } diff --git a/src/renderer.rs b/src/renderer.rs index 215dda8..e48f3e4 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -153,11 +153,15 @@ impl Renderer { config: &RenderWindowConfig, event_loop: &EventLoop<()>, ) -> Result { + #[cfg(feature = "profile-with-tracy")] + profiling::tracy_client::Client::start(); + profiling::register_thread!("main"); + // build the window let window = config.to_window().build(event_loop)?; // the instance's main purpose is to create an adapter and a surface - let instance = wgpu::Instance::new(wgpu::Backends::all()); + let instance = wgpu::Instance::new(wgpu::Backends::VULKAN); // the surface is the part of the screen we'll draw to let surface = unsafe { instance.create_surface(&window) }; @@ -181,9 +185,15 @@ impl Renderer { .expect("there was no device with the selected features"); // configuration for the surface - let supported_present_modes = surface.get_supported_modes(&adapter).into_boxed_slice(); + let supported_present_modes = surface + .get_supported_present_modes(&adapter) + .into_boxed_slice(); + let supported_alpha_modes = surface + .get_supported_alpha_modes(&adapter) + .into_boxed_slice(); let surface_config = config.to_surface_configuration( - &surface.get_supported_modes(&adapter), + &supported_present_modes, + &supported_alpha_modes, surface.get_supported_formats(&adapter)[0], ); surface.configure(&device, &surface_config); @@ -344,11 +354,16 @@ impl Renderer { self.textures.texture_y(id) } + pub fn clear_textures(&mut self) { + self.textures.clear_textures(); + } + fn expand_instance_buffer(&mut self) { (self.instance_buffer, self.instance_buffer_size) = Self::new_instance_buffer(&self.device, &self.instances); } + #[profiling::function] fn fill_instance_buffer(&mut self) { if self.instances.len() > self.instance_buffer_size { self.expand_instance_buffer(); @@ -369,6 +384,7 @@ impl Renderer { /// trying to acquire the next frame. There may also be no more memory left /// that can be used for the new frame. // TODO this is too big + #[profiling::function] fn render(&mut self) -> Result<(), wgpu::SurfaceError> { // the new texture we can render to let output = self.surface.get_current_texture()?; @@ -394,6 +410,7 @@ impl Renderer { self.textures.fill_textures(&self.queue); { + profiling::scope!("encode render pass"); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { @@ -418,6 +435,7 @@ impl Renderer { // render pass is dropped // submit the command buffer to the GPU + profiling::scope!("submit render"); self.queue.submit(std::iter::once(encoder.finish())); output.present(); @@ -450,7 +468,8 @@ impl Renderer { } // otherwise, we'll just log the error Err(e) => log::error!("{}", e), - } + }; + profiling::finish_frame!(); } _ => {} }) diff --git a/src/texture.rs b/src/texture.rs index 457fea8..a3e2c10 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -3,10 +3,11 @@ use std::num::NonZeroU32; use std::sync::atomic::{AtomicUsize, Ordering}; use image::error::DecodingError; -use image::{DynamicImage, EncodableLayout, GenericImage, ImageError, RgbaImage}; +use image::{EncodableLayout, GenericImage, ImageError, RgbaImage}; +use texture_packer::TexturePacker; use texture_packer::{ exporter::{ExportResult, ImageExporter}, - MultiTexturePacker, TexturePackerConfig, + TexturePackerConfig, }; use thiserror::Error; @@ -64,8 +65,8 @@ impl From for TextureError { // TODO make these resizable // TODO this could probably be moved into WgpuTextures pub struct TextureAtlases<'a> { - packer: MultiTexturePacker<'a, image::RgbaImage, TextureId>, - images: Vec, + packer: TexturePacker<'a, image::RgbaImage, TextureId>, + image: RgbaImage, width: u32, height: u32, } @@ -90,14 +91,19 @@ impl<'a> TextureAtlases<'a> { // TODO why is this u32? pub fn new(width: u32, height: u32) -> Self { Self { - packer: MultiTexturePacker::new_skyline(TexturePackerConfig { + packer: TexturePacker::new_skyline(TexturePackerConfig { max_width: width, max_height: height, ..Default::default() }), width, height, - images: Vec::with_capacity(1), + image: RgbaImage::from_raw( + width, + height, + vec![0; 4 * width as usize * height as usize], + ) + .unwrap(), } } @@ -117,11 +123,7 @@ impl<'a> TextureAtlases<'a> { } fn texture_frame(&self, id: TextureId) -> Option<&texture_packer::Frame> { - self.packer - .get_pages() - .iter() - .map(|a| a.get_frame(&id)) - .next()? + self.packer.get_frame(&id) } texture_info!(texture_width, w); @@ -137,59 +139,29 @@ impl<'a> TextureAtlases<'a> { } } - fn atlases(&self) -> ExportResult> { - self.packer - .get_pages() - .iter() - .map(ImageExporter::export) - .collect::>>() - .map(Vec::into_boxed_slice) - } - - fn push_image(&mut self) -> &mut RgbaImage { - self.images.push( - RgbaImage::from_raw( - self.width, - self.height, - vec![0; 4 * self.width as usize * self.height as usize], - ) - .expect("the image was the wrong size"), - ); - - self.images - .last_mut() - .expect("we just added an image to the list") - } - - fn fill_images(&mut self) -> ExportResult<()> { - for (i, atlas) in self.atlases()?.iter().enumerate() { - #[allow(clippy::option_if_let_else)] - if let Some(image) = self.images.get_mut(i) { - image - .copy_from(atlas, 0, 0) - .expect("atlases shouldn't be too large"); - } else { - let image = self.push_image(); - image - .copy_from(atlas, 0, 0) - .expect("atlases shouldn't be too large"); - } - } - + fn fill_image(&mut self) -> ExportResult<()> { + let atlas = { + profiling::scope!("export atlas"); + ImageExporter::export(&self.packer)? + }; + profiling::scope!("copy image"); + self.image + .copy_from(&atlas, 0, 0) + .expect("image cache is too small"); Ok(()) } fn clear(&mut self) { - self.packer = MultiTexturePacker::new_skyline(TexturePackerConfig { + self.packer = TexturePacker::new_skyline(TexturePackerConfig { max_width: self.width, max_height: self.height, ..Default::default() }); } - fn images(&mut self) -> ExportResult<&[RgbaImage]> { - self.fill_images()?; - Ok(&self.images) + fn image(&mut self) -> ExportResult<&RgbaImage> { + self.fill_image()?; + Ok(&self.image) } } @@ -197,6 +169,7 @@ pub struct WgpuTextures { atlases: TextureAtlases<'static>, diffuse_texture: wgpu::Texture, diffuse_bind_group: wgpu::BindGroup, + changed: bool, } macro_rules! get_info { @@ -276,6 +249,7 @@ impl WgpuTextures { atlases, diffuse_texture, diffuse_bind_group, + changed: true, }, texture_bind_group_layout, ) @@ -292,6 +266,7 @@ impl WgpuTextures { texture: &[u8], format: ImageFormat, ) -> Result { + self.changed = true; self.atlases.load_from_memory(texture, format) } @@ -304,12 +279,20 @@ impl WgpuTextures { &self.diffuse_bind_group } + #[profiling::function] pub fn fill_textures(&mut self, queue: &wgpu::Queue) { + // saves time if nothing changed since the last time we did this + // FIXME This doesn't do much good once we get procedurally generated animation + // We'll have to create our own texture packer, with mutable subtextures, + // and more efficient algorithms. This'll also make frame times more consistent + if !self.changed { + return; + } + let atlas_size = self.atlases.extent_3d(); // put the packed texture into the base image - let Ok(atlases) = self.atlases.images() else { return }; - let Some(atlas) = atlases.first() else { return }; + let Ok(atlas) = self.atlases.image() else { return }; // copy that to the gpu queue.write_texture( @@ -327,9 +310,12 @@ impl WgpuTextures { }, atlas_size, ); + + self.changed = false; } pub fn clear_textures(&mut self) { + self.changed = true; self.atlases.clear(); } } -- cgit v1.2.3