Benchmark the server-side partitioning

This commit is contained in:
Patrick Walton 2017-09-26 15:38:50 -07:00
parent 7f84e98e6e
commit b6c6c70ef0
8 changed files with 169 additions and 110 deletions

View File

@ -102,6 +102,12 @@ button > svg path {
overflow: scroll; overflow: scroll;
max-height: 75vh; max-height: 75vh;
} }
.pf-benchmark-results-global {
padding: 0.75rem;
}
.pf-benchmark-results-label {
font-weight: bold;
}
/* /*
* Arrow * Arrow

View File

@ -45,6 +45,11 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="pf-benchmark-results-global">
<span class="pf-benchmark-results-label">Partitioning:</span>
&nbsp;
<span id="pf-benchmark-results-partitioning-time">0</span> µs/glyph
</div>
<table class="table table-striped"> <table class="table table-striped">
<thead class="thead-default"> <thead class="thead-default">
<tr><th>Font size (px)</th><th>GPU time per glyph (µs)</th></tr> <tr><th>Font size (px)</th><th>GPU time per glyph (µs)</th></tr>

View File

@ -166,9 +166,9 @@ class ThreeDController extends DemoAppController<ThreeDView> {
this.glyphStorage = new TextFrameGlyphStorage(fileData, textFrames, font); this.glyphStorage = new TextFrameGlyphStorage(fileData, textFrames, font);
this.glyphStorage.layoutRuns(); this.glyphStorage.layoutRuns();
this.glyphStorage.partition().then((baseMeshes: PathfinderMeshData) => { this.glyphStorage.partition().then(result => {
this.baseMeshes = baseMeshes; this.baseMeshes = result.meshes;
this.expandedMeshes = this.glyphStorage.expandMeshes(baseMeshes); this.expandedMeshes = this.glyphStorage.expandMeshes(this.baseMeshes);
this.view.then(view => { this.view.then(view => {
view.uploadPathColors(this.expandedMeshes.length); view.uploadPathColors(this.expandedMeshes.length);
view.uploadPathTransforms(this.expandedMeshes.length); view.uploadPathTransforms(this.expandedMeshes.length);

View File

@ -58,6 +58,9 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
this.resultsTableBody = this.resultsTableBody =
unwrapNull(document.getElementById('pf-benchmark-results-table-body')) as unwrapNull(document.getElementById('pf-benchmark-results-table-body')) as
HTMLTableSectionElement; HTMLTableSectionElement;
this.resultsPartitioningTimeLabel =
unwrapNull(document.getElementById('pf-benchmark-results-partitioning-time')) as
HTMLSpanElement;
const resultsCloseButton = const resultsCloseButton =
unwrapNull(document.getElementById('pf-benchmark-results-close-button')); unwrapNull(document.getElementById('pf-benchmark-results-close-button'));
@ -82,10 +85,18 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
const textFrame = new TextFrame([textRun], font); const textFrame = new TextFrame([textRun], font);
this.glyphStorage = new TextFrameGlyphStorage(fileData, [textFrame], font); this.glyphStorage = new TextFrameGlyphStorage(fileData, [textFrame], font);
this.glyphStorage.partition().then(baseMeshes => { this.glyphStorage.partition().then(result => {
this.baseMeshes = baseMeshes; this.baseMeshes = result.meshes;
const expandedMeshes = this.glyphStorage.expandMeshes(baseMeshes)[0];
const partitionTime = result.time / this.glyphStorage.uniqueGlyphs.length * 1e6;
const timeLabel = this.resultsPartitioningTimeLabel;
while (timeLabel.firstChild != null)
timeLabel.removeChild(timeLabel.firstChild);
timeLabel.appendChild(document.createTextNode("" + partitionTime));
const expandedMeshes = this.glyphStorage.expandMeshes(this.baseMeshes)[0];
this.expandedMeshes = expandedMeshes; this.expandedMeshes = expandedMeshes;
this.view.then(view => { this.view.then(view => {
view.uploadPathColors(1); view.uploadPathColors(1);
view.uploadPathTransforms(1); view.uploadPathTransforms(1);
@ -149,6 +160,7 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
private resultsModal: HTMLDivElement; private resultsModal: HTMLDivElement;
private resultsTableBody: HTMLTableSectionElement; private resultsTableBody: HTMLTableSectionElement;
private resultsPartitioningTimeLabel: HTMLSpanElement;
private glyphStorage: TextFrameGlyphStorage<BenchmarkGlyph>; private glyphStorage: TextFrameGlyphStorage<BenchmarkGlyph>;
private baseMeshes: PathfinderMeshData; private baseMeshes: PathfinderMeshData;
@ -156,6 +168,7 @@ class BenchmarkAppController extends DemoAppController<BenchmarkTestView> {
private pixelsPerEm: number; private pixelsPerEm: number;
private elapsedTimes: ElapsedTime[]; private elapsedTimes: ElapsedTime[];
private partitionTime: number;
font: opentype.Font | null; font: opentype.Font | null;
textRun: TextRun<BenchmarkGlyph> | null; textRun: TextRun<BenchmarkGlyph> | null;

View File

@ -156,7 +156,7 @@ class MeshDebuggerAppController extends AppController {
const glyph = new MeshDebuggerGlyph(opentypeGlyph); const glyph = new MeshDebuggerGlyph(opentypeGlyph);
const glyphStorage = new GlyphStorage(this.fileData, [glyph], this.file); const glyphStorage = new GlyphStorage(this.fileData, [glyph], this.file);
promise = glyphStorage.partition(); promise = glyphStorage.partition().then(result => result.meshes);
} else if (this.file instanceof SVGLoader) { } else if (this.file instanceof SVGLoader) {
promise = this.file.partition(this.fontPathSelect.selectedIndex); promise = this.file.partition(this.fontPathSelect.selectedIndex);
} else { } else {

View File

@ -176,10 +176,10 @@ class TextDemoController extends DemoAppController<TextDemoView> {
const newLayout = new SimpleTextLayout(fileData, const newLayout = new SimpleTextLayout(fileData,
this.text, this.text,
glyph => new GlyphInstance(glyph)); glyph => new GlyphInstance(glyph));
newLayout.glyphStorage.partition().then((meshes: PathfinderMeshData) => { newLayout.glyphStorage.partition().then(result => {
this.view.then(view => { this.view.then(view => {
this.layout = newLayout; this.layout = newLayout;
this.meshes = meshes; this.meshes = result.meshes;
view.attachText(); view.attachText();
view.uploadPathColors(1); view.uploadPathColors(1);

View File

@ -25,6 +25,11 @@ export interface ExpandedMeshData {
meshes: PathfinderMeshData; meshes: PathfinderMeshData;
} }
export interface PartitionResult {
meshes: PathfinderMeshData,
time: number,
}
type CreateGlyphFn<Glyph> = (glyph: opentype.Glyph) => Glyph; type CreateGlyphFn<Glyph> = (glyph: opentype.Glyph) => Glyph;
export interface PixelMetrics { export interface PixelMetrics {
@ -250,7 +255,7 @@ export class GlyphStorage<Glyph extends PathfinderGlyph> {
this.uniqueGlyphs = _.sortedUniqBy(this.uniqueGlyphs, glyph => glyph.index); this.uniqueGlyphs = _.sortedUniqBy(this.uniqueGlyphs, glyph => glyph.index);
} }
partition(): Promise<PathfinderMeshData> { partition(): Promise<PartitionResult> {
// Build the partitioning request to the server. // Build the partitioning request to the server.
// //
// FIXME(pcwalton): If this is a builtin font, don't resend it to the server! // FIXME(pcwalton): If this is a builtin font, don't resend it to the server!
@ -278,7 +283,10 @@ export class GlyphStorage<Glyph extends PathfinderGlyph> {
const response = JSON.parse(responseText); const response = JSON.parse(responseText);
if (!('Ok' in response)) if (!('Ok' in response))
panic("Failed to partition the font!"); panic("Failed to partition the font!");
return new PathfinderMeshData(response.Ok.pathData); return {
meshes: new PathfinderMeshData(response.Ok.pathData),
time: response.Ok.time,
};
}); });
} }

View File

@ -39,6 +39,7 @@ use std::fs::File;
use std::io::{self, Read}; use std::io::{self, Read};
use std::mem; use std::mem;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use std::u32; use std::u32;
static STATIC_INDEX_PATH: &'static str = "../client/index.html"; static STATIC_INDEX_PATH: &'static str = "../client/index.html";
@ -152,6 +153,7 @@ struct PartitionFontResponse {
glyph_info: Vec<PartitionGlyphInfo>, glyph_info: Vec<PartitionGlyphInfo>,
#[serde(rename = "pathData")] #[serde(rename = "pathData")]
path_data: PartitionEncodedPathData, path_data: PartitionEncodedPathData,
time: f64,
} }
#[derive(Clone, Copy, Serialize, Deserialize)] #[derive(Clone, Copy, Serialize, Deserialize)]
@ -255,108 +257,126 @@ struct PartitionSvgPathsResponse {
path_data: PartitionEncodedPathData, path_data: PartitionEncodedPathData,
} }
fn partition_paths(partitioner: &mut Partitioner, subpath_indices: &[SubpathRange]) struct PathPartitioningResult {
-> (PartitionEncodedPathData, Vec<PartitionPathIndices>) { encoded_data: PartitionEncodedPathData,
let (mut b_quads, mut b_vertex_positions) = (vec![], vec![]); indices: Vec<PartitionPathIndices>,
let (mut b_vertex_path_ids, mut b_vertex_loop_blinn_data) = (vec![], vec![]); time: Duration,
let (mut cover_interior_indices, mut cover_curve_indices) = (vec![], vec![]); }
let (mut edge_upper_line_indices, mut edge_upper_curve_indices) = (vec![], vec![]);
let (mut edge_lower_line_indices, mut edge_lower_curve_indices) = (vec![], vec![]);
let mut path_indices = vec![]; impl PathPartitioningResult {
fn compute(partitioner: &mut Partitioner, subpath_indices: &[SubpathRange])
-> PathPartitioningResult {
let timestamp_before = Instant::now();
for (path_index, subpath_range) in subpath_indices.iter().enumerate() { let (mut b_quads, mut b_vertex_positions) = (vec![], vec![]);
partitioner.partition((path_index + 1) as u16, subpath_range.start, subpath_range.end); let (mut b_vertex_path_ids, mut b_vertex_loop_blinn_data) = (vec![], vec![]);
let (mut cover_interior_indices, mut cover_curve_indices) = (vec![], vec![]);
let (mut edge_upper_line_indices, mut edge_upper_curve_indices) = (vec![], vec![]);
let (mut edge_lower_line_indices, mut edge_lower_curve_indices) = (vec![], vec![]);
let path_b_vertex_positions = partitioner.b_vertex_positions(); let mut path_indices = vec![];
let path_b_vertex_path_ids = partitioner.b_vertex_path_ids();
let path_b_vertex_loop_blinn_data = partitioner.b_vertex_loop_blinn_data();
let cover_indices = partitioner.cover_indices();
let edge_indices = partitioner.edge_indices();
let positions_start = IndexRange::from_data(&mut b_vertex_positions, for (path_index, subpath_range) in subpath_indices.iter().enumerate() {
path_b_vertex_positions).unwrap().start as u32; partitioner.partition((path_index + 1) as u16, subpath_range.start, subpath_range.end);
IndexRange::from_data(&mut b_vertex_path_ids, path_b_vertex_path_ids).unwrap();
let mut path_b_quads = partitioner.b_quads().to_vec(); let path_b_vertex_positions = partitioner.b_vertex_positions();
let mut path_cover_interior_indices = cover_indices.interior_indices.to_vec(); let path_b_vertex_path_ids = partitioner.b_vertex_path_ids();
let mut path_cover_curve_indices = cover_indices.curve_indices.to_vec(); let path_b_vertex_loop_blinn_data = partitioner.b_vertex_loop_blinn_data();
let mut path_edge_upper_line_indices = edge_indices.upper_line_indices.to_vec(); let cover_indices = partitioner.cover_indices();
let mut path_edge_upper_curve_indices = edge_indices.upper_curve_indices.to_vec(); let edge_indices = partitioner.edge_indices();
let mut path_edge_lower_line_indices = edge_indices.lower_line_indices.to_vec();
let mut path_edge_lower_curve_indices = edge_indices.lower_curve_indices.to_vec();
for path_b_quad in &mut path_b_quads { let positions_start =
path_b_quad.offset(positions_start); IndexRange::from_data(&mut b_vertex_positions,
} path_b_vertex_positions).unwrap().start as u32;
for path_cover_interior_index in &mut path_cover_interior_indices { IndexRange::from_data(&mut b_vertex_path_ids, path_b_vertex_path_ids).unwrap();
*path_cover_interior_index += positions_start
} let mut path_b_quads = partitioner.b_quads().to_vec();
for path_cover_curve_index in &mut path_cover_curve_indices { let mut path_cover_interior_indices = cover_indices.interior_indices.to_vec();
*path_cover_curve_index += positions_start let mut path_cover_curve_indices = cover_indices.curve_indices.to_vec();
} let mut path_edge_upper_line_indices = edge_indices.upper_line_indices.to_vec();
for path_edge_upper_line_indices in &mut path_edge_upper_line_indices { let mut path_edge_upper_curve_indices = edge_indices.upper_curve_indices.to_vec();
path_edge_upper_line_indices.offset(positions_start); let mut path_edge_lower_line_indices = edge_indices.lower_line_indices.to_vec();
} let mut path_edge_lower_curve_indices = edge_indices.lower_curve_indices.to_vec();
for path_edge_upper_curve_indices in &mut path_edge_upper_curve_indices {
path_edge_upper_curve_indices.offset(positions_start); for path_b_quad in &mut path_b_quads {
} path_b_quad.offset(positions_start);
for path_edge_lower_line_indices in &mut path_edge_lower_line_indices { }
path_edge_lower_line_indices.offset(positions_start); for path_cover_interior_index in &mut path_cover_interior_indices {
} *path_cover_interior_index += positions_start
for path_edge_lower_curve_indices in &mut path_edge_lower_curve_indices { }
path_edge_lower_curve_indices.offset(positions_start); for path_cover_curve_index in &mut path_cover_curve_indices {
*path_cover_curve_index += positions_start
}
for path_edge_upper_line_indices in &mut path_edge_upper_line_indices {
path_edge_upper_line_indices.offset(positions_start);
}
for path_edge_upper_curve_indices in &mut path_edge_upper_curve_indices {
path_edge_upper_curve_indices.offset(positions_start);
}
for path_edge_lower_line_indices in &mut path_edge_lower_line_indices {
path_edge_lower_line_indices.offset(positions_start);
}
for path_edge_lower_curve_indices in &mut path_edge_lower_curve_indices {
path_edge_lower_curve_indices.offset(positions_start);
}
path_indices.push(PartitionPathIndices {
b_quad_indices: IndexRange::from_data(&mut b_quads, &path_b_quads).unwrap(),
b_vertex_indices: IndexRange::from_data(&mut b_vertex_loop_blinn_data,
path_b_vertex_loop_blinn_data).unwrap(),
cover_interior_indices: IndexRange::from_data(&mut cover_interior_indices,
&path_cover_interior_indices).unwrap(),
cover_curve_indices: IndexRange::from_data(&mut cover_curve_indices,
&path_cover_curve_indices).unwrap(),
edge_upper_line_indices:
IndexRange::from_data(&mut edge_upper_line_indices,
&path_edge_upper_line_indices).unwrap(),
edge_upper_curve_indices:
IndexRange::from_data(&mut edge_upper_curve_indices,
&path_edge_upper_curve_indices).unwrap(),
edge_lower_line_indices: IndexRange::from_data(&mut edge_lower_line_indices,
&path_edge_lower_line_indices).unwrap(),
edge_lower_curve_indices:
IndexRange::from_data(&mut edge_lower_curve_indices,
&path_edge_lower_curve_indices).unwrap(),
})
} }
path_indices.push(PartitionPathIndices { // Reverse interior indices for early Z optimizations.
b_quad_indices: IndexRange::from_data(&mut b_quads, &path_b_quads).unwrap(), let mut new_cover_interior_indices = Vec::with_capacity(cover_interior_indices.len());
b_vertex_indices: IndexRange::from_data(&mut b_vertex_loop_blinn_data, for path_indices in path_indices.iter_mut().rev() {
path_b_vertex_loop_blinn_data).unwrap(), let old_byte_start = path_indices.cover_interior_indices.start * mem::size_of::<u32>();
cover_interior_indices: IndexRange::from_data(&mut cover_interior_indices, let old_byte_end = path_indices.cover_interior_indices.end * mem::size_of::<u32>();
&path_cover_interior_indices).unwrap(), let new_start_index = new_cover_interior_indices.len() / mem::size_of::<u32>();
cover_curve_indices: IndexRange::from_data(&mut cover_curve_indices, new_cover_interior_indices.extend(
&path_cover_curve_indices).unwrap(), cover_interior_indices[old_byte_start..old_byte_end].into_iter());
edge_upper_line_indices: IndexRange::from_data(&mut edge_upper_line_indices, let new_end_index = new_cover_interior_indices.len() / mem::size_of::<u32>();
&path_edge_upper_line_indices).unwrap(), path_indices.cover_interior_indices.start = new_start_index;
edge_upper_curve_indices: path_indices.cover_interior_indices.end = new_end_index;
IndexRange::from_data(&mut edge_upper_curve_indices, }
&path_edge_upper_curve_indices).unwrap(), cover_interior_indices = new_cover_interior_indices;
edge_lower_line_indices: IndexRange::from_data(&mut edge_lower_line_indices,
&path_edge_lower_line_indices).unwrap(), let time_elapsed = timestamp_before.elapsed();
edge_lower_curve_indices:
IndexRange::from_data(&mut edge_lower_curve_indices, let encoded_path_data = PartitionEncodedPathData {
&path_edge_lower_curve_indices).unwrap(), b_quads: base64::encode(&b_quads),
}) b_vertex_positions: base64::encode(&b_vertex_positions),
b_vertex_path_ids: base64::encode(&b_vertex_path_ids),
b_vertex_loop_blinn_data: base64::encode(&b_vertex_loop_blinn_data),
cover_interior_indices: base64::encode(&cover_interior_indices),
cover_curve_indices: base64::encode(&cover_curve_indices),
edge_upper_line_indices: base64::encode(&edge_upper_line_indices),
edge_upper_curve_indices: base64::encode(&edge_upper_curve_indices),
edge_lower_line_indices: base64::encode(&edge_lower_line_indices),
edge_lower_curve_indices: base64::encode(&edge_lower_curve_indices),
};
PathPartitioningResult {
encoded_data: encoded_path_data,
indices: path_indices,
time: time_elapsed,
}
} }
// Reverse interior indices for early Z optimizations.
let mut new_cover_interior_indices = Vec::with_capacity(cover_interior_indices.len());
for path_indices in path_indices.iter_mut().rev() {
let old_byte_start = path_indices.cover_interior_indices.start * mem::size_of::<u32>();
let old_byte_end = path_indices.cover_interior_indices.end * mem::size_of::<u32>();
let new_start_index = new_cover_interior_indices.len() / mem::size_of::<u32>();
new_cover_interior_indices.extend(
cover_interior_indices[old_byte_start..old_byte_end].into_iter());
let new_end_index = new_cover_interior_indices.len() / mem::size_of::<u32>();
path_indices.cover_interior_indices.start = new_start_index;
path_indices.cover_interior_indices.end = new_end_index;
}
cover_interior_indices = new_cover_interior_indices;
let encoded_path_data = PartitionEncodedPathData {
b_quads: base64::encode(&b_quads),
b_vertex_positions: base64::encode(&b_vertex_positions),
b_vertex_path_ids: base64::encode(&b_vertex_path_ids),
b_vertex_loop_blinn_data: base64::encode(&b_vertex_loop_blinn_data),
cover_interior_indices: base64::encode(&cover_interior_indices),
cover_curve_indices: base64::encode(&cover_curve_indices),
edge_upper_line_indices: base64::encode(&edge_upper_line_indices),
edge_upper_curve_indices: base64::encode(&edge_upper_curve_indices),
edge_lower_line_indices: base64::encode(&edge_lower_line_indices),
edge_lower_curve_indices: base64::encode(&edge_lower_curve_indices),
};
(encoded_path_data, path_indices)
} }
#[post("/partition-font", format = "application/json", data = "<request>")] #[post("/partition-font", format = "application/json", data = "<request>")]
@ -428,11 +448,14 @@ fn partition_font(request: Json<PartitionFontRequest>)
// Partition the decoded glyph outlines. // Partition the decoded glyph outlines.
let mut partitioner = Partitioner::new(); let mut partitioner = Partitioner::new();
partitioner.init_with_path_buffer(&path_buffer); partitioner.init_with_path_buffer(&path_buffer);
let (encoded_path_data, path_indices) = partition_paths(&mut partitioner, &subpath_indices); let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner,
&subpath_indices);
// Package up other miscellaneous glyph info. // Package up other miscellaneous glyph info.
let mut glyph_info = vec![]; let mut glyph_info = vec![];
for (glyph, glyph_path_indices) in request.glyphs.iter().zip(path_indices.iter()) { for (glyph, glyph_path_indices) in request.glyphs
.iter()
.zip(path_partitioning_result.indices.iter()) {
let glyph_key = GlyphKey::new(glyph.id); let glyph_key = GlyphKey::new(glyph.id);
let dimensions = match font_context.glyph_dimensions(&font_instance_key, &glyph_key) { let dimensions = match font_context.glyph_dimensions(&font_instance_key, &glyph_key) {
@ -453,10 +476,14 @@ fn partition_font(request: Json<PartitionFontRequest>)
}) })
} }
let time = path_partitioning_result.time.as_secs() as f64 +
path_partitioning_result.time.subsec_nanos() as f64 * 1e-9;
// Return the response. // Return the response.
Json(Ok(PartitionFontResponse { Json(Ok(PartitionFontResponse {
glyph_info: glyph_info, glyph_info: glyph_info,
path_data: encoded_path_data, path_data: path_partitioning_result.encoded_data,
time: time,
})) }))
} }
@ -531,12 +558,12 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
// Partition the paths. // Partition the paths.
let mut partitioner = Partitioner::new(); let mut partitioner = Partitioner::new();
partitioner.init_with_path_buffer(&path_buffer); partitioner.init_with_path_buffer(&path_buffer);
let (encoded_path_data, path_indices) = partition_paths(&mut partitioner, &paths); let path_partitioning_result = PathPartitioningResult::compute(&mut partitioner, &paths);
// Return the response. // Return the response.
Json(Ok(PartitionSvgPathsResponse { Json(Ok(PartitionSvgPathsResponse {
path_indices: path_indices, path_indices: path_partitioning_result.indices,
path_data: encoded_path_data, path_data: path_partitioning_result.encoded_data,
})) }))
} }