From 86e52a3f4756bd7dd122e19d3f3331718a60d452 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 16 Apr 2020 14:04:40 -0700 Subject: [PATCH] Start image rendering Populates the piet-gpu subdir, with an extremely simple renderer. The main program saves the image to a PNG. Contains a few fixes (I was confused about the need for multiple bindings, as opposed to multiple descriptors within a binding). --- Cargo.lock | 72 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + piet-gpu-hal/src/lib.rs | 2 +- piet-gpu-hal/src/vulkan.rs | 61 +++++++++++++++--------------- piet-gpu/Cargo.toml | 13 +++++++ piet-gpu/shader/build.ninja | 10 +++++ piet-gpu/shader/image.comp | 31 ++++++++++++++++ piet-gpu/shader/image.spv | Bin 0 -> 2552 bytes piet-gpu/src/main.rs | 63 +++++++++++++++++++++++++++++++ 9 files changed, 223 insertions(+), 30 deletions(-) create mode 100644 piet-gpu/Cargo.toml create mode 100644 piet-gpu/shader/build.ninja create mode 100644 piet-gpu/shader/image.comp create mode 100644 piet-gpu/shader/image.spv create mode 100644 piet-gpu/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index eb747a9..ec2aebd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,11 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" + [[package]] name = "ash" version = "0.30.0" @@ -9,12 +15,58 @@ dependencies = [ "libloading", ] +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + [[package]] name = "cc" version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "deflate" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e5d2a2273fed52a7f947ee55b092c4057025d7a3e04e5ecdbd25d6c3fb1bd7" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "inflate" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" +dependencies = [ + "adler32", +] + [[package]] name = "libloading" version = "0.5.2" @@ -25,6 +77,14 @@ dependencies = [ "winapi", ] +[[package]] +name = "piet-gpu" +version = "0.1.0" +dependencies = [ + "piet-gpu-hal", + "png", +] + [[package]] name = "piet-gpu-derive" version = "0.0.0" @@ -41,6 +101,18 @@ dependencies = [ "ash", ] +[[package]] +name = "png" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "910f09135b1ed14bb16be445a8c23ddf0777eca485fbfc7cee00d81fecab158a" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "inflate", +] + [[package]] name = "proc-macro2" version = "1.0.10" diff --git a/Cargo.toml b/Cargo.toml index 6a9525f..3098acc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ + "piet-gpu", "piet-gpu-derive", "piet-gpu-hal" ] diff --git a/piet-gpu-hal/src/lib.rs b/piet-gpu-hal/src/lib.rs index 07814e4..7fa133a 100644 --- a/piet-gpu-hal/src/lib.rs +++ b/piet-gpu-hal/src/lib.rs @@ -74,6 +74,6 @@ pub trait CmdBuf { unsafe fn write_timestamp(&mut self, pool: &D::QueryPool, query: u32); } -pub trait MemFlags: Sized { +pub trait MemFlags: Sized + Clone + Copy { fn host_coherent() -> Self; } diff --git a/piet-gpu-hal/src/vulkan.rs b/piet-gpu-hal/src/vulkan.rs index 2acfec4..f250abc 100644 --- a/piet-gpu-hal/src/vulkan.rs +++ b/piet-gpu-hal/src/vulkan.rs @@ -58,6 +58,7 @@ pub struct QueryPool { n_queries: u32, } +#[derive(Clone, Copy)] pub struct MemFlags(vk::MemoryPropertyFlags); impl VkInstance { @@ -169,23 +170,25 @@ impl crate::Device for VkDevice { /// This creates a pipeline that runs over the buffer. /// - /// The code is included from "../comp.spv", and the descriptor set layout is just some - /// number of buffers. + /// The descriptor set layout is just some number of buffers (this will change). unsafe fn create_simple_compute_pipeline( &self, code: &[u8], n_buffers: u32, ) -> Result { let device = &self.device.device; - let descriptor_set_layout = device.create_descriptor_set_layout( - &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&[ + let bindings = (0..n_buffers) + .map(|i| { vk::DescriptorSetLayoutBinding::builder() - .binding(0) + .binding(i) .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) - .descriptor_count(n_buffers) + .descriptor_count(1) .stage_flags(vk::ShaderStageFlags::COMPUTE) - .build(), - ]), + .build() + }) + .collect::>(); + let descriptor_set_layout = device.create_descriptor_set_layout( + &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&bindings), None, )?; @@ -248,25 +251,22 @@ impl crate::Device for VkDevice { .set_layouts(&descriptor_set_layouts), ) .unwrap(); - let buf_infos = bufs - .iter() - .map(|buf| { - vk::DescriptorBufferInfo::builder() - .buffer(buf.buffer) - .offset(0) - .range(vk::WHOLE_SIZE) - .build() - }) - .collect::>(); - device.update_descriptor_sets( - &[vk::WriteDescriptorSet::builder() - .dst_set(descriptor_sets[0]) - .dst_binding(0) - .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) - .buffer_info(&buf_infos) - .build()], - &[], - ); + for (i, buf) in bufs.iter().enumerate() { + let buf_info = vk::DescriptorBufferInfo::builder() + .buffer(buf.buffer) + .offset(0) + .range(vk::WHOLE_SIZE) + .build(); + device.update_descriptor_sets( + &[vk::WriteDescriptorSet::builder() + .dst_set(descriptor_sets[0]) + .dst_binding(i as u32) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) + .buffer_info(&[buf_info]) + .build()], + &[], + ); + } Ok(DescriptorSet { descriptor_set: descriptor_sets[0], }) @@ -321,7 +321,10 @@ impl crate::Device for VkDevice { device.destroy_query_pool(pool.pool, None); let ts0 = buf[0]; let tsp = self.timestamp_period as f64 * 1e-9; - let result = buf[1..].iter().map(|ts| ts.wrapping_sub(ts0) as f64 * tsp).collect(); + let result = buf[1..] + .iter() + .map(|ts| ts.wrapping_sub(ts0) as f64 * tsp) + .collect(); Ok(result) } @@ -354,7 +357,7 @@ impl crate::Device for VkDevice { result: &mut Vec, ) -> Result<(), Error> { let device = &self.device.device; - let size = buffer.size as usize; + let size = buffer.size as usize / std::mem::size_of::(); let buf = device.map_memory( buffer.buffer_memory, 0, diff --git a/piet-gpu/Cargo.toml b/piet-gpu/Cargo.toml new file mode 100644 index 0000000..26684ea --- /dev/null +++ b/piet-gpu/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "piet-gpu" +version = "0.1.0" +authors = ["Raph Levien "] +description = "A compute-centric GPU 2D renderer." +license = "MIT/Apache-2.0" +edition = "2018" + +[dependencies.piet-gpu-hal] +path = "../piet-gpu-hal" + +[dependencies] +png = "0.16.2" diff --git a/piet-gpu/shader/build.ninja b/piet-gpu/shader/build.ninja new file mode 100644 index 0000000..d0086bc --- /dev/null +++ b/piet-gpu/shader/build.ninja @@ -0,0 +1,10 @@ +# Build file for shaders. + +# You must have glslangValidator in your path, or patch here. + +glslang_validator = glslangValidator + +rule glsl + command = $glslang_validator -V -o $out $in + +build image.spv: glsl image.comp diff --git a/piet-gpu/shader/image.comp b/piet-gpu/shader/image.comp new file mode 100644 index 0000000..2c35e74 --- /dev/null +++ b/piet-gpu/shader/image.comp @@ -0,0 +1,31 @@ +// A simple kernel to create an image. + +// Right now, this kernel stores the image in a buffer, but a better +// plan is to use a texture. This is because of limited support. + +#version 450 +layout(local_size_x = 16, local_size_y = 16) in; + +layout(set = 0, binding = 0) readonly buffer SceneBuf { + uint[] scene; +}; + +layout(set = 0, binding = 1) buffer ImageBuf { + uint[] image; +}; + +// TODO: make the image size dynamic. +#define IMAGE_WIDTH 2048 +#define IMAGE_HEIGHT 1535 + +void main() { + uvec2 xy = gl_GlobalInvocationID.xy; + vec2 uv = vec2(xy) * vec2(1.0 / IMAGE_WIDTH, 1.0 / IMAGE_HEIGHT); + vec4 rgba = vec4(uv.xyy, 1.0); + uvec4 s = uvec4(round(rgba * 255.0)); + uint rgba_packed = s.x | (s.y << 8) | (s.z << 16) | (s.w << 24); + image[xy.y * IMAGE_WIDTH + xy.x] = rgba_packed; + if (xy.y == 0 && xy.x < 8) { + image[xy.x] = scene[xy.x]; + } +} diff --git a/piet-gpu/shader/image.spv b/piet-gpu/shader/image.spv new file mode 100644 index 0000000000000000000000000000000000000000..dd4bbc8df31821aa89bf7d2c2cca0ec4e346a6b2 GIT binary patch literal 2552 zcmZvcS5K5t5QY!JDpu^hEMf<-H&7AVf})7n8!ihXN-?5Fuk=E1{UKiY8;r(ZWGsn^ z&-3|B+z`)ZGVi=o&YU^t+u9jTvr?)__58-u-?b>sfJtd?nwjZRPk&GA)U%=1{Reg% zu^`oEfyOitQ$W`tWqvH zl*T3p%VXUWFD5JHXQPu7-KU8$b}{<-i@$Z&fS-;GmQk5+3wkQUg|ri`Z{SI}@_2X% ze<7WL>mDzU44-`d0GORZ&NV8Jc8^lVhzqF#?yn3_ROiUKrsUD)s9hya=Pc$TYjfM) zXCj`vi|E+13ENd7U(N}e@BSMRSJ;hUXQ@4i{#UOq>k%rgM64hERo9n``u18~U)%aF z>yM%Be-*NXoAd4($u?iSj2QPXBCZwKJgAJ*U&O9Q%PIQjpv|#{e(TpD^6k0p+~k%M z)qpM{=K7AbqqgtMy7JBV^({kKZDB9RX#BdPt%Z3Qw*oOn-kP4xJnzJQR->&cuiv}V zZ@*D<130BO9r`zc;ttH;jBUPr#BT$q^!k+k7La~(^s8GDSJ->Np5OWIM;CKjzxDyd zxkmh9urmpNJ2+-y{o_dVZ+<7@8(9Z?8g0yauy0S>KF^}9Dd)XtmvS!l>B4qJALlh_ z%=|u}`PNPJW1ZLs~j{~$W{uYk49A3~ci=l!I)20vg5XlvSo|2f`=w(rURm9Tva{-etTi%7v!A0 zs|GnvC+@4yJmZ!ia!;^5)6$%Km%m%@OV0OezJ1D>Z~Qt$u9s8!X4mJOyvu!+KI-|N zi-<9<;2Obl)o~kh&N*J9Q@@KQq=*==-|xYmoRvO%GHxp(_ntkqx98k8^gQlNZU^Fi zivDqOH=&ymeb#m6+MU#K=DQF%zZ>`R{p?2MBW5qy7~es}>;ubxrj9uW5jnr7FFAJz zY%P6J>j>CderHju4J;qEj)LXdk*IYHY(0HZs{?E;zsaa|0xTc3PJ-n+k*IYFY(0I> z#CInbb-Tg7pX&3T!|+4!vJYeKA#(O1jy~>#r}t5YlaH7O|A`ra olaF~m1p9yIJoUxgN5T3c?h!cV?mo_4&N$B)J*t26l^sU@0O@L%!vFvP literal 0 HcmV?d00001 diff --git a/piet-gpu/src/main.rs b/piet-gpu/src/main.rs new file mode 100644 index 0000000..c269cb3 --- /dev/null +++ b/piet-gpu/src/main.rs @@ -0,0 +1,63 @@ +use std::path::Path; +use std::fs::File; +use std::io::BufWriter; + +use piet_gpu_hal::vulkan::VkInstance; +use piet_gpu_hal::{CmdBuf, Device, MemFlags}; + +const WIDTH: usize = 2048; +const HEIGHT: usize = 1536; + +const TILE_W: usize = 16; +const TILE_H: usize = 16; + +fn main() { + let instance = VkInstance::new().unwrap(); + unsafe { + let device = instance.device().unwrap(); + let mem_flags = MemFlags::host_coherent(); + let src = (0..256).map(|x| x + 1).collect::>(); + let scene_buf = device + .create_buffer(std::mem::size_of_val(&src[..]) as u64, mem_flags) + .unwrap(); + device.write_buffer(&scene_buf, &src).unwrap(); + let image_buf = device + .create_buffer((WIDTH * HEIGHT * 4) as u64, mem_flags) + .unwrap(); + let code = include_bytes!("../shader/image.spv"); + let pipeline = device.create_simple_compute_pipeline(code, 2).unwrap(); + let descriptor_set = device + .create_descriptor_set(&pipeline, &[&scene_buf, &image_buf]) + .unwrap(); + let query_pool = device.create_query_pool(2).unwrap(); + let mut cmd_buf = device.create_cmd_buf().unwrap(); + cmd_buf.begin(); + cmd_buf.write_timestamp(&query_pool, 0); + cmd_buf.dispatch( + &pipeline, + &descriptor_set, + ((WIDTH / TILE_W) as u32, (HEIGHT / TILE_H) as u32, 1), + ); + cmd_buf.write_timestamp(&query_pool, 1); + cmd_buf.finish(); + device.run_cmd_buf(&cmd_buf).unwrap(); + let timestamps = device.reap_query_pool(query_pool).unwrap(); + println!("Render time: {:.3}ms", timestamps[0] * 1e3); + let mut img_data: Vec = Default::default(); + // Note: because png can use a `&[u8]` slice, we could avoid an extra copy + // (probably passing a slice into a closure). But for now: keep it simple. + device.read_buffer(&image_buf, &mut img_data).unwrap(); + + // Write image as PNG file. + let path = Path::new("image.png"); + let file = File::create(path).unwrap(); + let ref mut w = BufWriter::new(file); + + let mut encoder = png::Encoder::new(w, WIDTH as u32, HEIGHT as u32); + encoder.set_color(png::ColorType::RGBA); + encoder.set_depth(png::BitDepth::Eight); + let mut writer = encoder.write_header().unwrap(); + + writer.write_image_data(&img_data).unwrap(); + } +}