diff --git a/src/app.rs b/src/app.rs index c7157bf..ad16692 100644 --- a/src/app.rs +++ b/src/app.rs @@ -823,6 +823,8 @@ impl App { .id(egui::Id::new("resize window")) .collapsible(false) .resizable(false) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(self.size / 2.0) .open(&mut open) .show(ctx, |ui| { egui::Grid::new("resize grid").show(ui, |ui| { @@ -946,6 +948,8 @@ impl App { .id(egui::Id::new("crop window")) .collapsible(false) .resizable(false) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(self.size / 2.0) .show(ctx, |ui| { egui::Grid::new("crop grid").show(ui, |ui| { ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| { diff --git a/src/app/color.rs b/src/app/color.rs index 8a0d0cc..622b055 100644 --- a/src/app/color.rs +++ b/src/app/color.rs @@ -11,6 +11,8 @@ impl App { .id(egui::Id::new("color window")) .collapsible(false) .resizable(false) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(self.size / 2.0) .open(&mut open) .show(ctx, |ui| { egui::Grid::new("color grid").show(ui, |ui| { diff --git a/src/app/help.rs b/src/app/help.rs index 142c47a..82d1fa5 100644 --- a/src/app/help.rs +++ b/src/app/help.rs @@ -9,6 +9,8 @@ impl App { .id(egui::Id::new("help window")) .collapsible(false) .resizable(false) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(self.size / 2.0) .open(&mut open) .show(ctx, |ui| { egui::Grid::new("help grid") diff --git a/src/app/image_view/image_renderer.rs b/src/app/image_view/image_renderer.rs index 73e8902..da6366d 100644 --- a/src/app/image_view/image_renderer.rs +++ b/src/app/image_view/image_renderer.rs @@ -48,13 +48,13 @@ impl Vertex { pub struct Uniform { pub matrix: Matrix4, pub size: Vec2, - pub padding: Vec2, // Padding because glsl is adding dumb padding pub hue: f32, pub contrast: f32, pub brightness: f32, pub saturation: f32, pub grayscale: u32, pub invert: u32, + pub padding: Vec2, // Padding because glsl is adding dumb padding } impl Default for Uniform { diff --git a/src/app/image_view/mosaic.rs b/src/app/image_view/mosaic.rs index 729c814..6269e30 100644 --- a/src/app/image_view/mosaic.rs +++ b/src/app/image_view/mosaic.rs @@ -20,6 +20,12 @@ impl Mosaic { pub fn from_images(wgpu: &WgpuState, images: Arc) -> Vec { let limit = Limits::default().max_texture_dimension_2d; + let mut encoder = wgpu + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + let mut output = Vec::new(); for image in &images.frames { let mut tiles = Vec::new(); @@ -28,7 +34,6 @@ impl Mosaic { 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; @@ -50,11 +55,23 @@ impl Mosaic { // Fast path for small images let texture = if tile_height == 1 && tile_width == 1 { - texture::Texture::from_image(&wgpu.device, &wgpu.queue, image, None) + texture::Texture::from_image( + &mut encoder, + &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) + texture::Texture::from_image( + &mut encoder, + &wgpu.device, + &wgpu.queue, + &sub_image, + None, + ) }; tiles.push(Tile { vertices, texture }); @@ -72,7 +89,7 @@ impl Mosaic { output.push(Mosaic { tiles, indices }) } - wgpu.queue.submit(iter::empty()); + wgpu.queue.submit(iter::once(encoder.finish())); output } } diff --git a/src/app/image_view/texture.rs b/src/app/image_view/texture.rs index 930b989..295757e 100644 --- a/src/app/image_view/texture.rs +++ b/src/app/image_view/texture.rs @@ -2,6 +2,8 @@ use std::{borrow::Cow, mem}; use image::GenericImageView; +const MIP_LEVEL_COUNT: u32 = 10; + pub struct Texture { pub texture: wgpu::Texture, pub view: wgpu::TextureView, @@ -20,6 +22,7 @@ fn to_u8(input: Vec) -> Vec { impl Texture { pub fn from_image( + command_encoder: &mut wgpu::CommandEncoder, device: &wgpu::Device, queue: &wgpu::Queue, img: &image::DynamicImage, @@ -50,11 +53,13 @@ impl Texture { let texture = device.create_texture(&wgpu::TextureDescriptor { label, size, - mip_level_count: 1, + mip_level_count: MIP_LEVEL_COUNT, sample_count: 1, dimension: wgpu::TextureDimension::D2, format, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + usage: wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }); @@ -103,6 +108,8 @@ impl Texture { label: Some("Diffuse Bind Group"), }); + generate_mipmaps(command_encoder, device, &texture, format, MIP_LEVEL_COUNT); + Self { texture, view, @@ -136,3 +143,102 @@ impl Texture { }) } } + +fn generate_mipmaps( + encoder: &mut wgpu::CommandEncoder, + device: &wgpu::Device, + texture: &wgpu::Texture, + texture_format: wgpu::TextureFormat, + mip_count: u32, +) { + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../../shader/blit.wgsl"))), + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("blit"), + layout: None, + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(texture_format.into())], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + }); + + let bind_group_layout = pipeline.get_bind_group_layout(0); + + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("mip"), + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }); + + let views = (0..mip_count) + .map(|mip| { + texture.create_view(&wgpu::TextureViewDescriptor { + label: Some("mip"), + format: None, + dimension: None, + aspect: wgpu::TextureAspect::All, + base_mip_level: mip, + mip_level_count: Some(1), + base_array_layer: 0, + array_layer_count: None, + }) + }) + .collect::>(); + + for target_mip in 1..mip_count as usize { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&views[target_mip - 1]), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&sampler), + }, + ], + label: None, + }); + + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &views[target_mip], + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + rpass.set_pipeline(&pipeline); + rpass.set_bind_group(0, &bind_group, &[]); + rpass.draw(0..3, 0..1); + } +} diff --git a/src/app/metadata.rs b/src/app/metadata.rs index 0b5334e..14be97e 100644 --- a/src/app/metadata.rs +++ b/src/app/metadata.rs @@ -9,6 +9,8 @@ impl App { .id(egui::Id::new("metadata window")) .collapsible(false) .resizable(true) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(self.size / 2.0) .open(&mut open) .show(ctx, |ui| { ScrollArea::vertical().show(ui, |ui| { diff --git a/src/app/popup_manager.rs b/src/app/popup_manager.rs index 6bdaa65..58a7d98 100644 --- a/src/app/popup_manager.rs +++ b/src/app/popup_manager.rs @@ -52,9 +52,9 @@ impl PopupManager { .id(egui::Id::new(key)) .collapsible(false) .resizable(false) - .open(&mut open) .pivot(egui::Align2::CENTER_CENTER) .default_pos(size / 2.0) + .open(&mut open) .show(ctx, |ui| done = (popup.closure)(ui)); if !open || done { self.popups.remove(&key); diff --git a/src/app/preferences.rs b/src/app/preferences.rs index f2f7941..b0bf50c 100644 --- a/src/app/preferences.rs +++ b/src/app/preferences.rs @@ -48,6 +48,8 @@ impl App { .id(egui::Id::new("preferences window")) .collapsible(false) .resizable(false) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(self.size / 2.0) .open(&mut open) .show(ctx, |ui| { egui::Grid::new("preferences grid").show(ui, |ui| { diff --git a/src/shader/blit.wgsl b/src/shader/blit.wgsl new file mode 100644 index 0000000..69adbb3 --- /dev/null +++ b/src/shader/blit.wgsl @@ -0,0 +1,52 @@ +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) tex_coords: vec2, +}; + +// meant to be called with 3 vertex indices: 0, 1, 2 +// draws one large triangle over the clip space like this: +// (the asterisks represent the clip space bounds) +//-1,1 1,1 +// --------------------------------- +// | * . +// | * . +// | * . +// | * . +// | * . +// | * . +// |*************** +// | . 1,-1 +// | . +// | . +// | . +// | . +// |. +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { + var result: VertexOutput; + let x = i32(vertex_index) / 2; + let y = i32(vertex_index) & 1; + let tc = vec2( + f32(x) * 2.0, + f32(y) * 2.0 + ); + result.position = vec4( + tc.x * 2.0 - 1.0, + 1.0 - tc.y * 2.0, + 0.0, 1.0 + ); + result.tex_coords = tc; + return result; +} + +@group(0) +@binding(0) +var r_color: texture_2d; +@group(0) +@binding(1) +var r_sampler: sampler; + +@fragment +fn fs_main(vertex: VertexOutput) -> @location(0) vec4 { + return textureSample(r_color, r_sampler, vertex.tex_coords); +} diff --git a/src/shader/image.frag b/src/shader/image.frag index 6071a93..2eb106f 100644 --- a/src/shader/image.frag +++ b/src/shader/image.frag @@ -139,7 +139,6 @@ vec3 getCheckColor() { } void main() { - vec2 size = input.size; float hue = input.hue; float contrast = input.contrast; float brightness = input.brightness;