diff --git a/demo2/cover.vs.glsl b/demo2/cover.vs.glsl index 5f51da53..5fd9db6f 100644 --- a/demo2/cover.vs.glsl +++ b/demo2/cover.vs.glsl @@ -15,6 +15,7 @@ precision highp float; uniform vec2 uFramebufferSize; uniform vec2 uTileSize; uniform vec2 uStencilTextureSize; +uniform vec2 uViewBoxOrigin; in vec2 aTessCoord; in vec2 aTileOrigin; @@ -33,7 +34,7 @@ vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) { void main() { uint tileIndex = uint(gl_InstanceID); - vec2 position = aTileOrigin + uTileSize * aTessCoord; + vec2 position = (aTileOrigin + aTessCoord) * uTileSize + uViewBoxOrigin; vec2 texCoord = computeTileOffset(tileIndex, uStencilTextureSize.x) + aTessCoord * uTileSize; vTexCoord = texCoord / uStencilTextureSize; vBackdrop = aBackdrop; diff --git a/demo2/geometry.ts b/demo2/geometry.ts index d8c8fcc0..6edfb601 100644 --- a/demo2/geometry.ts +++ b/demo2/geometry.ts @@ -54,9 +54,15 @@ export class Point2D { } } -export interface Size2D { +export class Size2D { width: number; height: number; + + constructor(width: number, height: number) { + this.width = width; + this.height = height; + Object.freeze(this); + } } export class Rect { diff --git a/demo2/opaque.vs.glsl b/demo2/opaque.vs.glsl index 08e4f796..a7227b0c 100644 --- a/demo2/opaque.vs.glsl +++ b/demo2/opaque.vs.glsl @@ -14,6 +14,7 @@ precision highp float; uniform vec2 uFramebufferSize; uniform vec2 uTileSize; +uniform vec2 uViewBoxOrigin; in vec2 aTessCoord; in vec2 aTileOrigin; @@ -22,7 +23,7 @@ in vec4 aColor; out vec4 vColor; void main() { - vec2 position = aTileOrigin + uTileSize * aTessCoord; + vec2 position = (aTileOrigin + aTessCoord) * uTileSize + uViewBoxOrigin; vColor = aColor; gl_Position = vec4((position / uFramebufferSize * 2.0 - 1.0) * vec2(1.0, -1.0), 0.0, 1.0); } diff --git a/demo2/pathfinder.ts b/demo2/pathfinder.ts index df5c847e..d919aefc 100644 --- a/demo2/pathfinder.ts +++ b/demo2/pathfinder.ts @@ -15,7 +15,7 @@ import OPAQUE_FRAGMENT_SHADER_SOURCE from "./opaque.fs.glsl"; import STENCIL_VERTEX_SHADER_SOURCE from "./stencil.vs.glsl"; import STENCIL_FRAGMENT_SHADER_SOURCE from "./stencil.fs.glsl"; import AREA_LUT from "../resources/textures/area-lut.png"; -import {Matrix2D, Size2D} from "./geometry"; +import {Matrix2D, Size2D, Rect, Point2D} from "./geometry"; import {SVGPath, TILE_SIZE} from "./tiling"; import {staticCast, unwrapNull} from "./util"; @@ -34,8 +34,8 @@ const QUAD_VERTEX_POSITIONS: Uint8Array = new Uint8Array([ ]); const FILL_INSTANCE_SIZE: number = 20; -const SOLID_TILE_INSTANCE_SIZE: number = 12; -const MASK_TILE_INSTANCE_SIZE: number = 16; +const SOLID_TILE_INSTANCE_SIZE: number = 8; +const MASK_TILE_INSTANCE_SIZE: number = 12; interface Color { r: number; @@ -58,10 +58,11 @@ class App { private stencilFramebuffer: WebGLFramebuffer; private fillProgram: Program<'FramebufferSize' | 'TileSize' | 'AreaLUT', 'TessCoord' | 'From' | 'To' | 'TileIndex'>; - private solidTileProgram: Program<'FramebufferSize' | 'TileSize', + private solidTileProgram: Program<'FramebufferSize' | 'TileSize' | 'ViewBoxOrigin', 'TessCoord' | 'TileOrigin' | 'Color'>; private maskTileProgram: - Program<'FramebufferSize' | 'TileSize' | 'StencilTexture' | 'StencilTextureSize', + Program<'FramebufferSize' | 'TileSize' | 'StencilTexture' | 'StencilTextureSize' | + 'ViewBoxOrigin', 'TessCoord' | 'TileOrigin' | 'Backdrop' | 'Color'>; private quadVertexBuffer: WebGLBuffer; private fillVertexBuffer: WebGLBuffer; @@ -71,6 +72,8 @@ class App { private maskTileVertexBuffer: WebGLBuffer; private maskVertexArray: WebGLVertexArrayObject; + private viewBox: Rect; + private fillPrimitiveCount: number; private solidTileCount: number; private maskTileCount: number; @@ -136,7 +139,8 @@ class App { 'FramebufferSize', 'TileSize', 'StencilTexture', - 'StencilTextureSize' + 'StencilTextureSize', + 'ViewBoxOrigin', ], [ 'TessCoord', @@ -150,7 +154,7 @@ class App { const solidTileProgram = new Program(gl, OPAQUE_VERTEX_SHADER_SOURCE, OPAQUE_FRAGMENT_SHADER_SOURCE, - ['FramebufferSize', 'TileSize'], + ['FramebufferSize', 'TileSize', 'ViewBoxOrigin'], ['TessCoord', 'TileOrigin', 'Color']); this.solidTileProgram = solidTileProgram; @@ -224,7 +228,7 @@ class App { gl.bindBuffer(gl.ARRAY_BUFFER, this.solidTileVertexBuffer); gl.vertexAttribPointer(solidTileProgram.attributes.TileOrigin, 2, - gl.FLOAT, + gl.SHORT, false, SOLID_TILE_INSTANCE_SIZE, 0); @@ -234,7 +238,7 @@ class App { gl.UNSIGNED_BYTE, true, SOLID_TILE_INSTANCE_SIZE, - 8); + 4); gl.vertexAttribDivisor(solidTileProgram.attributes.Color, 1); gl.enableVertexAttribArray(solidTileProgram.attributes.TessCoord); gl.enableVertexAttribArray(solidTileProgram.attributes.TileOrigin); @@ -254,7 +258,7 @@ class App { gl.bindBuffer(gl.ARRAY_BUFFER, this.maskTileVertexBuffer); gl.vertexAttribPointer(maskTileProgram.attributes.TileOrigin, 2, - gl.FLOAT, + gl.SHORT, false, MASK_TILE_INSTANCE_SIZE, 0); @@ -264,20 +268,22 @@ class App { gl.FLOAT, false, MASK_TILE_INSTANCE_SIZE, - 8); + 4); gl.vertexAttribDivisor(maskTileProgram.attributes.Backdrop, 1); gl.vertexAttribPointer(maskTileProgram.attributes.Color, 4, gl.UNSIGNED_BYTE, true, MASK_TILE_INSTANCE_SIZE, - 12); + 8); gl.vertexAttribDivisor(maskTileProgram.attributes.Color, 1); gl.enableVertexAttribArray(maskTileProgram.attributes.TessCoord); gl.enableVertexAttribArray(maskTileProgram.attributes.TileOrigin); gl.enableVertexAttribArray(maskTileProgram.attributes.Backdrop); gl.enableVertexAttribArray(maskTileProgram.attributes.Color); + this.viewBox = new Rect(new Point2D(0.0, 0.0), new Size2D(0.0, 0.0)); + // Set up event handlers. this.canvas.addEventListener('click', event => this.onClick(event), false); @@ -289,6 +295,8 @@ class App { redraw(): void { const gl = this.gl, canvas = this.canvas; + console.log("viewBox", this.viewBox); + // Start timer. let timerQuery = null; if (this.disjointTimerQueryExt != null) { @@ -333,6 +341,9 @@ class App { framebufferSize.width, framebufferSize.height); gl.uniform2f(this.solidTileProgram.uniforms.TileSize, TILE_SIZE.width, TILE_SIZE.height); + gl.uniform2f(this.solidTileProgram.uniforms.ViewBoxOrigin, + this.viewBox.origin.x, + this.viewBox.origin.y); gl.disable(gl.BLEND); gl.drawArraysInstanced(gl.TRIANGLE_FAN, 0, 4, this.solidTileCount); @@ -349,6 +360,9 @@ class App { gl.uniform2f(this.maskTileProgram.uniforms.StencilTextureSize, STENCIL_FRAMEBUFFER_SIZE.width, STENCIL_FRAMEBUFFER_SIZE.height); + gl.uniform2f(this.maskTileProgram.uniforms.ViewBoxOrigin, + this.viewBox.origin.x, + this.viewBox.origin.y); gl.blendEquation(gl.FUNC_ADD); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); gl.enable(gl.BLEND); @@ -402,11 +416,20 @@ class App { const arrayBuffer = staticCast(reader.result, ArrayBuffer); const root = new RIFFChunk(new DataView(arrayBuffer)); for (const subchunk of root.subchunks()) { + const id = subchunk.stringID(); + if (id === 'head') { + const headerData = subchunk.contents(); + this.viewBox = new Rect(new Point2D(headerData.getFloat32(0, true), + headerData.getFloat32(4, true)), + new Size2D(headerData.getFloat32(8, true), + headerData.getFloat32(12, true))); + continue; + } + let bindPoint, buffer; let countFieldName: 'fillPrimitiveCount' | 'totalTileCount' | 'solidTileCount' | 'maskTileCount'; let instanceSize; - const id = subchunk.stringID(); switch (id) { case 'fill': bindPoint = gl.ARRAY_BUFFER; diff --git a/utils/tile-svg/src/main.rs b/utils/tile-svg/src/main.rs index a0f97e51..9f1b0f7d 100644 --- a/utils/tile-svg/src/main.rs +++ b/utils/tile-svg/src/main.rs @@ -74,7 +74,7 @@ fn main() { println!("Scene bounds: {:?}", scene.bounds); let start_time = Instant::now(); - let mut built_scene = BuiltScene::new(); + let mut built_scene = BuiltScene::new(&scene.view_box); for _ in 0..runs { built_scene = scene.build(); } @@ -111,7 +111,7 @@ struct Scene { objects: Vec, styles: Vec, bounds: Rect, - view_box: Option>, + view_box: Rect, } #[derive(Debug)] @@ -154,7 +154,7 @@ struct StyleId(u32); impl Scene { fn new() -> Scene { - Scene { objects: vec![], styles: vec![], bounds: Rect::zero(), view_box: None } + Scene { objects: vec![], styles: vec![], bounds: Rect::zero(), view_box: Rect::zero() } } fn from_path(path: &Path) -> Scene { @@ -213,7 +213,7 @@ impl Scene { elements.next().unwrap()), Size2D::new(elements.next().unwrap(), elements.next().unwrap())); - scene.view_box = Some(global_transform.transform_rect(&view_box)); + scene.view_box = global_transform.transform_rect(&view_box); } } } @@ -311,7 +311,7 @@ impl Scene { } fn build(&self) -> BuiltScene { - let mut built_scene = BuiltScene::new(); + let mut built_scene = BuiltScene::new(&self.view_box); for object in &self.objects { let mut tiler = Tiler::from_outline(&object.outline, object.color, @@ -935,7 +935,7 @@ struct Tiler<'o, 'p> { fill_color: ColorU, built_scene: &'p mut BuiltScene, - view_box: Option>, + view_box: Rect, point_queue: SortedVector, active_edges: SortedVector, @@ -948,7 +948,7 @@ struct Tiler<'o, 'p> { impl<'o, 'p> Tiler<'o, 'p> { fn from_outline(outline: &'o Outline, fill_color: ColorU, - view_box: &Option>, + view_box: &Rect, built_scene: &'p mut BuiltScene) -> Tiler<'o, 'p> { Tiler { @@ -974,13 +974,11 @@ impl<'o, 'p> Tiler<'o, 'p> { // Clip to the view box. let mut bounds = self.outline.bounds; - if let Some(view_box) = self.view_box { - let max_x = f32::min(view_box.max_x(), bounds.max_x()); - let max_y = f32::min(view_box.max_y(), bounds.max_y()); - bounds.origin.x = f32::max(view_box.origin.x, bounds.origin.x); - bounds.size.width = f32::max(0.0, max_x - bounds.origin.x); - bounds.size.height = f32::max(0.0, max_y - bounds.origin.y); - } + let max_x = f32::min(self.view_box.max_x(), bounds.max_x()); + let max_y = f32::min(self.view_box.max_y(), bounds.max_y()); + bounds.origin.x = f32::max(self.view_box.origin.x, bounds.origin.x); + bounds.size.width = f32::max(0.0, max_x - bounds.origin.x); + bounds.size.height = f32::max(0.0, max_y - bounds.origin.y); self.active_edges.clear(); @@ -991,11 +989,8 @@ impl<'o, 'p> Tiler<'o, 'p> { let tiles_across = ((strip_right_extent - strip_origin.x) / TILE_WIDTH) as usize; - let view_box_origin_y = match self.view_box { - Some(view_box) => view_box.origin.y, - None => 0.0, - }; - let mut tile_index_y = (f32::floor(view_box_origin_y / TILE_HEIGHT) * TILE_HEIGHT) as i16; + let mut tile_index_y = (f32::floor(self.view_box.origin.y / TILE_HEIGHT) * TILE_HEIGHT) + as i16; self.strip_tiles.clear(); self.strip_tiles.reserve(tiles_across); @@ -1124,6 +1119,7 @@ impl<'o, 'p> Tiler<'o, 'p> { let contour = &outline.contours[point_index.contour_index]; + // TODO(pcwalton): Could use a bitset of processed edges… let prev_endpoint_index = contour.prev_endpoint_index_of(point_index.point_index); let next_endpoint_index = contour.next_endpoint_index_of(point_index.point_index); if contour.point_is_logically_above(point_index.point_index, prev_endpoint_index) { @@ -1166,7 +1162,6 @@ impl<'o, 'p> Tiler<'o, 'p> { #[inline(never)] fn flush_tiles(&mut self, tile_index_y: i16) { // Flush tiles. - let first_tile_index = self.built_scene.mask_tiles.len() as u32; for (tile_index_x, tile) in self.strip_tiles.iter().enumerate() { if self.used_strip_tiles.contains(tile_index_x) { self.built_scene.mask_tiles.push(*tile); @@ -1286,6 +1281,7 @@ fn process_active_edge(active_edge: &mut Segment, #[derive(Debug)] struct BuiltScene { + view_box: Rect, fills: Vec, solid_tiles: Vec, mask_tiles: Vec, @@ -1322,23 +1318,32 @@ struct ColorU { } impl BuiltScene { - fn new() -> BuiltScene { - BuiltScene { fills: vec![], solid_tiles: vec![], mask_tiles: vec![] } + fn new(view_box: &Rect) -> BuiltScene { + BuiltScene { view_box: *view_box, fills: vec![], solid_tiles: vec![], mask_tiles: vec![] } } fn write(&self, writer: &mut W) -> io::Result<()> where W: Write { writer.write_all(b"RIFF")?; + let header_size = 4 * 4; let fill_size = self.fills.len() * mem::size_of::(); let solid_tiles_size = self.solid_tiles.len() * mem::size_of::(); let mask_tiles_size = self.mask_tiles.len() * mem::size_of::(); writer.write_u32::((4 + + 8 + header_size + 8 + fill_size + 8 + solid_tiles_size + 8 + mask_tiles_size) as u32)?; writer.write_all(b"PF3S")?; + writer.write_all(b"head")?; + writer.write_u32::(header_size as u32)?; + writer.write_f32::(self.view_box.origin.x)?; + writer.write_f32::(self.view_box.origin.y)?; + writer.write_f32::(self.view_box.size.width)?; + writer.write_f32::(self.view_box.size.height)?; + writer.write_all(b"fill")?; writer.write_u32::(fill_size as u32)?; for fill_primitive in &self.fills {