@driftengine/core 4.4.0 → 4.4.2

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.
Files changed (49) hide show
  1. package/CHANGELOG.json +47 -0
  2. package/README.md +1 -1
  3. package/dist/render/ambientOcclusionPass.js +5 -1
  4. package/dist/render/backend/webgl2/renderer.d.ts +3 -0
  5. package/dist/render/backend/webgl2/renderer.js +13 -0
  6. package/dist/render/backend/webgpu/postPass.d.ts +20 -4
  7. package/dist/render/backend/webgpu/postPass.js +17 -4
  8. package/dist/render/backend/webgpu/renderer.d.ts +10 -0
  9. package/dist/render/backend/webgpu/renderer.js +74 -7
  10. package/dist/render/flameLight.js +15 -2
  11. package/dist/render/pointShadowBudget.d.ts +12 -2
  12. package/dist/render/pointShadowBudget.js +67 -51
  13. package/dist/render/recon/disocclusion.d.ts +36 -3
  14. package/dist/render/recon/disocclusion.js +56 -4
  15. package/dist/render/recon/resolve.js +95 -12
  16. package/dist/render/sceneTarget.d.ts +2 -0
  17. package/dist/render/sceneTarget.js +1 -1
  18. package/dist/render/shaders/ambientOcclusion.d.ts +39 -1
  19. package/dist/render/shaders/ambientOcclusion.js +69 -9
  20. package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +8 -3
  21. package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +8 -3
  22. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +31 -6
  23. package/dist/render/shaders/generated/temporalResolve.wgsl.js +31 -6
  24. package/dist/render/shaders/recon/resolve.wgsl.d.ts +1 -1
  25. package/dist/render/shaders/recon/resolve.wgsl.js +112 -8
  26. package/dist/render/shaders/temporalResolve.js +133 -5
  27. package/dist/render/temporalAa.d.ts +145 -0
  28. package/dist/render/temporalAa.js +190 -0
  29. package/dist/render/temporalPass.d.ts +16 -14
  30. package/dist/render/temporalPass.js +59 -14
  31. package/package.json +6 -6
  32. package/src/render/ambientOcclusionPass.ts +5 -1
  33. package/src/render/backend/webgl2/renderer.ts +21 -1
  34. package/src/render/backend/webgpu/postPass.ts +34 -4
  35. package/src/render/backend/webgpu/renderer.ts +77 -7
  36. package/src/render/flameLight.ts +15 -2
  37. package/src/render/pointShadowBudget.ts +65 -47
  38. package/src/render/recon/disocclusion.ts +54 -3
  39. package/src/render/recon/resolve.ts +137 -10
  40. package/src/render/sceneTarget.ts +8 -1
  41. package/src/render/shaders/ambientOcclusion.ts +81 -9
  42. package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +8 -3
  43. package/src/render/shaders/generated/temporalResolve.wgsl.ts +31 -6
  44. package/src/render/shaders/recon/resolve.wgsl.ts +112 -8
  45. package/src/render/shaders/temporalResolve.ts +141 -5
  46. package/src/render/temporalAa.ts +227 -0
  47. package/src/render/temporalPass.ts +88 -12
  48. package/dist/render/shaders/agx.d.ts +0 -31
  49. package/dist/render/shaders/agx.js +0 -81
