@woosh/meep-engine 3.50.0 → 3.51.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 (112) hide show
  1. package/package.json +1 -1
  2. package/src/engine/graphics3/make_default_environment.d.ts.map +1 -1
  3. package/src/engine/graphics3/make_default_environment.js +11 -5
  4. package/src/shade/playground/particle_ecs/moonlit_environment.d.ts.map +1 -1
  5. package/src/shade/playground/particle_ecs/moonlit_environment.js +11 -5
  6. package/src/shade/playground/sh_exponential/sh_exp_playground_shaders.d.ts.map +1 -1
  7. package/src/shade/playground/sh_exponential/sh_exp_playground_shaders.js +3 -1
  8. package/src/shade/renderer/atmosphere/chunk_sample_environment_color.d.ts +3 -0
  9. package/src/shade/renderer/atmosphere/chunk_sample_environment_color.d.ts.map +1 -1
  10. package/src/shade/renderer/atmosphere/chunk_sample_environment_color.js +11 -8
  11. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.d.ts +6 -0
  12. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.d.ts.map +1 -1
  13. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.js +227 -215
  14. package/src/shade/renderer/deferred/SHADING_LIGHT_RESOURCE_GROUP.d.ts.map +1 -1
  15. package/src/shade/renderer/deferred/SHADING_LIGHT_RESOURCE_GROUP.js +2 -0
  16. package/src/shade/renderer/deferred/indirect/chunk_get_ibl_irradiance.d.ts.map +1 -1
  17. package/src/shade/renderer/deferred/indirect/chunk_get_ibl_irradiance.js +4 -2
  18. package/src/shade/renderer/deferred/indirect/chunk_get_ibl_radiance.d.ts.map +1 -1
  19. package/src/shade/renderer/deferred/indirect/chunk_get_ibl_radiance.js +4 -2
  20. package/src/shade/renderer/deferred/shader_background.d.ts.map +1 -1
  21. package/src/shade/renderer/deferred/shader_background.js +4 -2
  22. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_probes.d.ts.map +1 -1
  23. package/src/shade/renderer/global_illumination/brick4/gpu/bake/shader_brick4_bake_probes.js +3 -1
  24. package/src/shade/renderer/global_illumination/ibl/shader_diffuse_IBL.d.ts.map +1 -1
  25. package/src/shade/renderer/global_illumination/ibl/shader_diffuse_IBL.js +3 -1
  26. package/src/shade/renderer/global_illumination/ibl/shader_specular_IBL.d.ts.map +1 -1
  27. package/src/shade/renderer/global_illumination/ibl/shader_specular_IBL.js +3 -1
  28. package/src/shade/renderer/global_illumination/lpv/deprecated/probe_trace_compute_shader_code.d.ts.map +1 -1
  29. package/src/shade/renderer/global_illumination/lpv/deprecated/probe_trace_compute_shader_code.js +3 -1
  30. package/src/shade/renderer/global_illumination/probe/octahedral/shader_lpv_build_gbuffer.d.ts.map +1 -1
  31. package/src/shade/renderer/global_illumination/probe/octahedral/shader_lpv_build_gbuffer.js +4 -2
  32. package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
  33. package/src/shade/renderer/light/GPULightCollection.js +44 -0
  34. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_RESOURCE.d.ts +2 -1
  35. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_RESOURCE.d.ts.map +1 -1
  36. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_RESOURCE.js +3 -2
  37. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_SAMPLER_RESOURCE.d.ts +14 -0
  38. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_SAMPLER_RESOURCE.d.ts.map +1 -0
  39. package/src/shade/renderer/light/environment/ENVIRONMENT_MAP_SAMPLER_RESOURCE.js +25 -0
  40. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts.map +1 -1
  41. package/src/shade/renderer/light/environment/execute_filter_environment_map.js +3 -4
  42. package/src/shade/renderer/light/environment/octahedral_skirt_matches.d.ts +19 -0
  43. package/src/shade/renderer/light/environment/octahedral_skirt_matches.d.ts.map +1 -0
  44. package/src/shade/renderer/light/environment/octahedral_skirt_matches.js +67 -0
  45. package/src/shade/renderer/light/environment/octahedral_skirt_source_texel.d.ts +16 -0
  46. package/src/shade/renderer/light/environment/octahedral_skirt_source_texel.d.ts.map +1 -0
  47. package/src/shade/renderer/light/environment/octahedral_skirt_source_texel.js +35 -0
  48. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.d.ts +7 -2
  49. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.d.ts.map +1 -1
  50. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +23 -7
  51. package/src/shade/renderer/light/environment/resample_equirectangular_to_octahedral.d.ts +8 -2
  52. package/src/shade/renderer/light/environment/resample_equirectangular_to_octahedral.d.ts.map +1 -1
  53. package/src/shade/renderer/light/environment/resample_equirectangular_to_octahedral.js +24 -7
  54. package/src/shade/renderer/light/environment/sampler2d_octahedral_skirt_fill.d.ts +12 -0
  55. package/src/shade/renderer/light/environment/sampler2d_octahedral_skirt_fill.d.ts.map +1 -0
  56. package/src/shade/renderer/light/environment/sampler2d_octahedral_skirt_fill.js +54 -0
  57. package/src/shade/renderer/light/environment/sh_exponential/shader_sh_exp_environment_cells.d.ts +2 -2
  58. package/src/shade/renderer/light/environment/sh_exponential/shader_sh_exp_environment_cells.d.ts.map +1 -1
  59. package/src/shade/renderer/light/environment/sh_exponential/shader_sh_exp_environment_cells.js +5 -4
  60. package/src/shade/renderer/light/environment/shader_convolve_reflection_map.d.ts.map +1 -1
  61. package/src/shade/renderer/light/environment/shader_convolve_reflection_map.js +13 -9
  62. package/src/shade/renderer/light/environment/shader_project_environment_sh.d.ts.map +1 -1
  63. package/src/shade/renderer/light/environment/shader_project_environment_sh.js +3 -1
  64. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_irradiance_flat.d.ts.map +1 -1
  65. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_irradiance_flat.js +3 -1
  66. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_sh.d.ts.map +1 -1
  67. package/src/shade/renderer/lightmap/bake/shader_lightmap_viz_to_sh.js +3 -1
  68. package/src/shade/renderer/path_tracer/accumulating/shader_accumulating_path_tracer.d.ts.map +1 -1
  69. package/src/shade/renderer/path_tracer/accumulating/shader_accumulating_path_tracer.js +3 -1
  70. package/src/shade/renderer/path_tracer/graph_path_trace_scene.d.ts.map +1 -1
  71. package/src/shade/renderer/path_tracer/graph_path_trace_scene.js +3 -1
  72. package/src/shade/renderer/path_tracer/shader/chunk_render_trace_path.js +1 -1
  73. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.d.ts.map +1 -1
  74. package/src/shade/renderer/postprocess/ssr/resolve/ssr_resolve_shader.js +3 -1
  75. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.d.ts +0 -5
  76. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.d.ts.map +1 -1
  77. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_CONSTANTS.js +0 -6
  78. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.d.ts.map +1 -1
  79. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_counts_to_extinction.js +4 -1
  80. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -1
  81. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +3 -0
  82. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.d.ts.map +1 -1
  83. package/src/shade/renderer/rasterize/native/oit/graph_rasterize_meshes_transparent_oit.js +3 -0
  84. package/src/shade/renderer/shader/chunk/color/tonemap/chunk_PAPER_WHITE_NITS.js +1 -1
  85. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_texel_uv.d.ts +15 -0
  86. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_texel_uv.d.ts.map +1 -0
  87. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_texel_uv.js +27 -0
  88. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_uv.d.ts +18 -0
  89. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_uv.d.ts.map +1 -0
  90. package/src/shade/renderer/shader/chunk/texture/octahedral/chunk_texture_octahedral_skirt_uv.js +24 -0
  91. package/src/shade/renderer/shader/chunk/texture/sample/chunk_sample_environment_map.d.ts +18 -1
  92. package/src/shade/renderer/shader/chunk/texture/sample/chunk_sample_environment_map.d.ts.map +1 -1
  93. package/src/shade/renderer/shader/chunk/texture/sample/chunk_sample_environment_map.js +45 -27
  94. package/src/shade/renderer/view/GPUViewSkyContext.js +169 -169
  95. package/src/shade/renderer/volumetrics/NOTES.md +209 -0
  96. package/src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.d.ts.map +1 -1
  97. package/src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.js +1 -1
  98. package/src/shade/renderer/volumetrics/chunk_volumetrics_media_group_list.d.ts +36 -0
  99. package/src/shade/renderer/volumetrics/chunk_volumetrics_media_group_list.d.ts.map +1 -0
  100. package/src/shade/renderer/volumetrics/chunk_volumetrics_media_group_list.js +129 -0
  101. package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
  102. package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +75 -0
  103. package/src/shade/renderer/volumetrics/shader_volumetrics_bin_participating_media.d.ts +26 -0
  104. package/src/shade/renderer/volumetrics/shader_volumetrics_bin_participating_media.d.ts.map +1 -0
  105. package/src/shade/renderer/volumetrics/shader_volumetrics_bin_participating_media.js +110 -0
  106. package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.d.ts.map +1 -1
  107. package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.js +38 -14
  108. package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.d.ts.map +1 -1
  109. package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js +9 -4
  110. package/src/shade/renderer/volumetrics/shader_volumetrics_split_participating_media.d.ts +31 -0
  111. package/src/shade/renderer/volumetrics/shader_volumetrics_split_participating_media.d.ts.map +1 -0
  112. package/src/shade/renderer/volumetrics/shader_volumetrics_split_participating_media.js +139 -0
