@woosh/meep-engine 3.11.1 → 3.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/bundle-worker-terrain.js +1 -1
- package/package.json +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +294 -290
- package/src/engine/graphics3/ParticipatingMedia.d.ts +111 -0
- package/src/engine/graphics3/ParticipatingMedia.d.ts.map +1 -0
- package/src/engine/graphics3/ParticipatingMedia.js +203 -0
- package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.d.ts +34 -0
- package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.d.ts.map +1 -0
- package/src/engine/graphics3/ParticipatingMediaSerializationAdapter.js +73 -0
- package/src/engine/graphics3/ParticipatingMediaSystem3.d.ts +87 -0
- package/src/engine/graphics3/ParticipatingMediaSystem3.d.ts.map +1 -0
- package/src/engine/graphics3/ParticipatingMediaSystem3.js +184 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.d.ts +16 -2
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.d.ts.map +1 -1
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.js +28 -4
- package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts +1 -1
- package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts.map +1 -1
- package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.js +5 -2
- package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +564 -451
- package/src/shade/device/ShadeGPUCommandContext.d.ts +18 -2
- package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
- package/src/shade/device/ShadeGPUCommandContext.js +994 -940
- package/src/shade/device/immediate_data_supported.d.ts +33 -0
- package/src/shade/device/immediate_data_supported.d.ts.map +1 -0
- package/src/shade/device/immediate_data_supported.js +53 -0
- package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts +18 -0
- package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPUCommandEncoder.js +24 -0
- package/src/shade/device/mock/SoftwareGPUComputePassEncoder.d.ts +7 -0
- package/src/shade/device/mock/SoftwareGPUComputePassEncoder.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPUComputePassEncoder.js +49 -1
- package/src/shade/device/mock/SoftwareGPUDevice.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPUDevice.js +20 -2
- package/src/shade/device/mock/SoftwareGPUImmediateData.d.ts +65 -0
- package/src/shade/device/mock/SoftwareGPUImmediateData.d.ts.map +1 -0
- package/src/shade/device/mock/SoftwareGPUImmediateData.js +160 -0
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts +7 -0
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.js +50 -0
- package/src/shade/device/mock/pipeline_immediate_size.d.ts +11 -0
- package/src/shade/device/mock/pipeline_immediate_size.d.ts.map +1 -0
- package/src/shade/device/mock/pipeline_immediate_size.js +18 -0
- package/src/shade/device/pipeline/PipelineLayoutManager.d.ts.map +1 -1
- package/src/shade/device/pipeline/PipelineLayoutManager.js +1 -0
- package/src/shade/playground/volumetrics_froxel/README.md +104 -1
- package/src/shade/playground/volumetrics_froxel/index.html +20 -1
- package/src/shade/playground/volumetrics_froxel/main.d.ts.map +1 -1
- package/src/shade/playground/volumetrics_froxel/main.js +298 -0
- package/src/shade/playground/volumetrics_froxel/measure.d.ts +119 -0
- package/src/shade/playground/volumetrics_froxel/measure.d.ts.map +1 -0
- package/src/shade/playground/volumetrics_froxel/measure.js +559 -0
- package/src/shade/renderer/GraphicsContext.d.ts +9 -2
- package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
- package/src/shade/renderer/GraphicsContext.js +13 -1
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +22 -18
- package/src/shade/renderer/atmosphere/GPUSky.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/GPUSky.js +145 -142
- package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts +34 -0
- package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.js +36 -0
- package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.d.ts +20 -0
- package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.js +25 -0
- package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts +14 -1
- package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.js +163 -177
- package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.d.ts +20 -0
- package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.js +46 -0
- package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.d.ts +11 -1
- package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.js +57 -27
- package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.d.ts +19 -1
- package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.js +69 -33
- package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.d.ts +13 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.js +35 -35
- package/src/shade/renderer/atmosphere/chunk_sky_lut_params.d.ts +13 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_params.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_params.js +38 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.d.ts +17 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.js +29 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.d.ts +21 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.js +42 -0
- package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts +2 -2
- package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/shader_multiscatter_lut.js +165 -84
- package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.js +215 -204
- package/src/shade/renderer/atmosphere/shader_transmittance_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/shader_transmittance_lut.js +79 -67
- package/src/shade/renderer/light/cluster/graph_assign_clsuters.d.ts.map +1 -1
- package/src/shade/renderer/light/cluster/graph_assign_clsuters.js +6 -2
- package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/GTAO.js +4 -0
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +341 -345
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +0 -2
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +58 -57
- package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.js +7 -3
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +77 -81
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +5 -1
- package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
- package/src/shade/renderer/shader/ComputeShader.js +6 -2
- package/src/shade/renderer/shader/ImageShader.d.ts.map +1 -1
- package/src/shade/renderer/shader/ImageShader.js +4 -1
- package/src/shade/renderer/shader/ShaderDescriptor.d.ts.map +1 -1
- package/src/shade/renderer/shader/ShaderDescriptor.js +26 -0
- package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.d.ts +18 -0
- package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.js +32 -0
- package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.d.ts +7 -0
- package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.js +17 -6
- package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.d.ts +21 -0
- package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.js +69 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.d.ts +9 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.js +22 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.d.ts +16 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.js +41 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.d.ts +11 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.js +15 -19
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.d.ts +23 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.js +44 -0
- package/src/shade/renderer/shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.js +1 -4
- package/src/shade/renderer/shader/compiler/CodeChunk.d.ts +7 -1
- package/src/shade/renderer/shader/compiler/CodeChunk.d.ts.map +1 -1
- package/src/shade/renderer/shader/compiler/CodeChunk.js +37 -2
- package/src/shade/renderer/shader/graph/compute/compute_binding_read_names.d.ts.map +1 -1
- package/src/shade/renderer/shader/graph/compute/compute_binding_read_names.js +6 -0
- package/src/shade/renderer/shader/graph/compute/graph_compute_indirect_pass.js +1 -1
- package/src/shade/renderer/shader/graph/compute/graph_compute_pass.js +1 -1
- package/src/shade/renderer/shader/graph/graph_image_pass.js +1 -1
- package/src/shade/renderer/shader/graph/graph_prepare_bindings.d.ts.map +1 -1
- package/src/shade/renderer/shader/graph/graph_prepare_bindings.js +7 -0
- package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.d.ts +3 -1
- package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.d.ts.map +1 -1
- package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.js +12 -1
- package/src/shade/renderer/shader/pass/RenderPassDescriptor.d.ts.map +1 -1
- package/src/shade/renderer/shader/pass/RenderPassDescriptor.js +1 -0
- package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.d.ts +21 -0
- package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.d.ts.map +1 -0
- package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.js +21 -0
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts +59 -1
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +195 -2
- package/src/shade/renderer/shader/type/type_has_arrays.d.ts +14 -0
- package/src/shade/renderer/shader/type/type_has_arrays.d.ts.map +1 -0
- package/src/shade/renderer/shader/type/type_has_arrays.js +33 -0
- package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.d.ts +14 -13
- package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.d.ts.map +1 -1
- package/src/shade/renderer/shadow/map/shader/chunk_shadowmap_sample_point2.js +133 -129
- package/src/shade/renderer/view/GPUViewSkyContext.d.ts.map +1 -1
- package/src/shade/renderer/view/GPUViewSkyContext.js +169 -173
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +480 -315
- package/src/shade/renderer/volumetrics/NOTES.md +887 -8
- package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts +5 -0
- package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/ParticipatingMediaVolume.js +21 -2
- package/src/shade/renderer/volumetrics/SceneVolumetrics.d.ts +26 -0
- package/src/shade/renderer/volumetrics/SceneVolumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/SceneVolumetrics.js +64 -21
- package/src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/VOLUMETRICS_METADATA_STRUCT.js +84 -28
- package/src/shade/renderer/volumetrics/VOLUMETRICS_SUN_SHADOW_RESOLUTION.d.ts +98 -0
- package/src/shade/renderer/volumetrics/VOLUMETRICS_SUN_SHADOW_RESOLUTION.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/VOLUMETRICS_SUN_SHADOW_RESOLUTION.js +111 -0
- package/src/shade/renderer/volumetrics/build_volumetrics_sun_shadow_transform.d.ts +84 -0
- package/src/shade/renderer/volumetrics/build_volumetrics_sun_shadow_transform.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/build_volumetrics_sun_shadow_transform.js +252 -0
- package/src/shade/renderer/volumetrics/chunk_integrate_optical_depth.js +75 -75
- package/src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.d.ts +43 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.js +145 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.d.ts +20 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sample_froxel_extinction.js +48 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.d.ts +38 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.js +130 -0
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +18 -3
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +193 -70
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts +16 -0
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +83 -15
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.js +29 -18
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js +260 -260
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts +58 -0
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.js +290 -0
- package/src/shade/wgsl/emulator/CPUBitmapData.d.ts +25 -0
- package/src/shade/wgsl/emulator/CPUBitmapData.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/CPUBitmapData.js +33 -0
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +2228 -2215
- package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.js +210 -2
- package/src/shade/wgsl/emulator/cpu_texture_gather.d.ts +69 -0
- package/src/shade/wgsl/emulator/cpu_texture_gather.d.ts.map +1 -0
- package/src/shade/wgsl/emulator/cpu_texture_gather.js +148 -0
- package/src/shade/wgsl/emulator/cpu_texture_sample.d.ts +16 -0
- package/src/shade/wgsl/emulator/cpu_texture_sample.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/cpu_texture_sample.js +176 -172
- package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.d.ts +0 -2
- package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.js +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { normalize_dynamic_offsets } from "./normalize_dynamic_offsets.js";
|
|
2
|
+
import { pipeline_immediate_size } from "./pipeline_immediate_size.js";
|
|
3
|
+
import { SoftwareGPUImmediateData } from "./SoftwareGPUImmediateData.js";
|
|
2
4
|
import { SoftwareGPUObjectBase } from "./SoftwareGPUObjectBase.js";
|
|
3
5
|
import { write_pass_timestamp } from "./write_pass_timestamp.js";
|
|
4
6
|
|
|
@@ -79,6 +81,14 @@ export class SoftwareGPURenderPassEncoder extends SoftwareGPUObjectBase {
|
|
|
79
81
|
*/
|
|
80
82
|
#scissor = null;
|
|
81
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Immediate data for this pass. Per-encoder state in WebGPU, and a new pass starts over — which
|
|
86
|
+
* is why it is constructed here rather than carried in from the command encoder.
|
|
87
|
+
*
|
|
88
|
+
* @type {SoftwareGPUImmediateData}
|
|
89
|
+
*/
|
|
90
|
+
#immediates;
|
|
91
|
+
|
|
82
92
|
#ended = false;
|
|
83
93
|
|
|
84
94
|
/**
|
|
@@ -99,6 +109,7 @@ export class SoftwareGPURenderPassEncoder extends SoftwareGPUObjectBase {
|
|
|
99
109
|
|
|
100
110
|
this.#encoder = encoder;
|
|
101
111
|
this.descriptor = descriptor;
|
|
112
|
+
this.#immediates = new SoftwareGPUImmediateData(encoder.device.limits.maxImmediateSize);
|
|
102
113
|
|
|
103
114
|
write_pass_timestamp(descriptor, 'beginningOfPassWriteIndex');
|
|
104
115
|
}
|
|
@@ -138,6 +149,22 @@ export class SoftwareGPURenderPassEncoder extends SoftwareGPUObjectBase {
|
|
|
138
149
|
);
|
|
139
150
|
}
|
|
140
151
|
|
|
152
|
+
/**
|
|
153
|
+
* @param {number} range_offset byte offset into the immediate data range
|
|
154
|
+
* @param {ArrayBuffer|ArrayBufferView} data
|
|
155
|
+
* @param {number} [data_offset] in elements if `data` is a TypedArray, bytes otherwise
|
|
156
|
+
* @param {number} [data_size] in elements if `data` is a TypedArray, bytes otherwise
|
|
157
|
+
*/
|
|
158
|
+
setImmediates(range_offset, data, data_offset = 0, data_size) {
|
|
159
|
+
this.#assert_encoding();
|
|
160
|
+
|
|
161
|
+
const message = this.#immediates.write(range_offset, data, data_offset, data_size);
|
|
162
|
+
|
|
163
|
+
if (message !== null) {
|
|
164
|
+
this.#encoder.notify_pass_validation_error(message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
141
168
|
/**
|
|
142
169
|
* @param {number} slot
|
|
143
170
|
* @param {GPUBuffer} buffer
|
|
@@ -206,6 +233,24 @@ export class SoftwareGPURenderPassEncoder extends SoftwareGPUObjectBase {
|
|
|
206
233
|
this.stencil_reference = reference;
|
|
207
234
|
}
|
|
208
235
|
|
|
236
|
+
/**
|
|
237
|
+
* WebGPU's *Validate immediate data*: every slot the pipeline's immediate range covers must have
|
|
238
|
+
* been written before a command reads it. The bytes default to zero but the slots do not default
|
|
239
|
+
* to set, so a forgotten `setImmediates` is a refused draw on a real device rather than a shader
|
|
240
|
+
* quietly reading zeroes. Catching it here is the whole reason the slot bookkeeping exists.
|
|
241
|
+
*
|
|
242
|
+
* @returns {void}
|
|
243
|
+
*/
|
|
244
|
+
#validate_immediates() {
|
|
245
|
+
const size = pipeline_immediate_size(this.#pipeline);
|
|
246
|
+
|
|
247
|
+
if (size > 0 && !this.#immediates.isRangeSet(0, size)) {
|
|
248
|
+
this.#encoder.notify_pass_validation_error(
|
|
249
|
+
`draw: pipeline declares ${size} bytes of immediate data, and not all of it was set by setImmediates`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
209
254
|
/**
|
|
210
255
|
* The state a draw would run under, copied so later state changes do not rewrite a draw already
|
|
211
256
|
* recorded.
|
|
@@ -221,6 +266,7 @@ export class SoftwareGPURenderPassEncoder extends SoftwareGPUObjectBase {
|
|
|
221
266
|
index_buffer: this.#index_buffer,
|
|
222
267
|
viewport: this.#viewport,
|
|
223
268
|
scissor: this.#scissor,
|
|
269
|
+
immediates: this.#immediates.snapshot(),
|
|
224
270
|
};
|
|
225
271
|
}
|
|
226
272
|
|
|
@@ -230,6 +276,7 @@ export class SoftwareGPURenderPassEncoder extends SoftwareGPUObjectBase {
|
|
|
230
276
|
*/
|
|
231
277
|
draw(vertex_count, instance_count = 1) {
|
|
232
278
|
this.#assert_encoding();
|
|
279
|
+
this.#validate_immediates();
|
|
233
280
|
|
|
234
281
|
this.draws.push({
|
|
235
282
|
...this.#snapshot(),
|
|
@@ -246,6 +293,7 @@ export class SoftwareGPURenderPassEncoder extends SoftwareGPUObjectBase {
|
|
|
246
293
|
*/
|
|
247
294
|
drawIndexed(index_count, instance_count = 1) {
|
|
248
295
|
this.#assert_encoding();
|
|
296
|
+
this.#validate_immediates();
|
|
249
297
|
|
|
250
298
|
this.draws.push({
|
|
251
299
|
...this.#snapshot(),
|
|
@@ -262,6 +310,7 @@ export class SoftwareGPURenderPassEncoder extends SoftwareGPUObjectBase {
|
|
|
262
310
|
*/
|
|
263
311
|
drawIndirect(indirect_buffer, indirect_offset) {
|
|
264
312
|
this.#assert_encoding();
|
|
313
|
+
this.#validate_immediates();
|
|
265
314
|
|
|
266
315
|
this.draws.push({
|
|
267
316
|
...this.#snapshot(),
|
|
@@ -281,6 +330,7 @@ export class SoftwareGPURenderPassEncoder extends SoftwareGPUObjectBase {
|
|
|
281
330
|
*/
|
|
282
331
|
drawIndexedIndirect(indirect_buffer, indirect_offset) {
|
|
283
332
|
this.#assert_encoding();
|
|
333
|
+
this.#validate_immediates();
|
|
284
334
|
|
|
285
335
|
this.draws.push({
|
|
286
336
|
...this.#snapshot(),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bytes of immediate data a pipeline's layout declares, or 0 when it declares none.
|
|
3
|
+
*
|
|
4
|
+
* A pipeline built with `layout: "auto"` reports 0 rather than deriving a size: deriving one would
|
|
5
|
+
* mean reading the WGSL, and nothing in this folder compiles WGSL.
|
|
6
|
+
*
|
|
7
|
+
* @param {GPUPipelineBase|null} pipeline
|
|
8
|
+
* @returns {number}
|
|
9
|
+
*/
|
|
10
|
+
export function pipeline_immediate_size(pipeline: GPUPipelineBase | null): number;
|
|
11
|
+
//# sourceMappingURL=pipeline_immediate_size.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline_immediate_size.d.ts","sourceRoot":"","sources":["../../../../../src/shade/device/mock/pipeline_immediate_size.js"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,kDAHW,kBAAgB,IAAI,GAClB,MAAM,CAUlB"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bytes of immediate data a pipeline's layout declares, or 0 when it declares none.
|
|
3
|
+
*
|
|
4
|
+
* A pipeline built with `layout: "auto"` reports 0 rather than deriving a size: deriving one would
|
|
5
|
+
* mean reading the WGSL, and nothing in this folder compiles WGSL.
|
|
6
|
+
*
|
|
7
|
+
* @param {GPUPipelineBase|null} pipeline
|
|
8
|
+
* @returns {number}
|
|
9
|
+
*/
|
|
10
|
+
export function pipeline_immediate_size(pipeline) {
|
|
11
|
+
const layout = pipeline?.descriptor?.layout;
|
|
12
|
+
|
|
13
|
+
if (layout === undefined || layout === null || layout === "auto") {
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return layout.immediateSize ?? 0;
|
|
18
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PipelineLayoutManager.d.ts","sourceRoot":"","sources":["../../../../../src/shade/device/pipeline/PipelineLayoutManager.js"],"names":[],"mappings":"AAGA;IAMI;;;OAGG;IACH,+BAEC;IAoBD;;;;OAIG;IACH,2DAFa,4BAA0B,SAAS,CAI/C;IAgBD;;;;OAIG;IACH,iFAKC;IAED;;;;OAIG;IACH,8EAEC;;
|
|
1
|
+
{"version":3,"file":"PipelineLayoutManager.d.ts","sourceRoot":"","sources":["../../../../../src/shade/device/pipeline/PipelineLayoutManager.js"],"names":[],"mappings":"AAGA;IAMI;;;OAGG;IACH,+BAEC;IAoBD;;;;OAIG;IACH,2DAFa,4BAA0B,SAAS,CAI/C;IAgBD;;;;OAIG;IACH,iFAKC;IAED;;;;OAIG;IACH,8EAEC;;CAmBJ"}
|
|
@@ -30,6 +30,36 @@ Those fixes are in the permanent path now and there is nothing left to A/B. What
|
|
|
30
30
|
the *next* change here — the performance findings especially, since every one of them trades image
|
|
31
31
|
quality for time and none can be judged from a screenshot.
|
|
32
32
|
|
|
33
|
+
Q-01 was judged here and now ships unconditionally: every clustered point and spot light is occluded
|
|
34
|
+
by geometry, sampled with one nearest tap out of the shadow atlas. The two checkboxes that switched
|
|
35
|
+
that off, or swapped the tap for the deferred pass's filter kernels, are gone along with the metadata
|
|
36
|
+
word behind them — `volumetrics/NOTES.md` keeps the measurements that closed the question. The
|
|
37
|
+
torches are the local lights here: 130 point lights, every one of them a shadow caster, and **zero
|
|
38
|
+
spot lights**, so the spot half has no on-screen coverage on this page and is covered by
|
|
39
|
+
`shader_volumetrics_build_lighting_local_shadow.spec.js` instead. Adding a `SpotLight` from the
|
|
40
|
+
console is the way to look at it.
|
|
41
|
+
|
|
42
|
+
Q-04 went the same way and its switch is gone too. The sun's media self-shadowing is one fetch into
|
|
43
|
+
a ladder of three cascaded volumes built once a frame with their third axis on the sun, and that is
|
|
44
|
+
now the only path for it: the `__VOL_SUN_SHADOW` global and the checkbox over it are removed along
|
|
45
|
+
with the `sun_shadow_mode` word they drove, so nothing on this page reproduces the march/cascade
|
|
46
|
+
comparison any more. Restoring it means re-adding that word and the branch in
|
|
47
|
+
`shader_volumetrics_build_lighting` that reads it.
|
|
48
|
+
|
|
49
|
+
Worth knowing what it was, because it is the size of what the page can no longer show. Against a
|
|
50
|
+
march at `STEP_COUNT = 512` the cascades read within 1.2 mean luminance at every viewpoint here and
|
|
51
|
+
across 40 to 110 degrees of field of view, while sixteen steps read **+58** and **+70** at the two
|
|
52
|
+
poses beside the small fog boxes — the step there is eight units, the box is three thick, and the
|
|
53
|
+
march goes straight over it. They are also cheaper: 0.37 ms against 0.53 at 1080p, 0.81 against 1.52
|
|
54
|
+
at 2160p, per pass. `volumetrics/NOTES.md` has the whole measurement, including the one that says a
|
|
55
|
+
*single* world-uniform box could not have been sized correctly at any resolution — every shape
|
|
56
|
+
measured has a field of view at which it is 28 to 50 luminance from the truth, and which one that is
|
|
57
|
+
moves with the resolution.
|
|
58
|
+
|
|
59
|
+
The march itself is still in the tree and still runs: every point and spot light uses it, and so
|
|
60
|
+
would a second directional light. Only the first one is amortized, because there is one ladder of
|
|
61
|
+
boxes and it is aligned to one direction.
|
|
62
|
+
|
|
33
63
|
## Measuring, rather than eyeballing
|
|
34
64
|
|
|
35
65
|
This pass is stochastic and temporally filtered, so most changes to it move the image by less than
|
|
@@ -50,6 +80,14 @@ confident wrong answer during the work above:
|
|
|
50
80
|
*change → settle ~45 frames → average*, and take the floor by making an identical no-op change so
|
|
51
81
|
both paths cost the same.
|
|
52
82
|
|
|
83
|
+
- **Frame time here is presentation-throttled, not GPU-bound.** At 1920×1080 every setting reads
|
|
84
|
+
~16.5 ms, including settings that do obviously different amounts of work — `ms_octaves` at 1 and at
|
|
85
|
+
8 come back within noise of each other, which is the tell. The swap chain is pacing the
|
|
86
|
+
measurement. Raise `renderer.pixel_ratio` until the GPU is the bottleneck (3 gives 3840×2160 and
|
|
87
|
+
~10 ms of real work here), interleave the settings round-robin so drift hits all of them equally,
|
|
88
|
+
and take medians. Remember the froxel grid scales with resolution, so a per-froxel cost measured at
|
|
89
|
+
4× the pixels is 4× the one you will pay.
|
|
90
|
+
|
|
53
91
|
Prefer the **signed mean** when the change has a direction; it discriminates far better than the
|
|
54
92
|
mean-absolute, which is dominated by noise. C-01b read −0.148 at 60° FOV against a floor of ±0.05,
|
|
55
93
|
and −1.071 at 110°: same fix, and that 7× is the `1/cos θ` scaling it predicts.
|
|
@@ -57,6 +95,68 @@ and −1.071 at 110°: same fix, and that 7× is the `1/cos θ` scaling it predi
|
|
|
57
95
|
Also: mutating `light.intensity` does not mark the light buffer dirty, so a sweep of it renders
|
|
58
96
|
identically. Remove and re-add the light instead.
|
|
59
97
|
|
|
98
|
+
### measure.js
|
|
99
|
+
|
|
100
|
+
`measure.js` is that protocol as code, loaded from the console rather than by the page:
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
const m = await (await import('./measure.js')).attach();
|
|
104
|
+
const r = await m.ab(v => globalThis.__VOL_TAA_ENABLED = v, false, true);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`attach` pauses the page's loop — which matters for a reason beyond frame counting: that loop
|
|
108
|
+
assigns `renderer.pixel_ratio = window.devicePixelRatio` on every tick, ahead of its own pause check,
|
|
109
|
+
so a raised pixel ratio is undone before the next frame unless the loop is stopped. `set_pixel_ratio`
|
|
110
|
+
replaces the property for the same reason.
|
|
111
|
+
|
|
112
|
+
Everything renders into a texture the module owns, through `Renderer.render_to_target`, which shares
|
|
113
|
+
the view context and therefore all the temporal history with the canvas path. That is not a
|
|
114
|
+
refinement: a canvas texture is configured by the presentation path, cannot carry `COPY_SRC`, and is
|
|
115
|
+
rotated away the moment the frame is presented, so a `drawImage` off it a task later returns an empty
|
|
116
|
+
bitmap — and a comparison of two blanks reports perfect agreement. Driving the frames from here also
|
|
117
|
+
takes the swap chain out of the timing entirely, which is the presentation-throttling trap above.
|
|
118
|
+
|
|
119
|
+
`ab` runs the two settings interleaved and takes its own floor from a repeat of each. `fly`,
|
|
120
|
+
`converge_at` and `series_stats` are the camera-path half: a structure fitted to the camera can be
|
|
121
|
+
perfectly correct standing still and swim under motion, and neither swimming nor the ghosting the
|
|
122
|
+
0.95-blend TAA turns it into is visible in a still. `time` and `profile` answer different questions and
|
|
123
|
+
`profile` is the one to believe — at 1080p, whole-frame timing of Q-04 came back with the wrong sign
|
|
124
|
+
three runs in a row while the per-pass capture read it cleanly.
|
|
125
|
+
|
|
126
|
+
`profile` records a `WORKLOAD` session and decodes it in the page, so a cost number does not need a
|
|
127
|
+
downloaded `.sgpt` and a second tool. It returns the median duration per pass label over the frames
|
|
128
|
+
it recorded. Interleave the settings and take medians across rounds even so — the run-to-run spread
|
|
129
|
+
on this machine moves a pass by more than most changes to it do:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
const p = await m.profile({ frames: 24 });
|
|
133
|
+
p['Volumetrics / build sun shadow volume 0'];
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Recording a GPU profile
|
|
137
|
+
|
|
138
|
+
**T** starts a recording, **T** again stops it and downloads a `.sgpt` capture. The engine side is
|
|
139
|
+
already there — `Renderer.profile_session` is a nullable field and the null checks around it are the
|
|
140
|
+
whole integration — so this page is only the two imports, the key handler, and the metadata a capture
|
|
141
|
+
needs in order to mean anything later: adapter, engine version, device features, and a note carrying
|
|
142
|
+
the viewpoint, FOV, pixel ratio and volumetric TAA setting that were in force when you pressed the
|
|
143
|
+
key.
|
|
144
|
+
|
|
145
|
+
Level is `WORKLOAD`: pass spans, the frame graph, and dispatch/draw counts. Measured on this page it
|
|
146
|
+
runs about 11 KB a frame — roughly 660 KB/s at 60 Hz, and around 393 spans per frame — with the frame
|
|
147
|
+
graph topology stored once and shared by every frame. `VERBOSE` above it adds bind group contents at
|
|
148
|
+
about seven times the rate and wants a frame limit set; `PROFILE_LEVEL` in `main.js` is the one line
|
|
149
|
+
to change.
|
|
150
|
+
|
|
151
|
+
Two things to know. Stopping does not end the recording immediately: a frame is committed when its
|
|
152
|
+
timestamp readback lands, a `mapAsync` a frame or two behind submit, so the handler drains the queue
|
|
153
|
+
and waits for the frame count to settle before it writes the file — otherwise the last frames, the
|
|
154
|
+
ones you were probably looking at, are the ones missing. And the browser asks before letting a page
|
|
155
|
+
save more than one file, so a second capture may never appear; the filename goes to the console
|
|
156
|
+
either way.
|
|
157
|
+
|
|
158
|
+
Frames only accumulate while the page is rendering, so a recording taken while paused stays at zero.
|
|
159
|
+
|
|
60
160
|
## Scene
|
|
61
161
|
|
|
62
162
|
`flying_world_-_battle_of_the_trash_god/v2`, six participating-media volumes, a low sun, and the
|
|
@@ -80,7 +180,10 @@ accepts version 2 only, deliberately and with no backward compatibility.
|
|
|
80
180
|
## Notes
|
|
81
181
|
|
|
82
182
|
Two viewpoints from the sibling repo (`v_volumetrics_light_extinction_0` and `_1`) sit inside
|
|
83
|
-
geometry here and were replaced with placements verified against this build.
|
|
183
|
+
geometry here and were replaced with placements verified against this build. Two more —
|
|
184
|
+
*Beside the fog box* and *Beside the fog box, deeper* — were added for Q-04: they are the only
|
|
185
|
+
placements in the scene where the sun's path through the medium is short enough for the 16-step
|
|
186
|
+
march to miss it, which makes them the ones any change to that path has to be judged at. *Log camera* writes the
|
|
84
187
|
current placement to the console in the form the `VIEWS` table takes, which is how to add more.
|
|
85
188
|
|
|
86
189
|
Frame time settles around 28 ms at 1920×1080 on a 4070-class part. That is the whole frame, not the
|
|
@@ -117,12 +117,14 @@
|
|
|
117
117
|
#status.fail { color: var(--fail); }
|
|
118
118
|
#status.warn { color: var(--warn); }
|
|
119
119
|
|
|
120
|
-
#stats {
|
|
120
|
+
#stats, #profile_status {
|
|
121
121
|
margin-top: 4px;
|
|
122
122
|
font: 12px ui-monospace, SFMono-Regular, Consolas, monospace;
|
|
123
123
|
color: var(--text);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
#profile_status.recording { color: var(--fail); }
|
|
127
|
+
|
|
126
128
|
p.note { margin: 6px 0 0; font-size: 11.5px; color: var(--muted); }
|
|
127
129
|
</style>
|
|
128
130
|
</head>
|
|
@@ -220,6 +222,23 @@
|
|
|
220
222
|
|
|
221
223
|
<hr>
|
|
222
224
|
|
|
225
|
+
<h2>Profiling</h2>
|
|
226
|
+
|
|
227
|
+
<div id="profile_status">idle</div>
|
|
228
|
+
<p class="note">
|
|
229
|
+
<strong>T</strong> starts a GPU profile recording; <strong>T</strong> again stops it and
|
|
230
|
+
downloads the <code>.sgpt</code> capture. Level is <code>WORKLOAD</code> — pass spans, the
|
|
231
|
+
frame graph, and dispatch/draw counts, which measures around 11 KB a frame on this page.
|
|
232
|
+
Frames only accumulate while the page is rendering, so a recording taken while paused stays
|
|
233
|
+
at zero.
|
|
234
|
+
</p>
|
|
235
|
+
<p class="note">
|
|
236
|
+
The browser asks before letting a page save more than one file. If a second capture never
|
|
237
|
+
appears, that prompt is why — allow it, and the filename is on the console either way.
|
|
238
|
+
</p>
|
|
239
|
+
|
|
240
|
+
<hr>
|
|
241
|
+
|
|
223
242
|
<div class="row">
|
|
224
243
|
<button id="pause">Pause</button>
|
|
225
244
|
<button id="log_camera">Log camera</button>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/volumetrics_froxel/main.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/volumetrics_froxel/main.js"],"names":[],"mappings":"AA8ZA,uCAsfC"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import package_json from "../../../../package.json";
|
|
1
2
|
import Quaternion from "../../../core/geom/Quaternion.js";
|
|
2
3
|
import Vector3 from "../../../core/geom/Vector3.js";
|
|
3
4
|
import { DEG_TO_RAD } from "../../../core/math/DEG_TO_RAD.js";
|
|
4
5
|
import { MIE_PARTICLES_STANDARD_PRECOMPUTED } from "../../../core/math/physics/mie/MIE_PARTICLES_STANDARD_PRECOMPUTED.js";
|
|
6
|
+
import { GPUProfileLevel } from "../../device/timing/profile/GPUProfileLevel.js";
|
|
7
|
+
import { GPUProfileSession } from "../../device/timing/profile/GPUProfileSession.js";
|
|
5
8
|
import { OrbitalCameraController } from "../../renderer/camera/OrbitalCameraController.js";
|
|
6
9
|
import { PerspectiveCamera } from "../../renderer/camera/PerspectiveCamera.js";
|
|
7
10
|
import { create_frame_loop } from "../../renderer/create_frame_loop.js";
|
|
@@ -143,6 +146,32 @@ const VIEWS = [
|
|
|
143
146
|
position: { x: -36.56903863396318, y: 19.1017887067011, z: 13.881382214134083 },
|
|
144
147
|
rotation: { x: -0.0961072760435774, y: 0.9036695806244697, z: -0.3054532939528032, w: -0.28432897453277794 }
|
|
145
148
|
},
|
|
149
|
+
{
|
|
150
|
+
id: 'fog_box_0',
|
|
151
|
+
label: 'Beside the fog box',
|
|
152
|
+
c: 'Q-04',
|
|
153
|
+
hint: 'the sun crosses three units of medium here — where sixteen march steps read +58',
|
|
154
|
+
position: { x: -49.64917207609583, y: 3.9195318273572317, z: -4.211048766762115 },
|
|
155
|
+
rotation: {
|
|
156
|
+
x: -0.037898077937503966,
|
|
157
|
+
y: 0.6176094542736352,
|
|
158
|
+
z: 0.029816629610983452,
|
|
159
|
+
w: 0.7850052651282708
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'fog_box_1',
|
|
164
|
+
label: 'Beside the fog box, deeper',
|
|
165
|
+
c: 'Q-04',
|
|
166
|
+
hint: 'the same medium a little further in; +70 for the march, and where the volume strobed',
|
|
167
|
+
position: { x: -50.28590468756837, y: 4.239170044321892, z: -3.9966900541348767 },
|
|
168
|
+
rotation: {
|
|
169
|
+
x: -0.03789807793750395,
|
|
170
|
+
y: 0.6176094542736352,
|
|
171
|
+
z: 0.02981662961098345,
|
|
172
|
+
w: 0.7850052651282708
|
|
173
|
+
}
|
|
174
|
+
},
|
|
146
175
|
{
|
|
147
176
|
id: 'inside_out',
|
|
148
177
|
label: 'Under the island',
|
|
@@ -317,6 +346,72 @@ function aim_light(light, azimuth_degrees, elevation_degrees) {
|
|
|
317
346
|
light.updateMatrices();
|
|
318
347
|
}
|
|
319
348
|
|
|
349
|
+
/**
|
|
350
|
+
* How much each profile capture records. `WORKLOAD` is the highest of the three levels the proposal
|
|
351
|
+
* calls shippable: pass spans, the frame graph, and dispatch/draw counts.
|
|
352
|
+
*
|
|
353
|
+
* Measured on this page it runs ~11 KB a frame over ~393 spans, so ~660 KB/s at 60 Hz — the
|
|
354
|
+
* proposal's estimate of 6 KB assumed ~200 passes and this graph is about twice that. Still small
|
|
355
|
+
* enough that a hand-driven recording will not notice. `VERBOSE` above it adds bind group contents
|
|
356
|
+
* at roughly seven times the rate and wants a frame limit set, not a key you can lean on.
|
|
357
|
+
*
|
|
358
|
+
* @type {number}
|
|
359
|
+
*/
|
|
360
|
+
const PROFILE_LEVEL = GPUProfileLevel.WORKLOAD;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Hand an ArrayBuffer to the browser as a file.
|
|
364
|
+
*
|
|
365
|
+
* @param {ArrayBuffer} bytes
|
|
366
|
+
* @param {string} filename
|
|
367
|
+
*/
|
|
368
|
+
function download_bytes(bytes, filename) {
|
|
369
|
+
const url = URL.createObjectURL(new Blob([bytes], { type: 'application/octet-stream' }));
|
|
370
|
+
|
|
371
|
+
const anchor = document.createElement('a');
|
|
372
|
+
|
|
373
|
+
anchor.href = url;
|
|
374
|
+
anchor.download = filename;
|
|
375
|
+
|
|
376
|
+
document.body.appendChild(anchor);
|
|
377
|
+
anchor.click();
|
|
378
|
+
anchor.remove();
|
|
379
|
+
|
|
380
|
+
// Revoking in the same task cancels the download in some browsers — the click only schedules it.
|
|
381
|
+
setTimeout(() => URL.revokeObjectURL(url), 60000);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* `YYYYMMDD-HHMMSS` in local time, matching the naming the other `.sgpt` captures on this machine
|
|
386
|
+
* already use — same sort order, and it reads as the clock you took it by rather than as UTC.
|
|
387
|
+
*
|
|
388
|
+
* @returns {string}
|
|
389
|
+
*/
|
|
390
|
+
function file_timestamp() {
|
|
391
|
+
const now = new Date();
|
|
392
|
+
|
|
393
|
+
const pad = value => String(value).padStart(2, '0');
|
|
394
|
+
|
|
395
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`
|
|
396
|
+
+ `-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* @param {number} bytes
|
|
401
|
+
* @returns {string}
|
|
402
|
+
*/
|
|
403
|
+
function format_bytes(bytes) {
|
|
404
|
+
if (bytes < 1024) {
|
|
405
|
+
return `${bytes} B`;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (bytes < 1024 * 1024) {
|
|
409
|
+
return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
413
|
+
}
|
|
414
|
+
|
|
320
415
|
export async function start() {
|
|
321
416
|
|
|
322
417
|
const element = id => document.getElementById(id);
|
|
@@ -407,8 +502,17 @@ export async function start() {
|
|
|
407
502
|
|
|
408
503
|
const views_element = element('views');
|
|
409
504
|
|
|
505
|
+
/**
|
|
506
|
+
* Which placement the camera was last sent to, for the profile capture's note — a capture that
|
|
507
|
+
* cannot say where it was taken is much less use a week later.
|
|
508
|
+
* @type {object|null}
|
|
509
|
+
*/
|
|
510
|
+
let current_view = null;
|
|
511
|
+
|
|
410
512
|
/** @param {object} view */
|
|
411
513
|
function go_to(view) {
|
|
514
|
+
current_view = view;
|
|
515
|
+
|
|
412
516
|
camera.transform.fromJSON({ position: view.position, rotation: view.rotation });
|
|
413
517
|
|
|
414
518
|
// the controller holds its own spherical state and would otherwise snap the camera back on
|
|
@@ -559,6 +663,200 @@ export async function start() {
|
|
|
559
663
|
repaint();
|
|
560
664
|
});
|
|
561
665
|
|
|
666
|
+
// -- profiling: T starts a recording, T again stops it and downloads the capture
|
|
667
|
+
|
|
668
|
+
const profile_status = element('profile_status');
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* The recording in progress, or null.
|
|
672
|
+
*
|
|
673
|
+
* The engine never constructs one of these — `Renderer.profile_session` is a nullable field and
|
|
674
|
+
* the null checks around it are the entire integration, which is what keeps the profiler out of
|
|
675
|
+
* a bundle that does not ask for it. The two imports at the top of this file are the whole
|
|
676
|
+
* opt-in.
|
|
677
|
+
*
|
|
678
|
+
* @type {GPUProfileSession|null}
|
|
679
|
+
*/
|
|
680
|
+
let profile_session = null;
|
|
681
|
+
|
|
682
|
+
/** Guards against T being leaned on while a stop is still draining. */
|
|
683
|
+
let profile_busy = false;
|
|
684
|
+
|
|
685
|
+
function set_profile_status(text, recording = false) {
|
|
686
|
+
profile_status.textContent = text;
|
|
687
|
+
profile_status.className = recording ? 'recording' : '';
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function start_profile() {
|
|
691
|
+
const device = renderer.device;
|
|
692
|
+
|
|
693
|
+
const settings = [
|
|
694
|
+
current_view === null ? 'free camera' : current_view.label,
|
|
695
|
+
`fov ${Math.round(camera.fov / DEG_TO_RAD)}°`,
|
|
696
|
+
`volumetric TAA ${(globalThis.__VOL_TAA_ENABLED ?? true) ? 'on' : 'off'}`,
|
|
697
|
+
`${renderer.pixel_ratio.toFixed(2)}x`,
|
|
698
|
+
];
|
|
699
|
+
|
|
700
|
+
const session = new GPUProfileSession({
|
|
701
|
+
level: PROFILE_LEVEL,
|
|
702
|
+
note: `volumetrics_froxel — ${settings.join(' · ')}`,
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
// Populate before start(): the meta record is written into the stream there and not read
|
|
706
|
+
// again. Without it the capture is unreadable in the sense that matters — a pass taking
|
|
707
|
+
// 0.4 ms says nothing until you know which GPU and at what resolution.
|
|
708
|
+
const meta = session.meta;
|
|
709
|
+
|
|
710
|
+
// `GPUDevice.adapterInfo` is recent enough that a browser running this page may not have it,
|
|
711
|
+
// and several browsers withhold the fields even when they do. Empty is what the format
|
|
712
|
+
// expects in that case.
|
|
713
|
+
const info = device.adapterInfo;
|
|
714
|
+
|
|
715
|
+
if (info !== undefined && info !== null) {
|
|
716
|
+
meta.adapter_vendor = info.vendor ?? "";
|
|
717
|
+
meta.adapter_architecture = info.architecture ?? "";
|
|
718
|
+
meta.adapter_device = info.device ?? "";
|
|
719
|
+
meta.adapter_description = info.description ?? "";
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
meta.features = [...device.features];
|
|
723
|
+
meta.engine_version = package_json.version;
|
|
724
|
+
|
|
725
|
+
session.onBytesWritten.add(bytes => {
|
|
726
|
+
set_profile_status(
|
|
727
|
+
`● recording — ${session.frames_recorded} frames, ${format_bytes(bytes)} · T to stop`,
|
|
728
|
+
true
|
|
729
|
+
);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
renderer.profile_session = session;
|
|
733
|
+
session.start();
|
|
734
|
+
|
|
735
|
+
profile_session = session;
|
|
736
|
+
|
|
737
|
+
// The timers go quiet without this rather than failing, so the capture still carries the
|
|
738
|
+
// frame graph and the workload — it just has no durations in it. Worth knowing before you
|
|
739
|
+
// record for a minute and then look.
|
|
740
|
+
const timed = device.features.has('timestamp-query');
|
|
741
|
+
|
|
742
|
+
set_profile_status(
|
|
743
|
+
timed
|
|
744
|
+
? '● recording — 0 frames · T to stop'
|
|
745
|
+
: '● recording without timestamp-query — structure only, no durations · T to stop',
|
|
746
|
+
true
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Wait for frames already submitted to finish landing in the session.
|
|
752
|
+
*
|
|
753
|
+
* A frame is committed when its timestamp readback resolves — a `mapAsync` a frame or two behind
|
|
754
|
+
* submit, see `ShadeGPUCommandContext.profiling_absorbed` — and `record_frame` silently ignores
|
|
755
|
+
* anything arriving after `stop()`. Stopping the instant the key goes down therefore discards
|
|
756
|
+
* the last few frames, which are the ones you were most likely looking at.
|
|
757
|
+
*
|
|
758
|
+
* Drains the queue, then waits for the frame count to stop moving. Capped, because a recording
|
|
759
|
+
* taken while the page is paused never moves at all.
|
|
760
|
+
*
|
|
761
|
+
* @param {GPUProfileSession} session
|
|
762
|
+
*/
|
|
763
|
+
async function drain_profile(session) {
|
|
764
|
+
const DEADLINE_MS = 1000;
|
|
765
|
+
const QUIET_MS = 60;
|
|
766
|
+
|
|
767
|
+
await renderer.device.queue.onSubmittedWorkDone();
|
|
768
|
+
|
|
769
|
+
const deadline = performance.now() + DEADLINE_MS;
|
|
770
|
+
|
|
771
|
+
let previous = session.frames_recorded;
|
|
772
|
+
|
|
773
|
+
while (performance.now() < deadline) {
|
|
774
|
+
await new Promise(resolve => setTimeout(resolve, QUIET_MS));
|
|
775
|
+
|
|
776
|
+
if (session.frames_recorded === previous) {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
previous = session.frames_recorded;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
async function stop_profile() {
|
|
785
|
+
const session = profile_session;
|
|
786
|
+
|
|
787
|
+
profile_session = null;
|
|
788
|
+
|
|
789
|
+
// Detach first, so nothing encoded during the drain below joins the recording. Frames
|
|
790
|
+
// already encoded are unaffected: Renderer captures the session into a local before it
|
|
791
|
+
// subscribes to the readback.
|
|
792
|
+
renderer.profile_session = null;
|
|
793
|
+
|
|
794
|
+
set_profile_status('stopping — draining in-flight frames…');
|
|
795
|
+
|
|
796
|
+
await drain_profile(session);
|
|
797
|
+
|
|
798
|
+
const bytes = session.stop();
|
|
799
|
+
|
|
800
|
+
const filename = `volumetrics_froxel-${file_timestamp()}.sgpt`;
|
|
801
|
+
|
|
802
|
+
download_bytes(bytes, filename);
|
|
803
|
+
|
|
804
|
+
// Also to the console, because the status line is the only other place the name appears and
|
|
805
|
+
// a browser that declines the save says nothing about which capture it declined.
|
|
806
|
+
console.log(
|
|
807
|
+
`profile: ${session.frames_recorded} frames, ${format_bytes(bytes.byteLength)}, `
|
|
808
|
+
+ `${session.topology_count} topolog${session.topology_count === 1 ? 'y' : 'ies'} -> ${filename}`
|
|
809
|
+
);
|
|
810
|
+
|
|
811
|
+
set_profile_status(
|
|
812
|
+
`${session.frames_recorded} frames, ${format_bytes(bytes.byteLength)}`
|
|
813
|
+
+ `, ${session.topology_count} topolog${session.topology_count === 1 ? 'y' : 'ies'}`
|
|
814
|
+
+ ` · ${filename}`
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
window.addEventListener('keydown', event => {
|
|
819
|
+
if (event.ctrlKey || event.metaKey || event.altKey || event.repeat) {
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// `key` first, because it is the letter actually printed on the key the user pressed —
|
|
824
|
+
// `code` is physical position, so on AZERTY it names the key where T sits on QWERTY. `code`
|
|
825
|
+
// is the fallback for layouts whose `key` is not Latin at all (Cyrillic gives "е" here).
|
|
826
|
+
if (event.key.toLowerCase() !== 't' && event.code !== 'KeyT') {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// the panel's sliders and buttons are focusable; do not steal a key from a field
|
|
831
|
+
const target = event.target;
|
|
832
|
+
|
|
833
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement) {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (profile_busy) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
event.preventDefault();
|
|
842
|
+
|
|
843
|
+
profile_busy = true;
|
|
844
|
+
|
|
845
|
+
const done = profile_session === null
|
|
846
|
+
? Promise.resolve(start_profile())
|
|
847
|
+
: stop_profile();
|
|
848
|
+
|
|
849
|
+
done.catch(error => {
|
|
850
|
+
set_profile_status(`profiling failed: ${error?.message ?? error}`);
|
|
851
|
+
console.error(error);
|
|
852
|
+
|
|
853
|
+
profile_session = null;
|
|
854
|
+
renderer.profile_session = null;
|
|
855
|
+
}).finally(() => {
|
|
856
|
+
profile_busy = false;
|
|
857
|
+
});
|
|
858
|
+
});
|
|
859
|
+
|
|
562
860
|
element('pause').addEventListener('click', () => {
|
|
563
861
|
paused = !paused;
|
|
564
862
|
element('pause').textContent = paused ? 'Resume' : 'Pause';
|