@@ -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 TEMPORAL_RESOLVE_FRAG_WGSL = "struct Uniforms {\n uReprojection: mat4x4<f32>,\n uTexel: vec2<f32>,\n uHistoryBlend: f32,\n}\n\n@group(0) @binding(32) \nvar uScene_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uScene_s: sampler;\nvar<private> vUv_1: vec2<f32>;\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(34) \nvar uHistory_t: texture_2d<f32>;\n@group(0) @binding(35) \nvar uHistory_s: sampler;\nvar<private> fragColor: vec4<f32>;\n@group(0) @binding(36) \nvar uDepth_t: texture_2d<f32>;\n@group(0) @binding(37) \nvar uDepth_s: sampler;\n\nfn clipToNeighbourhood_u0028_vf3_u003b_vf3_u003b_vf3_u003b(history: ptr<function, vec3<f32>>, lo: ptr<function, vec3<f32>>, hi: ptr<function, vec3<f32>>) -> vec3<f32> {\n var centre: vec3<f32>;\n var extent: vec3<f32>;\n var offset: vec3<f32>;\n var ratios: vec3<f32>;\n var ratio: f32;\n\n let _e39 = (*lo);\n let _e40 = (*hi);\n centre = ((_e39 + _e40) * 0.5f);\n let _e43 = (*hi);\n let _e44 = (*lo);\n extent = ((_e43 - _e44) * 0.5f);\n let _e47 = (*history);\n let _e48 = centre;\n offset = (_e47 - _e48);\n let _e50 = offset;\n let _e52 = extent;\n ratios = (abs(_e50) / max(_e52, vec3<f32>(0.0000001f, 0.0000001f, 0.0000001f)));\n let _e56 = ratios[0u];\n let _e58 = ratios[1u];\n let _e60 = ratios[2u];\n ratio = max(_e56, max(_e58, _e60));\n let _e63 = ratio;\n if (_e63 <= 1f) {\n let _e65 = (*history);\n return _e65;\n }\n let _e66 = centre;\n let _e67 = offset;\n let _e68 = ratio;\n return (_e66 + (_e67 / vec3(_e68)));\n}\n\nfn historyCatmullRom_u0028_vf2_u003b(uv: ptr<function, vec2<f32>>) -> vec3<f32> {\n var size: vec2<f32>;\n var at: vec2<f32>;\n var base: vec2<f32>;\n var f: vec2<f32>;\n var w0_: vec2<f32>;\n var w1_: vec2<f32>;\n var w2_: vec2<f32>;\n var w3_: vec2<f32>;\n var w12_: vec2<f32>;\n var at0_: vec2<f32>;\n var at3_: vec2<f32>;\n var at12_: vec2<f32>;\n var sum: vec3<f32>;\n var weight: f32;\n\n let _e47 = unnamed.uTexel;\n size = (vec2(1f) / _e47);\n let _e50 = (*uv);\n let _e51 = size;\n at = (_e50 * _e51);\n let _e53 = at;\n base = (floor((_e53 - vec2(0.5f))) + vec2(0.5f));\n let _e59 = at;\n let _e60 = base;\n f = (_e59 - _e60);\n let _e62 = f;\n let _e63 = f;\n let _e64 = f;\n w0_ = (_e62 * (vec2(-0.5f) + (_e63 * (vec2(1f) - (_e64 * 0.5f)))));\n let _e72 = f;\n let _e73 = f;\n let _e75 = f;\n w1_ = (vec2(1f) + ((_e72 * _e73) * (vec2(-2.5f) + (_e75 * 1.5f))));\n let _e82 = f;\n let _e83 = f;\n let _e84 = f;\n w2_ = (_e82 * (vec2(0.5f) + (_e83 * (vec2(2f) - (_e84 * 1.5f)))));\n let _e92 = f;\n let _e93 = f;\n let _e95 = f;\n w3_ = ((_e92 * _e93) * (vec2(-0.5f) + (_e95 * 0.5f)));\n let _e100 = w1_;\n let _e101 = w2_;\n w12_ = (_e100 + _e101);\n let _e103 = base;\n let _e107 = unnamed.uTexel;\n at0_ = ((_e103 - vec2(1f)) * _e107);\n let _e109 = base;\n let _e113 = unnamed.uTexel;\n at3_ = ((_e109 + vec2(2f)) * _e113);\n let _e115 = base;\n let _e116 = w2_;\n let _e117 = w12_;\n let _e121 = unnamed.uTexel;\n at12_ = ((_e115 + (_e116 / _e117)) * _e121);\n let _e124 = at12_[0u];\n let _e126 = at0_[1u];\n let _e128 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e124, _e126), 0f);\n let _e131 = w12_[0u];\n let _e133 = w0_[1u];\n let _e137 = at0_[0u];\n let _e139 = at12_[1u];\n let _e141 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e137, _e139), 0f);\n let _e144 = w0_[0u];\n let _e146 = w12_[1u];\n let _e150 = at12_;\n let _e151 = textureSampleLevel(uHistory_t, uHistory_s, _e150, 0f);\n let _e154 = w12_[0u];\n let _e156 = w12_[1u];\n let _e161 = at3_[0u];\n let _e163 = at12_[1u];\n let _e165 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e161, _e163), 0f);\n let _e168 = w3_[0u];\n let _e170 = w12_[1u];\n let _e175 = at12_[0u];\n let _e177 = at3_[1u];\n let _e179 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e175, _e177), 0f);\n let _e182 = w12_[0u];\n let _e184 = w3_[1u];\n sum = (((((_e128.xyz * (_e131 * _e133)) + (_e141.xyz * (_e144 * _e146))) + (_e151.xyz * (_e154 * _e156))) + (_e165.xyz * (_e168 * _e170))) + (_e179.xyz * (_e182 * _e184)));\n let _e189 = w12_[0u];\n let _e191 = w0_[1u];\n let _e194 = w0_[0u];\n let _e196 = w12_[1u];\n let _e200 = w12_[0u];\n let _e202 = w12_[1u];\n let _e206 = w3_[0u];\n let _e208 = w12_[1u];\n let _e212 = w12_[0u];\n let _e214 = w3_[1u];\n weight = (((((_e189 * _e191) + (_e194 * _e196)) + (_e200 * _e202)) + (_e206 * _e208)) + (_e212 * _e214));\n let _e217 = sum;\n let _e218 = weight;\n return max((_e217 / vec3(_e218)), vec3<f32>(0f, 0f, 0f));\n}\n\nfn neighbourhood_u0028_vf3_u003b_vf3_u003b(lo_1: ptr<function, vec3<f32>>, hi_1: ptr<function, vec3<f32>>) {\n var y: i32;\n var x: i32;\n var c: vec3<f32>;\n\n (*lo_1) = vec3<f32>(1000000000f, 1000000000f, 1000000000f);\n (*hi_1) = vec3<f32>(-1000000000f, -1000000000f, -1000000000f);\n y = -1i;\n loop {\n let _e36 = y;\n if (_e36 <= 1i) {\n x = -1i;\n loop {\n let _e38 = x;\n if (_e38 <= 1i) {\n let _e40 = vUv_1;\n let _e41 = x;\n let _e43 = y;\n let _e47 = unnamed.uTexel;\n let _e50 = textureSampleLevel(uScene_t, uScene_s, (_e40 + (vec2<f32>(f32(_e41), f32(_e43)) * _e47)), 0f);\n c = _e50.xyz;\n let _e52 = (*lo_1);\n let _e53 = c;\n (*lo_1) = min(_e52, _e53);\n let _e55 = (*hi_1);\n let _e56 = c;\n (*hi_1) = max(_e55, _e56);\n continue;\n } else {\n break;\n }\n continuing {\n let _e58 = x;\n x = (_e58 + 1i);\n }\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e60 = y;\n y = (_e60 + 1i);\n }\n }\n return;\n}\n\nfn main_1() {\n var current: vec3<f32>;\n var depth: f32;\n var clip: vec4<f32>;\n var previous: vec4<f32>;\n var wasUv: vec2<f32>;\n var lo_2: vec3<f32>;\n var hi_2: vec3<f32>;\n var param: vec3<f32>;\n var param_1: vec3<f32>;\n var history_1: vec3<f32>;\n var param_2: vec2<f32>;\n var bounded: vec3<f32>;\n var param_3: vec3<f32>;\n var param_4: vec3<f32>;\n var param_5: vec3<f32>;\n var phi_440_: bool;\n var phi_447_: bool;\n var phi_454_: bool;\n\n let _e46 = vUv_1;\n let _e47 = textureSampleLevel(uScene_t, uScene_s, _e46, 0f);\n current = _e47.xyz;\n let _e50 = unnamed.uHistoryBlend;\n if (_e50 <= 0f) {\n let _e52 = current;\n fragColor = vec4<f32>(_e52.x, _e52.y, _e52.z, 1f);\n return;\n }\n let _e57 = vUv_1;\n let _e58 = textureSampleLevel(uDepth_t, uDepth_s, _e57, 0f);\n depth = _e58.x;\n let _e60 = vUv_1;\n let _e63 = ((_e60 * 2f) - vec2(1f));\n let _e64 = depth;\n clip = vec4<f32>(_e63.x, _e63.y, (1f - (_e64 * 2f)), 1f);\n let _e71 = unnamed.uReprojection;\n let _e72 = clip;\n previous = (_e71 * _e72);\n let _e75 = previous[3u];\n if (_e75 <= 0f) {\n let _e77 = current;\n fragColor = vec4<f32>(_e77.x, _e77.y, _e77.z, 1f);\n return;\n }\n let _e82 = previous;\n let _e85 = previous[3u];\n wasUv = (((_e82.xy / vec2(_e85)) * 0.5f) + vec2(0.5f));\n let _e92 = wasUv[0u];\n let _e93 = (_e92 < 0f);\n phi_440_ = _e93;\n if !(_e93) {\n let _e96 = wasUv[0u];\n phi_440_ = (_e96 > 1f);\n }\n let _e99 = phi_440_;\n phi_447_ = _e99;\n if !(_e99) {\n let _e102 = wasUv[1u];\n phi_447_ = (_e102 < 0f);\n }\n let _e105 = phi_447_;\n phi_454_ = _e105;\n if !(_e105) {\n let _e108 = wasUv[1u];\n phi_454_ = (_e108 > 1f);\n }\n let _e111 = phi_454_;\n if _e111 {\n let _e112 = current;\n fragColor = vec4<f32>(_e112.x, _e112.y, _e112.z, 1f);\n return;\n }\n neighbourhood_u0028_vf3_u003b_vf3_u003b((&param), (&param_1));\n let _e117 = param;\n lo_2 = _e117;\n let _e118 = param_1;\n hi_2 = _e118;\n let _e119 = wasUv;\n param_2 = _e119;\n let _e120 = historyCatmullRom_u0028_vf2_u003b((&param_2));\n history_1 = _e120;\n let _e121 = history_1;\n param_3 = _e121;\n let _e122 = lo_2;\n param_4 = _e122;\n let _e123 = hi_2;\n param_5 = _e123;\n let _e124 = clipToNeighbourhood_u0028_vf3_u003b_vf3_u003b_vf3_u003b((&param_3), (&param_4), (&param_5));\n bounded = _e124;\n let _e125 = current;\n let _e126 = bounded;\n let _e128 = unnamed.uHistoryBlend;\n let _e130 = mix(_e125, _e126, vec3(_e128));\n fragColor = vec4<f32>(_e130.x, _e130.y, _e130.z, 1f);\n return;\n}\n\n@fragment \nfn main(@location(0) vUv: vec2<f32>) -> @location(0) vec4<f32> {\n vUv_1 = vUv;\n main_1();\n let _e3 = fragColor;\n return _e3;\n}\n";
7
+ export const TEMPORAL_RESOLVE_FRAG_WGSL = "struct Uniforms {\n uDepthToViewZ: vec4<f32>,\n uAntiFlicker: f32,\n uReprojection: mat4x4<f32>,\n uTexel: vec2<f32>,\n uPeriodStart: f32,\n uHistoryBlend: f32,\n}\n\nstruct FragmentOutput {\n @location(0) member: vec4<f32>,\n @location(1) member_1: vec4<f32>,\n @location(2) member_2: vec4<f32>,\n}\n\n@group(0) @binding(1) \nvar<uniform> unnamed: Uniforms;\n@group(0) @binding(32) \nvar uScene_t: texture_2d<f32>;\n@group(0) @binding(33) \nvar uScene_s: sampler;\nvar<private> vUv_1: vec2<f32>;\n@group(0) @binding(34) \nvar uHistory_t: texture_2d<f32>;\n@group(0) @binding(35) \nvar uHistory_s: sampler;\nvar<private> fragColor: vec4<f32>;\nvar<private> flickerOut: vec4<f32>;\nvar<private> flickerMotionOut: vec4<f32>;\n@group(0) @binding(40) \nvar uDepth_t: texture_2d<f32>;\n@group(0) @binding(41) \nvar uDepth_s: sampler;\n@group(0) @binding(36) \nvar uFlicker_t: texture_2d<f32>;\n@group(0) @binding(38) \nvar uFlickerMotion_t: texture_2d<f32>;\n@group(0) @binding(37) \nvar uFlicker_s: sampler;\n@group(0) @binding(39) \nvar uFlickerMotion_s: sampler;\n\nfn repeats_u0028_f1_u003b_f1_u003b(mean: ptr<function, f32>, previous: ptr<function, f32>) -> f32 {\n var apart: f32;\n\n let _e63 = (*mean);\n let _e64 = (*previous);\n let _e67 = (*mean);\n let _e68 = (*previous);\n apart = (abs((_e63 - _e64)) / max(max(_e67, _e68), 0.003921569f));\n let _e72 = apart;\n return (1f - smoothstep(0.05f, 0.1f, _e72));\n}\n\nfn clipToNeighbourhood_u0028_vf3_u003b_vf3_u003b_vf3_u003b(history: ptr<function, vec3<f32>>, lo: ptr<function, vec3<f32>>, hi: ptr<function, vec3<f32>>) -> vec3<f32> {\n var centre: vec3<f32>;\n var extent: vec3<f32>;\n var offset: vec3<f32>;\n var ratios: vec3<f32>;\n var ratio: f32;\n\n let _e68 = (*lo);\n let _e69 = (*hi);\n centre = ((_e68 + _e69) * 0.5f);\n let _e72 = (*hi);\n let _e73 = (*lo);\n extent = ((_e72 - _e73) * 0.5f);\n let _e76 = (*history);\n let _e77 = centre;\n offset = (_e76 - _e77);\n let _e79 = offset;\n let _e81 = extent;\n ratios = (abs(_e79) / max(_e81, vec3<f32>(0.0000001f, 0.0000001f, 0.0000001f)));\n let _e85 = ratios[0u];\n let _e87 = ratios[1u];\n let _e89 = ratios[2u];\n ratio = max(_e85, max(_e87, _e89));\n let _e92 = ratio;\n if (_e92 <= 1f) {\n let _e94 = (*history);\n return _e94;\n }\n let _e95 = centre;\n let _e96 = offset;\n let _e97 = ratio;\n return (_e95 + (_e96 / vec3(_e97)));\n}\n\nfn luma_u0028_vf3_u003b(c: ptr<function, vec3<f32>>) -> f32 {\n let _e61 = (*c);\n return dot(_e61, vec3<f32>(0.2126f, 0.7152f, 0.0722f));\n}\n\nfn historyCatmullRom_u0028_vf2_u003b(uv: ptr<function, vec2<f32>>) -> vec3<f32> {\n var size: vec2<f32>;\n var at: vec2<f32>;\n var base: vec2<f32>;\n var f: vec2<f32>;\n var w0_: vec2<f32>;\n var w1_: vec2<f32>;\n var w2_: vec2<f32>;\n var w3_: vec2<f32>;\n var w12_: vec2<f32>;\n var at0_: vec2<f32>;\n var at3_: vec2<f32>;\n var at12_: vec2<f32>;\n var sum: vec3<f32>;\n var weight: f32;\n\n let _e76 = unnamed.uTexel;\n size = (vec2(1f) / _e76);\n let _e79 = (*uv);\n let _e80 = size;\n at = (_e79 * _e80);\n let _e82 = at;\n base = (floor((_e82 - vec2(0.5f))) + vec2(0.5f));\n let _e88 = at;\n let _e89 = base;\n f = (_e88 - _e89);\n let _e91 = f;\n let _e92 = f;\n let _e93 = f;\n w0_ = (_e91 * (vec2(-0.5f) + (_e92 * (vec2(1f) - (_e93 * 0.5f)))));\n let _e101 = f;\n let _e102 = f;\n let _e104 = f;\n w1_ = (vec2(1f) + ((_e101 * _e102) * (vec2(-2.5f) + (_e104 * 1.5f))));\n let _e111 = f;\n let _e112 = f;\n let _e113 = f;\n w2_ = (_e111 * (vec2(0.5f) + (_e112 * (vec2(2f) - (_e113 * 1.5f)))));\n let _e121 = f;\n let _e122 = f;\n let _e124 = f;\n w3_ = ((_e121 * _e122) * (vec2(-0.5f) + (_e124 * 0.5f)));\n let _e129 = w1_;\n let _e130 = w2_;\n w12_ = (_e129 + _e130);\n let _e132 = base;\n let _e136 = unnamed.uTexel;\n at0_ = ((_e132 - vec2(1f)) * _e136);\n let _e138 = base;\n let _e142 = unnamed.uTexel;\n at3_ = ((_e138 + vec2(2f)) * _e142);\n let _e144 = base;\n let _e145 = w2_;\n let _e146 = w12_;\n let _e150 = unnamed.uTexel;\n at12_ = ((_e144 + (_e145 / _e146)) * _e150);\n let _e153 = at12_[0u];\n let _e155 = at0_[1u];\n let _e157 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e153, _e155), 0f);\n let _e160 = w12_[0u];\n let _e162 = w0_[1u];\n let _e166 = at0_[0u];\n let _e168 = at12_[1u];\n let _e170 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e166, _e168), 0f);\n let _e173 = w0_[0u];\n let _e175 = w12_[1u];\n let _e179 = at12_;\n let _e180 = textureSampleLevel(uHistory_t, uHistory_s, _e179, 0f);\n let _e183 = w12_[0u];\n let _e185 = w12_[1u];\n let _e190 = at3_[0u];\n let _e192 = at12_[1u];\n let _e194 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e190, _e192), 0f);\n let _e197 = w3_[0u];\n let _e199 = w12_[1u];\n let _e204 = at12_[0u];\n let _e206 = at3_[1u];\n let _e208 = textureSampleLevel(uHistory_t, uHistory_s, vec2<f32>(_e204, _e206), 0f);\n let _e211 = w12_[0u];\n let _e213 = w3_[1u];\n sum = (((((_e157.xyz * (_e160 * _e162)) + (_e170.xyz * (_e173 * _e175))) + (_e180.xyz * (_e183 * _e185))) + (_e194.xyz * (_e197 * _e199))) + (_e208.xyz * (_e211 * _e213)));\n let _e218 = w12_[0u];\n let _e220 = w0_[1u];\n let _e223 = w0_[0u];\n let _e225 = w12_[1u];\n let _e229 = w12_[0u];\n let _e231 = w12_[1u];\n let _e235 = w3_[0u];\n let _e237 = w12_[1u];\n let _e241 = w12_[0u];\n let _e243 = w3_[1u];\n weight = (((((_e218 * _e220) + (_e223 * _e225)) + (_e229 * _e231)) + (_e235 * _e237)) + (_e241 * _e243));\n let _e246 = sum;\n let _e247 = weight;\n return max((_e246 / vec3(_e247)), vec3<f32>(0f, 0f, 0f));\n}\n\nfn neighbourhood_u0028_vf3_u003b_vf3_u003b(lo_1: ptr<function, vec3<f32>>, hi_1: ptr<function, vec3<f32>>) {\n var y: i32;\n var x: i32;\n var c_1: vec3<f32>;\n\n (*lo_1) = vec3<f32>(1000000000f, 1000000000f, 1000000000f);\n (*hi_1) = vec3<f32>(-1000000000f, -1000000000f, -1000000000f);\n y = -1i;\n loop {\n let _e65 = y;\n if (_e65 <= 1i) {\n x = -1i;\n loop {\n let _e67 = x;\n if (_e67 <= 1i) {\n let _e69 = vUv_1;\n let _e70 = x;\n let _e72 = y;\n let _e76 = unnamed.uTexel;\n let _e79 = textureSampleLevel(uScene_t, uScene_s, (_e69 + (vec2<f32>(f32(_e70), f32(_e72)) * _e76)), 0f);\n c_1 = _e79.xyz;\n let _e81 = (*lo_1);\n let _e82 = c_1;\n (*lo_1) = min(_e81, _e82);\n let _e84 = (*hi_1);\n let _e85 = c_1;\n (*hi_1) = max(_e84, _e85);\n continue;\n } else {\n break;\n }\n continuing {\n let _e87 = x;\n x = (_e87 + 1i);\n }\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e89 = y;\n y = (_e89 + 1i);\n }\n }\n return;\n}\n\nfn viewDepthOf_u0028_f1_u003b(depth: ptr<function, f32>) -> f32 {\n var z: f32;\n\n let _e62 = (*depth);\n z = (1f - (_e62 * 2f));\n let _e67 = unnamed.uDepthToViewZ[0u];\n let _e68 = z;\n let _e72 = unnamed.uDepthToViewZ[1u];\n let _e77 = unnamed.uDepthToViewZ[2u];\n let _e78 = z;\n let _e82 = unnamed.uDepthToViewZ[3u];\n return (-(((_e67 * _e68) + _e72)) / ((_e77 * _e78) + _e82));\n}\n\nfn freshRecord_u0028_vf3_u003b(current: ptr<function, vec3<f32>>) -> vec4<f32> {\n var seen: f32;\n var param: vec3<f32>;\n\n let _e63 = (*current);\n param = _e63;\n let _e64 = luma_u0028_vf3_u003b((&param));\n seen = _e64;\n let _e65 = seen;\n let _e67 = seen;\n return vec4<f32>(0f, (_e65 * 0.125f), _e67, 0f);\n}\n\nfn main_1() {\n var current_1: vec3<f32>;\n var param_1: vec3<f32>;\n var nearestAt: vec2<f32>;\n var depth_1: f32;\n var metres: f32;\n var param_2: f32;\n var y_1: i32;\n var x_1: i32;\n var at_1: vec2<f32>;\n var tap: f32;\n var tapMetres: f32;\n var param_3: f32;\n var clip: vec4<f32>;\n var previous_1: vec4<f32>;\n var param_4: vec3<f32>;\n var wasUv: vec2<f32>;\n var param_5: vec3<f32>;\n var lo_2: vec3<f32>;\n var hi_2: vec3<f32>;\n var param_6: vec3<f32>;\n var param_7: vec3<f32>;\n var history_1: vec3<f32>;\n var param_8: vec2<f32>;\n var size_1: vec2<i32>;\n var was: vec2<i32>;\n var nearest: f32;\n var n: i32;\n var at_2: vec2<i32>;\n var local: vec2<i32>;\n var local_1: vec2<i32>;\n var local_2: vec2<i32>;\n var stood: f32;\n var motionThere: vec4<f32>;\n var missedAll: bool;\n var same: bool;\n var swingOut: f32;\n var local_3: f32;\n var seen_1: f32;\n var param_9: vec3<f32>;\n var held: vec4<f32>;\n var local_4: vec4<f32>;\n var param_10: vec3<f32>;\n var still: f32;\n var apart_1: f32;\n var reach: f32;\n var within: bool;\n var proven: bool;\n var widen: f32;\n var local_5: f32;\n var blend: f32;\n var local_6: f32;\n var bounded: vec3<f32>;\n var param_11: vec3<f32>;\n var param_12: vec3<f32>;\n var param_13: vec3<f32>;\n var sum_1: f32;\n var mean_1: f32;\n var repeated: f32;\n var local_7: f32;\n var param_14: f32;\n var param_15: f32;\n var local_8: f32;\n var phi_589_: bool;\n var phi_596_: bool;\n var phi_603_: bool;\n var phi_740_: bool;\n\n let _e122 = vUv_1;\n let _e123 = textureSampleLevel(uScene_t, uScene_s, _e122, 0f);\n current_1 = _e123.xyz;\n let _e126 = unnamed.uHistoryBlend;\n if (_e126 <= 0f) {\n let _e128 = current_1;\n fragColor = vec4<f32>(_e128.x, _e128.y, _e128.z, 1f);\n let _e133 = current_1;\n param_1 = _e133;\n let _e134 = freshRecord_u0028_vf3_u003b((&param_1));\n flickerOut = _e134;\n flickerMotionOut = vec4<f32>(0f, 0f, 0f, 0f);\n return;\n }\n let _e135 = vUv_1;\n nearestAt = _e135;\n let _e136 = vUv_1;\n let _e137 = textureSampleLevel(uDepth_t, uDepth_s, _e136, 0f);\n depth_1 = _e137.x;\n let _e139 = depth_1;\n param_2 = _e139;\n let _e140 = viewDepthOf_u0028_f1_u003b((&param_2));\n metres = _e140;\n y_1 = -1i;\n loop {\n let _e141 = y_1;\n if (_e141 <= 1i) {\n x_1 = -1i;\n loop {\n let _e143 = x_1;\n if (_e143 <= 1i) {\n let _e145 = vUv_1;\n let _e146 = x_1;\n let _e148 = y_1;\n let _e152 = unnamed.uTexel;\n at_1 = (_e145 + (vec2<f32>(f32(_e146), f32(_e148)) * _e152));\n let _e155 = at_1;\n let _e156 = textureSampleLevel(uDepth_t, uDepth_s, _e155, 0f);\n tap = _e156.x;\n let _e158 = tap;\n param_3 = _e158;\n let _e159 = viewDepthOf_u0028_f1_u003b((&param_3));\n tapMetres = _e159;\n let _e160 = tapMetres;\n let _e161 = metres;\n if (_e160 < _e161) {\n let _e163 = tapMetres;\n metres = _e163;\n let _e164 = tap;\n depth_1 = _e164;\n let _e165 = at_1;\n nearestAt = _e165;\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e166 = x_1;\n x_1 = (_e166 + 1i);\n }\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e168 = y_1;\n y_1 = (_e168 + 1i);\n }\n }\n let _e170 = nearestAt;\n let _e173 = ((_e170 * 2f) - vec2(1f));\n let _e174 = depth_1;\n clip = vec4<f32>(_e173.x, _e173.y, (1f - (_e174 * 2f)), 1f);\n let _e181 = unnamed.uReprojection;\n let _e182 = clip;\n previous_1 = (_e181 * _e182);\n let _e185 = previous_1[3u];\n if (_e185 <= 0f) {\n let _e187 = current_1;\n fragColor = vec4<f32>(_e187.x, _e187.y, _e187.z, 1f);\n let _e192 = current_1;\n param_4 = _e192;\n let _e193 = freshRecord_u0028_vf3_u003b((&param_4));\n flickerOut = _e193;\n let _e194 = metres;\n flickerMotionOut = vec4<f32>(_e194, 0f, 0f, 0f);\n return;\n }\n let _e196 = vUv_1;\n let _e197 = previous_1;\n let _e200 = previous_1[3u];\n let _e206 = nearestAt;\n wasUv = (_e196 + ((((_e197.xy / vec2(_e200)) * 0.5f) + vec2(0.5f)) - _e206));\n let _e210 = wasUv[0u];\n let _e211 = (_e210 < 0f);\n phi_589_ = _e211;\n if !(_e211) {\n let _e214 = wasUv[0u];\n phi_589_ = (_e214 > 1f);\n }\n let _e217 = phi_589_;\n phi_596_ = _e217;\n if !(_e217) {\n let _e220 = wasUv[1u];\n phi_596_ = (_e220 < 0f);\n }\n let _e223 = phi_596_;\n phi_603_ = _e223;\n if !(_e223) {\n let _e226 = wasUv[1u];\n phi_603_ = (_e226 > 1f);\n }\n let _e229 = phi_603_;\n if _e229 {\n let _e230 = current_1;\n fragColor = vec4<f32>(_e230.x, _e230.y, _e230.z, 1f);\n let _e235 = current_1;\n param_5 = _e235;\n let _e236 = freshRecord_u0028_vf3_u003b((&param_5));\n flickerOut = _e236;\n let _e237 = metres;\n flickerMotionOut = vec4<f32>(_e237, 0f, 0f, 0f);\n return;\n }\n neighbourhood_u0028_vf3_u003b_vf3_u003b((&param_6), (&param_7));\n let _e239 = param_6;\n lo_2 = _e239;\n let _e240 = param_7;\n hi_2 = _e240;\n let _e241 = wasUv;\n param_8 = _e241;\n let _e242 = historyCatmullRom_u0028_vf2_u003b((&param_8));\n history_1 = _e242;\n let _e243 = textureDimensions(uFlicker_t, 0i);\n size_1 = vec2<i32>(_e243);\n let _e245 = wasUv;\n let _e246 = size_1;\n let _e250 = size_1;\n was = clamp(vec2<i32>((_e245 * vec2<f32>(_e246))), vec2<i32>(0i, 0i), (_e250 - vec2(1i)));\n nearest = 1000000000f;\n n = 0i;\n loop {\n let _e254 = n;\n if (_e254 < 5i) {\n let _e256 = was;\n let _e257 = n;\n if (_e257 == 1i) {\n local = vec2<i32>(1i, 0i);\n } else {\n let _e259 = n;\n if (_e259 == 2i) {\n local_1 = vec2<i32>(-1i, 0i);\n } else {\n let _e261 = n;\n if (_e261 == 3i) {\n local_2 = vec2<i32>(0i, 1i);\n } else {\n let _e263 = n;\n local_2 = select(vec2<i32>(0i, 0i), vec2<i32>(0i, -1i), vec2((_e263 == 4i)));\n }\n let _e267 = local_2;\n local_1 = _e267;\n }\n let _e268 = local_1;\n local = _e268;\n }\n let _e269 = local;\n at_2 = (_e256 + _e269);\n let _e271 = at_2;\n let _e272 = size_1;\n let _e276 = textureLoad(uFlickerMotion_t, clamp(_e271, vec2<i32>(0i, 0i), (_e272 - vec2(1i))), 0i);\n stood = _e276.x;\n let _e278 = stood;\n if (_e278 > 0f) {\n let _e280 = nearest;\n let _e281 = stood;\n let _e283 = previous_1[3u];\n nearest = min(_e280, abs((_e281 - _e283)));\n }\n continue;\n } else {\n break;\n }\n continuing {\n let _e287 = n;\n n = (_e287 + 1i);\n }\n }\n let _e289 = was;\n let _e290 = textureLoad(uFlickerMotion_t, _e289, 0i);\n motionThere = _e290;\n let _e291 = nearest;\n missedAll = (_e291 > 100000000f);\n let _e294 = unnamed.uAntiFlicker;\n let _e296 = missedAll;\n let _e298 = ((_e294 > 0.5f) && !(_e296));\n phi_740_ = _e298;\n if _e298 {\n let _e299 = nearest;\n let _e301 = motionThere[1u];\n let _e304 = previous_1[3u];\n phi_740_ = (_e299 <= ((6f * _e301) + (0.02f * _e304)));\n }\n let _e309 = phi_740_;\n same = _e309;\n let _e310 = missedAll;\n if _e310 {\n local_3 = 0f;\n } else {\n let _e312 = motionThere[1u];\n let _e313 = nearest;\n let _e315 = motionThere[1u];\n local_3 = (_e312 + ((_e313 - _e315) * 0.125f));\n }\n let _e319 = local_3;\n swingOut = _e319;\n let _e320 = current_1;\n param_9 = _e320;\n let _e321 = luma_u0028_vf3_u003b((&param_9));\n seen_1 = _e321;\n let _e322 = same;\n if _e322 {\n let _e323 = was;\n let _e324 = textureLoad(uFlicker_t, _e323, 0i);\n local_4 = _e324;\n } else {\n let _e325 = current_1;\n param_10 = _e325;\n let _e326 = freshRecord_u0028_vf3_u003b((&param_10));\n local_4 = _e326;\n }\n let _e327 = local_4;\n held = _e327;\n let _e328 = wasUv;\n let _e329 = vUv_1;\n let _e332 = unnamed.uTexel;\n still = (1f - smoothstep(0.01f, 0.05f, length(((_e328 - _e329) / _e332))));\n let _e337 = seen_1;\n let _e339 = held[2u];\n apart_1 = abs((_e337 - _e339));\n let _e343 = held[0u];\n reach = ((6f * _e343) + 0.003921569f);\n let _e346 = apart_1;\n let _e347 = reach;\n within = (_e346 <= _e347);\n let _e350 = held[3u];\n let _e352 = within;\n proven = ((_e350 > 0.75f) && _e352);\n let _e354 = proven;\n if _e354 {\n let _e356 = held[0u];\n let _e358 = still;\n local_5 = ((6f * _e356) * _e358);\n } else {\n local_5 = 0f;\n }\n let _e360 = local_5;\n widen = _e360;\n let _e361 = proven;\n if _e361 {\n let _e363 = unnamed.uHistoryBlend;\n let _e364 = still;\n local_6 = mix(_e363, 0.97f, _e364);\n } else {\n let _e367 = unnamed.uHistoryBlend;\n local_6 = _e367;\n }\n let _e368 = local_6;\n blend = _e368;\n let _e369 = lo_2;\n let _e370 = widen;\n let _e373 = hi_2;\n let _e374 = widen;\n let _e377 = history_1;\n param_11 = _e377;\n param_12 = (_e369 - vec3(_e370));\n param_13 = (_e373 + vec3(_e374));\n let _e378 = clipToNeighbourhood_u0028_vf3_u003b_vf3_u003b_vf3_u003b((&param_11), (&param_12), (&param_13));\n bounded = _e378;\n let _e380 = held[1u];\n sum_1 = _e380;\n let _e382 = held[2u];\n mean_1 = _e382;\n let _e383 = within;\n if _e383 {\n let _e385 = held[3u];\n local_7 = _e385;\n } else {\n local_7 = 0f;\n }\n let _e386 = local_7;\n repeated = _e386;\n let _e388 = unnamed.uPeriodStart;\n if (_e388 > 0.5f) {\n let _e390 = sum_1;\n param_14 = _e390;\n let _e391 = mean_1;\n param_15 = _e391;\n let _e392 = repeats_u0028_f1_u003b_f1_u003b((&param_14), (&param_15));\n if (_e392 > 0.5f) {\n let _e394 = repeated;\n local_8 = min(1f, (_e394 + 0.5f));\n } else {\n local_8 = 0f;\n }\n let _e397 = local_8;\n repeated = _e397;\n let _e398 = sum_1;\n mean_1 = _e398;\n sum_1 = 0f;\n }\n let _e400 = held[0u];\n let _e401 = apart_1;\n let _e403 = held[0u];\n let _e407 = sum_1;\n let _e408 = seen_1;\n let _e411 = mean_1;\n let _e412 = repeated;\n let _e413 = still;\n flickerOut = vec4<f32>((_e400 + ((_e401 - _e403) * 0.125f)), (_e407 + (_e408 * 0.125f)), _e411, (_e412 * _e413));\n let _e416 = metres;\n let _e417 = swingOut;\n flickerMotionOut = vec4<f32>(_e416, _e417, 0f, 0f);\n let _e419 = current_1;\n let _e420 = bounded;\n let _e421 = blend;\n let _e423 = mix(_e419, _e420, vec3(_e421));\n fragColor = vec4<f32>(_e423.x, _e423.y, _e423.z, 1f);\n return;\n}\n\n@fragment \nfn main(@location(0) vUv: vec2<f32>) -> FragmentOutput {\n vUv_1 = vUv;\n main_1();\n let _e5 = fragColor;\n let _e6 = flickerOut;\n let _e7 = flickerMotionOut;\n return FragmentOutput(_e5, _e6, _e7);\n}\n";
8
8
  /**
9
9
  * What the transform assigned, so the renderer binds the same numbers.
10
10
  *
@@ -13,20 +13,35 @@ export const TEMPORAL_RESOLVE_FRAG_WGSL = "struct Uniforms {\n uReprojection:
13
13
  export const TEMPORALRESOLVE_BINDINGS = {
14
14
  "TEMPORAL_RESOLVE_FRAG": {
15
15
  "uniforms": 1,
16
- "uniformSize": 80,
16
+ "uniformSize": 112,
17
17
  "fields": {
18
- "uReprojection": {
18
+ "uDepthToViewZ": {
19
19
  "offset": 0,
20
+ "size": 16,
21
+ "type": "vec4"
22
+ },
23
+ "uAntiFlicker": {
24
+ "offset": 16,
25
+ "size": 4,
26
+ "type": "float"
27
+ },
28
+ "uReprojection": {
29
+ "offset": 32,
20
30
  "size": 64,
21
31
  "type": "mat4"
22
32
  },
23
33
  "uTexel": {
24
- "offset": 64,
34
+ "offset": 96,
25
35
  "size": 8,
26
36
  "type": "vec2"
27
37
  },
38
+ "uPeriodStart": {
39
+ "offset": 104,
40
+ "size": 4,
41
+ "type": "float"
42
+ },
28
43
  "uHistoryBlend": {
29
- "offset": 72,
44
+ "offset": 108,
30
45
  "size": 4,
31
46
  "type": "float"
32
47
  }
@@ -42,10 +57,20 @@ export const TEMPORALRESOLVE_BINDINGS = {
42
57
  "sampler": 35,
43
58
  "type": "sampler2D"
44
59
  },
45
- "uDepth": {
60
+ "uFlicker": {
46
61
  "texture": 36,
47
62
  "sampler": 37,
48
63
  "type": "sampler2D"
64
+ },
65
+ "uFlickerMotion": {
66
+ "texture": 38,
67
+ "sampler": 39,
68
+ "type": "sampler2D"
69
+ },
70
+ "uDepth": {
71
+ "texture": 40,
72
+ "sampler": 41,
73
+ "type": "sampler2D"
49
74
  }
50
75
  }
51
76
  }
@@ -27,7 +27,7 @@ export declare const RECON_HISTORY_FORMAT: GPUTextureFormat;
27
27
  * colour and the weight it has gathered — and `reconSharpenAt(ox, oy) -> vec3<f32>`, the picture
28
28
  * shown, which reads the history the first has already written.
29
29
  */
