@vib3code/sdk 2.0.1
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/CHANGELOG.md +118 -0
- package/DOCS/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +34 -0
- package/DOCS/CI_TESTING.md +38 -0
- package/DOCS/CLI_ONBOARDING.md +75 -0
- package/DOCS/CONTROL_REFERENCE.md +64 -0
- package/DOCS/DEV_TRACK_ANALYSIS.md +77 -0
- package/DOCS/DEV_TRACK_PLAN_2026-01-07.md +42 -0
- package/DOCS/DEV_TRACK_SESSION_2026-01-31.md +220 -0
- package/DOCS/ENV_SETUP.md +189 -0
- package/DOCS/EXPORT_FORMATS.md +417 -0
- package/DOCS/GPU_DISPOSAL_GUIDE.md +21 -0
- package/DOCS/LICENSING_TIERS.md +275 -0
- package/DOCS/MASTER_PLAN_2026-01-31.md +570 -0
- package/DOCS/OBS_SETUP_GUIDE.md +98 -0
- package/DOCS/PROJECT_SETUP.md +66 -0
- package/DOCS/RENDERER_LIFECYCLE.md +40 -0
- package/DOCS/REPO_MANIFEST.md +121 -0
- package/DOCS/SESSION_014_PLAN.md +195 -0
- package/DOCS/SESSION_LOG_2026-01-07.md +56 -0
- package/DOCS/STRATEGIC_BLUEPRINT_2026-01-07.md +72 -0
- package/DOCS/SYSTEM_AUDIT_2026-01-30.md +738 -0
- package/DOCS/SYSTEM_INVENTORY.md +520 -0
- package/DOCS/TELEMETRY_EXPORTS.md +34 -0
- package/DOCS/WEBGPU_STATUS.md +38 -0
- package/DOCS/XR_BENCHMARKS.md +608 -0
- package/LICENSE +21 -0
- package/README.md +426 -0
- package/docs/.nojekyll +0 -0
- package/docs/01-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/02-hyperspatial_ego_death.html +346 -0
- package/docs/03-post_cartesian_sublime.html +346 -0
- package/docs/04-crystalline_void_meditation.html +346 -0
- package/docs/05-quantum_decoherence_ballet.html +346 -0
- package/docs/06-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/07-hyperspatial_ego_death.html +346 -0
- package/docs/08-post_cartesian_sublime.html +346 -0
- package/docs/09-crystalline_void_meditation.html +346 -0
- package/docs/10-quantum_decoherence_ballet.html +346 -0
- package/docs/11-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/12-hyperspatial_ego_death.html +346 -0
- package/docs/13-post_cartesian_sublime.html +346 -0
- package/docs/index.html +794 -0
- package/docs/test-hub.html +441 -0
- package/docs/url-state.js +102 -0
- package/docs/vib3-exports/01-quantum-quantum-tetrahedron-lattice.html +489 -0
- package/docs/vib3-exports/02-quantum-quantum-hypersphere-matrix.html +489 -0
- package/docs/vib3-exports/03-quantum-quantum-hypertetra-fractal.html +489 -0
- package/docs/vib3-exports/04-faceted-faceted-crystal-structure.html +407 -0
- package/docs/vib3-exports/05-faceted-faceted-klein-bottle.html +407 -0
- package/docs/vib3-exports/06-faceted-faceted-hypertetra-torus.html +407 -0
- package/docs/vib3-exports/07-holographic-holographic-wave-field.html +457 -0
- package/docs/vib3-exports/08-holographic-holographic-hypersphere-sphere.html +457 -0
- package/docs/vib3-exports/09-holographic-holographic-hypertetra-crystal.html +457 -0
- package/docs/vib3-exports/index.html +238 -0
- package/docs/webgpu-live.html +702 -0
- package/package.json +367 -0
- package/src/advanced/AIPresetGenerator.js +777 -0
- package/src/advanced/MIDIController.js +703 -0
- package/src/advanced/OffscreenWorker.js +1051 -0
- package/src/advanced/WebGPUCompute.js +1051 -0
- package/src/advanced/WebXRRenderer.js +680 -0
- package/src/agent/cli/AgentCLI.js +615 -0
- package/src/agent/cli/index.js +14 -0
- package/src/agent/index.js +73 -0
- package/src/agent/mcp/MCPServer.js +950 -0
- package/src/agent/mcp/index.js +9 -0
- package/src/agent/mcp/tools.js +548 -0
- package/src/agent/telemetry/EventStream.js +669 -0
- package/src/agent/telemetry/Instrumentation.js +618 -0
- package/src/agent/telemetry/TelemetryExporters.js +427 -0
- package/src/agent/telemetry/TelemetryService.js +464 -0
- package/src/agent/telemetry/index.js +52 -0
- package/src/benchmarks/BenchmarkRunner.js +381 -0
- package/src/benchmarks/MetricsCollector.js +299 -0
- package/src/benchmarks/index.js +9 -0
- package/src/benchmarks/scenes.js +259 -0
- package/src/cli/index.js +675 -0
- package/src/config/ApiConfig.js +88 -0
- package/src/core/CanvasManager.js +217 -0
- package/src/core/ErrorReporter.js +117 -0
- package/src/core/ParameterMapper.js +333 -0
- package/src/core/Parameters.js +396 -0
- package/src/core/RendererContracts.js +200 -0
- package/src/core/UnifiedResourceManager.js +370 -0
- package/src/core/VIB3Engine.js +636 -0
- package/src/core/renderers/FacetedRendererAdapter.js +32 -0
- package/src/core/renderers/HolographicRendererAdapter.js +29 -0
- package/src/core/renderers/QuantumRendererAdapter.js +29 -0
- package/src/core/renderers/RendererLifecycleManager.js +63 -0
- package/src/creative/ColorPresetsSystem.js +980 -0
- package/src/creative/ParameterTimeline.js +1061 -0
- package/src/creative/PostProcessingPipeline.js +1113 -0
- package/src/creative/TransitionAnimator.js +683 -0
- package/src/export/CSSExporter.js +226 -0
- package/src/export/CardGeneratorBase.js +279 -0
- package/src/export/ExportManager.js +580 -0
- package/src/export/FacetedCardGenerator.js +279 -0
- package/src/export/HolographicCardGenerator.js +543 -0
- package/src/export/LottieExporter.js +552 -0
- package/src/export/QuantumCardGenerator.js +315 -0
- package/src/export/SVGExporter.js +519 -0
- package/src/export/ShaderExporter.js +903 -0
- package/src/export/TradingCardGenerator.js +3055 -0
- package/src/export/TradingCardManager.js +181 -0
- package/src/export/VIB3PackageExporter.js +559 -0
- package/src/export/index.js +14 -0
- package/src/export/systems/TradingCardSystemFaceted.js +494 -0
- package/src/export/systems/TradingCardSystemHolographic.js +452 -0
- package/src/export/systems/TradingCardSystemQuantum.js +411 -0
- package/src/faceted/FacetedSystem.js +963 -0
- package/src/features/CollectionManager.js +433 -0
- package/src/gallery/CollectionManager.js +240 -0
- package/src/gallery/GallerySystem.js +485 -0
- package/src/geometry/GeometryFactory.js +314 -0
- package/src/geometry/GeometryLibrary.js +72 -0
- package/src/geometry/buffers/BufferBuilder.js +338 -0
- package/src/geometry/buffers/index.js +18 -0
- package/src/geometry/generators/Crystal.js +420 -0
- package/src/geometry/generators/Fractal.js +298 -0
- package/src/geometry/generators/KleinBottle.js +197 -0
- package/src/geometry/generators/Sphere.js +192 -0
- package/src/geometry/generators/Tesseract.js +160 -0
- package/src/geometry/generators/Tetrahedron.js +225 -0
- package/src/geometry/generators/Torus.js +304 -0
- package/src/geometry/generators/Wave.js +341 -0
- package/src/geometry/index.js +142 -0
- package/src/geometry/warp/HypersphereCore.js +211 -0
- package/src/geometry/warp/HypertetraCore.js +386 -0
- package/src/geometry/warp/index.js +57 -0
- package/src/holograms/HolographicVisualizer.js +1073 -0
- package/src/holograms/RealHolographicSystem.js +966 -0
- package/src/holograms/variantRegistry.js +69 -0
- package/src/integrations/FigmaPlugin.js +854 -0
- package/src/integrations/OBSMode.js +754 -0
- package/src/integrations/ThreeJsPackage.js +660 -0
- package/src/integrations/TouchDesignerExport.js +552 -0
- package/src/integrations/frameworks/Vib3React.js +591 -0
- package/src/integrations/frameworks/Vib3Svelte.js +654 -0
- package/src/integrations/frameworks/Vib3Vue.js +628 -0
- package/src/llm/LLMParameterInterface.js +240 -0
- package/src/llm/LLMParameterUI.js +577 -0
- package/src/math/Mat4x4.js +708 -0
- package/src/math/Projection.js +341 -0
- package/src/math/Rotor4D.js +637 -0
- package/src/math/Vec4.js +476 -0
- package/src/math/constants.js +164 -0
- package/src/math/index.js +68 -0
- package/src/math/projections.js +54 -0
- package/src/math/rotations.js +196 -0
- package/src/quantum/QuantumEngine.js +906 -0
- package/src/quantum/QuantumVisualizer.js +1103 -0
- package/src/reactivity/ReactivityConfig.js +499 -0
- package/src/reactivity/ReactivityManager.js +586 -0
- package/src/reactivity/SpatialInputSystem.js +1783 -0
- package/src/reactivity/index.js +93 -0
- package/src/render/CommandBuffer.js +465 -0
- package/src/render/MultiCanvasBridge.js +340 -0
- package/src/render/RenderCommand.js +514 -0
- package/src/render/RenderResourceRegistry.js +523 -0
- package/src/render/RenderState.js +552 -0
- package/src/render/RenderTarget.js +512 -0
- package/src/render/ShaderLoader.js +253 -0
- package/src/render/ShaderProgram.js +599 -0
- package/src/render/UnifiedRenderBridge.js +496 -0
- package/src/render/backends/WebGLBackend.js +1108 -0
- package/src/render/backends/WebGPUBackend.js +1409 -0
- package/src/render/commands/CommandBufferExecutor.js +607 -0
- package/src/render/commands/RenderCommandBuffer.js +661 -0
- package/src/render/commands/index.js +17 -0
- package/src/render/index.js +367 -0
- package/src/scene/Disposable.js +498 -0
- package/src/scene/MemoryPool.js +618 -0
- package/src/scene/Node4D.js +697 -0
- package/src/scene/ResourceManager.js +599 -0
- package/src/scene/Scene4D.js +540 -0
- package/src/scene/index.js +98 -0
- package/src/schemas/error.schema.json +84 -0
- package/src/schemas/extension.schema.json +88 -0
- package/src/schemas/index.js +214 -0
- package/src/schemas/parameters.schema.json +142 -0
- package/src/schemas/tool-pack.schema.json +44 -0
- package/src/schemas/tool-response.schema.json +127 -0
- package/src/shaders/common/fullscreen.vert.glsl +5 -0
- package/src/shaders/common/fullscreen.vert.wgsl +17 -0
- package/src/shaders/common/geometry24.glsl +65 -0
- package/src/shaders/common/geometry24.wgsl +54 -0
- package/src/shaders/common/rotation4d.glsl +85 -0
- package/src/shaders/common/rotation4d.wgsl +86 -0
- package/src/shaders/common/uniforms.glsl +44 -0
- package/src/shaders/common/uniforms.wgsl +48 -0
- package/src/shaders/faceted/faceted.frag.glsl +129 -0
- package/src/shaders/faceted/faceted.frag.wgsl +164 -0
- package/src/shaders/holographic/holographic.frag.glsl +406 -0
- package/src/shaders/holographic/holographic.frag.wgsl +185 -0
- package/src/shaders/quantum/quantum.frag.glsl +513 -0
- package/src/shaders/quantum/quantum.frag.wgsl +361 -0
- package/src/testing/ParallelTestFramework.js +519 -0
- package/src/testing/__snapshots__/exportFormats.test.js.snap +24 -0
- package/src/testing/exportFormats.test.js +8 -0
- package/src/testing/projections.test.js +14 -0
- package/src/testing/rotations.test.js +37 -0
- package/src/ui/InteractivityMenu.js +516 -0
- package/src/ui/StatusManager.js +96 -0
- package/src/ui/adaptive/renderers/webgpu/BufferLayout.ts +252 -0
- package/src/ui/adaptive/renderers/webgpu/PolytopeInstanceBuffer.ts +144 -0
- package/src/ui/adaptive/renderers/webgpu/TripleBufferedUniform.ts +170 -0
- package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +735 -0
- package/src/ui/adaptive/renderers/webgpu/index.ts +112 -0
- package/src/variations/VariationManager.js +431 -0
- package/src/viewer/AudioReactivity.js +505 -0
- package/src/viewer/CardBending.js +481 -0
- package/src/viewer/GalleryUI.js +832 -0
- package/src/viewer/ReactivityManager.js +590 -0
- package/src/viewer/TradingCardExporter.js +600 -0
- package/src/viewer/ViewerPortal.js +374 -0
- package/src/viewer/index.js +12 -0
- package/src/wasm/WasmLoader.js +296 -0
- package/src/wasm/index.js +132 -0
- package/tools/agentic/mcpTools.js +88 -0
- package/tools/cli/agent-cli.js +92 -0
- package/tools/export/formats.js +24 -0
- package/tools/math/rotation-baseline.mjs +64 -0
- package/tools/shader-sync-verify.js +937 -0
- package/tools/telemetry/manifestPipeline.js +141 -0
- package/tools/telemetry/telemetryEvents.js +35 -0
- package/types/adaptive-sdk.d.ts +185 -0
- package/types/advanced/AIPresetGenerator.d.ts +81 -0
- package/types/advanced/MIDIController.d.ts +100 -0
- package/types/advanced/OffscreenWorker.d.ts +82 -0
- package/types/advanced/WebGPUCompute.d.ts +52 -0
- package/types/advanced/WebXRRenderer.d.ts +77 -0
- package/types/advanced/index.d.ts +46 -0
- package/types/core/ErrorReporter.d.ts +50 -0
- package/types/core/VIB3Engine.d.ts +204 -0
- package/types/creative/ColorPresetsSystem.d.ts +91 -0
- package/types/creative/ParameterTimeline.d.ts +74 -0
- package/types/creative/PostProcessingPipeline.d.ts +109 -0
- package/types/creative/TransitionAnimator.d.ts +71 -0
- package/types/creative/index.d.ts +35 -0
- package/types/integrations/FigmaPlugin.d.ts +46 -0
- package/types/integrations/OBSMode.d.ts +74 -0
- package/types/integrations/ThreeJsPackage.d.ts +62 -0
- package/types/integrations/TouchDesignerExport.d.ts +36 -0
- package/types/integrations/Vib3React.d.ts +74 -0
- package/types/integrations/Vib3Svelte.d.ts +63 -0
- package/types/integrations/Vib3Vue.d.ts +55 -0
- package/types/integrations/index.d.ts +52 -0
- package/types/reactivity/SpatialInputSystem.d.ts +173 -0
- package/types/reactivity/index.d.ts +394 -0
- package/types/render/CommandBuffer.d.ts +169 -0
- package/types/render/RenderCommand.d.ts +312 -0
- package/types/render/RenderState.d.ts +279 -0
- package/types/render/RenderTarget.d.ts +254 -0
- package/types/render/ShaderProgram.d.ts +277 -0
- package/types/render/UnifiedRenderBridge.d.ts +143 -0
- package/types/render/WebGLBackend.d.ts +168 -0
- package/types/render/WebGPUBackend.d.ts +186 -0
- package/types/render/index.d.ts +141 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
export type LayoutMode = 'std140' | 'std430';
|
|
2
|
+
|
|
3
|
+
export type LayoutFieldType = 'f32' | 'vec2' | 'vec3' | 'vec4' | 'mat4x4';
|
|
4
|
+
|
|
5
|
+
export interface LayoutFieldDefinition {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly type: LayoutFieldType;
|
|
8
|
+
/** Number of elements for arrays. Defaults to 1. */
|
|
9
|
+
readonly count?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LayoutField {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly type: LayoutFieldType;
|
|
15
|
+
readonly count: number;
|
|
16
|
+
/** Byte offset from the start of the struct. */
|
|
17
|
+
readonly offset: number;
|
|
18
|
+
/** Total byte span occupied by the field including padding. */
|
|
19
|
+
readonly size: number;
|
|
20
|
+
/** Byte distance between array elements (equals size when count === 1). */
|
|
21
|
+
readonly stride: number;
|
|
22
|
+
/** Number of numeric components per element (e.g. vec3 → 3). */
|
|
23
|
+
readonly componentCount: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface BufferLayout {
|
|
27
|
+
readonly mode: LayoutMode;
|
|
28
|
+
readonly byteSize: number;
|
|
29
|
+
readonly fields: Readonly<Record<string, LayoutField>>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface WriteFieldOptions {
|
|
33
|
+
/** Index of the array element to write (defaults to 0). */
|
|
34
|
+
readonly elementIndex?: number;
|
|
35
|
+
/** Number of array elements that will be written from the provided values. */
|
|
36
|
+
readonly elementCount?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const COMPONENT_COUNT: Record<LayoutFieldType, number> = {
|
|
40
|
+
f32: 1,
|
|
41
|
+
vec2: 2,
|
|
42
|
+
vec3: 3,
|
|
43
|
+
vec4: 4,
|
|
44
|
+
mat4x4: 16,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const NATURAL_SIZE: Record<LayoutFieldType, number> = {
|
|
48
|
+
f32: 4,
|
|
49
|
+
vec2: 8,
|
|
50
|
+
vec3: 12,
|
|
51
|
+
vec4: 16,
|
|
52
|
+
mat4x4: 64,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const BASE_ALIGNMENT: Record<LayoutFieldType, number> = {
|
|
56
|
+
f32: 4,
|
|
57
|
+
vec2: 8,
|
|
58
|
+
vec3: 16,
|
|
59
|
+
vec4: 16,
|
|
60
|
+
mat4x4: 16,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function align(value: number, alignment: number): number {
|
|
64
|
+
return Math.ceil(value / alignment) * alignment;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveCount(def: LayoutFieldDefinition): number {
|
|
68
|
+
const count = Math.floor(def.count ?? 1);
|
|
69
|
+
if (count <= 0) {
|
|
70
|
+
throw new Error(`Field "${def.name}" requires a positive element count.`);
|
|
71
|
+
}
|
|
72
|
+
return count;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function computeArrayAlignment(mode: LayoutMode, baseAlignment: number): number {
|
|
76
|
+
if (mode === 'std140') {
|
|
77
|
+
return Math.max(baseAlignment, 16);
|
|
78
|
+
}
|
|
79
|
+
return baseAlignment;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function computeStride(mode: LayoutMode, type: LayoutFieldType, arrayAlignment: number): number {
|
|
83
|
+
const natural = NATURAL_SIZE[type];
|
|
84
|
+
if (mode === 'std140') {
|
|
85
|
+
return align(natural, arrayAlignment);
|
|
86
|
+
}
|
|
87
|
+
return align(natural, arrayAlignment);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function computeElementSize(type: LayoutFieldType): number {
|
|
91
|
+
const baseAlignment = BASE_ALIGNMENT[type];
|
|
92
|
+
return align(NATURAL_SIZE[type], baseAlignment);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function createLayout(mode: LayoutMode, definitions: readonly LayoutFieldDefinition[]): BufferLayout {
|
|
96
|
+
let size = 0;
|
|
97
|
+
const fields: Record<string, LayoutField> = {};
|
|
98
|
+
|
|
99
|
+
for (const def of definitions) {
|
|
100
|
+
if (!def?.name) {
|
|
101
|
+
throw new Error('Layout fields require a name.');
|
|
102
|
+
}
|
|
103
|
+
if (!COMPONENT_COUNT[def.type]) {
|
|
104
|
+
throw new Error(`Unsupported field type "${String(def.type)}" for ${def.name}.`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const baseAlignment = BASE_ALIGNMENT[def.type];
|
|
108
|
+
const count = resolveCount(def);
|
|
109
|
+
const componentCount = COMPONENT_COUNT[def.type];
|
|
110
|
+
|
|
111
|
+
if (count > 1) {
|
|
112
|
+
const arrayAlignment = computeArrayAlignment(mode, baseAlignment);
|
|
113
|
+
size = align(size, arrayAlignment);
|
|
114
|
+
const stride = computeStride(mode, def.type, arrayAlignment);
|
|
115
|
+
const fieldSize = stride * count;
|
|
116
|
+
fields[def.name] = {
|
|
117
|
+
name: def.name,
|
|
118
|
+
type: def.type,
|
|
119
|
+
count,
|
|
120
|
+
offset: size,
|
|
121
|
+
size: fieldSize,
|
|
122
|
+
stride,
|
|
123
|
+
componentCount,
|
|
124
|
+
};
|
|
125
|
+
size += fieldSize;
|
|
126
|
+
} else {
|
|
127
|
+
size = align(size, baseAlignment);
|
|
128
|
+
const elementSize = computeElementSize(def.type);
|
|
129
|
+
fields[def.name] = {
|
|
130
|
+
name: def.name,
|
|
131
|
+
type: def.type,
|
|
132
|
+
count,
|
|
133
|
+
offset: size,
|
|
134
|
+
size: elementSize,
|
|
135
|
+
stride: elementSize,
|
|
136
|
+
componentCount,
|
|
137
|
+
};
|
|
138
|
+
size += elementSize;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const byteSize = align(size, 16);
|
|
143
|
+
return Object.freeze({
|
|
144
|
+
mode,
|
|
145
|
+
byteSize,
|
|
146
|
+
fields: Object.freeze(fields),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function createStd140Layout(definitions: readonly LayoutFieldDefinition[]): BufferLayout {
|
|
151
|
+
return createLayout('std140', definitions);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function createStd430Layout(definitions: readonly LayoutFieldDefinition[]): BufferLayout {
|
|
155
|
+
return createLayout('std430', definitions);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function createFloat32ArrayForLayout(layout: BufferLayout): Float32Array {
|
|
159
|
+
return new Float32Array(layout.byteSize / Float32Array.BYTES_PER_ELEMENT);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function ensureTargetSize(layout: BufferLayout, target: Float32Array): void {
|
|
163
|
+
if (target.length * Float32Array.BYTES_PER_ELEMENT < layout.byteSize) {
|
|
164
|
+
throw new Error('Target buffer is too small for the provided layout.');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function writeField(
|
|
169
|
+
layout: BufferLayout,
|
|
170
|
+
target: Float32Array,
|
|
171
|
+
fieldName: string,
|
|
172
|
+
values: ArrayLike<number>,
|
|
173
|
+
options: WriteFieldOptions = {}
|
|
174
|
+
): void {
|
|
175
|
+
ensureTargetSize(layout, target);
|
|
176
|
+
const field = layout.fields[fieldName];
|
|
177
|
+
if (!field) {
|
|
178
|
+
throw new Error(`Unknown field "${fieldName}" in layout.`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const elementCount = Math.min(field.count, Math.floor(options.elementCount ?? field.count));
|
|
182
|
+
if (elementCount <= 0) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const elementIndex = Math.min(field.count - 1, Math.max(0, Math.floor(options.elementIndex ?? 0)));
|
|
186
|
+
const components = field.componentCount;
|
|
187
|
+
const strideFloats = field.stride / Float32Array.BYTES_PER_ELEMENT;
|
|
188
|
+
const offsetFloats = field.offset / Float32Array.BYTES_PER_ELEMENT + elementIndex * strideFloats;
|
|
189
|
+
|
|
190
|
+
const requiredValues = components * elementCount;
|
|
191
|
+
if (values.length < requiredValues) {
|
|
192
|
+
throw new Error(`Field "${fieldName}" requires at least ${requiredValues} components.`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let cursor = 0;
|
|
196
|
+
for (let element = 0; element < elementCount; element += 1) {
|
|
197
|
+
const base = offsetFloats + element * strideFloats;
|
|
198
|
+
for (let i = 0; i < components; i += 1) {
|
|
199
|
+
target[base + i] = values[cursor + i];
|
|
200
|
+
}
|
|
201
|
+
for (let i = components; i < strideFloats; i += 1) {
|
|
202
|
+
target[base + i] = 0;
|
|
203
|
+
}
|
|
204
|
+
cursor += components;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const remaining = field.count - (elementIndex + elementCount);
|
|
208
|
+
if (remaining > 0) {
|
|
209
|
+
const base = offsetFloats + elementCount * strideFloats;
|
|
210
|
+
const total = remaining * strideFloats;
|
|
211
|
+
for (let i = 0; i < total; i += 1) {
|
|
212
|
+
target[base + i] = 0;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function readField(
|
|
218
|
+
layout: BufferLayout,
|
|
219
|
+
source: Float32Array,
|
|
220
|
+
fieldName: string,
|
|
221
|
+
options: WriteFieldOptions = {}
|
|
222
|
+
): Float32Array {
|
|
223
|
+
ensureTargetSize(layout, source);
|
|
224
|
+
const field = layout.fields[fieldName];
|
|
225
|
+
if (!field) {
|
|
226
|
+
throw new Error(`Unknown field "${fieldName}" in layout.`);
|
|
227
|
+
}
|
|
228
|
+
const elementIndex = Math.min(field.count - 1, Math.max(0, Math.floor(options.elementIndex ?? 0)));
|
|
229
|
+
const elementCount = Math.min(field.count - elementIndex, Math.floor(options.elementCount ?? 1));
|
|
230
|
+
const strideFloats = field.stride / Float32Array.BYTES_PER_ELEMENT;
|
|
231
|
+
const offsetFloats = field.offset / Float32Array.BYTES_PER_ELEMENT + elementIndex * strideFloats;
|
|
232
|
+
const result = new Float32Array(field.componentCount * elementCount);
|
|
233
|
+
for (let element = 0; element < elementCount; element += 1) {
|
|
234
|
+
const base = offsetFloats + element * strideFloats;
|
|
235
|
+
for (let i = 0; i < field.componentCount; i += 1) {
|
|
236
|
+
result[element * field.componentCount + i] = source[base + i];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export const GlassUniformLayout = createStd140Layout([
|
|
243
|
+
{ name: 'leftViewProj', type: 'mat4x4' },
|
|
244
|
+
{ name: 'rightViewProj', type: 'mat4x4' },
|
|
245
|
+
{ name: 'headMatrix', type: 'mat4x4' },
|
|
246
|
+
{ name: 'rotor4d', type: 'vec4' },
|
|
247
|
+
{ name: 'euler', type: 'vec4' },
|
|
248
|
+
{ name: 'metrics', type: 'vec4' },
|
|
249
|
+
{ name: 'audio', type: 'vec4' },
|
|
250
|
+
{ name: 'localization', type: 'vec4' },
|
|
251
|
+
{ name: 'visual', type: 'vec4' },
|
|
252
|
+
]);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { createStd430Layout, createFloat32ArrayForLayout, writeField, readField, type BufferLayout } from './BufferLayout.ts';
|
|
2
|
+
import type { GPUBufferLike, GPUDeviceLike, GPUQueueLike } from './TripleBufferedUniform.ts';
|
|
3
|
+
|
|
4
|
+
const GPU_BUFFER_USAGE_STORAGE = 0x20;
|
|
5
|
+
const GPU_BUFFER_USAGE_COPY_DST = 0x8;
|
|
6
|
+
|
|
7
|
+
export interface PolytopeInstance {
|
|
8
|
+
readonly modelMatrix: ArrayLike<number>;
|
|
9
|
+
readonly rotor: ArrayLike<number>;
|
|
10
|
+
readonly color: ArrayLike<number>;
|
|
11
|
+
readonly misc?: {
|
|
12
|
+
readonly scale?: number;
|
|
13
|
+
readonly audioEnergy?: number;
|
|
14
|
+
readonly glitch?: number;
|
|
15
|
+
readonly id?: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PolytopeInstanceBufferOptions {
|
|
20
|
+
readonly device: GPUDeviceLike;
|
|
21
|
+
readonly maxInstances: number;
|
|
22
|
+
readonly label?: string;
|
|
23
|
+
readonly usage?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface InstanceWriteOptions {
|
|
27
|
+
readonly index: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class PolytopeInstanceBuffer {
|
|
31
|
+
readonly device: GPUDeviceLike;
|
|
32
|
+
readonly layout: BufferLayout;
|
|
33
|
+
readonly maxInstances: number;
|
|
34
|
+
readonly label: string;
|
|
35
|
+
readonly buffer: GPUBufferLike;
|
|
36
|
+
readonly data: Float32Array;
|
|
37
|
+
|
|
38
|
+
private instanceCount = 0;
|
|
39
|
+
|
|
40
|
+
constructor(options: PolytopeInstanceBufferOptions) {
|
|
41
|
+
if (!options?.device) {
|
|
42
|
+
throw new Error('PolytopeInstanceBuffer requires a WebGPU-compatible device.');
|
|
43
|
+
}
|
|
44
|
+
if (!Number.isFinite(options.maxInstances) || options.maxInstances <= 0) {
|
|
45
|
+
throw new Error('PolytopeInstanceBuffer maxInstances must be a positive integer.');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.device = options.device;
|
|
49
|
+
this.maxInstances = Math.floor(options.maxInstances);
|
|
50
|
+
this.label = options.label ?? 'PolytopeInstanceBuffer';
|
|
51
|
+
|
|
52
|
+
this.layout = createStd430Layout([
|
|
53
|
+
{ name: 'modelMatrices', type: 'mat4x4', count: this.maxInstances },
|
|
54
|
+
{ name: 'rotors', type: 'vec4', count: this.maxInstances },
|
|
55
|
+
{ name: 'colors', type: 'vec4', count: this.maxInstances },
|
|
56
|
+
{ name: 'misc', type: 'vec4', count: this.maxInstances },
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
this.data = createFloat32ArrayForLayout(this.layout);
|
|
60
|
+
|
|
61
|
+
const usage = options.usage ?? (GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST);
|
|
62
|
+
this.buffer = this.device.createBuffer({
|
|
63
|
+
size: this.layout.byteSize,
|
|
64
|
+
usage,
|
|
65
|
+
label: `${this.label}-storage`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get count(): number {
|
|
70
|
+
return this.instanceCount;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
reset(): void {
|
|
74
|
+
this.instanceCount = 0;
|
|
75
|
+
this.data.fill(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
writeInstance(instance: PolytopeInstance, options: InstanceWriteOptions): void {
|
|
79
|
+
const index = Math.floor(options.index);
|
|
80
|
+
if (index < 0 || index >= this.maxInstances) {
|
|
81
|
+
throw new Error(`Instance index ${index} is out of bounds for maxInstances=${this.maxInstances}.`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
writeField(this.layout, this.data, 'modelMatrices', ensureLength(instance.modelMatrix, 16), {
|
|
85
|
+
elementIndex: index,
|
|
86
|
+
elementCount: 1,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const rotor = ensureLength(instance.rotor, 4);
|
|
90
|
+
writeField(this.layout, this.data, 'rotors', rotor, { elementIndex: index, elementCount: 1 });
|
|
91
|
+
|
|
92
|
+
const color = ensureLength(instance.color, 4);
|
|
93
|
+
writeField(this.layout, this.data, 'colors', color, { elementIndex: index, elementCount: 1 });
|
|
94
|
+
|
|
95
|
+
const misc = buildMisc(instance.misc);
|
|
96
|
+
writeField(this.layout, this.data, 'misc', misc, { elementIndex: index, elementCount: 1 });
|
|
97
|
+
|
|
98
|
+
this.instanceCount = Math.max(this.instanceCount, index + 1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
readInstance(index: number): { modelMatrix: Float32Array; rotor: Float32Array; color: Float32Array; misc: Float32Array } {
|
|
102
|
+
if (index < 0 || index >= this.maxInstances) {
|
|
103
|
+
throw new Error(`Instance index ${index} is out of bounds for maxInstances=${this.maxInstances}.`);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
modelMatrix: readField(this.layout, this.data, 'modelMatrices', { elementIndex: index }),
|
|
107
|
+
rotor: readField(this.layout, this.data, 'rotors', { elementIndex: index }),
|
|
108
|
+
color: readField(this.layout, this.data, 'colors', { elementIndex: index }),
|
|
109
|
+
misc: readField(this.layout, this.data, 'misc', { elementIndex: index }),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
upload(queue: GPUQueueLike = this.device.queue): void {
|
|
114
|
+
if (!queue || typeof queue.writeBuffer !== 'function') {
|
|
115
|
+
throw new Error('PolytopeInstanceBuffer.upload requires a valid GPU queue.');
|
|
116
|
+
}
|
|
117
|
+
queue.writeBuffer(this.buffer, 0, this.data);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
bindGroupEntry(binding = 0): { binding: number; resource: { buffer: GPUBufferLike } } {
|
|
121
|
+
return { binding, resource: { buffer: this.buffer } };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function ensureLength(values: ArrayLike<number>, expected: number): Float32Array {
|
|
126
|
+
const result = new Float32Array(expected);
|
|
127
|
+
const length = Math.min(values.length, expected);
|
|
128
|
+
for (let i = 0; i < length; i += 1) {
|
|
129
|
+
result[i] = Number(values[i]) || 0;
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildMisc(misc: PolytopeInstance['misc']): Float32Array {
|
|
135
|
+
const result = new Float32Array(4);
|
|
136
|
+
if (!misc) {
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
result[0] = Number(misc.scale) || 0;
|
|
140
|
+
result[1] = Number(misc.audioEnergy) || 0;
|
|
141
|
+
result[2] = Number(misc.glitch) || 0;
|
|
142
|
+
result[3] = Number(misc.id) || 0;
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Triple-buffered uniform buffer for WebGPU
|
|
3
|
+
* Provides smooth animation by maintaining 3 buffers for CPU write, GPU read, and swap
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Type definitions for WebGPU-like interfaces (compatible with actual WebGPU or mocks)
|
|
7
|
+
export interface GPUBufferLike {
|
|
8
|
+
readonly size: number;
|
|
9
|
+
readonly usage: number;
|
|
10
|
+
readonly label?: string;
|
|
11
|
+
mapAsync?: (mode: number) => Promise<void>;
|
|
12
|
+
getMappedRange?: () => ArrayBuffer;
|
|
13
|
+
unmap?: () => void;
|
|
14
|
+
destroy?: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface GPUDeviceLike {
|
|
18
|
+
readonly queue: GPUQueueLike;
|
|
19
|
+
createBuffer(descriptor: {
|
|
20
|
+
size: number;
|
|
21
|
+
usage: number;
|
|
22
|
+
label?: string;
|
|
23
|
+
mappedAtCreation?: boolean;
|
|
24
|
+
}): GPUBufferLike;
|
|
25
|
+
createBindGroup?(descriptor: unknown): unknown;
|
|
26
|
+
createBindGroupLayout?(descriptor: unknown): unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface GPUQueueLike {
|
|
30
|
+
writeBuffer(buffer: GPUBufferLike, offset: number, data: ArrayBufferView | ArrayBuffer): void;
|
|
31
|
+
submit?(commandBuffers: unknown[]): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TripleBufferedUniformOptions {
|
|
35
|
+
readonly device: GPUDeviceLike;
|
|
36
|
+
readonly byteSize: number;
|
|
37
|
+
readonly label?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const GPU_BUFFER_USAGE_UNIFORM = 0x40;
|
|
41
|
+
const GPU_BUFFER_USAGE_COPY_DST = 0x8;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Triple-buffered uniform buffer for smooth animations
|
|
45
|
+
* - Buffer 0: Currently being written by CPU
|
|
46
|
+
* - Buffer 1: Staged for next GPU read
|
|
47
|
+
* - Buffer 2: Currently being read by GPU
|
|
48
|
+
*/
|
|
49
|
+
export class TripleBufferedUniform {
|
|
50
|
+
readonly device: GPUDeviceLike;
|
|
51
|
+
readonly byteSize: number;
|
|
52
|
+
readonly label: string;
|
|
53
|
+
readonly buffers: [GPUBufferLike, GPUBufferLike, GPUBufferLike];
|
|
54
|
+
readonly data: Float32Array;
|
|
55
|
+
|
|
56
|
+
private writeIndex = 0;
|
|
57
|
+
private readIndex = 2;
|
|
58
|
+
|
|
59
|
+
constructor(options: TripleBufferedUniformOptions) {
|
|
60
|
+
if (!options?.device) {
|
|
61
|
+
throw new Error('TripleBufferedUniform requires a WebGPU-compatible device.');
|
|
62
|
+
}
|
|
63
|
+
if (!Number.isFinite(options.byteSize) || options.byteSize <= 0) {
|
|
64
|
+
throw new Error('TripleBufferedUniform byteSize must be a positive number.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.device = options.device;
|
|
68
|
+
// Align to 256 bytes (WebGPU requirement for uniform buffers)
|
|
69
|
+
this.byteSize = Math.ceil(options.byteSize / 256) * 256;
|
|
70
|
+
this.label = options.label ?? 'TripleBufferedUniform';
|
|
71
|
+
|
|
72
|
+
// Create CPU-side data buffer
|
|
73
|
+
this.data = new Float32Array(this.byteSize / Float32Array.BYTES_PER_ELEMENT);
|
|
74
|
+
|
|
75
|
+
// Create three GPU buffers
|
|
76
|
+
this.buffers = [
|
|
77
|
+
this.createBuffer(0),
|
|
78
|
+
this.createBuffer(1),
|
|
79
|
+
this.createBuffer(2)
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private createBuffer(index: number): GPUBufferLike {
|
|
84
|
+
return this.device.createBuffer({
|
|
85
|
+
size: this.byteSize,
|
|
86
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
87
|
+
label: `${this.label}-${index}`
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the buffer currently available for GPU reading
|
|
93
|
+
*/
|
|
94
|
+
get currentBuffer(): GPUBufferLike {
|
|
95
|
+
return this.buffers[this.readIndex];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Write data to the current write buffer and rotate
|
|
100
|
+
* @param queue - GPU queue for buffer upload
|
|
101
|
+
*/
|
|
102
|
+
upload(queue: GPUQueueLike = this.device.queue): void {
|
|
103
|
+
if (!queue || typeof queue.writeBuffer !== 'function') {
|
|
104
|
+
throw new Error('TripleBufferedUniform.upload requires a valid GPU queue.');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Write to current write buffer
|
|
108
|
+
queue.writeBuffer(this.buffers[this.writeIndex], 0, this.data);
|
|
109
|
+
|
|
110
|
+
// Rotate indices
|
|
111
|
+
const oldWrite = this.writeIndex;
|
|
112
|
+
this.writeIndex = (this.writeIndex + 1) % 3;
|
|
113
|
+
this.readIndex = oldWrite;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Write a float value at the specified offset
|
|
118
|
+
*/
|
|
119
|
+
writeFloat(offset: number, value: number): void {
|
|
120
|
+
const index = Math.floor(offset / Float32Array.BYTES_PER_ELEMENT);
|
|
121
|
+
if (index >= 0 && index < this.data.length) {
|
|
122
|
+
this.data[index] = value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Write a vec4 at the specified offset
|
|
128
|
+
*/
|
|
129
|
+
writeVec4(offset: number, values: ArrayLike<number>): void {
|
|
130
|
+
const startIndex = Math.floor(offset / Float32Array.BYTES_PER_ELEMENT);
|
|
131
|
+
for (let i = 0; i < 4 && i < values.length; i++) {
|
|
132
|
+
if (startIndex + i < this.data.length) {
|
|
133
|
+
this.data[startIndex + i] = values[i];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Write a mat4x4 at the specified offset
|
|
140
|
+
*/
|
|
141
|
+
writeMat4(offset: number, values: ArrayLike<number>): void {
|
|
142
|
+
const startIndex = Math.floor(offset / Float32Array.BYTES_PER_ELEMENT);
|
|
143
|
+
for (let i = 0; i < 16 && i < values.length; i++) {
|
|
144
|
+
if (startIndex + i < this.data.length) {
|
|
145
|
+
this.data[startIndex + i] = values[i];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create a bind group entry for this buffer
|
|
152
|
+
*/
|
|
153
|
+
bindGroupEntry(binding = 0): { binding: number; resource: { buffer: GPUBufferLike } } {
|
|
154
|
+
return {
|
|
155
|
+
binding,
|
|
156
|
+
resource: { buffer: this.currentBuffer }
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Destroy all buffers
|
|
162
|
+
*/
|
|
163
|
+
destroy(): void {
|
|
164
|
+
for (const buffer of this.buffers) {
|
|
165
|
+
if (buffer.destroy) {
|
|
166
|
+
buffer.destroy();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|