@driftengine/ui2d 4.8.1 → 4.8.3
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/README.md +1 -1
- package/dist/shaders/generated/sprite.wgsl.d.ts +1 -1
- package/dist/shaders/generated/sprite.wgsl.js +1 -1
- package/dist/spriteGl.js +4 -2
- package/dist/spriteGpu.d.ts +25 -2
- package/dist/spriteGpu.js +94 -51
- package/dist/spritePass.js +3 -2
- package/dist/uiDraw.js +22 -5
- package/dist/uiFocus.js +20 -5
- package/dist/uiLayer.d.ts +7 -0
- package/dist/uiLayer.js +24 -0
- package/package.json +2 -2
- package/src/shaders/generated/sprite.wgsl.ts +1 -1
- package/src/spriteGl.ts +5 -2
- package/src/spriteGpu.ts +121 -51
- package/src/spritePass.ts +8 -1
- package/src/uiDraw.ts +35 -3
- package/src/uiFocus.ts +21 -5
- package/src/uiLayer.ts +23 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The 2D layer: quads with a texture on them, batched, in the order you drew them.
|
|
4
4
|
|
|
5
|
-
**Cost:
|
|
5
|
+
**Cost: 10.1 KB gzipped on top of core.** Measured by `scripts/size-gate.test.mjs`, which fails if it
|
|
6
6
|
drifts more than 3% — the number is derived from the same floors that gate asserts, so a README
|
|
7
7
|
quoting a stale one is a red suite rather than a thing somebody notices.
|
|
8
8
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SPRITE_FRAG_WGSL = "struct Uniforms {\n uOutputTransform: i32,\n uOutputExposure: f32,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(32) \nvar uSpriteTexture_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uSpriteTexture_s: sampler;\nvar<private> vUv_1: vec2<f32>;\nvar<private> vTint_1: vec4<f32>;\nvar<private> fragColor: vec4<f32>;\n\nfn linearToSrgb_u0028_vf3_u003b(c: ptr<function, vec3<f32>>) -> vec3<f32> {\n var low: vec3<f32>;\n var high: vec3<f32>;\n\n let
|
|
1
|
+
export declare const SPRITE_FRAG_WGSL = "struct Uniforms {\n uOutputTransform: i32,\n uOutputExposure: f32,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(32) \nvar uSpriteTexture_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uSpriteTexture_s: sampler;\nvar<private> vUv_1: vec2<f32>;\nvar<private> vTint_1: vec4<f32>;\nvar<private> fragColor: vec4<f32>;\n\nfn linearToSrgb_u0028_vf3_u003b(c: ptr<function, vec3<f32>>) -> vec3<f32> {\n var low: vec3<f32>;\n var high: vec3<f32>;\n\n let _e61 = (*c);\n low = (_e61 * 12.92f);\n let _e63 = (*c);\n high = ((pow(max(_e63, vec3<f32>(0f, 0f, 0f)), vec3<f32>(0.41666666f, 0.41666666f, 0.41666666f)) * 1.055f) - vec3(0.055f));\n let _e69 = high;\n let _e70 = low;\n let _e71 = (*c);\n return mix(_e69, _e70, step(_e71, vec3<f32>(0.0031308f, 0.0031308f, 0.0031308f)));\n}\n\nfn highlightShoulder_u0028_vf3_u003b(c_1: ptr<function, vec3<f32>>) -> vec3<f32> {\n var m: f32;\n var e: f32;\n\n let _e62 = (*c_1)[0u];\n let _e64 = (*c_1)[1u];\n let _e66 = (*c_1)[2u];\n m = max(_e62, max(_e64, _e66));\n let _e69 = m;\n if (_e69 <= 0.8f) {\n let _e71 = (*c_1);\n return _e71;\n }\n let _e72 = m;\n e = (_e72 - 0.8f);\n let _e74 = (*c_1);\n let _e75 = e;\n let _e77 = e;\n let _e81 = m;\n return (_e74 * ((0.8f + ((0.2f * _e75) / (_e77 + 0.2f))) / _e81));\n}\n\nfn rrtAndOdtFit_u0028_vf3_u003b(v: ptr<function, vec3<f32>>) -> vec3<f32> {\n var a: vec3<f32>;\n var b: vec3<f32>;\n\n let _e61 = (*v);\n let _e62 = (*v);\n a = ((_e61 * (_e62 + vec3(0.0245786f))) - vec3(0.000090537f));\n let _e68 = (*v);\n let _e69 = (*v);\n b = ((_e68 * ((_e69 * 0.983729f) + vec3(0.432951f))) + vec3(0.238081f));\n let _e76 = a;\n let _e77 = b;\n return (_e76 / _e77);\n}\n\nfn acesFilmic_u0028_vf3_u003b(x: ptr<function, vec3<f32>>) -> vec3<f32> {\n var param: vec3<f32>;\n\n let _e61 = unnamed.uOutputExposure;\n let _e62 = (*x);\n (*x) = (_e62 * _e61);\n let _e64 = (*x);\n param = (mat3x3<f32>(vec3<f32>(0.59719f, 0.076f, 0.0284f), vec3<f32>(0.35458f, 0.90834f, 0.13383f), vec3<f32>(0.04823f, 0.01566f, 0.83777f)) * _e64);\n let _e66 = rrtAndOdtFit_u0028_vf3_u003b((¶m));\n return clamp((mat3x3<f32>(vec3<f32>(1.60475f, -0.10208f, -0.00327f), vec3<f32>(-0.53108f, 1.10813f, -0.07276f), vec3<f32>(-0.07367f, -0.00605f, 1.07602f)) * _e66), vec3(0f), vec3(1f));\n}\n\nfn applyOutputTransform_u0028_vf3_u003b(c_2: ptr<function, vec3<f32>>) -> vec3<f32> {\n var param_1: vec3<f32>;\n var param_2: vec3<f32>;\n var param_3: vec3<f32>;\n\n let _e63 = unnamed.uOutputTransform;\n if (_e63 == 0i) {\n let _e65 = (*c_2);\n return _e65;\n }\n let _e67 = unnamed.uOutputTransform;\n if (_e67 == 2i) {\n let _e69 = (*c_2);\n param_1 = _e69;\n let _e70 = acesFilmic_u0028_vf3_u003b((¶m_1));\n (*c_2) = _e70;\n }\n let _e72 = unnamed.uOutputTransform;\n if (_e72 == 3i) {\n let _e74 = (*c_2);\n let _e76 = unnamed.uOutputExposure;\n param_2 = (_e74 * _e76);\n let _e78 = highlightShoulder_u0028_vf3_u003b((¶m_2));\n (*c_2) = _e78;\n }\n let _e79 = (*c_2);\n param_3 = _e79;\n let _e80 = linearToSrgb_u0028_vf3_u003b((¶m_3));\n return _e80;\n}\n\nfn main_1() {\n var texel: vec4<f32>;\n var colour: vec4<f32>;\n var param_4: vec3<f32>;\n\n let _e61 = vUv_1;\n let _e62 = textureSample(uSpriteTexture_t, uSpriteTexture_s, _e61);\n texel = _e62;\n let _e63 = texel;\n let _e64 = vTint_1;\n colour = (_e63 * _e64);\n let _e67 = colour[3u];\n if (_e67 < 0.00392157f) {\n discard;\n }\n let _e69 = colour;\n param_4 = _e69.xyz;\n let _e71 = applyOutputTransform_u0028_vf3_u003b((¶m_4));\n let _e72 = colour;\n colour = vec4<f32>(_e71.x, _e71.y, _e71.z, _e72.w);\n let _e78 = colour;\n let _e81 = colour[3u];\n let _e82 = (_e78.xyz * _e81);\n let _e84 = colour[3u];\n fragColor = vec4<f32>(_e82.x, _e82.y, _e82.z, _e84);\n return;\n}\n\n@fragment \nfn main(@location(0) vUv: vec2<f32>, @location(1) vTint: vec4<f32>) -> @location(0) vec4<f32> {\n vUv_1 = vUv;\n vTint_1 = vTint;\n main_1();\n let _e5 = fragColor;\n return _e5;\n}\n";
|
|
2
2
|
export declare const SPRITE_VERT_WGSL = "struct Uniforms {\n uToNdc0_: vec4<f32>,\n uToNdc1_: vec4<f32>,\n uClipCorrection: mat4x4<f32>,\n}\n\nstruct gl_PerVertex {\n @builtin(position) gl_Position: vec4<f32>,\n gl_PointSize: f32,\n}\n\nstruct VertexOutput {\n @location(0) member: vec2<f32>,\n @location(1) member_1: vec4<f32>,\n @builtin(position) gl_Position: vec4<f32>,\n}\n\nvar<private> gl_VertexIndex_1: i32;\nvar<private> aOrigin_1: vec2<f32>;\nvar<private> aEdges_1: vec4<f32>;\n@group(0) @binding(0) \nvar<uniform> unnamed: Uniforms;\nvar<private> vUv: vec2<f32>;\nvar<private> aUv_1: vec4<f32>;\nvar<private> vTint: vec4<f32>;\nvar<private> aTint_1: vec4<f32>;\nvar<private> unnamed_1: gl_PerVertex = gl_PerVertex(vec4<f32>(0f, 0f, 0f, 1f), 1f);\n\nfn main_1() {\n var corner: vec2<f32>;\n var indexable: array<vec2<f32>, 6>;\n var p: vec2<f32>;\n var ndc: vec2<f32>;\n var orientation: f32;\n var along: vec2<f32>;\n var local: f32;\n\n let _e31 = gl_VertexIndex_1;\n indexable = array<vec2<f32>, 6>(vec2<f32>(0f, 0f), vec2<f32>(1f, 0f), vec2<f32>(0f, 1f), vec2<f32>(0f, 1f), vec2<f32>(1f, 0f), vec2<f32>(1f, 1f));\n let _e33 = indexable[_e31];\n corner = _e33;\n let _e34 = aOrigin_1;\n let _e35 = aEdges_1;\n let _e38 = corner[0u];\n let _e41 = aEdges_1;\n let _e44 = corner[1u];\n p = ((_e34 + (_e35.xy * _e38)) + (_e41.zw * _e44));\n let _e49 = unnamed.uToNdc0_[0u];\n let _e51 = p[0u];\n let _e55 = unnamed.uToNdc0_[2u];\n let _e57 = p[1u];\n let _e62 = unnamed.uToNdc1_[0u];\n let _e66 = unnamed.uToNdc0_[1u];\n let _e68 = p[0u];\n let _e72 = unnamed.uToNdc0_[3u];\n let _e74 = p[1u];\n let _e79 = unnamed.uToNdc1_[1u];\n ndc = vec2<f32>((((_e49 * _e51) + (_e55 * _e57)) + _e62), (((_e66 * _e68) + (_e72 * _e74)) + _e79));\n let _e84 = unnamed.uToNdc0_[0u];\n let _e87 = unnamed.uToNdc0_[3u];\n let _e91 = unnamed.uToNdc0_[1u];\n let _e94 = unnamed.uToNdc0_[2u];\n orientation = ((_e84 * _e87) - (_e91 * _e94));\n let _e98 = corner[0u];\n let _e99 = orientation;\n if (_e99 > 0f) {\n let _e102 = corner[1u];\n local = (1f - _e102);\n } else {\n let _e105 = corner[1u];\n local = _e105;\n }\n let _e106 = local;\n along = vec2<f32>(_e98, _e106);\n let _e108 = aUv_1;\n let _e110 = aUv_1;\n let _e112 = along;\n vUv = mix(_e108.xy, _e110.zw, _e112);\n let _e114 = aTint_1;\n vTint = _e114;\n let _e116 = unnamed.uClipCorrection;\n let _e117 = ndc;\n unnamed_1.gl_Position = (_e116 * vec4<f32>(_e117.x, _e117.y, 0f, 1f));\n return;\n}\n\n@vertex \nfn main(@builtin(vertex_index) gl_VertexIndex: u32, @location(3) aOrigin: vec2<f32>, @location(0) aEdges: vec4<f32>, @location(1) aUv: vec4<f32>, @location(2) aTint: vec4<f32>) -> VertexOutput {\n gl_VertexIndex_1 = i32(gl_VertexIndex);\n aOrigin_1 = aOrigin;\n aEdges_1 = aEdges;\n aUv_1 = aUv;\n aTint_1 = aTint;\n main_1();\n let _e16 = unnamed_1.gl_Position.y;\n unnamed_1.gl_Position.y = -(_e16);\n let _e18 = vUv;\n let _e19 = vTint;\n let _e20 = unnamed_1.gl_Position;\n return VertexOutput(_e18, _e19, _e20);\n}\n";
|
|
3
3
|
/**
|
|
4
4
|
* What the transform assigned, so the renderer binds the same numbers.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The GLSL beside this file is the source of truth. A hand edit here is discarded by
|
|
5
5
|
* the next generation, and `npm run wgsl:check` fails the build when this is stale.
|
|
6
6
|
*/
|
|
7
|
-
export const SPRITE_FRAG_WGSL = "struct Uniforms {\n uOutputTransform: i32,\n uOutputExposure: f32,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(32) \nvar uSpriteTexture_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uSpriteTexture_s: sampler;\nvar<private> vUv_1: vec2<f32>;\nvar<private> vTint_1: vec4<f32>;\nvar<private> fragColor: vec4<f32>;\n\nfn linearToSrgb_u0028_vf3_u003b(c: ptr<function, vec3<f32>>) -> vec3<f32> {\n var low: vec3<f32>;\n var high: vec3<f32>;\n\n let
|
|
7
|
+
export const SPRITE_FRAG_WGSL = "struct Uniforms {\n uOutputTransform: i32,\n uOutputExposure: f32,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(32) \nvar uSpriteTexture_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uSpriteTexture_s: sampler;\nvar<private> vUv_1: vec2<f32>;\nvar<private> vTint_1: vec4<f32>;\nvar<private> fragColor: vec4<f32>;\n\nfn linearToSrgb_u0028_vf3_u003b(c: ptr<function, vec3<f32>>) -> vec3<f32> {\n var low: vec3<f32>;\n var high: vec3<f32>;\n\n let _e61 = (*c);\n low = (_e61 * 12.92f);\n let _e63 = (*c);\n high = ((pow(max(_e63, vec3<f32>(0f, 0f, 0f)), vec3<f32>(0.41666666f, 0.41666666f, 0.41666666f)) * 1.055f) - vec3(0.055f));\n let _e69 = high;\n let _e70 = low;\n let _e71 = (*c);\n return mix(_e69, _e70, step(_e71, vec3<f32>(0.0031308f, 0.0031308f, 0.0031308f)));\n}\n\nfn highlightShoulder_u0028_vf3_u003b(c_1: ptr<function, vec3<f32>>) -> vec3<f32> {\n var m: f32;\n var e: f32;\n\n let _e62 = (*c_1)[0u];\n let _e64 = (*c_1)[1u];\n let _e66 = (*c_1)[2u];\n m = max(_e62, max(_e64, _e66));\n let _e69 = m;\n if (_e69 <= 0.8f) {\n let _e71 = (*c_1);\n return _e71;\n }\n let _e72 = m;\n e = (_e72 - 0.8f);\n let _e74 = (*c_1);\n let _e75 = e;\n let _e77 = e;\n let _e81 = m;\n return (_e74 * ((0.8f + ((0.2f * _e75) / (_e77 + 0.2f))) / _e81));\n}\n\nfn rrtAndOdtFit_u0028_vf3_u003b(v: ptr<function, vec3<f32>>) -> vec3<f32> {\n var a: vec3<f32>;\n var b: vec3<f32>;\n\n let _e61 = (*v);\n let _e62 = (*v);\n a = ((_e61 * (_e62 + vec3(0.0245786f))) - vec3(0.000090537f));\n let _e68 = (*v);\n let _e69 = (*v);\n b = ((_e68 * ((_e69 * 0.983729f) + vec3(0.432951f))) + vec3(0.238081f));\n let _e76 = a;\n let _e77 = b;\n return (_e76 / _e77);\n}\n\nfn acesFilmic_u0028_vf3_u003b(x: ptr<function, vec3<f32>>) -> vec3<f32> {\n var param: vec3<f32>;\n\n let _e61 = unnamed.uOutputExposure;\n let _e62 = (*x);\n (*x) = (_e62 * _e61);\n let _e64 = (*x);\n param = (mat3x3<f32>(vec3<f32>(0.59719f, 0.076f, 0.0284f), vec3<f32>(0.35458f, 0.90834f, 0.13383f), vec3<f32>(0.04823f, 0.01566f, 0.83777f)) * _e64);\n let _e66 = rrtAndOdtFit_u0028_vf3_u003b((¶m));\n return clamp((mat3x3<f32>(vec3<f32>(1.60475f, -0.10208f, -0.00327f), vec3<f32>(-0.53108f, 1.10813f, -0.07276f), vec3<f32>(-0.07367f, -0.00605f, 1.07602f)) * _e66), vec3(0f), vec3(1f));\n}\n\nfn applyOutputTransform_u0028_vf3_u003b(c_2: ptr<function, vec3<f32>>) -> vec3<f32> {\n var param_1: vec3<f32>;\n var param_2: vec3<f32>;\n var param_3: vec3<f32>;\n\n let _e63 = unnamed.uOutputTransform;\n if (_e63 == 0i) {\n let _e65 = (*c_2);\n return _e65;\n }\n let _e67 = unnamed.uOutputTransform;\n if (_e67 == 2i) {\n let _e69 = (*c_2);\n param_1 = _e69;\n let _e70 = acesFilmic_u0028_vf3_u003b((¶m_1));\n (*c_2) = _e70;\n }\n let _e72 = unnamed.uOutputTransform;\n if (_e72 == 3i) {\n let _e74 = (*c_2);\n let _e76 = unnamed.uOutputExposure;\n param_2 = (_e74 * _e76);\n let _e78 = highlightShoulder_u0028_vf3_u003b((¶m_2));\n (*c_2) = _e78;\n }\n let _e79 = (*c_2);\n param_3 = _e79;\n let _e80 = linearToSrgb_u0028_vf3_u003b((¶m_3));\n return _e80;\n}\n\nfn main_1() {\n var texel: vec4<f32>;\n var colour: vec4<f32>;\n var param_4: vec3<f32>;\n\n let _e61 = vUv_1;\n let _e62 = textureSample(uSpriteTexture_t, uSpriteTexture_s, _e61);\n texel = _e62;\n let _e63 = texel;\n let _e64 = vTint_1;\n colour = (_e63 * _e64);\n let _e67 = colour[3u];\n if (_e67 < 0.00392157f) {\n discard;\n }\n let _e69 = colour;\n param_4 = _e69.xyz;\n let _e71 = applyOutputTransform_u0028_vf3_u003b((¶m_4));\n let _e72 = colour;\n colour = vec4<f32>(_e71.x, _e71.y, _e71.z, _e72.w);\n let _e78 = colour;\n let _e81 = colour[3u];\n let _e82 = (_e78.xyz * _e81);\n let _e84 = colour[3u];\n fragColor = vec4<f32>(_e82.x, _e82.y, _e82.z, _e84);\n return;\n}\n\n@fragment \nfn main(@location(0) vUv: vec2<f32>, @location(1) vTint: vec4<f32>) -> @location(0) vec4<f32> {\n vUv_1 = vUv;\n vTint_1 = vTint;\n main_1();\n let _e5 = fragColor;\n return _e5;\n}\n";
|
|
8
8
|
export const SPRITE_VERT_WGSL = "struct Uniforms {\n uToNdc0_: vec4<f32>,\n uToNdc1_: vec4<f32>,\n uClipCorrection: mat4x4<f32>,\n}\n\nstruct gl_PerVertex {\n @builtin(position) gl_Position: vec4<f32>,\n gl_PointSize: f32,\n}\n\nstruct VertexOutput {\n @location(0) member: vec2<f32>,\n @location(1) member_1: vec4<f32>,\n @builtin(position) gl_Position: vec4<f32>,\n}\n\nvar<private> gl_VertexIndex_1: i32;\nvar<private> aOrigin_1: vec2<f32>;\nvar<private> aEdges_1: vec4<f32>;\n@group(0) @binding(0) \nvar<uniform> unnamed: Uniforms;\nvar<private> vUv: vec2<f32>;\nvar<private> aUv_1: vec4<f32>;\nvar<private> vTint: vec4<f32>;\nvar<private> aTint_1: vec4<f32>;\nvar<private> unnamed_1: gl_PerVertex = gl_PerVertex(vec4<f32>(0f, 0f, 0f, 1f), 1f);\n\nfn main_1() {\n var corner: vec2<f32>;\n var indexable: array<vec2<f32>, 6>;\n var p: vec2<f32>;\n var ndc: vec2<f32>;\n var orientation: f32;\n var along: vec2<f32>;\n var local: f32;\n\n let _e31 = gl_VertexIndex_1;\n indexable = array<vec2<f32>, 6>(vec2<f32>(0f, 0f), vec2<f32>(1f, 0f), vec2<f32>(0f, 1f), vec2<f32>(0f, 1f), vec2<f32>(1f, 0f), vec2<f32>(1f, 1f));\n let _e33 = indexable[_e31];\n corner = _e33;\n let _e34 = aOrigin_1;\n let _e35 = aEdges_1;\n let _e38 = corner[0u];\n let _e41 = aEdges_1;\n let _e44 = corner[1u];\n p = ((_e34 + (_e35.xy * _e38)) + (_e41.zw * _e44));\n let _e49 = unnamed.uToNdc0_[0u];\n let _e51 = p[0u];\n let _e55 = unnamed.uToNdc0_[2u];\n let _e57 = p[1u];\n let _e62 = unnamed.uToNdc1_[0u];\n let _e66 = unnamed.uToNdc0_[1u];\n let _e68 = p[0u];\n let _e72 = unnamed.uToNdc0_[3u];\n let _e74 = p[1u];\n let _e79 = unnamed.uToNdc1_[1u];\n ndc = vec2<f32>((((_e49 * _e51) + (_e55 * _e57)) + _e62), (((_e66 * _e68) + (_e72 * _e74)) + _e79));\n let _e84 = unnamed.uToNdc0_[0u];\n let _e87 = unnamed.uToNdc0_[3u];\n let _e91 = unnamed.uToNdc0_[1u];\n let _e94 = unnamed.uToNdc0_[2u];\n orientation = ((_e84 * _e87) - (_e91 * _e94));\n let _e98 = corner[0u];\n let _e99 = orientation;\n if (_e99 > 0f) {\n let _e102 = corner[1u];\n local = (1f - _e102);\n } else {\n let _e105 = corner[1u];\n local = _e105;\n }\n let _e106 = local;\n along = vec2<f32>(_e98, _e106);\n let _e108 = aUv_1;\n let _e110 = aUv_1;\n let _e112 = along;\n vUv = mix(_e108.xy, _e110.zw, _e112);\n let _e114 = aTint_1;\n vTint = _e114;\n let _e116 = unnamed.uClipCorrection;\n let _e117 = ndc;\n unnamed_1.gl_Position = (_e116 * vec4<f32>(_e117.x, _e117.y, 0f, 1f));\n return;\n}\n\n@vertex \nfn main(@builtin(vertex_index) gl_VertexIndex: u32, @location(3) aOrigin: vec2<f32>, @location(0) aEdges: vec4<f32>, @location(1) aUv: vec4<f32>, @location(2) aTint: vec4<f32>) -> VertexOutput {\n gl_VertexIndex_1 = i32(gl_VertexIndex);\n aOrigin_1 = aOrigin;\n aEdges_1 = aEdges;\n aUv_1 = aUv;\n aTint_1 = aTint;\n main_1();\n let _e16 = unnamed_1.gl_Position.y;\n unnamed_1.gl_Position.y = -(_e16);\n let _e18 = vUv;\n let _e19 = vTint;\n let _e20 = unnamed_1.gl_Position;\n return VertexOutput(_e18, _e19, _e20);\n}\n";
|
|
9
9
|
/**
|
|
10
10
|
* What the transform assigned, so the renderer binds the same numbers.
|
package/dist/spriteGl.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** The WebGL2 half of the sprite pass: one program, one instance buffer, one texture slot table. */
|
|
2
|
+
import { blendCovering } from '@driftengine/core';
|
|
2
3
|
import { SPRITE_FRAG, SPRITE_VERT } from './shaders/sprite.js';
|
|
3
4
|
import { SPRITE_FLOATS } from './spriteBatch.js';
|
|
4
5
|
/**
|
|
@@ -191,8 +192,9 @@ export function drawWebgl2Sprites(gl, sprites, batch, toNdc, clipCorrection, out
|
|
|
191
192
|
*/
|
|
192
193
|
gl.disable(gl.CULL_FACE);
|
|
193
194
|
gl.enable(gl.BLEND);
|
|
194
|
-
/* Premultiplied `over`: the fragment stage folds alpha in, so this composes onto opaque pixels
|
|
195
|
-
|
|
195
|
+
/* Premultiplied `over`: the fragment stage folds alpha in, so this composes onto opaque pixels,
|
|
196
|
+
and covers the scene's surface by its alpha so occlusion behind it does not darken it. */
|
|
197
|
+
blendCovering(gl, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
196
198
|
gl.disable(gl.DEPTH_TEST);
|
|
197
199
|
gl.depthMask(false);
|
|
198
200
|
gl.activeTexture(gl.TEXTURE0 + SPRITE_UNIT);
|
package/dist/spriteGpu.d.ts
CHANGED
|
@@ -6,8 +6,20 @@ export interface GpuSpriteSlot {
|
|
|
6
6
|
readonly texture: GPUTexture;
|
|
7
7
|
readonly bindGroup: GPUBindGroup;
|
|
8
8
|
}
|
|
9
|
+
/** Pipelines by colour format, then depth format, then sample count: a lookup that allocates nothing. */
|
|
10
|
+
type PipelinesByTarget = Map<GPUTextureFormat, Map<GPUTextureFormat, Map<number, GPURenderPipeline>>>;
|
|
9
11
|
export interface GpuSprites {
|
|
10
|
-
|
|
12
|
+
/**
|
|
13
|
+
* One pipeline per pass the sprites have been drawn into, chosen at the draw by
|
|
14
|
+
* `spritePipelineFor`. A pipeline must match its pass's attachments exactly, and a 2D layer is
|
|
15
|
+
* drawn in the frame and over it, which are two different passes: the scene's target at the
|
|
16
|
+
* scene's samples, and the canvas at one.
|
|
17
|
+
*/
|
|
18
|
+
readonly pipelines: PipelinesByTarget;
|
|
19
|
+
readonly pipelineLayout: GPUPipelineLayout;
|
|
20
|
+
readonly vertexModule: GPUShaderModule;
|
|
21
|
+
readonly fragmentModule: GPUShaderModule;
|
|
22
|
+
readonly label: string;
|
|
11
23
|
readonly layout: GPUBindGroupLayout;
|
|
12
24
|
readonly vertexUniforms: GPUBuffer;
|
|
13
25
|
readonly fragmentUniforms: GPUBuffer;
|
|
@@ -28,9 +40,20 @@ export interface GpuSprites {
|
|
|
28
40
|
readonly fragmentInts: Int32Array;
|
|
29
41
|
}
|
|
30
42
|
export declare function createGpuSprites(device: GPUDevice, format: GPUTextureFormat, depthFormat: GPUTextureFormat, samples: number, capacity: number, slots: number, label: string): GpuSprites;
|
|
43
|
+
/**
|
|
44
|
+
* The pipeline for a pass with these attachments, built the first time it is asked for.
|
|
45
|
+
*
|
|
46
|
+
* Chosen at the draw from what the pass is (`PassContext.format`, `depthFormat`, `samples`), so a
|
|
47
|
+
* 2D layer drawn over the finished frame, on the canvas at one sample, is not refused for having
|
|
48
|
+
* been built for the scene target. A lookup through three maps on every later draw, no allocation.
|
|
49
|
+
*/
|
|
50
|
+
export declare function spritePipelineFor(device: GPUDevice, sprites: GpuSprites, format: GPUTextureFormat, depthFormat: GPUTextureFormat, samples: number): GPURenderPipeline;
|
|
31
51
|
export declare function setGpuSpriteTexture(device: GPUDevice, sprites: GpuSprites, slot: number, source: SpriteImage, options: SpriteTextureOptions): void;
|
|
32
52
|
/** The same one white texel, written straight into a texture. See the WebGL2 half for why. */
|
|
33
53
|
export declare function setGpuWhiteTexture(device: GPUDevice, sprites: GpuSprites, slot: number): void;
|
|
34
54
|
export declare function uploadGpuInstances(device: GPUDevice, sprites: GpuSprites, batch: SpriteBatch): void;
|
|
35
|
-
export declare function drawGpuSprites(pass: GPURenderPassEncoder, sprites: GpuSprites, batch: SpriteBatch
|
|
55
|
+
export declare function drawGpuSprites(pass: GPURenderPassEncoder, sprites: GpuSprites, batch: SpriteBatch,
|
|
56
|
+
/** The pipeline for this pass's attachments: `spritePipelineFor`. */
|
|
57
|
+
pipeline: GPURenderPipeline): void;
|
|
36
58
|
export declare function disposeGpuSprites(sprites: GpuSprites): void;
|
|
59
|
+
export {};
|
package/dist/spriteGpu.js
CHANGED
|
@@ -10,7 +10,7 @@ import { SPRITE_FLOATS } from './spriteBatch.js';
|
|
|
10
10
|
* and nothing else. The alternative was a second copy of the mip blit living here, and the second
|
|
11
11
|
* copy is the one that ends up disagreeing about the colour space.
|
|
12
12
|
*/
|
|
13
|
-
import { generateMipChain, mipLevelCount } from '@driftengine/core';
|
|
13
|
+
import { SCENE_ALPHA_COVERS, generateMipChain, mipLevelCount, } from '@driftengine/core';
|
|
14
14
|
const VERT = SPRITE_BINDINGS.SPRITE_VERT;
|
|
15
15
|
const FRAG = SPRITE_BINDINGS.SPRITE_FRAG;
|
|
16
16
|
const STRIDE = SPRITE_FLOATS * 4;
|
|
@@ -37,52 +37,14 @@ export function createGpuSprites(device, format, depthFormat, samples, capacity,
|
|
|
37
37
|
},
|
|
38
38
|
],
|
|
39
39
|
});
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
arrayStride: STRIDE,
|
|
49
|
-
stepMode: 'instance',
|
|
50
|
-
attributes: [
|
|
51
|
-
{ shaderLocation: 0, offset: 0, format: 'float32x4' },
|
|
52
|
-
{ shaderLocation: 1, offset: 16, format: 'float32x4' },
|
|
53
|
-
{ shaderLocation: 2, offset: 32, format: 'float32x4' },
|
|
54
|
-
{ shaderLocation: 3, offset: 48, format: 'float32x2' },
|
|
55
|
-
],
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
fragment: {
|
|
60
|
-
module: device.createShaderModule({ label: `${label}.frag`, code: SPRITE_FRAG_WGSL }),
|
|
61
|
-
entryPoint: 'main',
|
|
62
|
-
targets: [
|
|
63
|
-
{
|
|
64
|
-
format,
|
|
65
|
-
/* Premultiplied `over`, matching the WebGL2 half exactly. */
|
|
66
|
-
blend: {
|
|
67
|
-
color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
68
|
-
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
},
|
|
73
|
-
/*
|
|
74
|
-
* Nothing is culled: a sprite is mirrored by giving it a negative width, which reverses its
|
|
75
|
-
* winding, and a character facing left is exactly that.
|
|
76
|
-
*/
|
|
77
|
-
primitive: { topology: 'triangle-list', cullMode: 'none' },
|
|
78
|
-
multisample: { count: samples },
|
|
79
|
-
/*
|
|
80
|
-
* Neither tested nor written. The 2D layer's order *is* its layering — there is no z to sort
|
|
81
|
-
* by — and a pass that depth-tested would let whatever 3D geometry is in the frame punch holes
|
|
82
|
-
* in an overlay drawn over it. Declared all the same, because a pipeline in a render pass that
|
|
83
|
-
* has a depth attachment must name its format.
|
|
84
|
-
*/
|
|
85
|
-
depthStencil: { format: depthFormat, depthWriteEnabled: false, depthCompare: 'always' },
|
|
40
|
+
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [layout] });
|
|
41
|
+
const vertexModule = device.createShaderModule({
|
|
42
|
+
label: `${label}.vert`,
|
|
43
|
+
code: SPRITE_VERT_WGSL,
|
|
44
|
+
});
|
|
45
|
+
const fragmentModule = device.createShaderModule({
|
|
46
|
+
label: `${label}.frag`,
|
|
47
|
+
code: SPRITE_FRAG_WGSL,
|
|
86
48
|
});
|
|
87
49
|
const vertexUniforms = device.createBuffer({
|
|
88
50
|
label: `${label}.vertexUniforms`,
|
|
@@ -101,8 +63,12 @@ export function createGpuSprites(device, format, depthFormat, samples, capacity,
|
|
|
101
63
|
});
|
|
102
64
|
const vertexScratch = new ArrayBuffer(VERT.uniformSize);
|
|
103
65
|
const fragmentScratch = new ArrayBuffer(FRAG.uniformSize);
|
|
104
|
-
|
|
105
|
-
|
|
66
|
+
const sprites = {
|
|
67
|
+
pipelines: new Map(),
|
|
68
|
+
pipelineLayout,
|
|
69
|
+
vertexModule,
|
|
70
|
+
fragmentModule,
|
|
71
|
+
label,
|
|
106
72
|
layout,
|
|
107
73
|
vertexUniforms,
|
|
108
74
|
fragmentUniforms,
|
|
@@ -138,6 +104,81 @@ export function createGpuSprites(device, format, depthFormat, samples, capacity,
|
|
|
138
104
|
fragmentFloats: new Float32Array(fragmentScratch),
|
|
139
105
|
fragmentInts: new Int32Array(fragmentScratch),
|
|
140
106
|
};
|
|
107
|
+
/* The frame's own target, built now, so the common case compiles before its first draw. */
|
|
108
|
+
spritePipelineFor(device, sprites, format, depthFormat, samples);
|
|
109
|
+
return sprites;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The pipeline for a pass with these attachments, built the first time it is asked for.
|
|
113
|
+
*
|
|
114
|
+
* Chosen at the draw from what the pass is (`PassContext.format`, `depthFormat`, `samples`), so a
|
|
115
|
+
* 2D layer drawn over the finished frame, on the canvas at one sample, is not refused for having
|
|
116
|
+
* been built for the scene target. A lookup through three maps on every later draw, no allocation.
|
|
117
|
+
*/
|
|
118
|
+
export function spritePipelineFor(device, sprites, format, depthFormat, samples) {
|
|
119
|
+
let byDepth = sprites.pipelines.get(format);
|
|
120
|
+
if (byDepth === undefined) {
|
|
121
|
+
byDepth = new Map();
|
|
122
|
+
sprites.pipelines.set(format, byDepth);
|
|
123
|
+
}
|
|
124
|
+
let bySamples = byDepth.get(depthFormat);
|
|
125
|
+
if (bySamples === undefined) {
|
|
126
|
+
bySamples = new Map();
|
|
127
|
+
byDepth.set(depthFormat, bySamples);
|
|
128
|
+
}
|
|
129
|
+
const found = bySamples.get(samples);
|
|
130
|
+
if (found !== undefined)
|
|
131
|
+
return found;
|
|
132
|
+
const pipeline = device.createRenderPipeline({
|
|
133
|
+
label: `${sprites.label}:${format}x${samples}`,
|
|
134
|
+
layout: sprites.pipelineLayout,
|
|
135
|
+
vertex: {
|
|
136
|
+
module: sprites.vertexModule,
|
|
137
|
+
entryPoint: 'main',
|
|
138
|
+
buffers: [
|
|
139
|
+
{
|
|
140
|
+
arrayStride: STRIDE,
|
|
141
|
+
stepMode: 'instance',
|
|
142
|
+
attributes: [
|
|
143
|
+
{ shaderLocation: 0, offset: 0, format: 'float32x4' },
|
|
144
|
+
{ shaderLocation: 1, offset: 16, format: 'float32x4' },
|
|
145
|
+
{ shaderLocation: 2, offset: 32, format: 'float32x4' },
|
|
146
|
+
{ shaderLocation: 3, offset: 48, format: 'float32x2' },
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
fragment: {
|
|
152
|
+
module: sprites.fragmentModule,
|
|
153
|
+
entryPoint: 'main',
|
|
154
|
+
targets: [
|
|
155
|
+
{
|
|
156
|
+
format,
|
|
157
|
+
/* Premultiplied `over`, matching the WebGL2 half exactly; a sprite covers the scene's
|
|
158
|
+
surface by its alpha, so occlusion behind it does not darken it. */
|
|
159
|
+
blend: {
|
|
160
|
+
color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
161
|
+
alpha: SCENE_ALPHA_COVERS,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
/*
|
|
167
|
+
* Nothing is culled: a sprite is mirrored by giving it a negative width, which reverses its
|
|
168
|
+
* winding, and a character facing left is exactly that.
|
|
169
|
+
*/
|
|
170
|
+
primitive: { topology: 'triangle-list', cullMode: 'none' },
|
|
171
|
+
multisample: { count: samples },
|
|
172
|
+
/*
|
|
173
|
+
* Neither tested nor written. The 2D layer's order *is* its layering — there is no z to sort
|
|
174
|
+
* by — and a pass that depth-tested would let whatever 3D geometry is in the frame punch holes
|
|
175
|
+
* in an overlay drawn over it. Declared all the same, because a pipeline in a render pass that
|
|
176
|
+
* has a depth attachment must name its format.
|
|
177
|
+
*/
|
|
178
|
+
depthStencil: { format: depthFormat, depthWriteEnabled: false, depthCompare: 'always' },
|
|
179
|
+
});
|
|
180
|
+
bySamples.set(samples, pipeline);
|
|
181
|
+
return pipeline;
|
|
141
182
|
}
|
|
142
183
|
/**
|
|
143
184
|
* One pipeline per format, kept on the sprite pass rather than in the renderer's cache.
|
|
@@ -231,10 +272,12 @@ export function uploadGpuInstances(device, sprites, batch) {
|
|
|
231
272
|
return;
|
|
232
273
|
device.queue.writeBuffer(sprites.instances, 0, batch.instances.buffer, batch.instances.byteOffset, batch.count * STRIDE);
|
|
233
274
|
}
|
|
234
|
-
export function drawGpuSprites(pass, sprites, batch
|
|
275
|
+
export function drawGpuSprites(pass, sprites, batch,
|
|
276
|
+
/** The pipeline for this pass's attachments: `spritePipelineFor`. */
|
|
277
|
+
pipeline) {
|
|
235
278
|
if (batch.count === 0)
|
|
236
279
|
return;
|
|
237
|
-
pass.setPipeline(
|
|
280
|
+
pass.setPipeline(pipeline);
|
|
238
281
|
for (let run = 0; run < batch.runCount; run += 1) {
|
|
239
282
|
const at = run * 3;
|
|
240
283
|
const slot = sprites.slots[batch.runs[at]];
|
package/dist/spritePass.js
CHANGED
|
@@ -3,7 +3,7 @@ import { SPRITE_BINDINGS } from './shaders/generated/sprite.wgsl.js';
|
|
|
3
3
|
import { createAffine2D } from './camera2d.js';
|
|
4
4
|
import { createSpriteBatch, resetSpriteBatch } from './spriteBatch.js';
|
|
5
5
|
import { createWebgl2Sprites, disposeWebgl2Sprites, drawWebgl2Sprites, setWebgl2SpriteTexture, setWebgl2WhiteTexture, uploadWebgl2Instances, } from './spriteGl.js';
|
|
6
|
-
import { createGpuSprites, disposeGpuSprites, drawGpuSprites, setGpuSpriteTexture, setGpuWhiteTexture, uploadGpuInstances, } from './spriteGpu.js';
|
|
6
|
+
import { createGpuSprites, disposeGpuSprites, drawGpuSprites, spritePipelineFor, setGpuSpriteTexture, setGpuWhiteTexture, uploadGpuInstances, } from './spriteGpu.js';
|
|
7
7
|
import { DEFAULT_SPRITE_TEXTURE_OPTIONS } from './spriteTexture.js';
|
|
8
8
|
const VERT = SPRITE_BINDINGS.SPRITE_VERT;
|
|
9
9
|
const FRAG = SPRITE_BINDINGS.SPRITE_FRAG;
|
|
@@ -126,7 +126,8 @@ export function createSpritePass(options = {}) {
|
|
|
126
126
|
device.queue.writeBuffer(gpu.vertexUniforms, 0, gpu.vertexScratch);
|
|
127
127
|
device.queue.writeBuffer(gpu.fragmentUniforms, 0, gpu.fragmentScratch);
|
|
128
128
|
uploadGpuInstances(device, gpu, batch);
|
|
129
|
-
|
|
129
|
+
/* For the pass this lands in, which after `endFrame` is the canvas: see `spritePipelineFor`. */
|
|
130
|
+
drawGpuSprites(ctx.pass, gpu, batch, spritePipelineFor(device, gpu, ctx.format, ctx.depthFormat, ctx.samples));
|
|
130
131
|
},
|
|
131
132
|
dispose() {
|
|
132
133
|
if (gl !== null && context !== null)
|
package/dist/uiDraw.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Turn a laid-out interface tree into quads, parents first so children land on top. */
|
|
2
2
|
import { drawSprite } from './spriteBatch.js';
|
|
3
3
|
import { createSpriteFrame } from './spriteSheet.js';
|
|
4
|
+
import { layersPresent } from './uiLayer.js';
|
|
4
5
|
/* One placement, refilled per node. This is a per-frame path and these are the only objects in it. */
|
|
5
6
|
const PLACEMENT = {
|
|
6
7
|
x: 0,
|
|
@@ -30,11 +31,26 @@ const CUT = createSpriteFrame();
|
|
|
30
31
|
* left at.
|
|
31
32
|
*/
|
|
32
33
|
export function drawUiTree(batch, root, white, sink) {
|
|
33
|
-
|
|
34
|
+
/*
|
|
35
|
+
* **Layer by layer, lowest first, and tree order within one.** A raised node — a dropdown that
|
|
36
|
+
* must cover the panel its control sits in — was drawn in tree order with everything else, so a
|
|
37
|
+
* later sibling covered it. Each layer is a walk of the whole tree with the clips it would have
|
|
38
|
+
* had anyway, so a raised node is still cut by a clipping ancestor; a tree with no raised node is
|
|
39
|
+
* one walk, as before.
|
|
40
|
+
*/
|
|
41
|
+
const count = layersPresent(root, LAYERS);
|
|
42
|
+
let drawn = 0;
|
|
43
|
+
for (let at = 0; at < count; at += 1) {
|
|
44
|
+
drawn += drawWithin(batch, root, white, sink, -Infinity, -Infinity, Infinity, Infinity, 0, LAYERS[at] ?? 0);
|
|
45
|
+
}
|
|
46
|
+
return drawn;
|
|
34
47
|
}
|
|
35
|
-
|
|
48
|
+
/** The layers of the tree being drawn, reused: this runs per frame. */
|
|
49
|
+
const LAYERS = [];
|
|
50
|
+
function drawWithin(batch, node, white, sink, left, top, right, bottom, inherited, only) {
|
|
36
51
|
if (node.hidden)
|
|
37
52
|
return 0;
|
|
53
|
+
const layer = node.layer === 0 ? inherited : node.layer;
|
|
38
54
|
const rect = node.rect;
|
|
39
55
|
const clipped = left !== -Infinity;
|
|
40
56
|
const x0 = Math.max(rect.x, left);
|
|
@@ -47,7 +63,7 @@ function drawWithin(batch, node, white, sink, left, top, right, bottom) {
|
|
|
47
63
|
* a node is drawn as it always was, whatever its size. Its children are cut one by one either way,
|
|
48
64
|
* since a child can lie outside its parent's box and still inside the clip.
|
|
49
65
|
*/
|
|
50
|
-
if (!clipped || (x1 > x0 && y1 > y0)) {
|
|
66
|
+
if (layer === only && (!clipped || (x1 > x0 && y1 > y0))) {
|
|
51
67
|
PLACEMENT.x = clipped ? x0 : rect.x;
|
|
52
68
|
PLACEMENT.y = clipped ? y0 : rect.y;
|
|
53
69
|
PLACEMENT.w = clipped ? x1 - x0 : rect.w;
|
|
@@ -75,8 +91,9 @@ function drawWithin(batch, node, white, sink, left, top, right, bottom) {
|
|
|
75
91
|
const ct = keep ? Math.max(top, rect.y) : top;
|
|
76
92
|
const cr = keep ? Math.min(right, rect.x + rect.w) : right;
|
|
77
93
|
const cb = keep ? Math.min(bottom, rect.y + rect.h) : bottom;
|
|
78
|
-
for (const child of node.children)
|
|
79
|
-
drawn += drawWithin(batch, child, white, sink, cl, ct, cr, cb);
|
|
94
|
+
for (const child of node.children) {
|
|
95
|
+
drawn += drawWithin(batch, child, white, sink, cl, ct, cr, cb, layer, only);
|
|
96
|
+
}
|
|
80
97
|
return drawn;
|
|
81
98
|
}
|
|
82
99
|
/** The part of a node's frame its visible box shows, or the whole frame when nothing was cut. */
|
package/dist/uiFocus.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Which node a point is on, and which node a keyboard should be talking to. */
|
|
2
2
|
import { uiRectHolds } from './uiNode.js';
|
|
3
|
+
import { layersPresent } from './uiLayer.js';
|
|
3
4
|
/**
|
|
4
5
|
* The interactive node under a point, or `null`.
|
|
5
6
|
*
|
|
@@ -14,18 +15,32 @@ import { uiRectHolds } from './uiNode.js';
|
|
|
14
15
|
* Allocates nothing.
|
|
15
16
|
*/
|
|
16
17
|
export function uiHitTest(root, x, y) {
|
|
17
|
-
|
|
18
|
+
/* The highest layer first, as it is drawn last: a raised popup takes the pointer over a later
|
|
19
|
+
sibling it covers. See `drawUiTree`. */
|
|
20
|
+
const count = layersPresent(root, HIT_LAYERS);
|
|
21
|
+
for (let at = count - 1; at >= 0; at -= 1) {
|
|
22
|
+
const hit = hitOn(root, x, y, 0, HIT_LAYERS[at] ?? 0);
|
|
23
|
+
if (hit !== null)
|
|
24
|
+
return hit;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/** The layers of the tree being pointed at, reused: a hit test runs per pointer event. */
|
|
29
|
+
const HIT_LAYERS = [];
|
|
30
|
+
function hitOn(node, x, y, inherited, only) {
|
|
31
|
+
if (node.hidden)
|
|
18
32
|
return null;
|
|
19
33
|
/* A clipping node bounds its descendants here as it does in the picture: a row scrolled out of a
|
|
20
34
|
list is not under the pointer, whatever is drawn where it would have been. */
|
|
21
|
-
if (
|
|
35
|
+
if (node.clip && !uiRectHolds(node, x, y))
|
|
22
36
|
return null;
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
const layer = node.layer === 0 ? inherited : node.layer;
|
|
38
|
+
for (let i = node.children.length - 1; i >= 0; i -= 1) {
|
|
39
|
+
const hit = hitOn(node.children[i], x, y, layer, only);
|
|
25
40
|
if (hit !== null)
|
|
26
41
|
return hit;
|
|
27
42
|
}
|
|
28
|
-
return
|
|
43
|
+
return layer === only && node.interactive && uiRectHolds(node, x, y) ? node : null;
|
|
29
44
|
}
|
|
30
45
|
/**
|
|
31
46
|
* Every focusable node, in tree order, appended to `out`.
|
package/dist/uiLayer.d.ts
CHANGED
|
@@ -16,3 +16,10 @@
|
|
|
16
16
|
import type { UiNode } from './uiNode.ts';
|
|
17
17
|
/** Fill `out` with the tree in draw order and return how many entries were written. */
|
|
18
18
|
export declare function layerOrder(root: UiNode, out: UiNode[]): number;
|
|
19
|
+
/**
|
|
20
|
+
* The layers a tree draws on, ascending, into `out`; how many there are.
|
|
21
|
+
*
|
|
22
|
+
* What `drawUiTree` and `uiHitTest` walk a tree once per: a tree with no raised node answers one
|
|
23
|
+
* layer and is walked once, exactly as it was before either read a layer.
|
|
24
|
+
*/
|
|
25
|
+
export declare function layersPresent(root: UiNode, out: number[]): number;
|
package/dist/uiLayer.js
CHANGED
|
@@ -28,3 +28,27 @@ export function layerOrder(root, out) {
|
|
|
28
28
|
out.length = count;
|
|
29
29
|
return count;
|
|
30
30
|
}
|
|
31
|
+
/** Ascending, without allocating a comparator per call. */
|
|
32
|
+
const ASCENDING = (a, b) => a - b;
|
|
33
|
+
/**
|
|
34
|
+
* The layers a tree draws on, ascending, into `out`; how many there are.
|
|
35
|
+
*
|
|
36
|
+
* What `drawUiTree` and `uiHitTest` walk a tree once per: a tree with no raised node answers one
|
|
37
|
+
* layer and is walked once, exactly as it was before either read a layer.
|
|
38
|
+
*/
|
|
39
|
+
export function layersPresent(root, out) {
|
|
40
|
+
out.length = 0;
|
|
41
|
+
collect(root, 0, out);
|
|
42
|
+
if (out.length > 1)
|
|
43
|
+
out.sort(ASCENDING);
|
|
44
|
+
return out.length;
|
|
45
|
+
}
|
|
46
|
+
function collect(node, inherited, out) {
|
|
47
|
+
if (node.hidden)
|
|
48
|
+
return;
|
|
49
|
+
const layer = node.layer === 0 ? inherited : node.layer;
|
|
50
|
+
if (!out.includes(layer))
|
|
51
|
+
out.push(layer);
|
|
52
|
+
for (const child of node.children)
|
|
53
|
+
collect(child, layer, out);
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@driftengine/ui2d",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.3",
|
|
4
4
|
"description": "The 2D layer: batched sprites, sheets, tilemaps, and a retained interface tree over them",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
],
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@driftengine/core": "4.8.
|
|
30
|
+
"@driftengine/core": "4.8.3"
|
|
31
31
|
},
|
|
32
32
|
"author": "Drift Technologies",
|
|
33
33
|
"repository": {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* the next generation, and `npm run wgsl:check` fails the build when this is stale.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export const SPRITE_FRAG_WGSL = "struct Uniforms {\n uOutputTransform: i32,\n uOutputExposure: f32,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(32) \nvar uSpriteTexture_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uSpriteTexture_s: sampler;\nvar<private> vUv_1: vec2<f32>;\nvar<private> vTint_1: vec4<f32>;\nvar<private> fragColor: vec4<f32>;\n\nfn linearToSrgb_u0028_vf3_u003b(c: ptr<function, vec3<f32>>) -> vec3<f32> {\n var low: vec3<f32>;\n var high: vec3<f32>;\n\n let
|
|
8
|
+
export const SPRITE_FRAG_WGSL = "struct Uniforms {\n uOutputTransform: i32,\n uOutputExposure: f32,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(32) \nvar uSpriteTexture_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uSpriteTexture_s: sampler;\nvar<private> vUv_1: vec2<f32>;\nvar<private> vTint_1: vec4<f32>;\nvar<private> fragColor: vec4<f32>;\n\nfn linearToSrgb_u0028_vf3_u003b(c: ptr<function, vec3<f32>>) -> vec3<f32> {\n var low: vec3<f32>;\n var high: vec3<f32>;\n\n let _e61 = (*c);\n low = (_e61 * 12.92f);\n let _e63 = (*c);\n high = ((pow(max(_e63, vec3<f32>(0f, 0f, 0f)), vec3<f32>(0.41666666f, 0.41666666f, 0.41666666f)) * 1.055f) - vec3(0.055f));\n let _e69 = high;\n let _e70 = low;\n let _e71 = (*c);\n return mix(_e69, _e70, step(_e71, vec3<f32>(0.0031308f, 0.0031308f, 0.0031308f)));\n}\n\nfn highlightShoulder_u0028_vf3_u003b(c_1: ptr<function, vec3<f32>>) -> vec3<f32> {\n var m: f32;\n var e: f32;\n\n let _e62 = (*c_1)[0u];\n let _e64 = (*c_1)[1u];\n let _e66 = (*c_1)[2u];\n m = max(_e62, max(_e64, _e66));\n let _e69 = m;\n if (_e69 <= 0.8f) {\n let _e71 = (*c_1);\n return _e71;\n }\n let _e72 = m;\n e = (_e72 - 0.8f);\n let _e74 = (*c_1);\n let _e75 = e;\n let _e77 = e;\n let _e81 = m;\n return (_e74 * ((0.8f + ((0.2f * _e75) / (_e77 + 0.2f))) / _e81));\n}\n\nfn rrtAndOdtFit_u0028_vf3_u003b(v: ptr<function, vec3<f32>>) -> vec3<f32> {\n var a: vec3<f32>;\n var b: vec3<f32>;\n\n let _e61 = (*v);\n let _e62 = (*v);\n a = ((_e61 * (_e62 + vec3(0.0245786f))) - vec3(0.000090537f));\n let _e68 = (*v);\n let _e69 = (*v);\n b = ((_e68 * ((_e69 * 0.983729f) + vec3(0.432951f))) + vec3(0.238081f));\n let _e76 = a;\n let _e77 = b;\n return (_e76 / _e77);\n}\n\nfn acesFilmic_u0028_vf3_u003b(x: ptr<function, vec3<f32>>) -> vec3<f32> {\n var param: vec3<f32>;\n\n let _e61 = unnamed.uOutputExposure;\n let _e62 = (*x);\n (*x) = (_e62 * _e61);\n let _e64 = (*x);\n param = (mat3x3<f32>(vec3<f32>(0.59719f, 0.076f, 0.0284f), vec3<f32>(0.35458f, 0.90834f, 0.13383f), vec3<f32>(0.04823f, 0.01566f, 0.83777f)) * _e64);\n let _e66 = rrtAndOdtFit_u0028_vf3_u003b((¶m));\n return clamp((mat3x3<f32>(vec3<f32>(1.60475f, -0.10208f, -0.00327f), vec3<f32>(-0.53108f, 1.10813f, -0.07276f), vec3<f32>(-0.07367f, -0.00605f, 1.07602f)) * _e66), vec3(0f), vec3(1f));\n}\n\nfn applyOutputTransform_u0028_vf3_u003b(c_2: ptr<function, vec3<f32>>) -> vec3<f32> {\n var param_1: vec3<f32>;\n var param_2: vec3<f32>;\n var param_3: vec3<f32>;\n\n let _e63 = unnamed.uOutputTransform;\n if (_e63 == 0i) {\n let _e65 = (*c_2);\n return _e65;\n }\n let _e67 = unnamed.uOutputTransform;\n if (_e67 == 2i) {\n let _e69 = (*c_2);\n param_1 = _e69;\n let _e70 = acesFilmic_u0028_vf3_u003b((¶m_1));\n (*c_2) = _e70;\n }\n let _e72 = unnamed.uOutputTransform;\n if (_e72 == 3i) {\n let _e74 = (*c_2);\n let _e76 = unnamed.uOutputExposure;\n param_2 = (_e74 * _e76);\n let _e78 = highlightShoulder_u0028_vf3_u003b((¶m_2));\n (*c_2) = _e78;\n }\n let _e79 = (*c_2);\n param_3 = _e79;\n let _e80 = linearToSrgb_u0028_vf3_u003b((¶m_3));\n return _e80;\n}\n\nfn main_1() {\n var texel: vec4<f32>;\n var colour: vec4<f32>;\n var param_4: vec3<f32>;\n\n let _e61 = vUv_1;\n let _e62 = textureSample(uSpriteTexture_t, uSpriteTexture_s, _e61);\n texel = _e62;\n let _e63 = texel;\n let _e64 = vTint_1;\n colour = (_e63 * _e64);\n let _e67 = colour[3u];\n if (_e67 < 0.00392157f) {\n discard;\n }\n let _e69 = colour;\n param_4 = _e69.xyz;\n let _e71 = applyOutputTransform_u0028_vf3_u003b((¶m_4));\n let _e72 = colour;\n colour = vec4<f32>(_e71.x, _e71.y, _e71.z, _e72.w);\n let _e78 = colour;\n let _e81 = colour[3u];\n let _e82 = (_e78.xyz * _e81);\n let _e84 = colour[3u];\n fragColor = vec4<f32>(_e82.x, _e82.y, _e82.z, _e84);\n return;\n}\n\n@fragment \nfn main(@location(0) vUv: vec2<f32>, @location(1) vTint: vec4<f32>) -> @location(0) vec4<f32> {\n vUv_1 = vUv;\n vTint_1 = vTint;\n main_1();\n let _e5 = fragColor;\n return _e5;\n}\n";
|
|
9
9
|
|
|
10
10
|
export const SPRITE_VERT_WGSL = "struct Uniforms {\n uToNdc0_: vec4<f32>,\n uToNdc1_: vec4<f32>,\n uClipCorrection: mat4x4<f32>,\n}\n\nstruct gl_PerVertex {\n @builtin(position) gl_Position: vec4<f32>,\n gl_PointSize: f32,\n}\n\nstruct VertexOutput {\n @location(0) member: vec2<f32>,\n @location(1) member_1: vec4<f32>,\n @builtin(position) gl_Position: vec4<f32>,\n}\n\nvar<private> gl_VertexIndex_1: i32;\nvar<private> aOrigin_1: vec2<f32>;\nvar<private> aEdges_1: vec4<f32>;\n@group(0) @binding(0) \nvar<uniform> unnamed: Uniforms;\nvar<private> vUv: vec2<f32>;\nvar<private> aUv_1: vec4<f32>;\nvar<private> vTint: vec4<f32>;\nvar<private> aTint_1: vec4<f32>;\nvar<private> unnamed_1: gl_PerVertex = gl_PerVertex(vec4<f32>(0f, 0f, 0f, 1f), 1f);\n\nfn main_1() {\n var corner: vec2<f32>;\n var indexable: array<vec2<f32>, 6>;\n var p: vec2<f32>;\n var ndc: vec2<f32>;\n var orientation: f32;\n var along: vec2<f32>;\n var local: f32;\n\n let _e31 = gl_VertexIndex_1;\n indexable = array<vec2<f32>, 6>(vec2<f32>(0f, 0f), vec2<f32>(1f, 0f), vec2<f32>(0f, 1f), vec2<f32>(0f, 1f), vec2<f32>(1f, 0f), vec2<f32>(1f, 1f));\n let _e33 = indexable[_e31];\n corner = _e33;\n let _e34 = aOrigin_1;\n let _e35 = aEdges_1;\n let _e38 = corner[0u];\n let _e41 = aEdges_1;\n let _e44 = corner[1u];\n p = ((_e34 + (_e35.xy * _e38)) + (_e41.zw * _e44));\n let _e49 = unnamed.uToNdc0_[0u];\n let _e51 = p[0u];\n let _e55 = unnamed.uToNdc0_[2u];\n let _e57 = p[1u];\n let _e62 = unnamed.uToNdc1_[0u];\n let _e66 = unnamed.uToNdc0_[1u];\n let _e68 = p[0u];\n let _e72 = unnamed.uToNdc0_[3u];\n let _e74 = p[1u];\n let _e79 = unnamed.uToNdc1_[1u];\n ndc = vec2<f32>((((_e49 * _e51) + (_e55 * _e57)) + _e62), (((_e66 * _e68) + (_e72 * _e74)) + _e79));\n let _e84 = unnamed.uToNdc0_[0u];\n let _e87 = unnamed.uToNdc0_[3u];\n let _e91 = unnamed.uToNdc0_[1u];\n let _e94 = unnamed.uToNdc0_[2u];\n orientation = ((_e84 * _e87) - (_e91 * _e94));\n let _e98 = corner[0u];\n let _e99 = orientation;\n if (_e99 > 0f) {\n let _e102 = corner[1u];\n local = (1f - _e102);\n } else {\n let _e105 = corner[1u];\n local = _e105;\n }\n let _e106 = local;\n along = vec2<f32>(_e98, _e106);\n let _e108 = aUv_1;\n let _e110 = aUv_1;\n let _e112 = along;\n vUv = mix(_e108.xy, _e110.zw, _e112);\n let _e114 = aTint_1;\n vTint = _e114;\n let _e116 = unnamed.uClipCorrection;\n let _e117 = ndc;\n unnamed_1.gl_Position = (_e116 * vec4<f32>(_e117.x, _e117.y, 0f, 1f));\n return;\n}\n\n@vertex \nfn main(@builtin(vertex_index) gl_VertexIndex: u32, @location(3) aOrigin: vec2<f32>, @location(0) aEdges: vec4<f32>, @location(1) aUv: vec4<f32>, @location(2) aTint: vec4<f32>) -> VertexOutput {\n gl_VertexIndex_1 = i32(gl_VertexIndex);\n aOrigin_1 = aOrigin;\n aEdges_1 = aEdges;\n aUv_1 = aUv;\n aTint_1 = aTint;\n main_1();\n let _e16 = unnamed_1.gl_Position.y;\n unnamed_1.gl_Position.y = -(_e16);\n let _e18 = vUv;\n let _e19 = vTint;\n let _e20 = unnamed_1.gl_Position;\n return VertexOutput(_e18, _e19, _e20);\n}\n";
|
|
11
11
|
|
package/src/spriteGl.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** The WebGL2 half of the sprite pass: one program, one instance buffer, one texture slot table. */
|
|
2
2
|
|
|
3
|
+
import { blendCovering } from '@driftengine/core';
|
|
4
|
+
|
|
3
5
|
import { SPRITE_FRAG, SPRITE_VERT } from './shaders/sprite.ts';
|
|
4
6
|
import { SPRITE_FLOATS } from './spriteBatch.ts';
|
|
5
7
|
import type { SpriteBatch } from './spriteBatch.ts';
|
|
@@ -260,8 +262,9 @@ export function drawWebgl2Sprites(
|
|
|
260
262
|
*/
|
|
261
263
|
gl.disable(gl.CULL_FACE);
|
|
262
264
|
gl.enable(gl.BLEND);
|
|
263
|
-
/* Premultiplied `over`: the fragment stage folds alpha in, so this composes onto opaque pixels
|
|
264
|
-
|
|
265
|
+
/* Premultiplied `over`: the fragment stage folds alpha in, so this composes onto opaque pixels,
|
|
266
|
+
and covers the scene's surface by its alpha so occlusion behind it does not darken it. */
|
|
267
|
+
blendCovering(gl, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
265
268
|
gl.disable(gl.DEPTH_TEST);
|
|
266
269
|
gl.depthMask(false);
|
|
267
270
|
gl.activeTexture(gl.TEXTURE0 + SPRITE_UNIT);
|
package/src/spriteGpu.ts
CHANGED
|
@@ -16,7 +16,12 @@ import type { SpriteBatch } from './spriteBatch.ts';
|
|
|
16
16
|
* and nothing else. The alternative was a second copy of the mip blit living here, and the second
|
|
17
17
|
* copy is the one that ends up disagreeing about the colour space.
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
SCENE_ALPHA_COVERS,
|
|
21
|
+
generateMipChain,
|
|
22
|
+
mipLevelCount,
|
|
23
|
+
type MipPipelines,
|
|
24
|
+
} from '@driftengine/core';
|
|
20
25
|
import type { SpriteImage, SpriteTextureOptions } from './spriteTexture.ts';
|
|
21
26
|
|
|
22
27
|
const VERT = SPRITE_BINDINGS.SPRITE_VERT;
|
|
@@ -34,8 +39,24 @@ export interface GpuSpriteSlot {
|
|
|
34
39
|
readonly bindGroup: GPUBindGroup;
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
/** Pipelines by colour format, then depth format, then sample count: a lookup that allocates nothing. */
|
|
43
|
+
type PipelinesByTarget = Map<
|
|
44
|
+
GPUTextureFormat,
|
|
45
|
+
Map<GPUTextureFormat, Map<number, GPURenderPipeline>>
|
|
46
|
+
>;
|
|
47
|
+
|
|
37
48
|
export interface GpuSprites {
|
|
38
|
-
|
|
49
|
+
/**
|
|
50
|
+
* One pipeline per pass the sprites have been drawn into, chosen at the draw by
|
|
51
|
+
* `spritePipelineFor`. A pipeline must match its pass's attachments exactly, and a 2D layer is
|
|
52
|
+
* drawn in the frame and over it, which are two different passes: the scene's target at the
|
|
53
|
+
* scene's samples, and the canvas at one.
|
|
54
|
+
*/
|
|
55
|
+
readonly pipelines: PipelinesByTarget;
|
|
56
|
+
readonly pipelineLayout: GPUPipelineLayout;
|
|
57
|
+
readonly vertexModule: GPUShaderModule;
|
|
58
|
+
readonly fragmentModule: GPUShaderModule;
|
|
59
|
+
readonly label: string;
|
|
39
60
|
readonly layout: GPUBindGroupLayout;
|
|
40
61
|
readonly vertexUniforms: GPUBuffer;
|
|
41
62
|
readonly fragmentUniforms: GPUBuffer;
|
|
@@ -83,52 +104,14 @@ export function createGpuSprites(
|
|
|
83
104
|
],
|
|
84
105
|
});
|
|
85
106
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
arrayStride: STRIDE,
|
|
95
|
-
stepMode: 'instance',
|
|
96
|
-
attributes: [
|
|
97
|
-
{ shaderLocation: 0, offset: 0, format: 'float32x4' },
|
|
98
|
-
{ shaderLocation: 1, offset: 16, format: 'float32x4' },
|
|
99
|
-
{ shaderLocation: 2, offset: 32, format: 'float32x4' },
|
|
100
|
-
{ shaderLocation: 3, offset: 48, format: 'float32x2' },
|
|
101
|
-
],
|
|
102
|
-
},
|
|
103
|
-
],
|
|
104
|
-
},
|
|
105
|
-
fragment: {
|
|
106
|
-
module: device.createShaderModule({ label: `${label}.frag`, code: SPRITE_FRAG_WGSL }),
|
|
107
|
-
entryPoint: 'main',
|
|
108
|
-
targets: [
|
|
109
|
-
{
|
|
110
|
-
format,
|
|
111
|
-
/* Premultiplied `over`, matching the WebGL2 half exactly. */
|
|
112
|
-
blend: {
|
|
113
|
-
color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
114
|
-
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
|
-
},
|
|
119
|
-
/*
|
|
120
|
-
* Nothing is culled: a sprite is mirrored by giving it a negative width, which reverses its
|
|
121
|
-
* winding, and a character facing left is exactly that.
|
|
122
|
-
*/
|
|
123
|
-
primitive: { topology: 'triangle-list', cullMode: 'none' },
|
|
124
|
-
multisample: { count: samples },
|
|
125
|
-
/*
|
|
126
|
-
* Neither tested nor written. The 2D layer's order *is* its layering — there is no z to sort
|
|
127
|
-
* by — and a pass that depth-tested would let whatever 3D geometry is in the frame punch holes
|
|
128
|
-
* in an overlay drawn over it. Declared all the same, because a pipeline in a render pass that
|
|
129
|
-
* has a depth attachment must name its format.
|
|
130
|
-
*/
|
|
131
|
-
depthStencil: { format: depthFormat, depthWriteEnabled: false, depthCompare: 'always' },
|
|
107
|
+
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [layout] });
|
|
108
|
+
const vertexModule = device.createShaderModule({
|
|
109
|
+
label: `${label}.vert`,
|
|
110
|
+
code: SPRITE_VERT_WGSL,
|
|
111
|
+
});
|
|
112
|
+
const fragmentModule = device.createShaderModule({
|
|
113
|
+
label: `${label}.frag`,
|
|
114
|
+
code: SPRITE_FRAG_WGSL,
|
|
132
115
|
});
|
|
133
116
|
|
|
134
117
|
const vertexUniforms = device.createBuffer({
|
|
@@ -150,8 +133,12 @@ export function createGpuSprites(
|
|
|
150
133
|
const vertexScratch = new ArrayBuffer(VERT.uniformSize);
|
|
151
134
|
const fragmentScratch = new ArrayBuffer(FRAG.uniformSize);
|
|
152
135
|
|
|
153
|
-
|
|
154
|
-
|
|
136
|
+
const sprites: GpuSprites = {
|
|
137
|
+
pipelines: new Map(),
|
|
138
|
+
pipelineLayout,
|
|
139
|
+
vertexModule,
|
|
140
|
+
fragmentModule,
|
|
141
|
+
label,
|
|
155
142
|
layout,
|
|
156
143
|
vertexUniforms,
|
|
157
144
|
fragmentUniforms,
|
|
@@ -187,6 +174,87 @@ export function createGpuSprites(
|
|
|
187
174
|
fragmentFloats: new Float32Array(fragmentScratch),
|
|
188
175
|
fragmentInts: new Int32Array(fragmentScratch),
|
|
189
176
|
};
|
|
177
|
+
/* The frame's own target, built now, so the common case compiles before its first draw. */
|
|
178
|
+
spritePipelineFor(device, sprites, format, depthFormat, samples);
|
|
179
|
+
return sprites;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The pipeline for a pass with these attachments, built the first time it is asked for.
|
|
184
|
+
*
|
|
185
|
+
* Chosen at the draw from what the pass is (`PassContext.format`, `depthFormat`, `samples`), so a
|
|
186
|
+
* 2D layer drawn over the finished frame, on the canvas at one sample, is not refused for having
|
|
187
|
+
* been built for the scene target. A lookup through three maps on every later draw, no allocation.
|
|
188
|
+
*/
|
|
189
|
+
export function spritePipelineFor(
|
|
190
|
+
device: GPUDevice,
|
|
191
|
+
sprites: GpuSprites,
|
|
192
|
+
format: GPUTextureFormat,
|
|
193
|
+
depthFormat: GPUTextureFormat,
|
|
194
|
+
samples: number,
|
|
195
|
+
): GPURenderPipeline {
|
|
196
|
+
let byDepth = sprites.pipelines.get(format);
|
|
197
|
+
if (byDepth === undefined) {
|
|
198
|
+
byDepth = new Map();
|
|
199
|
+
sprites.pipelines.set(format, byDepth);
|
|
200
|
+
}
|
|
201
|
+
let bySamples = byDepth.get(depthFormat);
|
|
202
|
+
if (bySamples === undefined) {
|
|
203
|
+
bySamples = new Map();
|
|
204
|
+
byDepth.set(depthFormat, bySamples);
|
|
205
|
+
}
|
|
206
|
+
const found = bySamples.get(samples);
|
|
207
|
+
if (found !== undefined) return found;
|
|
208
|
+
const pipeline = device.createRenderPipeline({
|
|
209
|
+
label: `${sprites.label}:${format}x${samples}`,
|
|
210
|
+
layout: sprites.pipelineLayout,
|
|
211
|
+
vertex: {
|
|
212
|
+
module: sprites.vertexModule,
|
|
213
|
+
entryPoint: 'main',
|
|
214
|
+
buffers: [
|
|
215
|
+
{
|
|
216
|
+
arrayStride: STRIDE,
|
|
217
|
+
stepMode: 'instance',
|
|
218
|
+
attributes: [
|
|
219
|
+
{ shaderLocation: 0, offset: 0, format: 'float32x4' },
|
|
220
|
+
{ shaderLocation: 1, offset: 16, format: 'float32x4' },
|
|
221
|
+
{ shaderLocation: 2, offset: 32, format: 'float32x4' },
|
|
222
|
+
{ shaderLocation: 3, offset: 48, format: 'float32x2' },
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
fragment: {
|
|
228
|
+
module: sprites.fragmentModule,
|
|
229
|
+
entryPoint: 'main',
|
|
230
|
+
targets: [
|
|
231
|
+
{
|
|
232
|
+
format,
|
|
233
|
+
/* Premultiplied `over`, matching the WebGL2 half exactly; a sprite covers the scene's
|
|
234
|
+
surface by its alpha, so occlusion behind it does not darken it. */
|
|
235
|
+
blend: {
|
|
236
|
+
color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
|
|
237
|
+
alpha: SCENE_ALPHA_COVERS,
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
/*
|
|
243
|
+
* Nothing is culled: a sprite is mirrored by giving it a negative width, which reverses its
|
|
244
|
+
* winding, and a character facing left is exactly that.
|
|
245
|
+
*/
|
|
246
|
+
primitive: { topology: 'triangle-list', cullMode: 'none' },
|
|
247
|
+
multisample: { count: samples },
|
|
248
|
+
/*
|
|
249
|
+
* Neither tested nor written. The 2D layer's order *is* its layering — there is no z to sort
|
|
250
|
+
* by — and a pass that depth-tested would let whatever 3D geometry is in the frame punch holes
|
|
251
|
+
* in an overlay drawn over it. Declared all the same, because a pipeline in a render pass that
|
|
252
|
+
* has a depth attachment must name its format.
|
|
253
|
+
*/
|
|
254
|
+
depthStencil: { format: depthFormat, depthWriteEnabled: false, depthCompare: 'always' },
|
|
255
|
+
});
|
|
256
|
+
bySamples.set(samples, pipeline);
|
|
257
|
+
return pipeline;
|
|
190
258
|
}
|
|
191
259
|
|
|
192
260
|
/**
|
|
@@ -313,9 +381,11 @@ export function drawGpuSprites(
|
|
|
313
381
|
pass: GPURenderPassEncoder,
|
|
314
382
|
sprites: GpuSprites,
|
|
315
383
|
batch: SpriteBatch,
|
|
384
|
+
/** The pipeline for this pass's attachments: `spritePipelineFor`. */
|
|
385
|
+
pipeline: GPURenderPipeline,
|
|
316
386
|
): void {
|
|
317
387
|
if (batch.count === 0) return;
|
|
318
|
-
pass.setPipeline(
|
|
388
|
+
pass.setPipeline(pipeline);
|
|
319
389
|
for (let run = 0; run < batch.runCount; run += 1) {
|
|
320
390
|
const at = run * 3;
|
|
321
391
|
const slot = sprites.slots[batch.runs[at] as number];
|
package/src/spritePass.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
createGpuSprites,
|
|
21
21
|
disposeGpuSprites,
|
|
22
22
|
drawGpuSprites,
|
|
23
|
+
spritePipelineFor,
|
|
23
24
|
setGpuSpriteTexture,
|
|
24
25
|
setGpuWhiteTexture,
|
|
25
26
|
uploadGpuInstances,
|
|
@@ -216,7 +217,13 @@ export function createSpritePass(options: SpritePassOptions = {}): SpritePass {
|
|
|
216
217
|
device.queue.writeBuffer(gpu.vertexUniforms, 0, gpu.vertexScratch);
|
|
217
218
|
device.queue.writeBuffer(gpu.fragmentUniforms, 0, gpu.fragmentScratch);
|
|
218
219
|
uploadGpuInstances(device, gpu, batch);
|
|
219
|
-
|
|
220
|
+
/* For the pass this lands in, which after `endFrame` is the canvas: see `spritePipelineFor`. */
|
|
221
|
+
drawGpuSprites(
|
|
222
|
+
ctx.pass,
|
|
223
|
+
gpu,
|
|
224
|
+
batch,
|
|
225
|
+
spritePipelineFor(device, gpu, ctx.format, ctx.depthFormat, ctx.samples),
|
|
226
|
+
);
|
|
220
227
|
},
|
|
221
228
|
|
|
222
229
|
dispose(): void {
|
package/src/uiDraw.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { SpriteBatch, SpritePlacement } from './spriteBatch.ts';
|
|
|
5
5
|
import { createSpriteFrame } from './spriteSheet.ts';
|
|
6
6
|
import type { SpriteFrame } from './spriteSheet.ts';
|
|
7
7
|
import type { UiNode, UiRect } from './uiNode.ts';
|
|
8
|
+
import { layersPresent } from './uiLayer.ts';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Where a caller draws what a quad cannot be.
|
|
@@ -61,9 +62,35 @@ export function drawUiTree(
|
|
|
61
62
|
white: number,
|
|
62
63
|
sink: UiContentSink | null,
|
|
63
64
|
): number {
|
|
64
|
-
|
|
65
|
+
/*
|
|
66
|
+
* **Layer by layer, lowest first, and tree order within one.** A raised node — a dropdown that
|
|
67
|
+
* must cover the panel its control sits in — was drawn in tree order with everything else, so a
|
|
68
|
+
* later sibling covered it. Each layer is a walk of the whole tree with the clips it would have
|
|
69
|
+
* had anyway, so a raised node is still cut by a clipping ancestor; a tree with no raised node is
|
|
70
|
+
* one walk, as before.
|
|
71
|
+
*/
|
|
72
|
+
const count = layersPresent(root, LAYERS);
|
|
73
|
+
let drawn = 0;
|
|
74
|
+
for (let at = 0; at < count; at += 1) {
|
|
75
|
+
drawn += drawWithin(
|
|
76
|
+
batch,
|
|
77
|
+
root,
|
|
78
|
+
white,
|
|
79
|
+
sink,
|
|
80
|
+
-Infinity,
|
|
81
|
+
-Infinity,
|
|
82
|
+
Infinity,
|
|
83
|
+
Infinity,
|
|
84
|
+
0,
|
|
85
|
+
LAYERS[at] ?? 0,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return drawn;
|
|
65
89
|
}
|
|
66
90
|
|
|
91
|
+
/** The layers of the tree being drawn, reused: this runs per frame. */
|
|
92
|
+
const LAYERS: number[] = [];
|
|
93
|
+
|
|
67
94
|
function drawWithin(
|
|
68
95
|
batch: SpriteBatch,
|
|
69
96
|
node: UiNode,
|
|
@@ -73,8 +100,11 @@ function drawWithin(
|
|
|
73
100
|
top: number,
|
|
74
101
|
right: number,
|
|
75
102
|
bottom: number,
|
|
103
|
+
inherited: number,
|
|
104
|
+
only: number,
|
|
76
105
|
): number {
|
|
77
106
|
if (node.hidden) return 0;
|
|
107
|
+
const layer = node.layer === 0 ? inherited : node.layer;
|
|
78
108
|
const rect = node.rect;
|
|
79
109
|
const clipped = left !== -Infinity;
|
|
80
110
|
const x0 = Math.max(rect.x, left);
|
|
@@ -87,7 +117,7 @@ function drawWithin(
|
|
|
87
117
|
* a node is drawn as it always was, whatever its size. Its children are cut one by one either way,
|
|
88
118
|
* since a child can lie outside its parent's box and still inside the clip.
|
|
89
119
|
*/
|
|
90
|
-
if (!clipped || (x1 > x0 && y1 > y0)) {
|
|
120
|
+
if (layer === only && (!clipped || (x1 > x0 && y1 > y0))) {
|
|
91
121
|
PLACEMENT.x = clipped ? x0 : rect.x;
|
|
92
122
|
PLACEMENT.y = clipped ? y0 : rect.y;
|
|
93
123
|
PLACEMENT.w = clipped ? x1 - x0 : rect.w;
|
|
@@ -115,7 +145,9 @@ function drawWithin(
|
|
|
115
145
|
const ct = keep ? Math.max(top, rect.y) : top;
|
|
116
146
|
const cr = keep ? Math.min(right, rect.x + rect.w) : right;
|
|
117
147
|
const cb = keep ? Math.min(bottom, rect.y + rect.h) : bottom;
|
|
118
|
-
for (const child of node.children)
|
|
148
|
+
for (const child of node.children) {
|
|
149
|
+
drawn += drawWithin(batch, child, white, sink, cl, ct, cr, cb, layer, only);
|
|
150
|
+
}
|
|
119
151
|
return drawn;
|
|
120
152
|
}
|
|
121
153
|
|
package/src/uiFocus.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { uiRectHolds } from './uiNode.ts';
|
|
4
4
|
import type { UiNode } from './uiNode.ts';
|
|
5
|
+
import { layersPresent } from './uiLayer.ts';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* The interactive node under a point, or `null`.
|
|
@@ -17,15 +18,30 @@ import type { UiNode } from './uiNode.ts';
|
|
|
17
18
|
* Allocates nothing.
|
|
18
19
|
*/
|
|
19
20
|
export function uiHitTest(root: UiNode, x: number, y: number): UiNode | null {
|
|
20
|
-
|
|
21
|
+
/* The highest layer first, as it is drawn last: a raised popup takes the pointer over a later
|
|
22
|
+
sibling it covers. See `drawUiTree`. */
|
|
23
|
+
const count = layersPresent(root, HIT_LAYERS);
|
|
24
|
+
for (let at = count - 1; at >= 0; at -= 1) {
|
|
25
|
+
const hit = hitOn(root, x, y, 0, HIT_LAYERS[at] ?? 0);
|
|
26
|
+
if (hit !== null) return hit;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The layers of the tree being pointed at, reused: a hit test runs per pointer event. */
|
|
32
|
+
const HIT_LAYERS: number[] = [];
|
|
33
|
+
|
|
34
|
+
function hitOn(node: UiNode, x: number, y: number, inherited: number, only: number): UiNode | null {
|
|
35
|
+
if (node.hidden) return null;
|
|
21
36
|
/* A clipping node bounds its descendants here as it does in the picture: a row scrolled out of a
|
|
22
37
|
list is not under the pointer, whatever is drawn where it would have been. */
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
if (node.clip && !uiRectHolds(node, x, y)) return null;
|
|
39
|
+
const layer = node.layer === 0 ? inherited : node.layer;
|
|
40
|
+
for (let i = node.children.length - 1; i >= 0; i -= 1) {
|
|
41
|
+
const hit = hitOn(node.children[i] as UiNode, x, y, layer, only);
|
|
26
42
|
if (hit !== null) return hit;
|
|
27
43
|
}
|
|
28
|
-
return
|
|
44
|
+
return layer === only && node.interactive && uiRectHolds(node, x, y) ? node : null;
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
/**
|
package/src/uiLayer.ts
CHANGED
|
@@ -45,3 +45,26 @@ export function layerOrder(root: UiNode, out: UiNode[]): number {
|
|
|
45
45
|
out.length = count;
|
|
46
46
|
return count;
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
/** Ascending, without allocating a comparator per call. */
|
|
50
|
+
const ASCENDING = (a: number, b: number): number => a - b;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The layers a tree draws on, ascending, into `out`; how many there are.
|
|
54
|
+
*
|
|
55
|
+
* What `drawUiTree` and `uiHitTest` walk a tree once per: a tree with no raised node answers one
|
|
56
|
+
* layer and is walked once, exactly as it was before either read a layer.
|
|
57
|
+
*/
|
|
58
|
+
export function layersPresent(root: UiNode, out: number[]): number {
|
|
59
|
+
out.length = 0;
|
|
60
|
+
collect(root, 0, out);
|
|
61
|
+
if (out.length > 1) out.sort(ASCENDING);
|
|
62
|
+
return out.length;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function collect(node: UiNode, inherited: number, out: number[]): void {
|
|
66
|
+
if (node.hidden) return;
|
|
67
|
+
const layer = node.layer === 0 ? inherited : node.layer;
|
|
68
|
+
if (!out.includes(layer)) out.push(layer);
|
|
69
|
+
for (const child of node.children) collect(child, layer, out);
|
|
70
|
+
}
|