30
- export declare const RECON_RESOLVE_CORE_WGSL = "\nconst RECON_SAMPLE_SPREAD: f32 = 0.47;\nconst RECON_CONFIDENT_WEIGHT: f32 = 1.0;\n\nfn reconClampTexel(value: i32, size: i32) -> i32 {\n return clamp(value, 0, size - 1);\n}\n\n/* JavaScript's Math.round, which is not WGSL's round: a half goes up, not to even. */\nfn reconRound(value: f32) -> f32 {\n return floor(value + 0.5);\n}\n\n/* Written out rather than the built-in, whose result is undefined where the edges are equal. */\nfn reconSmoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {\n let t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n return t * t * (3.0 - 2.0 * t);\n}\n\nfn reconWeightAt(dx: f32, dy: f32) -> f32 {\n return exp(-(dx * dx + dy * dy) / (2.0 * RECON_SAMPLE_SPREAD * RECON_SAMPLE_SPREAD));\n}\n\nfn reconToYCoCg(c: vec3<f32>) -> vec3<f32> {\n return vec3<f32>(\n c.r * 0.25 + c.g * 0.5 + c.b * 0.25,\n c.r * 0.5 - c.b * 0.5,\n -c.r * 0.25 + c.g * 0.5 - c.b * 0.25,\n );\n}\n\nfn reconFromYCoCg(c: vec3<f32>) -> vec3<f32> {\n let base = c.x - c.z;\n return vec3<f32>(base + c.y, c.x + c.z, base - c.y);\n}\n\nstruct ReconBox {\n low: vec3<f32>,\n high: vec3<f32>,\n}\n\n/*\n * Each channel's mean plus or minus gamma of its standard deviation \u2014 and a channel whose nine\n * samples all agree is that value exactly, because a mean of nine equal floats is not always one\n * of them and a box a rounding wide lets a history through by a rounding.\n */\nfn reconVarianceBox(samples: array<vec3<f32>, 9>, gamma: f32) -> ReconBox {\n var s = samples;\n var box: ReconBox;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let first = s[0][c];\n var same = true;\n var sum = 0.0;\n for (var i = 0u; i < 9u; i = i + 1u) {\n let value = s[i][c];\n sum = sum + value;\n if (value != first) { same = false; }\n }\n if (same) {\n box.low[c] = first;\n box.high[c] = first;\n continue;\n }\n let mean = sum / 9.0;\n var spread = 0.0;\n for (var i = 0u; i < 9u; i = i + 1u) {\n let d = s[i][c] - mean;\n spread = spread + d * d;\n }\n let deviation = sqrt(spread / 9.0);\n box.low[c] = mean - gamma * deviation;\n box.high[c] = mean + gamma * deviation;\n }\n return box;\n}\n\n/*\n * The history pulled towards the box's centre until it is inside, by one ratio for all three\n * channels \u2014 a clamp per channel would land it on a corner and change its hue, which is coloured\n * fringing on every moving edge. A channel with no extent bounds anything that differs from it\n * infinitely, so such a history collapses onto the centre.\n */\nfn reconClipToBox(history: vec3<f32>, low: vec3<f32>, high: vec3<f32>) -> vec3<f32> {\n let centre = (low + high) * 0.5;\n var offset = history - centre;\n var extent = (high - low) * 0.5;\n var ratio = 0.0;\n var infinite = false;\n for (var c = 0u; c < 3u; c = c + 1u) {\n if (extent[c] > 1e-7) {\n ratio = max(ratio, abs(offset[c]) / extent[c]);\n } else if (abs(offset[c]) > 1e-7) {\n infinite = true;\n }\n }\n if (!infinite && !(ratio > 1.0)) { return history; }\n var scale = 0.0;\n if (!infinite) { scale = 1.0 / ratio; }\n return centre + offset * scale;\n}\n\nfn reconAccumulate(current: vec3<f32>, history: vec3<f32>, weight: f32, alpha: f32) -> vec3<f32> {\n let kept = clamp(weight, 0.0, 1.0) * (1.0 - clamp(alpha, 0.0, 1.0));\n return current * (1.0 - kept) + history * kept;\n}\n\nfn reconCatmullRom(t: f32) -> vec4<f32> {\n let t2 = t * t;\n let t3 = t2 * t;\n return vec4<f32>(\n -0.5 * t3 + t2 - 0.5 * t,\n 1.5 * t3 - 2.5 * t2 + 1.0,\n -1.5 * t3 + 2.0 * t2 + 0.5 * t,\n 0.5 * t3 - 0.5 * t2,\n );\n}\n\n/*\n * Five bilinear taps standing for the sixteen-texel Catmull-Rom: each axis's middle pair folded\n * into one tap placed between them, the corners dropped, the five weights renormalised. It is the\n * five-tap form because the taps are the expensive part \u2014 which is only true while the bilinear is\n * the sampler's, so the production module's accessor is a filtered sample and the check's is the\n * reference's own arithmetic over loaded texels.\n */\nstruct ReconTaps {\n at0: vec2<f32>,\n at1: vec2<f32>,\n at2: vec2<f32>,\n at3: vec2<f32>,\n at4: vec2<f32>,\n weights: array<f32, 5>,\n total: f32,\n}\n\nfn reconBicubicTaps(x: f32, y: f32) -> ReconTaps {\n let x1 = floor(x);\n let y1 = floor(y);\n let wx = reconCatmullRom(x - x1);\n let wy = reconCatmullRom(y - y1);\n let wx12 = wx[1] + wx[2];\n let wy12 = wy[1] + wy[2];\n let x12 = x1 + wx[2] / wx12;\n let y12 = y1 + wy[2] / wy12;\n var taps: ReconTaps;\n taps.at0 = vec2<f32>(x12, y1 - 1.0);\n taps.at1 = vec2<f32>(x1 - 1.0, y12);\n taps.at2 = vec2<f32>(x12, y12);\n taps.at3 = vec2<f32>(x1 + 2.0, y12);\n taps.at4 = vec2<f32>(x12, y1 + 2.0);\n taps.weights[0] = wx12 * wy[0];\n taps.weights[1] = wx[0] * wy12;\n taps.weights[2] = wx12 * wy12;\n taps.weights[3] = wx[3] * wy12;\n taps.weights[4] = wx12 * wy[3];\n taps.total =\n taps.weights[0] + taps.weights[1] + taps.weights[2] + taps.weights[3] + taps.weights[4];\n return taps;\n}\n\n/* A colour cannot be negative, and Catmull-Rom's negative lobes undershoot beside a bright edge. */\nfn reconSceneBicubic5(x: f32, y: f32) -> vec3<f32> {\n let taps = reconBicubicTaps(x, y);\n var sum = reconSceneBilinear(taps.at0) * taps.weights[0];\n sum = sum + reconSceneBilinear(taps.at1) * taps.weights[1];\n sum = sum + reconSceneBilinear(taps.at2) * taps.weights[2];\n sum = sum + reconSceneBilinear(taps.at3) * taps.weights[3];\n sum = sum + reconSceneBilinear(taps.at4) * taps.weights[4];\n return max(sum / taps.total, vec3<f32>(0.0));\n}\n\nfn reconHistoryBicubic5(x: f32, y: f32) -> vec4<f32> {\n let taps = reconBicubicTaps(x, y);\n var sum = reconHistoryBilinear(taps.at0) * taps.weights[0];\n sum = sum + reconHistoryBilinear(taps.at1) * taps.weights[1];\n sum = sum + reconHistoryBilinear(taps.at2) * taps.weights[2];\n sum = sum + reconHistoryBilinear(taps.at3) * taps.weights[3];\n sum = sum + reconHistoryBilinear(taps.at4) * taps.weights[4];\n return max(sum / taps.total, vec4<f32>(0.0));\n}\n\nstruct ReconWorld {\n ok: bool,\n position: vec3<f32>,\n}\n\n/* The world position a clip depth stands for, and nothing where the matrix has no answer there. */\nfn reconWorldFromDepth(m: mat4x4<f32>, u: f32, v: f32, clipDepth: f32) -> ReconWorld {\n var out: ReconWorld;\n out.position = vec3<f32>(0.0);\n out.ok = false;\n let x = u * 2.0 - 1.0;\n let y = v * 2.0 - 1.0;\n let w = m[0][3] * x + m[1][3] * y + m[2][3] * clipDepth + m[3][3];\n if (!(abs(w) > 1e-12)) { return out; }\n let p = m * vec4<f32>(x, y, clipDepth, 1.0);\n out.position = p.xyz / w;\n out.ok = all(abs(out.position) < vec3<f32>(3.4e38));\n return out;\n}\n\nstruct ReconNormal {\n ok: bool,\n normal: vec3<f32>,\n}\n\n/* Turned to face the eye, so the answer is the same whichever way the target's rows run. */\nfn reconNormalFrom(\n centre: vec3<f32>,\n right: vec3<f32>,\n up: vec3<f32>,\n eye: vec3<f32>,\n) -> ReconNormal {\n var out: ReconNormal;\n out.normal = vec3<f32>(0.0);\n out.ok = false;\n let a = right - centre;\n let b = up - centre;\n let n = cross(a, b);\n let length2 = dot(n, n);\n let scale2 = dot(a, a) * dot(b, b);\n if (!(length2 > 1e-12 * scale2)) { return out; }\n let toEye = dot(n, eye - centre);\n var sign = 1.0;\n if (toEye < 0.0) { sign = -1.0; }\n out.normal = n * (sign / sqrt(length2));\n out.ok = true;\n return out;\n}\n\nfn reconDisocclusion(\n currDepth: f32,\n histDepth: f32,\n motionLength: f32,\n normalDot: f32,\n) -> f32 {\n if (!(currDepth > 0.0) || !(histDepth > 0.0) || !(motionLength >= 0.0)) { return 0.0; }\n let disagreement = abs(currDepth - histDepth) / currDepth;\n let tolerance = recon.depthTolerance + recon.motionScale * motionLength;\n let depth = 1.0 - reconSmoothstep(tolerance, 2.0 * tolerance, disagreement);\n let normal = reconSmoothstep(recon.normalFloor, recon.normalCeiling, normalDot);\n return depth * normal;\n}\n\nstruct ReconAt {\n depth: f32,\n position: vec3<f32>,\n}\n\n/* A texel's world position at its unjittered uv, and the view depth it stands at, or zero. */\nfn reconWorldAt(previous: bool, x: i32, y: i32) -> ReconAt {\n let w = i32(recon.renderSize.x);\n let h = i32(recon.renderSize.y);\n let tx = reconClampTexel(x, w);\n let ty = reconClampTexel(y, h);\n var jitter = recon.jitter;\n var m = recon.inverseViewProj;\n var z = reconDepth(tx, ty);\n if (previous) {\n jitter = recon.previousJitter;\n m = recon.previousInverseViewProj;\n z = reconPreviousDepth(tx, ty);\n }\n let u = (f32(tx) + 0.5 - jitter.x) / f32(w);\n let v = (f32(ty) + 0.5 - jitter.y) / f32(h);\n var out: ReconAt;\n out.depth = 0.0;\n out.position = vec3<f32>(0.0);\n let world = reconWorldFromDepth(m, u, v, z);\n if (!world.ok) { return out; }\n out.position = world.position;\n /* The view depth is the clip w, which the inverse's w is the reciprocal of. */\n let cw = m[0][3] * (u * 2.0 - 1.0) + m[1][3] * (v * 2.0 - 1.0) + m[2][3] * z + m[3][3];\n out.depth = 1.0 / cw;\n return out;\n}\n\n/* The neighbour on the inside of the border, so the edge texels have a normal too. */\nfn reconNormalAt(previous: bool, x: i32, y: i32) -> ReconNormal {\n var out: ReconNormal;\n out.normal = vec3<f32>(0.0);\n out.ok = false;\n let w = i32(recon.renderSize.x);\n let h = i32(recon.renderSize.y);\n let centre = reconWorldAt(previous, x, y);\n if (!(centre.depth > 0.0)) { return out; }\n var dx = -1;\n if (x + 1 < w) { dx = 1; }\n var dy = -1;\n if (y + 1 < h) { dy = 1; }\n let right = reconWorldAt(previous, x + dx, y);\n if (!(right.depth > 0.0)) { return out; }\n let up = reconWorldAt(previous, x, y + dy);\n if (!(up.depth > 0.0)) { return out; }\n var eye = recon.eye.xyz;\n if (previous) { eye = recon.previousEye.xyz; }\n return reconNormalFrom(centre.position, right.position, up.position, eye);\n}\n\nstruct ReconHistory {\n weight: f32,\n colour: vec4<f32>,\n}\n\n/*\n * The weight a pixel's history still deserves: none on the first frame, none where the surface was\n * behind last frame's eye or off its picture, and otherwise what it gathered, scaled by whether it\n * is the same surface.\n */\nfn reconHistoryWeight(u: f32, v: f32, dx: i32, dy: i32) -> ReconHistory {\n var out: ReconHistory;\n out.weight = 0.0;\n out.colour = vec4<f32>(0.0);\n if (recon.hasHistory == 0u) { return out; }\n let rw = f32(recon.renderSize.x);\n let rh = f32(recon.renderSize.y);\n let motion = reconMotion(dx, dy);\n var mu = 0.0;\n var mv = 0.0;\n var expected = 0.0;\n if (motion.w > 0.5) {\n mu = motion.x;\n mv = motion.y;\n expected = motion.z;\n } else {\n let here = reconWorldAt(false, dx, dy);\n if (!(here.depth > 0.0)) { return out; }\n let clip = recon.previousViewProj * vec4<f32>(here.position, 1.0);\n /*\n * A surface behind last frame's eye has a w that is not positive: the disocclusion trusts no\n * such depth, so it needs no refusal of its own here.\n */\n expected = clip.w;\n let du = (f32(dx) + 0.5 - recon.jitter.x) / rw;\n let dv = (f32(dy) + 0.5 - recon.jitter.y) / rh;\n mu = (clip.x / expected) * 0.5 + 0.5 - du;\n mv = (clip.y / expected) * 0.5 + 0.5 - dv;\n }\n\n let pu = u + mu;\n let pv = v + mv;\n if (!(pu >= 0.0 && pu <= 1.0 && pv >= 0.0 && pv <= 1.0)) { return out; }\n out.colour = reconHistoryBicubic5(\n pu * f32(recon.outputSize.x) - 0.5,\n pv * f32(recon.outputSize.y) - 0.5,\n );\n let px = reconClampTexel(i32(reconRound(pu * rw + recon.previousJitter.x - 0.5)), i32(rw));\n let py = reconClampTexel(i32(reconRound(pv * rh + recon.previousJitter.y - 0.5)), i32(rh));\n let held = reconWorldAt(true, px, py).depth;\n var normalDot = 1.0;\n let here = reconNormalAt(false, dx, dy);\n let there = reconNormalAt(true, px, py);\n if (here.ok && there.ok) { normalDot = dot(here.normal, there.normal); }\n let trust = reconDisocclusion(expected, held, length(vec2<f32>(mu, mv)), normalDot);\n out.weight = out.colour.w * trust;\n return out;\n}\n\n/* One output pixel: the history's next colour, before sharpening, and the weight it has gathered. */\nfn reconResolvePixel(ox: i32, oy: i32) -> vec4<f32> {\n let rw = i32(recon.renderSize.x);\n let rh = i32(recon.renderSize.y);\n let ow = i32(recon.outputSize.x);\n let oh = i32(recon.outputSize.y);\n let u = (f32(ox) + 0.5) / f32(ow);\n let v = (f32(oy) + 0.5) / f32(oh);\n let jx = recon.jitter.x;\n let jy = recon.jitter.y;\n\n /* Where this frame observed the pixel, and the render read there for the gaps. */\n let rx = u * f32(rw) + jx - 0.5;\n let ry = v * f32(rh) + jy - 0.5;\n let sample = reconSceneBicubic5(rx, ry);\n let nx = reconClampTexel(i32(reconRound(rx)), rw);\n let ny = reconClampTexel(i32(reconRound(ry)), rh);\n\n /* Each texel around: its weight, its colour in the box, and its depth. */\n var nearest = reconDepth(nx, ny);\n var dx = nx;\n var dy = ny;\n var gathered = 0.0;\n var accum = vec3<f32>(0.0);\n var neighbours: array<vec3<f32>, 9>;\n for (var j = -1; j <= 1; j = j + 1) {\n for (var i = -1; i <= 1; i = i + 1) {\n let tx = reconClampTexel(nx + i, rw);\n let ty = reconClampTexel(ny + j, rh);\n let colour = reconScene(tx, ty);\n let weight = reconWeightAt(\n ((f32(tx) + 0.5 - jx) / f32(rw)) * f32(ow) - (f32(ox) + 0.5),\n ((f32(ty) + 0.5 - jy) / f32(rh)) * f32(oh) - (f32(oy) + 0.5),\n );\n gathered = gathered + weight;\n accum = accum + colour * weight;\n neighbours[(j + 1) * 3 + (i + 1)] = reconToYCoCg(colour);\n let z = reconDepth(tx, ty);\n if (z < nearest) {\n nearest = z;\n dx = tx;\n dy = ty;\n }\n }\n }\n let box = reconVarianceBox(neighbours, recon.clampGamma);\n accum = accum / gathered;\n\n /* The history, and the weight it still deserves; a tenth of each frame is always new. */\n let cap = (1.0 - recon.alpha) / recon.alpha;\n let history = reconHistoryWeight(u, v, dx, dy);\n let kept = min(cap, history.weight);\n\n let total = kept + gathered;\n var mixed = accum;\n if (kept > 0.0) {\n let clipped = reconClipToBox(reconToYCoCg(history.colour.xyz), box.low, box.high);\n mixed = reconAccumulate(accum, reconFromYCoCg(clipped), 1.0, gathered / total);\n }\n let known = min(1.0, total / RECON_CONFIDENT_WEIGHT);\n return vec4<f32>(sample * (1.0 - known) + mixed * known, total);\n}\n\n/*\n * The centre pushed away from its four neighbours' mean, and held inside the range of the five \u2014\n * the halo a plain unsharp mask draws beside every edge is a value outside that range.\n */\nfn reconSharpenAt(ox: i32, oy: i32) -> vec3<f32> {\n let ow = i32(recon.outputSize.x);\n let oh = i32(recon.outputSize.y);\n let centre = reconHistory(reconClampTexel(ox, ow), reconClampTexel(oy, oh)).xyz;\n var offsets = array<vec2<i32>, 4>(\n vec2<i32>(-1, 0),\n vec2<i32>(1, 0),\n vec2<i32>(0, -1),\n vec2<i32>(0, 1),\n );\n var low = centre;\n var high = centre;\n var sum = vec3<f32>(0.0);\n for (var n = 0; n < 4; n = n + 1) {\n let at = offsets[n];\n let value = reconHistory(\n reconClampTexel(ox + at.x, ow),\n reconClampTexel(oy + at.y, oh),\n ).xyz;\n sum = sum + value;\n low = min(low, value);\n high = max(high, value);\n }\n let pushed = centre + recon.sharpenAmount * (centre - sum / 4.0);\n return min(high, max(low, pushed));\n}\n";
30
+ export declare const RECON_RESOLVE_CORE_WGSL = "\nconst RECON_SAMPLE_SPREAD: f32 = 0.47;\nconst RECON_CONFIDENT_WEIGHT: f32 = 1.0;\nconst RECON_PLANAR_FROM: f32 = 0.25;\nconst RECON_PLANAR_TO: f32 = 0.5;\n\nfn reconClampTexel(value: i32, size: i32) -> i32 {\n return clamp(value, 0, size - 1);\n}\n\n/* JavaScript's Math.round, which is not WGSL's round: a half goes up, not to even. */\nfn reconRound(value: f32) -> f32 {\n return floor(value + 0.5);\n}\n\n/* Written out rather than the built-in, whose result is undefined where the edges are equal. */\nfn reconSmoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {\n let t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n return t * t * (3.0 - 2.0 * t);\n}\n\nfn reconWeightAt(dx: f32, dy: f32) -> f32 {\n return exp(-(dx * dx + dy * dy) / (2.0 * RECON_SAMPLE_SPREAD * RECON_SAMPLE_SPREAD));\n}\n\nfn reconToYCoCg(c: vec3<f32>) -> vec3<f32> {\n return vec3<f32>(\n c.r * 0.25 + c.g * 0.5 + c.b * 0.25,\n c.r * 0.5 - c.b * 0.5,\n -c.r * 0.25 + c.g * 0.5 - c.b * 0.25,\n );\n}\n\nfn reconFromYCoCg(c: vec3<f32>) -> vec3<f32> {\n let base = c.x - c.z;\n return vec3<f32>(base + c.y, c.x + c.z, base - c.y);\n}\n\nstruct ReconBox {\n low: vec3<f32>,\n high: vec3<f32>,\n}\n\n/*\n * Each channel's mean plus or minus gamma of its standard deviation \u2014 and a channel whose nine\n * samples all agree is that value exactly, because a mean of nine equal floats is not always one\n * of them and a box a rounding wide lets a history through by a rounding.\n */\nfn reconVarianceBox(samples: array<vec3<f32>, 9>, gamma: f32) -> ReconBox {\n var s = samples;\n var box: ReconBox;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let first = s[0][c];\n var same = true;\n var sum = 0.0;\n for (var i = 0u; i < 9u; i = i + 1u) {\n let value = s[i][c];\n sum = sum + value;\n if (value != first) { same = false; }\n }\n if (same) {\n box.low[c] = first;\n box.high[c] = first;\n continue;\n }\n let mean = sum / 9.0;\n var spread = 0.0;\n for (var i = 0u; i < 9u; i = i + 1u) {\n let d = s[i][c] - mean;\n spread = spread + d * d;\n }\n let deviation = sqrt(spread / 9.0);\n box.low[c] = mean - gamma * deviation;\n box.high[c] = mean + gamma * deviation;\n }\n return box;\n}\n\n/*\n * The history pulled towards the box's centre until it is inside, by one ratio for all three\n * channels \u2014 a clamp per channel would land it on a corner and change its hue, which is coloured\n * fringing on every moving edge. A channel with no extent bounds anything that differs from it\n * infinitely, so such a history collapses onto the centre.\n */\nfn reconClipToBox(history: vec3<f32>, low: vec3<f32>, high: vec3<f32>) -> vec3<f32> {\n let centre = (low + high) * 0.5;\n var offset = history - centre;\n var extent = (high - low) * 0.5;\n var ratio = 0.0;\n var infinite = false;\n for (var c = 0u; c < 3u; c = c + 1u) {\n if (extent[c] > 1e-7) {\n ratio = max(ratio, abs(offset[c]) / extent[c]);\n } else if (abs(offset[c]) > 1e-7) {\n infinite = true;\n }\n }\n if (!infinite && !(ratio > 1.0)) { return history; }\n var scale = 0.0;\n if (!infinite) { scale = 1.0 / ratio; }\n return centre + offset * scale;\n}\n\nfn reconAccumulate(current: vec3<f32>, history: vec3<f32>, weight: f32, alpha: f32) -> vec3<f32> {\n let kept = clamp(weight, 0.0, 1.0) * (1.0 - clamp(alpha, 0.0, 1.0));\n return current * (1.0 - kept) + history * kept;\n}\n\nfn reconCatmullRom(t: f32) -> vec4<f32> {\n let t2 = t * t;\n let t3 = t2 * t;\n return vec4<f32>(\n -0.5 * t3 + t2 - 0.5 * t,\n 1.5 * t3 - 2.5 * t2 + 1.0,\n -1.5 * t3 + 2.0 * t2 + 0.5 * t,\n 0.5 * t3 - 0.5 * t2,\n );\n}\n\n/*\n * Five bilinear taps standing for the sixteen-texel Catmull-Rom: each axis's middle pair folded\n * into one tap placed between them, the corners dropped, the five weights renormalised. It is the\n * five-tap form because the taps are the expensive part \u2014 which is only true while the bilinear is\n * the sampler's, so the production module's accessor is a filtered sample and the check's is the\n * reference's own arithmetic over loaded texels.\n */\nstruct ReconTaps {\n at0: vec2<f32>,\n at1: vec2<f32>,\n at2: vec2<f32>,\n at3: vec2<f32>,\n at4: vec2<f32>,\n weights: array<f32, 5>,\n total: f32,\n}\n\nfn reconBicubicTaps(x: f32, y: f32) -> ReconTaps {\n let x1 = floor(x);\n let y1 = floor(y);\n let wx = reconCatmullRom(x - x1);\n let wy = reconCatmullRom(y - y1);\n let wx12 = wx[1] + wx[2];\n let wy12 = wy[1] + wy[2];\n let x12 = x1 + wx[2] / wx12;\n let y12 = y1 + wy[2] / wy12;\n var taps: ReconTaps;\n taps.at0 = vec2<f32>(x12, y1 - 1.0);\n taps.at1 = vec2<f32>(x1 - 1.0, y12);\n taps.at2 = vec2<f32>(x12, y12);\n taps.at3 = vec2<f32>(x1 + 2.0, y12);\n taps.at4 = vec2<f32>(x12, y1 + 2.0);\n taps.weights[0] = wx12 * wy[0];\n taps.weights[1] = wx[0] * wy12;\n taps.weights[2] = wx12 * wy12;\n taps.weights[3] = wx[3] * wy12;\n taps.weights[4] = wx12 * wy[3];\n taps.total =\n taps.weights[0] + taps.weights[1] + taps.weights[2] + taps.weights[3] + taps.weights[4];\n return taps;\n}\n\n/* A colour cannot be negative, and Catmull-Rom's negative lobes undershoot beside a bright edge. */\nfn reconSceneBicubic5(x: f32, y: f32) -> vec3<f32> {\n let taps = reconBicubicTaps(x, y);\n var sum = reconSceneBilinear(taps.at0) * taps.weights[0];\n sum = sum + reconSceneBilinear(taps.at1) * taps.weights[1];\n sum = sum + reconSceneBilinear(taps.at2) * taps.weights[2];\n sum = sum + reconSceneBilinear(taps.at3) * taps.weights[3];\n sum = sum + reconSceneBilinear(taps.at4) * taps.weights[4];\n return max(sum / taps.total, vec3<f32>(0.0));\n}\n\nfn reconHistoryBicubic5(x: f32, y: f32) -> vec4<f32> {\n let taps = reconBicubicTaps(x, y);\n var sum = reconHistoryBilinear(taps.at0) * taps.weights[0];\n sum = sum + reconHistoryBilinear(taps.at1) * taps.weights[1];\n sum = sum + reconHistoryBilinear(taps.at2) * taps.weights[2];\n sum = sum + reconHistoryBilinear(taps.at3) * taps.weights[3];\n sum = sum + reconHistoryBilinear(taps.at4) * taps.weights[4];\n return max(sum / taps.total, vec4<f32>(0.0));\n}\n\nstruct ReconWorld {\n ok: bool,\n position: vec3<f32>,\n}\n\n/* The world position a clip depth stands for, and nothing where the matrix has no answer there. */\nfn reconWorldFromDepth(m: mat4x4<f32>, u: f32, v: f32, clipDepth: f32) -> ReconWorld {\n var out: ReconWorld;\n out.position = vec3<f32>(0.0);\n out.ok = false;\n let x = u * 2.0 - 1.0;\n let y = v * 2.0 - 1.0;\n let w = m[0][3] * x + m[1][3] * y + m[2][3] * clipDepth + m[3][3];\n if (!(abs(w) > 1e-12)) { return out; }\n let p = m * vec4<f32>(x, y, clipDepth, 1.0);\n out.position = p.xyz / w;\n out.ok = all(abs(out.position) < vec3<f32>(3.4e38));\n return out;\n}\n\nstruct ReconNormal {\n ok: bool,\n normal: vec3<f32>,\n}\n\n/* Turned to face the eye, so the answer is the same whichever way the target's rows run. */\nfn reconNormalFrom(\n centre: vec3<f32>,\n right: vec3<f32>,\n up: vec3<f32>,\n eye: vec3<f32>,\n) -> ReconNormal {\n var out: ReconNormal;\n out.normal = vec3<f32>(0.0);\n out.ok = false;\n let a = right - centre;\n let b = up - centre;\n let n = cross(a, b);\n let length2 = dot(n, n);\n let scale2 = dot(a, a) * dot(b, b);\n if (!(length2 > 1e-12 * scale2)) { return out; }\n let toEye = dot(n, eye - centre);\n var sign = 1.0;\n if (toEye < 0.0) { sign = -1.0; }\n out.normal = n * (sign / sqrt(length2));\n out.ok = true;\n return out;\n}\n\nfn reconDisocclusion(\n currDepth: f32,\n histDepth: f32,\n motionLength: f32,\n normalDot: f32,\n slope: f32,\n) -> f32 {\n if (!(currDepth > 0.0) || !(histDepth > 0.0) || !(motionLength >= 0.0)) { return 0.0; }\n let disagreement = abs(currDepth - histDepth) / currDepth;\n let tolerance = recon.depthTolerance + recon.motionScale * motionLength + slope;\n let depth = 1.0 - reconSmoothstep(tolerance, 2.0 * tolerance, disagreement);\n let normal = reconSmoothstep(recon.normalFloor, recon.normalCeiling, normalDot);\n return depth * normal;\n}\n\nstruct ReconAt {\n depth: f32,\n position: vec3<f32>,\n}\n\n/* A texel's world position at its unjittered uv, and the view depth it stands at, or zero. */\nfn reconWorldAt(previous: bool, x: i32, y: i32) -> ReconAt {\n let w = i32(recon.renderSize.x);\n let h = i32(recon.renderSize.y);\n let tx = reconClampTexel(x, w);\n let ty = reconClampTexel(y, h);\n var jitter = recon.jitter;\n var m = recon.inverseViewProj;\n var z = reconDepth(tx, ty);\n if (previous) {\n jitter = recon.previousJitter;\n m = recon.previousInverseViewProj;\n z = reconPreviousDepth(tx, ty);\n }\n let u = (f32(tx) + 0.5 - jitter.x) / f32(w);\n let v = (f32(ty) + 0.5 - jitter.y) / f32(h);\n var out: ReconAt;\n out.depth = 0.0;\n out.position = vec3<f32>(0.0);\n let world = reconWorldFromDepth(m, u, v, z);\n if (!world.ok) { return out; }\n out.position = world.position;\n /* The view depth is the clip w, which the inverse's w is the reciprocal of. */\n let cw = m[0][3] * (u * 2.0 - 1.0) + m[1][3] * (v * 2.0 - 1.0) + m[2][3] * z + m[3][3];\n out.depth = 1.0 / cw;\n return out;\n}\n\n/* The neighbour on the inside of the border, so the edge texels have a normal too. */\nfn reconNormalAt(previous: bool, x: i32, y: i32) -> ReconNormal {\n var out: ReconNormal;\n out.normal = vec3<f32>(0.0);\n out.ok = false;\n let w = i32(recon.renderSize.x);\n let h = i32(recon.renderSize.y);\n let centre = reconWorldAt(previous, x, y);\n if (!(centre.depth > 0.0)) { return out; }\n var dx = -1;\n if (x + 1 < w) { dx = 1; }\n var dy = -1;\n if (y + 1 < h) { dy = 1; }\n let right = reconWorldAt(previous, x + dx, y);\n if (!(right.depth > 0.0)) { return out; }\n let up = reconWorldAt(previous, x, y + dy);\n if (!(up.depth > 0.0)) { return out; }\n var eye = recon.eye.xyz;\n if (previous) { eye = recon.previousEye.xyz; }\n return reconNormalFrom(centre.position, right.position, up.position, eye);\n}\n\n/* resolve.ts's viewDepthAt. */\nfn reconViewDepthAt(previous: bool, x: i32, y: i32) -> f32 {\n let w = i32(recon.renderSize.x);\n let h = i32(recon.renderSize.y);\n if (x < 0 || y < 0 || x >= w || y >= h) { return 0.0; }\n var jitter = recon.jitter;\n var m = recon.inverseViewProj;\n var z = 0.0;\n if (previous) {\n jitter = recon.previousJitter;\n m = recon.previousInverseViewProj;\n z = reconPreviousDepth(x, y);\n } else {\n z = reconDepth(x, y);\n }\n let u = (f32(x) + 0.5 - jitter.x) / f32(w);\n let v = (f32(y) + 0.5 - jitter.y) / f32(h);\n let cw = m[0][3] * (u * 2.0 - 1.0) + m[1][3] * (v * 2.0 - 1.0) + m[2][3] * z + m[3][3];\n if (!(abs(cw) > 1e-12)) { return 0.0; }\n return 1.0 / cw;\n}\n\n/* disocclusion.ts's planarity. */\nfn reconPlanarity(before: f32, centre: f32, after: f32) -> f32 {\n if (!(before > 0.0) || !(centre > 0.0) || !(after > 0.0)) { return 1.0; }\n let a = 1.0 / after - 1.0 / centre;\n let b = 1.0 / before - 1.0 / centre;\n let spread = abs(a) + abs(b);\n if (!(spread > 0.0)) { return 1.0; }\n return 1.0 - reconSmoothstep(RECON_PLANAR_FROM, RECON_PLANAR_TO, abs(a + b) / spread);\n}\n\n/* disocclusion.ts's surfaceSlope. */\nfn reconSurfaceSlope(before: f32, centre: f32, after: f32) -> f32 {\n let hasAfter = after > 0.0;\n let hasBefore = before > 0.0;\n if (!hasAfter) {\n if (!hasBefore) { return 0.0; }\n return abs(before - centre) / centre;\n }\n if (!hasBefore) { return abs(after - centre) / centre; }\n let a = after - centre;\n let b = before - centre;\n if (a * b > 0.0) { return 0.0; }\n return min(abs(a), abs(b)) / centre;\n}\n\nstruct ReconSurface {\n planar: f32,\n slope: f32,\n}\n\n/* resolve.ts's surfaceAt. */\nfn reconSurfaceAt(previous: bool, x: i32, y: i32, centre: f32) -> ReconSurface {\n var out: ReconSurface;\n out.planar = 1.0;\n out.slope = 0.0;\n for (var axis = 0; axis < 2; axis = axis + 1) {\n let ax = 1 - axis;\n let ay = axis;\n let after = reconViewDepthAt(previous, x + ax, y + ay);\n let before = reconViewDepthAt(previous, x - ax, y - ay);\n out.planar = out.planar * reconPlanarity(before, centre, after);\n out.slope = max(out.slope, reconSurfaceSlope(before, centre, after));\n }\n return out;\n}\n\nstruct ReconHistory {\n weight: f32,\n colour: vec4<f32>,\n}\n\n/*\n * The weight a pixel's history still deserves: none on the first frame, none where the surface was\n * behind last frame's eye or off its picture, and otherwise what it gathered, scaled by whether it\n * is the same surface.\n */\nfn reconHistoryWeight(u: f32, v: f32, dx: i32, dy: i32) -> ReconHistory {\n var out: ReconHistory;\n out.weight = 0.0;\n out.colour = vec4<f32>(0.0);\n if (recon.hasHistory == 0u) { return out; }\n let rw = f32(recon.renderSize.x);\n let rh = f32(recon.renderSize.y);\n let motion = reconMotion(dx, dy);\n var mu = 0.0;\n var mv = 0.0;\n var expected = 0.0;\n if (motion.w > 0.5) {\n mu = motion.x;\n mv = motion.y;\n expected = motion.z;\n } else {\n let here = reconWorldAt(false, dx, dy);\n if (!(here.depth > 0.0)) { return out; }\n let clip = recon.previousViewProj * vec4<f32>(here.position, 1.0);\n /*\n * A surface behind last frame's eye has a w that is not positive: the disocclusion trusts no\n * such depth, so it needs no refusal of its own here.\n */\n expected = clip.w;\n let du = (f32(dx) + 0.5 - recon.jitter.x) / rw;\n let dv = (f32(dy) + 0.5 - recon.jitter.y) / rh;\n mu = (clip.x / expected) * 0.5 + 0.5 - du;\n mv = (clip.y / expected) * 0.5 + 0.5 - dv;\n }\n\n let pu = u + mu;\n let pv = v + mv;\n if (!(pu >= 0.0 && pu <= 1.0 && pv >= 0.0 && pv <= 1.0)) { return out; }\n out.colour = reconHistoryBicubic5(\n pu * f32(recon.outputSize.x) - 0.5,\n pv * f32(recon.outputSize.y) - 0.5,\n );\n /* Last frame's depth, searched around where the dilated texel was: see resolve.ts. */\n let su = (f32(dx) + 0.5 - recon.jitter.x) / rw + mu;\n let sv = (f32(dy) + 0.5 - recon.jitter.y) / rh + mv;\n let cx = reconClampTexel(i32(reconRound(su * rw + recon.previousJitter.x - 0.5)), i32(rw));\n let cy = reconClampTexel(i32(reconRound(sv * rh + recon.previousJitter.y - 0.5)), i32(rh));\n var held = reconViewDepthAt(true, cx, cy);\n var px = cx;\n var py = cy;\n if (!(held > 0.0 && abs(held - expected) <= recon.depthTolerance * expected)) {\n held = 0.0;\n var nearest = 3.4e38;\n for (var j = -1; j <= 1; j = j + 1) {\n for (var i = -1; i <= 1; i = i + 1) {\n let tx = reconClampTexel(cx + i, i32(rw));\n let ty = reconClampTexel(cy + j, i32(rh));\n let depth = reconViewDepthAt(true, tx, ty);\n if (!(depth > 0.0)) { continue; }\n let off = abs(depth - expected);\n if (off < nearest) {\n nearest = off;\n held = depth;\n px = tx;\n py = ty;\n }\n }\n }\n }\n\n let here = reconSurfaceAt(false, dx, dy, reconViewDepthAt(false, dx, dy));\n var planar = here.planar;\n if (planar > 0.0) { planar = planar * reconSurfaceAt(true, px, py, held).planar; }\n var normalDot = 1.0;\n if (planar > 0.0) {\n let hereNormal = reconNormalAt(false, dx, dy);\n let thereNormal = reconNormalAt(true, px, py);\n if (hereNormal.ok && thereNormal.ok) {\n normalDot = 1.0 - planar * (1.0 - dot(hereNormal.normal, thereNormal.normal));\n }\n }\n let trust = reconDisocclusion(expected, held, length(vec2<f32>(mu, mv)), normalDot, here.slope);\n out.weight = out.colour.w * trust;\n return out;\n}\n\n/* One output pixel: the history's next colour, before sharpening, and the weight it has gathered. */\nfn reconResolvePixel(ox: i32, oy: i32) -> vec4<f32> {\n let rw = i32(recon.renderSize.x);\n let rh = i32(recon.renderSize.y);\n let ow = i32(recon.outputSize.x);\n let oh = i32(recon.outputSize.y);\n let u = (f32(ox) + 0.5) / f32(ow);\n let v = (f32(oy) + 0.5) / f32(oh);\n let jx = recon.jitter.x;\n let jy = recon.jitter.y;\n\n /* Where this frame observed the pixel, and the render read there for the gaps. */\n let rx = u * f32(rw) + jx - 0.5;\n let ry = v * f32(rh) + jy - 0.5;\n let sample = reconSceneBicubic5(rx, ry);\n let nx = reconClampTexel(i32(reconRound(rx)), rw);\n let ny = reconClampTexel(i32(reconRound(ry)), rh);\n\n /* Each texel around: its weight, its colour in the box, and its depth. */\n var nearest = reconDepth(nx, ny);\n var dx = nx;\n var dy = ny;\n var gathered = 0.0;\n var accum = vec3<f32>(0.0);\n var neighbours: array<vec3<f32>, 9>;\n for (var j = -1; j <= 1; j = j + 1) {\n for (var i = -1; i <= 1; i = i + 1) {\n let tx = reconClampTexel(nx + i, rw);\n let ty = reconClampTexel(ny + j, rh);\n let colour = reconScene(tx, ty);\n let weight = reconWeightAt(\n ((f32(tx) + 0.5 - jx) / f32(rw)) * f32(ow) - (f32(ox) + 0.5),\n ((f32(ty) + 0.5 - jy) / f32(rh)) * f32(oh) - (f32(oy) + 0.5),\n );\n gathered = gathered + weight;\n accum = accum + colour * weight;\n neighbours[(j + 1) * 3 + (i + 1)] = reconToYCoCg(colour);\n let z = reconDepth(tx, ty);\n if (z < nearest) {\n nearest = z;\n dx = tx;\n dy = ty;\n }\n }\n }\n let box = reconVarianceBox(neighbours, recon.clampGamma);\n accum = accum / gathered;\n\n /* The history, and the weight it still deserves; a tenth of each frame is always new. */\n let cap = (1.0 - recon.alpha) / recon.alpha;\n let history = reconHistoryWeight(u, v, dx, dy);\n let kept = min(cap, history.weight);\n\n let total = kept + gathered;\n var mixed = accum;\n if (kept > 0.0) {\n let clipped = reconClipToBox(reconToYCoCg(history.colour.xyz), box.low, box.high);\n mixed = reconAccumulate(accum, reconFromYCoCg(clipped), 1.0, gathered / total);\n }\n let known = min(1.0, total / RECON_CONFIDENT_WEIGHT);\n return vec4<f32>(sample * (1.0 - known) + mixed * known, total);\n}\n\n/*\n * The centre pushed away from its four neighbours' mean, and held inside the range of the five \u2014\n * the halo a plain unsharp mask draws beside every edge is a value outside that range.\n */\nfn reconSharpenAt(ox: i32, oy: i32) -> vec3<f32> {\n let ow = i32(recon.outputSize.x);\n let oh = i32(recon.outputSize.y);\n let centre = reconHistory(reconClampTexel(ox, ow), reconClampTexel(oy, oh)).xyz;\n var offsets = array<vec2<i32>, 4>(\n vec2<i32>(-1, 0),\n vec2<i32>(1, 0),\n vec2<i32>(0, -1),\n vec2<i32>(0, 1),\n );\n var low = centre;\n var high = centre;\n var sum = vec3<f32>(0.0);\n for (var n = 0; n < 4; n = n + 1) {\n let at = offsets[n];\n let value = reconHistory(\n reconClampTexel(ox + at.x, ow),\n reconClampTexel(oy + at.y, oh),\n ).xyz;\n sum = sum + value;\n low = min(low, value);\n high = max(high, value);\n }\n let pushed = centre + recon.sharpenAmount * (centre - sum / 4.0);\n return min(high, max(low, pushed));\n}\n";
31
31
  /**
32
32
  * The parity module: the core over plain float textures written in the reference's own row order,
33
33
  * doing its own bilinear so the comparison is arithmetic rather than a tolerance on the device's
@@ -39,6 +39,7 @@
39
39
  * and the uniform `recon: ReconParams`, whose struct this module declares.
40
40
  */
