summaryrefslogtreecommitdiff
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
parent39e36dd10cd7a335897e66e0f613d0191e7f9eba (diff)
Hacked in textures
-rw-r--r--Cargo.toml5
-rw-r--r--examples/bmp.rs40
-rw-r--r--examples/res/sample.bmpbin0 -> 1000138 bytes
-rw-r--r--examples/res/square.icobin0 -> 270398 bytes
-rw-r--r--examples/square.rs19
-rw-r--r--shaders/sprite.wgsl13
-rw-r--r--src/lib.rs2
-rw-r--r--src/renderer.rs201
-rw-r--r--src/texture.rs59
9 files changed, 298 insertions, 41 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 6c1be3a..a3fe21e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,4 +20,7 @@ texture_packer = "0.24"
name = "black"
[[example]]
-name = "square" \ No newline at end of file
+name = "square"
+
+[[example]]
+name = "bmp"
diff --git a/examples/bmp.rs b/examples/bmp.rs
new file mode 100644
index 0000000..b1c2117
--- /dev/null
+++ b/examples/bmp.rs
@@ -0,0 +1,40 @@
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+
+use std::num::NonZeroU32;
+
+use alligator_render::{config::WindowMode, ImageFormat, Instance, RenderWindowConfig, Renderer};
+use winit::event_loop::EventLoop;
+
+fn main() {
+ // configure the render window
+ let config = RenderWindowConfig {
+ title: "Bumper Sticker",
+ instance_capacity: 1,
+ default_width: NonZeroU32::new(1280).unwrap(),
+ default_height: NonZeroU32::new(720).unwrap(),
+ mode: WindowMode::BorderlessFullscreen,
+ vsync: false,
+ ..Default::default()
+ };
+
+ let texture = include_bytes!("res/sample.bmp");
+
+ let event_loop = EventLoop::new();
+ let mut renderer = Renderer::new(&config, &event_loop).unwrap();
+
+ let texture = renderer
+ .texture_from_mem(texture, ImageFormat::Bmp)
+ .unwrap();
+ let width = renderer.texture_width(texture).unwrap();
+ let height = renderer.texture_height(texture).unwrap();
+ let x = renderer.texture_x(texture).unwrap();
+ let y = renderer.texture_y(texture).unwrap();
+
+ renderer.push_instance(Instance {
+ texture_size: [width, height],
+ texture_coordinates: [x, y],
+ ..Default::default()
+ });
+
+ renderer.run(event_loop);
+}
diff --git a/examples/res/sample.bmp b/examples/res/sample.bmp
new file mode 100644
index 0000000..b31b58e
--- /dev/null
+++ b/examples/res/sample.bmp
Binary files differ
diff --git a/examples/res/square.ico b/examples/res/square.ico
new file mode 100644
index 0000000..43d5a8c
--- /dev/null
+++ b/examples/res/square.ico
Binary files differ
diff --git a/examples/square.rs b/examples/square.rs
index d03a4a9..f95bd00 100644
--- a/examples/square.rs
+++ b/examples/square.rs
@@ -1,6 +1,6 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
-use alligator_render::{Instance, RenderWindowConfig, Renderer};
+use alligator_render::{ImageFormat, Instance, RenderWindowConfig, Renderer};
use winit::event_loop::EventLoop;
fn main() {
@@ -11,9 +11,24 @@ fn main() {
..Default::default()
};
+ let texture = include_bytes!("res/square.ico");
+
let event_loop = EventLoop::new();
let mut renderer = Renderer::new(&config, &event_loop).unwrap();
- renderer.push_instance(Instance::default());
+
+ let texture = renderer
+ .texture_from_mem(texture, ImageFormat::Ico)
+ .unwrap();
+ let width = renderer.texture_width(texture).unwrap();
+ let height = renderer.texture_height(texture).unwrap();
+ let x = renderer.texture_x(texture).unwrap();
+ let y = renderer.texture_y(texture).unwrap();
+
+ renderer.push_instance(Instance {
+ texture_size: [width, height],
+ texture_coordinates: [x, y],
+ ..Default::default()
+ });
renderer.run(event_loop);
}
diff --git a/shaders/sprite.wgsl b/shaders/sprite.wgsl
index 22225f7..61944a1 100644
--- a/shaders/sprite.wgsl
+++ b/shaders/sprite.wgsl
@@ -42,13 +42,22 @@ fn vs_main(model: VertexInput, instance: InstanceInput) -> VertexOutput {
let position4d = vec4<f32>(position2d, 0.0, 1.0);
let position = camera * position4d;
+ let tex_coords = vec2<f32>(model.position[0] + 0.5, 1.0 - (model.position[1] + 0.5));
+
out.clip_position = position;
out.texture_atlas_index = instance.texture_atlas_index;
- out.texture_coordinates = (model.position + 0.5) * instance.texture_size + instance.texture_coordinates;
+ out.texture_coordinates = tex_coords * instance.texture_size + instance.texture_coordinates;
return out;
}
+@group(1) @binding(0)
+var t_diffuse: texture_2d<f32>;
+@group(1) @binding(1)
+var s_diffuse: sampler;
+
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
- return vec4<f32>(1.0, 1.0, 1.0, 0.0);
+ //return vec4<f32>(1.0, 1.0, 1.0, 0.0);
+ return textureSample(t_diffuse, s_diffuse, in.texture_coordinates);
+ //return textureSample(t_diffuse, s_diffuse, );
} \ No newline at end of file
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()