Skip to content

Commit

Permalink
[rs] Merge gfx-rs#400
Browse files Browse the repository at this point in the history
400: Added test for "capture" example r=kvark a=bfrazho

I've added a test to validate the capture example. I was planning on using the existing "screenshot.png" that was in there for the assertion for the test, but apparently was slightly different from the one that I was generating. I ended up replacing the screenshot.png with what the test was generating.

I'm a little concerned that they were different from the start, but maybe the png generating library made some small changes over time. I did check to make sure that it generated the same png on Windows and Linux on the same computer.

Let me know if there is anything that you would like me to change!

Co-authored-by: Brian <brian@linux-ccip.lan>
  • Loading branch information
bors[bot] and Brian authored Jun 24, 2020
2 parents 24dbd6a + 6b4ee0d commit 81a7ba6
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 41 deletions.
137 changes: 96 additions & 41 deletions wgpu/examples/capture/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,26 @@ use std::env;
use std::fs::File;
use std::io::Write;
use std::mem::size_of;
use wgpu::{Buffer, Device};

async fn run() {
async fn run(png_output_path: &str) {
let args: Vec<_> = env::args().collect();
let (width, height) = match args.len() {
// 0 on wasm, 1 on desktop
0 | 1 => (100usize, 200usize),
3 => (args[1].parse().unwrap(), args[2].parse().unwrap()),
_ => {
println!("Incorrect number of arguments, possible usages:");
println!("* 0 arguments - uses default width and height of (100, 200)");
println!("* 2 arguments - uses specified width and height values");
return;
}
};
let (device, buffer, buffer_dimensions) = create_red_image_with_dimensions(width, height).await;
create_png(png_output_path, device, buffer, &buffer_dimensions).await;
}

async fn create_red_image_with_dimensions(width: usize, height: usize) -> (Device, Buffer, BufferDimensions) {
let adapter = wgpu::Instance::new(wgpu::BackendBit::PRIMARY)
.request_adapter(
&wgpu::RequestAdapterOptions {
Expand All @@ -30,40 +48,22 @@ async fn run() {
.await
.unwrap();

let args: Vec<_> = env::args().collect();
let (width, height) = match args.len() {
// 0 on wasm, 1 on desktop
0 | 1 => (100usize, 200usize),
3 => (args[1].parse().unwrap(), args[2].parse().unwrap()),
_ => {
println!("Incorrect number of arguments, possible usages:");
println!("* 0 arguments - uses default width and height of (100, 200)");
println!("* 2 arguments - uses specified width and height values");
return;
}
};

// It is a webgpu requirement that BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
// So we calculate padded_bytes_per_row by rounding unpadded_bytes_per_row
// up to the next multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT.
// https://en.wikipedia.org/wiki/Data_structure_alignment#Computing_padding
let bytes_per_pixel = size_of::<u32>();
let unpadded_bytes_per_row = width * bytes_per_pixel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;

let buffer_dimensions = BufferDimensions::new(width, height);
// The output buffer lets us retrieve the data as an array
let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
size: (padded_bytes_per_row * height) as u64,
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64,
usage: wgpu::BufferUsage::MAP_READ | wgpu::BufferUsage::COPY_DST,
mapped_at_creation: false,
});

let texture_extent = wgpu::Extent3d {
width: width as u32,
height: height as u32,
width: buffer_dimensions.width as u32,
height: buffer_dimensions.height as u32,
depth: 1,
};

Expand Down Expand Up @@ -105,7 +105,7 @@ async fn run() {
buffer: &output_buffer,
layout: wgpu::TextureDataLayout {
offset: 0,
bytes_per_row: padded_bytes_per_row as u32,
bytes_per_row: buffer_dimensions.padded_bytes_per_row as u32,
rows_per_image: 0,
},
},
Expand All @@ -116,16 +116,18 @@ async fn run() {
};

queue.submit(Some(command_buffer));
(device, output_buffer, buffer_dimensions)
}

// Note that we're not calling `.await` here.
async fn create_png(png_output_path: &str, device: Device, output_buffer: Buffer, buffer_dimensions: &BufferDimensions) {
// Note that we're not calling `.await` here.
let buffer_slice = output_buffer.slice(..);
let buffer_future = buffer_slice.map_async(wgpu::MapMode::Read);

// Poll the device in a blocking manner so that our future resolves.
// In an actual application, `device.poll(...)` should
// be called in an event loop or on another thread.
device.poll(wgpu::Maintain::Wait);

// If a file system is available, write the buffer as a PNG
let has_file_system_available = cfg!(not(target_arch = "wasm32"));
if !has_file_system_available {
Expand All @@ -136,20 +138,20 @@ async fn run() {
let padded_buffer = buffer_slice.get_mapped_range();

let mut png_encoder = png::Encoder::new(
File::create("red.png").unwrap(),
width as u32,
height as u32,
File::create(png_output_path).unwrap(),
buffer_dimensions.width as u32,
buffer_dimensions.height as u32,
);
png_encoder.set_depth(png::BitDepth::Eight);
png_encoder.set_color(png::ColorType::RGBA);
let mut png_writer = png_encoder
.write_header()
.unwrap()
.into_stream_writer_with_size(unpadded_bytes_per_row);
.into_stream_writer_with_size(buffer_dimensions.unpadded_bytes_per_row);

// from the padded_buffer we write just the unpadded bytes into the image
for chunk in padded_buffer.chunks(padded_bytes_per_row) {
png_writer.write(&chunk[..unpadded_bytes_per_row]).unwrap();
for chunk in padded_buffer.chunks(buffer_dimensions.padded_bytes_per_row) {
png_writer.write(&chunk[..buffer_dimensions.unpadded_bytes_per_row]).unwrap();
}
png_writer.finish().unwrap();

Expand All @@ -161,16 +163,69 @@ async fn run() {
}
}

struct BufferDimensions {
width: usize,
height: usize,
unpadded_bytes_per_row: usize,
padded_bytes_per_row: usize,
}

impl BufferDimensions {
fn new(width: usize, height: usize) -> Self {
let bytes_per_pixel = size_of::<u32>();
let unpadded_bytes_per_row = width * bytes_per_pixel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
Self { width, height, unpadded_bytes_per_row, padded_bytes_per_row }
}
}

fn main() {
#[cfg(not(target_arch = "wasm32"))]
{
env_logger::init();
futures::executor::block_on(run());
}
{
env_logger::init();
futures::executor::block_on(run("red.png"));
}
#[cfg(target_arch = "wasm32")]
{
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init().expect("could not initialize logger");
wasm_bindgen_futures::spawn_local(run());
}
{
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init().expect("could not initialize logger");
wasm_bindgen_futures::spawn_local(run("red.png"));
}
}


#[cfg(test)]
mod tests {
use super::*;
use wgpu::BufferView;

#[test]
fn ensure_generated_data_matches_expected() {
futures::executor::block_on(assert_generated_data_matches_expected());
}

async fn assert_generated_data_matches_expected() {
let (device, output_buffer, dimensions) = create_red_image_with_dimensions(100usize, 200usize).await;
let buffer_slice = output_buffer.slice(..);
let buffer_future = buffer_slice.map_async(wgpu::MapMode::Read);
device.poll(wgpu::Maintain::Wait);
buffer_future.await.expect("failed to map buffer slice for capture test");
let padded_buffer = buffer_slice.get_mapped_range();
let expected_buffer_size = dimensions.padded_bytes_per_row*dimensions.height;
assert_eq!(padded_buffer.len(),expected_buffer_size);
assert_that_content_is_all_red(&dimensions, padded_buffer);
}

fn assert_that_content_is_all_red(dimensions: &BufferDimensions, padded_buffer: BufferView) {
let red = [0xFFu8, 0, 0, 0xFFu8];
let single_rgba = 4;
padded_buffer.chunks(dimensions.padded_bytes_per_row)
.map(|padded_buffer_row| &padded_buffer_row[..dimensions.unpadded_bytes_per_row])
.for_each(|unpadded_row|
unpadded_row.chunks(single_rgba)
.for_each(|chunk| assert_eq!(chunk, &red))
);
}
}
Binary file modified wgpu/examples/capture/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 81a7ba6

Please sign in to comment.