@zkmake/three-meter 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -241,6 +241,177 @@ var RingBuffer = class {
241
241
  }
242
242
  };
243
243
  //#endregion
244
+ //#region src/core/scene-cost.ts
245
+ /** three's `DoubleSide` and `WebGPUCoordinateSystem`. */
246
+ const DOUBLE_SIDE = 2;
247
+ const WEBGPU_COORDINATES = 2001;
248
+ /** Column-major `a × b`, as three stores matrices. */
249
+ const multiply = (a, b) => {
250
+ const out = Array.from({ length: 16 });
251
+ for (let column = 0; column < 4; column += 1) for (let row = 0; row < 4; row += 1) {
252
+ let sum = 0;
253
+ for (let k = 0; k < 4; k += 1) sum += a[k * 4 + row] * b[column * 4 + k];
254
+ out[column * 4 + row] = sum;
255
+ }
256
+ return out;
257
+ };
258
+ const normalize = (plane) => {
259
+ const length = Math.hypot(plane[0], plane[1], plane[2]) || 1;
260
+ return [
261
+ plane[0] / length,
262
+ plane[1] / length,
263
+ plane[2] / length,
264
+ plane[3] / length
265
+ ];
266
+ };
267
+ /** Mirrors three's `Frustum.setFromProjectionMatrix`. Near and far only where the depth range is known. */
268
+ const frustumPlanes = (camera) => {
269
+ if (!camera.projectionMatrix || !camera.matrixWorldInverse) return null;
270
+ const m = multiply(camera.projectionMatrix.elements, camera.matrixWorldInverse.elements);
271
+ const row = (index) => [
272
+ m[index],
273
+ m[index + 4],
274
+ m[index + 8],
275
+ m[index + 12]
276
+ ];
277
+ const [r0, r1, r2, r3] = [
278
+ row(0),
279
+ row(1),
280
+ row(2),
281
+ row(3)
282
+ ];
283
+ const add = (a, b) => [
284
+ a[0] + b[0],
285
+ a[1] + b[1],
286
+ a[2] + b[2],
287
+ a[3] + b[3]
288
+ ];
289
+ const sub = (a, b) => [
290
+ a[0] - b[0],
291
+ a[1] - b[1],
292
+ a[2] - b[2],
293
+ a[3] - b[3]
294
+ ];
295
+ const planes = [
296
+ sub(r3, r0),
297
+ add(r3, r0),
298
+ add(r3, r1),
299
+ sub(r3, r1)
300
+ ];
301
+ if (!camera.reversedDepth) planes.push(sub(r3, r2), camera.coordinateSystem === WEBGPU_COORDINATES ? r2 : add(r3, r2));
302
+ return planes.map(normalize);
303
+ };
304
+ const inFrustum = (object, planes) => {
305
+ const sphere = object.boundingSphere ?? object.geometry?.boundingSphere;
306
+ const e = object.matrixWorld?.elements;
307
+ if (!planes || !sphere || !e) return true;
308
+ const { x, y, z } = sphere.center;
309
+ const cx = e[0] * x + e[4] * y + e[8] * z + e[12];
310
+ const cy = e[1] * x + e[5] * y + e[9] * z + e[13];
311
+ const cz = e[2] * x + e[6] * y + e[10] * z + e[14];
312
+ const scale = Math.sqrt(Math.max(e[0] ** 2 + e[1] ** 2 + e[2] ** 2, e[4] ** 2 + e[5] ** 2 + e[6] ** 2, e[8] ** 2 + e[9] ** 2 + e[10] ** 2));
313
+ const radius = sphere.radius * scale;
314
+ return !planes.some(([a, b, c, d]) => a * cx + b * cy + c * cz + d < -radius);
315
+ };
316
+ const layersMatch = (object, camera) => !object.layers || !camera.layers || (object.layers.mask & camera.layers.mask) !== 0;
317
+ const passes = (material) => material.transparent === true && material.side === DOUBLE_SIDE && material.forceSinglePass !== true ? 2 : 1;
318
+ /** Vertices a draw covers, clipped to `drawRange` and, when given, a group. */
319
+ const drawnVertices = (geometry, group) => {
320
+ const total = geometry.index?.count ?? geometry.attributes?.position?.count ?? 0;
321
+ const rangeStart = geometry.drawRange?.start ?? 0;
322
+ const rangeEnd = Math.min(total, rangeStart + (geometry.drawRange?.count ?? Infinity));
323
+ const start = Math.max(rangeStart, group?.start ?? 0);
324
+ const end = Math.min(rangeEnd, group ? group.start + group.count : Infinity);
325
+ return Math.max(0, end - start);
326
+ };
327
+ /** One draw per visible material, or per group when the material is an array. */
328
+ const drawsOf = (object) => {
329
+ const { geometry, material } = object;
330
+ if (!geometry || !material) return [];
331
+ const instances = object.isInstancedMesh ? object.count ?? 1 : 1;
332
+ const trianglesFor = (vertices) => object.isMesh ? Math.floor(vertices / 3) * instances : 0;
333
+ if (Array.isArray(material)) {
334
+ const draws = [];
335
+ for (const group of geometry.groups ?? []) {
336
+ const groupMaterial = material[group.materialIndex ?? 0];
337
+ if (groupMaterial && groupMaterial.visible !== false) {
338
+ const count = passes(groupMaterial);
339
+ draws.push({
340
+ calls: count,
341
+ material: groupMaterial,
342
+ triangles: trianglesFor(drawnVertices(geometry, group)) * count
343
+ });
344
+ }
345
+ }
346
+ return draws;
347
+ }
348
+ return material.visible === false ? [] : [{
349
+ calls: passes(material),
350
+ material,
351
+ triangles: trianglesFor(drawnVertices(geometry)) * passes(material)
352
+ }];
353
+ };
354
+ const objectLabel = (object) => object.name || object.geometry?.name || object.type || "Object";
355
+ const materialLabel = (material) => material.name || material.type || "Material";
356
+ const entryFor = (groups, key, label) => {
357
+ let entry = groups.get(key);
358
+ if (!entry) {
359
+ entry = {
360
+ calls: 0,
361
+ instances: 0,
362
+ label,
363
+ objects: [],
364
+ triangles: 0
365
+ };
366
+ groups.set(key, entry);
367
+ }
368
+ return entry;
369
+ };
370
+ const byCost = (a, b) => b.calls - a.calls || b.triangles - a.triangles;
371
+ const computeSceneCost = (scene, camera) => {
372
+ const root = scene;
373
+ const view = camera;
374
+ if (typeof root?.traverseVisible !== "function") return null;
375
+ const planes = frustumPlanes(view);
376
+ const meshes = /* @__PURE__ */ new Map();
377
+ const materials = /* @__PURE__ */ new Map();
378
+ let calls = 0;
379
+ let triangles = 0;
380
+ root.traverseVisible((object) => {
381
+ if (!(object.isMesh || object.isLine || object.isPoints || object.isSprite) || !layersMatch(object, view)) return;
382
+ if (object.frustumCulled !== false && !object.isSprite && !inFrustum(object, planes)) return;
383
+ const instances = object.isInstancedMesh ? object.count ?? 1 : 1;
384
+ const label = objectLabel(object);
385
+ const meshKey = `${label}\u0000${object.geometry?.uuid ?? ""}`;
386
+ const draws = drawsOf(object);
387
+ if (draws.length === 0) return;
388
+ const mesh = entryFor(meshes, meshKey, label);
389
+ mesh.objects.push(object);
390
+ mesh.instances += instances;
391
+ const listed = /* @__PURE__ */ new Set();
392
+ for (const draw of draws) {
393
+ calls += draw.calls;
394
+ triangles += draw.triangles;
395
+ mesh.calls += draw.calls;
396
+ mesh.triangles += draw.triangles;
397
+ const material = entryFor(materials, draw.material, materialLabel(draw.material));
398
+ material.calls += draw.calls;
399
+ material.triangles += draw.triangles;
400
+ if (!listed.has(draw.material)) {
401
+ listed.add(draw.material);
402
+ material.objects.push(object);
403
+ material.instances += instances;
404
+ }
405
+ }
406
+ });
407
+ return {
408
+ calls,
409
+ materials: [...materials.values()].sort(byCost),
410
+ meshes: [...meshes.values()].sort(byCost),
411
+ triangles
412
+ };
413
+ };
414
+ //#endregion
244
415
  //#region src/core/performance-monitor.ts
245
416
  const DEFAULT_GPU_POOL_SIZE = 5;
246
417
  /** One monitor per renderer: a second would stack the `render` patch and double-count passes. */
