Compare commits

..

9 Commits

13 changed files with 203 additions and 69 deletions

1
.gitattributes vendored
View File

@ -1,2 +1,3 @@
*.jpg filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text
*.tiff filter=lfs diff=lfs merge=lfs -text
*.data filter=lfs diff=lfs merge=lfs -text *.data filter=lfs diff=lfs merge=lfs -text

View File

@ -11,7 +11,7 @@ use {
}; };
pub struct DemRenderer { pub struct DemRenderer {
source: Rc<Dem>, pub source: Rc<Dem>,
pipeline: wgpu::RenderPipeline, pipeline: wgpu::RenderPipeline,
bind_group: wgpu::BindGroup, bind_group: wgpu::BindGroup,
vertex_buffer: wgpu::Buffer, vertex_buffer: wgpu::Buffer,
@ -577,5 +577,5 @@ fn get_animated_camera_position(animation_phase: f32, dem_size: f32) -> glam::Ve
) )
} }
#[cfg(test)] //#[cfg(test)]
mod tests; //mod tests;

View File

@ -23,8 +23,8 @@ async fn run_compute_shader_test(
required_features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_defaults(), required_limits: wgpu::Limits::downlevel_defaults(),
memory_hints: wgpu::MemoryHints::MemoryUsage, memory_hints: wgpu::MemoryHints::MemoryUsage,
trace: wgpu::Trace::Off,
}, },
None,
) )
.await .await
.unwrap(); .unwrap();
@ -87,7 +87,7 @@ async fn run_compute_shader_test(
let (tx, rx) = channel(); let (tx, rx) = channel();
let buffer_slice = staging_buffer.slice(..); let buffer_slice = staging_buffer.slice(..);
buffer_slice.map_async(wgpu::MapMode::Read, move |v| tx.send(v).unwrap()); buffer_slice.map_async(wgpu::MapMode::Read, move |v| tx.send(v).unwrap());
device.poll(wgpu::Maintain::wait()).panic_on_timeout(); assert!(device.poll(wgpu::PollType::wait()).unwrap().wait_finished());
if let Ok(Ok(())) = rx.recv() { if let Ok(Ok(())) = rx.recv() {
buffer_slice.get_mapped_range().to_vec() buffer_slice.get_mapped_range().to_vec()
} else { } else {
@ -99,7 +99,7 @@ async fn run_compute_shader_test(
#[derive(Clone, Copy, Pod, Zeroable)] #[derive(Clone, Copy, Pod, Zeroable)]
struct Vec3 { struct Vec3 {
elements: [f32; 3], elements: [f32; 3],
_padding: f32 _padding: f32,
} }
#[repr(C)] #[repr(C)]
@ -114,7 +114,7 @@ struct TestInput {
fn vec3(x: f32, y: f32, z: f32) -> Vec3 { fn vec3(x: f32, y: f32, z: f32) -> Vec3 {
Vec3 { Vec3 {
elements: [x, y, z], elements: [x, y, z],
_padding: 0.0 _padding: 0.0,
} }
} }
@ -146,7 +146,7 @@ fn test_shaders() {
bytemuck::cast_slice(&input_buffer), bytemuck::cast_slice(&input_buffer),
input_buffer.len() * 4, input_buffer.len() * 4,
))) )))
.to_vec(); .to_vec();
assert!(dbg!(output_buffer[0]) >= 0.0); assert!(dbg!(output_buffer[0]) >= 0.0);
assert!(dbg!(output_buffer[1]) < 0.0); assert!(dbg!(output_buffer[1]) < 0.0);
assert!(dbg!(output_buffer[2]) >= 0.0); assert!(dbg!(output_buffer[2]) >= 0.0);

View File

