@plasius/gpu-renderer 0.1.14 → 0.1.15
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 +26 -0
- package/README.md +13 -0
- package/dist/index.cjs +392 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +392 -76
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.d.ts +66 -1
- package/src/wavefront-compute.js +404 -73
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ var DEFAULT_MAX_DEPTH = 6;
|
|
|
5
5
|
var DEFAULT_TILE_SIZE = 128;
|
|
6
6
|
var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
7
7
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
8
|
+
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
8
9
|
var WORKGROUP_SIZE = 64;
|
|
9
10
|
var RAY_RECORD_BYTES = 80;
|
|
10
11
|
var HIT_RECORD_BYTES = 208;
|
|
@@ -15,9 +16,11 @@ var TRIANGLE_RECORD_BYTES = 208;
|
|
|
15
16
|
var BVH_NODE_RECORD_BYTES = 48;
|
|
16
17
|
var BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
17
18
|
var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
19
|
+
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
18
20
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
19
|
-
var CONFIG_BUFFER_BYTES =
|
|
21
|
+
var CONFIG_BUFFER_BYTES = 272;
|
|
20
22
|
var COUNTER_BUFFER_BYTES = 16;
|
|
23
|
+
var TRACE_STORAGE_BUFFER_BINDINGS = 9;
|
|
21
24
|
var MATERIAL_DIFFUSE = 0;
|
|
22
25
|
var MATERIAL_METAL = 1;
|
|
23
26
|
var MATERIAL_DIELECTRIC = 2;
|
|
@@ -44,6 +47,7 @@ var DEFAULT_ENVIRONMENT_LIGHTING = Object.freeze({
|
|
|
44
47
|
});
|
|
45
48
|
var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
46
49
|
workgroupSize: WORKGROUP_SIZE,
|
|
50
|
+
traceStorageBufferBindings: TRACE_STORAGE_BUFFER_BINDINGS,
|
|
47
51
|
rayRecordBytes: RAY_RECORD_BYTES,
|
|
48
52
|
hitRecordBytes: HIT_RECORD_BYTES,
|
|
49
53
|
sceneObjectRecordBytes: SCENE_OBJECT_RECORD_BYTES,
|
|
@@ -54,6 +58,7 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
54
58
|
bvhLeafReferenceRecordBytes: BVH_LEAF_REF_RECORD_BYTES,
|
|
55
59
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
56
60
|
emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
61
|
+
environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
57
62
|
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES
|
|
58
63
|
});
|
|
59
64
|
var wavefrontSceneObjectKinds = Object.freeze({
|
|
@@ -145,6 +150,9 @@ function subtract(a, b) {
|
|
|
145
150
|
function scale(a, scalar) {
|
|
146
151
|
return [a[0] * scalar, a[1] * scalar, a[2] * scalar];
|
|
147
152
|
}
|
|
153
|
+
function dot(a, b) {
|
|
154
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
155
|
+
}
|
|
148
156
|
function cross(a, b) {
|
|
149
157
|
return [
|
|
150
158
|
a[1] * b[2] - a[2] * b[1],
|
|
@@ -757,6 +765,127 @@ function resolveEnvironmentLighting(input, environmentColor, ambientColor) {
|
|
|
757
765
|
exposure: Math.max(1e-4, readFiniteNumber("environmentLighting.exposure", source.exposure, DEFAULT_ENVIRONMENT_LIGHTING.exposure))
|
|
758
766
|
});
|
|
759
767
|
}
|
|
768
|
+
function resolveEnvironmentPortalMode(value, hasPortals) {
|
|
769
|
+
if (value === void 0 || value === null) {
|
|
770
|
+
return hasPortals ? 2 : 0;
|
|
771
|
+
}
|
|
772
|
+
if (Number.isInteger(value) && value >= 0 && value <= 2) {
|
|
773
|
+
return value;
|
|
774
|
+
}
|
|
775
|
+
if (value === "disabled") {
|
|
776
|
+
return 0;
|
|
777
|
+
}
|
|
778
|
+
if (value === "guide") {
|
|
779
|
+
return 1;
|
|
780
|
+
}
|
|
781
|
+
if (value === "guide-and-gate" || value === "gate") {
|
|
782
|
+
return 2;
|
|
783
|
+
}
|
|
784
|
+
throw new Error(
|
|
785
|
+
"environmentPortalMode must be disabled, guide, guide-and-gate, or an integer between 0 and 2."
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
function orthogonalPortalTangent(normal) {
|
|
789
|
+
if (Math.abs(normal[1]) < 0.92) {
|
|
790
|
+
return normalize(cross([0, 1, 0], normal), [1, 0, 0]);
|
|
791
|
+
}
|
|
792
|
+
return normalize(cross([1, 0, 0], normal), [0, 0, 1]);
|
|
793
|
+
}
|
|
794
|
+
function resolvePortalTangent(value, normal) {
|
|
795
|
+
const fallback = orthogonalPortalTangent(normal);
|
|
796
|
+
const tangent = asUnitVec3(value, fallback);
|
|
797
|
+
const projected = subtract(tangent, scale(normal, dot(tangent, normal)));
|
|
798
|
+
return normalize(projected, fallback);
|
|
799
|
+
}
|
|
800
|
+
function readPositiveFiniteNumber(name, value, fallback) {
|
|
801
|
+
const numeric = readFiniteNumber(name, value, fallback);
|
|
802
|
+
if (numeric <= 0) {
|
|
803
|
+
throw new Error(`${name} must be a positive finite number.`);
|
|
804
|
+
}
|
|
805
|
+
return numeric;
|
|
806
|
+
}
|
|
807
|
+
function readPortalExtent(name, value, halfName, halfValue) {
|
|
808
|
+
if (value !== void 0 && value !== null) {
|
|
809
|
+
return readPositiveFiniteNumber(name, value, 1);
|
|
810
|
+
}
|
|
811
|
+
return readPositiveFiniteNumber(halfName, halfValue, 0.5) * 2;
|
|
812
|
+
}
|
|
813
|
+
function normalizeEnvironmentPortal(portal, index) {
|
|
814
|
+
if (!portal || typeof portal !== "object") {
|
|
815
|
+
throw new Error(`environmentPortals[${index}] must be an object.`);
|
|
816
|
+
}
|
|
817
|
+
const shape = portal.shape ?? portal.kind ?? "rectangle";
|
|
818
|
+
if (shape !== "rectangle") {
|
|
819
|
+
throw new Error(`environmentPortals[${index}].shape must be "rectangle".`);
|
|
820
|
+
}
|
|
821
|
+
const position = asVec3(portal.position ?? portal.center, [0, 0, 0]);
|
|
822
|
+
const normal = asUnitVec3(portal.normal, [0, 0, 1]);
|
|
823
|
+
const tangent = resolvePortalTangent(portal.tangent, normal);
|
|
824
|
+
const bitangent = normalize(cross(normal, tangent), [0, 1, 0]);
|
|
825
|
+
const width = readPortalExtent(
|
|
826
|
+
`environmentPortals[${index}].width`,
|
|
827
|
+
portal.width,
|
|
828
|
+
`environmentPortals[${index}].halfWidth`,
|
|
829
|
+
portal.halfWidth
|
|
830
|
+
);
|
|
831
|
+
const height = readPortalExtent(
|
|
832
|
+
`environmentPortals[${index}].height`,
|
|
833
|
+
portal.height,
|
|
834
|
+
`environmentPortals[${index}].halfHeight`,
|
|
835
|
+
portal.halfHeight
|
|
836
|
+
);
|
|
837
|
+
const radianceScale = Math.max(
|
|
838
|
+
0,
|
|
839
|
+
readFiniteNumber(
|
|
840
|
+
`environmentPortals[${index}].radianceScale`,
|
|
841
|
+
portal.radianceScale ?? portal.intensity,
|
|
842
|
+
1
|
|
843
|
+
)
|
|
844
|
+
);
|
|
845
|
+
return Object.freeze({
|
|
846
|
+
kind: 1,
|
|
847
|
+
flags: portal.twoSided === false ? 0 : 1,
|
|
848
|
+
position: Object.freeze([position[0], position[1], position[2], width * height]),
|
|
849
|
+
normal: Object.freeze([normal[0], normal[1], normal[2], radianceScale]),
|
|
850
|
+
tangent: Object.freeze([tangent[0], tangent[1], tangent[2], width * 0.5]),
|
|
851
|
+
bitangent: Object.freeze([bitangent[0], bitangent[1], bitangent[2], height * 0.5]),
|
|
852
|
+
color: Object.freeze(asColor(portal.color, [1, 1, 1, 1]))
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
function normalizeEnvironmentPortals(value) {
|
|
856
|
+
if (value === void 0 || value === null) {
|
|
857
|
+
return Object.freeze([]);
|
|
858
|
+
}
|
|
859
|
+
if (!Array.isArray(value)) {
|
|
860
|
+
throw new Error("environmentPortals must be an array when provided.");
|
|
861
|
+
}
|
|
862
|
+
return Object.freeze(value.map(normalizeEnvironmentPortal));
|
|
863
|
+
}
|
|
864
|
+
function packEnvironmentPortals(portals, capacity) {
|
|
865
|
+
const bytes = new ArrayBuffer(capacity * ENVIRONMENT_PORTAL_RECORD_BYTES);
|
|
866
|
+
const data = new DataView(bytes);
|
|
867
|
+
const floatView = new Float32Array(bytes);
|
|
868
|
+
portals.forEach((portal, index) => {
|
|
869
|
+
const byteOffset = index * ENVIRONMENT_PORTAL_RECORD_BYTES;
|
|
870
|
+
const floatOffset = byteOffset / Float32Array.BYTES_PER_ELEMENT;
|
|
871
|
+
data.setUint32(byteOffset, portal.kind, true);
|
|
872
|
+
data.setUint32(byteOffset + 4, portal.flags, true);
|
|
873
|
+
data.setUint32(byteOffset + 8, 0, true);
|
|
874
|
+
data.setUint32(byteOffset + 12, 0, true);
|
|
875
|
+
writeVec4(floatView, floatOffset + 4, portal.position);
|
|
876
|
+
writeVec4(floatView, floatOffset + 8, portal.normal);
|
|
877
|
+
writeVec4(floatView, floatOffset + 12, portal.tangent);
|
|
878
|
+
writeVec4(floatView, floatOffset + 16, portal.bitangent);
|
|
879
|
+
writeVec4(floatView, floatOffset + 20, portal.color);
|
|
880
|
+
});
|
|
881
|
+
return Object.freeze({
|
|
882
|
+
buffer: bytes,
|
|
883
|
+
portals,
|
|
884
|
+
count: portals.length,
|
|
885
|
+
capacity,
|
|
886
|
+
recordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES
|
|
887
|
+
});
|
|
888
|
+
}
|
|
760
889
|
function getCanvasDimension(canvas, key, fallback) {
|
|
761
890
|
const value = Number(canvas?.[key]);
|
|
762
891
|
if (Number.isFinite(value) && value > 0) {
|
|
@@ -812,6 +941,11 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
812
941
|
options.emissiveTriangleCapacity,
|
|
813
942
|
0
|
|
814
943
|
);
|
|
944
|
+
const environmentPortalCapacity = readNonNegativeInteger(
|
|
945
|
+
"environmentPortalCapacity",
|
|
946
|
+
options.environmentPortalCapacity,
|
|
947
|
+
0
|
|
948
|
+
);
|
|
815
949
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
816
950
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
817
951
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
@@ -820,6 +954,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
820
954
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
821
955
|
const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
|
|
822
956
|
const emissiveTriangleMetadataBytes = emissiveTriangleCapacity * BVH_NODE_RECORD_BYTES;
|
|
957
|
+
const environmentPortalBytes = environmentPortalCapacity * ENVIRONMENT_PORTAL_RECORD_BYTES;
|
|
823
958
|
return Object.freeze({
|
|
824
959
|
queueBytes,
|
|
825
960
|
queuePairBytes: queueBytes * 2,
|
|
@@ -830,9 +965,10 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
830
965
|
bvhNodeBytes,
|
|
831
966
|
bvhLeafReferenceBytes,
|
|
832
967
|
emissiveTriangleMetadataBytes,
|
|
968
|
+
environmentPortalBytes,
|
|
833
969
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
834
970
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
835
|
-
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES
|
|
971
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES
|
|
836
972
|
});
|
|
837
973
|
}
|
|
838
974
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -893,6 +1029,21 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
893
1029
|
environmentColor,
|
|
894
1030
|
ambientColor
|
|
895
1031
|
);
|
|
1032
|
+
const environmentPortals = normalizeEnvironmentPortals(
|
|
1033
|
+
options.environmentPortals ?? options.environmentLightPortals ?? options.environmentLighting?.environmentPortals
|
|
1034
|
+
);
|
|
1035
|
+
const environmentPortalCapacity = Math.max(
|
|
1036
|
+
environmentPortals.length,
|
|
1037
|
+
readNonNegativeInteger(
|
|
1038
|
+
"environmentPortalCapacity",
|
|
1039
|
+
options.environmentPortalCapacity,
|
|
1040
|
+
DEFAULT_ENVIRONMENT_PORTAL_CAPACITY
|
|
1041
|
+
)
|
|
1042
|
+
);
|
|
1043
|
+
const environmentPortalMode = resolveEnvironmentPortalMode(
|
|
1044
|
+
options.environmentPortalMode ?? options.portalMode ?? options.environmentLighting?.environmentPortalMode,
|
|
1045
|
+
environmentPortals.length > 0
|
|
1046
|
+
);
|
|
896
1047
|
return Object.freeze({
|
|
897
1048
|
width,
|
|
898
1049
|
height,
|
|
@@ -921,6 +1072,10 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
921
1072
|
environmentColor: environmentLighting.environmentColor,
|
|
922
1073
|
ambientColor: environmentLighting.ambientColor,
|
|
923
1074
|
environmentLighting,
|
|
1075
|
+
environmentPortals,
|
|
1076
|
+
environmentPortalCount: environmentPortals.length,
|
|
1077
|
+
environmentPortalCapacity,
|
|
1078
|
+
environmentPortalMode,
|
|
924
1079
|
displayQuality: options.displayQuality === true,
|
|
925
1080
|
requiresMeshBvhForDisplayQuality: true,
|
|
926
1081
|
denoise: options.denoise !== false,
|
|
@@ -931,7 +1086,8 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
931
1086
|
triangleCapacity,
|
|
932
1087
|
bvhNodeCapacity,
|
|
933
1088
|
bvhLeafSortCapacity,
|
|
934
|
-
emissiveTriangleCapacity: emissiveTriangleIndices.capacity
|
|
1089
|
+
emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
|
|
1090
|
+
environmentPortalCapacity
|
|
935
1091
|
})
|
|
936
1092
|
});
|
|
937
1093
|
}
|
|
@@ -1115,6 +1271,10 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1115
1271
|
data.setUint32(244, buildRange.count ?? 0, true);
|
|
1116
1272
|
data.setUint32(248, buildRange.sortItemCount ?? 0, true);
|
|
1117
1273
|
data.setUint32(252, config.emissiveTriangleCount ?? 0, true);
|
|
1274
|
+
data.setUint32(256, config.environmentPortalCount ?? 0, true);
|
|
1275
|
+
data.setUint32(260, config.environmentPortalMode ?? 0, true);
|
|
1276
|
+
data.setUint32(264, 0, true);
|
|
1277
|
+
data.setUint32(268, 0, true);
|
|
1118
1278
|
return bytes;
|
|
1119
1279
|
}
|
|
1120
1280
|
function createTiles(width, height, tileSize) {
|
|
@@ -1361,6 +1521,10 @@ struct FrameConfig {
|
|
|
1361
1521
|
bvhBuildNodeCount: u32,
|
|
1362
1522
|
bvhSortItemCount: u32,
|
|
1363
1523
|
emissiveTriangleCount: u32,
|
|
1524
|
+
environmentPortalCount: u32,
|
|
1525
|
+
environmentPortalMode: u32,
|
|
1526
|
+
_portalPad0: u32,
|
|
1527
|
+
_portalPad1: u32,
|
|
1364
1528
|
};
|
|
1365
1529
|
|
|
1366
1530
|
struct Counters {
|
|
@@ -1384,6 +1548,18 @@ struct Candidate {
|
|
|
1384
1548
|
mediumRefId: u32,
|
|
1385
1549
|
};
|
|
1386
1550
|
|
|
1551
|
+
struct EnvironmentPortal {
|
|
1552
|
+
kind: u32,
|
|
1553
|
+
flags: u32,
|
|
1554
|
+
_pad0: u32,
|
|
1555
|
+
_pad1: u32,
|
|
1556
|
+
position: vec4<f32>,
|
|
1557
|
+
normal: vec4<f32>,
|
|
1558
|
+
tangent: vec4<f32>,
|
|
1559
|
+
bitangent: vec4<f32>,
|
|
1560
|
+
color: vec4<f32>,
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1387
1563
|
@group(0) @binding(0) var<storage, read_write> activeQueue: array<RayRecord>;
|
|
1388
1564
|
@group(0) @binding(1) var<storage, read_write> nextQueue: array<RayRecord>;
|
|
1389
1565
|
@group(0) @binding(2) var<storage, read_write> hits: array<HitRecord>;
|
|
@@ -1403,6 +1579,7 @@ struct Candidate {
|
|
|
1403
1579
|
@group(0) @binding(16) var radianceImage: texture_storage_2d<rgba16float, write>;
|
|
1404
1580
|
@group(0) @binding(17) var finalDenoiseInputRadiance: texture_2d<f32>;
|
|
1405
1581
|
@group(0) @binding(18) var denoisedOutputImage: texture_storage_2d<rgba8unorm, write>;
|
|
1582
|
+
@group(0) @binding(19) var<storage, read> environmentPortals: array<EnvironmentPortal>;
|
|
1406
1583
|
|
|
1407
1584
|
fn hash_u32(value: u32) -> u32 {
|
|
1408
1585
|
var x = value;
|
|
@@ -1443,7 +1620,48 @@ fn saturate(value: f32) -> f32 {
|
|
|
1443
1620
|
return clamp(value, 0.0, 1.0);
|
|
1444
1621
|
}
|
|
1445
1622
|
|
|
1446
|
-
fn
|
|
1623
|
+
fn max_component(value: vec3<f32>) -> f32 {
|
|
1624
|
+
return max(max(value.x, value.y), value.z);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
fn environment_portal_radiance_scale(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1628
|
+
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
1629
|
+
return vec3<f32>(1.0);
|
|
1630
|
+
}
|
|
1631
|
+
var scale = vec3<f32>(0.0);
|
|
1632
|
+
for (var portalIndex = 0u; portalIndex < config.environmentPortalCount; portalIndex = portalIndex + 1u) {
|
|
1633
|
+
let portal = environmentPortals[portalIndex];
|
|
1634
|
+
if (portal.kind == 1u) {
|
|
1635
|
+
let portalNormal = safe_normalize(portal.normal.xyz, vec3<f32>(0.0, 0.0, 1.0));
|
|
1636
|
+
let denominator = dot(direction, portalNormal);
|
|
1637
|
+
let twoSided = (portal.flags & 1u) != 0u;
|
|
1638
|
+
var facing = abs(denominator) > 0.0001;
|
|
1639
|
+
if (!twoSided && denominator <= 0.0001) {
|
|
1640
|
+
facing = false;
|
|
1641
|
+
}
|
|
1642
|
+
if (facing) {
|
|
1643
|
+
let distance = dot(portal.position.xyz - origin, portalNormal) / denominator;
|
|
1644
|
+
if (distance > 0.001) {
|
|
1645
|
+
let hitPosition = origin + direction * distance;
|
|
1646
|
+
let local = hitPosition - portal.position.xyz;
|
|
1647
|
+
let tangent = safe_normalize(portal.tangent.xyz, vec3<f32>(1.0, 0.0, 0.0));
|
|
1648
|
+
let bitangent = safe_normalize(portal.bitangent.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
1649
|
+
let u = dot(local, tangent);
|
|
1650
|
+
let v = dot(local, bitangent);
|
|
1651
|
+
if (abs(u) <= portal.tangent.w && abs(v) <= portal.bitangent.w) {
|
|
1652
|
+
let areaWeight = clamp(sqrt(max(portal.position.w, 0.0001)), 0.25, 4.0);
|
|
1653
|
+
let angleWeight = max(abs(denominator), 0.08);
|
|
1654
|
+
let portalScale = portal.color.rgb * portal.normal.w * portal.color.a * areaWeight * angleWeight;
|
|
1655
|
+
scale = max(scale, portalScale);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return scale;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1447
1665
|
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
1448
1666
|
let upFactor = saturate(rayDirection.y * 0.5 + 0.5);
|
|
1449
1667
|
let sunDirection = safe_normalize(
|
|
@@ -1454,10 +1672,26 @@ fn environment_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
|
1454
1672
|
let gradient =
|
|
1455
1673
|
config.environmentHorizonColor.xyz * (1.0 - upFactor) +
|
|
1456
1674
|
config.environmentZenithColor.xyz * upFactor;
|
|
1675
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
1676
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
1457
1677
|
return (
|
|
1458
1678
|
gradient +
|
|
1459
1679
|
config.environmentSunColor.xyz * sunGlow
|
|
1460
|
-
) *
|
|
1680
|
+
) *
|
|
1681
|
+
max(config.environmentSunDirectionIntensity.w, 0.0001) *
|
|
1682
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1686
|
+
let portalScale = environment_portal_radiance_scale(origin, safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0)));
|
|
1687
|
+
if (
|
|
1688
|
+
config.environmentPortalCount > 0u &&
|
|
1689
|
+
config.environmentPortalMode == 2u &&
|
|
1690
|
+
max_component(portalScale) <= 0.0001
|
|
1691
|
+
) {
|
|
1692
|
+
return config.ambientColor.xyz * 0.65;
|
|
1693
|
+
}
|
|
1694
|
+
return environment_radiance(origin, direction);
|
|
1461
1695
|
}
|
|
1462
1696
|
|
|
1463
1697
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -1728,7 +1962,7 @@ fn make_ray(pixelIndex: u32) -> RayRecord {
|
|
|
1728
1962
|
}
|
|
1729
1963
|
|
|
1730
1964
|
fn make_miss(ray: RayRecord) -> HitRecord {
|
|
1731
|
-
let radiance =
|
|
1965
|
+
let radiance = gated_environment_radiance(ray.origin.xyz, ray.direction.xyz);
|
|
1732
1966
|
return HitRecord(
|
|
1733
1967
|
ray.rayId,
|
|
1734
1968
|
ray.sourcePixelId,
|
|
@@ -2214,6 +2448,21 @@ fn sample_emissive_triangle_direction(hit: HitRecord, seed: u32, fallback: vec3<
|
|
|
2214
2448
|
return safe_normalize(lightPoint - hit.position.xyz, fallback);
|
|
2215
2449
|
}
|
|
2216
2450
|
|
|
2451
|
+
fn sample_environment_portal_direction(hit: HitRecord, seed: u32, fallback: vec3<f32>) -> vec3<f32> {
|
|
2452
|
+
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
2453
|
+
return fallback;
|
|
2454
|
+
}
|
|
2455
|
+
let portalSlot = min(
|
|
2456
|
+
u32(random01(seed + 211u) * f32(config.environmentPortalCount)),
|
|
2457
|
+
config.environmentPortalCount - 1u
|
|
2458
|
+
);
|
|
2459
|
+
let portal = environmentPortals[portalSlot];
|
|
2460
|
+
let u = (random01(seed + 223u) * 2.0 - 1.0) * portal.tangent.w;
|
|
2461
|
+
let v = (random01(seed + 227u) * 2.0 - 1.0) * portal.bitangent.w;
|
|
2462
|
+
let portalTarget = portal.position.xyz + portal.tangent.xyz * u + portal.bitangent.xyz * v;
|
|
2463
|
+
return safe_normalize(portalTarget - hit.position.xyz, fallback);
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2217
2466
|
fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult {
|
|
2218
2467
|
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
2219
2468
|
if (hit.materialKind == 1u) {
|
|
@@ -2253,8 +2502,17 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
2253
2502
|
let canSampleLight = dot(hit.shadingNormal.xyz, guidedLight) > -0.04;
|
|
2254
2503
|
let guideProbability = select(0.38, 0.72, ray.bounce == 0u);
|
|
2255
2504
|
let useGuidedLight = canSampleLight && random01(seed + 37u) < guideProbability;
|
|
2505
|
+
let guidedPortal = sample_environment_portal_direction(hit, seed, randomDiffuse);
|
|
2506
|
+
let canSamplePortal = dot(hit.shadingNormal.xyz, guidedPortal) > -0.04;
|
|
2507
|
+
let useGuidedPortal =
|
|
2508
|
+
!useGuidedLight &&
|
|
2509
|
+
canSamplePortal &&
|
|
2510
|
+
config.environmentPortalCount > 0u &&
|
|
2511
|
+
config.environmentPortalMode > 0u &&
|
|
2512
|
+
random01(seed + 89u) < 0.58;
|
|
2513
|
+
let guidedDirection = select(randomDiffuse, guidedPortal, useGuidedPortal);
|
|
2256
2514
|
return ScatterResult(
|
|
2257
|
-
vec4<f32>(select(
|
|
2515
|
+
vec4<f32>(select(guidedDirection, guidedLight, useGuidedLight), 0.0),
|
|
2258
2516
|
select(0u, RAY_FLAG_GUIDED_EMISSIVE, useGuidedLight),
|
|
2259
2517
|
0u,
|
|
2260
2518
|
0u,
|
|
@@ -2461,6 +2719,29 @@ fn fragmentMain(in: VertexOut) -> @location(0) vec4<f32> {
|
|
|
2461
2719
|
return textureSample(renderTexture, renderSampler, in.uv);
|
|
2462
2720
|
}
|
|
2463
2721
|
`;
|
|
2722
|
+
function createWavefrontDeviceDescriptor(adapter, options = {}) {
|
|
2723
|
+
const requiredLimits = { ...options.requiredLimits ?? {} };
|
|
2724
|
+
const exposedStorageBufferLimit = Number(adapter?.limits?.maxStorageBuffersPerShaderStage);
|
|
2725
|
+
if (Number.isFinite(exposedStorageBufferLimit)) {
|
|
2726
|
+
if (exposedStorageBufferLimit < TRACE_STORAGE_BUFFER_BINDINGS) {
|
|
2727
|
+
throw new Error(
|
|
2728
|
+
`Wavefront mesh tracing requires maxStorageBuffersPerShaderStage>=${TRACE_STORAGE_BUFFER_BINDINGS}, but this adapter exposes ${exposedStorageBufferLimit}.`
|
|
2729
|
+
);
|
|
2730
|
+
}
|
|
2731
|
+
requiredLimits.maxStorageBuffersPerShaderStage = Math.max(
|
|
2732
|
+
Number(requiredLimits.maxStorageBuffersPerShaderStage ?? 0),
|
|
2733
|
+
TRACE_STORAGE_BUFFER_BINDINGS
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
const descriptor = { ...options.deviceDescriptor ?? {} };
|
|
2737
|
+
if (Object.keys(requiredLimits).length > 0) {
|
|
2738
|
+
descriptor.requiredLimits = {
|
|
2739
|
+
...descriptor.requiredLimits ?? {},
|
|
2740
|
+
...requiredLimits
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
return Object.keys(descriptor).length > 0 ? descriptor : void 0;
|
|
2744
|
+
}
|
|
2464
2745
|
async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
2465
2746
|
assertAnalyticDisplayQualityPolicy(options);
|
|
2466
2747
|
const constants = getGpuUsageConstants();
|
|
@@ -2479,7 +2760,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2479
2760
|
if (!adapter) {
|
|
2480
2761
|
throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
|
|
2481
2762
|
}
|
|
2482
|
-
const device = await adapter.requestDevice();
|
|
2763
|
+
const device = await adapter.requestDevice(createWavefrontDeviceDescriptor(adapter, options));
|
|
2483
2764
|
const context = canvas.getContext("webgpu");
|
|
2484
2765
|
if (!context || typeof context.configure !== "function") {
|
|
2485
2766
|
throw new Error("Canvas WebGPU context does not support configure().");
|
|
@@ -2552,23 +2833,34 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2552
2833
|
Math.max(1, config.gpuMeshSource.meshes.count) * MESH_RANGE_RECORD_BYTES,
|
|
2553
2834
|
"plasius.wavefront.meshRanges"
|
|
2554
2835
|
);
|
|
2836
|
+
const environmentPortalBuffer = createBuffer(
|
|
2837
|
+
device,
|
|
2838
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
2839
|
+
Math.max(1, config.environmentPortalCapacity) * ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
2840
|
+
"plasius.wavefront.environmentPortals"
|
|
2841
|
+
);
|
|
2555
2842
|
const bvhLeafRefBuffer = createBuffer(
|
|
2556
2843
|
device,
|
|
2557
2844
|
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
2558
2845
|
Math.max(1, config.bvhLeafSortCapacity) * BVH_LEAF_REF_RECORD_BYTES,
|
|
2559
2846
|
"plasius.wavefront.bvhLeafRefs"
|
|
2560
2847
|
);
|
|
2561
|
-
const
|
|
2562
|
-
device,
|
|
2563
|
-
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
2564
|
-
CONFIG_BUFFER_BYTES,
|
|
2565
|
-
"plasius.wavefront.frameConfig"
|
|
2566
|
-
);
|
|
2848
|
+
const tiles = createTiles(config.width, config.height, config.tileSize);
|
|
2567
2849
|
const uniformOffsetAlignment = Number(device?.limits?.minUniformBufferOffsetAlignment);
|
|
2568
2850
|
const configBufferStride = alignTo(
|
|
2569
2851
|
CONFIG_BUFFER_BYTES,
|
|
2570
2852
|
Number.isFinite(uniformOffsetAlignment) && uniformOffsetAlignment > 0 ? uniformOffsetAlignment : CONFIG_BUFFER_BYTES
|
|
2571
2853
|
);
|
|
2854
|
+
const frameConfigSlotCount = Math.max(
|
|
2855
|
+
1,
|
|
2856
|
+
tiles.length * config.samplesPerPixel + tiles.length + (config.denoise ? 1 : 0)
|
|
2857
|
+
);
|
|
2858
|
+
const configBuffer = createBuffer(
|
|
2859
|
+
device,
|
|
2860
|
+
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
2861
|
+
frameConfigSlotCount * configBufferStride,
|
|
2862
|
+
"plasius.wavefront.frameConfig"
|
|
2863
|
+
);
|
|
2572
2864
|
const bvhBuildConfigSlots = 1 + config.bvhSortStages.length + config.bvhBuildLevels.length;
|
|
2573
2865
|
const bvhBuildConfigBuffer = createBuffer(
|
|
2574
2866
|
device,
|
|
@@ -2602,6 +2894,11 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2602
2894
|
device.queue.writeBuffer(meshVertexBuffer, 0, config.gpuMeshSource.vertices.buffer);
|
|
2603
2895
|
device.queue.writeBuffer(meshIndexBuffer, 0, config.gpuMeshSource.indices.buffer);
|
|
2604
2896
|
device.queue.writeBuffer(meshRangeBuffer, 0, config.gpuMeshSource.meshes.buffer);
|
|
2897
|
+
const packedEnvironmentPortals = packEnvironmentPortals(
|
|
2898
|
+
config.environmentPortals,
|
|
2899
|
+
Math.max(1, config.environmentPortalCapacity)
|
|
2900
|
+
);
|
|
2901
|
+
device.queue.writeBuffer(environmentPortalBuffer, 0, packedEnvironmentPortals.buffer);
|
|
2605
2902
|
const radianceTexture = device.createTexture({
|
|
2606
2903
|
label: "plasius.wavefront.radiance",
|
|
2607
2904
|
size: { width: config.width, height: config.height },
|
|
@@ -2653,7 +2950,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2653
2950
|
binding: 16,
|
|
2654
2951
|
visibility: constants.shader.COMPUTE,
|
|
2655
2952
|
storageTexture: { access: "write-only", format: "rgba16float" }
|
|
2656
|
-
}
|
|
2953
|
+
},
|
|
2954
|
+
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } }
|
|
2657
2955
|
]
|
|
2658
2956
|
});
|
|
2659
2957
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -2826,7 +3124,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2826
3124
|
{ binding: 7, resource: outputView },
|
|
2827
3125
|
{ binding: 8, resource: { buffer: triangleBuffer } },
|
|
2828
3126
|
{ binding: 9, resource: { buffer: bvhNodeBuffer } },
|
|
2829
|
-
{ binding: 16, resource: radianceView }
|
|
3127
|
+
{ binding: 16, resource: radianceView },
|
|
3128
|
+
{ binding: 19, resource: { buffer: environmentPortalBuffer } }
|
|
2830
3129
|
]
|
|
2831
3130
|
});
|
|
2832
3131
|
}
|
|
@@ -2913,10 +3212,27 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2913
3212
|
]
|
|
2914
3213
|
});
|
|
2915
3214
|
let frame = 0;
|
|
2916
|
-
|
|
3215
|
+
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
3216
|
+
let accelerationBuildCount = 0;
|
|
3217
|
+
function createFrameConfigWriter(frameIndex) {
|
|
3218
|
+
let slot = 0;
|
|
3219
|
+
return (tile, buildRange = {}) => {
|
|
3220
|
+
if (slot >= frameConfigSlotCount) {
|
|
3221
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
3222
|
+
}
|
|
3223
|
+
const offset = slot * configBufferStride;
|
|
3224
|
+
slot += 1;
|
|
3225
|
+
device.queue.writeBuffer(
|
|
3226
|
+
configBuffer,
|
|
3227
|
+
offset,
|
|
3228
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
3229
|
+
);
|
|
3230
|
+
return offset;
|
|
3231
|
+
};
|
|
3232
|
+
}
|
|
2917
3233
|
function dispatchGpuAccelerationBuild(frameIndex) {
|
|
2918
|
-
if (!config.gpuAccelerationBuildRequired) {
|
|
2919
|
-
return;
|
|
3234
|
+
if (!config.gpuAccelerationBuildRequired || accelerationBuilt) {
|
|
3235
|
+
return false;
|
|
2920
3236
|
}
|
|
2921
3237
|
const buildTile = tiles[0] ?? { x: 0, y: 0, width: 1, height: 1 };
|
|
2922
3238
|
const encoder = device.createCommandEncoder({
|
|
@@ -2974,27 +3290,21 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2974
3290
|
}
|
|
2975
3291
|
passEncoder.end();
|
|
2976
3292
|
device.queue.submit([encoder.finish()]);
|
|
3293
|
+
accelerationBuilt = true;
|
|
3294
|
+
accelerationBuildCount += 1;
|
|
3295
|
+
return true;
|
|
2977
3296
|
}
|
|
2978
|
-
function
|
|
2979
|
-
const sampleWeight = 1 / config.samplesPerPixel;
|
|
2980
|
-
const configPayload = createConfigPayload(config, tile, frameIndex, {
|
|
2981
|
-
sampleIndex,
|
|
2982
|
-
sampleWeight
|
|
2983
|
-
});
|
|
2984
|
-
device.queue.writeBuffer(configBuffer, 0, configPayload);
|
|
2985
|
-
const encoder = device.createCommandEncoder({
|
|
2986
|
-
label: `plasius.wavefront.frame.${frameIndex}.tile.${tile.x}.${tile.y}.sample.${sampleIndex}`
|
|
2987
|
-
});
|
|
3297
|
+
function encodeTileSample(encoder, tile, configOffset) {
|
|
2988
3298
|
const passEncoder = encoder.beginComputePass({
|
|
2989
3299
|
label: "plasius.wavefront.computePass"
|
|
2990
3300
|
});
|
|
2991
3301
|
const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
|
|
2992
3302
|
const capacityWorkgroups = Math.ceil(config.tilePixelCapacity / WORKGROUP_SIZE);
|
|
2993
|
-
passEncoder.setBindGroup(0, bindGroups[0], [
|
|
3303
|
+
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
2994
3304
|
passEncoder.setPipeline(pipelines.generatePrimaryRays);
|
|
2995
3305
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
2996
3306
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
2997
|
-
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [
|
|
3307
|
+
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
2998
3308
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
2999
3309
|
passEncoder.dispatchWorkgroups(capacityWorkgroups);
|
|
3000
3310
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
@@ -3003,71 +3313,38 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3003
3313
|
passEncoder.dispatchWorkgroups(1);
|
|
3004
3314
|
}
|
|
3005
3315
|
passEncoder.end();
|
|
3006
|
-
device.queue.submit([encoder.finish()]);
|
|
3007
3316
|
}
|
|
3008
|
-
function
|
|
3009
|
-
const configPayload = createConfigPayload(config, tile, frameIndex, {
|
|
3010
|
-
sampleIndex: 0,
|
|
3011
|
-
sampleWeight: 1 / config.samplesPerPixel
|
|
3012
|
-
});
|
|
3013
|
-
device.queue.writeBuffer(configBuffer, 0, configPayload);
|
|
3014
|
-
const encoder = device.createCommandEncoder({
|
|
3015
|
-
label: `plasius.wavefront.frame.${frameIndex}.tile.${tile.x}.${tile.y}.output`
|
|
3016
|
-
});
|
|
3317
|
+
function encodeTileOutput(encoder, tile, configOffset) {
|
|
3017
3318
|
const passEncoder = encoder.beginComputePass({
|
|
3018
3319
|
label: "plasius.wavefront.outputPass"
|
|
3019
3320
|
});
|
|
3020
3321
|
const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
|
|
3021
|
-
passEncoder.setBindGroup(0, bindGroups[0], [
|
|
3322
|
+
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3022
3323
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
3023
3324
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
3024
3325
|
passEncoder.end();
|
|
3025
|
-
device.queue.submit([encoder.finish()]);
|
|
3026
|
-
}
|
|
3027
|
-
function dispatchTile(tile, frameIndex) {
|
|
3028
|
-
for (let sampleIndex = 0; sampleIndex < config.samplesPerPixel; sampleIndex += 1) {
|
|
3029
|
-
dispatchTileSample(tile, frameIndex, sampleIndex);
|
|
3030
|
-
}
|
|
3031
|
-
dispatchTileOutput(tile, frameIndex);
|
|
3032
3326
|
}
|
|
3033
|
-
function
|
|
3327
|
+
function encodeDenoise(encoder, configOffset) {
|
|
3034
3328
|
if (!config.denoise) {
|
|
3035
3329
|
return;
|
|
3036
3330
|
}
|
|
3037
|
-
device.queue.writeBuffer(
|
|
3038
|
-
configBuffer,
|
|
3039
|
-
0,
|
|
3040
|
-
createConfigPayload(
|
|
3041
|
-
config,
|
|
3042
|
-
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
3043
|
-
frameIndex,
|
|
3044
|
-
{ sampleIndex: 0, sampleWeight: 1 / config.samplesPerPixel }
|
|
3045
|
-
)
|
|
3046
|
-
);
|
|
3047
|
-
const encoder = device.createCommandEncoder({
|
|
3048
|
-
label: `plasius.wavefront.frame.${frameIndex}.denoise`
|
|
3049
|
-
});
|
|
3050
3331
|
const radiancePass = encoder.beginComputePass({
|
|
3051
3332
|
label: "plasius.wavefront.denoiseRadiancePass"
|
|
3052
3333
|
});
|
|
3053
|
-
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [
|
|
3334
|
+
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
3054
3335
|
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
3055
3336
|
radiancePass.dispatchWorkgroups(Math.ceil(config.width / 8), Math.ceil(config.height / 8));
|
|
3056
3337
|
radiancePass.end();
|
|
3057
3338
|
const resolvePass = encoder.beginComputePass({
|
|
3058
3339
|
label: "plasius.wavefront.denoiseResolvePass"
|
|
3059
3340
|
});
|
|
3060
|
-
resolvePass.setBindGroup(0, denoiseResolveBindGroup, [
|
|
3341
|
+
resolvePass.setBindGroup(0, denoiseResolveBindGroup, [configOffset]);
|
|
3061
3342
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
3062
3343
|
resolvePass.dispatchWorkgroups(Math.ceil(config.width / 8), Math.ceil(config.height / 8));
|
|
3063
3344
|
resolvePass.end();
|
|
3064
|
-
device.queue.submit([encoder.finish()]);
|
|
3065
3345
|
}
|
|
3066
|
-
function
|
|
3346
|
+
function encodePresent(encoder) {
|
|
3067
3347
|
const texture = context.getCurrentTexture();
|
|
3068
|
-
const encoder = device.createCommandEncoder({
|
|
3069
|
-
label: `plasius.wavefront.present.${frame}`
|
|
3070
|
-
});
|
|
3071
3348
|
const passEncoder = encoder.beginRenderPass({
|
|
3072
3349
|
label: "plasius.wavefront.presentPass",
|
|
3073
3350
|
colorAttachments: [
|
|
@@ -3083,16 +3360,42 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3083
3360
|
passEncoder.setBindGroup(0, presentBindGroup);
|
|
3084
3361
|
passEncoder.draw(3);
|
|
3085
3362
|
passEncoder.end();
|
|
3363
|
+
}
|
|
3364
|
+
function dispatchFrame(frameIndex) {
|
|
3365
|
+
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
3366
|
+
const encoder = device.createCommandEncoder({
|
|
3367
|
+
label: `plasius.wavefront.frame.${frameIndex}.batched`
|
|
3368
|
+
});
|
|
3369
|
+
for (const tile of tiles) {
|
|
3370
|
+
for (let sampleIndex = 0; sampleIndex < config.samplesPerPixel; sampleIndex += 1) {
|
|
3371
|
+
const configOffset = writeFrameConfig(tile, {
|
|
3372
|
+
sampleIndex,
|
|
3373
|
+
sampleWeight: 1 / config.samplesPerPixel
|
|
3374
|
+
});
|
|
3375
|
+
encodeTileSample(encoder, tile, configOffset);
|
|
3376
|
+
}
|
|
3377
|
+
const outputConfigOffset = writeFrameConfig(tile, {
|
|
3378
|
+
sampleIndex: 0,
|
|
3379
|
+
sampleWeight: 1 / config.samplesPerPixel
|
|
3380
|
+
});
|
|
3381
|
+
encodeTileOutput(encoder, tile, outputConfigOffset);
|
|
3382
|
+
}
|
|
3383
|
+
if (config.denoise) {
|
|
3384
|
+
const denoiseConfigOffset = writeFrameConfig(
|
|
3385
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
3386
|
+
{ sampleIndex: 0, sampleWeight: 1 / config.samplesPerPixel }
|
|
3387
|
+
);
|
|
3388
|
+
encodeDenoise(encoder, denoiseConfigOffset);
|
|
3389
|
+
}
|
|
3390
|
+
encodePresent(encoder);
|
|
3086
3391
|
device.queue.submit([encoder.finish()]);
|
|
3392
|
+
return 1;
|
|
3087
3393
|
}
|
|
3088
3394
|
function renderOnce() {
|
|
3089
3395
|
frame += 1;
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
}
|
|
3094
|
-
dispatchDenoise(frame + config.frameIndex);
|
|
3095
|
-
present();
|
|
3396
|
+
const frameIndex = frame + config.frameIndex;
|
|
3397
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex);
|
|
3398
|
+
const frameSubmissionCount = dispatchFrame(frameIndex);
|
|
3096
3399
|
return Object.freeze({
|
|
3097
3400
|
frame,
|
|
3098
3401
|
width: config.width,
|
|
@@ -3106,10 +3409,17 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3106
3409
|
sceneObjectCount: config.sceneObjectCount,
|
|
3107
3410
|
triangleCount: config.triangleCount,
|
|
3108
3411
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3412
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
3413
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
3109
3414
|
bvhNodeCount: config.bvhNodeCount,
|
|
3110
3415
|
displayQuality: config.displayQuality,
|
|
3111
3416
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
3112
3417
|
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
3418
|
+
accelerationBuildSubmitted,
|
|
3419
|
+
accelerationBuilt,
|
|
3420
|
+
accelerationBuildCount,
|
|
3421
|
+
commandSubmissions: frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
3422
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
3113
3423
|
memory: config.memory
|
|
3114
3424
|
});
|
|
3115
3425
|
}
|
|
@@ -3175,10 +3485,15 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3175
3485
|
sceneObjectCount: config.sceneObjectCount,
|
|
3176
3486
|
triangleCount: config.triangleCount,
|
|
3177
3487
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3488
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
3489
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
3178
3490
|
bvhNodeCount: config.bvhNodeCount,
|
|
3179
3491
|
displayQuality: config.displayQuality,
|
|
3180
3492
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
3181
3493
|
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
3494
|
+
accelerationBuilt,
|
|
3495
|
+
accelerationBuildCount,
|
|
3496
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
3182
3497
|
memory: config.memory
|
|
3183
3498
|
});
|
|
3184
3499
|
}
|
|
@@ -3193,6 +3508,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3193
3508
|
meshVertexBuffer.destroy?.();
|
|
3194
3509
|
meshIndexBuffer.destroy?.();
|
|
3195
3510
|
meshRangeBuffer.destroy?.();
|
|
3511
|
+
environmentPortalBuffer.destroy?.();
|
|
3196
3512
|
bvhLeafRefBuffer.destroy?.();
|
|
3197
3513
|
configBuffer.destroy?.();
|
|
3198
3514
|
bvhBuildConfigBuffer.destroy?.();
|