@plasius/gpu-renderer 0.2.6 → 0.2.8
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/CHANGELOG.md +27 -2
- package/README.md +26 -10
- package/dist/index.cjs +662 -282
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +662 -282
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.d.ts +56 -4
- package/src/wavefront-compute.js +728 -259
package/src/wavefront-compute.js
CHANGED
|
@@ -13,17 +13,19 @@ const DEFAULT_MAX_DEPTH = 6;
|
|
|
13
13
|
const DEFAULT_TILE_SIZE = 128;
|
|
14
14
|
const DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
15
15
|
const MAX_SAMPLES_PER_PIXEL = 256;
|
|
16
|
-
const DEFAULT_BRDF_LUT_SIZE =
|
|
16
|
+
const DEFAULT_BRDF_LUT_SIZE = 128;
|
|
17
|
+
const DEFAULT_BRDF_LUT_SAMPLE_COUNT = 256;
|
|
17
18
|
const DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
18
19
|
const DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
19
20
|
const DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
21
|
+
const DEFAULT_MEDIUM_PHASE_MODEL = 0;
|
|
20
22
|
const WORKGROUP_SIZE = 64;
|
|
21
23
|
export const rendererWavefrontComputeMode = "webgpu-compute";
|
|
22
24
|
export const rendererWavefrontComputeWorkgroupSize = WORKGROUP_SIZE;
|
|
23
25
|
export const rendererWavefrontComputeStatsStride = 8;
|
|
24
26
|
const RAY_RECORD_BYTES = 80;
|
|
25
27
|
const HIT_RECORD_BYTES = 256;
|
|
26
|
-
const SCENE_OBJECT_RECORD_BYTES =
|
|
28
|
+
const SCENE_OBJECT_RECORD_BYTES = 160;
|
|
27
29
|
const MESH_VERTEX_RECORD_BYTES = 48;
|
|
28
30
|
const MESH_RANGE_RECORD_BYTES = 240;
|
|
29
31
|
const TRIANGLE_RECORD_BYTES = 352;
|
|
@@ -32,11 +34,13 @@ const BVH_NODE_RECORD_BYTES = 48;
|
|
|
32
34
|
const BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
33
35
|
const EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
34
36
|
const ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
37
|
+
const MEDIUM_TABLE_ROWS = 2;
|
|
35
38
|
const ACCUMULATION_RECORD_BYTES = 16;
|
|
36
39
|
const PATH_VERTEX_RECORD_BYTES = 16;
|
|
37
40
|
const GPU_SUBMITTED_WORK_TIMEOUT_MS = 5_000;
|
|
38
41
|
const GPU_READBACK_COMPLETION_TIMEOUT_MS = 60_000;
|
|
39
42
|
const GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS = 60_000;
|
|
43
|
+
const GPU_MAX_SUBMITTED_WORK_DEADLINE_MS = 180_000;
|
|
40
44
|
const CONFIG_BUFFER_BYTES = 320;
|
|
41
45
|
const COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
42
46
|
const INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
@@ -437,6 +441,183 @@ function deriveBounds(input) {
|
|
|
437
441
|
return null;
|
|
438
442
|
}
|
|
439
443
|
|
|
444
|
+
function deriveBeerLambertAbsorptionFromAttenuationColor(
|
|
445
|
+
attenuationColor,
|
|
446
|
+
attenuationDistance,
|
|
447
|
+
density = 1
|
|
448
|
+
) {
|
|
449
|
+
const distance = Number(attenuationDistance);
|
|
450
|
+
const densityScale = Math.max(0, Number(density) || 0);
|
|
451
|
+
if (!Number.isFinite(distance) || distance <= 0 || densityScale <= 0) {
|
|
452
|
+
return [0, 0, 0];
|
|
453
|
+
}
|
|
454
|
+
return attenuationColor.slice(0, 3).map((channel) => {
|
|
455
|
+
const clamped = clamp(Number(channel) || 0, 0.0001, 1);
|
|
456
|
+
return Math.max(0, (-Math.log(clamped) / distance) * densityScale);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function readMediumPhaseModel(value) {
|
|
461
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
462
|
+
return Math.max(0, Math.trunc(value));
|
|
463
|
+
}
|
|
464
|
+
switch (String(value ?? "").trim().toLowerCase()) {
|
|
465
|
+
case "isotropic":
|
|
466
|
+
default:
|
|
467
|
+
return DEFAULT_MEDIUM_PHASE_MODEL;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function resolveWavefrontVolumeInput(input) {
|
|
472
|
+
return input?.volume ?? input?.material?.volume ?? null;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function normalizeWavefrontThickness(input, label) {
|
|
476
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
477
|
+
return Math.max(
|
|
478
|
+
0,
|
|
479
|
+
readFiniteNumber(
|
|
480
|
+
label,
|
|
481
|
+
input?.thickness ?? volume?.thickness ?? input?.material?.thickness,
|
|
482
|
+
0
|
|
483
|
+
)
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function resolveWavefrontMediumId(input, fallbackId = 1) {
|
|
488
|
+
return (
|
|
489
|
+
input?.mediumRefId ??
|
|
490
|
+
input?.mediumId ??
|
|
491
|
+
input?.material?.mediumId ??
|
|
492
|
+
input?.materialRefId ??
|
|
493
|
+
input?.material?.id ??
|
|
494
|
+
input?.materialId ??
|
|
495
|
+
input?.id ??
|
|
496
|
+
fallbackId
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function deriveWavefrontTransportMedium(input, fallbackId = 1) {
|
|
501
|
+
const resolvedId = resolveWavefrontMediumId(input, fallbackId);
|
|
502
|
+
if (input?.medium) {
|
|
503
|
+
return normalizeWavefrontMedium(
|
|
504
|
+
{
|
|
505
|
+
...input.medium,
|
|
506
|
+
id: input.medium.id ?? input.medium.mediumId ?? resolvedId,
|
|
507
|
+
},
|
|
508
|
+
fallbackId
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
const volume = resolveWavefrontVolumeInput(input);
|
|
512
|
+
if (!volume) {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
return normalizeWavefrontMedium(
|
|
516
|
+
{
|
|
517
|
+
id: resolvedId,
|
|
518
|
+
phaseModel: volume.phaseModel,
|
|
519
|
+
density: volume.density,
|
|
520
|
+
attenuationColor: volume.attenuationColor,
|
|
521
|
+
attenuationDistance: volume.attenuationDistance,
|
|
522
|
+
absorption: volume.absorption,
|
|
523
|
+
scattering: volume.scattering,
|
|
524
|
+
},
|
|
525
|
+
fallbackId
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function normalizeWavefrontMedium(input = {}, index = 0) {
|
|
530
|
+
const id = readNonNegativeInteger("medium id", input.id ?? input.mediumId, index);
|
|
531
|
+
const density = Math.max(0, readFiniteNumber("medium density", input.density, 1));
|
|
532
|
+
const attenuationColor = asColor(
|
|
533
|
+
input.attenuationColor ?? input.color ?? input.medium?.attenuationColor,
|
|
534
|
+
[1, 1, 1, 1]
|
|
535
|
+
);
|
|
536
|
+
const attenuationDistance = readFiniteNumber(
|
|
537
|
+
"medium attenuationDistance",
|
|
538
|
+
input.attenuationDistance ?? input.distance ?? input.medium?.attenuationDistance,
|
|
539
|
+
0
|
|
540
|
+
);
|
|
541
|
+
const absorption =
|
|
542
|
+
Array.isArray(input.absorption) || Array.isArray(input.medium?.absorption)
|
|
543
|
+
? asVec3(input.absorption ?? input.medium?.absorption, [0, 0, 0]).map((value) =>
|
|
544
|
+
Math.max(0, Number(value) || 0)
|
|
545
|
+
)
|
|
546
|
+
: deriveBeerLambertAbsorptionFromAttenuationColor(
|
|
547
|
+
attenuationColor,
|
|
548
|
+
attenuationDistance,
|
|
549
|
+
density
|
|
550
|
+
);
|
|
551
|
+
const scattering = asVec3(
|
|
552
|
+
input.scattering ?? input.medium?.scattering,
|
|
553
|
+
[0, 0, 0]
|
|
554
|
+
).map((value) => Math.max(0, Number(value) || 0));
|
|
555
|
+
return Object.freeze({
|
|
556
|
+
id,
|
|
557
|
+
phaseModel: readMediumPhaseModel(input.phaseModel ?? input.medium?.phaseModel),
|
|
558
|
+
density,
|
|
559
|
+
attenuationColor: Object.freeze(attenuationColor),
|
|
560
|
+
attenuationDistance,
|
|
561
|
+
absorption: Object.freeze(absorption),
|
|
562
|
+
scattering: Object.freeze(scattering),
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function collectWavefrontMediums(options, meshes, sceneObjects = []) {
|
|
567
|
+
const mediumsById = new Map();
|
|
568
|
+
mediumsById.set(
|
|
569
|
+
0,
|
|
570
|
+
Object.freeze({
|
|
571
|
+
id: 0,
|
|
572
|
+
phaseModel: DEFAULT_MEDIUM_PHASE_MODEL,
|
|
573
|
+
density: 0,
|
|
574
|
+
attenuationColor: Object.freeze([1, 1, 1, 1]),
|
|
575
|
+
attenuationDistance: 0,
|
|
576
|
+
absorption: Object.freeze([0, 0, 0]),
|
|
577
|
+
scattering: Object.freeze([0, 0, 0]),
|
|
578
|
+
})
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
const register = (input, fallbackId = mediumsById.size) => {
|
|
582
|
+
if (!input) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const normalized = normalizeWavefrontMedium(
|
|
586
|
+
typeof input === "object" ? { id: fallbackId, ...input } : { id: fallbackId },
|
|
587
|
+
fallbackId
|
|
588
|
+
);
|
|
589
|
+
const existing = mediumsById.get(normalized.id);
|
|
590
|
+
if (existing && JSON.stringify(existing) !== JSON.stringify(normalized)) {
|
|
591
|
+
throw new Error(`Medium id ${normalized.id} is defined more than once with different values.`);
|
|
592
|
+
}
|
|
593
|
+
mediumsById.set(normalized.id, normalized);
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
for (const medium of options.mediums ?? []) {
|
|
597
|
+
register(medium);
|
|
598
|
+
}
|
|
599
|
+
for (const mesh of meshes) {
|
|
600
|
+
register(mesh.medium, mesh.mediumRefId ?? mesh.medium?.id ?? 0);
|
|
601
|
+
}
|
|
602
|
+
for (const mesh of meshes) {
|
|
603
|
+
if ((mesh.mediumRefId ?? 0) > 0 && !mediumsById.has(mesh.mediumRefId)) {
|
|
604
|
+
register({ id: mesh.mediumRefId });
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
for (const object of sceneObjects) {
|
|
608
|
+
register(object.medium, object.mediumRefId ?? object.medium?.id ?? 0);
|
|
609
|
+
}
|
|
610
|
+
for (const object of sceneObjects) {
|
|
611
|
+
if ((object.mediumRefId ?? 0) > 0 && !mediumsById.has(object.mediumRefId)) {
|
|
612
|
+
register({ id: object.mediumRefId });
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return Object.freeze(
|
|
617
|
+
Array.from(mediumsById.values()).sort((left, right) => left.id - right.id)
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
440
621
|
export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
441
622
|
const bounds = deriveBounds(input);
|
|
442
623
|
const kind = readObjectKind(input.kind ?? input.type ?? (bounds ? "box" : "sphere"));
|
|
@@ -474,6 +655,7 @@ export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
474
655
|
input.specularColor ?? input.material?.specularColor,
|
|
475
656
|
[1, 1, 1, 1]
|
|
476
657
|
).map((value, componentIndex) => (componentIndex < 3 ? clamp(value, 0, 1) : 1));
|
|
658
|
+
const medium = deriveWavefrontTransportMedium(input, index + 1);
|
|
477
659
|
const resolvedMaterialKind =
|
|
478
660
|
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
479
661
|
? MATERIAL_EMISSIVE
|
|
@@ -488,6 +670,12 @@ export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
488
670
|
kind,
|
|
489
671
|
materialKind: resolvedMaterialKind,
|
|
490
672
|
flags: readNonNegativeInteger("flags", input.flags, 0),
|
|
673
|
+
mediumRefId: readNonNegativeInteger(
|
|
674
|
+
"mediumRefId",
|
|
675
|
+
input.mediumRefId ?? medium?.id ?? input.medium?.id ?? input.mediumId,
|
|
676
|
+
0
|
|
677
|
+
),
|
|
678
|
+
medium,
|
|
491
679
|
center: Object.freeze(center),
|
|
492
680
|
halfExtent: Object.freeze(halfExtent),
|
|
493
681
|
color: Object.freeze(color),
|
|
@@ -511,6 +699,7 @@ export function normalizeWavefrontSceneObject(input = {}, index = 0) {
|
|
|
511
699
|
),
|
|
512
700
|
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
513
701
|
specularColor: Object.freeze(specularColor),
|
|
702
|
+
thickness: normalizeWavefrontThickness(input, "thickness"),
|
|
514
703
|
transmission,
|
|
515
704
|
});
|
|
516
705
|
}
|
|
@@ -620,6 +809,7 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
620
809
|
input.specularColor ?? input.material?.specularColor,
|
|
621
810
|
[1, 1, 1, 1]
|
|
622
811
|
).map((value, componentIndex) => (componentIndex < 3 ? clamp(value, 0, 1) : 1));
|
|
812
|
+
const medium = deriveWavefrontTransportMedium(input, meshIndex + 1);
|
|
623
813
|
const resolvedMaterialKind =
|
|
624
814
|
emission[0] > 0 || emission[1] > 0 || emission[2] > 0
|
|
625
815
|
? MATERIAL_EMISSIVE
|
|
@@ -644,9 +834,14 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
644
834
|
),
|
|
645
835
|
mediumRefId: readNonNegativeInteger(
|
|
646
836
|
"mesh mediumRefId",
|
|
647
|
-
input.mediumRefId ??
|
|
837
|
+
input.mediumRefId ??
|
|
838
|
+
medium?.id ??
|
|
839
|
+
input.medium?.id ??
|
|
840
|
+
input.mediumId ??
|
|
841
|
+
input.material?.mediumId,
|
|
648
842
|
0
|
|
649
843
|
),
|
|
844
|
+
medium,
|
|
650
845
|
color: Object.freeze(color),
|
|
651
846
|
emission: Object.freeze(emission),
|
|
652
847
|
roughness: clamp(readFiniteNumber("roughness", input.roughness ?? input.material?.roughness, 0.72), 0, 1),
|
|
@@ -668,6 +863,7 @@ export function normalizeWavefrontMesh(input = {}, meshIndex = 0) {
|
|
|
668
863
|
),
|
|
669
864
|
specular: clamp(readFiniteNumber("specular", input.specular ?? input.material?.specular, 1), 0, 1),
|
|
670
865
|
specularColor: Object.freeze(specularColor),
|
|
866
|
+
thickness: normalizeWavefrontThickness(input, "mesh thickness"),
|
|
671
867
|
transmission,
|
|
672
868
|
baseColorTexture: input.baseColorTexture ?? input.material?.baseColorTexture ?? null,
|
|
673
869
|
metallicRoughnessTexture:
|
|
@@ -738,104 +934,9 @@ function normalizeVectorOrFallback(vector, fallback) {
|
|
|
738
934
|
return normalize(Array.isArray(vector) ? vector : fallback, fallback);
|
|
739
935
|
}
|
|
740
936
|
|
|
741
|
-
function
|
|
742
|
-
const edge1 = subtract(v1, v0);
|
|
743
|
-
const edge2 = subtract(v2, v0);
|
|
744
|
-
const deltaUv1 = [uv1[0] - uv0[0], uv1[1] - uv0[1]];
|
|
745
|
-
const deltaUv2 = [uv2[0] - uv0[0], uv2[1] - uv0[1]];
|
|
746
|
-
const determinant = deltaUv1[0] * deltaUv2[1] - deltaUv1[1] * deltaUv2[0];
|
|
747
|
-
if (Math.abs(determinant) < 1e-6) {
|
|
748
|
-
const tangentFallback = Math.abs(fallbackNormal[1]) < 0.999 ? [0, 1, 0] : [1, 0, 0];
|
|
749
|
-
const tangent = normalize(cross(tangentFallback, fallbackNormal), [1, 0, 0]);
|
|
750
|
-
const bitangent = normalize(cross(fallbackNormal, tangent), [0, 0, 1]);
|
|
751
|
-
return { tangent, bitangent };
|
|
752
|
-
}
|
|
753
|
-
const inverse = 1 / determinant;
|
|
754
|
-
const tangent = normalize(
|
|
755
|
-
[
|
|
756
|
-
inverse * (edge1[0] * deltaUv2[1] - edge2[0] * deltaUv1[1]),
|
|
757
|
-
inverse * (edge1[1] * deltaUv2[1] - edge2[1] * deltaUv1[1]),
|
|
758
|
-
inverse * (edge1[2] * deltaUv2[1] - edge2[2] * deltaUv1[1]),
|
|
759
|
-
],
|
|
760
|
-
[1, 0, 0]
|
|
761
|
-
);
|
|
762
|
-
const bitangent = normalize(
|
|
763
|
-
[
|
|
764
|
-
inverse * (-edge1[0] * deltaUv2[0] + edge2[0] * deltaUv1[0]),
|
|
765
|
-
inverse * (-edge1[1] * deltaUv2[0] + edge2[1] * deltaUv1[0]),
|
|
766
|
-
inverse * (-edge1[2] * deltaUv2[0] + edge2[2] * deltaUv1[0]),
|
|
767
|
-
],
|
|
768
|
-
[0, 0, 1]
|
|
769
|
-
);
|
|
770
|
-
return { tangent, bitangent };
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
function applyNormalMap(normal, tangent, bitangent, normalTexture, uv) {
|
|
774
|
-
if (!normalTexture) {
|
|
775
|
-
return normalizeVectorOrFallback(normal, [0, 1, 0]);
|
|
776
|
-
}
|
|
777
|
-
const sample = sampleTextureRgba(normalTexture, uv, "linear");
|
|
778
|
-
const strength = clampUnit(normalTexture.scale ?? 1);
|
|
779
|
-
const tangentNormal = normalize(
|
|
780
|
-
[
|
|
781
|
-
(sample[0] * 2 - 1) * strength,
|
|
782
|
-
(sample[1] * 2 - 1) * strength,
|
|
783
|
-
1 + (sample[2] * 2 - 1 - 1) * strength,
|
|
784
|
-
],
|
|
785
|
-
[0, 0, 1]
|
|
786
|
-
);
|
|
787
|
-
return normalize(
|
|
788
|
-
[
|
|
789
|
-
tangent[0] * tangentNormal[0] + bitangent[0] * tangentNormal[1] + normal[0] * tangentNormal[2],
|
|
790
|
-
tangent[1] * tangentNormal[0] + bitangent[1] * tangentNormal[1] + normal[1] * tangentNormal[2],
|
|
791
|
-
tangent[2] * tangentNormal[0] + bitangent[2] * tangentNormal[1] + normal[2] * tangentNormal[2],
|
|
792
|
-
],
|
|
793
|
-
normal
|
|
794
|
-
);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
function sampleBaseColor(mesh, uv) {
|
|
798
|
-
const sample = mesh.baseColorTexture ? sampleTextureRgba(mesh.baseColorTexture, uv, "srgb") : [1, 1, 1, 1];
|
|
799
|
-
return [
|
|
800
|
-
clampUnit(mesh.color[0] * sample[0]),
|
|
801
|
-
clampUnit(mesh.color[1] * sample[1]),
|
|
802
|
-
clampUnit(mesh.color[2] * sample[2]),
|
|
803
|
-
clampUnit((mesh.color[3] ?? 1) * sample[3]),
|
|
804
|
-
];
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
function sampleSurfaceMaterial(mesh, uv) {
|
|
808
|
-
const textureSample = mesh.metallicRoughnessTexture
|
|
809
|
-
? sampleTextureRgba(mesh.metallicRoughnessTexture, uv, "linear")
|
|
810
|
-
: [1, 1, 1, 1];
|
|
811
|
-
return {
|
|
812
|
-
roughness: clamp(mesh.roughness * textureSample[1], 0, 1),
|
|
813
|
-
metallic: clamp(mesh.metallic * textureSample[2], 0, 1),
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
function averageColors(colors) {
|
|
818
|
-
const count = Math.max(colors.length, 1);
|
|
819
|
-
return colors.reduce(
|
|
820
|
-
(accumulator, color) => [
|
|
821
|
-
accumulator[0] + color[0] / count,
|
|
822
|
-
accumulator[1] + color[1] / count,
|
|
823
|
-
accumulator[2] + color[2] / count,
|
|
824
|
-
accumulator[3] + color[3] / count,
|
|
825
|
-
],
|
|
826
|
-
[0, 0, 0, 0]
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
function averageNumbers(values, fallback = 0) {
|
|
831
|
-
if (!Array.isArray(values) || values.length === 0) {
|
|
832
|
-
return fallback;
|
|
833
|
-
}
|
|
834
|
-
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
function createMeshTriangleRecords(meshes) {
|
|
937
|
+
function createMeshTriangleRecords(meshes, gpuMaterialSource = null) {
|
|
838
938
|
const source = Array.isArray(meshes) ? meshes : [];
|
|
939
|
+
const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
|
|
839
940
|
let nextTriangleId = 0;
|
|
840
941
|
return source.flatMap((meshInput, meshIndex) => {
|
|
841
942
|
const mesh = normalizeWavefrontMesh(meshInput, meshIndex);
|
|
@@ -854,16 +955,6 @@ function createMeshTriangleRecords(meshes) {
|
|
|
854
955
|
const uv0 = mesh.uvs ? readVector2(mesh.uvs, a) : [0, 0];
|
|
855
956
|
const uv1 = mesh.uvs ? readVector2(mesh.uvs, b) : [0, 0];
|
|
856
957
|
const uv2 = mesh.uvs ? readVector2(mesh.uvs, c) : [0, 0];
|
|
857
|
-
const tangentBasis = buildTriangleTangentBasis(v0, v1, v2, uv0, uv1, uv2, faceNormal);
|
|
858
|
-
const shadedN0 = applyNormalMap(n0, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv0);
|
|
859
|
-
const shadedN1 = applyNormalMap(n1, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv1);
|
|
860
|
-
const shadedN2 = applyNormalMap(n2, tangentBasis.tangent, tangentBasis.bitangent, mesh.normalTexture, uv2);
|
|
861
|
-
const sampledColors = [sampleBaseColor(mesh, uv0), sampleBaseColor(mesh, uv1), sampleBaseColor(mesh, uv2)];
|
|
862
|
-
const sampledMaterials = [
|
|
863
|
-
sampleSurfaceMaterial(mesh, uv0),
|
|
864
|
-
sampleSurfaceMaterial(mesh, uv1),
|
|
865
|
-
sampleSurfaceMaterial(mesh, uv2),
|
|
866
|
-
];
|
|
867
958
|
const bounds = triangleBounds(v0, v1, v2);
|
|
868
959
|
|
|
869
960
|
triangles.push(
|
|
@@ -878,17 +969,17 @@ function createMeshTriangleRecords(meshes) {
|
|
|
878
969
|
v0: Object.freeze(v0),
|
|
879
970
|
v1: Object.freeze(v1),
|
|
880
971
|
v2: Object.freeze(v2),
|
|
881
|
-
n0: Object.freeze(
|
|
882
|
-
n1: Object.freeze(
|
|
883
|
-
n2: Object.freeze(
|
|
972
|
+
n0: Object.freeze(n0),
|
|
973
|
+
n1: Object.freeze(n1),
|
|
974
|
+
n2: Object.freeze(n2),
|
|
884
975
|
uv0: Object.freeze(uv0),
|
|
885
976
|
uv1: Object.freeze(uv1),
|
|
886
977
|
uv2: Object.freeze(uv2),
|
|
887
|
-
color:
|
|
978
|
+
color: mesh.color,
|
|
888
979
|
emission: mesh.emission,
|
|
889
980
|
material: Object.freeze([
|
|
890
|
-
|
|
891
|
-
|
|
981
|
+
mesh.roughness,
|
|
982
|
+
mesh.metallic,
|
|
892
983
|
mesh.opacity,
|
|
893
984
|
mesh.ior,
|
|
894
985
|
]),
|
|
@@ -902,7 +993,7 @@ function createMeshTriangleRecords(meshes) {
|
|
|
902
993
|
mesh.clearcoatRoughness,
|
|
903
994
|
mesh.specular,
|
|
904
995
|
mesh.transmission,
|
|
905
|
-
|
|
996
|
+
mesh.thickness,
|
|
906
997
|
]),
|
|
907
998
|
specularColor: Object.freeze([
|
|
908
999
|
mesh.specularColor[0] ?? 1,
|
|
@@ -910,6 +1001,27 @@ function createMeshTriangleRecords(meshes) {
|
|
|
910
1001
|
mesh.specularColor[2] ?? 1,
|
|
911
1002
|
1,
|
|
912
1003
|
]),
|
|
1004
|
+
baseColorAtlas: Object.freeze(
|
|
1005
|
+
resolvedMaterialSource.baseColorAtlas.resolveRect(mesh.baseColorTexture)
|
|
1006
|
+
),
|
|
1007
|
+
metallicRoughnessAtlas: Object.freeze(
|
|
1008
|
+
resolvedMaterialSource.metallicRoughnessAtlas.resolveRect(mesh.metallicRoughnessTexture)
|
|
1009
|
+
),
|
|
1010
|
+
normalAtlas: Object.freeze(
|
|
1011
|
+
resolvedMaterialSource.normalAtlas.resolveRect(mesh.normalTexture)
|
|
1012
|
+
),
|
|
1013
|
+
occlusionAtlas: Object.freeze(
|
|
1014
|
+
resolvedMaterialSource.occlusionAtlas.resolveRect(mesh.occlusionTexture)
|
|
1015
|
+
),
|
|
1016
|
+
emissiveAtlas: Object.freeze(
|
|
1017
|
+
resolvedMaterialSource.emissiveAtlas.resolveRect(mesh.emissiveTexture)
|
|
1018
|
+
),
|
|
1019
|
+
textureSettings: Object.freeze([
|
|
1020
|
+
clampUnit(mesh.normalTexture?.scale ?? mesh.normalTexture?.strength ?? 1),
|
|
1021
|
+
clampUnit(mesh.occlusionTexture?.strength ?? 1),
|
|
1022
|
+
clampUnit(mesh.emissiveTexture?.strength ?? 1),
|
|
1023
|
+
0,
|
|
1024
|
+
]),
|
|
913
1025
|
bounds: Object.freeze({
|
|
914
1026
|
min: Object.freeze(bounds.min),
|
|
915
1027
|
max: Object.freeze(bounds.max),
|
|
@@ -992,9 +1104,10 @@ function buildBvh(triangles, maxLeafTriangles = 4) {
|
|
|
992
1104
|
});
|
|
993
1105
|
}
|
|
994
1106
|
|
|
995
|
-
export function createWavefrontMeshAcceleration(meshes = []) {
|
|
1107
|
+
export function createWavefrontMeshAcceleration(meshes = [], gpuMaterialSource = null) {
|
|
996
1108
|
const source = Array.isArray(meshes) ? meshes : [meshes];
|
|
997
|
-
const
|
|
1109
|
+
const resolvedMaterialSource = gpuMaterialSource ?? createWavefrontGpuMaterialSource(source);
|
|
1110
|
+
const triangles = createMeshTriangleRecords(source, resolvedMaterialSource);
|
|
998
1111
|
return buildBvh(triangles);
|
|
999
1112
|
}
|
|
1000
1113
|
|
|
@@ -1231,7 +1344,7 @@ export function createWavefrontGpuMaterialSource(meshes = []) {
|
|
|
1231
1344
|
mesh.clearcoatRoughness,
|
|
1232
1345
|
mesh.specular,
|
|
1233
1346
|
mesh.transmission,
|
|
1234
|
-
|
|
1347
|
+
mesh.thickness,
|
|
1235
1348
|
]);
|
|
1236
1349
|
writeVec4(floatView, byteOffset + 80, [
|
|
1237
1350
|
mesh.specularColor[0] ?? 1,
|
|
@@ -1329,12 +1442,13 @@ export function createWavefrontBvhBuildLevels(triangleCountInput) {
|
|
|
1329
1442
|
}
|
|
1330
1443
|
|
|
1331
1444
|
function resolveAccelerationBuildMode(options = {}) {
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1445
|
+
const requestedMode =
|
|
1446
|
+
options.accelerationBuildMode ?? (options.displayQuality === true ? "cpu-upload" : "cpu-debug");
|
|
1447
|
+
const mode = requestedMode === "cpu-debug" ? "cpu-upload" : requestedMode;
|
|
1448
|
+
if (mode !== "gpu" && mode !== "cpu-upload") {
|
|
1449
|
+
throw new Error(
|
|
1450
|
+
"accelerationBuildMode must be either \"gpu\", \"cpu-upload\", or the legacy alias \"cpu-debug\"."
|
|
1451
|
+
);
|
|
1338
1452
|
}
|
|
1339
1453
|
return mode;
|
|
1340
1454
|
}
|
|
@@ -1419,7 +1533,7 @@ export function createWavefrontGpuMeshSource(meshes = [], gpuMaterialSourceInput
|
|
|
1419
1533
|
mesh.clearcoatRoughness,
|
|
1420
1534
|
mesh.specular,
|
|
1421
1535
|
mesh.transmission,
|
|
1422
|
-
|
|
1536
|
+
mesh.thickness,
|
|
1423
1537
|
]);
|
|
1424
1538
|
writeVec4(meshFloats, floatOffset * 4 + 128, [
|
|
1425
1539
|
mesh.specularColor[0] ?? 1,
|
|
@@ -1534,12 +1648,17 @@ function normalizeSceneObjects(sceneObjects, useDefaultScene = true) {
|
|
|
1534
1648
|
return source.map((object, index) => normalizeWavefrontSceneObject(object, index));
|
|
1535
1649
|
}
|
|
1536
1650
|
|
|
1651
|
+
function normalizeWavefrontMeshes(meshes) {
|
|
1652
|
+
const source = Array.isArray(meshes) ? meshes : [];
|
|
1653
|
+
return source.map((mesh, index) => normalizeWavefrontMesh(mesh, index));
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1537
1656
|
function normalizeMeshes(options = {}) {
|
|
1538
1657
|
if (Array.isArray(options.meshes)) {
|
|
1539
|
-
return options.meshes;
|
|
1658
|
+
return normalizeWavefrontMeshes(options.meshes);
|
|
1540
1659
|
}
|
|
1541
1660
|
if (options.mesh) {
|
|
1542
|
-
return [options.mesh];
|
|
1661
|
+
return normalizeWavefrontMeshes([options.mesh]);
|
|
1543
1662
|
}
|
|
1544
1663
|
return [];
|
|
1545
1664
|
}
|
|
@@ -1881,8 +2000,8 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1881
2000
|
? createWavefrontGpuMeshSource(meshes, gpuMaterialSource)
|
|
1882
2001
|
: createWavefrontGpuMeshSource([]);
|
|
1883
2002
|
const meshAcceleration =
|
|
1884
|
-
accelerationBuildMode === "cpu-
|
|
1885
|
-
? createWavefrontMeshAcceleration(meshes)
|
|
2003
|
+
accelerationBuildMode === "cpu-upload"
|
|
2004
|
+
? createWavefrontMeshAcceleration(meshes, gpuMaterialSource)
|
|
1886
2005
|
: Object.freeze({ nodes: Object.freeze([]), triangles: Object.freeze([]) });
|
|
1887
2006
|
const emissiveTriangleIndices = createWavefrontEmissiveTriangleIndexSource(
|
|
1888
2007
|
meshes,
|
|
@@ -1899,6 +2018,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1899
2018
|
const sceneObjects = Object.freeze(
|
|
1900
2019
|
normalizeSceneObjects(options.sceneObjects, meshes.length === 0)
|
|
1901
2020
|
);
|
|
2021
|
+
const mediums = collectWavefrontMediums(options, meshes, sceneObjects);
|
|
1902
2022
|
const sceneObjectCapacity = Math.max(
|
|
1903
2023
|
sceneObjects.length,
|
|
1904
2024
|
readPositiveInteger("sceneObjectCapacity", options.sceneObjectCapacity, DEFAULT_SCENE_OBJECT_CAPACITY)
|
|
@@ -1971,6 +2091,8 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1971
2091
|
sceneObjects,
|
|
1972
2092
|
sceneObjectCount: sceneObjects.length,
|
|
1973
2093
|
sceneObjectCapacity,
|
|
2094
|
+
mediums,
|
|
2095
|
+
mediumCount: mediums.length,
|
|
1974
2096
|
accelerationBuildMode,
|
|
1975
2097
|
gpuAccelerationBuildRequired: accelerationBuildMode === "gpu" && triangleCount > 0,
|
|
1976
2098
|
gpuMeshSource,
|
|
@@ -2089,29 +2211,30 @@ export function packWavefrontSceneObjects(sceneObjects, capacity = sceneObjects.
|
|
|
2089
2211
|
uintView[u32 + 1] = object.id;
|
|
2090
2212
|
uintView[u32 + 2] = object.materialKind;
|
|
2091
2213
|
uintView[u32 + 3] = object.flags;
|
|
2092
|
-
|
|
2093
|
-
writeVec4(floatView, byteOffset + 32, [...object.
|
|
2094
|
-
writeVec4(floatView, byteOffset + 48, object.
|
|
2095
|
-
writeVec4(floatView, byteOffset + 64, object.
|
|
2096
|
-
writeVec4(floatView, byteOffset + 80,
|
|
2214
|
+
uintView[u32 + 4] = object.mediumRefId;
|
|
2215
|
+
writeVec4(floatView, byteOffset + 32, [...object.center, 0]);
|
|
2216
|
+
writeVec4(floatView, byteOffset + 48, [...object.halfExtent, 0]);
|
|
2217
|
+
writeVec4(floatView, byteOffset + 64, object.color);
|
|
2218
|
+
writeVec4(floatView, byteOffset + 80, object.emission);
|
|
2219
|
+
writeVec4(floatView, byteOffset + 96, [
|
|
2097
2220
|
object.roughness,
|
|
2098
2221
|
object.metallic,
|
|
2099
2222
|
object.opacity,
|
|
2100
2223
|
object.ior,
|
|
2101
2224
|
]);
|
|
2102
|
-
writeVec4(floatView, byteOffset +
|
|
2225
|
+
writeVec4(floatView, byteOffset + 112, [
|
|
2103
2226
|
object.sheenColor[0] ?? 0,
|
|
2104
2227
|
object.sheenColor[1] ?? 0,
|
|
2105
2228
|
object.sheenColor[2] ?? 0,
|
|
2106
2229
|
object.clearcoat,
|
|
2107
2230
|
]);
|
|
2108
|
-
writeVec4(floatView, byteOffset +
|
|
2231
|
+
writeVec4(floatView, byteOffset + 128, [
|
|
2109
2232
|
object.clearcoatRoughness,
|
|
2110
2233
|
object.specular,
|
|
2111
2234
|
object.transmission,
|
|
2112
|
-
|
|
2235
|
+
object.thickness,
|
|
2113
2236
|
]);
|
|
2114
|
-
writeVec4(floatView, byteOffset +
|
|
2237
|
+
writeVec4(floatView, byteOffset + 144, [
|
|
2115
2238
|
object.specularColor[0] ?? 1,
|
|
2116
2239
|
object.specularColor[1] ?? 1,
|
|
2117
2240
|
object.specularColor[2] ?? 1,
|
|
@@ -2710,7 +2833,10 @@ function integrateBrdfSample(nDotV, roughness, sampleCount) {
|
|
|
2710
2833
|
return [scaleTerm / sampleCount, biasTerm / sampleCount];
|
|
2711
2834
|
}
|
|
2712
2835
|
|
|
2713
|
-
function createBrdfLutUploadBytes(
|
|
2836
|
+
function createBrdfLutUploadBytes(
|
|
2837
|
+
size = DEFAULT_BRDF_LUT_SIZE,
|
|
2838
|
+
sampleCount = DEFAULT_BRDF_LUT_SAMPLE_COUNT
|
|
2839
|
+
) {
|
|
2714
2840
|
const cacheKey = `${Math.max(1, Math.trunc(size))}:${Math.max(1, Math.trunc(sampleCount))}`;
|
|
2715
2841
|
const cached = BRDF_LUT_UPLOAD_CACHE.get(cacheKey);
|
|
2716
2842
|
if (cached) {
|
|
@@ -3137,6 +3263,85 @@ function createBrdfLutResource(device, constants, size = DEFAULT_BRDF_LUT_SIZE)
|
|
|
3137
3263
|
});
|
|
3138
3264
|
}
|
|
3139
3265
|
|
|
3266
|
+
function createMediumTextureResource(device, constants, mediums) {
|
|
3267
|
+
const normalized = Array.isArray(mediums) && mediums.length > 0 ? mediums : [{ id: 0 }];
|
|
3268
|
+
const width = Math.max(
|
|
3269
|
+
1,
|
|
3270
|
+
normalized.reduce((maximum, medium) => Math.max(maximum, medium.id ?? 0), 0) + 1
|
|
3271
|
+
);
|
|
3272
|
+
const level = {
|
|
3273
|
+
width,
|
|
3274
|
+
height: MEDIUM_TABLE_ROWS,
|
|
3275
|
+
data: new Float32Array(width * MEDIUM_TABLE_ROWS * 4),
|
|
3276
|
+
};
|
|
3277
|
+
|
|
3278
|
+
for (const medium of normalized) {
|
|
3279
|
+
const mediumId = Math.max(0, Math.trunc(Number(medium.id) || 0));
|
|
3280
|
+
const absorptionOffset = mediumId * 4;
|
|
3281
|
+
level.data[absorptionOffset] = Math.max(0, medium.absorption?.[0] ?? 0);
|
|
3282
|
+
level.data[absorptionOffset + 1] = Math.max(0, medium.absorption?.[1] ?? 0);
|
|
3283
|
+
level.data[absorptionOffset + 2] = Math.max(0, medium.absorption?.[2] ?? 0);
|
|
3284
|
+
level.data[absorptionOffset + 3] = Math.max(0, medium.phaseModel ?? 0);
|
|
3285
|
+
|
|
3286
|
+
const scatteringOffset = (width + mediumId) * 4;
|
|
3287
|
+
level.data[scatteringOffset] = Math.max(0, medium.scattering?.[0] ?? 0);
|
|
3288
|
+
level.data[scatteringOffset + 1] = Math.max(0, medium.scattering?.[1] ?? 0);
|
|
3289
|
+
level.data[scatteringOffset + 2] = Math.max(0, medium.scattering?.[2] ?? 0);
|
|
3290
|
+
level.data[scatteringOffset + 3] = Math.max(0, medium.density ?? 0);
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
const upload = createFloat16RgbaUploadFromLevels([level])[0];
|
|
3294
|
+
const texture = device.createTexture({
|
|
3295
|
+
label: "plasius.wavefront.mediumTable",
|
|
3296
|
+
size: { width, height: MEDIUM_TABLE_ROWS },
|
|
3297
|
+
format: "rgba16float",
|
|
3298
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST,
|
|
3299
|
+
});
|
|
3300
|
+
device.queue.writeTexture(
|
|
3301
|
+
{ texture },
|
|
3302
|
+
upload.bytes,
|
|
3303
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
3304
|
+
{ width, height: MEDIUM_TABLE_ROWS, depthOrArrayLayers: 1 }
|
|
3305
|
+
);
|
|
3306
|
+
return Object.freeze({
|
|
3307
|
+
texture,
|
|
3308
|
+
view: texture.createView(),
|
|
3309
|
+
ownsTexture: true,
|
|
3310
|
+
count: normalized.length,
|
|
3311
|
+
width,
|
|
3312
|
+
});
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
function mediumTablesEqual(left, right) {
|
|
3316
|
+
const leftMediums = Array.isArray(left) ? left : [];
|
|
3317
|
+
const rightMediums = Array.isArray(right) ? right : [];
|
|
3318
|
+
if (leftMediums.length !== rightMediums.length) {
|
|
3319
|
+
return false;
|
|
3320
|
+
}
|
|
3321
|
+
for (let index = 0; index < leftMediums.length; index += 1) {
|
|
3322
|
+
const leftMedium = leftMediums[index];
|
|
3323
|
+
const rightMedium = rightMediums[index];
|
|
3324
|
+
if ((leftMedium?.id ?? 0) !== (rightMedium?.id ?? 0)) {
|
|
3325
|
+
return false;
|
|
3326
|
+
}
|
|
3327
|
+
if ((leftMedium?.phaseModel ?? 0) !== (rightMedium?.phaseModel ?? 0)) {
|
|
3328
|
+
return false;
|
|
3329
|
+
}
|
|
3330
|
+
if ((leftMedium?.density ?? 0) !== (rightMedium?.density ?? 0)) {
|
|
3331
|
+
return false;
|
|
3332
|
+
}
|
|
3333
|
+
for (let component = 0; component < 3; component += 1) {
|
|
3334
|
+
if ((leftMedium?.absorption?.[component] ?? 0) !== (rightMedium?.absorption?.[component] ?? 0)) {
|
|
3335
|
+
return false;
|
|
3336
|
+
}
|
|
3337
|
+
if ((leftMedium?.scattering?.[component] ?? 0) !== (rightMedium?.scattering?.[component] ?? 0)) {
|
|
3338
|
+
return false;
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
return true;
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3140
3345
|
function createAtlasTextureResource(device, constants, atlas, label) {
|
|
3141
3346
|
const upload = createRgba8TextureUpload(atlas);
|
|
3142
3347
|
const texture = device.createTexture({
|
|
@@ -3283,6 +3488,10 @@ struct SceneObject {
|
|
|
3283
3488
|
objectId: u32,
|
|
3284
3489
|
materialKind: u32,
|
|
3285
3490
|
flags: u32,
|
|
3491
|
+
mediumRefId: u32,
|
|
3492
|
+
pad0: u32,
|
|
3493
|
+
pad1: u32,
|
|
3494
|
+
pad2: u32,
|
|
3286
3495
|
center: vec4<f32>,
|
|
3287
3496
|
halfExtent: vec4<f32>,
|
|
3288
3497
|
color: vec4<f32>,
|
|
@@ -3343,9 +3552,9 @@ struct BvhLeafRef {
|
|
|
3343
3552
|
struct ScatterResult {
|
|
3344
3553
|
direction: vec4<f32>,
|
|
3345
3554
|
pdf: f32,
|
|
3555
|
+
mediumRefId: u32,
|
|
3346
3556
|
flags: u32,
|
|
3347
3557
|
pad0: u32,
|
|
3348
|
-
pad1: u32,
|
|
3349
3558
|
};
|
|
3350
3559
|
|
|
3351
3560
|
struct MeshVertex {
|
|
@@ -3491,6 +3700,7 @@ struct EnvironmentPortal {
|
|
|
3491
3700
|
@group(0) @binding(29) var brdfLutTexture: texture_2d<f32>;
|
|
3492
3701
|
@group(0) @binding(30) var brdfLutSampler: sampler;
|
|
3493
3702
|
@group(0) @binding(31) var environmentSamplingTexture: texture_2d<f32>;
|
|
3703
|
+
@group(0) @binding(32) var mediumTableTexture: texture_2d<f32>;
|
|
3494
3704
|
|
|
3495
3705
|
fn hash_u32(value: u32) -> u32 {
|
|
3496
3706
|
var x = value;
|
|
@@ -4156,6 +4366,60 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
4156
4366
|
return environment_radiance(origin, direction);
|
|
4157
4367
|
}
|
|
4158
4368
|
|
|
4369
|
+
fn medium_dimensions() -> vec2<u32> {
|
|
4370
|
+
return textureDimensions(mediumTableTexture);
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
fn medium_valid(mediumRefId: u32) -> bool {
|
|
4374
|
+
let dimensions = medium_dimensions();
|
|
4375
|
+
return mediumRefId > 0u && mediumRefId < dimensions.x;
|
|
4376
|
+
}
|
|
4377
|
+
|
|
4378
|
+
fn medium_absorption(mediumRefId: u32) -> vec3<f32> {
|
|
4379
|
+
if (!medium_valid(mediumRefId)) {
|
|
4380
|
+
return vec3<f32>(0.0);
|
|
4381
|
+
}
|
|
4382
|
+
return max(
|
|
4383
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 0), 0).xyz,
|
|
4384
|
+
vec3<f32>(0.0)
|
|
4385
|
+
);
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
fn medium_scattering(mediumRefId: u32) -> vec3<f32> {
|
|
4389
|
+
if (!medium_valid(mediumRefId)) {
|
|
4390
|
+
return vec3<f32>(0.0);
|
|
4391
|
+
}
|
|
4392
|
+
return max(
|
|
4393
|
+
textureLoad(mediumTableTexture, vec2<i32>(i32(mediumRefId), 1), 0).xyz,
|
|
4394
|
+
vec3<f32>(0.0)
|
|
4395
|
+
);
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
fn medium_transmittance(mediumRefId: u32, distance: f32) -> vec3<f32> {
|
|
4399
|
+
if (!medium_valid(mediumRefId) || distance <= 0.000001) {
|
|
4400
|
+
return vec3<f32>(1.0);
|
|
4401
|
+
}
|
|
4402
|
+
let extinction = medium_absorption(mediumRefId) + medium_scattering(mediumRefId);
|
|
4403
|
+
return vec3<f32>(
|
|
4404
|
+
exp(-extinction.x * distance),
|
|
4405
|
+
exp(-extinction.y * distance),
|
|
4406
|
+
exp(-extinction.z * distance)
|
|
4407
|
+
);
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4410
|
+
fn transmitted_medium_ref_id(ray: RayRecord, hit: HitRecord) -> u32 {
|
|
4411
|
+
if (hit.mediumRefId == 0u) {
|
|
4412
|
+
return ray.mediumRefId;
|
|
4413
|
+
}
|
|
4414
|
+
if (hit.frontFace == 1u) {
|
|
4415
|
+
return hit.mediumRefId;
|
|
4416
|
+
}
|
|
4417
|
+
if (ray.mediumRefId == hit.mediumRefId) {
|
|
4418
|
+
return 0u;
|
|
4419
|
+
}
|
|
4420
|
+
return ray.mediumRefId;
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4159
4423
|
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
4160
4424
|
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
4161
4425
|
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
@@ -4254,11 +4518,15 @@ fn terminal_surface_environment_source(ray: RayRecord, hit: HitRecord) -> vec3<f
|
|
|
4254
4518
|
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
4255
4519
|
}
|
|
4256
4520
|
|
|
4257
|
-
fn terminal_surface_environment_contribution(
|
|
4521
|
+
fn terminal_surface_environment_contribution(
|
|
4522
|
+
ray: RayRecord,
|
|
4523
|
+
throughput: vec3<f32>,
|
|
4524
|
+
hit: HitRecord
|
|
4525
|
+
) -> vec3<f32> {
|
|
4258
4526
|
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
4259
4527
|
let occlusion = mix(0.75, 1.0, clamp(hit.occlusion, 0.0, 1.0));
|
|
4260
4528
|
return clamp_sample_radiance(
|
|
4261
|
-
|
|
4529
|
+
throughput *
|
|
4262
4530
|
surfaceColor *
|
|
4263
4531
|
terminal_surface_environment_source(ray, hit) *
|
|
4264
4532
|
occlusion
|
|
@@ -4732,7 +5000,7 @@ fn intersect_sphere(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
4732
5000
|
0xffffffffu,
|
|
4733
5001
|
object.objectId,
|
|
4734
5002
|
object.objectId,
|
|
4735
|
-
|
|
5003
|
+
object.mediumRefId
|
|
4736
5004
|
);
|
|
4737
5005
|
}
|
|
4738
5006
|
|
|
@@ -4784,7 +5052,7 @@ fn intersect_box(ray: RayRecord, object: SceneObject) -> Candidate {
|
|
|
4784
5052
|
0xffffffffu,
|
|
4785
5053
|
object.objectId,
|
|
4786
5054
|
object.objectId,
|
|
4787
|
-
|
|
5055
|
+
object.mediumRefId
|
|
4788
5056
|
);
|
|
4789
5057
|
}
|
|
4790
5058
|
|
|
@@ -5035,6 +5303,10 @@ fn intersectActiveQueue(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5035
5303
|
let ray = activeQueue[index];
|
|
5036
5304
|
var nearest = 1000000.0;
|
|
5037
5305
|
var hitObject = SceneObject(
|
|
5306
|
+
0u,
|
|
5307
|
+
0u,
|
|
5308
|
+
0u,
|
|
5309
|
+
0u,
|
|
5038
5310
|
0u,
|
|
5039
5311
|
0u,
|
|
5040
5312
|
0u,
|
|
@@ -5238,9 +5510,9 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5238
5510
|
return ScatterResult(
|
|
5239
5511
|
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5240
5512
|
1.0,
|
|
5513
|
+
ray.mediumRefId,
|
|
5241
5514
|
RAY_FLAG_DELTA_SAMPLE,
|
|
5242
5515
|
0u,
|
|
5243
|
-
0u
|
|
5244
5516
|
);
|
|
5245
5517
|
}
|
|
5246
5518
|
|
|
@@ -5260,17 +5532,17 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5260
5532
|
return ScatterResult(
|
|
5261
5533
|
vec4<f32>(reflect(ray.direction.xyz, normal), 0.0),
|
|
5262
5534
|
1.0,
|
|
5535
|
+
ray.mediumRefId,
|
|
5263
5536
|
RAY_FLAG_DELTA_SAMPLE,
|
|
5264
5537
|
0u,
|
|
5265
|
-
0u
|
|
5266
5538
|
);
|
|
5267
5539
|
}
|
|
5268
5540
|
return ScatterResult(
|
|
5269
5541
|
vec4<f32>(refract_direction(ray.direction.xyz, normal, etaRatio), 0.0),
|
|
5270
5542
|
1.0,
|
|
5543
|
+
transmitted_medium_ref_id(ray, hit),
|
|
5271
5544
|
RAY_FLAG_DELTA_SAMPLE,
|
|
5272
5545
|
0u,
|
|
5273
|
-
0u
|
|
5274
5546
|
);
|
|
5275
5547
|
}
|
|
5276
5548
|
|
|
@@ -5285,9 +5557,9 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5285
5557
|
return ScatterResult(
|
|
5286
5558
|
vec4<f32>(guidedDirection, 0.0),
|
|
5287
5559
|
guidedPdf,
|
|
5560
|
+
ray.mediumRefId,
|
|
5288
5561
|
RAY_FLAG_GUIDED_EMISSIVE,
|
|
5289
5562
|
0u,
|
|
5290
|
-
0u
|
|
5291
5563
|
);
|
|
5292
5564
|
}
|
|
5293
5565
|
}
|
|
@@ -5295,7 +5567,7 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5295
5567
|
let guidedDirection = sample_environment_portal_direction(hit, seed + 131u, normal);
|
|
5296
5568
|
if (dot(normal, guidedDirection) > 0.000001) {
|
|
5297
5569
|
let guidedPdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, guidedDirection), 0.000001);
|
|
5298
|
-
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf,
|
|
5570
|
+
return ScatterResult(vec4<f32>(guidedDirection, 0.0), guidedPdf, ray.mediumRefId, 0u, 0u);
|
|
5299
5571
|
}
|
|
5300
5572
|
}
|
|
5301
5573
|
|
|
@@ -5329,7 +5601,7 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
5329
5601
|
);
|
|
5330
5602
|
}
|
|
5331
5603
|
let pdf = max(evaluate_surface_bsdf_pdf(hit, viewDirection, lightDirection), 0.000001);
|
|
5332
|
-
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf,
|
|
5604
|
+
return ScatterResult(vec4<f32>(lightDirection, 0.0), pdf, ray.mediumRefId, 0u, 0u);
|
|
5333
5605
|
}
|
|
5334
5606
|
|
|
5335
5607
|
@compute @workgroup_size(64)
|
|
@@ -5342,15 +5614,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5342
5614
|
|
|
5343
5615
|
let ray = activeQueue[index];
|
|
5344
5616
|
let hit = hits[index];
|
|
5617
|
+
let segmentTransmittance = medium_transmittance(ray.mediumRefId, hit.distance);
|
|
5618
|
+
let arrivingThroughput = ray.throughput.xyz * segmentTransmittance;
|
|
5345
5619
|
var contribution = vec3<f32>(0.0);
|
|
5346
5620
|
|
|
5347
5621
|
if (hit.hitType == 1u) {
|
|
5348
5622
|
let guidedLightWeight = select(1.0, 0.24, (ray.flags & RAY_FLAG_GUIDED_EMISSIVE) != 0u);
|
|
5349
5623
|
let sourceRadiance = max(hit.emission.xyz, hit.color.xyz) * guidedLightWeight;
|
|
5350
5624
|
if (deferred_path_resolve_enabled()) {
|
|
5351
|
-
record_deferred_terminal_source(ray, sourceRadiance);
|
|
5625
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
5352
5626
|
} else {
|
|
5353
|
-
contribution = clamp_sample_radiance(
|
|
5627
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
5354
5628
|
accumulation[ray.rayId] =
|
|
5355
5629
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
5356
5630
|
}
|
|
@@ -5367,9 +5641,9 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5367
5641
|
sourceRadiance = sourceRadiance * misWeight;
|
|
5368
5642
|
}
|
|
5369
5643
|
if (deferred_path_resolve_enabled()) {
|
|
5370
|
-
record_deferred_terminal_source(ray, sourceRadiance);
|
|
5644
|
+
record_deferred_terminal_source(ray, sourceRadiance * segmentTransmittance);
|
|
5371
5645
|
} else {
|
|
5372
|
-
contribution = clamp_sample_radiance(
|
|
5646
|
+
contribution = clamp_sample_radiance(arrivingThroughput * sourceRadiance);
|
|
5373
5647
|
accumulation[ray.rayId] =
|
|
5374
5648
|
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
5375
5649
|
}
|
|
@@ -5377,7 +5651,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5377
5651
|
return;
|
|
5378
5652
|
}
|
|
5379
5653
|
|
|
5380
|
-
let response = stabilize_surface_path_response(
|
|
5654
|
+
let response = stabilize_surface_path_response(
|
|
5655
|
+
ray,
|
|
5656
|
+
hit,
|
|
5657
|
+
surface_path_response(hit) * segmentTransmittance
|
|
5658
|
+
);
|
|
5381
5659
|
record_deferred_path_response(ray, response);
|
|
5382
5660
|
|
|
5383
5661
|
let shouldEstimateDirectEnvironment =
|
|
@@ -5385,7 +5663,22 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5385
5663
|
hit.material.z >= 0.95 &&
|
|
5386
5664
|
ray.bounce < 2u;
|
|
5387
5665
|
if (shouldEstimateDirectEnvironment) {
|
|
5388
|
-
let directEnvironment = surface_direct_environment_contribution(
|
|
5666
|
+
let directEnvironment = surface_direct_environment_contribution(
|
|
5667
|
+
RayRecord(
|
|
5668
|
+
ray.rayId,
|
|
5669
|
+
ray.parentRayId,
|
|
5670
|
+
ray.sourcePixelId,
|
|
5671
|
+
ray.sampleId,
|
|
5672
|
+
ray.bounce,
|
|
5673
|
+
ray.mediumRefId,
|
|
5674
|
+
ray.flags,
|
|
5675
|
+
0u,
|
|
5676
|
+
ray.origin,
|
|
5677
|
+
ray.direction,
|
|
5678
|
+
vec4<f32>(arrivingThroughput, ray.throughput.w)
|
|
5679
|
+
),
|
|
5680
|
+
hit
|
|
5681
|
+
);
|
|
5389
5682
|
accumulation[ray.rayId] =
|
|
5390
5683
|
accumulation[ray.rayId] + vec4<f32>(directEnvironment * sample_weight(), 0.0);
|
|
5391
5684
|
}
|
|
@@ -5394,7 +5687,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5394
5687
|
if (deferred_path_resolve_enabled()) {
|
|
5395
5688
|
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
5396
5689
|
} else {
|
|
5397
|
-
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5690
|
+
let terminalEnvironment = terminal_surface_environment_contribution(
|
|
5691
|
+
ray,
|
|
5692
|
+
arrivingThroughput,
|
|
5693
|
+
hit
|
|
5694
|
+
);
|
|
5398
5695
|
accumulation[ray.rayId] =
|
|
5399
5696
|
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
5400
5697
|
}
|
|
@@ -5409,7 +5706,11 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5409
5706
|
if (deferred_path_resolve_enabled()) {
|
|
5410
5707
|
record_deferred_terminal_source(ray, terminal_surface_environment_source(ray, hit));
|
|
5411
5708
|
} else {
|
|
5412
|
-
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5709
|
+
let overflowEnvironment = terminal_surface_environment_contribution(
|
|
5710
|
+
ray,
|
|
5711
|
+
arrivingThroughput,
|
|
5712
|
+
hit
|
|
5713
|
+
);
|
|
5413
5714
|
accumulation[ray.rayId] =
|
|
5414
5715
|
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
5415
5716
|
}
|
|
@@ -5423,7 +5724,7 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
5423
5724
|
ray.sourcePixelId,
|
|
5424
5725
|
ray.sampleId,
|
|
5425
5726
|
ray.bounce + 1u,
|
|
5426
|
-
|
|
5727
|
+
scatter.mediumRefId,
|
|
5427
5728
|
scatter.flags,
|
|
5428
5729
|
0u,
|
|
5429
5730
|
vec4<f32>(offset_origin(hit.position.xyz, hit.shadingNormal.xyz), 1.0),
|
|
@@ -5694,9 +5995,28 @@ function nowMs() {
|
|
|
5694
5995
|
return Date.now();
|
|
5695
5996
|
}
|
|
5696
5997
|
|
|
5697
|
-
function
|
|
5998
|
+
function estimateAccelerationBuildWaitFactor(config) {
|
|
5999
|
+
if (config?.gpuAccelerationBuildRequired !== true) {
|
|
6000
|
+
return 1;
|
|
6001
|
+
}
|
|
6002
|
+
const bvhSortStageCount = Array.isArray(config?.bvhSortStages) ? config.bvhSortStages.length : 0;
|
|
6003
|
+
const bvhBuildLevelCount = Array.isArray(config?.bvhBuildLevels) ? config.bvhBuildLevels.length : 0;
|
|
6004
|
+
const accelerationStageCount = 2 + bvhSortStageCount + bvhBuildLevelCount;
|
|
6005
|
+
return Math.max(1, 1 + accelerationStageCount / 96);
|
|
6006
|
+
}
|
|
6007
|
+
|
|
6008
|
+
function estimateSubmittedGpuWorkTiming(
|
|
6009
|
+
config,
|
|
6010
|
+
tileCount,
|
|
6011
|
+
overrideTimeoutMs = null,
|
|
6012
|
+
options = {}
|
|
6013
|
+
) {
|
|
5698
6014
|
if (Number.isFinite(overrideTimeoutMs)) {
|
|
5699
|
-
|
|
6015
|
+
const overrideMs = Math.max(1, Math.trunc(Number(overrideTimeoutMs)));
|
|
6016
|
+
return Object.freeze({
|
|
6017
|
+
timeoutMs: overrideMs,
|
|
6018
|
+
maxWaitMs: overrideMs,
|
|
6019
|
+
});
|
|
5700
6020
|
}
|
|
5701
6021
|
const samplesPerPixel = Math.max(
|
|
5702
6022
|
1,
|
|
@@ -5708,10 +6028,28 @@ function estimateSubmittedGpuWorkTimeoutMs(config, tileCount, overrideTimeoutMs
|
|
|
5708
6028
|
const tiles = Math.max(1, Number(tileCount ?? 1));
|
|
5709
6029
|
const estimatedPasses =
|
|
5710
6030
|
tiles * (samplesPerPixel * (maxDepth + 1 + deferredResolvePasses) + denoisePasses + 1);
|
|
5711
|
-
|
|
6031
|
+
const triangleCount = Math.max(0, Number(config?.triangleCount ?? 0));
|
|
6032
|
+
const geometryFactor = Math.max(1, triangleCount / 131072);
|
|
6033
|
+
const includeAccelerationBuild = options.includeAccelerationBuild === true;
|
|
6034
|
+
const accelerationFactor = includeAccelerationBuild
|
|
6035
|
+
? estimateAccelerationBuildWaitFactor(config)
|
|
6036
|
+
: 1;
|
|
6037
|
+
const estimatedWindowMs = Math.round(
|
|
6038
|
+
(GPU_SUBMITTED_WORK_TIMEOUT_MS + estimatedPasses * 5) * geometryFactor * accelerationFactor
|
|
6039
|
+
);
|
|
6040
|
+
const timeoutMs = Math.min(
|
|
5712
6041
|
GPU_MAX_SUBMITTED_WORK_TIMEOUT_MS,
|
|
5713
|
-
GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
6042
|
+
Math.max(GPU_SUBMITTED_WORK_TIMEOUT_MS, estimatedWindowMs)
|
|
6043
|
+
);
|
|
6044
|
+
const maxWaitMultiplier = includeAccelerationBuild ? 3 : 2;
|
|
6045
|
+
const maxWaitMs = Math.min(
|
|
6046
|
+
GPU_MAX_SUBMITTED_WORK_DEADLINE_MS,
|
|
6047
|
+
Math.max(timeoutMs, estimatedWindowMs * maxWaitMultiplier)
|
|
5714
6048
|
);
|
|
6049
|
+
return Object.freeze({
|
|
6050
|
+
timeoutMs,
|
|
6051
|
+
maxWaitMs,
|
|
6052
|
+
});
|
|
5715
6053
|
}
|
|
5716
6054
|
|
|
5717
6055
|
export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
@@ -5945,6 +6283,11 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
5945
6283
|
config.environmentMap,
|
|
5946
6284
|
config.environmentColor
|
|
5947
6285
|
);
|
|
6286
|
+
let mediumTextureResource = createMediumTextureResource(
|
|
6287
|
+
device,
|
|
6288
|
+
constants,
|
|
6289
|
+
config.mediums
|
|
6290
|
+
);
|
|
5948
6291
|
config = Object.freeze({
|
|
5949
6292
|
...config,
|
|
5950
6293
|
environmentMap: Object.freeze({
|
|
@@ -6033,6 +6376,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6033
6376
|
{ binding: 29, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6034
6377
|
{ binding: 30, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
6035
6378
|
{ binding: 31, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6379
|
+
{ binding: 32, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
6036
6380
|
],
|
|
6037
6381
|
});
|
|
6038
6382
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -6222,14 +6566,19 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6222
6566
|
{ binding: 29, resource: brdfLutResource.view },
|
|
6223
6567
|
{ binding: 30, resource: brdfLutResource.sampler },
|
|
6224
6568
|
{ binding: 31, resource: environmentSamplingResource.view },
|
|
6569
|
+
{ binding: 32, resource: mediumTextureResource.view },
|
|
6225
6570
|
],
|
|
6226
6571
|
});
|
|
6227
6572
|
}
|
|
6228
6573
|
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6574
|
+
function createTraceBindGroups() {
|
|
6575
|
+
return [
|
|
6576
|
+
createTraceBindGroup(activeQueue, nextQueue, "plasius.wavefront.bind.activeNext"),
|
|
6577
|
+
createTraceBindGroup(nextQueue, activeQueue, "plasius.wavefront.bind.nextActive"),
|
|
6578
|
+
];
|
|
6579
|
+
}
|
|
6580
|
+
|
|
6581
|
+
let bindGroups = createTraceBindGroups();
|
|
6233
6582
|
const bvhBuildBindGroup = device.createBindGroup({
|
|
6234
6583
|
label: "plasius.wavefront.bind.bvhBuild",
|
|
6235
6584
|
layout: accelerationBindGroupLayout,
|
|
@@ -6418,6 +6767,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6418
6767
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
6419
6768
|
environmentPortalCount: config.environmentPortalCount,
|
|
6420
6769
|
environmentPortalMode: config.environmentPortalMode,
|
|
6770
|
+
mediumCount: config.mediumCount,
|
|
6421
6771
|
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
6422
6772
|
deferredPathResolve: config.deferredPathResolve,
|
|
6423
6773
|
bvhNodeCount: config.bvhNodeCount,
|
|
@@ -6730,6 +7080,10 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6730
7080
|
? Number(options.timeoutMs)
|
|
6731
7081
|
: GPU_SUBMITTED_WORK_TIMEOUT_MS
|
|
6732
7082
|
);
|
|
7083
|
+
const maxWaitMs = Math.max(
|
|
7084
|
+
timeoutMs,
|
|
7085
|
+
Number.isFinite(options.maxWaitMs) ? Number(options.maxWaitMs) : timeoutMs
|
|
7086
|
+
);
|
|
6733
7087
|
const allowTimeout = options.allowTimeout !== false;
|
|
6734
7088
|
const completionPromise = device.queue.onSubmittedWorkDone().then(
|
|
6735
7089
|
() => ({ status: "done" }),
|
|
@@ -6745,47 +7099,62 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6745
7099
|
);
|
|
6746
7100
|
})
|
|
6747
7101
|
: null;
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
if (
|
|
6753
|
-
|
|
7102
|
+
const startedAtMs = nowMs();
|
|
7103
|
+
while (true) {
|
|
7104
|
+
const elapsedMs = Math.max(0, nowMs() - startedAtMs);
|
|
7105
|
+
const remainingMs = Math.max(0, maxWaitMs - elapsedMs);
|
|
7106
|
+
if (remainingMs <= 0) {
|
|
7107
|
+
if (!allowTimeout) {
|
|
7108
|
+
throw new Error(`Timed out after ${Math.round(maxWaitMs)} ms waiting for submitted GPU work.`);
|
|
7109
|
+
}
|
|
7110
|
+
console.warn(
|
|
7111
|
+
`[plasius.wavefront] Submitted GPU work did not report completion within ${Math.round(maxWaitMs)} ms; continuing.`
|
|
7112
|
+
);
|
|
7113
|
+
return false;
|
|
6754
7114
|
}
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
)
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
7115
|
+
const waitWindowMs = Math.max(1, Math.min(timeoutMs, remainingMs));
|
|
7116
|
+
let timeoutHandle = null;
|
|
7117
|
+
let resolveTimeoutPromise = null;
|
|
7118
|
+
let timeoutSettled = false;
|
|
7119
|
+
const settleTimeoutPromise = (value) => {
|
|
7120
|
+
if (timeoutSettled) {
|
|
7121
|
+
return;
|
|
7122
|
+
}
|
|
7123
|
+
timeoutSettled = true;
|
|
7124
|
+
resolveTimeoutPromise?.(value);
|
|
7125
|
+
};
|
|
7126
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
7127
|
+
resolveTimeoutPromise = resolve;
|
|
7128
|
+
timeoutHandle = setTimeout(
|
|
7129
|
+
() => settleTimeoutPromise({ status: "timeout" }),
|
|
7130
|
+
waitWindowMs
|
|
7131
|
+
);
|
|
7132
|
+
});
|
|
7133
|
+
let result;
|
|
7134
|
+
try {
|
|
7135
|
+
result = await Promise.race(
|
|
7136
|
+
[completionPromise, timeoutPromise, lossPromise].filter(Boolean)
|
|
7137
|
+
);
|
|
7138
|
+
} finally {
|
|
7139
|
+
if (timeoutHandle !== null) {
|
|
7140
|
+
clearTimeout(timeoutHandle);
|
|
7141
|
+
settleTimeoutPromise({ status: "cancelled" });
|
|
7142
|
+
}
|
|
6771
7143
|
}
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
7144
|
+
if (result?.status === "done") {
|
|
7145
|
+
return true;
|
|
7146
|
+
}
|
|
7147
|
+
if (result?.status !== "timeout") {
|
|
7148
|
+
return true;
|
|
6776
7149
|
}
|
|
6777
|
-
console.warn(
|
|
6778
|
-
`[plasius.wavefront] Submitted GPU work did not report completion within ${timeoutMs} ms; continuing.`
|
|
6779
|
-
);
|
|
6780
|
-
return false;
|
|
6781
7150
|
}
|
|
6782
|
-
return true;
|
|
6783
7151
|
}
|
|
6784
7152
|
|
|
6785
7153
|
function dispatchFrameAwaitingGpu(
|
|
6786
7154
|
frameIndex,
|
|
6787
7155
|
parallelism,
|
|
6788
|
-
renderedSamplesPerPixel = config.samplesPerPixel
|
|
7156
|
+
renderedSamplesPerPixel = config.samplesPerPixel,
|
|
7157
|
+
optionsForFrame = {}
|
|
6789
7158
|
) {
|
|
6790
7159
|
const samplePassesPerSample = config.maxDepth + 1 + (config.deferredPathResolve ? 1 : 0);
|
|
6791
7160
|
const denoisePassCount = config.denoise ? (renderedSamplesPerPixel < 4 ? 2 : 1) : 0;
|
|
@@ -6794,25 +7163,50 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6794
7163
|
1,
|
|
6795
7164
|
Math.floor(
|
|
6796
7165
|
Math.max(config.maxFramePassesPerSubmission - tailPassCount, 1) /
|
|
6797
|
-
|
|
7166
|
+
Math.max(samplePassesPerSample, 1)
|
|
6798
7167
|
)
|
|
6799
7168
|
);
|
|
6800
|
-
|
|
7169
|
+
const sampleRangeStart = clamp(
|
|
7170
|
+
readNonNegativeInteger("sampleRangeStart", optionsForFrame.sampleRangeStart, 0),
|
|
7171
|
+
0,
|
|
7172
|
+
renderedSamplesPerPixel
|
|
7173
|
+
);
|
|
7174
|
+
const sampleRangeEnd = clamp(
|
|
7175
|
+
readPositiveInteger("sampleRangeEnd", optionsForFrame.sampleRangeEnd, renderedSamplesPerPixel),
|
|
7176
|
+
sampleRangeStart,
|
|
7177
|
+
renderedSamplesPerPixel
|
|
7178
|
+
);
|
|
7179
|
+
const includeDenoise = optionsForFrame.includeDenoise === true;
|
|
7180
|
+
const includePresent = optionsForFrame.includePresent === true;
|
|
7181
|
+
const tileStartIndex = clamp(
|
|
7182
|
+
readNonNegativeInteger("tileStartIndex", optionsForFrame.tileStartIndex, 0),
|
|
7183
|
+
0,
|
|
7184
|
+
tiles.length
|
|
7185
|
+
);
|
|
7186
|
+
const tileEndIndex = clamp(
|
|
7187
|
+
readPositiveInteger("tileEndIndex", optionsForFrame.tileEndIndex, tiles.length),
|
|
7188
|
+
tileStartIndex,
|
|
7189
|
+
tiles.length
|
|
7190
|
+
);
|
|
7191
|
+
let submissionCount = Math.max(
|
|
7192
|
+
0,
|
|
7193
|
+
readNonNegativeInteger("startingSubmissionCount", optionsForFrame.startingSubmissionCount, 0)
|
|
7194
|
+
);
|
|
7195
|
+
let slot = Math.max(0, readNonNegativeInteger("startingSlot", optionsForFrame.startingSlot, 0));
|
|
6801
7196
|
|
|
6802
|
-
for (const tile of tiles) {
|
|
7197
|
+
for (const tile of tiles.slice(tileStartIndex, tileEndIndex)) {
|
|
6803
7198
|
for (
|
|
6804
|
-
let sampleStart =
|
|
6805
|
-
sampleStart <
|
|
7199
|
+
let sampleStart = sampleRangeStart;
|
|
7200
|
+
sampleStart < sampleRangeEnd;
|
|
6806
7201
|
sampleStart += sampleBatchSize
|
|
6807
7202
|
) {
|
|
6808
|
-
const sampleEnd = Math.min(
|
|
7203
|
+
const sampleEnd = Math.min(sampleRangeEnd, sampleStart + sampleBatchSize);
|
|
6809
7204
|
const batch = createGpuSubmissionBatcher({
|
|
6810
7205
|
device,
|
|
6811
7206
|
frameIndex,
|
|
6812
7207
|
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6813
7208
|
startingSubmissionCount: submissionCount,
|
|
6814
7209
|
});
|
|
6815
|
-
let slot = 0;
|
|
6816
7210
|
for (let sampleIndex = sampleStart; sampleIndex < sampleEnd; sampleIndex += 1) {
|
|
6817
7211
|
const configOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6818
7212
|
sampleIndex,
|
|
@@ -6829,11 +7223,12 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6829
7223
|
encodeTileOutput(batch.reserve(1), tile, configOffset, parallelism);
|
|
6830
7224
|
}
|
|
6831
7225
|
}
|
|
6832
|
-
if (!config.deferredPathResolve &&
|
|
7226
|
+
if (!config.deferredPathResolve && sampleRangeEnd >= renderedSamplesPerPixel) {
|
|
6833
7227
|
const outputConfigOffset = writeFrameConfigSlot(slot, tile, frameIndex, {
|
|
6834
7228
|
sampleIndex: 0,
|
|
6835
7229
|
sampleWeight: 1 / renderedSamplesPerPixel,
|
|
6836
7230
|
});
|
|
7231
|
+
slot += 1;
|
|
6837
7232
|
encodeTileOutput(batch.reserve(1), tile, outputConfigOffset, parallelism);
|
|
6838
7233
|
}
|
|
6839
7234
|
batch.flush();
|
|
@@ -6841,30 +7236,38 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6841
7236
|
}
|
|
6842
7237
|
}
|
|
6843
7238
|
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
6848
|
-
startingSubmissionCount: submissionCount,
|
|
6849
|
-
});
|
|
6850
|
-
if (config.denoise) {
|
|
6851
|
-
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
6852
|
-
0,
|
|
6853
|
-
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
7239
|
+
if (includeDenoise || includePresent) {
|
|
7240
|
+
const tail = createGpuSubmissionBatcher({
|
|
7241
|
+
device,
|
|
6854
7242
|
frameIndex,
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
denoiseConfigOffset
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
7243
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
7244
|
+
startingSubmissionCount: submissionCount,
|
|
7245
|
+
});
|
|
7246
|
+
if (includeDenoise && config.denoise) {
|
|
7247
|
+
const denoiseConfigOffset = writeFrameConfigSlot(
|
|
7248
|
+
slot,
|
|
7249
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
7250
|
+
frameIndex,
|
|
7251
|
+
{ sampleIndex: 0, sampleWeight: 1 / renderedSamplesPerPixel }
|
|
7252
|
+
);
|
|
7253
|
+
slot += 1;
|
|
7254
|
+
encodeDenoise(
|
|
7255
|
+
tail.reserve(denoisePassCount),
|
|
7256
|
+
denoiseConfigOffset,
|
|
7257
|
+
parallelism,
|
|
7258
|
+
renderedSamplesPerPixel
|
|
7259
|
+
);
|
|
7260
|
+
}
|
|
7261
|
+
if (includePresent) {
|
|
7262
|
+
encodePresent(tail.reserve(1));
|
|
7263
|
+
}
|
|
7264
|
+
tail.flush();
|
|
7265
|
+
submissionCount += tail.getSubmissionCount();
|
|
6863
7266
|
}
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
7267
|
+
return Object.freeze({
|
|
7268
|
+
submissionCount,
|
|
7269
|
+
slot,
|
|
7270
|
+
});
|
|
6868
7271
|
}
|
|
6869
7272
|
|
|
6870
7273
|
async function readOutputProbe(optionsForProbe = {}) {
|
|
@@ -6913,26 +7316,59 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6913
7316
|
const samplingPlan = resolveRenderedSamplesPerPixel(renderOptions, awaitGPUCompletion);
|
|
6914
7317
|
const useThrottledHighSamplePath =
|
|
6915
7318
|
awaitGPUCompletion && samplingPlan.renderedSamplesPerPixel >= 8;
|
|
6916
|
-
const submittedWorkTimeoutMs = estimateSubmittedGpuWorkTimeoutMs(
|
|
6917
|
-
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
6918
|
-
tiles.length,
|
|
6919
|
-
renderOptions.submittedWorkTimeoutMs
|
|
6920
|
-
);
|
|
6921
7319
|
const frameStartTimeMs = nowMs();
|
|
6922
|
-
const submissionWaitOptions = awaitGPUCompletion
|
|
6923
|
-
? { timeoutMs: submittedWorkTimeoutMs, allowTimeout: false }
|
|
6924
|
-
: { timeoutMs: submittedWorkTimeoutMs };
|
|
6925
7320
|
let frameStats;
|
|
6926
7321
|
if (useThrottledHighSamplePath) {
|
|
6927
7322
|
frame += 1;
|
|
6928
7323
|
const frameIndex = frame + config.frameIndex;
|
|
6929
7324
|
const parallelismCounters = createGpuParallelismCounters();
|
|
6930
7325
|
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
7326
|
+
let frameSubmissionCount = 0;
|
|
7327
|
+
let frameConfigSlot = 0;
|
|
7328
|
+
if (accelerationBuildSubmitted) {
|
|
7329
|
+
const accelerationWaitOptions = {
|
|
7330
|
+
...estimateSubmittedGpuWorkTiming(
|
|
7331
|
+
{ ...config, renderedSamplesPerPixel: 1 },
|
|
7332
|
+
1,
|
|
7333
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
7334
|
+
{ includeAccelerationBuild: true }
|
|
7335
|
+
),
|
|
7336
|
+
allowTimeout: false,
|
|
7337
|
+
};
|
|
7338
|
+
await waitForSubmittedGpuWork(accelerationWaitOptions);
|
|
7339
|
+
}
|
|
7340
|
+
for (let tileIndex = 0; tileIndex < tiles.length; tileIndex += 1) {
|
|
7341
|
+
const tileRangeDispatch = dispatchFrameAwaitingGpu(
|
|
7342
|
+
frameIndex,
|
|
7343
|
+
parallelismCounters,
|
|
7344
|
+
samplingPlan.renderedSamplesPerPixel,
|
|
7345
|
+
{
|
|
7346
|
+
sampleRangeStart: 0,
|
|
7347
|
+
sampleRangeEnd: samplingPlan.renderedSamplesPerPixel,
|
|
7348
|
+
tileStartIndex: tileIndex,
|
|
7349
|
+
tileEndIndex: tileIndex + 1,
|
|
7350
|
+
startingSubmissionCount: frameSubmissionCount,
|
|
7351
|
+
startingSlot: frameConfigSlot,
|
|
7352
|
+
includeDenoise: tileIndex + 1 >= tiles.length,
|
|
7353
|
+
includePresent: tileIndex + 1 >= tiles.length,
|
|
7354
|
+
}
|
|
7355
|
+
);
|
|
7356
|
+
frameSubmissionCount = tileRangeDispatch.submissionCount;
|
|
7357
|
+
frameConfigSlot = tileRangeDispatch.slot;
|
|
7358
|
+
const tileWaitOptions = {
|
|
7359
|
+
...estimateSubmittedGpuWorkTiming(
|
|
7360
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
7361
|
+
1,
|
|
7362
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
7363
|
+
{
|
|
7364
|
+
includeDenoise: tileIndex + 1 >= tiles.length && config.denoise,
|
|
7365
|
+
includePresent: tileIndex + 1 >= tiles.length,
|
|
7366
|
+
}
|
|
7367
|
+
),
|
|
7368
|
+
allowTimeout: false,
|
|
7369
|
+
};
|
|
7370
|
+
await waitForSubmittedGpuWork(tileWaitOptions);
|
|
7371
|
+
}
|
|
6936
7372
|
frameStats = createFrameStats({
|
|
6937
7373
|
frameIndex,
|
|
6938
7374
|
accelerationBuildSubmitted,
|
|
@@ -6944,10 +7380,26 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
6944
7380
|
budgetConstrained: samplingPlan.budgetConstrained,
|
|
6945
7381
|
});
|
|
6946
7382
|
} else {
|
|
7383
|
+
const submittedWorkTiming = estimateSubmittedGpuWorkTiming(
|
|
7384
|
+
{ ...config, renderedSamplesPerPixel: samplingPlan.renderedSamplesPerPixel },
|
|
7385
|
+
tiles.length,
|
|
7386
|
+
renderOptions.submittedWorkTimeoutMs,
|
|
7387
|
+
{ includeAccelerationBuild: config.gpuAccelerationBuildRequired && !accelerationBuilt }
|
|
7388
|
+
);
|
|
7389
|
+
const submissionWaitOptions = awaitGPUCompletion
|
|
7390
|
+
? {
|
|
7391
|
+
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
7392
|
+
maxWaitMs: submittedWorkTiming.maxWaitMs,
|
|
7393
|
+
allowTimeout: false,
|
|
7394
|
+
}
|
|
7395
|
+
: {
|
|
7396
|
+
timeoutMs: submittedWorkTiming.timeoutMs,
|
|
7397
|
+
maxWaitMs: submittedWorkTiming.maxWaitMs,
|
|
7398
|
+
};
|
|
6947
7399
|
frameStats = renderOnce(renderOptions, samplingPlan);
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
7400
|
+
if (awaitGPUCompletion) {
|
|
7401
|
+
await waitForSubmittedGpuWork(submissionWaitOptions);
|
|
7402
|
+
}
|
|
6951
7403
|
}
|
|
6952
7404
|
const frameTimeMs = Math.max(0, nowMs() - frameStartTimeMs);
|
|
6953
7405
|
if (awaitGPUCompletion) {
|
|
@@ -7007,10 +7459,23 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
7007
7459
|
});
|
|
7008
7460
|
}
|
|
7009
7461
|
|
|
7462
|
+
function rebuildMediumResources(nextConfig) {
|
|
7463
|
+
const previousMediumTextureResource = mediumTextureResource;
|
|
7464
|
+
mediumTextureResource = createMediumTextureResource(device, constants, nextConfig.mediums);
|
|
7465
|
+
bindGroups = createTraceBindGroups();
|
|
7466
|
+
if (previousMediumTextureResource?.ownsTexture) {
|
|
7467
|
+
previousMediumTextureResource.texture?.destroy?.();
|
|
7468
|
+
}
|
|
7469
|
+
}
|
|
7470
|
+
|
|
7010
7471
|
function updateSceneObjects(sceneObjects) {
|
|
7011
7472
|
const nextPackedScene = packWavefrontSceneObjects(sceneObjects, config.sceneObjectCapacity);
|
|
7012
7473
|
packedScene = nextPackedScene;
|
|
7013
|
-
|
|
7474
|
+
const nextConfig = rebuildLiveConfig();
|
|
7475
|
+
if (!mediumTablesEqual(config.mediums, nextConfig.mediums)) {
|
|
7476
|
+
rebuildMediumResources(nextConfig);
|
|
7477
|
+
}
|
|
7478
|
+
config = nextConfig;
|
|
7014
7479
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
7015
7480
|
return config;
|
|
7016
7481
|
}
|
|
@@ -7036,6 +7501,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
7036
7501
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
7037
7502
|
environmentPortalCount: config.environmentPortalCount,
|
|
7038
7503
|
environmentPortalMode: config.environmentPortalMode,
|
|
7504
|
+
mediumCount: config.mediumCount,
|
|
7039
7505
|
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
7040
7506
|
deferredPathResolve: config.deferredPathResolve,
|
|
7041
7507
|
bvhNodeCount: config.bvhNodeCount,
|
|
@@ -7070,17 +7536,20 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
7070
7536
|
activeDispatchBuffer.destroy?.();
|
|
7071
7537
|
radianceTexture.destroy?.();
|
|
7072
7538
|
denoiseScratchTexture.destroy?.();
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7539
|
+
outputTexture.destroy?.();
|
|
7540
|
+
if (environmentMapResource.ownsTexture) {
|
|
7541
|
+
environmentMapResource.texture?.destroy?.();
|
|
7542
|
+
}
|
|
7543
|
+
if (environmentSamplingResource.ownsTexture) {
|
|
7544
|
+
environmentSamplingResource.texture?.destroy?.();
|
|
7545
|
+
}
|
|
7546
|
+
if (mediumTextureResource.ownsTexture) {
|
|
7547
|
+
mediumTextureResource.texture?.destroy?.();
|
|
7548
|
+
}
|
|
7549
|
+
brdfLutResource.texture?.destroy?.();
|
|
7550
|
+
if (baseColorAtlasResource.ownsTexture) {
|
|
7551
|
+
baseColorAtlasResource.texture?.destroy?.();
|
|
7552
|
+
}
|
|
7084
7553
|
if (metallicRoughnessAtlasResource.ownsTexture) {
|
|
7085
7554
|
metallicRoughnessAtlasResource.texture?.destroy?.();
|
|
7086
7555
|
}
|