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).
This commit is contained in:
Raph Levien 2020-04-16 14:04:40 -07:00
parent 5c147b8576
commit 86e52a3f47
9 changed files with 223 additions and 30 deletions

72
Cargo.lock generated
View file

@ -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"

View file

@ -1,6 +1,7 @@
[workspace]
members = [
"piet-gpu",
"piet-gpu-derive",
"piet-gpu-hal"
]

View file

@ -74,6 +74,6 @@ pub trait CmdBuf<D: Device> {
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;
}

View file

@ -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<Pipeline, Error> {
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::<Vec<_>>();
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::<Vec<_>>();
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<T>,
) -> Result<(), Error> {
let device = &self.device.device;
let size = buffer.size as usize;
let size = buffer.size as usize / std::mem::size_of::<T>();
let buf = device.map_memory(
buffer.buffer_memory,
0,

13
piet-gpu/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "piet-gpu"
version = "0.1.0"
authors = ["Raph Levien <raph.levien@gmail.com>"]
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"

View file

@ -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

View file

@ -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];
}
}

BIN
piet-gpu/shader/image.spv Normal file

Binary file not shown.

63
piet-gpu/src/main.rs Normal file
View file

@ -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::<Vec<u32>>();
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<u8> = 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();
}
}