@woosh/meep-engine 3.29.0 → 3.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/build/bundle-worker-terrain.js +1 -1
  3. package/package.json +1 -1
  4. package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
  5. package/src/core/binary/BinaryBuffer.js +14 -2
  6. package/src/core/bvh2/bvh3/BVH.d.ts.map +1 -1
  7. package/src/core/bvh2/bvh3/BVH.js +14 -1
  8. package/src/core/cache/FrequencySketch.d.ts +3 -1
  9. package/src/core/cache/FrequencySketch.d.ts.map +1 -1
  10. package/src/core/cache/FrequencySketch.js +10 -4
  11. package/src/core/cache/wtinylfu/CacheWTinylfu.d.ts.map +1 -1
  12. package/src/core/cache/wtinylfu/CacheWTinylfu.js +24 -2
  13. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts +20 -0
  14. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.d.ts.map +1 -1
  15. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +69 -4
  16. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts +48 -18
  17. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
  18. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +262 -70
  19. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts +25 -0
  20. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.d.ts.map +1 -0
  21. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_fold_limit.js +228 -0
  22. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts +13 -0
  23. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.d.ts.map +1 -1
  24. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_would_fold.js +10 -128
  25. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.d.ts.map +1 -1
  26. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_face_bvh.js +11 -1
  27. package/src/core/graph/mn_graph_collapse_weighted_edge.js +6 -6
  28. package/src/core/math/lookup/ParameterLookupTable.d.ts.map +1 -1
  29. package/src/core/math/lookup/ParameterLookupTable.js +10 -5
  30. package/src/core/math/noise/sdnoise.d.ts.map +1 -1
  31. package/src/core/math/noise/sdnoise.js +16 -10
  32. package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
  33. package/src/core/model/object/ImmutableObjectPool.js +17 -7
  34. package/src/core/process/executor/ConcurrentExecutor.d.ts +1 -5
  35. package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
  36. package/src/core/process/executor/ConcurrentExecutor.js +95 -1
  37. package/src/core/process/task/task_find_dependency_cycle.d.ts +17 -0
  38. package/src/core/process/task/task_find_dependency_cycle.d.ts.map +1 -0
  39. package/src/core/process/task/task_find_dependency_cycle.js +81 -0
  40. package/src/core/wfc/WFCCellChoice.d.ts +17 -0
  41. package/src/core/wfc/WFCCellChoice.d.ts.map +1 -0
  42. package/src/core/wfc/WFCCellChoice.js +36 -0
  43. package/src/core/wfc/WFCLattice.d.ts +105 -0
  44. package/src/core/wfc/WFCLattice.d.ts.map +1 -0
  45. package/src/core/wfc/WFCLattice.js +235 -0
  46. package/src/core/wfc/WFCRuleTable.d.ts +172 -0
  47. package/src/core/wfc/WFCRuleTable.d.ts.map +1 -0
  48. package/src/core/wfc/WFCRuleTable.js +334 -0
  49. package/src/core/wfc/WFCState.d.ts +273 -0
  50. package/src/core/wfc/WFCState.d.ts.map +1 -0
  51. package/src/core/wfc/WFCState.js +552 -0
  52. package/src/core/wfc/WFCStatus.d.ts +11 -0
  53. package/src/core/wfc/WFCStatus.d.ts.map +1 -0
  54. package/src/core/wfc/WFCStatus.js +28 -0
  55. package/src/core/wfc/WFCTileWeights.d.ts +48 -0
  56. package/src/core/wfc/WFCTileWeights.d.ts.map +1 -0
  57. package/src/core/wfc/WFCTileWeights.js +86 -0
  58. package/src/core/wfc/WFC_UNRESOLVED.d.ts +12 -0
  59. package/src/core/wfc/WFC_UNRESOLVED.d.ts.map +1 -0
  60. package/src/core/wfc/WFC_UNRESOLVED.js +11 -0
  61. package/src/core/wfc/wfc_estimate_bytes.d.ts +24 -0
  62. package/src/core/wfc/wfc_estimate_bytes.d.ts.map +1 -0
  63. package/src/core/wfc/wfc_estimate_bytes.js +47 -0
  64. package/src/core/wfc/wfc_rule_table_tile_components.d.ts +27 -0
  65. package/src/core/wfc/wfc_rule_table_tile_components.d.ts.map +1 -0
  66. package/src/core/wfc/wfc_rule_table_tile_components.js +80 -0
  67. package/src/core/wfc/wfc_solve.d.ts +21 -0
  68. package/src/core/wfc/wfc_solve.d.ts.map +1 -0
  69. package/src/core/wfc/wfc_solve.js +154 -0
  70. package/src/core/wfc/wfc_state_apply_boundary.d.ts +18 -0
  71. package/src/core/wfc/wfc_state_apply_boundary.d.ts.map +1 -0
  72. package/src/core/wfc/wfc_state_apply_boundary.js +61 -0
  73. package/src/core/wfc/wfc_state_assign.d.ts +11 -0
  74. package/src/core/wfc/wfc_state_assign.d.ts.map +1 -0
  75. package/src/core/wfc/wfc_state_assign.js +36 -0
  76. package/src/core/wfc/wfc_state_ban_bits.d.ts +17 -0
  77. package/src/core/wfc/wfc_state_ban_bits.d.ts.map +1 -0
  78. package/src/core/wfc/wfc_state_ban_bits.js +81 -0
  79. package/src/core/wfc/wfc_state_collapse.d.ts +12 -0
  80. package/src/core/wfc/wfc_state_collapse.d.ts.map +1 -0
  81. package/src/core/wfc/wfc_state_collapse.js +73 -0
  82. package/src/core/wfc/wfc_state_flush_dirty.d.ts +12 -0
  83. package/src/core/wfc/wfc_state_flush_dirty.d.ts.map +1 -0
  84. package/src/core/wfc/wfc_state_flush_dirty.js +39 -0
  85. package/src/core/wfc/wfc_state_propagate.d.ts +14 -0
  86. package/src/core/wfc/wfc_state_propagate.d.ts.map +1 -0
  87. package/src/core/wfc/wfc_state_propagate.js +155 -0
  88. package/src/core/wfc/wfc_state_read.d.ts +11 -0
  89. package/src/core/wfc/wfc_state_read.d.ts.map +1 -0
  90. package/src/core/wfc/wfc_state_read.js +38 -0
  91. package/src/core/wfc/wfc_state_restrict.d.ts +18 -0
  92. package/src/core/wfc/wfc_state_restrict.d.ts.map +1 -0
  93. package/src/core/wfc/wfc_state_restrict.js +39 -0
  94. package/src/core/wfc/wfc_state_set_eligible.d.ts +24 -0
  95. package/src/core/wfc/wfc_state_set_eligible.d.ts.map +1 -0
  96. package/src/core/wfc/wfc_state_set_eligible.js +49 -0
  97. package/src/core/wfc/wfc_state_undo_to.d.ts +13 -0
  98. package/src/core/wfc/wfc_state_undo_to.d.ts.map +1 -0
  99. package/src/core/wfc/wfc_state_undo_to.js +84 -0
  100. package/src/core/wfc/wfc_words_per_cell.d.ts +11 -0
  101. package/src/core/wfc/wfc_words_per_cell.d.ts.map +1 -0
  102. package/src/core/wfc/wfc_words_per_cell.js +16 -0
  103. package/src/engine/ecs/EntityComponentDataset.d.ts +8 -0
  104. package/src/engine/ecs/EntityComponentDataset.d.ts.map +1 -1
  105. package/src/engine/ecs/EntityComponentDataset.js +37 -14
  106. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts +8 -1
  107. package/src/engine/ecs/fow/FogOfWarRevealerSystem.d.ts.map +1 -1
  108. package/src/engine/ecs/fow/FogOfWarRevealerSystem.js +37 -2
  109. package/src/engine/ecs/guid/UUID.d.ts.map +1 -1
  110. package/src/engine/ecs/guid/UUID.js +45 -17
  111. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts.map +1 -1
  112. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +3 -1
  113. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.d.ts.map +1 -1
  114. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +366 -360
  115. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.d.ts.map +1 -1
  116. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_linear.js +24 -10
  117. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
  118. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.js +24 -5
  119. package/src/engine/interpolation/Interpoland.d.ts +7 -1
  120. package/src/engine/interpolation/Interpoland.d.ts.map +1 -1
  121. package/src/engine/interpolation/Interpoland.js +8 -1
  122. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -1
  123. package/src/engine/interpolation/InterpolationSystem.js +3 -0
  124. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +5 -3
  125. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -1
  126. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +14 -3
  127. package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -1
  128. package/src/engine/interpolation/pose_interpoland.js +2 -0
  129. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  130. package/src/engine/network/orchestrator/NetworkPeer.js +15 -10
  131. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  132. package/src/engine/physics/cloth/ecs/ClothWorld.js +17 -0
  133. package/src/engine/physics/mls-mpm/MLS_MPM.d.ts.map +1 -1
  134. package/src/engine/physics/mls-mpm/MLS_MPM.js +14 -0
  135. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +2 -1
  136. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -1
  137. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +1 -1
  138. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +1 -0
  139. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -1
  140. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +1 -0
  141. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -1
  142. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +2 -1
  143. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -1
  144. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +12 -3
  145. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +1 -0
  146. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -1
  147. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +1 -0
  148. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  149. package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -1
  150. package/src/engine/sound/sopra/runtime/EventInstance.js +4 -1
  151. package/src/format/image/avif/av1/decode/decode_coefficients.d.ts.map +1 -1
  152. package/src/format/image/avif/av1/decode/decode_coefficients.js +248 -259
  153. package/src/format/image/avif/av1/decode/read_golomb.d.ts +21 -0
  154. package/src/format/image/avif/av1/decode/read_golomb.d.ts.map +1 -0
  155. package/src/format/image/avif/av1/decode/read_golomb.js +49 -0
  156. package/src/format/image/avif/av1/filter/loop_restoration.js +461 -451
  157. package/src/format/image/avif/av1/util/round2.d.ts +19 -2
  158. package/src/format/image/avif/av1/util/round2.d.ts.map +1 -1
  159. package/src/format/image/avif/av1/util/round2.js +51 -27
  160. package/src/format/image/avif/heif/parse_avif_file.js +24 -0
  161. package/src/shade/renderer/buffer/table/GPUTypedTable.d.ts.map +1 -1
  162. package/src/shade/renderer/buffer/table/GPUTypedTable.js +14 -0
  163. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +46 -3
  164. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +91 -1
  165. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts +0 -13
  166. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.d.ts.map +1 -1
  167. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +0 -15
  168. package/src/shade/renderer/geometry/virtual/build/level/vgeo_build_group_step.js +5 -5
  169. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts +11 -1
  170. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.d.ts.map +1 -1
  171. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_DEFAULT_ATTRIBUTE_WEIGHT_RATIOS.js +11 -1
  172. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts +18 -9
  173. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.d.ts.map +1 -1
  174. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_compute_vertex_normals.js +20 -26
  175. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts +1 -3
  176. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.d.ts.map +1 -1
  177. package/src/shade/renderer/geometry/virtual/build/mesh/vgeo_normalize_source.js +2 -5
  178. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +1 -1
  179. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.d.ts.map +1 -1
  180. package/src/shade/renderer/geometry/virtual/runtime/VGeoResidency.js +14 -1
  181. package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.d.ts.map +1 -1
  182. package/src/shade/renderer/global_illumination/probe/octahedral/GPUProbeAtlas.js +12 -3
  183. package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.d.ts.map +1 -1
  184. package/src/shade/renderer/light/environment/rgbe/RGBE_ReadPixels_RLE.js +7 -1
  185. package/src/shade/renderer/lightmap/GPULightMap.d.ts.map +1 -1
  186. package/src/shade/renderer/lightmap/GPULightMap.js +349 -336
  187. package/src/shade/renderer/loader/gltf/load_gltf.d.ts.map +1 -1
  188. package/src/shade/renderer/loader/gltf/load_gltf.js +1137 -1128
  189. package/src/shade/renderer/particles/DESIGN.md +14 -5
  190. package/src/shade/renderer/particles/GPUParticleSystem.d.ts +25 -10
  191. package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
  192. package/src/shade/renderer/particles/GPUParticleSystem.js +182 -38
  193. package/src/shade/renderer/particles/graph/groups/simplex_reference.d.ts.map +1 -1
  194. package/src/shade/renderer/particles/graph/groups/simplex_reference.js +16 -10
  195. package/src/shade/renderer/particles/graph_particles.d.ts +4 -2
  196. package/src/shade/renderer/particles/graph_particles.d.ts.map +1 -1
  197. package/src/shade/renderer/particles/graph_particles.js +8 -2
  198. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
  199. package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +8 -2
  200. package/src/shade/renderer/particles/optimizer/pass_fold_constants.d.ts.map +1 -1
  201. package/src/shade/renderer/particles/optimizer/pass_fold_constants.js +10 -4
  202. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts +31 -0
  203. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.d.ts.map +1 -0
  204. package/src/shade/renderer/particles/shaders/shader_particle_init_free_list.js +62 -0
  205. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +13 -0
  206. package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
  207. package/src/shade/renderer/particles/vm/ParticleVMReference.js +95 -17
  208. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts +21 -0
  209. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.d.ts.map +1 -0
  210. package/src/shade/renderer/rasterize/bucket/rasterization_has_opaque_bucket.js +28 -0
  211. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.d.ts.map +1 -1
  212. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +19 -2
  213. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
  214. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +19 -2
  215. package/src/shade/renderer/shader/type/WebGPUStruct.d.ts.map +1 -1
  216. package/src/shade/renderer/shader/type/WebGPUStruct.js +12 -0
  217. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts +8 -2
  218. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.d.ts.map +1 -1
  219. package/src/shade/renderer/texture/mipmaps/WebGPUMipmapGenerator.js +41 -6
  220. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +2383 -2356
  221. package/src/view/tooltip/DomTooltipManager.d.ts +7 -0
  222. package/src/view/tooltip/DomTooltipManager.d.ts.map +1 -1
  223. package/src/view/tooltip/DomTooltipManager.js +22 -0
  224. package/src/view/tooltip/DomTooltipObserver.d.ts.map +1 -1
  225. package/src/view/tooltip/DomTooltipObserver.js +13 -3
  226. package/src/shade/renderer/geometry/virtual/build/mesh/VGEO_CREASE_ANGLE.js +0 -45
