@woosh/meep-engine 3.18.0 → 3.20.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.
- package/build/bundle-worker-image-decoder.js +1 -1
- package/build/bundle-worker-terrain.js +1 -1
- package/editor/view/particles/effect/ParticleCurveEditorView.d.ts.map +1 -1
- package/editor/view/particles/effect/ParticleCurveEditorView.js +303 -120
- package/editor/view/particles/effect/ParticleGradientEditorView.d.ts.map +1 -1
- package/editor/view/particles/effect/ParticleGradientEditorView.js +108 -63
- package/editor/view/particles/effect/ParticleGraphEditorView.js +2 -2
- package/editor/view/particles/effect/ParticleNodeParametersView.d.ts.map +1 -1
- package/editor/view/particles/effect/ParticleNodeParametersView.js +7 -46
- package/editor/view/particles/effect/particle-editor.css +60 -30
- package/package.json +1 -2
- package/samples/engine/README.md +1 -1
- package/src/core/binary/compression/decompress_bytes.d.ts +13 -0
- package/src/core/binary/compression/decompress_bytes.d.ts.map +1 -0
- package/src/core/binary/compression/decompress_bytes.js +28 -0
- package/src/core/collection/array/array_group_by.d.ts.map +1 -1
- package/src/core/collection/array/array_group_by.js +2 -11
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.js +4 -9
- package/src/core/model/node-graph/visual/layout/layout_assign_coordinates.js +67 -22
- package/src/core/model/object/optional_clone.d.ts +7 -0
- package/src/core/model/object/optional_clone.d.ts.map +1 -0
- package/src/core/model/object/optional_equality.d.ts +10 -0
- package/src/core/model/object/optional_equality.d.ts.map +1 -0
- package/src/{shade/descriptor/util → core/model/object}/optional_equality.js +2 -2
- package/src/core/model/object/optional_hash.d.ts +9 -0
- package/src/core/model/object/optional_hash.d.ts.map +1 -0
- package/src/{shade/descriptor/util → core/model/object}/optional_hash.js +3 -3
- package/src/engine/asset/Asset.d.ts +1 -1
- package/src/engine/asset/Asset.d.ts.map +1 -1
- package/src/engine/asset/Asset.js +3 -1
- package/src/engine/asset/loaders/image/ImageDecoderWorker.js +12 -27
- package/src/engine/asset/loaders/image/prototypePNG.js +8 -7
- package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.d.ts.map +1 -1
- package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.js +9 -10
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
- package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +4 -0
- package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts +266 -0
- package/src/engine/graphics/ecs/particles/ParticleEffect.d.ts.map +1 -0
- package/src/engine/graphics/ecs/particles/ParticleEffect.js +439 -0
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.d.ts +58 -0
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.d.ts.map +1 -0
- package/src/engine/graphics/ecs/particles/ParticleEffectSerializationAdapter.js +219 -0
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts +178 -25
- package/src/engine/graphics3/GPUParticleEmitterSystem.d.ts.map +1 -1
- package/src/engine/graphics3/GPUParticleEmitterSystem.js +809 -310
- package/src/format/image/png/PNGReader.d.ts +7 -6
- package/src/format/image/png/PNGReader.d.ts.map +1 -1
- package/src/format/image/png/PNGReader.js +13 -12
- package/src/format/image/png/chunk/png_chunk_decode_iTXt.d.ts +3 -2
- package/src/format/image/png/chunk/png_chunk_decode_iTXt.d.ts.map +1 -1
- package/src/format/image/png/chunk/png_chunk_decode_iTXt.js +5 -4
- package/src/format/image/png/chunk/png_chunk_decode_zTXt.d.ts +3 -2
- package/src/format/image/png/chunk/png_chunk_decode_zTXt.d.ts.map +1 -1
- package/src/format/image/png/chunk/png_chunk_decode_zTXt.js +5 -4
- package/src/format/image/png/png_inflate.d.ts +3 -3
- package/src/format/image/png/png_inflate.d.ts.map +1 -1
- package/src/format/image/png/png_inflate.js +29 -39
- package/src/format/texture/ktx2/ktx2_read.d.ts +4 -4
- package/src/format/texture/ktx2/ktx2_read.d.ts.map +1 -1
- package/src/format/texture/ktx2/ktx2_read.js +18 -21
- package/src/shade/descriptor/binding/BindGroupLayoutDescriptor.js +1 -1
- package/src/shade/descriptor/binding/BindGroupLayoutEntry.js +3 -3
- package/src/shade/descriptor/pipeline/PipelineDescriptorBase.js +1 -1
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.js +1 -1
- package/src/shade/descriptor/pipeline/render/ColorTargetState.js +3 -3
- package/src/shade/descriptor/pipeline/render/FragmentState.js +1 -1
- package/src/shade/descriptor/pipeline/render/RenderPipelineDescriptor.js +3 -3
- package/src/shade/playground/particle_ecs/README.md +203 -0
- package/src/shade/playground/particle_ecs/bonfire_editor.d.ts +39 -0
- package/src/shade/playground/particle_ecs/bonfire_editor.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/bonfire_editor.js +315 -0
- package/src/shade/playground/particle_ecs/bonfire_effects.d.ts +145 -0
- package/src/shade/playground/particle_ecs/bonfire_effects.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/bonfire_effects.js +202 -0
- package/src/shade/playground/particle_ecs/bonfire_sprites.d.ts +23 -0
- package/src/shade/playground/particle_ecs/bonfire_sprites.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/bonfire_sprites.js +315 -0
- package/src/shade/playground/particle_ecs/bonfire_world.d.ts +86 -0
- package/src/shade/playground/particle_ecs/bonfire_world.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/bonfire_world.js +303 -0
- package/src/shade/playground/particle_ecs/effects/embers.json +1634 -0
- package/src/shade/playground/particle_ecs/effects/flame.json +1882 -0
- package/src/shade/playground/particle_ecs/effects/smoke.json +1860 -0
- package/src/shade/playground/particle_ecs/effects/soot.json +1606 -0
- package/src/shade/playground/particle_ecs/index.html +330 -0
- package/src/shade/playground/particle_ecs/main.d.ts +2 -0
- package/src/shade/playground/particle_ecs/main.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/main.js +566 -0
- package/src/shade/playground/particle_ecs/moonlit_environment.d.ts +16 -0
- package/src/shade/playground/particle_ecs/moonlit_environment.d.ts.map +1 -0
- package/src/shade/playground/particle_ecs/moonlit_environment.js +144 -0
- package/src/shade/playground/particle_editor/README.md +5 -4
- package/src/shade/playground/particle_system/particle_prototype.d.ts +7 -9
- package/src/shade/playground/particle_system/particle_prototype.d.ts.map +1 -1
- package/src/shade/playground/particle_system/particle_prototype.js +7 -9
- package/src/shade/playground/profile_hotkey.d.ts +58 -0
- package/src/shade/playground/profile_hotkey.d.ts.map +1 -0
- package/src/shade/playground/profile_hotkey.js +325 -0
- package/src/shade/playground/ssr_motion/README.md +22 -0
- package/src/shade/playground/ssr_motion/index.html +18 -0
- package/src/shade/playground/ssr_motion/main.d.ts +2 -0
- package/src/shade/playground/ssr_motion/main.d.ts.map +1 -0
- package/src/shade/playground/ssr_motion/main.js +149 -0
- package/src/shade/playground/ssr_variance/README.md +105 -0
- package/src/shade/playground/ssr_variance/capture.d.ts +16 -0
- package/src/shade/playground/ssr_variance/capture.d.ts.map +1 -0
- package/src/shade/playground/ssr_variance/capture.js +96 -0
- package/src/shade/playground/ssr_variance/index.html +23 -0
- package/src/shade/playground/ssr_variance/main.d.ts +2 -0
- package/src/shade/playground/ssr_variance/main.d.ts.map +1 -0
- package/src/shade/playground/ssr_variance/main.js +246 -0
- package/src/shade/playground/ssr_variance/reference.d.ts +18 -0
- package/src/shade/playground/ssr_variance/reference.d.ts.map +1 -0
- package/src/shade/playground/ssr_variance/reference.js +78 -0
- package/src/shade/playground/ssr_variance/scene.d.ts +11 -0
- package/src/shade/playground/ssr_variance/scene.d.ts.map +1 -0
- package/src/shade/playground/ssr_variance/scene.js +61 -0
- package/src/shade/playground/ssr_variance/statistics.d.ts +44 -0
- package/src/shade/playground/ssr_variance/statistics.d.ts.map +1 -0
- package/src/shade/playground/ssr_variance/statistics.js +51 -0
- package/src/shade/renderer/geometry/Geometry.d.ts.map +1 -1
- package/src/shade/renderer/geometry/Geometry.js +4 -7
- package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts.map +1 -1
- package/src/shade/renderer/loader/gltf/tiny-gltf.js +9 -3
- package/src/shade/renderer/loader/usd/usd_decode_image.d.ts +4 -4
- package/src/shade/renderer/loader/usd/usd_decode_image.d.ts.map +1 -1
- package/src/shade/renderer/loader/usd/usd_decode_image.js +4 -4
- package/src/shade/renderer/material/StandardShadeMaterial.js +2 -2
- package/src/shade/renderer/particles/DESIGN.md +745 -702
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.js +3 -1
- package/src/shade/renderer/particles/graph/ParticleGroupBuilder.d.ts +1 -1
- package/src/shade/renderer/particles/graph/ParticleGroupBuilder.js +1 -1
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +9 -15
- package/src/shade/renderer/particles/graph/particle_group_library.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_group_library.js +8 -3
- package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts +0 -2
- package/src/shade/renderer/particles/graph/particle_node_parameters.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_node_parameters.js +0 -9
- package/src/shade/renderer/particles/graph/particle_node_presentation.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_node_presentation.js +0 -10
- package/src/shade/renderer/particles/graph/particle_ramp.d.ts +2 -8
- package/src/shade/renderer/particles/graph/particle_ramp.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/particle_ramp.js +2 -8
- package/src/shade/renderer/particles/graph/validate_particle_graph.js +0 -6
- package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph_particles_avboit.js +13 -4
- package/src/shade/renderer/particles/isa/InstructionStream.d.ts +1 -12
- package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/InstructionStream.js +1 -12
- package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts +54 -60
- package/src/shade/renderer/particles/isa/ParticleVMISA.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/ParticleVMISA.js +9 -17
- package/src/shade/renderer/particles/isa/particle_assembly.d.ts +0 -1
- package/src/shade/renderer/particles/isa/particle_assembly.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/particle_assembly.js +2 -16
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.d.ts +1 -1
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_graph.js +1 -1
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_lift.js +0 -14
- package/src/shade/renderer/particles/optimizer/dag/particle_vector_pack.js +3 -17
- package/src/shade/renderer/particles/optimizer/particle_program_analysis.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_program_analysis.js +15 -36
- package/src/shade/renderer/particles/optimizer/particle_program_equivalence.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_program_equivalence.js +1 -5
- package/src/shade/renderer/particles/optimizer/particle_program_fuzz.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_program_fuzz.js +1 -8
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts +2 -11
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.d.ts.map +1 -1
- package/src/shade/renderer/particles/optimizer/particle_vm_semantics.js +2 -11
- package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts +11 -4
- package/src/shade/renderer/particles/runtime/ParticleEmitter.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/ParticleEmitter.js +431 -424
- package/src/shade/renderer/particles/shaders/chunk_particle_avboit_contribution.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_avboit_contribution.js +13 -0
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts +3 -6
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.js +49 -17
- package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emit.js +0 -2
- package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emit_wide.js +0 -2
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +1 -5
- package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_simulate_wide.js +0 -2
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts +1 -3
- package/src/shade/renderer/particles/vm/ParticleVMReference.d.ts.map +1 -1
- package/src/shade/renderer/particles/vm/ParticleVMReference.js +4 -20
- package/src/shade/renderer/particles/vm/chunk_particle_vm.d.ts.map +1 -1
- package/src/shade/renderer/particles/vm/chunk_particle_vm.js +2 -5
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance.d.ts.map +1 -1
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance.js +0 -2
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance_wide.d.ts.map +1 -1
- package/src/shade/renderer/particles/warmup/shader_particle_warmup_advance_wide.js +0 -2
- package/src/shade/renderer/postprocess/ssr/SSR.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/SSR.js +2 -1
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_reprojection.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_reprojection.js +20 -5
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_sample_history.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_sample_history.js +19 -5
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.d.ts +4 -0
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.d.ts.map +1 -0
- package/src/shade/renderer/postprocess/ssr/reproject/chunk_ssr_temporal_accumulate.js +137 -0
- package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts +0 -8
- package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/ssr/ssr_reproject_shader.js +18 -114
- package/src/shade/renderer/scene/Mesh.js +1 -1
- package/src/shade/renderer/texture/ShadeTexture.js +1 -1
- package/src/shade/renderer/texture/source/texel_data_from_ktx2.d.ts +2 -2
- package/src/shade/renderer/texture/source/texel_data_from_ktx2.d.ts.map +1 -1
- package/src/shade/renderer/texture/source/texel_data_from_ktx2.js +3 -3
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts +0 -19
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts.map +0 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.js +0 -30
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_disabled.d.ts +0 -11
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_disabled.d.ts.map +0 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_disabled.js +0 -13
- package/src/shade/renderer/particles/shaders/chunk_particle_emitter_warmup.d.ts +0 -20
- package/src/shade/renderer/particles/shaders/chunk_particle_emitter_warmup.d.ts.map +0 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_emitter_world_sphere.d.ts +0 -14
- package/src/shade/renderer/particles/shaders/chunk_particle_emitter_world_sphere.d.ts.map +0 -1
- package/src/shade/renderer/particles/shaders/shader_particle_reclaim.d.ts +0 -50
- package/src/shade/renderer/particles/shaders/shader_particle_reclaim.d.ts.map +0 -1
- package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.d.ts +0 -17
- package/src/shade/renderer/postprocess/ssr/reproject/shader_ffx_denoiser_reflections_reproject.d.ts.map +0 -1
- /package/src/{shade/descriptor/util → core/model/object}/optional_clone.js +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import Vector3 from "../../../core/geom/Vector3.js";
|
|
2
|
+
import { Sampler2D } from "../../../engine/graphics/texture/sampler/Sampler2D.js";
|
|
3
|
+
import { sampler2d_to_f16 } from "../../../engine/graphics/texture/sampler/sampler2d_to_f16.js";
|
|
4
|
+
import { octahedral_uv_to_direction } from "../../renderer/light/environment/octahedral_uv_to_direction.js";
|
|
5
|
+
import { ColorSpace } from "../../renderer/texture/ColorSpace.js";
|
|
6
|
+
import { ShadeImage } from "../../renderer/texture/source/ShadeImage.js";
|
|
7
|
+
import { ShadeTexture } from "../../renderer/texture/ShadeTexture.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A moonlit night sky, generated: what the bonfire is lit by when it is not lighting itself.
|
|
11
|
+
*
|
|
12
|
+
* Built rather than loaded for the same reason the sprites are — `test_assets/` is gitignored, so a
|
|
13
|
+
* fresh checkout has no environment map, and a page that needs one to look right would look wrong
|
|
14
|
+
* for everybody who has not fetched the asset repository. This is small, it is in linear radiance,
|
|
15
|
+
* and it is the same octahedral projection `scene.lights.environment` samples.
|
|
16
|
+
*
|
|
17
|
+
* **The sky is the reason the fire reads as a fire.** Shade assumes global illumination, so a scene
|
|
18
|
+
* with no environment renders unlit; give it a bright day and the flame washes out to a pale smear,
|
|
19
|
+
* because the fire's own light is then a small fraction of what is already there. A night sky about
|
|
20
|
+
* three orders of magnitude below daylight is what leaves the fire room to be the brightest thing
|
|
21
|
+
* in the frame, and what makes the smoke read as blue where the firelight has stopped reaching it.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolution per octahedral face grid.
|
|
26
|
+
*
|
|
27
|
+
* Small on purpose: this is an ambient source, convolved into irradiance and a handful of roughness
|
|
28
|
+
* mips before anything samples it. What it has to be is smooth and correctly oriented.
|
|
29
|
+
*
|
|
30
|
+
* @type {number}
|
|
31
|
+
*/
|
|
32
|
+
const RESOLUTION = 128;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Where the moon is. Unit, +Y up — the direction light arrives *from*, so the shadows fall the
|
|
36
|
+
* other way.
|
|
37
|
+
*
|
|
38
|
+
* @type {Vector3}
|
|
39
|
+
*/
|
|
40
|
+
export const MOON_DIRECTION = new Vector3(-0.42, 0.62, -0.66).normalize();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Angular radius of the moon's disc, in radians. Far larger than the real one (about 0.0045): the
|
|
44
|
+
* map is 128 texels across a hemisphere, so a true disc would be a fraction of a texel and would
|
|
45
|
+
* disappear into the first mip. This is a moon to look at, not an ephemeris.
|
|
46
|
+
*
|
|
47
|
+
* @type {number}
|
|
48
|
+
*/
|
|
49
|
+
const MOON_RADIUS = 0.075;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @type {Vector3}
|
|
53
|
+
*/
|
|
54
|
+
const SCRATCH_DIRECTION = new Vector3();
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Radiance arriving from a direction, in linear space.
|
|
58
|
+
*
|
|
59
|
+
* @param {number[]} out rgb
|
|
60
|
+
* @param {number} x
|
|
61
|
+
* @param {number} y +Y is up
|
|
62
|
+
* @param {number} z
|
|
63
|
+
*/
|
|
64
|
+
function moonlit_radiance(out, x, y, z) {
|
|
65
|
+
// How close this direction is to the moon, as the cosine of the angle between them.
|
|
66
|
+
const alignment = x * MOON_DIRECTION.x + y * MOON_DIRECTION.y + z * MOON_DIRECTION.z;
|
|
67
|
+
const angle = Math.acos(Math.min(1, Math.max(-1, alignment)));
|
|
68
|
+
|
|
69
|
+
if (y >= 0) {
|
|
70
|
+
// Night sky: deepest at the zenith, lifting toward the horizon where the atmosphere is
|
|
71
|
+
// thicker and there is more of it to scatter what little light there is.
|
|
72
|
+
const horizon = Math.pow(1 - y, 4);
|
|
73
|
+
|
|
74
|
+
out[0] = 0.0035 + horizon * 0.011;
|
|
75
|
+
out[1] = 0.0052 + horizon * 0.014;
|
|
76
|
+
out[2] = 0.0115 + horizon * 0.020;
|
|
77
|
+
} else {
|
|
78
|
+
// Below the horizon: ground bounce, dimmer and less blue than the sky. This is what lights
|
|
79
|
+
// the underside of the smoke, so it is not zero however dark the night is.
|
|
80
|
+
const t = Math.pow(1 + y, 2);
|
|
81
|
+
|
|
82
|
+
out[0] = 0.0016 + t * 0.0042;
|
|
83
|
+
out[1] = 0.0017 + t * 0.0044;
|
|
84
|
+
out[2] = 0.0022 + t * 0.0055;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// A broad halo, then the disc itself. The halo is what makes the sky around the moon read as
|
|
88
|
+
// sky with a moon in it rather than as a sticker on a gradient.
|
|
89
|
+
const halo = Math.exp(-angle * 5.5) * 0.05;
|
|
90
|
+
|
|
91
|
+
out[0] += halo * 0.75;
|
|
92
|
+
out[1] += halo * 0.82;
|
|
93
|
+
out[2] += halo;
|
|
94
|
+
|
|
95
|
+
if (angle < MOON_RADIUS) {
|
|
96
|
+
// Soft-edged, so the disc survives the convolution into the roughness mips with a shape
|
|
97
|
+
// rather than as ringing.
|
|
98
|
+
const disc = 1 - Math.pow(angle / MOON_RADIUS, 3);
|
|
99
|
+
|
|
100
|
+
out[0] += disc * 5.2;
|
|
101
|
+
out[1] += disc * 5.6;
|
|
102
|
+
out[2] += disc * 6.4;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The environment map. Assign it to `scene.lights.environment`.
|
|
108
|
+
*
|
|
109
|
+
* @returns {ShadeTexture}
|
|
110
|
+
*/
|
|
111
|
+
export function moonlit_environment() {
|
|
112
|
+
const sampler = Sampler2D.float32(4, RESOLUTION, RESOLUTION);
|
|
113
|
+
|
|
114
|
+
const data = sampler.data;
|
|
115
|
+
const rgb = [0, 0, 0];
|
|
116
|
+
|
|
117
|
+
for (let v = 0; v < RESOLUTION; v++) {
|
|
118
|
+
for (let u = 0; u < RESOLUTION; u++) {
|
|
119
|
+
// texel centres, so the mapping is symmetric across the octahedron's folds
|
|
120
|
+
octahedral_uv_to_direction(
|
|
121
|
+
SCRATCH_DIRECTION,
|
|
122
|
+
(u + 0.5) / RESOLUTION,
|
|
123
|
+
(v + 0.5) / RESOLUTION
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
moonlit_radiance(rgb, SCRATCH_DIRECTION.x, SCRATCH_DIRECTION.y, SCRATCH_DIRECTION.z);
|
|
127
|
+
|
|
128
|
+
const offset = (v * RESOLUTION + u) * 4;
|
|
129
|
+
|
|
130
|
+
data[offset] = rgb[0];
|
|
131
|
+
data[offset + 1] = rgb[1];
|
|
132
|
+
data[offset + 2] = rgb[2];
|
|
133
|
+
data[offset + 3] = 1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const image = ShadeImage.fromSampler2D(sampler2d_to_f16(sampler));
|
|
138
|
+
|
|
139
|
+
// radiance, not colour: the values above are light arriving, and treating them as sRGB would
|
|
140
|
+
// put a transfer curve through numbers that never had one
|
|
141
|
+
image.color_space = ColorSpace.LinearSRGB;
|
|
142
|
+
|
|
143
|
+
return ShadeTexture.from(image);
|
|
144
|
+
}
|
|
@@ -78,10 +78,11 @@ edits it at both altitudes.
|
|
|
78
78
|
- Two of them are **ramps**, edited as a picture. An **RGBA Gradient** (and an RGB one) is a strip of
|
|
79
79
|
the colour it produces with its stops underneath: drag a stop, click the lane to add one — in the
|
|
80
80
|
colour the gradient already is there, so adding never changes anything — and middle-click to remove.
|
|
81
|
-
A **Curve**
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
A **Curve** plots normalized time against unrestricted values, with a Y axis that fits the whole
|
|
82
|
+
curve, including peaks between keys. Its fields edit the selected key's time, value and tangents.
|
|
83
|
+
The scale stays fixed during a drag and refits on release. Neither ramp is sampled from a table
|
|
84
|
+
at runtime: both are written out into the program as arithmetic, which is what the **Code** tab
|
|
85
|
+
shows.
|
|
85
86
|
- The strip along the bottom lists every problem with the effect, from **both** phases, loudest
|
|
86
87
|
first. Click one to jump to the phase it is in and select the node; the node is outlined in the
|
|
87
88
|
graph too.
|
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Worked examples of authoring particle effects with the node graph + placing them in a scene for a
|
|
3
3
|
* {@link GPUParticleSystem} to run. Demonstrates: distinct per-particle lifecycle (no built-in age — the
|
|
4
|
-
* effect declares it),
|
|
4
|
+
* effect declares it), authored curves over life, flipbook frames, random emission,
|
|
5
5
|
* additive vs alpha blending, projection modes, soft depth and lighting toggles.
|
|
6
6
|
*
|
|
7
7
|
* These are examples, not engine code, which is why they sit here rather than under
|
|
8
8
|
* `renderer/particles/`: the only thing that opens them is this playground's page, and keeping them
|
|
9
9
|
* on this side of the line means the dependency runs playground -> renderer and never back.
|
|
10
10
|
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
13
|
-
* yields effects that use constant size/alpha instead (curve handle 0, evaluated as 0 by the
|
|
14
|
-
* disabled provider), which is handy for the headless tests.
|
|
11
|
+
* `size_curve` / `alpha_curve` are authored curve parameters compiled into arithmetic. Both are
|
|
12
|
+
* optional; omitted curves leave the initial size/colour unchanged.
|
|
15
13
|
*/
|
|
16
14
|
/**
|
|
17
15
|
* A fire effect: buoyant, turbulent, additive-blended camera-facing sprites whose size and opacity
|
|
18
16
|
* follow curves over their lifetime and whose flipbook advances with age.
|
|
19
17
|
*
|
|
20
18
|
* @param {object} [options]
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {
|
|
19
|
+
* @param {import("../../renderer/particles/graph/particle_ramp.js").ParticleCurveParameter} [options.size_curve] size(normalizedAge)
|
|
20
|
+
* @param {import("../../renderer/particles/graph/particle_ramp.js").ParticleCurveParameter} [options.alpha_curve] alpha(normalizedAge)
|
|
23
21
|
* @param {number} [options.flipbook_frames]
|
|
24
22
|
* @returns {{layout: ParticleLayout, effect: object, emitter: object}}
|
|
25
23
|
*/
|
|
26
24
|
export function fire_effect({ size_curve, alpha_curve, flipbook_frames }?: {
|
|
27
|
-
size_curve?:
|
|
28
|
-
alpha_curve?:
|
|
25
|
+
size_curve?: import("../../renderer/particles/graph/particle_ramp.js").ParticleCurveParameter;
|
|
26
|
+
alpha_curve?: import("../../renderer/particles/graph/particle_ramp.js").ParticleCurveParameter;
|
|
29
27
|
flipbook_frames?: number;
|
|
30
28
|
}): {
|
|
31
29
|
layout: ParticleLayout;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"particle_prototype.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/particle_system/particle_prototype.js"],"names":[],"mappings":"AAYA
|
|
1
|
+
{"version":3,"file":"particle_prototype.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/particle_system/particle_prototype.js"],"names":[],"mappings":"AAYA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;GASG;AACH;IALsG,UAAU,GAArG,OAAO,iDAAiD,EAAE,sBAAsB;IACW,WAAW,GAAtG,OAAO,iDAAiD,EAAE,sBAAsB;IAC/D,eAAe,GAAhC,MAAM;;YACK,cAAc;YAAU,MAAM;aAAW,MAAM;EA+GpE;AAED;;;;GAIG;AACH;YAFsB,cAAc;YAAU,MAAM;aAAW,MAAM;EAYpE;AAED;;;;;;;;;;;;;GAaG;AACH,yCANW,OAAO,+BAA+B,EAAE,KAAK;IAEa,IAAI,GAA9D,OAAO,gCAAgC,EAAE,MAAM;;UAEtC,eAAe;WAAS,eAAe;EAe1D;+BAvL8B,mDAAmD;gCAElD,qDAAqD"}
|
|
@@ -13,17 +13,15 @@ import { ParticleEmitter } from "../../renderer/particles/runtime/ParticleEmitte
|
|
|
13
13
|
/**
|
|
14
14
|
* Worked examples of authoring particle effects with the node graph + placing them in a scene for a
|
|
15
15
|
* {@link GPUParticleSystem} to run. Demonstrates: distinct per-particle lifecycle (no built-in age — the
|
|
16
|
-
* effect declares it),
|
|
16
|
+
* effect declares it), authored curves over life, flipbook frames, random emission,
|
|
17
17
|
* additive vs alpha blending, projection modes, soft depth and lighting toggles.
|
|
18
18
|
*
|
|
19
19
|
* These are examples, not engine code, which is why they sit here rather than under
|
|
20
20
|
* `renderer/particles/`: the only thing that opens them is this playground's page, and keeping them
|
|
21
21
|
* on this side of the line means the dependency runs playground -> renderer and never back.
|
|
22
22
|
*
|
|
23
|
-
* `
|
|
24
|
-
*
|
|
25
|
-
* yields effects that use constant size/alpha instead (curve handle 0, evaluated as 0 by the
|
|
26
|
-
* disabled provider), which is handy for the headless tests.
|
|
23
|
+
* `size_curve` / `alpha_curve` are authored curve parameters compiled into arithmetic. Both are
|
|
24
|
+
* optional; omitted curves leave the initial size/colour unchanged.
|
|
27
25
|
*/
|
|
28
26
|
|
|
29
27
|
/**
|
|
@@ -31,8 +29,8 @@ import { ParticleEmitter } from "../../renderer/particles/runtime/ParticleEmitte
|
|
|
31
29
|
* follow curves over their lifetime and whose flipbook advances with age.
|
|
32
30
|
*
|
|
33
31
|
* @param {object} [options]
|
|
34
|
-
* @param {
|
|
35
|
-
* @param {
|
|
32
|
+
* @param {import("../../renderer/particles/graph/particle_ramp.js").ParticleCurveParameter} [options.size_curve] size(normalizedAge)
|
|
33
|
+
* @param {import("../../renderer/particles/graph/particle_ramp.js").ParticleCurveParameter} [options.alpha_curve] alpha(normalizedAge)
|
|
36
34
|
* @param {number} [options.flipbook_frames]
|
|
37
35
|
* @returns {{layout: ParticleLayout, effect: object, emitter: object}}
|
|
38
36
|
*/
|
|
@@ -111,12 +109,12 @@ export function fire_effect({ size_curve, alpha_curve, flipbook_frames = 16 } =
|
|
|
111
109
|
|
|
112
110
|
// size + alpha from curves over life (or constants if no curve provided)
|
|
113
111
|
if (size_curve !== undefined) {
|
|
114
|
-
const sc = particle_node(update, "
|
|
112
|
+
const sc = particle_node(update, "curveLiteral", { curve: size_curve }); particle_wire(update, sc, "t", nAge);
|
|
115
113
|
particle_wire(update, particle_node(update, "setAttribute", { name: "size" }), "value", sc);
|
|
116
114
|
}
|
|
117
115
|
const color = particle_node(update, "attribute", { name: "color" });
|
|
118
116
|
if (alpha_curve !== undefined) {
|
|
119
|
-
const ac = particle_node(update, "
|
|
117
|
+
const ac = particle_node(update, "curveLiteral", { curve: alpha_curve }); particle_wire(update, ac, "t", nAge);
|
|
120
118
|
// color.rgb unchanged, color.a = ac : rebuild via select on a swizzle mask is overkill;
|
|
121
119
|
// simplest: multiply color by (1,1,1,ac_normalized) — approximate by scaling alpha only.
|
|
122
120
|
const alphaVec = particle_node(update, "mul");
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One key that records a GPU profile capture and downloads it.
|
|
3
|
+
*
|
|
4
|
+
* `T` starts a recording, `T` again stops it and saves an `.sgpt` next to the browser's other
|
|
5
|
+
* downloads. That is the whole interaction, and it is what every playground page wants: a capture is
|
|
6
|
+
* something you take *while looking at* the thing you are suspicious of, so reaching for a panel is
|
|
7
|
+
* already too slow.
|
|
8
|
+
*
|
|
9
|
+
* **The engine never constructs a `GPUProfileSession`.** `Renderer.profile_session` is a nullable
|
|
10
|
+
* field and the null checks around it are the entire integration, which is what keeps the profiler
|
|
11
|
+
* out of a bundle that does not ask for it. Importing this module is the opt-in.
|
|
12
|
+
*
|
|
13
|
+
* `volumetrics_froxel/main.js` has its own copy of this, written first; it is the page this was
|
|
14
|
+
* lifted from and it can adopt this when somebody has the assets to verify it against.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {object} ProfileHotkeyHandle
|
|
18
|
+
* @property {function(): boolean} is_recording
|
|
19
|
+
* @property {function(): Promise<void>} toggle start a recording, or stop and save the one running
|
|
20
|
+
* @property {function(): void} dispose remove the key listener
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Wire the key up.
|
|
24
|
+
*
|
|
25
|
+
* @param {object} params
|
|
26
|
+
* @param {import("../renderer/Renderer.js").Renderer} params.renderer initialized — this reads its
|
|
27
|
+
* device for the capture's metadata
|
|
28
|
+
* @param {string} params.name goes in the filename and in the capture's note
|
|
29
|
+
* @param {HTMLElement|null} [params.status] written with what the recording is doing; the element
|
|
30
|
+
* gets the class `recording` while one is running, and nothing otherwise
|
|
31
|
+
* @param {function(): string[]} [params.settings] what the page was set to when the capture was
|
|
32
|
+
* taken. A pass timing says nothing until you know that
|
|
33
|
+
* @param {number} [params.level] see {@link GPUProfileLevel}; `WORKLOAD` is the most detailed
|
|
34
|
+
* @param {string} [params.key] the letter to bind, lowercase
|
|
35
|
+
* @param {EventTarget} [params.target] where the listener goes
|
|
36
|
+
* @returns {ProfileHotkeyHandle}
|
|
37
|
+
*/
|
|
38
|
+
export function install_profile_hotkey({ renderer, name, status, settings, level, key, target, }: {
|
|
39
|
+
renderer: import("../renderer/Renderer.js").Renderer;
|
|
40
|
+
name: string;
|
|
41
|
+
status?: HTMLElement | null;
|
|
42
|
+
settings?: () => string[];
|
|
43
|
+
level?: number;
|
|
44
|
+
key?: string;
|
|
45
|
+
target?: EventTarget;
|
|
46
|
+
}): ProfileHotkeyHandle;
|
|
47
|
+
export type ProfileHotkeyHandle = {
|
|
48
|
+
is_recording: () => boolean;
|
|
49
|
+
/**
|
|
50
|
+
* start a recording, or stop and save the one running
|
|
51
|
+
*/
|
|
52
|
+
toggle: () => Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* remove the key listener
|
|
55
|
+
*/
|
|
56
|
+
dispose: () => void;
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=profile_hotkey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile_hotkey.d.ts","sourceRoot":"","sources":["../../../../src/shade/playground/profile_hotkey.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH;;;;;GAKG;AAEH;;;;;;;;;;;;;;;GAeG;AACH;IAZ8D,QAAQ,EAA3D,OAAO,yBAAyB,EAAE,QAAQ;IAE3B,IAAI,EAAnB,MAAM;IACoB,MAAM,GAAhC,WAAW,GAAC,IAAI;IAEc,QAAQ,SAA1B,MAAM,EAAE;IAEJ,KAAK,GAArB,MAAM;IACU,GAAG,GAAnB,MAAM;IACe,MAAM,GAA3B,WAAW;IACT,mBAAmB,CAwO/B;;wBA3PyB,OAAO;;;;kBACP,QAAQ,IAAI,CAAC;;;;mBACb,IAAI"}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import package_json from "../../../package.json";
|
|
2
|
+
import { GPUProfileLevel } from "../device/timing/profile/GPUProfileLevel.js";
|
|
3
|
+
import { GPUProfileSession } from "../device/timing/profile/GPUProfileSession.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One key that records a GPU profile capture and downloads it.
|
|
7
|
+
*
|
|
8
|
+
* `T` starts a recording, `T` again stops it and saves an `.sgpt` next to the browser's other
|
|
9
|
+
* downloads. That is the whole interaction, and it is what every playground page wants: a capture is
|
|
10
|
+
* something you take *while looking at* the thing you are suspicious of, so reaching for a panel is
|
|
11
|
+
* already too slow.
|
|
12
|
+
*
|
|
13
|
+
* **The engine never constructs a `GPUProfileSession`.** `Renderer.profile_session` is a nullable
|
|
14
|
+
* field and the null checks around it are the entire integration, which is what keeps the profiler
|
|
15
|
+
* out of a bundle that does not ask for it. Importing this module is the opt-in.
|
|
16
|
+
*
|
|
17
|
+
* `volumetrics_froxel/main.js` has its own copy of this, written first; it is the page this was
|
|
18
|
+
* lifted from and it can adopt this when somebody has the assets to verify it against.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {object} ProfileHotkeyHandle
|
|
23
|
+
* @property {function(): boolean} is_recording
|
|
24
|
+
* @property {function(): Promise<void>} toggle start a recording, or stop and save the one running
|
|
25
|
+
* @property {function(): void} dispose remove the key listener
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Wire the key up.
|
|
30
|
+
*
|
|
31
|
+
* @param {object} params
|
|
32
|
+
* @param {import("../renderer/Renderer.js").Renderer} params.renderer initialized — this reads its
|
|
33
|
+
* device for the capture's metadata
|
|
34
|
+
* @param {string} params.name goes in the filename and in the capture's note
|
|
35
|
+
* @param {HTMLElement|null} [params.status] written with what the recording is doing; the element
|
|
36
|
+
* gets the class `recording` while one is running, and nothing otherwise
|
|
37
|
+
* @param {function(): string[]} [params.settings] what the page was set to when the capture was
|
|
38
|
+
* taken. A pass timing says nothing until you know that
|
|
39
|
+
* @param {number} [params.level] see {@link GPUProfileLevel}; `WORKLOAD` is the most detailed
|
|
40
|
+
* @param {string} [params.key] the letter to bind, lowercase
|
|
41
|
+
* @param {EventTarget} [params.target] where the listener goes
|
|
42
|
+
* @returns {ProfileHotkeyHandle}
|
|
43
|
+
*/
|
|
44
|
+
export function install_profile_hotkey({
|
|
45
|
+
renderer,
|
|
46
|
+
name,
|
|
47
|
+
status = null,
|
|
48
|
+
settings = () => [],
|
|
49
|
+
level = GPUProfileLevel.WORKLOAD,
|
|
50
|
+
key = "t",
|
|
51
|
+
target = window,
|
|
52
|
+
}) {
|
|
53
|
+
/**
|
|
54
|
+
* The recording in progress, or null.
|
|
55
|
+
* @type {GPUProfileSession|null}
|
|
56
|
+
*/
|
|
57
|
+
let session = null;
|
|
58
|
+
|
|
59
|
+
/** Guards against the key being leaned on while a stop is still draining. */
|
|
60
|
+
let busy = false;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {string} text
|
|
64
|
+
* @param {boolean} [recording]
|
|
65
|
+
*/
|
|
66
|
+
function set_status(text, recording = false) {
|
|
67
|
+
if (status === null) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
status.textContent = text;
|
|
72
|
+
status.className = recording ? "recording" : "";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function start() {
|
|
76
|
+
const device = renderer.device;
|
|
77
|
+
|
|
78
|
+
const recording = new GPUProfileSession({
|
|
79
|
+
level,
|
|
80
|
+
note: `${name} — ${[...settings(), `${renderer.pixel_ratio.toFixed(2)}x`].join(" · ")}`,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/*
|
|
84
|
+
Populated before start(): the meta record is written into the stream there and not read
|
|
85
|
+
again. Without it the capture is unreadable in the sense that matters — a pass taking 0.4 ms
|
|
86
|
+
says nothing until you know which GPU and at what resolution.
|
|
87
|
+
*/
|
|
88
|
+
const meta = recording.meta;
|
|
89
|
+
|
|
90
|
+
// `GPUDevice.adapterInfo` is recent enough that a browser running this page may not have it,
|
|
91
|
+
// and several browsers withhold the fields even when they do. Empty is what the format
|
|
92
|
+
// expects in that case.
|
|
93
|
+
const info = device.adapterInfo;
|
|
94
|
+
|
|
95
|
+
if (info !== undefined && info !== null) {
|
|
96
|
+
meta.adapter_vendor = info.vendor ?? "";
|
|
97
|
+
meta.adapter_architecture = info.architecture ?? "";
|
|
98
|
+
meta.adapter_device = info.device ?? "";
|
|
99
|
+
meta.adapter_description = info.description ?? "";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
meta.features = [...device.features];
|
|
103
|
+
meta.engine_version = package_json.version;
|
|
104
|
+
|
|
105
|
+
recording.onBytesWritten.add(bytes => {
|
|
106
|
+
/*
|
|
107
|
+
Only while this is still the live recording. Frames keep landing all through the drain
|
|
108
|
+
in `stop`, which is the whole point of draining — and without this check each one would
|
|
109
|
+
overwrite "stopping…" with "● recording · T to stop", so a capture that is on its way to
|
|
110
|
+
disk reads as one that is still running.
|
|
111
|
+
*/
|
|
112
|
+
if (session !== recording) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
set_status(
|
|
117
|
+
`● recording — ${recording.frames_recorded} frames, ${format_bytes(bytes)}`
|
|
118
|
+
+ ` · ${key.toUpperCase()} to stop`,
|
|
119
|
+
true
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
renderer.profile_session = recording;
|
|
124
|
+
recording.start();
|
|
125
|
+
|
|
126
|
+
session = recording;
|
|
127
|
+
|
|
128
|
+
// The timers go quiet without this rather than failing, so the capture still carries the
|
|
129
|
+
// frame graph and the workload — it just has no durations in it. Worth knowing before you
|
|
130
|
+
// record for a minute and then look.
|
|
131
|
+
const timed = device.features.has("timestamp-query");
|
|
132
|
+
|
|
133
|
+
set_status(
|
|
134
|
+
timed
|
|
135
|
+
? `● recording — 0 frames · ${key.toUpperCase()} to stop`
|
|
136
|
+
: "● recording without timestamp-query — structure only, no durations"
|
|
137
|
+
+ ` · ${key.toUpperCase()} to stop`,
|
|
138
|
+
true
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Wait for frames already submitted to finish landing in the session.
|
|
144
|
+
*
|
|
145
|
+
* A frame is committed when its timestamp readback resolves — a `mapAsync` a frame or two behind
|
|
146
|
+
* submit, see `ShadeGPUCommandContext.profiling_absorbed` — and `record_frame` silently ignores
|
|
147
|
+
* anything arriving after `stop()`. Stopping the instant the key goes down therefore discards
|
|
148
|
+
* the last few frames, which are the ones you were most likely looking at.
|
|
149
|
+
*
|
|
150
|
+
* Drains the queue, then waits for the frame count to stop moving. Capped, because a recording
|
|
151
|
+
* taken while the page is paused never moves at all.
|
|
152
|
+
*
|
|
153
|
+
* @param {GPUProfileSession} recording
|
|
154
|
+
* @returns {Promise<void>}
|
|
155
|
+
*/
|
|
156
|
+
async function drain(recording) {
|
|
157
|
+
const DEADLINE_MS = 1000;
|
|
158
|
+
const QUIET_MS = 60;
|
|
159
|
+
|
|
160
|
+
await renderer.device.queue.onSubmittedWorkDone();
|
|
161
|
+
|
|
162
|
+
const deadline = performance.now() + DEADLINE_MS;
|
|
163
|
+
|
|
164
|
+
let previous = recording.frames_recorded;
|
|
165
|
+
|
|
166
|
+
while (performance.now() < deadline) {
|
|
167
|
+
await new Promise(resolve => setTimeout(resolve, QUIET_MS));
|
|
168
|
+
|
|
169
|
+
if (recording.frames_recorded === previous) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
previous = recording.frames_recorded;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function stop() {
|
|
178
|
+
const recording = session;
|
|
179
|
+
|
|
180
|
+
session = null;
|
|
181
|
+
|
|
182
|
+
// Detached first, so nothing encoded during the drain below joins the recording. Frames
|
|
183
|
+
// already encoded are unaffected: Renderer captures the session into a local before it
|
|
184
|
+
// subscribes to the readback.
|
|
185
|
+
renderer.profile_session = null;
|
|
186
|
+
|
|
187
|
+
set_status("stopping — draining in-flight frames…");
|
|
188
|
+
|
|
189
|
+
await drain(recording);
|
|
190
|
+
|
|
191
|
+
const bytes = recording.stop();
|
|
192
|
+
const filename = `${name}-${file_timestamp()}.sgpt`;
|
|
193
|
+
|
|
194
|
+
download_bytes(bytes, filename);
|
|
195
|
+
|
|
196
|
+
const topologies = `${recording.topology_count} topolog${recording.topology_count === 1 ? "y" : "ies"}`;
|
|
197
|
+
|
|
198
|
+
// Also to the console, because the status line is the only other place the name appears and
|
|
199
|
+
// a browser that declines the save says nothing about which capture it declined.
|
|
200
|
+
console.log(
|
|
201
|
+
`profile: ${recording.frames_recorded} frames, ${format_bytes(bytes.byteLength)}, `
|
|
202
|
+
+ `${topologies} -> ${filename}`
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
set_status(
|
|
206
|
+
`${recording.frames_recorded} frames, ${format_bytes(bytes.byteLength)}, ${topologies}`
|
|
207
|
+
+ ` · ${filename}`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @returns {Promise<void>}
|
|
213
|
+
*/
|
|
214
|
+
function toggle() {
|
|
215
|
+
if (busy) {
|
|
216
|
+
return Promise.resolve();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
busy = true;
|
|
220
|
+
|
|
221
|
+
const done = session === null ? Promise.resolve(start()) : stop();
|
|
222
|
+
|
|
223
|
+
return done.catch(error => {
|
|
224
|
+
set_status(`profiling failed: ${error?.message ?? error}`);
|
|
225
|
+
console.error(error);
|
|
226
|
+
|
|
227
|
+
session = null;
|
|
228
|
+
renderer.profile_session = null;
|
|
229
|
+
}).finally(() => {
|
|
230
|
+
busy = false;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @param {KeyboardEvent} event
|
|
236
|
+
*/
|
|
237
|
+
function on_keydown(event) {
|
|
238
|
+
if (event.ctrlKey || event.metaKey || event.altKey || event.repeat) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/*
|
|
243
|
+
`key` first, because it is the letter actually printed on the key the user pressed — `code`
|
|
244
|
+
is physical position, so on AZERTY it names the key where T sits on QWERTY. `code` is the
|
|
245
|
+
fallback for layouts whose `key` is not Latin at all (Cyrillic gives "е" here).
|
|
246
|
+
*/
|
|
247
|
+
if (event.key.toLowerCase() !== key && event.code !== `Key${key.toUpperCase()}`) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// a panel's fields and buttons are focusable; do not steal a key from one
|
|
252
|
+
const focused = event.target;
|
|
253
|
+
|
|
254
|
+
if (
|
|
255
|
+
focused instanceof HTMLInputElement
|
|
256
|
+
|| focused instanceof HTMLTextAreaElement
|
|
257
|
+
|| focused instanceof HTMLSelectElement
|
|
258
|
+
) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
event.preventDefault();
|
|
263
|
+
|
|
264
|
+
toggle();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
target.addEventListener("keydown", on_keydown);
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
is_recording: () => session !== null,
|
|
271
|
+
toggle,
|
|
272
|
+
dispose: () => target.removeEventListener("keydown", on_keydown),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param {Uint8Array} bytes
|
|
278
|
+
* @param {string} filename
|
|
279
|
+
*/
|
|
280
|
+
function download_bytes(bytes, filename) {
|
|
281
|
+
const url = URL.createObjectURL(new Blob([bytes], { type: "application/octet-stream" }));
|
|
282
|
+
|
|
283
|
+
const anchor = document.createElement("a");
|
|
284
|
+
|
|
285
|
+
anchor.href = url;
|
|
286
|
+
anchor.download = filename;
|
|
287
|
+
|
|
288
|
+
document.body.appendChild(anchor);
|
|
289
|
+
anchor.click();
|
|
290
|
+
anchor.remove();
|
|
291
|
+
|
|
292
|
+
// Revoking in the same task cancels the download in some browsers — the click only schedules it.
|
|
293
|
+
setTimeout(() => URL.revokeObjectURL(url), 60000);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* `YYYYMMDD-HHMMSS` in local time, matching the naming the other `.sgpt` captures already use — same
|
|
298
|
+
* sort order, and it reads as the clock you took it by rather than as UTC.
|
|
299
|
+
*
|
|
300
|
+
* @returns {string}
|
|
301
|
+
*/
|
|
302
|
+
function file_timestamp() {
|
|
303
|
+
const now = new Date();
|
|
304
|
+
|
|
305
|
+
const pad = value => String(value).padStart(2, "0");
|
|
306
|
+
|
|
307
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}`
|
|
308
|
+
+ `-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @param {number} bytes
|
|
313
|
+
* @returns {string}
|
|
314
|
+
*/
|
|
315
|
+
function format_bytes(bytes) {
|
|
316
|
+
if (bytes < 1024) {
|
|
317
|
+
return `${bytes} B`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (bytes < 1024 * 1024) {
|
|
321
|
+
return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
325
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# SSR cross camera playground
|
|
2
|
+
|
|
3
|
+
Open `/src/shade/playground/ssr_motion/` on the engine's Vite development server.
|
|
4
|
+
It shares the exact scene, camera lens, resolution and fixed exposure of
|
|
5
|
+
`ssr_variance`: one metallic floor and an emissive cross card with two colored
|
|
6
|
+
markers. Final-image TAA and raster jitter default off independently.
|
|
7
|
+
|
|
8
|
+
Drag to orbit, right-drag to pan, wheel to zoom, or focus the canvas and use WASD.
|
|
9
|
+
Floor roughness changes live from 0.01 to 1.00. Material changes clear history;
|
|
10
|
+
ordinary camera movement does not. The lateral path uses the same motion-speed
|
|
11
|
+
scale as `ssr_reprojection` (x = 2 sin(phase), phase rate = 0.7 × speed).
|
|
12
|
+
Choose speed 2.0 to reproduce the faster lateral movement.
|
|
13
|
+
|
|
14
|
+
Pause motion continues sampling at the current pose. Pause rendering freezes the
|
|
15
|
+
image. Step frame submits one 1/60-second frame. SSR parallax, surface-only and
|
|
16
|
+
no-history modes, spatial denoising, final TAA and jitter can be compared separately.
|
|
17
|
+
|
|
18
|
+
Query controls: `?motion=lateral&speed=2&roughness=0.3`, `taa=on`, `jitter=on`,
|
|
19
|
+
`spatial=off`, and `temporal=0|1|2`. `manual` starts at frame zero with rendering
|
|
20
|
+
paused. `await ssr_motion_ready` exposes the renderer, camera, scene, `pose`,
|
|
21
|
+
`frame`, and `step(n, advance=true)` for controlled experiments. `advance=false`
|
|
22
|
+
keeps the current pose while sampling. Exact stepping requires rendering paused.
|