@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.cjs
CHANGED
|
@@ -69,6 +69,7 @@ var DEFAULT_MAX_DEPTH = 6;
|
|
|
69
69
|
var DEFAULT_TILE_SIZE = 128;
|
|
70
70
|
var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
71
71
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
72
|
+
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
72
73
|
var WORKGROUP_SIZE = 64;
|
|
73
74
|
var RAY_RECORD_BYTES = 80;
|
|
74
75
|
var HIT_RECORD_BYTES = 208;
|
|
@@ -79,9 +80,11 @@ var TRIANGLE_RECORD_BYTES = 208;
|
|
|
79
80
|
var BVH_NODE_RECORD_BYTES = 48;
|
|
80
81
|
var BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
81
82
|
var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
83
|
+
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
82
84
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
83
|
-
var CONFIG_BUFFER_BYTES =
|
|
85
|
+
var CONFIG_BUFFER_BYTES = 272;
|
|
84
86
|
var COUNTER_BUFFER_BYTES = 16;
|
|
87
|
+
var TRACE_STORAGE_BUFFER_BINDINGS = 9;
|
|
85
88
|
var MATERIAL_DIFFUSE = 0;
|
|
86
89
|
var MATERIAL_METAL = 1;
|
|
87
90
|
var MATERIAL_DIELECTRIC = 2;
|
|
@@ -108,6 +111,7 @@ var DEFAULT_ENVIRONMENT_LIGHTING = Object.freeze({
|
|
|
108
111
|
});
|
|
109
112
|
var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
110
113
|
workgroupSize: WORKGROUP_SIZE,
|
|
114
|
+
traceStorageBufferBindings: TRACE_STORAGE_BUFFER_BINDINGS,
|
|
111
115
|
rayRecordBytes: RAY_RECORD_BYTES,
|
|
112
116
|
hitRecordBytes: HIT_RECORD_BYTES,
|
|
113
117
|
sceneObjectRecordBytes: SCENE_OBJECT_RECORD_BYTES,
|
|
@@ -118,6 +122,7 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
118
122
|
bvhLeafReferenceRecordBytes: BVH_LEAF_REF_RECORD_BYTES,
|
|
119
123
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
120
124
|
emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
125
|
+
environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
121
126
|
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES
|
|
122
127
|
});
|
|
123
128
|
var wavefrontSceneObjectKinds = Object.freeze({
|
|
@@ -209,6 +214,9 @@ function subtract(a, b) {
|
|
|
209
214
|
function scale(a, scalar) {
|
|
210
215
|
return [a[0] * scalar, a[1] * scalar, a[2] * scalar];
|
|
211
216
|
}
|
|
217
|
+
function dot(a, b) {
|
|
218
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
219
|
+
}
|
|
212
220
|
function cross(a, b) {
|
|
213
221
|
return [
|
|
214
222
|
a[1] * b[2] - a[2] * b[1],
|
|
@@ -821,6 +829,127 @@ function resolveEnvironmentLighting(input, environmentColor, ambientColor) {
|
|
|
821
829
|
exposure: Math.max(1e-4, readFiniteNumber("environmentLighting.exposure", source.exposure, DEFAULT_ENVIRONMENT_LIGHTING.exposure))
|
|
822
830
|
});
|
|
823
831
|
}
|
|
832
|
+
function resolveEnvironmentPortalMode(value, hasPortals) {
|
|
833
|
+
if (value === void 0 || value === null) {
|
|
834
|
+
return hasPortals ? 2 : 0;
|
|
835
|
+
}
|
|
836
|
+
if (Number.isInteger(value) && value >= 0 && value <= 2) {
|
|
837
|
+
return value;
|
|
838
|
+
}
|
|
839
|
+
if (value === "disabled") {
|
|
840
|
+
return 0;
|
|
841
|
+
}
|
|
842
|
+
if (value === "guide") {
|
|
843
|
+
return 1;
|
|
844
|
+
}
|
|
845
|
+
if (value === "guide-and-gate" || value === "gate") {
|
|
846
|
+
return 2;
|
|
847
|
+
}
|
|
848
|
+
throw new Error(
|
|
849
|
+
"environmentPortalMode must be disabled, guide, guide-and-gate, or an integer between 0 and 2."
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
function orthogonalPortalTangent(normal) {
|
|
853
|
+
if (Math.abs(normal[1]) < 0.92) {
|
|
854
|
+
return normalize(cross([0, 1, 0], normal), [1, 0, 0]);
|
|
855
|
+
}
|
|
856
|
+
return normalize(cross([1, 0, 0], normal), [0, 0, 1]);
|
|
857
|
+
}
|
|
858
|
+
function resolvePortalTangent(value, normal) {
|
|
859
|
+
const fallback = orthogonalPortalTangent(normal);
|
|
860
|
+
const tangent = asUnitVec3(value, fallback);
|
|
861
|
+
const projected = subtract(tangent, scale(normal, dot(tangent, normal)));
|
|
862
|
+
return normalize(projected, fallback);
|
|
863
|
+
}
|
|
864
|
+
function readPositiveFiniteNumber(name, value, fallback) {
|
|
865
|
+
const numeric = readFiniteNumber(name, value, fallback);
|
|
866
|
+
if (numeric <= 0) {
|
|
867
|
+
throw new Error(`${name} must be a positive finite number.`);
|
|
868
|
+
}
|
|
869
|
+
return numeric;
|
|
870
|
+
}
|
|
871
|
+
function readPortalExtent(name, value, halfName, halfValue) {
|
|
872
|
+
if (value !== void 0 && value !== null) {
|
|
873
|
+
return readPositiveFiniteNumber(name, value, 1);
|
|
874
|
+
}
|
|
875
|
+
return readPositiveFiniteNumber(halfName, halfValue, 0.5) * 2;
|
|
876
|
+
}
|
|
877
|
+
function normalizeEnvironmentPortal(portal, index) {
|
|
878
|
+
if (!portal || typeof portal !== "object") {
|
|
879
|
+
throw new Error(`environmentPortals[${index}] must be an object.`);
|
|
880
|
+
}
|
|
881
|
+
const shape = portal.shape ?? portal.kind ?? "rectangle";
|
|
882
|
+
if (shape !== "rectangle") {
|
|
883
|
+
throw new Error(`environmentPortals[${index}].shape must be "rectangle".`);
|
|
884
|
+
}
|
|
885
|
+
const position = asVec3(portal.position ?? portal.center, [0, 0, 0]);
|
|
886
|
+
const normal = asUnitVec3(portal.normal, [0, 0, 1]);
|
|
887
|
+
const tangent = resolvePortalTangent(portal.tangent, normal);
|
|
888
|
+
const bitangent = normalize(cross(normal, tangent), [0, 1, 0]);
|
|
889
|
+
const width = readPortalExtent(
|
|
890
|
+
`environmentPortals[${index}].width`,
|
|
891
|
+
portal.width,
|
|
892
|
+
`environmentPortals[${index}].halfWidth`,
|
|
893
|
+
portal.halfWidth
|
|
894
|
+
);
|
|
895
|
+
const height = readPortalExtent(
|
|
896
|
+
`environmentPortals[${index}].height`,
|
|
897
|
+
portal.height,
|
|
898
|
+
`environmentPortals[${index}].halfHeight`,
|
|
899
|
+
portal.halfHeight
|
|
900
|
+
);
|
|
901
|
+
const radianceScale = Math.max(
|
|
902
|
+
0,
|
|
903
|
+
readFiniteNumber(
|
|
904
|
+
`environmentPortals[${index}].radianceScale`,
|
|
905
|
+
portal.radianceScale ?? portal.intensity,
|
|
906
|
+
1
|
|
907
|
+
)
|
|
908
|
+
);
|
|
909
|
+
return Object.freeze({
|
|
910
|
+
kind: 1,
|
|
911
|
+
flags: portal.twoSided === false ? 0 : 1,
|
|
912
|
+
position: Object.freeze([position[0], position[1], position[2], width * height]),
|
|
913
|
+
normal: Object.freeze([normal[0], normal[1], normal[2], radianceScale]),
|
|
914
|
+
tangent: Object.freeze([tangent[0], tangent[1], tangent[2], width * 0.5]),
|
|
915
|
+
bitangent: Object.freeze([bitangent[0], bitangent[1], bitangent[2], height * 0.5]),
|
|
916
|
+
color: Object.freeze(asColor(portal.color, [1, 1, 1, 1]))
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
function normalizeEnvironmentPortals(value) {
|
|
920
|
+
if (value === void 0 || value === null) {
|
|
921
|
+
return Object.freeze([]);
|
|
922
|
+
}
|
|
923
|
+
if (!Array.isArray(value)) {
|
|
924
|
+
throw new Error("environmentPortals must be an array when provided.");
|
|
925
|
+
}
|
|
926
|
+
return Object.freeze(value.map(normalizeEnvironmentPortal));
|
|
927
|
+
}
|
|
928
|
+
function packEnvironmentPortals(portals, capacity) {
|
|
929
|
+
const bytes = new ArrayBuffer(capacity * ENVIRONMENT_PORTAL_RECORD_BYTES);
|
|
930
|
+
const data = new DataView(bytes);
|
|
931
|
+
const floatView = new Float32Array(bytes);
|
|
932
|
+
portals.forEach((portal, index) => {
|
|
933
|
+
const byteOffset = index * ENVIRONMENT_PORTAL_RECORD_BYTES;
|
|
934
|
+
const floatOffset = byteOffset / Float32Array.BYTES_PER_ELEMENT;
|
|
935
|
+
data.setUint32(byteOffset, portal.kind, true);
|
|
936
|
+
data.setUint32(byteOffset + 4, portal.flags, true);
|
|
937
|
+
data.setUint32(byteOffset + 8, 0, true);
|
|
938
|
+
data.setUint32(byteOffset + 12, 0, true);
|
|
939
|
+
writeVec4(floatView, floatOffset + 4, portal.position);
|
|
940
|
+
writeVec4(floatView, floatOffset + 8, portal.normal);
|
|
941
|
+
writeVec4(floatView, floatOffset + 12, portal.tangent);
|
|
942
|
+
writeVec4(floatView, floatOffset + 16, portal.bitangent);
|
|
943
|
+
writeVec4(floatView, floatOffset + 20, portal.color);
|
|
944
|
+
});
|
|
945
|
+
return Object.freeze({
|
|
946
|
+
buffer: bytes,
|
|
947
|
+
portals,
|
|
948
|
+
count: portals.length,
|
|
949
|
+
capacity,
|
|
950
|
+
recordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES
|
|
951
|
+
});
|
|
952
|
+
}
|
|
824
953
|
function getCanvasDimension(canvas, key, fallback) {
|
|
825
954
|
const value = Number(canvas?.[key]);
|
|
826
955
|
if (Number.isFinite(value) && value > 0) {
|
|
@@ -876,6 +1005,11 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
876
1005
|
options.emissiveTriangleCapacity,
|
|
877
1006
|
0
|
|
878
1007
|
);
|
|
1008
|
+
const environmentPortalCapacity = readNonNegativeInteger(
|
|
1009
|
+
"environmentPortalCapacity",
|
|
1010
|
+
options.environmentPortalCapacity,
|
|
1011
|
+
0
|
|
1012
|
+
);
|
|
879
1013
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
880
1014
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
881
1015
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
@@ -884,6 +1018,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
884
1018
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
885
1019
|
const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
|
|
886
1020
|
const emissiveTriangleMetadataBytes = emissiveTriangleCapacity * BVH_NODE_RECORD_BYTES;
|
|
1021
|
+
const environmentPortalBytes = environmentPortalCapacity * ENVIRONMENT_PORTAL_RECORD_BYTES;
|
|
887
1022
|
return Object.freeze({
|
|
888
1023
|
queueBytes,
|
|
889
1024
|
queuePairBytes: queueBytes * 2,
|
|
@@ -894,9 +1029,10 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
894
1029
|
bvhNodeBytes,
|
|
895
1030
|
bvhLeafReferenceBytes,
|
|
896
1031
|
emissiveTriangleMetadataBytes,
|
|
1032
|
+
environmentPortalBytes,
|
|
897
1033
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
898
1034
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
899
|
-
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES
|
|
1035
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES
|
|
900
1036
|
});
|
|
901
1037
|
}
|
|
902
1038
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -957,6 +1093,21 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
957
1093
|
environmentColor,
|
|
958
1094
|
ambientColor
|
|
959
1095
|
);
|
|
1096
|
+
const environmentPortals = normalizeEnvironmentPortals(
|
|
1097
|
+
options.environmentPortals ?? options.environmentLightPortals ?? options.environmentLighting?.environmentPortals
|
|
1098
|
+
);
|
|
1099
|
+
const environmentPortalCapacity = Math.max(
|
|
1100
|
+
environmentPortals.length,
|
|
1101
|
+
readNonNegativeInteger(
|
|
1102
|
+
"environmentPortalCapacity",
|
|
1103
|
+
options.environmentPortalCapacity,
|
|
1104
|
+
DEFAULT_ENVIRONMENT_PORTAL_CAPACITY
|
|
1105
|
+
)
|
|
1106
|
+
);
|
|
1107
|
+
const environmentPortalMode = resolveEnvironmentPortalMode(
|
|
1108
|
+
options.environmentPortalMode ?? options.portalMode ?? options.environmentLighting?.environmentPortalMode,
|
|
1109
|
+
environmentPortals.length > 0
|
|
1110
|
+
);
|
|
960
1111
|
return Object.freeze({
|
|
961
1112
|
width,
|
|
962
1113
|
height,
|
|
@@ -985,6 +1136,10 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
985
1136
|
environmentColor: environmentLighting.environmentColor,
|
|
986
1137
|
ambientColor: environmentLighting.ambientColor,
|
|
987
1138
|
environmentLighting,
|
|
1139
|
+
environmentPortals,
|
|
1140
|
+
environmentPortalCount: environmentPortals.length,
|
|
1141
|
+
environmentPortalCapacity,
|
|
1142
|
+
environmentPortalMode,
|
|
988
1143
|
displayQuality: options.displayQuality === true,
|
|
989
1144
|
requiresMeshBvhForDisplayQuality: true,
|
|
990
1145
|
denoise: options.denoise !== false,
|
|
@@ -995,7 +1150,8 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
995
1150
|
triangleCapacity,
|
|
996
1151
|
bvhNodeCapacity,
|
|
997
1152
|
bvhLeafSortCapacity,
|
|
998
|
-
emissiveTriangleCapacity: emissiveTriangleIndices.capacity
|
|
1153
|
+
emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
|
|
1154
|
+
environmentPortalCapacity
|
|
999
1155
|
})
|
|
1000
1156
|
});
|
|
1001
1157
|
}
|
|
@@ -1179,6 +1335,10 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1179
1335
|
data.setUint32(244, buildRange.count ?? 0, true);
|
|
1180
1336
|
data.setUint32(248, buildRange.sortItemCount ?? 0, true);
|
|
1181
1337
|
data.setUint32(252, config.emissiveTriangleCount ?? 0, true);
|
|
1338
|
+
data.setUint32(256, config.environmentPortalCount ?? 0, true);
|
|
1339
|
+
data.setUint32(260, config.environmentPortalMode ?? 0, true);
|
|
1340
|
+
data.setUint32(264, 0, true);
|
|
1341
|
+
data.setUint32(268, 0, true);
|
|
1182
1342
|
return bytes;
|
|
1183
1343
|
}
|
|
1184
1344
|
function createTiles(width, height, tileSize) {
|
|
@@ -1425,6 +1585,10 @@ struct FrameConfig {
|
|
|
1425
1585
|
bvhBuildNodeCount: u32,
|
|
1426
1586
|
bvhSortItemCount: u32,
|
|
1427
1587
|
emissiveTriangleCount: u32,
|
|
1588
|
+
environmentPortalCount: u32,
|
|
1589
|
+
environmentPortalMode: u32,
|
|
1590
|
+
_portalPad0: u32,
|
|
1591
|
+
_portalPad1: u32,
|
|
1428
1592
|
};
|
|
1429
1593
|
|
|
1430
1594
|
struct Counters {
|
|
@@ -1448,6 +1612,18 @@ struct Candidate {
|
|
|
1448
1612
|
mediumRefId: u32,
|
|
1449
1613
|
};
|
|
1450
1614
|
|
|
1615
|
+
struct EnvironmentPortal {
|
|
1616
|
+
kind: u32,
|
|
1617
|
+
flags: u32,
|
|
1618
|
+
_pad0: u32,
|
|
1619
|
+
_pad1: u32,
|
|
1620
|
+
position: vec4<f32>,
|
|
1621
|
+
normal: vec4<f32>,
|
|
1622
|
+
tangent: vec4<f32>,
|
|
1623
|
+
bitangent: vec4<f32>,
|
|
1624
|
+
color: vec4<f32>,
|
|
1625
|
+
};
|
|
1626
|
+
|
|
1451
1627
|
@group(0) @binding(0) var<storage, read_write> activeQueue: array<RayRecord>;
|
|
1452
1628
|
@group(0) @binding(1) var<storage, read_write> nextQueue: array<RayRecord>;
|
|
1453
1629
|
@group(0) @binding(2) var<storage, read_write> hits: array<HitRecord>;
|
|
@@ -1467,6 +1643,7 @@ struct Candidate {
|
|
|
1467
1643
|
@group(0) @binding(16) var radianceImage: texture_storage_2d<rgba16float, write>;
|
|
1468
1644
|
@group(0) @binding(17) var finalDenoiseInputRadiance: texture_2d<f32>;
|
|
1469
1645
|
@group(0) @binding(18) var denoisedOutputImage: texture_storage_2d<rgba8unorm, write>;
|
|
1646
|
+
@group(0) @binding(19) var<storage, read> environmentPortals: array<EnvironmentPortal>;
|
|
1470
1647
|
|
|
1471
1648
|
fn hash_u32(value: u32) -> u32 {
|
|
1472
1649
|
var x = value;
|
|
@@ -1507,7 +1684,48 @@ fn saturate(value: f32) -> f32 {
|
|
|
1507
1684
|
return clamp(value, 0.0, 1.0);
|
|
1508
1685
|
}
|
|
1509
1686
|
|
|
1510
|
-
fn
|
|
1687
|
+
fn max_component(value: vec3<f32>) -> f32 {
|
|
1688
|
+
return max(max(value.x, value.y), value.z);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
fn environment_portal_radiance_scale(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1692
|
+
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
1693
|
+
return vec3<f32>(1.0);
|
|
1694
|
+
}
|
|
1695
|
+
var scale = vec3<f32>(0.0);
|
|
1696
|
+
for (var portalIndex = 0u; portalIndex < config.environmentPortalCount; portalIndex = portalIndex + 1u) {
|
|
1697
|
+
let portal = environmentPortals[portalIndex];
|
|
1698
|
+
if (portal.kind == 1u) {
|
|
1699
|
+
let portalNormal = safe_normalize(portal.normal.xyz, vec3<f32>(0.0, 0.0, 1.0));
|
|
1700
|
+
let denominator = dot(direction, portalNormal);
|
|
1701
|
+
let twoSided = (portal.flags & 1u) != 0u;
|
|
1702
|
+
var facing = abs(denominator) > 0.0001;
|
|
1703
|
+
if (!twoSided && denominator <= 0.0001) {
|
|
1704
|
+
facing = false;
|
|
1705
|
+
}
|
|
1706
|
+
if (facing) {
|
|
1707
|
+
let distance = dot(portal.position.xyz - origin, portalNormal) / denominator;
|
|
1708
|
+
if (distance > 0.001) {
|
|
1709
|
+
let hitPosition = origin + direction * distance;
|
|
1710
|
+
let local = hitPosition - portal.position.xyz;
|
|
1711
|
+
let tangent = safe_normalize(portal.tangent.xyz, vec3<f32>(1.0, 0.0, 0.0));
|
|
1712
|
+
let bitangent = safe_normalize(portal.bitangent.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
1713
|
+
let u = dot(local, tangent);
|
|
1714
|
+
let v = dot(local, bitangent);
|
|
1715
|
+
if (abs(u) <= portal.tangent.w && abs(v) <= portal.bitangent.w) {
|
|
1716
|
+
let areaWeight = clamp(sqrt(max(portal.position.w, 0.0001)), 0.25, 4.0);
|
|
1717
|
+
let angleWeight = max(abs(denominator), 0.08);
|
|
1718
|
+
let portalScale = portal.color.rgb * portal.normal.w * portal.color.a * areaWeight * angleWeight;
|
|
1719
|
+
scale = max(scale, portalScale);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return scale;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1511
1729
|
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
1512
1730
|
let upFactor = saturate(rayDirection.y * 0.5 + 0.5);
|
|
1513
1731
|
let sunDirection = safe_normalize(
|
|
@@ -1518,10 +1736,26 @@ fn environment_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
|
1518
1736
|
let gradient =
|
|
1519
1737
|
config.environmentHorizonColor.xyz * (1.0 - upFactor) +
|
|
1520
1738
|
config.environmentZenithColor.xyz * upFactor;
|
|
1739
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
1740
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
1521
1741
|
return (
|
|
1522
1742
|
gradient +
|
|
1523
1743
|
config.environmentSunColor.xyz * sunGlow
|
|
1524
|
-
) *
|
|
1744
|
+
) *
|
|
1745
|
+
max(config.environmentSunDirectionIntensity.w, 0.0001) *
|
|
1746
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1750
|
+
let portalScale = environment_portal_radiance_scale(origin, safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0)));
|
|
1751
|
+
if (
|
|
1752
|
+
config.environmentPortalCount > 0u &&
|
|
1753
|
+
config.environmentPortalMode == 2u &&
|
|
1754
|
+
max_component(portalScale) <= 0.0001
|
|
1755
|
+
) {
|
|
1756
|
+
return config.ambientColor.xyz * 0.65;
|
|
1757
|
+
}
|
|
1758
|
+
return environment_radiance(origin, direction);
|
|
1525
1759
|
}
|
|
1526
1760
|
|
|
1527
1761
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -1792,7 +2026,7 @@ fn make_ray(pixelIndex: u32) -> RayRecord {
|
|
|
1792
2026
|
}
|
|
1793
2027
|
|
|
1794
2028
|
fn make_miss(ray: RayRecord) -> HitRecord {
|
|
1795
|
-
let radiance =
|
|
2029
|
+
let radiance = gated_environment_radiance(ray.origin.xyz, ray.direction.xyz);
|
|
1796
2030
|
return HitRecord(
|
|
1797
2031
|
ray.rayId,
|
|
1798
2032
|
ray.sourcePixelId,
|
|
@@ -2278,6 +2512,21 @@ fn sample_emissive_triangle_direction(hit: HitRecord, seed: u32, fallback: vec3<
|
|
|
2278
2512
|
return safe_normalize(lightPoint - hit.position.xyz, fallback);
|
|
2279
2513
|
}
|
|
2280
2514
|
|
|
2515
|
+
fn sample_environment_portal_direction(hit: HitRecord, seed: u32, fallback: vec3<f32>) -> vec3<f32> {
|
|
2516
|
+
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
2517
|
+
return fallback;
|
|
2518
|
+
}
|
|
2519
|
+
let portalSlot = min(
|
|
2520
|
+
u32(random01(seed + 211u) * f32(config.environmentPortalCount)),
|
|
2521
|
+
config.environmentPortalCount - 1u
|
|
2522
|
+
);
|
|
2523
|
+
let portal = environmentPortals[portalSlot];
|
|
2524
|
+
let u = (random01(seed + 223u) * 2.0 - 1.0) * portal.tangent.w;
|
|
2525
|
+
let v = (random01(seed + 227u) * 2.0 - 1.0) * portal.bitangent.w;
|
|
2526
|
+
let portalTarget = portal.position.xyz + portal.tangent.xyz * u + portal.bitangent.xyz * v;
|
|
2527
|
+
return safe_normalize(portalTarget - hit.position.xyz, fallback);
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2281
2530
|
fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult {
|
|
2282
2531
|
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
2283
2532
|
if (hit.materialKind == 1u) {
|
|
@@ -2317,8 +2566,17 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
2317
2566
|
let canSampleLight = dot(hit.shadingNormal.xyz, guidedLight) > -0.04;
|
|
2318
2567
|
let guideProbability = select(0.38, 0.72, ray.bounce == 0u);
|
|
2319
2568
|
let useGuidedLight = canSampleLight && random01(seed + 37u) < guideProbability;
|
|
2569
|
+
let guidedPortal = sample_environment_portal_direction(hit, seed, randomDiffuse);
|
|
2570
|
+
let canSamplePortal = dot(hit.shadingNormal.xyz, guidedPortal) > -0.04;
|
|
2571
|
+
let useGuidedPortal =
|
|
2572
|
+
!useGuidedLight &&
|
|
2573
|
+
canSamplePortal &&
|
|
2574
|
+
config.environmentPortalCount > 0u &&
|
|
2575
|
+
config.environmentPortalMode > 0u &&
|
|
2576
|
+
random01(seed + 89u) < 0.58;
|
|
2577
|
+
let guidedDirection = select(randomDiffuse, guidedPortal, useGuidedPortal);
|
|
2320
2578
|
return ScatterResult(
|
|
2321
|
-
vec4<f32>(select(
|
|
2579
|
+
vec4<f32>(select(guidedDirection, guidedLight, useGuidedLight), 0.0),
|
|
2322
2580
|
select(0u, RAY_FLAG_GUIDED_EMISSIVE, useGuidedLight),
|
|
2323
2581
|
0u,
|
|
2324
2582
|
0u,
|
|
@@ -2525,6 +2783,29 @@ fn fragmentMain(in: VertexOut) -> @location(0) vec4<f32> {
|
|
|
2525
2783
|
return textureSample(renderTexture, renderSampler, in.uv);
|
|
2526
2784
|
}
|
|
2527
2785
|
`;
|
|
2786
|
+
function createWavefrontDeviceDescriptor(adapter, options = {}) {
|
|
2787
|
+
const requiredLimits = { ...options.requiredLimits ?? {} };
|
|
2788
|
+
const exposedStorageBufferLimit = Number(adapter?.limits?.maxStorageBuffersPerShaderStage);
|
|
2789
|
+
if (Number.isFinite(exposedStorageBufferLimit)) {
|
|
2790
|
+
if (exposedStorageBufferLimit < TRACE_STORAGE_BUFFER_BINDINGS) {
|
|
2791
|
+
throw new Error(
|
|
2792
|
+
`Wavefront mesh tracing requires maxStorageBuffersPerShaderStage>=${TRACE_STORAGE_BUFFER_BINDINGS}, but this adapter exposes ${exposedStorageBufferLimit}.`
|
|
2793
|
+
);
|
|
2794
|
+
}
|
|
2795
|
+
requiredLimits.maxStorageBuffersPerShaderStage = Math.max(
|
|
2796
|
+
Number(requiredLimits.maxStorageBuffersPerShaderStage ?? 0),
|
|
2797
|
+
TRACE_STORAGE_BUFFER_BINDINGS
|
|
2798
|
+
);
|
|
2799
|
+
}
|
|
2800
|
+
const descriptor = { ...options.deviceDescriptor ?? {} };
|
|
2801
|
+
if (Object.keys(requiredLimits).length > 0) {
|
|
2802
|
+
descriptor.requiredLimits = {
|
|
2803
|
+
...descriptor.requiredLimits ?? {},
|
|
2804
|
+
...requiredLimits
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
return Object.keys(descriptor).length > 0 ? descriptor : void 0;
|
|
2808
|
+
}
|
|
2528
2809
|
async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
2529
2810
|
assertAnalyticDisplayQualityPolicy(options);
|
|
2530
2811
|
const constants = getGpuUsageConstants();
|
|
@@ -2543,7 +2824,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2543
2824
|
if (!adapter) {
|
|
2544
2825
|
throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
|
|
2545
2826
|
}
|
|
2546
|
-
const device = await adapter.requestDevice();
|
|
2827
|
+
const device = await adapter.requestDevice(createWavefrontDeviceDescriptor(adapter, options));
|
|
2547
2828
|
const context = canvas.getContext("webgpu");
|
|
2548
2829
|
if (!context || typeof context.configure !== "function") {
|
|
2549
2830
|
throw new Error("Canvas WebGPU context does not support configure().");
|
|
@@ -2616,23 +2897,34 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2616
2897
|
Math.max(1, config.gpuMeshSource.meshes.count) * MESH_RANGE_RECORD_BYTES,
|
|
2617
2898
|
"plasius.wavefront.meshRanges"
|
|
2618
2899
|
);
|
|
2900
|
+
const environmentPortalBuffer = createBuffer(
|
|
2901
|
+
device,
|
|
2902
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
2903
|
+
Math.max(1, config.environmentPortalCapacity) * ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
2904
|
+
"plasius.wavefront.environmentPortals"
|
|
2905
|
+
);
|
|
2619
2906
|
const bvhLeafRefBuffer = createBuffer(
|
|
2620
2907
|
device,
|
|
2621
2908
|
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
2622
2909
|
Math.max(1, config.bvhLeafSortCapacity) * BVH_LEAF_REF_RECORD_BYTES,
|
|
2623
2910
|
"plasius.wavefront.bvhLeafRefs"
|
|
2624
2911
|
);
|
|
2625
|
-
const
|
|
2626
|
-
device,
|
|
2627
|
-
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
2628
|
-
CONFIG_BUFFER_BYTES,
|
|
2629
|
-
"plasius.wavefront.frameConfig"
|
|
2630
|
-
);
|
|
2912
|
+
const tiles = createTiles(config.width, config.height, config.tileSize);
|
|
2631
2913
|
const uniformOffsetAlignment = Number(device?.limits?.minUniformBufferOffsetAlignment);
|
|
2632
2914
|
const configBufferStride = alignTo(
|
|
2633
2915
|
CONFIG_BUFFER_BYTES,
|
|
2634
2916
|
Number.isFinite(uniformOffsetAlignment) && uniformOffsetAlignment > 0 ? uniformOffsetAlignment : CONFIG_BUFFER_BYTES
|
|
2635
2917
|
);
|
|
2918
|
+
const frameConfigSlotCount = Math.max(
|
|
2919
|
+
1,
|
|
2920
|
+
tiles.length * config.samplesPerPixel + tiles.length + (config.denoise ? 1 : 0)
|
|
2921
|
+
);
|
|
2922
|
+
const configBuffer = createBuffer(
|
|
2923
|
+
device,
|
|
2924
|
+
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
2925
|
+
frameConfigSlotCount * configBufferStride,
|
|
2926
|
+
"plasius.wavefront.frameConfig"
|
|
2927
|
+
);
|
|
2636
2928
|
const bvhBuildConfigSlots = 1 + config.bvhSortStages.length + config.bvhBuildLevels.length;
|
|
2637
2929
|
const bvhBuildConfigBuffer = createBuffer(
|
|
2638
2930
|
device,
|
|
@@ -2666,6 +2958,11 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2666
2958
|
device.queue.writeBuffer(meshVertexBuffer, 0, config.gpuMeshSource.vertices.buffer);
|
|
2667
2959
|
device.queue.writeBuffer(meshIndexBuffer, 0, config.gpuMeshSource.indices.buffer);
|
|
2668
2960
|
device.queue.writeBuffer(meshRangeBuffer, 0, config.gpuMeshSource.meshes.buffer);
|
|
2961
|
+
const packedEnvironmentPortals = packEnvironmentPortals(
|
|
2962
|
+
config.environmentPortals,
|
|
2963
|
+
Math.max(1, config.environmentPortalCapacity)
|
|
2964
|
+
);
|
|
2965
|
+
device.queue.writeBuffer(environmentPortalBuffer, 0, packedEnvironmentPortals.buffer);
|
|
2669
2966
|
const radianceTexture = device.createTexture({
|
|
2670
2967
|
label: "plasius.wavefront.radiance",
|
|
2671
2968
|
size: { width: config.width, height: config.height },
|
|
@@ -2717,7 +3014,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2717
3014
|
binding: 16,
|
|
2718
3015
|
visibility: constants.shader.COMPUTE,
|
|
2719
3016
|
storageTexture: { access: "write-only", format: "rgba16float" }
|
|
2720
|
-
}
|
|
3017
|
+
},
|
|
3018
|
+
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } }
|
|
2721
3019
|
]
|
|
2722
3020
|
});
|
|
2723
3021
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -2890,7 +3188,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2890
3188
|
{ binding: 7, resource: outputView },
|
|
2891
3189
|
{ binding: 8, resource: { buffer: triangleBuffer } },
|
|
2892
3190
|
{ binding: 9, resource: { buffer: bvhNodeBuffer } },
|
|
2893
|
-
{ binding: 16, resource: radianceView }
|
|
3191
|
+
{ binding: 16, resource: radianceView },
|
|
3192
|
+
{ binding: 19, resource: { buffer: environmentPortalBuffer } }
|
|
2894
3193
|
]
|
|
2895
3194
|
});
|
|
2896
3195
|
}
|
|
@@ -2977,10 +3276,27 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2977
3276
|
]
|
|
2978
3277
|
});
|
|
2979
3278
|
let frame = 0;
|
|
2980
|
-
|
|
3279
|
+
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
3280
|
+
let accelerationBuildCount = 0;
|
|
3281
|
+
function createFrameConfigWriter(frameIndex) {
|
|
3282
|
+
let slot = 0;
|
|
3283
|
+
return (tile, buildRange = {}) => {
|
|
3284
|
+
if (slot >= frameConfigSlotCount) {
|
|
3285
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
3286
|
+
}
|
|
3287
|
+
const offset = slot * configBufferStride;
|
|
3288
|
+
slot += 1;
|
|
3289
|
+
device.queue.writeBuffer(
|
|
3290
|
+
configBuffer,
|
|
3291
|
+
offset,
|
|
3292
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
3293
|
+
);
|
|
3294
|
+
return offset;
|
|
3295
|
+
};
|
|
3296
|
+
}
|
|
2981
3297
|
function dispatchGpuAccelerationBuild(frameIndex) {
|
|
2982
|
-
if (!config.gpuAccelerationBuildRequired) {
|
|
2983
|
-
return;
|
|
3298
|
+
if (!config.gpuAccelerationBuildRequired || accelerationBuilt) {
|
|
3299
|
+
return false;
|
|
2984
3300
|
}
|
|
2985
3301
|
const buildTile = tiles[0] ?? { x: 0, y: 0, width: 1, height: 1 };
|
|
2986
3302
|
const encoder = device.createCommandEncoder({
|
|
@@ -3038,27 +3354,21 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3038
3354
|
}
|
|
3039
3355
|
passEncoder.end();
|
|
3040
3356
|
device.queue.submit([encoder.finish()]);
|
|
3357
|
+
accelerationBuilt = true;
|
|
3358
|
+
accelerationBuildCount += 1;
|
|
3359
|
+
return true;
|
|
3041
3360
|
}
|
|
3042
|
-
function
|
|
3043
|
-
const sampleWeight = 1 / config.samplesPerPixel;
|
|
3044
|
-
const configPayload = createConfigPayload(config, tile, frameIndex, {
|
|
3045
|
-
sampleIndex,
|
|
3046
|
-
sampleWeight
|
|
3047
|
-
});
|
|
3048
|
-
device.queue.writeBuffer(configBuffer, 0, configPayload);
|
|
3049
|
-
const encoder = device.createCommandEncoder({
|
|
3050
|
-
label: `plasius.wavefront.frame.${frameIndex}.tile.${tile.x}.${tile.y}.sample.${sampleIndex}`
|
|
3051
|
-
});
|
|
3361
|
+
function encodeTileSample(encoder, tile, configOffset) {
|
|
3052
3362
|
const passEncoder = encoder.beginComputePass({
|
|
3053
3363
|
label: "plasius.wavefront.computePass"
|
|
3054
3364
|
});
|
|
3055
3365
|
const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
|
|
3056
3366
|
const capacityWorkgroups = Math.ceil(config.tilePixelCapacity / WORKGROUP_SIZE);
|
|
3057
|
-
passEncoder.setBindGroup(0, bindGroups[0], [
|
|
3367
|
+
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3058
3368
|
passEncoder.setPipeline(pipelines.generatePrimaryRays);
|
|
3059
3369
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
3060
3370
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
3061
|
-
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [
|
|
3371
|
+
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
3062
3372
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
3063
3373
|
passEncoder.dispatchWorkgroups(capacityWorkgroups);
|
|
3064
3374
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
@@ -3067,71 +3377,38 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3067
3377
|
passEncoder.dispatchWorkgroups(1);
|
|
3068
3378
|
}
|
|
3069
3379
|
passEncoder.end();
|
|
3070
|
-
device.queue.submit([encoder.finish()]);
|
|
3071
3380
|
}
|
|
3072
|
-
function
|
|
3073
|
-
const configPayload = createConfigPayload(config, tile, frameIndex, {
|
|
3074
|
-
sampleIndex: 0,
|
|
3075
|
-
sampleWeight: 1 / config.samplesPerPixel
|
|
3076
|
-
});
|
|
3077
|
-
device.queue.writeBuffer(configBuffer, 0, configPayload);
|
|
3078
|
-
const encoder = device.createCommandEncoder({
|
|
3079
|
-
label: `plasius.wavefront.frame.${frameIndex}.tile.${tile.x}.${tile.y}.output`
|
|
3080
|
-
});
|
|
3381
|
+
function encodeTileOutput(encoder, tile, configOffset) {
|
|
3081
3382
|
const passEncoder = encoder.beginComputePass({
|
|
3082
3383
|
label: "plasius.wavefront.outputPass"
|
|
3083
3384
|
});
|
|
3084
3385
|
const tileWorkgroups = Math.ceil(tile.width * tile.height / WORKGROUP_SIZE);
|
|
3085
|
-
passEncoder.setBindGroup(0, bindGroups[0], [
|
|
3386
|
+
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3086
3387
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
3087
3388
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
3088
3389
|
passEncoder.end();
|
|
3089
|
-
device.queue.submit([encoder.finish()]);
|
|
3090
|
-
}
|
|
3091
|
-
function dispatchTile(tile, frameIndex) {
|
|
3092
|
-
for (let sampleIndex = 0; sampleIndex < config.samplesPerPixel; sampleIndex += 1) {
|
|
3093
|
-
dispatchTileSample(tile, frameIndex, sampleIndex);
|
|
3094
|
-
}
|
|
3095
|
-
dispatchTileOutput(tile, frameIndex);
|
|
3096
3390
|
}
|
|
3097
|
-
function
|
|
3391
|
+
function encodeDenoise(encoder, configOffset) {
|
|
3098
3392
|
if (!config.denoise) {
|
|
3099
3393
|
return;
|
|
3100
3394
|
}
|
|
3101
|
-
device.queue.writeBuffer(
|
|
3102
|
-
configBuffer,
|
|
3103
|
-
0,
|
|
3104
|
-
createConfigPayload(
|
|
3105
|
-
config,
|
|
3106
|
-
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
3107
|
-
frameIndex,
|
|
3108
|
-
{ sampleIndex: 0, sampleWeight: 1 / config.samplesPerPixel }
|
|
3109
|
-
)
|
|
3110
|
-
);
|
|
3111
|
-
const encoder = device.createCommandEncoder({
|
|
3112
|
-
label: `plasius.wavefront.frame.${frameIndex}.denoise`
|
|
3113
|
-
});
|
|
3114
3395
|
const radiancePass = encoder.beginComputePass({
|
|
3115
3396
|
label: "plasius.wavefront.denoiseRadiancePass"
|
|
3116
3397
|
});
|
|
3117
|
-
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [
|
|
3398
|
+
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
3118
3399
|
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
3119
3400
|
radiancePass.dispatchWorkgroups(Math.ceil(config.width / 8), Math.ceil(config.height / 8));
|
|
3120
3401
|
radiancePass.end();
|
|
3121
3402
|
const resolvePass = encoder.beginComputePass({
|
|
3122
3403
|
label: "plasius.wavefront.denoiseResolvePass"
|
|
3123
3404
|
});
|
|
3124
|
-
resolvePass.setBindGroup(0, denoiseResolveBindGroup, [
|
|
3405
|
+
resolvePass.setBindGroup(0, denoiseResolveBindGroup, [configOffset]);
|
|
3125
3406
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
3126
3407
|
resolvePass.dispatchWorkgroups(Math.ceil(config.width / 8), Math.ceil(config.height / 8));
|
|
3127
3408
|
resolvePass.end();
|
|
3128
|
-
device.queue.submit([encoder.finish()]);
|
|
3129
3409
|
}
|
|
3130
|
-
function
|
|
3410
|
+
function encodePresent(encoder) {
|
|
3131
3411
|
const texture = context.getCurrentTexture();
|
|
3132
|
-
const encoder = device.createCommandEncoder({
|
|
3133
|
-
label: `plasius.wavefront.present.${frame}`
|
|
3134
|
-
});
|
|
3135
3412
|
const passEncoder = encoder.beginRenderPass({
|
|
3136
3413
|
label: "plasius.wavefront.presentPass",
|
|
3137
3414
|
colorAttachments: [
|
|
@@ -3147,16 +3424,42 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3147
3424
|
passEncoder.setBindGroup(0, presentBindGroup);
|
|
3148
3425
|
passEncoder.draw(3);
|
|
3149
3426
|
passEncoder.end();
|
|
3427
|
+
}
|
|
3428
|
+
function dispatchFrame(frameIndex) {
|
|
3429
|
+
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
3430
|
+
const encoder = device.createCommandEncoder({
|
|
3431
|
+
label: `plasius.wavefront.frame.${frameIndex}.batched`
|
|
3432
|
+
});
|
|
3433
|
+
for (const tile of tiles) {
|
|
3434
|
+
for (let sampleIndex = 0; sampleIndex < config.samplesPerPixel; sampleIndex += 1) {
|
|
3435
|
+
const configOffset = writeFrameConfig(tile, {
|
|
3436
|
+
sampleIndex,
|
|
3437
|
+
sampleWeight: 1 / config.samplesPerPixel
|
|
3438
|
+
});
|
|
3439
|
+
encodeTileSample(encoder, tile, configOffset);
|
|
3440
|
+
}
|
|
3441
|
+
const outputConfigOffset = writeFrameConfig(tile, {
|
|
3442
|
+
sampleIndex: 0,
|
|
3443
|
+
sampleWeight: 1 / config.samplesPerPixel
|
|
3444
|
+
});
|
|
3445
|
+
encodeTileOutput(encoder, tile, outputConfigOffset);
|
|
3446
|
+
}
|
|
3447
|
+
if (config.denoise) {
|
|
3448
|
+
const denoiseConfigOffset = writeFrameConfig(
|
|
3449
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
3450
|
+
{ sampleIndex: 0, sampleWeight: 1 / config.samplesPerPixel }
|
|
3451
|
+
);
|
|
3452
|
+
encodeDenoise(encoder, denoiseConfigOffset);
|
|
3453
|
+
}
|
|
3454
|
+
encodePresent(encoder);
|
|
3150
3455
|
device.queue.submit([encoder.finish()]);
|
|
3456
|
+
return 1;
|
|
3151
3457
|
}
|
|
3152
3458
|
function renderOnce() {
|
|
3153
3459
|
frame += 1;
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
}
|
|
3158
|
-
dispatchDenoise(frame + config.frameIndex);
|
|
3159
|
-
present();
|
|
3460
|
+
const frameIndex = frame + config.frameIndex;
|
|
3461
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex);
|
|
3462
|
+
const frameSubmissionCount = dispatchFrame(frameIndex);
|
|
3160
3463
|
return Object.freeze({
|
|
3161
3464
|
frame,
|
|
3162
3465
|
width: config.width,
|
|
@@ -3170,10 +3473,17 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3170
3473
|
sceneObjectCount: config.sceneObjectCount,
|
|
3171
3474
|
triangleCount: config.triangleCount,
|
|
3172
3475
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3476
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
3477
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
3173
3478
|
bvhNodeCount: config.bvhNodeCount,
|
|
3174
3479
|
displayQuality: config.displayQuality,
|
|
3175
3480
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
3176
3481
|
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
3482
|
+
accelerationBuildSubmitted,
|
|
3483
|
+
accelerationBuilt,
|
|
3484
|
+
accelerationBuildCount,
|
|
3485
|
+
commandSubmissions: frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
3486
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
3177
3487
|
memory: config.memory
|
|
3178
3488
|
});
|
|
3179
3489
|
}
|
|
@@ -3239,10 +3549,15 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3239
3549
|
sceneObjectCount: config.sceneObjectCount,
|
|
3240
3550
|
triangleCount: config.triangleCount,
|
|
3241
3551
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3552
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
3553
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
3242
3554
|
bvhNodeCount: config.bvhNodeCount,
|
|
3243
3555
|
displayQuality: config.displayQuality,
|
|
3244
3556
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
3245
3557
|
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
3558
|
+
accelerationBuilt,
|
|
3559
|
+
accelerationBuildCount,
|
|
3560
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
3246
3561
|
memory: config.memory
|
|
3247
3562
|
});
|
|
3248
3563
|
}
|
|
@@ -3257,6 +3572,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3257
3572
|
meshVertexBuffer.destroy?.();
|
|
3258
3573
|
meshIndexBuffer.destroy?.();
|
|
3259
3574
|
meshRangeBuffer.destroy?.();
|
|
3575
|
+
environmentPortalBuffer.destroy?.();
|
|
3260
3576
|
bvhLeafRefBuffer.destroy?.();
|
|
3261
3577
|
configBuffer.destroy?.();
|
|
3262
3578
|
bvhBuildConfigBuffer.destroy?.();
|