@plasius/gpu-renderer 0.2.2 → 0.2.4
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 +28 -5
- package/README.md +36 -11
- package/dist/index.cjs +749 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +749 -71
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.d.ts +88 -0
- package/src/wavefront-compute.js +785 -66
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var DEFAULT_HEIGHT = 720;
|
|
|
4
4
|
var DEFAULT_MAX_DEPTH = 6;
|
|
5
5
|
var DEFAULT_TILE_SIZE = 128;
|
|
6
6
|
var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
7
|
+
var DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
7
8
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
8
9
|
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
9
10
|
var WORKGROUP_SIZE = 64;
|
|
@@ -21,11 +22,12 @@ var BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
|
21
22
|
var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
22
23
|
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
23
24
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
24
|
-
var
|
|
25
|
+
var PATH_VERTEX_RECORD_BYTES = 16;
|
|
26
|
+
var CONFIG_BUFFER_BYTES = 304;
|
|
25
27
|
var COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
26
28
|
var INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
27
29
|
var COUNTER_BUFFER_BYTES = 32;
|
|
28
|
-
var TRACE_STORAGE_BUFFER_BINDINGS =
|
|
30
|
+
var TRACE_STORAGE_BUFFER_BINDINGS = 10;
|
|
29
31
|
var MATERIAL_DIFFUSE = 0;
|
|
30
32
|
var MATERIAL_METAL = 1;
|
|
31
33
|
var MATERIAL_DIELECTRIC = 2;
|
|
@@ -48,7 +50,8 @@ var DEFAULT_ENVIRONMENT_LIGHTING = Object.freeze({
|
|
|
48
50
|
sunColor: Object.freeze([2.8, 2.65, 2.35, 1]),
|
|
49
51
|
intensity: 1,
|
|
50
52
|
mode: 0,
|
|
51
|
-
exposure: 1
|
|
53
|
+
exposure: 1,
|
|
54
|
+
sunlitBaseline: 0.16
|
|
52
55
|
});
|
|
53
56
|
var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
54
57
|
workgroupSize: WORKGROUP_SIZE,
|
|
@@ -65,6 +68,7 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
65
68
|
emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
66
69
|
environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
67
70
|
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES,
|
|
71
|
+
pathVertexRecordBytes: PATH_VERTEX_RECORD_BYTES,
|
|
68
72
|
counterRecordBytes: COUNTER_BUFFER_BYTES,
|
|
69
73
|
indirectDispatchRecordBytes: INDIRECT_DISPATCH_ARGS_BYTES
|
|
70
74
|
});
|
|
@@ -141,6 +145,33 @@ function asColor(value, fallback = [1, 1, 1, 1]) {
|
|
|
141
145
|
clamp(readFiniteNumber("color[3]", value[3], fallback[3] ?? 1), 0, 1)
|
|
142
146
|
];
|
|
143
147
|
}
|
|
148
|
+
function resolveEnvironmentMap(input = null) {
|
|
149
|
+
const source = input && typeof input === "object" ? input : null;
|
|
150
|
+
const hasTexture = Boolean(source?.view || source?.texture || source?.data);
|
|
151
|
+
const width = readPositiveInteger("environmentMap.width", source?.width, 1);
|
|
152
|
+
const height = readPositiveInteger("environmentMap.height", source?.height, 1);
|
|
153
|
+
return Object.freeze({
|
|
154
|
+
enabled: hasTexture && source?.enabled !== false,
|
|
155
|
+
width,
|
|
156
|
+
height,
|
|
157
|
+
format: typeof source?.format === "string" ? source.format : "rgba16float",
|
|
158
|
+
projection: typeof source?.projection === "string" ? source.projection : "equirectangular",
|
|
159
|
+
texture: source?.texture ?? null,
|
|
160
|
+
view: source?.view ?? null,
|
|
161
|
+
sampler: source?.sampler ?? null,
|
|
162
|
+
data: source?.data ?? null,
|
|
163
|
+
intensity: Math.max(0, readFiniteNumber("environmentMap.intensity", source?.intensity ?? source?.radianceScale, 1)),
|
|
164
|
+
rotationRadians: readFiniteNumber("environmentMap.rotationRadians", source?.rotationRadians ?? source?.rotation, 0),
|
|
165
|
+
ambientStrength: Math.max(
|
|
166
|
+
0,
|
|
167
|
+
readFiniteNumber("environmentMap.ambientStrength", source?.ambientStrength, 0.32)
|
|
168
|
+
)
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function resolveDeferredPathResolve(options = {}) {
|
|
172
|
+
const value = options.deferredPathResolve ?? options.deferredResolve ?? options.pathResolve?.deferred ?? true;
|
|
173
|
+
return value !== false;
|
|
174
|
+
}
|
|
144
175
|
function emissionPower(emission) {
|
|
145
176
|
return Math.max(0, emission?.[0] ?? 0) + Math.max(0, emission?.[1] ?? 0) + Math.max(0, emission?.[2] ?? 0);
|
|
146
177
|
}
|
|
@@ -788,7 +819,15 @@ function resolveEnvironmentLighting(input, environmentColor, ambientColor) {
|
|
|
788
819
|
sunColor: Object.freeze(asColor(source.sunColor, DEFAULT_ENVIRONMENT_LIGHTING.sunColor)),
|
|
789
820
|
intensity: Math.max(1e-4, readFiniteNumber("environmentLighting.intensity", source.intensity, DEFAULT_ENVIRONMENT_LIGHTING.intensity)),
|
|
790
821
|
mode: readNonNegativeInteger("environmentLighting.mode", source.mode, DEFAULT_ENVIRONMENT_LIGHTING.mode),
|
|
791
|
-
exposure: Math.max(1e-4, readFiniteNumber("environmentLighting.exposure", source.exposure, DEFAULT_ENVIRONMENT_LIGHTING.exposure))
|
|
822
|
+
exposure: Math.max(1e-4, readFiniteNumber("environmentLighting.exposure", source.exposure, DEFAULT_ENVIRONMENT_LIGHTING.exposure)),
|
|
823
|
+
sunlitBaseline: Math.max(
|
|
824
|
+
0,
|
|
825
|
+
readFiniteNumber(
|
|
826
|
+
"environmentLighting.sunlitBaseline",
|
|
827
|
+
source.sunlitBaseline ?? source.daylightBaseline,
|
|
828
|
+
DEFAULT_ENVIRONMENT_LIGHTING.sunlitBaseline
|
|
829
|
+
)
|
|
830
|
+
)
|
|
792
831
|
});
|
|
793
832
|
}
|
|
794
833
|
function evaluateReferenceEnvironmentRadiance(config, origin, direction) {
|
|
@@ -973,6 +1012,11 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
973
1012
|
options.tilePixelCapacity,
|
|
974
1013
|
DEFAULT_TILE_SIZE * DEFAULT_TILE_SIZE
|
|
975
1014
|
);
|
|
1015
|
+
const maxDepth = clamp(
|
|
1016
|
+
readPositiveInteger("maxDepth", options.maxDepth, DEFAULT_MAX_DEPTH),
|
|
1017
|
+
1,
|
|
1018
|
+
16
|
|
1019
|
+
);
|
|
976
1020
|
const sceneObjectCapacity = readPositiveInteger(
|
|
977
1021
|
"sceneObjectCapacity",
|
|
978
1022
|
options.sceneObjectCapacity,
|
|
@@ -998,6 +1042,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
998
1042
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
999
1043
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
1000
1044
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
1045
|
+
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
1001
1046
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
1002
1047
|
const triangleBytes = triangleCapacity * TRIANGLE_RECORD_BYTES;
|
|
1003
1048
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
@@ -1009,6 +1054,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1009
1054
|
queuePairBytes: queueBytes * 2,
|
|
1010
1055
|
hitBytes,
|
|
1011
1056
|
accumulationBytes,
|
|
1057
|
+
pathVertexBytes,
|
|
1012
1058
|
sceneObjectBytes,
|
|
1013
1059
|
triangleBytes,
|
|
1014
1060
|
bvhNodeBytes,
|
|
@@ -1018,7 +1064,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1018
1064
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
1019
1065
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
1020
1066
|
indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
|
|
1021
|
-
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1067
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + pathVertexBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1022
1068
|
});
|
|
1023
1069
|
}
|
|
1024
1070
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -1034,6 +1080,15 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1034
1080
|
1,
|
|
1035
1081
|
64
|
|
1036
1082
|
);
|
|
1083
|
+
const maxFramePassesPerSubmission = clamp(
|
|
1084
|
+
readPositiveInteger(
|
|
1085
|
+
"maxFramePassesPerSubmission",
|
|
1086
|
+
options.maxFramePassesPerSubmission,
|
|
1087
|
+
DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION
|
|
1088
|
+
),
|
|
1089
|
+
1,
|
|
1090
|
+
4096
|
|
1091
|
+
);
|
|
1037
1092
|
const tilePixelCapacity = readPositiveInteger(
|
|
1038
1093
|
"tilePixelCapacity",
|
|
1039
1094
|
options.tilePixelCapacity,
|
|
@@ -1094,6 +1149,10 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1094
1149
|
options.environmentPortalMode ?? options.portalMode ?? options.environmentLighting?.environmentPortalMode,
|
|
1095
1150
|
environmentPortals.length > 0
|
|
1096
1151
|
);
|
|
1152
|
+
const environmentMap = resolveEnvironmentMap(
|
|
1153
|
+
options.environmentMap ?? options.environmentTexture ?? options.environmentLighting?.environmentMap
|
|
1154
|
+
);
|
|
1155
|
+
const deferredPathResolve = resolveDeferredPathResolve(options);
|
|
1097
1156
|
return Object.freeze({
|
|
1098
1157
|
mode: rendererWavefrontComputeMode,
|
|
1099
1158
|
width,
|
|
@@ -1101,6 +1160,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1101
1160
|
maxDepth,
|
|
1102
1161
|
tileSize,
|
|
1103
1162
|
samplesPerPixel,
|
|
1163
|
+
maxFramePassesPerSubmission,
|
|
1104
1164
|
tilePixelCapacity,
|
|
1105
1165
|
sceneObjects,
|
|
1106
1166
|
sceneObjectCount: sceneObjects.length,
|
|
@@ -1127,12 +1187,15 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1127
1187
|
environmentPortalCount: environmentPortals.length,
|
|
1128
1188
|
environmentPortalCapacity,
|
|
1129
1189
|
environmentPortalMode,
|
|
1190
|
+
environmentMap,
|
|
1191
|
+
deferredPathResolve,
|
|
1130
1192
|
displayQuality: options.displayQuality === true,
|
|
1131
1193
|
requiresMeshBvhForDisplayQuality: true,
|
|
1132
1194
|
denoise: options.denoise !== false,
|
|
1133
1195
|
frameIndex: readNonNegativeInteger("frameIndex", options.frameIndex, 0),
|
|
1134
1196
|
memory: estimateWavefrontPathTracingMemory({
|
|
1135
1197
|
tilePixelCapacity,
|
|
1198
|
+
maxDepth,
|
|
1136
1199
|
sceneObjectCapacity,
|
|
1137
1200
|
triangleCapacity,
|
|
1138
1201
|
bvhNodeCapacity,
|
|
@@ -1329,6 +1392,18 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1329
1392
|
data.setUint32(260, config.environmentPortalMode ?? 0, true);
|
|
1330
1393
|
data.setUint32(264, 0, true);
|
|
1331
1394
|
data.setUint32(268, 0, true);
|
|
1395
|
+
writeVec4(floatView, 272, [
|
|
1396
|
+
config.environmentMap.enabled ? 1 : 0,
|
|
1397
|
+
config.environmentMap.intensity,
|
|
1398
|
+
config.environmentMap.rotationRadians,
|
|
1399
|
+
config.environmentMap.ambientStrength
|
|
1400
|
+
]);
|
|
1401
|
+
writeVec4(floatView, 288, [
|
|
1402
|
+
config.deferredPathResolve ? 1 : 0,
|
|
1403
|
+
config.environmentLighting.sunlitBaseline,
|
|
1404
|
+
0,
|
|
1405
|
+
0
|
|
1406
|
+
]);
|
|
1332
1407
|
return bytes;
|
|
1333
1408
|
}
|
|
1334
1409
|
function createTiles(width, height, tileSize) {
|
|
@@ -1574,6 +1649,136 @@ function alignTo(value, alignment) {
|
|
|
1574
1649
|
const resolvedAlignment = Math.max(1, alignment);
|
|
1575
1650
|
return Math.ceil(value / resolvedAlignment) * resolvedAlignment;
|
|
1576
1651
|
}
|
|
1652
|
+
function float32ToFloat16Bits(value) {
|
|
1653
|
+
const floatView = new Float32Array(1);
|
|
1654
|
+
const intView = new Uint32Array(floatView.buffer);
|
|
1655
|
+
floatView[0] = Number.isFinite(value) ? value : 0;
|
|
1656
|
+
const x = intView[0];
|
|
1657
|
+
const sign = x >> 16 & 32768;
|
|
1658
|
+
let mantissa = x & 8388607;
|
|
1659
|
+
let exponent = x >> 23 & 255;
|
|
1660
|
+
if (exponent === 255) {
|
|
1661
|
+
return sign | (mantissa ? 32256 : 31744);
|
|
1662
|
+
}
|
|
1663
|
+
exponent = exponent - 127 + 15;
|
|
1664
|
+
if (exponent >= 31) {
|
|
1665
|
+
return sign | 31744;
|
|
1666
|
+
}
|
|
1667
|
+
if (exponent <= 0) {
|
|
1668
|
+
if (exponent < -10) {
|
|
1669
|
+
return sign;
|
|
1670
|
+
}
|
|
1671
|
+
mantissa = (mantissa | 8388608) >> 1 - exponent;
|
|
1672
|
+
return sign | mantissa + 4096 >> 13;
|
|
1673
|
+
}
|
|
1674
|
+
return sign | exponent << 10 | mantissa + 4096 >> 13;
|
|
1675
|
+
}
|
|
1676
|
+
function environmentMapIntegerScale(data) {
|
|
1677
|
+
if (data instanceof Uint8Array) {
|
|
1678
|
+
return 1 / 255;
|
|
1679
|
+
}
|
|
1680
|
+
if (data instanceof Uint16Array) {
|
|
1681
|
+
return 1 / 65535;
|
|
1682
|
+
}
|
|
1683
|
+
return 1;
|
|
1684
|
+
}
|
|
1685
|
+
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
1686
|
+
if (!data || index >= data.length) {
|
|
1687
|
+
return fallback;
|
|
1688
|
+
}
|
|
1689
|
+
const value = Number(data[index]);
|
|
1690
|
+
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
1691
|
+
}
|
|
1692
|
+
function createEnvironmentMapUploadBytes(environmentMap, fallbackColor) {
|
|
1693
|
+
const width = Math.max(1, environmentMap.width);
|
|
1694
|
+
const height = Math.max(1, environmentMap.height);
|
|
1695
|
+
const rowBytes = width * 8;
|
|
1696
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
1697
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
1698
|
+
const data = environmentMap.data;
|
|
1699
|
+
const integerScale = environmentMapIntegerScale(data);
|
|
1700
|
+
const view = new DataView(bytes.buffer);
|
|
1701
|
+
const writeComponent = (targetOffset, sourceOffset, fallback) => {
|
|
1702
|
+
view.setUint16(
|
|
1703
|
+
targetOffset,
|
|
1704
|
+
float32ToFloat16Bits(
|
|
1705
|
+
readEnvironmentMapComponent(data, sourceOffset, fallback, integerScale)
|
|
1706
|
+
),
|
|
1707
|
+
true
|
|
1708
|
+
);
|
|
1709
|
+
};
|
|
1710
|
+
for (let y = 0; y < height; y += 1) {
|
|
1711
|
+
for (let x = 0; x < width; x += 1) {
|
|
1712
|
+
const sourceOffset = (y * width + x) * 4;
|
|
1713
|
+
const targetOffset = y * bytesPerRow + x * 8;
|
|
1714
|
+
writeComponent(targetOffset, sourceOffset, fallbackColor[0]);
|
|
1715
|
+
writeComponent(targetOffset + 2, sourceOffset + 1, fallbackColor[1]);
|
|
1716
|
+
writeComponent(targetOffset + 4, sourceOffset + 2, fallbackColor[2]);
|
|
1717
|
+
writeComponent(targetOffset + 6, sourceOffset + 3, fallbackColor[3] ?? 1);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
return Object.freeze({
|
|
1721
|
+
bytes,
|
|
1722
|
+
bytesPerRow,
|
|
1723
|
+
width,
|
|
1724
|
+
height
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
1728
|
+
if (environmentMap.view) {
|
|
1729
|
+
return Object.freeze({
|
|
1730
|
+
view: environmentMap.view,
|
|
1731
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
1732
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
1733
|
+
addressModeU: "repeat",
|
|
1734
|
+
addressModeV: "clamp-to-edge",
|
|
1735
|
+
magFilter: "linear",
|
|
1736
|
+
minFilter: "linear"
|
|
1737
|
+
}),
|
|
1738
|
+
texture: null,
|
|
1739
|
+
ownsTexture: false
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
if (environmentMap.texture && typeof environmentMap.texture.createView === "function") {
|
|
1743
|
+
return Object.freeze({
|
|
1744
|
+
view: environmentMap.texture.createView(),
|
|
1745
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
1746
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
1747
|
+
addressModeU: "repeat",
|
|
1748
|
+
addressModeV: "clamp-to-edge",
|
|
1749
|
+
magFilter: "linear",
|
|
1750
|
+
minFilter: "linear"
|
|
1751
|
+
}),
|
|
1752
|
+
texture: environmentMap.texture,
|
|
1753
|
+
ownsTexture: false
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
const upload = createEnvironmentMapUploadBytes(environmentMap, fallbackColor);
|
|
1757
|
+
const texture = device.createTexture({
|
|
1758
|
+
label: environmentMap.enabled ? "plasius.wavefront.environmentMap" : "plasius.wavefront.environmentMapFallback",
|
|
1759
|
+
size: { width: upload.width, height: upload.height },
|
|
1760
|
+
format: "rgba16float",
|
|
1761
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
1762
|
+
});
|
|
1763
|
+
device.queue.writeTexture(
|
|
1764
|
+
{ texture },
|
|
1765
|
+
upload.bytes,
|
|
1766
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
1767
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
1768
|
+
);
|
|
1769
|
+
return Object.freeze({
|
|
1770
|
+
view: texture.createView(),
|
|
1771
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
1772
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
1773
|
+
addressModeU: "repeat",
|
|
1774
|
+
addressModeV: "clamp-to-edge",
|
|
1775
|
+
magFilter: "linear",
|
|
1776
|
+
minFilter: "linear"
|
|
1777
|
+
}),
|
|
1778
|
+
texture,
|
|
1779
|
+
ownsTexture: true
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1577
1782
|
async function getPipelineDiagnostics(shaderModule) {
|
|
1578
1783
|
if (typeof shaderModule?.compilationInfo !== "function") {
|
|
1579
1784
|
return "";
|
|
@@ -1782,6 +1987,8 @@ struct FrameConfig {
|
|
|
1782
1987
|
environmentPortalMode: u32,
|
|
1783
1988
|
_portalPad0: u32,
|
|
1784
1989
|
_portalPad1: u32,
|
|
1990
|
+
environmentMapSettings: vec4<f32>,
|
|
1991
|
+
pathResolveSettings: vec4<f32>,
|
|
1785
1992
|
};
|
|
1786
1993
|
|
|
1787
1994
|
struct Counters {
|
|
@@ -1841,6 +2048,9 @@ struct EnvironmentPortal {
|
|
|
1841
2048
|
@group(0) @binding(17) var finalDenoiseInputRadiance: texture_2d<f32>;
|
|
1842
2049
|
@group(0) @binding(18) var denoisedOutputImage: texture_storage_2d<rgba8unorm, write>;
|
|
1843
2050
|
@group(0) @binding(19) var<storage, read> environmentPortals: array<EnvironmentPortal>;
|
|
2051
|
+
@group(0) @binding(20) var environmentMapTexture: texture_2d<f32>;
|
|
2052
|
+
@group(0) @binding(21) var environmentMapSampler: sampler;
|
|
2053
|
+
@group(0) @binding(22) var<storage, read_write> pathVertices: array<vec4<f32>>;
|
|
1844
2054
|
|
|
1845
2055
|
fn hash_u32(value: u32) -> u32 {
|
|
1846
2056
|
var x = value;
|
|
@@ -1885,6 +2095,89 @@ fn max_component(value: vec3<f32>) -> f32 {
|
|
|
1885
2095
|
return max(max(value.x, value.y), value.z);
|
|
1886
2096
|
}
|
|
1887
2097
|
|
|
2098
|
+
fn environment_map_enabled() -> bool {
|
|
2099
|
+
return config.environmentMapSettings.x > 0.5;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
fn deferred_path_resolve_enabled() -> bool {
|
|
2103
|
+
return config.pathResolveSettings.x > 0.5;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
fn path_vertex_count_per_ray() -> u32 {
|
|
2107
|
+
return config.maxDepth + 1u;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
fn path_vertex_index(rayId: u32, depth: u32) -> u32 {
|
|
2111
|
+
return rayId * path_vertex_count_per_ray() + min(depth, config.maxDepth);
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
fn clear_deferred_path(rayId: u32) {
|
|
2115
|
+
if (!deferred_path_resolve_enabled()) {
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
for (var depth = 0u; depth <= config.maxDepth; depth = depth + 1u) {
|
|
2120
|
+
pathVertices[path_vertex_index(rayId, depth)] = vec4<f32>(0.0);
|
|
2121
|
+
if (depth == config.maxDepth) {
|
|
2122
|
+
break;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
fn record_deferred_path_response(ray: RayRecord, response: vec3<f32>) {
|
|
2128
|
+
if (!deferred_path_resolve_enabled() || ray.rayId >= config.tilePixelCount || ray.bounce >= config.maxDepth) {
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
pathVertices[path_vertex_index(ray.rayId, ray.bounce)] =
|
|
2132
|
+
vec4<f32>(max(response, vec3<f32>(0.0)), 1.0);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
fn record_deferred_terminal_source(ray: RayRecord, sourceRadiance: vec3<f32>) {
|
|
2136
|
+
if (!deferred_path_resolve_enabled() || ray.rayId >= config.tilePixelCount) {
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
pathVertices[path_vertex_index(ray.rayId, config.maxDepth)] =
|
|
2140
|
+
vec4<f32>(clamp_sample_radiance(sourceRadiance), 1.0);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
fn environment_map_uv(direction: vec3<f32>) -> vec2<f32> {
|
|
2144
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
2145
|
+
let rotationTurns = config.environmentMapSettings.z / 6.28318530718;
|
|
2146
|
+
let u = fract(atan2(rayDirection.z, rayDirection.x) / 6.28318530718 + 0.5 + rotationTurns);
|
|
2147
|
+
let v = acos(clamp(rayDirection.y, -1.0, 1.0)) / 3.14159265359;
|
|
2148
|
+
return vec2<f32>(u, clamp(v, 0.0, 1.0));
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
fn environment_map_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
2152
|
+
let uv = environment_map_uv(direction);
|
|
2153
|
+
let texel = max(textureSampleLevel(environmentMapTexture, environmentMapSampler, uv, 0.0).rgb, vec3<f32>(0.0));
|
|
2154
|
+
return texel * max(config.environmentMapSettings.y, 0.0);
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
fn procedural_environment_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
2158
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
2159
|
+
let upFactor = saturate(rayDirection.y * 0.5 + 0.5);
|
|
2160
|
+
let sunDirection = safe_normalize(
|
|
2161
|
+
config.environmentSunDirectionIntensity.xyz,
|
|
2162
|
+
vec3<f32>(0.0, 1.0, 0.0)
|
|
2163
|
+
);
|
|
2164
|
+
let sunGlow = pow(saturate(dot(rayDirection, sunDirection)), 192.0);
|
|
2165
|
+
let gradient =
|
|
2166
|
+
config.environmentHorizonColor.xyz * (1.0 - upFactor) +
|
|
2167
|
+
config.environmentZenithColor.xyz * upFactor;
|
|
2168
|
+
return (
|
|
2169
|
+
gradient +
|
|
2170
|
+
config.environmentSunColor.xyz * sunGlow
|
|
2171
|
+
) * max(config.environmentSunDirectionIntensity.w, 0.0001);
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
fn base_environment_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
2175
|
+
if (environment_map_enabled()) {
|
|
2176
|
+
return environment_map_radiance(direction);
|
|
2177
|
+
}
|
|
2178
|
+
return procedural_environment_radiance(direction);
|
|
2179
|
+
}
|
|
2180
|
+
|
|
1888
2181
|
fn environment_portal_radiance_scale(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1889
2182
|
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
1890
2183
|
return vec3<f32>(1.0);
|
|
@@ -1924,22 +2217,9 @@ fn environment_portal_radiance_scale(origin: vec3<f32>, direction: vec3<f32>) ->
|
|
|
1924
2217
|
|
|
1925
2218
|
fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1926
2219
|
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
1927
|
-
let upFactor = saturate(rayDirection.y * 0.5 + 0.5);
|
|
1928
|
-
let sunDirection = safe_normalize(
|
|
1929
|
-
config.environmentSunDirectionIntensity.xyz,
|
|
1930
|
-
vec3<f32>(0.0, 1.0, 0.0)
|
|
1931
|
-
);
|
|
1932
|
-
let sunGlow = pow(saturate(dot(rayDirection, sunDirection)), 192.0);
|
|
1933
|
-
let gradient =
|
|
1934
|
-
config.environmentHorizonColor.xyz * (1.0 - upFactor) +
|
|
1935
|
-
config.environmentZenithColor.xyz * upFactor;
|
|
1936
2220
|
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
1937
2221
|
let portalHit = max_component(portalScale) > 0.0001;
|
|
1938
|
-
return (
|
|
1939
|
-
gradient +
|
|
1940
|
-
config.environmentSunColor.xyz * sunGlow
|
|
1941
|
-
) *
|
|
1942
|
-
max(config.environmentSunDirectionIntensity.w, 0.0001) *
|
|
2222
|
+
return base_environment_radiance(rayDirection) *
|
|
1943
2223
|
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
1944
2224
|
}
|
|
1945
2225
|
|
|
@@ -1955,16 +2235,143 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
1955
2235
|
return environment_radiance(origin, direction);
|
|
1956
2236
|
}
|
|
1957
2237
|
|
|
1958
|
-
fn
|
|
2238
|
+
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
2239
|
+
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
2240
|
+
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
2241
|
+
let materialEnergy = select(0.68, 0.92, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2242
|
+
let transparentEnergy = select(materialEnergy, 0.9, hit.hitType == 3u);
|
|
2243
|
+
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
2247
|
+
let baseline = max(config.pathResolveSettings.y, 0.0);
|
|
2248
|
+
if (baseline <= 0.000001) {
|
|
2249
|
+
return vec3<f32>(0.0);
|
|
2250
|
+
}
|
|
2251
|
+
let sunDirection = safe_normalize(
|
|
2252
|
+
config.environmentSunDirectionIntensity.xyz,
|
|
2253
|
+
vec3<f32>(0.0, 1.0, 0.0)
|
|
2254
|
+
);
|
|
2255
|
+
let sunFacing = saturate(dot(normal, sunDirection));
|
|
2256
|
+
let skyFacing = 0.35 + saturate(normal.y * 0.5 + 0.5) * 0.65;
|
|
2257
|
+
let directionalWeight = 0.38 + sunFacing * 0.62;
|
|
2258
|
+
let sunTint = max(config.environmentSunColor.xyz, vec3<f32>(0.0));
|
|
2259
|
+
return clamp_sample_radiance(sunTint * baseline * skyFacing * directionalWeight * 0.04);
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
1959
2263
|
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
1960
|
-
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
1961
2264
|
let normalEnvironment = gated_environment_radiance(
|
|
1962
2265
|
hit.position.xyz + normal * 0.003,
|
|
1963
2266
|
normal
|
|
1964
2267
|
);
|
|
1965
|
-
let
|
|
2268
|
+
let sunlitFloor = sunlit_baseline_radiance(normal);
|
|
2269
|
+
let ambientFloor = select(
|
|
2270
|
+
max(config.ambientColor.xyz, sunlitFloor * 0.82),
|
|
2271
|
+
max(config.ambientColor.xyz * 0.35, sunlitFloor * 0.58),
|
|
2272
|
+
environment_map_enabled()
|
|
2273
|
+
);
|
|
2274
|
+
let environmentInfluence = select(
|
|
2275
|
+
max(0.12, config.pathResolveSettings.y * 0.42),
|
|
2276
|
+
max(config.environmentMapSettings.w, max(0.12, config.pathResolveSettings.y * 0.42)),
|
|
2277
|
+
environment_map_enabled()
|
|
2278
|
+
);
|
|
2279
|
+
let environmentFloor = max(ambientFloor, max(sunlitFloor, normalEnvironment * environmentInfluence));
|
|
1966
2280
|
let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
|
|
1967
|
-
return clamp_sample_radiance(
|
|
2281
|
+
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2285
|
+
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
2286
|
+
return clamp_sample_radiance(
|
|
2287
|
+
ray.throughput.xyz *
|
|
2288
|
+
surfaceColor *
|
|
2289
|
+
terminal_surface_environment_source(hit)
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
2294
|
+
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
2295
|
+
return vec3<f32>(0.0);
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
var irradiance = vec3<f32>(0.0);
|
|
2299
|
+
for (var portalIndex = 0u; portalIndex < config.environmentPortalCount; portalIndex = portalIndex + 1u) {
|
|
2300
|
+
let portal = environmentPortals[portalIndex];
|
|
2301
|
+
if (portal.kind != 1u) {
|
|
2302
|
+
continue;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
let toPortal = portal.position.xyz - origin;
|
|
2306
|
+
let distanceSquared = max(dot(toPortal, toPortal), 0.01);
|
|
2307
|
+
let direction = safe_normalize(toPortal, normal);
|
|
2308
|
+
let surfaceFacing = saturate(dot(normal, direction));
|
|
2309
|
+
if (surfaceFacing <= 0.0001) {
|
|
2310
|
+
continue;
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
let portalNormal = safe_normalize(portal.normal.xyz, vec3<f32>(0.0, 0.0, 1.0));
|
|
2314
|
+
let twoSided = (portal.flags & 1u) != 0u;
|
|
2315
|
+
let portalFacing = select(
|
|
2316
|
+
saturate(dot(-direction, portalNormal)),
|
|
2317
|
+
max(abs(dot(direction, portalNormal)), 0.15),
|
|
2318
|
+
twoSided
|
|
2319
|
+
);
|
|
2320
|
+
let area = max(portal.position.w, 0.0001);
|
|
2321
|
+
let distanceFalloff = clamp(area / max(distanceSquared, area * 0.25), 0.0, 2.5);
|
|
2322
|
+
irradiance = irradiance +
|
|
2323
|
+
portal.color.rgb *
|
|
2324
|
+
portal.normal.w *
|
|
2325
|
+
portal.color.a *
|
|
2326
|
+
surfaceFacing *
|
|
2327
|
+
portalFacing *
|
|
2328
|
+
distanceFalloff;
|
|
2329
|
+
}
|
|
2330
|
+
return irradiance;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
fn surface_direct_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2334
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
2335
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
2336
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
2337
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
2338
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
2339
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
2340
|
+
|
|
2341
|
+
let normalEnvironment = gated_environment_radiance(origin, normal);
|
|
2342
|
+
let skyVisibility = 0.35 + saturate(normal.y * 0.5 + 0.5) * 0.45;
|
|
2343
|
+
let sunlitFloor = sunlit_baseline_radiance(normal);
|
|
2344
|
+
let ambientIrradiance = max(
|
|
2345
|
+
select(config.ambientColor.xyz * 0.72, config.ambientColor.xyz * 0.28, environment_map_enabled()),
|
|
2346
|
+
sunlitFloor * select(0.72, 0.45, environment_map_enabled())
|
|
2347
|
+
);
|
|
2348
|
+
let environmentIrradianceScale = select(
|
|
2349
|
+
max(0.16, config.pathResolveSettings.y * 0.45),
|
|
2350
|
+
max(config.environmentMapSettings.w, max(0.16, config.pathResolveSettings.y * 0.45)),
|
|
2351
|
+
environment_map_enabled()
|
|
2352
|
+
);
|
|
2353
|
+
let skyIrradiance = max(ambientIrradiance, normalEnvironment * skyVisibility * environmentIrradianceScale);
|
|
2354
|
+
|
|
2355
|
+
let sunDirection = safe_normalize(
|
|
2356
|
+
config.environmentSunDirectionIntensity.xyz,
|
|
2357
|
+
vec3<f32>(0.0, 1.0, 0.0)
|
|
2358
|
+
);
|
|
2359
|
+
let sunFacing = saturate(dot(normal, sunDirection));
|
|
2360
|
+
let sunRadiance = gated_environment_radiance(origin, sunDirection);
|
|
2361
|
+
let sunIrradiance = sunRadiance * sunFacing * 0.2;
|
|
2362
|
+
let portalIrradiance = direct_environment_portal_irradiance(origin, normal);
|
|
2363
|
+
|
|
2364
|
+
let diffuseWeight = select(1.0 - metallic * 0.65, 0.22, hit.materialKind == 1u);
|
|
2365
|
+
let diffuse = surfaceColor * (skyIrradiance + sunIrradiance + portalIrradiance) * diffuseWeight;
|
|
2366
|
+
|
|
2367
|
+
let halfVector = safe_normalize(sunDirection + viewDirection, normal);
|
|
2368
|
+
let specularPower = 8.0 + (1.0 - roughness) * 96.0;
|
|
2369
|
+
let specularFacing = pow(saturate(dot(normal, halfVector)), specularPower) * sunFacing;
|
|
2370
|
+
let specularTint = mix(vec3<f32>(0.04), surfaceColor, metallic);
|
|
2371
|
+
let specular = specularTint * sunRadiance * specularFacing * select(0.16, 0.48, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2372
|
+
|
|
2373
|
+
let bounceWeight = select(1.0, 0.38, ray.bounce > 0u);
|
|
2374
|
+
return clamp_sample_radiance(ray.throughput.xyz * (diffuse + specular) * bounceWeight);
|
|
1968
2375
|
}
|
|
1969
2376
|
|
|
1970
2377
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -2567,6 +2974,7 @@ fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2567
2974
|
return;
|
|
2568
2975
|
}
|
|
2569
2976
|
activeQueue[index] = make_ray(index);
|
|
2977
|
+
clear_deferred_path(index);
|
|
2570
2978
|
if (u32(config.projectionAndSampling.w) == 0u) {
|
|
2571
2979
|
accumulation[index] = vec4<f32>(0.0);
|
|
2572
2980
|
}
|
|
@@ -2819,27 +3227,51 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2819
3227
|
|
|
2820
3228
|
if (hit.hitType == 1u) {
|
|
2821
3229
|
let guidedLightWeight = select(1.0, 0.24, (ray.flags & RAY_FLAG_GUIDED_EMISSIVE) != 0u);
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
3230
|
+
let sourceRadiance = max(hit.emission.xyz, hit.color.xyz) * guidedLightWeight;
|
|
3231
|
+
if (deferred_path_resolve_enabled()) {
|
|
3232
|
+
record_deferred_terminal_source(ray, sourceRadiance);
|
|
3233
|
+
} else {
|
|
3234
|
+
contribution = clamp_sample_radiance(ray.throughput.xyz * sourceRadiance);
|
|
3235
|
+
accumulation[ray.rayId] =
|
|
3236
|
+
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3237
|
+
}
|
|
2827
3238
|
atomicAdd(&counters.terminatedCount, 1u);
|
|
2828
3239
|
return;
|
|
2829
3240
|
}
|
|
2830
3241
|
|
|
2831
3242
|
if (hit.hitType == 2u) {
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
3243
|
+
if (deferred_path_resolve_enabled()) {
|
|
3244
|
+
record_deferred_terminal_source(ray, hit.color.xyz);
|
|
3245
|
+
} else {
|
|
3246
|
+
contribution = clamp_sample_radiance(ray.throughput.xyz * max(hit.color.xyz, config.ambientColor.xyz));
|
|
3247
|
+
accumulation[ray.rayId] =
|
|
3248
|
+
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3249
|
+
}
|
|
2835
3250
|
atomicAdd(&counters.terminatedCount, 1u);
|
|
2836
3251
|
return;
|
|
2837
3252
|
}
|
|
2838
3253
|
|
|
2839
|
-
|
|
2840
|
-
|
|
3254
|
+
let response = surface_path_response(hit);
|
|
3255
|
+
record_deferred_path_response(ray, response);
|
|
3256
|
+
|
|
3257
|
+
let shouldEstimateDirectEnvironment =
|
|
3258
|
+
!deferred_path_resolve_enabled() &&
|
|
3259
|
+
(hit.materialKind == 0u || hit.materialKind == 1u) &&
|
|
3260
|
+
hit.material.z >= 0.95;
|
|
3261
|
+
if (shouldEstimateDirectEnvironment) {
|
|
3262
|
+
let directEnvironment = surface_direct_environment_contribution(ray, hit);
|
|
2841
3263
|
accumulation[ray.rayId] =
|
|
2842
|
-
accumulation[ray.rayId] + vec4<f32>(
|
|
3264
|
+
accumulation[ray.rayId] + vec4<f32>(directEnvironment * sample_weight(), 0.0);
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
if (ray.bounce + 1u >= config.maxDepth) {
|
|
3268
|
+
if (deferred_path_resolve_enabled()) {
|
|
3269
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
3270
|
+
} else {
|
|
3271
|
+
let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
3272
|
+
accumulation[ray.rayId] =
|
|
3273
|
+
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
3274
|
+
}
|
|
2843
3275
|
atomicAdd(&counters.terminatedCount, 1u);
|
|
2844
3276
|
return;
|
|
2845
3277
|
}
|
|
@@ -2848,17 +3280,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2848
3280
|
let scatter = scatter_direction(ray, hit, seed);
|
|
2849
3281
|
let nextIndex = atomicAdd(&counters.nextCount, 1u);
|
|
2850
3282
|
if (nextIndex >= config.tilePixelCount) {
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
3283
|
+
if (deferred_path_resolve_enabled()) {
|
|
3284
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
3285
|
+
} else {
|
|
3286
|
+
let overflowEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
3287
|
+
accumulation[ray.rayId] =
|
|
3288
|
+
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
3289
|
+
}
|
|
2854
3290
|
atomicAdd(&counters.terminatedCount, 1u);
|
|
2855
3291
|
return;
|
|
2856
3292
|
}
|
|
2857
|
-
let
|
|
2858
|
-
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
2859
|
-
let materialEnergy = select(0.68, 0.92, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2860
|
-
let transparentEnergy = select(materialEnergy, 0.9, hit.hitType == 3u);
|
|
2861
|
-
let throughput = ray.throughput.xyz * mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy;
|
|
3293
|
+
let throughput = ray.throughput.xyz * response;
|
|
2862
3294
|
nextQueue[nextIndex] = RayRecord(
|
|
2863
3295
|
ray.rayId,
|
|
2864
3296
|
ray.rayId,
|
|
@@ -2886,6 +3318,27 @@ fn compactAndSwapQueues(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2886
3318
|
write_active_dispatch_args(activeCount);
|
|
2887
3319
|
}
|
|
2888
3320
|
|
|
3321
|
+
fn resolve_deferred_path_radiance(rayId: u32) -> vec3<f32> {
|
|
3322
|
+
let terminal = pathVertices[path_vertex_index(rayId, config.maxDepth)];
|
|
3323
|
+
if (terminal.w <= 0.0) {
|
|
3324
|
+
return vec3<f32>(0.0);
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
var radiance = terminal.xyz;
|
|
3328
|
+
var depth = config.maxDepth;
|
|
3329
|
+
loop {
|
|
3330
|
+
if (depth == 0u) {
|
|
3331
|
+
break;
|
|
3332
|
+
}
|
|
3333
|
+
depth = depth - 1u;
|
|
3334
|
+
let response = pathVertices[path_vertex_index(rayId, depth)];
|
|
3335
|
+
if (response.w > 0.0) {
|
|
3336
|
+
radiance = radiance * response.xyz;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
return clamp_sample_radiance(radiance);
|
|
3340
|
+
}
|
|
3341
|
+
|
|
2889
3342
|
@compute @workgroup_size(64)
|
|
2890
3343
|
fn accumulateTerminalRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
2891
3344
|
let index = globalId.x;
|
|
@@ -2895,7 +3348,12 @@ fn accumulateTerminalRadiance(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
2895
3348
|
let localX = index % config.tileWidth;
|
|
2896
3349
|
let localY = index / config.tileWidth;
|
|
2897
3350
|
let pixel = vec2<i32>(i32(config.tileX + localX), i32(config.tileY + localY));
|
|
2898
|
-
|
|
3351
|
+
var radiance = max(accumulation[index].xyz, vec3<f32>(0.0));
|
|
3352
|
+
if (deferred_path_resolve_enabled()) {
|
|
3353
|
+
let resolved = resolve_deferred_path_radiance(index) * sample_weight();
|
|
3354
|
+
radiance = clamp_sample_radiance(radiance + resolved);
|
|
3355
|
+
accumulation[index] = vec4<f32>(radiance, 1.0);
|
|
3356
|
+
}
|
|
2899
3357
|
|
|
2900
3358
|
textureStore(radianceImage, pixel, vec4<f32>(radiance, 1.0));
|
|
2901
3359
|
if (config.denoise == 0u) {
|
|
@@ -3033,6 +3491,125 @@ function createWavefrontDeviceDescriptor(adapter, options = {}) {
|
|
|
3033
3491
|
}
|
|
3034
3492
|
return Object.keys(descriptor).length > 0 ? descriptor : void 0;
|
|
3035
3493
|
}
|
|
3494
|
+
function readGpuLimit(adapter, device, name) {
|
|
3495
|
+
const adapterValue = Number(adapter?.limits?.[name]);
|
|
3496
|
+
if (Number.isFinite(adapterValue)) {
|
|
3497
|
+
return adapterValue;
|
|
3498
|
+
}
|
|
3499
|
+
const deviceValue = Number(device?.limits?.[name]);
|
|
3500
|
+
return Number.isFinite(deviceValue) ? deviceValue : null;
|
|
3501
|
+
}
|
|
3502
|
+
function createAdapterInfoSnapshot(adapter) {
|
|
3503
|
+
const info = adapter?.info;
|
|
3504
|
+
if (!info || typeof info !== "object") {
|
|
3505
|
+
return null;
|
|
3506
|
+
}
|
|
3507
|
+
return Object.freeze({
|
|
3508
|
+
vendor: typeof info.vendor === "string" ? info.vendor : "",
|
|
3509
|
+
architecture: typeof info.architecture === "string" ? info.architecture : "",
|
|
3510
|
+
device: typeof info.device === "string" ? info.device : "",
|
|
3511
|
+
description: typeof info.description === "string" ? info.description : ""
|
|
3512
|
+
});
|
|
3513
|
+
}
|
|
3514
|
+
function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
3515
|
+
return Object.freeze({
|
|
3516
|
+
physicalCoreCount: null,
|
|
3517
|
+
physicalCoreCountAvailable: false,
|
|
3518
|
+
physicalCoreCountUnavailableReason: "WebGPU does not expose physical GPU core counts.",
|
|
3519
|
+
adapterInfo: createAdapterInfoSnapshot(adapter),
|
|
3520
|
+
adapterLimits: Object.freeze({
|
|
3521
|
+
maxComputeInvocationsPerWorkgroup: readGpuLimit(adapter, device, "maxComputeInvocationsPerWorkgroup"),
|
|
3522
|
+
maxComputeWorkgroupSizeX: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeX"),
|
|
3523
|
+
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
3524
|
+
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
3525
|
+
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
3526
|
+
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
3527
|
+
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize")
|
|
3528
|
+
}),
|
|
3529
|
+
configuredWorkgroupSize: WORKGROUP_SIZE
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3532
|
+
function createGpuParallelismCounters() {
|
|
3533
|
+
return {
|
|
3534
|
+
directDispatches: 0,
|
|
3535
|
+
directWorkgroups: 0,
|
|
3536
|
+
directShaderInvocations: 0,
|
|
3537
|
+
multiWorkgroupDispatches: 0,
|
|
3538
|
+
largestDirectWorkgroupsPerDispatch: 0,
|
|
3539
|
+
indirectDispatches: 0,
|
|
3540
|
+
estimatedIndirectWorkgroupsUpperBound: 0,
|
|
3541
|
+
estimatedIndirectShaderInvocationsUpperBound: 0,
|
|
3542
|
+
indirectDispatchesWithMultiWorkgroupCapacity: 0,
|
|
3543
|
+
largestEstimatedIndirectWorkgroupsPerDispatch: 0
|
|
3544
|
+
};
|
|
3545
|
+
}
|
|
3546
|
+
function countDispatchWorkgroups(groups) {
|
|
3547
|
+
return groups.reduce((product, value) => {
|
|
3548
|
+
const numeric = Number(value ?? 1);
|
|
3549
|
+
const count = Number.isFinite(numeric) ? Math.max(1, Math.trunc(numeric)) : 1;
|
|
3550
|
+
return product * count;
|
|
3551
|
+
}, 1);
|
|
3552
|
+
}
|
|
3553
|
+
function recordDirectDispatch(parallelism, groups, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3554
|
+
const workgroups = countDispatchWorkgroups(groups);
|
|
3555
|
+
parallelism.directDispatches += 1;
|
|
3556
|
+
parallelism.directWorkgroups += workgroups;
|
|
3557
|
+
parallelism.directShaderInvocations += workgroups * invocationsPerWorkgroup;
|
|
3558
|
+
parallelism.largestDirectWorkgroupsPerDispatch = Math.max(
|
|
3559
|
+
parallelism.largestDirectWorkgroupsPerDispatch,
|
|
3560
|
+
workgroups
|
|
3561
|
+
);
|
|
3562
|
+
if (workgroups > 1) {
|
|
3563
|
+
parallelism.multiWorkgroupDispatches += 1;
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
function recordIndirectDispatch(parallelism, estimatedWorkgroupsUpperBound, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3567
|
+
const workgroups = Math.max(1, Math.trunc(Number(estimatedWorkgroupsUpperBound) || 1));
|
|
3568
|
+
parallelism.indirectDispatches += 1;
|
|
3569
|
+
parallelism.estimatedIndirectWorkgroupsUpperBound += workgroups;
|
|
3570
|
+
parallelism.estimatedIndirectShaderInvocationsUpperBound += workgroups * invocationsPerWorkgroup;
|
|
3571
|
+
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch = Math.max(
|
|
3572
|
+
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3573
|
+
workgroups
|
|
3574
|
+
);
|
|
3575
|
+
if (workgroups > 1) {
|
|
3576
|
+
parallelism.indirectDispatchesWithMultiWorkgroupCapacity += 1;
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
function createGpuParallelismDiagnostics(adapterDiagnostics, counters) {
|
|
3580
|
+
const totalEstimatedWorkgroupsUpperBound = counters.directWorkgroups + counters.estimatedIndirectWorkgroupsUpperBound;
|
|
3581
|
+
const totalEstimatedShaderInvocationsUpperBound = counters.directShaderInvocations + counters.estimatedIndirectShaderInvocationsUpperBound;
|
|
3582
|
+
const exposesMultiWorkgroupParallelism = counters.multiWorkgroupDispatches > 0 || counters.indirectDispatchesWithMultiWorkgroupCapacity > 0;
|
|
3583
|
+
return Object.freeze({
|
|
3584
|
+
...adapterDiagnostics,
|
|
3585
|
+
directDispatches: counters.directDispatches,
|
|
3586
|
+
directWorkgroups: counters.directWorkgroups,
|
|
3587
|
+
directShaderInvocations: counters.directShaderInvocations,
|
|
3588
|
+
multiWorkgroupDispatches: counters.multiWorkgroupDispatches,
|
|
3589
|
+
largestDirectWorkgroupsPerDispatch: counters.largestDirectWorkgroupsPerDispatch,
|
|
3590
|
+
indirectDispatches: counters.indirectDispatches,
|
|
3591
|
+
estimatedIndirectWorkgroupsUpperBound: counters.estimatedIndirectWorkgroupsUpperBound,
|
|
3592
|
+
estimatedIndirectShaderInvocationsUpperBound: counters.estimatedIndirectShaderInvocationsUpperBound,
|
|
3593
|
+
indirectDispatchesWithMultiWorkgroupCapacity: counters.indirectDispatchesWithMultiWorkgroupCapacity,
|
|
3594
|
+
largestEstimatedIndirectWorkgroupsPerDispatch: counters.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3595
|
+
totalEstimatedWorkgroupsUpperBound,
|
|
3596
|
+
totalEstimatedShaderInvocationsUpperBound,
|
|
3597
|
+
exposesMultiWorkgroupParallelism,
|
|
3598
|
+
likelyUsesMoreThanOnePhysicalGpuCore: null,
|
|
3599
|
+
coreUtilizationStatus: "not-exposed-by-webgpu"
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
function createEnvironmentMapSnapshot(environmentMap) {
|
|
3603
|
+
return Object.freeze({
|
|
3604
|
+
enabled: environmentMap.enabled,
|
|
3605
|
+
width: environmentMap.width,
|
|
3606
|
+
height: environmentMap.height,
|
|
3607
|
+
projection: environmentMap.projection,
|
|
3608
|
+
intensity: environmentMap.intensity,
|
|
3609
|
+
rotationRadians: environmentMap.rotationRadians,
|
|
3610
|
+
ambientStrength: environmentMap.ambientStrength
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3036
3613
|
async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
3037
3614
|
assertAnalyticDisplayQualityPolicy(options);
|
|
3038
3615
|
const constants = getGpuUsageConstants();
|
|
@@ -3052,6 +3629,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3052
3629
|
throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
|
|
3053
3630
|
}
|
|
3054
3631
|
const device = await adapter.requestDevice(createWavefrontDeviceDescriptor(adapter, options));
|
|
3632
|
+
const gpuAdapterParallelism = createGpuAdapterParallelismDiagnostics(adapter, device);
|
|
3055
3633
|
const context = canvas.getContext("webgpu");
|
|
3056
3634
|
if (!context || typeof context.configure !== "function") {
|
|
3057
3635
|
throw new Error("Canvas WebGPU context does not support configure().");
|
|
@@ -3079,6 +3657,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3079
3657
|
const rayQueueBytes = config.tilePixelCapacity * RAY_RECORD_BYTES;
|
|
3080
3658
|
const hitBytes = config.tilePixelCapacity * HIT_RECORD_BYTES;
|
|
3081
3659
|
const accumulationBytes = config.tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
3660
|
+
const pathVertexBytes = config.tilePixelCapacity * (config.maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
3082
3661
|
const activeQueue = createBuffer(device, bufferUsage, rayQueueBytes, "plasius.wavefront.activeQueue");
|
|
3083
3662
|
const nextQueue = createBuffer(device, bufferUsage, rayQueueBytes, "plasius.wavefront.nextQueue");
|
|
3084
3663
|
const hitBuffer = createBuffer(device, bufferUsage, hitBytes, "plasius.wavefront.hitBuffer");
|
|
@@ -3088,6 +3667,12 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3088
3667
|
accumulationBytes,
|
|
3089
3668
|
"plasius.wavefront.accumulation"
|
|
3090
3669
|
);
|
|
3670
|
+
const pathVertexBuffer = createBuffer(
|
|
3671
|
+
device,
|
|
3672
|
+
bufferUsage,
|
|
3673
|
+
pathVertexBytes,
|
|
3674
|
+
"plasius.wavefront.pathVertices"
|
|
3675
|
+
);
|
|
3091
3676
|
const sceneObjectBuffer = createBuffer(
|
|
3092
3677
|
device,
|
|
3093
3678
|
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
@@ -3142,9 +3727,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3142
3727
|
CONFIG_BUFFER_BYTES,
|
|
3143
3728
|
Number.isFinite(uniformOffsetAlignment) && uniformOffsetAlignment > 0 ? uniformOffsetAlignment : CONFIG_BUFFER_BYTES
|
|
3144
3729
|
);
|
|
3730
|
+
const outputConfigSlotCount = config.deferredPathResolve ? 0 : tiles.length;
|
|
3145
3731
|
const frameConfigSlotCount = Math.max(
|
|
3146
3732
|
1,
|
|
3147
|
-
tiles.length * config.samplesPerPixel +
|
|
3733
|
+
tiles.length * config.samplesPerPixel + outputConfigSlotCount + (config.denoise ? 1 : 0)
|
|
3148
3734
|
);
|
|
3149
3735
|
const configBuffer = createBuffer(
|
|
3150
3736
|
device,
|
|
@@ -3222,6 +3808,12 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3222
3808
|
magFilter: "nearest",
|
|
3223
3809
|
minFilter: "nearest"
|
|
3224
3810
|
});
|
|
3811
|
+
const environmentMapResource = createEnvironmentMapResource(
|
|
3812
|
+
device,
|
|
3813
|
+
constants,
|
|
3814
|
+
config.environmentMap,
|
|
3815
|
+
config.environmentColor
|
|
3816
|
+
);
|
|
3225
3817
|
const traceBindGroupLayout = device.createBindGroupLayout({
|
|
3226
3818
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
3227
3819
|
entries: [
|
|
@@ -3248,7 +3840,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3248
3840
|
visibility: constants.shader.COMPUTE,
|
|
3249
3841
|
storageTexture: { access: "write-only", format: "rgba16float" }
|
|
3250
3842
|
},
|
|
3251
|
-
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } }
|
|
3843
|
+
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
3844
|
+
{ binding: 20, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
3845
|
+
{ binding: 21, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
3846
|
+
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } }
|
|
3252
3847
|
]
|
|
3253
3848
|
});
|
|
3254
3849
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -3422,7 +4017,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3422
4017
|
{ binding: 8, resource: { buffer: triangleBuffer } },
|
|
3423
4018
|
{ binding: 9, resource: { buffer: bvhNodeBuffer } },
|
|
3424
4019
|
{ binding: 16, resource: radianceView },
|
|
3425
|
-
{ binding: 19, resource: { buffer: environmentPortalBuffer } }
|
|
4020
|
+
{ binding: 19, resource: { buffer: environmentPortalBuffer } },
|
|
4021
|
+
{ binding: 20, resource: environmentMapResource.view },
|
|
4022
|
+
{ binding: 21, resource: environmentMapResource.sampler },
|
|
4023
|
+
{ binding: 22, resource: { buffer: pathVertexBuffer } }
|
|
3426
4024
|
]
|
|
3427
4025
|
});
|
|
3428
4026
|
}
|
|
@@ -3511,6 +4109,11 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3511
4109
|
let frame = 0;
|
|
3512
4110
|
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
3513
4111
|
let accelerationBuildCount = 0;
|
|
4112
|
+
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA;
|
|
4113
|
+
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
4114
|
+
gpuAdapterParallelism,
|
|
4115
|
+
createGpuParallelismCounters()
|
|
4116
|
+
);
|
|
3514
4117
|
function createFrameConfigWriter(frameIndex) {
|
|
3515
4118
|
let slot = 0;
|
|
3516
4119
|
return (tile, buildRange = {}) => {
|
|
@@ -3527,7 +4130,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3527
4130
|
return offset;
|
|
3528
4131
|
};
|
|
3529
4132
|
}
|
|
3530
|
-
function dispatchGpuAccelerationBuild(frameIndex) {
|
|
4133
|
+
function dispatchGpuAccelerationBuild(frameIndex, parallelism) {
|
|
3531
4134
|
if (!config.gpuAccelerationBuildRequired || accelerationBuilt) {
|
|
3532
4135
|
return false;
|
|
3533
4136
|
}
|
|
@@ -3566,24 +4169,32 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3566
4169
|
});
|
|
3567
4170
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [0]);
|
|
3568
4171
|
passEncoder.setPipeline(pipelines.prepareMeshTrianglesAndLeaves);
|
|
3569
|
-
|
|
4172
|
+
const prepareWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4173
|
+
passEncoder.dispatchWorkgroups(prepareWorkgroups);
|
|
4174
|
+
recordDirectDispatch(parallelism, [prepareWorkgroups]);
|
|
3570
4175
|
passEncoder.setPipeline(pipelines.sortBvhLeafRefs);
|
|
3571
4176
|
for (let stageIndex = 0; stageIndex < config.bvhSortStages.length; stageIndex += 1) {
|
|
3572
4177
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [
|
|
3573
4178
|
(stageIndex + 1) * configBufferStride
|
|
3574
4179
|
]);
|
|
3575
|
-
|
|
4180
|
+
const sortWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4181
|
+
passEncoder.dispatchWorkgroups(sortWorkgroups);
|
|
4182
|
+
recordDirectDispatch(parallelism, [sortWorkgroups]);
|
|
3576
4183
|
}
|
|
3577
4184
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [0]);
|
|
3578
4185
|
passEncoder.setPipeline(pipelines.writeSortedBvhLeaves);
|
|
3579
|
-
|
|
4186
|
+
const leafWriteWorkgroups = Math.ceil(config.triangleCount / WORKGROUP_SIZE);
|
|
4187
|
+
passEncoder.dispatchWorkgroups(leafWriteWorkgroups);
|
|
4188
|
+
recordDirectDispatch(parallelism, [leafWriteWorkgroups]);
|
|
3580
4189
|
passEncoder.setPipeline(pipelines.buildBvhInternalLevel);
|
|
3581
4190
|
for (let levelIndex = 0; levelIndex < config.bvhBuildLevels.length; levelIndex += 1) {
|
|
3582
4191
|
const buildLevel = config.bvhBuildLevels[levelIndex];
|
|
3583
4192
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [
|
|
3584
4193
|
(buildLevelConfigStart + levelIndex) * configBufferStride
|
|
3585
4194
|
]);
|
|
3586
|
-
|
|
4195
|
+
const levelWorkgroups = Math.ceil(buildLevel.count / WORKGROUP_SIZE);
|
|
4196
|
+
passEncoder.dispatchWorkgroups(levelWorkgroups);
|
|
4197
|
+
recordDirectDispatch(parallelism, [levelWorkgroups]);
|
|
3587
4198
|
}
|
|
3588
4199
|
passEncoder.end();
|
|
3589
4200
|
device.queue.submit([encoder.finish()]);
|
|
@@ -3591,7 +4202,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3591
4202
|
accelerationBuildCount += 1;
|
|
3592
4203
|
return true;
|
|
3593
4204
|
}
|
|
3594
|
-
function encodeTileSample(encoder, tile, configOffset) {
|
|
4205
|
+
function encodeTileSample(encoder, tile, configOffset, parallelism) {
|
|
3595
4206
|
const generatePass = encoder.beginComputePass({
|
|
3596
4207
|
label: "plasius.wavefront.generatePrimaryRaysPass"
|
|
3597
4208
|
});
|
|
@@ -3599,6 +4210,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3599
4210
|
generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3600
4211
|
generatePass.setPipeline(pipelines.generatePrimaryRays);
|
|
3601
4212
|
generatePass.dispatchWorkgroups(tileWorkgroups);
|
|
4213
|
+
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
3602
4214
|
generatePass.end();
|
|
3603
4215
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
3604
4216
|
encoder.copyBufferToBuffer(
|
|
@@ -3614,14 +4226,17 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3614
4226
|
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
3615
4227
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
3616
4228
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4229
|
+
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
3617
4230
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
3618
4231
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4232
|
+
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
3619
4233
|
passEncoder.setPipeline(pipelines.compactAndSwapQueues);
|
|
3620
4234
|
passEncoder.dispatchWorkgroups(1);
|
|
4235
|
+
recordDirectDispatch(parallelism, [1], 1);
|
|
3621
4236
|
passEncoder.end();
|
|
3622
4237
|
}
|
|
3623
4238
|
}
|
|
3624
|
-
function encodeTileOutput(encoder, tile, configOffset) {
|
|
4239
|
+
function encodeTileOutput(encoder, tile, configOffset, parallelism) {
|
|
3625
4240
|
const passEncoder = encoder.beginComputePass({
|
|
3626
4241
|
label: "plasius.wavefront.outputPass"
|
|
3627
4242
|
});
|
|
@@ -3629,25 +4244,30 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3629
4244
|
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3630
4245
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
3631
4246
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
4247
|
+
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
3632
4248
|
passEncoder.end();
|
|
3633
4249
|
}
|
|
3634
|
-
function encodeDenoise(encoder, configOffset) {
|
|
4250
|
+
function encodeDenoise(encoder, configOffset, parallelism) {
|
|
3635
4251
|
if (!config.denoise) {
|
|
3636
4252
|
return;
|
|
3637
4253
|
}
|
|
4254
|
+
const denoiseWorkgroupsX = Math.ceil(config.width / 8);
|
|
4255
|
+
const denoiseWorkgroupsY = Math.ceil(config.height / 8);
|
|
3638
4256
|
const radiancePass = encoder.beginComputePass({
|
|
3639
4257
|
label: "plasius.wavefront.denoiseRadiancePass"
|
|
3640
4258
|
});
|
|
3641
4259
|
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
3642
4260
|
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
3643
|
-
radiancePass.dispatchWorkgroups(
|
|
4261
|
+
radiancePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
4262
|
+
recordDirectDispatch(parallelism, [denoiseWorkgroupsX, denoiseWorkgroupsY]);
|
|
3644
4263
|
radiancePass.end();
|
|
3645
4264
|
const resolvePass = encoder.beginComputePass({
|
|
3646
4265
|
label: "plasius.wavefront.denoiseResolvePass"
|
|
3647
4266
|
});
|
|
3648
4267
|
resolvePass.setBindGroup(0, denoiseResolveBindGroup, [configOffset]);
|
|
3649
4268
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
3650
|
-
resolvePass.dispatchWorkgroups(
|
|
4269
|
+
resolvePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
4270
|
+
recordDirectDispatch(parallelism, [denoiseWorkgroupsX, denoiseWorkgroupsY]);
|
|
3651
4271
|
resolvePass.end();
|
|
3652
4272
|
}
|
|
3653
4273
|
function encodePresent(encoder) {
|
|
@@ -3668,41 +4288,68 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3668
4288
|
passEncoder.draw(3);
|
|
3669
4289
|
passEncoder.end();
|
|
3670
4290
|
}
|
|
3671
|
-
function dispatchFrame(frameIndex) {
|
|
4291
|
+
function dispatchFrame(frameIndex, parallelism) {
|
|
3672
4292
|
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
3673
|
-
|
|
3674
|
-
|
|
4293
|
+
let submissionCount = 0;
|
|
4294
|
+
let encodedFramePasses = 0;
|
|
4295
|
+
let encoder = device.createCommandEncoder({
|
|
4296
|
+
label: `plasius.wavefront.frame.${frameIndex}.batched.${submissionCount + 1}`
|
|
3675
4297
|
});
|
|
4298
|
+
function submitCurrentEncoder() {
|
|
4299
|
+
if (encodedFramePasses <= 0) {
|
|
4300
|
+
return;
|
|
4301
|
+
}
|
|
4302
|
+
device.queue.submit([encoder.finish()]);
|
|
4303
|
+
submissionCount += 1;
|
|
4304
|
+
encodedFramePasses = 0;
|
|
4305
|
+
encoder = device.createCommandEncoder({
|
|
4306
|
+
label: `plasius.wavefront.frame.${frameIndex}.batched.${submissionCount + 1}`
|
|
4307
|
+
});
|
|
4308
|
+
}
|
|
4309
|
+
function reserveEncoder(passCount = 1) {
|
|
4310
|
+
if (encodedFramePasses > 0 && encodedFramePasses + passCount > config.maxFramePassesPerSubmission) {
|
|
4311
|
+
submitCurrentEncoder();
|
|
4312
|
+
}
|
|
4313
|
+
encodedFramePasses += passCount;
|
|
4314
|
+
return encoder;
|
|
4315
|
+
}
|
|
3676
4316
|
for (const tile of tiles) {
|
|
3677
4317
|
for (let sampleIndex = 0; sampleIndex < config.samplesPerPixel; sampleIndex += 1) {
|
|
3678
4318
|
const configOffset = writeFrameConfig(tile, {
|
|
3679
4319
|
sampleIndex,
|
|
3680
4320
|
sampleWeight: 1 / config.samplesPerPixel
|
|
3681
4321
|
});
|
|
3682
|
-
encodeTileSample(
|
|
4322
|
+
encodeTileSample(reserveEncoder(), tile, configOffset, parallelism);
|
|
4323
|
+
if (config.deferredPathResolve) {
|
|
4324
|
+
encodeTileOutput(reserveEncoder(), tile, configOffset, parallelism);
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
if (!config.deferredPathResolve) {
|
|
4328
|
+
const outputConfigOffset = writeFrameConfig(tile, {
|
|
4329
|
+
sampleIndex: 0,
|
|
4330
|
+
sampleWeight: 1 / config.samplesPerPixel
|
|
4331
|
+
});
|
|
4332
|
+
encodeTileOutput(reserveEncoder(), tile, outputConfigOffset, parallelism);
|
|
3683
4333
|
}
|
|
3684
|
-
const outputConfigOffset = writeFrameConfig(tile, {
|
|
3685
|
-
sampleIndex: 0,
|
|
3686
|
-
sampleWeight: 1 / config.samplesPerPixel
|
|
3687
|
-
});
|
|
3688
|
-
encodeTileOutput(encoder, tile, outputConfigOffset);
|
|
3689
4334
|
}
|
|
3690
4335
|
if (config.denoise) {
|
|
3691
4336
|
const denoiseConfigOffset = writeFrameConfig(
|
|
3692
4337
|
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
3693
4338
|
{ sampleIndex: 0, sampleWeight: 1 / config.samplesPerPixel }
|
|
3694
4339
|
);
|
|
3695
|
-
encodeDenoise(
|
|
4340
|
+
encodeDenoise(reserveEncoder(), denoiseConfigOffset, parallelism);
|
|
3696
4341
|
}
|
|
3697
|
-
encodePresent(
|
|
3698
|
-
|
|
3699
|
-
return
|
|
4342
|
+
encodePresent(reserveEncoder());
|
|
4343
|
+
submitCurrentEncoder();
|
|
4344
|
+
return submissionCount;
|
|
3700
4345
|
}
|
|
3701
4346
|
function renderOnce() {
|
|
3702
4347
|
frame += 1;
|
|
3703
4348
|
const frameIndex = frame + config.frameIndex;
|
|
3704
|
-
const
|
|
3705
|
-
const
|
|
4349
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
4350
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
4351
|
+
const frameSubmissionCount = dispatchFrame(frameIndex, parallelismCounters);
|
|
4352
|
+
lastGpuParallelism = createGpuParallelismDiagnostics(gpuAdapterParallelism, parallelismCounters);
|
|
3706
4353
|
return Object.freeze({
|
|
3707
4354
|
frame,
|
|
3708
4355
|
width: config.width,
|
|
@@ -3711,6 +4358,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3711
4358
|
tiles: tiles.length,
|
|
3712
4359
|
tileSize: config.tileSize,
|
|
3713
4360
|
samplesPerPixel: config.samplesPerPixel,
|
|
4361
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
3714
4362
|
screenRays: config.width * config.height,
|
|
3715
4363
|
primaryRays: config.width * config.height * config.samplesPerPixel,
|
|
3716
4364
|
sceneObjectCount: config.sceneObjectCount,
|
|
@@ -3718,6 +4366,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3718
4366
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3719
4367
|
environmentPortalCount: config.environmentPortalCount,
|
|
3720
4368
|
environmentPortalMode: config.environmentPortalMode,
|
|
4369
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
4370
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
3721
4371
|
bvhNodeCount: config.bvhNodeCount,
|
|
3722
4372
|
displayQuality: config.displayQuality,
|
|
3723
4373
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
@@ -3727,6 +4377,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3727
4377
|
accelerationBuildCount,
|
|
3728
4378
|
commandSubmissions: frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
3729
4379
|
frameConfigSlots: frameConfigSlotCount,
|
|
4380
|
+
gpuParallelism: lastGpuParallelism,
|
|
3730
4381
|
memory: config.memory
|
|
3731
4382
|
});
|
|
3732
4383
|
}
|
|
@@ -3797,11 +4448,29 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3797
4448
|
samplesPerPixel: config.samplesPerPixel,
|
|
3798
4449
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
3799
4450
|
sceneObjects: packedScene.objects,
|
|
4451
|
+
camera: activeCameraOptions,
|
|
3800
4452
|
frameIndex: config.frameIndex
|
|
3801
4453
|
});
|
|
3802
4454
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
3803
4455
|
return config;
|
|
3804
4456
|
}
|
|
4457
|
+
function updateCamera(cameraOptions = {}) {
|
|
4458
|
+
activeCameraOptions = cameraOptions;
|
|
4459
|
+
config = createWavefrontPathTracingComputeConfig({
|
|
4460
|
+
...options,
|
|
4461
|
+
canvas,
|
|
4462
|
+
width: config.width,
|
|
4463
|
+
height: config.height,
|
|
4464
|
+
maxDepth: config.maxDepth,
|
|
4465
|
+
tileSize: config.tileSize,
|
|
4466
|
+
samplesPerPixel: config.samplesPerPixel,
|
|
4467
|
+
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4468
|
+
sceneObjects: packedScene.objects,
|
|
4469
|
+
camera: activeCameraOptions,
|
|
4470
|
+
frameIndex: config.frameIndex
|
|
4471
|
+
});
|
|
4472
|
+
return config;
|
|
4473
|
+
}
|
|
3805
4474
|
function getSnapshot() {
|
|
3806
4475
|
return Object.freeze({
|
|
3807
4476
|
frame,
|
|
@@ -3811,11 +4480,14 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3811
4480
|
tiles: tiles.length,
|
|
3812
4481
|
tileSize: config.tileSize,
|
|
3813
4482
|
samplesPerPixel: config.samplesPerPixel,
|
|
4483
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
3814
4484
|
sceneObjectCount: config.sceneObjectCount,
|
|
3815
4485
|
triangleCount: config.triangleCount,
|
|
3816
4486
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3817
4487
|
environmentPortalCount: config.environmentPortalCount,
|
|
3818
4488
|
environmentPortalMode: config.environmentPortalMode,
|
|
4489
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
4490
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
3819
4491
|
bvhNodeCount: config.bvhNodeCount,
|
|
3820
4492
|
displayQuality: config.displayQuality,
|
|
3821
4493
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
@@ -3823,6 +4495,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3823
4495
|
accelerationBuilt,
|
|
3824
4496
|
accelerationBuildCount,
|
|
3825
4497
|
frameConfigSlots: frameConfigSlotCount,
|
|
4498
|
+
gpuParallelism: lastGpuParallelism,
|
|
3826
4499
|
memory: config.memory
|
|
3827
4500
|
});
|
|
3828
4501
|
}
|
|
@@ -3831,6 +4504,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3831
4504
|
nextQueue.destroy?.();
|
|
3832
4505
|
hitBuffer.destroy?.();
|
|
3833
4506
|
accumulationBuffer.destroy?.();
|
|
4507
|
+
pathVertexBuffer.destroy?.();
|
|
3834
4508
|
sceneObjectBuffer.destroy?.();
|
|
3835
4509
|
triangleBuffer.destroy?.();
|
|
3836
4510
|
bvhNodeBuffer.destroy?.();
|
|
@@ -3846,6 +4520,9 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3846
4520
|
radianceTexture.destroy?.();
|
|
3847
4521
|
denoiseScratchTexture.destroy?.();
|
|
3848
4522
|
outputTexture.destroy?.();
|
|
4523
|
+
if (environmentMapResource.ownsTexture) {
|
|
4524
|
+
environmentMapResource.texture?.destroy?.();
|
|
4525
|
+
}
|
|
3849
4526
|
context.unconfigure?.();
|
|
3850
4527
|
}
|
|
3851
4528
|
return Object.freeze({
|
|
@@ -3858,6 +4535,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3858
4535
|
renderFrame,
|
|
3859
4536
|
readOutputProbe,
|
|
3860
4537
|
updateSceneObjects,
|
|
4538
|
+
updateCamera,
|
|
3861
4539
|
getSnapshot,
|
|
3862
4540
|
destroy
|
|
3863
4541
|
});
|