summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml7
-rw-r--r--examples/black.rs4
-rw-r--r--examples/bmp.rs4
-rw-r--r--src/camera.rs1
-rw-r--r--src/config.rs15
-rw-r--r--src/renderer.rs27
-rw-r--r--src/texture.rs98
7 files changed, 90 insertions, 66 deletions
diff --git a/Cargo.toml b/Cargo.toml
index a3fe21e..935d493 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,13 +8,18 @@ edition = "2021"
[dependencies]
winit = "0.27"
log = "0.4"
-wgpu = "0.13"
+wgpu = "0.14"
thiserror = "1"
pollster = "0.2"
bytemuck = { version = "1.4", features = ["derive"] }
cgmath = "0.18"
image = "0.24"
texture_packer = "0.24"
+profiling = "1"
+tracy-client = "0.14"
+
+[features]
+profile-with-tracy = ["profiling/profile-with-tracy"]
[[example]]
name = "black"
diff --git a/examples/black.rs b/examples/black.rs
index fc53ab1..291fafb 100644
--- a/examples/black.rs
+++ b/examples/black.rs
@@ -7,9 +7,9 @@ fn main() {
let start = std::time::Instant::now();
// configure the render window
let config = RenderWindowConfig {
- vsync: false,
+ //vsync: false,
//mode: alligator_render::config::WindowMode::BorderlessFullscreen,
- //title: "Black Screen.exe",
+ title: "Black Screen.exe",
..Default::default()
};
diff --git a/examples/bmp.rs b/examples/bmp.rs
index 566e098..98561ba 100644
--- a/examples/bmp.rs
+++ b/examples/bmp.rs
@@ -9,7 +9,7 @@ fn main() {
// configure the render window
let config = RenderWindowConfig {
title: "Bumper Sticker",
- instance_capacity: 1,
+ instance_capacity: 2,
default_width: NonZeroU32::new(1280).unwrap(),
default_height: NonZeroU32::new(720).unwrap(),
//mode: alligator_render::config::WindowMode::BorderlessFullscreen,
@@ -32,6 +32,7 @@ fn main() {
renderer.push_instance(Instance {
position: [-0.5, 0.0],
+ size: [0.75; 2],
texture_size: [width, height],
texture_coordinates: [x, y],
..Default::default()
@@ -39,6 +40,7 @@ fn main() {
renderer.push_instance(Instance {
position: [0.5, 0.0],
+ size: [0.75; 2],
texture_size: [width, height],
texture_coordinates: [x, y],
..Default::default()
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<Self, NewRendererError> {
+ #[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<ImageError> 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<RgbaImage>,
+ 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<TextureId>> {
- 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<Box<[DynamicImage]>> {
- self.packer
- .get_pages()
- .iter()
- .map(ImageExporter::export)
- .collect::<ExportResult<Vec<DynamicImage>>>()
- .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<TextureId, TextureError> {
+ 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();
}
}