diff --git a/contributor_docs/performance_testing.md b/contributor_docs/performance_testing.md new file mode 100644 index 0000000000..33d355d5f3 --- /dev/null +++ b/contributor_docs/performance_testing.md @@ -0,0 +1,34 @@ + + +# Performance Testing + +Performance testing is an essential part of measuring how fast isolated code paths take to execute under a known range of conditions. + +Implementation notes: + +- All pre-existing benchmarks included the full p5 instance setup which adds ~50ms to each test. The typography performance tests show how to isolate performance testing away from setup overhead. +- The `textToPoints() single word, with render` test draws the calculated points, others just run `textToPoints()`. + +## Typography Tests + +To isolate typography tests, run the following: + +```shell +npx vitest bench test/bench/typography.bench.js --reporter=verbose +``` + +### Manual tests on v1.x + +These manual tests are useful to loosely check performance against v2.x as a baseline. The test harness is compatible with the one in `test/bench/typography.bench.js`. + +To manually run typography tests against p5.js v1.x, open the following files in a browser: + +- `textToPoints()`: `test/bench/v1_manual/text_to_points.html` + +Note: you will need to use a [local server](https://github.com/processing/p5.js/wiki/Local-server) to avoid CORS issues when loading fonts. For example, from the root of the repo: + +```shell +npx http-server -c-1 +``` + +Then tests can be accessed at `http://localhost:8080/test/bench/v1_manual/text_to_points.html`. diff --git a/test/bench/typography.bench.js b/test/bench/typography.bench.js new file mode 100644 index 0000000000..0b7b611ac3 --- /dev/null +++ b/test/bench/typography.bench.js @@ -0,0 +1,125 @@ +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 +}; + +// Parameterizing test cases by function ensures consistency in test parameters across all renderers. +// Future tests should follow a similar format (e.g. TO_CONTOURS_CASES, etc) +const TO_POINTS_CASES = [ + {label: "textToPoints() single word", str: strs.single, textSize: 20, sampleFactor: 0.5, points: 317, variance: 5, render: false}, + {label: "textToPoints() single word, 150pt", str: strs.single, textSize: 150, sampleFactor: 0.5, points: 2336, variance: 50, render: false}, + {label: "textToPoints() single word, with render", str: strs.single, textSize: 20, sampleFactor: 0.5, points: 317, variance: 5, render: true}, + {label: "textToPoints() 10 words", str: strs.ten, textSize: 20, sampleFactor: 0.5, points: 1453, variance: 5, render: false}, + {label: "textToPoints() paragraph", str: strs.paragraph, textSize: 20, sampleFactor: 0.5, points: 21275, variance: 50, render: false}, +]; + +async function bootstrap(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; + + for (let testCase of TO_POINTS_CASES) { + bench( + testCase.label, + async () => { + const points = font.textToPoints(testCase.str, 10, 20, { sampleFactor: testCase.sampleFactor }); + assert.closeTo(points.length, testCase.points, testCase.variance); + if (testCase.render) { + drawPoints(myp5, points); + } + }, + { + ...options, + setup: async () => { + ({myp5, font} = await bootstrap()); + myp5.textSize(testCase.textSize); + }, + teardown: () => myp5.remove() + } + ); + } + +}); + +describe("Typography: bench WebGL", function() { + var myp5, font; + + for (let testCase of TO_POINTS_CASES) { + bench( + testCase.label, + async () => { + const points = font.textToPoints(testCase.str, 10, 20, { sampleFactor: testCase.sampleFactor }); + assert.closeTo(points.length, testCase.points, testCase.variance); + if (testCase.render) { + drawPoints(myp5, points); + } + }, + { + ...options, + setup: async () => { + ({myp5, font} = await bootstrap(400, 400, WEBGL)); + myp5.textSize(testCase.textSize); + }, + teardown: () => myp5.remove() + } + ); + } + +}); + +describe("Typography: bench WebGPU", function() { + var myp5, font; + + for (let testCase of TO_POINTS_CASES) { + bench( + testCase.label, + async () => { + const points = font.textToPoints(testCase.str, 10, 20, { sampleFactor: testCase.sampleFactor }); + assert.closeTo(points.length, testCase.points, testCase.variance); + if (testCase.render) { + drawPoints(myp5, points); + } + }, + { + ...options, + setup: async () => { + ({myp5, font} = await bootstrap(400, 400, WEBGPU)); + myp5.textSize(testCase.textSize); + }, + teardown: () => myp5.remove() + } + ); + } + +}); \ No newline at end of file diff --git a/test/bench/v1_manual/text_to_points.html b/test/bench/v1_manual/text_to_points.html new file mode 100644 index 0000000000..254e3caf8c --- /dev/null +++ b/test/bench/v1_manual/text_to_points.html @@ -0,0 +1,39 @@ + + +
+