From 2b8ef72ab8503e18e5a45de7ed811e7729d4898c Mon Sep 17 00:00:00 2001 From: Axel Kappel <69117984+Kl4rry@users.noreply.github.com> Date: Mon, 22 Jan 2024 22:40:29 +0100 Subject: [PATCH] add mosaic tiling for large image support --- src/app.rs | 4 +- src/app/image_view.rs | 70 ++----------- src/app/image_view/image_renderer.rs | 14 +-- src/app/image_view/mosaic.rs | 142 +++++++++++++++++++++++++++ src/app/image_view/texture.rs | 1 + 5 files changed, 161 insertions(+), 70 deletions(-) create mode 100644 src/app/image_view/mosaic.rs diff --git a/src/app.rs b/src/app.rs index c83053c..3864a0d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -700,11 +700,11 @@ impl App { }); } - pub fn update(&mut self, wgpu: &WgpuState) -> (bool, Duration) { + pub fn update(&mut self, _wgpu: &WgpuState) -> (bool, Duration) { self.delay = Duration::MAX; if let Some(ref mut image) = self.image_view { - self.delay = self.delay.min(image.animate(wgpu)); + self.delay = self.delay.min(image.animate()); } if let Some(ref mut image) = self.image_view { diff --git a/src/app/image_view.rs b/src/app/image_view.rs index fa0eee9..5c9d07b 100644 --- a/src/app/image_view.rs +++ b/src/app/image_view.rs @@ -8,10 +8,9 @@ use std::{ use cgmath::{Deg, Matrix4, Ortho, Vector3, Vector4}; use image::GenericImageView; -use wgpu::util::DeviceExt; use winit::event_loop::EventLoopProxy; -use self::image_renderer::Vertex; +use self::mosaic::Mosaic; use super::op_queue::{Output, UserEventLoopProxyExt}; use crate::{ max, @@ -23,6 +22,7 @@ use crate::{ pub mod crop_renderer; pub mod image_renderer; +pub mod mosaic; mod crop; use crop::Crop; @@ -30,6 +30,7 @@ use crop::Crop; mod texture; pub struct ImageView { + pub mosaic: Vec, pub size: Vec2, pub position: Vec2, pub scale: f32, @@ -47,9 +48,6 @@ pub struct ImageView { pub grayscale: bool, pub invert: bool, pub crop: Crop, - vertices: wgpu::Buffer, - indices: wgpu::Buffer, - texture: texture::Texture, } impl ImageView { @@ -57,18 +55,10 @@ impl ImageView { let frames = &image_data.frames; let image = frames[0].buffer(); let (width, height) = image.dimensions(); - let texture = texture::Texture::from_image(&wgpu.device, &wgpu.queue, image, None); - - let indices: &[u32] = &[0, 1, 2, 2, 1, 3]; - let indices = wgpu - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Image Index Buffer"), - contents: bytemuck::cast_slice(indices), - usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, - }); + let mosaic = Mosaic::from_images(wgpu, image_data.clone()); Self { + mosaic, size: Vec2::new(width as f32, height as f32), position: Vec2::default(), scale: 1.0, @@ -79,9 +69,6 @@ impl ImageView { horizontal_flip: false, vertical_flip: false, crop: Crop::new(), - vertices: get_vertex_buffer(wgpu, width as f32, height as f32), - indices, - texture, path, hue: 0.0, contrast: 0.0, @@ -137,14 +124,6 @@ impl ImageView { } } - pub fn get_buffers(&self) -> (&wgpu::Buffer, &wgpu::Buffer) { - (&self.vertices, &self.indices) - } - - pub fn get_texture(&self) -> &texture::Texture { - &self.texture - } - pub fn scaled(&self) -> Vec2 { self.size * self.scale } @@ -187,7 +166,7 @@ impl ImageView { self.vertical_flip = !self.vertical_flip; } - pub fn animate(&mut self, wgpu: &WgpuState) -> Duration { + pub fn animate(&mut self) -> Duration { let guard = self.image_data.read().unwrap(); let frames = &guard.frames; if frames.len() > 1 { @@ -201,9 +180,6 @@ impl ImageView { self.index = 0; } - drop(guard); - self.update_image_data(wgpu); - self.last_frame = now; delay @@ -251,16 +227,8 @@ impl ImageView { mem::swap(&mut Arc::make_mut(&mut *guard).frames, frames); let (width, height) = guard.frames[0].buffer().dimensions(); drop(guard); - self.update_image_data(wgpu); - self.vertices = get_vertex_buffer(wgpu, width as f32, height as f32); - } - - fn update_image_data(&mut self, wgpu: &WgpuState) { - let guard = self.image_data.read().unwrap(); - let frames = &guard.frames; - let image = frames[self.index].buffer(); - self.size = Vec2::new(image.width() as f32, image.height() as f32); - self.texture = texture::Texture::from_image(&wgpu.device, &wgpu.queue, image, None); + self.mosaic = Mosaic::from_images(wgpu, self.image_data.read().unwrap().clone()); + self.size = Vec2::new(width as f32, height as f32); } pub fn rotation(&self) -> i32 { @@ -324,25 +292,3 @@ impl ImageView { } } } - -fn get_vertex_buffer(wgpu: &WgpuState, width: f32, height: f32) -> wgpu::Buffer { - let texture_cords = ( - Vec2::new(1.0, 1.0), - Vec2::new(1.0, 0.0), - Vec2::new(0.0, 1.0), - Vec2::new(0.0, 0.0), - ); - let shape = [ - Vertex::new(0.0, 0.0, texture_cords.0.x(), texture_cords.0.y()), - Vertex::new(0.0, height, texture_cords.1.x(), texture_cords.1.y()), - Vertex::new(width, 0.0, texture_cords.2.x(), texture_cords.2.y()), - Vertex::new(width, height, texture_cords.3.x(), texture_cords.3.y()), - ]; - - wgpu.device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Image Vertex Buffer"), - contents: bytemuck::cast_slice(shape.as_slice()), - usage: wgpu::BufferUsages::VERTEX, - }) -} diff --git a/src/app/image_view/image_renderer.rs b/src/app/image_view/image_renderer.rs index 5c74a8c..60efb72 100644 --- a/src/app/image_view/image_renderer.rs +++ b/src/app/image_view/image_renderer.rs @@ -211,13 +211,15 @@ impl Renderer { rpass: &mut wgpu::RenderPass<'rpass>, image_view: &'rpass ImageView, ) { - let (vertices, indices) = image_view.get_buffers(); - let texture = image_view.get_texture(); rpass.set_pipeline(&self.pipeline); rpass.set_bind_group(0, &self.uniform_bind_group, &[]); - rpass.set_bind_group(1, &texture.diffuse_bind_group, &[]); - rpass.set_vertex_buffer(0, vertices.slice(..)); - rpass.set_index_buffer(indices.slice(..), wgpu::IndexFormat::Uint32); - rpass.draw_indexed(0..6, 0, 0..1); + let mosaic = &image_view.mosaic[image_view.index]; + rpass.set_index_buffer(mosaic.indices.slice(..), wgpu::IndexFormat::Uint32); + + for tile in &mosaic.tiles { + rpass.set_bind_group(1, &tile.texture.diffuse_bind_group, &[]); + rpass.set_vertex_buffer(0, tile.vertices.slice(..)); + rpass.draw_indexed(0..6, 0, 0..1); + } } } diff --git a/src/app/image_view/mosaic.rs b/src/app/image_view/mosaic.rs new file mode 100644 index 0000000..b4aac94 --- /dev/null +++ b/src/app/image_view/mosaic.rs @@ -0,0 +1,142 @@ +use std::{iter, sync::Arc}; + +use image::{DynamicImage, SubImage}; +use wgpu::{util::DeviceExt, Limits}; + +use super::{image_renderer::Vertex, texture}; +use crate::{util::ImageData, vec2::Vec2, WgpuState}; + +pub struct Tile { + pub vertices: wgpu::Buffer, + pub texture: texture::Texture, +} + +pub struct Mosaic { + pub tiles: Vec, + pub indices: wgpu::Buffer, +} + +impl Mosaic { + pub fn from_images(wgpu: &WgpuState, images: Arc) -> Vec { + let limit = Limits::default().max_texture_dimension_2d; + + let mut output = Vec::new(); + for image in &images.frames { + let mut tiles = Vec::new(); + let image = &image.image; + + let tile_width = (image.width() / limit) + 1; + let tile_height = (image.height() / limit) + 1; + + // TODO: make parallel + for x in 0..tile_width { + for y in 0..tile_height { + let start_x = x * limit; + let start_y = y * limit; + let end_x = ((x + 1) * limit).min(image.width()); + let end_y = ((y + 1) * limit).min(image.height()); + + if end_x.saturating_sub(start_x) == 0 || end_x.saturating_sub(start_x) == 0 { + continue; + } + + let vertices = get_vertex_buffer( + wgpu, + start_x as f32, + start_y as f32, + end_x as f32, + end_y as f32, + ); + + // Fast path for small images + let texture = if tile_height == 1 && tile_width == 1 { + texture::Texture::from_image(&wgpu.device, &wgpu.queue, image, None) + } else { + let sub_image = + get_tile(image, start_x, start_y, end_x - start_x, end_y - start_y); + texture::Texture::from_image(&wgpu.device, &wgpu.queue, &sub_image, None) + }; + + tiles.push(Tile { vertices, texture }); + } + } + + let indices: &[u32] = &[0, 1, 2, 2, 1, 3]; + let indices = wgpu + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Image Index Buffer"), + contents: bytemuck::cast_slice(indices), + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + }); + + output.push(Mosaic { tiles, indices }) + } + wgpu.queue.submit(iter::empty()); + output + } +} + +fn get_tile(image: &DynamicImage, x: u32, y: u32, width: u32, height: u32) -> DynamicImage { + match image { + DynamicImage::ImageLuma8(image) => { + DynamicImage::ImageLuma8(SubImage::new(image, x, y, width, height).to_image()) + } + DynamicImage::ImageLumaA8(image) => { + DynamicImage::ImageLumaA8(SubImage::new(image, x, y, width, height).to_image()) + } + DynamicImage::ImageRgb8(image) => { + DynamicImage::ImageRgb8(SubImage::new(image, x, y, width, height).to_image()) + } + DynamicImage::ImageRgba8(image) => { + DynamicImage::ImageRgba8(SubImage::new(image, x, y, width, height).to_image()) + } + DynamicImage::ImageLuma16(image) => { + DynamicImage::ImageLuma16(SubImage::new(image, x, y, width, height).to_image()) + } + DynamicImage::ImageLumaA16(image) => { + DynamicImage::ImageLumaA16(SubImage::new(image, x, y, width, height).to_image()) + } + DynamicImage::ImageRgb16(image) => { + DynamicImage::ImageRgb16(SubImage::new(image, x, y, width, height).to_image()) + } + DynamicImage::ImageRgba16(image) => { + DynamicImage::ImageRgba16(SubImage::new(image, x, y, width, height).to_image()) + } + DynamicImage::ImageRgb32F(image) => { + DynamicImage::ImageRgb32F(SubImage::new(image, x, y, width, height).to_image()) + } + DynamicImage::ImageRgba32F(image) => { + DynamicImage::ImageRgba32F(SubImage::new(image, x, y, width, height).to_image()) + } + image => DynamicImage::ImageRgba8(SubImage::new(image, x, y, width, height).to_image()), + } +} + +fn get_vertex_buffer( + wgpu: &WgpuState, + start_x: f32, + start_y: f32, + end_x: f32, + end_y: f32, +) -> wgpu::Buffer { + let texture_cords = ( + Vec2::new(1.0, 1.0), + Vec2::new(1.0, 0.0), + Vec2::new(0.0, 1.0), + Vec2::new(0.0, 0.0), + ); + let shape = [ + Vertex::new(start_x, start_y, texture_cords.0.x(), texture_cords.0.y()), + Vertex::new(start_x, end_y, texture_cords.1.x(), texture_cords.1.y()), + Vertex::new(end_x, start_y, texture_cords.2.x(), texture_cords.2.y()), + Vertex::new(end_x, end_y, texture_cords.3.x(), texture_cords.3.y()), + ]; + + wgpu.device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Image Vertex Buffer"), + contents: bytemuck::cast_slice(shape.as_slice()), + usage: wgpu::BufferUsages::VERTEX, + }) +} diff --git a/src/app/image_view/texture.rs b/src/app/image_view/texture.rs index 1911c67..930b989 100644 --- a/src/app/image_view/texture.rs +++ b/src/app/image_view/texture.rs @@ -74,6 +74,7 @@ impl Texture { size, ); + // TODO: add mip maps let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); let sampler = device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::ClampToEdge,