41
41
  import { wgslSceneDepthToNdc } from '../../depthConvention.js';
42
+ import { PLANAR_FROM, PLANAR_TO } from '../../recon/disocclusion.js';
42
43
  /**
43
44
  * The parameters one resolve reads, as `ResolveFrame` minus its images.
44
45
  *
@@ -91,6 +92,8 @@ export const RECON_HISTORY_FORMAT = 'rgba16float';
91
92
  export const RECON_RESOLVE_CORE_WGSL = /* wgsl */ `
92
93
  const RECON_SAMPLE_SPREAD: f32 = ${RECON_SAMPLE_SPREAD};
93
94
  const RECON_CONFIDENT_WEIGHT: f32 = ${RECON_CONFIDENT_WEIGHT}.0;
95
+ const RECON_PLANAR_FROM: f32 = ${PLANAR_FROM};
96
+ const RECON_PLANAR_TO: f32 = ${PLANAR_TO};
94
97
 
95
98
  fn reconClampTexel(value: i32, size: i32) -> i32 {
96
99
  return clamp(value, 0, size - 1);
@@ -322,10 +325,11 @@ fn reconDisocclusion(
322
325
  histDepth: f32,
323
326
  motionLength: f32,
324
327
  normalDot: f32,
328
+ slope: f32,
325
329
  ) -> f32 {
326
330
  if (!(currDepth > 0.0) || !(histDepth > 0.0) || !(motionLength >= 0.0)) { return 0.0; }
327
331
  let disagreement = abs(currDepth - histDepth) / currDepth;
328
- let tolerance = recon.depthTolerance + recon.motionScale * motionLength;
332
+ let tolerance = recon.depthTolerance + recon.motionScale * motionLength + slope;
329
333
  let depth = 1.0 - reconSmoothstep(tolerance, 2.0 * tolerance, disagreement);
330
334
  let normal = reconSmoothstep(recon.normalFloor, recon.normalCeiling, normalDot);
331
335
  return depth * normal;
@@ -386,6 +390,74 @@ fn reconNormalAt(previous: bool, x: i32, y: i32) -> ReconNormal {
386
390
  return reconNormalFrom(centre.position, right.position, up.position, eye);
387
391
  }
388
392
 
393
+ /* resolve.ts's viewDepthAt. */
394
+ fn reconViewDepthAt(previous: bool, x: i32, y: i32) -> f32 {
395
+ let w = i32(recon.renderSize.x);
396
+ let h = i32(recon.renderSize.y);
397
+ if (x < 0 || y < 0 || x >= w || y >= h) { return 0.0; }
398
+ var jitter = recon.jitter;
399
+ var m = recon.inverseViewProj;
400
+ var z = 0.0;
401
+ if (previous) {
402
+ jitter = recon.previousJitter;
403
+ m = recon.previousInverseViewProj;
404
+ z = reconPreviousDepth(x, y);
405
+ } else {
406
+ z = reconDepth(x, y);
407
+ }
408
+ let u = (f32(x) + 0.5 - jitter.x) / f32(w);
409
+ let v = (f32(y) + 0.5 - jitter.y) / f32(h);
410
+ let cw = m[0][3] * (u * 2.0 - 1.0) + m[1][3] * (v * 2.0 - 1.0) + m[2][3] * z + m[3][3];
411
+ if (!(abs(cw) > 1e-12)) { return 0.0; }
412
+ return 1.0 / cw;
413
+ }
414
+
415
+ /* disocclusion.ts's planarity. */
416
+ fn reconPlanarity(before: f32, centre: f32, after: f32) -> f32 {
417
+ if (!(before > 0.0) || !(centre > 0.0) || !(after > 0.0)) { return 1.0; }
418
+ let a = 1.0 / after - 1.0 / centre;
419
+ let b = 1.0 / before - 1.0 / centre;
420
+ let spread = abs(a) + abs(b);
421
+ if (!(spread > 0.0)) { return 1.0; }
422
+ return 1.0 - reconSmoothstep(RECON_PLANAR_FROM, RECON_PLANAR_TO, abs(a + b) / spread);
423
+ }
424
+
425
+ /* disocclusion.ts's surfaceSlope. */
426
+ fn reconSurfaceSlope(before: f32, centre: f32, after: f32) -> f32 {
427
+ let hasAfter = after > 0.0;
428
+ let hasBefore = before > 0.0;
429
+ if (!hasAfter) {
430
+ if (!hasBefore) { return 0.0; }
431
+ return abs(before - centre) / centre;
432
+ }
433
+ if (!hasBefore) { return abs(after - centre) / centre; }
434
+ let a = after - centre;
435
+ let b = before - centre;
436
+ if (a * b > 0.0) { return 0.0; }
437
+ return min(abs(a), abs(b)) / centre;
438
+ }
439
+
440
+ struct ReconSurface {
441
+ planar: f32,
442
+ slope: f32,
443
+ }
444
+
445
+ /* resolve.ts's surfaceAt. */
446
+ fn reconSurfaceAt(previous: bool, x: i32, y: i32, centre: f32) -> ReconSurface {
447
+ var out: ReconSurface;
448
+ out.planar = 1.0;
449
+ out.slope = 0.0;
450
+ for (var axis = 0; axis < 2; axis = axis + 1) {
451
+ let ax = 1 - axis;
452
+ let ay = axis;
453
+ let after = reconViewDepthAt(previous, x + ax, y + ay);
454
+ let before = reconViewDepthAt(previous, x - ax, y - ay);
455
+ out.planar = out.planar * reconPlanarity(before, centre, after);
456
+ out.slope = max(out.slope, reconSurfaceSlope(before, centre, after));
457
+ }
458
+ return out;
459
+ }
460
+
389
461
  struct ReconHistory {
390
462
  weight: f32,
391
463
  colour: vec4<f32>,
@@ -433,14 +505,46 @@ fn reconHistoryWeight(u: f32, v: f32, dx: i32, dy: i32) -> ReconHistory {
433
505
  pu * f32(recon.outputSize.x) - 0.5,
434
506
  pv * f32(recon.outputSize.y) - 0.5,
435
507
  );
436
- let px = reconClampTexel(i32(reconRound(pu * rw + recon.previousJitter.x - 0.5)), i32(rw));
437
- let py = reconClampTexel(i32(reconRound(pv * rh + recon.previousJitter.y - 0.5)), i32(rh));
438
- let held = reconWorldAt(true, px, py).depth;
508
+ /* Last frame's depth, searched around where the dilated texel was: see resolve.ts. */
509
+ let su = (f32(dx) + 0.5 - recon.jitter.x) / rw + mu;
510
+ let sv = (f32(dy) + 0.5 - recon.jitter.y) / rh + mv;
511
+ let cx = reconClampTexel(i32(reconRound(su * rw + recon.previousJitter.x - 0.5)), i32(rw));
512
+ let cy = reconClampTexel(i32(reconRound(sv * rh + recon.previousJitter.y - 0.5)), i32(rh));
513
+ var held = reconViewDepthAt(true, cx, cy);
514
+ var px = cx;
515
+ var py = cy;
516
+ if (!(held > 0.0 && abs(held - expected) <= recon.depthTolerance * expected)) {
517
+ held = 0.0;
518
+ var nearest = 3.4e38;
519
+ for (var j = -1; j <= 1; j = j + 1) {
520
+ for (var i = -1; i <= 1; i = i + 1) {
521
+ let tx = reconClampTexel(cx + i, i32(rw));
522
+ let ty = reconClampTexel(cy + j, i32(rh));
523
+ let depth = reconViewDepthAt(true, tx, ty);
524
+ if (!(depth > 0.0)) { continue; }
525
+ let off = abs(depth - expected);
526
+ if (off < nearest) {
527
+ nearest = off;
528
+ held = depth;
529
+ px = tx;
530
+ py = ty;
531
+ }
532
+ }
533
+ }
534
+ }
535
+
536
+ let here = reconSurfaceAt(false, dx, dy, reconViewDepthAt(false, dx, dy));
537
+ var planar = here.planar;
538
+ if (planar > 0.0) { planar = planar * reconSurfaceAt(true, px, py, held).planar; }
439
539
  var normalDot = 1.0;
440
- let here = reconNormalAt(false, dx, dy);
441
- let there = reconNormalAt(true, px, py);
442
- if (here.ok && there.ok) { normalDot = dot(here.normal, there.normal); }
443
- let trust = reconDisocclusion(expected, held, length(vec2<f32>(mu, mv)), normalDot);
540
+ if (planar > 0.0) {
541
+ let hereNormal = reconNormalAt(false, dx, dy);
542
+ let thereNormal = reconNormalAt(true, px, py);
543
+ if (hereNormal.ok && thereNormal.ok) {
544
+ normalDot = 1.0 - planar * (1.0 - dot(hereNormal.normal, thereNormal.normal));
545
+ }
546
+ }
547
+ let trust = reconDisocclusion(expected, held, length(vec2<f32>(mu, mv)), normalDot, here.slope);
444
548
  out.weight = out.colour.w * trust;
445
549
  return out;
446
550
  }
@@ -20,6 +20,7 @@
20
20
  * The vertex stage is `FULLSCREEN_VERT`, shared with the occlusion pass, its blur and the rush.
21
21
  */
22
22
  import { glslSceneDepthToNdc } from '../depthConvention.js';
23
+ import { FLICKER_BLEND, FLICKER_MEMORY, FLICKER_REPEAT, FLICKER_SURFACE, FLICKER_WIDTH, STILL_FROM, STILL_TO, } from '../temporalAa.js';
23
24
  export const TEMPORAL_RESOLVE_FRAG = `#version 300 es
