This commit is contained in:
Schrottkatze 2024-05-17 21:06:32 +02:00
commit 22f43dbb78
Signed by: schrottkatze
SSH key fingerprint: SHA256:hXb3t1vINBFCiDCmhRABHX5ocdbLiKyCdKI4HK2Rbbc
12 changed files with 4314 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake . --impure

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/target
/.direnv
/.devenv

2
.helix/languages.toml Normal file
View file

@ -0,0 +1,2 @@
[language-server.rust-analyzer]
# config = { cargo = { target = "wasm32-unknown-unknown" } }

3737
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

28
Cargo.toml Normal file
View file

@ -0,0 +1,28 @@
[package]
name = "mathe-img-processor"
version = "0.1.0"
edition = "2021"
[dependencies]
bytemuck = "1.16.0"
eframe = {version = "0.27", features = ["wgpu"]}
env_logger = "0.11.3"
# winit = { version = "0.30", features = ["rwh_05" ] }
# log = "0.4"
# console_error_panic_hook = "0.1.6"
# console_log = "1.0"
# cfg-if = "1"
# wgpu = { version = "0.19", features = ["webgl"]}
# wasm-bindgen = "0.2"
# wasm-bindgen-futures = "0.4.30"
# web-sys = { version = "0.3", features = [
# "Document",
# "Window",
# "Element",
# ]}
# [lib]
# crate-type = ["cdylib"]
# [build]
# target = "wasm32-unknown-unknown"

21
TODO.md Normal file
View file

@ -0,0 +1,21 @@
# TODOs
- [ ] GPU/Processing/Rendering
- [ ] color matrix multiplication
- [ ] transform matrix multiplication
- [ ] Optional: convolutions
- [ ] abstractions over egui stuff
- [ ] independent lib/cli app?
- [ ] ui
- [ ] components
- [ ] number picker with slider at bottom?
- [ ] (arbitrary size?) matrix component
- [ ] matrix list that outputs single matrix multiplied from them all
- [ ] Optional: help/what is this buttons
- [ ] Optional: explainer per pixel what's happening on hover
- [ ] view
- [ ] image view
- [ ] matrix ("columns"?) to apply
- [ ] transform
- [ ] color
- [ ] Optional: Convolution

179
flake.lock Normal file
View file

