@aura3d/debug 1.4.0 → 1.4.2
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.
- package/package.json +37 -5
- package/src/AnimationInspector.ts +0 -71
- package/src/ChromeTraceExporter.ts +0 -158
- package/src/DebugLineCanvasRenderer.ts +0 -96
- package/src/DebugOverlay.ts +0 -35
- package/src/DrawCallTracker.ts +0 -46
- package/src/ECSInspector.ts +0 -23
- package/src/GPUProfiler.ts +0 -48
- package/src/MaterialDiagnostics.ts +0 -59
- package/src/PhysicsDebugAdapter.ts +0 -86
- package/src/Profiler.ts +0 -77
- package/src/RenderStateInspector.ts +0 -45
- package/src/ReportExporter.ts +0 -32
- package/src/ResourceTracker.ts +0 -59
- package/src/SceneHelpers.ts +0 -188
- package/src/ShaderDiagnostics.ts +0 -64
- package/src/index.ts +0 -46
- package/tsconfig.json +0 -32
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aura3d/debug",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@aura3d/animation": "1.4.
|
|
7
|
-
"@aura3d/
|
|
8
|
-
"@aura3d/
|
|
6
|
+
"@aura3d/animation": "1.4.2",
|
|
7
|
+
"@aura3d/physics": "1.4.2",
|
|
8
|
+
"@aura3d/rendering": "1.4.2"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
@@ -14,5 +14,37 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"main": "./dist/index.js",
|
|
17
|
-
"types": "./dist/index.d.ts"
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"description": "Diagnostics helpers for rendering, materials, shaders, physics, animation, ECS, profiling, resource leaks, overlays, and report export.",
|
|
19
|
+
"keywords": [
|
|
20
|
+
"aura3d",
|
|
21
|
+
"3d",
|
|
22
|
+
"browser-3d",
|
|
23
|
+
"web-3d",
|
|
24
|
+
"typescript",
|
|
25
|
+
"debug",
|
|
26
|
+
"diagnostics",
|
|
27
|
+
"profiler",
|
|
28
|
+
"render-debug",
|
|
29
|
+
"shader-debug",
|
|
30
|
+
"overlays",
|
|
31
|
+
"telemetry"
|
|
32
|
+
],
|
|
33
|
+
"homepage": "https://aura3d.auraone.ai",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/auraoneai/aura3d.git",
|
|
37
|
+
"directory": "packages/debug"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/auraoneai/aura3d/issues"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"README.md",
|
|
48
|
+
"dist"
|
|
49
|
+
]
|
|
18
50
|
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { AnimationMixer, AnimationMixerSnapshot, Skeleton } from "@aura3d/animation";
|
|
2
|
-
|
|
3
|
-
export type SkeletonDebugSnapshot = {
|
|
4
|
-
readonly boneCount: number;
|
|
5
|
-
readonly boneNames: readonly string[];
|
|
6
|
-
readonly paletteMatrixCount: number;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
export type AnimationDebugSnapshot = {
|
|
10
|
-
readonly mixer: AnimationMixerSnapshot;
|
|
11
|
-
readonly skeleton?: SkeletonDebugSnapshot;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type AnimationVisualEvidence = {
|
|
15
|
-
readonly actionCount: number;
|
|
16
|
-
readonly playingActionCount: number;
|
|
17
|
-
readonly sampledTargetCount: number;
|
|
18
|
-
readonly sampledTargets: readonly string[];
|
|
19
|
-
readonly skeletonBoneCount: number;
|
|
20
|
-
readonly paletteMatrixCount: number;
|
|
21
|
-
readonly paletteHash?: string;
|
|
22
|
-
readonly stableHash: string;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export class AnimationInspector {
|
|
26
|
-
snapshot(mixer: Pick<AnimationMixer, "snapshot">, skeleton?: Skeleton): AnimationDebugSnapshot {
|
|
27
|
-
const snapshot: AnimationDebugSnapshot = {
|
|
28
|
-
mixer: mixer.snapshot()
|
|
29
|
-
};
|
|
30
|
-
if (skeleton) {
|
|
31
|
-
return {
|
|
32
|
-
...snapshot,
|
|
33
|
-
skeleton: {
|
|
34
|
-
boneCount: skeleton.bones.length,
|
|
35
|
-
boneNames: skeleton.bones.map((bone) => bone.name),
|
|
36
|
-
paletteMatrixCount: skeleton.matrixPalette().length
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
return snapshot;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
visualEvidence(mixer: Pick<AnimationMixer, "snapshot">, skeleton?: Skeleton): AnimationVisualEvidence {
|
|
44
|
-
const snapshot = mixer.snapshot();
|
|
45
|
-
const sampledTargets = Object.keys(snapshot.values).sort();
|
|
46
|
-
const palette = skeleton?.matrixPalette();
|
|
47
|
-
const evidenceWithoutHash = {
|
|
48
|
-
actionCount: snapshot.actionCount,
|
|
49
|
-
playingActionCount: snapshot.actions.filter((action) => action.playing).length,
|
|
50
|
-
sampledTargetCount: sampledTargets.length,
|
|
51
|
-
sampledTargets,
|
|
52
|
-
skeletonBoneCount: skeleton?.bones.length ?? 0,
|
|
53
|
-
paletteMatrixCount: palette?.length ?? 0,
|
|
54
|
-
...(palette ? { paletteHash: stableHash(palette.map((matrix) => matrix.map((value) => Number(value.toFixed(6))))) } : {})
|
|
55
|
-
};
|
|
56
|
-
return {
|
|
57
|
-
...evidenceWithoutHash,
|
|
58
|
-
stableHash: stableHash(evidenceWithoutHash)
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function stableHash(value: unknown): string {
|
|
64
|
-
const input = JSON.stringify(value);
|
|
65
|
-
let hash = 2166136261;
|
|
66
|
-
for (let index = 0; index < input.length; index += 1) {
|
|
67
|
-
hash ^= input.charCodeAt(index);
|
|
68
|
-
hash = Math.imul(hash, 16777619);
|
|
69
|
-
}
|
|
70
|
-
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
71
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import type { ProfilerMarker, ProfilerSnapshot } from "./Profiler.js";
|
|
2
|
-
import type { GPUSample, GPUProfilerSnapshot } from "./GPUProfiler.js";
|
|
3
|
-
|
|
4
|
-
export interface ChromeTraceEvent {
|
|
5
|
-
readonly name: string;
|
|
6
|
-
readonly cat: string;
|
|
7
|
-
readonly ph: "M" | "X" | "C" | "i";
|
|
8
|
-
readonly ts: number;
|
|
9
|
-
readonly pid: number;
|
|
10
|
-
readonly tid: number;
|
|
11
|
-
readonly dur?: number;
|
|
12
|
-
readonly args?: Record<string, number | string | boolean | null>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ChromeTrace {
|
|
16
|
-
readonly traceEvents: readonly ChromeTraceEvent[];
|
|
17
|
-
readonly displayTimeUnit: "ms";
|
|
18
|
-
readonly metadata: {
|
|
19
|
-
readonly source: "aura3d-debug-chrome-trace-exporter";
|
|
20
|
-
readonly profileName: string;
|
|
21
|
-
readonly cpuMarkerCount: number;
|
|
22
|
-
readonly gpuSampleCount: number;
|
|
23
|
-
readonly claimBoundary: string;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface ChromeTraceExportOptions {
|
|
28
|
-
readonly profileName?: string;
|
|
29
|
-
readonly processId?: number;
|
|
30
|
-
readonly cpuThreadId?: number;
|
|
31
|
-
readonly gpuThreadId?: number;
|
|
32
|
-
readonly includeCounters?: boolean;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export class ChromeTraceExporter {
|
|
36
|
-
static create(
|
|
37
|
-
cpu: ProfilerSnapshot,
|
|
38
|
-
gpu?: GPUProfilerSnapshot,
|
|
39
|
-
options: ChromeTraceExportOptions = {}
|
|
40
|
-
): ChromeTrace {
|
|
41
|
-
const profileName = options.profileName?.trim() || "Aura3D profile";
|
|
42
|
-
const processId = positiveInteger(options.processId, 1);
|
|
43
|
-
const cpuThreadId = positiveInteger(options.cpuThreadId, 1);
|
|
44
|
-
const gpuThreadId = positiveInteger(options.gpuThreadId, 2);
|
|
45
|
-
const events: ChromeTraceEvent[] = [
|
|
46
|
-
metadataEvent("process_name", processId, cpuThreadId, { name: "Aura3D" }),
|
|
47
|
-
metadataEvent("thread_name", processId, cpuThreadId, { name: "CPU" }),
|
|
48
|
-
metadataEvent("thread_name", processId, gpuThreadId, { name: "GPU" })
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
for (const marker of cpu.markers) {
|
|
52
|
-
events.push(cpuMarkerEvent(marker, processId, cpuThreadId));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (gpu) {
|
|
56
|
-
for (const sample of gpu.samples) {
|
|
57
|
-
events.push(gpuSampleEvent(sample, processId, gpuThreadId));
|
|
58
|
-
}
|
|
59
|
-
if (gpu.supported === false) {
|
|
60
|
-
events.push({
|
|
61
|
-
name: "gpu_timing_unavailable",
|
|
62
|
-
cat: "gpu",
|
|
63
|
-
ph: "i",
|
|
64
|
-
ts: 0,
|
|
65
|
-
pid: processId,
|
|
66
|
-
tid: gpuThreadId,
|
|
67
|
-
args: { reason: gpu.unavailableReason ?? "GPU timing unavailable" }
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (options.includeCounters !== false) {
|
|
73
|
-
events.push({
|
|
74
|
-
name: "profile_counters",
|
|
75
|
-
cat: "counters",
|
|
76
|
-
ph: "C",
|
|
77
|
-
ts: 0,
|
|
78
|
-
pid: processId,
|
|
79
|
-
tid: cpuThreadId,
|
|
80
|
-
args: {
|
|
81
|
-
cpuMarkerCount: cpu.markerCount,
|
|
82
|
-
cpuTotalDurationMs: fixed(cpu.totalDurationMs),
|
|
83
|
-
gpuSampleCount: gpu?.sampleCount ?? 0,
|
|
84
|
-
gpuSupported: gpu?.supported ?? false
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
traceEvents: events,
|
|
91
|
-
displayTimeUnit: "ms",
|
|
92
|
-
metadata: {
|
|
93
|
-
source: "aura3d-debug-chrome-trace-exporter",
|
|
94
|
-
profileName,
|
|
95
|
-
cpuMarkerCount: cpu.markerCount,
|
|
96
|
-
gpuSampleCount: gpu?.sampleCount ?? 0,
|
|
97
|
-
claimBoundary: "Chrome trace export is bounded local profiling evidence; it is not Unity/Unreal profiler parity."
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
static toJson(trace: ChromeTrace): string {
|
|
103
|
-
return `${JSON.stringify(trace, null, 2)}\n`;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function metadataEvent(name: "process_name" | "thread_name", pid: number, tid: number, args: { readonly name: string }): ChromeTraceEvent {
|
|
108
|
-
return {
|
|
109
|
-
name,
|
|
110
|
-
cat: "__metadata",
|
|
111
|
-
ph: "M",
|
|
112
|
-
ts: 0,
|
|
113
|
-
pid,
|
|
114
|
-
tid,
|
|
115
|
-
args
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function cpuMarkerEvent(marker: ProfilerMarker, pid: number, tid: number): ChromeTraceEvent {
|
|
120
|
-
return {
|
|
121
|
-
name: marker.name,
|
|
122
|
-
cat: "cpu",
|
|
123
|
-
ph: "X",
|
|
124
|
-
ts: millisecondsToMicroseconds(marker.startMs),
|
|
125
|
-
dur: millisecondsToMicroseconds(marker.durationMs),
|
|
126
|
-
pid,
|
|
127
|
-
tid,
|
|
128
|
-
args: {
|
|
129
|
-
depth: marker.depth,
|
|
130
|
-
endMs: fixed(marker.endMs)
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function gpuSampleEvent(sample: GPUSample, pid: number, tid: number): ChromeTraceEvent {
|
|
136
|
-
return {
|
|
137
|
-
name: sample.label,
|
|
138
|
-
cat: "gpu",
|
|
139
|
-
ph: "X",
|
|
140
|
-
ts: 0,
|
|
141
|
-
dur: millisecondsToMicroseconds(sample.durationMs),
|
|
142
|
-
pid,
|
|
143
|
-
tid,
|
|
144
|
-
args: { durationMs: fixed(sample.durationMs) }
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function millisecondsToMicroseconds(value: number): number {
|
|
149
|
-
return Math.max(0, Math.round(value * 1_000));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function positiveInteger(value: number | undefined, fallback: number): number {
|
|
153
|
-
return Number.isInteger(value) && Number(value) > 0 ? Number(value) : fallback;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function fixed(value: number): number {
|
|
157
|
-
return Number(value.toFixed(4));
|
|
158
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
export interface DebugRenderLine {
|
|
2
|
-
readonly from: readonly [number, number, number];
|
|
3
|
-
readonly to: readonly [number, number, number];
|
|
4
|
-
readonly color: readonly [number, number, number, number];
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface DebugLineCanvasRendererOptions {
|
|
8
|
-
readonly scale?: number;
|
|
9
|
-
readonly origin?: readonly [number, number];
|
|
10
|
-
readonly axis?: "xy" | "xz";
|
|
11
|
-
readonly lineWidth?: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface DebugLineCanvasRenderResult {
|
|
15
|
-
readonly lineCount: number;
|
|
16
|
-
readonly bounds: {
|
|
17
|
-
readonly minX: number;
|
|
18
|
-
readonly minY: number;
|
|
19
|
-
readonly maxX: number;
|
|
20
|
-
readonly maxY: number;
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class DebugLineCanvasRenderer {
|
|
25
|
-
render(
|
|
26
|
-
context: CanvasRenderingContext2D,
|
|
27
|
-
lines: readonly DebugRenderLine[],
|
|
28
|
-
options: DebugLineCanvasRendererOptions = {}
|
|
29
|
-
): DebugLineCanvasRenderResult {
|
|
30
|
-
const scale = options.scale ?? 1;
|
|
31
|
-
if (!Number.isFinite(scale) || scale <= 0) {
|
|
32
|
-
throw new RangeError("Debug line canvas scale must be a finite positive number.");
|
|
33
|
-
}
|
|
34
|
-
const origin = options.origin ?? [0, 0];
|
|
35
|
-
const axis = options.axis ?? "xy";
|
|
36
|
-
const lineWidth = options.lineWidth ?? 1;
|
|
37
|
-
if (!Number.isFinite(lineWidth) || lineWidth <= 0) {
|
|
38
|
-
throw new RangeError("Debug line canvas width must be a finite positive number.");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const bounds = {
|
|
42
|
-
minX: Number.POSITIVE_INFINITY,
|
|
43
|
-
minY: Number.POSITIVE_INFINITY,
|
|
44
|
-
maxX: Number.NEGATIVE_INFINITY,
|
|
45
|
-
maxY: Number.NEGATIVE_INFINITY
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
context.save();
|
|
49
|
-
context.lineWidth = lineWidth;
|
|
50
|
-
for (const line of lines) {
|
|
51
|
-
const from = project(line.from, axis, scale, origin);
|
|
52
|
-
const to = project(line.to, axis, scale, origin);
|
|
53
|
-
include(bounds, from);
|
|
54
|
-
include(bounds, to);
|
|
55
|
-
context.strokeStyle = rgba(line.color);
|
|
56
|
-
context.beginPath();
|
|
57
|
-
context.moveTo(from[0], from[1]);
|
|
58
|
-
context.lineTo(to[0], to[1]);
|
|
59
|
-
context.stroke();
|
|
60
|
-
}
|
|
61
|
-
context.restore();
|
|
62
|
-
|
|
63
|
-
if (lines.length === 0) {
|
|
64
|
-
return { lineCount: 0, bounds: { minX: 0, minY: 0, maxX: 0, maxY: 0 } };
|
|
65
|
-
}
|
|
66
|
-
return { lineCount: lines.length, bounds };
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function project(
|
|
71
|
-
point: readonly [number, number, number],
|
|
72
|
-
axis: "xy" | "xz",
|
|
73
|
-
scale: number,
|
|
74
|
-
origin: readonly [number, number]
|
|
75
|
-
): readonly [number, number] {
|
|
76
|
-
const y = axis === "xy" ? point[1] : point[2];
|
|
77
|
-
return [origin[0] + point[0] * scale, origin[1] - y * scale];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function include(bounds: { minX: number; minY: number; maxX: number; maxY: number }, point: readonly [number, number]): void {
|
|
81
|
-
bounds.minX = Math.min(bounds.minX, point[0]);
|
|
82
|
-
bounds.minY = Math.min(bounds.minY, point[1]);
|
|
83
|
-
bounds.maxX = Math.max(bounds.maxX, point[0]);
|
|
84
|
-
bounds.maxY = Math.max(bounds.maxY, point[1]);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function rgba(color: readonly [number, number, number, number]): string {
|
|
88
|
-
const r = Math.round(clamp01(color[0]) * 255);
|
|
89
|
-
const g = Math.round(clamp01(color[1]) * 255);
|
|
90
|
-
const b = Math.round(clamp01(color[2]) * 255);
|
|
91
|
-
return `rgba(${r}, ${g}, ${b}, ${clamp01(color[3])})`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function clamp01(value: number): number {
|
|
95
|
-
return Math.min(1, Math.max(0, Number.isFinite(value) ? value : 0));
|
|
96
|
-
}
|
package/src/DebugOverlay.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export interface DebugOverlaySection {
|
|
2
|
-
readonly title: string;
|
|
3
|
-
readonly rows: readonly DebugOverlayRow[];
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface DebugOverlayRow {
|
|
7
|
-
readonly label: string;
|
|
8
|
-
readonly value: string | number | boolean | null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface DebugOverlaySnapshot {
|
|
12
|
-
readonly sections: readonly DebugOverlaySection[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class DebugOverlay {
|
|
16
|
-
private readonly sections = new Map<string, DebugOverlayRow[]>();
|
|
17
|
-
|
|
18
|
-
setSection(title: string, rows: readonly DebugOverlayRow[]): void {
|
|
19
|
-
const trimmed = title.trim();
|
|
20
|
-
if (trimmed.length === 0) {
|
|
21
|
-
throw new Error("Debug overlay section title is required");
|
|
22
|
-
}
|
|
23
|
-
this.sections.set(trimmed, rows.map((row) => ({ ...row })));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
removeSection(title: string): void {
|
|
27
|
-
this.sections.delete(title);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
snapshot(): DebugOverlaySnapshot {
|
|
31
|
-
return {
|
|
32
|
-
sections: [...this.sections.entries()].map(([title, rows]) => ({ title, rows: [...rows] }))
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
}
|
package/src/DrawCallTracker.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { type DrawCommand, type RenderDeviceDiagnostics } from "@aura3d/rendering";
|
|
2
|
-
|
|
3
|
-
export interface DrawCallRecord {
|
|
4
|
-
readonly label: string;
|
|
5
|
-
readonly topology: string;
|
|
6
|
-
readonly indexed: boolean;
|
|
7
|
-
readonly count: number;
|
|
8
|
-
readonly shader: string | null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface DrawCallSnapshot {
|
|
12
|
-
readonly frame: number;
|
|
13
|
-
readonly total: number;
|
|
14
|
-
readonly records: readonly DrawCallRecord[];
|
|
15
|
-
readonly zeroDrawCallFailure: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class DrawCallTracker {
|
|
19
|
-
private frame = 0;
|
|
20
|
-
private readonly records: DrawCallRecord[] = [];
|
|
21
|
-
|
|
22
|
-
beginFrame(): void {
|
|
23
|
-
this.frame += 1;
|
|
24
|
-
this.records.length = 0;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
record(command: DrawCommand): void {
|
|
28
|
-
this.records.push({
|
|
29
|
-
label: command.label ?? "draw",
|
|
30
|
-
topology: command.topology,
|
|
31
|
-
indexed: command.indexBuffer !== undefined,
|
|
32
|
-
count: command.indexCount ?? command.vertexCount,
|
|
33
|
-
shader: command.shader?.label ?? null
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
capture(deviceDiagnostics?: RenderDeviceDiagnostics): DrawCallSnapshot {
|
|
38
|
-
const total = deviceDiagnostics?.drawCalls ?? this.records.length;
|
|
39
|
-
return {
|
|
40
|
-
frame: this.frame,
|
|
41
|
-
total,
|
|
42
|
-
records: [...this.records],
|
|
43
|
-
zeroDrawCallFailure: total === 0
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
}
|
package/src/ECSInspector.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export interface ECSWorldLike {
|
|
2
|
-
entities?(): readonly unknown[];
|
|
3
|
-
query?(...args: unknown[]): Iterable<unknown>;
|
|
4
|
-
readonly entityCount?: number;
|
|
5
|
-
profiler?: { snapshot(): unknown };
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface ECSInspectorSnapshot {
|
|
9
|
-
readonly entityCount: number;
|
|
10
|
-
readonly profiler?: unknown;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class ECSInspector {
|
|
14
|
-
snapshot(world: ECSWorldLike): ECSInspectorSnapshot {
|
|
15
|
-
const entityCount = typeof world.entityCount === "number"
|
|
16
|
-
? world.entityCount
|
|
17
|
-
: world.entities?.().length ?? 0;
|
|
18
|
-
return {
|
|
19
|
-
entityCount,
|
|
20
|
-
...(world.profiler ? { profiler: world.profiler.snapshot() } : {})
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
}
|
package/src/GPUProfiler.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export interface GPUProfilerSnapshot {
|
|
2
|
-
readonly supported: boolean;
|
|
3
|
-
readonly sampleCount: number;
|
|
4
|
-
readonly samples: readonly GPUSample[];
|
|
5
|
-
readonly unavailableReason?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface GPUSample {
|
|
9
|
-
readonly label: string;
|
|
10
|
-
readonly durationMs: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface GPUProfilerTimer {
|
|
14
|
-
end(durationMs?: number): GPUSample;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class GPUProfiler {
|
|
18
|
-
private readonly samples: GPUSample[] = [];
|
|
19
|
-
|
|
20
|
-
constructor(
|
|
21
|
-
private readonly supported = false,
|
|
22
|
-
private readonly unavailableReason = "GPU timing extension unavailable"
|
|
23
|
-
) {}
|
|
24
|
-
|
|
25
|
-
begin(label: string): GPUProfilerTimer {
|
|
26
|
-
const trimmed = label.trim();
|
|
27
|
-
if (trimmed.length === 0) {
|
|
28
|
-
throw new Error("GPU profiler sample label is required");
|
|
29
|
-
}
|
|
30
|
-
const start = performance.now();
|
|
31
|
-
return {
|
|
32
|
-
end: (durationMs = performance.now() - start) => {
|
|
33
|
-
const sample = { label: trimmed, durationMs };
|
|
34
|
-
this.samples.push(sample);
|
|
35
|
-
return sample;
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
snapshot(): GPUProfilerSnapshot {
|
|
41
|
-
return {
|
|
42
|
-
supported: this.supported,
|
|
43
|
-
sampleCount: this.samples.length,
|
|
44
|
-
samples: [...this.samples],
|
|
45
|
-
...(this.supported ? {} : { unavailableReason: this.unavailableReason })
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MaterialBinding,
|
|
3
|
-
MaterialBindingError,
|
|
4
|
-
type Material,
|
|
5
|
-
type MaterialInstance,
|
|
6
|
-
type RenderShaderProgram
|
|
7
|
-
} from "@aura3d/rendering";
|
|
8
|
-
|
|
9
|
-
export interface MaterialDiagnosticReport {
|
|
10
|
-
readonly materialName: string;
|
|
11
|
-
readonly shaderLabel: string;
|
|
12
|
-
readonly valid: boolean;
|
|
13
|
-
readonly diagnostics: readonly string[];
|
|
14
|
-
readonly uniforms: readonly string[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class MaterialDiagnostics {
|
|
18
|
-
inspect(material: Material | MaterialInstance, shader: RenderShaderProgram): MaterialDiagnosticReport {
|
|
19
|
-
const binding = new MaterialBinding();
|
|
20
|
-
const materialName = "baseMaterial" in material ? material.baseMaterial.name : material.name;
|
|
21
|
-
try {
|
|
22
|
-
const result = binding.bind(material, shader);
|
|
23
|
-
return {
|
|
24
|
-
materialName,
|
|
25
|
-
shaderLabel: shader.label,
|
|
26
|
-
valid: true,
|
|
27
|
-
diagnostics: [],
|
|
28
|
-
uniforms: [...result.uniforms.keys()]
|
|
29
|
-
};
|
|
30
|
-
} catch (error) {
|
|
31
|
-
if (error instanceof MaterialBindingError) {
|
|
32
|
-
return {
|
|
33
|
-
materialName,
|
|
34
|
-
shaderLabel: shader.label,
|
|
35
|
-
valid: false,
|
|
36
|
-
diagnostics: error.diagnostics,
|
|
37
|
-
uniforms: []
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
throw error;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
assertValid(report: MaterialDiagnosticReport): void {
|
|
45
|
-
if (!report.valid) {
|
|
46
|
-
throw new MaterialDiagnosticError("Material diagnostics reported invalid binding", report);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export class MaterialDiagnosticError extends Error {
|
|
52
|
-
constructor(
|
|
53
|
-
message: string,
|
|
54
|
-
public readonly report: MaterialDiagnosticReport
|
|
55
|
-
) {
|
|
56
|
-
super(message);
|
|
57
|
-
this.name = "MaterialDiagnosticError";
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { PhysicsDebugDraw, type DebugLine, type PhysicsWorld } from "@aura3d/physics";
|
|
2
|
-
|
|
3
|
-
export type PhysicsDebugSnapshot = {
|
|
4
|
-
readonly bodyCount: number;
|
|
5
|
-
readonly colliderCount: number;
|
|
6
|
-
readonly contactCount: number;
|
|
7
|
-
readonly lines: readonly DebugLine[];
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type PhysicsStackEvidence = {
|
|
11
|
-
readonly bodyCount: number;
|
|
12
|
-
readonly dynamicBodyCount: number;
|
|
13
|
-
readonly contactCount: number;
|
|
14
|
-
readonly lineCount: number;
|
|
15
|
-
readonly minY: number;
|
|
16
|
-
readonly maxY: number;
|
|
17
|
-
readonly settledCount: number;
|
|
18
|
-
readonly bodyPositions: readonly {
|
|
19
|
-
readonly id: number;
|
|
20
|
-
readonly type: string;
|
|
21
|
-
readonly position: readonly [number, number, number];
|
|
22
|
-
}[];
|
|
23
|
-
readonly stableHash: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export class PhysicsDebugAdapter {
|
|
27
|
-
private readonly draw = new PhysicsDebugDraw();
|
|
28
|
-
|
|
29
|
-
buildLines(world: Pick<PhysicsWorld, "colliders" | "getBody">): readonly DebugLine[] {
|
|
30
|
-
return this.draw.buildLines(world);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
snapshot(world: Pick<PhysicsWorld, "colliders" | "bodies" | "getBody" | "snapshot">): PhysicsDebugSnapshot {
|
|
34
|
-
return {
|
|
35
|
-
bodyCount: world.bodies().length,
|
|
36
|
-
colliderCount: world.colliders().length,
|
|
37
|
-
contactCount: world.snapshot().contacts.length,
|
|
38
|
-
lines: this.buildLines(world)
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
stackEvidence(world: Pick<PhysicsWorld, "colliders" | "bodies" | "getBody" | "snapshot">, velocityEpsilon = 0.05): PhysicsStackEvidence {
|
|
43
|
-
const snapshot = world.snapshot();
|
|
44
|
-
const lines = this.buildLines(world);
|
|
45
|
-
const bodyPositions = snapshot.bodies.map((body) => ({
|
|
46
|
-
id: body.id,
|
|
47
|
-
type: body.type,
|
|
48
|
-
position: roundVec3(body.position)
|
|
49
|
-
}));
|
|
50
|
-
const dynamicBodies = snapshot.bodies.filter((body) => body.type === "dynamic");
|
|
51
|
-
const settledCount = dynamicBodies.filter((body) => Math.hypot(body.velocity[0], body.velocity[1], body.velocity[2]) <= velocityEpsilon).length;
|
|
52
|
-
const ys = snapshot.bodies.map((body) => body.position[1]);
|
|
53
|
-
const evidenceWithoutHash = {
|
|
54
|
-
bodyCount: snapshot.stats.bodies,
|
|
55
|
-
dynamicBodyCount: dynamicBodies.length,
|
|
56
|
-
contactCount: snapshot.contacts.length,
|
|
57
|
-
lineCount: lines.length,
|
|
58
|
-
minY: roundNumber(ys.length > 0 ? Math.min(...ys) : 0),
|
|
59
|
-
maxY: roundNumber(ys.length > 0 ? Math.max(...ys) : 0),
|
|
60
|
-
settledCount,
|
|
61
|
-
bodyPositions
|
|
62
|
-
};
|
|
63
|
-
return {
|
|
64
|
-
...evidenceWithoutHash,
|
|
65
|
-
stableHash: stableHash(evidenceWithoutHash)
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function roundNumber(value: number, places = 6): number {
|
|
71
|
-
return Number(value.toFixed(places));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function roundVec3(value: readonly [number, number, number]): [number, number, number] {
|
|
75
|
-
return [roundNumber(value[0]), roundNumber(value[1]), roundNumber(value[2])];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function stableHash(value: unknown): string {
|
|
79
|
-
const input = JSON.stringify(value);
|
|
80
|
-
let hash = 2166136261;
|
|
81
|
-
for (let index = 0; index < input.length; index += 1) {
|
|
82
|
-
hash ^= input.charCodeAt(index);
|
|
83
|
-
hash = Math.imul(hash, 16777619);
|
|
84
|
-
}
|
|
85
|
-
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
86
|
-
}
|
package/src/Profiler.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
export interface ProfilerMarker {
|
|
2
|
-
readonly name: string;
|
|
3
|
-
readonly startMs: number;
|
|
4
|
-
readonly endMs: number;
|
|
5
|
-
readonly durationMs: number;
|
|
6
|
-
readonly depth: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ProfilerSnapshot {
|
|
10
|
-
readonly markerCount: number;
|
|
11
|
-
readonly totalDurationMs: number;
|
|
12
|
-
readonly markers: readonly ProfilerMarker[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface ActiveMarker {
|
|
16
|
-
readonly name: string;
|
|
17
|
-
readonly startMs: number;
|
|
18
|
-
readonly depth: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class Profiler {
|
|
22
|
-
private readonly active: ActiveMarker[] = [];
|
|
23
|
-
private readonly markers: ProfilerMarker[] = [];
|
|
24
|
-
|
|
25
|
-
constructor(private readonly now: () => number = () => performance.now()) {}
|
|
26
|
-
|
|
27
|
-
begin(name: string): void {
|
|
28
|
-
const trimmed = name.trim();
|
|
29
|
-
if (trimmed.length === 0) {
|
|
30
|
-
throw new Error("Profiler marker name is required");
|
|
31
|
-
}
|
|
32
|
-
this.active.push({ name: trimmed, startMs: this.now(), depth: this.active.length });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
end(name?: string): ProfilerMarker {
|
|
36
|
-
const active = this.active.pop();
|
|
37
|
-
if (!active) {
|
|
38
|
-
throw new Error("Cannot end profiler marker because no marker is active");
|
|
39
|
-
}
|
|
40
|
-
if (name !== undefined && active.name !== name) {
|
|
41
|
-
throw new Error(`Profiler marker mismatch: expected ${active.name}, received ${name}`);
|
|
42
|
-
}
|
|
43
|
-
const endMs = this.now();
|
|
44
|
-
const marker: ProfilerMarker = {
|
|
45
|
-
name: active.name,
|
|
46
|
-
startMs: active.startMs,
|
|
47
|
-
endMs,
|
|
48
|
-
durationMs: endMs - active.startMs,
|
|
49
|
-
depth: active.depth
|
|
50
|
-
};
|
|
51
|
-
this.markers.push(marker);
|
|
52
|
-
return marker;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
measure<T>(name: string, fn: () => T): T {
|
|
56
|
-
this.begin(name);
|
|
57
|
-
try {
|
|
58
|
-
return fn();
|
|
59
|
-
} finally {
|
|
60
|
-
this.end(name);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
snapshot(): ProfilerSnapshot {
|
|
65
|
-
const markers = [...this.markers];
|
|
66
|
-
return Object.freeze({
|
|
67
|
-
markerCount: markers.length,
|
|
68
|
-
totalDurationMs: markers.reduce((total, marker) => total + marker.durationMs, 0),
|
|
69
|
-
markers
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
reset(): void {
|
|
74
|
-
this.active.length = 0;
|
|
75
|
-
this.markers.length = 0;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export type RenderStateValue = string | number | boolean | null;
|
|
2
|
-
export type RenderStateSnapshot = ReadonlyMap<string, RenderStateValue>;
|
|
3
|
-
|
|
4
|
-
export interface RenderStateDiff {
|
|
5
|
-
readonly key: string;
|
|
6
|
-
readonly before: RenderStateValue | undefined;
|
|
7
|
-
readonly after: RenderStateValue | undefined;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class RenderStateInspector {
|
|
11
|
-
capture(source: { captureState(): RenderStateSnapshot }): RenderStateSnapshot {
|
|
12
|
-
return new Map(source.captureState());
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
diff(before: RenderStateSnapshot, after: RenderStateSnapshot): readonly RenderStateDiff[] {
|
|
16
|
-
const keys = new Set([...before.keys(), ...after.keys()]);
|
|
17
|
-
const diffs: RenderStateDiff[] = [];
|
|
18
|
-
for (const key of keys) {
|
|
19
|
-
const beforeValue = before.get(key);
|
|
20
|
-
const afterValue = after.get(key);
|
|
21
|
-
if (beforeValue !== afterValue) {
|
|
22
|
-
diffs.push({ key, before: beforeValue, after: afterValue });
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return diffs;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
assertNoLeak(before: RenderStateSnapshot, after: RenderStateSnapshot, ignoredKeys: readonly string[] = []): void {
|
|
29
|
-
const ignored = new Set(ignoredKeys);
|
|
30
|
-
const leaks = this.diff(before, after).filter((diff) => !ignored.has(diff.key));
|
|
31
|
-
if (leaks.length > 0) {
|
|
32
|
-
throw new RenderStateLeakError("Render state leak detected", leaks);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export class RenderStateLeakError extends Error {
|
|
38
|
-
constructor(
|
|
39
|
-
message: string,
|
|
40
|
-
public readonly leaks: readonly RenderStateDiff[]
|
|
41
|
-
) {
|
|
42
|
-
super(message);
|
|
43
|
-
this.name = "RenderStateLeakError";
|
|
44
|
-
}
|
|
45
|
-
}
|
package/src/ReportExporter.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export interface DebugReport {
|
|
2
|
-
readonly generatedAt: string;
|
|
3
|
-
readonly name: string;
|
|
4
|
-
readonly data: unknown;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export class ReportExporter {
|
|
8
|
-
constructor(private readonly now: () => Date = () => new Date()) {}
|
|
9
|
-
|
|
10
|
-
create(name: string, data: unknown): DebugReport {
|
|
11
|
-
const trimmed = name.trim();
|
|
12
|
-
if (trimmed.length === 0) {
|
|
13
|
-
throw new Error("Report name is required");
|
|
14
|
-
}
|
|
15
|
-
return {
|
|
16
|
-
generatedAt: this.now().toISOString(),
|
|
17
|
-
name: trimmed,
|
|
18
|
-
data
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
toJson(report: DebugReport): string {
|
|
23
|
-
return `${JSON.stringify(report, sortKeys, 2)}\n`;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function sortKeys(_key: string, value: unknown): unknown {
|
|
28
|
-
if (value === null || Array.isArray(value) || typeof value !== "object") {
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
|
-
return Object.fromEntries(Object.entries(value as Record<string, unknown>).sort(([left], [right]) => left.localeCompare(right)));
|
|
32
|
-
}
|
package/src/ResourceTracker.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
export interface TrackedResource {
|
|
2
|
-
readonly id: string;
|
|
3
|
-
readonly type: string;
|
|
4
|
-
readonly disposed: boolean;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface ResourceLeakReport {
|
|
8
|
-
readonly total: number;
|
|
9
|
-
readonly leaked: number;
|
|
10
|
-
readonly resources: readonly TrackedResource[];
|
|
11
|
-
readonly leaks: readonly TrackedResource[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class ResourceTracker {
|
|
15
|
-
private readonly resources = new Map<string, TrackedResource>();
|
|
16
|
-
|
|
17
|
-
track(id: string, type: string): void {
|
|
18
|
-
if (this.resources.has(id)) {
|
|
19
|
-
throw new Error(`Resource already tracked: ${id}`);
|
|
20
|
-
}
|
|
21
|
-
this.resources.set(id, { id, type, disposed: false });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
dispose(id: string): void {
|
|
25
|
-
const resource = this.resources.get(id);
|
|
26
|
-
if (!resource) {
|
|
27
|
-
throw new Error(`Unknown resource: ${id}`);
|
|
28
|
-
}
|
|
29
|
-
this.resources.set(id, { ...resource, disposed: true });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
report(): ResourceLeakReport {
|
|
33
|
-
const resources = [...this.resources.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
34
|
-
const leaks = resources.filter((resource) => !resource.disposed);
|
|
35
|
-
return {
|
|
36
|
-
total: resources.length,
|
|
37
|
-
leaked: leaks.length,
|
|
38
|
-
resources,
|
|
39
|
-
leaks
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
assertNoLeaks(): void {
|
|
44
|
-
const report = this.report();
|
|
45
|
-
if (report.leaked > 0) {
|
|
46
|
-
throw new ResourceLeakError("Resource leaks detected", report);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export class ResourceLeakError extends Error {
|
|
52
|
-
constructor(
|
|
53
|
-
message: string,
|
|
54
|
-
public readonly report: ResourceLeakReport
|
|
55
|
-
) {
|
|
56
|
-
super(message);
|
|
57
|
-
this.name = "ResourceLeakError";
|
|
58
|
-
}
|
|
59
|
-
}
|
package/src/SceneHelpers.ts
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import type { DebugRenderLine } from "./DebugLineCanvasRenderer.js";
|
|
2
|
-
|
|
3
|
-
export type DebugVec3 = readonly [number, number, number];
|
|
4
|
-
export type DebugColor = readonly [number, number, number, number];
|
|
5
|
-
|
|
6
|
-
export interface AxesHelperOptions {
|
|
7
|
-
readonly size?: number;
|
|
8
|
-
readonly origin?: DebugVec3;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface GridHelperOptions {
|
|
12
|
-
readonly size?: number;
|
|
13
|
-
readonly divisions?: number;
|
|
14
|
-
readonly y?: number;
|
|
15
|
-
readonly color?: DebugColor;
|
|
16
|
-
readonly centerColor?: DebugColor;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface BoundsHelperOptions {
|
|
20
|
-
readonly min: DebugVec3;
|
|
21
|
-
readonly max: DebugVec3;
|
|
22
|
-
readonly color?: DebugColor;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface CameraFrustumHelperOptions {
|
|
26
|
-
readonly nearHalfWidth: number;
|
|
27
|
-
readonly nearHalfHeight: number;
|
|
28
|
-
readonly farHalfWidth: number;
|
|
29
|
-
readonly farHalfHeight: number;
|
|
30
|
-
readonly nearZ?: number;
|
|
31
|
-
readonly farZ?: number;
|
|
32
|
-
readonly color?: DebugColor;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface DirectionalLightHelperOptions {
|
|
36
|
-
readonly direction: DebugVec3;
|
|
37
|
-
readonly origin?: DebugVec3;
|
|
38
|
-
readonly length?: number;
|
|
39
|
-
readonly color?: DebugColor;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface SkeletonHelperJoint {
|
|
43
|
-
readonly id: string;
|
|
44
|
-
readonly parentId?: string;
|
|
45
|
-
readonly position: DebugVec3;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const AXIS_X: DebugColor = [1, 0.18, 0.14, 1];
|
|
49
|
-
const AXIS_Y: DebugColor = [0.18, 0.86, 0.28, 1];
|
|
50
|
-
const AXIS_Z: DebugColor = [0.2, 0.48, 1, 1];
|
|
51
|
-
const DEFAULT_GRID: DebugColor = [0.38, 0.46, 0.58, 1];
|
|
52
|
-
const DEFAULT_CENTER: DebugColor = [0.72, 0.78, 0.86, 1];
|
|
53
|
-
const DEFAULT_HELPER: DebugColor = [1, 0.84, 0.28, 1];
|
|
54
|
-
|
|
55
|
-
export function buildAxesHelper(options: AxesHelperOptions = {}): readonly DebugRenderLine[] {
|
|
56
|
-
const size = positive(options.size ?? 1, "Axes helper size");
|
|
57
|
-
const origin = options.origin ?? [0, 0, 0];
|
|
58
|
-
return [
|
|
59
|
-
{ from: origin, to: [origin[0] + size, origin[1], origin[2]], color: AXIS_X },
|
|
60
|
-
{ from: origin, to: [origin[0], origin[1] + size, origin[2]], color: AXIS_Y },
|
|
61
|
-
{ from: origin, to: [origin[0], origin[1], origin[2] + size], color: AXIS_Z }
|
|
62
|
-
];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function buildGridHelper(options: GridHelperOptions = {}): readonly DebugRenderLine[] {
|
|
66
|
-
const size = positive(options.size ?? 10, "Grid helper size");
|
|
67
|
-
const divisions = integerAtLeast(options.divisions ?? 10, 1, "Grid helper divisions");
|
|
68
|
-
const y = finite(options.y ?? 0, "Grid helper y");
|
|
69
|
-
const half = size / 2;
|
|
70
|
-
const step = size / divisions;
|
|
71
|
-
const lines: DebugRenderLine[] = [];
|
|
72
|
-
for (let index = 0; index <= divisions; index += 1) {
|
|
73
|
-
const offset = -half + index * step;
|
|
74
|
-
const isCenter = Math.abs(offset) < 1e-8;
|
|
75
|
-
const color = isCenter ? options.centerColor ?? DEFAULT_CENTER : options.color ?? DEFAULT_GRID;
|
|
76
|
-
lines.push({ from: [-half, y, offset], to: [half, y, offset], color });
|
|
77
|
-
lines.push({ from: [offset, y, -half], to: [offset, y, half], color });
|
|
78
|
-
}
|
|
79
|
-
return lines;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function buildBoundsHelper(options: BoundsHelperOptions): readonly DebugRenderLine[] {
|
|
83
|
-
validateVec3(options.min, "Bounds helper min");
|
|
84
|
-
validateVec3(options.max, "Bounds helper max");
|
|
85
|
-
const [minX, minY, minZ] = options.min;
|
|
86
|
-
const [maxX, maxY, maxZ] = options.max;
|
|
87
|
-
if (maxX < minX || maxY < minY || maxZ < minZ) {
|
|
88
|
-
throw new RangeError("Bounds helper max must be greater than or equal to min.");
|
|
89
|
-
}
|
|
90
|
-
const color = options.color ?? DEFAULT_HELPER;
|
|
91
|
-
const p: readonly DebugVec3[] = [
|
|
92
|
-
[minX, minY, minZ], [maxX, minY, minZ], [maxX, maxY, minZ], [minX, maxY, minZ],
|
|
93
|
-
[minX, minY, maxZ], [maxX, minY, maxZ], [maxX, maxY, maxZ], [minX, maxY, maxZ]
|
|
94
|
-
];
|
|
95
|
-
return edges([
|
|
96
|
-
[0, 1], [1, 2], [2, 3], [3, 0],
|
|
97
|
-
[4, 5], [5, 6], [6, 7], [7, 4],
|
|
98
|
-
[0, 4], [1, 5], [2, 6], [3, 7]
|
|
99
|
-
], p, color);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function buildCameraFrustumHelper(options: CameraFrustumHelperOptions): readonly DebugRenderLine[] {
|
|
103
|
-
const nearHalfWidth = positive(options.nearHalfWidth, "Camera frustum nearHalfWidth");
|
|
104
|
-
const nearHalfHeight = positive(options.nearHalfHeight, "Camera frustum nearHalfHeight");
|
|
105
|
-
const farHalfWidth = positive(options.farHalfWidth, "Camera frustum farHalfWidth");
|
|
106
|
-
const farHalfHeight = positive(options.farHalfHeight, "Camera frustum farHalfHeight");
|
|
107
|
-
const nearZ = finite(options.nearZ ?? -0.1, "Camera frustum nearZ");
|
|
108
|
-
const farZ = finite(options.farZ ?? -1, "Camera frustum farZ");
|
|
109
|
-
if (Math.abs(nearZ - farZ) < 1e-8) {
|
|
110
|
-
throw new RangeError("Camera frustum nearZ and farZ must be distinct.");
|
|
111
|
-
}
|
|
112
|
-
const p: readonly DebugVec3[] = [
|
|
113
|
-
[-nearHalfWidth, -nearHalfHeight, nearZ],
|
|
114
|
-
[nearHalfWidth, -nearHalfHeight, nearZ],
|
|
115
|
-
[nearHalfWidth, nearHalfHeight, nearZ],
|
|
116
|
-
[-nearHalfWidth, nearHalfHeight, nearZ],
|
|
117
|
-
[-farHalfWidth, -farHalfHeight, farZ],
|
|
118
|
-
[farHalfWidth, -farHalfHeight, farZ],
|
|
119
|
-
[farHalfWidth, farHalfHeight, farZ],
|
|
120
|
-
[-farHalfWidth, farHalfHeight, farZ]
|
|
121
|
-
];
|
|
122
|
-
return edges([
|
|
123
|
-
[0, 1], [1, 2], [2, 3], [3, 0],
|
|
124
|
-
[4, 5], [5, 6], [6, 7], [7, 4],
|
|
125
|
-
[0, 4], [1, 5], [2, 6], [3, 7]
|
|
126
|
-
], p, options.color ?? DEFAULT_HELPER);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function buildDirectionalLightHelper(options: DirectionalLightHelperOptions): readonly DebugRenderLine[] {
|
|
130
|
-
const origin = options.origin ?? [0, 0, 0];
|
|
131
|
-
const length = positive(options.length ?? 1, "Directional light helper length");
|
|
132
|
-
const direction = normalize(options.direction, "Directional light helper direction");
|
|
133
|
-
const to: DebugVec3 = [
|
|
134
|
-
origin[0] + direction[0] * length,
|
|
135
|
-
origin[1] + direction[1] * length,
|
|
136
|
-
origin[2] + direction[2] * length
|
|
137
|
-
];
|
|
138
|
-
return [{ from: origin, to, color: options.color ?? DEFAULT_HELPER }];
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function buildSkeletonHelper(joints: readonly SkeletonHelperJoint[], color: DebugColor = DEFAULT_HELPER): readonly DebugRenderLine[] {
|
|
142
|
-
const byId = new Map<string, SkeletonHelperJoint>();
|
|
143
|
-
for (const joint of joints) {
|
|
144
|
-
if (!joint.id) throw new Error("Skeleton helper joints require stable ids.");
|
|
145
|
-
validateVec3(joint.position, `Skeleton helper joint ${joint.id}`);
|
|
146
|
-
byId.set(joint.id, joint);
|
|
147
|
-
}
|
|
148
|
-
const lines: DebugRenderLine[] = [];
|
|
149
|
-
for (const joint of joints) {
|
|
150
|
-
if (!joint.parentId) continue;
|
|
151
|
-
const parent = byId.get(joint.parentId);
|
|
152
|
-
if (!parent) continue;
|
|
153
|
-
lines.push({ from: parent.position, to: joint.position, color });
|
|
154
|
-
}
|
|
155
|
-
return lines;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function edges(indices: readonly (readonly [number, number])[], points: readonly DebugVec3[], color: DebugColor): readonly DebugRenderLine[] {
|
|
159
|
-
return indices.map(([from, to]) => ({ from: points[from]!, to: points[to]!, color }));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function positive(value: number, label: string): number {
|
|
163
|
-
if (!Number.isFinite(value) || value <= 0) throw new RangeError(`${label} must be finite and positive.`);
|
|
164
|
-
return value;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function finite(value: number, label: string): number {
|
|
168
|
-
if (!Number.isFinite(value)) throw new RangeError(`${label} must be finite.`);
|
|
169
|
-
return value;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function integerAtLeast(value: number, minimum: number, label: string): number {
|
|
173
|
-
if (!Number.isInteger(value) || value < minimum) throw new RangeError(`${label} must be an integer >= ${minimum}.`);
|
|
174
|
-
return value;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function validateVec3(value: readonly number[], label: string): void {
|
|
178
|
-
if (value.length !== 3 || value.some((component) => !Number.isFinite(component))) {
|
|
179
|
-
throw new RangeError(`${label} must contain three finite values.`);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function normalize(value: DebugVec3, label: string): DebugVec3 {
|
|
184
|
-
validateVec3(value, label);
|
|
185
|
-
const length = Math.hypot(value[0], value[1], value[2]);
|
|
186
|
-
if (length <= 1e-8) throw new RangeError(`${label} must be non-zero.`);
|
|
187
|
-
return [value[0] / length, value[1] / length, value[2] / length];
|
|
188
|
-
}
|
package/src/ShaderDiagnostics.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { type RenderShaderProgram, type ShaderSources } from "@aura3d/rendering";
|
|
2
|
-
|
|
3
|
-
export interface ShaderDiagnosticReport {
|
|
4
|
-
readonly label: string;
|
|
5
|
-
readonly marker: string;
|
|
6
|
-
readonly markerPresentInVertex: boolean;
|
|
7
|
-
readonly markerPresentInFragment: boolean;
|
|
8
|
-
readonly attributes: readonly string[];
|
|
9
|
-
readonly uniforms: readonly string[];
|
|
10
|
-
readonly compileLog: string | null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class ShaderDiagnostics {
|
|
14
|
-
inspectSources(sources: ShaderSources, compileLog: string | null = null): ShaderDiagnosticReport {
|
|
15
|
-
return {
|
|
16
|
-
label: sources.label,
|
|
17
|
-
marker: sources.marker,
|
|
18
|
-
markerPresentInVertex: sources.vertex.includes(sources.marker),
|
|
19
|
-
markerPresentInFragment: sources.fragment.includes(sources.marker),
|
|
20
|
-
attributes: extractNames(/\b(?:in|attribute)\s+\w+\s+(\w+)\s*;/g, `${sources.vertex}\n${sources.fragment}`),
|
|
21
|
-
uniforms: extractNames(/\buniform\s+\w+\s+(\w+)(?:\s*\[[^\]]+\])?\s*;/g, `${sources.vertex}\n${sources.fragment}`),
|
|
22
|
-
compileLog
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
inspectProgram(program: RenderShaderProgram, compileLog: string | null = null): ShaderDiagnosticReport {
|
|
27
|
-
return {
|
|
28
|
-
label: program.label,
|
|
29
|
-
marker: program.marker,
|
|
30
|
-
markerPresentInVertex: true,
|
|
31
|
-
markerPresentInFragment: true,
|
|
32
|
-
attributes: [...program.reflection.attributes.keys()],
|
|
33
|
-
uniforms: [...program.reflection.uniforms],
|
|
34
|
-
compileLog
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
assertMarker(report: ShaderDiagnosticReport): void {
|
|
39
|
-
if (!report.markerPresentInVertex || !report.markerPresentInFragment) {
|
|
40
|
-
throw new ShaderDiagnosticError("Shader marker mismatch", report);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export class ShaderDiagnosticError extends Error {
|
|
46
|
-
constructor(
|
|
47
|
-
message: string,
|
|
48
|
-
public readonly report: ShaderDiagnosticReport
|
|
49
|
-
) {
|
|
50
|
-
super(message);
|
|
51
|
-
this.name = "ShaderDiagnosticError";
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function extractNames(pattern: RegExp, source: string): readonly string[] {
|
|
56
|
-
const names = new Set<string>();
|
|
57
|
-
let match: RegExpExecArray | null;
|
|
58
|
-
while ((match = pattern.exec(source)) !== null) {
|
|
59
|
-
if (match[1]) {
|
|
60
|
-
names.add(match[1]);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return [...names];
|
|
64
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
export { DrawCallTracker } from "./DrawCallTracker.js";
|
|
2
|
-
export type { DrawCallRecord, DrawCallSnapshot } from "./DrawCallTracker.js";
|
|
3
|
-
export { RenderStateInspector, RenderStateLeakError } from "./RenderStateInspector.js";
|
|
4
|
-
export type { RenderStateDiff, RenderStateSnapshot, RenderStateValue } from "./RenderStateInspector.js";
|
|
5
|
-
export { ShaderDiagnosticError, ShaderDiagnostics } from "./ShaderDiagnostics.js";
|
|
6
|
-
export type { ShaderDiagnosticReport } from "./ShaderDiagnostics.js";
|
|
7
|
-
export { MaterialDiagnosticError, MaterialDiagnostics } from "./MaterialDiagnostics.js";
|
|
8
|
-
export type { MaterialDiagnosticReport } from "./MaterialDiagnostics.js";
|
|
9
|
-
export { PhysicsDebugAdapter } from "./PhysicsDebugAdapter.js";
|
|
10
|
-
export type { PhysicsDebugSnapshot, PhysicsStackEvidence } from "./PhysicsDebugAdapter.js";
|
|
11
|
-
export { AnimationInspector } from "./AnimationInspector.js";
|
|
12
|
-
export type { AnimationDebugSnapshot, AnimationVisualEvidence, SkeletonDebugSnapshot } from "./AnimationInspector.js";
|
|
13
|
-
export { Profiler } from "./Profiler.js";
|
|
14
|
-
export type { ProfilerMarker, ProfilerSnapshot } from "./Profiler.js";
|
|
15
|
-
export { GPUProfiler } from "./GPUProfiler.js";
|
|
16
|
-
export type { GPUProfilerSnapshot, GPUSample, GPUProfilerTimer } from "./GPUProfiler.js";
|
|
17
|
-
export { ChromeTraceExporter } from "./ChromeTraceExporter.js";
|
|
18
|
-
export type { ChromeTrace, ChromeTraceEvent, ChromeTraceExportOptions } from "./ChromeTraceExporter.js";
|
|
19
|
-
export { ResourceLeakError, ResourceTracker } from "./ResourceTracker.js";
|
|
20
|
-
export type { ResourceLeakReport, TrackedResource } from "./ResourceTracker.js";
|
|
21
|
-
export { ECSInspector } from "./ECSInspector.js";
|
|
22
|
-
export type { ECSInspectorSnapshot, ECSWorldLike } from "./ECSInspector.js";
|
|
23
|
-
export { DebugOverlay } from "./DebugOverlay.js";
|
|
24
|
-
export type { DebugOverlayRow, DebugOverlaySection, DebugOverlaySnapshot } from "./DebugOverlay.js";
|
|
25
|
-
export { DebugLineCanvasRenderer } from "./DebugLineCanvasRenderer.js";
|
|
26
|
-
export type { DebugLineCanvasRendererOptions, DebugLineCanvasRenderResult, DebugRenderLine } from "./DebugLineCanvasRenderer.js";
|
|
27
|
-
export {
|
|
28
|
-
buildAxesHelper,
|
|
29
|
-
buildBoundsHelper,
|
|
30
|
-
buildCameraFrustumHelper,
|
|
31
|
-
buildDirectionalLightHelper,
|
|
32
|
-
buildGridHelper,
|
|
33
|
-
buildSkeletonHelper
|
|
34
|
-
} from "./SceneHelpers.js";
|
|
35
|
-
export type {
|
|
36
|
-
AxesHelperOptions,
|
|
37
|
-
BoundsHelperOptions,
|
|
38
|
-
CameraFrustumHelperOptions,
|
|
39
|
-
DebugColor,
|
|
40
|
-
DebugVec3,
|
|
41
|
-
DirectionalLightHelperOptions,
|
|
42
|
-
GridHelperOptions,
|
|
43
|
-
SkeletonHelperJoint
|
|
44
|
-
} from "./SceneHelpers.js";
|
|
45
|
-
export { ReportExporter } from "./ReportExporter.js";
|
|
46
|
-
export type { DebugReport } from "./ReportExporter.js";
|
package/tsconfig.json
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "Bundler",
|
|
6
|
-
"lib": ["ES2022", "DOM"],
|
|
7
|
-
"strict": true,
|
|
8
|
-
"noImplicitAny": true,
|
|
9
|
-
"strictNullChecks": true,
|
|
10
|
-
"noImplicitReturns": true,
|
|
11
|
-
"noFallthroughCasesInSwitch": true,
|
|
12
|
-
"useUnknownInCatchVariables": true,
|
|
13
|
-
"exactOptionalPropertyTypes": false,
|
|
14
|
-
"isolatedModules": true,
|
|
15
|
-
"declaration": true,
|
|
16
|
-
"skipLibCheck": true,
|
|
17
|
-
"noEmit": true,
|
|
18
|
-
"paths": {
|
|
19
|
-
"@aura3d/rendering": ["../rendering/src/index.ts"],
|
|
20
|
-
"@aura3d/scene": ["../scene/src/index.ts"],
|
|
21
|
-
"@aura3d/math": ["../math/src/index.ts"],
|
|
22
|
-
"@aura3d/core": ["../core/src/index.ts"]
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
"include": [
|
|
26
|
-
"src/DrawCallTracker.ts",
|
|
27
|
-
"src/RenderStateInspector.ts",
|
|
28
|
-
"src/ShaderDiagnostics.ts",
|
|
29
|
-
"src/MaterialDiagnostics.ts",
|
|
30
|
-
"src/index.ts"
|
|
31
|
-
]
|
|
32
|
-
}
|