@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,1409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebGPUBackend - WebGPU rendering backend for VIB3+ engine
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Device/context initialization with feature detection
|
|
6
|
+
* - Canvas configuration + resize handling
|
|
7
|
+
* - Shader pipeline management (custom WGSL compilation)
|
|
8
|
+
* - Uniform buffer handling (custom layouts)
|
|
9
|
+
* - Fullscreen quad procedural rendering (core VIB3+ pattern)
|
|
10
|
+
* - Multi-pipeline support with named pipelines
|
|
11
|
+
* - Texture creation and binding
|
|
12
|
+
* - Render state management
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { RenderResourceRegistry } from '../RenderResourceRegistry.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default vertex shader for geometry rendering
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_VERTEX_SHADER = /* wgsl */`
|
|
21
|
+
struct Uniforms {
|
|
22
|
+
modelMatrix: mat4x4<f32>,
|
|
23
|
+
viewMatrix: mat4x4<f32>,
|
|
24
|
+
projectionMatrix: mat4x4<f32>,
|
|
25
|
+
time: f32,
|
|
26
|
+
dimension: f32,
|
|
27
|
+
_padding: vec2<f32>,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
31
|
+
|
|
32
|
+
struct VertexInput {
|
|
33
|
+
@location(0) position: vec4<f32>,
|
|
34
|
+
@location(1) color: vec4<f32>,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
struct VertexOutput {
|
|
38
|
+
@builtin(position) position: vec4<f32>,
|
|
39
|
+
@location(0) color: vec4<f32>,
|
|
40
|
+
@location(1) worldPos: vec3<f32>,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
@vertex
|
|
44
|
+
fn main(input: VertexInput) -> VertexOutput {
|
|
45
|
+
var output: VertexOutput;
|
|
46
|
+
|
|
47
|
+
// Apply 4D to 3D projection based on dimension parameter
|
|
48
|
+
let w = input.position.w;
|
|
49
|
+
let projectionFactor = 1.0 / (uniforms.dimension - w);
|
|
50
|
+
let projected = vec4<f32>(
|
|
51
|
+
input.position.x * projectionFactor,
|
|
52
|
+
input.position.y * projectionFactor,
|
|
53
|
+
input.position.z * projectionFactor,
|
|
54
|
+
1.0
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Apply model-view-projection
|
|
58
|
+
let worldPos = uniforms.modelMatrix * projected;
|
|
59
|
+
let viewPos = uniforms.viewMatrix * worldPos;
|
|
60
|
+
output.position = uniforms.projectionMatrix * viewPos;
|
|
61
|
+
output.worldPos = worldPos.xyz;
|
|
62
|
+
output.color = input.color;
|
|
63
|
+
|
|
64
|
+
return output;
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Default fragment shader for geometry rendering
|
|
70
|
+
*/
|
|
71
|
+
const DEFAULT_FRAGMENT_SHADER = /* wgsl */`
|
|
72
|
+
struct FragmentInput {
|
|
73
|
+
@location(0) color: vec4<f32>,
|
|
74
|
+
@location(1) worldPos: vec3<f32>,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
@fragment
|
|
78
|
+
fn main(input: FragmentInput) -> @location(0) vec4<f32> {
|
|
79
|
+
// Add subtle depth-based shading
|
|
80
|
+
let depth = clamp(length(input.worldPos) * 0.2, 0.0, 1.0);
|
|
81
|
+
let shaded = input.color.rgb * (1.0 - depth * 0.3);
|
|
82
|
+
|
|
83
|
+
return vec4<f32>(shaded, input.color.a);
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Fullscreen quad vertex shader for procedural rendering.
|
|
89
|
+
* Generates a fullscreen triangle (3 vertices, no vertex buffer needed).
|
|
90
|
+
*/
|
|
91
|
+
const FULLSCREEN_VERTEX_SHADER = /* wgsl */`
|
|
92
|
+
struct VertexOutput {
|
|
93
|
+
@builtin(position) position: vec4<f32>,
|
|
94
|
+
@location(0) uv: vec2<f32>,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
@vertex
|
|
98
|
+
fn main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
99
|
+
var output: VertexOutput;
|
|
100
|
+
|
|
101
|
+
// Generate fullscreen triangle (covers entire screen with 3 vertices)
|
|
102
|
+
// Uses the oversized triangle technique - no vertex buffer needed
|
|
103
|
+
let x = f32(i32(vertexIndex & 1u) * 4 - 1);
|
|
104
|
+
let y = f32(i32(vertexIndex >> 1u) * 4 - 1);
|
|
105
|
+
output.position = vec4<f32>(x, y, 0.0, 1.0);
|
|
106
|
+
output.uv = vec2<f32>((x + 1.0) * 0.5, (1.0 - y) * 0.5);
|
|
107
|
+
|
|
108
|
+
return output;
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* VIB3+ standard uniform struct for procedural shaders (WGSL).
|
|
114
|
+
* Matches the GLSL uniforms used in Quantum/Faceted/Holographic systems.
|
|
115
|
+
*/
|
|
116
|
+
const VIB3_UNIFORM_STRUCT = /* wgsl */`
|
|
117
|
+
struct VIB3Uniforms {
|
|
118
|
+
// Time and resolution
|
|
119
|
+
time: f32,
|
|
120
|
+
_pad0: f32,
|
|
121
|
+
resolution: vec2<f32>,
|
|
122
|
+
|
|
123
|
+
// Geometry selection (0-23)
|
|
124
|
+
geometry: f32,
|
|
125
|
+
|
|
126
|
+
// 6D Rotation (radians)
|
|
127
|
+
rot4dXY: f32,
|
|
128
|
+
rot4dXZ: f32,
|
|
129
|
+
rot4dYZ: f32,
|
|
130
|
+
rot4dXW: f32,
|
|
131
|
+
rot4dYW: f32,
|
|
132
|
+
rot4dZW: f32,
|
|
133
|
+
|
|
134
|
+
// Visual parameters
|
|
135
|
+
dimension: f32,
|
|
136
|
+
gridDensity: f32,
|
|
137
|
+
morphFactor: f32,
|
|
138
|
+
chaos: f32,
|
|
139
|
+
speed: f32,
|
|
140
|
+
hue: f32,
|
|
141
|
+
intensity: f32,
|
|
142
|
+
saturation: f32,
|
|
143
|
+
|
|
144
|
+
// Reactivity
|
|
145
|
+
mouseIntensity: f32,
|
|
146
|
+
clickIntensity: f32,
|
|
147
|
+
bass: f32,
|
|
148
|
+
mid: f32,
|
|
149
|
+
high: f32,
|
|
150
|
+
|
|
151
|
+
// Layer parameters (for holographic multi-layer)
|
|
152
|
+
layerScale: f32,
|
|
153
|
+
layerOpacity: f32,
|
|
154
|
+
_pad1: f32,
|
|
155
|
+
layerColor: vec3<f32>,
|
|
156
|
+
densityMult: f32,
|
|
157
|
+
speedMult: f32,
|
|
158
|
+
_pad2: vec3<f32>,
|
|
159
|
+
};
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* VIB3+ 4D rotation functions in WGSL
|
|
164
|
+
*/
|
|
165
|
+
const VIB3_ROTATION_WGSL = /* wgsl */`
|
|
166
|
+
fn rotateXY(angle: f32) -> mat4x4<f32> {
|
|
167
|
+
let c = cos(angle);
|
|
168
|
+
let s = sin(angle);
|
|
169
|
+
return mat4x4<f32>(
|
|
170
|
+
vec4<f32>(c, -s, 0.0, 0.0),
|
|
171
|
+
vec4<f32>(s, c, 0.0, 0.0),
|
|
172
|
+
vec4<f32>(0.0, 0.0, 1.0, 0.0),
|
|
173
|
+
vec4<f32>(0.0, 0.0, 0.0, 1.0)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fn rotateXZ(angle: f32) -> mat4x4<f32> {
|
|
178
|
+
let c = cos(angle);
|
|
179
|
+
let s = sin(angle);
|
|
180
|
+
return mat4x4<f32>(
|
|
181
|
+
vec4<f32>(c, 0.0, -s, 0.0),
|
|
182
|
+
vec4<f32>(0.0, 1.0, 0.0, 0.0),
|
|
183
|
+
vec4<f32>(s, 0.0, c, 0.0),
|
|
184
|
+
vec4<f32>(0.0, 0.0, 0.0, 1.0)
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fn rotateYZ(angle: f32) -> mat4x4<f32> {
|
|
189
|
+
let c = cos(angle);
|
|
190
|
+
let s = sin(angle);
|
|
191
|
+
return mat4x4<f32>(
|
|
192
|
+
vec4<f32>(1.0, 0.0, 0.0, 0.0),
|
|
193
|
+
vec4<f32>(0.0, c, -s, 0.0),
|
|
194
|
+
vec4<f32>(0.0, s, c, 0.0),
|
|
195
|
+
vec4<f32>(0.0, 0.0, 0.0, 1.0)
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
fn rotateXW(angle: f32) -> mat4x4<f32> {
|
|
200
|
+
let c = cos(angle);
|
|
201
|
+
let s = sin(angle);
|
|
202
|
+
return mat4x4<f32>(
|
|
203
|
+
vec4<f32>(c, 0.0, 0.0, -s),
|
|
204
|
+
vec4<f32>(0.0, 1.0, 0.0, 0.0),
|
|
205
|
+
vec4<f32>(0.0, 0.0, 1.0, 0.0),
|
|
206
|
+
vec4<f32>(s, 0.0, 0.0, c)
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
fn rotateYW(angle: f32) -> mat4x4<f32> {
|
|
211
|
+
let c = cos(angle);
|
|
212
|
+
let s = sin(angle);
|
|
213
|
+
return mat4x4<f32>(
|
|
214
|
+
vec4<f32>(1.0, 0.0, 0.0, 0.0),
|
|
215
|
+
vec4<f32>(0.0, c, 0.0, -s),
|
|
216
|
+
vec4<f32>(0.0, 0.0, 1.0, 0.0),
|
|
217
|
+
vec4<f32>(0.0, s, 0.0, c)
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn rotateZW(angle: f32) -> mat4x4<f32> {
|
|
222
|
+
let c = cos(angle);
|
|
223
|
+
let s = sin(angle);
|
|
224
|
+
return mat4x4<f32>(
|
|
225
|
+
vec4<f32>(1.0, 0.0, 0.0, 0.0),
|
|
226
|
+
vec4<f32>(0.0, 1.0, 0.0, 0.0),
|
|
227
|
+
vec4<f32>(0.0, 0.0, c, -s),
|
|
228
|
+
vec4<f32>(0.0, 0.0, s, c)
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fn apply6DRotation(pos: vec4<f32>, u: VIB3Uniforms) -> vec4<f32> {
|
|
233
|
+
var p = pos;
|
|
234
|
+
p = rotateXY(u.rot4dXY) * p;
|
|
235
|
+
p = rotateXZ(u.rot4dXZ) * p;
|
|
236
|
+
p = rotateYZ(u.rot4dYZ) * p;
|
|
237
|
+
p = rotateXW(u.rot4dXW) * p;
|
|
238
|
+
p = rotateYW(u.rot4dYW) * p;
|
|
239
|
+
p = rotateZW(u.rot4dZW) * p;
|
|
240
|
+
return p;
|
|
241
|
+
}
|
|
242
|
+
`;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* WebGPU feature flags
|
|
246
|
+
*/
|
|
247
|
+
export const WebGPUFeatures = {
|
|
248
|
+
TIMESTAMP_QUERY: 'timestamp-query',
|
|
249
|
+
INDIRECT_FIRST_INSTANCE: 'indirect-first-instance',
|
|
250
|
+
SHADER_F16: 'shader-f16',
|
|
251
|
+
DEPTH_CLIP_CONTROL: 'depth-clip-control',
|
|
252
|
+
DEPTH32_STENCIL8: 'depth32float-stencil8',
|
|
253
|
+
TEXTURE_COMPRESSION_BC: 'texture-compression-bc',
|
|
254
|
+
RG11B10_UFLOAT_RENDERABLE: 'rg11b10ufloat-renderable',
|
|
255
|
+
BGRA8_UNORM_STORAGE: 'bgra8unorm-storage'
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* WGSL shader library for VIB3+ systems
|
|
260
|
+
*/
|
|
261
|
+
export const WGSLShaderLib = {
|
|
262
|
+
uniformStruct: VIB3_UNIFORM_STRUCT,
|
|
263
|
+
rotation4D: VIB3_ROTATION_WGSL,
|
|
264
|
+
fullscreenVertex: FULLSCREEN_VERTEX_SHADER
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export class WebGPUBackend {
|
|
268
|
+
/**
|
|
269
|
+
* @param {object} params
|
|
270
|
+
* @param {HTMLCanvasElement} params.canvas
|
|
271
|
+
* @param {GPUDevice} params.device
|
|
272
|
+
* @param {GPUCanvasContext} params.context
|
|
273
|
+
* @param {GPUTextureFormat} params.format
|
|
274
|
+
* @param {GPUAdapter} [params.adapter]
|
|
275
|
+
* @param {object} [options]
|
|
276
|
+
*/
|
|
277
|
+
constructor({ canvas, device, context, format, adapter }, options = {}) {
|
|
278
|
+
this.canvas = canvas;
|
|
279
|
+
this.device = device;
|
|
280
|
+
this.context = context;
|
|
281
|
+
this.format = format;
|
|
282
|
+
this.adapter = adapter || null;
|
|
283
|
+
|
|
284
|
+
this.debug = options.debug || false;
|
|
285
|
+
this.depthEnabled = options.depth !== false;
|
|
286
|
+
this._resources = options.resourceRegistry || new RenderResourceRegistry();
|
|
287
|
+
|
|
288
|
+
/** @type {GPUTexture|null} */
|
|
289
|
+
this._depthTexture = null;
|
|
290
|
+
|
|
291
|
+
/** @type {Map<string, GPURenderPipeline>} */
|
|
292
|
+
this._pipelines = new Map();
|
|
293
|
+
|
|
294
|
+
/** @type {Map<string, GPUShaderModule>} */
|
|
295
|
+
this._shaderModules = new Map();
|
|
296
|
+
|
|
297
|
+
/** @type {GPUBuffer|null} - Default geometry uniform buffer */
|
|
298
|
+
this._uniformBuffer = null;
|
|
299
|
+
|
|
300
|
+
/** @type {GPUBindGroup|null} */
|
|
301
|
+
this._uniformBindGroup = null;
|
|
302
|
+
|
|
303
|
+
/** @type {GPUBindGroupLayout|null} */
|
|
304
|
+
this._uniformBindGroupLayout = null;
|
|
305
|
+
|
|
306
|
+
/** @type {Map<string, {buffer: GPUBuffer, bindGroup: GPUBindGroup, layout: GPUBindGroupLayout}>} */
|
|
307
|
+
this._customUniformBuffers = new Map();
|
|
308
|
+
|
|
309
|
+
/** @type {Map<string, GPUTexture>} */
|
|
310
|
+
this._textures = new Map();
|
|
311
|
+
|
|
312
|
+
/** @type {Map<string, GPUSampler>} */
|
|
313
|
+
this._samplers = new Map();
|
|
314
|
+
|
|
315
|
+
/** @type {Set<string>} */
|
|
316
|
+
this._enabledFeatures = new Set(options.features || []);
|
|
317
|
+
|
|
318
|
+
this._stats = {
|
|
319
|
+
frames: 0,
|
|
320
|
+
commandEncoders: 0,
|
|
321
|
+
drawCalls: 0,
|
|
322
|
+
triangles: 0,
|
|
323
|
+
pipelineChanges: 0
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Initialize uniform buffer
|
|
327
|
+
this._initUniformBuffer();
|
|
328
|
+
|
|
329
|
+
// Create default pipeline
|
|
330
|
+
this._createDefaultPipeline();
|
|
331
|
+
|
|
332
|
+
this.resize(canvas.clientWidth || canvas.width, canvas.clientHeight || canvas.height);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ========================================================================
|
|
336
|
+
// Feature Detection
|
|
337
|
+
// ========================================================================
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Check if a feature is supported
|
|
341
|
+
* @param {string} feature
|
|
342
|
+
* @returns {boolean}
|
|
343
|
+
*/
|
|
344
|
+
hasFeature(feature) {
|
|
345
|
+
return this._enabledFeatures.has(feature);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Get GPU info
|
|
350
|
+
* @returns {object}
|
|
351
|
+
*/
|
|
352
|
+
getGPUInfo() {
|
|
353
|
+
if (!this.adapter) return { vendor: 'unknown', architecture: 'unknown' };
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
vendor: this.adapter.info?.vendor || 'unknown',
|
|
357
|
+
architecture: this.adapter.info?.architecture || 'unknown',
|
|
358
|
+
device: this.adapter.info?.device || 'unknown',
|
|
359
|
+
description: this.adapter.info?.description || 'unknown',
|
|
360
|
+
features: Array.from(this._enabledFeatures)
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ========================================================================
|
|
365
|
+
// Uniform Buffer Management
|
|
366
|
+
// ========================================================================
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Initialize default uniform buffer (geometry rendering)
|
|
370
|
+
* @private
|
|
371
|
+
*/
|
|
372
|
+
_initUniformBuffer() {
|
|
373
|
+
const uniformBufferSize = 256;
|
|
374
|
+
|
|
375
|
+
this._uniformBuffer = this.device.createBuffer({
|
|
376
|
+
size: uniformBufferSize,
|
|
377
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
378
|
+
});
|
|
379
|
+
this._resources.register('buffer', this._uniformBuffer);
|
|
380
|
+
|
|
381
|
+
this._uniformBindGroupLayout = this.device.createBindGroupLayout({
|
|
382
|
+
entries: [{
|
|
383
|
+
binding: 0,
|
|
384
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
385
|
+
buffer: { type: 'uniform' }
|
|
386
|
+
}]
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
this._uniformBindGroup = this.device.createBindGroup({
|
|
390
|
+
layout: this._uniformBindGroupLayout,
|
|
391
|
+
entries: [{
|
|
392
|
+
binding: 0,
|
|
393
|
+
resource: { buffer: this._uniformBuffer }
|
|
394
|
+
}]
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Create a custom uniform buffer with its own bind group.
|
|
400
|
+
* Used for VIB3+ procedural shader uniforms.
|
|
401
|
+
* @param {string} name - Unique name for the buffer
|
|
402
|
+
* @param {number} size - Buffer size in bytes (will be aligned to 256)
|
|
403
|
+
* @param {number} [visibility] - Shader stage visibility
|
|
404
|
+
* @returns {{buffer: GPUBuffer, bindGroup: GPUBindGroup, layout: GPUBindGroupLayout}}
|
|
405
|
+
*/
|
|
406
|
+
createCustomUniformBuffer(name, size, visibility) {
|
|
407
|
+
const vis = visibility ??
|
|
408
|
+
(GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT);
|
|
409
|
+
|
|
410
|
+
// Align to 256 bytes for WebGPU requirements
|
|
411
|
+
const alignedSize = Math.ceil(size / 256) * 256;
|
|
412
|
+
|
|
413
|
+
const buffer = this.device.createBuffer({
|
|
414
|
+
size: alignedSize,
|
|
415
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
416
|
+
});
|
|
417
|
+
this._resources.register('buffer', buffer);
|
|
418
|
+
|
|
419
|
+
const layout = this.device.createBindGroupLayout({
|
|
420
|
+
entries: [{
|
|
421
|
+
binding: 0,
|
|
422
|
+
visibility: vis,
|
|
423
|
+
buffer: { type: 'uniform' }
|
|
424
|
+
}]
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const bindGroup = this.device.createBindGroup({
|
|
428
|
+
layout,
|
|
429
|
+
entries: [{
|
|
430
|
+
binding: 0,
|
|
431
|
+
resource: { buffer }
|
|
432
|
+
}]
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const entry = { buffer, bindGroup, layout };
|
|
436
|
+
this._customUniformBuffers.set(name, entry);
|
|
437
|
+
return entry;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Update a custom uniform buffer with Float32Array data
|
|
442
|
+
* @param {string} name - Buffer name
|
|
443
|
+
* @param {Float32Array} data - Data to write
|
|
444
|
+
*/
|
|
445
|
+
updateCustomUniforms(name, data) {
|
|
446
|
+
const entry = this._customUniformBuffers.get(name);
|
|
447
|
+
if (!entry) {
|
|
448
|
+
if (this.debug) {
|
|
449
|
+
console.warn(`Custom uniform buffer "${name}" not found`);
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
this.device.queue.writeBuffer(entry.buffer, 0, data);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Get a custom uniform buffer entry
|
|
458
|
+
* @param {string} name
|
|
459
|
+
* @returns {{buffer: GPUBuffer, bindGroup: GPUBindGroup, layout: GPUBindGroupLayout}|undefined}
|
|
460
|
+
*/
|
|
461
|
+
getCustomUniformBuffer(name) {
|
|
462
|
+
return this._customUniformBuffers.get(name);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Update default uniform buffer with current state
|
|
467
|
+
* @param {object} uniforms
|
|
468
|
+
*/
|
|
469
|
+
updateUniforms(uniforms) {
|
|
470
|
+
const data = new Float32Array(64); // 256 bytes / 4
|
|
471
|
+
|
|
472
|
+
const model = uniforms.modelMatrix || [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];
|
|
473
|
+
data.set(model, 0);
|
|
474
|
+
|
|
475
|
+
const view = uniforms.viewMatrix || [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,-3,1];
|
|
476
|
+
data.set(view, 16);
|
|
477
|
+
|
|
478
|
+
const proj = uniforms.projectionMatrix || this._createProjectionMatrix();
|
|
479
|
+
data.set(proj, 32);
|
|
480
|
+
|
|
481
|
+
data[48] = uniforms.time || 0;
|
|
482
|
+
data[49] = uniforms.dimension || 3.5;
|
|
483
|
+
|
|
484
|
+
this.device.queue.writeBuffer(this._uniformBuffer, 0, data);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Create perspective projection matrix
|
|
489
|
+
* @private
|
|
490
|
+
*/
|
|
491
|
+
_createProjectionMatrix() {
|
|
492
|
+
const fov = Math.PI / 4;
|
|
493
|
+
const aspect = this.canvas.width / this.canvas.height;
|
|
494
|
+
const near = 0.1;
|
|
495
|
+
const far = 100;
|
|
496
|
+
|
|
497
|
+
const f = 1.0 / Math.tan(fov / 2);
|
|
498
|
+
const rangeInv = 1 / (near - far);
|
|
499
|
+
|
|
500
|
+
return [
|
|
501
|
+
f / aspect, 0, 0, 0,
|
|
502
|
+
0, f, 0, 0,
|
|
503
|
+
0, 0, (near + far) * rangeInv, -1,
|
|
504
|
+
0, 0, near * far * rangeInv * 2, 0
|
|
505
|
+
];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// ========================================================================
|
|
509
|
+
// Shader & Pipeline Management
|
|
510
|
+
// ========================================================================
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Compile a WGSL shader module from source code
|
|
514
|
+
* @param {string} name - Unique shader name
|
|
515
|
+
* @param {string} code - WGSL source code
|
|
516
|
+
* @returns {GPUShaderModule}
|
|
517
|
+
*/
|
|
518
|
+
compileShader(name, code) {
|
|
519
|
+
const module = this.device.createShaderModule({
|
|
520
|
+
code,
|
|
521
|
+
label: name
|
|
522
|
+
});
|
|
523
|
+
this._shaderModules.set(name, module);
|
|
524
|
+
return module;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Get or create shader module
|
|
529
|
+
* @param {string} name
|
|
530
|
+
* @param {string} code
|
|
531
|
+
* @returns {GPUShaderModule}
|
|
532
|
+
*/
|
|
533
|
+
_getOrCreateShaderModule(name, code) {
|
|
534
|
+
if (this._shaderModules.has(name)) {
|
|
535
|
+
return this._shaderModules.get(name);
|
|
536
|
+
}
|
|
537
|
+
return this.compileShader(name, code);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Create a fullscreen quad pipeline for procedural fragment rendering.
|
|
542
|
+
* This is the core rendering pattern for all VIB3+ visualization systems.
|
|
543
|
+
*
|
|
544
|
+
* @param {string} name - Pipeline name
|
|
545
|
+
* @param {string} fragmentCode - WGSL fragment shader code
|
|
546
|
+
* @param {object} [options]
|
|
547
|
+
* @param {string} [options.vertexCode] - Custom vertex shader (defaults to fullscreen quad)
|
|
548
|
+
* @param {GPUBindGroupLayout[]} [options.bindGroupLayouts] - Custom bind group layouts
|
|
549
|
+
* @param {object} [options.blend] - Custom blend state
|
|
550
|
+
* @param {boolean} [options.depth] - Enable depth testing
|
|
551
|
+
* @returns {GPURenderPipeline}
|
|
552
|
+
*/
|
|
553
|
+
createFullscreenPipeline(name, fragmentCode, options = {}) {
|
|
554
|
+
const vertexCode = options.vertexCode || FULLSCREEN_VERTEX_SHADER;
|
|
555
|
+
const vertexModule = this._getOrCreateShaderModule(
|
|
556
|
+
`${name}-vertex`, vertexCode
|
|
557
|
+
);
|
|
558
|
+
const fragmentModule = this._getOrCreateShaderModule(
|
|
559
|
+
`${name}-fragment`, fragmentCode
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
// Use provided layouts or default to the standard uniform layout
|
|
563
|
+
const bindGroupLayouts = options.bindGroupLayouts ||
|
|
564
|
+
[this._uniformBindGroupLayout];
|
|
565
|
+
|
|
566
|
+
const pipelineLayout = this.device.createPipelineLayout({
|
|
567
|
+
bindGroupLayouts
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
const blend = options.blend || {
|
|
571
|
+
color: {
|
|
572
|
+
srcFactor: 'src-alpha',
|
|
573
|
+
dstFactor: 'one-minus-src-alpha',
|
|
574
|
+
operation: 'add'
|
|
575
|
+
},
|
|
576
|
+
alpha: {
|
|
577
|
+
srcFactor: 'one',
|
|
578
|
+
dstFactor: 'one-minus-src-alpha',
|
|
579
|
+
operation: 'add'
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const useDepth = options.depth !== undefined ? options.depth : false;
|
|
584
|
+
|
|
585
|
+
const pipeline = this.device.createRenderPipeline({
|
|
586
|
+
layout: pipelineLayout,
|
|
587
|
+
vertex: {
|
|
588
|
+
module: vertexModule,
|
|
589
|
+
entryPoint: 'main',
|
|
590
|
+
// No vertex buffers - fullscreen triangle uses vertex_index
|
|
591
|
+
},
|
|
592
|
+
fragment: {
|
|
593
|
+
module: fragmentModule,
|
|
594
|
+
entryPoint: 'main',
|
|
595
|
+
targets: [{
|
|
596
|
+
format: this.format,
|
|
597
|
+
blend
|
|
598
|
+
}]
|
|
599
|
+
},
|
|
600
|
+
primitive: {
|
|
601
|
+
topology: 'triangle-list'
|
|
602
|
+
},
|
|
603
|
+
depthStencil: useDepth ? {
|
|
604
|
+
depthWriteEnabled: true,
|
|
605
|
+
depthCompare: 'less',
|
|
606
|
+
format: 'depth24plus'
|
|
607
|
+
} : undefined
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
this._pipelines.set(name, pipeline);
|
|
611
|
+
return pipeline;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Create a custom render pipeline with vertex buffers (for geometry rendering)
|
|
616
|
+
* @param {string} name - Pipeline name
|
|
617
|
+
* @param {object} desc - Pipeline descriptor
|
|
618
|
+
* @param {string} desc.vertexCode - WGSL vertex shader
|
|
619
|
+
* @param {string} desc.fragmentCode - WGSL fragment shader
|
|
620
|
+
* @param {GPUVertexBufferLayout[]} [desc.vertexBuffers] - Vertex buffer layouts
|
|
621
|
+
* @param {GPUBindGroupLayout[]} [desc.bindGroupLayouts]
|
|
622
|
+
* @param {object} [desc.blend]
|
|
623
|
+
* @param {string} [desc.topology]
|
|
624
|
+
* @param {boolean} [desc.depth]
|
|
625
|
+
* @returns {GPURenderPipeline}
|
|
626
|
+
*/
|
|
627
|
+
createPipeline(name, desc) {
|
|
628
|
+
const vertexModule = this._getOrCreateShaderModule(
|
|
629
|
+
`${name}-vertex`, desc.vertexCode
|
|
630
|
+
);
|
|
631
|
+
const fragmentModule = this._getOrCreateShaderModule(
|
|
632
|
+
`${name}-fragment`, desc.fragmentCode
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
const bindGroupLayouts = desc.bindGroupLayouts ||
|
|
636
|
+
[this._uniformBindGroupLayout];
|
|
637
|
+
|
|
638
|
+
const pipelineLayout = this.device.createPipelineLayout({
|
|
639
|
+
bindGroupLayouts
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const blend = desc.blend || {
|
|
643
|
+
color: {
|
|
644
|
+
srcFactor: 'src-alpha',
|
|
645
|
+
dstFactor: 'one-minus-src-alpha',
|
|
646
|
+
operation: 'add'
|
|
647
|
+
},
|
|
648
|
+
alpha: {
|
|
649
|
+
srcFactor: 'one',
|
|
650
|
+
dstFactor: 'one-minus-src-alpha',
|
|
651
|
+
operation: 'add'
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
const pipeline = this.device.createRenderPipeline({
|
|
656
|
+
layout: pipelineLayout,
|
|
657
|
+
vertex: {
|
|
658
|
+
module: vertexModule,
|
|
659
|
+
entryPoint: 'main',
|
|
660
|
+
buffers: desc.vertexBuffers || [{
|
|
661
|
+
arrayStride: 32,
|
|
662
|
+
attributes: [
|
|
663
|
+
{ shaderLocation: 0, offset: 0, format: 'float32x4' },
|
|
664
|
+
{ shaderLocation: 1, offset: 16, format: 'float32x4' }
|
|
665
|
+
]
|
|
666
|
+
}]
|
|
667
|
+
},
|
|
668
|
+
fragment: {
|
|
669
|
+
module: fragmentModule,
|
|
670
|
+
entryPoint: 'main',
|
|
671
|
+
targets: [{
|
|
672
|
+
format: this.format,
|
|
673
|
+
blend
|
|
674
|
+
}]
|
|
675
|
+
},
|
|
676
|
+
primitive: {
|
|
677
|
+
topology: desc.topology || 'triangle-list',
|
|
678
|
+
cullMode: desc.cullMode || 'none',
|
|
679
|
+
frontFace: 'ccw'
|
|
680
|
+
},
|
|
681
|
+
depthStencil: (desc.depth !== false && this.depthEnabled) ? {
|
|
682
|
+
depthWriteEnabled: true,
|
|
683
|
+
depthCompare: 'less',
|
|
684
|
+
format: 'depth24plus'
|
|
685
|
+
} : undefined
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
this._pipelines.set(name, pipeline);
|
|
689
|
+
return pipeline;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Get a named pipeline
|
|
694
|
+
* @param {string} name
|
|
695
|
+
* @returns {GPURenderPipeline|undefined}
|
|
696
|
+
*/
|
|
697
|
+
getPipeline(name) {
|
|
698
|
+
return this._pipelines.get(name);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Create default rendering pipeline
|
|
703
|
+
* @private
|
|
704
|
+
*/
|
|
705
|
+
_createDefaultPipeline() {
|
|
706
|
+
const vertexModule = this._getOrCreateShaderModule('default-vertex', DEFAULT_VERTEX_SHADER);
|
|
707
|
+
const fragmentModule = this._getOrCreateShaderModule('default-fragment', DEFAULT_FRAGMENT_SHADER);
|
|
708
|
+
|
|
709
|
+
const pipelineLayout = this.device.createPipelineLayout({
|
|
710
|
+
bindGroupLayouts: [this._uniformBindGroupLayout]
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
const pipeline = this.device.createRenderPipeline({
|
|
714
|
+
layout: pipelineLayout,
|
|
715
|
+
vertex: {
|
|
716
|
+
module: vertexModule,
|
|
717
|
+
entryPoint: 'main',
|
|
718
|
+
buffers: [{
|
|
719
|
+
arrayStride: 32,
|
|
720
|
+
attributes: [
|
|
721
|
+
{ shaderLocation: 0, offset: 0, format: 'float32x4' },
|
|
722
|
+
{ shaderLocation: 1, offset: 16, format: 'float32x4' }
|
|
723
|
+
]
|
|
724
|
+
}]
|
|
725
|
+
},
|
|
726
|
+
fragment: {
|
|
727
|
+
module: fragmentModule,
|
|
728
|
+
entryPoint: 'main',
|
|
729
|
+
targets: [{
|
|
730
|
+
format: this.format,
|
|
731
|
+
blend: {
|
|
732
|
+
color: {
|
|
733
|
+
srcFactor: 'src-alpha',
|
|
734
|
+
dstFactor: 'one-minus-src-alpha',
|
|
735
|
+
operation: 'add'
|
|
736
|
+
},
|
|
737
|
+
alpha: {
|
|
738
|
+
srcFactor: 'one',
|
|
739
|
+
dstFactor: 'one-minus-src-alpha',
|
|
740
|
+
operation: 'add'
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}]
|
|
744
|
+
},
|
|
745
|
+
primitive: {
|
|
746
|
+
topology: 'triangle-list',
|
|
747
|
+
cullMode: 'back',
|
|
748
|
+
frontFace: 'ccw'
|
|
749
|
+
},
|
|
750
|
+
depthStencil: this.depthEnabled ? {
|
|
751
|
+
depthWriteEnabled: true,
|
|
752
|
+
depthCompare: 'less',
|
|
753
|
+
format: 'depth24plus'
|
|
754
|
+
} : undefined
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
this._pipelines.set('default', pipeline);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// ========================================================================
|
|
761
|
+
// Texture Management
|
|
762
|
+
// ========================================================================
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Create a 2D texture
|
|
766
|
+
* @param {string} name
|
|
767
|
+
* @param {object} desc
|
|
768
|
+
* @param {number} desc.width
|
|
769
|
+
* @param {number} desc.height
|
|
770
|
+
* @param {string} [desc.format]
|
|
771
|
+
* @param {number} [desc.usage]
|
|
772
|
+
* @returns {GPUTexture}
|
|
773
|
+
*/
|
|
774
|
+
createTexture(name, desc) {
|
|
775
|
+
const texture = this.device.createTexture({
|
|
776
|
+
size: { width: desc.width, height: desc.height },
|
|
777
|
+
format: desc.format || 'rgba8unorm',
|
|
778
|
+
usage: desc.usage || (
|
|
779
|
+
GPUTextureUsage.TEXTURE_BINDING |
|
|
780
|
+
GPUTextureUsage.COPY_DST |
|
|
781
|
+
GPUTextureUsage.RENDER_ATTACHMENT
|
|
782
|
+
)
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
this._resources.register('texture', texture);
|
|
786
|
+
this._textures.set(name, texture);
|
|
787
|
+
return texture;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Create a sampler
|
|
792
|
+
* @param {string} name
|
|
793
|
+
* @param {object} [desc]
|
|
794
|
+
* @returns {GPUSampler}
|
|
795
|
+
*/
|
|
796
|
+
createSampler(name, desc = {}) {
|
|
797
|
+
const sampler = this.device.createSampler({
|
|
798
|
+
magFilter: desc.magFilter || 'linear',
|
|
799
|
+
minFilter: desc.minFilter || 'linear',
|
|
800
|
+
mipmapFilter: desc.mipmapFilter || 'linear',
|
|
801
|
+
addressModeU: desc.addressModeU || 'clamp-to-edge',
|
|
802
|
+
addressModeV: desc.addressModeV || 'clamp-to-edge'
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
this._samplers.set(name, sampler);
|
|
806
|
+
return sampler;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Get a texture by name
|
|
811
|
+
* @param {string} name
|
|
812
|
+
* @returns {GPUTexture|undefined}
|
|
813
|
+
*/
|
|
814
|
+
getTexture(name) {
|
|
815
|
+
return this._textures.get(name);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Get a sampler by name
|
|
820
|
+
* @param {string} name
|
|
821
|
+
* @returns {GPUSampler|undefined}
|
|
822
|
+
*/
|
|
823
|
+
getSampler(name) {
|
|
824
|
+
return this._samplers.get(name);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// ========================================================================
|
|
828
|
+
// Canvas / Resize
|
|
829
|
+
// ========================================================================
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Resize the canvas and recreate depth resources if enabled.
|
|
833
|
+
* @param {number} width
|
|
834
|
+
* @param {number} height
|
|
835
|
+
*/
|
|
836
|
+
resize(width, height) {
|
|
837
|
+
const clampedWidth = Math.max(1, Math.floor(width));
|
|
838
|
+
const clampedHeight = Math.max(1, Math.floor(height));
|
|
839
|
+
|
|
840
|
+
this.canvas.width = clampedWidth;
|
|
841
|
+
this.canvas.height = clampedHeight;
|
|
842
|
+
|
|
843
|
+
this.context.configure({
|
|
844
|
+
device: this.device,
|
|
845
|
+
format: this.format,
|
|
846
|
+
alphaMode: 'premultiplied'
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
if (this.depthEnabled) {
|
|
850
|
+
this._destroyDepthTexture();
|
|
851
|
+
this._depthTexture = this.device.createTexture({
|
|
852
|
+
size: { width: clampedWidth, height: clampedHeight, depthOrArrayLayers: 1 },
|
|
853
|
+
format: 'depth24plus',
|
|
854
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
855
|
+
});
|
|
856
|
+
this._resources.register('texture', this._depthTexture);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// ========================================================================
|
|
861
|
+
// Buffer Creation
|
|
862
|
+
// ========================================================================
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Create a vertex buffer from geometry data
|
|
866
|
+
* @param {Float32Array} data - Interleaved vertex data
|
|
867
|
+
* @returns {GPUBuffer}
|
|
868
|
+
*/
|
|
869
|
+
createVertexBuffer(data) {
|
|
870
|
+
const buffer = this.device.createBuffer({
|
|
871
|
+
size: data.byteLength,
|
|
872
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
873
|
+
mappedAtCreation: true
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
new Float32Array(buffer.getMappedRange()).set(data);
|
|
877
|
+
buffer.unmap();
|
|
878
|
+
|
|
879
|
+
this._resources.register('buffer', buffer);
|
|
880
|
+
return buffer;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Create an index buffer
|
|
885
|
+
* @param {Uint16Array|Uint32Array} data
|
|
886
|
+
* @returns {GPUBuffer}
|
|
887
|
+
*/
|
|
888
|
+
createIndexBuffer(data) {
|
|
889
|
+
const buffer = this.device.createBuffer({
|
|
890
|
+
size: data.byteLength,
|
|
891
|
+
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
892
|
+
mappedAtCreation: true
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
if (data instanceof Uint16Array) {
|
|
896
|
+
new Uint16Array(buffer.getMappedRange()).set(data);
|
|
897
|
+
} else {
|
|
898
|
+
new Uint32Array(buffer.getMappedRange()).set(data);
|
|
899
|
+
}
|
|
900
|
+
buffer.unmap();
|
|
901
|
+
|
|
902
|
+
this._resources.register('buffer', buffer);
|
|
903
|
+
return buffer;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// ========================================================================
|
|
907
|
+
// Rendering - Fullscreen Quad (VIB3+ Procedural)
|
|
908
|
+
// ========================================================================
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Render a fullscreen quad using a named pipeline.
|
|
912
|
+
* This is the primary rendering method for VIB3+ procedural visualization.
|
|
913
|
+
*
|
|
914
|
+
* @param {object} options
|
|
915
|
+
* @param {string} options.pipeline - Pipeline name (created via createFullscreenPipeline)
|
|
916
|
+
* @param {GPUBindGroup[]} [options.bindGroups] - Bind groups to set
|
|
917
|
+
* @param {number[]} [options.clearColor] - RGBA clear color (0-1)
|
|
918
|
+
* @param {boolean} [options.clear] - Whether to clear (default true)
|
|
919
|
+
*/
|
|
920
|
+
renderFullscreenQuad(options) {
|
|
921
|
+
const {
|
|
922
|
+
pipeline: pipelineName,
|
|
923
|
+
bindGroups = [],
|
|
924
|
+
clearColor = [0, 0, 0, 1],
|
|
925
|
+
clear = true
|
|
926
|
+
} = options;
|
|
927
|
+
|
|
928
|
+
const pipeline = this._pipelines.get(pipelineName);
|
|
929
|
+
if (!pipeline) {
|
|
930
|
+
if (this.debug) {
|
|
931
|
+
console.warn(`Pipeline "${pipelineName}" not found`);
|
|
932
|
+
}
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
const encoder = this.device.createCommandEncoder();
|
|
937
|
+
this._stats.commandEncoders += 1;
|
|
938
|
+
|
|
939
|
+
const colorView = this.context.getCurrentTexture().createView();
|
|
940
|
+
|
|
941
|
+
const pass = encoder.beginRenderPass({
|
|
942
|
+
colorAttachments: [{
|
|
943
|
+
view: colorView,
|
|
944
|
+
clearValue: {
|
|
945
|
+
r: clearColor[0],
|
|
946
|
+
g: clearColor[1],
|
|
947
|
+
b: clearColor[2],
|
|
948
|
+
a: clearColor[3]
|
|
949
|
+
},
|
|
950
|
+
loadOp: clear ? 'clear' : 'load',
|
|
951
|
+
storeOp: 'store'
|
|
952
|
+
}]
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
pass.setPipeline(pipeline);
|
|
956
|
+
this._stats.pipelineChanges += 1;
|
|
957
|
+
|
|
958
|
+
// Set bind groups
|
|
959
|
+
for (let i = 0; i < bindGroups.length; i++) {
|
|
960
|
+
pass.setBindGroup(i, bindGroups[i]);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// Draw fullscreen triangle (3 vertices, no vertex buffer)
|
|
964
|
+
pass.draw(3);
|
|
965
|
+
this._stats.drawCalls += 1;
|
|
966
|
+
|
|
967
|
+
pass.end();
|
|
968
|
+
this.device.queue.submit([encoder.finish()]);
|
|
969
|
+
this._stats.frames += 1;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// ========================================================================
|
|
973
|
+
// Rendering - Geometry
|
|
974
|
+
// ========================================================================
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* Render a single frame (clear-only pass by default).
|
|
978
|
+
* @param {object} [options]
|
|
979
|
+
* @param {number[]} [options.clearColor] - RGBA in 0-1
|
|
980
|
+
*/
|
|
981
|
+
renderFrame(options = {}) {
|
|
982
|
+
const clearColor = options.clearColor || [0, 0, 0, 1];
|
|
983
|
+
const encoder = this.device.createCommandEncoder();
|
|
984
|
+
this._stats.commandEncoders += 1;
|
|
985
|
+
|
|
986
|
+
const colorView = this.context.getCurrentTexture().createView();
|
|
987
|
+
const depthAttachment = this.depthEnabled && this._depthTexture
|
|
988
|
+
? {
|
|
989
|
+
view: this._depthTexture.createView(),
|
|
990
|
+
depthClearValue: 1.0,
|
|
991
|
+
depthLoadOp: 'clear',
|
|
992
|
+
depthStoreOp: 'store'
|
|
993
|
+
}
|
|
994
|
+
: undefined;
|
|
995
|
+
|
|
996
|
+
const pass = encoder.beginRenderPass({
|
|
997
|
+
colorAttachments: [{
|
|
998
|
+
view: colorView,
|
|
999
|
+
clearValue: { r: clearColor[0], g: clearColor[1], b: clearColor[2], a: clearColor[3] },
|
|
1000
|
+
loadOp: 'clear',
|
|
1001
|
+
storeOp: 'store'
|
|
1002
|
+
}],
|
|
1003
|
+
depthStencilAttachment: depthAttachment
|
|
1004
|
+
});
|
|
1005
|
+
pass.end();
|
|
1006
|
+
|
|
1007
|
+
this.device.queue.submit([encoder.finish()]);
|
|
1008
|
+
this._stats.frames += 1;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Render geometry with the default pipeline
|
|
1013
|
+
* @param {object} options
|
|
1014
|
+
* @param {GPUBuffer} options.vertexBuffer
|
|
1015
|
+
* @param {GPUBuffer} [options.indexBuffer]
|
|
1016
|
+
* @param {number} options.vertexCount
|
|
1017
|
+
* @param {number} [options.indexCount]
|
|
1018
|
+
* @param {object} [options.uniforms]
|
|
1019
|
+
* @param {number[]} [options.clearColor]
|
|
1020
|
+
*/
|
|
1021
|
+
renderGeometry(options) {
|
|
1022
|
+
const {
|
|
1023
|
+
vertexBuffer,
|
|
1024
|
+
indexBuffer,
|
|
1025
|
+
vertexCount,
|
|
1026
|
+
indexCount,
|
|
1027
|
+
uniforms = {},
|
|
1028
|
+
clearColor = [0, 0, 0, 1]
|
|
1029
|
+
} = options;
|
|
1030
|
+
|
|
1031
|
+
this.updateUniforms(uniforms);
|
|
1032
|
+
|
|
1033
|
+
const encoder = this.device.createCommandEncoder();
|
|
1034
|
+
this._stats.commandEncoders += 1;
|
|
1035
|
+
|
|
1036
|
+
const colorView = this.context.getCurrentTexture().createView();
|
|
1037
|
+
const depthAttachment = this.depthEnabled && this._depthTexture
|
|
1038
|
+
? {
|
|
1039
|
+
view: this._depthTexture.createView(),
|
|
1040
|
+
depthClearValue: 1.0,
|
|
1041
|
+
depthLoadOp: 'clear',
|
|
1042
|
+
depthStoreOp: 'store'
|
|
1043
|
+
}
|
|
1044
|
+
: undefined;
|
|
1045
|
+
|
|
1046
|
+
const pass = encoder.beginRenderPass({
|
|
1047
|
+
colorAttachments: [{
|
|
1048
|
+
view: colorView,
|
|
1049
|
+
clearValue: { r: clearColor[0], g: clearColor[1], b: clearColor[2], a: clearColor[3] },
|
|
1050
|
+
loadOp: 'clear',
|
|
1051
|
+
storeOp: 'store'
|
|
1052
|
+
}],
|
|
1053
|
+
depthStencilAttachment: depthAttachment
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
const pipeline = this._pipelines.get('default');
|
|
1057
|
+
pass.setPipeline(pipeline);
|
|
1058
|
+
this._stats.pipelineChanges += 1;
|
|
1059
|
+
|
|
1060
|
+
pass.setBindGroup(0, this._uniformBindGroup);
|
|
1061
|
+
pass.setVertexBuffer(0, vertexBuffer);
|
|
1062
|
+
|
|
1063
|
+
if (indexBuffer && indexCount) {
|
|
1064
|
+
pass.setIndexBuffer(indexBuffer, 'uint16');
|
|
1065
|
+
pass.drawIndexed(indexCount);
|
|
1066
|
+
this._stats.triangles += indexCount / 3;
|
|
1067
|
+
} else {
|
|
1068
|
+
pass.draw(vertexCount);
|
|
1069
|
+
this._stats.triangles += vertexCount / 3;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
this._stats.drawCalls += 1;
|
|
1073
|
+
|
|
1074
|
+
pass.end();
|
|
1075
|
+
this.device.queue.submit([encoder.finish()]);
|
|
1076
|
+
this._stats.frames += 1;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* Render using a named pipeline with arbitrary vertex/index buffers
|
|
1081
|
+
* @param {object} options
|
|
1082
|
+
* @param {string} options.pipeline - Pipeline name
|
|
1083
|
+
* @param {GPUBuffer} options.vertexBuffer
|
|
1084
|
+
* @param {GPUBuffer} [options.indexBuffer]
|
|
1085
|
+
* @param {number} options.vertexCount
|
|
1086
|
+
* @param {number} [options.indexCount]
|
|
1087
|
+
* @param {GPUBindGroup[]} [options.bindGroups]
|
|
1088
|
+
* @param {number[]} [options.clearColor]
|
|
1089
|
+
* @param {boolean} [options.clear]
|
|
1090
|
+
*/
|
|
1091
|
+
renderWithPipeline(options) {
|
|
1092
|
+
const {
|
|
1093
|
+
pipeline: pipelineName,
|
|
1094
|
+
vertexBuffer,
|
|
1095
|
+
indexBuffer,
|
|
1096
|
+
vertexCount,
|
|
1097
|
+
indexCount,
|
|
1098
|
+
bindGroups = [],
|
|
1099
|
+
clearColor = [0, 0, 0, 1],
|
|
1100
|
+
clear = true
|
|
1101
|
+
} = options;
|
|
1102
|
+
|
|
1103
|
+
const pipeline = this._pipelines.get(pipelineName);
|
|
1104
|
+
if (!pipeline) {
|
|
1105
|
+
if (this.debug) {
|
|
1106
|
+
console.warn(`Pipeline "${pipelineName}" not found`);
|
|
1107
|
+
}
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const encoder = this.device.createCommandEncoder();
|
|
1112
|
+
this._stats.commandEncoders += 1;
|
|
1113
|
+
|
|
1114
|
+
const colorView = this.context.getCurrentTexture().createView();
|
|
1115
|
+
const depthAttachment = this.depthEnabled && this._depthTexture
|
|
1116
|
+
? {
|
|
1117
|
+
view: this._depthTexture.createView(),
|
|
1118
|
+
depthClearValue: 1.0,
|
|
1119
|
+
depthLoadOp: clear ? 'clear' : 'load',
|
|
1120
|
+
depthStoreOp: 'store'
|
|
1121
|
+
}
|
|
1122
|
+
: undefined;
|
|
1123
|
+
|
|
1124
|
+
const pass = encoder.beginRenderPass({
|
|
1125
|
+
colorAttachments: [{
|
|
1126
|
+
view: colorView,
|
|
1127
|
+
clearValue: { r: clearColor[0], g: clearColor[1], b: clearColor[2], a: clearColor[3] },
|
|
1128
|
+
loadOp: clear ? 'clear' : 'load',
|
|
1129
|
+
storeOp: 'store'
|
|
1130
|
+
}],
|
|
1131
|
+
depthStencilAttachment: depthAttachment
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
pass.setPipeline(pipeline);
|
|
1135
|
+
this._stats.pipelineChanges += 1;
|
|
1136
|
+
|
|
1137
|
+
for (let i = 0; i < bindGroups.length; i++) {
|
|
1138
|
+
pass.setBindGroup(i, bindGroups[i]);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
pass.setVertexBuffer(0, vertexBuffer);
|
|
1142
|
+
|
|
1143
|
+
if (indexBuffer && indexCount) {
|
|
1144
|
+
pass.setIndexBuffer(indexBuffer, 'uint16');
|
|
1145
|
+
pass.drawIndexed(indexCount);
|
|
1146
|
+
this._stats.triangles += indexCount / 3;
|
|
1147
|
+
} else {
|
|
1148
|
+
pass.draw(vertexCount);
|
|
1149
|
+
this._stats.triangles += vertexCount / 3;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
this._stats.drawCalls += 1;
|
|
1153
|
+
|
|
1154
|
+
pass.end();
|
|
1155
|
+
this.device.queue.submit([encoder.finish()]);
|
|
1156
|
+
this._stats.frames += 1;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// ========================================================================
|
|
1160
|
+
// Manual Render Pass Control
|
|
1161
|
+
// ========================================================================
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* Begin a new render pass (for manual control)
|
|
1165
|
+
* @param {object} [options]
|
|
1166
|
+
* @returns {{encoder: GPUCommandEncoder, pass: GPURenderPassEncoder}}
|
|
1167
|
+
*/
|
|
1168
|
+
beginRenderPass(options = {}) {
|
|
1169
|
+
const clearColor = options.clearColor || [0, 0, 0, 1];
|
|
1170
|
+
const encoder = this.device.createCommandEncoder();
|
|
1171
|
+
|
|
1172
|
+
const colorView = this.context.getCurrentTexture().createView();
|
|
1173
|
+
const depthAttachment = this.depthEnabled && this._depthTexture
|
|
1174
|
+
? {
|
|
1175
|
+
view: this._depthTexture.createView(),
|
|
1176
|
+
depthClearValue: 1.0,
|
|
1177
|
+
depthLoadOp: options.loadDepth ? 'load' : 'clear',
|
|
1178
|
+
depthStoreOp: 'store'
|
|
1179
|
+
}
|
|
1180
|
+
: undefined;
|
|
1181
|
+
|
|
1182
|
+
const pass = encoder.beginRenderPass({
|
|
1183
|
+
colorAttachments: [{
|
|
1184
|
+
view: colorView,
|
|
1185
|
+
clearValue: { r: clearColor[0], g: clearColor[1], b: clearColor[2], a: clearColor[3] },
|
|
1186
|
+
loadOp: options.loadColor ? 'load' : 'clear',
|
|
1187
|
+
storeOp: 'store'
|
|
1188
|
+
}],
|
|
1189
|
+
depthStencilAttachment: depthAttachment
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
return { encoder, pass };
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* End a render pass and submit
|
|
1197
|
+
* @param {GPUCommandEncoder} encoder
|
|
1198
|
+
* @param {GPURenderPassEncoder} pass
|
|
1199
|
+
*/
|
|
1200
|
+
endRenderPass(encoder, pass) {
|
|
1201
|
+
pass.end();
|
|
1202
|
+
this.device.queue.submit([encoder.finish()]);
|
|
1203
|
+
this._stats.frames += 1;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// ========================================================================
|
|
1207
|
+
// Statistics & Cleanup
|
|
1208
|
+
// ========================================================================
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* Return backend statistics.
|
|
1212
|
+
*/
|
|
1213
|
+
getStats() {
|
|
1214
|
+
return {
|
|
1215
|
+
...this._stats,
|
|
1216
|
+
resources: this._resources.getStats()
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Reset per-frame statistics
|
|
1222
|
+
*/
|
|
1223
|
+
resetFrameStats() {
|
|
1224
|
+
this._stats.drawCalls = 0;
|
|
1225
|
+
this._stats.triangles = 0;
|
|
1226
|
+
this._stats.pipelineChanges = 0;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Dispose of GPU resources.
|
|
1231
|
+
*/
|
|
1232
|
+
dispose() {
|
|
1233
|
+
this._destroyDepthTexture();
|
|
1234
|
+
|
|
1235
|
+
// Destroy uniform buffers
|
|
1236
|
+
if (this._uniformBuffer) {
|
|
1237
|
+
this._uniformBuffer.destroy();
|
|
1238
|
+
this._uniformBuffer = null;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Destroy custom uniform buffers
|
|
1242
|
+
for (const [, entry] of this._customUniformBuffers) {
|
|
1243
|
+
entry.buffer.destroy();
|
|
1244
|
+
}
|
|
1245
|
+
this._customUniformBuffers.clear();
|
|
1246
|
+
|
|
1247
|
+
// Destroy textures
|
|
1248
|
+
for (const [, texture] of this._textures) {
|
|
1249
|
+
texture.destroy();
|
|
1250
|
+
}
|
|
1251
|
+
this._textures.clear();
|
|
1252
|
+
this._samplers.clear();
|
|
1253
|
+
|
|
1254
|
+
// Clear pipelines and shaders
|
|
1255
|
+
this._pipelines.clear();
|
|
1256
|
+
this._shaderModules.clear();
|
|
1257
|
+
|
|
1258
|
+
this._resources.disposeAll();
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
_destroyDepthTexture() {
|
|
1262
|
+
if (this._depthTexture) {
|
|
1263
|
+
this._resources.release('texture', this._depthTexture);
|
|
1264
|
+
this._depthTexture.destroy();
|
|
1265
|
+
this._depthTexture = null;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* Check if WebGPU is available
|
|
1272
|
+
* @returns {boolean}
|
|
1273
|
+
*/
|
|
1274
|
+
export function isWebGPUSupported() {
|
|
1275
|
+
return typeof navigator !== 'undefined' && !!navigator.gpu;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* Get available WebGPU features
|
|
1280
|
+
* @returns {Promise<Set<string>|null>}
|
|
1281
|
+
*/
|
|
1282
|
+
export async function getWebGPUFeatures() {
|
|
1283
|
+
if (!isWebGPUSupported()) return null;
|
|
1284
|
+
|
|
1285
|
+
try {
|
|
1286
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
1287
|
+
if (!adapter) return null;
|
|
1288
|
+
|
|
1289
|
+
return new Set(adapter.features);
|
|
1290
|
+
} catch {
|
|
1291
|
+
return null;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* Create a WebGPU backend (async).
|
|
1297
|
+
* @param {HTMLCanvasElement} canvas
|
|
1298
|
+
* @param {object} [options]
|
|
1299
|
+
* @param {string} [options.powerPreference] - 'high-performance' or 'low-power'
|
|
1300
|
+
* @param {string[]} [options.requiredFeatures] - Features to request
|
|
1301
|
+
* @param {boolean} [options.debug] - Enable debug mode
|
|
1302
|
+
* @param {boolean} [options.depth] - Enable depth buffer
|
|
1303
|
+
* @returns {Promise<WebGPUBackend|null>}
|
|
1304
|
+
*/
|
|
1305
|
+
export async function createWebGPUBackend(canvas, options = {}) {
|
|
1306
|
+
if (!canvas || !isWebGPUSupported()) {
|
|
1307
|
+
if (options.debug) {
|
|
1308
|
+
console.warn('WebGPU not supported');
|
|
1309
|
+
}
|
|
1310
|
+
return null;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
const context = canvas.getContext('webgpu');
|
|
1314
|
+
if (!context) {
|
|
1315
|
+
if (options.debug) {
|
|
1316
|
+
console.warn('Could not get WebGPU context');
|
|
1317
|
+
}
|
|
1318
|
+
return null;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
try {
|
|
1322
|
+
const adapter = await navigator.gpu.requestAdapter({
|
|
1323
|
+
powerPreference: options.powerPreference || 'high-performance'
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
if (!adapter) {
|
|
1327
|
+
if (options.debug) {
|
|
1328
|
+
console.warn('Could not get WebGPU adapter');
|
|
1329
|
+
}
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Determine which features to request
|
|
1334
|
+
const availableFeatures = new Set(adapter.features);
|
|
1335
|
+
const requestedFeatures = [];
|
|
1336
|
+
|
|
1337
|
+
const requiredFeatures = options.requiredFeatures || [];
|
|
1338
|
+
for (const feature of requiredFeatures) {
|
|
1339
|
+
if (availableFeatures.has(feature)) {
|
|
1340
|
+
requestedFeatures.push(feature);
|
|
1341
|
+
} else if (options.debug) {
|
|
1342
|
+
console.warn(`WebGPU feature not available: ${feature}`);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
const optionalFeatures = [
|
|
1347
|
+
WebGPUFeatures.TIMESTAMP_QUERY,
|
|
1348
|
+
WebGPUFeatures.INDIRECT_FIRST_INSTANCE
|
|
1349
|
+
];
|
|
1350
|
+
|
|
1351
|
+
for (const feature of optionalFeatures) {
|
|
1352
|
+
if (availableFeatures.has(feature) && !requestedFeatures.includes(feature)) {
|
|
1353
|
+
requestedFeatures.push(feature);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
const device = await adapter.requestDevice({
|
|
1358
|
+
requiredFeatures: requestedFeatures.length > 0 ? requestedFeatures : undefined
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
device.lost.then((info) => {
|
|
1362
|
+
console.error('WebGPU device lost:', info.reason, info.message);
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
if (options.debug) {
|
|
1366
|
+
device.onuncapturederror = (event) => {
|
|
1367
|
+
console.error('WebGPU error:', event.error.message);
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
const format = navigator.gpu.getPreferredCanvasFormat();
|
|
1372
|
+
|
|
1373
|
+
if (options.debug) {
|
|
1374
|
+
console.log('WebGPU initialized:', {
|
|
1375
|
+
vendor: adapter.info?.vendor,
|
|
1376
|
+
architecture: adapter.info?.architecture,
|
|
1377
|
+
format,
|
|
1378
|
+
features: requestedFeatures
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
return new WebGPUBackend(
|
|
1383
|
+
{ canvas, device, context, format, adapter },
|
|
1384
|
+
{ ...options, features: requestedFeatures }
|
|
1385
|
+
);
|
|
1386
|
+
} catch (error) {
|
|
1387
|
+
if (options.debug) {
|
|
1388
|
+
console.error('WebGPU initialization failed:', error);
|
|
1389
|
+
}
|
|
1390
|
+
return null;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
/**
|
|
1395
|
+
* Create WebGPU backend with fallback to WebGL
|
|
1396
|
+
* @param {HTMLCanvasElement} canvas
|
|
1397
|
+
* @param {object} [options]
|
|
1398
|
+
* @returns {Promise<{backend: WebGPUBackend|null, type: 'webgpu'|'webgl'|null}>}
|
|
1399
|
+
*/
|
|
1400
|
+
export async function createWebGPUWithFallback(canvas, options = {}) {
|
|
1401
|
+
const webgpuBackend = await createWebGPUBackend(canvas, options);
|
|
1402
|
+
if (webgpuBackend) {
|
|
1403
|
+
return { backend: webgpuBackend, type: 'webgpu' };
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
return { backend: null, type: null };
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
export default WebGPUBackend;
|