├── .gitignore
├── src
├── lib.rs
├── main.rs
├── shader.wgsl
└── window.rs
├── README.md
├── index.html
├── index.css
├── Cargo.toml
└── Cargo.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[cfg(target_arch = "wasm32")]
2 | mod window;
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | #[tokio::main]
2 | async fn main() {
3 | // build our application with a single route
4 | let app = axum::Router::new().nest("/app", axum_static::static_router("."));
5 |
6 | println!("Running on http://0.0.0.0:3000/app/index.html");
7 | axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
8 | .serve(app.into_make_service())
9 | .await
10 | .unwrap();
11 | }
12 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Bloomen
6 |
7 |
8 |
9 |
10 |
11 | bloomen
12 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | padding: 0;
3 | margin: 0;
4 | font-family: sans-serif;
5 | }
6 | h1 {
7 | background-color: #222;
8 | color: #eee;
9 | text-align: center;
10 | width: 100%;
11 | padding: 0.5em 0;
12 | }
13 | .images {
14 | display: flex;
15 | padding: 1%;
16 | justify-content: center;
17 | }
18 |
19 | canvas {
20 | margin: 1%;
21 | background-color: #ddd;
22 | object-fit: contain;
23 | max-height: 100%;
24 | max-width: 100%;
25 | image-rendering: crisp-edges;
26 | image-rendering: pixelated;
27 | }
28 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "bloomen"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | log = "0.4.17"
10 | wgpu = { version = "0.16", features = ["webgl"]}
11 | console_error_panic_hook = "0.1.7"
12 | console_log = "1.0.0"
13 | raw-window-handle = "0.5.2"
14 |
15 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
16 | tokio = { version = "1.27.0", features = ["macros", "rt-multi-thread"] }
17 | axum = "0.6.16"
18 | axum_static = "1.2.1"
19 |
20 | [dependencies.image]
21 | version = "0.24.6"
22 | default-features = false
23 | features = ["png", "jpeg"]
24 |
25 | [target.'cfg(target_arch = "wasm32")'.dependencies]
26 | tokio = { version = "1.27.0", features = ["macros", "sync"] }
27 | wasm-bindgen = "0.2"
28 | wasm-bindgen-futures = "0.4.34"
29 | web-sys = { version = "0.3", features = [
30 | "Document",
31 | "Window",
32 | "Element",
33 | "MouseEvent",
34 | "DomRect",
35 | ]}
36 | js-sys = "0.3.61"
37 | bytemuck = { version = "1.13.1", features = ["derive"] }
38 | instant = { version = "0.1.12", features = [ "wasm-bindgen" ] }
39 |
40 | [lib]
41 | crate-type = ["cdylib", "rlib"]
42 |
--------------------------------------------------------------------------------
/src/shader.wgsl:
--------------------------------------------------------------------------------
1 | @group(0) @binding(0) var texture0: texture_2d;
2 | @group(0) @binding(1) var texture1: texture_2d;
3 | @group(0) @binding(2) var texture2: texture_2d;
4 | @group(0) @binding(3) var sampler1: sampler;
5 | @group(1) @binding(0) var uniforms: Uniforms;
6 |
7 | //https://stackoverflow.com/questions/5149544/can-i-generate-a-random-number-inside-a-pixel-shader
8 | fn random(p: vec2) -> f32 {
9 | let K1 = vec2(
10 | 23.14069263277926, // e^pi (Gelfond's constant)
11 | 2.665144142690225 // 2^sqrt(2) (Gelfond–Schneider constant)
12 | );
13 | return fract( cos( dot(p,K1) ) * 12345.6789 );
14 | }
15 |
16 | struct Uniforms {
17 | mouse_pos: vec2,
18 | level: u32,
19 | time: f32,
20 | };
21 |
22 | struct VertexOutput {
23 | @builtin(position) clip_position: vec4,
24 | };
25 |
26 | @vertex
27 | fn vs(
28 | @builtin(vertex_index) in_vertex_index: u32,
29 | ) -> VertexOutput {
30 | var out: VertexOutput;
31 | let x = f32(1 - i32(in_vertex_index)) * 5.0;
32 | let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 2.0;
33 | out.clip_position = vec4(x, y, 0.0, 1.0);
34 | return out;
35 | }
36 |
37 | @fragment
38 | fn fs_bloom_select(in: VertexOutput) -> @location(0) vec4 {
39 | let x = i32(in.clip_position.x);
40 | let y = i32(in.clip_position.y);
41 |
42 | if x+i32(100.0*sin(0.5 * uniforms.time)*sin(f32(y)/100.0+0.25 * uniforms.time)) >= 512 - 25 && x+i32(100.0*sin(0.5 * uniforms.time)*sin(f32(y)/100.0+0.25 * uniforms.time)) <= 512+25 || y == 50 || y == 1024 - 50 {
43 | return vec4(10.0, 1.0, 1.0, 1.0);
44 | }
45 |
46 | return vec4(0.0, 0.0, 0.0, 1.0);
47 | }
48 |
49 | fn blur(tex: texture_2d, pos: vec2f, direction: vec2f, mip: i32) -> vec4f {
50 | let hstep = direction.x;
51 | let vstep = direction.y;
52 |
53 | var sum: vec4f;
54 | sum = textureSampleLevel(tex, sampler1, pos+vec2(-4.0*hstep, -4.0*vstep), f32(mip)) * 0.0162162162;
55 | sum += textureSampleLevel(tex, sampler1, pos+vec2(-3.0*hstep, -3.0*vstep), f32(mip)) * 0.0540540541;
56 | sum += textureSampleLevel(tex, sampler1, pos+vec2(-2.0*hstep, -2.0*vstep), f32(mip)) * 0.1216216216;
57 | sum += textureSampleLevel(tex, sampler1, pos+vec2(-1.0*hstep, -1.0*vstep), f32(mip)) * 0.1945945946;
58 | sum += textureSampleLevel(tex, sampler1, pos+vec2(0.0*hstep, 0.0*vstep), f32(mip)) * 0.2270270270;
59 | sum += textureSampleLevel(tex, sampler1, pos+vec2(1.0*hstep, 1.0*vstep), f32(mip)) * 0.1945945946;
60 | sum += textureSampleLevel(tex, sampler1, pos+vec2(2.0*hstep, 2.0*vstep), f32(mip)) * 0.1216216216;
61 | sum += textureSampleLevel(tex, sampler1, pos+vec2(3.0*hstep, 3.0*vstep), f32(mip)) * 0.0540540541;
62 | sum += textureSampleLevel(tex, sampler1, pos+vec2(4.0*hstep, 4.0*vstep), f32(mip)) * 0.0162162162;
63 |
64 | return vec4(sum.rgb, 1.0);
65 | }
66 |
67 | @fragment
68 | fn fs_bloom_blur1(in: VertexOutput) -> @location(0) vec4 {
69 | let size = 1024.0 / pow(2.0, f32(uniforms.level)) * vec2(1.0, 1.0);
70 | let pos = in.clip_position.xy / size;
71 |
72 | return blur(texture2, pos, vec2(1.0, 0.0)/size, i32(uniforms.level - 1u));
73 | }
74 |
75 | @fragment
76 | fn fs_bloom_blur2(in: VertexOutput) -> @location(0) vec4 {
77 | let size = 1024.0 / pow(2.0, f32(uniforms.level)) * vec2(1.0, 1.0);
78 | let pos = in.clip_position.xy / size;
79 |
80 | return blur(texture2, pos, vec2(0.0, 1.0)/size, i32(uniforms.level));
81 | //return textureLoad(texture2, vec2(x, y), i32(uniforms.level));
82 | }
83 |
84 | fn powi(base: i32, exp: u32) -> i32 {
85 | return i32(pow(f32(base), f32(exp)));
86 | }
87 |
88 | @fragment
89 | fn fs_bloom_add(in: VertexOutput) -> @location(0) vec4 {
90 | let size = 1024.0 / pow(2.0, f32(uniforms.level - 1u)) * vec2(1.0, 1.0);
91 | //let size = vec2f(textureDimensions(texture2).xy);
92 | let pos = in.clip_position.xy / size;
93 |
94 | var color: vec4f;
95 | color = textureSampleLevel(texture2, sampler1, pos, f32(uniforms.level - 1u));
96 | color += textureSampleLevel(texture2, sampler1, pos, f32(uniforms.level));
97 | //color += blur(texture2, x/2, y/2, vec2(1, 0), i32(uniforms.level));
98 | //color = vec4(0.0, 0.0, 0.0, 0.0);
99 | //color += 0.05 * textureLoad(texture2, vec2(x, y), 0);
100 | //color += textureLoad(texture2, vec2(x, y), i32(uniforms.padding));
101 | //color += textureSampleLevel(texture2, sampler1, vec2(f32(x), f32(y))/1024.0, f32(uniforms.padding));
102 | //color += textureLoad(texture2, vec2(x/powi(2, uniforms.level), y/powi(2, uniforms.level)), i32(uniforms.level));
103 |
104 | return vec4(color.rgb, 1.0);
105 | }
106 |
107 | @fragment
108 | fn fs_main(in: VertexOutput) -> @location(0) vec4 {
109 | let size = 1024.0 * vec2(1.0, 1.0);
110 | let pos = in.clip_position.xy / size;
111 |
112 | return vec4(textureSampleLevel(texture2, sampler1, pos, 1.0).rgb, 1.0);
113 | }
114 |
--------------------------------------------------------------------------------
/src/window.rs:
--------------------------------------------------------------------------------
1 | use instant::Instant;
2 | use log::warn;
3 | use raw_window_handle::{
4 | HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, WebDisplayHandle,
5 | WebWindowHandle,
6 | };
7 | use std::cell::RefCell;
8 | use std::rc::Rc;
9 | use std::sync::Arc;
10 | use std::sync::RwLock;
11 | use wasm_bindgen::prelude::*;
12 | use wgpu::util::DeviceExt;
13 |
14 | struct WebWindow;
15 | unsafe impl HasRawDisplayHandle for WebWindow {
16 | fn raw_display_handle(&self) -> RawDisplayHandle {
17 | RawDisplayHandle::Web(WebDisplayHandle::empty())
18 | }
19 | }
20 | unsafe impl HasRawWindowHandle for WebWindow {
21 | fn raw_window_handle(&self) -> RawWindowHandle {
22 | RawWindowHandle::Web(WebWindowHandle::empty())
23 | }
24 | }
25 |
26 | #[repr(C)]
27 | #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
28 | struct Uniforms {
29 | mouse_pos: [f32; 2],
30 | level: u32,
31 | time: f32,
32 | }
33 |
34 | impl Uniforms {
35 | fn new() -> Self {
36 | Self {
37 | mouse_pos: [-1000.0, 0.0],
38 | level: 0,
39 | time: 0.0,
40 | }
41 | }
42 | }
43 |
44 | struct State {
45 | surface: wgpu::Surface,
46 | device: wgpu::Device,
47 | queue: wgpu::Queue,
48 | config: wgpu::SurfaceConfiguration,
49 | bloom_select_pipeline: wgpu::RenderPipeline,
50 | bloom_blur1_pipeline: wgpu::RenderPipeline,
51 | bloom_blur2_pipeline: wgpu::RenderPipeline,
52 | bloom_add_pipeline: wgpu::RenderPipeline,
53 | render_pipeline: wgpu::RenderPipeline,
54 | mousedown: RwLock,
55 | last_mousepos: RwLock