@driftengine/core 4.8.7 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +123 -11
- package/README.md +43 -38
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -3
- package/dist/render/ambientHarmonics.d.ts +1 -1
- package/dist/render/ambientHarmonics.js +1 -1
- package/dist/render/areaLights.d.ts +4 -4
- package/dist/render/areaLights.js +1 -1
- package/dist/render/atmosphere.d.ts +13 -0
- package/dist/render/atmosphere.js +9 -1
- package/dist/render/backend/api.d.ts +19 -4
- package/dist/render/backend/createRenderer.d.ts +6 -0
- package/dist/render/backend/createRenderer.js +4 -1
- package/dist/render/backend/select.js +11 -5
- package/dist/render/backend/webgl2/boneAnimations.d.ts +29 -0
- package/dist/render/backend/webgl2/boneAnimations.js +80 -0
- package/dist/render/backend/webgl2/instanced.d.ts +5 -1
- package/dist/render/backend/webgl2/instanced.js +13 -1
- package/dist/render/backend/webgl2/renderer.d.ts +180 -4
- package/dist/render/backend/webgl2/renderer.js +550 -40
- package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +5 -3
- package/dist/render/backend/webgl2/skinPaletteTexture.js +6 -4
- package/dist/render/backend/webgpu/boneAnimations.d.ts +34 -0
- package/dist/render/backend/webgpu/boneAnimations.js +76 -0
- package/dist/render/backend/webgpu/buffers.d.ts +19 -4
- package/dist/render/backend/webgpu/buffers.js +18 -11
- package/dist/render/backend/webgpu/compressedUpload.d.ts +0 -1
- package/dist/render/backend/webgpu/compressedUpload.js +53 -8
- package/dist/render/backend/webgpu/depthPass.d.ts +59 -3
- package/dist/render/backend/webgpu/depthPass.js +49 -14
- package/dist/render/backend/webgpu/device.d.ts +7 -1
- package/dist/render/backend/webgpu/device.js +31 -3
- package/dist/render/backend/webgpu/drawCommand.d.ts +18 -4
- package/dist/render/backend/webgpu/drawCommand.js +10 -3
- package/dist/render/backend/webgpu/flatPass.d.ts +100 -42
- package/dist/render/backend/webgpu/flatPass.js +191 -43
- package/dist/render/backend/webgpu/instanced.d.ts +14 -1
- package/dist/render/backend/webgpu/instanced.js +24 -1
- package/dist/render/backend/webgpu/materialStore.d.ts +101 -0
- package/dist/render/backend/webgpu/materialStore.js +265 -0
- package/dist/render/backend/webgpu/pipelineCache.d.ts +6 -0
- package/dist/render/backend/webgpu/pipelineCache.js +7 -0
- package/dist/render/backend/webgpu/postPass.d.ts +51 -9
- package/dist/render/backend/webgpu/postPass.js +16 -2
- package/dist/render/backend/webgpu/reflectionSurfacePass.d.ts +30 -0
- package/dist/render/backend/webgpu/reflectionSurfacePass.js +100 -0
- package/dist/render/backend/webgpu/renderer.d.ts +283 -37
- package/dist/render/backend/webgpu/renderer.js +1363 -202
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +7 -3
- package/dist/render/backend/webgpu/skinPaletteRing.js +13 -7
- package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +6 -0
- package/dist/render/backend/webgpu/skinPaletteTexture.js +7 -1
- package/dist/render/backend/webgpu/staticBundles.d.ts +125 -0
- package/dist/render/backend/webgpu/staticBundles.js +241 -0
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +10 -3
- package/dist/render/backend/webgpu/surfaceTexturePass.js +20 -2
- package/dist/render/backend/webgpu/uniformRing.d.ts +21 -0
- package/dist/render/backend/webgpu/uniformRing.js +9 -1
- package/dist/render/bloomChain.d.ts +3 -3
- package/dist/render/boneAnimation.d.ts +92 -0
- package/dist/render/boneAnimation.js +133 -0
- package/dist/render/compressedSource.d.ts +33 -13
- package/dist/render/compressedSource.js +70 -25
- package/dist/render/displayRange.d.ts +31 -0
- package/dist/render/displayRange.js +47 -0
- package/dist/render/filmicCurve.d.ts +74 -0
- package/dist/render/filmicCurve.js +203 -0
- package/dist/render/fog.d.ts +4 -1
- package/dist/render/fog.js +12 -2
- package/dist/render/frame/resources.d.ts +2 -2
- package/dist/render/frame/resources.js +6 -0
- package/dist/render/frameReflections.d.ts +40 -0
- package/dist/render/frameReflections.js +48 -0
- package/dist/render/glCompressed.js +83 -0
- package/dist/render/instances.d.ts +26 -0
- package/dist/render/lightBudget.d.ts +10 -0
- package/dist/render/lightBudget.js +10 -0
- package/dist/render/lightmap.d.ts +1 -1
- package/dist/render/mesh.d.ts +5 -0
- package/dist/render/mesh.js +6 -0
- package/dist/render/pass.d.ts +49 -0
- package/dist/render/pointLightSelection.d.ts +4 -4
- package/dist/render/reflectionSurfaceTargets.d.ts +28 -0
- package/dist/render/reflectionSurfaceTargets.js +99 -0
- package/dist/render/renderQuality.d.ts +39 -8
- package/dist/render/renderQuality.js +4 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +24 -1
- package/dist/render/screenSpaceReflection.d.ts +7 -1
- package/dist/render/screenSpaceReflection.js +5 -0
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/boneAnimation.d.ts +19 -0
- package/dist/render/shaders/boneAnimation.js +63 -0
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/depth.d.ts +7 -0
- package/dist/render/shaders/depth.js +35 -10
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/filmLook.js +2 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.d.ts +1 -1
- package/dist/render/shaders/flat/glassTint.js +2 -0
- package/dist/render/shaders/flat/index.d.ts +16 -0
- package/dist/render/shaders/flat/index.js +33 -5
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +43 -5
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/overlay.d.ts +16 -0
- package/dist/render/shaders/flat/overlay.js +117 -0
- package/dist/render/shaders/flat/pointShadow.js +3 -3
- package/dist/render/shaders/flat/preamble.js +38 -24
- package/dist/render/shaders/flat/reflectionSurface.d.ts +21 -0
- package/dist/render/shaders/flat/reflectionSurface.js +61 -0
- package/dist/render/shaders/fog.d.ts +1 -1
- package/dist/render/shaders/fog.js +9 -2
- package/dist/render/shaders/fringe.d.ts +29 -0
- package/dist/render/shaders/fringe.js +55 -0
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/arcane.wgsl.js +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/caustics.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +93 -0
- package/dist/render/shaders/generated/depth.wgsl.js +93 -0
- package/dist/render/shaders/generated/film.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/film.wgsl.js +1 -1
- package/dist/render/shaders/generated/fire.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/fire.wgsl.js +1 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +3254 -2920
- package/dist/render/shaders/generated/flat.wgsl.js +3292 -2957
- package/dist/render/shaders/generated/line.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/line.wgsl.js +1 -1
- package/dist/render/shaders/generated/particle.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/particle.wgsl.js +4 -4
- package/dist/render/shaders/generated/rush.wgsl.d.ts +56 -26
- package/dist/render/shaders/generated/rush.wgsl.js +56 -26
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/scatter.wgsl.js +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/smoke.wgsl.js +1 -1
- package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +29 -0
- package/dist/render/shaders/generated/ssrResolve.wgsl.js +29 -0
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +22 -2
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +22 -2
- package/dist/render/shaders/generated/water.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/water.wgsl.js +1 -1
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +2 -2
- package/dist/render/shaders/gpudriven/fog.wgsl.js +11 -3
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +7 -0
- package/dist/render/shaders/outputTransform.js +28 -14
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/rush.js +54 -11
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/ssrResolve.d.ts +17 -0
- package/dist/render/shaders/ssrResolve.js +51 -0
- package/dist/render/shaders/ssrTrace.js +26 -0
- package/dist/render/shaders/vertexChannel.d.ts +7 -0
- package/dist/render/shaders/vertexChannel.js +26 -0
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/ssrPass.d.ts +16 -0
- package/dist/render/ssrPass.js +106 -1
- package/dist/render/staticDraws.d.ts +47 -0
- package/dist/render/staticDraws.js +43 -0
- package/dist/render/surfaceOverlay.d.ts +132 -0
- package/dist/render/surfaceOverlay.js +85 -0
- package/dist/render/surfaceTexture.d.ts +57 -7
- package/dist/render/surfaceTexture.js +125 -17
- package/dist/render/temporalAa.d.ts +14 -0
- package/dist/render/temporalAa.js +20 -0
- package/dist/render/vertexDefaults.d.ts +17 -0
- package/dist/render/vertexDefaults.js +24 -0
- package/package.json +6 -6
- package/src/index.ts +24 -2
- package/src/render/ambientHarmonics.ts +1 -1
- package/src/render/areaLights.ts +4 -4
- package/src/render/atmosphere.ts +22 -1
- package/src/render/backend/api.ts +22 -3
- package/src/render/backend/createRenderer.ts +18 -1
- package/src/render/backend/select.ts +10 -5
- package/src/render/backend/webgl2/boneAnimations.ts +105 -0
- package/src/render/backend/webgl2/instanced.ts +20 -1
- package/src/render/backend/webgl2/renderer.ts +672 -37
- package/src/render/backend/webgl2/skinPaletteTexture.ts +6 -4
- package/src/render/backend/webgpu/boneAnimations.ts +97 -0
- package/src/render/backend/webgpu/buffers.ts +42 -16
- package/src/render/backend/webgpu/compressedUpload.ts +59 -9
- package/src/render/backend/webgpu/depthPass.ts +51 -10
- package/src/render/backend/webgpu/device.ts +42 -3
- package/src/render/backend/webgpu/drawCommand.ts +22 -4
- package/src/render/backend/webgpu/flatPass.ts +250 -40
- package/src/render/backend/webgpu/instanced.ts +32 -1
- package/src/render/backend/webgpu/materialStore.ts +276 -0
- package/src/render/backend/webgpu/pipelineCache.ts +7 -0
- package/src/render/backend/webgpu/postPass.ts +18 -0
- package/src/render/backend/webgpu/reflectionSurfacePass.ts +114 -0
- package/src/render/backend/webgpu/renderer.ts +1655 -230
- package/src/render/backend/webgpu/skinPaletteRing.ts +13 -7
- package/src/render/backend/webgpu/skinPaletteTexture.ts +7 -1
- package/src/render/backend/webgpu/staticBundles.ts +363 -0
- package/src/render/backend/webgpu/surfaceTexturePass.ts +29 -3
- package/src/render/backend/webgpu/uniformRing.ts +10 -1
- package/src/render/bloomChain.ts +3 -3
- package/src/render/boneAnimation.ts +223 -0
- package/src/render/compressedSource.ts +121 -29
- package/src/render/displayRange.ts +61 -0
- package/src/render/filmicCurve.ts +229 -0
- package/src/render/fog.ts +19 -3
- package/src/render/frame/resources.ts +6 -0
- package/src/render/frameReflections.ts +72 -0
- package/src/render/glCompressed.ts +83 -0
- package/src/render/instances.ts +27 -0
- package/src/render/lightBudget.ts +11 -0
- package/src/render/lightmap.ts +1 -1
- package/src/render/mesh.ts +6 -0
- package/src/render/pass.ts +53 -1
- package/src/render/pointLightSelection.ts +4 -4
- package/src/render/reflectionSurfaceTargets.ts +105 -0
- package/src/render/renderQuality.ts +44 -8
- package/src/render/sceneTarget.ts +30 -2
- package/src/render/screenSpaceReflection.ts +7 -1
- package/src/render/shaders/boneAnimation.ts +63 -0
- package/src/render/shaders/depth.ts +45 -10
- package/src/render/shaders/filmLook.ts +2 -1
- package/src/render/shaders/flat/glassTint.ts +5 -1
- package/src/render/shaders/flat/index.ts +49 -5
- package/src/render/shaders/flat/main.ts +43 -5
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/overlay.ts +117 -0
- package/src/render/shaders/flat/pointShadow.ts +3 -3
- package/src/render/shaders/flat/preamble.ts +38 -24
- package/src/render/shaders/flat/reflectionSurface.ts +61 -0
- package/src/render/shaders/fog.ts +9 -2
- package/src/render/shaders/fringe.ts +59 -0
- package/src/render/shaders/generated/arcane.wgsl.ts +1 -1
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/caustics.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +95 -0
- package/src/render/shaders/generated/film.wgsl.ts +1 -1
- package/src/render/shaders/generated/fire.wgsl.ts +1 -1
- package/src/render/shaders/generated/flat.wgsl.ts +3292 -2957
- package/src/render/shaders/generated/line.wgsl.ts +1 -1
- package/src/render/shaders/generated/particle.wgsl.ts +4 -4
- package/src/render/shaders/generated/rush.wgsl.ts +56 -26
- package/src/render/shaders/generated/scatter.wgsl.ts +1 -1
- package/src/render/shaders/generated/smoke.wgsl.ts +1 -1
- package/src/render/shaders/generated/ssrResolve.wgsl.ts +30 -0
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +22 -2
- package/src/render/shaders/generated/water.wgsl.ts +1 -1
- package/src/render/shaders/gpudriven/fog.wgsl.ts +11 -3
- package/src/render/shaders/outputTransform.ts +30 -14
- package/src/render/shaders/rush.ts +54 -11
- package/src/render/shaders/ssrResolve.ts +52 -0
- package/src/render/shaders/ssrTrace.ts +26 -0
- package/src/render/shaders/vertexChannel.ts +27 -0
- package/src/render/ssrPass.ts +138 -1
- package/src/render/staticDraws.ts +95 -0
- package/src/render/surfaceOverlay.ts +195 -0
- package/src/render/surfaceTexture.ts +161 -27
- package/src/render/temporalAa.ts +20 -0
- package/src/render/vertexDefaults.ts +28 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import type { BudgetCounter } from '../budget.ts';
|
|
2
|
+
import { DYNAMIC_ALIGNMENT, MAX_RING_BYTES } from './uniformRing.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The lit stage's material blocks, each kept in its slot for as long as later draws ask for the
|
|
6
|
+
* same numbers, so a material that has not changed since the last frame costs no upload at all.
|
|
7
|
+
*
|
|
8
|
+
* **Keyed by content, because nothing else names a material.** A material here is whatever the
|
|
9
|
+
* setters left in the block when a draw took it — a consumer never hands over an object that could
|
|
10
|
+
* carry an identity — so the block's own words are the key: hashed to find a bucket, compared word
|
|
11
|
+
* for word to accept a match, and bound by the slot's offset. What it gives up is a hash and a
|
|
12
|
+
* compare of a few hundred bytes per material change, against copying and uploading the whole
|
|
13
|
+
* block every frame, which a stage of four hundred materials measured at megabytes a frame.
|
|
14
|
+
*
|
|
15
|
+
* **A slot the frame has asked for is never given away within it**, for the reason `UniformRing`
|
|
16
|
+
* exists: a queue write does not interleave with recorded draws, so rewriting a slot a recorded draw
|
|
17
|
+
* reads would hand that draw the new numbers. A frame asking for more distinct blocks than there are
|
|
18
|
+
* slots is refused, counted and skipped, as the ring was. Across frames rewriting is safe, because a
|
|
19
|
+
* write issued after a submit is ordered after the commands in it.
|
|
20
|
+
*
|
|
21
|
+
* **What a new block displaces is a slot nobody asked for this frame or the last**, found by a hand
|
|
22
|
+
* that sweeps the slots in turn; only when every slot was asked for recently does it take one the
|
|
23
|
+
* last frame used. What would make that wrong is a scene whose distinct materials a frame approach
|
|
24
|
+
* the store's size: it then rewrites some of its steady materials every frame, which costs their
|
|
25
|
+
* upload and nothing else.
|
|
26
|
+
*/
|
|
27
|
+
export class MaterialStore {
|
|
28
|
+
/** Replaced by `growTo`, so every bind group over it is rebuilt when that says so. */
|
|
29
|
+
buffer: GPUBuffer;
|
|
30
|
+
/** Bytes per slot, rounded up to the alignment a dynamic offset requires. */
|
|
31
|
+
readonly slotSize: number;
|
|
32
|
+
|
|
33
|
+
private readonly words: number;
|
|
34
|
+
private readonly stride: number;
|
|
35
|
+
private capacity: number;
|
|
36
|
+
private staging: ArrayBuffer;
|
|
37
|
+
private ints: Int32Array;
|
|
38
|
+
/** Per slot: its block's hash, the next slot in its bucket or -1, and whether it holds one. */
|
|
39
|
+
private hashes: Int32Array;
|
|
40
|
+
private next: Int32Array;
|
|
41
|
+
private held: Uint8Array;
|
|
42
|
+
/** Per slot: the frame that last asked for it, and whether it was written since the last flush. */
|
|
43
|
+
private askedIn: Int32Array;
|
|
44
|
+
private pending: Uint8Array;
|
|
45
|
+
/** Per slot: how many holders pinned it. A pinned slot is never displaced. */
|
|
46
|
+
private pins: Int32Array;
|
|
47
|
+
/**
|
|
48
|
+
* How many times the store has forgotten everything it held, which growth does: a holder of an
|
|
49
|
+
* offset compares this, because an offset from before is a slot of a buffer that is gone.
|
|
50
|
+
*/
|
|
51
|
+
epoch = 0;
|
|
52
|
+
/** Per bucket: its first slot, or -1. A power of two at least twice the slots. */
|
|
53
|
+
private heads: Int32Array;
|
|
54
|
+
private mask: number;
|
|
55
|
+
private lowPending: number;
|
|
56
|
+
private highPending = -1;
|
|
57
|
+
private hand = 0;
|
|
58
|
+
/** Starts past zero, so a slot never asked for reads as long unasked. */
|
|
59
|
+
private frame = 2;
|
|
60
|
+
|
|
61
|
+
constructor(
|
|
62
|
+
private readonly device: GPUDevice,
|
|
63
|
+
bytesPerBlock: number,
|
|
64
|
+
slots: number,
|
|
65
|
+
private readonly usage: number,
|
|
66
|
+
private readonly label = 'materials.store',
|
|
67
|
+
private readonly budget: BudgetCounter | null = null,
|
|
68
|
+
) {
|
|
69
|
+
this.words = Math.ceil(bytesPerBlock / 4);
|
|
70
|
+
this.slotSize = Math.ceil(bytesPerBlock / DYNAMIC_ALIGNMENT) * DYNAMIC_ALIGNMENT;
|
|
71
|
+
this.stride = this.slotSize / 4;
|
|
72
|
+
this.capacity = slots;
|
|
73
|
+
this.lowPending = slots;
|
|
74
|
+
this.staging = new ArrayBuffer(this.slotSize * slots);
|
|
75
|
+
this.ints = new Int32Array(this.staging);
|
|
76
|
+
this.hashes = new Int32Array(slots);
|
|
77
|
+
this.next = new Int32Array(slots);
|
|
78
|
+
this.held = new Uint8Array(slots);
|
|
79
|
+
this.askedIn = new Int32Array(slots);
|
|
80
|
+
this.pending = new Uint8Array(slots);
|
|
81
|
+
this.pins = new Int32Array(slots);
|
|
82
|
+
this.heads = new Int32Array(bucketsFor(slots)).fill(-1);
|
|
83
|
+
this.mask = this.heads.length - 1;
|
|
84
|
+
this.buffer = device.createBuffer({ label, size: this.staging.byteLength, usage });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** How many distinct blocks a frame may hold. */
|
|
88
|
+
get slots(): number {
|
|
89
|
+
return this.capacity;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Start a frame: every slot the last one asked for may be displaced from here on, last. */
|
|
93
|
+
beginFrame(): void {
|
|
94
|
+
this.frame += 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The byte offset of a slot holding exactly `block`, written there if no slot did, or null when
|
|
99
|
+
* every slot is one this frame has asked for. `block` is the whole block, word for word.
|
|
100
|
+
*/
|
|
101
|
+
place(block: Int32Array): number | null {
|
|
102
|
+
this.budget?.ask();
|
|
103
|
+
const hash = hashWords(block, this.words);
|
|
104
|
+
const bucket = hash & this.mask;
|
|
105
|
+
for (let slot = this.heads[bucket] as number; slot >= 0; slot = this.next[slot] as number) {
|
|
106
|
+
if (this.hashes[slot] === hash && this.holds(slot, block)) {
|
|
107
|
+
this.askedIn[slot] = this.frame;
|
|
108
|
+
return slot * this.slotSize;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const slot = this.displace();
|
|
112
|
+
if (slot < 0) {
|
|
113
|
+
this.budget?.drop();
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (this.held[slot] === 1) this.unlink(slot);
|
|
117
|
+
this.ints.set(block, slot * this.stride);
|
|
118
|
+
this.hashes[slot] = hash;
|
|
119
|
+
this.next[slot] = this.heads[bucket] as number;
|
|
120
|
+
this.heads[bucket] = slot;
|
|
121
|
+
this.held[slot] = 1;
|
|
122
|
+
this.askedIn[slot] = this.frame;
|
|
123
|
+
this.pending[slot] = 1;
|
|
124
|
+
if (slot < this.lowPending) this.lowPending = slot;
|
|
125
|
+
if (slot > this.highPending) this.highPending = slot;
|
|
126
|
+
return slot * this.slotSize;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Upload every slot written since the last flush, one write per run of neighbouring slots. */
|
|
130
|
+
flush(): void {
|
|
131
|
+
let slot = this.lowPending;
|
|
132
|
+
while (slot <= this.highPending) {
|
|
133
|
+
if (this.pending[slot] !== 1) {
|
|
134
|
+
slot += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const from = slot;
|
|
138
|
+
while (slot <= this.highPending && this.pending[slot] === 1) {
|
|
139
|
+
this.pending[slot] = 0;
|
|
140
|
+
slot += 1;
|
|
141
|
+
}
|
|
142
|
+
const at = from * this.slotSize;
|
|
143
|
+
this.device.queue.writeBuffer(
|
|
144
|
+
this.buffer,
|
|
145
|
+
at,
|
|
146
|
+
this.staging,
|
|
147
|
+
at,
|
|
148
|
+
(slot - from) * this.slotSize,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
this.lowPending = this.capacity;
|
|
152
|
+
this.highPending = -1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Make room for at least `slots`, forgetting every block held, and say whether the buffer was
|
|
157
|
+
* replaced. Called where `UniformRing.growTo` is, at a frame's start, for the reasons it gives,
|
|
158
|
+
* and refused past the same ceiling.
|
|
159
|
+
*/
|
|
160
|
+
growTo(slots: number): boolean {
|
|
161
|
+
if (slots <= this.capacity) return false;
|
|
162
|
+
if (this.slotSize * slots > MAX_RING_BYTES) return false;
|
|
163
|
+
this.buffer.destroy();
|
|
164
|
+
this.capacity = slots;
|
|
165
|
+
this.staging = new ArrayBuffer(this.slotSize * slots);
|
|
166
|
+
this.ints = new Int32Array(this.staging);
|
|
167
|
+
this.hashes = new Int32Array(slots);
|
|
168
|
+
this.next = new Int32Array(slots);
|
|
169
|
+
this.held = new Uint8Array(slots);
|
|
170
|
+
this.askedIn = new Int32Array(slots);
|
|
171
|
+
this.pending = new Uint8Array(slots);
|
|
172
|
+
this.pins = new Int32Array(slots);
|
|
173
|
+
this.heads = new Int32Array(bucketsFor(slots)).fill(-1);
|
|
174
|
+
this.mask = this.heads.length - 1;
|
|
175
|
+
this.lowPending = slots;
|
|
176
|
+
this.epoch += 1;
|
|
177
|
+
this.highPending = -1;
|
|
178
|
+
this.hand = 0;
|
|
179
|
+
this.buffer = this.device.createBuffer({
|
|
180
|
+
label: this.label,
|
|
181
|
+
size: this.staging.byteLength,
|
|
182
|
+
usage: this.usage,
|
|
183
|
+
});
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Keep the block at `offset` where it is until `unpin`: a holder binding it by offset frame after
|
|
189
|
+
* frame — a list recorded once — whether or not anything asks for it. Counted, so two holders of
|
|
190
|
+
* one block each let go of their own pin.
|
|
191
|
+
*/
|
|
192
|
+
pin(offset: number): void {
|
|
193
|
+
const slot = offset / this.slotSize;
|
|
194
|
+
this.pins[slot] = (this.pins[slot] as number) + 1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
unpin(offset: number): void {
|
|
198
|
+
const slot = offset / this.slotSize;
|
|
199
|
+
if ((this.pins[slot] as number) > 0) this.pins[slot] = (this.pins[slot] as number) - 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
dispose(): void {
|
|
203
|
+
this.buffer.destroy();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private holds(slot: number, block: Int32Array): boolean {
|
|
207
|
+
const base = slot * this.stride;
|
|
208
|
+
for (let word = 0; word < this.words; word++) {
|
|
209
|
+
if (this.ints[base + word] !== block[word]) return false;
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The slot a new block goes in: the first from the hand nobody asked for this frame or the last,
|
|
216
|
+
* else the first this frame has not asked for, else none. One sweep at most.
|
|
217
|
+
*/
|
|
218
|
+
private displace(): number {
|
|
219
|
+
let fallback = -1;
|
|
220
|
+
for (let step = 0; step < this.capacity; step++) {
|
|
221
|
+
const slot = this.hand;
|
|
222
|
+
this.hand = slot + 1 === this.capacity ? 0 : slot + 1;
|
|
223
|
+
if ((this.pins[slot] as number) > 0) continue;
|
|
224
|
+
const asked = this.askedIn[slot] as number;
|
|
225
|
+
if (asked < this.frame - 1) return slot;
|
|
226
|
+
if (fallback < 0 && asked !== this.frame) fallback = slot;
|
|
227
|
+
}
|
|
228
|
+
return fallback;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private unlink(slot: number): void {
|
|
232
|
+
const bucket = (this.hashes[slot] as number) & this.mask;
|
|
233
|
+
let at = this.heads[bucket] as number;
|
|
234
|
+
if (at === slot) {
|
|
235
|
+
this.heads[bucket] = this.next[slot] as number;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
while (at >= 0) {
|
|
239
|
+
const after = this.next[at] as number;
|
|
240
|
+
if (after === slot) {
|
|
241
|
+
this.next[at] = this.next[slot] as number;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
at = after;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function bucketsFor(slots: number): number {
|
|
250
|
+
let buckets = 16;
|
|
251
|
+
while (buckets < slots * 2) buckets *= 2;
|
|
252
|
+
return buckets;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* MurmurHash3's 32-bit mix over the block's words. Not FNV, whose multiply carries a difference only
|
|
257
|
+
* upward: the bucket is the hash's low bits, and a material's words are mostly floats, whose low
|
|
258
|
+
* mantissa bits are zero for every value a person types — 0.5, 1, 2 — so blocks differing only in
|
|
259
|
+
* those would all have shared one bucket. The compare that follows a match is what decides.
|
|
260
|
+
*/
|
|
261
|
+
export function hashWords(block: Int32Array, words: number): number {
|
|
262
|
+
let hash = 0x9747b28c;
|
|
263
|
+
for (let word = 0; word < words; word++) {
|
|
264
|
+
let k = Math.imul(block[word] as number, 0xcc9e2d51);
|
|
265
|
+
k = (k << 15) | (k >>> 17);
|
|
266
|
+
hash ^= Math.imul(k, 0x1b873593);
|
|
267
|
+
hash = (hash << 13) | (hash >>> 19);
|
|
268
|
+
hash = (Math.imul(hash, 5) + 0xe6546b64) | 0;
|
|
269
|
+
}
|
|
270
|
+
hash ^= words * 4;
|
|
271
|
+
hash ^= hash >>> 16;
|
|
272
|
+
hash = Math.imul(hash, 0x85ebca6b);
|
|
273
|
+
hash ^= hash >>> 13;
|
|
274
|
+
hash = Math.imul(hash, 0xc2b2ae35);
|
|
275
|
+
return hash ^ (hash >>> 16);
|
|
276
|
+
}
|
|
@@ -58,6 +58,12 @@ export class PipelineCache {
|
|
|
58
58
|
|
|
59
59
|
/** How to describe each lit pipeline again, so `enable` can rebuild every one. */
|
|
60
60
|
private readonly litDescribers = new Map<string, () => GPURenderPipelineDescriptor>();
|
|
61
|
+
/**
|
|
62
|
+
* Bumped when a switch's rebuilt set lands, which replaces pipelines already handed out: a holder
|
|
63
|
+
* of one — a recorded render bundle — compares this and records again rather than drawing on
|
|
64
|
+
* with the pipelines from before the switch.
|
|
65
|
+
*/
|
|
66
|
+
version = 0;
|
|
61
67
|
/** Bumped by every switch, so a lit build started before one is not kept after it. */
|
|
62
68
|
private litGeneration = 0;
|
|
63
69
|
/** The last rebuild, which the next one waits for so they land in the order they were asked. */
|
|
@@ -220,6 +226,7 @@ export class PipelineCache {
|
|
|
220
226
|
for (const result of results) {
|
|
221
227
|
if (result.status === 'fulfilled') this.pipelines.set(result.value[0], result.value[1]);
|
|
222
228
|
}
|
|
229
|
+
this.version += 1;
|
|
223
230
|
});
|
|
224
231
|
return this.litRebuild;
|
|
225
232
|
}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from '../../shaders/generated/ssrTrace.wgsl.ts';
|
|
30
30
|
import {
|
|
31
31
|
SSRRESOLVE_BINDINGS as SSR_RESOLVE_BINDINGS,
|
|
32
|
+
SSR_MATERIAL_RESOLVE_FRAG_WGSL,
|
|
32
33
|
SSR_RESOLVE_FRAG_WGSL,
|
|
33
34
|
} from '../../shaders/generated/ssrResolve.wgsl.ts';
|
|
34
35
|
import {
|
|
@@ -331,6 +332,16 @@ export const REFLECTION_OVER_BLEND: GPUBlendState = {
|
|
|
331
332
|
alpha: SCENE_ALPHA_KEEPS,
|
|
332
333
|
};
|
|
333
334
|
|
|
335
|
+
/**
|
|
336
|
+
* The frame's materials' reflection, added: the found colour times the tint, less the probe's share,
|
|
337
|
+
* which is negative where the probe was brighter than what replaced it. A half-float scene takes
|
|
338
|
+
* the negative addend as the subtraction it is. Its alpha kept.
|
|
339
|
+
*/
|
|
340
|
+
export const FRAME_REFLECTION_BLEND: GPUBlendState = {
|
|
341
|
+
color: { srcFactor: 'one', dstFactor: 'one', operation: 'add' },
|
|
342
|
+
alpha: SCENE_ALPHA_KEEPS,
|
|
343
|
+
};
|
|
344
|
+
|
|
334
345
|
/**
|
|
335
346
|
* The bloom pyramid's way back up: an octave added onto a level that the blend multiplies by that
|
|
336
347
|
* level's tint first — the blend constant, which white makes `one, one` exactly. Into the pyramid's
|
|
@@ -450,6 +461,12 @@ export const SSR_FRAG_SIZE = SSR_BINDINGS.SSR_TRACE_FRAG.uniformSize;
|
|
|
450
461
|
export const SSR_UNIFORMS = SSR_BINDINGS.SSR_TRACE_FRAG.uniforms;
|
|
451
462
|
export const SSR_TEXTURES = SSR_BINDINGS.SSR_TRACE_FRAG.textures;
|
|
452
463
|
export const SSR_RESOLVE_TEXTURES = SSR_RESOLVE_BINDINGS.SSR_RESOLVE_FRAG.textures;
|
|
464
|
+
/** The material resolve's block — the blur — and its three inputs. See `frameReflections.ts`. */
|
|
465
|
+
export const SSR_MATERIAL_FIELDS: UniformFields =
|
|
466
|
+
SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.fields;
|
|
467
|
+
export const SSR_MATERIAL_SIZE = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.uniformSize;
|
|
468
|
+
export const SSR_MATERIAL_UNIFORMS = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.uniforms;
|
|
469
|
+
export const SSR_MATERIAL_TEXTURES = SSR_RESOLVE_BINDINGS.SSR_MATERIAL_RESOLVE_FRAG.textures;
|
|
453
470
|
|
|
454
471
|
/**
|
|
455
472
|
* The resolve's layout: one texture and no uniform block.
|
|
@@ -513,6 +530,7 @@ export {
|
|
|
513
530
|
DECAL_PROJECT_FRAG_WGSL,
|
|
514
531
|
SSR_TRACE_FRAG_WGSL,
|
|
515
532
|
SSR_RESOLVE_FRAG_WGSL,
|
|
533
|
+
SSR_MATERIAL_RESOLVE_FRAG_WGSL,
|
|
516
534
|
};
|
|
517
535
|
export { BLOOM_PREFILTER_FRAG_WGSL, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_UPSAMPLE_FRAG_WGSL };
|
|
518
536
|
export { RUSH_UNIFORMS, RUSH_TEXTURES };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The surface half of a reflection traced through the frame's materials, on this backend: the
|
|
3
|
+
* frame's opaque lit draws again, against the depth they wrote, into two half-float targets — the
|
|
4
|
+
* environment as the frame shows it with the surface's roughness, and what a reflection found in the
|
|
5
|
+
* frame is multiplied by to land in its place. `shaders/flat/reflectionSurface.ts` is what the two
|
|
6
|
+
* hold and why the swap is exact; this is where they are drawn.
|
|
7
|
+
*
|
|
8
|
+
* **Why a second pass and not a second output of the frame's.** Every pipeline drawn into the
|
|
9
|
+
* frame's pass would have to declare a target it never writes — the sky, water, particles, text and
|
|
10
|
+
* every pass a consumer registers — so the frame keeps its one attachment, and the lit stage's two
|
|
11
|
+
* extra outputs land nowhere there. Here they land: the same pipelines with `REFLECTION_SURFACE` on,
|
|
12
|
+
* which leaves before the lamps, the same bind groups and ring slots, tested `equal` against the
|
|
13
|
+
* frame's depth so only what the frame kept writes.
|
|
14
|
+
*
|
|
15
|
+
* **The draws are commands, kept the way the skin's halves are** (`skinScatterPass.ts`): the frame
|
|
16
|
+
* graph's pool is spent at every flush, and these are replayed after the flush that ends the frame's
|
|
17
|
+
* pass. Each holds the ring slots its frame draw used, which stay valid until the frame's last
|
|
18
|
+
* submit, after this has run.
|
|
19
|
+
*
|
|
20
|
+
* **What it costs**: every opaque lit draw twice, the second without its lamps; two half-float
|
|
21
|
+
* targets the size of the frame. Single-sampled only, as the trace that reads them is.
|
|
22
|
+
*/
|
|
23
|
+
import { createCommandPool, resetPool, takeCommand } from './drawCommand.ts';
|
|
24
|
+
import type { CommandPool, DrawCommand } from './drawCommand.ts';
|
|
25
|
+
|
|
26
|
+
/** Both targets' format: linear light and a tint, which an eight-bit target would clip and band. */
|
|
27
|
+
export const REFLECTION_SURFACE_FORMAT: GPUTextureFormat = 'rgba16float';
|
|
28
|
+
|
|
29
|
+
const USAGE_TARGET = 0x10 | 0x4; // RENDER_ATTACHMENT | TEXTURE_BINDING
|
|
30
|
+
|
|
31
|
+
export class ReflectionSurfacePass {
|
|
32
|
+
private readonly device: GPUDevice;
|
|
33
|
+
private readonly commands: CommandPool = createCommandPool(256);
|
|
34
|
+
private probe: GPUTexture | null = null;
|
|
35
|
+
private tint: GPUTexture | null = null;
|
|
36
|
+
/** The environment as the frame shows it, and the roughness in alpha. */
|
|
37
|
+
probeView: GPUTextureView | null = null;
|
|
38
|
+
/** What a found reflection is multiplied by. */
|
|
39
|
+
tintView: GPUTextureView | null = null;
|
|
40
|
+
|
|
41
|
+
constructor(device: GPUDevice) {
|
|
42
|
+
this.device = device;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A command for one opaque lit draw's surface half, filled by the caller. */
|
|
46
|
+
take(): DrawCommand {
|
|
47
|
+
return this.commands.commands[takeCommand(this.commands)] as DrawCommand;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** How many draws the frame kept. */
|
|
51
|
+
get kept(): number {
|
|
52
|
+
return this.commands.taken;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A new frame keeps nothing yet. */
|
|
56
|
+
reset(): void {
|
|
57
|
+
resetPool(this.commands);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The two targets at the frame's size, made again only when it changes. */
|
|
61
|
+
size(width: number, height: number): void {
|
|
62
|
+
if (this.probe !== null && this.probe.width === width && this.probe.height === height) return;
|
|
63
|
+
this.release();
|
|
64
|
+
const make = (label: string): GPUTexture =>
|
|
65
|
+
this.device.createTexture({
|
|
66
|
+
label,
|
|
67
|
+
size: [width, height],
|
|
68
|
+
format: REFLECTION_SURFACE_FORMAT,
|
|
69
|
+
usage: USAGE_TARGET,
|
|
70
|
+
});
|
|
71
|
+
this.probe = make('reflection.probe');
|
|
72
|
+
this.tint = make('reflection.tint');
|
|
73
|
+
this.probeView = this.probe.createView();
|
|
74
|
+
this.tintView = this.tint.createView();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Every kept draw into the two targets against the frame's depth, which it reads and does not
|
|
79
|
+
* write; both cleared first, so a pixel no lit surface reached reflects nothing. Called with no
|
|
80
|
+
* pass open on `encoder`, and drawn even with nothing kept, since the clear is what the trace
|
|
81
|
+
* reads there.
|
|
82
|
+
*/
|
|
83
|
+
draw(
|
|
84
|
+
encoder: GPUCommandEncoder,
|
|
85
|
+
depth: GPUTextureView,
|
|
86
|
+
issue: (pass: GPURenderPassEncoder, command: DrawCommand) => void,
|
|
87
|
+
): void {
|
|
88
|
+
const probe = this.probeView;
|
|
89
|
+
const tint = this.tintView;
|
|
90
|
+
if (probe === null || tint === null) return;
|
|
91
|
+
const pass = encoder.beginRenderPass({
|
|
92
|
+
label: 'reflection.surface',
|
|
93
|
+
colorAttachments: [
|
|
94
|
+
null,
|
|
95
|
+
{ view: probe, clearValue: [0, 0, 0, 1], loadOp: 'clear', storeOp: 'store' },
|
|
96
|
+
{ view: tint, clearValue: [0, 0, 0, 0], loadOp: 'clear', storeOp: 'store' },
|
|
97
|
+
],
|
|
98
|
+
depthStencilAttachment: { view: depth, depthReadOnly: true },
|
|
99
|
+
});
|
|
100
|
+
for (let i = 0; i < this.commands.taken; i += 1) {
|
|
101
|
+
issue(pass, this.commands.commands[i] as DrawCommand);
|
|
102
|
+
}
|
|
103
|
+
pass.end();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
release(): void {
|
|
107
|
+
this.probe?.destroy();
|
|
108
|
+
this.tint?.destroy();
|
|
109
|
+
this.probe = null;
|
|
110
|
+
this.tint = null;
|
|
111
|
+
this.probeView = null;
|
|
112
|
+
this.tintView = null;
|
|
113
|
+
}
|
|
114
|
+
}
|