@@ -282,6 +453,9 @@ var PerformanceMonitor = class {
282
453
  gpuHistory;
283
454
  frameIntervals;
284
455
  frameStats = null;
456
+ /** The render call with the most draw calls this frame, and the scene and camera it drew. */
457
+ mainPassCalls = -1;
458
+ mainPass = null;
285
459
  cpuStart = 0;
286
460
  lastBeginAt = null;
287
461
  frameFps = 0;
@@ -297,10 +471,12 @@ var PerformanceMonitor = class {
297
471
  this.originalRender = renderer.render;
298
472
  renderer.render = (...args) => {
299
473
  this.renderPasses += 1;
474
+ const callsBefore = frameCalls(renderer.info);
300
475
  try {
301
476
  return this.originalRender.apply(renderer, args);
302
477
  } finally {
303
478
  this.lastRenderEndAt = performance.now();
479
+ this.notePass(args[0], args[1], frameCalls(renderer.info) - callsBefore);
304
480
  }
305
481
  };
306
482
  this.gpuTimer = trackGPU ? GpuTimer.create(renderer, gpuQueryPoolSize) : null;
@@ -324,6 +500,7 @@ var PerformanceMonitor = class {
324
500
  this.cpuStart = now;
325
501
  this.lastRenderEndAt = null;
326
502
  this.renderPasses = 0;
503
+ this.mainPassCalls = -1;
327
504
  this.renderer.info.reset();
328
505
  this.gpuTimer?.begin();
329
506
  }
@@ -375,6 +552,17 @@ var PerformanceMonitor = class {
375
552
  return this.frameStats;
376
553
  }
377
554
  /**
555
+ * Where the main pass's draw calls and triangles come from, by mesh and by
556
+ * material. Walks the scene graph, so read it on demand (the HUD does, at
557
+ * 2 Hz while its section is open), not every frame. `null` before the first
558
+ * render, or once the scene is garbage collected.
559
+ */
560
+ getSceneCost() {
561
+ const scene = this.mainPass?.scene.deref();
562
+ const camera = this.mainPass?.camera.deref();
563
+ return scene && camera ? computeSceneCost(scene, camera) : null;
564
+ }
565
+ /**
378
566
  * three revision, backend and GPU name. Cached once the backend is known;
379
567
  * before `WebGPURenderer.init()` settles it, `backend` is `null` and the
380
568
  * next call reads again.
@@ -395,6 +583,21 @@ var PerformanceMonitor = class {
395
583
  this.gpuHistory.clear();
396
584
  this.frameIntervals.clear();
397
585
  this.frameStats = null;
586
+ this.mainPass = null;
587
+ }
588
+ /**
589
+ * The main pass is the render call that drew the most, so a post-processing
590
+ * quad rendered last doesn't stand in for the scene. Weak references: the
591
+ * monitor never keeps a scene alive.
592
+ */
593
+ notePass(scene, camera, calls) {
594
+ if (calls > this.mainPassCalls && typeof scene === "object" && scene !== null && typeof camera === "object" && camera !== null) {
595
+ this.mainPassCalls = calls;
596
+ this.mainPass = {
597
+ camera: new WeakRef(camera),
598
+ scene: new WeakRef(scene)
599
+ };
600
+ }
398
601
  }
399
602
  buildEmptySample() {
400
603
  return Object.freeze({
@@ -422,4 +625,4 @@ var PerformanceMonitor = class {
422
625
  //#endregion
423
626
  export { RingBuffer as n, PerformanceMonitor as t };
424
627
 
425
- //# sourceMappingURL=performance-monitor-yxsxs57v.js.map
628
+ //# sourceMappingURL=performance-monitor-DsBp13ts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"performance-monitor-DsBp13ts.js","names":[],"sources":["../src/core/environment.ts","../src/core/frame-stats.ts","../src/core/gpu-timer.ts","../src/core/ring-buffer.ts","../src/core/scene-cost.ts","../src/core/performance-monitor.ts"],"sourcesContent":["/**\n * What the monitor is running on: three revision, rendering backend, GPU.\n * Read from the renderer and the page, never from an import of three.\n *\n * `WebGPURenderer` settles its backend in `init()`, so until then the backend\n * reads `null`; callers poll and the monitor caches once it is known.\n */\nimport type { Environment, PerfRenderer, RenderBackend } from \"./types.ts\";\n\ntype BackendShape = {\n device?: { adapterInfo?: AdapterInfoShape } | null;\n gl?: unknown;\n isWebGLBackend?: boolean;\n isWebGPUBackend?: boolean;\n parameters?: { forceWebGL?: boolean };\n};\n\ntype AdapterInfoShape = {\n architecture?: string;\n description?: string;\n device?: string;\n vendor?: string;\n};\n\ntype DebugRendererInfoExt = { readonly UNMASKED_RENDERER_WEBGL: number };\n\n/** three sets `window.__THREE__` to its revision on import (and warns on a second copy). */\nconst readThreeRevision = (): string | null => {\n const revision = (globalThis as { __THREE__?: unknown }).__THREE__;\n\n return typeof revision === \"string\" && revision !== \"\" ? revision : null;\n};\n\n/**\n * Trim a WebGL renderer string to the part a person recognises. Chrome wraps\n * it in ANGLE, e.g. `ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 (0x00002786)\n * Direct3D11 vs_5_0 ps_5_0, D3D11)` or `ANGLE (Apple, ANGLE Metal Renderer:\n * Apple M3 Pro, Unspecified Version)`.\n */\nconst cleanGpuName = (raw: string): string => {\n let name = raw.trim();\n const angle = /^ANGLE \\((.*)\\)$/.exec(name);\n\n if (angle) {\n const parts = angle[1]!.split(\", \");\n name = parts[1] ?? parts[0]!;\n }\n\n return name\n .replace(/^ANGLE [\\w ]+ Renderer: /, \"\")\n .replace(/ \\(0x[0-9a-f]+\\)/i, \"\")\n .replace(/ (Direct3D\\d*|OpenGL|Vulkan|Metal)\\b.*$/, \"\")\n .trim();\n};\n\nconst webglGpuName = (gl: WebGLRenderingContext | WebGL2RenderingContext): string | null => {\n // Firefox deprecates the extension but already returns the unmasked name from RENDERER.\n const ext = gl.getExtension(\"WEBGL_debug_renderer_info\") as DebugRendererInfoExt | null;\n const raw = gl.getParameter(ext ? ext.UNMASKED_RENDERER_WEBGL : gl.RENDERER) as unknown;\n\n return typeof raw === \"string\" && raw !== \"\" && raw !== \"WebKit WebGL\" ? cleanGpuName(raw) : null;\n};\n\n/** WebGPU reports vendors lowercase (`apple`, `nvidia`). */\nconst VENDOR_NAMES: Record<string, string> = {\n amd: \"AMD\",\n apple: \"Apple\",\n arm: \"Arm\",\n intel: \"Intel\",\n nvidia: \"NVIDIA\",\n qualcomm: \"Qualcomm\",\n};\n\n/**\n * `GPUDevice.adapterInfo`. Chrome leaves `description` empty to limit\n * fingerprinting, so this is usually vendor + architecture: `Apple metal-3`.\n */\nconst webgpuGpuName = (info: AdapterInfoShape | undefined): string | null => {\n if (!info) {\n return null;\n }\n\n if (info.description) {\n return info.description;\n }\n\n const vendor = info.vendor ? (VENDOR_NAMES[info.vendor] ?? info.vendor) : \"\";\n const name = [vendor, info.architecture].filter(Boolean).join(\" \");\n\n return name === \"\" ? null : name;\n};\n\nconst isWebglContext = (value: unknown): value is WebGLRenderingContext | WebGL2RenderingContext =>\n (typeof WebGL2RenderingContext !== \"undefined\" && value instanceof WebGL2RenderingContext) ||\n (typeof WebGLRenderingContext !== \"undefined\" && value instanceof WebGLRenderingContext);\n\nconst webglBackend = (gl: WebGLRenderingContext | WebGL2RenderingContext): RenderBackend =>\n typeof WebGL2RenderingContext !== \"undefined\" && gl instanceof WebGL2RenderingContext\n ? \"webgl2\"\n : \"webgl\";\n\nconst readEnvironment = (renderer: PerfRenderer): Environment => {\n const three = readThreeRevision();\n const backend = renderer.backend as BackendShape | undefined;\n\n // WebGPURenderer. Read the backend's own fields: `getContext()` before\n // `init()` is what the GPU timer learned not to touch.\n if (backend) {\n if (backend.isWebGPUBackend) {\n const device = backend.device;\n\n return device\n ? { backend: \"webgpu\", fallback: false, gpu: webgpuGpuName(device.adapterInfo), three }\n : { backend: null, fallback: false, gpu: null, three };\n }\n\n if (backend.isWebGLBackend && isWebglContext(backend.gl)) {\n return {\n backend: webglBackend(backend.gl),\n fallback: backend.parameters?.forceWebGL !== true,\n gpu: webglGpuName(backend.gl),\n three,\n };\n }\n\n return { backend: null, fallback: false, gpu: null, three };\n }\n\n // WebGLRenderer.\n const context = renderer.getContext?.();\n\n return isWebglContext(context)\n ? { backend: webglBackend(context), fallback: false, gpu: webglGpuName(context), three }\n : { backend: null, fallback: false, gpu: null, three };\n};\n\nexport { cleanGpuName, readEnvironment };\n","/**\n * Stutter statistics over a window of frame intervals. Average FPS hides a\n * hitch every few seconds; these don't. Stalls (hidden tab, breakpoint) never\n * reach the window, so they don't count as hitches.\n */\nimport type { FrameStats } from \"./types.ts\";\n\n/** A frame over this multiple of the window's median interval is a hitch. */\nconst HITCH_FACTOR = 2;\n\nconst EMPTY: FrameStats = Object.freeze({ frames: 0, hitches: 0, lowFps: 0, p99Ms: 0 });\n\nconst computeFrameStats = (intervals: readonly number[]): FrameStats => {\n const frames = intervals.length;\n\n if (frames === 0) {\n return EMPTY;\n }\n\n const sorted = [...intervals].sort((a, b) => a - b);\n // Nearest rank: the smallest interval at or above 99% of frames.\n const p99Ms = sorted[Math.ceil(frames * 0.99) - 1]!;\n\n // 1% low: the mean rate across the slowest 1% of frames (at least one frame).\n const slowest = Math.max(1, Math.ceil(frames * 0.01));\n let slowSum = 0;\n\n for (let index = frames - slowest; index < frames; index += 1) {\n slowSum += sorted[index]!;\n }\n\n const middle = frames >> 1;\n const median = frames % 2 === 0 ? (sorted[middle - 1]! + sorted[middle]!) / 2 : sorted[middle]!;\n const threshold = median * HITCH_FACTOR;\n let hitches = 0;\n\n for (const interval of intervals) {\n if (interval > threshold) {\n hitches += 1;\n }\n }\n\n return Object.freeze({ frames, hitches, lowFps: 1000 / (slowSum / slowest), p99Ms });\n};\n\nexport { computeFrameStats, HITCH_FACTOR };\n","/**\n * Real GPU frame timing.\n *\n * WebGPU: Three's `trackTimestamp` + `resolveTimestampsAsync` (ms, a frame or\n * two late). Never calls `getExtension`: that is what remounted the canvas.\n *\n * WebGL2 fallback (Safari without WebGPU, or a forced GL backend):\n * `EXT_disjoint_timer_query_webgl2`, same pool-of-queries pattern as the\n * platform-typing monitor.\n */\nimport type { PerfRenderer } from \"./types.ts\";\n\ntype DisjointTimerQueryExt = {\n readonly GPU_DISJOINT_EXT: number;\n readonly TIME_ELAPSED_EXT: number;\n};\n\nclass GpuTimer {\n private lastMs = 0;\n private readonly webgpu: PerfRenderer | null;\n private webgpuInFlight = false;\n private readonly gl: WebGL2RenderingContext | null;\n private readonly ext: DisjointTimerQueryExt | null;\n private readonly free: WebGLQuery[] = [];\n private readonly pending: WebGLQuery[] = [];\n private active: WebGLQuery | null = null;\n\n private constructor(\n webgpu: PerfRenderer | null,\n gl: WebGL2RenderingContext | null,\n ext: DisjointTimerQueryExt | null,\n poolSize: number,\n ) {\n this.webgpu = webgpu;\n this.gl = gl;\n this.ext = ext;\n\n if (!gl) {\n return;\n }\n\n for (let index = 0; index < poolSize; index += 1) {\n const query = gl.createQuery();\n\n if (query) {\n this.free.push(query);\n }\n }\n }\n\n static create(renderer: PerfRenderer, poolSize: number): GpuTimer | null {\n const backend = renderer.backend as { trackTimestamp?: boolean } | undefined;\n\n if (backend?.trackTimestamp && renderer.resolveTimestampsAsync) {\n return new GpuTimer(renderer, null, null, poolSize);\n }\n\n const context = renderer.getContext?.();\n\n if (!(context instanceof WebGL2RenderingContext)) {\n return null;\n }\n\n const ext = context.getExtension(\n \"EXT_disjoint_timer_query_webgl2\",\n ) as DisjointTimerQueryExt | null;\n\n return ext ? new GpuTimer(null, context, ext, poolSize) : null;\n }\n\n begin() {\n if (this.webgpu || !this.gl || !this.ext) {\n return;\n }\n\n const query = this.free.pop();\n\n if (!query) {\n this.active = null;\n\n return;\n }\n\n this.active = query;\n this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, query);\n }\n\n end() {\n if (this.webgpu || !this.gl || !this.ext || !this.active) {\n return;\n }\n\n this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);\n this.pending.push(this.active);\n this.active = null;\n }\n\n poll() {\n if (this.webgpu) {\n this.pollWebgpu();\n\n return;\n }\n\n this.pollWebgl();\n }\n\n getMs() {\n return this.lastMs;\n }\n\n dispose() {\n if (this.gl && this.ext && this.active) {\n this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);\n this.active = null;\n }\n\n if (this.gl) {\n for (const query of this.free) {\n this.gl.deleteQuery(query);\n }\n\n for (const query of this.pending) {\n this.gl.deleteQuery(query);\n }\n }\n\n this.free.length = 0;\n this.pending.length = 0;\n }\n\n private pollWebgpu() {\n const resolve = this.webgpu?.resolveTimestampsAsync;\n\n if (!resolve || this.webgpuInFlight) {\n return;\n }\n\n this.webgpuInFlight = true;\n\n void resolve\n .call(this.webgpu, \"render\")\n .then((ms) => {\n if (typeof ms === \"number\" && Number.isFinite(ms)) {\n this.lastMs = ms;\n }\n\n this.webgpuInFlight = false;\n })\n .catch(() => {\n this.webgpuInFlight = false;\n });\n }\n\n private pollWebgl() {\n if (!this.gl || !this.ext) {\n return;\n }\n\n const disjoint = this.gl.getParameter(this.ext.GPU_DISJOINT_EXT) as boolean;\n\n if (disjoint) {\n while (this.pending.length > 0) {\n this.free.push(this.pending.shift()!);\n }\n\n return;\n }\n\n while (this.pending.length > 0) {\n const query = this.pending[0]!;\n const ready = this.gl.getQueryParameter(query, this.gl.QUERY_RESULT_AVAILABLE) as boolean;\n\n if (!ready) {\n break;\n }\n\n this.pending.shift();\n const ns = this.gl.getQueryParameter(query, this.gl.QUERY_RESULT) as number;\n this.lastMs = ns / 1e6;\n this.free.push(query);\n }\n }\n}\n\nexport { GpuTimer };\n","/**\n * Fixed-capacity numeric ring buffer. Backs the per-frame timing history the\n * view reads to draw sparklines. Push is O(1); `toArray()` returns values in\n * chronological (oldest → newest) order.\n */\nclass RingBuffer {\n private readonly data: Float32Array;\n private readonly capacity: number;\n private head = 0;\n private count = 0;\n\n constructor(capacity: number) {\n this.capacity = capacity;\n this.data = new Float32Array(capacity);\n }\n\n push(value: number) {\n this.data[this.head] = value;\n this.head = (this.head + 1) % this.capacity;\n\n if (this.count < this.capacity) {\n this.count += 1;\n }\n }\n\n /** Values oldest → newest. Length grows up to capacity as samples accumulate. */\n toArray(): number[] {\n const out = Array.from<number>({ length: this.count });\n const start = (this.head - this.count + this.capacity) % this.capacity;\n\n for (let index = 0; index < this.count; index += 1) {\n out[index] = this.data[(start + index) % this.capacity]!;\n }\n\n return out;\n }\n\n clear() {\n this.head = 0;\n this.count = 0;\n }\n}\n\nexport { RingBuffer };\n","/**\n * Where a frame's draw calls and triangles come from, estimated from the\n * scene graph the way three's renderer walks it: hidden objects, other\n * layers and anything outside the camera's frustum are left out; each\n * visible material (or material group) is one draw call, two for a\n * transparent double-sided material. Main pass only: shadow maps and\n * post-processing passes aren't counted.\n *\n * Structural like the rest of core: no import of three, every field read\n * defensively, since this runs against whatever the app renders.\n */\nimport type { CostEntry, SceneCost } from \"./types.ts\";\n\ntype Elements = ArrayLike<number>;\n\ntype Sphere = { center: { x: number; y: number; z: number }; radius: number };\n\ntype GeometryShape = {\n attributes?: { position?: { count: number } };\n boundingSphere?: Sphere | null;\n drawRange?: { count: number; start: number };\n groups?: { count: number; materialIndex?: number; start: number }[];\n index?: { count: number } | null;\n name?: string;\n uuid?: string;\n};\n\ntype MaterialShape = {\n forceSinglePass?: boolean;\n name?: string;\n side?: number;\n transparent?: boolean;\n type?: string;\n visible?: boolean;\n};\n\ntype ObjectShape = {\n boundingSphere?: Sphere | null;\n count?: number;\n frustumCulled?: boolean;\n geometry?: GeometryShape;\n isInstancedMesh?: boolean;\n isLine?: boolean;\n isMesh?: boolean;\n isPoints?: boolean;\n isSprite?: boolean;\n layers?: { mask: number };\n material?: MaterialShape | MaterialShape[];\n matrixWorld?: { elements: Elements };\n name?: string;\n type?: string;\n};\n\ntype CameraShape = {\n coordinateSystem?: number;\n layers?: { mask: number };\n matrixWorldInverse?: { elements: Elements };\n projectionMatrix?: { elements: Elements };\n reversedDepth?: boolean;\n};\n\ntype SceneShape = { traverseVisible?: (callback: (object: ObjectShape) => void) => void };\n\n/** three's `DoubleSide` and `WebGPUCoordinateSystem`. */\nconst DOUBLE_SIDE = 2;\nconst WEBGPU_COORDINATES = 2001;\n\ntype Plane = [number, number, number, number];\n\n/** Column-major `a × b`, as three stores matrices. */\nconst multiply = (a: Elements, b: Elements) => {\n const out = Array.from<number>({ length: 16 });\n\n for (let column = 0; column < 4; column += 1) {\n for (let row = 0; row < 4; row += 1) {\n let sum = 0;\n\n for (let k = 0; k < 4; k += 1) {\n sum += a[k * 4 + row]! * b[column * 4 + k]!;\n }\n\n out[column * 4 + row] = sum;\n }\n }\n\n return out;\n};\n\nconst normalize = (plane: Plane): Plane => {\n const length = Math.hypot(plane[0], plane[1], plane[2]) || 1;\n\n return [plane[0] / length, plane[1] / length, plane[2] / length, plane[3] / length];\n};\n\n/** Mirrors three's `Frustum.setFromProjectionMatrix`. Near and far only where the depth range is known. */\nconst frustumPlanes = (camera: CameraShape): Plane[] | null => {\n if (!camera.projectionMatrix || !camera.matrixWorldInverse) {\n return null;\n }\n\n const m = multiply(camera.projectionMatrix.elements, camera.matrixWorldInverse.elements);\n const row = (index: number): Plane => [m[index]!, m[index + 4]!, m[index + 8]!, m[index + 12]!];\n const [r0, r1, r2, r3] = [row(0), row(1), row(2), row(3)];\n const add = (a: Plane, b: Plane): Plane => [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]];\n const sub = (a: Plane, b: Plane): Plane => [a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]];\n\n const planes = [sub(r3, r0), add(r3, r0), add(r3, r1), sub(r3, r1)];\n\n if (!camera.reversedDepth) {\n planes.push(sub(r3, r2), camera.coordinateSystem === WEBGPU_COORDINATES ? r2 : add(r3, r2));\n }\n\n return planes.map(normalize);\n};\n\nconst inFrustum = (object: ObjectShape, planes: Plane[] | null) => {\n const sphere = object.boundingSphere ?? object.geometry?.boundingSphere;\n const e = object.matrixWorld?.elements;\n\n // three computes bounding spheres lazily while rendering; without one, count the object.\n if (!planes || !sphere || !e) {\n return true;\n }\n\n const { x, y, z } = sphere.center;\n const cx = e[0]! * x + e[4]! * y + e[8]! * z + e[12]!;\n const cy = e[1]! * x + e[5]! * y + e[9]! * z + e[13]!;\n const cz = e[2]! * x + e[6]! * y + e[10]! * z + e[14]!;\n const scale = Math.sqrt(\n Math.max(\n e[0]! ** 2 + e[1]! ** 2 + e[2]! ** 2,\n e[4]! ** 2 + e[5]! ** 2 + e[6]! ** 2,\n e[8]! ** 2 + e[9]! ** 2 + e[10]! ** 2,\n ),\n );\n const radius = sphere.radius * scale;\n\n // three's own test, `distance < -radius`. Written this way round a NaN plane (a camera with a\n // zero-size aspect, say) keeps the object, as three does, rather than hiding everything.\n return !planes.some(([a, b, c, d]) => a * cx + b * cy + c * cz + d < -radius);\n};\n\nconst layersMatch = (object: ObjectShape, camera: CameraShape) =>\n !object.layers || !camera.layers || (object.layers.mask & camera.layers.mask) !== 0;\n\nconst passes = (material: MaterialShape) =>\n material.transparent === true &&\n material.side === DOUBLE_SIDE &&\n material.forceSinglePass !== true\n ? 2\n : 1;\n\n/** Vertices a draw covers, clipped to `drawRange` and, when given, a group. */\nconst drawnVertices = (\n geometry: GeometryShape,\n group?: { count: number; start: number },\n): number => {\n const total = geometry.index?.count ?? geometry.attributes?.position?.count ?? 0;\n const rangeStart = geometry.drawRange?.start ?? 0;\n const rangeEnd = Math.min(total, rangeStart + (geometry.drawRange?.count ?? Infinity));\n const start = Math.max(rangeStart, group?.start ?? 0);\n const end = Math.min(rangeEnd, group ? group.start + group.count : Infinity);\n\n return Math.max(0, end - start);\n};\n\ntype Draw = { calls: number; material: MaterialShape; triangles: number };\n\n/** One draw per visible material, or per group when the material is an array. */\nconst drawsOf = (object: ObjectShape): Draw[] => {\n const { geometry, material } = object;\n\n if (!geometry || !material) {\n return [];\n }\n\n const instances = object.isInstancedMesh ? (object.count ?? 1) : 1;\n const trianglesFor = (vertices: number) =>\n object.isMesh ? Math.floor(vertices / 3) * instances : 0;\n\n if (Array.isArray(material)) {\n const draws: Draw[] = [];\n\n for (const group of geometry.groups ?? []) {\n const groupMaterial = material[group.materialIndex ?? 0];\n\n if (groupMaterial && groupMaterial.visible !== false) {\n const count = passes(groupMaterial);\n draws.push({\n calls: count,\n material: groupMaterial,\n triangles: trianglesFor(drawnVertices(geometry, group)) * count,\n });\n }\n }\n\n return draws;\n }\n\n return material.visible === false\n ? []\n : [\n {\n calls: passes(material),\n material,\n // A second pass draws the triangles again, and three's info counts them again.\n triangles: trianglesFor(drawnVertices(geometry)) * passes(material),\n },\n ];\n};\n\nconst objectLabel = (object: ObjectShape) =>\n object.name || object.geometry?.name || object.type || \"Object\";\n\nconst materialLabel = (material: MaterialShape) => material.name || material.type || \"Material\";\n\nconst entryFor = (groups: Map<unknown, CostEntry>, key: unknown, label: string) => {\n let entry = groups.get(key);\n\n if (!entry) {\n entry = { calls: 0, instances: 0, label, objects: [], triangles: 0 };\n groups.set(key, entry);\n }\n\n return entry;\n};\n\nconst byCost = (a: CostEntry, b: CostEntry) => b.calls - a.calls || b.triangles - a.triangles;\n\nconst computeSceneCost = (scene: unknown, camera: unknown): SceneCost | null => {\n const root = scene as SceneShape;\n const view = camera as CameraShape;\n\n if (typeof root?.traverseVisible !== \"function\") {\n return null;\n }\n\n const planes = frustumPlanes(view);\n // Same label and geometry is one row, so 500 unmerged copies read as \"tree ×500\".\n const meshes = new Map<unknown, CostEntry>();\n const materials = new Map<unknown, CostEntry>();\n let calls = 0;\n let triangles = 0;\n\n root.traverseVisible((object) => {\n const drawable = object.isMesh || object.isLine || object.isPoints || object.isSprite;\n\n if (!drawable || !layersMatch(object, view)) {\n return;\n }\n\n if (object.frustumCulled !== false && !object.isSprite && !inFrustum(object, planes)) {\n return;\n }\n\n const instances = object.isInstancedMesh ? (object.count ?? 1) : 1;\n const label = objectLabel(object);\n const meshKey = `${label}\\u0000${object.geometry?.uuid ?? \"\"}`;\n\n const draws = drawsOf(object);\n\n if (draws.length === 0) {\n return;\n }\n\n const mesh = entryFor(meshes, meshKey, label);\n mesh.objects.push(object);\n mesh.instances += instances;\n // A multi-material object can draw one material in several groups; list it once per material.\n const listed = new Set<MaterialShape>();\n\n for (const draw of draws) {\n calls += draw.calls;\n triangles += draw.triangles;\n mesh.calls += draw.calls;\n mesh.triangles += draw.triangles;\n\n const material = entryFor(materials, draw.material, materialLabel(draw.material));\n material.calls += draw.calls;\n material.triangles += draw.triangles;\n\n if (!listed.has(draw.material)) {\n listed.add(draw.material);\n material.objects.push(object);\n material.instances += instances;\n }\n }\n });\n\n return {\n calls,\n materials: [...materials.values()].sort(byCost),\n meshes: [...meshes.values()].sort(byCost),\n triangles,\n };\n};\n\nexport { computeSceneCost };\n","import { readEnvironment } from \"./environment.ts\";\nimport { computeFrameStats } from \"./frame-stats.ts\";\nimport { GpuTimer } from \"./gpu-timer.ts\";\nimport { RingBuffer } from \"./ring-buffer.ts\";\nimport { computeSceneCost } from \"./scene-cost.ts\";\nimport type {\n Environment,\n FrameStats,\n PerfRenderer,\n PerformanceMonitorOptions,\n Sample,\n SceneCost,\n TimingMetric,\n} from \"./types.ts\";\n\nconst DEFAULT_GPU_POOL_SIZE = 5;\n/** One monitor per renderer: a second would stack the `render` patch and double-count passes. */\nconst ATTACHED = new WeakSet<PerfRenderer>();\nconst DEFAULT_HISTORY_SIZE = 120;\nconst DEFAULT_FRAME_STATS_SIZE = 1000;\nconst FPS_SMOOTHING = 0.1;\n/**\n * A frame interval past this is a stall (hidden tab, breakpoint, laptop lid),\n * not a slow frame. It is left out of FPS so one gap doesn't flatten the\n * graph's scale for the whole history window.\n */\nconst STALL_MS = 1000;\n\nconst frameCalls = (info: PerfRenderer[\"info\"]) => info.render.drawCalls ?? info.render.calls;\n\nconst programCount = (info: PerfRenderer[\"info\"]) =>\n info.memory.programs ?? info.programs?.length ?? 0;\n\n/**\n * Collector of three.js frame metrics. Bracket each frame with {@link begin}/\n * {@link end}, or let {@link wrapAnimationLoop} or the R3F `PerfSampler` do it.\n * Works on WebGPURenderer (drawCalls, memory.programs, timestamp queries) and\n * on a WebGL renderer (calls, programs[], timer-query ext).\n *\n * While attached, `renderer.info.autoReset` is off and `info` is reset in\n * {@link begin}; host code that reads `renderer.info` mid-frame sees this\n * frame's counts only. {@link dispose} restores both.\n *\n * CPU time runs from {@link begin} to the return of the frame's last\n * `render()` call, which the `render` patch stamps. So it holds even when\n * {@link end} is only reached on the next tick, as in the R3F sampler: it is\n * the JS work of the frame, not the interval to the next one.\n */\nclass PerformanceMonitor {\n private readonly renderer: PerfRenderer;\n private readonly gpuTimer: GpuTimer | null;\n private readonly originalRender: PerfRenderer[\"render\"];\n private renderPasses = 0;\n private lastRenderEndAt: number | null = null;\n private readonly fpsHistory: RingBuffer;\n private readonly cpuHistory: RingBuffer;\n private readonly gpuHistory: RingBuffer;\n private readonly frameIntervals: RingBuffer;\n private frameStats: FrameStats | null = null;\n /** The render call with the most draw calls this frame, and the scene and camera it drew. */\n private mainPassCalls = -1;\n private mainPass: { camera: WeakRef<object>; scene: WeakRef<object> } | null = null;\n private cpuStart = 0;\n private lastBeginAt: number | null = null;\n private frameFps = 0;\n private smoothedFps = 0;\n private sample: Sample;\n private environment: Environment | null = null;\n\n constructor(options: PerformanceMonitorOptions) {\n const {\n renderer,\n trackGPU = true,\n gpuQueryPoolSize = DEFAULT_GPU_POOL_SIZE,\n historySize = DEFAULT_HISTORY_SIZE,\n frameStatsSize = DEFAULT_FRAME_STATS_SIZE,\n } = options;\n\n if (ATTACHED.has(renderer)) {\n throw new Error(\n \"three-meter: a PerformanceMonitor is already attached to this renderer. dispose() it first.\",\n );\n }\n\n ATTACHED.add(renderer);\n this.renderer = renderer;\n renderer.info.autoReset = false;\n this.originalRender = renderer.render;\n\n renderer.render = (...args: never[]) => {\n this.renderPasses += 1;\n const callsBefore = frameCalls(renderer.info);\n\n try {\n return this.originalRender.apply(renderer, args);\n } finally {\n this.lastRenderEndAt = performance.now();\n this.notePass(args[0], args[1], frameCalls(renderer.info) - callsBefore);\n }\n };\n\n this.gpuTimer = trackGPU ? GpuTimer.create(renderer, gpuQueryPoolSize) : null;\n this.fpsHistory = new RingBuffer(historySize);\n this.cpuHistory = new RingBuffer(historySize);\n this.gpuHistory = new RingBuffer(historySize);\n this.frameIntervals = new RingBuffer(frameStatsSize);\n this.sample = this.buildEmptySample();\n }\n\n begin() {\n const now = performance.now();\n\n if (this.lastBeginAt !== null) {\n const frameMs = now - this.lastBeginAt;\n\n // A stall keeps the previous reading rather than logging a near-zero frame.\n if (frameMs > 0 && frameMs <= STALL_MS) {\n this.frameFps = 1000 / frameMs;\n this.frameIntervals.push(frameMs);\n this.frameStats = null;\n }\n }\n\n this.lastBeginAt = now;\n this.cpuStart = now;\n this.lastRenderEndAt = null;\n this.renderPasses = 0;\n this.mainPassCalls = -1;\n this.renderer.info.reset();\n this.gpuTimer?.begin();\n }\n\n end() {\n const now = performance.now();\n // Without a render this frame there is nothing better than \"until end()\".\n const cpu = (this.lastRenderEndAt ?? now) - this.cpuStart;\n\n this.gpuTimer?.end();\n this.gpuTimer?.poll();\n\n const gpuMs = this.gpuTimer?.getMs() ?? 0;\n\n this.smoothedFps =\n this.smoothedFps === 0\n ? this.frameFps\n : this.smoothedFps + (this.frameFps - this.smoothedFps) * FPS_SMOOTHING;\n\n this.fpsHistory.push(this.frameFps);\n this.cpuHistory.push(cpu);\n this.gpuHistory.push(gpuMs);\n\n const info = this.renderer.info;\n\n this.sample = Object.freeze({\n cpu,\n fps: this.smoothedFps,\n gpu: { available: this.gpuTimer !== null, ms: gpuMs },\n render: {\n calls: frameCalls(info),\n lines: info.render.lines,\n passes: this.renderPasses,\n points: info.render.points,\n triangles: info.render.triangles,\n },\n resources: {\n geometries: info.memory.geometries,\n programs: programCount(info),\n textures: info.memory.textures,\n },\n });\n }\n\n getSample(): Sample {\n return this.sample;\n }\n\n getHistory(metric: TimingMetric): readonly number[] {\n switch (metric) {\n case \"fps\":\n return this.fpsHistory.toArray();\n case \"cpu\":\n return this.cpuHistory.toArray();\n case \"gpu\":\n return this.gpuHistory.toArray();\n }\n }\n\n /** 1% low, p99 frame time and hitches. Computed on read, cached until the next frame. */\n getFrameStats(): FrameStats {\n this.frameStats ??= computeFrameStats(this.frameIntervals.toArray());\n\n return this.frameStats;\n }\n\n /**\n * Where the main pass's draw calls and triangles come from, by mesh and by\n * material. Walks the scene graph, so read it on demand (the HUD does, at\n * 2 Hz while its section is open), not every frame. `null` before the first\n * render, or once the scene is garbage collected.\n */\n getSceneCost(): SceneCost | null {\n const scene = this.mainPass?.scene.deref();\n const camera = this.mainPass?.camera.deref();\n\n return scene && camera ? computeSceneCost(scene, camera) : null;\n }\n\n /**\n * three revision, backend and GPU name. Cached once the backend is known;\n * before `WebGPURenderer.init()` settles it, `backend` is `null` and the\n * next call reads again.\n */\n getEnvironment(): Environment {\n if (this.environment) {\n return this.environment;\n }\n\n const environment = Object.freeze(readEnvironment(this.renderer));\n\n if (environment.backend !== null) {\n this.environment = environment;\n }\n\n return environment;\n }\n\n dispose() {\n ATTACHED.delete(this.renderer);\n this.renderer.info.autoReset = true;\n this.renderer.render = this.originalRender;\n this.gpuTimer?.dispose();\n this.fpsHistory.clear();\n this.cpuHistory.clear();\n this.gpuHistory.clear();\n this.frameIntervals.clear();\n this.frameStats = null;\n this.mainPass = null;\n }\n\n /**\n * The main pass is the render call that drew the most, so a post-processing\n * quad rendered last doesn't stand in for the scene. Weak references: the\n * monitor never keeps a scene alive.\n */\n private notePass(scene: unknown, camera: unknown, calls: number) {\n if (\n calls > this.mainPassCalls &&\n typeof scene === \"object\" &&\n scene !== null &&\n typeof camera === \"object\" &&\n camera !== null\n ) {\n this.mainPassCalls = calls;\n this.mainPass = { camera: new WeakRef(camera), scene: new WeakRef(scene) };\n }\n }\n\n private buildEmptySample(): Sample {\n return Object.freeze({\n cpu: 0,\n fps: 0,\n gpu: { available: this.gpuTimer !== null, ms: 0 },\n render: { calls: 0, lines: 0, passes: 0, points: 0, triangles: 0 },\n resources: { geometries: 0, programs: 0, textures: 0 },\n });\n }\n}\n\nexport { PerformanceMonitor };\n"],"mappings":"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"}
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { o as PerformanceMonitorOptions, t as PerformanceMonitor } from "./performance-monitor-CBN05_BC.js";
2
- import { t as MountPerfHudOptions } from "./mount-perf-hud-CmINOH3T.js";
1
+ import { s as PerformanceMonitorOptions, t as PerformanceMonitor } from "./performance-monitor-DgHxeQ2X.js";
2
+ import { t as MountPerfHudOptions } from "./mount-perf-hud-DkSNfZmB.js";
3
3
  //#region src/react/monitor-store.d.ts
4
4
  type MonitorListener = (monitor: PerformanceMonitor | null) => void;
5
5
  type PerfMonitorStore = {
@@ -20,12 +20,13 @@ type PerfHudProps = Omit<MountPerfHudOptions, "settings"> & {
20
20
  * as three objects. Mounts the dockable HUD whenever a `PerfSampler` has
21
21
  * published a monitor and tears it down when that sampler unmounts.
22
22
  *
23
- * `theme`, `mode` and `label` apply live through the handle. `defaultPlacement`
24
- * is compared by value, so an inline `{{ edge: "right" }}` literal is fine.
23
+ * `theme`, `mode`, `label` and `budgets` apply live through the handle.
24
+ * `defaultPlacement` and `budgets` are compared by value, so inline literals
25
+ * like `{{ edge: "right" }}` are fine.
25
26
  * The remaining props (`storageKey`, `parent`, `refreshHz`, `injectStyles`,
26
27
  * `store`) remount the HUD when they change.
27
28
  */
28
- declare function PerfHud({ defaultPlacement, injectStyles, label, mode, parent, refreshHz, storageKey, store, theme }: PerfHudProps): null;
29
+ declare function PerfHud({ budgets, defaultPlacement, injectStyles, label, mode, parent, refreshHz, storageKey, store, theme }: PerfHudProps): null;
29
30
  //#endregion
30
31
  //#region src/react/perf-sampler.d.ts
31
32
  type PerfSamplerProps = Omit<PerformanceMonitorOptions, "renderer"> & {
package/dist/react.js CHANGED
@@ -1,5 +1,5 @@
1
- import { t as PerformanceMonitor } from "./performance-monitor-yxsxs57v.js";
2
- import { t as mountPerfHud } from "./mount-perf-hud-C5wdjgmM.js";
1
+ import { t as PerformanceMonitor } from "./performance-monitor-DsBp13ts.js";
2
+ import { t as mountPerfHud } from "./mount-perf-hud-B_cmTG2M.js";
3
3
  import { useEffect, useRef } from "react";
4
4
  import { useFrame, useThree } from "@react-three/fiber";
5
5
  //#region src/react/monitor-store.ts
@@ -28,17 +28,20 @@ const defaultPerfMonitorStore = createPerfMonitorStore();
28
28
  * as three objects. Mounts the dockable HUD whenever a `PerfSampler` has
29
29
  * published a monitor and tears it down when that sampler unmounts.
30
30
  *
31
- * `theme`, `mode` and `label` apply live through the handle. `defaultPlacement`
32
- * is compared by value, so an inline `{{ edge: "right" }}` literal is fine.
31
+ * `theme`, `mode`, `label` and `budgets` apply live through the handle.
32
+ * `defaultPlacement` and `budgets` are compared by value, so inline literals
33
+ * like `{{ edge: "right" }}` are fine.
33
34
  * The remaining props (`storageKey`, `parent`, `refreshHz`, `injectStyles`,
34
35
  * `store`) remount the HUD when they change.
35
36
  */
36
- function PerfHud({ defaultPlacement, injectStyles, label, mode, parent, refreshHz, storageKey, store, theme }) {
37
+ function PerfHud({ budgets, defaultPlacement, injectStyles, label, mode, parent, refreshHz, storageKey, store, theme }) {
37
38
  const source = store ?? defaultPerfMonitorStore;
38
39
  const handleRef = useRef(null);
39
40
  const themeRef = useRef(theme);
40
41
  const modeRef = useRef(mode);
41
42
  const labelRef = useRef(label);
43
+ const budgetsRef = useRef(budgets);
44
+ const budgetsKey = JSON.stringify(budgets ?? null);
42
45
  const placementEdge = defaultPlacement?.edge;
43
46
  const placementAlign = defaultPlacement?.align;
44
47
  useEffect(() => {
@@ -47,6 +50,7 @@ function PerfHud({ defaultPlacement, injectStyles, label, mode, parent, refreshH
47
50
  handleRef.current = null;
48
51
  if (!monitor) return;
49
52
  handleRef.current = mountPerfHud(monitor, {
53
+ budgets: budgetsRef.current,
50
54
  defaultPlacement: placementEdge ? {
51
55
  align: placementAlign,
52
56
  edge: placementEdge
@@ -84,6 +88,11 @@ function PerfHud({ defaultPlacement, injectStyles, label, mode, parent, refreshH
84
88
  modeRef.current = mode;
85
89
  handleRef.current?.setMode(mode ?? "compact");
86
90
  }, [mode]);
91
+ useEffect(() => {
92
+ const next = JSON.parse(budgetsKey) ?? void 0;
93
+ budgetsRef.current = next;
94
+ handleRef.current?.setBudgets(next);
95
+ }, [budgetsKey]);
87
96
  useEffect(() => {
88
97
  labelRef.current = label;
89
98
  handleRef.current?.element.setAttribute("aria-label", label ?? "Performance");
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","names":[],"sources":["../src/react/monitor-store.ts","../src/react/perf-hud.tsx","../src/react/perf-sampler.tsx"],"sourcesContent":["/**\n * Hands the in-canvas monitor to the DOM-side HUD. A React context can't do\n * it: R3F's `<Canvas>` is its own reconciler root, so a provider outside it\n * isn't visible inside without a bridge. A tiny external store crosses that\n * boundary for free. One default store; pass your own to both components to\n * run two canvases on one page.\n */\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\n\ntype MonitorListener = (monitor: PerformanceMonitor | null) => void;\n\ntype PerfMonitorStore = {\n get: () => PerformanceMonitor | null;\n set: (monitor: PerformanceMonitor | null) => void;\n subscribe: (listener: MonitorListener) => () => void;\n};\n\nconst createPerfMonitorStore = (): PerfMonitorStore => {\n let current: PerformanceMonitor | null = null;\n const listeners = new Set<MonitorListener>();\n\n return {\n get: () => current,\n set: (monitor) => {\n current = monitor;\n\n for (const listener of listeners) {\n listener(monitor);\n }\n },\n subscribe: (listener) => {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n },\n };\n};\n\nconst defaultPerfMonitorStore = createPerfMonitorStore();\n\nexport { createPerfMonitorStore, defaultPerfMonitorStore };\nexport type { PerfMonitorStore };\n","import { useEffect, useRef } from \"react\";\n\nimport {\n mountPerfHud,\n type MountPerfHudOptions,\n type PerfHudHandle,\n} from \"../ui/mount-perf-hud.ts\";\nimport { defaultPerfMonitorStore, type PerfMonitorStore } from \"./monitor-store.ts\";\n\ntype PerfHudProps = Omit<MountPerfHudOptions, \"settings\"> & {\n /** Must match the `PerfSampler`'s. Default: the shared store. */\n store?: PerfMonitorStore;\n};\n\n/**\n * DOM overlay. Render it *outside* `<Canvas>`, or Fiber would treat its markup\n * as three objects. Mounts the dockable HUD whenever a `PerfSampler` has\n * published a monitor and tears it down when that sampler unmounts.\n *\n * `theme`, `mode` and `label` apply live through the handle. `defaultPlacement`\n * is compared by value, so an inline `{{ edge: \"right\" }}` literal is fine.\n * The remaining props (`storageKey`, `parent`, `refreshHz`, `injectStyles`,\n * `store`) remount the HUD when they change.\n */\nfunction PerfHud({\n defaultPlacement,\n injectStyles,\n label,\n mode,\n parent,\n refreshHz,\n storageKey,\n store,\n theme,\n}: PerfHudProps) {\n const source = store ?? defaultPerfMonitorStore;\n const handleRef = useRef<PerfHudHandle | null>(null);\n // The mount effect reads these through refs so changing them never remounts;\n // the live effects below keep the refs current and push changes to the handle.\n const themeRef = useRef(theme);\n const modeRef = useRef(mode);\n const labelRef = useRef(label);\n // Primitives, so an inline `{{ edge: \"right\" }}` literal doesn't remount each render.\n const placementEdge = defaultPlacement?.edge;\n const placementAlign = defaultPlacement?.align;\n\n useEffect(() => {\n const attach = (monitor: ReturnType<PerfMonitorStore[\"get\"]>) => {\n handleRef.current?.dispose();\n handleRef.current = null;\n\n if (!monitor) {\n return;\n }\n\n handleRef.current = mountPerfHud(monitor, {\n defaultPlacement: placementEdge\n ? { align: placementAlign, edge: placementEdge }\n : undefined,\n injectStyles,\n label: labelRef.current,\n mode: modeRef.current,\n parent,\n refreshHz,\n storageKey,\n theme: themeRef.current,\n });\n };\n\n const unsubscribe = source.subscribe(attach);\n attach(source.get());\n\n return () => {\n unsubscribe();\n handleRef.current?.dispose();\n handleRef.current = null;\n };\n }, [injectStyles, parent, placementAlign, placementEdge, refreshHz, source, storageKey]);\n\n useEffect(() => {\n themeRef.current = theme;\n handleRef.current?.setTheme(theme ?? \"system\");\n }, [theme]);\n\n useEffect(() => {\n modeRef.current = mode;\n handleRef.current?.setMode(mode ?? \"compact\");\n }, [mode]);\n\n useEffect(() => {\n labelRef.current = label;\n handleRef.current?.element.setAttribute(\"aria-label\", label ?? \"Performance\");\n }, [label]);\n\n return null;\n}\n\nexport { PerfHud };\nexport type { PerfHudProps };\n","import { useFrame, useThree } from \"@react-three/fiber\";\nimport { useEffect, useRef } from \"react\";\n\nimport { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport type { PerformanceMonitorOptions, PerfRenderer } from \"../core/types.ts\";\nimport { defaultPerfMonitorStore, type PerfMonitorStore } from \"./monitor-store.ts\";\n\ntype PerfSamplerProps = Omit<PerformanceMonitorOptions, \"renderer\"> & {\n /** Where the monitor is published for `PerfHud`. Default: the shared store. */\n store?: PerfMonitorStore;\n};\n\n/**\n * In-canvas collector. Renders nothing. Fiber runs every `useFrame` before\n * `gl.render`, so the previous frame is closed here and the next one opens, and\n * GPU queries then span the render that just ran. The monitor is created in\n * an effect so StrictMode's double mount attaches and detaches cleanly.\n */\nfunction PerfSampler({\n frameStatsSize,\n gpuQueryPoolSize,\n historySize,\n store,\n trackGPU,\n}: PerfSamplerProps) {\n const renderer = useThree((state) => state.gl);\n const monitorRef = useRef<PerformanceMonitor | null>(null);\n const startedRef = useRef(false);\n const target = store ?? defaultPerfMonitorStore;\n\n useEffect(() => {\n const monitor = new PerformanceMonitor({\n frameStatsSize,\n gpuQueryPoolSize,\n historySize,\n renderer: renderer as unknown as PerfRenderer,\n trackGPU,\n });\n monitorRef.current = monitor;\n startedRef.current = false;\n target.set(monitor);\n\n return () => {\n target.set(null);\n monitorRef.current = null;\n monitor.dispose();\n };\n }, [frameStatsSize, gpuQueryPoolSize, historySize, renderer, target, trackGPU]);\n\n useFrame(() => {\n const monitor = monitorRef.current;\n\n if (!monitor) {\n return;\n }\n\n if (startedRef.current) {\n monitor.end();\n }\n\n startedRef.current = true;\n monitor.begin();\n });\n\n return null;\n}\n\nexport { PerfSampler };\nexport type { PerfSamplerProps };\n"],"mappings":";;;;;AAiBA,MAAM,+BAAiD;CACrD,IAAI,UAAqC;CACzC,MAAM,4BAAY,IAAI,IAAqB;CAE3C,OAAO;EACL,WAAW;EACX,MAAM,YAAY;GAChB,UAAU;GAEV,KAAK,MAAM,YAAY,WACrB,SAAS,OAAO;EAEpB;EACA,YAAY,aAAa;GACvB,UAAU,IAAI,QAAQ;GAEtB,aAAa;IACX,UAAU,OAAO,QAAQ;GAC3B;EACF;CACF;AACF;AAEA,MAAM,0BAA0B,uBAAuB;;;;;;;;;;;;;AChBvD,SAAS,QAAQ,EACf,kBACA,cACA,OACA,MACA,QACA,WACA,YACA,OACA,SACe;CACf,MAAM,SAAS,SAAS;CACxB,MAAM,YAAY,OAA6B,IAAI;CAGnD,MAAM,WAAW,OAAO,KAAK;CAC7B,MAAM,UAAU,OAAO,IAAI;CAC3B,MAAM,WAAW,OAAO,KAAK;CAE7B,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,iBAAiB,kBAAkB;CAEzC,gBAAgB;EACd,MAAM,UAAU,YAAiD;GAC/D,UAAU,SAAS,QAAQ;GAC3B,UAAU,UAAU;GAEpB,IAAI,CAAC,SACH;GAGF,UAAU,UAAU,aAAa,SAAS;IACxC,kBAAkB,gBACd;KAAE,OAAO;KAAgB,MAAM;IAAc,IAC7C,KAAA;IACJ;IACA,OAAO,SAAS;IAChB,MAAM,QAAQ;IACd;IACA;IACA;IACA,OAAO,SAAS;GAClB,CAAC;EACH;EAEA,MAAM,cAAc,OAAO,UAAU,MAAM;EAC3C,OAAO,OAAO,IAAI,CAAC;EAEnB,aAAa;GACX,YAAY;GACZ,UAAU,SAAS,QAAQ;GAC3B,UAAU,UAAU;EACtB;CACF,GAAG;EAAC;EAAc;EAAQ;EAAgB;EAAe;EAAW;EAAQ;CAAU,CAAC;CAEvF,gBAAgB;EACd,SAAS,UAAU;EACnB,UAAU,SAAS,SAAS,SAAS,QAAQ;CAC/C,GAAG,CAAC,KAAK,CAAC;CAEV,gBAAgB;EACd,QAAQ,UAAU;EAClB,UAAU,SAAS,QAAQ,QAAQ,SAAS;CAC9C,GAAG,CAAC,IAAI,CAAC;CAET,gBAAgB;EACd,SAAS,UAAU;EACnB,UAAU,SAAS,QAAQ,aAAa,cAAc,SAAS,aAAa;CAC9E,GAAG,CAAC,KAAK,CAAC;CAEV,OAAO;AACT;;;;;;;;;AC7EA,SAAS,YAAY,EACnB,gBACA,kBACA,aACA,OACA,YACmB;CACnB,MAAM,WAAW,UAAU,UAAU,MAAM,EAAE;CAC7C,MAAM,aAAa,OAAkC,IAAI;CACzD,MAAM,aAAa,OAAO,KAAK;CAC/B,MAAM,SAAS,SAAS;CAExB,gBAAgB;EACd,MAAM,UAAU,IAAI,mBAAmB;GACrC;GACA;GACA;GACU;GACV;EACF,CAAC;EACD,WAAW,UAAU;EACrB,WAAW,UAAU;EACrB,OAAO,IAAI,OAAO;EAElB,aAAa;GACX,OAAO,IAAI,IAAI;GACf,WAAW,UAAU;GACrB,QAAQ,QAAQ;EAClB;CACF,GAAG;EAAC;EAAgB;EAAkB;EAAa;EAAU;EAAQ;CAAQ,CAAC;CAE9E,eAAe;EACb,MAAM,UAAU,WAAW;EAE3B,IAAI,CAAC,SACH;EAGF,IAAI,WAAW,SACb,QAAQ,IAAI;EAGd,WAAW,UAAU;EACrB,QAAQ,MAAM;CAChB,CAAC;CAED,OAAO;AACT"}
1
+ {"version":3,"file":"react.js","names":[],"sources":["../src/react/monitor-store.ts","../src/react/perf-hud.tsx","../src/react/perf-sampler.tsx"],"sourcesContent":["/**\n * Hands the in-canvas monitor to the DOM-side HUD. A React context can't do\n * it: R3F's `<Canvas>` is its own reconciler root, so a provider outside it\n * isn't visible inside without a bridge. A tiny external store crosses that\n * boundary for free. One default store; pass your own to both components to\n * run two canvases on one page.\n */\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\n\ntype MonitorListener = (monitor: PerformanceMonitor | null) => void;\n\ntype PerfMonitorStore = {\n get: () => PerformanceMonitor | null;\n set: (monitor: PerformanceMonitor | null) => void;\n subscribe: (listener: MonitorListener) => () => void;\n};\n\nconst createPerfMonitorStore = (): PerfMonitorStore => {\n let current: PerformanceMonitor | null = null;\n const listeners = new Set<MonitorListener>();\n\n return {\n get: () => current,\n set: (monitor) => {\n current = monitor;\n\n for (const listener of listeners) {\n listener(monitor);\n }\n },\n subscribe: (listener) => {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n },\n };\n};\n\nconst defaultPerfMonitorStore = createPerfMonitorStore();\n\nexport { createPerfMonitorStore, defaultPerfMonitorStore };\nexport type { PerfMonitorStore };\n","import { useEffect, useRef } from \"react\";\n\nimport type { Budgets } from \"../ui/budgets.ts\";\nimport {\n mountPerfHud,\n type MountPerfHudOptions,\n type PerfHudHandle,\n} from \"../ui/mount-perf-hud.ts\";\nimport { defaultPerfMonitorStore, type PerfMonitorStore } from \"./monitor-store.ts\";\n\ntype PerfHudProps = Omit<MountPerfHudOptions, \"settings\"> & {\n /** Must match the `PerfSampler`'s. Default: the shared store. */\n store?: PerfMonitorStore;\n};\n\n/**\n * DOM overlay. Render it *outside* `<Canvas>`, or Fiber would treat its markup\n * as three objects. Mounts the dockable HUD whenever a `PerfSampler` has\n * published a monitor and tears it down when that sampler unmounts.\n *\n * `theme`, `mode`, `label` and `budgets` apply live through the handle.\n * `defaultPlacement` and `budgets` are compared by value, so inline literals\n * like `{{ edge: \"right\" }}` are fine.\n * The remaining props (`storageKey`, `parent`, `refreshHz`, `injectStyles`,\n * `store`) remount the HUD when they change.\n */\nfunction PerfHud({\n budgets,\n defaultPlacement,\n injectStyles,\n label,\n mode,\n parent,\n refreshHz,\n storageKey,\n store,\n theme,\n}: PerfHudProps) {\n const source = store ?? defaultPerfMonitorStore;\n const handleRef = useRef<PerfHudHandle | null>(null);\n // The mount effect reads these through refs so changing them never remounts;\n // the live effects below keep the refs current and push changes to the handle.\n const themeRef = useRef(theme);\n const modeRef = useRef(mode);\n const labelRef = useRef(label);\n const budgetsRef = useRef(budgets);\n // By value: an inline `{{ calls: 500 }}` is a new object every render.\n const budgetsKey = JSON.stringify(budgets ?? null);\n // Primitives, so an inline `{{ edge: \"right\" }}` literal doesn't remount each render.\n const placementEdge = defaultPlacement?.edge;\n const placementAlign = defaultPlacement?.align;\n\n useEffect(() => {\n const attach = (monitor: ReturnType<PerfMonitorStore[\"get\"]>) => {\n handleRef.current?.dispose();\n handleRef.current = null;\n\n if (!monitor) {\n return;\n }\n\n handleRef.current = mountPerfHud(monitor, {\n budgets: budgetsRef.current,\n defaultPlacement: placementEdge\n ? { align: placementAlign, edge: placementEdge }\n : undefined,\n injectStyles,\n label: labelRef.current,\n mode: modeRef.current,\n parent,\n refreshHz,\n storageKey,\n theme: themeRef.current,\n });\n };\n\n const unsubscribe = source.subscribe(attach);\n attach(source.get());\n\n return () => {\n unsubscribe();\n handleRef.current?.dispose();\n handleRef.current = null;\n };\n }, [injectStyles, parent, placementAlign, placementEdge, refreshHz, source, storageKey]);\n\n useEffect(() => {\n themeRef.current = theme;\n handleRef.current?.setTheme(theme ?? \"system\");\n }, [theme]);\n\n useEffect(() => {\n modeRef.current = mode;\n handleRef.current?.setMode(mode ?? \"compact\");\n }, [mode]);\n\n useEffect(() => {\n // Read back from the key so the effect depends on the value, not the object.\n const next = (JSON.parse(budgetsKey) as Budgets | false | null) ?? undefined;\n budgetsRef.current = next;\n handleRef.current?.setBudgets(next);\n }, [budgetsKey]);\n\n useEffect(() => {\n labelRef.current = label;\n handleRef.current?.element.setAttribute(\"aria-label\", label ?? \"Performance\");\n }, [label]);\n\n return null;\n}\n\nexport { PerfHud };\nexport type { PerfHudProps };\n","import { useFrame, useThree } from \"@react-three/fiber\";\nimport { useEffect, useRef } from \"react\";\n\nimport { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport type { PerformanceMonitorOptions, PerfRenderer } from \"../core/types.ts\";\nimport { defaultPerfMonitorStore, type PerfMonitorStore } from \"./monitor-store.ts\";\n\ntype PerfSamplerProps = Omit<PerformanceMonitorOptions, \"renderer\"> & {\n /** Where the monitor is published for `PerfHud`. Default: the shared store. */\n store?: PerfMonitorStore;\n};\n\n/**\n * In-canvas collector. Renders nothing. Fiber runs every `useFrame` before\n * `gl.render`, so the previous frame is closed here and the next one opens, and\n * GPU queries then span the render that just ran. The monitor is created in\n * an effect so StrictMode's double mount attaches and detaches cleanly.\n */\nfunction PerfSampler({\n frameStatsSize,\n gpuQueryPoolSize,\n historySize,\n store,\n trackGPU,\n}: PerfSamplerProps) {\n const renderer = useThree((state) => state.gl);\n const monitorRef = useRef<PerformanceMonitor | null>(null);\n const startedRef = useRef(false);\n const target = store ?? defaultPerfMonitorStore;\n\n useEffect(() => {\n const monitor = new PerformanceMonitor({\n frameStatsSize,\n gpuQueryPoolSize,\n historySize,\n renderer: renderer as unknown as PerfRenderer,\n trackGPU,\n });\n monitorRef.current = monitor;\n startedRef.current = false;\n target.set(monitor);\n\n return () => {\n target.set(null);\n monitorRef.current = null;\n monitor.dispose();\n };\n }, [frameStatsSize, gpuQueryPoolSize, historySize, renderer, target, trackGPU]);\n\n useFrame(() => {\n const monitor = monitorRef.current;\n\n if (!monitor) {\n return;\n }\n\n if (startedRef.current) {\n monitor.end();\n }\n\n startedRef.current = true;\n monitor.begin();\n });\n\n return null;\n}\n\nexport { PerfSampler };\nexport type { PerfSamplerProps };\n"],"mappings":";;;;;AAiBA,MAAM,+BAAiD;CACrD,IAAI,UAAqC;CACzC,MAAM,4BAAY,IAAI,IAAqB;CAE3C,OAAO;EACL,WAAW;EACX,MAAM,YAAY;GAChB,UAAU;GAEV,KAAK,MAAM,YAAY,WACrB,SAAS,OAAO;EAEpB;EACA,YAAY,aAAa;GACvB,UAAU,IAAI,QAAQ;GAEtB,aAAa;IACX,UAAU,OAAO,QAAQ;GAC3B;EACF;CACF;AACF;AAEA,MAAM,0BAA0B,uBAAuB;;;;;;;;;;;;;;ACdvD,SAAS,QAAQ,EACf,SACA,kBACA,cACA,OACA,MACA,QACA,WACA,YACA,OACA,SACe;CACf,MAAM,SAAS,SAAS;CACxB,MAAM,YAAY,OAA6B,IAAI;CAGnD,MAAM,WAAW,OAAO,KAAK;CAC7B,MAAM,UAAU,OAAO,IAAI;CAC3B,MAAM,WAAW,OAAO,KAAK;CAC7B,MAAM,aAAa,OAAO,OAAO;CAEjC,MAAM,aAAa,KAAK,UAAU,WAAW,IAAI;CAEjD,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,iBAAiB,kBAAkB;CAEzC,gBAAgB;EACd,MAAM,UAAU,YAAiD;GAC/D,UAAU,SAAS,QAAQ;GAC3B,UAAU,UAAU;GAEpB,IAAI,CAAC,SACH;GAGF,UAAU,UAAU,aAAa,SAAS;IACxC,SAAS,WAAW;IACpB,kBAAkB,gBACd;KAAE,OAAO;KAAgB,MAAM;IAAc,IAC7C,KAAA;IACJ;IACA,OAAO,SAAS;IAChB,MAAM,QAAQ;IACd;IACA;IACA;IACA,OAAO,SAAS;GAClB,CAAC;EACH;EAEA,MAAM,cAAc,OAAO,UAAU,MAAM;EAC3C,OAAO,OAAO,IAAI,CAAC;EAEnB,aAAa;GACX,YAAY;GACZ,UAAU,SAAS,QAAQ;GAC3B,UAAU,UAAU;EACtB;CACF,GAAG;EAAC;EAAc;EAAQ;EAAgB;EAAe;EAAW;EAAQ;CAAU,CAAC;CAEvF,gBAAgB;EACd,SAAS,UAAU;EACnB,UAAU,SAAS,SAAS,SAAS,QAAQ;CAC/C,GAAG,CAAC,KAAK,CAAC;CAEV,gBAAgB;EACd,QAAQ,UAAU;EAClB,UAAU,SAAS,QAAQ,QAAQ,SAAS;CAC9C,GAAG,CAAC,IAAI,CAAC;CAET,gBAAgB;EAEd,MAAM,OAAQ,KAAK,MAAM,UAAU,KAAgC,KAAA;EACnE,WAAW,UAAU;EACrB,UAAU,SAAS,WAAW,IAAI;CACpC,GAAG,CAAC,UAAU,CAAC;CAEf,gBAAgB;EACd,SAAS,UAAU;EACnB,UAAU,SAAS,QAAQ,aAAa,cAAc,SAAS,aAAa;CAC9E,GAAG,CAAC,KAAK,CAAC;CAEV,OAAO;AACT;;;;;;;;;AC3FA,SAAS,YAAY,EACnB,gBACA,kBACA,aACA,OACA,YACmB;CACnB,MAAM,WAAW,UAAU,UAAU,MAAM,EAAE;CAC7C,MAAM,aAAa,OAAkC,IAAI;CACzD,MAAM,aAAa,OAAO,KAAK;CAC/B,MAAM,SAAS,SAAS;CAExB,gBAAgB;EACd,MAAM,UAAU,IAAI,mBAAmB;GACrC;GACA;GACA;GACU;GACV;EACF,CAAC;EACD,WAAW,UAAU;EACrB,WAAW,UAAU;EACrB,OAAO,IAAI,OAAO;EAElB,aAAa;GACX,OAAO,IAAI,IAAI;GACf,WAAW,UAAU;GACrB,QAAQ,QAAQ;EAClB;CACF,GAAG;EAAC;EAAgB;EAAkB;EAAa;EAAU;EAAQ;CAAQ,CAAC;CAE9E,eAAe;EACb,MAAM,UAAU,WAAW;EAE3B,IAAI,CAAC,SACH;EAGF,IAAI,WAAW,SACb,QAAQ,IAAI;EAGd,WAAW,UAAU;EACrB,QAAQ,MAAM;CAChB,CAAC;CAED,OAAO;AACT"}
package/dist/styles.css CHANGED
@@ -7,6 +7,7 @@
7
7
  --perf-row: rgba(255, 255, 255, 0.04);
8
8
  --perf-border: rgba(255, 255, 255, 0.12);
9
9
  --perf-accent: #60a5fa;
10
+ --perf-warn: #f59e0b;
10
11
  --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.32);
11
12
 
12
13
  color-scheme: dark;
@@ -21,6 +22,7 @@
21
22
  --perf-row: rgba(0, 0, 0, 0.05);
22
23
  --perf-border: rgba(0, 0, 0, 0.12);
23
24
  --perf-accent: #2563eb;
25
+ --perf-warn: #b45309;
24
26
  --perf-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
25
27
 
26
28
  color-scheme: light;
@@ -217,6 +219,96 @@
217
219
  outline-offset: 1px;
218
220
  }
219
221
 
222
+ /* Top costs: name, calls, triangles. Rows are buttons that log their objects. */
223
+ .perf-monitor__costs-body {
224
+ display: flex;
225
+ flex-direction: column;
226
+ gap: 1px;
227
+ padding-top: 2px;
228
+ }
229
+
230
+ .perf-monitor__costs-body[hidden] {
231
+ display: none;
232
+ }
233
+
234
+ .perf-monitor__cost {
235
+ /* Rows are buttons, which shrink to their content without a width. */
236
+ box-sizing: border-box;
237
+ width: 100%;
238
+ display: grid;
239
+ grid-template-columns: minmax(0, 1fr) 5ch 6ch;
240
+ gap: 6px;
241
+ align-items: baseline;
242
+ padding: 2px 6px;
243
+ border: 0;
244
+ border-radius: 3px;
245
+ background: transparent;
246
+ color: inherit;
247
+ font: inherit;
248
+ text-align: left;
249
+ cursor: pointer;
250
+ }
251
+
252
+ .perf-monitor__cost:not(.perf-monitor__cost--head):hover {
253
+ background: var(--perf-row);
254
+ }
255
+
256
+ .perf-monitor__cost:focus-visible {
257
+ outline: 1px solid color-mix(in srgb, var(--perf-accent) 60%, transparent);
258
+ outline-offset: -1px;
259
+ }
260
+
261
+ .perf-monitor__cost--head {
262
+ align-items: center;
263
+ cursor: default;
264
+ }
265
+
266
+ .perf-monitor__cost-name {
267
+ overflow: hidden;
268
+ text-overflow: ellipsis;
269
+ white-space: nowrap;
270
+ color: var(--perf-fg-dim);
271
+ }
272
+
273
+ .perf-monitor__cost-copies {
274
+ color: var(--perf-muted);
275
+ }
276
+
277
+ .perf-monitor__cost-value {
278
+ font-variant-numeric: tabular-nums lining-nums;
279
+ text-align: right;
280
+ }
281
+
282
+ .perf-monitor__cost-sort {
283
+ padding: 0;
284
+ border: 0;
285
+ background: transparent;
286
+ color: var(--perf-muted);
287
+ font: inherit;
288
+ font-size: 10px;
289
+ text-align: right;
290
+ cursor: pointer;
291
+ }
292
+
293
+ .perf-monitor__cost-sort[aria-pressed="true"] {
294
+ color: var(--perf-fg);
295
+ text-decoration: underline;
296
+ text-underline-offset: 2px;
297
+ }
298
+
299
+ .perf-monitor__segment-option--text {
300
+ width: auto;
301
+ padding: 0 5px;
302
+ font: inherit;
303
+ font-size: 10px;
304
+ }
305
+
306
+ .perf-monitor__costs-note {
307
+ padding: 2px 6px;
308
+ color: var(--perf-muted);
309
+ font-size: 10px;
310
+ }
311
+
220
312
  .perf-monitor__checkbox {
221
313
  flex: none;
222
314
  margin: 0;
@@ -332,13 +424,15 @@
332
424
  }
333
425
 
334
426
  .perf-monitor__badge.is-fallback {
335
- border-color: #f59e0b;
336
- color: #f59e0b;
427
+ border-color: var(--perf-warn);
428
+ color: var(--perf-warn);
337
429
  }
338
430
 
339
- .perf-monitor[data-theme="light"] .perf-monitor__badge.is-fallback {
340
- border-color: #d97706;
341
- color: #b45309;
431
+ /* Past its budget. The value's tooltip names the budget. */
432
+ .perf-monitor__value.is-over,
433
+ .perf-monitor__hud-value.is-over,
434
+ .perf-monitor__graph-value.is-over {
435
+ color: var(--perf-warn);
342
436
  }
343
437
 
344
438
  .perf-monitor__link {