@woosh/meep-engine 3.14.0 → 3.14.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 (220) hide show
  1. package/README.md +33 -64
  2. package/package.json +1 -1
  3. package/src/avif/native/DECISIONS.md +544 -536
  4. package/src/avif/native/heif/parse_avif_file.d.ts.map +1 -1
  5. package/src/avif/native/heif/parse_avif_file.js +384 -383
  6. package/src/avif/native/heif/parse_image_grid.d.ts.map +1 -1
  7. package/src/avif/native/heif/parse_image_grid.js +49 -48
  8. package/src/basis/BasisBitReader.d.ts +77 -0
  9. package/src/basis/BasisBitReader.d.ts.map +1 -0
  10. package/src/basis/BasisBitReader.js +231 -0
  11. package/src/basis/BasisHuffmanTable.d.ts +53 -0
  12. package/src/basis/BasisHuffmanTable.d.ts.map +1 -0
  13. package/src/basis/BasisHuffmanTable.js +263 -0
  14. package/src/basis/basis_read_huffman_table.d.ts +20 -0
  15. package/src/basis/basis_read_huffman_table.d.ts.map +1 -0
  16. package/src/basis/basis_read_huffman_table.js +187 -0
  17. package/src/basis/etc1s/Etc1sSelectorHistory.d.ts +50 -0
  18. package/src/basis/etc1s/Etc1sSelectorHistory.d.ts.map +1 -0
  19. package/src/basis/etc1s/Etc1sSelectorHistory.js +89 -0
  20. package/src/basis/etc1s/etc1s_block_colors.d.ts +14 -0
  21. package/src/basis/etc1s/etc1s_block_colors.d.ts.map +1 -0
  22. package/src/basis/etc1s/etc1s_block_colors.js +75 -0
  23. package/src/basis/etc1s/etc1s_decode_slice.d.ts +57 -0
  24. package/src/basis/etc1s/etc1s_decode_slice.d.ts.map +1 -0
  25. package/src/basis/etc1s/etc1s_decode_slice.js +258 -0
  26. package/src/basis/etc1s/etc1s_read_endpoint_codebook.d.ts +48 -0
  27. package/src/basis/etc1s/etc1s_read_endpoint_codebook.d.ts.map +1 -0
  28. package/src/basis/etc1s/etc1s_read_endpoint_codebook.js +111 -0
  29. package/src/basis/etc1s/etc1s_read_selector_codebook.d.ts +42 -0
  30. package/src/basis/etc1s/etc1s_read_selector_codebook.d.ts.map +1 -0
  31. package/src/basis/etc1s/etc1s_read_selector_codebook.js +127 -0
  32. package/src/basis/etc1s/etc1s_read_slice_tables.d.ts +54 -0
  33. package/src/basis/etc1s/etc1s_read_slice_tables.d.ts.map +1 -0
  34. package/src/basis/etc1s/etc1s_read_slice_tables.js +47 -0
  35. package/src/basis/transcode/build_range_plan.js +54 -0
  36. package/src/basis/transcode/etc1s_to_bc1.d.ts +24 -0
  37. package/src/basis/transcode/etc1s_to_bc1.d.ts.map +1 -0
  38. package/src/basis/transcode/etc1s_to_bc1.js +205 -0
  39. package/src/basis/transcode/etc1s_to_bc7.d.ts +30 -0
  40. package/src/basis/transcode/etc1s_to_bc7.d.ts.map +1 -0
  41. package/src/basis/transcode/etc1s_to_bc7.js +496 -0
  42. package/src/basis/transcode/fit_endpoints.d.ts +59 -0
  43. package/src/basis/transcode/fit_endpoints.d.ts.map +1 -0
  44. package/src/basis/transcode/fit_endpoints.js +226 -0
  45. package/src/{avif/native/isobmff → core/binary}/ByteCursor.d.ts +19 -6
  46. package/src/core/binary/ByteCursor.d.ts.map +1 -0
  47. package/src/{avif/native/isobmff → core/binary}/ByteCursor.js +41 -14
  48. package/src/engine/EngineHarness.js +1 -1
  49. package/src/engine/asset/codec/Codec.d.ts.map +1 -0
  50. package/src/engine/asset/codec/CodecWithFallback.d.ts.map +1 -0
  51. package/src/engine/asset/{loaders/image/codec → codec}/CodecWithFallback.js +2 -2
  52. package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +4 -4
  53. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts +1 -1
  54. package/src/engine/asset/loaders/image/ImageRGBADataLoader.d.ts.map +1 -1
  55. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +1 -1
  56. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.d.ts +1 -1
  57. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.d.ts.map +1 -1
  58. package/src/engine/asset/loaders/image/codec/NativeImageDecoder.js +1 -1
  59. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.d.ts +1 -1
  60. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.d.ts.map +1 -1
  61. package/src/engine/asset/loaders/image/codec/ThreadedImageDecoder.js +1 -1
  62. package/src/engine/graphics/texture/atlas/TextureAtlas.d.ts.map +1 -1
  63. package/src/engine/graphics/texture/atlas/TextureAtlas.js +13 -0
  64. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  65. package/src/engine/physics/broadphase/generate_pairs.js +7 -1
  66. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  67. package/src/ktx2/KTX2_IDENTIFIER.d.ts +14 -0
  68. package/src/ktx2/KTX2_IDENTIFIER.d.ts.map +1 -0
  69. package/src/ktx2/KTX2_IDENTIFIER.js +15 -0
  70. package/src/ktx2/KhrDfModel.d.ts +35 -0
  71. package/src/ktx2/KhrDfModel.d.ts.map +1 -0
  72. package/src/ktx2/KhrDfModel.js +49 -0
  73. package/src/ktx2/SupercompressionScheme.d.ts +16 -0
  74. package/src/ktx2/SupercompressionScheme.d.ts.map +1 -0
  75. package/src/ktx2/SupercompressionScheme.js +23 -0
  76. package/src/ktx2/ktx2_read.d.ts +71 -0
  77. package/src/ktx2/ktx2_read.d.ts.map +1 -0
  78. package/src/ktx2/ktx2_read.js +581 -0
  79. package/src/ktx2/ktx2_read_basis_lz_global_data.d.ts +27 -0
  80. package/src/ktx2/ktx2_read_basis_lz_global_data.d.ts.map +1 -0
  81. package/src/ktx2/ktx2_read_basis_lz_global_data.js +189 -0
  82. package/src/ktx2/vk_format_to_texture_format.d.ts +55 -0
  83. package/src/ktx2/vk_format_to_texture_format.d.ts.map +1 -0
  84. package/src/ktx2/vk_format_to_texture_format.js +290 -0
  85. package/src/shade/descriptor/GPUTextureUsage.d.ts +23 -0
  86. package/src/shade/descriptor/GPUTextureUsage.d.ts.map +1 -0
  87. package/src/shade/descriptor/GPUTextureUsage.js +24 -0
  88. package/src/shade/descriptor/WebGPUExtensionType.d.ts +3 -0
  89. package/src/shade/descriptor/WebGPUExtensionType.js +26 -0
  90. package/src/shade/descriptor/texture/TextureDescriptor.d.ts +2 -6
  91. package/src/shade/descriptor/texture/TextureDescriptor.d.ts.map +1 -1
  92. package/src/shade/descriptor/texture/TextureDescriptor.js +24 -40
  93. package/src/shade/descriptor/texture/format/TextureFormat.d.ts +125 -0
  94. package/src/shade/descriptor/texture/format/TextureFormat.d.ts.map +1 -0
  95. package/src/shade/descriptor/texture/format/TextureFormat.js +146 -0
  96. package/src/shade/descriptor/texture/format/gpu_texture_format_info.d.ts +60 -0
  97. package/src/shade/descriptor/texture/format/gpu_texture_format_info.d.ts.map +1 -1
  98. package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +102 -0
  99. package/src/shade/device/graph/TextureResourceDescriptor.d.ts.map +1 -1
  100. package/src/shade/device/graph/TextureResourceDescriptor.js +160 -155
  101. package/src/shade/device/mock/SoftwareGPUQueue.d.ts +11 -4
  102. package/src/shade/device/mock/SoftwareGPUQueue.d.ts.map +1 -1
  103. package/src/shade/device/mock/SoftwareGPUQueue.js +31 -37
  104. package/src/shade/device/mock/SoftwareGPUTexture.d.ts.map +1 -1
  105. package/src/shade/device/mock/SoftwareGPUTexture.js +8 -4
  106. package/src/shade/device/timing/profile/frame_graph_extract_topology.d.ts.map +1 -1
  107. package/src/shade/device/timing/profile/frame_graph_extract_topology.js +164 -174
  108. package/src/shade/playground/ground_seam/README.md +235 -0
  109. package/src/shade/playground/ground_seam/capture_scene_color.d.ts +45 -0
  110. package/src/shade/playground/ground_seam/capture_scene_color.d.ts.map +1 -0
  111. package/src/shade/playground/ground_seam/capture_scene_color.js +242 -0
  112. package/src/shade/playground/ground_seam/depth_step_across_seam.d.ts +11 -0
  113. package/src/shade/playground/ground_seam/depth_step_across_seam.d.ts.map +1 -0
  114. package/src/shade/playground/ground_seam/depth_step_across_seam.js +185 -0
  115. package/src/shade/playground/ground_seam/index.html +195 -0
  116. package/src/shade/playground/ground_seam/main.d.ts +2 -0
  117. package/src/shade/playground/ground_seam/main.d.ts.map +1 -0
  118. package/src/shade/playground/ground_seam/main.js +879 -0
  119. package/src/shade/playground/ground_seam/make_grid_texture.d.ts +29 -0
  120. package/src/shade/playground/ground_seam/make_grid_texture.d.ts.map +1 -0
  121. package/src/shade/playground/ground_seam/make_grid_texture.js +105 -0
  122. package/src/shade/playground/ground_seam/make_ground.d.ts +70 -0
  123. package/src/shade/playground/ground_seam/make_ground.d.ts.map +1 -0
  124. package/src/shade/playground/ground_seam/make_ground.js +161 -0
  125. package/src/shade/playground/ground_seam/measure_flicker.d.ts +71 -0
  126. package/src/shade/playground/ground_seam/measure_flicker.d.ts.map +1 -0
  127. package/src/shade/playground/ground_seam/measure_flicker.js +531 -0
  128. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  129. package/src/shade/renderer/Renderer.js +2519 -2511
  130. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  131. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +275 -274
  132. package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
  133. package/src/shade/renderer/light/GPULightCollection.js +323 -314
  134. package/src/shade/renderer/lightmap/GPULightMap.js +336 -336
  135. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
  136. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +12 -2
  137. package/src/shade/renderer/material/standard/fragment_gbuffer.d.ts.map +1 -1
  138. package/src/shade/renderer/material/standard/fragment_gbuffer.js +3 -1
  139. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  140. package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.d.ts.map +1 -1
  141. package/src/shade/renderer/path_tracer/shader/chunk_sample_material_data.js +3 -1
  142. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  143. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_brick4.d.ts.map +1 -1
  144. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_brick4.js +3 -1
  145. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_ibl.d.ts.map +1 -1
  146. package/src/shade/renderer/rasterize/native/oit/chunk_forward_shade_standard_fragment_ibl.js +3 -1
  147. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_tangent_normal.d.ts +28 -0
  148. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_tangent_normal.d.ts.map +1 -0
  149. package/src/shade/renderer/shader/chunk/geometry/normal/chunk_decode_tangent_normal.js +39 -0
  150. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_full.d.ts +45 -0
  151. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_full.d.ts.map +1 -1
  152. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_full.js +94 -61
  153. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_solve.d.ts +24 -0
  154. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_solve.d.ts.map +1 -0
  155. package/src/shade/renderer/shader/chunk/geometry/triangle/chunk_barycentric_solve.js +43 -0
  156. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +1462 -0
  157. package/src/shade/renderer/texture/TextureManager.d.ts.map +1 -1
  158. package/src/shade/renderer/texture/TextureManager.js +20 -8
  159. package/src/shade/renderer/texture/TextureRole.d.ts +19 -0
  160. package/src/shade/renderer/texture/TextureRole.d.ts.map +1 -0
  161. package/src/shade/renderer/texture/TextureRole.js +26 -0
  162. package/src/shade/renderer/texture/TextureVariantSet.d.ts +71 -0
  163. package/src/shade/renderer/texture/TextureVariantSet.d.ts.map +1 -0
  164. package/src/shade/renderer/texture/TextureVariantSet.js +105 -0
  165. package/src/shade/renderer/texture/download_texture_data.d.ts.map +1 -1
  166. package/src/shade/renderer/texture/download_texture_data.js +22 -0
  167. package/src/shade/renderer/texture/format/gpu_texture_compression_support.d.ts +15 -0
  168. package/src/shade/renderer/texture/format/gpu_texture_compression_support.d.ts.map +1 -0
  169. package/src/shade/renderer/texture/format/gpu_texture_compression_support.js +50 -0
  170. package/src/shade/renderer/texture/format/gpu_texture_format_can_generate_mipmaps.d.ts +27 -0
  171. package/src/shade/renderer/texture/format/gpu_texture_format_can_generate_mipmaps.d.ts.map +1 -0
  172. package/src/shade/renderer/texture/format/gpu_texture_format_can_generate_mipmaps.js +65 -0
  173. package/src/shade/renderer/texture/format/infer_format_from_parameters.d.ts +11 -0
  174. package/src/shade/renderer/texture/format/infer_format_from_parameters.d.ts.map +1 -0
  175. package/src/shade/renderer/texture/format/infer_format_from_parameters.js +88 -0
  176. package/src/shade/renderer/texture/format/texture_format_from_shade_image.d.ts +24 -0
  177. package/src/shade/renderer/texture/format/texture_format_from_shade_image.d.ts.map +1 -0
  178. package/src/shade/renderer/texture/format/texture_format_from_shade_image.js +71 -0
  179. package/src/shade/renderer/texture/format/texture_format_preference.d.ts +22 -0
  180. package/src/shade/renderer/texture/format/texture_format_preference.d.ts.map +1 -0
  181. package/src/shade/renderer/texture/format/texture_format_preference.js +89 -0
  182. package/src/shade/renderer/texture/format/texture_variant_select.d.ts +24 -0
  183. package/src/shade/renderer/texture/format/texture_variant_select.d.ts.map +1 -0
  184. package/src/shade/renderer/texture/format/texture_variant_select.js +38 -0
  185. package/src/shade/renderer/texture/shade_texture_might_have_alpha.d.ts +9 -1
  186. package/src/shade/renderer/texture/shade_texture_might_have_alpha.d.ts.map +1 -1
  187. package/src/shade/renderer/texture/shade_texture_might_have_alpha.js +73 -8
  188. package/src/shade/renderer/texture/source/AlphaMode.d.ts +27 -0
  189. package/src/shade/renderer/texture/source/AlphaMode.d.ts.map +1 -0
  190. package/src/shade/renderer/texture/source/AlphaMode.js +52 -0
  191. package/src/shade/renderer/texture/source/ShadeImage.d.ts +33 -0
  192. package/src/shade/renderer/texture/source/ShadeImage.d.ts.map +1 -1
  193. package/src/shade/renderer/texture/source/ShadeImage.js +74 -0
  194. package/src/shade/renderer/texture/source/ShadeTexelData.d.ts +78 -0
  195. package/src/shade/renderer/texture/source/ShadeTexelData.d.ts.map +1 -0
  196. package/src/shade/renderer/texture/source/ShadeTexelData.js +158 -0
  197. package/src/shade/renderer/texture/texture_descriptor_from_texture_shade.d.ts +0 -10
  198. package/src/shade/renderer/texture/texture_descriptor_from_texture_shade.d.ts.map +1 -1
  199. package/src/shade/renderer/texture/texture_descriptor_from_texture_shade.js +73 -175
  200. package/src/shade/renderer/texture/texture_write_level.d.ts +32 -0
  201. package/src/shade/renderer/texture/texture_write_level.d.ts.map +1 -0
  202. package/src/shade/renderer/texture/texture_write_level.js +77 -0
  203. package/src/shade/renderer/texture/texture_write_to_gpu.d.ts +8 -1
  204. package/src/shade/renderer/texture/texture_write_to_gpu.d.ts.map +1 -1
  205. package/src/shade/renderer/texture/texture_write_to_gpu.js +62 -37
  206. package/src/shade/renderer/texture/virtual/source/VTSourceImage.d.ts +8 -0
  207. package/src/shade/renderer/texture/virtual/source/VTSourceImage.d.ts.map +1 -1
  208. package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +8 -0
  209. package/src/shade/util/texture_mip_extent.d.ts +49 -0
  210. package/src/shade/util/texture_mip_extent.d.ts.map +1 -0
  211. package/src/shade/util/texture_mip_extent.js +87 -0
  212. package/src/avif/native/isobmff/ByteCursor.d.ts.map +0 -1
  213. package/src/engine/asset/loaders/image/codec/Codec.d.ts.map +0 -1
  214. package/src/engine/asset/loaders/image/codec/CodecWithFallback.d.ts.map +0 -1
  215. package/src/shade/util/compute_max_mip_count.d.ts +0 -8
  216. package/src/shade/util/compute_max_mip_count.d.ts.map +0 -1
  217. package/src/shade/util/compute_max_mip_count.js +0 -27
  218. /package/src/engine/asset/{loaders/image/codec → codec}/Codec.d.ts +0 -0
  219. /package/src/engine/asset/{loaders/image/codec → codec}/Codec.js +0 -0
  220. /package/src/engine/asset/{loaders/image/codec → codec}/CodecWithFallback.d.ts +0 -0