24
25
  precision highp float;
25
26
 
@@ -29,6 +30,22 @@ in vec2 vUv;
29
30
  uniform sampler2D uScene;
30
31
  /** What this pass wrote last frame: resolved, and conceptually unjittered. */
31
32
  uniform sampler2D uHistory;
33
+ /**
34
+ * Each pixel's flicker record from last frame, the anti-flicker in temporalAa.ts: its spread about
35
+ * its period's mean, the sum this period has gathered, the last period's mean, and how many periods
36
+ * running have repeated. highp for the reason the depth's is: a luma in a half-float scene is not a
37
+ * value from 0 to 1.
38
+ */
39
+ uniform highp sampler2D uFlicker;
40
+ /**
41
+ * The rest of the record: the view depth the pixel's sample stood at, so the next frame can tell
42
+ * whether it follows the same surface, and the pixel's own depth swing, which that test allows for.
43
+ */
44
+ uniform highp sampler2D uFlickerMotion;
45
+ /** The inverse projection's depth row and w row, as the other passes read a depth into metres. */
46
+ uniform vec4 uDepthToViewZ;
47
+ /** One where the records can hold what they hold, zero where they cannot: then 4.4.1's resolve. */
48
+ uniform float uAntiFlicker;
32
49
  /**
33
50
  * The frame's depth.
34
51
  *
@@ -52,6 +69,8 @@ uniform highp sampler2D uDepth;
52
69
  uniform mat4 uReprojection;
53
70
  /** One texel of the target, for walking the neighbourhood. */