@@ -2,10 +2,28 @@ import {
2
2
  chunk_sphere_probe_roughness_to_lod
3
3
  } from "../../../../light/environment/chunk_sphere_probe_roughness_to_lod.js";
4
4
  import { CodeChunk } from "../../../compiler/CodeChunk.js";
5
- import { chunk_texture_octahedral_sample_bilinear } from "../octahedral/chunk_texture_octahedral_sample_bilinear.js";
5
+ import { chunk_uv_octahedral_unit_encode } from "../../geometry/octahedron/chunk_uv_octahedral_unit_encode.js";
6
+ import { chunk_texture_octahedral_skirt_uv } from "../octahedral/chunk_texture_octahedral_skirt_uv.js";
6
7
 
7
8
  /**
8
- * Works on ann octahedral-encoded pre-filtered environment map
9
+ * Works on an octahedral-encoded pre-filtered environment map with a one-texel octahedral skirt on
10
+ * every level it reads (see `texture_octahedral_skirt_uv`).
11
+ *
12
+ * Trilinear as two hardware bilinear taps, one per level, blended here: each level's interior is its
13
+ * own size less the skirt, so one uv cannot address two levels and the sampler's own mip blend would
14
+ * read the coarser one up to a texel off. Most texture units filter trilinear at half the bilinear
15
+ * rate anyway, so the second tap costs about what the hardware blend would.
16
+ *
17
+ * Measured against the manual form this replaces (8 loads, 4 octahedral wraps a level, each wrap
18
+ * two integer divisions and remainders), radiance + irradiance over 1920x1080: 88 → 17 µs on an RTX
19
+ * 4090, 15.4 → 1.3 ms on an RDNA2 iGPU, whose integer division is emulated.
20
+ *
21
+ * Every level the roughness mapping reaches (0 to `SPHERE_PROBE_MIPMAP_LEVELS - 1`) must be at least
22
+ * 3 texels wide to have an interior. A level's size is the texture's halved and kept at 1 or more, as
23
+ * a level's extent is, so a smaller map (the renderer's 1x1 black stand-in, a test's constant) still
24
+ * samples at a finite uv. At the roughest level the second tap reads the level past it at weight 0; a
25
+ * texture without one clamps to the last level, which the weight discards just the same.
26
+ *
9
27
  * @see https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/1bbe66e7ea0d358e84af4471a90aa9c35b583de0/samples/hybridreflections/shaders/Common.hlsl#L118
10
28
  * @type {CodeChunk}
11
29
  */
