@woosh/meep-engine 3.11.1 → 3.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +22 -18
- package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
- 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/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +480 -315
- package/src/shade/renderer/volumetrics/NOTES.md +649 -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 +107 -0
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +4 -1
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +577 -471
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +64 -13
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts +8 -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 +174 -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 +2221 -2215
- package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.js +1297 -1117
- 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
|
@@ -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';
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The measurement protocol from this page's README, as code.
|
|
3
|
+
*
|
|
4
|
+
* Not imported by the page — it is loaded from the console when there is something to measure:
|
|
5
|
+
*
|
|
6
|
+
* ```js
|
|
7
|
+
* const m = await (await import('./measure.js')).attach();
|
|
8
|
+
* await m.ab(v => globalThis.__VOL_TAA_ENABLED = v, false, true);
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* It exists because every trap the README lists is a trap about *procedure*, and a procedure
|
|
12
|
+
* written down in prose gets followed differently each time. Three of them are structural and are
|
|
13
|
+
* dealt with here rather than left to the operator:
|
|
14
|
+
*
|
|
15
|
+
* - **The canvas cannot be read back.** A canvas texture is configured by the presentation path,
|
|
16
|
+
* cannot carry `COPY_SRC`, and is rotated away the moment the frame is presented — a `drawImage`
|
|
17
|
+
* off it a task later returns an empty bitmap, and a comparison of two blanks reports perfect
|
|
18
|
+
* agreement. Everything here renders into a texture this module owns, via
|
|
19
|
+
* `Renderer.render_to_target`, which shares the view context (and therefore all the temporal
|
|
20
|
+
* history) with the canvas path.
|
|
21
|
+
*
|
|
22
|
+
* - **Presentation throttling.** Whole-frame timing off the page's own loop is paced by the swap
|
|
23
|
+
* chain, which is why the README says two settings that plainly differ can report the same
|
|
24
|
+
* number. Driving the frames from here removes the swap chain from the measurement entirely.
|
|
25
|
+
*
|
|
26
|
+
* - **The noise floor has to pay the same costs the signal does.** `ab` takes the floor by making
|
|
27
|
+
* an identical no-op change and settling exactly as the real change does, and it interleaves the
|
|
28
|
+
* two sides so drift lands on both.
|
|
29
|
+
*
|
|
30
|
+
* The camera-path helpers exist for a narrower reason: a structure that is fitted to the camera can
|
|
31
|
+
* be perfectly correct standing still and swim under motion, and the froxel volume then goes
|
|
32
|
+
* through a 0.95-blend temporal filter that turns swimming into either ghosting or flicker. Neither
|
|
33
|
+
* is visible in a still. `fly` drives a pose sequence one frame at a time and records what the
|
|
34
|
+
* image does while it happens.
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* @param {object} [context] defaults to the globals the page publishes
|
|
38
|
+
* @param {Renderer} [context.renderer]
|
|
39
|
+
* @param {Scene} [context.scene]
|
|
40
|
+
* @param {PerspectiveCamera} [context.camera]
|
|
41
|
+
* @param {OrbitalCameraController} [context.controller]
|
|
42
|
+
*/
|
|
43
|
+
export function attach({ renderer, scene, camera, controller, }?: {
|
|
44
|
+
renderer?: Renderer;
|
|
45
|
+
scene?: Scene;
|
|
46
|
+
camera?: PerspectiveCamera;
|
|
47
|
+
controller?: OrbitalCameraController;
|
|
48
|
+
}): Promise<{
|
|
49
|
+
renderer: Renderer;
|
|
50
|
+
scene: Scene;
|
|
51
|
+
camera: PerspectiveCamera;
|
|
52
|
+
controller: OrbitalCameraController;
|
|
53
|
+
device: any;
|
|
54
|
+
readonly capture: ({ settle: settle_frames, average }?: {
|
|
55
|
+
settle?: number;
|
|
56
|
+
average?: number;
|
|
57
|
+
}) => Promise<Float32Array>;
|
|
58
|
+
make_target: () => any;
|
|
59
|
+
target: () => any;
|
|
60
|
+
frame: (time_delta_s?: number) => void;
|
|
61
|
+
settle: (n: any) => Promise<void>;
|
|
62
|
+
accumulate_luminance: (out: Float32Array) => Promise<void>;
|
|
63
|
+
compare: (a: any, b: any) => {
|
|
64
|
+
signed_mean: number;
|
|
65
|
+
mean_abs: number;
|
|
66
|
+
max_abs: number;
|
|
67
|
+
mean_a: number;
|
|
68
|
+
mean_b: number;
|
|
69
|
+
};
|
|
70
|
+
mean: (values: any) => number;
|
|
71
|
+
ab: (apply: any, a: any, b: any, options?: {}) => Promise<{
|
|
72
|
+
floor: {
|
|
73
|
+
signed_mean: number;
|
|
74
|
+
mean_abs: number;
|
|
75
|
+
max_abs: number;
|
|
76
|
+
mean_a: number;
|
|
77
|
+
mean_b: number;
|
|
78
|
+
}[];
|
|
79
|
+
signal: {
|
|
80
|
+
signed_mean: number;
|
|
81
|
+
mean_abs: number;
|
|
82
|
+
max_abs: number;
|
|
83
|
+
mean_a: number;
|
|
84
|
+
mean_b: number;
|
|
85
|
+
}[];
|
|
86
|
+
}>;
|
|
87
|
+
read_pose: () => {
|
|
88
|
+
position: any[];
|
|
89
|
+
rotation: any[];
|
|
90
|
+
};
|
|
91
|
+
write_pose: (pose: any) => void;
|
|
92
|
+
interpolate: (from: any, to: any, t: any) => {
|
|
93
|
+
position: any;
|
|
94
|
+
rotation: any;
|
|
95
|
+
};
|
|
96
|
+
fly: (to: any, { frames, hold }?: {
|
|
97
|
+
frames?: number;
|
|
98
|
+
hold?: number;
|
|
99
|
+
}) => Promise<number[]>;
|
|
100
|
+
series_stats: (series: any) => {
|
|
101
|
+
frames: any;
|
|
102
|
+
mean: number;
|
|
103
|
+
flicker: number;
|
|
104
|
+
max_step: number;
|
|
105
|
+
drift: number;
|
|
106
|
+
};
|
|
107
|
+
converge_at: (pose: any, options?: {}) => Promise<Float32Array>;
|
|
108
|
+
time: ({ frames }?: {
|
|
109
|
+
frames?: number;
|
|
110
|
+
}) => Promise<number>;
|
|
111
|
+
profile: ({ frames, settle: settle_frames }?: {
|
|
112
|
+
frames?: number;
|
|
113
|
+
settle?: number;
|
|
114
|
+
}) => Promise<{
|
|
115
|
+
[x: string]: number;
|
|
116
|
+
}>;
|
|
117
|
+
set_pixel_ratio: (ratio: any) => void;
|
|
118
|
+
}>;
|
|
119
|
+
//# sourceMappingURL=measure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure.d.ts","sourceRoot":"","sources":["../../../../../src/shade/playground/volumetrics_froxel/measure.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;;;;;GAMG;AACH;IAL8B,QAAQ;IACX,KAAK;IACO,MAAM;IACA,UAAU;;;;;;;;;;UAuLtC,QAAQ,YAAY,CAAC;;;;;gCA5DvB,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA4MV,QAAQ,MAAM,EAAE,CAAC;;;;;;;;;;;;;QAsGF,MAAM,GAAvB,MAAM;QACW,MAAM,GAAvB,MAAM;UACJ;YAAe,MAAM,GAAE,MAAM;MAAE;;GAsF/C"}
|