54
71
  uniform vec2 uTexel;
72
+ /** One on the first frame of the jitter's period, where the anti-flicker compares two periods. */
73
+ uniform float uPeriodStart;
55
74
  /**
56
75
  * How much of the clipped history to keep, 0 to 1.
57
76
  *
@@ -62,7 +81,41 @@ uniform vec2 uTexel;
62
81
  */
63
82
  uniform float uHistoryBlend;
64
83
 
65
- out vec4 fragColor;
84
+ layout(location = 0) out vec4 fragColor;
85
+ /** The record carried out, in the texture uFlicker reads next frame. */
86
+ layout(location = 1) out vec4 flickerOut;
87
+ /** And the rest of it, in the texture uFlickerMotion reads. */
88
+ layout(location = 2) out vec4 flickerMotionOut;
89
+
90
+ const float FLICKER_MEMORY = ${FLICKER_MEMORY.toFixed(3)};
91
+ const float FLICKER_WIDTH = ${FLICKER_WIDTH.toFixed(1)};
92
+ const float FLICKER_REPEAT = ${FLICKER_REPEAT.toFixed(2)};
93
+ const float FLICKER_BLEND = ${FLICKER_BLEND.toFixed(2)};
94
+ const float FLICKER_SURFACE = ${FLICKER_SURFACE.toFixed(2)};
95
+ const float STILL_FROM = ${STILL_FROM.toFixed(2)};
96
+ const float STILL_TO = ${STILL_TO.toFixed(2)};
97
+
98
+ float luma(vec3 c) {
99
+ return dot(c, vec3(0.2126, 0.7152, 0.0722));
100
+ }
101
+
102
+ /** A record starting over: no spread, and nothing repeated yet. */
103
+ vec4 freshRecord(vec3 current) {
104
+ float seen = luma(current);
105
+ return vec4(0.0, seen * FLICKER_MEMORY, seen, 0.0);
106
+ }
107
+
108
+ /** Metres in front of the camera, from a depth sample, as depthOfField.ts reads one. */
109
+ float viewDepthOf(float depth) {
110
+ float z = ${glslSceneDepthToNdc('depth')};
111
+ return -(uDepthToViewZ.x * z + uDepthToViewZ.y) / (uDepthToViewZ.z * z + uDepthToViewZ.w);
112
+ }
113
+
114
+ /** Whether two periods' means are the same, one to zero. */
115
+ float repeats(float mean, float previous) {
116
+ float apart = abs(mean - previous) / max(max(mean, previous), 1.0 / 255.0);
117
+ return 1.0 - smoothstep(FLICKER_REPEAT, 2.0 * FLICKER_REPEAT, apart);
118
+ }
66
119
 
