summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMicha White <botahamec@outlook.com>2022-10-02 13:49:47 -0400
committerMicha White <botahamec@outlook.com>2022-10-02 13:49:47 -0400
commit511d3873f5f567c97eecd69d186bb4f93f927d58 (patch)
treeb99779ade2b150d51d800b7275a0c310a7591439 /src
parent39e36dd10cd7a335897e66e0f613d0191e7f9eba (diff)
Hacked in textures
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs2
-rw-r--r--src/renderer.rs201
-rw-r--r--src/texture.rs59
3 files changed, 226 insertions, 36 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 1453ef0..8a4e8cf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,4 +16,6 @@ pub(crate) use camera::Camera;
pub use config::RenderWindowConfig;
pub use instance::Instance;
pub use renderer::Renderer;
+pub use texture::ImageFormat;
+pub(crate) use texture::TextureAtlases;
pub(crate) use vertex::Vertex;
diff --git a/src/renderer.rs b/src/renderer.rs
index d45233b..14a7b6a 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -1,9 +1,13 @@
use std::{convert::TryInto, mem::size_of, num::NonZeroU32};
use crate::{
- camera::CameraUniform, instance::InstanceId, vertex::SQUARE, Camera, Instance,
- RenderWindowConfig, Vertex,
+ camera::CameraUniform,
+ instance::InstanceId,
+ texture::{TextureError, TextureId},
+ vertex::SQUARE,
+ Camera, ImageFormat, Instance, RenderWindowConfig, TextureAtlases, Vertex,
};
+use image::{EncodableLayout, GenericImage};
use pollster::FutureExt;
use thiserror::Error;
use wgpu::{include_wgsl, util::DeviceExt};
@@ -39,7 +43,7 @@ pub enum NewRendererError {
WindowInitError(#[from] OsError),
}
-#[derive(Debug)]
+// TODO make this Debug
pub struct Renderer {
// TODO move some of this data elsewhere
surface: wgpu::Surface,
@@ -56,6 +60,10 @@ 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,
window: Window,
}
@@ -222,17 +230,6 @@ impl Renderer {
}],
});
- let render_pipeline_layout =
- device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- label: Some("Sprite Render Pipeline Layout"),
- bind_group_layouts: &[&camera_bind_group_layout],
- push_constant_ranges: &[],
- });
-
- // set up a pipeline for sprite rendering
- let render_pipeline =
- Self::sprite_render_pipeline(&device, surface_config.format, &render_pipeline_layout);
-
// the vertex buffer used for rendering squares
let square_vertices = SQUARE
.len()
@@ -249,6 +246,71 @@ impl Renderer {
let (instance_buffer, instance_buffer_size) =
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),
+ },
+ ],
+ });
+
+ 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],
+ push_constant_ranges: &[],
+ });
+
+ // set up a pipeline for sprite rendering
+ let render_pipeline =
+ Self::sprite_render_pipeline(&device, surface_config.format, &render_pipeline_layout);
+
Ok(Self {
surface,
surface_config,
@@ -264,6 +326,10 @@ impl Renderer {
camera,
camera_buffer,
camera_bind_group,
+ textures,
+ texture_size,
+ diffuse_texture,
+ diffuse_bind_group,
window,
})
}
@@ -304,23 +370,6 @@ impl Renderer {
self.window.set_title(title);
}
- fn expand_instance_buffer(&mut self) {
- (self.instance_buffer, self.instance_buffer_size) =
- Self::new_instance_buffer(&self.device, &self.instances);
- }
-
- fn fill_instance_buffer(&mut self) {
- if self.instances.len() > self.instance_buffer_size {
- self.expand_instance_buffer();
- }
-
- self.queue.write_buffer(
- &self.instance_buffer,
- 0 as wgpu::BufferAddress,
- bytemuck::cast_slice(&self.instances),
- );
- }
-
/// Add an instance to the renderer, and returns an `InstanceId` to the
/// instance. This id becomes invalid if the instances are cleared.
pub fn push_instance(&mut self, instance: Instance) -> InstanceId {
@@ -354,6 +403,61 @@ impl Renderer {
&mut self.camera
}
+ /// 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_mem(
+ &mut self,
+ texture: &[u8],
+ format: ImageFormat,
+ ) -> Result<TextureId, TextureError> {
+ self.textures.load_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)
+ }
+
+ 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)
+ }
+
+ 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)
+ }
+
+ 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)
+ }
+
+ fn expand_instance_buffer(&mut self) {
+ (self.instance_buffer, self.instance_buffer_size) =
+ Self::new_instance_buffer(&self.device, &self.instances);
+ }
+
+ fn fill_instance_buffer(&mut self) {
+ if self.instances.len() > self.instance_buffer_size {
+ self.expand_instance_buffer();
+ }
+
+ self.queue.write_buffer(
+ &self.instance_buffer,
+ 0 as wgpu::BufferAddress,
+ bytemuck::cast_slice(&self.instances),
+ );
+ }
+
fn refresh_camera_buffer(&mut self) {
self.queue.write_buffer(
&self.camera_buffer,
@@ -362,6 +466,39 @@ impl Renderer {
);
}
+ // TODO optimize this
+ fn fill_textures(&mut self) {
+ // create a base image
+ let mut image = image::RgbaImage::from_vec(
+ self.texture_size.width,
+ self.texture_size.height,
+ vec![0; 4 * self.texture_size.width as usize * self.texture_size.height as usize],
+ )
+ .unwrap();
+
+ // put the packed texture into the base image
+ let Ok(atlases) = self.textures.atlases() else { return };
+ let Some(atlas) = atlases.first() else { return };
+ 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,
+ },
+ 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
@@ -392,6 +529,7 @@ impl Renderer {
.expect("expected less than 3 billion instances");
self.refresh_camera_buffer();
+ self.fill_textures();
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -409,6 +547,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_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 8a36334..9184304 100644
--- a/src/texture.rs
+++ b/src/texture.rs
@@ -12,9 +12,10 @@ use thiserror::Error;
static NEXT_TEXTURE_ID: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-struct TextureId(usize);
+pub struct TextureId(usize);
impl TextureId {
+ #[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self(NEXT_TEXTURE_ID.fetch_add(1, Ordering::Relaxed))
}
@@ -58,15 +59,16 @@ impl From<ImageError> for TextureError {
}
}
-struct TextureAtlases<'a> {
+// TODO make this Debug
+pub struct TextureAtlases<'a> {
packer: MultiTexturePacker<'a, image::RgbaImage, TextureId>,
+ width: u32,
+ height: u32,
}
impl<'a> Default for TextureAtlases<'a> {
fn default() -> Self {
- Self {
- packer: MultiTexturePacker::new_skyline(TexturePackerConfig::default()),
- }
+ Self::new(1024, 1024)
}
}
@@ -78,8 +80,11 @@ impl<'a> TextureAtlases<'a> {
packer: MultiTexturePacker::new_skyline(TexturePackerConfig {
max_width: width,
max_height: height,
+ //trim: false,
..Default::default()
}),
+ width,
+ height,
}
}
@@ -98,6 +103,50 @@ impl<'a> TextureAtlases<'a> {
Ok(id)
}
+ fn texture_frame(&self, id: TextureId) -> Option<&texture_packer::Frame<TextureId>> {
+ self.packer
+ .get_pages()
+ .iter()
+ .map(|a| a.get_frame(&id))
+ .next()?
+ }
+
+ pub fn texture_width(&self, id: TextureId) -> Option<u32> {
+ let frame = self.texture_frame(id)?;
+ Some(frame.frame.w)
+ }
+
+ pub fn texture_height(&self, id: TextureId) -> Option<u32> {
+ let frame = self.texture_frame(id)?;
+ Some(frame.frame.h)
+ }
+
+ pub fn texture_x(&self, id: TextureId) -> Option<u32> {
+ let frame = self.texture_frame(id)?;
+ Some(frame.frame.x)
+ }
+
+ pub fn texture_y(&self, id: TextureId) -> Option<u32> {
+ let frame = self.texture_frame(id)?;
+ 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 {
+ wgpu::Extent3d {
+ width: self.width,
+ height: self.height,
+ depth_or_array_layers: 1,
+ }
+ }
+
pub(crate) fn atlases(&self) -> ExportResult<Box<[DynamicImage]>> {
self.packer
.get_pages()