@ -0,0 +1,179 @@
{
"nodes": {
"crane": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1715274763,
"narHash": "sha256-3Iv1PGHJn9sV3HO4FlOVaaztOxa9uGLfOmUWrH7v7+A=",
"owner": "ipetkov",
"repo": "crane",
"rev": "27025ab71bdca30e7ed0a16c88fd74c5970fc7f5",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1715927173,
"narHash": "sha256-2S8hVck6nlyiBifzymDvePl5HWgqvVgxkBZCRax1qD8=",
"owner": "nix-community",
"repo": "fenix",
"rev": "a2d19ef9305841f26c8ab908b1c09a84ca307e18",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1715867414,
"narHash": "sha256-cu4UEffKkBByyGR6CFs9XP6iSNsKTkq1r66DA5BkYnE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "bf446f08bff6814b569265bef8374cfdd3d8f0e0",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"fenix": "fenix",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"wgsl_analyzer": "wgsl_analyzer"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1715839492,
"narHash": "sha256-EyjtjocGLtB7tqyqwBfadP4y5BBtT5EkoG3kq/zym5U=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "83ba42043166948db91fcfcfe30e0b7eac10b3d5",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"wgsl_analyzer": {
"inputs": {
"crane": [
"crane"
],
"flake-utils": "flake-utils_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1702151450,
"narHash": "sha256-bhosTihbW89vkqp1ua0C1HGLJJdCNfRde98z4+IjkOc=",
"owner": "wgsl-analyzer",
"repo": "wgsl-analyzer",
"rev": "8851962fc191aa5ea85a25b0ebd10cb9f70627b3",
"type": "github"
},
"original": {
"owner": "wgsl-analyzer",
"repo": "wgsl-analyzer",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

90
flake.nix Normal file
View file

@ -0,0 +1,90 @@
{
description = "Build a cargo project without extra checks";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
crane = {
url = "github:ipetkov/crane";
inputs.nixpkgs.follows = "nixpkgs";
};
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
wgsl_analyzer = {
url = "github:wgsl-analyzer/wgsl-analyzer";
inputs.nixpkgs.follows = "nixpkgs";
inputs.crane.follows = "crane";
};
};
outputs = {
self,
nixpkgs,
crane,
fenix,
flake-utils,
wgsl_analyzer,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
craneLib = crane.lib.${system};
rs-toolchain = with fenix.packages.${system};
combine [
complete.toolchain
targets.wasm32-unknown-unknown.latest.rust-std
# rust-analyzer
];
my-crate = craneLib.buildPackage {
src = craneLib.cleanCargoSource (craneLib.path ./.);
strictDeps = true;
buildInputs =
[
# Add additional build inputs here
]
++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
# Additional darwin specific inputs can be set here
pkgs.libiconv
];
# Additional environment variables can be set directly
# MY_CUSTOM_VAR = "some value";
};
in {
packages.default = my-crate;
apps.default = flake-utils.lib.mkApp {
drv = my-crate;
};
devShells.default = pkgs.mkShell rec {
buildInputs = with pkgs; [
pkg-config
rs-toolchain
udev
alsa-lib
vulkan-loader
libxkbcommon
wayland
xorg.libX11
xorg.libXcursor
xorg.libXi
xorg.libXrandr # mold-wrapped
gtk3
clang
trunk
binaryen
wgsl_analyzer.outputs.packages."x86_64-linux".default
];
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
};
});
}

11
index.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=, initial-scale=">
<title>Wasm test</title>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,39 @@
struct VertexOut {
@location(0) color: vec4<f32>,
@builtin(position) position: vec4<f32>,
};
struct Uniforms {
@size(16) angle: f32, // pad to 16 bytes
};
@group(0) @binding(0)
var<uniform> uniforms: Uniforms;
var<private> v_positions: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(-1.0, -1.0),
);
var<private> v_colors: array<vec4<f32>, 3> = array<vec4<f32>, 3>(
vec4<f32>(1.0, 0.0, 0.0, 1.0),
vec4<f32>(0.0, 1.0, 0.0, 1.0),
vec4<f32>(0.0, 0.0, 1.0, 1.0),
);
@vertex
fn vs_main(@builtin(vertex_index) v_idx: u32) -> VertexOut {
var out: VertexOut;
out.position = vec4<f32>(v_positions[v_idx], 0.0, 1.0);
out.position.x = out.position.x * cos(uniforms.angle);
out.color = v_colors[v_idx];
return out;
}
@fragment
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
return in.color;
}

190
src/lib.rs Normal file
View file

@ -0,0 +1,190 @@
use std::num::NonZeroU64;
use cb::TriangleRenderResources;
use eframe::{
egui, egui_wgpu,
wgpu::{self, util::DeviceExt},
};
use crate::cb::CustomTriangleCallback;
pub struct App {
name: String,
age: u32,
}
impl App {
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 {
name: "Jade".to_owned(),
age: 19,
})
}
}
impl eframe::App for App {
fn update(&mut self, ctx: &eframe::egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("My egui Application");
ui.horizontal(|ui| {
let name_label = ui.label("Your name: ");
ui.text_edit_singleline(&mut self.name)
.labelled_by(name_label.id);
});
ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
if ui.button("Increment").clicked() {
self.age += 1;
}
ui.label(format!("Hello '{}', age {}", self.name, self.age));
egui::Frame::canvas(ui.style()).show(ui, |ui| {
let (rect, response) =
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
dbg!(response);
let callback = egui_wgpu::Callback::new_paint_callback(
rect,
CustomTriangleCallback { angle: 0.5 },
);
ui.painter().add(callback);
});
});
}
}
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);
}
}
}

13
src/main.rs Normal file
View file

@ -0,0 +1,13 @@
use mathe_img_processor::App;
fn main() -> Result<(), eframe::Error> {
env_logger::init();
eframe::run_native(
"meow",
eframe::NativeOptions {
renderer: eframe::Renderer::Wgpu,
..Default::default()
},
Box::new(|cc| Box::new(App::new(cc).unwrap())),
)
}