@@ -1,451 +1,461 @@
1
- import { clamp } from "../../../../../core/math/clamp.js";
2
- import {
3
- FILTER_BITS,
4
- MI_SIZE,
5
- RESTORE_NONE,
6
- RESTORE_SGRPROJ,
7
- RESTORE_WIENER,
8
- SGRPROJ_MTABLE_BITS,
9
- SGRPROJ_PRJ_BITS,
10
- SGRPROJ_RECIP_BITS,
11
- SGRPROJ_RST_BITS,
12
- SGRPROJ_SGR_BITS
13
- } from "../tables/av1_symbols.js";
14
- import { SGR_PARAMS } from "../tables/filter_tables.js";
15
- import { round2 } from "../util/round2.js";
16
-
17
- /**
18
- * `A` and `B` from the box filter, valid over one extra sample in each direction.
19
- * @type {number}
20
- */
21
- const BOX_STRIDE = MI_SIZE + 2;
22
-
23
- /** @type {Int32Array} */
24
- const BOX_A = new Int32Array(BOX_STRIDE * BOX_STRIDE);
25
-
26
- /** @type {Int32Array} */
27
- const BOX_B = new Int32Array(BOX_STRIDE * BOX_STRIDE);
28
-
29
- /** @type {Int32Array} the two self-guided passes' outputs */
30
- const FLT0 = new Int32Array(MI_SIZE * MI_SIZE);
31
-
32
- /** @type {Int32Array} */
33
- const FLT1 = new Int32Array(MI_SIZE * MI_SIZE);
34
-
35
- /** @type {Int32Array} the Wiener filter's horizontal pass */
36
- const WIENER_INTERMEDIATE = new Int32Array((MI_SIZE + 6) * MI_SIZE);
37
-
38
- /** @type {Int32Array} the seven symmetric taps expanded from the three coded ones */
39
- const WIENER_VERTICAL = new Int32Array(7);
40
-
41
- /** @type {Int32Array} */
42
- const WIENER_HORIZONTAL = new Int32Array(7);
43
-
44
- /**
45
- * State the source-sample fetch needs, kept here rather than threaded through every call.
46
- *
47
- * The fetch decides between the deringed frame and the merely deblocked one based on which stripe
48
- * the sample falls in, and that boundary changes per block. Hoisting it into a small module-level
49
- * record keeps the innermost loops free of parameters they would otherwise carry six deep.
50
- */
51
- const SOURCE = {
52
- ctx: null,
53
- plane: 0,
54
- stripe_start_y: 0,
55
- stripe_end_y: 0,
56
- plane_end_x: 0,
57
- plane_end_y: 0,
58
- stride: 0,
59
- cdef: null,
60
- current: null
61
- };
62
-
63
- /**
64
- * Apply loop restoration.
65
- *
66
- * Reads the deringed frame and the pre-CDEF frame, and writes the restored result. The two inputs
67
- * are not interchangeable: within a stripe the filter reads deringed samples, and outside it reads
68
- * deblocked ones, which is what lets a hardware decoder run restoration a stripe behind CDEF
69
- * without an extra line buffer.
70
- *
71
- * @param {Av1FrameContext} ctx
72
- * @param {Uint16Array[]} current the deblocked frame, upscaled if superres is in use
73
- * @param {Uint16Array[]} cdef the deringed frame, upscaled if superres is in use
74
- * @param {Uint16Array[]} output
75
- * @param {Int32Array} stride
76
- * @see AV1 spec 7.17 "Loop restoration process"
77
- *
78
- * @author Alex Goldring
79
- * @copyright Company Named Limited (c) 2026
80
- */
81
- export function apply_loop_restoration(ctx, current, cdef, output, stride) {
82
- const frame = ctx.frame;
83
-
84
- for (let plane = 0; plane < ctx.num_planes; plane++) {
85
- // the buffers are reused across frames, so the source may be longer than this frame
86
- const samples = stride[plane] * ctx.plane_rows[plane];
87
-
88
- output[plane].set(cdef[plane].subarray(0, samples));
89
- }
90
-
91
- if (frame.uses_lr === 0) {
92
- return;
93
- }
94
-
95
- for (let y = 0; y < frame.frame_height; y += MI_SIZE) {
96
- for (let x = 0; x < frame.upscaled_width; x += MI_SIZE) {
97
- for (let plane = 0; plane < ctx.num_planes; plane++) {
98
- if (frame.frame_restoration_type[plane] === RESTORE_NONE) {
99
- continue;
100
- }
101
-
102
- restore_block(ctx, current, cdef, output, stride, plane, y >> 2, x >> 2);
103
- }
104
- }
105
- }
106
- }
107
-
108
- /**
109
- * @param {Av1FrameContext} ctx
110
- * @param {Uint16Array[]} current
111
- * @param {Uint16Array[]} cdef
112
- * @param {Uint16Array[]} output
113
- * @param {Int32Array} stride
114
- * @param {number} plane
115
- * @param {number} row
116
- * @param {number} col
117
- * @see AV1 spec 7.17.1 "Loop restore block process"
118
- */
119
- function restore_block(ctx, current, cdef, output, stride, plane, row, col) {
120
- const frame = ctx.frame;
121
-
122
- const luma_y = row * MI_SIZE;
123
-
124
- // stripes are offset up by eight luma samples so a pipelined decoder can restore a stripe as
125
- // soon as the superblock row above it is deblocked
126
- const stripe = Math.floor((luma_y + 8) / 64);
127
-
128
- const sub_x = plane > 0 ? ctx.subsampling_x : 0;
129
- const sub_y = plane > 0 ? ctx.subsampling_y : 0;
130
-
131
- SOURCE.ctx = ctx;
132
- SOURCE.plane = plane;
133
- SOURCE.stripe_start_y = (-8 + stripe * 64) >> sub_y;
134
- SOURCE.stripe_end_y = SOURCE.stripe_start_y + (64 >> sub_y) - 1;
135
- SOURCE.plane_end_x = round2(frame.upscaled_width, sub_x) - 1;
136
- SOURCE.plane_end_y = round2(frame.frame_height, sub_y) - 1;
137
- SOURCE.stride = stride[plane];
138
- SOURCE.cdef = cdef[plane];
139
- SOURCE.current = current[plane];
140
-
141
- const x = (col * MI_SIZE) >> sub_x;
142
- const y = (row * MI_SIZE) >> sub_y;
143
-
144
- const w = Math.min(MI_SIZE >> sub_x, SOURCE.plane_end_x - x + 1);
145
- const h = Math.min(MI_SIZE >> sub_y, SOURCE.plane_end_y - y + 1);
146
-
147
- if (w <= 0 || h <= 0) {
148
- return;
149
- }
150
-
151
- const unit_index = restoration_unit_of(ctx, plane, row, col);
152
-
153
- const type = ctx.lr_type[unit_index];
154
-
155
- if (type === RESTORE_WIENER) {
156
- wiener_filter(ctx, output, stride, plane, unit_index, x, y, w, h);
157
- } else if (type === RESTORE_SGRPROJ) {
158
- self_guided_filter(ctx, output, stride, plane, unit_index, x, y, w, h);
159
- }
160
- }
161
-
162
- /**
163
- * `count_units_in_frame`: how many restoration units of one size a frame is divided into.
164
- *
165
- * @param {number} unit_size
166
- * @param {number} frame_size
167
- * @returns {number}
168
- */
169
- export function count_units_in_frame(unit_size, frame_size) {
170
- return Math.max(Math.floor((frame_size + (unit_size >> 1)) / unit_size), 1);
171
- }
172
-
173
- /**
174
- * Which restoration unit a 4x4 block is filtered by.
175
- *
176
- * The vertical mapping is offset by eight luma samples, because restoration works in stripes that
177
- * are offset by that much. Exported because the encoder has to attribute a block's error to the
178
- * same unit this attributes its filtering to, and the two agreeing is the whole point.
179
- *
180
- * @param {Av1FrameContext} ctx
181
- * @param {number} plane
182
- * @param {number} row 4x4 block row
183
- * @param {number} col 4x4 block column
184
- * @returns {number} index into the per-unit arrays
185
- */
186
- export function restoration_unit_of(ctx, plane, row, col) {
187
- const frame = ctx.frame;
188
-
189
- const sub_x = plane > 0 ? ctx.subsampling_x : 0;
190
- const sub_y = plane > 0 ? ctx.subsampling_y : 0;
191
-
192
- const unit_size = frame.loop_restoration_size[plane];
193
-
194
- const unit_rows = count_units_in_frame(unit_size, round2(frame.frame_height, sub_y));
195
- const unit_cols = count_units_in_frame(unit_size, round2(frame.upscaled_width, sub_x));
196
-
197
- const unit_row = Math.min(unit_rows - 1, Math.floor(((row * MI_SIZE + 8) >> sub_y) / unit_size));
198
- const unit_col = Math.min(unit_cols - 1, Math.floor(((col * MI_SIZE) >> sub_x) / unit_size));
199
-
200
- return ctx.lr_unit_index(plane, unit_row, unit_col);
201
- }
202
-
203
- /**
204
- * `get_source_sample`: deringed inside the current stripe, merely deblocked outside it.
205
- *
206
- * @param {number} x
207
- * @param {number} y
208
- * @returns {number}
209
- * @see AV1 spec 7.17.6 "Get source sample process"
210
- */
211
- function source_sample(x, y) {
212
- const clamped_x = clamp(x, 0, SOURCE.plane_end_x);
213
-
214
- let clamped_y = clamp(y, 0, SOURCE.plane_end_y);
215
-
216
- if (clamped_y < SOURCE.stripe_start_y) {
217
- clamped_y = Math.max(SOURCE.stripe_start_y - 2, clamped_y);
218
-
219
- return SOURCE.current[clamped_y * SOURCE.stride + clamped_x];
220
- }
221
-
222
- if (clamped_y > SOURCE.stripe_end_y) {
223
- clamped_y = Math.min(SOURCE.stripe_end_y + 2, clamped_y);
224
-
225
- return SOURCE.current[clamped_y * SOURCE.stride + clamped_x];
226
- }
227
-
228
- return SOURCE.cdef[clamped_y * SOURCE.stride + clamped_x];
229
- }
230
-
231
- /**
232
- * @param {Av1FrameContext} ctx
233
- * @param {Uint16Array[]} output
234
- * @param {Int32Array} stride
235
- * @param {number} plane
236
- * @param {number} unit_index
237
- * @param {number} x
238
- * @param {number} y
239
- * @param {number} w
240
- * @param {number} h
241
- * @see AV1 spec 7.17.4 "Wiener filter process"
242
- */
243
- function wiener_filter(ctx, output, stride, plane, unit_index, x, y, w, h) {
244
- const bit_depth = ctx.bit_depth;
245
-
246
- // the rounding the specification derives for a non-compound inter prediction, reused here
247
- const round0 = bit_depth === 12 ? 5 : 3;
248
- const round1 = bit_depth === 12 ? 9 : 11;
249
-
250
- expand_wiener(ctx.lr_wiener, unit_index * 6, WIENER_VERTICAL);
251
- expand_wiener(ctx.lr_wiener, unit_index * 6 + 3, WIENER_HORIZONTAL);
252
-
253
- const offset = 1 << (bit_depth + FILTER_BITS - round0 - 1);
254
- const limit = (1 << (bit_depth + 1 + FILTER_BITS - round0)) - 1;
255
-
256
- for (let r = 0; r < h + 6; r++) {
257
- for (let c = 0; c < w; c++) {
258
- let s = 0;
259
-
260
- for (let t = 0; t < 7; t++) {
261
- s += WIENER_HORIZONTAL[t] * source_sample(x + c + t - 3, y + r - 3);
262
- }
263
-
264
- WIENER_INTERMEDIATE[r * MI_SIZE + c] = clamp(round2(s, round0), -offset, limit - offset);
265
- }
266
- }
267
-
268
- const max_value = (1 << bit_depth) - 1;
269
-
270
- const plane_output = output[plane];
271
- const plane_stride = stride[plane];
272
-
273
- for (let r = 0; r < h; r++) {
274
- for (let c = 0; c < w; c++) {
275
- let s = 0;
276
-
277
- for (let t = 0; t < 7; t++) {
278
- s += WIENER_VERTICAL[t] * WIENER_INTERMEDIATE[(r + t) * MI_SIZE + c];
279
- }
280
-
281
- plane_output[(y + r) * plane_stride + x + c] = clamp(round2(s, round1), 0, max_value);
282
- }
283
- }
284
- }
285
-
286
- /**
287
- * Expand three coded taps into the seven of a symmetric unit-gain filter.
288
- *
289
- * @param {Int8Array} coefficients
290
- * @param {number} offset
291
- * @param {Int32Array} filter
292
- * @see AV1 spec 7.17.5 "Wiener coefficient process"
293
- */
294
- function expand_wiener(coefficients, offset, filter) {
295
- filter[3] = 128;
296
-
297
- for (let i = 0; i < 3; i++) {
298
- const c = coefficients[offset + i];
299
-
300
- filter[i] = c;
301
- filter[6 - i] = c;
302
- filter[3] -= 2 * c;
303
- }
304
- }
305
-
306
- /**
307
- * @param {Av1FrameContext} ctx
308
- * @param {Uint16Array[]} output
309
- * @param {Int32Array} stride
310
- * @param {number} plane
311
- * @param {number} unit_index
312
- * @param {number} x
313
- * @param {number} y
314
- * @param {number} w
315
- * @param {number} h
316
- * @see AV1 spec 7.17.2 "Self guided filter process"
317
- */
318
- function self_guided_filter(ctx, output, stride, plane, unit_index, x, y, w, h) {
319
- const set = ctx.lr_sgr_set[unit_index];
320
-
321
- box_filter(ctx, x, y, w, h, set, 0, FLT0);
322
- box_filter(ctx, x, y, w, h, set, 1, FLT1);
323
-
324
- const w0 = ctx.lr_sgr_xqd[unit_index * 2];
325
- const w1 = ctx.lr_sgr_xqd[unit_index * 2 + 1];
326
- const w2 = (1 << SGRPROJ_PRJ_BITS) - w0 - w1;
327
-
328
- const r0 = SGR_PARAMS[set * 4];
329
- const r1 = SGR_PARAMS[set * 4 + 2];
330
-
331
- const max_value = (1 << ctx.bit_depth) - 1;
332
-
333
- const plane_output = output[plane];
334
- const plane_stride = stride[plane];
335
-
336
- for (let i = 0; i < h; i++) {
337
- for (let j = 0; j < w; j++) {
338
- const u = SOURCE.cdef[(y + i) * SOURCE.stride + x + j] << SGRPROJ_RST_BITS;
339
-
340
- let v = w1 * u;
341
-
342
- v += r0 !== 0 ? w0 * FLT0[i * MI_SIZE + j] : w0 * u;
343
- v += r1 !== 0 ? w2 * FLT1[i * MI_SIZE + j] : w2 * u;
344
-
345
- plane_output[(y + i) * plane_stride + x + j] = clamp(
346
- round2(v, SGRPROJ_RST_BITS + SGRPROJ_PRJ_BITS),
347
- 0,
348
- max_value
349
- );
350
- }
351
- }
352
- }
353
-
354
- /**
355
- * @param {Av1FrameContext} ctx
356
- * @param {number} x
357
- * @param {number} y
358
- * @param {number} w
359
- * @param {number} h
360
- * @param {number} set
361
- * @param {number} pass
362
- * @param {Int32Array} out
363
- * @see AV1 spec 7.17.3 "Box filter process"
364
- */
365
- function box_filter(ctx, x, y, w, h, set, pass, out) {
366
- const r = SGR_PARAMS[set * 4 + pass * 2];
367
-
368
- if (r === 0) {
369
- return;
370
- }
371
-
372
- const eps = SGR_PARAMS[set * 4 + pass * 2 + 1];
373
-
374
- const bit_depth = ctx.bit_depth;
375
-
376
- const n = (2 * r + 1) * (2 * r + 1);
377
- const n2e = n * n * eps;
378
- const s = Math.floor(((1 << SGRPROJ_MTABLE_BITS) + Math.floor(n2e / 2)) / n2e);
379
-
380
- const one_over_n = Math.floor(((1 << SGRPROJ_RECIP_BITS) + Math.floor(n / 2)) / n);
381
-
382
- for (let i = -1; i < h + 1; i++) {
383
- for (let j = -1; j < w + 1; j++) {
384
- let a = 0;
385
- let b = 0;
386
-
387
- for (let dy = -r; dy <= r; dy++) {
388
- for (let dx = -r; dx <= r; dx++) {
389
- const c = source_sample(x + j + dx, y + i + dy);
390
-
391
- a += c * c;
392
- b += c;
393
- }
394
- }
395
-
396
- a = round2(a, 2 * (bit_depth - 8));
397
-
398
- const d = round2(b, bit_depth - 8);
399
-
400
- const p = Math.max(0, a * n - d * d);
401
- const z = round2(p * s, SGRPROJ_MTABLE_BITS);
402
-
403
- let a2;
404
-
405
- if (z >= 255) {
406
- a2 = 256;
407
- } else if (z === 0) {
408
- a2 = 1;
409
- } else {
410
- a2 = Math.floor(((z << SGRPROJ_SGR_BITS) + Math.floor(z / 2)) / (z + 1));
411
- }
412
-
413
- const b2 = ((1 << SGRPROJ_SGR_BITS) - a2) * b * one_over_n;
414
-
415
- const index = (i + 1) * BOX_STRIDE + j + 1;
416
-
417
- BOX_A[index] = a2;
418
- BOX_B[index] = round2(b2, SGRPROJ_RECIP_BITS);
419
- }
420
- }
421
-
422
- for (let i = 0; i < h; i++) {
423
- const shift = pass === 0 && (i & 1) !== 0 ? 4 : 5;
424
-
425
- for (let j = 0; j < w; j++) {
426
- let a = 0;
427
- let b = 0;
428
-
429
- for (let dy = -1; dy <= 1; dy++) {
430
- for (let dx = -1; dx <= 1; dx++) {
431
- let weight;
432
-
433
- if (pass === 0) {
434
- weight = ((i + dy) & 1) !== 0 ? (dx === 0 ? 6 : 5) : 0;
435
- } else {
436
- weight = dx === 0 || dy === 0 ? 4 : 3;
437
- }
438
-
439
- const index = (i + dy + 1) * BOX_STRIDE + j + dx + 1;
440
-
441
- a += weight * BOX_A[index];
442
- b += weight * BOX_B[index];
443
- }
444
- }
445
-
446
- const v = a * SOURCE.cdef[(y + i) * SOURCE.stride + x + j] + b;
447
-
448
- out[i * MI_SIZE + j] = round2(v, SGRPROJ_SGR_BITS + shift - SGRPROJ_RST_BITS);
449
- }
450
- }
451
- }
1
+ import { clamp } from "../../../../../core/math/clamp.js";
2
+ import {
3
+ FILTER_BITS,
4
+ MI_SIZE,
5
+ RESTORE_NONE,
6
+ RESTORE_SGRPROJ,
7
+ RESTORE_WIENER,
8
+ SGRPROJ_MTABLE_BITS,
9
+ SGRPROJ_PRJ_BITS,
10
+ SGRPROJ_RECIP_BITS,
11
+ SGRPROJ_RST_BITS,
12
+ SGRPROJ_SGR_BITS
13
+ } from "../tables/av1_symbols.js";
14
+ import { SGR_PARAMS } from "../tables/filter_tables.js";
15
+ import { round2, round2_wide } from "../util/round2.js";
16
+
17
+ /**
18
+ * `A` and `B` from the box filter, valid over one extra sample in each direction.
19
+ * @type {number}
20
+ */
21
+ const BOX_STRIDE = MI_SIZE + 2;
22
+
23
+ /** @type {Int32Array} */
24
+ const BOX_A = new Int32Array(BOX_STRIDE * BOX_STRIDE);
25
+
26
+ /** @type {Int32Array} */
27
+ const BOX_B = new Int32Array(BOX_STRIDE * BOX_STRIDE);
28
+
29
+ /** @type {Int32Array} the two self-guided passes' outputs */
30
+ const FLT0 = new Int32Array(MI_SIZE * MI_SIZE);
31
+
32
+ /** @type {Int32Array} */
33
+ const FLT1 = new Int32Array(MI_SIZE * MI_SIZE);
34
+
35
+ /** @type {Int32Array} the Wiener filter's horizontal pass */
36
+ const WIENER_INTERMEDIATE = new Int32Array((MI_SIZE + 6) * MI_SIZE);
37
+
38
+ /** @type {Int32Array} the seven symmetric taps expanded from the three coded ones */
39
+ const WIENER_VERTICAL = new Int32Array(7);
40
+
41
+ /** @type {Int32Array} */
42
+ const WIENER_HORIZONTAL = new Int32Array(7);
43
+
44
+ /**
45
+ * State the source-sample fetch needs, kept here rather than threaded through every call.
46
+ *
47
+ * The fetch decides between the deringed frame and the merely deblocked one based on which stripe
48
+ * the sample falls in, and that boundary changes per block. Hoisting it into a small module-level
49
+ * record keeps the innermost loops free of parameters they would otherwise carry six deep.
50
+ */
51
+ const SOURCE = {
52
+ ctx: null,
53
+ plane: 0,
54
+ stripe_start_y: 0,
55
+ stripe_end_y: 0,
56
+ plane_end_x: 0,
57
+ plane_end_y: 0,
58
+ stride: 0,
59
+ cdef: null,
60
+ current: null
61
+ };
62
+
63
+ /**
64
+ * Apply loop restoration.
65
+ *
66
+ * Reads the deringed frame and the pre-CDEF frame, and writes the restored result. The two inputs
67
+ * are not interchangeable: within a stripe the filter reads deringed samples, and outside it reads
68
+ * deblocked ones, which is what lets a hardware decoder run restoration a stripe behind CDEF
69
+ * without an extra line buffer.
70
+ *
71
+ * @param {Av1FrameContext} ctx
72
+ * @param {Uint16Array[]} current the deblocked frame, upscaled if superres is in use
73
+ * @param {Uint16Array[]} cdef the deringed frame, upscaled if superres is in use
74
+ * @param {Uint16Array[]} output
75
+ * @param {Int32Array} stride
76
+ * @see AV1 spec 7.17 "Loop restoration process"
77
+ *
78
+ * @author Alex Goldring
79
+ * @copyright Company Named Limited (c) 2026
80
+ */
81
+ export function apply_loop_restoration(ctx, current, cdef, output, stride) {
82
+ const frame = ctx.frame;
83
+
84
+ for (let plane = 0; plane < ctx.num_planes; plane++) {
85
+ // the buffers are reused across frames, so the source may be longer than this frame
86
+ const samples = stride[plane] * ctx.plane_rows[plane];
87
+
88
+ output[plane].set(cdef[plane].subarray(0, samples));
89
+ }
90
+
91
+ if (frame.uses_lr === 0) {
92
+ return;
93
+ }
94
+
95
+ for (let y = 0; y < frame.frame_height; y += MI_SIZE) {
96
+ for (let x = 0; x < frame.upscaled_width; x += MI_SIZE) {
97
+ for (let plane = 0; plane < ctx.num_planes; plane++) {
98
+ if (frame.frame_restoration_type[plane] === RESTORE_NONE) {
99
+ continue;
100
+ }
101
+
102
+ restore_block(ctx, current, cdef, output, stride, plane, y >> 2, x >> 2);
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ /**
109
+ * @param {Av1FrameContext} ctx
110
+ * @param {Uint16Array[]} current
111
+ * @param {Uint16Array[]} cdef
112
+ * @param {Uint16Array[]} output
113
+ * @param {Int32Array} stride
114
+ * @param {number} plane
115
+ * @param {number} row
116
+ * @param {number} col
117
+ * @see AV1 spec 7.17.1 "Loop restore block process"
118
+ */
119
+ function restore_block(ctx, current, cdef, output, stride, plane, row, col) {
120
+ const frame = ctx.frame;
121
+
122
+ const luma_y = row * MI_SIZE;
123
+
124
+ // stripes are offset up by eight luma samples so a pipelined decoder can restore a stripe as
125
+ // soon as the superblock row above it is deblocked
126
+ const stripe = Math.floor((luma_y + 8) / 64);
127
+
128
+ const sub_x = plane > 0 ? ctx.subsampling_x : 0;
129
+ const sub_y = plane > 0 ? ctx.subsampling_y : 0;
130
+
131
+ SOURCE.ctx = ctx;
132
+ SOURCE.plane = plane;
133
+ SOURCE.stripe_start_y = (-8 + stripe * 64) >> sub_y;
134
+ SOURCE.stripe_end_y = SOURCE.stripe_start_y + (64 >> sub_y) - 1;
135
+ SOURCE.plane_end_x = round2(frame.upscaled_width, sub_x) - 1;
136
+ SOURCE.plane_end_y = round2(frame.frame_height, sub_y) - 1;
137
+ SOURCE.stride = stride[plane];
138
+ SOURCE.cdef = cdef[plane];
139
+ SOURCE.current = current[plane];
140
+
141
+ const x = (col * MI_SIZE) >> sub_x;
142
+ const y = (row * MI_SIZE) >> sub_y;
143
+
144
+ const w = Math.min(MI_SIZE >> sub_x, SOURCE.plane_end_x - x + 1);
145
+ const h = Math.min(MI_SIZE >> sub_y, SOURCE.plane_end_y - y + 1);
146
+
147
+ if (w <= 0 || h <= 0) {
148
+ return;
149
+ }
150
+
151
+ const unit_index = restoration_unit_of(ctx, plane, row, col);
152
+
153
+ const type = ctx.lr_type[unit_index];
154
+
155
+ if (type === RESTORE_WIENER) {
156
+ wiener_filter(ctx, output, stride, plane, unit_index, x, y, w, h);
157
+ } else if (type === RESTORE_SGRPROJ) {
158
+ self_guided_filter(ctx, output, stride, plane, unit_index, x, y, w, h);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * `count_units_in_frame`: how many restoration units of one size a frame is divided into.
164
+ *
165
+ * @param {number} unit_size
166
+ * @param {number} frame_size
167
+ * @returns {number}
168
+ */
169
+ export function count_units_in_frame(unit_size, frame_size) {
170
+ return Math.max(Math.floor((frame_size + (unit_size >> 1)) / unit_size), 1);
171
+ }
172
+
173
+ /**
174
+ * Which restoration unit a 4x4 block is filtered by.
175
+ *
176
+ * The vertical mapping is offset by eight luma samples, because restoration works in stripes that
177
+ * are offset by that much. Exported because the encoder has to attribute a block's error to the
178
+ * same unit this attributes its filtering to, and the two agreeing is the whole point.
179
+ *
180
+ * @param {Av1FrameContext} ctx
181
+ * @param {number} plane
182
+ * @param {number} row 4x4 block row
183
+ * @param {number} col 4x4 block column
184
+ * @returns {number} index into the per-unit arrays
185
+ */
186
+ export function restoration_unit_of(ctx, plane, row, col) {
187
+ const frame = ctx.frame;
188
+
189
+ const sub_x = plane > 0 ? ctx.subsampling_x : 0;
190
+ const sub_y = plane > 0 ? ctx.subsampling_y : 0;
191
+
192
+ const unit_size = frame.loop_restoration_size[plane];
193
+
194
+ const unit_rows = count_units_in_frame(unit_size, round2(frame.frame_height, sub_y));
195
+ const unit_cols = count_units_in_frame(unit_size, round2(frame.upscaled_width, sub_x));
196
+
197
+ const unit_row = Math.min(unit_rows - 1, Math.floor(((row * MI_SIZE + 8) >> sub_y) / unit_size));
198
+ const unit_col = Math.min(unit_cols - 1, Math.floor(((col * MI_SIZE) >> sub_x) / unit_size));
199
+
200
+ return ctx.lr_unit_index(plane, unit_row, unit_col);
201
+ }
202
+
203
+ /**
204
+ * `get_source_sample`: deringed inside the current stripe, merely deblocked outside it.
205
+ *
206
+ * @param {number} x
207
+ * @param {number} y
208
+ * @returns {number}
209
+ * @see AV1 spec 7.17.6 "Get source sample process"
210
+ */
211
+ function source_sample(x, y) {
212
+ const clamped_x = clamp(x, 0, SOURCE.plane_end_x);
213
+
214
+ let clamped_y = clamp(y, 0, SOURCE.plane_end_y);
215
+
216
+ if (clamped_y < SOURCE.stripe_start_y) {
217
+ clamped_y = Math.max(SOURCE.stripe_start_y - 2, clamped_y);
218
+
219
+ return SOURCE.current[clamped_y * SOURCE.stride + clamped_x];
220
+ }
221
+
222
+ if (clamped_y > SOURCE.stripe_end_y) {
223
+ clamped_y = Math.min(SOURCE.stripe_end_y + 2, clamped_y);
224
+
225
+ return SOURCE.current[clamped_y * SOURCE.stride + clamped_x];
226
+ }
227
+
228
+ return SOURCE.cdef[clamped_y * SOURCE.stride + clamped_x];
229
+ }
230
+
231
+ /**
232
+ * @param {Av1FrameContext} ctx
233
+ * @param {Uint16Array[]} output
234
+ * @param {Int32Array} stride
235
+ * @param {number} plane
236
+ * @param {number} unit_index
237
+ * @param {number} x
238
+ * @param {number} y
239
+ * @param {number} w
240
+ * @param {number} h
241
+ * @see AV1 spec 7.17.4 "Wiener filter process"
242
+ */
243
+ function wiener_filter(ctx, output, stride, plane, unit_index, x, y, w, h) {
244
+ const bit_depth = ctx.bit_depth;
245
+
246
+ // the rounding the specification derives for a non-compound inter prediction, reused here
247
+ const round0 = bit_depth === 12 ? 5 : 3;
248
+ const round1 = bit_depth === 12 ? 9 : 11;
249
+
250
+ expand_wiener(ctx.lr_wiener, unit_index * 6, WIENER_VERTICAL);
251
+ expand_wiener(ctx.lr_wiener, unit_index * 6 + 3, WIENER_HORIZONTAL);
252
+
253
+ const offset = 1 << (bit_depth + FILTER_BITS - round0 - 1);
254
+ const limit = (1 << (bit_depth + 1 + FILTER_BITS - round0)) - 1;
255
+
256
+ for (let r = 0; r < h + 6; r++) {
257
+ for (let c = 0; c < w; c++) {
258
+ let s = 0;
259
+
260
+ for (let t = 0; t < 7; t++) {
261
+ s += WIENER_HORIZONTAL[t] * source_sample(x + c + t - 3, y + r - 3);
262
+ }
263
+
264
+ WIENER_INTERMEDIATE[r * MI_SIZE + c] = clamp(round2(s, round0), -offset, limit - offset);
265
+ }
266
+ }
267
+
268
+ const max_value = (1 << bit_depth) - 1;
269
+
270
+ const plane_output = output[plane];
271
+ const plane_stride = stride[plane];
272
+
273
+ for (let r = 0; r < h; r++) {
274
+ for (let c = 0; c < w; c++) {
275
+ let s = 0;
276
+
277
+ for (let t = 0; t < 7; t++) {
278
+ s += WIENER_VERTICAL[t] * WIENER_INTERMEDIATE[(r + t) * MI_SIZE + c];
279
+ }
280
+
281
+ plane_output[(y + r) * plane_stride + x + c] = clamp(round2(s, round1), 0, max_value);
282
+ }
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Expand three coded taps into the seven of a symmetric unit-gain filter.
288
+ *
289
+ * @param {Int8Array} coefficients
290
+ * @param {number} offset
291
+ * @param {Int32Array} filter
292
+ * @see AV1 spec 7.17.5 "Wiener coefficient process"
293
+ */
294
+ function expand_wiener(coefficients, offset, filter) {
295
+ filter[3] = 128;
296
+
297
+ for (let i = 0; i < 3; i++) {
298
+ const c = coefficients[offset + i];
299
+
300
+ filter[i] = c;
301
+ filter[6 - i] = c;
302
+ filter[3] -= 2 * c;
303
+ }
304
+ }
305
+
306
+ /**
307
+ * @param {Av1FrameContext} ctx
308
+ * @param {Uint16Array[]} output
309
+ * @param {Int32Array} stride
310
+ * @param {number} plane
311
+ * @param {number} unit_index
312
+ * @param {number} x
313
+ * @param {number} y
314
+ * @param {number} w
315
+ * @param {number} h
316
+ * @see AV1 spec 7.17.2 "Self guided filter process"
317
+ */
318
+ function self_guided_filter(ctx, output, stride, plane, unit_index, x, y, w, h) {
319
+ const set = ctx.lr_sgr_set[unit_index];
320
+
321
+ box_filter(ctx, x, y, w, h, set, 0, FLT0);
322
+ box_filter(ctx, x, y, w, h, set, 1, FLT1);
323
+
324
+ const w0 = ctx.lr_sgr_xqd[unit_index * 2];
325
+ const w1 = ctx.lr_sgr_xqd[unit_index * 2 + 1];
326
+ const w2 = (1 << SGRPROJ_PRJ_BITS) - w0 - w1;
327
+
328
+ const r0 = SGR_PARAMS[set * 4];
329
+ const r1 = SGR_PARAMS[set * 4 + 2];
330
+
331
+ const max_value = (1 << ctx.bit_depth) - 1;
332
+
333
+ const plane_output = output[plane];
334
+ const plane_stride = stride[plane];
335
+
336
+ for (let i = 0; i < h; i++) {
337
+ for (let j = 0; j < w; j++) {
338
+ const u = SOURCE.cdef[(y + i) * SOURCE.stride + x + j] << SGRPROJ_RST_BITS;
339
+
340
+ let v = w1 * u;
341
+
342
+ v += r0 !== 0 ? w0 * FLT0[i * MI_SIZE + j] : w0 * u;
343
+ v += r1 !== 0 ? w2 * FLT1[i * MI_SIZE + j] : w2 * u;
344
+
345
+ plane_output[(y + i) * plane_stride + x + j] = clamp(
346
+ round2(v, SGRPROJ_RST_BITS + SGRPROJ_PRJ_BITS),
347
+ 0,
348
+ max_value
349
+ );
350
+ }
351
+ }
352
+ }
353
+
354
+ /**
355
+ * @param {Av1FrameContext} ctx
356
+ * @param {number} x
357
+ * @param {number} y
358
+ * @param {number} w
359
+ * @param {number} h
360
+ * @param {number} set
361
+ * @param {number} pass
362
+ * @param {Int32Array} out
363
+ * @see AV1 spec 7.17.3 "Box filter process"
364
+ */
365
+ function box_filter(ctx, x, y, w, h, set, pass, out) {
366
+ const r = SGR_PARAMS[set * 4 + pass * 2];
367
+
368
+ if (r === 0) {
369
+ return;
370
+ }
371
+
372
+ const eps = SGR_PARAMS[set * 4 + pass * 2 + 1];
373
+
374
+ const bit_depth = ctx.bit_depth;
375
+
376
+ const n = (2 * r + 1) * (2 * r + 1);
377
+ const n2e = n * n * eps;
378
+ const s = Math.floor(((1 << SGRPROJ_MTABLE_BITS) + Math.floor(n2e / 2)) / n2e);
379
+
380
+ const one_over_n = Math.floor(((1 << SGRPROJ_RECIP_BITS) + Math.floor(n / 2)) / n);
381
+
382
+ for (let i = -1; i < h + 1; i++) {
383
+ for (let j = -1; j < w + 1; j++) {
384
+ let a = 0;
385
+ let b = 0;
386
+
387
+ for (let dy = -r; dy <= r; dy++) {
388
+ for (let dx = -r; dx <= r; dx++) {
389
+ const c = source_sample(x + j + dx, y + i + dy);
390
+
391
+ a += c * c;
392
+ b += c;
393
+ }
394
+ }
395
+
396
+ a = round2(a, 2 * (bit_depth - 8));
397
+
398
+ const d = round2(b, bit_depth - 8);
399
+
400
+ const p = Math.max(0, a * n - d * d);
401
+
402
+ /**
403
+ * `round2_wide`, not `round2`: `p` reaches n^2 times the window variance and `s` is up to
404
+ * 3236, so `p * s` runs past 2^31 on ordinary content — a 3x3 window across a hard
405
+ * black/white edge at 8 bit gives 3,787,576,200, where the shifted form returns -484
406
+ * against an exact 3612. `a2` then comes out at 257 instead of the saturated 256 and the
407
+ * pixel is filtered with an inverted correction, exactly where restoration is meant to
408
+ * help. A `z` of -1 would divide by zero and black the pixel out outright.
409
+ */
410
+ const z = round2_wide(p * s, SGRPROJ_MTABLE_BITS);
411
+
412
+ let a2;
413
+
414
+ if (z >= 255) {
415
+ a2 = 256;
416
+ } else if (z === 0) {
417
+ a2 = 1;
418
+ } else {
419
+ a2 = Math.floor(((z << SGRPROJ_SGR_BITS) + Math.floor(z / 2)) / (z + 1));
420
+ }
421
+
422
+ const b2 = ((1 << SGRPROJ_SGR_BITS) - a2) * b * one_over_n;
423
+
424
+ const index = (i + 1) * BOX_STRIDE + j + 1;
425
+
426
+ BOX_A[index] = a2;
427
+ //same overflow as `z` above: at 12 bit a flat near-white region gives b2 = 4,281,322,500
428
+ BOX_B[index] = round2_wide(b2, SGRPROJ_RECIP_BITS);
429
+ }
430
+ }
431
+
432
+ for (let i = 0; i < h; i++) {
433
+ const shift = pass === 0 && (i & 1) !== 0 ? 4 : 5;
434
+
435
+ for (let j = 0; j < w; j++) {
436
+ let a = 0;
437
+ let b = 0;
438
+
439
+ for (let dy = -1; dy <= 1; dy++) {
440
+ for (let dx = -1; dx <= 1; dx++) {
441
+ let weight;
442
+
443
+ if (pass === 0) {
444
+ weight = ((i + dy) & 1) !== 0 ? (dx === 0 ? 6 : 5) : 0;
445
+ } else {
446
+ weight = dx === 0 || dy === 0 ? 4 : 3;
447
+ }
448
+
449
+ const index = (i + dy + 1) * BOX_STRIDE + j + dx + 1;
450
+
451
+ a += weight * BOX_A[index];
452
+ b += weight * BOX_B[index];
453
+ }
454
+ }
455
+
456
+ const v = a * SOURCE.cdef[(y + i) * SOURCE.stride + x + j] + b;
457
+
458
+ out[i * MI_SIZE + j] = round2(v, SGRPROJ_SGR_BITS + shift - SGRPROJ_RST_BITS);
459
+ }
460
+ }
461
+ }