@@ -13,39 +31,39 @@ export const chunk_sample_environment_map = CodeChunk.from(
13
31
  //language=WGSL
14
32
  `
15
33
  fn sample_environment_map(
16
- tEnvironment:texture_2d<f32>,
17
- direction: vec3<f32>,
34
+ tEnvironment: texture_2d<f32>,
35
+ sEnvironment: sampler,
36
+ direction: vec3<f32>,
18
37
  roughness: f32
19
38
  ) -> vec3<f32> {
20
-
39
+
21
40
  let lod = sphere_probe_roughness_to_lod(roughness);
22
-
23
- let lod_0 = u32(floor(lod));
24
- let f = fract(lod);
25
-
26
- let color_0 = texture_octahedral_sample_bilinear(
41
+
42
+ let lod_0 = floor(lod);
43
+ let level_0 = u32(lod_0);
44
+
45
+ let octahedral_uv = uv_octahedral_unit_encode(direction);
46
+ let size = textureDimensions(tEnvironment).x;
47
+
48
+ let color_0 = textureSampleLevel(
27
49
  tEnvironment,
28
- vec2(0u),
29
- textureDimensions(tEnvironment, lod_0).x,
30
- direction,
50
+ sEnvironment,
51
+ texture_octahedral_skirt_uv(octahedral_uv, f32(max(size >> level_0, 1u))),
31
52
  lod_0
32
- ).rgb;
33
-
34
- let lod_1 = lod_0 + 1u;
35
-
36
- let color_1 = texture_octahedral_sample_bilinear(
53
+ ).rgb;
54
+
55
+ let color_1 = textureSampleLevel(
37
56
  tEnvironment,
38
- vec2(0u),
39
- textureDimensions(tEnvironment, lod_1).x,
40
- direction,
41
- lod_1
57
+ sEnvironment,
58
+ texture_octahedral_skirt_uv(octahedral_uv, f32(max(size >> (level_0 + 1u), 1u))),
59
+ lod_0 + 1.0
42
60
  ).rgb;
43
-
44
-
45
- return mix(color_0, color_1, f);
61
+
62
+ return mix(color_0, color_1, lod - lod_0);
46
63
  }
47
64
  `, [
48
65
  chunk_sphere_probe_roughness_to_lod,
49
- chunk_texture_octahedral_sample_bilinear,
66
+ chunk_uv_octahedral_unit_encode,
67
+ chunk_texture_octahedral_skirt_uv,
50
68
  ]
51
- );
69
+ );
@@ -1,170 +1,170 @@
1
- import { m3_cm_compose_transform } from "../../../core/geom/mat3/m3_cm_compose_transform.js";
2
- import { FrameGraph } from "../../../engine/graphics/render/frame_graph/FrameGraph.js";
3
- import { Sampler2D } from "../../../engine/graphics/texture/sampler/Sampler2D.js";
4
- import sampler2d_to_html_canvas
5
- from "../../../engine/graphics/texture/sampler/sampler2d_to_html_canvas.js";
6
- import { writeCssTransformMatrix } from "../../../view/writeCssTransformMatrix.js";
7
- import { TextureResourceDescriptor } from "../../device/graph/TextureResourceDescriptor.js";
8
- import { ShadeGPUCommandContext } from "../../device/ShadeGPUCommandContext.js";
9
- import { SPHERE_PROBE_MIPMAP_LEVELS } from "../light/environment/DEFINES.js";
10
- import { graph_filter_environment_map } from "../light/environment/graph_filter_environment_map.js";
11
- import { shader_sky_irradiance_lut } from "../atmosphere/shader_sky_irradiance_lut.js";
12
- import { graph_image_pass } from "../shader/graph/graph_image_pass.js";
13
- import { GPUTextureContext } from "../texture/GPUTextureContext.js";
14
- import { sampler_linear_to_sRGB } from "../texture/sampler_linear_to_sRGB.js";
15
-
16
- let debug_counter = 0;
17
-
18
- export class GPUViewSkyContext {
19
-
20
-
21
- /**
22
- * @type {GraphicsContext}
23
- */
24
- #graphics;
25
-
26
- /**
27
- * @type {GPUTextureContext}
28
- */
29
- #lut_sky;
30
-
31
-
32
- /**
33
- *
34
- * @returns {GPUTextureContext}
35
- */
36
- get irradiance_lut() {
37
- return this.#lut_sky;
38
- }
39
-
40
- /**
41
- *
42
- * @param {GraphicsContext} graphics
43
- */
44
- constructor(graphics) {
45
- this.#graphics = graphics;
46
-
47
- const device = graphics.device;
48
-
49
- this.#lut_sky = new GPUTextureContext(device);
50
- this.#lut_sky.descriptor.label = "Sky / Irradiance LUT";
51
- this.#lut_sky.descriptor.format = "rgba16float";
52
- this.#lut_sky.descriptor.mipLevelCount = SPHERE_PROBE_MIPMAP_LEVELS;
53
- this.#lut_sky.descriptor.usage = GPUTextureUsage.RENDER_ATTACHMENT
54
- | GPUTextureUsage.TEXTURE_BINDING
55
- | GPUTextureUsage.STORAGE_BINDING
56
- | GPUTextureUsage.COPY_SRC
57
- ;
58
- this.#lut_sky.resize(1024, 1024);
59
-
60
- }
61
-
62
- #needs_update = true;
63
-
64
- set needs_update(v) {
65
- if (v) {
66
- this.#needs_update = true;
67
- }
68
- }
69
-
70
- /**
71
- *
72
- * @param {GPUCameraContext} camera
73
- * @param {GPUSceneContext} scene
74
- */
75
- update(camera, scene) {
76
-
77
- if (!this.#needs_update) {
78
- return;
79
- }
80
-
81
- this.#needs_update = false;
82
-
83
- // ensure base sky LUTs are up-to-date
84
- /**
85
- *
86
- * @type {GPUSky}
87
- */
88
- const scene_sky = scene.sky;
89
- scene_sky.update();
90
-
91
- const graph = new FrameGraph("sky update");
92
-
93
- const command_ctx = ShadeGPUCommandContext.create(
94
- this.#graphics,
95
- "Renderer/main"
96
- );
97
-
98
- const gr_transmittance = graph.import_resource("transmittance", TextureResourceDescriptor.fromTexture(scene_sky.lut_transmittance.gpu_texture), scene_sky.lut_transmittance);
99
- const gr_multiscatter = graph.import_resource("multiscatter", TextureResourceDescriptor.fromTexture(scene_sky.lut_multiple_scattering.gpu_texture), scene_sky.lut_multiple_scattering);
100
- const gr_sky = graph.import_resource("sky_irradiance", TextureResourceDescriptor.fromTexture(this.#lut_sky.gpu_texture), this.#lut_sky);
101
-
102
- let [out] = graph_image_pass({
103
- graph,
104
- shader: shader_sky_irradiance_lut,
105
- inputs: {
106
- camera: camera.buffer,
107
-
108
- light_data: scene.lights.buffer_data,
109
-
110
- tSkyLutTransmittance: gr_transmittance,
111
- tSkyLutMultiscatter: gr_multiscatter
112
- },
113
- output: [gr_sky]
114
- });
115
-
116
- // convolve
117
- out = graph_filter_environment_map({
118
- graph,
119
- map: out
120
- });
121
-
122
- command_ctx.encodeGraph(graph);
123
-
124
- command_ctx.finish();
125
-
126
- debug_counter++;
127
-
128
-
129
- // this.debug(3);
130
- }
131
-
132
- async debug(mip = 0) {
133
- if (this.__debug_waiting) {
134
- return;
135
- }
136
-
137
- this.__debug_waiting = true;
138
-
139
- const texture = this.#lut_sky;
140
-
141
- const data = await texture.download(mip);
142
-
143
- const s = new Sampler2D(data, 4, texture.size[0] >> mip, texture.size[1] >> mip);
144
- sampler_linear_to_sRGB(s);
145
-
146
- const canvas = sampler2d_to_html_canvas(s, 255, 0, this.__debug_canvas);
147
-
148
- if (this.__debug_canvas === undefined) {
149
-
150
- canvas.style.zIndex = "100";
151
- canvas.style.position = "absolute";
152
- canvas.style.bottom = `${32}px`;
153
- canvas.style.right = `${32}px`;
154
-
155
- const transform = new Float32Array(9);
156
- m3_cm_compose_transform(transform, 0, 0, 1, -1, 0, 0, 0);
157
- writeCssTransformMatrix(transform, canvas);
158
-
159
- document.body.appendChild(canvas);
160
- }
161
-
162
- this.__debug_canvas = canvas;
163
-
164
- this.__debug_waiting = false;
165
- }
166
-
167
- destroy() {
168
- this.#lut_sky.destroy();
169
- }
1
+ import { m3_cm_compose_transform } from "../../../core/geom/mat3/m3_cm_compose_transform.js";
2
+ import { FrameGraph } from "../../../engine/graphics/render/frame_graph/FrameGraph.js";
3
+ import { Sampler2D } from "../../../engine/graphics/texture/sampler/Sampler2D.js";
4
+ import sampler2d_to_html_canvas
5
+ from "../../../engine/graphics/texture/sampler/sampler2d_to_html_canvas.js";
6
+ import { writeCssTransformMatrix } from "../../../view/writeCssTransformMatrix.js";
7
+ import { TextureResourceDescriptor } from "../../device/graph/TextureResourceDescriptor.js";
8
+ import { ShadeGPUCommandContext } from "../../device/ShadeGPUCommandContext.js";
9
+ import { SPHERE_PROBE_MIPMAP_LEVELS } from "../light/environment/DEFINES.js";
10
+ import { graph_filter_environment_map } from "../light/environment/graph_filter_environment_map.js";
11
+ import { SKY_IRRADIANCE_LUT_RESOLUTION, shader_sky_irradiance_lut } from "../atmosphere/shader_sky_irradiance_lut.js";
12
+ import { graph_image_pass } from "../shader/graph/graph_image_pass.js";
13
+ import { GPUTextureContext } from "../texture/GPUTextureContext.js";
14
+ import { sampler_linear_to_sRGB } from "../texture/sampler_linear_to_sRGB.js";
15
+
16
+ let debug_counter = 0;
17
+
18
+ export class GPUViewSkyContext {
19
+
20
+
21
+ /**
22
+ * @type {GraphicsContext}
23
+ */
24
+ #graphics;
25
+
26
+ /**
27
+ * @type {GPUTextureContext}
28
+ */
29
+ #lut_sky;
30
+
31
+
32
+ /**
33
+ *
34
+ * @returns {GPUTextureContext}
35
+ */
36
+ get irradiance_lut() {
37
+ return this.#lut_sky;
38
+ }
39
+
40
+ /**
41
+ *
42
+ * @param {GraphicsContext} graphics
43
+ */
44
+ constructor(graphics) {
45
+ this.#graphics = graphics;
46
+
47
+ const device = graphics.device;
48
+
49
+ this.#lut_sky = new GPUTextureContext(device);
50
+ this.#lut_sky.descriptor.label = "Sky / Irradiance LUT";
51
+ this.#lut_sky.descriptor.format = "rgba16float";
52
+ this.#lut_sky.descriptor.mipLevelCount = SPHERE_PROBE_MIPMAP_LEVELS;
53
+ this.#lut_sky.descriptor.usage = GPUTextureUsage.RENDER_ATTACHMENT
54
+ | GPUTextureUsage.TEXTURE_BINDING
55
+ | GPUTextureUsage.STORAGE_BINDING
56
+ | GPUTextureUsage.COPY_SRC
57
+ ;
58
+ this.#lut_sky.resize(SKY_IRRADIANCE_LUT_RESOLUTION, SKY_IRRADIANCE_LUT_RESOLUTION);
59
+
60
+ }
61
+
62
+ #needs_update = true;
63
+
64
+ set needs_update(v) {
65
+ if (v) {
66
+ this.#needs_update = true;
67
+ }
68
+ }
69
+
70
+ /**
71
+ *
72
+ * @param {GPUCameraContext} camera
73
+ * @param {GPUSceneContext} scene
74
+ */
75
+ update(camera, scene) {
76
+
77
+ if (!this.#needs_update) {
78
+ return;
79
+ }
80
+
81
+ this.#needs_update = false;
82
+
83
+ // ensure base sky LUTs are up-to-date
84
+ /**
85
+ *
86
+ * @type {GPUSky}
87
+ */
88
+ const scene_sky = scene.sky;
89
+ scene_sky.update();
90
+
91
+ const graph = new FrameGraph("sky update");
92
+
93
+ const command_ctx = ShadeGPUCommandContext.create(
94
+ this.#graphics,
95
+ "Renderer/main"
96
+ );
97
+
98
+ const gr_transmittance = graph.import_resource("transmittance", TextureResourceDescriptor.fromTexture(scene_sky.lut_transmittance.gpu_texture), scene_sky.lut_transmittance);
99
+ const gr_multiscatter = graph.import_resource("multiscatter", TextureResourceDescriptor.fromTexture(scene_sky.lut_multiple_scattering.gpu_texture), scene_sky.lut_multiple_scattering);
100
+ const gr_sky = graph.import_resource("sky_irradiance", TextureResourceDescriptor.fromTexture(this.#lut_sky.gpu_texture), this.#lut_sky);
101
+
102
+ let [out] = graph_image_pass({
103
+ graph,
104
+ shader: shader_sky_irradiance_lut,
105
+ inputs: {
106
+ camera: camera.buffer,
107
+
108
+ light_data: scene.lights.buffer_data,
109
+
110
+ tSkyLutTransmittance: gr_transmittance,
111
+ tSkyLutMultiscatter: gr_multiscatter
112
+ },
113
+ output: [gr_sky]
114
+ });
115
+
116
+ // convolve
117
+ out = graph_filter_environment_map({
118
+ graph,
119
+ map: out
120
+ });
121
+
122
+ command_ctx.encodeGraph(graph);
123
+
124
+ command_ctx.finish();
125
+
126
+ debug_counter++;
127
+
128
+
129
+ // this.debug(3);
130
+ }
131
+
132
+ async debug(mip = 0) {
133
+ if (this.__debug_waiting) {
134
+ return;
135
+ }
136
+
137
+ this.__debug_waiting = true;
138
+
139
+ const texture = this.#lut_sky;
140
+
141
+ const data = await texture.download(mip);
142
+
143
+ const s = new Sampler2D(data, 4, texture.size[0] >> mip, texture.size[1] >> mip);
144
+ sampler_linear_to_sRGB(s);
145
+
146
+ const canvas = sampler2d_to_html_canvas(s, 255, 0, this.__debug_canvas);
147
+
148
+ if (this.__debug_canvas === undefined) {
149
+
150
+ canvas.style.zIndex = "100";
151
+ canvas.style.position = "absolute";
152
+ canvas.style.bottom = `${32}px`;
153
+ canvas.style.right = `${32}px`;
154
+
155
+ const transform = new Float32Array(9);
156
+ m3_cm_compose_transform(transform, 0, 0, 1, -1, 0, 0, 0);
157
+ writeCssTransformMatrix(transform, canvas);
158
+
159
+ document.body.appendChild(canvas);
160
+ }
161
+
162
+ this.__debug_canvas = canvas;
163
+
164
+ this.__debug_waiting = false;
165
+ }
166
+
167
+ destroy() {
168
+ this.#lut_sky.destroy();
169
+ }
170
170
  }
@@ -1118,3 +1118,212 @@ uniform wind of zero). On the software device, the froxel build dispatches the u
1118
1118
  scene with no fluid and the full one when the field has a cascade, judged by the module it was
1119
1119
  handed (red against a pass that never and one that always picks it). Driver: `wfu_*` in session
1120
1120
  f2a32857's scratchpad, a one-off, adapted from the GPU fluid session's `s3g_*`.
1121
+
1122
+ ## The media build walks its workgroup's volumes, pre-culled by a bin — measured on the iGPU, 2026-10-17
1123
+
1124
+ `shader_volumetrics_build_participating_media` walked every volume of the scene from every froxel.
1125
+ Each volume cost a plane transform and a six-plane box test even where it was nowhere near the
1126
+ froxel, and on a modest GPU that was the pass. On the box's AMD RDNA2 iGPU (2 CUs, headless
1127
+ Chrome, `--use-adapter-luid`) at 1898×982, small volumes placed beyond the far plane, where no
1128
+ froxel can reach them, cost the build **0.78 ms each**:
1129
+
1130
+ | volumes beyond the far plane | 0 | 16 | 64 | 256 |
1131
+ |---|---|---|---|---|
1132
+ | build participating media (ms) | 7.78 | 20.18 | 57.53 | 207.05 |
1133
+
1134
+ Lighting and TAA did not move in any arm (4.0–4.4 and 5.73–5.77 ms).
1135
+
1136
+ **Now a bin runs first.** `shader_volumetrics_bin_participating_media` dispatches one 64-lane
1137
+ workgroup per workgroup of the build (16×16 froxels × 1 slice). Its lanes take the volumes in
1138
+ turn, test each against the group's slab with the build's own rejection
1139
+ (`volumetrics_volume_in_slab`), and set the volume's bit in that group's list. The build then walks
1140
+ its group's list in row order. There is a list a group, a bit a volume, in a FrameGraph transient
1141
+ sized from the scene's volume count and cleared by the graph. A lane of the bin tests ⌈volumes /
1142
+ 64⌉ volumes, whatever the resolution and however large a volume is.
1143
+
1144
+ **The output does not change.** A volume adds to a froxel only where its coverage is above zero,
1145
+ and coverage samples points inside the froxel. The group's slab holds every froxel of the group,
1146
+ widened a quarter froxel in xy and, in z, by the column jitter's half slice plus a quarter. So a
1147
+ volume that adds anything is in the list. The walk is in row order, so the sums add in the order
1148
+ they did. `shader_volumetrics_bin_participating_media.spec.js` runs both passes under the emulator
1149
+ and compares the build's bits through the bin's lists against lists holding every volume. It is red
1150
+ with the z widening cut to the jitter-free quarter slice (1,536 and 24 differing words), and green
1151
+ with it.
1152
+
1153
+ **The walk has a budget**, `VOLUMETRICS_MEDIA_GROUP_VOLUME_BUDGET` = 32, chosen. Past it a froxel
1154
+ drops its group's highest-indexed volumes. Before the bin the loop had no budget at all.
1155
+
1156
+ **After**, same driver, same scene, before/after/before/after in separate processes:
1157
+
1158
+ | volumes beyond the far plane | 0 | 16 | 64 | 256 | 1,024 |
1159
+ |---|---|---|---|---|---|
1160
+ | build participating media (ms) | 4.48 | 4.48 | 4.58 | 4.95 | 6.99 |
1161
+ | bin participating media (ms) | 0.37 | 0.37 | 0.56 | 1.15 | 3.54 |
1162
+ | sum, against before | 4.85 / 7.78 | 4.85 / 20.18 | 5.14 / 57.53 | 6.10 / 207.05 | 10.53 / — |
1163
+
1164
+ The two runs of each side agree to 0.01 ms. The scene's own six volumes, with nothing added, go
1165
+ from 7.78 to 4.85 ms. The old build was not run at 1,024; at 0.78 ms a volume it would be about
1166
+ 0.8 s. At 1,024 the build's remaining growth is the list itself: 33 words a froxel to load.
1167
+
1168
+ The picture, by the README's protocol (45 frames settled, 16 averaged, block means of 32×32
1169
+ pixels): before against after reads a mean |d| of 0.117–0.156 with signed means within ±0.004,
1170
+ inside the floor between two processes running the *same* build (0.121 and 0.151).
1171
+
1172
+ **A defect this turned up, fixed below.** The build's Y flip negates as well as swapping
1173
+ (`planes[2] = -planes[3]; planes[3] = -t`), so its froxel's two Y planes face *out*. The corners
1174
+ `frustum_sample_point` builds are unchanged, being intersections, but `aabb3_intersects_frustum`
1175
+ on those planes keeps a box only when it spans the froxel's whole row. Measured on the emulator: a
1176
+ 1×1×1 box at depth 10 covers rows 8.8–11.2 of a 20-row grid and fogs rows 9 and 10 only. The fog
1177
+ loses up to a row at every volume's top and bottom edge, and a volume thinner than a row is never
1178
+ drawn. The bin slices its slab with the planes facing in (`volumetrics_froxel_planes`: a swap, no
1179
+ negation). Inverted planes across a group's 16 rows rejected nearly every volume, and that is how
1180
+ it showed. The build keeps its arithmetic, so the output above stays byte-identical. Fixing it
1181
+ changes the fog.
1182
+
1183
+ **Not covered:** the aerial pass's far field, `far_sample_volumes` in
1184
+ `shader_volumetrics_build_scattering_lut`, still walks every volume at every march sample past
1185
+ `froxel_far`. With `froxel_far` at its default, the camera's far, that is only each column's last
1186
+ step, whose dithered depth can land past it: about one far-field sample a column a frame. The
1187
+ playground's `froxel_far` slider below 120 hands the whole tail to the far field, many samples a
1188
+ column. Measured in the same runs, at the default, the pass reads 0.40–0.47 ms with the scene's own
1189
+ volumes and 2.27 ms with 256 added. In the 1,024 run it reads 9.56 ms.
1190
+
1191
+ ### The split ahead of the bin: a near field and a far field — measured on the iGPU, 2026-10-17
1192
+
1193
+ `shader_volumetrics_split_participating_media` runs before the bin, with one thread per volume.
1194
+ It drops a volume outside any of the grid's four side planes or its near plane. Those are the bin's
1195
+ planes, widened the same quarter froxel. The plane at `froxel_far` then sorts the rest:
1196
+
1197
+ - any part of a volume nearer than `froxel_far` puts it in the near field;
1198
+ - any part beyond it puts it in the far field;
1199
+ - a volume across it goes in both.
1200
+
1201
+ Each field is a list, a count and rows in no particular order, which the split appends to from a
1202
+ count of zero. The bin now tests only the near field, and the aerial pass's far field walks the
1203
+ far field (see "The aerial pass's far field walks the far-field list" below).
1204
+
1205
+ **The near field holds every volume the bin could keep, at the default `froxel_far`.** That
1206
+ default is the camera's far, where the bin's last groups end. The emulator spec runs split, bin
1207
+ and build together against lists holding every volume, byte for byte. It is red with the
1208
+ discriminator at half of `froxel_far`, and its own split cases are red with the far test's
1209
+ negation dropped. With `froxel_far` pulled nearer, the grid's last slice still reaches the camera's
1210
+ far, because its jittered slab clamps there. A volume wholly between the two now belongs to the
1211
+ far field alone and no longer reaches that slice. It had been counted by both until now.
1212
+
1213
+ **After**, against master (bin only), master/split/master/split in separate processes, with a third
1214
+ master run. Same driver, scene and 1898×982 as the section above, figures in ms:
1215
+
1216
+ | volumes beyond the far plane | 0 | 64 | 256 | 1,024 |
1217
+ |---|---|---|---|---|
1218
+ | split | 0.012–0.014 | 0.020–0.022 | 0.009–0.011 | 0.016–0.018 |
1219
+ | bin, master → split | 0.36–0.37 → 0.35 | 0.55–0.56 → 0.35 | 1.14–1.15 → 0.35 | 3.54 → 0.35 |
1220
+ | build, master → split | 4.48–4.50 → 4.17–4.18 | 4.57–4.61 → 4.29–4.31 | 4.94–4.98 → 4.69–4.71 | 7.03–7.11 → 6.87–6.95 |
1221
+
1222
+ The bin no longer grows with volumes the camera cannot see. The build's code is unchanged, yet it
1223
+ reads 0.3 ms less at every count. Its lists are now built from the near field only, which fits
1224
+ the split dropping volumes that a group's plane-by-plane test let through. That account is not
1225
+ verified.
1226
+
1227
+ The picture reads the same. Master against split gives a mean |d| of 0.057–0.164, with signed means
1228
+ within ±0.003. Two runs of the same build differ by 0.177 (master) and 0.284 (split). One master
1229
+ run read 1.54 against every other run, its own build's included, and is left out.
1230
+
1231
+ ## The media build's Y planes face in — measured on the emulator and the iGPU, 2026-10-17
1232
+
1233
+ The build turned the metadata frustum's Y pair to the grid's rows by swapping and negating. That
1234
+ left both of a froxel's Y planes facing out (the defect above). It now takes the pair from
1235
+ `volumetrics_froxel_planes`, the same swap the bin slices its slabs with, and both planes face in.
1236
+ A plane and its negation meet the other planes at the same points, so the froxel's corners do not
1237
+ move. `frustum_sample_point`, the coverage estimate and the fade therefore read exactly what they
1238
+ did. Only the box test changes. It now rejects a volume only where the froxel lies wholly outside
1239
+ it. Before, it kept a volume only in the rows the volume spanned from top to bottom.
1240
+
1241
+ **On the emulator** (`shader_volumetrics_build_participating_media.spec.js`, a 40×20×8 grid, the
1242
+ bin spec's camera). Take a box 1 tall and 1 deep, across the whole view at depth 10. It projects
1243
+ onto rows 8.73–11.27. With its fade off, a froxel's fog is its coverage alone: the box fogged rows 9
1244
+ and 10, and now fogs 8 to 11, the rows it reaches. A box half a row tall across the middle of row 9,
1245
+ with the volume's default fade, fogged nothing and now fogs row 9. Both tests were red against the
1246
+ old flip (`[9, 10]` and `[]`) and are green. The bin's spec stays green against the corrected
1247
+ build: through the bin's lists the build still writes, bit for bit, what it writes with every volume
1248
+ listed.
1249
+
1250
+ **The fade hides half of what the box test dropped.** A froxel whose centre ray misses the box
1251
+ takes the fallback in `compute_volume_fade_factor`, the fade at the froxel's centre. Outside the
1252
+ box that is 0. So an edge froxel gets its fog back only when its centre ray reaches into the
1253
+ volume, which means only rows the volume covers past their middle. The 1×1×1 box at depth 10 with a
1254
+ fade of 0.25 still fogs rows 9 and 10 only: it covers rows 8 and 11 to about a quarter of their
1255
+ height, short of their middles. The fallback's comment calls this case "rare if coverage > 0", but
1256
+ at a volume's edges it is every froxel whose centre ray misses. Not changed here.
1257
+
1258
+ **In the playground, on the iGPU** (headless Chrome, `--use-adapter-luid`, 1898×982, so 119×62
1259
+ froxels of 16 px). Both flips ran in one page behind a temporary switch, on the bin as committed at
1260
+ `7f09e80b4`, before the split above; the split's spec holds the build's output byte for byte at the
1261
+ default `froxel_far`. By the README's protocol at each of the 11 viewpoints: old, new, old, new, each
1262
+ after a view change, a 45-frame settle and a 16-frame average. Each floor compares a flip with its own repeat. Mean luminance in 8-bit codes, per
1263
+ pixel:
1264
+
1265
+ | viewpoint | signed mean, old → new | floor, old / new | mean \|d\|, old → new | floor, old / new |
1266
+ |---|---|---|---|---|
1267
+ | Dead overview | +0.011, −0.013 | −0.000 / −0.025 | 0.162, 0.160 | 0.213 / 0.214 |
1268
+ | Fog bank, wide | +0.001, +0.000 | +0.001 / +0.000 | 0.303, 0.236 | 0.320 / 0.301 |
1269
+ | Inside the fog | +0.001, +0.002 | +0.002 / +0.003 | 0.138, 0.137 | 0.163 / 0.163 |
1270
+ | Island broadside | −0.007, −0.004 | +0.001 / +0.004 | 0.114, 0.124 | 0.153 / 0.168 |
1271
+ | Island from above | −0.008, −0.003 | −0.003 / +0.002 | 0.100, 0.093 | 0.134 / 0.124 |
1272
+ | Inside the cloud deck | −0.003, −0.002 | −0.003 / −0.002 | 0.081, 0.081 | 0.093 / 0.093 |
1273
+ | Volume edge | −0.007, −0.002 | −0.009 / −0.004 | 0.129, 0.141 | 0.185 / 0.199 |
1274
+ | Volume edge, above | −0.003, +0.001 | +0.006 / +0.010 | 0.130, 0.133 | 0.179 / 0.176 |
1275
+ | Beside the fog box | −0.003, −0.022 | +0.031 / +0.012 | 0.129, 0.131 | 0.159 / 0.158 |
1276
+ | Beside the fog box, deeper | +0.023, +0.023 | −0.013 / −0.013 | 0.126, 0.127 | 0.155 / 0.153 |
1277
+ | Under the island | +0.001, +0.002 | +0.011 / +0.013 | 0.129, 0.126 | 0.165 / 0.166 |
1278
+
1279
+ On 32×32 block means one block, in *Fog bank, wide*, moved the same way in both pairs by more
1280
+ than the largest block move between repeats of one flip: +0.09 and +0.07 against 0.06. No other
1281
+ block at any viewpoint did. The one viewpoint whose signed means agree and clear their floors,
1282
+ *Beside the fog box, deeper*, moves by 0.023 of a code. **The change is inside the picture's noise
1283
+ at every viewpoint.** The six volumes are large on screen and fade over 0.5–7 m, so a row at their
1284
+ edge carries little of their density. What the defect hid was fog in volumes that are thin or small
1285
+ on screen: one under 16 px tall here never drew at all. The floors span two captures and the
1286
+ signals one, so a slow drift (the noise scrolling with the wind) counts double in a floor. That is
1287
+ why the signals' mean |d| sits below the floors'.
1288
+
1289
+ **Cost: unchanged.** Old against new, alternating which goes first, two rounds a viewpoint: the
1290
+ media build reads 3.19–5.62 ms across the viewpoints, and new/old has a median of 0.998 (range
1291
+ 0.982–1.026). More froxels at a volume's edge now run the coverage estimate, but too few to show.
1292
+
1293
+ Driver: `bench/job_flip.js` and `summarize_flip.mjs` in session b5223342's scratchpad, run by the
1294
+ bin session's `drive.mjs`.
1295
+
1296
+ ## The aerial pass's far field walks the far-field list — measured on the iGPU, 2026-10-17
1297
+
1298
+ `far_sample_volumes` in `shader_volumetrics_build_scattering_lut` used to walk every volume of the
1299
+ scene at each march sample past `froxel_far`. It now walks the split's far field. The list holds
1300
+ every volume with some part beyond `froxel_far` and inside the grid's widened side planes. A sample
1301
+ on a column's view ray past `froxel_far` lies inside both, so a volume left off the list cannot
1302
+ contain it.
1303
+
1304
+ The list is in no particular order, so the sums round differently from frame to frame. Alex
1305
+ accepted that: the volumetric TAA and the final image's TAA absorb it.
1306
+ `shader_volumetrics_build_scattering_lut.far_field.spec.js` pins that the far field adds the
1307
+ volumes the list names and no others. It is red against the shader that walked every volume.
1308
+
1309
+ Master (the split, the far field unread) against this, in separate processes, at 1898×982, ms:
1310
+
1311
+ | aerial pass (`build scattering lut`) | 0 volumes added | 256 | 1,024 |
1312
+ |---|---|---|---|
1313
+ | `froxel_far` 40 m (camera far 120), master | 0.68–0.75 | 12.87–12.90 | 55.75–56.27 |
1314
+ | `froxel_far` 40 m, far-field list | 0.62–0.68 | 1.60–1.63 | 5.86–5.88 |
1315
+ | `froxel_far` at its default, master | 0.54 | 2.39 | 10.72 |
1316
+ | `froxel_far` at its default, far-field list | 0.63 | 0.69 | 1.13 |
1317
+
1318
+ The added volumes are the ones the earlier sections place beyond the far plane in random
1319
+ directions. The far field keeps only those inside the view's side planes. At the default the far
1320
+ field is reached only by each column's dithered last step, and the pass grew a tenth as fast to
1321
+ begin with. One run each at the default: its 0.09 ms at no added volumes is inside the 0.54–0.75
1322
+ the pass reads across all runs, and is not shown to be a cost. The media build reads the same in
1323
+ every arm.
1324
+
1325
+ The picture reads the same. At the default, master against list gives a mean |d| of 0.083 against
1326
+ floors of 0.075. At 40 m the pairs give 0.15 and 0.41, against 0.55 between the two runs of the
1327
+ list's own build. The first run of the batch read 1.58 against every other run, its own build's
1328
+ included, as one master run did in the section above. Both times it was the first run of a batch,
1329
+ so it is left out and taken as a property of the driver, not of either build.
@@ -1 +1 @@
1
- {"version":3,"file":"VOLUMETRICS_METADATA_STRUCT.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.js"],"names":[],"mappings":"AAKA,uDA+EG;6BAlF0B,gCAAgC"}
1
+ {"version":3,"file":"VOLUMETRICS_METADATA_STRUCT.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.js"],"names":[],"mappings":"AAKA,uDA+EU;6BAlFmB,gCAAgC"}
@@ -82,4 +82,4 @@ export const VOLUMETRICS_METADATA_STRUCT = WebGPUStruct.from({
82
82
  * are short and local and there is far less to amortize.
83
83
  */
84
84
  sun_shadow_light_index: u32,
85
- });
85
+ }).pack();