67
120
  /**
68
121
  * The colours actually present around this pixel this frame, as a box.
@@ -154,19 +207,45 @@ void main() {
154
207
  /* Nothing to blend towards: the first frame, a resize, a cut, or the effect switched off. */
155
208
  if (uHistoryBlend <= 0.0) {
156
209
  fragColor = vec4(current, 1.0);
210
+ flickerOut = freshRecord(current);
211
+ flickerMotionOut = vec4(0.0);
157
212
  return;
158
213
  }
159
214
 
215
+ /*
216
+ * **The motion is the nearest surface's among the nine**, the dilation every temporal resolve
217
+ * uses: a pixel at a thin thing samples it in some frames and what is behind it in others, and the
218
+ * two move apart as the camera does, so reprojecting each frame by whichever it caught sends its
219
+ * history and its record somewhere different every frame. A crack of light round a door moves with
220
+ * the door that frames it, and so does its pixel. The depth kept is that surface's too, so the record
221
+ * keeps and next frame tests the depth the reprojection followed.
222
+ */
223
+ vec2 nearestAt = vUv;
160
224
  float depth = textureLod(uDepth, vUv, 0.0).r;
161
- vec4 clip = vec4(vUv * 2.0 - 1.0, ${glslSceneDepthToNdc('depth')}, 1.0);
225
+ float metres = viewDepthOf(depth);
226
+ for (int y = -1; y <= 1; y++) {
227
+ for (int x = -1; x <= 1; x++) {
228
+ vec2 at = vUv + vec2(float(x), float(y)) * uTexel;
229
+ float tap = textureLod(uDepth, at, 0.0).r;
230
+ float tapMetres = viewDepthOf(tap);
231
+ if (tapMetres < metres) {
232
+ metres = tapMetres;
233
+ depth = tap;
234
+ nearestAt = at;
235
+ }
236
+ }
237
+ }
238
+ vec4 clip = vec4(nearestAt * 2.0 - 1.0, ${glslSceneDepthToNdc('depth')}, 1.0);
162
239
  vec4 previous = uReprojection * clip;
