diff --git a/test/bench/REPORTS/WIP_REPORT_TYPOGRAPHY_TEST.md b/test/bench/REPORTS/WIP_REPORT_TYPOGRAPHY_TEST.md new file mode 100644 index 0000000000..d13f76edba --- /dev/null +++ b/test/bench/REPORTS/WIP_REPORT_TYPOGRAPHY_TEST.md @@ -0,0 +1,65 @@ +# Typography Benchmarks + +To isolate typography tests, run the following: + +```shell +npx vitest bench test/bench/typography.bench.js --reporter=verbose +``` + +Implementation notes: + +- All pre-existing benchmarks included the full p5 instance setup which adds ~50ms to each test. The `textToPoints() single word, isolated benchmark` test is included as a potential way to isolate performance testing away from setup overhead. +- The `with render` test draws the points, others just run `textToPoints()`. + +## Open tasks + +1. Add benchmarks for the following functions: + + - textToContours() + - textToPaths() + - textToPoints() + - textToContours() + - textToPaths() + - textToModel() + +2. Consider how we might parameterize code re-use accross tests for the 3 renderers. See initial notes in `typography.bench.js`. Without this, tests may begin to drift over time without careful code reviews. +3. Create a custom vitest reporter to help automate updating this document. + +## 2D + +### textToPoints() + + ✓ unit-tests-webgpu (chrome) test/bench/typography.bench.js > Typography: bench 2D 8671ms + name hz min max mean p75 p99 p995 p999 rme samples + · textToPoints() single word 18.9179 51.9000 54.2000 52.8600 53.2000 54.2000 54.2000 54.2000 ±0.49% 20 + · textToPoints() single word, 150pt 18.2715 53.4000 57.1000 54.7300 55.0000 57.1000 57.1000 57.1000 ±0.68% 20 + · textToPoints() single word, isolated benchmark 1,220.78 0.5000 4.4000 0.8191 0.8000 3.0000 3.5000 4.4000 ±4.35% 611 + · textToPoints() single word, with render 18.7161 52.7000 54.4000 53.4300 53.6000 54.4000 54.4000 54.4000 ±0.43% 20 + · textToPoints() 10 words 18.0766 54.8000 56.2000 55.3200 55.5000 56.2000 56.2000 56.2000 ±0.34% 20 + · textToPoints() Paragraph 9.9265 97.8000 112.10 100.74 101.00 112.10 112.10 112.10 ±1.36% 20 + +## WebGL + +### textToPoints() + + ✓ unit-tests-webgpu (chrome) test/bench/typography.bench.js > Typography: bench WebGL 9082ms + name hz min max mean p75 p99 p995 p999 rme samples + · textToPoints() single word 18.9502 52.0000 54.4000 52.7700 53.0000 54.4000 54.4000 54.4000 ±0.52% 20 + · textToPoints() single word, 150pt 18.3469 53.6000 55.7000 54.5050 54.9000 55.7000 55.7000 55.7000 ±0.52% 20 + · textToPoints() single word, isolated benchmark 1,288.71 0.5000 5.8000 0.7760 0.7000 2.6000 4.0000 5.8000 ±4.69% 645 + · textToPoints() single word, with render 14.2867 68.9000 72.1000 69.9950 70.5000 72.1000 72.1000 72.1000 ±0.61% 20 + · textToPoints() 10 words 17.9388 55.0000 56.8000 55.7450 56.0000 56.8000 56.8000 56.8000 ±0.48% 20 + · textToPoints() Paragraph 9.9285 99.1000 102.90 100.72 101.40 102.90 102.90 102.90 ±0.46% 20 + +## WebGPU + +### textToPoints() + + ✓ unit-tests-webgpu (chrome) test/bench/typography.bench.js > Typography: bench WebGPU 8687ms + name hz min max mean p75 p99 p995 p999 rme samples + · textToPoints() single word 18.8840 52.2000 54.0000 52.9550 53.3000 54.0000 54.0000 54.0000 ±0.45% 20 + · textToPoints() single word, 150pt 18.4349 53.5000 55.3000 54.2450 54.6000 55.3000 55.3000 55.3000 ±0.52% 20 + · textToPoints() single word, isolated benchmark 1,248.50 0.6000 6.8000 0.8010 0.7000 2.9000 3.7000 6.8000 ±5.27% 625 + · textToPoints() single word, with render 18.5874 52.8000 55.7000 53.8000 54.1000 55.7000 55.7000 55.7000 ±0.56% 20 + · textToPoints() 10 words 17.9937 55.0000 56.7000 55.5750 55.9000 56.7000 56.7000 56.7000 ±0.44% 20 + · textToPoints() Paragraph 9.8712 99.6000 103.40 101.30 102.00 103.40 103.40 103.40 ±0.46% 20 diff --git a/test/bench/typography.bench.js b/test/bench/typography.bench.js new file mode 100644 index 0000000000..31c0d49a86 --- /dev/null +++ b/test/bench/typography.bench.js @@ -0,0 +1,385 @@ +import { bench, describe } from "vitest"; +import p5 from "../../src/app"; +import { WEBGL, WEBGPU } from "../../src/core/constants"; + +const fontFile = "../../test/manual-test-examples/type/font/LiberationSans-Bold.ttf"; + +const strs = { + single: "Performance", + ten: "Performance testing 10 words at a time is exhaustive! Right?", + paragraph: Array.from({ length: 10 }, (_, i) => + `${i === 0 ? "\t": ""}Performance is vital in all aspects of text rendering, even 10 lines at a time. This is line ${i + 1} of 10.` + ).join("\n") // This will hit around 15fps, 21275 points +}; + +// TODO: We might consider parameterizing tests since they mainly vary by renderer. +// For example, `TO_POINTS` each function can have an array of test parameters. +const TO_POINTS = { + single: {str: "Performance", size: 20, sf: 0.5, points: 317, variance: 5} +}; + +// TODO: Alternatively, we can create an array of test bodies which take a p5 instance which has already run _setup() +const TO_POINTS_FNS = { + single: { + label: "textToPoints() single word", + fn: (myp5, font) => { + myp5.textSize(20); + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 317, 5); + myp5.remove(); + } + } +}; + +async function _setup(w = 400, h = 400, renderer = undefined) { + var myp5; + var font; + new p5(function (p) { + p.setup = async function () { + myp5 = p; + font = await p.loadFont(fontFile); + p.createCanvas(w, h, renderer); + }; + }); + await vi.waitFor(() => { + if (myp5 === undefined) throw new Error("not ready"); + }); + return { myp5, font }; +} + +function drawPoints(myp5, points) { + for (let point of points) { + myp5.point(point.x, point.y); + } +} + +const options = { iterations: 20, time: 500 }; + +describe("Typography: bench 2D", function() { + var myp5, font; + + bench( + "textToPoints() single word", + async () => { + try { + const { myp5, font } = await _setup(); + myp5.textSize(20); + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 317, 5); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + bench( + "textToPoints() single word, 150pt", + async () => { + try { + const { myp5, font } = await _setup(); + myp5.textSize(150); + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 2336, 50); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + // TODO: This is an example of using setup() to isolate benchmarks away from p5 instance setup code + // which adds ~50ms universally to all benchmarks. + bench( + "textToPoints() single word, isolated benchmark", + async () => { + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 317, 5); + }, + { + ...options, + setup: async () => { + ({ myp5, font } = await _setup()); + myp5.textSize(20); + }, + teardown: () => myp5.remove() + } + ); + + bench( + "textToPoints() single word, with render", + async () => { + try { + const { myp5, font } = await _setup(); + myp5.textSize(20); + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 317, 5); + drawPoints(myp5, points); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + bench( + "textToPoints() 10 words", + async () => { + try { + const { myp5, font } = await _setup(); + myp5.textSize(20); + const points = font.textToPoints(strs.ten, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 1453, 5); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + bench( + "textToPoints() Paragraph", + async () => { + try { + const { myp5, font } = await _setup(); + myp5.textSize(20); + const points = font.textToPoints(strs.paragraph, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 21275, 50); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + // TODO: textToContours() + // TODO: textToPaths() + +}); + +describe("Typography: bench WebGL", function() { + var myp5, font; + + bench( + "textToPoints() single word", + async () => { + try { + const { myp5, font } = await _setup(400, 400, WEBGL); + myp5.textSize(20); + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 317, 5); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + bench( + "textToPoints() single word, 150pt", + async () => { + try { + const { myp5, font } = await _setup(400, 400, WEBGL); + myp5.textSize(150); + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 2336, 50); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + // TODO: this is an example of using setup() to isolate benchmarks away from p5 instance setup code + // which adds ~50ms universally to all benchmarks + bench( + "textToPoints() single word, isolated benchmark", + async () => { + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 317, 5); + }, + { + ...options, + setup: async () => { + ({ myp5, font } = await _setup(400, 400, WEBGL)); + myp5.textSize(20); + }, + teardown: () => myp5.remove() + } + ); + + bench( + "textToPoints() single word, with render", + async () => { + try { + const { myp5, font } = await _setup(400, 400, WEBGL); + myp5.textSize(20); + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 317, 5); + drawPoints(myp5, points); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + bench( + "textToPoints() 10 words", + async () => { + try { + const { myp5, font } = await _setup(400, 400, WEBGL); + myp5.textSize(20); + const points = font.textToPoints(strs.ten, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 1453, 5); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + bench( + "textToPoints() Paragraph", + async () => { + try { + const { myp5, font } = await _setup(400, 400, WEBGL); + myp5.textSize(20); + const points = font.textToPoints(strs.paragraph, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 21275, 50); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + // TODO: textToContours() + // TODO: textToPaths() + // TODO: textToPoints() + // TODO: textToContours() + // TODO: textToPaths() + // TODO: textToModel() + +}); + +describe("Typography: bench WebGPU", function() { + var myp5, font; + + bench( + "textToPoints() single word", + async () => { + try { + const { myp5, font } = await _setup(400, 400, WEBGPU); + myp5.textSize(20); + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 317, 5); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + bench( + "textToPoints() single word, 150pt", + async () => { + try { + const { myp5, font } = await _setup(400, 400, WEBGPU); + myp5.textSize(150); + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 2336, 50); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + // TODO: this is an example of using setup() to isolate benchmarks away from p5 instance setup code + // which adds ~50ms universally to all benchmarks + bench( + "textToPoints() single word, isolated benchmark", + async () => { + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 317, 5); + }, + { + ...options, + setup: async () => { + ({ myp5, font } = await _setup(400, 400, WEBGPU)); + myp5.textSize(20); + }, + teardown: () => myp5.remove() + } + ); + + bench( + "textToPoints() single word, with render", + async () => { + try { + const { myp5, font } = await _setup(400, 400, WEBGPU); + myp5.textSize(20); + const points = font.textToPoints(strs.single, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 317, 5); + drawPoints(myp5, points); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + bench( + "textToPoints() 10 words", + async () => { + try { + const { myp5, font } = await _setup(400, 400, WEBGPU); + myp5.textSize(20); + const points = font.textToPoints(strs.ten, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 1453, 5); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + bench( + "textToPoints() Paragraph", + async () => { + try { + const { myp5, font } = await _setup(400, 400, WEBGPU); + myp5.textSize(20); + const points = font.textToPoints(strs.paragraph, 6, 20, { sampleFactor: 0.5 }); + assert.closeTo(points.length, 21275, 50); + myp5.remove(); + } catch (error) { + console.log(error); + } + }, + options + ); + + // TODO: textToContours() + // TODO: textToPaths() + // TODO: textToPoints() + // TODO: textToContours() + // TODO: textToPaths() + // TODO: textToModel() + +}); \ No newline at end of file