@ -1,6 +1,6 @@
use std::rc::Rc; use std::rc::Rc;
use crate::mvu::{Event, File, MvuApp, Size2i, FrameTimer}; use crate::mvu::{Event, File, FrameTimer, MvuApp, Size2i};
use { use {
log::info, log::info,
std::borrow::Cow, std::borrow::Cow,
@ -11,6 +11,9 @@ mod dem_renderer;
mod raster; mod raster;
use dem_renderer::DemRenderer; use dem_renderer::DemRenderer;
mod statistics_reporter;
use statistics_reporter::StatisticsReporter;
#[derive(Clone)] #[derive(Clone)]
pub struct Model { pub struct Model {
dem: Option<Rc<raster::Dem>>, dem: Option<Rc<raster::Dem>>,
@ -30,6 +33,7 @@ struct Context {
queue: Queue, queue: Queue,
scene_data: Option<DemRenderer>, scene_data: Option<DemRenderer>,
frame_timer: Box<dyn FrameTimer>, frame_timer: Box<dyn FrameTimer>,
statistics_reporter: StatisticsReporter,
} }
#[derive(Default)] #[derive(Default)]
@ -59,14 +63,17 @@ impl MvuApp<Model> for App {
}) })
.await .await
.expect("Failed to find an appropriate adapter"); .expect("Failed to find an appropriate adapter");
info!("Using {} backend with {}", adapter.get_info().backend, adapter.get_info().name); info!(
"Using {} backend with {}",
adapter.get_info().backend,
adapter.get_info().name
);
let (device, queue) = adapter let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor { .request_device(&wgpu::DeviceDescriptor {
label: None, label: None,
required_features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default() required_limits: wgpu::Limits::default().using_resolution(adapter.limits()),
.using_resolution(adapter.limits()),
memory_hints: wgpu::MemoryHints::MemoryUsage, memory_hints: wgpu::MemoryHints::MemoryUsage,
trace: Trace::Off, trace: Trace::Off,
}) })
@ -123,7 +130,8 @@ impl MvuApp<Model> for App {
render_pipeline, render_pipeline,
queue, queue,
scene_data: None, scene_data: None,
frame_timer: frame_timer, frame_timer,
statistics_reporter: StatisticsReporter::new(),
}); });
info!("Initialized {}x{}.", size.width, size.height); info!("Initialized {}x{}.", size.width, size.height);
@ -149,14 +157,22 @@ impl MvuApp<Model> for App {
match event { match event {
Event::MouseButtonPressed => model, Event::MouseButtonPressed => model,
Event::OpenTestFile(file_path) => open_test_file(file_path, model.clone()), Event::OpenTestFile(file_path) => open_test_file(file_path, model.clone()),
Event::ClearScene => clear_scene(model.clone()),
} }
} }
async fn view(&mut self, model: Rc<Model>) -> Result<(), Box<dyn std::error::Error>> { async fn view(&mut self, model: Rc<Model>) -> Result<(), Box<dyn std::error::Error>> {
if let Some(context) = &mut self.context { if let Some(context) = &mut self.context {
context.frame_timer.mark_frame_start(); context.frame_timer.mark_frame_start();
if context.scene_data.is_none() { context
if let Some(dem) = &model.dem { .statistics_reporter
.log_frame_time_seconds(context.frame_timer.get_frame_time_seconds());
if let Some(dem) = &model.dem {
if context
.scene_data
.as_ref()
.is_none_or(|scene_data| scene_data.source.id != dem.id)
{
context.scene_data = Some(DemRenderer::new( context.scene_data = Some(DemRenderer::new(
dem.clone(), dem.clone(),
&context.device, &context.device,
@ -164,6 +180,8 @@ impl MvuApp<Model> for App {
&context.queue, &context.queue,
)) ))
} }
} else {
context.scene_data = None;
} }
let frame = context let frame = context
@ -215,3 +233,10 @@ fn open_test_file(file: Box<dyn File>, model: Rc<Model>) -> Rc<Model> {
let dem = Some(Rc::new(raster::Dem::load_from_image(file))); let dem = Some(Rc::new(raster::Dem::load_from_image(file)));
Rc::new(Model { dem, ..*model }) Rc::new(Model { dem, ..*model })
} }
fn clear_scene(model: Rc<Model>) -> Rc<Model> {
Rc::new(Model {
dem: None,
..*model
})
}

View File

@ -1,4 +1,5 @@
use crate::mvu::File; use crate::mvu::File;
use std::sync::atomic::{AtomicU32, Ordering};
#[derive(Clone)] #[derive(Clone)]
pub struct Dem { pub struct Dem {
@ -11,6 +12,7 @@ pub struct Dem {
pub z_min: f32, pub z_min: f32,
pub z_max: f32, pub z_max: f32,
pub grid: Vec<u16>, pub grid: Vec<u16>,
pub id: Id,
} }
impl std::fmt::Debug for Dem { impl std::fmt::Debug for Dem {
@ -46,6 +48,18 @@ pub struct DemBvhLayer {
pub data: Vec<u16>, pub data: Vec<u16>,
} }
#[derive(Clone, Eq, PartialEq)]
pub struct Id(u32);
impl Id {
fn get_next() -> Self {
NEXT_ID.fetch_add(1, Ordering::SeqCst);
Self(NEXT_ID.load(Ordering::SeqCst))
}
}
static NEXT_ID: AtomicU32 = AtomicU32::new(0);
/// Find the smallest number that's larger then the input and that is also one /// Find the smallest number that's larger then the input and that is also one
/// less than a power of two. /// less than a power of two.
fn round_bound(v: u32) -> u32 { fn round_bound(v: u32) -> u32 {
@ -198,6 +212,7 @@ impl Dem {
z_min, z_min,
z_max, z_max,
grid, grid,
id: Id::get_next(),
} }
} }
_ => { _ => {
@ -283,6 +298,7 @@ mod tests {
z_min: z1.min(z2), z_min: z1.min(z2),
z_max: z1.max(z2), z_max: z1.max(z2),
grid, grid,
id: Id::get_next()
}, },
) )
.boxed() .boxed()

View File

@ -15,6 +15,7 @@ impl File for std::io::Cursor<Vec<u8>> {}
pub enum Event { pub enum Event {
MouseButtonPressed, MouseButtonPressed,
OpenTestFile(Box<dyn File>), OpenTestFile(Box<dyn File>),
ClearScene,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]

View File

@ -112,6 +112,16 @@ impl PteropusCanvas {
.await; .await;
} }
#[wasm_bindgen]
pub async fn clear_scene(&mut self) {
self.model = self
.app
.lock()
.expect("get app mutex")
.update(Rc::clone(&self.model), Event::ClearScene)
.await;
}
pub fn get_canvas(&self) -> HtmlCanvasElement { pub fn get_canvas(&self) -> HtmlCanvasElement {
let window = web_sys::window().expect("get window"); let window = web_sys::window().expect("get window");
let document = window.document().expect("get HTML document"); let document = window.document().expect("get HTML document");

View File

@ -2,61 +2,20 @@
<html> <html>
<head> <head>
<title>Pteropus</title> <title>Pteropus</title>
<style> <link rel="stylesheet" href="pteropus-frame.css">
#pteropus-canvas {
height: 100%;
width: 100%;
display: block;
}
</style> </style>
</head> </head>
<body> <body>
<div id="whole-window">
<script type="module" src="pteropus-frame.js"></script>
<canvas id="pteropus-canvas"></canvas> <canvas id="pteropus-canvas"></canvas>
<script type="module"> <div id="bottom-panel">
import init, {init_pteropus} from '../pkg/pteropus.js'; <input type="file" id="file-upload-file-input" accept="image/tiff, .tif, .tiff">
<button id="clear-scene-button">Clear Scene</button>
function fileDragOverHandler(event) { <button id="load-small-sample-button">Load Small Sample</button>
event.preventDefault(); <button id="load-medium-sample-button">Load Medium Sample</button>
} <button id="load-large-sample-button">Load Large Sample</button>
</div>
async function run() { </div>
await init(); </body>
let pteropus = await init_pteropus();
let test_file_data = null;
let needs_resize = true;
let mainCanvas = document.getElementById("pteropus-canvas");
mainCanvas.addEventListener("drop", async (event) => {
event.preventDefault();
test_file_data = event.dataTransfer.files[0]
});
mainCanvas.addEventListener("dragover", fileDragOverHandler);
const resizeObserver = new ResizeObserver((entries) => {
needs_resize = true;
});
resizeObserver.observe(mainCanvas)
while(true)
{
await pteropus.render();
// TODO: I really want to do this asynchronously, with the drop
// event listener calling pteropus_load_file directly, but I need
// to figure out how to get that to work.
if(test_file_data) {
await pteropus.load_file(test_file_data);
test_file_data = null
}
if(needs_resize) {
await pteropus.on_resize();
needs_resize = false;
}
await new Promise(requestAnimationFrame);
}
}
run();
</script>
</body>
</html> </html>

BIN
web/large_sample.tiff (Stored with Git LFS) Normal file

Binary file not shown.

BIN
web/medium_sample.tiff (Stored with Git LFS) Normal file

Binary file not shown.

27
web/pteropus-frame.css Normal file
View File

@ -0,0 +1,27 @@
body {
margin: 0;
}
#whole-window {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
}
#pteropus-canvas {
display: flex;
flex-grow: 1;
margin: 0.4rem;
}
#bottom-panel {
display: flex;
flex-grow: 0;
margin: 0.4rem;
}
button {
margin-left: 0.2rem;
margin-right: 0.2rem;
}

86
web/pteropus-frame.js Normal file
View File

@ -0,0 +1,86 @@
import init, {init_pteropus} from '../pkg/pteropus.js';
function fileDragOverHandler(event) {
event.preventDefault();
}
async function run() {
await init();
let pteropus = await init_pteropus();
let test_file_data = null;
let needs_resize = true;
let clear_scene = false;
let mainCanvas = document.getElementById("pteropus-canvas");
mainCanvas.addEventListener("drop", async (event) => {
event.preventDefault();
test_file_data = event.dataTransfer.files[0]
});
mainCanvas.addEventListener("dragover", fileDragOverHandler);
const resizeObserver = new ResizeObserver((entries) => {
needs_resize = true;
});
resizeObserver.observe(mainCanvas);
let fileUploadButton = document.getElementById("file-upload-file-input");
fileUploadButton.addEventListener("change", () => {
const files = fileUploadButton.files;
if(files.length > 0) {
test_file_data = files[0]
}
});
let clearSceneButton = document.getElementById("clear-scene-button");
clearSceneButton.addEventListener("click", () => {
clear_scene = true;
});
[
{
buttonId: "load-small-sample-button",
url: "small_sample.tiff"
},
{
buttonId: "load-medium-sample-button",
url: "medium_sample.tiff"
},
{
buttonId: "load-large-sample-button",
url: "large_sample.tiff"
}
].forEach( (sample) => {
let loadSampleButton = document.getElementById(sample.buttonId);
loadSampleButton.addEventListener("click", async () => {
const response = await fetch(sample.url);
if(!response.ok) {
console.error("Couild not load sample file.")
}
test_file_data = await response.blob()
});
});
while(true)
{
await pteropus.render();
if(needs_resize) {
await pteropus.on_resize();
needs_resize = false;
}
if(clear_scene) {
await pteropus.clear_scene();
clear_scene = false;
}
// TODO: I really want to do this asynchronously, with the drop
// event listener calling pteropus_load_file directly, but I need
// to figure out how to get that to work.
if(test_file_data) {
await pteropus.load_file(test_file_data);
test_file_data = null
}
await new Promise(requestAnimationFrame);
}
}
run();

BIN
web/small_sample.tiff (Stored with Git LFS) Normal file

Binary file not shown.