@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,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vertex animation from bone textures: a clip that turns and places each bone frame by frame,
|
|
3
|
+
* which every vertex of an instanced batch's mesh follows — one bone a vertex, named by the mesh's
|
|
4
|
+
* second coordinates — at a moment each instance keeps its own clock for.
|
|
5
|
+
*
|
|
6
|
+
* **What a crowd is built from.** A rigged character skinned per draw costs a palette upload and a
|
|
7
|
+
* draw each; a crowd of them, played back from a clip a mesh, costs nothing on the processor once
|
|
8
|
+
* the clip is on the device, and every instance moves at the frame's rate on its own clock rather
|
|
9
|
+
* than in step with the others. What it gives up against skinning is the blend: a vertex follows
|
|
10
|
+
* exactly one bone, so a joint bends hard, which at a crowd's distance is what such clips are made
|
|
11
|
+
* for.
|
|
12
|
+
*
|
|
13
|
+
* **The vertex's bone is its second coordinates' u times `boneScale`**, rounded: a mesh's second
|
|
14
|
+
* coordinates (`MeshData.lightmapUvs`) are free on a crowd, which bakes no lightmap, and the format
|
|
15
|
+
* that writes one bone a sixty-fourth of u is the format this reads with `boneScale: 64`.
|
|
16
|
+
*
|
|
17
|
+
* **A frame's turn and place move the vertex as the bone does**: the turn about the mesh's origin,
|
|
18
|
+
* then the place added — `turn(q, p) + t` — and its normal and tangent turned with it. Between two
|
|
19
|
+
* frames both are blended: the places linearly, the turns normalised along the shorter arc. The
|
|
20
|
+
* moment is `(sceneTime × rate + phase) × framesPerSecond`, looping, where the scene's time is the
|
|
21
|
+
* clock `setAnimationTime` sets, once a frame before the shadows, and each instance's phase and
|
|
22
|
+
* rate are `MeshInstances.clocks`.
|
|
23
|
+
*
|
|
24
|
+
* Backend-neutral: both backends upload what `packBoneAnimation` packs and run
|
|
25
|
+
* `shaders/boneAnimation.ts`, which is `animateBoneVertex` below, line for line.
|
|
26
|
+
*/
|
|
27
|
+
import type { MeshInstances } from './instances.ts';
|
|
28
|
+
/** A clip on the device, from `createBoneAnimation`. Opaque but for its size. */
|
|
29
|
+
export interface BoneAnimationHandle {
|
|
30
|
+
readonly bones: number;
|
|
31
|
+
readonly frames: number;
|
|
32
|
+
}
|
|
33
|
+
/** A clip, as a caller hands it to `createBoneAnimation`. */
|
|
34
|
+
export interface BoneAnimationClip {
|
|
35
|
+
/** Bones each frame holds. */
|
|
36
|
+
readonly bones: number;
|
|
37
|
+
/** Frames, played in order and looped. */
|
|
38
|
+
readonly frames: number;
|
|
39
|
+
readonly framesPerSecond: number;
|
|
40
|
+
/** Where each bone puts what it carries: three floats a bone, bone by bone, frame by frame. */
|
|
41
|
+
readonly places: Float32Array;
|
|
42
|
+
/** Each bone's turn: a unit quaternion, x y z w, laid out as `places` is. */
|
|
43
|
+
readonly turns: Float32Array;
|
|
44
|
+
/** How many bones one unit of the second coordinates' u counts: `bones` unless given. */
|
|
45
|
+
readonly boneScale?: number;
|
|
46
|
+
}
|
|
47
|
+
/** A clip as textures hold it: a texel a bone a frame, bones across and frames down. */
|
|
48
|
+
export interface BoneAnimationTexels {
|
|
49
|
+
readonly bones: number;
|
|
50
|
+
readonly frames: number;
|
|
51
|
+
readonly framesPerSecond: number;
|
|
52
|
+
readonly boneScale: number;
|
|
53
|
+
/** RGBA floats: the place, and 1. */
|
|
54
|
+
readonly places: Float32Array;
|
|
55
|
+
/** RGBA floats: the turn, normalised. */
|
|
56
|
+
readonly turns: Float32Array;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The most bones or frames a clip may have: WebGL2's guaranteed texture size. A clip past it is
|
|
60
|
+
* refused rather than cut, since a cut clip is a crowd frozen at its last row.
|
|
61
|
+
*/
|
|
62
|
+
export declare const MAX_BONE_TEXTURE_SIZE = 2048;
|
|
63
|
+
/** A clip checked and packed into its two textures' texels. Runs once, when the clip is made. */
|
|
64
|
+
export declare function packBoneAnimation(clip: BoneAnimationClip): BoneAnimationTexels;
|
|
65
|
+
/** How wide a batch's clock texture is: one row up to this many instances, then more rows. */
|
|
66
|
+
export declare const CLOCK_TEXTURE_WIDTH = 1024;
|
|
67
|
+
/**
|
|
68
|
+
* Each instance's clock into `out`, two floats an instance — its phase in seconds and its rate —
|
|
69
|
+
* from `data.clocks`, and `(0, 1)` where it gives none: every instance at the clip's own speed,
|
|
70
|
+
* in step. `out` is the batch's, sized once.
|
|
71
|
+
*/
|
|
72
|
+
export declare function packInstanceClocks(data: MeshInstances, out: Float32Array): void;
|
|
73
|
+
/**
|
|
74
|
+
* Which two frames a moment falls between, and how far: what the shader reads. Looped, with a
|
|
75
|
+
* moment before the clip's start counted back from its end. **The first frame is clamped to the
|
|
76
|
+
* last**, with the blend left at whatever reaches the next: a moment a hair short of the loop that
|
|
77
|
+
* float error rounds up to `frames` lands on the last frame blended fully into the first, which is
|
|
78
|
+
* the first frame — the right answer — where an index of `frames` would read past the texture.
|
|
79
|
+
*/
|
|
80
|
+
export declare function boneFrame(moment: number, frames: number, out: {
|
|
81
|
+
first: number;
|
|
82
|
+
second: number;
|
|
83
|
+
blend: number;
|
|
84
|
+
}): void;
|
|
85
|
+
/**
|
|
86
|
+
* Where the clip puts a vertex at rest at `position`, following `bone`, at the scene's `time` on an
|
|
87
|
+
* instance clock of `phase` and `rate`, into `out`. The processor's statement of what the shader
|
|
88
|
+
* does, which the tests hold the arithmetic to and a device check holds the shader to.
|
|
89
|
+
*/
|
|
90
|
+
export declare function animateBoneVertex(clip: BoneAnimationTexels, bone: number, position: ArrayLike<number>, time: number, phase: number, rate: number, out: Float32Array): void;
|
|
91
|
+
/** The bone a vertex follows, from its second coordinates' u: what the shader rounds. */
|
|
92
|
+
export declare function boneOf(u: number, boneScale: number): number;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The most bones or frames a clip may have: WebGL2's guaranteed texture size. A clip past it is
|
|
3
|
+
* refused rather than cut, since a cut clip is a crowd frozen at its last row.
|
|
4
|
+
*/
|
|
5
|
+
export const MAX_BONE_TEXTURE_SIZE = 2048;
|
|
6
|
+
/** A clip checked and packed into its two textures' texels. Runs once, when the clip is made. */
|
|
7
|
+
export function packBoneAnimation(clip) {
|
|
8
|
+
const { bones, frames, framesPerSecond } = clip;
|
|
9
|
+
for (const [name, value] of [
|
|
10
|
+
['bones', bones],
|
|
11
|
+
['frames', frames],
|
|
12
|
+
]) {
|
|
13
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_BONE_TEXTURE_SIZE) {
|
|
14
|
+
throw new Error(`createBoneAnimation: ${name} is ${value}; a whole number from 1 to ${MAX_BONE_TEXTURE_SIZE}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (!(framesPerSecond > 0)) {
|
|
18
|
+
throw new Error(`createBoneAnimation: framesPerSecond is ${framesPerSecond}; above zero`);
|
|
19
|
+
}
|
|
20
|
+
const count = bones * frames;
|
|
21
|
+
if (clip.places.length !== count * 3 || clip.turns.length !== count * 4) {
|
|
22
|
+
throw new Error(`createBoneAnimation: ${bones} bones over ${frames} frames is ${count * 3} places and ` +
|
|
23
|
+
`${count * 4} turn numbers, and was given ${clip.places.length} and ${clip.turns.length}`);
|
|
24
|
+
}
|
|
25
|
+
const boneScale = clip.boneScale ?? bones;
|
|
26
|
+
if (!(boneScale > 0)) {
|
|
27
|
+
throw new Error(`createBoneAnimation: boneScale is ${boneScale}; above zero`);
|
|
28
|
+
}
|
|
29
|
+
const places = new Float32Array(count * 4);
|
|
30
|
+
const turns = new Float32Array(count * 4);
|
|
31
|
+
for (let i = 0; i < count; i++) {
|
|
32
|
+
places[i * 4] = clip.places[i * 3];
|
|
33
|
+
places[i * 4 + 1] = clip.places[i * 3 + 1];
|
|
34
|
+
places[i * 4 + 2] = clip.places[i * 3 + 2];
|
|
35
|
+
places[i * 4 + 3] = 1;
|
|
36
|
+
const x = clip.turns[i * 4];
|
|
37
|
+
const y = clip.turns[i * 4 + 1];
|
|
38
|
+
const z = clip.turns[i * 4 + 2];
|
|
39
|
+
const w = clip.turns[i * 4 + 3];
|
|
40
|
+
const length = Math.hypot(x, y, z, w);
|
|
41
|
+
/* A zero turn is no turn, rather than a NaN through every vertex that reads it. */
|
|
42
|
+
const scale = length > 0 ? 1 / length : 0;
|
|
43
|
+
turns[i * 4] = x * scale;
|
|
44
|
+
turns[i * 4 + 1] = y * scale;
|
|
45
|
+
turns[i * 4 + 2] = z * scale;
|
|
46
|
+
turns[i * 4 + 3] = length > 0 ? w * scale : 1;
|
|
47
|
+
}
|
|
48
|
+
return { bones, frames, framesPerSecond, boneScale, places, turns };
|
|
49
|
+
}
|
|
50
|
+
/** How wide a batch's clock texture is: one row up to this many instances, then more rows. */
|
|
51
|
+
export const CLOCK_TEXTURE_WIDTH = 1024;
|
|
52
|
+
/**
|
|
53
|
+
* Each instance's clock into `out`, two floats an instance — its phase in seconds and its rate —
|
|
54
|
+
* from `data.clocks`, and `(0, 1)` where it gives none: every instance at the clip's own speed,
|
|
55
|
+
* in step. `out` is the batch's, sized once.
|
|
56
|
+
*/
|
|
57
|
+
export function packInstanceClocks(data, out) {
|
|
58
|
+
const clocks = data.clocks;
|
|
59
|
+
const count = Math.min(data.count, out.length / 2);
|
|
60
|
+
for (let i = 0; i < count; i++) {
|
|
61
|
+
out[i * 2] = clocks === undefined ? 0 : (clocks[i * 2] ?? 0);
|
|
62
|
+
out[i * 2 + 1] = clocks === undefined ? 1 : (clocks[i * 2 + 1] ?? 1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Which two frames a moment falls between, and how far: what the shader reads. Looped, with a
|
|
67
|
+
* moment before the clip's start counted back from its end. **The first frame is clamped to the
|
|
68
|
+
* last**, with the blend left at whatever reaches the next: a moment a hair short of the loop that
|
|
69
|
+
* float error rounds up to `frames` lands on the last frame blended fully into the first, which is
|
|
70
|
+
* the first frame — the right answer — where an index of `frames` would read past the texture.
|
|
71
|
+
*/
|
|
72
|
+
export function boneFrame(moment, frames, out) {
|
|
73
|
+
const wrapped = moment - frames * Math.floor(moment / frames);
|
|
74
|
+
const first = Math.min(Math.floor(wrapped), frames - 1);
|
|
75
|
+
out.first = first;
|
|
76
|
+
out.second = first + 1 === frames ? 0 : first + 1;
|
|
77
|
+
out.blend = Math.min(Math.max(wrapped - first, 0), 1);
|
|
78
|
+
}
|
|
79
|
+
const scratchFrame = { first: 0, second: 0, blend: 0 };
|
|
80
|
+
const mixed = new Float32Array(4);
|
|
81
|
+
/** `v` turned by the unit quaternion `q`, into `out`. */
|
|
82
|
+
function turn(q, at, v, out) {
|
|
83
|
+
const qx = q[at];
|
|
84
|
+
const qy = q[at + 1];
|
|
85
|
+
const qz = q[at + 2];
|
|
86
|
+
const qw = q[at + 3];
|
|
87
|
+
const vx = v[0];
|
|
88
|
+
const vy = v[1];
|
|
89
|
+
const vz = v[2];
|
|
90
|
+
/* v + 2 q.xyz × (q.xyz × v + q.w v), as the shader writes it. */
|
|
91
|
+
const cx = qy * vz - qz * vy + qw * vx;
|
|
92
|
+
const cy = qz * vx - qx * vz + qw * vy;
|
|
93
|
+
const cz = qx * vy - qy * vx + qw * vz;
|
|
94
|
+
out[0] = vx + 2 * (qy * cz - qz * cy);
|
|
95
|
+
out[1] = vy + 2 * (qz * cx - qx * cz);
|
|
96
|
+
out[2] = vz + 2 * (qx * cy - qy * cx);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Where the clip puts a vertex at rest at `position`, following `bone`, at the scene's `time` on an
|
|
100
|
+
* instance clock of `phase` and `rate`, into `out`. The processor's statement of what the shader
|
|
101
|
+
* does, which the tests hold the arithmetic to and a device check holds the shader to.
|
|
102
|
+
*/
|
|
103
|
+
export function animateBoneVertex(clip, bone, position, time, phase, rate, out) {
|
|
104
|
+
boneFrame((time * rate + phase) * clip.framesPerSecond, clip.frames, scratchFrame);
|
|
105
|
+
const { first, second, blend } = scratchFrame;
|
|
106
|
+
const b = Math.min(Math.max(bone, 0), clip.bones - 1);
|
|
107
|
+
const a = (first * clip.bones + b) * 4;
|
|
108
|
+
const c = (second * clip.bones + b) * 4;
|
|
109
|
+
const q = clip.turns;
|
|
110
|
+
/* The shorter arc: a quaternion and its negation are one turn. */
|
|
111
|
+
const sign = q[a] * q[c] +
|
|
112
|
+
q[a + 1] * q[c + 1] +
|
|
113
|
+
q[a + 2] * q[c + 2] +
|
|
114
|
+
q[a + 3] * q[c + 3] <
|
|
115
|
+
0
|
|
116
|
+
? -1
|
|
117
|
+
: 1;
|
|
118
|
+
for (let k = 0; k < 4; k++) {
|
|
119
|
+
mixed[k] = q[a + k] * (1 - blend) + sign * q[c + k] * blend;
|
|
120
|
+
}
|
|
121
|
+
const length = Math.hypot(mixed[0], mixed[1], mixed[2], mixed[3]);
|
|
122
|
+
for (let k = 0; k < 4; k++)
|
|
123
|
+
mixed[k] = mixed[k] / length;
|
|
124
|
+
turn(mixed, 0, position, out);
|
|
125
|
+
const p = clip.places;
|
|
126
|
+
for (let k = 0; k < 3; k++) {
|
|
127
|
+
out[k] = out[k] + p[a + k] * (1 - blend) + p[c + k] * blend;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** The bone a vertex follows, from its second coordinates' u: what the shader rounds. */
|
|
131
|
+
export function boneOf(u, boneScale) {
|
|
132
|
+
return Math.floor(u * boneScale + 0.5);
|
|
133
|
+
}
|
|
@@ -2,22 +2,39 @@
|
|
|
2
2
|
* A block-compressed image a caller hands to `createSurfaceTexture`, and the one decision about it
|
|
3
3
|
* both backends share: whether this device takes it as blocks.
|
|
4
4
|
*
|
|
5
|
-
* **Uploaded as it is, or not at all.** A
|
|
6
|
-
* of one (BC1) against four for RGBA, and keeps the mip
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* **Uploaded as it is, or not at all.** A block format costs a byte a texel on the GPU (BC7, ETC2
|
|
6
|
+
* with alpha, ASTC 4x4) or half of one (BC1, ETC2 without) against four for RGBA, and keeps the mip
|
|
7
|
+
* chain its author built rather than one a box filter makes. **Three families, and a device has the
|
|
8
|
+
* ones it has**: desktops sample BC, phones ETC2 and ASTC, and few anything else. A device that
|
|
9
|
+
* cannot sample a format gets nothing from its blocks, and core does not convert them: the loader
|
|
10
|
+
* in `@driftengine/assets` asks `uploadsCompressed` first, and where the answer is no it decodes
|
|
11
|
+
* them — and encodes ETC2 where the device takes that — off the main thread. A caller handing a
|
|
12
|
+
* source this device cannot take is refused by name.
|
|
11
13
|
*
|
|
12
14
|
* Backend-neutral, because both backends must agree on what they accept before either touches a
|
|
13
|
-
* device. `
|
|
15
|
+
* device. `@driftengine/drft`'s `BcFormat` is the BC part of `BlockFormat`; core imports nothing
|
|
14
16
|
* from that package, and a format added there without a twin here fails where the loader passes one
|
|
15
17
|
* to the other, at compile time.
|
|
16
18
|
*/
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
+
/**
|
|
20
|
+
* BC1 to BC5 and BC7; ETC2's three and EAC's two; and ASTC at every block size the LDR profile has.
|
|
21
|
+
* BC6H, ASTC HDR and the signed forms are not among them.
|
|
22
|
+
*/
|
|
23
|
+
export type BlockFormat = 'bc1' | 'bc2' | 'bc3' | 'bc4' | 'bc5' | 'bc7' | 'etc2-rgb8' | 'etc2-rgb8a1' | 'etc2-rgba8' | 'eac-r11' | 'eac-rg11' | 'astc-4x4' | 'astc-5x4' | 'astc-5x5' | 'astc-6x5' | 'astc-6x6' | 'astc-8x5' | 'astc-8x6' | 'astc-8x8' | 'astc-10x5' | 'astc-10x6' | 'astc-10x8' | 'astc-10x10' | 'astc-12x10' | 'astc-12x12';
|
|
24
|
+
/** The formats with no sRGB form: one or two channels of data, never colour. */
|
|
25
|
+
type LinearOnly = 'bc4' | 'bc5' | 'eac-r11' | 'eac-rg11';
|
|
19
26
|
/** A format as a device offers it: the colour space is part of the name, as WebGPU's are. */
|
|
20
|
-
export type CompressedTextureFormat =
|
|
27
|
+
export type CompressedTextureFormat = BlockFormat | `${Exclude<BlockFormat, LinearOnly>}-srgb`;
|
|
28
|
+
/** A format's block: how many texels across and down, and how many bytes it takes. */
|
|
29
|
+
export interface BlockShape {
|
|
30
|
+
readonly width: number;
|
|
31
|
+
readonly height: number;
|
|
32
|
+
readonly bytes: number;
|
|
33
|
+
}
|
|
34
|
+
/** Every format's block. ASTC's are sixteen bytes whatever their size, which is its whole idea. */
|
|
35
|
+
export declare const BLOCK_SHAPES: Readonly<Record<BlockFormat, BlockShape>>;
|
|
36
|
+
/** Every format's family, which is what a device offers or does not. */
|
|
37
|
+
export declare function blockFamily(format: BlockFormat): 'bc' | 'etc2' | 'astc';
|
|
21
38
|
/** Block-compressed texels with their stored mip chain, level 0 first. */
|
|
22
39
|
export interface CompressedTextureSource {
|
|
23
40
|
readonly format: BlockFormat;
|
|
@@ -32,7 +49,7 @@ export type SurfaceSource = TexImageSource | CompressedTextureSource;
|
|
|
32
49
|
* one GPU texture has one format.
|
|
33
50
|
*/
|
|
34
51
|
export declare function compressedLayers(sources: readonly SurfaceSource[]): readonly CompressedTextureSource[] | null;
|
|
35
|
-
/** Bytes per
|
|
52
|
+
/** Bytes per block. See `BLOCK_SHAPES` for the block's size, which is not always 4x4. */
|
|
36
53
|
export declare const BLOCK_BYTES: Readonly<Record<BlockFormat, number>>;
|
|
37
54
|
/** Whether `source` is a compressed source rather than an image. */
|
|
38
55
|
export declare function isCompressedSource(source: unknown): source is CompressedTextureSource;
|
|
@@ -40,11 +57,13 @@ export declare function isCompressedSource(source: unknown): source is Compresse
|
|
|
40
57
|
export declare function compressedFormatName(format: BlockFormat, srgb: boolean): CompressedTextureFormat | null;
|
|
41
58
|
/**
|
|
42
59
|
* Whether a `width` by `height` source in `format` uploads as blocks on a device offering
|
|
43
|
-
* `available`. Level 0 must be whole blocks each way: WebGPU refuses any other size for a
|
|
44
|
-
* texture, and WebGL2's
|
|
60
|
+
* `available`. Level 0 must be whole blocks each way: WebGPU refuses any other size for a block
|
|
61
|
+
* texture, and WebGL2's extensions say the same, so a 6x6 image in 4x4 blocks is decoded rather than
|
|
45
62
|
* refused. What would make that wrong is a device that pads; none is relied on here.
|
|
46
63
|
*/
|
|
47
64
|
export declare function uploadsCompressed(format: BlockFormat, srgb: boolean, width: number, height: number, available: readonly CompressedTextureFormat[]): boolean;
|
|
65
|
+
/** The bytes level `level` of a `width` by `height` image takes: whole blocks, one at least. */
|
|
66
|
+
export declare function levelBytes(format: BlockFormat, width: number, height: number, level: number): number;
|
|
48
67
|
/**
|
|
49
68
|
* How many levels the layers carry, after checking each is the size its place requires and every
|
|
50
69
|
* layer matches the first. Refuses by name: a short level uploaded anyway is a device error at
|
|
@@ -64,3 +83,4 @@ export declare function planBlocks(blocks: readonly CompressedTextureSource[], s
|
|
|
64
83
|
* quietly reallocated, so the handle a draw holds keeps meaning what it meant.
|
|
65
84
|
*/
|
|
66
85
|
export declare function refuseBlockUpdate(format: BlockFormat | null): void;
|
|
86
|
+
export {};
|
|
@@ -2,18 +2,61 @@
|
|
|
2
2
|
* A block-compressed image a caller hands to `createSurfaceTexture`, and the one decision about it
|
|
3
3
|
* both backends share: whether this device takes it as blocks.
|
|
4
4
|
*
|
|
5
|
-
* **Uploaded as it is, or not at all.** A
|
|
6
|
-
* of one (BC1) against four for RGBA, and keeps the mip
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* **Uploaded as it is, or not at all.** A block format costs a byte a texel on the GPU (BC7, ETC2
|
|
6
|
+
* with alpha, ASTC 4x4) or half of one (BC1, ETC2 without) against four for RGBA, and keeps the mip
|
|
7
|
+
* chain its author built rather than one a box filter makes. **Three families, and a device has the
|
|
8
|
+
* ones it has**: desktops sample BC, phones ETC2 and ASTC, and few anything else. A device that
|
|
9
|
+
* cannot sample a format gets nothing from its blocks, and core does not convert them: the loader
|
|
10
|
+
* in `@driftengine/assets` asks `uploadsCompressed` first, and where the answer is no it decodes
|
|
11
|
+
* them — and encodes ETC2 where the device takes that — off the main thread. A caller handing a
|
|
12
|
+
* source this device cannot take is refused by name.
|
|
11
13
|
*
|
|
12
14
|
* Backend-neutral, because both backends must agree on what they accept before either touches a
|
|
13
|
-
* device. `
|
|
15
|
+
* device. `@driftengine/drft`'s `BcFormat` is the BC part of `BlockFormat`; core imports nothing
|
|
14
16
|
* from that package, and a format added there without a twin here fails where the loader passes one
|
|
15
17
|
* to the other, at compile time.
|
|
16
18
|
*/
|
|
19
|
+
const shape = (width, height, bytes) => ({
|
|
20
|
+
width,
|
|
21
|
+
height,
|
|
22
|
+
bytes,
|
|
23
|
+
});
|
|
24
|
+
/** Every format's block. ASTC's are sixteen bytes whatever their size, which is its whole idea. */
|
|
25
|
+
export const BLOCK_SHAPES = {
|
|
26
|
+
bc1: shape(4, 4, 8),
|
|
27
|
+
bc2: shape(4, 4, 16),
|
|
28
|
+
bc3: shape(4, 4, 16),
|
|
29
|
+
bc4: shape(4, 4, 8),
|
|
30
|
+
bc5: shape(4, 4, 16),
|
|
31
|
+
bc7: shape(4, 4, 16),
|
|
32
|
+
'etc2-rgb8': shape(4, 4, 8),
|
|
33
|
+
'etc2-rgb8a1': shape(4, 4, 8),
|
|
34
|
+
'etc2-rgba8': shape(4, 4, 16),
|
|
35
|
+
'eac-r11': shape(4, 4, 8),
|
|
36
|
+
'eac-rg11': shape(4, 4, 16),
|
|
37
|
+
'astc-4x4': shape(4, 4, 16),
|
|
38
|
+
'astc-5x4': shape(5, 4, 16),
|
|
39
|
+
'astc-5x5': shape(5, 5, 16),
|
|
40
|
+
'astc-6x5': shape(6, 5, 16),
|
|
41
|
+
'astc-6x6': shape(6, 6, 16),
|
|
42
|
+
'astc-8x5': shape(8, 5, 16),
|
|
43
|
+
'astc-8x6': shape(8, 6, 16),
|
|
44
|
+
'astc-8x8': shape(8, 8, 16),
|
|
45
|
+
'astc-10x5': shape(10, 5, 16),
|
|
46
|
+
'astc-10x6': shape(10, 6, 16),
|
|
47
|
+
'astc-10x8': shape(10, 8, 16),
|
|
48
|
+
'astc-10x10': shape(10, 10, 16),
|
|
49
|
+
'astc-12x10': shape(12, 10, 16),
|
|
50
|
+
'astc-12x12': shape(12, 12, 16),
|
|
51
|
+
};
|
|
52
|
+
/** Every format's family, which is what a device offers or does not. */
|
|
53
|
+
export function blockFamily(format) {
|
|
54
|
+
if (format.startsWith('bc'))
|
|
55
|
+
return 'bc';
|
|
56
|
+
if (format.startsWith('astc'))
|
|
57
|
+
return 'astc';
|
|
58
|
+
return 'etc2';
|
|
59
|
+
}
|
|
17
60
|
/**
|
|
18
61
|
* The layers as blocks, or null where every one is an image. An array mixing the two is refused:
|
|
19
62
|
* one GPU texture has one format.
|
|
@@ -31,15 +74,8 @@ export function compressedLayers(sources) {
|
|
|
31
74
|
}
|
|
32
75
|
return sources;
|
|
33
76
|
}
|
|
34
|
-
/** Bytes per
|
|
35
|
-
export const BLOCK_BYTES =
|
|
36
|
-
bc1: 8,
|
|
37
|
-
bc2: 16,
|
|
38
|
-
bc3: 16,
|
|
39
|
-
bc4: 8,
|
|
40
|
-
bc5: 16,
|
|
41
|
-
bc7: 16,
|
|
42
|
-
};
|
|
77
|
+
/** Bytes per block. See `BLOCK_SHAPES` for the block's size, which is not always 4x4. */
|
|
78
|
+
export const BLOCK_BYTES = Object.fromEntries(Object.entries(BLOCK_SHAPES).map(([format, block]) => [format, block.bytes]));
|
|
43
79
|
/** Whether `source` is a compressed source rather than an image. */
|
|
44
80
|
export function isCompressedSource(source) {
|
|
45
81
|
const candidate = source;
|
|
@@ -52,23 +88,30 @@ export function isCompressedSource(source) {
|
|
|
52
88
|
export function compressedFormatName(format, srgb) {
|
|
53
89
|
if (!srgb)
|
|
54
90
|
return format;
|
|
55
|
-
return format === 'bc4' || format === 'bc5'
|
|
91
|
+
return format === 'bc4' || format === 'bc5' || format === 'eac-r11' || format === 'eac-rg11'
|
|
92
|
+
? null
|
|
93
|
+
: `${format}-srgb`;
|
|
56
94
|
}
|
|
57
95
|
/**
|
|
58
96
|
* Whether a `width` by `height` source in `format` uploads as blocks on a device offering
|
|
59
|
-
* `available`. Level 0 must be whole blocks each way: WebGPU refuses any other size for a
|
|
60
|
-
* texture, and WebGL2's
|
|
97
|
+
* `available`. Level 0 must be whole blocks each way: WebGPU refuses any other size for a block
|
|
98
|
+
* texture, and WebGL2's extensions say the same, so a 6x6 image in 4x4 blocks is decoded rather than
|
|
61
99
|
* refused. What would make that wrong is a device that pads; none is relied on here.
|
|
62
100
|
*/
|
|
63
101
|
export function uploadsCompressed(format, srgb, width, height, available) {
|
|
64
102
|
const name = compressedFormatName(format, srgb);
|
|
65
|
-
|
|
103
|
+
const block = BLOCK_SHAPES[format];
|
|
104
|
+
return (name !== null &&
|
|
105
|
+
width % block.width === 0 &&
|
|
106
|
+
height % block.height === 0 &&
|
|
107
|
+
available.includes(name));
|
|
66
108
|
}
|
|
67
109
|
/** The bytes level `level` of a `width` by `height` image takes: whole blocks, one at least. */
|
|
68
|
-
function levelBytes(format, width, height, level) {
|
|
110
|
+
export function levelBytes(format, width, height, level) {
|
|
69
111
|
const w = Math.max(1, width >> level);
|
|
70
112
|
const h = Math.max(1, height >> level);
|
|
71
|
-
|
|
113
|
+
const block = BLOCK_SHAPES[format];
|
|
114
|
+
return Math.ceil(w / block.width) * Math.ceil(h / block.height) * block.bytes;
|
|
72
115
|
}
|
|
73
116
|
/**
|
|
74
117
|
* How many levels the layers carry, after checking each is the size its place requires and every
|
|
@@ -111,10 +154,12 @@ export function compressedLevels(layers) {
|
|
|
111
154
|
/** What a refusal names: the format, its colour space, and what the device offers instead. */
|
|
112
155
|
export function compressedRefusal(format, srgb, width, height, available) {
|
|
113
156
|
const asked = `${format.toUpperCase()}${srgb ? ' sRGB' : ''} at ${width}x${height}`;
|
|
114
|
-
const offered = available.length === 0 ? 'no
|
|
157
|
+
const offered = available.length === 0 ? 'no block format' : available.join(', ');
|
|
158
|
+
const block = BLOCK_SHAPES[format];
|
|
115
159
|
return (`createSurfaceTexture: this device cannot take ${asked} as blocks — it offers ${offered}, and ` +
|
|
116
|
-
|
|
117
|
-
'@driftengine/assets), or ask
|
|
160
|
+
`the texture must be whole ${block.width}x${block.height} blocks at level 0. Decode it first ` +
|
|
161
|
+
'(decodeBc, or encodeEtc2 for a device that takes ETC2, in @driftengine/assets), or ask ' +
|
|
162
|
+
'renderer.compressedFormats before choosing.');
|
|
118
163
|
}
|
|
119
164
|
/** The format name and level count blocks upload as here, or a refusal naming why they cannot. */
|
|
120
165
|
export function planBlocks(blocks, srgb, compressed) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which dynamic range the frame goes out in, and why: the one decision behind
|
|
3
|
+
* `RenderQuality.highDynamicRange`, made here so both backends answer it in the same words.
|
|
4
|
+
*
|
|
5
|
+
* **A high range is asked for, found, and held, or it is not had.** Three things must all be true:
|
|
6
|
+
* the profile asked; the frame has a composite holding light past white (`screenEffects` and
|
|
7
|
+
* `hdrScene`), since every other pass writes display values already clipped; and the display says
|
|
8
|
+
* it can show more than paper white. The backend then has to be able to configure such a canvas,
|
|
9
|
+
* which only it knows, and it may still answer standard after this says high.
|
|
10
|
+
*
|
|
11
|
+
* **What a high range gives up** is nothing a standard frame had: paper white stays where it is,
|
|
12
|
+
* and only what was above it — a lamp, a flash, a sunlit edge — goes on rising to the display's
|
|
13
|
+
* peak instead of rolling off at white. **What would make it wrong** is a display that reports a
|
|
14
|
+
* high range it cannot show, which a page cannot detect and the platform owns.
|
|
15
|
+
*/
|
|
16
|
+
export type DisplayRange = 'high' | 'standard';
|
|
17
|
+
export interface DisplayRangeChoice {
|
|
18
|
+
readonly range: DisplayRange;
|
|
19
|
+
/** A sentence saying why, for `RendererApi.displayRangeReason`. */
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
}
|
|
22
|
+
/** The display's own answer, as the browser gives it. A host without a DOM supplies its own. */
|
|
23
|
+
export declare function browserHighDynamicRange(): boolean;
|
|
24
|
+
/** The range a profile gets on a display, before the backend has tried to configure it. */
|
|
25
|
+
export declare function chooseDisplayRange(asked: boolean, composite: boolean, displayIsHigh: boolean): DisplayRangeChoice;
|
|
26
|
+
/**
|
|
27
|
+
* The display's peak over the frame's paper white, as `setDisplayLuminance` is handed it: at
|
|
28
|
+
* least 1, and 1 where the range is standard or a number is not one. Capped at 64, a thousand nits
|
|
29
|
+
* over a sixteen-nit white, which no display reaches and no curve should be asked to.
|
|
30
|
+
*/
|
|
31
|
+
export declare function displayHeadroom(range: DisplayRange, paperWhite: number, peak: number): number;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which dynamic range the frame goes out in, and why: the one decision behind
|
|
3
|
+
* `RenderQuality.highDynamicRange`, made here so both backends answer it in the same words.
|
|
4
|
+
*
|
|
5
|
+
* **A high range is asked for, found, and held, or it is not had.** Three things must all be true:
|
|
6
|
+
* the profile asked; the frame has a composite holding light past white (`screenEffects` and
|
|
7
|
+
* `hdrScene`), since every other pass writes display values already clipped; and the display says
|
|
8
|
+
* it can show more than paper white. The backend then has to be able to configure such a canvas,
|
|
9
|
+
* which only it knows, and it may still answer standard after this says high.
|
|
10
|
+
*
|
|
11
|
+
* **What a high range gives up** is nothing a standard frame had: paper white stays where it is,
|
|
12
|
+
* and only what was above it — a lamp, a flash, a sunlit edge — goes on rising to the display's
|
|
13
|
+
* peak instead of rolling off at white. **What would make it wrong** is a display that reports a
|
|
14
|
+
* high range it cannot show, which a page cannot detect and the platform owns.
|
|
15
|
+
*/
|
|
16
|
+
/** The display's own answer, as the browser gives it. A host without a DOM supplies its own. */
|
|
17
|
+
export function browserHighDynamicRange() {
|
|
18
|
+
// platform: browser default — `CreateRendererOptions.highDynamicRangeDisplay` replaces it
|
|
19
|
+
return typeof matchMedia === 'function' && matchMedia('(dynamic-range: high)').matches;
|
|
20
|
+
}
|
|
21
|
+
/** The range a profile gets on a display, before the backend has tried to configure it. */
|
|
22
|
+
export function chooseDisplayRange(asked, composite, displayIsHigh) {
|
|
23
|
+
if (!asked)
|
|
24
|
+
return { range: 'standard', reason: 'highDynamicRange was not asked for' };
|
|
25
|
+
if (!composite) {
|
|
26
|
+
return {
|
|
27
|
+
range: 'standard',
|
|
28
|
+
reason: 'highDynamicRange needs screenEffects and hdrScene: without a composite holding light ' +
|
|
29
|
+
'past white, every pass writes values already clipped to it',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (!displayIsHigh) {
|
|
33
|
+
return { range: 'standard', reason: 'the display reports a standard dynamic range' };
|
|
34
|
+
}
|
|
35
|
+
return { range: 'high', reason: 'an extended-range canvas on a high dynamic range display' };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The display's peak over the frame's paper white, as `setDisplayLuminance` is handed it: at
|
|
39
|
+
* least 1, and 1 where the range is standard or a number is not one. Capped at 64, a thousand nits
|
|
40
|
+
* over a sixteen-nit white, which no display reaches and no curve should be asked to.
|
|
41
|
+
*/
|
|
42
|
+
export function displayHeadroom(range, paperWhite, peak) {
|
|
43
|
+
if (range !== 'high')
|
|
44
|
+
return 1;
|
|
45
|
+
const ratio = peak / paperWhite;
|
|
46
|
+
return Number.isFinite(ratio) ? Math.min(Math.max(ratio, 1), 64) : 1;
|
|
47
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `filmic` output transform: a parametric film curve, its five numbers, and the eight
|
|
3
|
+
* constants the resolve reads, decided once here for both backends.
|
|
4
|
+
*
|
|
5
|
+
* **Why a second filmic curve beside `aces`.** `aces` is the reference RRT and ODT fit, which
|
|
6
|
+
* holds a mid grey of 0.18 at about 0.11 of display linear: graded for a dark cinema and kept dark.
|
|
7
|
+
* The curve here holds 0.18 at 0.18 by construction, and is shaped by five numbers a grading
|
|
8
|
+
* volume carries — a slope through mid grey, a toe and a shoulder, and how far black and white are
|
|
9
|
+
* clipped past the ends — so a look authored against such a curve is matched by passing its numbers
|
|
10
|
+
* rather than by baking the difference into a colour lookup table, which has the fewest steps
|
|
11
|
+
* exactly where two curves differ most: in the shadows.
|
|
12
|
+
*
|
|
13
|
+
* **What the curve is.** In the ACEScg working space, after the ACES reference's glow and red
|
|
14
|
+
* modifier and a 4% desaturation: a straight line of `slope` through mid grey in log10, joined to a
|
|
15
|
+
* logistic toe below `toeMatch` and a logistic shoulder above `shoulderMatch`, blended by a smooth
|
|
16
|
+
* step between the two, then desaturated by 7% and taken back to linear sRGB. The toe's position is
|
|
17
|
+
* solved so that 0.18 in is 0.18 out.
|
|
18
|
+
*
|
|
19
|
+
* **What it gives up.** It needs the composite to grade: `screenEffects` and `hdrScene`, since the
|
|
20
|
+
* forward passes that grade a frame without one carry two uniforms between them and not eight.
|
|
21
|
+
* Without them every pass grades with `aces` and the renderer says so once at construction.
|
|
22
|
+
* **What would make it wrong** is a profile wanting this curve without a scene target, which would
|
|
23
|
+
* then pay six floats in every forward program to have it.
|
|
24
|
+
*/
|
|
25
|
+
/** A film curve's five numbers, as a grading volume states them. */
|
|
26
|
+
export interface FilmicCurve {
|
|
27
|
+
/** The straight segment's slope in log10, through mid grey. Positive. */
|
|
28
|
+
readonly slope: number;
|
|
29
|
+
/** How much of the curve below mid grey the toe takes, 0 to 1. Larger is a longer, softer toe. */
|
|
30
|
+
readonly toe: number;
|
|
31
|
+
/** How much of the curve above mid grey the shoulder takes, 0 to 1. Larger rolls off sooner. */
|
|
32
|
+
readonly shoulder: number;
|
|
33
|
+
/** How far below 0 the toe's asymptote sits: 0 holds black at black, more crushes it. */
|
|
34
|
+
readonly blackClip: number;
|
|
35
|
+
/** How far above 1 the shoulder's asymptote sits: 0 never reaches white, more lets it clip. */
|
|
36
|
+
readonly whiteClip: number;
|
|
37
|
+
}
|
|
38
|
+
/** The defaults the curve ships with where a volume states nothing. */
|
|
39
|
+
export declare const DEFAULT_FILMIC_CURVE: FilmicCurve;
|
|
40
|
+
/** How many floats `resolveFilmicCurve` fills: two vectors of the resolve's uniforms. */
|
|
41
|
+
export declare const FILMIC_CONSTANTS = 8;
|
|
42
|
+
/**
|
|
43
|
+
* The eight constants the resolve reads, into `out`: `uFilmA` = (slope, black clip, white clip,
|
|
44
|
+
* toe scale) and `uFilmB` = (shoulder scale, toe match, straight match, shoulder match). Returns
|
|
45
|
+
* whether any of the five numbers had to be brought into range.
|
|
46
|
+
*
|
|
47
|
+
* Here rather than in the shader, because they are one solve a curve and would be one per pixel
|
|
48
|
+
* there. **Clamped rather than refused**, because a grading volume is set from inside a frame and
|
|
49
|
+
* the frame loop never throws: a slope below 0.01, a toe, shoulder or clip outside 0 to 1, and a
|
|
50
|
+
* toe or shoulder of exactly 1 — whose scale is then zero and divides the curve by it — are each
|
|
51
|
+
* a picture made of NaN otherwise. A number that is not finite takes the default's. The renderer
|
|
52
|
+
* says once when anything was clamped.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveFilmicCurve(curve: FilmicCurve, out: Float32Array,
|
|
55
|
+
/**
|
|
56
|
+
* The display's peak over paper white (`displayHeadroom`), 1 for a standard range. Above 1 the
|
|
57
|
+
* white clip is raised by the difference, so the shoulder rolls off toward the display's peak
|
|
58
|
+
* rather than toward white, and the toe and mid grey stay exactly where they were.
|
|
59
|
+
*/
|
|
60
|
+
headroom?: number): boolean;
|
|
61
|
+
/** Said once a renderer, the first time `setFilmicCurve` was handed a number out of range. */
|
|
62
|
+
export declare const FILMIC_CLAMPED: string;
|
|
63
|
+
/**
|
|
64
|
+
* Said once at construction where `filmic` was asked for and the frame has no composite that
|
|
65
|
+
* grades: the passes grade with `aces`, which is the nearest curve they carry.
|
|
66
|
+
*/
|
|
67
|
+
export declare const FILMIC_WITHOUT_COMPOSITE: string;
|
|
68
|
+
/**
|
|
69
|
+
* `filmicCurve(c)`: linear sRGB in, display-linear sRGB out, clamped to 0 to 1 — or, on a high
|
|
70
|
+
* range display, to the shoulder's own asymptote. Reads `uFilmA`, `uFilmB` and `uDisplayHeadroom`,
|
|
71
|
+
* which the including stage declares. The ACES helpers are the reference's own:
|
|
72
|
+
* saturation, `yc`, the glow's sigmoid, and hue in degrees.
|
|
73
|
+
*/
|
|
74
|
+
export declare const FILMIC_GLSL = "\n/* sRGB to ACEScg (through D60), ACEScg back, and ACEScg to and from ACES2065-1. Columns. */\nconst mat3 FILM_SRGB_TO_AP1 = mat3(\n 0.61319, 0.07021, 0.02062,\n 0.33951, 0.91634, 0.10957,\n 0.04737, 0.01345, 0.86961\n);\n/* The inverse of the one above, computed rather than copied, so a round trip is the identity. */\nconst mat3 FILM_AP1_TO_SRGB = mat3(\n 1.70480, -0.13027, -0.02401,\n -0.62168, 1.14082, -0.12900,\n -0.08325, -0.01055, 1.15324\n);\nconst mat3 FILM_AP1_TO_AP0 = mat3(\n 0.6954522414, 0.0447945634, -0.0055258826,\n 0.1406786965, 0.8596711185, 0.0040252103,\n 0.1638690622, 0.0955343182, 1.0015006723\n);\nconst mat3 FILM_AP0_TO_AP1 = mat3(\n 1.4514393161, -0.0765537734, 0.0083161484,\n -0.2365107469, 1.1762296998, -0.0060324498,\n -0.2149285693, -0.0996759264, 0.9977163014\n);\nconst vec3 FILM_AP1_LUMA = vec3(0.2722287168, 0.6740817658, 0.0536895174);\n\nfloat filmSaturation(vec3 c) {\n float hi = max(c.r, max(c.g, c.b));\n float lo = min(c.r, min(c.g, c.b));\n return (max(hi, 1e-10) - max(lo, 1e-10)) / max(hi, 1e-2);\n}\n\nfloat filmYc(vec3 c) {\n float chroma = sqrt(max(c.b * (c.b - c.g) + c.g * (c.g - c.r) + c.r * (c.r - c.b), 0.0));\n return (c.b + c.g + c.r + 1.75 * chroma) / 3.0;\n}\n\nfloat filmSigmoid(float x) {\n float t = max(1.0 - abs(x / 2.0), 0.0);\n return (1.0 + sign(x) * (1.0 - t * t)) / 2.0;\n}\n\nfloat filmGlow(float yc, float gain, float mid) {\n if (yc <= 2.0 / 3.0 * mid) return gain;\n if (yc >= 2.0 * mid) return 0.0;\n return gain * (mid / yc - 0.5);\n}\n\n/* Hue in degrees, -180 to 180 about red; 0 for a grey, which then takes no red modifier. */\nfloat filmCentredHue(vec3 c) {\n float y = sqrt(3.0) * (c.g - c.b);\n float x = 2.0 * c.r - c.g - c.b;\n if (abs(x) < 1e-10 && abs(y) < 1e-10) return 0.0;\n return degrees(atan(y, x));\n}\n\nvec3 filmicCurve(vec3 linear) {\n vec3 ap0 = FILM_AP1_TO_AP0 * (FILM_SRGB_TO_AP1 * linear);\n /* The reference's glow, lifting dark saturated colours a little. */\n float saturation = filmSaturation(ap0);\n ap0 *= 1.0 + filmGlow(filmYc(ap0), 0.05 * filmSigmoid((saturation - 0.4) / 0.2), 0.08);\n /* And its red modifier, pulling saturated reds toward a pivot so they do not run to orange. */\n float weight = smoothstep(0.0, 1.0, 1.0 - abs(2.0 * filmCentredHue(ap0) / 135.0));\n ap0.r += weight * weight * saturation * (0.03 - ap0.r) * (1.0 - 0.82);\n vec3 work = max(FILM_AP0_TO_AP1 * ap0, vec3(0.0));\n work = mix(vec3(dot(work, FILM_AP1_LUMA)), work, 0.96);\n\n float slope = uFilmA.x;\n float black = uFilmA.y;\n float white = uFilmA.z;\n float toeScale = uFilmA.w;\n float shoulderScale = uFilmB.x;\n float toeMatch = uFilmB.y;\n float straightMatch = uFilmB.z;\n float shoulderMatch = uFilmB.w;\n /* log10, through log2: GLSL ES has no log10. */\n vec3 logc = log2(max(work, vec3(1e-10))) * 0.30102999566;\n vec3 straight = slope * (logc + straightMatch);\n vec3 toeC = -black + (2.0 * toeScale) / (1.0 + exp((-2.0 * slope / toeScale) * (logc - toeMatch)));\n vec3 shoulderC =\n (1.0 + white) - (2.0 * shoulderScale) / (1.0 + exp((2.0 * slope / shoulderScale) * (logc - shoulderMatch)));\n toeC = mix(straight, toeC, vec3(lessThan(logc, vec3(toeMatch))));\n shoulderC = mix(straight, shoulderC, vec3(greaterThan(logc, vec3(shoulderMatch))));\n vec3 t = clamp((logc - toeMatch) / (shoulderMatch - toeMatch), 0.0, 1.0);\n if (shoulderMatch < toeMatch) t = 1.0 - t;\n t = (3.0 - 2.0 * t) * t * t;\n vec3 tone = mix(toeC, shoulderC, t);\n tone = mix(vec3(dot(tone, FILM_AP1_LUMA)), tone, 0.93);\n /* White is the ceiling on a standard display; on a high range one the shoulder's asymptote is. */\n float ceiling = uDisplayHeadroom > 1.0 ? 1.0 + white : 1.0;\n return clamp(FILM_AP1_TO_SRGB * max(tone, vec3(0.0)), 0.0, ceiling);\n}\n";
|