// Copyright 2021 The piet-gpu authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Also licensed under MIT license, at your choice. //! Low-level scene encoding. use crate::Blend; use bytemuck::{Pod, Zeroable}; use piet_gpu_hal::BufWrite; use crate::stages::{ self, Config, PathEncoder, Transform, CLIP_PART_SIZE, DRAW_PART_SIZE, PATHSEG_PART_SIZE, TRANSFORM_PART_SIZE, }; pub struct Encoder { transform_stream: Vec, tag_stream: Vec, pathseg_stream: Vec, linewidth_stream: Vec, drawtag_stream: Vec, drawdata_stream: Vec, n_path: u32, n_pathseg: u32, n_clip: u32, } #[derive(Copy, Clone, Debug)] pub struct EncodedSceneRef<'a, T: Copy + Pod> { pub transform_stream: &'a [T], pub tag_stream: &'a [u8], pub pathseg_stream: &'a [u8], pub linewidth_stream: &'a [f32], pub drawtag_stream: &'a [u32], pub drawdata_stream: &'a [u8], pub n_path: u32, pub n_pathseg: u32, pub n_clip: u32, pub ramp_data: &'a [u32], } impl<'a, T: Copy + Pod> EncodedSceneRef<'a, T> { /// Return a config for the element processing pipeline. /// /// This does not include further pipeline processing. Also returns the /// beginning of free memory. pub fn stage_config(&self) -> (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_stream.len(); let n_trans = self.transform_stream.len(); let n_trans_padded = align_up(n_trans, TRANSFORM_PART_SIZE as usize); let linewidth_offset = trans_offset + n_trans_padded * TRANSFORM_SIZE; let n_linewidth = self.linewidth_stream.len(); let pathtag_offset = linewidth_offset + n_linewidth * LINEWIDTH_SIZE; let n_pathtag = self.tag_stream.len(); 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 trans_alloc = alloc; alloc += trans_alloc + n_trans_padded * TRANSFORM_SIZE; 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; let config = Config { n_elements: n_drawobj as u32, n_pathseg: self.n_pathseg, pathseg_alloc: pathseg_alloc as u32, anno_alloc: anno_alloc as u32, trans_alloc: trans_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, ..Default::default() }; (config, alloc) } pub fn write_scene(&self, buf: &mut BufWrite) { buf.extend_slice(&self.drawtag_stream); let n_drawobj = self.drawtag_stream.len(); buf.fill_zero(padding(n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE); buf.extend_slice(&self.drawdata_stream); buf.extend_slice(&self.transform_stream); let n_trans = self.transform_stream.len(); buf.fill_zero(padding(n_trans, TRANSFORM_PART_SIZE as usize) * TRANSFORM_SIZE); buf.extend_slice(&self.linewidth_stream); buf.extend_slice(&self.tag_stream); let n_pathtag = self.tag_stream.len(); buf.fill_zero(padding(n_pathtag, PATHSEG_PART_SIZE as usize)); buf.extend_slice(&self.pathseg_stream); } /// The number of draw objects in the draw object stream. pub(crate) fn n_drawobj(&self) -> usize { self.drawtag_stream.len() } /// The number of paths. pub(crate) fn n_path(&self) -> u32 { self.n_path } /// The number of path segments. pub(crate) fn n_pathseg(&self) -> u32 { self.n_pathseg } pub(crate) fn n_transform(&self) -> usize { self.transform_stream.len() } /// The number of tags in the path stream. pub(crate) fn n_pathtag(&self) -> usize { self.tag_stream.len() } pub(crate) fn n_clip(&self) -> u32 { self.n_clip } } /// A scene fragment encoding a glyph. /// /// This is a reduced version of the full encoder. #[derive(Default)] pub struct GlyphEncoder { tag_stream: Vec, pathseg_stream: Vec, drawtag_stream: Vec, drawdata_stream: Vec, n_path: u32, n_pathseg: u32, } const TRANSFORM_SIZE: usize = 24; const LINEWIDTH_SIZE: usize = 4; 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; // Tags for draw objects. See shader/drawtag.h for the authoritative source. const DRAWTAG_FILLCOLOR: u32 = 0x44; const DRAWTAG_FILLLINGRADIENT: u32 = 0x114; const DRAWTAG_FILLRADGRADIENT: u32 = 0x2dc; const DRAWTAG_BEGINCLIP: u32 = 0x05; const DRAWTAG_ENDCLIP: u32 = 0x25; #[repr(C)] #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] pub struct FillColor { rgba_color: u32, } #[repr(C)] #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] pub struct FillLinGradient { index: u32, p0: [f32; 2], p1: [f32; 2], } #[repr(C)] #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] pub struct FillRadGradient { index: u32, p0: [f32; 2], p1: [f32; 2], r0: f32, r1: f32, } #[allow(unused)] #[repr(C)] #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] pub struct FillImage { index: u32, // [i16; 2] offset: u32, } #[repr(C)] #[derive(Clone, Copy, Debug, Default, Zeroable, Pod)] pub struct Clip { blend: u32, } impl Encoder { pub fn new() -> Encoder { Encoder { transform_stream: vec![Transform::IDENTITY], tag_stream: Vec::new(), pathseg_stream: Vec::new(), linewidth_stream: vec![-1.0], drawtag_stream: Vec::new(), drawdata_stream: Vec::new(), n_path: 0, n_pathseg: 0, n_clip: 0, } } pub fn path_encoder(&mut self) -> PathEncoder { PathEncoder::new(&mut self.tag_stream, &mut self.pathseg_stream) } pub fn finish_path(&mut self, n_pathseg: u32) { self.n_path += 1; self.n_pathseg += n_pathseg; } pub fn transform(&mut self, transform: Transform) { self.tag_stream.push(0x20); self.transform_stream.push(transform); } // Swap the last two tags in the tag stream; used for transformed // gradients. pub fn swap_last_tags(&mut self) { let len = self.tag_stream.len(); self.tag_stream.swap(len - 1, len - 2); } // -1.0 means "fill" pub fn linewidth(&mut self, linewidth: f32) { self.tag_stream.push(0x40); self.linewidth_stream.push(linewidth); } /// Encode a fill color draw object. /// /// This should be encoded after a path. pub fn fill_color(&mut self, rgba_color: u32) { self.drawtag_stream.push(DRAWTAG_FILLCOLOR); let element = FillColor { rgba_color }; self.drawdata_stream.extend(bytemuck::bytes_of(&element)); } /// Encode a fill linear gradient draw object. /// /// This should be encoded after a path. pub fn fill_lin_gradient(&mut self, index: u32, p0: [f32; 2], p1: [f32; 2]) { self.drawtag_stream.push(DRAWTAG_FILLLINGRADIENT); let element = FillLinGradient { index, p0, p1 }; self.drawdata_stream.extend(bytemuck::bytes_of(&element)); } /// Encode a fill radial gradient draw object. /// /// This should be encoded after a path. pub fn fill_rad_gradient(&mut self, index: u32, p0: [f32; 2], p1: [f32; 2], r0: f32, r1: f32) { self.drawtag_stream.push(DRAWTAG_FILLRADGRADIENT); let element = FillRadGradient { index, p0, p1, r0, r1, }; self.drawdata_stream.extend(bytemuck::bytes_of(&element)); } /// Start a clip. pub fn begin_clip(&mut self, blend: Option) { self.drawtag_stream.push(DRAWTAG_BEGINCLIP); let element = Clip { blend: blend.unwrap_or(Blend::default()).pack(), }; self.drawdata_stream.extend(bytemuck::bytes_of(&element)); self.n_clip += 1; } pub fn end_clip(&mut self, blend: Option) { self.drawtag_stream.push(DRAWTAG_ENDCLIP); let element = Clip { blend: blend.unwrap_or(Blend::default()).pack(), }; self.drawdata_stream.extend(bytemuck::bytes_of(&element)); // This is a dummy path, and will go away with the new clip impl. self.tag_stream.push(0x10); self.n_path += 1; self.n_clip += 1; } /// Return a config for the element processing pipeline. /// /// This does not include further pipeline processing. Also returns the /// beginning of free memory. pub fn stage_config(&self) -> (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_stream.len(); let n_trans = self.transform_stream.len(); let n_trans_padded = align_up(n_trans, TRANSFORM_PART_SIZE as usize); let linewidth_offset = trans_offset + n_trans_padded * TRANSFORM_SIZE; let n_linewidth = self.linewidth_stream.len(); let pathtag_offset = linewidth_offset + n_linewidth * LINEWIDTH_SIZE; let n_pathtag = self.tag_stream.len(); 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 trans_alloc = alloc; alloc += trans_alloc + n_trans_padded * TRANSFORM_SIZE; 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; let config = Config { n_elements: n_drawobj as u32, n_pathseg: self.n_pathseg, pathseg_alloc: pathseg_alloc as u32, anno_alloc: anno_alloc as u32, trans_alloc: trans_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, ..Default::default() }; (config, alloc) } pub fn write_scene(&self, buf: &mut BufWrite) { buf.extend_slice(&self.drawtag_stream); let n_drawobj = self.drawtag_stream.len(); buf.fill_zero(padding(n_drawobj, DRAW_PART_SIZE as usize) * DRAWTAG_SIZE); buf.extend_slice(&self.drawdata_stream); buf.extend_slice(&self.transform_stream); let n_trans = self.transform_stream.len(); buf.fill_zero(padding(n_trans, TRANSFORM_PART_SIZE as usize) * TRANSFORM_SIZE); buf.extend_slice(&self.linewidth_stream); buf.extend_slice(&self.tag_stream); let n_pathtag = self.tag_stream.len(); buf.fill_zero(padding(n_pathtag, PATHSEG_PART_SIZE as usize)); buf.extend_slice(&self.pathseg_stream); } /// The number of draw objects in the draw object stream. pub(crate) fn n_drawobj(&self) -> usize { self.drawtag_stream.len() } /// The number of paths. pub(crate) fn n_path(&self) -> u32 { self.n_path } /// The number of path segments. pub(crate) fn n_pathseg(&self) -> u32 { self.n_pathseg } pub(crate) fn n_transform(&self) -> usize { self.transform_stream.len() } /// The number of tags in the path stream. pub(crate) fn n_pathtag(&self) -> usize { self.tag_stream.len() } pub(crate) fn n_clip(&self) -> u32 { self.n_clip } pub(crate) fn encode_glyph(&mut self, glyph: &GlyphEncoder) { self.tag_stream.extend(&glyph.tag_stream); self.pathseg_stream.extend(&glyph.pathseg_stream); self.drawtag_stream.extend(&glyph.drawtag_stream); self.drawdata_stream.extend(&glyph.drawdata_stream); self.n_path += glyph.n_path; self.n_pathseg += glyph.n_pathseg; } } fn align_up(x: usize, align: usize) -> usize { debug_assert!(align.is_power_of_two()); (x + align - 1) & !(align - 1) } fn padding(x: usize, align: usize) -> usize { x.wrapping_neg() & (align - 1) } impl GlyphEncoder { pub(crate) fn path_encoder(&mut self) -> PathEncoder { PathEncoder::new(&mut self.tag_stream, &mut self.pathseg_stream) } pub(crate) fn finish_path(&mut self, n_pathseg: u32) { self.n_path += 1; self.n_pathseg += n_pathseg; } /// Encode a fill color draw object. /// /// This should be encoded after a path. pub(crate) fn fill_color(&mut self, rgba_color: u32) { self.drawtag_stream.push(DRAWTAG_FILLCOLOR); let element = FillColor { rgba_color }; self.drawdata_stream.extend(bytemuck::bytes_of(&element)); } pub(crate) fn is_color(&self) -> bool { !self.drawtag_stream.is_empty() } }