@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/src/wavefront-compute.js
CHANGED
|
@@ -4,6 +4,7 @@ const DEFAULT_MAX_DEPTH = 6;
|
|
|
4
4
|
const DEFAULT_TILE_SIZE = 128;
|
|
5
5
|
const DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
6
6
|
const DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
7
|
+
const DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
7
8
|
const WORKGROUP_SIZE = 64;
|
|
8
9
|
const RAY_RECORD_BYTES = 80;
|
|
9
10
|
const HIT_RECORD_BYTES = 208;
|
|
@@ -14,9 +15,11 @@ const TRIANGLE_RECORD_BYTES = 208;
|
|
|
14
15
|
const BVH_NODE_RECORD_BYTES = 48;
|
|
15
16
|
const BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
16
17
|
const EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
18
|
+
const ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
17
19
|
const ACCUMULATION_RECORD_BYTES = 16;
|
|
18
|
-
const CONFIG_BUFFER_BYTES =
|
|
20
|
+
const CONFIG_BUFFER_BYTES = 272;
|
|
19
21
|
const COUNTER_BUFFER_BYTES = 16;
|
|
22
|
+
const TRACE_STORAGE_BUFFER_BINDINGS = 9;
|
|
20
23
|
const HIT_TYPE_SURFACE = 0;
|
|
21
24
|
const HIT_TYPE_EMISSIVE = 1;
|
|
22
25
|
const MATERIAL_DIFFUSE = 0;
|
|
@@ -48,6 +51,7 @@ const DEFAULT_ENVIRONMENT_LIGHTING = Object.freeze({
|
|
|
48
51
|
|
|
49
52
|
export const wavefrontPathTracingComputeLimits = Object.freeze({
|
|
50
53
|
workgroupSize: WORKGROUP_SIZE,
|
|
54
|
+
traceStorageBufferBindings: TRACE_STORAGE_BUFFER_BINDINGS,
|
|
51
55
|
rayRecordBytes: RAY_RECORD_BYTES,
|
|
52
56
|
hitRecordBytes: HIT_RECORD_BYTES,
|
|
53
57
|
sceneObjectRecordBytes: SCENE_OBJECT_RECORD_BYTES,
|
|
@@ -58,6 +62,7 @@ export const wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
58
62
|
bvhLeafReferenceRecordBytes: BVH_LEAF_REF_RECORD_BYTES,
|
|
59
63
|
emissiveTriangleIndexBytes: EMISSIVE_TRIANGLE_INDEX_BYTES,
|
|
60
64
|
emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
65
|
+
environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
61
66
|
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES,
|
|
62
67
|
});
|
|
63
68
|
|
|
@@ -877,6 +882,135 @@ function resolveEnvironmentLighting(input, environmentColor, ambientColor) {
|
|
|
877
882
|
});
|
|
878
883
|
}
|
|
879
884
|
|
|
885
|
+
function resolveEnvironmentPortalMode(value, hasPortals) {
|
|
886
|
+
if (value === undefined || value === null) {
|
|
887
|
+
return hasPortals ? 2 : 0;
|
|
888
|
+
}
|
|
889
|
+
if (Number.isInteger(value) && value >= 0 && value <= 2) {
|
|
890
|
+
return value;
|
|
891
|
+
}
|
|
892
|
+
if (value === "disabled") {
|
|
893
|
+
return 0;
|
|
894
|
+
}
|
|
895
|
+
if (value === "guide") {
|
|
896
|
+
return 1;
|
|
897
|
+
}
|
|
898
|
+
if (value === "guide-and-gate" || value === "gate") {
|
|
899
|
+
return 2;
|
|
900
|
+
}
|
|
901
|
+
throw new Error(
|
|
902
|
+
"environmentPortalMode must be disabled, guide, guide-and-gate, or an integer between 0 and 2."
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function orthogonalPortalTangent(normal) {
|
|
907
|
+
if (Math.abs(normal[1]) < 0.92) {
|
|
908
|
+
return normalize(cross([0, 1, 0], normal), [1, 0, 0]);
|
|
909
|
+
}
|
|
910
|
+
return normalize(cross([1, 0, 0], normal), [0, 0, 1]);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function resolvePortalTangent(value, normal) {
|
|
914
|
+
const fallback = orthogonalPortalTangent(normal);
|
|
915
|
+
const tangent = asUnitVec3(value, fallback);
|
|
916
|
+
const projected = subtract(tangent, scale(normal, dot(tangent, normal)));
|
|
917
|
+
return normalize(projected, fallback);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function readPositiveFiniteNumber(name, value, fallback) {
|
|
921
|
+
const numeric = readFiniteNumber(name, value, fallback);
|
|
922
|
+
if (numeric <= 0) {
|
|
923
|
+
throw new Error(`${name} must be a positive finite number.`);
|
|
924
|
+
}
|
|
925
|
+
return numeric;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function readPortalExtent(name, value, halfName, halfValue) {
|
|
929
|
+
if (value !== undefined && value !== null) {
|
|
930
|
+
return readPositiveFiniteNumber(name, value, 1);
|
|
931
|
+
}
|
|
932
|
+
return readPositiveFiniteNumber(halfName, halfValue, 0.5) * 2;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function normalizeEnvironmentPortal(portal, index) {
|
|
936
|
+
if (!portal || typeof portal !== "object") {
|
|
937
|
+
throw new Error(`environmentPortals[${index}] must be an object.`);
|
|
938
|
+
}
|
|
939
|
+
const shape = portal.shape ?? portal.kind ?? "rectangle";
|
|
940
|
+
if (shape !== "rectangle") {
|
|
941
|
+
throw new Error(`environmentPortals[${index}].shape must be "rectangle".`);
|
|
942
|
+
}
|
|
943
|
+
const position = asVec3(portal.position ?? portal.center, [0, 0, 0]);
|
|
944
|
+
const normal = asUnitVec3(portal.normal, [0, 0, 1]);
|
|
945
|
+
const tangent = resolvePortalTangent(portal.tangent, normal);
|
|
946
|
+
const bitangent = normalize(cross(normal, tangent), [0, 1, 0]);
|
|
947
|
+
const width = readPortalExtent(
|
|
948
|
+
`environmentPortals[${index}].width`,
|
|
949
|
+
portal.width,
|
|
950
|
+
`environmentPortals[${index}].halfWidth`,
|
|
951
|
+
portal.halfWidth
|
|
952
|
+
);
|
|
953
|
+
const height = readPortalExtent(
|
|
954
|
+
`environmentPortals[${index}].height`,
|
|
955
|
+
portal.height,
|
|
956
|
+
`environmentPortals[${index}].halfHeight`,
|
|
957
|
+
portal.halfHeight
|
|
958
|
+
);
|
|
959
|
+
const radianceScale = Math.max(
|
|
960
|
+
0,
|
|
961
|
+
readFiniteNumber(
|
|
962
|
+
`environmentPortals[${index}].radianceScale`,
|
|
963
|
+
portal.radianceScale ?? portal.intensity,
|
|
964
|
+
1
|
|
965
|
+
)
|
|
966
|
+
);
|
|
967
|
+
return Object.freeze({
|
|
968
|
+
kind: 1,
|
|
969
|
+
flags: portal.twoSided === false ? 0 : 1,
|
|
970
|
+
position: Object.freeze([position[0], position[1], position[2], width * height]),
|
|
971
|
+
normal: Object.freeze([normal[0], normal[1], normal[2], radianceScale]),
|
|
972
|
+
tangent: Object.freeze([tangent[0], tangent[1], tangent[2], width * 0.5]),
|
|
973
|
+
bitangent: Object.freeze([bitangent[0], bitangent[1], bitangent[2], height * 0.5]),
|
|
974
|
+
color: Object.freeze(asColor(portal.color, [1, 1, 1, 1])),
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function normalizeEnvironmentPortals(value) {
|
|
979
|
+
if (value === undefined || value === null) {
|
|
980
|
+
return Object.freeze([]);
|
|
981
|
+
}
|
|
982
|
+
if (!Array.isArray(value)) {
|
|
983
|
+
throw new Error("environmentPortals must be an array when provided.");
|
|
984
|
+
}
|
|
985
|
+
return Object.freeze(value.map(normalizeEnvironmentPortal));
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function packEnvironmentPortals(portals, capacity) {
|
|
989
|
+
const bytes = new ArrayBuffer(capacity * ENVIRONMENT_PORTAL_RECORD_BYTES);
|
|
990
|
+
const data = new DataView(bytes);
|
|
991
|
+
const floatView = new Float32Array(bytes);
|
|
992
|
+
portals.forEach((portal, index) => {
|
|
993
|
+
const byteOffset = index * ENVIRONMENT_PORTAL_RECORD_BYTES;
|
|
994
|
+
const floatOffset = byteOffset / Float32Array.BYTES_PER_ELEMENT;
|
|
995
|
+
data.setUint32(byteOffset, portal.kind, true);
|
|
996
|
+
data.setUint32(byteOffset + 4, portal.flags, true);
|
|
997
|
+
data.setUint32(byteOffset + 8, 0, true);
|
|
998
|
+
data.setUint32(byteOffset + 12, 0, true);
|
|
999
|
+
writeVec4(floatView, floatOffset + 4, portal.position);
|
|
1000
|
+
writeVec4(floatView, floatOffset + 8, portal.normal);
|
|
1001
|
+
writeVec4(floatView, floatOffset + 12, portal.tangent);
|
|
1002
|
+
writeVec4(floatView, floatOffset + 16, portal.bitangent);
|
|
1003
|
+
writeVec4(floatView, floatOffset + 20, portal.color);
|
|
1004
|
+
});
|
|
1005
|
+
return Object.freeze({
|
|
1006
|
+
buffer: bytes,
|
|
1007
|
+
portals,
|
|
1008
|
+
count: portals.length,
|
|
1009
|
+
capacity,
|
|
1010
|
+
recordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
|
|
880
1014
|
function getCanvasDimension(canvas, key, fallback) {
|
|
881
1015
|
const value = Number(canvas?.[key]);
|
|
882
1016
|
if (Number.isFinite(value) && value > 0) {
|
|
@@ -935,6 +1069,11 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
935
1069
|
options.emissiveTriangleCapacity,
|
|
936
1070
|
0
|
|
937
1071
|
);
|
|
1072
|
+
const environmentPortalCapacity = readNonNegativeInteger(
|
|
1073
|
+
"environmentPortalCapacity",
|
|
1074
|
+
options.environmentPortalCapacity,
|
|
1075
|
+
0
|
|
1076
|
+
);
|
|
938
1077
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
939
1078
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
940
1079
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
@@ -944,6 +1083,8 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
944
1083
|
const bvhLeafReferenceBytes = bvhLeafSortCapacity * BVH_LEAF_REF_RECORD_BYTES;
|
|
945
1084
|
const emissiveTriangleMetadataBytes =
|
|
946
1085
|
emissiveTriangleCapacity * BVH_NODE_RECORD_BYTES;
|
|
1086
|
+
const environmentPortalBytes =
|
|
1087
|
+
environmentPortalCapacity * ENVIRONMENT_PORTAL_RECORD_BYTES;
|
|
947
1088
|
|
|
948
1089
|
return Object.freeze({
|
|
949
1090
|
queueBytes,
|
|
@@ -955,6 +1096,7 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
955
1096
|
bvhNodeBytes,
|
|
956
1097
|
bvhLeafReferenceBytes,
|
|
957
1098
|
emissiveTriangleMetadataBytes,
|
|
1099
|
+
environmentPortalBytes,
|
|
958
1100
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
959
1101
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
960
1102
|
totalHotBufferBytes:
|
|
@@ -966,6 +1108,7 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
966
1108
|
bvhNodeBytes +
|
|
967
1109
|
bvhLeafReferenceBytes +
|
|
968
1110
|
emissiveTriangleMetadataBytes +
|
|
1111
|
+
environmentPortalBytes +
|
|
969
1112
|
CONFIG_BUFFER_BYTES +
|
|
970
1113
|
COUNTER_BUFFER_BYTES,
|
|
971
1114
|
});
|
|
@@ -1048,6 +1191,25 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1048
1191
|
environmentColor,
|
|
1049
1192
|
ambientColor
|
|
1050
1193
|
);
|
|
1194
|
+
const environmentPortals = normalizeEnvironmentPortals(
|
|
1195
|
+
options.environmentPortals ??
|
|
1196
|
+
options.environmentLightPortals ??
|
|
1197
|
+
options.environmentLighting?.environmentPortals
|
|
1198
|
+
);
|
|
1199
|
+
const environmentPortalCapacity = Math.max(
|
|
1200
|
+
environmentPortals.length,
|
|
1201
|
+
readNonNegativeInteger(
|
|
1202
|
+
"environmentPortalCapacity",
|
|
1203
|
+
options.environmentPortalCapacity,
|
|
1204
|
+
DEFAULT_ENVIRONMENT_PORTAL_CAPACITY
|
|
1205
|
+
)
|
|
1206
|
+
);
|
|
1207
|
+
const environmentPortalMode = resolveEnvironmentPortalMode(
|
|
1208
|
+
options.environmentPortalMode ??
|
|
1209
|
+
options.portalMode ??
|
|
1210
|
+
options.environmentLighting?.environmentPortalMode,
|
|
1211
|
+
environmentPortals.length > 0
|
|
1212
|
+
);
|
|
1051
1213
|
|
|
1052
1214
|
return Object.freeze({
|
|
1053
1215
|
width,
|
|
@@ -1077,6 +1239,10 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1077
1239
|
environmentColor: environmentLighting.environmentColor,
|
|
1078
1240
|
ambientColor: environmentLighting.ambientColor,
|
|
1079
1241
|
environmentLighting,
|
|
1242
|
+
environmentPortals,
|
|
1243
|
+
environmentPortalCount: environmentPortals.length,
|
|
1244
|
+
environmentPortalCapacity,
|
|
1245
|
+
environmentPortalMode,
|
|
1080
1246
|
displayQuality: options.displayQuality === true,
|
|
1081
1247
|
requiresMeshBvhForDisplayQuality: true,
|
|
1082
1248
|
denoise: options.denoise !== false,
|
|
@@ -1088,6 +1254,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1088
1254
|
bvhNodeCapacity,
|
|
1089
1255
|
bvhLeafSortCapacity,
|
|
1090
1256
|
emissiveTriangleCapacity: emissiveTriangleIndices.capacity,
|
|
1257
|
+
environmentPortalCapacity,
|
|
1091
1258
|
}),
|
|
1092
1259
|
});
|
|
1093
1260
|
}
|
|
@@ -1298,6 +1465,10 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1298
1465
|
data.setUint32(244, buildRange.count ?? 0, true);
|
|
1299
1466
|
data.setUint32(248, buildRange.sortItemCount ?? 0, true);
|
|
1300
1467
|
data.setUint32(252, config.emissiveTriangleCount ?? 0, true);
|
|
1468
|
+
data.setUint32(256, config.environmentPortalCount ?? 0, true);
|
|
1469
|
+
data.setUint32(260, config.environmentPortalMode ?? 0, true);
|
|
1470
|
+
data.setUint32(264, 0, true);
|
|
1471
|
+
data.setUint32(268, 0, true);
|
|
1301
1472
|
return bytes;
|
|
1302
1473
|
}
|
|
1303
1474
|
|
|
@@ -1556,6 +1727,10 @@ struct FrameConfig {
|
|
|
1556
1727
|
bvhBuildNodeCount: u32,
|
|
1557
1728
|
bvhSortItemCount: u32,
|
|
1558
1729
|
emissiveTriangleCount: u32,
|
|
1730
|
+
environmentPortalCount: u32,
|
|
1731
|
+
environmentPortalMode: u32,
|
|
1732
|
+
_portalPad0: u32,
|
|
1733
|
+
_portalPad1: u32,
|
|
1559
1734
|
};
|
|
1560
1735
|
|
|
1561
1736
|
struct Counters {
|
|
@@ -1579,6 +1754,18 @@ struct Candidate {
|
|
|
1579
1754
|
mediumRefId: u32,
|
|
1580
1755
|
};
|
|
1581
1756
|
|
|
1757
|
+
struct EnvironmentPortal {
|
|
1758
|
+
kind: u32,
|
|
1759
|
+
flags: u32,
|
|
1760
|
+
_pad0: u32,
|
|
1761
|
+
_pad1: u32,
|
|
1762
|
+
position: vec4<f32>,
|
|
1763
|
+
normal: vec4<f32>,
|
|
1764
|
+
tangent: vec4<f32>,
|
|
1765
|
+
bitangent: vec4<f32>,
|
|
1766
|
+
color: vec4<f32>,
|
|
1767
|
+
};
|
|
1768
|
+
|
|
1582
1769
|
@group(0) @binding(0) var<storage, read_write> activeQueue: array<RayRecord>;
|
|
1583
1770
|
@group(0) @binding(1) var<storage, read_write> nextQueue: array<RayRecord>;
|
|
1584
1771
|
@group(0) @binding(2) var<storage, read_write> hits: array<HitRecord>;
|
|
@@ -1598,6 +1785,7 @@ struct Candidate {
|
|
|
1598
1785
|
@group(0) @binding(16) var radianceImage: texture_storage_2d<rgba16float, write>;
|
|
1599
1786
|
@group(0) @binding(17) var finalDenoiseInputRadiance: texture_2d<f32>;
|
|
1600
1787
|
@group(0) @binding(18) var denoisedOutputImage: texture_storage_2d<rgba8unorm, write>;
|
|
1788
|
+
@group(0) @binding(19) var<storage, read> environmentPortals: array<EnvironmentPortal>;
|
|
1601
1789
|
|
|
1602
1790
|
fn hash_u32(value: u32) -> u32 {
|
|
1603
1791
|
var x = value;
|
|
@@ -1638,7 +1826,48 @@ fn saturate(value: f32) -> f32 {
|
|
|
1638
1826
|
return clamp(value, 0.0, 1.0);
|
|
1639
1827
|
}
|
|
1640
1828
|
|
|
1641
|
-
fn
|
|
1829
|
+
fn max_component(value: vec3<f32>) -> f32 {
|
|
1830
|
+
return max(max(value.x, value.y), value.z);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
fn environment_portal_radiance_scale(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1834
|
+
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
1835
|
+
return vec3<f32>(1.0);
|
|
1836
|
+
}
|
|
1837
|
+
var scale = vec3<f32>(0.0);
|
|
1838
|
+
for (var portalIndex = 0u; portalIndex < config.environmentPortalCount; portalIndex = portalIndex + 1u) {
|
|
1839
|
+
let portal = environmentPortals[portalIndex];
|
|
1840
|
+
if (portal.kind == 1u) {
|
|
1841
|
+
let portalNormal = safe_normalize(portal.normal.xyz, vec3<f32>(0.0, 0.0, 1.0));
|
|
1842
|
+
let denominator = dot(direction, portalNormal);
|
|
1843
|
+
let twoSided = (portal.flags & 1u) != 0u;
|
|
1844
|
+
var facing = abs(denominator) > 0.0001;
|
|
1845
|
+
if (!twoSided && denominator <= 0.0001) {
|
|
1846
|
+
facing = false;
|
|
1847
|
+
}
|
|
1848
|
+
if (facing) {
|
|
1849
|
+
let distance = dot(portal.position.xyz - origin, portalNormal) / denominator;
|
|
1850
|
+
if (distance > 0.001) {
|
|
1851
|
+
let hitPosition = origin + direction * distance;
|
|
1852
|
+
let local = hitPosition - portal.position.xyz;
|
|
1853
|
+
let tangent = safe_normalize(portal.tangent.xyz, vec3<f32>(1.0, 0.0, 0.0));
|
|
1854
|
+
let bitangent = safe_normalize(portal.bitangent.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
1855
|
+
let u = dot(local, tangent);
|
|
1856
|
+
let v = dot(local, bitangent);
|
|
1857
|
+
if (abs(u) <= portal.tangent.w && abs(v) <= portal.bitangent.w) {
|
|
1858
|
+
let areaWeight = clamp(sqrt(max(portal.position.w, 0.0001)), 0.25, 4.0);
|
|
1859
|
+
let angleWeight = max(abs(denominator), 0.08);
|
|
1860
|
+
let portalScale = portal.color.rgb * portal.normal.w * portal.color.a * areaWeight * angleWeight;
|
|
1861
|
+
scale = max(scale, portalScale);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
return scale;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1642
1871
|
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
1643
1872
|
let upFactor = saturate(rayDirection.y * 0.5 + 0.5);
|
|
1644
1873
|
let sunDirection = safe_normalize(
|
|
@@ -1649,10 +1878,26 @@ fn environment_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
|
1649
1878
|
let gradient =
|
|
1650
1879
|
config.environmentHorizonColor.xyz * (1.0 - upFactor) +
|
|
1651
1880
|
config.environmentZenithColor.xyz * upFactor;
|
|
1881
|
+
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
1882
|
+
let portalHit = max_component(portalScale) > 0.0001;
|
|
1652
1883
|
return (
|
|
1653
1884
|
gradient +
|
|
1654
1885
|
config.environmentSunColor.xyz * sunGlow
|
|
1655
|
-
) *
|
|
1886
|
+
) *
|
|
1887
|
+
max(config.environmentSunDirectionIntensity.w, 0.0001) *
|
|
1888
|
+
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1892
|
+
let portalScale = environment_portal_radiance_scale(origin, safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0)));
|
|
1893
|
+
if (
|
|
1894
|
+
config.environmentPortalCount > 0u &&
|
|
1895
|
+
config.environmentPortalMode == 2u &&
|
|
1896
|
+
max_component(portalScale) <= 0.0001
|
|
1897
|
+
) {
|
|
1898
|
+
return config.ambientColor.xyz * 0.65;
|
|
1899
|
+
}
|
|
1900
|
+
return environment_radiance(origin, direction);
|
|
1656
1901
|
}
|
|
1657
1902
|
|
|
1658
1903
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -1923,7 +2168,7 @@ fn make_ray(pixelIndex: u32) -> RayRecord {
|
|
|
1923
2168
|
}
|
|
1924
2169
|
|
|
1925
2170
|
fn make_miss(ray: RayRecord) -> HitRecord {
|
|
1926
|
-
let radiance =
|
|
2171
|
+
let radiance = gated_environment_radiance(ray.origin.xyz, ray.direction.xyz);
|
|
1927
2172
|
return HitRecord(
|
|
1928
2173
|
ray.rayId,
|
|
1929
2174
|
ray.sourcePixelId,
|
|
@@ -2409,6 +2654,21 @@ fn sample_emissive_triangle_direction(hit: HitRecord, seed: u32, fallback: vec3<
|
|
|
2409
2654
|
return safe_normalize(lightPoint - hit.position.xyz, fallback);
|
|
2410
2655
|
}
|
|
2411
2656
|
|
|
2657
|
+
fn sample_environment_portal_direction(hit: HitRecord, seed: u32, fallback: vec3<f32>) -> vec3<f32> {
|
|
2658
|
+
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
2659
|
+
return fallback;
|
|
2660
|
+
}
|
|
2661
|
+
let portalSlot = min(
|
|
2662
|
+
u32(random01(seed + 211u) * f32(config.environmentPortalCount)),
|
|
2663
|
+
config.environmentPortalCount - 1u
|
|
2664
|
+
);
|
|
2665
|
+
let portal = environmentPortals[portalSlot];
|
|
2666
|
+
let u = (random01(seed + 223u) * 2.0 - 1.0) * portal.tangent.w;
|
|
2667
|
+
let v = (random01(seed + 227u) * 2.0 - 1.0) * portal.bitangent.w;
|
|
2668
|
+
let portalTarget = portal.position.xyz + portal.tangent.xyz * u + portal.bitangent.xyz * v;
|
|
2669
|
+
return safe_normalize(portalTarget - hit.position.xyz, fallback);
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2412
2672
|
fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult {
|
|
2413
2673
|
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
2414
2674
|
if (hit.materialKind == 1u) {
|
|
@@ -2448,8 +2708,17 @@ fn scatter_direction(ray: RayRecord, hit: HitRecord, seed: u32) -> ScatterResult
|
|
|
2448
2708
|
let canSampleLight = dot(hit.shadingNormal.xyz, guidedLight) > -0.04;
|
|
2449
2709
|
let guideProbability = select(0.38, 0.72, ray.bounce == 0u);
|
|
2450
2710
|
let useGuidedLight = canSampleLight && random01(seed + 37u) < guideProbability;
|
|
2711
|
+
let guidedPortal = sample_environment_portal_direction(hit, seed, randomDiffuse);
|
|
2712
|
+
let canSamplePortal = dot(hit.shadingNormal.xyz, guidedPortal) > -0.04;
|
|
2713
|
+
let useGuidedPortal =
|
|
2714
|
+
!useGuidedLight &&
|
|
2715
|
+
canSamplePortal &&
|
|
2716
|
+
config.environmentPortalCount > 0u &&
|
|
2717
|
+
config.environmentPortalMode > 0u &&
|
|
2718
|
+
random01(seed + 89u) < 0.58;
|
|
2719
|
+
let guidedDirection = select(randomDiffuse, guidedPortal, useGuidedPortal);
|
|
2451
2720
|
return ScatterResult(
|
|
2452
|
-
vec4<f32>(select(
|
|
2721
|
+
vec4<f32>(select(guidedDirection, guidedLight, useGuidedLight), 0.0),
|
|
2453
2722
|
select(0u, RAY_FLAG_GUIDED_EMISSIVE, useGuidedLight),
|
|
2454
2723
|
0u,
|
|
2455
2724
|
0u,
|
|
@@ -2658,6 +2927,32 @@ fn fragmentMain(in: VertexOut) -> @location(0) vec4<f32> {
|
|
|
2658
2927
|
}
|
|
2659
2928
|
`;
|
|
2660
2929
|
|
|
2930
|
+
function createWavefrontDeviceDescriptor(adapter, options = {}) {
|
|
2931
|
+
const requiredLimits = { ...(options.requiredLimits ?? {}) };
|
|
2932
|
+
const exposedStorageBufferLimit = Number(adapter?.limits?.maxStorageBuffersPerShaderStage);
|
|
2933
|
+
if (Number.isFinite(exposedStorageBufferLimit)) {
|
|
2934
|
+
if (exposedStorageBufferLimit < TRACE_STORAGE_BUFFER_BINDINGS) {
|
|
2935
|
+
throw new Error(
|
|
2936
|
+
`Wavefront mesh tracing requires maxStorageBuffersPerShaderStage>=${TRACE_STORAGE_BUFFER_BINDINGS}, ` +
|
|
2937
|
+
`but this adapter exposes ${exposedStorageBufferLimit}.`
|
|
2938
|
+
);
|
|
2939
|
+
}
|
|
2940
|
+
requiredLimits.maxStorageBuffersPerShaderStage = Math.max(
|
|
2941
|
+
Number(requiredLimits.maxStorageBuffersPerShaderStage ?? 0),
|
|
2942
|
+
TRACE_STORAGE_BUFFER_BINDINGS
|
|
2943
|
+
);
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
const descriptor = { ...(options.deviceDescriptor ?? {}) };
|
|
2947
|
+
if (Object.keys(requiredLimits).length > 0) {
|
|
2948
|
+
descriptor.requiredLimits = {
|
|
2949
|
+
...(descriptor.requiredLimits ?? {}),
|
|
2950
|
+
...requiredLimits,
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
return Object.keys(descriptor).length > 0 ? descriptor : undefined;
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2661
2956
|
export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
2662
2957
|
assertAnalyticDisplayQualityPolicy(options);
|
|
2663
2958
|
const constants = getGpuUsageConstants();
|
|
@@ -2678,7 +2973,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2678
2973
|
throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
|
|
2679
2974
|
}
|
|
2680
2975
|
|
|
2681
|
-
const device = await adapter.requestDevice();
|
|
2976
|
+
const device = await adapter.requestDevice(createWavefrontDeviceDescriptor(adapter, options));
|
|
2682
2977
|
const context = canvas.getContext("webgpu");
|
|
2683
2978
|
if (!context || typeof context.configure !== "function") {
|
|
2684
2979
|
throw new Error("Canvas WebGPU context does not support configure().");
|
|
@@ -2758,18 +3053,19 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2758
3053
|
Math.max(1, config.gpuMeshSource.meshes.count) * MESH_RANGE_RECORD_BYTES,
|
|
2759
3054
|
"plasius.wavefront.meshRanges"
|
|
2760
3055
|
);
|
|
3056
|
+
const environmentPortalBuffer = createBuffer(
|
|
3057
|
+
device,
|
|
3058
|
+
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
3059
|
+
Math.max(1, config.environmentPortalCapacity) * ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
3060
|
+
"plasius.wavefront.environmentPortals"
|
|
3061
|
+
);
|
|
2761
3062
|
const bvhLeafRefBuffer = createBuffer(
|
|
2762
3063
|
device,
|
|
2763
3064
|
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
2764
3065
|
Math.max(1, config.bvhLeafSortCapacity) * BVH_LEAF_REF_RECORD_BYTES,
|
|
2765
3066
|
"plasius.wavefront.bvhLeafRefs"
|
|
2766
3067
|
);
|
|
2767
|
-
const
|
|
2768
|
-
device,
|
|
2769
|
-
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
2770
|
-
CONFIG_BUFFER_BYTES,
|
|
2771
|
-
"plasius.wavefront.frameConfig"
|
|
2772
|
-
);
|
|
3068
|
+
const tiles = createTiles(config.width, config.height, config.tileSize);
|
|
2773
3069
|
const uniformOffsetAlignment = Number(device?.limits?.minUniformBufferOffsetAlignment);
|
|
2774
3070
|
const configBufferStride = alignTo(
|
|
2775
3071
|
CONFIG_BUFFER_BYTES,
|
|
@@ -2777,6 +3073,16 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2777
3073
|
? uniformOffsetAlignment
|
|
2778
3074
|
: CONFIG_BUFFER_BYTES
|
|
2779
3075
|
);
|
|
3076
|
+
const frameConfigSlotCount = Math.max(
|
|
3077
|
+
1,
|
|
3078
|
+
tiles.length * config.samplesPerPixel + tiles.length + (config.denoise ? 1 : 0)
|
|
3079
|
+
);
|
|
3080
|
+
const configBuffer = createBuffer(
|
|
3081
|
+
device,
|
|
3082
|
+
constants.buffer.UNIFORM | constants.buffer.COPY_DST,
|
|
3083
|
+
frameConfigSlotCount * configBufferStride,
|
|
3084
|
+
"plasius.wavefront.frameConfig"
|
|
3085
|
+
);
|
|
2780
3086
|
const bvhBuildConfigSlots =
|
|
2781
3087
|
1 + config.bvhSortStages.length + config.bvhBuildLevels.length;
|
|
2782
3088
|
const bvhBuildConfigBuffer = createBuffer(
|
|
@@ -2812,6 +3118,11 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2812
3118
|
device.queue.writeBuffer(meshVertexBuffer, 0, config.gpuMeshSource.vertices.buffer);
|
|
2813
3119
|
device.queue.writeBuffer(meshIndexBuffer, 0, config.gpuMeshSource.indices.buffer);
|
|
2814
3120
|
device.queue.writeBuffer(meshRangeBuffer, 0, config.gpuMeshSource.meshes.buffer);
|
|
3121
|
+
const packedEnvironmentPortals = packEnvironmentPortals(
|
|
3122
|
+
config.environmentPortals,
|
|
3123
|
+
Math.max(1, config.environmentPortalCapacity)
|
|
3124
|
+
);
|
|
3125
|
+
device.queue.writeBuffer(environmentPortalBuffer, 0, packedEnvironmentPortals.buffer);
|
|
2815
3126
|
|
|
2816
3127
|
const radianceTexture = device.createTexture({
|
|
2817
3128
|
label: "plasius.wavefront.radiance",
|
|
@@ -2873,6 +3184,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
2873
3184
|
visibility: constants.shader.COMPUTE,
|
|
2874
3185
|
storageTexture: { access: "write-only", format: "rgba16float" },
|
|
2875
3186
|
},
|
|
3187
|
+
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
2876
3188
|
],
|
|
2877
3189
|
});
|
|
2878
3190
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -3048,6 +3360,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3048
3360
|
{ binding: 8, resource: { buffer: triangleBuffer } },
|
|
3049
3361
|
{ binding: 9, resource: { buffer: bvhNodeBuffer } },
|
|
3050
3362
|
{ binding: 16, resource: radianceView },
|
|
3363
|
+
{ binding: 19, resource: { buffer: environmentPortalBuffer } },
|
|
3051
3364
|
],
|
|
3052
3365
|
});
|
|
3053
3366
|
}
|
|
@@ -3139,11 +3452,29 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3139
3452
|
});
|
|
3140
3453
|
|
|
3141
3454
|
let frame = 0;
|
|
3142
|
-
|
|
3455
|
+
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
3456
|
+
let accelerationBuildCount = 0;
|
|
3457
|
+
|
|
3458
|
+
function createFrameConfigWriter(frameIndex) {
|
|
3459
|
+
let slot = 0;
|
|
3460
|
+
return (tile, buildRange = {}) => {
|
|
3461
|
+
if (slot >= frameConfigSlotCount) {
|
|
3462
|
+
throw new Error("Wavefront frame config slot capacity exceeded.");
|
|
3463
|
+
}
|
|
3464
|
+
const offset = slot * configBufferStride;
|
|
3465
|
+
slot += 1;
|
|
3466
|
+
device.queue.writeBuffer(
|
|
3467
|
+
configBuffer,
|
|
3468
|
+
offset,
|
|
3469
|
+
createConfigPayload(config, tile, frameIndex, buildRange)
|
|
3470
|
+
);
|
|
3471
|
+
return offset;
|
|
3472
|
+
};
|
|
3473
|
+
}
|
|
3143
3474
|
|
|
3144
3475
|
function dispatchGpuAccelerationBuild(frameIndex) {
|
|
3145
|
-
if (!config.gpuAccelerationBuildRequired) {
|
|
3146
|
-
return;
|
|
3476
|
+
if (!config.gpuAccelerationBuildRequired || accelerationBuilt) {
|
|
3477
|
+
return false;
|
|
3147
3478
|
}
|
|
3148
3479
|
const buildTile = tiles[0] ?? { x: 0, y: 0, width: 1, height: 1 };
|
|
3149
3480
|
const encoder = device.createCommandEncoder({
|
|
@@ -3201,30 +3532,24 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3201
3532
|
}
|
|
3202
3533
|
passEncoder.end();
|
|
3203
3534
|
device.queue.submit([encoder.finish()]);
|
|
3535
|
+
accelerationBuilt = true;
|
|
3536
|
+
accelerationBuildCount += 1;
|
|
3537
|
+
return true;
|
|
3204
3538
|
}
|
|
3205
3539
|
|
|
3206
|
-
function
|
|
3207
|
-
const sampleWeight = 1 / config.samplesPerPixel;
|
|
3208
|
-
const configPayload = createConfigPayload(config, tile, frameIndex, {
|
|
3209
|
-
sampleIndex,
|
|
3210
|
-
sampleWeight,
|
|
3211
|
-
});
|
|
3212
|
-
device.queue.writeBuffer(configBuffer, 0, configPayload);
|
|
3213
|
-
const encoder = device.createCommandEncoder({
|
|
3214
|
-
label: `plasius.wavefront.frame.${frameIndex}.tile.${tile.x}.${tile.y}.sample.${sampleIndex}`,
|
|
3215
|
-
});
|
|
3540
|
+
function encodeTileSample(encoder, tile, configOffset) {
|
|
3216
3541
|
const passEncoder = encoder.beginComputePass({
|
|
3217
3542
|
label: "plasius.wavefront.computePass",
|
|
3218
3543
|
});
|
|
3219
3544
|
const tileWorkgroups = Math.ceil((tile.width * tile.height) / WORKGROUP_SIZE);
|
|
3220
3545
|
const capacityWorkgroups = Math.ceil(config.tilePixelCapacity / WORKGROUP_SIZE);
|
|
3221
3546
|
|
|
3222
|
-
passEncoder.setBindGroup(0, bindGroups[0], [
|
|
3547
|
+
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3223
3548
|
passEncoder.setPipeline(pipelines.generatePrimaryRays);
|
|
3224
3549
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
3225
3550
|
|
|
3226
3551
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
3227
|
-
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [
|
|
3552
|
+
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
3228
3553
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
3229
3554
|
passEncoder.dispatchWorkgroups(capacityWorkgroups);
|
|
3230
3555
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
@@ -3234,58 +3559,28 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3234
3559
|
}
|
|
3235
3560
|
|
|
3236
3561
|
passEncoder.end();
|
|
3237
|
-
device.queue.submit([encoder.finish()]);
|
|
3238
3562
|
}
|
|
3239
3563
|
|
|
3240
|
-
function
|
|
3241
|
-
const configPayload = createConfigPayload(config, tile, frameIndex, {
|
|
3242
|
-
sampleIndex: 0,
|
|
3243
|
-
sampleWeight: 1 / config.samplesPerPixel,
|
|
3244
|
-
});
|
|
3245
|
-
device.queue.writeBuffer(configBuffer, 0, configPayload);
|
|
3246
|
-
const encoder = device.createCommandEncoder({
|
|
3247
|
-
label: `plasius.wavefront.frame.${frameIndex}.tile.${tile.x}.${tile.y}.output`,
|
|
3248
|
-
});
|
|
3564
|
+
function encodeTileOutput(encoder, tile, configOffset) {
|
|
3249
3565
|
const passEncoder = encoder.beginComputePass({
|
|
3250
3566
|
label: "plasius.wavefront.outputPass",
|
|
3251
3567
|
});
|
|
3252
3568
|
const tileWorkgroups = Math.ceil((tile.width * tile.height) / WORKGROUP_SIZE);
|
|
3253
3569
|
|
|
3254
|
-
passEncoder.setBindGroup(0, bindGroups[0], [
|
|
3570
|
+
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3255
3571
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
3256
3572
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
3257
3573
|
passEncoder.end();
|
|
3258
|
-
device.queue.submit([encoder.finish()]);
|
|
3259
3574
|
}
|
|
3260
3575
|
|
|
3261
|
-
function
|
|
3262
|
-
for (let sampleIndex = 0; sampleIndex < config.samplesPerPixel; sampleIndex += 1) {
|
|
3263
|
-
dispatchTileSample(tile, frameIndex, sampleIndex);
|
|
3264
|
-
}
|
|
3265
|
-
dispatchTileOutput(tile, frameIndex);
|
|
3266
|
-
}
|
|
3267
|
-
|
|
3268
|
-
function dispatchDenoise(frameIndex) {
|
|
3576
|
+
function encodeDenoise(encoder, configOffset) {
|
|
3269
3577
|
if (!config.denoise) {
|
|
3270
3578
|
return;
|
|
3271
3579
|
}
|
|
3272
|
-
device.queue.writeBuffer(
|
|
3273
|
-
configBuffer,
|
|
3274
|
-
0,
|
|
3275
|
-
createConfigPayload(
|
|
3276
|
-
config,
|
|
3277
|
-
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
3278
|
-
frameIndex,
|
|
3279
|
-
{ sampleIndex: 0, sampleWeight: 1 / config.samplesPerPixel }
|
|
3280
|
-
)
|
|
3281
|
-
);
|
|
3282
|
-
const encoder = device.createCommandEncoder({
|
|
3283
|
-
label: `plasius.wavefront.frame.${frameIndex}.denoise`,
|
|
3284
|
-
});
|
|
3285
3580
|
const radiancePass = encoder.beginComputePass({
|
|
3286
3581
|
label: "plasius.wavefront.denoiseRadiancePass",
|
|
3287
3582
|
});
|
|
3288
|
-
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [
|
|
3583
|
+
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
3289
3584
|
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
3290
3585
|
radiancePass.dispatchWorkgroups(Math.ceil(config.width / 8), Math.ceil(config.height / 8));
|
|
3291
3586
|
radiancePass.end();
|
|
@@ -3293,18 +3588,14 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3293
3588
|
const resolvePass = encoder.beginComputePass({
|
|
3294
3589
|
label: "plasius.wavefront.denoiseResolvePass",
|
|
3295
3590
|
});
|
|
3296
|
-
resolvePass.setBindGroup(0, denoiseResolveBindGroup, [
|
|
3591
|
+
resolvePass.setBindGroup(0, denoiseResolveBindGroup, [configOffset]);
|
|
3297
3592
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
3298
3593
|
resolvePass.dispatchWorkgroups(Math.ceil(config.width / 8), Math.ceil(config.height / 8));
|
|
3299
3594
|
resolvePass.end();
|
|
3300
|
-
device.queue.submit([encoder.finish()]);
|
|
3301
3595
|
}
|
|
3302
3596
|
|
|
3303
|
-
function
|
|
3597
|
+
function encodePresent(encoder) {
|
|
3304
3598
|
const texture = context.getCurrentTexture();
|
|
3305
|
-
const encoder = device.createCommandEncoder({
|
|
3306
|
-
label: `plasius.wavefront.present.${frame}`,
|
|
3307
|
-
});
|
|
3308
3599
|
const passEncoder = encoder.beginRenderPass({
|
|
3309
3600
|
label: "plasius.wavefront.presentPass",
|
|
3310
3601
|
colorAttachments: [
|
|
@@ -3320,17 +3611,44 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3320
3611
|
passEncoder.setBindGroup(0, presentBindGroup);
|
|
3321
3612
|
passEncoder.draw(3);
|
|
3322
3613
|
passEncoder.end();
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
function dispatchFrame(frameIndex) {
|
|
3617
|
+
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
3618
|
+
const encoder = device.createCommandEncoder({
|
|
3619
|
+
label: `plasius.wavefront.frame.${frameIndex}.batched`,
|
|
3620
|
+
});
|
|
3621
|
+
for (const tile of tiles) {
|
|
3622
|
+
for (let sampleIndex = 0; sampleIndex < config.samplesPerPixel; sampleIndex += 1) {
|
|
3623
|
+
const configOffset = writeFrameConfig(tile, {
|
|
3624
|
+
sampleIndex,
|
|
3625
|
+
sampleWeight: 1 / config.samplesPerPixel,
|
|
3626
|
+
});
|
|
3627
|
+
encodeTileSample(encoder, tile, configOffset);
|
|
3628
|
+
}
|
|
3629
|
+
const outputConfigOffset = writeFrameConfig(tile, {
|
|
3630
|
+
sampleIndex: 0,
|
|
3631
|
+
sampleWeight: 1 / config.samplesPerPixel,
|
|
3632
|
+
});
|
|
3633
|
+
encodeTileOutput(encoder, tile, outputConfigOffset);
|
|
3634
|
+
}
|
|
3635
|
+
if (config.denoise) {
|
|
3636
|
+
const denoiseConfigOffset = writeFrameConfig(
|
|
3637
|
+
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
3638
|
+
{ sampleIndex: 0, sampleWeight: 1 / config.samplesPerPixel }
|
|
3639
|
+
);
|
|
3640
|
+
encodeDenoise(encoder, denoiseConfigOffset);
|
|
3641
|
+
}
|
|
3642
|
+
encodePresent(encoder);
|
|
3323
3643
|
device.queue.submit([encoder.finish()]);
|
|
3644
|
+
return 1;
|
|
3324
3645
|
}
|
|
3325
3646
|
|
|
3326
3647
|
function renderOnce() {
|
|
3327
3648
|
frame += 1;
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
}
|
|
3332
|
-
dispatchDenoise(frame + config.frameIndex);
|
|
3333
|
-
present();
|
|
3649
|
+
const frameIndex = frame + config.frameIndex;
|
|
3650
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex);
|
|
3651
|
+
const frameSubmissionCount = dispatchFrame(frameIndex);
|
|
3334
3652
|
return Object.freeze({
|
|
3335
3653
|
frame,
|
|
3336
3654
|
width: config.width,
|
|
@@ -3344,10 +3662,17 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3344
3662
|
sceneObjectCount: config.sceneObjectCount,
|
|
3345
3663
|
triangleCount: config.triangleCount,
|
|
3346
3664
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3665
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
3666
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
3347
3667
|
bvhNodeCount: config.bvhNodeCount,
|
|
3348
3668
|
displayQuality: config.displayQuality,
|
|
3349
3669
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
3350
3670
|
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
3671
|
+
accelerationBuildSubmitted,
|
|
3672
|
+
accelerationBuilt,
|
|
3673
|
+
accelerationBuildCount,
|
|
3674
|
+
commandSubmissions: frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
3675
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
3351
3676
|
memory: config.memory,
|
|
3352
3677
|
});
|
|
3353
3678
|
}
|
|
@@ -3416,10 +3741,15 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3416
3741
|
sceneObjectCount: config.sceneObjectCount,
|
|
3417
3742
|
triangleCount: config.triangleCount,
|
|
3418
3743
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3744
|
+
environmentPortalCount: config.environmentPortalCount,
|
|
3745
|
+
environmentPortalMode: config.environmentPortalMode,
|
|
3419
3746
|
bvhNodeCount: config.bvhNodeCount,
|
|
3420
3747
|
displayQuality: config.displayQuality,
|
|
3421
3748
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
3422
3749
|
gpuAccelerationBuildRequired: config.gpuAccelerationBuildRequired,
|
|
3750
|
+
accelerationBuilt,
|
|
3751
|
+
accelerationBuildCount,
|
|
3752
|
+
frameConfigSlots: frameConfigSlotCount,
|
|
3423
3753
|
memory: config.memory,
|
|
3424
3754
|
});
|
|
3425
3755
|
}
|
|
@@ -3435,6 +3765,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3435
3765
|
meshVertexBuffer.destroy?.();
|
|
3436
3766
|
meshIndexBuffer.destroy?.();
|
|
3437
3767
|
meshRangeBuffer.destroy?.();
|
|
3768
|
+
environmentPortalBuffer.destroy?.();
|
|
3438
3769
|
bvhLeafRefBuffer.destroy?.();
|
|
3439
3770
|
configBuffer.destroy?.();
|
|
3440
3771
|
bvhBuildConfigBuffer.destroy?.();
|