summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/black.rs4
-rw-r--r--examples/bmp.rs10
-rw-r--r--examples/square.rs2
-rw-r--r--src/lib.rs2
-rw-r--r--src/renderer.rs128
-rw-r--r--src/texture.rs208
6 files changed, 223 insertions, 131 deletions
diff --git a/examples/black.rs b/examples/black.rs
index a4237a4..fc53ab1 100644
--- a/examples/black.rs
+++ b/examples/black.rs
@@ -8,8 +8,8 @@ fn main() {
// configure the render window
let config = RenderWindowConfig {
vsync: false,
- mode: alligator_render::config::WindowMode::BorderlessFullscreen,
- title: "Black Screen.exe",
+ //mode: alligator_render::config::WindowMode::BorderlessFullscreen,
+ //title: "Black Screen.exe",
..Default::default()
};
diff --git a/examples/bmp.rs b/examples/bmp.rs
index b527377..566e098 100644
--- a/examples/bmp.rs
+++ b/examples/bmp.rs
@@ -23,7 +23,7 @@ fn main() {
let mut renderer = Renderer::new(&config, &event_loop).unwrap();
let texture = renderer
- .texture_from_mem(texture, ImageFormat::Bmp)
+ .texture_from_memory(texture, ImageFormat::Bmp)
.unwrap();
let width = renderer.texture_width(texture).unwrap();
let height = renderer.texture_height(texture).unwrap();
@@ -31,6 +31,14 @@ fn main() {
let y = renderer.texture_y(texture).unwrap();
renderer.push_instance(Instance {
+ position: [-0.5, 0.0],
+ texture_size: [width, height],
+ texture_coordinates: [x, y],
+ ..Default::default()
+ });
+
+ renderer.push_instance(Instance {
+ position: [0.5, 0.0],
texture_size: [width, height],
texture_coordinates: [x, y],
..Default::default()
diff --git a/examples/square.rs b/examples/square.rs
index 7941fe1..1859647 100644
--- a/examples/square.rs
+++ b/examples/square.rs
@@ -19,7 +19,7 @@ fn main() {
let mut renderer = Renderer::new(&config, &event_loop).unwrap();
let texture = renderer
- .texture_from_mem(texture, ImageFormat::Ico)
+ .texture_from_memory(texture, ImageFormat::Ico)
.unwrap();
let width = renderer.texture_width(texture).unwrap();
let height = renderer.texture_height(texture).unwrap();
diff --git a/src/lib.rs b/src/lib.rs
index 8a4e8cf..2fdc59a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -17,5 +17,5 @@ pub use config::RenderWindowConfig;
pub use instance::Instance;
pub use renderer::Renderer;
pub use texture::ImageFormat;
-pub(crate) use texture::TextureAtlases;
+pub(crate) use texture::WgpuTextures;
pub(crate) use vertex::Vertex;
diff --git a/src/renderer.rs b/src/renderer.rs
index 516b1b1..2c26836 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -5,9 +5,8 @@ use crate::{
instance::InstanceId,
texture::{TextureError, TextureId},
vertex::SQUARE,
- Camera, ImageFormat, Instance, RenderWindowConfig, TextureAtlases, Vertex,
+ Camera, ImageFormat, Instance, RenderWindowConfig, Vertex, WgpuTextures,
};
-use image::{EncodableLayout, GenericImage};
use pollster::FutureExt;
use thiserror::Error;
use wgpu::{include_wgsl, util::DeviceExt};
@@ -60,11 +59,7 @@ pub struct Renderer {
camera: Camera,
camera_buffer: wgpu::Buffer,
camera_bind_group: wgpu::BindGroup,
- textures: TextureAtlases<'static>,
- texture_size: wgpu::Extent3d,
- diffuse_texture: wgpu::Texture,
- diffuse_bind_group: wgpu::BindGroup,
- image: image::RgbaImage,
+ textures: WgpuTextures,
window: Window,
}
@@ -248,70 +243,16 @@ impl Renderer {
Self::new_instance_buffer(&device, &instances);
// TODO make this configurable
- let textures = TextureAtlases::new(window.inner_size().width, window.inner_size().height);
- let texture_size = textures.extent_3d(); // textures.extent_3d();
- let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor {
- label: Some("diffuse texture"),
- size: texture_size,
- mip_level_count: 1,
- sample_count: 1,
- dimension: wgpu::TextureDimension::D2,
- format: wgpu::TextureFormat::Rgba8UnormSrgb,
- usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
- });
- // TODO I don't think this refreshes anything
- let diffuse_texture_view =
- diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());
- // TODO make this configurable
- let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
- let texture_bind_group_layout =
- device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- label: Some("Texture Bind Group Layout"),
- entries: &[
- wgpu::BindGroupLayoutEntry {
- binding: 0,
- visibility: wgpu::ShaderStages::FRAGMENT,
- ty: wgpu::BindingType::Texture {
- sample_type: wgpu::TextureSampleType::Float { filterable: true },
- view_dimension: wgpu::TextureViewDimension::D2,
- multisampled: false,
- },
- count: None,
- },
- wgpu::BindGroupLayoutEntry {
- binding: 1,
- visibility: wgpu::ShaderStages::FRAGMENT,
- ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
- count: None,
- },
- ],
- });
- let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("Diffuse Bind Group"),
- layout: &texture_bind_group_layout,
- entries: &[
- wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::TextureView(&diffuse_texture_view),
- },
- wgpu::BindGroupEntry {
- binding: 1,
- resource: wgpu::BindingResource::Sampler(&diffuse_sampler),
- },
- ],
- });
- // TODO make this all resizable
- let image = image::RgbaImage::from_vec(
- texture_size.width,
- texture_size.height,
- vec![0; 4 * texture_size.width as usize * texture_size.height as usize],
- )
- .unwrap();
+ let (textures, texture_layout) = WgpuTextures::new(
+ &device,
+ window.inner_size().width,
+ window.inner_size().height,
+ );
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Sprite Render Pipeline Layout"),
- bind_group_layouts: &[&camera_bind_group_layout, &texture_bind_group_layout],
+ bind_group_layouts: &[&camera_bind_group_layout, &texture_layout],
push_constant_ranges: &[],
});
@@ -335,10 +276,6 @@ impl Renderer {
camera_buffer,
camera_bind_group,
textures,
- texture_size,
- diffuse_texture,
- diffuse_bind_group,
- image,
window,
})
}
@@ -418,36 +355,28 @@ impl Renderer {
///
/// This returns an error if the texture is not in the given format, or if
/// the texture is so large that it cannot fit in the texture atlas.
- pub fn texture_from_mem(
+ pub fn texture_from_memory(
&mut self,
texture: &[u8],
format: ImageFormat,
) -> Result<TextureId, TextureError> {
- self.textures.load_from_memory(texture, format)
+ self.textures.texture_from_memory(texture, format)
}
pub fn texture_width(&self, id: TextureId) -> Option<f32> {
- self.textures
- .texture_width(id)
- .map(|u| u as f32 / self.texture_size.width as f32)
+ self.textures.texture_width(id)
}
pub fn texture_height(&self, id: TextureId) -> Option<f32> {
- self.textures
- .texture_height(id)
- .map(|u| u as f32 / self.texture_size.height as f32)
+ self.textures.texture_height(id)
}
pub fn texture_x(&self, id: TextureId) -> Option<f32> {
- self.textures
- .texture_x(id)
- .map(|u| u as f32 / self.texture_size.width as f32)
+ self.textures.texture_x(id)
}
pub fn texture_y(&self, id: TextureId) -> Option<f32> {
- self.textures
- .texture_y(id)
- .map(|u| u as f32 / self.texture_size.height as f32)
+ self.textures.texture_y(id)
}
fn expand_instance_buffer(&mut self) {
@@ -475,31 +404,6 @@ impl Renderer {
);
}
- // TODO optimize this
- fn fill_textures(&mut self) {
- // put the packed texture into the base image
- let Ok(atlases) = self.textures.atlases() else { return };
- let Some(atlas) = atlases.first() else { return };
- self.image.copy_from(atlas, 0, 0).unwrap();
-
- // copy that to the gpu
- self.queue.write_texture(
- wgpu::ImageCopyTexture {
- texture: &self.diffuse_texture,
- mip_level: 0,
- origin: wgpu::Origin3d::ZERO,
- aspect: wgpu::TextureAspect::All,
- },
- self.image.as_bytes(),
- wgpu::ImageDataLayout {
- offset: 0,
- bytes_per_row: NonZeroU32::new(self.texture_size.width * 4),
- rows_per_image: NonZeroU32::new(self.texture_size.height),
- },
- self.texture_size,
- );
- }
-
/// Renders a new frame to the window
///
/// # Errors
@@ -530,7 +434,7 @@ impl Renderer {
.expect("expected less than 3 billion instances");
self.refresh_camera_buffer();
- self.fill_textures();
+ self.textures.fill_textures(&self.queue);
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -548,7 +452,7 @@ impl Renderer {
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
- render_pass.set_bind_group(1, &self.diffuse_bind_group, &[]);
+ render_pass.set_bind_group(1, self.textures.bind_group(), &[]);
render_pass.set_vertex_buffer(0, self.square_vertex_buffer.slice(..));
render_pass.set_vertex_buffer(1, self.instance_buffer.slice(..));
render_pass.draw(0..self.square_vertices, 0..num_instances);
diff --git a/src/texture.rs b/src/texture.rs
index f5d1c34..4ba460d 100644
--- a/src/texture.rs
+++ b/src/texture.rs
@@ -1,8 +1,9 @@
use std::error::Error;
+use std::num::NonZeroU32;
use std::sync::atomic::{AtomicUsize, Ordering};
use image::error::DecodingError;
-use image::{DynamicImage, ImageError};
+use image::{DynamicImage, EncodableLayout, GenericImage, ImageError, RgbaImage};
use texture_packer::{
exporter::{ExportResult, ImageExporter},
MultiTexturePacker, TexturePackerConfig,
@@ -60,11 +61,12 @@ impl From<ImageError> for TextureError {
}
// TODO make this Debug
+// TODO make these resizable
pub struct TextureAtlases<'a> {
packer: MultiTexturePacker<'a, image::RgbaImage, TextureId>,
+ images: Vec<RgbaImage>,
width: u32,
height: u32,
- changed: bool,
}
impl<'a> Default for TextureAtlases<'a> {
@@ -86,7 +88,7 @@ impl<'a> TextureAtlases<'a> {
}),
width,
height,
- changed: false,
+ images: Vec::with_capacity(1),
}
}
@@ -101,7 +103,6 @@ impl<'a> TextureAtlases<'a> {
self.packer
.pack_own(id, img)
.map_err(TextureError::TextureTooLarge)?;
- self.changed = true;
Ok(id)
}
@@ -134,15 +135,7 @@ impl<'a> TextureAtlases<'a> {
Some(frame.frame.y)
}
- pub(crate) const fn bytes_per_row(&self) -> u32 {
- self.width * 4
- }
-
- pub(crate) const fn height(&self) -> u32 {
- self.height
- }
-
- pub(crate) const fn extent_3d(&self) -> wgpu::Extent3d {
+ const fn extent_3d(&self) -> wgpu::Extent3d {
wgpu::Extent3d {
width: self.width,
height: self.height,
@@ -150,7 +143,7 @@ impl<'a> TextureAtlases<'a> {
}
}
- pub(crate) fn atlases(&self) -> ExportResult<Box<[DynamicImage]>> {
+ fn atlases(&self) -> ExportResult<Box<[DynamicImage]>> {
self.packer
.get_pages()
.iter()
@@ -158,4 +151,191 @@ impl<'a> TextureAtlases<'a> {
.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");
+ }
+ }
+
+ Ok(())
+ }
+
+ fn images(&mut self) -> ExportResult<&[RgbaImage]> {
+ self.fill_images()?;
+ Ok(&self.images)
+ }
+}
+
+pub struct WgpuTextures {
+ atlases: TextureAtlases<'static>,
+ diffuse_texture: wgpu::Texture,
+ diffuse_bind_group: wgpu::BindGroup,
+}
+
+impl WgpuTextures {
+ // TODO this is still too large
+ pub fn new(device: &wgpu::Device, width: u32, height: u32) -> (Self, wgpu::BindGroupLayout) {
+ let atlases = TextureAtlases::new(width, height);
+ let atlas_size = atlases.extent_3d();
+
+ let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor {
+ label: Some("Diffuse Texture"),
+ size: atlas_size,
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Rgba8UnormSrgb,
+ usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
+ });
+
+ // TODO I don't think this refreshes anything
+ let diffuse_texture_view =
+ diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+ let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
+
+ let texture_bind_group_layout =
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ label: Some("Texture Bind Group Layout"),
+ entries: &[
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float { filterable: true },
+ view_dimension: wgpu::TextureViewDimension::D2,
+ multisampled: false,
+ },
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 1,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
+ count: None,
+ },
+ ],
+ });
+
+ let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("Diffuse Bind Group"),
+ layout: &texture_bind_group_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(&diffuse_texture_view),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::Sampler(&diffuse_sampler),
+ },
+ ],
+ });
+
+ (
+ Self {
+ atlases,
+ diffuse_texture,
+ diffuse_bind_group,
+ },
+ texture_bind_group_layout,
+ )
+ }
+
+ /// Loads a texture from memory, in the given file format
+ ///
+ /// # Errors
+ ///
+ /// This returns an error if the texture is not in the given format, or if
+ /// the texture is so large that it cannot fit in the texture atlas.
+ pub fn texture_from_memory(
+ &mut self,
+ texture: &[u8],
+ format: ImageFormat,
+ ) -> Result<TextureId, TextureError> {
+ self.atlases.load_from_memory(texture, format)
+ }
+
+ // TODO try to remove this
+ #[allow(clippy::cast_precision_loss)]
+ pub fn texture_width(&self, id: TextureId) -> Option<f32> {
+ self.atlases
+ .texture_width(id)
+ .map(|u| u as f32 / self.atlases.extent_3d().width as f32)
+ }
+
+ #[allow(clippy::cast_precision_loss)]
+ pub fn texture_height(&self, id: TextureId) -> Option<f32> {
+ self.atlases
+ .texture_height(id)
+ .map(|u| u as f32 / self.atlases.extent_3d().height as f32)
+ }
+
+ #[allow(clippy::cast_precision_loss)]
+ pub fn texture_x(&self, id: TextureId) -> Option<f32> {
+ self.atlases
+ .texture_x(id)
+ .map(|u| u as f32 / self.atlases.extent_3d().width as f32)
+ }
+
+ #[allow(clippy::cast_precision_loss)]
+ pub fn texture_y(&self, id: TextureId) -> Option<f32> {
+ self.atlases
+ .texture_y(id)
+ .map(|u| u as f32 / self.atlases.extent_3d().height as f32)
+ }
+
+ pub const fn bind_group(&self) -> &wgpu::BindGroup {
+ &self.diffuse_bind_group
+ }
+
+ pub fn fill_textures(&mut self, queue: &wgpu::Queue) {
+ 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 };
+
+ // copy that to the gpu
+ queue.write_texture(
+ wgpu::ImageCopyTexture {
+ texture: &self.diffuse_texture,
+ mip_level: 0,
+ origin: wgpu::Origin3d::ZERO,
+ aspect: wgpu::TextureAspect::All,
+ },
+ atlas.as_bytes(),
+ wgpu::ImageDataLayout {
+ offset: 0,
+ bytes_per_row: NonZeroU32::new(atlas_size.width * 4),
+ rows_per_image: NonZeroU32::new(atlas_size.height),
+ },
+ atlas_size,
+ );
+ }
}