Compare commits
No commits in common. "238519ee90992ce5ba79fae65637f0bc2f448f2b" and "5a6b7756e484ffbf369dc81a150d2735853b2ad3" have entirely different histories.
238519ee90
...
5a6b7756e4
6 changed files with 188 additions and 1077 deletions
831
Cargo.lock
generated
831
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -6,14 +6,9 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytemuck = "1.16.0"
|
bytemuck = "1.16.0"
|
||||||
eframe = {version = "0.27", features = ["wgpu"]}
|
eframe = {version = "0.27", features = ["wgpu"]}
|
||||||
egui-file-dialog = "0.5.0"
|
egui_extras = "0.27.2"
|
||||||
egui_extras = { version = "0.27.2", features = ["image"] }
|
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
image = "0.25.1"
|
|
||||||
nalgebra = "0.32.5"
|
nalgebra = "0.32.5"
|
||||||
oneshot = "0.1.6"
|
|
||||||
rayon = "1.10.0"
|
|
||||||
threadpool = "1.8.1"
|
|
||||||
# winit = { version = "0.30", features = ["rwh_05" ] }
|
# winit = { version = "0.30", features = ["rwh_05" ] }
|
||||||
# log = "0.4"
|
# log = "0.4"
|
||||||
# console_error_panic_hook = "0.1.6"
|
# console_error_panic_hook = "0.1.6"
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
use eframe::egui;
|
|
||||||
use nalgebra::{Matrix, SMatrix};
|
|
||||||
|
|
||||||
pub fn mat_editor<const R: usize, const C: usize>(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
mat: &mut SMatrix<f32, R, C>,
|
|
||||||
id: &str,
|
|
||||||
) -> egui::Response {
|
|
||||||
egui::Grid::new(id)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
mat.row_iter_mut().enumerate().for_each(|(i, mut row)| {
|
|
||||||
row.iter_mut().for_each(|item| {
|
|
||||||
ui.add(
|
|
||||||
egui::DragValue::new(item)
|
|
||||||
.speed(0.01)
|
|
||||||
.clamp_range(-10.0..=10.0),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
ui.end_row();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.response
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
use std::{
|
|
||||||
sync::{mpsc, Arc},
|
|
||||||
thread,
|
|
||||||
};
|
|
||||||
|
|
||||||
use eframe::egui::{self, TextureOptions};
|
|
||||||
use image::{DynamicImage, Pixel, Rgb, Rgb32FImage};
|
|
||||||
use nalgebra::{SMatrix, Vector4};
|
|
||||||
use rayon::prelude::*;
|
|
||||||
|
|
||||||
use image::EncodableLayout;
|
|
||||||
struct ProcessorInstruction {
|
|
||||||
img: Arc<Rgb32FImage>,
|
|
||||||
color_matrix: SMatrix<f32, 3, 4>,
|
|
||||||
pos_matrix: SMatrix<f32, 2, 3>,
|
|
||||||
ret: oneshot::Sender<(egui::TextureHandle, (f32, f32))>,
|
|
||||||
ctx: egui::Context,
|
|
||||||
handle: Option<egui::TextureHandle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Processor {
|
|
||||||
channel: mpsc::Sender<ProcessorInstruction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Processor {
|
|
||||||
pub fn init() -> Self {
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
thread::spawn(|| {
|
|
||||||
for ProcessorInstruction {
|
|
||||||
img,
|
|
||||||
color_matrix,
|
|
||||||
pos_matrix,
|
|
||||||
ret,
|
|
||||||
ctx,
|
|
||||||
handle,
|
|
||||||
} in rx
|
|
||||||
{
|
|
||||||
let (width, height) = img.dimensions();
|
|
||||||
|
|
||||||
let mut r = (*img).clone();
|
|
||||||
if !color_matrix.is_identity(f32::EPSILON) {
|
|
||||||
r.par_pixels_mut().for_each(|px| {
|
|
||||||
*px = Rgb::from(
|
|
||||||
TryInto::<[f32; 3]>::try_into(
|
|
||||||
(color_matrix * Vector4::new(px.0[0], px.0[1], px.0[2], 1.))
|
|
||||||
.as_slice(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let color_image = {
|
|
||||||
let image = DynamicImage::from(r).to_rgb8();
|
|
||||||
|
|
||||||
egui::ColorImage::from_rgb(
|
|
||||||
[image.width() as usize, image.height() as usize],
|
|
||||||
image.as_bytes(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let handle = if let Some(mut handle) = handle {
|
|
||||||
handle.set(color_image, TextureOptions::default());
|
|
||||||
handle
|
|
||||||
} else {
|
|
||||||
ctx.load_texture("res_tex", color_image, TextureOptions::default())
|
|
||||||
};
|
|
||||||
|
|
||||||
ret.send((handle, (width as f32, height as f32))).unwrap();
|
|
||||||
// (color_matrix.is_identity(f32::EPSILON), pos_matrix.is_identity(f32::EPSILON))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Self { channel: tx }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exec(
|
|
||||||
&self,
|
|
||||||
img: Arc<Rgb32FImage>,
|
|
||||||
color_matrix: SMatrix<f32, 3, 4>,
|
|
||||||
pos_matrix: SMatrix<f32, 2, 3>,
|
|
||||||
ctx: egui::Context,
|
|
||||||
handle: Option<egui::TextureHandle>,
|
|
||||||
) -> oneshot::Receiver<(egui::TextureHandle, (f32, f32))> {
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
|
|
||||||
let r = self.channel.send(ProcessorInstruction {
|
|
||||||
img,
|
|
||||||
color_matrix,
|
|
||||||
pos_matrix,
|
|
||||||
ret: tx,
|
|
||||||
ctx,
|
|
||||||
handle,
|
|
||||||
});
|
|
||||||
dbg!(r).unwrap();
|
|
||||||
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
}
|
|
289
src/lib.rs
289
src/lib.rs
|
@ -1,135 +1,206 @@
|
||||||
use std::{num::NonZeroU64, path::PathBuf, sync::Arc, thread};
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
|
use cb::TriangleRenderResources;
|
||||||
use eframe::{
|
use eframe::{
|
||||||
egui::{self, ImageSource},
|
egui, egui_wgpu,
|
||||||
egui_wgpu,
|
|
||||||
wgpu::{self, util::DeviceExt},
|
wgpu::{self, util::DeviceExt},
|
||||||
};
|
};
|
||||||
use egui_file_dialog::FileDialog;
|
|
||||||
use image::Rgb32FImage;
|
|
||||||
use img_processor::Processor;
|
|
||||||
use nalgebra::{Matrix3, SMatrix};
|
use nalgebra::{Matrix3, SMatrix};
|
||||||
use oneshot::TryRecvError;
|
|
||||||
|
|
||||||
mod components;
|
use crate::cb::CustomTriangleCallback;
|
||||||
mod img_processor;
|
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
color_matrix: SMatrix<f32, 3, 4>,
|
mat: SMatrix<f32, 3, 3>,
|
||||||
pos_matrix: SMatrix<f32, 2, 3>,
|
|
||||||
file_dialog: FileDialog,
|
|
||||||
new_file: Option<PathBuf>,
|
|
||||||
cur_img: Option<Arc<Rgb32FImage>>,
|
|
||||||
proc: Processor,
|
|
||||||
cur_rx: Option<oneshot::Receiver<(egui::TextureHandle, (f32, f32))>>,
|
|
||||||
cur_res: Option<egui::TextureHandle>,
|
|
||||||
cur_size: Option<(f32, f32)>,
|
|
||||||
}
|
}
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> {
|
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> {
|
||||||
|
// Get the WGPU render state from the eframe creation context. This can also be retrieved
|
||||||
|
// from `eframe::Frame` when you don't have a `CreationContext` available.
|
||||||
|
let wgpu_render_state = cc.wgpu_render_state.as_ref()?;
|
||||||
|
|
||||||
|
let device = &wgpu_render_state.device;
|
||||||
|
|
||||||
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
|
label: Some("custom3d"),
|
||||||
|
source: wgpu::ShaderSource::Wgsl(include_str!("./custom3d_wgpu_shader.wgsl").into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("custom3d"),
|
||||||
|
entries: &[wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::VERTEX,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: NonZeroU64::new(16),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("custom3d"),
|
||||||
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("custom3d"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "vs_main",
|
||||||
|
buffers: &[],
|
||||||
|
// compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: "fs_main",
|
||||||
|
targets: &[Some(wgpu_render_state.target_format.into())],
|
||||||
|
// compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState::default(),
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
multiview: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
|
label: Some("custom3d"),
|
||||||
|
contents: bytemuck::cast_slice(&[0.0_f32; 4]), // 16 bytes aligned!
|
||||||
|
// Mapping at creation (as done by the create_buffer_init utility) doesn't require us to to add the MAP_WRITE usage
|
||||||
|
// (this *happens* to workaround this bug )
|
||||||
|
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("custom3d"),
|
||||||
|
layout: &bind_group_layout,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: uniform_buffer.as_entire_binding(),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Because the graphics pipeline must have the same lifetime as the egui render pass,
|
||||||
|
// instead of storing the pipeline in our `Custom3D` struct, we insert it into the
|
||||||
|
// `paint_callback_resources` type map, which is stored alongside the render pass.
|
||||||
|
wgpu_render_state
|
||||||
|
.renderer
|
||||||
|
.write()
|
||||||
|
.callback_resources
|
||||||
|
.insert(TriangleRenderResources {
|
||||||
|
pipeline,
|
||||||
|
bind_group,
|
||||||
|
uniform_buffer,
|
||||||
|
});
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
color_matrix: SMatrix::identity(),
|
mat: SMatrix::identity(),
|
||||||
pos_matrix: SMatrix::identity(),
|
|
||||||
file_dialog: FileDialog::new(),
|
|
||||||
new_file: None,
|
|
||||||
cur_img: None,
|
|
||||||
proc: Processor::init(),
|
|
||||||
cur_rx: None,
|
|
||||||
cur_res: None,
|
|
||||||
cur_size: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for App {
|
impl eframe::App for App {
|
||||||
fn update(&mut self, ctx: &eframe::egui::Context, frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &eframe::egui::Context, frame: &mut eframe::Frame) {
|
||||||
let mut mats_changed = false;
|
|
||||||
egui::SidePanel::right("sidebar").show(ctx, |ui| {
|
|
||||||
if ui.button("Select Image").clicked() {
|
|
||||||
self.file_dialog.select_file()
|
|
||||||
}
|
|
||||||
if self.file_dialog.update(ctx).selected().is_some() {
|
|
||||||
if let Some(path) = self.file_dialog.take_selected() {
|
|
||||||
println!("path: {path:?}");
|
|
||||||
let Some(ext) = path.extension() else {
|
|
||||||
panic!("path should have an extension")
|
|
||||||
};
|
|
||||||
if ext == "jpg" || ext == "jpeg" || ext == "png" {
|
|
||||||
println!("img!");
|
|
||||||
self.cur_img = Some(Arc::new(image::open(path).unwrap().into_rgb32f()));
|
|
||||||
|
|
||||||
self.cur_rx = Some(self.proc.exec(
|
|
||||||
self.cur_img.clone().unwrap(),
|
|
||||||
self.color_matrix,
|
|
||||||
self.pos_matrix,
|
|
||||||
ctx.clone(),
|
|
||||||
self.cur_res.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.add_space(8.);
|
|
||||||
let old_color_mat = self.color_matrix;
|
|
||||||
let old_pos_mat = self.pos_matrix;
|
|
||||||
ui.label("Color Matrix Editor");
|
|
||||||
let color_mat_edited =
|
|
||||||
components::mat_editor(ui, &mut self.color_matrix, "color_matrix").dragged();
|
|
||||||
ui.add_space(8.);
|
|
||||||
ui.label("Position Matrix Editor");
|
|
||||||
let pos_mat_edited =
|
|
||||||
components::mat_editor(ui, &mut self.pos_matrix, "pos_matrix").dragged();
|
|
||||||
mats_changed |= old_color_mat == self.color_matrix || old_pos_mat == self.pos_matrix;
|
|
||||||
|
|
||||||
if mats_changed
|
|
||||||
&& self.cur_img.is_some()
|
|
||||||
&& self.cur_res.is_some()
|
|
||||||
&& !color_mat_edited
|
|
||||||
&& !pos_mat_edited
|
|
||||||
&& self.cur_rx.is_none()
|
|
||||||
{
|
|
||||||
self.cur_rx = Some(self.proc.exec(
|
|
||||||
self.cur_img.clone().unwrap(),
|
|
||||||
self.color_matrix,
|
|
||||||
self.pos_matrix,
|
|
||||||
ctx.clone(),
|
|
||||||
self.cur_res.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.heading("My egui Application");
|
ui.heading("My egui Application");
|
||||||
|
|
||||||
if let Some(r) = &self.cur_rx {
|
components::mat_editor(ui, &mut self.mat);
|
||||||
match r.try_recv() {
|
|
||||||
Ok((handle, size)) => {
|
|
||||||
println!("mew");
|
|
||||||
self.cur_res = Some(handle);
|
|
||||||
self.cur_size = Some(size);
|
|
||||||
self.cur_rx = None;
|
|
||||||
}
|
|
||||||
Err(TryRecvError::Empty) => {}
|
|
||||||
Err(TryRecvError::Disconnected) => panic!("oneshot channel disconnected"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(r) = &self.cur_res {
|
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||||
let sized_img =
|
let (rect, response) =
|
||||||
egui::load::SizedTexture::new(r.id(), egui::Vec2::from(self.cur_size.unwrap()));
|
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
|
||||||
let img = egui::Image::from_texture(sized_img);
|
dbg!(response);
|
||||||
ui.add(img);
|
|
||||||
}
|
|
||||||
// if let Some(dimensions) = self.cur_img_size {
|
|
||||||
// egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
|
||||||
// let (rect, response) = ui.allocate_exact_size(dimensions, egui::Sense::drag());
|
|
||||||
|
|
||||||
// let callback = egui_wgpu::Callback::new_paint_callback(
|
let callback = egui_wgpu::Callback::new_paint_callback(
|
||||||
// rect,
|
rect,
|
||||||
// CustomTriangleCallback { angle: 0.5 },
|
CustomTriangleCallback { angle: 0.5 },
|
||||||
// );
|
);
|
||||||
// ui.painter().add(callback);
|
ui.painter().add(callback);
|
||||||
// });
|
});
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod components {
|
||||||
|
use eframe::egui;
|
||||||
|
use nalgebra::{Matrix, SMatrix};
|
||||||
|
|
||||||
|
pub fn mat_editor<const R: usize, const C: usize>(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
mat: &mut SMatrix<f32, R, C>,
|
||||||
|
) -> egui::Response {
|
||||||
|
egui::Grid::new("mat")
|
||||||
|
.show(ui, |ui| {
|
||||||
|
mat.row_iter_mut().enumerate().for_each(|(i, mut row)| {
|
||||||
|
row.iter_mut().for_each(|item| {
|
||||||
|
ui.add(
|
||||||
|
egui::DragValue::new(item)
|
||||||
|
.speed(0.01)
|
||||||
|
.clamp_range(-1.0..=1.0),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
ui.end_row();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cb {
|
||||||
|
use eframe::{egui, egui_wgpu, wgpu};
|
||||||
|
|
||||||
|
pub struct CustomTriangleCallback {
|
||||||
|
pub angle: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl egui_wgpu::CallbackTrait for CustomTriangleCallback {
|
||||||
|
fn prepare(
|
||||||
|
&self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
_screen_descriptor: &egui_wgpu::ScreenDescriptor,
|
||||||
|
_egui_encoder: &mut wgpu::CommandEncoder,
|
||||||
|
resources: &mut egui_wgpu::CallbackResources,
|
||||||
|
) -> Vec<wgpu::CommandBuffer> {
|
||||||
|
let resources: &TriangleRenderResources = resources.get().unwrap();
|
||||||
|
resources.prepare(device, queue, self.angle);
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint<'a>(
|
||||||
|
&self,
|
||||||
|
_info: egui::PaintCallbackInfo,
|
||||||
|
render_pass: &mut wgpu::RenderPass<'a>,
|
||||||
|
resources: &'a egui_wgpu::CallbackResources,
|
||||||
|
) {
|
||||||
|
let resources: &TriangleRenderResources = resources.get().unwrap();
|
||||||
|
resources.paint(render_pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct TriangleRenderResources {
|
||||||
|
pub pipeline: wgpu::RenderPipeline,
|
||||||
|
pub bind_group: wgpu::BindGroup,
|
||||||
|
pub uniform_buffer: wgpu::Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TriangleRenderResources {
|
||||||
|
fn prepare(&self, _device: &wgpu::Device, queue: &wgpu::Queue, angle: f32) {
|
||||||
|
// Update our uniform buffer with the angle from the UI
|
||||||
|
queue.write_buffer(
|
||||||
|
&self.uniform_buffer,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&[angle, 0.0, 0.0, 0.0]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>) {
|
||||||
|
// Draw our triangle!
|
||||||
|
render_pass.set_pipeline(&self.pipeline);
|
||||||
|
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
render_pass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
var<private> v_positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
|
|
||||||
vec2<f32>(1., 1.),
|
|
||||||
vec2<f32>(1., -1.),
|
|
||||||
vec2<f32>(-1., 1.),
|
|
||||||
vec2<f32>(1., -1.),
|
|
||||||
vec2<f32>(-1., 1.),
|
|
||||||
vec2<f32>(-1., -1.),
|
|
||||||
);
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vs_main(@builtin(vertex_index) v_idx: u32) -> @builtin(position) vec4<f32> {
|
|
||||||
return vec4<f32>(v_positions[v_idx], 0., 0.);
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main() -> @location(0) vec4<f32> {
|
|
||||||
return vec4<f32>(0.8, 0.2, 0.8, 1.);
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue