blob: e0415d4e49e7cc3d746dcf996e7269b49e89fa45 [file] [log] [blame]
mod blend;
mod encoder;
pub mod glyph_render;
mod gradient;
mod pico_svg;
mod render_ctx;
mod render_driver;
pub mod stages;
pub mod test_scenes;
mod text;
use bytemuck::{Pod, Zeroable};
use std::convert::TryInto;
pub use blend::{Blend, BlendMode, CompositionMode};
pub use encoder::EncodedSceneRef;
pub use gradient::Colrv1RadialGradient;
pub use render_ctx::PietGpuRenderContext;
pub use render_driver::RenderDriver;
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, DRAW_PART_SIZE, PATHSEG_PART_SIZE};
use crate::stages::{ClipCode, Config, ElementStage, CLIP_PART_SIZE};
const TILE_W: usize = 16;
const TILE_H: usize = 16;
const PTCL_INITIAL_ALLOC: usize = 1024;
#[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,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Zeroable, Pod)]
pub(crate) struct MemoryHeader {
mem_offset: u32,
mem_error: u32,
blend_offset: u32,
}
/// The sizes of various objects in the encoded scene, needed for memory layout.
#[derive(Default)]
pub(crate) struct SceneStats {
// Slices of scene encoding, in order
pub n_drawobj: usize,
pub drawdata_len: usize,
pub n_transform: usize,
pub linewidth_len: usize,
pub pathseg_len: usize,
pub n_pathtag: usize,
// Additional stats needed needed for memory layout & dispatch
pub n_path: u32,
pub n_pathseg: u32,
pub n_clip: u32,
}
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
// TODO: two changes needed here. First, if we're fencing on the coarse
// pipeline, then we only need one copy (this changes if we also bind the
// scene buffer in fine rasterization, which might be a good idea to reduce
// copying). Second, there should be a staging buffer for discrete cards.
scene_bufs: Vec<Buffer>,
memory_buf_host: Vec<Buffer>,
memory_buf_dev: Buffer,
memory_buf_readback: Buffer,
// Staging buffers
config_bufs: Vec<Buffer>,
// Device config buf
config_buf: Buffer,
blend_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,
scene_stats: SceneStats,
// TODO: the following stats are now redundant and can be removed.
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 {
/// The number of query pool entries needed to run the renderer.
pub const QUERY_POOL_SIZE: u32 = Self::COARSE_QUERY_POOL_SIZE + Self::FINE_QUERY_POOL_SIZE;
/// The number of query pool entries needed to run the coarse pipeline.
pub const COARSE_QUERY_POOL_SIZE: u32 = 10;
/// The number of query pool entries needed to run the fine pipeline.
pub const FINE_QUERY_POOL_SIZE: u32 = 2;
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 usage_mem_dev = BufferUsage::STORAGE | BufferUsage::COPY_DST | BufferUsage::COPY_SRC;
let usage_blend = BufferUsage::STORAGE;
let usage_upload = BufferUsage::MAP_WRITE | BufferUsage::COPY_SRC;
let usage_readback = BufferUsage::MAP_READ | BufferUsage::COPY_DST;
// TODO: separate staging buffer (if needed)
let scene_bufs = (0..n_bufs)
.map(|_| {
session
.create_buffer(8 * 1024 * 1024, usage_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, usage_upload)
.unwrap()
})
.collect();
let memory_buf_host = (0..n_bufs)
.map(|_| {
session
.create_buffer(std::mem::size_of::<MemoryHeader>() as u64, usage_upload)
.unwrap()
})
.collect();
let memory_buf_dev = session.create_buffer(16 * 1024 * 1024, usage_mem_dev)?;
let memory_buf_readback =
session.create_buffer(std::mem::size_of::<MemoryHeader>() as u64, usage_readback)?;
let blend_buf = session.create_buffer(16 * 1024 * 1024, usage_blend)?;
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, usage_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::Buffer,
BindType::Image,
BindType::ImageRead,
BindType::ImageRead,
],
)?;
let k4_ds = session
.descriptor_set_builder()
.add_buffers(&[&memory_buf_dev, &config_buf, &blend_buf])
.add_images(&[&image_dev])
.add_textures(&[&bg_image, &gradients])
.build(&session, &k4_pipeline)?;
let scene_stats = Default::default();
Ok(Renderer {
width,
height,
scene_bufs,
memory_buf_host,
memory_buf_dev,
memory_buf_readback,
config_buf,
config_bufs,
blend_buf,
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,
scene_stats,
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> {
self.scene_stats = render_ctx.stats();
unsafe {
self.upload_config(buf_ix)?;
{
let mut mapped_scene = self.scene_bufs[buf_ix].map_write(..)?;
render_ctx.write_scene(&mut mapped_scene);
}
// 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> {
self.scene_stats = scene.stats();
unsafe {
self.upload_config(buf_ix)?;
{
let mut mapped_scene = self.scene_bufs[buf_ix].map_write(..)?;
scene.write_scene(&mut mapped_scene);
}
// 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(())
}
// Note: configuration has to be re-uploaded when memory buffer is resized
pub(crate) unsafe fn upload_config(&mut self, buf_ix: usize) -> Result<(), Error> {
let stats = &self.scene_stats;
let n_path = stats.n_path as usize;
self.n_paths = n_path;
self.n_transform = stats.n_transform;
self.n_drawobj = stats.n_drawobj;
self.n_pathseg = stats.n_pathseg as usize;
self.n_pathtag = stats.n_pathtag;
self.n_clip = stats.n_clip;
let (mut config, alloc) = stats.config(self.width, self.height);
config.mem_size = self.memory_buf_size() as u32;
self.config_bufs[buf_ix].write(&[config])?;
let mem_header = MemoryHeader {
mem_offset: alloc as u32,
mem_error: 0,
blend_offset: 0,
};
// Note: we could skip doing this on realloc, but probably not worth the bother
self.memory_buf_host[buf_ix].write(&[mem_header])?;
Ok(())
}
/// Get the size of memory for the allocations known in advance.
pub(crate) fn memory_size(&self, stats: &SceneStats) -> usize {
stats.config(self.width, self.height).1
}
/// Record the coarse part of a render pipeline.
pub unsafe fn record_coarse(
&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_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();
}
pub unsafe fn record_fine(
&self,
cmd_buf: &mut CmdBuf,
query_pool: &QueryPool,
query_start: u32,
) {
cmd_buf.reset_query_pool(&query_pool);
cmd_buf.begin_debug_label("Fine raster");
let mut pass = cmd_buf.begin_compute_pass(&ComputePassDescriptor::timer(
&query_pool,
query_start,
query_start + 1,
));
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 unsafe fn record_readback(&self, cmd_buf: &mut CmdBuf) {
cmd_buf.copy_buffer(&self.memory_buf_dev, &self.memory_buf_readback);
cmd_buf.memory_barrier();
}
/// Record a render pipeline.
///
/// This *assumes* the buffers are adequately sized.
pub unsafe fn record(&self, cmd_buf: &mut CmdBuf, query_pool: &QueryPool, buf_ix: usize) {
self.record_coarse(cmd_buf, query_pool, buf_ix);
self.record_fine(cmd_buf, query_pool, 10);
}
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()
}
}
pub(crate) unsafe fn realloc_scene_if_needed(
&mut self,
session: &Session,
new_size: u64,
buf_ix: usize,
) -> Result<(), Error> {
if new_size <= self.scene_bufs[buf_ix].size() {
return Ok(());
}
const ALIGN: u64 = 0x10000;
let new_size = (new_size + ALIGN - 1) & ALIGN.wrapping_neg();
println!(
"reallocating scene buf[{}] {} -> {}",
buf_ix,
self.scene_bufs[buf_ix].size(),
new_size
);
let usage_upload = BufferUsage::MAP_WRITE | BufferUsage::COPY_SRC;
let scene_buf = session.create_buffer(new_size, usage_upload)?;
self.element_bindings[buf_ix].rebind_scene(session, &scene_buf);
session.update_buffer_descriptor(&mut self.tile_ds[buf_ix], 2, &scene_buf);
session.update_buffer_descriptor(&mut self.coarse_ds[buf_ix], 2, &scene_buf);
self.scene_bufs[buf_ix] = scene_buf;
Ok(())
}
/// Get the size of the memory buffer.
///
/// This is the usable size (not including the header).
pub(crate) fn memory_buf_size(&self) -> u64 {
self.memory_buf_dev.size() - std::mem::size_of::<MemoryHeader>() as u64
}
pub(crate) unsafe fn realloc_memory(
&mut self,
session: &Session,
new_size: u64,
) -> Result<(), Error> {
println!(
"reallocating memory buf {} -> {}",
self.memory_buf_dev.size(),
new_size
);
let usage_mem_dev = BufferUsage::STORAGE | BufferUsage::COPY_DST | BufferUsage::COPY_SRC;
let memory_buf_dev = session.create_buffer(new_size, usage_mem_dev)?;
for element_binding in &mut self.element_bindings {
element_binding.rebind_memory(session, &memory_buf_dev);
}
self.clip_binding.rebind_memory(session, &memory_buf_dev);
for tile_ds in &mut self.tile_ds {
session.update_buffer_descriptor(tile_ds, 0, &memory_buf_dev);
}
session.update_buffer_descriptor(&mut self.path_ds, 0, &memory_buf_dev);
session.update_buffer_descriptor(&mut self.backdrop_ds, 0, &memory_buf_dev);
session.update_buffer_descriptor(&mut self.bin_ds, 0, &memory_buf_dev);
for coarse_ds in &mut self.coarse_ds {
session.update_buffer_descriptor(coarse_ds, 0, &memory_buf_dev);
}
session.update_buffer_descriptor(&mut self.k4_ds, 0, &memory_buf_dev);
self.memory_buf_dev = memory_buf_dev;
Ok(())
}
pub(crate) fn blend_size(&self) -> u64 {
self.blend_buf.size()
}
pub(crate) unsafe fn realloc_blend(
&mut self,
session: &Session,
new_size: u64,
) -> Result<(), Error> {
println!(
"reallocating blend buf {} -> {}",
self.blend_size(),
new_size
);
let usage_blend = BufferUsage::STORAGE;
let blend_buf = session.create_buffer(new_size, usage_blend)?;
session.update_buffer_descriptor(&mut self.k4_ds, 2, &blend_buf);
self.blend_buf = blend_buf;
Ok(())
}
}
const TRANSFORM_SIZE: usize = 24;
const PATHSEG_SIZE: usize = 52;
const PATH_BBOX_SIZE: usize = 24;
const DRAWMONOID_SIZE: usize = 16;
const DRAW_BBOX_SIZE: usize = 16;
const DRAWTAG_SIZE: usize = 4;
const ANNOTATED_SIZE: usize = 40;
impl SceneStats {
pub(crate) fn scene_size(&self) -> usize {
align_up(self.n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE
+ self.drawdata_len
+ self.n_transform * TRANSFORM_SIZE
+ self.linewidth_len
+ align_up(self.n_pathtag, PATHSEG_PART_SIZE as usize)
+ self.pathseg_len
}
/// Return a config for a scene with these stats.
///
/// Also returns the beginning of free (dynamic) memory.
fn config(&self, width: usize, height: usize) -> (Config, usize) {
// Layout of scene buffer
let drawtag_offset = 0;
let n_drawobj = self.n_drawobj;
let n_drawobj_padded = align_up(n_drawobj, DRAW_PART_SIZE as usize);
let drawdata_offset = drawtag_offset + n_drawobj_padded * DRAWTAG_SIZE;
let trans_offset = drawdata_offset + self.drawdata_len;
let n_trans = self.n_transform;
let linewidth_offset = trans_offset + n_trans * TRANSFORM_SIZE;
let pathtag_offset = linewidth_offset + self.linewidth_len;
let n_pathtag = self.n_pathtag;
let n_pathtag_padded = align_up(n_pathtag, PATHSEG_PART_SIZE as usize);
let pathseg_offset = pathtag_offset + n_pathtag_padded;
// Layout of memory
let mut alloc = 0;
let pathseg_alloc = alloc;
alloc += pathseg_alloc + self.n_pathseg as usize * PATHSEG_SIZE;
let path_bbox_alloc = alloc;
let n_path = self.n_path as usize;
alloc += path_bbox_alloc + n_path * PATH_BBOX_SIZE;
let drawmonoid_alloc = alloc;
alloc += n_drawobj_padded * DRAWMONOID_SIZE;
let anno_alloc = alloc;
alloc += n_drawobj * ANNOTATED_SIZE;
let clip_alloc = alloc;
let n_clip = self.n_clip as usize;
const CLIP_SIZE: usize = 4;
alloc += n_clip * CLIP_SIZE;
let clip_bic_alloc = alloc;
const CLIP_BIC_SIZE: usize = 8;
// This can round down, as we only reduce the prefix
alloc += (n_clip / CLIP_PART_SIZE as usize) * CLIP_BIC_SIZE;
let clip_stack_alloc = alloc;
const CLIP_EL_SIZE: usize = 20;
alloc += n_clip * CLIP_EL_SIZE;
let clip_bbox_alloc = alloc;
const CLIP_BBOX_SIZE: usize = 16;
alloc += align_up(n_clip as usize, CLIP_PART_SIZE as usize) * CLIP_BBOX_SIZE;
let draw_bbox_alloc = alloc;
alloc += n_drawobj * DRAW_BBOX_SIZE;
let drawinfo_alloc = alloc;
// TODO: not optimized; it can be accumulated during encoding or summed from drawtags
const MAX_DRAWINFO_SIZE: usize = 44;
alloc += n_drawobj * MAX_DRAWINFO_SIZE;
// These constants depend on encoding and may need to be updated.
const PATH_SIZE: usize = 12;
const BIN_SIZE: usize = 8;
let width_in_tiles = width / TILE_W;
let height_in_tiles = 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;
let config = Config {
mem_size: 0, // to be filled in later
n_elements: n_drawobj as u32,
n_pathseg: self.n_pathseg,
pathseg_alloc: pathseg_alloc as u32,
anno_alloc: anno_alloc as u32,
path_bbox_alloc: path_bbox_alloc as u32,
drawmonoid_alloc: drawmonoid_alloc as u32,
clip_alloc: clip_alloc as u32,
clip_bic_alloc: clip_bic_alloc as u32,
clip_stack_alloc: clip_stack_alloc as u32,
clip_bbox_alloc: clip_bbox_alloc as u32,
draw_bbox_alloc: draw_bbox_alloc as u32,
drawinfo_alloc: drawinfo_alloc as u32,
n_trans: n_trans as u32,
n_path: self.n_path,
n_clip: self.n_clip,
trans_offset: trans_offset as u32,
linewidth_offset: linewidth_offset as u32,
pathtag_offset: pathtag_offset as u32,
pathseg_offset: pathseg_offset as u32,
drawtag_offset: drawtag_offset as u32,
drawdata_offset: drawdata_offset as u32,
width_in_tiles: width_in_tiles as u32,
height_in_tiles: height_in_tiles as u32,
tile_alloc: tile_base as u32,
bin_alloc: bin_base as u32,
ptcl_alloc: ptcl_base as u32,
};
(config, alloc)
}
}
fn align_up(x: usize, align: usize) -> usize {
debug_assert!(align.is_power_of_two());
(x + align - 1) & !(align - 1)
}