@@ -0,0 +1,242 @@
1
+ import { FramePhase } from "../../renderer/extension/FramePhase.js";
2
+ import { RenderExtension } from "../../renderer/extension/RenderExtension.js";
3
+ import { SceneColor } from "../../renderer/extension/SceneColor.js";
4
+ import { ImageShader } from "../../renderer/shader/ImageShader.js";
5
+ import { CodeChunk } from "../../renderer/shader/compiler/CodeChunk.js";
6
+ import { graph_image_pass } from "../../renderer/shader/graph/graph_image_pass.js";
7
+ import { graph_import_texture } from "../../renderer/shader/graph/graph_import_texture.js";
8
+ import { ShaderResourceSetDescriptor } from "../../renderer/shader/resource/ShaderResourceSetDescriptor.js";
9
+ import { ShaderDescriptor } from "../../renderer/shader/ShaderDescriptor.js";
10
+ import { GPUTextureContext } from "../../renderer/texture/GPUTextureContext.js";
11
+
12
+ /**
13
+ * Copy the scene colour, exactly, into a texture the caller owns.
14
+ *
15
+ * A `textureLoad` at the fragment's own integer coordinate — no sampler, no filtering, no
16
+ * conversion. The point of this capture is to compare frames against each other at the texel, so
17
+ * anything that mixed neighbours would put the answer into the measurement.
18
+ */
19
+ const resources = new ShaderResourceSetDescriptor();
20
+
21
+ resources.createGroup().addTexture("tSource");
22
+
23
+ const shader_copy_scene_color = new ImageShader({
24
+ descriptor: ShaderDescriptor.from({
25
+ label: "ground_seam/copy scene colour",
26
+ resources,
27
+ body: CodeChunk.from(
28
+ //language=WGSL
29
+ `
30
+ @fragment
31
+ fn main(
32
+ @builtin(position) coord: vec4<f32>,
33
+ ) -> @location(0) vec4<f32> {
34
+ return textureLoad(tSource, vec2<u32>(coord.xy), 0);
35
+ }
36
+ `
37
+ ),
38
+ }),
39
+ targets: [
40
+ {
41
+ label: "ground_seam capture",
42
+ format: "rgba16float",
43
+ }
44
+ ],
45
+ });
46
+
47
+ /**
48
+ * The scene colour *before* the temporal filter, made readable.
49
+ *
50
+ * This exists to split one question in two. The burst measurement sees the picture the temporal
51
+ * filter produced, and a seam in it can come from either side of that filter: the shading may be
52
+ * responding differently to the sub-pixel jitter on the two triangles, or the shading may be
53
+ * identical and the filter may be resolving it differently. Those want opposite fixes, and no
54
+ * amount of staring at the output distinguishes them.
55
+ *
56
+ * {@link FramePhase.AfterTransparency} is the last phase before the upscale, so what is captured
57
+ * here is the full pre-TAA frame at internal resolution — the temporal filter's *input*.
58
+ *
59
+ * The pass writes an imported texture, which the graph treats as a side effect, so it is not elided
60
+ * for having no consumer inside the frame.
61
+ */
62
+ export class CaptureSceneColor extends RenderExtension {
63
+ name = 'ground_seam/capture scene colour';
64
+
65
+ /**
66
+ * Which phase to capture at. {@link FramePhase.AfterTransparency} is the temporal filter's
67
+ * *input* — the last phase before the upscale. {@link FramePhase.BeforePresent} is its output,
68
+ * still HDR but past DOF, motion blur and bloom. Capturing at both is how the chain gets split
69
+ * into 'what TAA was given' and 'what TAA produced'.
70
+ *
71
+ * Assigning it after the extension is registered has no effect: the registry resolves the
72
+ * ordering when an extension is added, so a phase change means removing and re-adding.
73
+ *
74
+ * @type {FramePhase}
75
+ */
76
+ phase = FramePhase.AfterTransparency;
77
+
78
+ /**
79
+ * @type {GPUTextureContext|undefined}
80
+ */
81
+ #texture;
82
+
83
+ /**
84
+ * @type {GPUDevice}
85
+ */
86
+ #device;
87
+
88
+ /**
89
+ * @type {GPUBuffer|undefined}
90
+ */
91
+ #buffer;
92
+
93
+ #width = 0;
94
+ #height = 0;
95
+ #bytes_per_row = 0;
96
+
97
+ /**
98
+ * Whether to record at all. Off by default: a capture costs a full-resolution copy every frame,
99
+ * and the burst measurement this page mostly runs does not want one.
100
+ *
101
+ * @type {boolean}
102
+ */
103
+ enabled = false;
104
+
105
+ /**
106
+ * @param {GPUDevice} device
107
+ */
108
+ constructor(device) {
109
+ super();
110
+
111
+ this.#device = device;
112
+ }
113
+
114
+ /**
115
+ * @param {number} width
116
+ * @param {number} height
117
+ */
118
+ #ensure(width, height) {
119
+ if (this.#width === width && this.#height === height) {
120
+ return;
121
+ }
122
+
123
+ this.#texture?.destroy();
124
+ this.#buffer?.destroy();
125
+
126
+ const device = this.#device;
127
+
128
+ this.#texture = GPUTextureContext.fromTexture(
129
+ device.createTexture({
130
+ label: 'ground_seam/scene colour capture',
131
+ size: [width, height, 1],
132
+ format: 'rgba16float',
133
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
134
+ | GPUTextureUsage.TEXTURE_BINDING
135
+ | GPUTextureUsage.COPY_SRC,
136
+ }),
137
+ device
138
+ );
139
+
140
+ this.#bytes_per_row = Math.ceil(width * 8 / 256) * 256;
141
+
142
+ this.#buffer = device.createBuffer({
143
+ label: 'ground_seam/scene colour readback',
144
+ size: this.#bytes_per_row * height,
145
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
146
+ });
147
+
148
+ this.#width = width;
149
+ this.#height = height;
150
+ }
151
+
152
+ /**
153
+ * @param {FrameContext} frame
154
+ */
155
+ record(frame) {
156
+ if (!this.enabled) {
157
+ return;
158
+ }
159
+
160
+ const colour = frame.get(SceneColor).color;
161
+
162
+ const resolution = frame.resolution;
163
+
164
+ this.#ensure(resolution[0], resolution[1]);
165
+
166
+ graph_image_pass({
167
+ graph: frame.graph,
168
+ shader: shader_copy_scene_color,
169
+ inputs: { tSource: colour },
170
+ output: [graph_import_texture(frame.graph, this.#texture, 'ground_seam capture')],
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Luminance of the captured frame, one entry per pixel.
176
+ *
177
+ * Rec. 709 weights on the linear HDR values — this is upstream of the tonemap, so the numbers
178
+ * are radiance rather than display code values and there is no 8-bit quantization floor under
179
+ * the measurement.
180
+ *
181
+ * @returns {Promise<{luma: Float32Array, width: number, height: number}>}
182
+ */
183
+ async read_luminance() {
184
+ if (this.#texture === undefined) {
185
+ throw new Error('nothing has been captured yet — enable the extension and render a frame');
186
+ }
187
+
188
+ const device = this.#device;
189
+
190
+ const encoder = device.createCommandEncoder({ label: 'ground_seam/scene colour readback' });
191
+
192
+ encoder.copyTextureToBuffer(
193
+ { texture: this.#texture.gpu_texture, mipLevel: 0 },
194
+ { buffer: this.#buffer, bytesPerRow: this.#bytes_per_row },
195
+ [this.#width, this.#height, 1]
196
+ );
197
+
198
+ device.queue.submit([encoder.finish()]);
199
+
200
+ await this.#buffer.mapAsync(GPUMapMode.READ);
201
+
202
+ const source = new Uint16Array(this.#buffer.getMappedRange());
203
+
204
+ const stride = this.#bytes_per_row / 2;
205
+
206
+ const luma = new Float32Array(this.#width * this.#height);
207
+
208
+ for (let y = 0; y < this.#height; y++) {
209
+ for (let x = 0; x < this.#width; x++) {
210
+ const i = y * stride + x * 4;
211
+
212
+ luma[y * this.#width + x] = 0.2126 * half_to_float(source[i])
213
+ + 0.7152 * half_to_float(source[i + 1])
214
+ + 0.0722 * half_to_float(source[i + 2]);
215
+ }
216
+ }
217
+
218
+ this.#buffer.unmap();
219
+
220
+ return { luma, width: this.#width, height: this.#height };
221
+ }
222
+ }
223
+
224
+ /**
225
+ * @param {number} h a float16 bit pattern
226
+ * @returns {number}
227
+ */
228
+ function half_to_float(h) {
229
+ const sign = (h & 0x8000) !== 0 ? -1 : 1;
230
+ const exponent = (h & 0x7C00) >> 10;
231
+ const fraction = h & 0x03FF;
232
+
233
+ if (exponent === 0) {
234
+ return sign * Math.pow(2, -14) * (fraction / 1024);
235
+ }
236
+
237
+ if (exponent === 31) {
238
+ return fraction !== 0 ? NaN : sign * Infinity;
239
+ }
240
+
241
+ return sign * Math.pow(2, exponent - 15) * (1 + fraction / 1024);
242
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @param {Float32Array} depth
3
+ * @param {Int8Array} side −1 / +1 / 0, as {@link classify_ground} produces
4
+ * @param {function(number, number): number} distance signed screen distance to the seam, in pixels
5
+ * @param {number} width
6
+ * @param {number} height
7
+ * @param {number} [exclude] pixels either side of the seam to leave out of the fits
8
+ * @returns {object}
9
+ */
10
+ export function depth_step_across_seam(depth: Float32Array, side: Int8Array, distance: (arg0: number, arg1: number) => number, width: number, height: number, exclude?: number): object;
11
+ //# sourceMappingURL=depth_step_across_seam.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"depth_step_across_seam.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/ground_seam/depth_step_across_seam.js"],"names":[],"mappings":"AAgHA;;;;;;;;GAQG;AACH,8CARW,YAAY,QACZ,SAAS,mBACA,MAAM,QAAE,MAAM,KAAG,MAAM,SAChC,MAAM,UACN,MAAM,YACN,MAAM,GACJ,MAAM,CAiElB"}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Does the rasterized depth step across the shared edge, and by how much?
3
+ *
4
+ * This is the first of the checks the ground-seam brief calls for and the only one that reaches the
5
+ * rasterizer's own output rather than an inference about it. The reasoning it rests on:
6
+ *
7
+ * Under a reverse-Z, infinite-far projection the depth written is `near / (−z_view)`, and
8
+ * `1/(−z_view)` is an affine function of the screen coordinate over a planar surface. So the depth
9
+ * across each of the ground quad's triangles is **exactly** a plane in `(x, y)` — not approximately.
10
+ * The two triangles share two vertices, so the seam is one segment and both interpolations must
11
+ * agree along it. Whether they *do* is a number.
12
+ *
13
+ * Each side is fitted separately by least squares over pixels at least `exclude` from the seam, so
14
+ * neither fit sees the other's pixels or the boundary's partially covered ones. The two fits are
15
+ * then evaluated *on* the seam and differenced. The residuals come back with them: the fit is exact
16
+ * for a plane, so a residual much above the depth format's own quantization means the premise is
17
+ * broken and the difference is not the quantity it claims to be.
18
+ */
19
+
20
+ /**
21
+ * Least-squares fit of `depth ≈ a·x + b·y + c` over the pixels a predicate accepts.
22
+ *
23
+ * Normal equations in f64 over a 3×3 symmetric system. The coordinates are centred on the frame
24
+ * before the system is built — `x` running to 1258 against a constant term of 1 spans six orders of
25
+ * magnitude, and the inverse of that would be all rounding.
26
+ *
27
+ * @param {Float32Array} depth
28
+ * @param {number} width
29
+ * @param {number} height
30
+ * @param {function(number): boolean} accept given the pixel index
31
+ * @returns {{a: number, b: number, c: number, residual_rms: number, count: number}}
32
+ */
33
+ function fit_plane(depth, width, height, accept) {
34
+ let sxx = 0, sxy = 0, sx1 = 0, syy = 0, sy1 = 0, s11 = 0;
35
+ let sxd = 0, syd = 0, s1d = 0;
36
+ let count = 0;
37
+
38
+ const cx = width * 0.5;
39
+ const cy = height * 0.5;
40
+
41
+ for (let py = 0; py < height; py++) {
42
+ for (let px = 0; px < width; px++) {
43
+ const p = py * width + px;
44
+
45
+ if (!accept(p)) {
46
+ continue;
47
+ }
48
+
49
+ const x = px - cx;
50
+ const y = py - cy;
51
+ const d = depth[p];
52
+
53
+ sxx += x * x;
54
+ sxy += x * y;
55
+ sx1 += x;
56
+ syy += y * y;
57
+ sy1 += y;
58
+ s11 += 1;
59
+ sxd += x * d;
60
+ syd += y * d;
61
+ s1d += d;
62
+
63
+ count++;
64
+ }
65
+ }
66
+
67
+ if (count < 16) {
68
+ throw new Error(`only ${count} pixels accepted — not enough to fit a plane`);
69
+ }
70
+
71
+ const m = [sxx, sxy, sx1, sxy, syy, sy1, sx1, sy1, s11];
72
+
73
+ const c00 = m[4] * m[8] - m[5] * m[7];
74
+ const c01 = m[5] * m[6] - m[3] * m[8];
75
+ const c02 = m[3] * m[7] - m[4] * m[6];
76
+
77
+ const det = m[0] * c00 + m[1] * c01 + m[2] * c02;
78
+
79
+ if (det === 0) {
80
+ throw new Error('degenerate depth fit');
81
+ }
82
+
83
+ const c10 = m[2] * m[7] - m[1] * m[8];
84
+ const c11 = m[0] * m[8] - m[2] * m[6];
85
+ const c12 = m[1] * m[6] - m[0] * m[7];
86
+ const c20 = m[1] * m[5] - m[2] * m[4];
87
+ const c21 = m[2] * m[3] - m[0] * m[5];
88
+ const c22 = m[0] * m[4] - m[1] * m[3];
89
+
90
+ const a = (c00 * sxd + c10 * syd + c20 * s1d) / det;
91
+ const b = (c01 * sxd + c11 * syd + c21 * s1d) / det;
92
+ const c = (c02 * sxd + c12 * syd + c22 * s1d) / det;
93
+
94
+ let residual = 0;
95
+
96
+ for (let py = 0; py < height; py++) {
97
+ for (let px = 0; px < width; px++) {
98
+ const p = py * width + px;
99
+
100
+ if (!accept(p)) {
101
+ continue;
102
+ }
103
+
104
+ const e = depth[p] - (a * (px - cx) + b * (py - cy) + c);
105
+
106
+ residual += e * e;
107
+ }
108
+ }
109
+
110
+ return { a, b, c, residual_rms: Math.sqrt(residual / count), count };
111
+ }
112
+
113
+ /**
114
+ * @param {Float32Array} depth
115
+ * @param {Int8Array} side −1 / +1 / 0, as {@link classify_ground} produces
116
+ * @param {function(number, number): number} distance signed screen distance to the seam, in pixels
117
+ * @param {number} width
118
+ * @param {number} height
119
+ * @param {number} [exclude] pixels either side of the seam to leave out of the fits
120
+ * @returns {object}
121
+ */
122
+ export function depth_step_across_seam(depth, side, distance, width, height, exclude = 8) {
123
+
124
+ const far_enough = new Uint8Array(width * height);
125
+
126
+ for (let py = 0; py < height; py++) {
127
+ for (let px = 0; px < width; px++) {
128
+ far_enough[py * width + px] = Math.abs(distance(px, py)) >= exclude ? 1 : 0;
129
+ }
130
+ }
131
+
132
+ const negative = fit_plane(depth, width, height, p => side[p] === -1 && far_enough[p] === 1);
133
+ const positive = fit_plane(depth, width, height, p => side[p] === 1 && far_enough[p] === 1);
134
+
135
+ /*
136
+ Walk the seam and evaluate both fits along it. One point would do for two exact planes; the
137
+ walk reports the worst disagreement anywhere on the edge, which is what a slight *rotation*
138
+ between the two planes looks like and what a single sample would miss.
139
+ */
140
+ const cx = width * 0.5;
141
+ const cy = height * 0.5;
142
+
143
+ let worst = 0;
144
+ let worst_at = null;
145
+ let worst_ulps = 0;
146
+ let samples = 0;
147
+
148
+ for (let py = 0; py < height; py++) {
149
+ for (let px = 0; px < width; px++) {
150
+ if (Math.abs(distance(px, py)) > 0.5) {
151
+ continue;
152
+ }
153
+
154
+ const x = px - cx;
155
+ const y = py - cy;
156
+
157
+ const dn = negative.a * x + negative.b * y + negative.c;
158
+ const dp = positive.a * x + positive.b * y + positive.c;
159
+
160
+ const difference = Math.abs(dn - dp);
161
+
162
+ samples++;
163
+
164
+ if (difference > worst) {
165
+ worst = difference;
166
+ worst_at = [px, py];
167
+
168
+ // one ULP of an f32 at this magnitude, which is the unit the question is asked in
169
+ const magnitude = Math.max(Math.abs(dn), 1e-30);
170
+ const ulp = Math.pow(2, Math.floor(Math.log2(magnitude)) - 23);
171
+
172
+ worst_ulps = difference / ulp;
173
+ }
174
+ }
175
+ }
176
+
177
+ return {
178
+ negative,
179
+ positive,
180
+ seam_samples: samples,
181
+ worst_difference: worst,
182
+ worst_difference_ulps: worst_ulps,
183
+ worst_at,
184
+ };
185
+ }
@@ -0,0 +1,195 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>meep — ground seam repro</title>
6
+ <style>
7
+ :root {
8
+ color-scheme: dark;
9
+ --bg: #14161a;
10
+ --panel: #1c1f25;
11
+ --line: #2b3038;
12
+ --text: #d6dae0;
13
+ --muted: #8b939f;
14
+ --pass: #55c46a;
15
+ --fail: #ff6b6b;
16
+ --warn: #e5b54a;
17
+ }
18
+
19
+ * { box-sizing: border-box; }
20
+
21
+ body {
22
+ margin: 0;
23
+ padding: 16px;
24
+ background: var(--bg);
25
+ color: var(--text);
26
+ font: 13px/1.5 system-ui, -apple-system, Segoe UI, sans-serif;
27
+ }
28
+
29
+ h1 { font-size: 16px; font-weight: 600; margin: 0 0 4px; }
30
+
31
+ p.lede { margin: 0 0 14px; color: var(--muted); max-width: 90ch; }
32
+
33
+ .stage { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
34
+
35
+ canvas {
36
+ display: block;
37
+ border: 1px solid var(--line);
38
+ border-radius: 4px;
39
+ background: #000;
40
+ }
41
+
42
+ aside {
43
+ width: 300px;
44
+ background: var(--panel);
45
+ border: 1px solid var(--line);
46
+ border-radius: 8px;
47
+ padding: 12px 14px;
48
+ }
49
+
50
+ aside h2 {
51
+ font-size: 11px;
52
+ text-transform: uppercase;
53
+ letter-spacing: 0.06em;
54
+ color: var(--muted);
55
+ margin: 0 0 8px;
56
+ }
57
+
58
+ aside h2 + * { margin-top: 0; }
59
+
60
+ label { display: flex; gap: 8px; align-items: center; padding: 3px 0; }
61
+
62
+ label span { flex: 1; }
63
+
64
+ select, input[type="number"] {
65
+ font: inherit;
66
+ background: #14161a;
67
+ color: var(--text);
68
+ border: 1px solid var(--line);
69
+ border-radius: 4px;
70
+ padding: 3px 6px;
71
+ width: 110px;
72
+ }
73
+
74
+ button {
75
+ font: inherit;
76
+ padding: 6px 12px;
77
+ border-radius: 6px;
78
+ border: 1px solid var(--line);
79
+ background: #22303f;
80
+ color: var(--text);
81
+ cursor: pointer;
82
+ margin-top: 10px;
83
+ width: 100%;
84
+ }
85
+
86
+ button:disabled { opacity: 0.45; cursor: default; }
87
+
88
+ #status { color: var(--muted); }
89
+ #status.pass { color: var(--pass); }
90
+ #status.fail { color: var(--fail); }
91
+ #status.warn { color: var(--warn); }
92
+
93
+ pre {
94
+ background: #14161a;
95
+ border: 1px solid var(--line);
96
+ border-radius: 6px;
97
+ padding: 8px 10px;
98
+ font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
99
+ font-size: 11.5px;
100
+ white-space: pre-wrap;
101
+ margin: 10px 0 0;
102
+ max-height: 40vh;
103
+ overflow: auto;
104
+ }
105
+
106
+ hr { border: 0; border-top: 1px solid var(--line); margin: 12px 0; }
107
+ </style>
108
+ </head>
109
+ <body>
110
+
111
+ <h1>ground seam — temporal repro</h1>
112
+ <p class="lede">
113
+ One 425×425 ground quad, two triangles, a tiling grid albedo, a still camera. “Measure” settles
114
+ the temporal filter, records a burst of frames into a texture this page owns, and maps the
115
+ per-pixel luminance spread across the burst. Pixels are labelled by which triangle covers them
116
+ by unprojecting through the camera’s own matrix — nothing is fitted to the picture.
117
+ </p>
118
+
119
+ <div class="stage">
120
+ <div>
121
+ <canvas id="view"></canvas>
122
+ <canvas id="diff" style="margin-top: 12px"></canvas>
123
+ </div>
124
+
125
+ <aside>
126
+ <h2>scene</h2>
127
+
128
+ <label><span>subdivision</span>
129
+ <select id="segments">
130
+ <option value="1" selected>1 × 1 (2 tris)</option>
131
+ <option value="2">2 × 2</option>
132
+ <option value="8">8 × 8</option>
133
+ <option value="32">32 × 32</option>
134
+ </select>
135
+ </label>
136
+
137
+ <label><span>triangulation</span>
138
+ <select id="triangulation">
139
+ <option value="default" selected>default (x = −z)</option>
140
+ <option value="flipped">flipped (x = z)</option>
141
+ <option value="rotated">same tris, anchor rotated</option>
142
+ </select>
143
+ </label>
144
+
145
+ <label><span>extent</span>
146
+ <select id="extent">
147
+ <option value="425" selected>425</option>
148
+ <option value="4000">4000</option>
149
+ <option value="40000">40000</option>
150
+ </select>
151
+ </label>
152
+
153
+ <label><span>anisotropy</span><input type="checkbox" id="anisotropy" checked></label>
154
+
155
+ <label><span>ground yaw°</span><input type="number" id="yaw" value="0" step="90"></label>
156
+
157
+ <hr>
158
+
159
+ <h2>renderer</h2>
160
+
161
+ <label><span>TAA</span><input type="checkbox" id="taa" checked></label>
162
+ <label><span>GTAO</span><input type="checkbox" id="ssao" checked></label>
163
+ <label><span>shadows</span><input type="checkbox" id="shadows" checked></label>
164
+ <label><span>sharpening</span><input type="checkbox" id="sharpening" checked></label>
165
+ <label><span>auto exposure</span><input type="checkbox" id="exposure"></label>
166
+ <label><span>environment</span><input type="checkbox" id="environment"></label>
167
+
168
+ <label><span>exposure (stops)</span><input type="number" id="exposure_compensation" value="2.5" step="0.5"></label>
169
+
170
+ <label><span>width</span><input type="number" id="width" value="1258" step="2"></label>
171
+ <label><span>height</span><input type="number" id="height" value="566" step="2"></label>
172
+
173
+ <hr>
174
+
175
+ <h2>burst</h2>
176
+
177
+ <label><span>settle frames</span><input type="number" id="settle" value="240"></label>
178
+ <label><span>burst frames</span><input type="number" id="frames" value="16"></label>
179
+ <label><span>diff gain</span><input type="number" id="gain" value="8"></label>
180
+
181
+ <button id="measure">Measure</button>
182
+ <button id="apply">Apply scene / renderer</button>
183
+
184
+ <pre id="report">ready</pre>
185
+ </aside>
186
+ </div>
187
+
188
+ <script type="module">
189
+ import { start } from './main.js';
190
+
191
+ start();
192
+ </script>
193
+
194
+ </body>
195
+ </html>
@@ -0,0 +1,2 @@
1
+ export function start(): Promise<void>;
2
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/ground_seam/main.js"],"names":[],"mappings":"AAiEA,uCA6yBC"}