reze-engine 0.22.1 → 0.23.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/dist/engine.d.ts +53 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +166 -24
- package/dist/physics-debug.d.ts +30 -0
- package/dist/physics-debug.d.ts.map +1 -0
- package/dist/physics-debug.js +526 -0
- package/dist/shaders/passes/composite.d.ts +1 -1
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +23 -2
- package/dist/shaders/passes/ground.d.ts +1 -1
- package/dist/shaders/passes/ground.d.ts.map +1 -1
- package/dist/shaders/passes/ground.js +12 -5
- package/dist/shaders/passes/physics-debug.d.ts +2 -0
- package/dist/shaders/passes/physics-debug.d.ts.map +1 -0
- package/dist/shaders/passes/physics-debug.js +69 -0
- package/package.json +2 -2
- package/src/engine.ts +189 -25
- package/src/shaders/passes/composite.ts +23 -2
- package/src/shaders/passes/ground.ts +12 -5
- package/dist/gpu-profile.d.ts +0 -19
- package/dist/gpu-profile.d.ts.map +0 -1
- package/dist/gpu-profile.js +0 -120
- package/dist/physics/profile.d.ts +0 -18
- package/dist/physics/profile.d.ts.map +0 -1
- package/dist/physics/profile.js +0 -44
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const PHYSICS_DEBUG_SHADER_WGSL = "\nstruct CameraUniforms {\n view: mat4x4f,\n projection: mat4x4f,\n viewPos: vec3f,\n _pad: f32,\n};\n\n@group(0) @binding(0) var<uniform> camera: CameraUniforms;\n\noverride SHAPE_KIND: u32 = 0u;\noverride SOLID_ALPHA: f32 = 1.0;\n\nstruct VsIn {\n @location(0) unit: vec4f, // xyz = unit pos; w = capsule axial anchor\n @location(1) modelCol0: vec4f,\n @location(2) modelCol1: vec4f,\n @location(3) modelCol2: vec4f,\n @location(4) modelCol3: vec4f,\n @location(5) size: vec4f, // xyz = size; w unused\n @location(6) color: vec4f,\n};\n\nstruct VsOut {\n @builtin(position) pos: vec4f,\n @location(0) color: vec4f,\n};\n\n@vertex\nfn vsMain(in: VsIn) -> VsOut {\n let model = mat4x4f(in.modelCol0, in.modelCol1, in.modelCol2, in.modelCol3);\n var local: vec3f;\n if (SHAPE_KIND == 1u) {\n local = in.unit.xyz * in.size.xyz;\n } else if (SHAPE_KIND == 2u) {\n // PMX capsule: size.x = radius, size.y = full cylinder height. Bullet 2.x's\n // btCapsuleShape(radius, height) stores height/2 internally, so the actual\n // collision cylinder spans \u00B1size.y/2 \u2014 halve here to match.\n let r = in.size.x;\n let halfH = in.size.y * 0.5;\n local = vec3f(in.unit.x * r, in.unit.y * r + halfH * in.unit.w, in.unit.z * r);\n } else {\n local = in.unit.xyz * in.size.x;\n }\n let world = model * vec4f(local, 1.0);\n var out: VsOut;\n out.pos = camera.projection * camera.view * world;\n out.color = in.color;\n return out;\n}\n\n@fragment\nfn fsMain(in: VsOut) -> @location(0) vec4f {\n return vec4f(in.color.rgb, in.color.a * SOLID_ALPHA);\n}\n";
|
|
2
|
+
//# sourceMappingURL=physics-debug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"physics-debug.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/physics-debug.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,yBAAyB,2kDAuDrC,CAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Wireframe + solid overlay for physics rigid bodies (sphere / box / capsule).
|
|
2
|
+
// Rendered in its own pass after composite, straight to the swapchain — no
|
|
3
|
+
// MSAA, no depth, no MRT — so it sits cleanly on top of the tonemapped scene
|
|
4
|
+
// regardless of camera angle, model occlusion, or the composite alpha gate.
|
|
5
|
+
//
|
|
6
|
+
// SHAPE_KIND 0 = sphere → unit-sphere line/tri primitive scaled by size.x (radius)
|
|
7
|
+
// 1 = box → unit cube ±1 scaled by size.xyz (half-extents)
|
|
8
|
+
// 2 = capsule → unit cap+ring with per-vertex `axial` (-1/0/+1)
|
|
9
|
+
// that lifts cap rings/arcs ±halfHeight along Y;
|
|
10
|
+
// XZ scales by size.x (radius), Y by size.y/2.
|
|
11
|
+
// SOLID_ALPHA fragment alpha multiplier. Wireframe pipelines leave this at
|
|
12
|
+
// 1.0 for crisp edges; solid pipelines set it to ~0.2 so the fill
|
|
13
|
+
// stays gentle under stacked bodies.
|
|
14
|
+
export const PHYSICS_DEBUG_SHADER_WGSL = /* wgsl */ `
|
|
15
|
+
struct CameraUniforms {
|
|
16
|
+
view: mat4x4f,
|
|
17
|
+
projection: mat4x4f,
|
|
18
|
+
viewPos: vec3f,
|
|
19
|
+
_pad: f32,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
23
|
+
|
|
24
|
+
override SHAPE_KIND: u32 = 0u;
|
|
25
|
+
override SOLID_ALPHA: f32 = 1.0;
|
|
26
|
+
|
|
27
|
+
struct VsIn {
|
|
28
|
+
@location(0) unit: vec4f, // xyz = unit pos; w = capsule axial anchor
|
|
29
|
+
@location(1) modelCol0: vec4f,
|
|
30
|
+
@location(2) modelCol1: vec4f,
|
|
31
|
+
@location(3) modelCol2: vec4f,
|
|
32
|
+
@location(4) modelCol3: vec4f,
|
|
33
|
+
@location(5) size: vec4f, // xyz = size; w unused
|
|
34
|
+
@location(6) color: vec4f,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
struct VsOut {
|
|
38
|
+
@builtin(position) pos: vec4f,
|
|
39
|
+
@location(0) color: vec4f,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
@vertex
|
|
43
|
+
fn vsMain(in: VsIn) -> VsOut {
|
|
44
|
+
let model = mat4x4f(in.modelCol0, in.modelCol1, in.modelCol2, in.modelCol3);
|
|
45
|
+
var local: vec3f;
|
|
46
|
+
if (SHAPE_KIND == 1u) {
|
|
47
|
+
local = in.unit.xyz * in.size.xyz;
|
|
48
|
+
} else if (SHAPE_KIND == 2u) {
|
|
49
|
+
// PMX capsule: size.x = radius, size.y = full cylinder height. Bullet 2.x's
|
|
50
|
+
// btCapsuleShape(radius, height) stores height/2 internally, so the actual
|
|
51
|
+
// collision cylinder spans ±size.y/2 — halve here to match.
|
|
52
|
+
let r = in.size.x;
|
|
53
|
+
let halfH = in.size.y * 0.5;
|
|
54
|
+
local = vec3f(in.unit.x * r, in.unit.y * r + halfH * in.unit.w, in.unit.z * r);
|
|
55
|
+
} else {
|
|
56
|
+
local = in.unit.xyz * in.size.x;
|
|
57
|
+
}
|
|
58
|
+
let world = model * vec4f(local, 1.0);
|
|
59
|
+
var out: VsOut;
|
|
60
|
+
out.pos = camera.projection * camera.view * world;
|
|
61
|
+
out.color = in.color;
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@fragment
|
|
66
|
+
fn fsMain(in: VsOut) -> @location(0) vec4f {
|
|
67
|
+
return vec4f(in.color.rgb, in.color.a * SOLID_ALPHA);
|
|
68
|
+
}
|
|
69
|
+
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reze-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.1",
|
|
4
4
|
"description": "A lightweight WebGPU engine for real-time 3D MMD/PMX model rendering",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"build": "tsc",
|
|
24
24
|
"dev": "tsc --watch",
|
|
25
25
|
"test": "node --import ./tests/register.mjs --test tests/*.test.mjs",
|
|
26
|
-
"prepublishOnly": "
|
|
26
|
+
"prepublishOnly": "node -e \"require('fs').copyFileSync('../README.md','README.md')\" && npm run build"
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|
|
29
29
|
"webgpu",
|
package/src/engine.ts
CHANGED
|
@@ -301,6 +301,9 @@ export type EngineOptions = {
|
|
|
301
301
|
world?: WorldOptions
|
|
302
302
|
sun?: SunOptions
|
|
303
303
|
camera?: CameraOptions
|
|
304
|
+
/** Canvas background (display-space sRGB 0–1), composited under the scene after
|
|
305
|
+
* tonemapping. Omit/null = transparent canvas (see setBackgroundColor). */
|
|
306
|
+
background?: Vec3 | null
|
|
304
307
|
/** Initial EEVEE-style bloom; tune at runtime with `setBloomOptions`. */
|
|
305
308
|
bloom?: Partial<BloomOptions>
|
|
306
309
|
/** View transform (exposure/gamma) applied in composite before/after Filmic. */
|
|
@@ -542,7 +545,15 @@ export class Engine {
|
|
|
542
545
|
private compositeBindGroup!: GPUBindGroup
|
|
543
546
|
private compositeUniformBuffer!: GPUBuffer
|
|
544
547
|
// [exposure, invGamma, _, _, bloomTint.x, bloomTint.y, bloomTint.z, bloomIntensity]
|
|
545
|
-
private readonly compositeUniformData = new Float32Array(
|
|
548
|
+
private readonly compositeUniformData = new Float32Array(24)
|
|
549
|
+
/** Composite background (display-space sRGB 0–1) — null = transparent canvas. */
|
|
550
|
+
private backgroundColor: Vec3 | null = null
|
|
551
|
+
// 360 backdrop (equirectangular skybox, sampled by view ray in composite).
|
|
552
|
+
private backdropEquirectTexture: GPUTexture | null = null
|
|
553
|
+
private backdropEquirectView: GPUTextureView | null = null
|
|
554
|
+
private fallbackEquirectTexture!: GPUTexture
|
|
555
|
+
private fallbackEquirectView!: GPUTextureView
|
|
556
|
+
private compositeBloomView: GPUTextureView | null = null
|
|
546
557
|
|
|
547
558
|
// EEVEE-style bloom pyramid (mirrors Blender 3.6 effect_bloom_frag.glsl):
|
|
548
559
|
// blit (HDR → half-res, 4-tap Karis + soft threshold/knee)
|
|
@@ -674,6 +685,8 @@ export class Engine {
|
|
|
674
685
|
this.onGizmoDrag = options?.onGizmoDrag
|
|
675
686
|
this.bloomSettings = Engine.mergeBloomDefaults(options?.bloom)
|
|
676
687
|
this.viewTransform = Engine.mergeViewTransformDefaults(options?.view)
|
|
688
|
+
const bg = options?.background
|
|
689
|
+
this.backgroundColor = bg ? new Vec3(bg.x, bg.y, bg.z) : null
|
|
677
690
|
}
|
|
678
691
|
|
|
679
692
|
/** Merge partial bloom with EEVEE defaults (same as constructor). */
|
|
@@ -745,9 +758,91 @@ export class Engine {
|
|
|
745
758
|
u[5] = b.color.y
|
|
746
759
|
u[6] = b.color.z
|
|
747
760
|
u[7] = effIntensity
|
|
761
|
+
// Background composited UNDER the scene in display space (post-tonemap), so it
|
|
762
|
+
// matches a CSS color of the same value exactly. Mode (u[11]): 0 = transparent
|
|
763
|
+
// (DOM shows), 1 = solid color, 2 = 360 equirect (sampled by view ray; the
|
|
764
|
+
// camera basis at u[12..23] is refreshed per frame by updateCameraUniforms).
|
|
765
|
+
const bg = this.backgroundColor
|
|
766
|
+
u[8] = bg?.x ?? 0
|
|
767
|
+
u[9] = bg?.y ?? 0
|
|
768
|
+
u[10] = bg?.z ?? 0
|
|
769
|
+
u[11] = this.backdropEquirectView ? 2 : bg ? 1 : 0
|
|
748
770
|
this.device.queue.writeBuffer(this.compositeUniformBuffer, 0, u)
|
|
749
771
|
}
|
|
750
772
|
|
|
773
|
+
/**
|
|
774
|
+
* Set the canvas background color (display-space sRGB, 0–1 per channel — the
|
|
775
|
+
* same value a CSS background of that color shows, applied after tonemapping).
|
|
776
|
+
* Pass null for a transparent canvas (the page/DOM shows through — e.g. when a
|
|
777
|
+
* backdrop image layer sits behind the canvas). Applies on the next frame.
|
|
778
|
+
* A 360 backdrop (setBackdropEquirect) takes precedence while set.
|
|
779
|
+
*/
|
|
780
|
+
setBackgroundColor(color: Vec3 | null): void {
|
|
781
|
+
this.backgroundColor = color ? new Vec3(color.x, color.y, color.z) : null
|
|
782
|
+
if (this.device && this.compositeUniformBuffer) this.writeCompositeViewUniforms()
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
private rebuildCompositeBindGroup(): void {
|
|
786
|
+
if (!this.device || !this.hdrResolveTexture || !this.compositeBloomView) return
|
|
787
|
+
this.compositeBindGroup = this.device.createBindGroup({
|
|
788
|
+
label: "composite bind group",
|
|
789
|
+
layout: this.compositeBindGroupLayout,
|
|
790
|
+
entries: [
|
|
791
|
+
{ binding: 0, resource: this.hdrResolveTexture.createView() },
|
|
792
|
+
{ binding: 1, resource: this.compositeBloomView },
|
|
793
|
+
{ binding: 2, resource: this.bloomSampler },
|
|
794
|
+
{ binding: 3, resource: { buffer: this.compositeUniformBuffer } },
|
|
795
|
+
{ binding: 4, resource: this.maskResolveView },
|
|
796
|
+
{ binding: 5, resource: this.filmicLutView },
|
|
797
|
+
{ binding: 6, resource: this.backdropEquirectView ?? this.fallbackEquirectView },
|
|
798
|
+
],
|
|
799
|
+
})
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Set a 360° backdrop from an equirectangular (2:1) image — a PhotoDome-style
|
|
804
|
+
* skybox at infinity, sampled per-pixel by view direction so it follows the
|
|
805
|
+
* camera. Display-only: composited in display space behind the scene, it never
|
|
806
|
+
* affects lighting, bloom, or tonemapping. Pass null to remove (the background
|
|
807
|
+
* color, or transparency, takes over again).
|
|
808
|
+
*/
|
|
809
|
+
setBackdropEquirect(source: ImageBitmap | HTMLImageElement | HTMLCanvasElement | null): void {
|
|
810
|
+
this.backdropEquirectTexture?.destroy()
|
|
811
|
+
this.backdropEquirectTexture = null
|
|
812
|
+
this.backdropEquirectView = null
|
|
813
|
+
if (source && this.device) {
|
|
814
|
+
let width = Math.max(1, "naturalWidth" in source ? source.naturalWidth : source.width)
|
|
815
|
+
let height = Math.max(1, "naturalHeight" in source ? source.naturalHeight : source.height)
|
|
816
|
+
let upload: ImageBitmap | HTMLImageElement | HTMLCanvasElement | OffscreenCanvas = source
|
|
817
|
+
// Panoramas routinely exceed maxTextureDimension2D (e.g. 10000×5000 vs the
|
|
818
|
+
// default 8192) — quietly downscale to fit rather than surfacing an error.
|
|
819
|
+
const limit = this.device.limits.maxTextureDimension2D
|
|
820
|
+
if (width > limit || height > limit) {
|
|
821
|
+
const scale = Math.min(limit / width, limit / height)
|
|
822
|
+
const w = Math.max(1, Math.floor(width * scale))
|
|
823
|
+
const h = Math.max(1, Math.floor(height * scale))
|
|
824
|
+
const canvas = new OffscreenCanvas(w, h)
|
|
825
|
+
const cx = canvas.getContext("2d")!
|
|
826
|
+
cx.imageSmoothingQuality = "high"
|
|
827
|
+
cx.drawImage(source, 0, 0, w, h)
|
|
828
|
+
upload = canvas
|
|
829
|
+
width = w
|
|
830
|
+
height = h
|
|
831
|
+
}
|
|
832
|
+
const tex = this.device.createTexture({
|
|
833
|
+
label: "backdrop equirect",
|
|
834
|
+
size: [width, height],
|
|
835
|
+
format: "rgba8unorm",
|
|
836
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
837
|
+
})
|
|
838
|
+
this.device.queue.copyExternalImageToTexture({ source: upload }, { texture: tex }, [width, height])
|
|
839
|
+
this.backdropEquirectTexture = tex
|
|
840
|
+
this.backdropEquirectView = tex.createView()
|
|
841
|
+
}
|
|
842
|
+
this.rebuildCompositeBindGroup()
|
|
843
|
+
if (this.device && this.compositeUniformBuffer) this.writeCompositeViewUniforms()
|
|
844
|
+
}
|
|
845
|
+
|
|
751
846
|
/** Patch bloom; GPU uniforms update immediately if `init()` has run. */
|
|
752
847
|
setBloomOptions(patch: Partial<BloomOptions>): void {
|
|
753
848
|
const b = this.bloomSettings
|
|
@@ -1545,7 +1640,9 @@ export class Engine {
|
|
|
1545
1640
|
// mirroring EEVEE where bloom color/intensity are combine-stage params, not prefilter).
|
|
1546
1641
|
this.compositeUniformBuffer = this.device.createBuffer({
|
|
1547
1642
|
label: "composite view uniforms",
|
|
1548
|
-
|
|
1643
|
+
// 6 × vec4f: (exposure, invGamma, _, _) · (bloom tint, intensity) ·
|
|
1644
|
+
// (bg rgb, mode) · camera right/up/forward basis for the 360 skybox ray.
|
|
1645
|
+
size: 96,
|
|
1549
1646
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
1550
1647
|
})
|
|
1551
1648
|
this.compositeBindGroupLayout = this.device.createBindGroupLayout({
|
|
@@ -1560,8 +1657,17 @@ export class Engine {
|
|
|
1560
1657
|
{ binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
1561
1658
|
// Filmic tone LUT (r16float, filterable) — sampled with the binding-2 sampler.
|
|
1562
1659
|
{ binding: 5, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
1660
|
+
// 360 backdrop equirect (PhotoDome-style skybox) — 1×1 fallback when unset.
|
|
1661
|
+
{ binding: 6, visibility: GPUShaderStage.FRAGMENT, texture: {} },
|
|
1563
1662
|
],
|
|
1564
1663
|
})
|
|
1664
|
+
this.fallbackEquirectTexture = this.device.createTexture({
|
|
1665
|
+
label: "equirect fallback",
|
|
1666
|
+
size: [1, 1],
|
|
1667
|
+
format: "rgba8unorm",
|
|
1668
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
|
|
1669
|
+
})
|
|
1670
|
+
this.fallbackEquirectView = this.fallbackEquirectTexture.createView()
|
|
1565
1671
|
|
|
1566
1672
|
const compositeShader = this.device.createShaderModule({
|
|
1567
1673
|
label: "composite shader",
|
|
@@ -1708,13 +1814,35 @@ export class Engine {
|
|
|
1708
1814
|
window.addEventListener("mouseup", this.handleGizmoMouseUp)
|
|
1709
1815
|
}
|
|
1710
1816
|
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1817
|
+
/** When set, render resolution is pinned to this size instead of tracking the
|
|
1818
|
+
* canvas's CSS size × devicePixelRatio (see setRenderSize). */
|
|
1819
|
+
private fixedRenderSize: { width: number; height: number } | null = null
|
|
1820
|
+
|
|
1821
|
+
/**
|
|
1822
|
+
* Pin the render resolution (canvas backing store + every render target) to an
|
|
1823
|
+
* explicit size, decoupled from the canvas's CSS layout size — for offline
|
|
1824
|
+
* rendering at arbitrary resolution (video export). On screen the browser scales
|
|
1825
|
+
* the buffer to the layout box, so the canvas may display letterboxed/stretched
|
|
1826
|
+
* while pinned; hosts typically cover it with an export overlay. Pass null to
|
|
1827
|
+
* return to CSS-size × devicePixelRatio tracking. Applies immediately (targets
|
|
1828
|
+
* rebuild before this returns), so the next render() is at the new size.
|
|
1829
|
+
*/
|
|
1830
|
+
setRenderSize(width: number, height: number): void
|
|
1831
|
+
setRenderSize(size: null): void
|
|
1832
|
+
setRenderSize(widthOrNull: number | null, height?: number): void {
|
|
1833
|
+
this.fixedRenderSize =
|
|
1834
|
+
widthOrNull === null
|
|
1835
|
+
? null
|
|
1836
|
+
: { width: Math.max(1, Math.floor(widthOrNull)), height: Math.max(1, Math.floor(height ?? 1)) }
|
|
1837
|
+
this.resizePending = false
|
|
1838
|
+
this.handleResize()
|
|
1839
|
+
}
|
|
1714
1840
|
|
|
1841
|
+
private handleResize() {
|
|
1842
|
+
// Fixed override (offline/video rendering) wins; otherwise track CSS size × dpr.
|
|
1715
1843
|
const dpr = window.devicePixelRatio || 1
|
|
1716
|
-
const width = Math.floor(
|
|
1717
|
-
const height = Math.floor(
|
|
1844
|
+
const width = this.fixedRenderSize ? this.fixedRenderSize.width : Math.floor(this.canvas.clientWidth * dpr)
|
|
1845
|
+
const height = this.fixedRenderSize ? this.fixedRenderSize.height : Math.floor(this.canvas.clientHeight * dpr)
|
|
1718
1846
|
|
|
1719
1847
|
if (!this.multisampleTexture || this.canvas.width !== width || this.canvas.height !== height) {
|
|
1720
1848
|
this.canvas.width = width
|
|
@@ -1927,19 +2055,8 @@ export class Engine {
|
|
|
1927
2055
|
)
|
|
1928
2056
|
}
|
|
1929
2057
|
// Composite reads bloomUp mip 0 (full pyramid collapsed); fallback to bloomDown mip 0 if no upsample level.
|
|
1930
|
-
|
|
1931
|
-
this.
|
|
1932
|
-
label: "composite bind group",
|
|
1933
|
-
layout: this.compositeBindGroupLayout,
|
|
1934
|
-
entries: [
|
|
1935
|
-
{ binding: 0, resource: this.hdrResolveTexture.createView() },
|
|
1936
|
-
{ binding: 1, resource: compositeBloomView },
|
|
1937
|
-
{ binding: 2, resource: this.bloomSampler },
|
|
1938
|
-
{ binding: 3, resource: { buffer: this.compositeUniformBuffer } },
|
|
1939
|
-
{ binding: 4, resource: this.maskResolveView },
|
|
1940
|
-
{ binding: 5, resource: this.filmicLutView },
|
|
1941
|
-
],
|
|
1942
|
-
})
|
|
2058
|
+
this.compositeBloomView = this.bloomMipCount > 1 ? this.bloomUpMipViews[0] : this.bloomDownMipViews[0]
|
|
2059
|
+
this.rebuildCompositeBindGroup()
|
|
1943
2060
|
}
|
|
1944
2061
|
|
|
1945
2062
|
this.writeCompositeViewUniforms()
|
|
@@ -2335,6 +2452,8 @@ export class Engine {
|
|
|
2335
2452
|
gridLineOpacity?: number
|
|
2336
2453
|
gridLineColor?: Vec3
|
|
2337
2454
|
noiseStrength?: number
|
|
2455
|
+
/** Whole-ground opacity, 0–1 (multiplies the radial edge fade). Default 1. */
|
|
2456
|
+
opacity?: number
|
|
2338
2457
|
}): void {
|
|
2339
2458
|
const opts = {
|
|
2340
2459
|
width: 160,
|
|
@@ -2348,6 +2467,7 @@ export class Engine {
|
|
|
2348
2467
|
gridLineOpacity: 0.4,
|
|
2349
2468
|
gridLineColor: new Vec3(0.85, 0.85, 0.85),
|
|
2350
2469
|
noiseStrength: 0.05,
|
|
2470
|
+
opacity: 1.0,
|
|
2351
2471
|
...options,
|
|
2352
2472
|
}
|
|
2353
2473
|
this.createGroundGeometry(opts.width, opts.height)
|
|
@@ -2968,6 +3088,7 @@ export class Engine {
|
|
|
2968
3088
|
gridLineOpacity: number
|
|
2969
3089
|
gridLineColor: Vec3
|
|
2970
3090
|
noiseStrength: number
|
|
3091
|
+
opacity: number
|
|
2971
3092
|
}) {
|
|
2972
3093
|
const {
|
|
2973
3094
|
diffuseColor,
|
|
@@ -2979,6 +3100,7 @@ export class Engine {
|
|
|
2979
3100
|
gridLineOpacity,
|
|
2980
3101
|
gridLineColor,
|
|
2981
3102
|
noiseStrength,
|
|
3103
|
+
opacity,
|
|
2982
3104
|
} = opts
|
|
2983
3105
|
// Shadow map is already created in setupPipelines()
|
|
2984
3106
|
const gb = new Float32Array(16)
|
|
@@ -2993,7 +3115,7 @@ export class Engine {
|
|
|
2993
3115
|
gb[8] = gridLineWidth
|
|
2994
3116
|
gb[9] = gridLineOpacity
|
|
2995
3117
|
gb[10] = noiseStrength
|
|
2996
|
-
gb[11] =
|
|
3118
|
+
gb[11] = opacity
|
|
2997
3119
|
gb[12] = gridLineColor.x
|
|
2998
3120
|
gb[13] = gridLineColor.y
|
|
2999
3121
|
gb[14] = gridLineColor.z
|
|
@@ -3907,15 +4029,32 @@ export class Engine {
|
|
|
3907
4029
|
render() {
|
|
3908
4030
|
if (!this.multisampleTexture || !this.camera || !this.device) return
|
|
3909
4031
|
|
|
4032
|
+
const currentTime = performance.now()
|
|
4033
|
+
const deltaTime = this.lastFrameTime > 0 ? (currentTime - this.lastFrameTime) / 1000 : 0.016
|
|
4034
|
+
this.lastFrameTime = currentTime
|
|
4035
|
+
this.renderWithDelta(deltaTime)
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
/**
|
|
4039
|
+
* Render one frame advancing every clock — animation, physics, tweens, and the
|
|
4040
|
+
* camera VMD — by exactly `deltaSeconds`, independent of wall time. This is the
|
|
4041
|
+
* offline-rendering primitive (video export): call it N times with 1/fps and the
|
|
4042
|
+
* result is deterministic whether the machine renders faster or slower than
|
|
4043
|
+
* realtime. Also resets the realtime clock so a later render() (returning to the
|
|
4044
|
+
* live loop) doesn't see the export's wall-clock gap as one giant delta.
|
|
4045
|
+
*/
|
|
4046
|
+
renderFrame(deltaSeconds: number) {
|
|
4047
|
+
if (!this.multisampleTexture || !this.camera || !this.device) return
|
|
4048
|
+
this.lastFrameTime = performance.now()
|
|
4049
|
+
this.renderWithDelta(deltaSeconds)
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
private renderWithDelta(deltaTime: number) {
|
|
3910
4053
|
if (this.resizePending) {
|
|
3911
4054
|
this.resizePending = false
|
|
3912
4055
|
this.handleResize()
|
|
3913
4056
|
}
|
|
3914
4057
|
|
|
3915
|
-
const currentTime = performance.now()
|
|
3916
|
-
const deltaTime = this.lastFrameTime > 0 ? (currentTime - this.lastFrameTime) / 1000 : 0.016
|
|
3917
|
-
this.lastFrameTime = currentTime
|
|
3918
|
-
|
|
3919
4058
|
const hasModels = this.modelInstances.size > 0
|
|
3920
4059
|
if (hasModels) {
|
|
3921
4060
|
this.updateInstances(deltaTime)
|
|
@@ -4538,6 +4677,31 @@ export class Engine {
|
|
|
4538
4677
|
this.cameraMatrixData[33] = cameraPos.y
|
|
4539
4678
|
this.cameraMatrixData[34] = cameraPos.z
|
|
4540
4679
|
this.device.queue.writeBuffer(this.cameraUniformBuffer, 0, this.cameraMatrixData)
|
|
4680
|
+
|
|
4681
|
+
// 360 backdrop: the composite reconstructs each pixel's view ray from the
|
|
4682
|
+
// camera basis — refresh it every frame the skybox is active. The view matrix
|
|
4683
|
+
// is LEFT-HANDED (+Z forward, see Mat4.lookAtInto), so the world-space
|
|
4684
|
+
// right/up/FORWARD vectors are rows 0/1/2 of its rotation block directly
|
|
4685
|
+
// (column-major storage: row i = values[i], values[i+4], values[i+8]).
|
|
4686
|
+
if (this.backdropEquirectView && this.compositeUniformBuffer) {
|
|
4687
|
+
const v = viewMatrix.values
|
|
4688
|
+
const u = this.compositeUniformData
|
|
4689
|
+
const tanHalf = Math.tan((this.camera.fov ?? Math.PI / 4) / 2)
|
|
4690
|
+
const aspect = this.canvas.width / Math.max(1, this.canvas.height)
|
|
4691
|
+
u[12] = v[0]
|
|
4692
|
+
u[13] = v[4]
|
|
4693
|
+
u[14] = v[8]
|
|
4694
|
+
u[15] = tanHalf * aspect
|
|
4695
|
+
u[16] = v[1]
|
|
4696
|
+
u[17] = v[5]
|
|
4697
|
+
u[18] = v[9]
|
|
4698
|
+
u[19] = tanHalf
|
|
4699
|
+
u[20] = v[2]
|
|
4700
|
+
u[21] = v[6]
|
|
4701
|
+
u[22] = v[10]
|
|
4702
|
+
u[23] = 0
|
|
4703
|
+
this.device.queue.writeBuffer(this.compositeUniformBuffer, 0, u)
|
|
4704
|
+
}
|
|
4541
4705
|
}
|
|
4542
4706
|
|
|
4543
4707
|
private updateSkinMatrices() {
|
|
@@ -12,7 +12,7 @@ override APPLY_GAMMA: bool = true;
|
|
|
12
12
|
@group(0) @binding(0) var hdrTex: texture_2d<f32>;
|
|
13
13
|
@group(0) @binding(1) var bloomTex: texture_2d<f32>; // bloomUpTexture mip 0 (full pyramid top)
|
|
14
14
|
@group(0) @binding(2) var bloomSamp: sampler;
|
|
15
|
-
@group(0) @binding(3) var<uniform> viewU: array<vec4<f32>,
|
|
15
|
+
@group(0) @binding(3) var<uniform> viewU: array<vec4<f32>, 6>;
|
|
16
16
|
// Aux mask/alpha texture. .r = bloom mask (unused here; bloom blit uses it).
|
|
17
17
|
// .g = accumulated canvas alpha (what hdr.a carried before the HDR format
|
|
18
18
|
// became rg11b10ufloat). We unpremultiply HDR by this alpha for tonemap, then
|
|
@@ -28,7 +28,13 @@ override APPLY_GAMMA: bool = true;
|
|
|
28
28
|
// continuity kills the banding — sampled with hardware linear filtering.
|
|
29
29
|
@group(0) @binding(5) var filmicLut: texture_2d<f32>;
|
|
30
30
|
// viewU[0] = (exposure, invGamma, _, _); viewU[1] = (tint.rgb, intensity)
|
|
31
|
+
// viewU[2] = (background.rgb, mode) — display-space sRGB, composited UNDER the
|
|
32
|
+
// scene post-tonemap. mode: 0 transparent (DOM shows), 1 solid color,
|
|
33
|
+
// 2 = 360 equirect skybox sampled by view ray.
|
|
34
|
+
// viewU[3] = (camera right, tanHalfFov·aspect); viewU[4] = (camera up, tanHalfFov);
|
|
35
|
+
// viewU[5] = (camera forward, _) — refreshed per frame while the skybox is active.
|
|
31
36
|
// invGamma = 1/gamma precomputed on CPU — avoids a per-pixel divide.
|
|
37
|
+
@group(0) @binding(6) var bgEquirect: texture_2d<f32>;
|
|
32
38
|
|
|
33
39
|
// Must match FILMIC_LUT_WIDTH in engine.ts (bakeFilmicLut).
|
|
34
40
|
const FILMIC_LUT_W: f32 = 256.0;
|
|
@@ -70,6 +76,21 @@ fn filmic(x: f32) -> f32 {
|
|
|
70
76
|
if (APPLY_GAMMA) {
|
|
71
77
|
disp = pow(disp, vec3f(viewU[0].y));
|
|
72
78
|
}
|
|
73
|
-
|
|
79
|
+
// Composite over the background in display space (premultiplied out).
|
|
80
|
+
let bg = viewU[2];
|
|
81
|
+
var bgRgb = bg.rgb;
|
|
82
|
+
var bgA = select(0.0, 1.0, bg.w > 0.5);
|
|
83
|
+
if (bg.w > 1.5) {
|
|
84
|
+
// 360 equirect: rebuild this pixel's world-space view ray from the camera
|
|
85
|
+
// basis, then latitude/longitude-map into the panorama. The dome sits at
|
|
86
|
+
// infinity (no parallax) — PhotoDome-style, display-only.
|
|
87
|
+
let ndc = vec2f(fragCoord.x / fullSz.x * 2.0 - 1.0, 1.0 - fragCoord.y / fullSz.y * 2.0);
|
|
88
|
+
let dir = normalize(viewU[5].xyz + ndc.x * viewU[3].w * viewU[3].xyz + ndc.y * viewU[4].w * viewU[4].xyz);
|
|
89
|
+
// LH world (+Z forward): longitude = atan2(x, z), Babylon-PhotoDome convention.
|
|
90
|
+
let su = 0.5 + atan2(dir.x, dir.z) * 0.15915494309; // 1/(2π)
|
|
91
|
+
let sv = 0.5 - asin(clamp(dir.y, -1.0, 1.0)) * 0.31830988618; // 1/π
|
|
92
|
+
bgRgb = textureSampleLevel(bgEquirect, bloomSamp, vec2f(su, sv), 0.0).rgb;
|
|
93
|
+
}
|
|
94
|
+
return vec4f(disp * alpha + bgRgb * bgA * (1.0 - alpha), alpha + bgA * (1.0 - alpha));
|
|
74
95
|
}
|
|
75
96
|
`
|
|
@@ -8,7 +8,7 @@ struct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };
|
|
|
8
8
|
struct GroundShadowMat {
|
|
9
9
|
diffuseColor: vec3f, fadeStart: f32,
|
|
10
10
|
fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, gridSpacing: f32,
|
|
11
|
-
gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32,
|
|
11
|
+
gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, opacity: f32,
|
|
12
12
|
gridLineColor: vec3f, _pad2: f32,
|
|
13
13
|
};
|
|
14
14
|
struct LightVP { viewProj: mat4x4f, };
|
|
@@ -86,13 +86,20 @@ struct FSOut { @location(0) color: vec4f, @location(1) mask: vec4f };
|
|
|
86
86
|
var baseColor = material.diffuseColor * sun * (1.0 - dark * 0.65);
|
|
87
87
|
baseColor *= noiseTint;
|
|
88
88
|
let finalColor = mix(baseColor, material.gridLineColor, gridLine * material.gridLineOpacity * edgeFade);
|
|
89
|
+
// Whole-ground opacity fades the SURFACE (color, grid) but the shadow stays —
|
|
90
|
+
// as opacity drops, the received shadow becomes a translucent dark layer
|
|
91
|
+
// (Blender's Shadow Catcher), so models still feel grounded on a photo or
|
|
92
|
+
// 360 backdrop. At opacity 1 this reduces exactly to the plain surface.
|
|
93
|
+
let surfA = edgeFade * material.opacity;
|
|
94
|
+
let catchA = dark * 0.65 * edgeFade * (1.0 - material.opacity);
|
|
95
|
+
let outA = surfA + catchA;
|
|
89
96
|
var out: FSOut;
|
|
90
|
-
out.color = vec4f(finalColor *
|
|
97
|
+
out.color = vec4f(finalColor * surfA, outA);
|
|
91
98
|
// mask.r = 0: ground never contributes to bloom. mask.g = 1.0 with src.a =
|
|
92
|
-
//
|
|
93
|
-
// from
|
|
99
|
+
// outA turns the aux blend into alpha-over, so the drawable alpha goes
|
|
100
|
+
// from outA at the center to 0 at the radial edge — letting the page
|
|
94
101
|
// background show through under the premultiplied canvas alphaMode.
|
|
95
|
-
out.mask = vec4f(0.0, 1.0, 0.0,
|
|
102
|
+
out.mask = vec4f(0.0, 1.0, 0.0, outA);
|
|
96
103
|
return out;
|
|
97
104
|
}
|
|
98
105
|
`
|
package/dist/gpu-profile.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export declare class GpuTimestamp {
|
|
2
|
-
enabled: boolean;
|
|
3
|
-
readonly supported: boolean;
|
|
4
|
-
private device;
|
|
5
|
-
private querySet;
|
|
6
|
-
private resolveBuffer;
|
|
7
|
-
private readbackBuffer;
|
|
8
|
-
private nextSlot;
|
|
9
|
-
private frameLabels;
|
|
10
|
-
private inFlight;
|
|
11
|
-
private pendingLabels;
|
|
12
|
-
constructor(device: GPUDevice);
|
|
13
|
-
private ensureCreated;
|
|
14
|
-
beginFrame(): void;
|
|
15
|
-
wrap(desc: GPURenderPassDescriptor, label: string): GPURenderPassDescriptor;
|
|
16
|
-
endFrame(encoder: GPUCommandEncoder): void;
|
|
17
|
-
afterSubmit(): void;
|
|
18
|
-
}
|
|
19
|
-
//# sourceMappingURL=gpu-profile.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gpu-profile.d.ts","sourceRoot":"","sources":["../src/gpu-profile.ts"],"names":[],"mappings":"AAoBA,qBAAa,YAAY;IACvB,OAAO,UAAQ;IACf,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,QAAQ,CAAQ;IAKxB,OAAO,CAAC,aAAa,CAAwB;gBAEjC,MAAM,EAAE,SAAS;IAK7B,OAAO,CAAC,aAAa;IAiBrB,UAAU,IAAI,IAAI;IAWlB,IAAI,CAAC,IAAI,EAAE,uBAAuB,EAAE,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAqB3E,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAW1C,WAAW,IAAI,IAAI;CAwBpB"}
|
package/dist/gpu-profile.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
// WebGPU timestamp-query profiling. Records pass durations into the same
|
|
2
|
-
// profiler module as CPU phases (so the snapshot table mixes both with a
|
|
3
|
-
// "gpu." prefix). Read-only — no effect on rendering output.
|
|
4
|
-
//
|
|
5
|
-
// Pattern (per spec):
|
|
6
|
-
// 1. Each render pass declares timestampWrites { begin, end } slot indices.
|
|
7
|
-
// 2. After all passes, encoder.resolveQuerySet writes uint64 ns timestamps
|
|
8
|
-
// into a QUERY_RESOLVE buffer.
|
|
9
|
-
// 3. We copy the resolve buffer into a MAP_READ readback buffer.
|
|
10
|
-
// 4. Submit. After the GPU finishes, mapAsync resolves; we read durations
|
|
11
|
-
// and record them.
|
|
12
|
-
//
|
|
13
|
-
// One readback buffer with an in-flight gate: if mapAsync hasn't resolved
|
|
14
|
-
// from a previous frame, we skip the copy this frame (no contention, just
|
|
15
|
-
// one frame of dropped sampling).
|
|
16
|
-
import { profiler } from "./physics/profile";
|
|
17
|
-
const MAX_PASSES_PER_FRAME = 64;
|
|
18
|
-
export class GpuTimestamp {
|
|
19
|
-
constructor(device) {
|
|
20
|
-
this.enabled = false;
|
|
21
|
-
this.querySet = null;
|
|
22
|
-
this.resolveBuffer = null;
|
|
23
|
-
this.readbackBuffer = null;
|
|
24
|
-
this.nextSlot = 0;
|
|
25
|
-
this.frameLabels = [];
|
|
26
|
-
this.inFlight = false;
|
|
27
|
-
// Set inside endFrame() when a copy was queued this frame; consumed by
|
|
28
|
-
// afterSubmit() to kick mapAsync. mapAsync must run AFTER queue.submit —
|
|
29
|
-
// calling it before submit puts the buffer into "mapping pending" state,
|
|
30
|
-
// which makes the copy in the encoder illegal.
|
|
31
|
-
this.pendingLabels = null;
|
|
32
|
-
this.device = device;
|
|
33
|
-
this.supported = device.features.has("timestamp-query");
|
|
34
|
-
}
|
|
35
|
-
ensureCreated() {
|
|
36
|
-
if (this.querySet || !this.supported)
|
|
37
|
-
return;
|
|
38
|
-
const slots = MAX_PASSES_PER_FRAME * 2;
|
|
39
|
-
this.querySet = this.device.createQuerySet({ type: "timestamp", count: slots });
|
|
40
|
-
this.resolveBuffer = this.device.createBuffer({
|
|
41
|
-
label: "gpu-timestamp-resolve",
|
|
42
|
-
size: slots * 8,
|
|
43
|
-
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
|
|
44
|
-
});
|
|
45
|
-
this.readbackBuffer = this.device.createBuffer({
|
|
46
|
-
label: "gpu-timestamp-readback",
|
|
47
|
-
size: slots * 8,
|
|
48
|
-
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
// Called once at the top of each render() before any pass wrap.
|
|
52
|
-
beginFrame() {
|
|
53
|
-
if (!this.enabled || !this.supported)
|
|
54
|
-
return;
|
|
55
|
-
this.ensureCreated();
|
|
56
|
-
this.nextSlot = 0;
|
|
57
|
-
this.frameLabels = [];
|
|
58
|
-
}
|
|
59
|
-
// Wrap a render-pass descriptor with timestampWrites for `label`. If
|
|
60
|
-
// profiling is off / unsupported / out of slots, returns desc unchanged.
|
|
61
|
-
// The same label called multiple times (e.g. each bloom mip) is fine —
|
|
62
|
-
// the profiler accumulates by label.
|
|
63
|
-
wrap(desc, label) {
|
|
64
|
-
if (!this.enabled || !this.querySet)
|
|
65
|
-
return desc;
|
|
66
|
-
if (this.nextSlot + 2 > MAX_PASSES_PER_FRAME * 2)
|
|
67
|
-
return desc;
|
|
68
|
-
const begin = this.nextSlot;
|
|
69
|
-
const end = this.nextSlot + 1;
|
|
70
|
-
this.nextSlot += 2;
|
|
71
|
-
this.frameLabels.push(label);
|
|
72
|
-
return {
|
|
73
|
-
...desc,
|
|
74
|
-
timestampWrites: {
|
|
75
|
-
querySet: this.querySet,
|
|
76
|
-
beginningOfPassWriteIndex: begin,
|
|
77
|
-
endOfPassWriteIndex: end,
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
// Call on the encoder after all passes, BEFORE encoder.finish(). Adds
|
|
82
|
-
// resolveQuerySet + copyBufferToBuffer commands. mapAsync is deferred to
|
|
83
|
-
// afterSubmit() — calling it before submit transitions the readback buffer
|
|
84
|
-
// into "mapping pending" state, which makes the queued copy illegal.
|
|
85
|
-
endFrame(encoder) {
|
|
86
|
-
if (!this.enabled || !this.querySet || this.frameLabels.length === 0)
|
|
87
|
-
return;
|
|
88
|
-
const count = this.frameLabels.length * 2;
|
|
89
|
-
encoder.resolveQuerySet(this.querySet, 0, count, this.resolveBuffer, 0);
|
|
90
|
-
if (this.inFlight)
|
|
91
|
-
return;
|
|
92
|
-
encoder.copyBufferToBuffer(this.resolveBuffer, 0, this.readbackBuffer, 0, count * 8);
|
|
93
|
-
this.pendingLabels = this.frameLabels.slice();
|
|
94
|
-
}
|
|
95
|
-
// Call AFTER device.queue.submit(). Kicks mapAsync if a copy was queued
|
|
96
|
-
// this frame; the .then() reads durations and unmaps.
|
|
97
|
-
afterSubmit() {
|
|
98
|
-
const labels = this.pendingLabels;
|
|
99
|
-
if (!labels)
|
|
100
|
-
return;
|
|
101
|
-
this.pendingLabels = null;
|
|
102
|
-
this.inFlight = true;
|
|
103
|
-
const readback = this.readbackBuffer;
|
|
104
|
-
void readback.mapAsync(GPUMapMode.READ).then(() => {
|
|
105
|
-
const arr = new BigInt64Array(readback.getMappedRange());
|
|
106
|
-
for (let i = 0; i < labels.length; i++) {
|
|
107
|
-
const start = arr[i * 2];
|
|
108
|
-
const end = arr[i * 2 + 1];
|
|
109
|
-
// Per spec, timestamps are nanoseconds.
|
|
110
|
-
const ms = Number(end - start) / 1e6;
|
|
111
|
-
if (ms >= 0 && ms < 1000)
|
|
112
|
-
profiler.record("gpu." + labels[i], ms);
|
|
113
|
-
}
|
|
114
|
-
readback.unmap();
|
|
115
|
-
this.inFlight = false;
|
|
116
|
-
}, () => {
|
|
117
|
-
this.inFlight = false;
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
declare class PhysicsProfiler {
|
|
2
|
-
enabled: boolean;
|
|
3
|
-
private sums;
|
|
4
|
-
private counts;
|
|
5
|
-
begin(): number;
|
|
6
|
-
end(label: string, t0: number): void;
|
|
7
|
-
record(label: string, ms: number): void;
|
|
8
|
-
snapshot(): Record<string, {
|
|
9
|
-
avgMs: number;
|
|
10
|
-
calls: number;
|
|
11
|
-
totalMs: number;
|
|
12
|
-
}>;
|
|
13
|
-
reset(): void;
|
|
14
|
-
}
|
|
15
|
-
export declare const profiler: PhysicsProfiler;
|
|
16
|
-
export type ProfileSnapshot = ReturnType<PhysicsProfiler["snapshot"]>;
|
|
17
|
-
export {};
|
|
18
|
-
//# sourceMappingURL=profile.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/physics/profile.ts"],"names":[],"mappings":"AAIA,cAAM,eAAe;IACnB,OAAO,UAAQ;IACf,OAAO,CAAC,IAAI,CAA8C;IAC1D,OAAO,CAAC,MAAM,CAA8C;IAE5D,KAAK,IAAI,MAAM;IAIf,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IASpC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAQvC,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAU7E,KAAK,IAAI,IAAI;CAId;AAED,eAAO,MAAM,QAAQ,iBAAwB,CAAA;AAC7C,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAA"}
|