blob: d32a9c53a8a465ceb97157f4e1455def5ae0cb6c [file] [log] [blame]
mod blend;
mod encoder;
pub mod glyph_render;
mod gradient;
mod pico_svg;
mod render_ctx;
pub mod stages;
pub mod test_scenes;
mod text;
use bytemuck::Pod;
use std::convert::TryInto;
pub use blend::{Blend, BlendMode, CompositionMode};
pub use encoder::EncodedSceneRef;
pub use render_ctx::PietGpuRenderContext;
pub use gradient::Colrv1RadialGradient;
use piet::kurbo::Vec2;
use piet::{ImageFormat, RenderContext};
use piet_gpu_hal::{
include_shader, BindType, Buffer, BufferUsage, CmdBuf, ComputePassDescriptor, DescriptorSet,
Error, Image, ImageLayout, Pipeline, QueryPool, Session,
};
pub use pico_svg::PicoSvg;
use stages::{ClipBinding, ElementBinding, ElementCode};
use crate::stages::{ClipCode, Config, ElementStage};
const TILE_W: usize = 16;
const TILE_H: usize = 16;
const PTCL_INITIAL_ALLOC: usize = 1024;
const MAX_BLEND_STACK: usize = 128;
#[allow(unused)]
fn dump_scene(buf: &[u8]) {
for i in 0..(buf.len() / 4) {
let mut buf_u32 = [0u8; 4];
buf_u32.copy_from_slice(&buf[i * 4..i * 4 + 4]);
println!("{:4x}: {:8x}", i * 4, u32::from_le_bytes(buf_u32));
}
}
#[allow(unused)]
pub fn dump_k1_data(k1_buf: &[u32]) {
for i in 0..k1_buf.len() {
if k1_buf[i] != 0 {
println!("{:4x}: {:8x}", i * 4, k1_buf[i]);
}
}
}
pub struct RenderConfig {
width: usize,
height: usize,
format: PixelFormat,
}
// Should we just use the enum from piet-gpu-hal?
pub enum PixelFormat {
A8,
Rgba8,
}
pub struct Renderer {
// These sizes are aligned to tile boundaries, though at some point
// we'll want to have a good strategy for dealing with odd sizes.
width: usize,
height: usize,
pub image_dev: Image, // resulting image
// The reference is held by the pipelines. We will be changing
// this to make the scene upload dynamic.
scene_bufs: Vec<Buffer>,
memory_buf_host: Vec<Buffer>,
memory_buf_dev: Buffer,
// Staging buffers
config_bufs: Vec<Buffer>,
// Device config buf
config_buf: Buffer,
// New element pipeline
element_code: ElementCode,
element_stage: ElementStage,
element_bindings: Vec<ElementBinding>,
clip_code: ClipCode,
clip_binding: ClipBinding,
tile_pipeline: Pipeline,
tile_ds: Vec<DescriptorSet>,
path_pipeline: Pipeline,
path_ds: DescriptorSet,
backdrop_pipeline: Pipeline,
backdrop_ds: DescriptorSet,
backdrop_y: u32,
bin_pipeline: Pipeline,
bin_ds: DescriptorSet,
coarse_pipeline: Pipeline,
coarse_ds: Vec<DescriptorSet>,
k4_pipeline: Pipeline,
k4_ds: DescriptorSet,
n_transform: usize,
n_drawobj: usize,
n_paths: usize,
n_pathseg: usize,
n_pathtag: usize,
n_clip: u32,
// Keep a reference to the image so that it is not destroyed.
_bg_image: Image,
gradient_bufs: Vec<Buffer>,
gradients: Image,
}
impl RenderConfig {
pub fn new(width: usize, height: usize) -> RenderConfig {
RenderConfig {
width,
height,
format: PixelFormat::Rgba8,
}
}
pub fn pixel_format(mut self, format: PixelFormat) -> Self {
self.format = format;
self
}
}
impl Renderer {
pub unsafe fn new(
session: &Session,
width: usize,
height: usize,
n_bufs: usize,
) -> Result<Self, Error> {
let config = RenderConfig::new(width, height);
Self::new_from_config(session, config, n_bufs)
}
/// Create a new renderer.
pub unsafe fn new_from_config(
session: &Session,
config: RenderConfig,
n_bufs: usize,
) -> Result<Self, Error> {
// For now, round up to tile alignment
let width = config.width;
let height = config.height;
let width = width + (width.wrapping_neg() & (TILE_W - 1));
let height = height + (height.wrapping_neg() & (TILE_W - 1));
let dev = BufferUsage::STORAGE | BufferUsage::COPY_DST;
let host_upload = BufferUsage::MAP_WRITE | BufferUsage::COPY_SRC;
// This may be inadequate for very complex scenes (paris etc)
// TODO: separate staging buffer (if needed)
let scene_bufs = (0..n_bufs)
.map(|_| session.create_buffer(8 * 1024 * 1024, host_upload).unwrap())
.collect::<Vec<_>>();
let image_format = match config.format {
PixelFormat::A8 => piet_gpu_hal::ImageFormat::A8,
PixelFormat::Rgba8 => piet_gpu_hal::ImageFormat::Rgba8,
};
let image_dev = session.create_image2d(width as u32, height as u32, image_format)?;
const CONFIG_BUFFER_SIZE: u64 = std::mem::size_of::<Config>() as u64;
let config_buf = session.create_buffer(CONFIG_BUFFER_SIZE, dev).unwrap();
let config_bufs = (0..n_bufs)
.map(|_| {
session
.create_buffer(CONFIG_BUFFER_SIZE, host_upload)
.unwrap()
})
.collect();
let memory_buf_host = (0..n_bufs)
.map(|_| session.create_buffer(2 * 4, host_upload).unwrap())
.collect();
let memory_buf_dev = session.create_buffer(128 * 1024 * 1024, dev)?;
let element_code = ElementCode::new(session);
let element_stage = ElementStage::new(session, &element_code);
let element_bindings = scene_bufs
.iter()
.map(|scene_buf| {
element_stage.bind(
session,
&element_code,
&config_buf,
scene_buf,
&memory_buf_dev,
)
})
.collect();
let clip_code = ClipCode::new(session);
let clip_binding = ClipBinding::new(session, &clip_code, &config_buf, &memory_buf_dev);
let tile_alloc_code = include_shader!(session, "../shader/gen/tile_alloc");
let tile_pipeline = session.create_compute_pipeline(
tile_alloc_code,
&[
BindType::Buffer,
BindType::BufReadOnly,
BindType::BufReadOnly,
],
)?;
let tile_ds = scene_bufs
.iter()
.map(|scene_buf| {
session.create_simple_descriptor_set(
&tile_pipeline,
&[&memory_buf_dev, &config_buf, scene_buf],
)
})
.collect::<Result<Vec<_>, _>>()?;
let path_alloc_code = include_shader!(session, "../shader/gen/path_coarse");
let path_pipeline = session
.create_compute_pipeline(path_alloc_code, &[BindType::Buffer, BindType::BufReadOnly])?;
let path_ds = session
.create_simple_descriptor_set(&path_pipeline, &[&memory_buf_dev, &config_buf])?;
let (backdrop_code, backdrop_y) =
if session.gpu_info().workgroup_limits.max_invocations >= 1024 {
(include_shader!(session, "../shader/gen/backdrop_lg"), 4)
} else {
println!("using small workgroup backdrop kernel");
(include_shader!(session, "../shader/gen/backdrop"), 1)
};
let backdrop_pipeline = session
.create_compute_pipeline(backdrop_code, &[BindType::Buffer, BindType::BufReadOnly])?;
let backdrop_ds = session
.create_simple_descriptor_set(&backdrop_pipeline, &[&memory_buf_dev, &config_buf])?;
// TODO: constants
let bin_code = include_shader!(session, "../shader/gen/binning");
let bin_pipeline = session
.create_compute_pipeline(bin_code, &[BindType::Buffer, BindType::BufReadOnly])?;
let bin_ds =
session.create_simple_descriptor_set(&bin_pipeline, &[&memory_buf_dev, &config_buf])?;
let coarse_code = include_shader!(session, "../shader/gen/coarse");
let coarse_pipeline = session.create_compute_pipeline(
coarse_code,
&[
BindType::Buffer,
BindType::BufReadOnly,
BindType::BufReadOnly,
],
)?;
let coarse_ds = scene_bufs
.iter()
.map(|scene_buf| {
session.create_simple_descriptor_set(
&coarse_pipeline,
&[&memory_buf_dev, &config_buf, scene_buf],
)
})
.collect::<Result<Vec<_>, _>>()?;
let bg_image = Self::make_test_bg_image(&session);
const GRADIENT_BUF_SIZE: usize =
crate::gradient::N_GRADIENTS * crate::gradient::N_SAMPLES * 4;
let gradient_bufs = (0..n_bufs)
.map(|_| {
session
.create_buffer(GRADIENT_BUF_SIZE as u64, host_upload)
.unwrap()
})
.collect();
let gradients = Self::make_gradient_image(&session);
let k4_code = match config.format {
PixelFormat::A8 => include_shader!(session, "../shader/gen/kernel4_gray"),
PixelFormat::Rgba8 => include_shader!(session, "../shader/gen/kernel4"),
};
let k4_pipeline = session.create_compute_pipeline(
k4_code,
&[
BindType::Buffer,
BindType::BufReadOnly,
BindType::Image,
BindType::ImageRead,
BindType::ImageRead,
],
)?;
let k4_ds = session
.descriptor_set_builder()
.add_buffers(&[&memory_buf_dev, &config_buf])
.add_images(&[&image_dev])
.add_textures(&[&bg_image, &gradients])
.build(&session, &k4_pipeline)?;
Ok(Renderer {
width,
height,
scene_bufs,
memory_buf_host,
memory_buf_dev,
config_buf,
config_bufs,
image_dev,
element_code,
element_stage,
element_bindings,
clip_code,
clip_binding,
tile_pipeline,
tile_ds,
path_pipeline,
path_ds,
backdrop_pipeline,
backdrop_ds,
backdrop_y,
bin_pipeline,
bin_ds,
coarse_pipeline,
coarse_ds,
k4_pipeline,
k4_ds,
n_transform: 0,
n_drawobj: 0,
n_paths: 0,
n_pathseg: 0,
n_pathtag: 0,
n_clip: 0,
_bg_image: bg_image,
gradient_bufs,
gradients,
})
}
/// Convert the scene in the render context to GPU resources.
///
/// At present, this requires that any command buffer submission has completed.
/// A future evolution will handle staging of the next frame's scene while the
/// rendering of the current frame is in flight.
pub fn upload_render_ctx(
&mut self,
render_ctx: &mut PietGpuRenderContext,
buf_ix: usize,
) -> Result<(), Error> {
let (mut config, mut alloc) = render_ctx.stage_config();
let n_drawobj = render_ctx.n_drawobj();
// TODO: be more consistent in size types
let n_path = render_ctx.n_path() as usize;
self.n_paths = n_path;
self.n_transform = render_ctx.n_transform();
self.n_drawobj = render_ctx.n_drawobj();
self.n_pathseg = render_ctx.n_pathseg() as usize;
self.n_pathtag = render_ctx.n_pathtag();
self.n_clip = render_ctx.n_clip();
// These constants depend on encoding and may need to be updated.
// Perhaps we can plumb these from piet-gpu-derive?
const PATH_SIZE: usize = 12;
const BIN_SIZE: usize = 8;
let width_in_tiles = self.width / TILE_W;
let height_in_tiles = self.height / TILE_H;
let tile_base = alloc;
alloc += ((n_path + 3) & !3) * PATH_SIZE;
let bin_base = alloc;
alloc += ((n_drawobj + 255) & !255) * BIN_SIZE;
let ptcl_base = alloc;
alloc += width_in_tiles * height_in_tiles * PTCL_INITIAL_ALLOC;
config.width_in_tiles = width_in_tiles as u32;
config.height_in_tiles = height_in_tiles as u32;
config.tile_alloc = tile_base as u32;
config.bin_alloc = bin_base as u32;
config.ptcl_alloc = ptcl_base as u32;
unsafe {
// TODO: reallocate scene buffer if size is inadequate
{
let mut mapped_scene = self.scene_bufs[buf_ix].map_write(..)?;
render_ctx.write_scene(&mut mapped_scene);
}
self.config_bufs[buf_ix].write(&[config])?;
self.memory_buf_host[buf_ix].write(&[alloc as u32, 0 /* Overflow flag */])?;
// Upload gradient data.
let ramp_data = render_ctx.get_ramp_data();
if !ramp_data.is_empty() {
assert!(
self.gradient_bufs[buf_ix].size() as usize
>= std::mem::size_of_val(&*ramp_data)
);
self.gradient_bufs[buf_ix].write(&ramp_data)?;
}
}
Ok(())
}
pub fn upload_scene<T: Copy + Pod>(
&mut self,
scene: &EncodedSceneRef<T>,
buf_ix: usize,
) -> Result<(), Error> {
let (mut config, mut alloc) = scene.stage_config();
let n_drawobj = scene.n_drawobj();
// TODO: be more consistent in size types
let n_path = scene.n_path() as usize;
self.n_paths = n_path;
self.n_transform = scene.n_transform();
self.n_drawobj = scene.n_drawobj();
self.n_pathseg = scene.n_pathseg() as usize;
self.n_pathtag = scene.n_pathtag();
self.n_clip = scene.n_clip();
// These constants depend on encoding and may need to be updated.
// Perhaps we can plumb these from piet-gpu-derive?
const PATH_SIZE: usize = 12;
const BIN_SIZE: usize = 8;
let width_in_tiles = self.width / TILE_W;
let height_in_tiles = self.height / TILE_H;
let tile_base = alloc;
alloc += ((n_path + 3) & !3) * PATH_SIZE;
let bin_base = alloc;
alloc += ((n_drawobj + 255) & !255) * BIN_SIZE;
let ptcl_base = alloc;
alloc += width_in_tiles * height_in_tiles * PTCL_INITIAL_ALLOC;
config.width_in_tiles = width_in_tiles as u32;
config.height_in_tiles = height_in_tiles as u32;
config.tile_alloc = tile_base as u32;
config.bin_alloc = bin_base as u32;
config.ptcl_alloc = ptcl_base as u32;
unsafe {
// TODO: reallocate scene buffer if size is inadequate
{
let mut mapped_scene = self.scene_bufs[buf_ix].map_write(..)?;
scene.write_scene(&mut mapped_scene);
}
self.config_bufs[buf_ix].write(&[config])?;
self.memory_buf_host[buf_ix].write(&[alloc as u32, 0 /* Overflow flag */])?;
// Upload gradient data.
if !scene.ramp_data.is_empty() {
assert!(
self.gradient_bufs[buf_ix].size() as usize
>= std::mem::size_of_val(&*scene.ramp_data)
);
self.gradient_bufs[buf_ix].write(scene.ramp_data)?;
}
}
Ok(())
}
pub unsafe fn record(&self, cmd_buf: &mut CmdBuf, query_pool: &QueryPool, buf_ix: usize) {
cmd_buf.copy_buffer(&self.config_bufs[buf_ix], &self.config_buf);
cmd_buf.copy_buffer(&self.memory_buf_host[buf_ix], &self.memory_buf_dev);
cmd_buf.memory_barrier();
cmd_buf.image_barrier(
&self.image_dev,
ImageLayout::Undefined,
ImageLayout::General,
);
// TODO: make gradient upload optional, only if it's changed
cmd_buf.image_barrier(
&self.gradients,
ImageLayout::Undefined,
ImageLayout::BlitDst,
);
cmd_buf.copy_buffer_to_image(&self.gradient_bufs[buf_ix], &self.gradients);
cmd_buf.image_barrier(&self.gradients, ImageLayout::BlitDst, ImageLayout::General);
cmd_buf.reset_query_pool(&query_pool);
cmd_buf.begin_debug_label("Element bounding box calculation");
let mut pass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::timer(&query_pool, 0, 1));
self.element_stage.record(
&mut pass,
&self.element_code,
&self.element_bindings[buf_ix],
self.n_transform as u64,
self.n_paths as u32,
self.n_pathtag as u32,
self.n_drawobj as u64,
);
pass.end();
cmd_buf.end_debug_label();
cmd_buf.memory_barrier();
let mut pass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::timer(&query_pool, 2, 3));
pass.begin_debug_label("Clip bounding box calculation");
self.clip_binding
.record(&mut pass, &self.clip_code, self.n_clip as u32);
pass.end_debug_label();
pass.begin_debug_label("Element binning");
pass.dispatch(
&self.bin_pipeline,
&self.bin_ds,
(((self.n_paths + 255) / 256) as u32, 1, 1),
(256, 1, 1),
);
pass.end_debug_label();
pass.memory_barrier();
pass.begin_debug_label("Tile allocation");
pass.dispatch(
&self.tile_pipeline,
&self.tile_ds[buf_ix],
(((self.n_paths + 255) / 256) as u32, 1, 1),
(256, 1, 1),
);
pass.end_debug_label();
pass.end();
cmd_buf.begin_debug_label("Path flattening");
cmd_buf.memory_barrier();
let mut pass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::timer(&query_pool, 4, 5));
pass.dispatch(
&self.path_pipeline,
&self.path_ds,
(((self.n_pathseg + 31) / 32) as u32, 1, 1),
(32, 1, 1),
);
pass.end();
cmd_buf.end_debug_label();
cmd_buf.memory_barrier();
cmd_buf.begin_debug_label("Backdrop propagation");
let mut pass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::timer(&query_pool, 6, 7));
pass.dispatch(
&self.backdrop_pipeline,
&self.backdrop_ds,
(((self.n_paths + 255) / 256) as u32, 1, 1),
(256, self.backdrop_y, 1),
);
pass.end();
cmd_buf.end_debug_label();
// TODO: redo query accounting
cmd_buf.memory_barrier();
cmd_buf.begin_debug_label("Coarse raster");
let mut pass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::timer(&query_pool, 8, 9));
pass.dispatch(
&self.coarse_pipeline,
&self.coarse_ds[buf_ix],
(
(self.width as u32 + 255) / 256,
(self.height as u32 + 255) / 256,
1,
),
(256, 1, 1),
);
pass.end();
cmd_buf.end_debug_label();
cmd_buf.memory_barrier();
cmd_buf.begin_debug_label("Fine raster");
let mut pass =
cmd_buf.begin_compute_pass(&ComputePassDescriptor::timer(&query_pool, 10, 11));
pass.dispatch(
&self.k4_pipeline,
&self.k4_ds,
(
(self.width / TILE_W) as u32,
(self.height / TILE_H) as u32,
1,
),
(8, 4, 1),
);
pass.end();
cmd_buf.end_debug_label();
cmd_buf.memory_barrier();
cmd_buf.image_barrier(&self.image_dev, ImageLayout::General, ImageLayout::BlitSrc);
}
pub fn make_image(
session: &Session,
width: usize,
height: usize,
buf: &[u8],
format: ImageFormat,
) -> Result<Image, Error> {
unsafe {
if format != ImageFormat::RgbaPremul {
return Err("unsupported image format".into());
}
let buffer = session.create_buffer_init(&buf, BufferUsage::COPY_SRC)?;
const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8;
let image = session.create_image2d(width.try_into()?, height.try_into()?, RGBA)?;
let mut cmd_buf = session.cmd_buf()?;
cmd_buf.begin();
cmd_buf.image_barrier(&image, ImageLayout::Undefined, ImageLayout::BlitDst);
cmd_buf.copy_buffer_to_image(&buffer, &image);
cmd_buf.image_barrier(&image, ImageLayout::BlitDst, ImageLayout::General);
cmd_buf.finish();
// Make sure not to drop the buffer and image until the command buffer completes.
cmd_buf.add_resource(&buffer);
cmd_buf.add_resource(&image);
let _ = session.run_cmd_buf(cmd_buf, &[], &[]);
// We let the session reclaim the fence.
Ok(image)
}
}
/// Make a test image.
fn make_test_bg_image(session: &Session) -> Image {
const WIDTH: usize = 256;
const HEIGHT: usize = 256;
let mut buf = vec![255u8; WIDTH * HEIGHT * 4];
for y in 0..HEIGHT {
for x in 0..WIDTH {
let r = x as u8;
let g = y as u8;
let b = r ^ g;
buf[(y * WIDTH + x) * 4] = r;
buf[(y * WIDTH + x) * 4 + 1] = g;
buf[(y * WIDTH + x) * 4 + 2] = b;
}
}
Self::make_image(session, WIDTH, HEIGHT, &buf, ImageFormat::RgbaPremul).unwrap()
}
fn make_gradient_image(session: &Session) -> Image {
unsafe {
const RGBA: piet_gpu_hal::ImageFormat = piet_gpu_hal::ImageFormat::Rgba8;
session
.create_image2d(
gradient::N_SAMPLES as u32,
gradient::N_GRADIENTS as u32,
RGBA,
)
.unwrap()
}
}
}