From ec48592ef15876c5c824b10ab5ff0c2f5bd3a76a Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 26 Aug 2024 23:39:51 -0700 Subject: [PATCH 01/12] Ray-caster: add a frame time monitor It's time to start optimizing. Frame time is easy to measure, and we can use it to gauge responsiveness. --- app-proto/inversive-display/Cargo.toml | 1 + app-proto/inversive-display/src/main.rs | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app-proto/inversive-display/Cargo.toml b/app-proto/inversive-display/Cargo.toml index bcf6b76..c0cbf3d 100644 --- a/app-proto/inversive-display/Cargo.toml +++ b/app-proto/inversive-display/Cargo.toml @@ -22,6 +22,7 @@ console_error_panic_hook = { version = "0.1.7", optional = true } version = "0.3.69" features = [ 'HtmlCanvasElement', + 'Performance', 'WebGl2RenderingContext', 'WebGlBuffer', 'WebGlProgram', diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index ebbf713..5f1987f 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -11,7 +11,7 @@ extern crate js_sys; use core::array; use nalgebra::DVector; use sycamore::{prelude::*, motion::create_raf, rt::{JsCast, JsValue}}; -use web_sys::{console, WebGl2RenderingContext, WebGlProgram, WebGlShader, WebGlUniformLocation}; +use web_sys::{console, window, WebGl2RenderingContext, WebGlProgram, WebGlShader, WebGlUniformLocation}; mod engine; @@ -99,6 +99,12 @@ fn main() { let layer_threshold = create_signal(0.0); let debug_mode = create_signal(false); + /* INSTRUMENTS */ + const SAMPLE_PERIOD: i32 = 20; + let mut last_frame_moment = 0.0; + let mut frames_since_last_sample = 0; + let frame_time = create_signal(0.0); + // display let display = create_node_ref(); @@ -112,6 +118,9 @@ fn main() { [0.25_f32, 0.00_f32, 1.00_f32] ]; + /* INSTRUMENTS */ + let performance = window().unwrap().performance().unwrap(); + // get the display canvas let canvas = display .get::() @@ -208,6 +217,16 @@ fn main() { // set up a repainting routine let (_, start_updating_display, _) = create_raf(move || { + /* INSTRUMENTS */ + // measure frame time + frames_since_last_sample += 1; + if frames_since_last_sample >= SAMPLE_PERIOD { + let frame_moment = performance.now(); + frame_time.set((frame_moment - last_frame_moment) / (SAMPLE_PERIOD as f64)); + last_frame_moment = frame_moment; + frames_since_last_sample = 0; + } + // update the construction sphere_vec.clear(); sphere_vec.push(engine::sphere(0.5, 0.5, -5.0 + ctrl_x.get(), radius_x.get())); @@ -261,6 +280,7 @@ fn main() { view! { div(id="app") { + div { (frame_time.get()) " ms" } canvas(ref=display, width="600", height="600") div(class="control") { label(for="ctrl-x") { "Sphere 0 depth" } From f62f44b5a77fa2cbefad4608538aa07449d149b3 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 27 Aug 2024 00:00:48 -0700 Subject: [PATCH 02/12] Optimization: decouple internal and uniform SPHERE_MAX --- app-proto/inversive-display/src/inversive.frag | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app-proto/inversive-display/src/inversive.frag b/app-proto/inversive-display/src/inversive.frag index 51bc0ff..ce6deae 100644 --- a/app-proto/inversive-display/src/inversive.frag +++ b/app-proto/inversive-display/src/inversive.frag @@ -25,10 +25,10 @@ vecInv sphere(vec3 center, float radius) { // construction. the SPHERE_MAX array size seems to affect frame rate a lot, // even though we should only be using the first few elements of each array -const int SPHERE_MAX = 12; +const int SPHERE_MAX_UNIFORM = 12; uniform int sphere_cnt; -uniform vecInv sphere_list[SPHERE_MAX]; -uniform vec3 color_list[SPHERE_MAX]; +uniform vecInv sphere_list[SPHERE_MAX_UNIFORM]; +uniform vec3 color_list[SPHERE_MAX_UNIFORM]; // view uniform vec2 resolution; @@ -135,8 +135,9 @@ void main() { vec3 dir = vec3(focal_slope * scr, -1.); // cast rays through the spheres - vec2 depth_pairs [SPHERE_MAX]; - taggedFrag frags [2*SPHERE_MAX]; + const int SPHERE_MAX_INTERNAL = 6; + vec2 depth_pairs [SPHERE_MAX_INTERNAL]; + taggedFrag frags [2*SPHERE_MAX_INTERNAL]; int frag_cnt = 0; for (int i = 0; i < sphere_cnt; ++i) { vec2 hit_depths = sphere_cast(sphere_list[i], dir); From a40a110788e577417cfede4fb6fb3b130d86b37e Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 27 Aug 2024 13:55:08 -0700 Subject: [PATCH 03/12] Ray-caster: only draw when the scene is changed This is how I typically schedule draw calls in JavaScript applications. The baseline CPU activity for the display prototype is now in line with other pages (though perhaps a bit higher), and the profiler shows little time being spent in draw calls, even when I'm continually moving a slider. The interface feels pretty responsive overall, although the sliders seem to be lagging a bit. --- app-proto/inversive-display/src/main.rs | 130 +++++++++++++----------- 1 file changed, 73 insertions(+), 57 deletions(-) diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index 5f1987f..15c2518 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -108,6 +108,21 @@ fn main() { // display let display = create_node_ref(); + // change listener + let scene_changed = create_signal(true); + create_effect(move || { + ctrl_x.track(); + ctrl_y.track(); + radius_x.track(); + radius_y.track(); + opacity.track(); + highlight.track(); + layer_threshold.track(); + debug_mode.track(); + + scene_changed.set(true); + }); + on_mount(move || { // list construction elements const SPHERE_MAX: usize = 12; @@ -216,66 +231,67 @@ fn main() { bind_vertex_attrib(&ctx, position_index, 3, &positions); // set up a repainting routine - let (_, start_updating_display, _) = create_raf(move || { - /* INSTRUMENTS */ - // measure frame time - frames_since_last_sample += 1; - if frames_since_last_sample >= SAMPLE_PERIOD { - let frame_moment = performance.now(); - frame_time.set((frame_moment - last_frame_moment) / (SAMPLE_PERIOD as f64)); - last_frame_moment = frame_moment; + let (_, start_animation_loop, _) = create_raf(move || { + if scene_changed.get() { + /* INSTRUMENTS */ + // measure frame time + frames_since_last_sample += 1; + if frames_since_last_sample >= SAMPLE_PERIOD { + let frame_moment = performance.now(); + frame_time.set((frame_moment - last_frame_moment) / (SAMPLE_PERIOD as f64)); + last_frame_moment = frame_moment; + frames_since_last_sample = 0; + } + + // update the construction + sphere_vec.clear(); + sphere_vec.push(engine::sphere(0.5, 0.5, -5.0 + ctrl_x.get(), radius_x.get())); + sphere_vec.push(engine::sphere(-0.5, -0.5, -5.0 + ctrl_y.get(), radius_y.get())); + sphere_vec.push(engine::sphere(-0.5, 0.5, -5.0, 0.75)); + + // set the resolution + let width = canvas.width() as f32; + let height = canvas.height() as f32; + ctx.uniform2f(resolution_loc.as_ref(), width, height); + ctx.uniform1f(shortdim_loc.as_ref(), width.min(height)); + + // pass the construction + ctx.uniform1i(sphere_cnt_loc.as_ref(), sphere_vec.len() as i32); + for n in 0..sphere_vec.len() { + let v = &sphere_vec[n]; + ctx.uniform3f( + sphere_sp_locs[n].as_ref(), + v[0] as f32, v[1] as f32, v[2] as f32 + ); + ctx.uniform2f( + sphere_lt_locs[n].as_ref(), + v[3] as f32, v[4] as f32 + ); + ctx.uniform3fv_with_f32_array( + color_locs[n].as_ref(), + &color_vec[n] + ); + } + + // pass the control parameters + ctx.uniform2f(ctrl_loc.as_ref(), ctrl_x.get() as f32, ctrl_y.get() as f32); /* DEBUG */ + ctx.uniform2f(radius_loc.as_ref(), radius_x.get() as f32, radius_y.get() as f32); /* DEBUG */ + ctx.uniform1f(opacity_loc.as_ref(), opacity.get() as f32); + ctx.uniform1f(highlight_loc.as_ref(), highlight.get() as f32); + ctx.uniform1i(layer_threshold_loc.as_ref(), layer_threshold.get() as i32); + ctx.uniform1i(debug_mode_loc.as_ref(), debug_mode.get() as i32); + + // draw the scene + ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32); + + // clear scene change flag + scene_changed.set(false); + } else { frames_since_last_sample = 0; + frame_time.set(-1.0); } - - // update the construction - sphere_vec.clear(); - sphere_vec.push(engine::sphere(0.5, 0.5, -5.0 + ctrl_x.get(), radius_x.get())); - sphere_vec.push(engine::sphere(-0.5, -0.5, -5.0 + ctrl_y.get(), radius_y.get())); - sphere_vec.push(engine::sphere(-0.5, 0.5, -5.0, 0.75)); - - // set the resolution - let width = canvas.width() as f32; - let height = canvas.height() as f32; - ctx.uniform2f(resolution_loc.as_ref(), width, height); - ctx.uniform1f(shortdim_loc.as_ref(), width.min(height)); - - // pass the construction - ctx.uniform1i(sphere_cnt_loc.as_ref(), sphere_vec.len() as i32); - for n in 0..sphere_vec.len() { - let v = &sphere_vec[n]; - ctx.uniform3f( - sphere_sp_locs[n].as_ref(), - v[0] as f32, v[1] as f32, v[2] as f32 - ); - ctx.uniform2f( - sphere_lt_locs[n].as_ref(), - v[3] as f32, v[4] as f32 - ); - ctx.uniform3fv_with_f32_array( - color_locs[n].as_ref(), - &color_vec[n] - ); - } - - // pass the control parameters - ctx.uniform2f(ctrl_loc.as_ref(), ctrl_x.get() as f32, ctrl_y.get() as f32); /* DEBUG */ - ctx.uniform2f(radius_loc.as_ref(), radius_x.get() as f32, radius_y.get() as f32); /* DEBUG */ - ctx.uniform1f(opacity_loc.as_ref(), opacity.get() as f32); - ctx.uniform1f(highlight_loc.as_ref(), highlight.get() as f32); - ctx.uniform1i(layer_threshold_loc.as_ref(), layer_threshold.get() as i32); - ctx.uniform1i(debug_mode_loc.as_ref(), debug_mode.get() as i32); - - // draw the scene - ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32); }); - - /* - this wastes CPU time by running an animation loop, which updates the - display even when nothing has changed. there should be a way to make - Sycamore do single-frame updates in response to changes, but i - haven't found it yet - */ - start_updating_display(); + start_animation_loop(); }); view! { From 01c2af6615a4a600c75fac3923276ec53516f709 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 27 Aug 2024 18:39:58 -0700 Subject: [PATCH 04/12] Rotate and translate construction In the process, write code to make updates that depend on the time between frames. --- app-proto/inversive-display/src/main.rs | 82 ++++++++++++++++++++----- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index 15c2518..4da24f5 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -9,7 +9,7 @@ extern crate js_sys; use core::array; -use nalgebra::DVector; +use nalgebra::{DMatrix, DVector}; use sycamore::{prelude::*, motion::create_raf, rt::{JsCast, JsValue}}; use web_sys::{console, window, WebGl2RenderingContext, WebGlProgram, WebGlShader, WebGlUniformLocation}; @@ -96,14 +96,15 @@ fn main() { let radius_y = create_signal(1.0); let opacity = create_signal(0.5); let highlight = create_signal(0.2); - let layer_threshold = create_signal(0.0); - let debug_mode = create_signal(false); + let turntable = create_signal(false); + let layer_threshold = create_signal(0.0); /* DEBUG */ + let debug_mode = create_signal(false); /* DEBUG */ /* INSTRUMENTS */ const SAMPLE_PERIOD: i32 = 20; - let mut last_frame_moment = 0.0; + let mut last_sample_time = 0.0; let mut frames_since_last_sample = 0; - let frame_time = create_signal(0.0); + let mean_frame_interval = create_signal(0.0); // display let display = create_node_ref(); @@ -117,6 +118,7 @@ fn main() { radius_y.track(); opacity.track(); highlight.track(); + turntable.track(); layer_threshold.track(); debug_mode.track(); @@ -133,6 +135,13 @@ fn main() { [0.25_f32, 0.00_f32, 1.00_f32] ]; + // timing + let mut last_time = 0.0; + + // scene parameters + const TURNTABLE_SPEED: f64 = 0.5; + let mut turntable_angle = 0.0; + /* INSTRUMENTS */ let performance = window().unwrap().performance().unwrap(); @@ -232,22 +241,57 @@ fn main() { // set up a repainting routine let (_, start_animation_loop, _) = create_raf(move || { + // get the time step + let time = performance.now(); + let time_step = 0.001*(time - last_time); + last_time = time; + + // move the turntable + let turntable_val = turntable.get(); + if turntable_val { + turntable_angle += TURNTABLE_SPEED * time_step; + } + if scene_changed.get() { /* INSTRUMENTS */ - // measure frame time + // measure mean frame interval frames_since_last_sample += 1; if frames_since_last_sample >= SAMPLE_PERIOD { - let frame_moment = performance.now(); - frame_time.set((frame_moment - last_frame_moment) / (SAMPLE_PERIOD as f64)); - last_frame_moment = frame_moment; + mean_frame_interval.set((time - last_sample_time) / (SAMPLE_PERIOD as f64)); + last_sample_time = time; frames_since_last_sample = 0; } + // set the orientation and translation + let orientation = { + let ang_cos = turntable_angle.cos(); + let ang_sin = turntable_angle.sin(); + DMatrix::from_column_slice(5, 5, &[ + ang_cos, 0.0, ang_sin, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, + -ang_sin, 0.0, ang_cos, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 1.0 + ]) + }; + let translation = { + const LEN: f64 = -5.0; + const LEN_SQ: f64 = LEN*LEN; + DMatrix::from_column_slice(5, 5, &[ + 1.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, LEN, + 0.0, 0.0, 2.0*LEN, 1.0, LEN_SQ, + 0.0, 0.0, 0.0, 0.0, 1.0 + ]) + }; + let construction_to_world = &translation * orientation; + // update the construction sphere_vec.clear(); - sphere_vec.push(engine::sphere(0.5, 0.5, -5.0 + ctrl_x.get(), radius_x.get())); - sphere_vec.push(engine::sphere(-0.5, -0.5, -5.0 + ctrl_y.get(), radius_y.get())); - sphere_vec.push(engine::sphere(-0.5, 0.5, -5.0, 0.75)); + sphere_vec.push(&construction_to_world * engine::sphere(0.5, 0.5, ctrl_x.get(), radius_x.get())); + sphere_vec.push(&construction_to_world * engine::sphere(-0.5, -0.5, ctrl_y.get(), radius_y.get())); + sphere_vec.push(&construction_to_world * engine::sphere(-0.5, 0.5, 0.0, 0.75)); // set the resolution let width = canvas.width() as f32; @@ -285,10 +329,10 @@ fn main() { ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32); // clear scene change flag - scene_changed.set(false); + scene_changed.set(turntable_val); } else { frames_since_last_sample = 0; - frame_time.set(-1.0); + mean_frame_interval.set(-1.0); } }); start_animation_loop(); @@ -296,7 +340,7 @@ fn main() { view! { div(id="app") { - div { (frame_time.get()) " ms" } + div { "Mean frame interval: " (mean_frame_interval.get()) " ms" } canvas(ref=display, width="600", height="600") div(class="control") { label(for="ctrl-x") { "Sphere 0 depth" } @@ -362,6 +406,14 @@ fn main() { bind:valueAsNumber=highlight ) } + div(class="control") { + label(for="turntable") { "Turntable" } + input( + type="checkbox", + id="turntable", + bind:checked=turntable + ) + } div(class="control") { label(for="layer-threshold") { "Layer threshold" } input( From c04e29f58672a8113a73a6a7ca29d0a7ba5c35db Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 28 Aug 2024 01:47:38 -0700 Subject: [PATCH 05/12] Reduce the frame interval sample frequency At the higher frequency we were using earlier, the overhead from updating the readout contributed significantly to the frame interval. --- app-proto/inversive-display/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index 4da24f5..c0e67a4 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -101,7 +101,7 @@ fn main() { let debug_mode = create_signal(false); /* DEBUG */ /* INSTRUMENTS */ - const SAMPLE_PERIOD: i32 = 20; + const SAMPLE_PERIOD: i32 = 60; let mut last_sample_time = 0.0; let mut frames_since_last_sample = 0; let mean_frame_interval = create_signal(0.0); From 3d7ee98dd64267cb00732079f5a51b45d5e900f2 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 28 Aug 2024 01:50:17 -0700 Subject: [PATCH 06/12] Ray-caster: check layer count In debug mode, show the layer count instead of the shaded image. This reveals a bug: testing whether the hit depth is NaN doesn't actually detect sphere misses, because `sphere_cast` returns -1 rather than NaN to indicate a miss. --- app-proto/inversive-display/src/inversive.frag | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app-proto/inversive-display/src/inversive.frag b/app-proto/inversive-display/src/inversive.frag index ce6deae..5620210 100644 --- a/app-proto/inversive-display/src/inversive.frag +++ b/app-proto/inversive-display/src/inversive.frag @@ -40,7 +40,7 @@ uniform vec2 radius; uniform float opacity; uniform float highlight; uniform int layer_threshold; -uniform bool test_mode; +uniform bool debug_mode; // light and camera const float focal_slope = 0.3; @@ -151,6 +151,19 @@ void main() { } } + /* DEBUG */ + // in debug mode, show the layer count instead of the shaded image + if (debug_mode) { + // at the bottom of the screen, show the color scale instead of the + // layer count + if (gl_FragCoord.y < 10.) frag_cnt = int(8. * gl_FragCoord.x / resolution.x); + + // convert number to color + ivec3 bits = frag_cnt / ivec3(1, 2, 4); + outColor = vec4(mod(vec3(bits), 2.), 1.); + return; + } + // sort the fragments by depth, using an insertion sort for (int take = 1; take < frag_cnt; ++take) { taggedFrag pulled = frags[take]; From e80adf831d03e64d37cb359796297420c15dbfab Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 28 Aug 2024 01:59:46 -0700 Subject: [PATCH 07/12] Ray-caster: correct hit detection --- app-proto/inversive-display/src/inversive.frag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app-proto/inversive-display/src/inversive.frag b/app-proto/inversive-display/src/inversive.frag index 5620210..da2f415 100644 --- a/app-proto/inversive-display/src/inversive.frag +++ b/app-proto/inversive-display/src/inversive.frag @@ -141,11 +141,11 @@ void main() { int frag_cnt = 0; for (int i = 0; i < sphere_cnt; ++i) { vec2 hit_depths = sphere_cast(sphere_list[i], dir); - if (!isnan(hit_depths[0])) { + if (hit_depths[0] > 0.) { frags[frag_cnt] = sphere_shading(sphere_list[i], hit_depths[0] * dir, color_list[i], i); ++frag_cnt; } - if (!isnan(hit_depths[1])) { + if (hit_depths[1] > 0.) { frags[frag_cnt] = sphere_shading(sphere_list[i], hit_depths[1] * dir, color_list[i], i); ++frag_cnt; } From 4afc82034b9ea6c6bd2f1ced53425dfc1420df0b Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 28 Aug 2024 14:37:29 -0700 Subject: [PATCH 08/12] Ray-caster: sort fragments while shading --- .../inversive-display/src/inversive.frag | 59 ++++++++++--------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/app-proto/inversive-display/src/inversive.frag b/app-proto/inversive-display/src/inversive.frag index da2f415..c469edc 100644 --- a/app-proto/inversive-display/src/inversive.frag +++ b/app-proto/inversive-display/src/inversive.frag @@ -138,16 +138,34 @@ void main() { const int SPHERE_MAX_INTERNAL = 6; vec2 depth_pairs [SPHERE_MAX_INTERNAL]; taggedFrag frags [2*SPHERE_MAX_INTERNAL]; - int frag_cnt = 0; - for (int i = 0; i < sphere_cnt; ++i) { - vec2 hit_depths = sphere_cast(sphere_list[i], dir); - if (hit_depths[0] > 0.) { - frags[frag_cnt] = sphere_shading(sphere_list[i], hit_depths[0] * dir, color_list[i], i); - ++frag_cnt; - } - if (hit_depths[1] > 0.) { - frags[frag_cnt] = sphere_shading(sphere_list[i], hit_depths[1] * dir, color_list[i], i); - ++frag_cnt; + int layer_cnt = 0; + for (int id = 0; id < sphere_cnt; ++id) { + // find out where the ray hits the sphere + vec2 hit_depths = sphere_cast(sphere_list[id], dir); + + // insertion-sort the fragments we hit into the fragment list + for (int side = 0; side < 2; ++side) { + if (hit_depths[side] > 0.) { + for (int layer = layer_cnt; layer >= 0; --layer) { + if (layer < 1 || frags[layer-1].pt.z >= -hit_depths[side]) { + // we're not as close to the screen as the fragment + // before the empty slot, so insert here + frags[layer] = sphere_shading( + sphere_list[id], + hit_depths[side] * dir, + color_list[id], + id + ); + break; + } else { + // we're closer to the screen than the fragment before + // the empty slot, so move that fragment into the empty + // slot + frags[layer] = frags[layer-1]; + } + } + ++layer_cnt; + } } } @@ -156,29 +174,16 @@ void main() { if (debug_mode) { // at the bottom of the screen, show the color scale instead of the // layer count - if (gl_FragCoord.y < 10.) frag_cnt = int(8. * gl_FragCoord.x / resolution.x); + if (gl_FragCoord.y < 10.) layer_cnt = int(8. * gl_FragCoord.x / resolution.x); // convert number to color - ivec3 bits = frag_cnt / ivec3(1, 2, 4); + ivec3 bits = layer_cnt / ivec3(1, 2, 4); outColor = vec4(mod(vec3(bits), 2.), 1.); return; } - // sort the fragments by depth, using an insertion sort - for (int take = 1; take < frag_cnt; ++take) { - taggedFrag pulled = frags[take]; - for (int put = take; put >= 0; --put) { - if (put < 1 || frags[put-1].pt.z >= pulled.pt.z) { - frags[put] = pulled; - break; - } else { - frags[put] = frags[put-1]; - } - } - } - // highlight intersections and cusps - for (int i = frag_cnt-1; i >= 1; --i) { + for (int i = layer_cnt-1; i >= 1; --i) { // intersections taggedFrag frag0 = frags[i]; taggedFrag frag1 = frags[i-1]; @@ -201,7 +206,7 @@ void main() { // composite the sphere fragments vec3 color = vec3(0.); - for (int i = frag_cnt-1; i >= layer_threshold; --i) { + for (int i = layer_cnt-1; i >= layer_threshold; --i) { if (frags[i].pt.z < 0.) { vec4 frag_color = frags[i].color; color = mix(color, frag_color.rgb, frag_color.a); From 8fde202911c1aa17aab72439ec4a965575ba9bd4 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 28 Aug 2024 15:52:19 -0700 Subject: [PATCH 09/12] Ray-caster: drop unused depth-pair array This doesn't affect GPU performance noticeably, so benchmarks before and after the change should be comparable. --- app-proto/inversive-display/src/inversive.frag | 1 - 1 file changed, 1 deletion(-) diff --git a/app-proto/inversive-display/src/inversive.frag b/app-proto/inversive-display/src/inversive.frag index c469edc..213fbe2 100644 --- a/app-proto/inversive-display/src/inversive.frag +++ b/app-proto/inversive-display/src/inversive.frag @@ -136,7 +136,6 @@ void main() { // cast rays through the spheres const int SPHERE_MAX_INTERNAL = 6; - vec2 depth_pairs [SPHERE_MAX_INTERNAL]; taggedFrag frags [2*SPHERE_MAX_INTERNAL]; int layer_cnt = 0; for (int id = 0; id < sphere_cnt; ++id) { From 3a721a4cc89bc5a8e41a075f4433c763dab1ede7 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 28 Aug 2024 16:06:33 -0700 Subject: [PATCH 10/12] Ray-caster: enlarge the construction data uniforms No noticeable effect on GPU performance. --- app-proto/inversive-display/src/inversive.frag | 2 +- app-proto/inversive-display/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app-proto/inversive-display/src/inversive.frag b/app-proto/inversive-display/src/inversive.frag index 213fbe2..fbb60f6 100644 --- a/app-proto/inversive-display/src/inversive.frag +++ b/app-proto/inversive-display/src/inversive.frag @@ -25,7 +25,7 @@ vecInv sphere(vec3 center, float radius) { // construction. the SPHERE_MAX array size seems to affect frame rate a lot, // even though we should only be using the first few elements of each array -const int SPHERE_MAX_UNIFORM = 12; +const int SPHERE_MAX_UNIFORM = 200; uniform int sphere_cnt; uniform vecInv sphere_list[SPHERE_MAX_UNIFORM]; uniform vec3 color_list[SPHERE_MAX_UNIFORM]; diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index c0e67a4..1855ce2 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -127,7 +127,7 @@ fn main() { on_mount(move || { // list construction elements - const SPHERE_MAX: usize = 12; + const SPHERE_MAX: usize = 200; let mut sphere_vec = Vec::>::new(); let color_vec = vec![ [1.00_f32, 0.25_f32, 0.00_f32], From 6db9f5be6c86af64226d21f220bb7795405c5196 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 28 Aug 2024 17:14:55 -0700 Subject: [PATCH 11/12] Enlarge the test construction to six spheres In debug mode, assign most of the color scale to even layer counts. Odd layer counts are topologically prohibited, so we should rarely see bugs severe enough to produce them. --- app-proto/inversive-display/src/inversive.frag | 12 +++++++++--- app-proto/inversive-display/src/main.rs | 8 +++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app-proto/inversive-display/src/inversive.frag b/app-proto/inversive-display/src/inversive.frag index fbb60f6..7eb16b5 100644 --- a/app-proto/inversive-display/src/inversive.frag +++ b/app-proto/inversive-display/src/inversive.frag @@ -173,11 +173,17 @@ void main() { if (debug_mode) { // at the bottom of the screen, show the color scale instead of the // layer count - if (gl_FragCoord.y < 10.) layer_cnt = int(8. * gl_FragCoord.x / resolution.x); + if (gl_FragCoord.y < 10.) layer_cnt = 2 * int(8. * gl_FragCoord.x / resolution.x); // convert number to color - ivec3 bits = layer_cnt / ivec3(1, 2, 4); - outColor = vec4(mod(vec3(bits), 2.), 1.); + vec3 color; + if (layer_cnt % 2 == 0) { + ivec3 bits = layer_cnt / ivec3(2, 4, 8); + color = mod(vec3(bits), 2.); + } else { + color = vec3(0.5); + } + outColor = vec4(color, 1.); return; } diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index 1855ce2..f5b42c5 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -132,7 +132,10 @@ fn main() { let color_vec = vec![ [1.00_f32, 0.25_f32, 0.00_f32], [0.00_f32, 0.25_f32, 1.00_f32], - [0.25_f32, 0.00_f32, 1.00_f32] + [0.25_f32, 0.00_f32, 1.00_f32], + [0.25_f32, 1.00_f32, 0.00_f32], + [0.75_f32, 0.75_f32, 0.00_f32], + [0.00_f32, 0.75_f32, 0.50_f32], ]; // timing @@ -292,6 +295,9 @@ fn main() { sphere_vec.push(&construction_to_world * engine::sphere(0.5, 0.5, ctrl_x.get(), radius_x.get())); sphere_vec.push(&construction_to_world * engine::sphere(-0.5, -0.5, ctrl_y.get(), radius_y.get())); sphere_vec.push(&construction_to_world * engine::sphere(-0.5, 0.5, 0.0, 0.75)); + sphere_vec.push(&construction_to_world * engine::sphere(0.5, -0.5, 0.0, 0.5)); + sphere_vec.push(&construction_to_world * engine::sphere(0.0, 0.15, 1.0, 0.25)); + sphere_vec.push(&construction_to_world * engine::sphere(0.0, -0.15, -1.0, 0.25)); // set the resolution let width = canvas.width() as f32; From f14855296485250277a7216c711eb598148d8346 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 29 Aug 2024 15:01:38 -0700 Subject: [PATCH 12/12] Ray-caster: only draw the top few fragments This seems to increase graphics pipe use from 50--60% to 55--65%. --- .../inversive-display/src/inversive.frag | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/app-proto/inversive-display/src/inversive.frag b/app-proto/inversive-display/src/inversive.frag index 7eb16b5..2fcd86e 100644 --- a/app-proto/inversive-display/src/inversive.frag +++ b/app-proto/inversive-display/src/inversive.frag @@ -25,10 +25,10 @@ vecInv sphere(vec3 center, float radius) { // construction. the SPHERE_MAX array size seems to affect frame rate a lot, // even though we should only be using the first few elements of each array -const int SPHERE_MAX_UNIFORM = 200; +const int SPHERE_MAX = 200; uniform int sphere_cnt; -uniform vecInv sphere_list[SPHERE_MAX_UNIFORM]; -uniform vec3 color_list[SPHERE_MAX_UNIFORM]; +uniform vecInv sphere_list[SPHERE_MAX]; +uniform vec3 color_list[SPHERE_MAX]; // view uniform vec2 resolution; @@ -135,8 +135,8 @@ void main() { vec3 dir = vec3(focal_slope * scr, -1.); // cast rays through the spheres - const int SPHERE_MAX_INTERNAL = 6; - taggedFrag frags [2*SPHERE_MAX_INTERNAL]; + const int LAYER_MAX = 12; + taggedFrag frags [LAYER_MAX]; int layer_cnt = 0; for (int id = 0; id < sphere_cnt; ++id) { // find out where the ray hits the sphere @@ -144,17 +144,20 @@ void main() { // insertion-sort the fragments we hit into the fragment list for (int side = 0; side < 2; ++side) { - if (hit_depths[side] > 0.) { + float hit_z = -hit_depths[side]; + if (0. > hit_z) { for (int layer = layer_cnt; layer >= 0; --layer) { - if (layer < 1 || frags[layer-1].pt.z >= -hit_depths[side]) { + if (layer < 1 || frags[layer-1].pt.z >= hit_z) { // we're not as close to the screen as the fragment // before the empty slot, so insert here - frags[layer] = sphere_shading( - sphere_list[id], - hit_depths[side] * dir, - color_list[id], - id - ); + if (layer < LAYER_MAX) { + frags[layer] = sphere_shading( + sphere_list[id], + hit_depths[side] * dir, + color_list[id], + id + ); + } break; } else { // we're closer to the screen than the fragment before @@ -163,7 +166,7 @@ void main() { frags[layer] = frags[layer-1]; } } - ++layer_cnt; + layer_cnt = min(layer_cnt + 1, LAYER_MAX); } } }