Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/benchmarks/src/benchmark/target-boundary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('benchmark target boundaries', () => {

it('keeps low-level targets downstream from renderer infrastructure', async () => {
const files = await sourceFiles(rendererDirectory);
expect(files.map((file) => file.slice(rendererDirectory.length + 1))).not.toContain('tsl-baseline.ts');
const offenders = await Promise.all(
files.map(async (file) => {
const source = await readFile(file, 'utf8');
Expand Down Expand Up @@ -74,9 +75,11 @@ describe('benchmark target boundaries', () => {
expect(registry).toContain("import('./conformance')");
expect(execution).toContain('await loadRegisteredTarget(request.targetId)');
expect(conformance).toContain("import('./advanced-shaping')");
expect(conformance).toContain("import('./tsl-baseline')");
expect(conformance).toContain("import('./raster/runtime-fallback')");
expect(conformance).toContain("import('./raster/bitmap-capture')");
expect(conformance).not.toContain('renderer/advanced-shaping-conformance');
expect(conformance).not.toContain('renderer/tsl-baseline');
expect(conformance).not.toContain('renderer/runtime-fallback-conformance');
expect(conformance).not.toContain('renderer/bitmap-text');
expect(product).toContain("import('./external-raster-proof')");
Expand Down
2 changes: 1 addition & 1 deletion apps/benchmarks/src/benchmark/targets/conformance/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function tslBaselineTarget(backend: Backend): BenchmarkTarget {
capabilities: new Set<Capability>(['deterministic', 'raster']),
status: () => 'ready',
},
async () => (await import('../../../renderer/tsl-baseline')).createTslBaselineTarget(backend),
async () => (await import('./tsl-baseline')).createTslBaselineTarget(backend),
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import * as THREE from 'three/webgpu';

import { compactRgba8Readback } from '../benchmark/low-level/raster/rgba-readback';
import type { Node } from 'three/webgpu';
import { float, mul, vec3 } from 'three/tsl';

import type { BenchmarkTarget, TargetRunOutput } from '../benchmark/contracts';
import { createConfiguredRenderer, disposeConfiguredRenderer, type RendererBackend } from './webgpu-renderer';
import { compactRgba8Readback } from '../../low-level/raster/rgba-readback';
import type { BenchmarkTarget, TargetRunOutput } from '../../contracts';
import {
createConfiguredRenderer,
disposeConfiguredRenderer,
type RendererBackend,
} from '../../../renderer/webgpu-renderer';

const TARGET_SIZE = 4;
const EXPECTED_PIXEL = [255, 0, 0, 255] as const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
BitmapTextPersistentScene,
BitmapTextSceneSnapshot,
} from '../../renderer/bitmap-text';
import { createLatestAsyncQueue, type LatestAsyncQueue } from '../../renderer/latest-async-queue';
import { createLatestAsyncQueue, type LatestAsyncQueue } from './latest-async-queue';
import { usePersistentRenderHost } from '../../renderer/persistent-render-host-context';
import {
benchmarkContentWidth,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,39 @@ import { describe, expect, it } from 'vitest';
import { createLatestAsyncQueue } from './latest-async-queue';

describe('latest async queue', () => {
it('finishes the active mutation and collapses queued requests to the latest input', async () => {
it('runs one input at a time and collapses pending inputs to the newest state', async () => {
const releases: Array<() => void> = [];
const runs: number[] = [];
const started: number[] = [];
const queue = createLatestAsyncQueue(async (input: number) => {
runs.push(input);
started.push(input);
await new Promise<void>((resolve) => releases.push(resolve));
return input * 2;
});

const first = queue.enqueue(1);
const second = queue.enqueue(2);
const third = queue.enqueue(3);
expect(runs).toEqual([1]);

releases.shift()!();
expect(started).toEqual([1]);
releases.shift()?.();
await first;
expect(runs).toEqual([1, 3]);
releases.shift()!();
expect(started).toEqual([1, 3]);
releases.shift()?.();

await expect(second).resolves.toEqual({ input: 3, output: 6 });
await expect(third).resolves.toEqual({ input: 3, output: 6 });
});

it('continues with the latest pending request after a failure', async () => {
it('rejects every waiter for a failed collapsed input and continues draining', async () => {
let fail = true;
const queue = createLatestAsyncQueue(async (input: string) => {
if (input === 'failed') throw new Error('failed');
return input;
if (fail) {
fail = false;
throw new Error(`failed ${input}`);
}
return input.toUpperCase();
});

await expect(queue.enqueue('failed')).rejects.toThrow('failed');
await expect(queue.enqueue('recovered')).resolves.toEqual({ input: 'recovered', output: 'recovered' });
await expect(queue.enqueue('first')).rejects.toThrow('failed first');
await expect(queue.enqueue('second')).resolves.toEqual({ input: 'second', output: 'SECOND' });
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useEffectEvent, useRef, useState, type RefObject } from 'react';

import type { FontDelivery, GraphicsBackend } from '../../benchmark/url-state';
import { createLatestAsyncQueue, type LatestAsyncQueue } from '../../renderer/latest-async-queue';
import { createLatestAsyncQueue, type LatestAsyncQueue } from './latest-async-queue';
import type { MtsdfTextLiveStats, MtsdfTextPersistentScene } from '../../renderer/mtsdf-text';
import { usePersistentRenderHost } from '../../renderer/persistent-render-host-context';
import type { SlugTextLiveStats, SlugTextPersistentScene } from '../../renderer/slug-text';
Expand Down
4 changes: 2 additions & 2 deletions apps/benchmarks/vitexec/core-text-frame.probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { bitmap } from '@pmndrs/text/raster/bitmap';
import * as THREE from 'three/webgpu';

const benchmarkIpsumPath = '/src/workloads/benchmark-ipsum.ts';
const tslBaselinePath = '/src/renderer/tsl-baseline.ts';
const compactRgba8ReadbackPath = '/src/benchmark/low-level/raster/rgba-readback.ts';
const rendererPath = '/src/renderer/webgpu-renderer.ts';
const [{ BENCHMARK_IPSUM_CONFORMANCE_TEXT }, { compactRgba8Readback }, { createConfiguredRenderer }] =
await Promise.all([
import(/* @vite-ignore */ benchmarkIpsumPath),
import(/* @vite-ignore */ tslBaselinePath),
import(/* @vite-ignore */ compactRgba8ReadbackPath),
import(/* @vite-ignore */ rendererPath),
]);

Expand Down
1 change: 1 addition & 0 deletions docs/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 2026-08-03

- **Final benchmark boundary locality** — Moved the deterministic TSL baseline and its tests from `renderer` into the conformance target tree, with the lazy registry and core-text browser probe resolving their true target and low-level readback owners. Moved the latest-value async queue beside the three React viewport controllers that exclusively consume it. Focused boundary/queue/TSL tests, strict type checking, the complete 317-test unit lane, and the real WebGPU/WebGL live browser probe passed.
- **Workload-owned retained comparison scene** — Moved the remaining 1,573-line multi-technique workload implementation and its 661-line focused test beside the authored workload definitions under `workloads/comparison`. Removed its standalone renderer, RAF, GPU-timer, and telemetry branch. Three Slug performance probes now use an 80-line measurement-owned adapter that creates one `PersistentRenderHost` and activates the same retained scene, completing all 40 fixed-32 browser runs across WebGPU/WebGL2, Inter/CJK, and both candidates. The complete 317-test gate passed; all 42 Presentation cells stayed visible with one renderer, and both timed demos returned to Off-axis / 3D at 59.90/60.02 Icon Grid FPS.
- **Canonical benchmark asset and raster metadata boundaries** — Removed renderer-owned font-loader, preload, baked-artifact, and raster-configuration compatibility facades. Live scenes and finite product/conformance targets now call the discriminated `workloads/font-assets` API directly, while Bitmap atlas, MTSDF extension, and Slug allocation inspection live under `benchmark/low-level/raster`. The complete 316-test gate and production build passed; all 19 isolated conformance/product scenarios remained deterministic, all 42 sequential Presentation cells rendered visible pixels with one renderer, and the retained comparison plus exclusive finite-job recovery probe passed on WebGPU and WebGL2. Live renderer chunks fell again to 9.26/3.38 kB minified/gzip for Bitmap, 7.77/2.85 for MTSDF, and 7.28/2.75 for Slug.
- **Persistent renderer API cleanup** — Removed the unused standalone Bitmap, MTSDF, and Slug preview constructors and 676 lines of duplicate renderer, RAF, GPU-timer, telemetry, resize, and disposal lifecycle. The remaining contracts are named for persistent scenes, and a boundary regression rejects reintroducing preview entrypoints. The complete 315-test benchmark gate and production build passed; live renderer chunks fell from 16.82/5.19 to 10.37/3.84 kB minified/gzip for Bitmap, 9.02/3.32 to 8.37/3.12 for MTSDF, and 9.26/3.37 to 8.19/3.09 for Slug. All 42 sequential Presentation cells rendered visible pixels with one renderer, and both timed demos completed their authored sequence, returned to Off-axis / 3D, and measured 59.92/60.02 Icon Grid FPS on WebGPU/WebGL2.
Expand Down
10 changes: 9 additions & 1 deletion docs/packages/benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Provides the shared interactive and automated benchmark product sur
resource: ../../apps/benchmarks
workspace_package: '@pmndrs/text-benchmarks'
documentation_type: reference
source_digest: 'sha256:64ff511cf7545a06d2efc9d942a22b1be8b2aa62b2f75f7aab5da4ad728b3a0f'
source_digest: 'sha256:6c6d99db4edf86b72b37772cf56b37edee4c486f2af7275e3a1376c114c8b892'
tags: [package, benchmarks, react, vite, product-e2e]
sources:
- id: manifest
Expand Down Expand Up @@ -134,6 +134,12 @@ sources:
- id: comparison-measurement-preview
resource: ../../apps/benchmarks/src/benchmark/targets/measurement/comparison-preview.ts
title: Isolated-canvas adapter for Slug performance measurements
- id: tsl-conformance-target
resource: ../../apps/benchmarks/src/benchmark/targets/conformance/tsl-baseline.ts
title: Deterministic TSL renderer conformance target
- id: latest-scene-update-queue
resource: ../../apps/benchmarks/src/surfaces/benchmark/latest-async-queue.ts
title: Latest-value React viewport update coordinator
- id: conformance-surface
resource: ../../apps/benchmarks/src/surfaces/conformance/conformance-surface.tsx
title: Host-borrowing conformance surface hierarchy
Expand Down Expand Up @@ -262,6 +268,8 @@ Canonical font loading is owned exclusively by `workloads/font-assets`, and rend

The multi-technique retained implementation lives beside its authored definitions under `workloads/comparison/scene`. It can use only an allowlisted set of generic host, canvas, telemetry, and activation primitives from `renderer`; it cannot create a renderer, animation loop, or GPU timer. Slug performance observations that require an isolated canvas enter through `benchmark/targets/measurement/comparison-preview`, which owns one `PersistentRenderHost`, activates the same workload scene, and guarantees host disposal after release. The app and workload rail preserve the literal lazy scene chunk boundary.

The deterministic TSL renderer baseline is an executable conformance target under `benchmark/targets/conformance`, not renderer infrastructure. The latest-value async queue is local to `surfaces/benchmark`, where the three React viewport controllers use it to serialize scene commits and collapse obsolete pending inputs.

### Benchmark ipsum corpus

The corpus is an executable fixture, not display copy. Its five lines isolate ordinary Latin rhythm, numerals, kerning pairs, punctuation, standard ligature candidates, and compact mathematical notation. Inter must shape every scalar without glyph 0; the renderer rejects the corpus before upload if coverage regresses. Every selectable family receives the identical diagnostic and paragraph source text. The live surface reports source length and missing glyphs, so fixture coverage differences remain visible and comparable instead of being hidden by font-specific copy.
Expand Down
Loading