163
240
  /* Behind the previous eye: there is no last-frame position to sample. */
164
241
  if (previous.w <= 0.0) {
165
242
  fragColor = vec4(current, 1.0);
243
+ flickerOut = freshRecord(current);
244
+ flickerMotionOut = vec4(metres, 0.0, 0.0, 0.0);
166
245
  return;
167
246
  }
168
247
 
169
- vec2 wasUv = (previous.xy / previous.w) * 0.5 + 0.5;
248
+ vec2 wasUv = vUv + ((previous.xy / previous.w) * 0.5 + 0.5 - nearestAt);
170
249
  /*
171
250
  * **Off the edge of the last frame is a disocclusion**, and the honest answer is this frame's
172
251
  * colour rather than a clamped sample of the border, which would drag one row of pixels inward
@@ -174,6 +253,8 @@ void main() {
174
253
  */
175
254
  if (wasUv.x < 0.0 || wasUv.x > 1.0 || wasUv.y < 0.0 || wasUv.y > 1.0) {
176
255
  fragColor = vec4(current, 1.0);
256
+ flickerOut = freshRecord(current);
257
+ flickerMotionOut = vec4(metres, 0.0, 0.0, 0.0);
177
258
  return;
178
259
  }
179
260
 
@@ -181,8 +262,55 @@ void main() {
181
262
  vec3 hi;
182
263
  neighbourhood(lo, hi);
183
264
  vec3 history = historyCatmullRom(wasUv);
184
- vec3 bounded = clipToNeighbourhood(history, lo, hi);
185
265
 
186
- fragColor = vec4(mix(current, bounded, uHistoryBlend), 1.0);
266
+ /*
267
+ * The anti-flicker, operation for operation the one in temporalAa.ts. The record is read at the
268
+ * texel where the surface was rather than filtered, so two surfaces' records never mix at an
269
+ * edge; it is the surface's own if last frame's depth there, or at one of the texels beside it,
270
+ * is the depth this surface should have had — the nearest of five, because this frame's sample
271
+ * and last frame's can straddle an edge with nothing moved. Otherwise it is no record.
272
+ */
273
+ ivec2 size = textureSize(uFlicker, 0);
274
+ ivec2 was = clamp(ivec2(wasUv * vec2(size)), ivec2(0), size - 1);
275
+ float nearest = 1e9;
276
+ for (int n = 0; n < 5; n++) {
277
+ ivec2 at = was + (n == 1 ? ivec2(1, 0) : n == 2 ? ivec2(-1, 0) : n == 3 ? ivec2(0, 1) : n == 4 ? ivec2(0, -1) : ivec2(0));
278
+ float stood = texelFetch(uFlickerMotion, clamp(at, ivec2(0), size - 1), 0).r;
279
+ if (stood > 0.0) nearest = min(nearest, abs(stood - previous.w));
280
+ }
281
+ /* The swing is the pixel's whatever the test says: resetting it would stop it learning one. */
282
+ vec4 motionThere = texelFetch(uFlickerMotion, was, 0);
283
+ bool missedAll = nearest > 1e8;
284
+ bool same = uAntiFlicker > 0.5 && !missedAll &&
285
+ nearest <= FLICKER_WIDTH * motionThere.g + FLICKER_SURFACE * previous.w;
286
+ float swingOut = missedAll ? 0.0 : motionThere.g + (nearest - motionThere.g) * FLICKER_MEMORY;
287
+ float seen = luma(current);
288
+ vec4 held = same ? texelFetch(uFlicker, was, 0) : freshRecord(current);
289
+
290
+ float still = 1.0 - smoothstep(STILL_FROM, STILL_TO, length((wasUv - vUv) / uTexel));
291
+ float apart = abs(seen - held.b);
292
+ float reach = FLICKER_WIDTH * held.r + 1.0 / 255.0;
293
+ bool within = apart <= reach;
294
+ bool proven = held.a > 0.75 && within;
295
+ float widen = proven ? FLICKER_WIDTH * held.r * still : 0.0;
296
+ float blend = proven ? mix(uHistoryBlend, FLICKER_BLEND, still) : uHistoryBlend;
297
+ vec3 bounded = clipToNeighbourhood(history, lo - widen, hi + widen);
298
+
299
+ float sum = held.g;
300
+ float mean = held.b;
301
+ float repeated = within ? held.a : 0.0;
302
+ if (uPeriodStart > 0.5) {
303
+ repeated = repeats(sum, mean) > 0.5 ? min(1.0, repeated + 0.5) : 0.0;
304
+ mean = sum;
305
+ sum = 0.0;
306
+ }
307
+ flickerOut = vec4(
308
+ held.r + (apart - held.r) * FLICKER_MEMORY,
309
+ sum + seen * FLICKER_MEMORY,
310
+ mean,
311
+ repeated * still
312
+ );
313
+ flickerMotionOut = vec4(metres, swingOut, 0.0, 0.0);
314
+ fragColor = vec4(mix(current, bounded, blend), 1.0);
187
315
  }
188
316
  `;