@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.cjs
CHANGED
|
@@ -76,6 +76,7 @@ var DEFAULT_HEIGHT = 720;
|
|
|
76
76
|
var DEFAULT_MAX_DEPTH = 6;
|
|
77
77
|
var DEFAULT_TILE_SIZE = 128;
|
|
78
78
|
var DEFAULT_SAMPLES_PER_PIXEL = 1;
|
|
79
|
+
var DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
|
|
79
80
|
var DEFAULT_SCENE_OBJECT_CAPACITY = 128;
|
|
80
81
|
var DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
|
|
81
82
|
var WORKGROUP_SIZE = 64;
|
|
@@ -93,11 +94,12 @@ var BVH_LEAF_REF_RECORD_BYTES = 16;
|
|
|
93
94
|
var EMISSIVE_TRIANGLE_INDEX_BYTES = 4;
|
|
94
95
|
var ENVIRONMENT_PORTAL_RECORD_BYTES = 96;
|
|
95
96
|
var ACCUMULATION_RECORD_BYTES = 16;
|
|
96
|
-
var
|
|
97
|
+
var PATH_VERTEX_RECORD_BYTES = 16;
|
|
98
|
+
var CONFIG_BUFFER_BYTES = 304;
|
|
97
99
|
var COUNTER_DISPATCH_ARGS_OFFSET = 16;
|
|
98
100
|
var INDIRECT_DISPATCH_ARGS_BYTES = 12;
|
|
99
101
|
var COUNTER_BUFFER_BYTES = 32;
|
|
100
|
-
var TRACE_STORAGE_BUFFER_BINDINGS =
|
|
102
|
+
var TRACE_STORAGE_BUFFER_BINDINGS = 10;
|
|
101
103
|
var MATERIAL_DIFFUSE = 0;
|
|
102
104
|
var MATERIAL_METAL = 1;
|
|
103
105
|
var MATERIAL_DIELECTRIC = 2;
|
|
@@ -120,7 +122,8 @@ var DEFAULT_ENVIRONMENT_LIGHTING = Object.freeze({
|
|
|
120
122
|
sunColor: Object.freeze([2.8, 2.65, 2.35, 1]),
|
|
121
123
|
intensity: 1,
|
|
122
124
|
mode: 0,
|
|
123
|
-
exposure: 1
|
|
125
|
+
exposure: 1,
|
|
126
|
+
sunlitBaseline: 0.16
|
|
124
127
|
});
|
|
125
128
|
var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
126
129
|
workgroupSize: WORKGROUP_SIZE,
|
|
@@ -137,6 +140,7 @@ var wavefrontPathTracingComputeLimits = Object.freeze({
|
|
|
137
140
|
emissiveTriangleMetadataRecordBytes: BVH_NODE_RECORD_BYTES,
|
|
138
141
|
environmentPortalRecordBytes: ENVIRONMENT_PORTAL_RECORD_BYTES,
|
|
139
142
|
accumulationRecordBytes: ACCUMULATION_RECORD_BYTES,
|
|
143
|
+
pathVertexRecordBytes: PATH_VERTEX_RECORD_BYTES,
|
|
140
144
|
counterRecordBytes: COUNTER_BUFFER_BYTES,
|
|
141
145
|
indirectDispatchRecordBytes: INDIRECT_DISPATCH_ARGS_BYTES
|
|
142
146
|
});
|
|
@@ -213,6 +217,33 @@ function asColor(value, fallback = [1, 1, 1, 1]) {
|
|
|
213
217
|
clamp(readFiniteNumber("color[3]", value[3], fallback[3] ?? 1), 0, 1)
|
|
214
218
|
];
|
|
215
219
|
}
|
|
220
|
+
function resolveEnvironmentMap(input = null) {
|
|
221
|
+
const source = input && typeof input === "object" ? input : null;
|
|
222
|
+
const hasTexture = Boolean(source?.view || source?.texture || source?.data);
|
|
223
|
+
const width = readPositiveInteger("environmentMap.width", source?.width, 1);
|
|
224
|
+
const height = readPositiveInteger("environmentMap.height", source?.height, 1);
|
|
225
|
+
return Object.freeze({
|
|
226
|
+
enabled: hasTexture && source?.enabled !== false,
|
|
227
|
+
width,
|
|
228
|
+
height,
|
|
229
|
+
format: typeof source?.format === "string" ? source.format : "rgba16float",
|
|
230
|
+
projection: typeof source?.projection === "string" ? source.projection : "equirectangular",
|
|
231
|
+
texture: source?.texture ?? null,
|
|
232
|
+
view: source?.view ?? null,
|
|
233
|
+
sampler: source?.sampler ?? null,
|
|
234
|
+
data: source?.data ?? null,
|
|
235
|
+
intensity: Math.max(0, readFiniteNumber("environmentMap.intensity", source?.intensity ?? source?.radianceScale, 1)),
|
|
236
|
+
rotationRadians: readFiniteNumber("environmentMap.rotationRadians", source?.rotationRadians ?? source?.rotation, 0),
|
|
237
|
+
ambientStrength: Math.max(
|
|
238
|
+
0,
|
|
239
|
+
readFiniteNumber("environmentMap.ambientStrength", source?.ambientStrength, 0.32)
|
|
240
|
+
)
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
function resolveDeferredPathResolve(options = {}) {
|
|
244
|
+
const value = options.deferredPathResolve ?? options.deferredResolve ?? options.pathResolve?.deferred ?? true;
|
|
245
|
+
return value !== false;
|
|
246
|
+
}
|
|
216
247
|
function emissionPower(emission) {
|
|
217
248
|
return Math.max(0, emission?.[0] ?? 0) + Math.max(0, emission?.[1] ?? 0) + Math.max(0, emission?.[2] ?? 0);
|
|
218
249
|
}
|
|
@@ -860,7 +891,15 @@ function resolveEnvironmentLighting(input, environmentColor, ambientColor) {
|
|
|
860
891
|
sunColor: Object.freeze(asColor(source.sunColor, DEFAULT_ENVIRONMENT_LIGHTING.sunColor)),
|
|
861
892
|
intensity: Math.max(1e-4, readFiniteNumber("environmentLighting.intensity", source.intensity, DEFAULT_ENVIRONMENT_LIGHTING.intensity)),
|
|
862
893
|
mode: readNonNegativeInteger("environmentLighting.mode", source.mode, DEFAULT_ENVIRONMENT_LIGHTING.mode),
|
|
863
|
-
exposure: Math.max(1e-4, readFiniteNumber("environmentLighting.exposure", source.exposure, DEFAULT_ENVIRONMENT_LIGHTING.exposure))
|
|
894
|
+
exposure: Math.max(1e-4, readFiniteNumber("environmentLighting.exposure", source.exposure, DEFAULT_ENVIRONMENT_LIGHTING.exposure)),
|
|
895
|
+
sunlitBaseline: Math.max(
|
|
896
|
+
0,
|
|
897
|
+
readFiniteNumber(
|
|
898
|
+
"environmentLighting.sunlitBaseline",
|
|
899
|
+
source.sunlitBaseline ?? source.daylightBaseline,
|
|
900
|
+
DEFAULT_ENVIRONMENT_LIGHTING.sunlitBaseline
|
|
901
|
+
)
|
|
902
|
+
)
|
|
864
903
|
});
|
|
865
904
|
}
|
|
866
905
|
function evaluateReferenceEnvironmentRadiance(config, origin, direction) {
|
|
@@ -1045,6 +1084,11 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1045
1084
|
options.tilePixelCapacity,
|
|
1046
1085
|
DEFAULT_TILE_SIZE * DEFAULT_TILE_SIZE
|
|
1047
1086
|
);
|
|
1087
|
+
const maxDepth = clamp(
|
|
1088
|
+
readPositiveInteger("maxDepth", options.maxDepth, DEFAULT_MAX_DEPTH),
|
|
1089
|
+
1,
|
|
1090
|
+
16
|
|
1091
|
+
);
|
|
1048
1092
|
const sceneObjectCapacity = readPositiveInteger(
|
|
1049
1093
|
"sceneObjectCapacity",
|
|
1050
1094
|
options.sceneObjectCapacity,
|
|
@@ -1070,6 +1114,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1070
1114
|
const queueBytes = tilePixelCapacity * RAY_RECORD_BYTES;
|
|
1071
1115
|
const hitBytes = tilePixelCapacity * HIT_RECORD_BYTES;
|
|
1072
1116
|
const accumulationBytes = tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
1117
|
+
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
1073
1118
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
1074
1119
|
const triangleBytes = triangleCapacity * TRIANGLE_RECORD_BYTES;
|
|
1075
1120
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
@@ -1081,6 +1126,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1081
1126
|
queuePairBytes: queueBytes * 2,
|
|
1082
1127
|
hitBytes,
|
|
1083
1128
|
accumulationBytes,
|
|
1129
|
+
pathVertexBytes,
|
|
1084
1130
|
sceneObjectBytes,
|
|
1085
1131
|
triangleBytes,
|
|
1086
1132
|
bvhNodeBytes,
|
|
@@ -1090,7 +1136,7 @@ function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
1090
1136
|
configBytes: CONFIG_BUFFER_BYTES,
|
|
1091
1137
|
counterBytes: COUNTER_BUFFER_BYTES,
|
|
1092
1138
|
indirectDispatchBytes: INDIRECT_DISPATCH_ARGS_BYTES,
|
|
1093
|
-
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1139
|
+
totalHotBufferBytes: queueBytes * 2 + hitBytes + accumulationBytes + pathVertexBytes + sceneObjectBytes + triangleBytes + bvhNodeBytes + bvhLeafReferenceBytes + emissiveTriangleMetadataBytes + environmentPortalBytes + CONFIG_BUFFER_BYTES + COUNTER_BUFFER_BYTES + INDIRECT_DISPATCH_ARGS_BYTES
|
|
1094
1140
|
});
|
|
1095
1141
|
}
|
|
1096
1142
|
function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
@@ -1106,6 +1152,15 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1106
1152
|
1,
|
|
1107
1153
|
64
|
|
1108
1154
|
);
|
|
1155
|
+
const maxFramePassesPerSubmission = clamp(
|
|
1156
|
+
readPositiveInteger(
|
|
1157
|
+
"maxFramePassesPerSubmission",
|
|
1158
|
+
options.maxFramePassesPerSubmission,
|
|
1159
|
+
DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION
|
|
1160
|
+
),
|
|
1161
|
+
1,
|
|
1162
|
+
4096
|
|
1163
|
+
);
|
|
1109
1164
|
const tilePixelCapacity = readPositiveInteger(
|
|
1110
1165
|
"tilePixelCapacity",
|
|
1111
1166
|
options.tilePixelCapacity,
|
|
@@ -1166,6 +1221,10 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1166
1221
|
options.environmentPortalMode ?? options.portalMode ?? options.environmentLighting?.environmentPortalMode,
|
|
1167
1222
|
environmentPortals.length > 0
|
|
1168
1223
|
);
|
|
1224
|
+
const environmentMap = resolveEnvironmentMap(
|
|
1225
|
+
options.environmentMap ?? options.environmentTexture ?? options.environmentLighting?.environmentMap
|
|
1226
|
+
);
|
|
1227
|
+
const deferredPathResolve = resolveDeferredPathResolve(options);
|
|
1169
1228
|
return Object.freeze({
|
|
1170
1229
|
mode: rendererWavefrontComputeMode,
|
|
1171
1230
|
width,
|
|
@@ -1173,6 +1232,7 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1173
1232
|
maxDepth,
|
|
1174
1233
|
tileSize,
|
|
1175
1234
|
samplesPerPixel,
|
|
1235
|
+
maxFramePassesPerSubmission,
|
|
1176
1236
|
tilePixelCapacity,
|
|
1177
1237
|
sceneObjects,
|
|
1178
1238
|
sceneObjectCount: sceneObjects.length,
|
|
@@ -1199,12 +1259,15 @@ function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
1199
1259
|
environmentPortalCount: environmentPortals.length,
|
|
1200
1260
|
environmentPortalCapacity,
|
|
1201
1261
|
environmentPortalMode,
|
|
1262
|
+
environmentMap,
|
|
1263
|
+
deferredPathResolve,
|
|
1202
1264
|
displayQuality: options.displayQuality === true,
|
|
1203
1265
|
requiresMeshBvhForDisplayQuality: true,
|
|
1204
1266
|
denoise: options.denoise !== false,
|
|
1205
1267
|
frameIndex: readNonNegativeInteger("frameIndex", options.frameIndex, 0),
|
|
1206
1268
|
memory: estimateWavefrontPathTracingMemory({
|
|
1207
1269
|
tilePixelCapacity,
|
|
1270
|
+
maxDepth,
|
|
1208
1271
|
sceneObjectCapacity,
|
|
1209
1272
|
triangleCapacity,
|
|
1210
1273
|
bvhNodeCapacity,
|
|
@@ -1401,6 +1464,18 @@ function createConfigPayload(config, tile, frameIndex, buildRange = {}) {
|
|
|
1401
1464
|
data.setUint32(260, config.environmentPortalMode ?? 0, true);
|
|
1402
1465
|
data.setUint32(264, 0, true);
|
|
1403
1466
|
data.setUint32(268, 0, true);
|
|
1467
|
+
writeVec4(floatView, 272, [
|
|
1468
|
+
config.environmentMap.enabled ? 1 : 0,
|
|
1469
|
+
config.environmentMap.intensity,
|
|
1470
|
+
config.environmentMap.rotationRadians,
|
|
1471
|
+
config.environmentMap.ambientStrength
|
|
1472
|
+
]);
|
|
1473
|
+
writeVec4(floatView, 288, [
|
|
1474
|
+
config.deferredPathResolve ? 1 : 0,
|
|
1475
|
+
config.environmentLighting.sunlitBaseline,
|
|
1476
|
+
0,
|
|
1477
|
+
0
|
|
1478
|
+
]);
|
|
1404
1479
|
return bytes;
|
|
1405
1480
|
}
|
|
1406
1481
|
function createTiles(width, height, tileSize) {
|
|
@@ -1646,6 +1721,136 @@ function alignTo(value, alignment) {
|
|
|
1646
1721
|
const resolvedAlignment = Math.max(1, alignment);
|
|
1647
1722
|
return Math.ceil(value / resolvedAlignment) * resolvedAlignment;
|
|
1648
1723
|
}
|
|
1724
|
+
function float32ToFloat16Bits(value) {
|
|
1725
|
+
const floatView = new Float32Array(1);
|
|
1726
|
+
const intView = new Uint32Array(floatView.buffer);
|
|
1727
|
+
floatView[0] = Number.isFinite(value) ? value : 0;
|
|
1728
|
+
const x = intView[0];
|
|
1729
|
+
const sign = x >> 16 & 32768;
|
|
1730
|
+
let mantissa = x & 8388607;
|
|
1731
|
+
let exponent = x >> 23 & 255;
|
|
1732
|
+
if (exponent === 255) {
|
|
1733
|
+
return sign | (mantissa ? 32256 : 31744);
|
|
1734
|
+
}
|
|
1735
|
+
exponent = exponent - 127 + 15;
|
|
1736
|
+
if (exponent >= 31) {
|
|
1737
|
+
return sign | 31744;
|
|
1738
|
+
}
|
|
1739
|
+
if (exponent <= 0) {
|
|
1740
|
+
if (exponent < -10) {
|
|
1741
|
+
return sign;
|
|
1742
|
+
}
|
|
1743
|
+
mantissa = (mantissa | 8388608) >> 1 - exponent;
|
|
1744
|
+
return sign | mantissa + 4096 >> 13;
|
|
1745
|
+
}
|
|
1746
|
+
return sign | exponent << 10 | mantissa + 4096 >> 13;
|
|
1747
|
+
}
|
|
1748
|
+
function environmentMapIntegerScale(data) {
|
|
1749
|
+
if (data instanceof Uint8Array) {
|
|
1750
|
+
return 1 / 255;
|
|
1751
|
+
}
|
|
1752
|
+
if (data instanceof Uint16Array) {
|
|
1753
|
+
return 1 / 65535;
|
|
1754
|
+
}
|
|
1755
|
+
return 1;
|
|
1756
|
+
}
|
|
1757
|
+
function readEnvironmentMapComponent(data, index, fallback, integerScale = 1) {
|
|
1758
|
+
if (!data || index >= data.length) {
|
|
1759
|
+
return fallback;
|
|
1760
|
+
}
|
|
1761
|
+
const value = Number(data[index]);
|
|
1762
|
+
return Number.isFinite(value) ? Math.max(0, value) * integerScale : fallback;
|
|
1763
|
+
}
|
|
1764
|
+
function createEnvironmentMapUploadBytes(environmentMap, fallbackColor) {
|
|
1765
|
+
const width = Math.max(1, environmentMap.width);
|
|
1766
|
+
const height = Math.max(1, environmentMap.height);
|
|
1767
|
+
const rowBytes = width * 8;
|
|
1768
|
+
const bytesPerRow = alignTo(rowBytes, 256);
|
|
1769
|
+
const bytes = new Uint8Array(bytesPerRow * height);
|
|
1770
|
+
const data = environmentMap.data;
|
|
1771
|
+
const integerScale = environmentMapIntegerScale(data);
|
|
1772
|
+
const view = new DataView(bytes.buffer);
|
|
1773
|
+
const writeComponent = (targetOffset, sourceOffset, fallback) => {
|
|
1774
|
+
view.setUint16(
|
|
1775
|
+
targetOffset,
|
|
1776
|
+
float32ToFloat16Bits(
|
|
1777
|
+
readEnvironmentMapComponent(data, sourceOffset, fallback, integerScale)
|
|
1778
|
+
),
|
|
1779
|
+
true
|
|
1780
|
+
);
|
|
1781
|
+
};
|
|
1782
|
+
for (let y = 0; y < height; y += 1) {
|
|
1783
|
+
for (let x = 0; x < width; x += 1) {
|
|
1784
|
+
const sourceOffset = (y * width + x) * 4;
|
|
1785
|
+
const targetOffset = y * bytesPerRow + x * 8;
|
|
1786
|
+
writeComponent(targetOffset, sourceOffset, fallbackColor[0]);
|
|
1787
|
+
writeComponent(targetOffset + 2, sourceOffset + 1, fallbackColor[1]);
|
|
1788
|
+
writeComponent(targetOffset + 4, sourceOffset + 2, fallbackColor[2]);
|
|
1789
|
+
writeComponent(targetOffset + 6, sourceOffset + 3, fallbackColor[3] ?? 1);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return Object.freeze({
|
|
1793
|
+
bytes,
|
|
1794
|
+
bytesPerRow,
|
|
1795
|
+
width,
|
|
1796
|
+
height
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
function createEnvironmentMapResource(device, constants, environmentMap, fallbackColor) {
|
|
1800
|
+
if (environmentMap.view) {
|
|
1801
|
+
return Object.freeze({
|
|
1802
|
+
view: environmentMap.view,
|
|
1803
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
1804
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
1805
|
+
addressModeU: "repeat",
|
|
1806
|
+
addressModeV: "clamp-to-edge",
|
|
1807
|
+
magFilter: "linear",
|
|
1808
|
+
minFilter: "linear"
|
|
1809
|
+
}),
|
|
1810
|
+
texture: null,
|
|
1811
|
+
ownsTexture: false
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
if (environmentMap.texture && typeof environmentMap.texture.createView === "function") {
|
|
1815
|
+
return Object.freeze({
|
|
1816
|
+
view: environmentMap.texture.createView(),
|
|
1817
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
1818
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
1819
|
+
addressModeU: "repeat",
|
|
1820
|
+
addressModeV: "clamp-to-edge",
|
|
1821
|
+
magFilter: "linear",
|
|
1822
|
+
minFilter: "linear"
|
|
1823
|
+
}),
|
|
1824
|
+
texture: environmentMap.texture,
|
|
1825
|
+
ownsTexture: false
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
const upload = createEnvironmentMapUploadBytes(environmentMap, fallbackColor);
|
|
1829
|
+
const texture = device.createTexture({
|
|
1830
|
+
label: environmentMap.enabled ? "plasius.wavefront.environmentMap" : "plasius.wavefront.environmentMapFallback",
|
|
1831
|
+
size: { width: upload.width, height: upload.height },
|
|
1832
|
+
format: "rgba16float",
|
|
1833
|
+
usage: constants.texture.TEXTURE_BINDING | constants.texture.COPY_DST
|
|
1834
|
+
});
|
|
1835
|
+
device.queue.writeTexture(
|
|
1836
|
+
{ texture },
|
|
1837
|
+
upload.bytes,
|
|
1838
|
+
{ bytesPerRow: upload.bytesPerRow, rowsPerImage: upload.height },
|
|
1839
|
+
{ width: upload.width, height: upload.height, depthOrArrayLayers: 1 }
|
|
1840
|
+
);
|
|
1841
|
+
return Object.freeze({
|
|
1842
|
+
view: texture.createView(),
|
|
1843
|
+
sampler: environmentMap.sampler ?? device.createSampler({
|
|
1844
|
+
label: "plasius.wavefront.environmentMapSampler",
|
|
1845
|
+
addressModeU: "repeat",
|
|
1846
|
+
addressModeV: "clamp-to-edge",
|
|
1847
|
+
magFilter: "linear",
|
|
1848
|
+
minFilter: "linear"
|
|
1849
|
+
}),
|
|
1850
|
+
texture,
|
|
1851
|
+
ownsTexture: true
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1649
1854
|
async function getPipelineDiagnostics(shaderModule) {
|
|
1650
1855
|
if (typeof shaderModule?.compilationInfo !== "function") {
|
|
1651
1856
|
return "";
|
|
@@ -1854,6 +2059,8 @@ struct FrameConfig {
|
|
|
1854
2059
|
environmentPortalMode: u32,
|
|
1855
2060
|
_portalPad0: u32,
|
|
1856
2061
|
_portalPad1: u32,
|
|
2062
|
+
environmentMapSettings: vec4<f32>,
|
|
2063
|
+
pathResolveSettings: vec4<f32>,
|
|
1857
2064
|
};
|
|
1858
2065
|
|
|
1859
2066
|
struct Counters {
|
|
@@ -1913,6 +2120,9 @@ struct EnvironmentPortal {
|
|
|
1913
2120
|
@group(0) @binding(17) var finalDenoiseInputRadiance: texture_2d<f32>;
|
|
1914
2121
|
@group(0) @binding(18) var denoisedOutputImage: texture_storage_2d<rgba8unorm, write>;
|
|
1915
2122
|
@group(0) @binding(19) var<storage, read> environmentPortals: array<EnvironmentPortal>;
|
|
2123
|
+
@group(0) @binding(20) var environmentMapTexture: texture_2d<f32>;
|
|
2124
|
+
@group(0) @binding(21) var environmentMapSampler: sampler;
|
|
2125
|
+
@group(0) @binding(22) var<storage, read_write> pathVertices: array<vec4<f32>>;
|
|
1916
2126
|
|
|
1917
2127
|
fn hash_u32(value: u32) -> u32 {
|
|
1918
2128
|
var x = value;
|
|
@@ -1957,6 +2167,89 @@ fn max_component(value: vec3<f32>) -> f32 {
|
|
|
1957
2167
|
return max(max(value.x, value.y), value.z);
|
|
1958
2168
|
}
|
|
1959
2169
|
|
|
2170
|
+
fn environment_map_enabled() -> bool {
|
|
2171
|
+
return config.environmentMapSettings.x > 0.5;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
fn deferred_path_resolve_enabled() -> bool {
|
|
2175
|
+
return config.pathResolveSettings.x > 0.5;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
fn path_vertex_count_per_ray() -> u32 {
|
|
2179
|
+
return config.maxDepth + 1u;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
fn path_vertex_index(rayId: u32, depth: u32) -> u32 {
|
|
2183
|
+
return rayId * path_vertex_count_per_ray() + min(depth, config.maxDepth);
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
fn clear_deferred_path(rayId: u32) {
|
|
2187
|
+
if (!deferred_path_resolve_enabled()) {
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
for (var depth = 0u; depth <= config.maxDepth; depth = depth + 1u) {
|
|
2192
|
+
pathVertices[path_vertex_index(rayId, depth)] = vec4<f32>(0.0);
|
|
2193
|
+
if (depth == config.maxDepth) {
|
|
2194
|
+
break;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
fn record_deferred_path_response(ray: RayRecord, response: vec3<f32>) {
|
|
2200
|
+
if (!deferred_path_resolve_enabled() || ray.rayId >= config.tilePixelCount || ray.bounce >= config.maxDepth) {
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
pathVertices[path_vertex_index(ray.rayId, ray.bounce)] =
|
|
2204
|
+
vec4<f32>(max(response, vec3<f32>(0.0)), 1.0);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
fn record_deferred_terminal_source(ray: RayRecord, sourceRadiance: vec3<f32>) {
|
|
2208
|
+
if (!deferred_path_resolve_enabled() || ray.rayId >= config.tilePixelCount) {
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
pathVertices[path_vertex_index(ray.rayId, config.maxDepth)] =
|
|
2212
|
+
vec4<f32>(clamp_sample_radiance(sourceRadiance), 1.0);
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
fn environment_map_uv(direction: vec3<f32>) -> vec2<f32> {
|
|
2216
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
2217
|
+
let rotationTurns = config.environmentMapSettings.z / 6.28318530718;
|
|
2218
|
+
let u = fract(atan2(rayDirection.z, rayDirection.x) / 6.28318530718 + 0.5 + rotationTurns);
|
|
2219
|
+
let v = acos(clamp(rayDirection.y, -1.0, 1.0)) / 3.14159265359;
|
|
2220
|
+
return vec2<f32>(u, clamp(v, 0.0, 1.0));
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
fn environment_map_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
2224
|
+
let uv = environment_map_uv(direction);
|
|
2225
|
+
let texel = max(textureSampleLevel(environmentMapTexture, environmentMapSampler, uv, 0.0).rgb, vec3<f32>(0.0));
|
|
2226
|
+
return texel * max(config.environmentMapSettings.y, 0.0);
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
fn procedural_environment_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
2230
|
+
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
2231
|
+
let upFactor = saturate(rayDirection.y * 0.5 + 0.5);
|
|
2232
|
+
let sunDirection = safe_normalize(
|
|
2233
|
+
config.environmentSunDirectionIntensity.xyz,
|
|
2234
|
+
vec3<f32>(0.0, 1.0, 0.0)
|
|
2235
|
+
);
|
|
2236
|
+
let sunGlow = pow(saturate(dot(rayDirection, sunDirection)), 192.0);
|
|
2237
|
+
let gradient =
|
|
2238
|
+
config.environmentHorizonColor.xyz * (1.0 - upFactor) +
|
|
2239
|
+
config.environmentZenithColor.xyz * upFactor;
|
|
2240
|
+
return (
|
|
2241
|
+
gradient +
|
|
2242
|
+
config.environmentSunColor.xyz * sunGlow
|
|
2243
|
+
) * max(config.environmentSunDirectionIntensity.w, 0.0001);
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
fn base_environment_radiance(direction: vec3<f32>) -> vec3<f32> {
|
|
2247
|
+
if (environment_map_enabled()) {
|
|
2248
|
+
return environment_map_radiance(direction);
|
|
2249
|
+
}
|
|
2250
|
+
return procedural_environment_radiance(direction);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
1960
2253
|
fn environment_portal_radiance_scale(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1961
2254
|
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
1962
2255
|
return vec3<f32>(1.0);
|
|
@@ -1996,22 +2289,9 @@ fn environment_portal_radiance_scale(origin: vec3<f32>, direction: vec3<f32>) ->
|
|
|
1996
2289
|
|
|
1997
2290
|
fn environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f32> {
|
|
1998
2291
|
let rayDirection = safe_normalize(direction, vec3<f32>(0.0, 1.0, 0.0));
|
|
1999
|
-
let upFactor = saturate(rayDirection.y * 0.5 + 0.5);
|
|
2000
|
-
let sunDirection = safe_normalize(
|
|
2001
|
-
config.environmentSunDirectionIntensity.xyz,
|
|
2002
|
-
vec3<f32>(0.0, 1.0, 0.0)
|
|
2003
|
-
);
|
|
2004
|
-
let sunGlow = pow(saturate(dot(rayDirection, sunDirection)), 192.0);
|
|
2005
|
-
let gradient =
|
|
2006
|
-
config.environmentHorizonColor.xyz * (1.0 - upFactor) +
|
|
2007
|
-
config.environmentZenithColor.xyz * upFactor;
|
|
2008
2292
|
let portalScale = environment_portal_radiance_scale(origin, rayDirection);
|
|
2009
2293
|
let portalHit = max_component(portalScale) > 0.0001;
|
|
2010
|
-
return (
|
|
2011
|
-
gradient +
|
|
2012
|
-
config.environmentSunColor.xyz * sunGlow
|
|
2013
|
-
) *
|
|
2014
|
-
max(config.environmentSunDirectionIntensity.w, 0.0001) *
|
|
2294
|
+
return base_environment_radiance(rayDirection) *
|
|
2015
2295
|
select(vec3<f32>(1.0), portalScale, portalHit);
|
|
2016
2296
|
}
|
|
2017
2297
|
|
|
@@ -2027,16 +2307,143 @@ fn gated_environment_radiance(origin: vec3<f32>, direction: vec3<f32>) -> vec3<f
|
|
|
2027
2307
|
return environment_radiance(origin, direction);
|
|
2028
2308
|
}
|
|
2029
2309
|
|
|
2030
|
-
fn
|
|
2310
|
+
fn surface_path_response(hit: HitRecord) -> vec3<f32> {
|
|
2311
|
+
let color = clamp(hit.color.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
2312
|
+
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
2313
|
+
let materialEnergy = select(0.68, 0.92, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2314
|
+
let transparentEnergy = select(materialEnergy, 0.9, hit.hitType == 3u);
|
|
2315
|
+
return mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
fn sunlit_baseline_radiance(normal: vec3<f32>) -> vec3<f32> {
|
|
2319
|
+
let baseline = max(config.pathResolveSettings.y, 0.0);
|
|
2320
|
+
if (baseline <= 0.000001) {
|
|
2321
|
+
return vec3<f32>(0.0);
|
|
2322
|
+
}
|
|
2323
|
+
let sunDirection = safe_normalize(
|
|
2324
|
+
config.environmentSunDirectionIntensity.xyz,
|
|
2325
|
+
vec3<f32>(0.0, 1.0, 0.0)
|
|
2326
|
+
);
|
|
2327
|
+
let sunFacing = saturate(dot(normal, sunDirection));
|
|
2328
|
+
let skyFacing = 0.35 + saturate(normal.y * 0.5 + 0.5) * 0.65;
|
|
2329
|
+
let directionalWeight = 0.38 + sunFacing * 0.62;
|
|
2330
|
+
let sunTint = max(config.environmentSunColor.xyz, vec3<f32>(0.0));
|
|
2331
|
+
return clamp_sample_radiance(sunTint * baseline * skyFacing * directionalWeight * 0.04);
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
fn terminal_surface_environment_source(hit: HitRecord) -> vec3<f32> {
|
|
2031
2335
|
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
2032
|
-
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
2033
2336
|
let normalEnvironment = gated_environment_radiance(
|
|
2034
2337
|
hit.position.xyz + normal * 0.003,
|
|
2035
2338
|
normal
|
|
2036
2339
|
);
|
|
2037
|
-
let
|
|
2340
|
+
let sunlitFloor = sunlit_baseline_radiance(normal);
|
|
2341
|
+
let ambientFloor = select(
|
|
2342
|
+
max(config.ambientColor.xyz, sunlitFloor * 0.82),
|
|
2343
|
+
max(config.ambientColor.xyz * 0.35, sunlitFloor * 0.58),
|
|
2344
|
+
environment_map_enabled()
|
|
2345
|
+
);
|
|
2346
|
+
let environmentInfluence = select(
|
|
2347
|
+
max(0.12, config.pathResolveSettings.y * 0.42),
|
|
2348
|
+
max(config.environmentMapSettings.w, max(0.12, config.pathResolveSettings.y * 0.42)),
|
|
2349
|
+
environment_map_enabled()
|
|
2350
|
+
);
|
|
2351
|
+
let environmentFloor = max(ambientFloor, max(sunlitFloor, normalEnvironment * environmentInfluence));
|
|
2038
2352
|
let materialFloor = select(0.7, 1.0, hit.materialKind == 0u || hit.materialKind == 3u);
|
|
2039
|
-
return clamp_sample_radiance(
|
|
2353
|
+
return clamp_sample_radiance(environmentFloor * materialFloor);
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2357
|
+
let surfaceColor = max(hit.color.xyz, config.ambientColor.xyz);
|
|
2358
|
+
return clamp_sample_radiance(
|
|
2359
|
+
ray.throughput.xyz *
|
|
2360
|
+
surfaceColor *
|
|
2361
|
+
terminal_surface_environment_source(hit)
|
|
2362
|
+
);
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) -> vec3<f32> {
|
|
2366
|
+
if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
|
|
2367
|
+
return vec3<f32>(0.0);
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
var irradiance = vec3<f32>(0.0);
|
|
2371
|
+
for (var portalIndex = 0u; portalIndex < config.environmentPortalCount; portalIndex = portalIndex + 1u) {
|
|
2372
|
+
let portal = environmentPortals[portalIndex];
|
|
2373
|
+
if (portal.kind != 1u) {
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
let toPortal = portal.position.xyz - origin;
|
|
2378
|
+
let distanceSquared = max(dot(toPortal, toPortal), 0.01);
|
|
2379
|
+
let direction = safe_normalize(toPortal, normal);
|
|
2380
|
+
let surfaceFacing = saturate(dot(normal, direction));
|
|
2381
|
+
if (surfaceFacing <= 0.0001) {
|
|
2382
|
+
continue;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
let portalNormal = safe_normalize(portal.normal.xyz, vec3<f32>(0.0, 0.0, 1.0));
|
|
2386
|
+
let twoSided = (portal.flags & 1u) != 0u;
|
|
2387
|
+
let portalFacing = select(
|
|
2388
|
+
saturate(dot(-direction, portalNormal)),
|
|
2389
|
+
max(abs(dot(direction, portalNormal)), 0.15),
|
|
2390
|
+
twoSided
|
|
2391
|
+
);
|
|
2392
|
+
let area = max(portal.position.w, 0.0001);
|
|
2393
|
+
let distanceFalloff = clamp(area / max(distanceSquared, area * 0.25), 0.0, 2.5);
|
|
2394
|
+
irradiance = irradiance +
|
|
2395
|
+
portal.color.rgb *
|
|
2396
|
+
portal.normal.w *
|
|
2397
|
+
portal.color.a *
|
|
2398
|
+
surfaceFacing *
|
|
2399
|
+
portalFacing *
|
|
2400
|
+
distanceFalloff;
|
|
2401
|
+
}
|
|
2402
|
+
return irradiance;
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
fn surface_direct_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
|
|
2406
|
+
let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
|
|
2407
|
+
let origin = hit.position.xyz + normal * 0.003;
|
|
2408
|
+
let viewDirection = safe_normalize(-ray.direction.xyz, normal);
|
|
2409
|
+
let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
|
|
2410
|
+
let roughness = clamp(hit.material.x, 0.0, 1.0);
|
|
2411
|
+
let metallic = clamp(hit.material.y, 0.0, 1.0);
|
|
2412
|
+
|
|
2413
|
+
let normalEnvironment = gated_environment_radiance(origin, normal);
|
|
2414
|
+
let skyVisibility = 0.35 + saturate(normal.y * 0.5 + 0.5) * 0.45;
|
|
2415
|
+
let sunlitFloor = sunlit_baseline_radiance(normal);
|
|
2416
|
+
let ambientIrradiance = max(
|
|
2417
|
+
select(config.ambientColor.xyz * 0.72, config.ambientColor.xyz * 0.28, environment_map_enabled()),
|
|
2418
|
+
sunlitFloor * select(0.72, 0.45, environment_map_enabled())
|
|
2419
|
+
);
|
|
2420
|
+
let environmentIrradianceScale = select(
|
|
2421
|
+
max(0.16, config.pathResolveSettings.y * 0.45),
|
|
2422
|
+
max(config.environmentMapSettings.w, max(0.16, config.pathResolveSettings.y * 0.45)),
|
|
2423
|
+
environment_map_enabled()
|
|
2424
|
+
);
|
|
2425
|
+
let skyIrradiance = max(ambientIrradiance, normalEnvironment * skyVisibility * environmentIrradianceScale);
|
|
2426
|
+
|
|
2427
|
+
let sunDirection = safe_normalize(
|
|
2428
|
+
config.environmentSunDirectionIntensity.xyz,
|
|
2429
|
+
vec3<f32>(0.0, 1.0, 0.0)
|
|
2430
|
+
);
|
|
2431
|
+
let sunFacing = saturate(dot(normal, sunDirection));
|
|
2432
|
+
let sunRadiance = gated_environment_radiance(origin, sunDirection);
|
|
2433
|
+
let sunIrradiance = sunRadiance * sunFacing * 0.2;
|
|
2434
|
+
let portalIrradiance = direct_environment_portal_irradiance(origin, normal);
|
|
2435
|
+
|
|
2436
|
+
let diffuseWeight = select(1.0 - metallic * 0.65, 0.22, hit.materialKind == 1u);
|
|
2437
|
+
let diffuse = surfaceColor * (skyIrradiance + sunIrradiance + portalIrradiance) * diffuseWeight;
|
|
2438
|
+
|
|
2439
|
+
let halfVector = safe_normalize(sunDirection + viewDirection, normal);
|
|
2440
|
+
let specularPower = 8.0 + (1.0 - roughness) * 96.0;
|
|
2441
|
+
let specularFacing = pow(saturate(dot(normal, halfVector)), specularPower) * sunFacing;
|
|
2442
|
+
let specularTint = mix(vec3<f32>(0.04), surfaceColor, metallic);
|
|
2443
|
+
let specular = specularTint * sunRadiance * specularFacing * select(0.16, 0.48, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2444
|
+
|
|
2445
|
+
let bounceWeight = select(1.0, 0.38, ray.bounce > 0u);
|
|
2446
|
+
return clamp_sample_radiance(ray.throughput.xyz * (diffuse + specular) * bounceWeight);
|
|
2040
2447
|
}
|
|
2041
2448
|
|
|
2042
2449
|
fn default_mesh_range() -> MeshRange {
|
|
@@ -2639,6 +3046,7 @@ fn generatePrimaryRays(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2639
3046
|
return;
|
|
2640
3047
|
}
|
|
2641
3048
|
activeQueue[index] = make_ray(index);
|
|
3049
|
+
clear_deferred_path(index);
|
|
2642
3050
|
if (u32(config.projectionAndSampling.w) == 0u) {
|
|
2643
3051
|
accumulation[index] = vec4<f32>(0.0);
|
|
2644
3052
|
}
|
|
@@ -2891,27 +3299,51 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2891
3299
|
|
|
2892
3300
|
if (hit.hitType == 1u) {
|
|
2893
3301
|
let guidedLightWeight = select(1.0, 0.24, (ray.flags & RAY_FLAG_GUIDED_EMISSIVE) != 0u);
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
3302
|
+
let sourceRadiance = max(hit.emission.xyz, hit.color.xyz) * guidedLightWeight;
|
|
3303
|
+
if (deferred_path_resolve_enabled()) {
|
|
3304
|
+
record_deferred_terminal_source(ray, sourceRadiance);
|
|
3305
|
+
} else {
|
|
3306
|
+
contribution = clamp_sample_radiance(ray.throughput.xyz * sourceRadiance);
|
|
3307
|
+
accumulation[ray.rayId] =
|
|
3308
|
+
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3309
|
+
}
|
|
2899
3310
|
atomicAdd(&counters.terminatedCount, 1u);
|
|
2900
3311
|
return;
|
|
2901
3312
|
}
|
|
2902
3313
|
|
|
2903
3314
|
if (hit.hitType == 2u) {
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3315
|
+
if (deferred_path_resolve_enabled()) {
|
|
3316
|
+
record_deferred_terminal_source(ray, hit.color.xyz);
|
|
3317
|
+
} else {
|
|
3318
|
+
contribution = clamp_sample_radiance(ray.throughput.xyz * max(hit.color.xyz, config.ambientColor.xyz));
|
|
3319
|
+
accumulation[ray.rayId] =
|
|
3320
|
+
accumulation[ray.rayId] + vec4<f32>(contribution * sample_weight(), 1.0);
|
|
3321
|
+
}
|
|
2907
3322
|
atomicAdd(&counters.terminatedCount, 1u);
|
|
2908
3323
|
return;
|
|
2909
3324
|
}
|
|
2910
3325
|
|
|
2911
|
-
|
|
2912
|
-
|
|
3326
|
+
let response = surface_path_response(hit);
|
|
3327
|
+
record_deferred_path_response(ray, response);
|
|
3328
|
+
|
|
3329
|
+
let shouldEstimateDirectEnvironment =
|
|
3330
|
+
!deferred_path_resolve_enabled() &&
|
|
3331
|
+
(hit.materialKind == 0u || hit.materialKind == 1u) &&
|
|
3332
|
+
hit.material.z >= 0.95;
|
|
3333
|
+
if (shouldEstimateDirectEnvironment) {
|
|
3334
|
+
let directEnvironment = surface_direct_environment_contribution(ray, hit);
|
|
2913
3335
|
accumulation[ray.rayId] =
|
|
2914
|
-
accumulation[ray.rayId] + vec4<f32>(
|
|
3336
|
+
accumulation[ray.rayId] + vec4<f32>(directEnvironment * sample_weight(), 0.0);
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
if (ray.bounce + 1u >= config.maxDepth) {
|
|
3340
|
+
if (deferred_path_resolve_enabled()) {
|
|
3341
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
3342
|
+
} else {
|
|
3343
|
+
let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
3344
|
+
accumulation[ray.rayId] =
|
|
3345
|
+
accumulation[ray.rayId] + vec4<f32>(terminalEnvironment * sample_weight(), 1.0);
|
|
3346
|
+
}
|
|
2915
3347
|
atomicAdd(&counters.terminatedCount, 1u);
|
|
2916
3348
|
return;
|
|
2917
3349
|
}
|
|
@@ -2920,17 +3352,17 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2920
3352
|
let scatter = scatter_direction(ray, hit, seed);
|
|
2921
3353
|
let nextIndex = atomicAdd(&counters.nextCount, 1u);
|
|
2922
3354
|
if (nextIndex >= config.tilePixelCount) {
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
3355
|
+
if (deferred_path_resolve_enabled()) {
|
|
3356
|
+
record_deferred_terminal_source(ray, terminal_surface_environment_source(hit));
|
|
3357
|
+
} else {
|
|
3358
|
+
let overflowEnvironment = terminal_surface_environment_contribution(ray, hit);
|
|
3359
|
+
accumulation[ray.rayId] =
|
|
3360
|
+
accumulation[ray.rayId] + vec4<f32>(overflowEnvironment * sample_weight(), 1.0);
|
|
3361
|
+
}
|
|
2926
3362
|
atomicAdd(&counters.terminatedCount, 1u);
|
|
2927
3363
|
return;
|
|
2928
3364
|
}
|
|
2929
|
-
let
|
|
2930
|
-
let opacity = clamp(hit.material.z, 0.0, 1.0);
|
|
2931
|
-
let materialEnergy = select(0.68, 0.92, hit.materialKind == 1u || hit.materialKind == 2u);
|
|
2932
|
-
let transparentEnergy = select(materialEnergy, 0.9, hit.hitType == 3u);
|
|
2933
|
-
let throughput = ray.throughput.xyz * mix(vec3<f32>(1.0), color, max(opacity, 0.18)) * transparentEnergy;
|
|
3365
|
+
let throughput = ray.throughput.xyz * response;
|
|
2934
3366
|
nextQueue[nextIndex] = RayRecord(
|
|
2935
3367
|
ray.rayId,
|
|
2936
3368
|
ray.rayId,
|
|
@@ -2958,6 +3390,27 @@ fn compactAndSwapQueues(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
|
2958
3390
|
write_active_dispatch_args(activeCount);
|
|
2959
3391
|
}
|
|
2960
3392
|
|
|
3393
|
+
fn resolve_deferred_path_radiance(rayId: u32) -> vec3<f32> {
|
|
3394
|
+
let terminal = pathVertices[path_vertex_index(rayId, config.maxDepth)];
|
|
3395
|
+
if (terminal.w <= 0.0) {
|
|
3396
|
+
return vec3<f32>(0.0);
|
|
3397
|
+
}
|
|
3398
|
+
|
|
3399
|
+
var radiance = terminal.xyz;
|
|
3400
|
+
var depth = config.maxDepth;
|
|
3401
|
+
loop {
|
|
3402
|
+
if (depth == 0u) {
|
|
3403
|
+
break;
|
|
3404
|
+
}
|
|
3405
|
+
depth = depth - 1u;
|
|
3406
|
+
let response = pathVertices[path_vertex_index(rayId, depth)];
|
|
3407
|
+
if (response.w > 0.0) {
|
|
3408
|
+
radiance = radiance * response.xyz;
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
return clamp_sample_radiance(radiance);
|
|
3412
|
+
}
|
|
3413
|
+
|
|
2961
3414
|
@compute @workgroup_size(64)
|
|
2962
3415
|
fn accumulateTerminalRadiance(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
2963
3416
|
let index = globalId.x;
|
|
@@ -2967,7 +3420,12 @@ fn accumulateTerminalRadiance(@builtin(global_invocation_id) globalId: vec3<u32>
|
|
|
2967
3420
|
let localX = index % config.tileWidth;
|
|
2968
3421
|
let localY = index / config.tileWidth;
|
|
2969
3422
|
let pixel = vec2<i32>(i32(config.tileX + localX), i32(config.tileY + localY));
|
|
2970
|
-
|
|
3423
|
+
var radiance = max(accumulation[index].xyz, vec3<f32>(0.0));
|
|
3424
|
+
if (deferred_path_resolve_enabled()) {
|
|
3425
|
+
let resolved = resolve_deferred_path_radiance(index) * sample_weight();
|
|
3426
|
+
radiance = clamp_sample_radiance(radiance + resolved);
|
|
3427
|
+
accumulation[index] = vec4<f32>(radiance, 1.0);
|
|
3428
|
+
}
|
|
2971
3429
|
|
|
2972
3430
|
textureStore(radianceImage, pixel, vec4<f32>(radiance, 1.0));
|
|
2973
3431
|
if (config.denoise == 0u) {
|
|
@@ -3105,6 +3563,125 @@ function createWavefrontDeviceDescriptor(adapter, options = {}) {
|
|
|
3105
3563
|
}
|
|
3106
3564
|
return Object.keys(descriptor).length > 0 ? descriptor : void 0;
|
|
3107
3565
|
}
|
|
3566
|
+
function readGpuLimit(adapter, device, name) {
|
|
3567
|
+
const adapterValue = Number(adapter?.limits?.[name]);
|
|
3568
|
+
if (Number.isFinite(adapterValue)) {
|
|
3569
|
+
return adapterValue;
|
|
3570
|
+
}
|
|
3571
|
+
const deviceValue = Number(device?.limits?.[name]);
|
|
3572
|
+
return Number.isFinite(deviceValue) ? deviceValue : null;
|
|
3573
|
+
}
|
|
3574
|
+
function createAdapterInfoSnapshot(adapter) {
|
|
3575
|
+
const info = adapter?.info;
|
|
3576
|
+
if (!info || typeof info !== "object") {
|
|
3577
|
+
return null;
|
|
3578
|
+
}
|
|
3579
|
+
return Object.freeze({
|
|
3580
|
+
vendor: typeof info.vendor === "string" ? info.vendor : "",
|
|
3581
|
+
architecture: typeof info.architecture === "string" ? info.architecture : "",
|
|
3582
|
+
device: typeof info.device === "string" ? info.device : "",
|
|
3583
|
+
description: typeof info.description === "string" ? info.description : ""
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
3586
|
+
function createGpuAdapterParallelismDiagnostics(adapter, device) {
|
|
3587
|
+
return Object.freeze({
|
|
3588
|
+
physicalCoreCount: null,
|
|
3589
|
+
physicalCoreCountAvailable: false,
|
|
3590
|
+
physicalCoreCountUnavailableReason: "WebGPU does not expose physical GPU core counts.",
|
|
3591
|
+
adapterInfo: createAdapterInfoSnapshot(adapter),
|
|
3592
|
+
adapterLimits: Object.freeze({
|
|
3593
|
+
maxComputeInvocationsPerWorkgroup: readGpuLimit(adapter, device, "maxComputeInvocationsPerWorkgroup"),
|
|
3594
|
+
maxComputeWorkgroupSizeX: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeX"),
|
|
3595
|
+
maxComputeWorkgroupSizeY: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeY"),
|
|
3596
|
+
maxComputeWorkgroupSizeZ: readGpuLimit(adapter, device, "maxComputeWorkgroupSizeZ"),
|
|
3597
|
+
maxComputeWorkgroupsPerDimension: readGpuLimit(adapter, device, "maxComputeWorkgroupsPerDimension"),
|
|
3598
|
+
maxStorageBuffersPerShaderStage: readGpuLimit(adapter, device, "maxStorageBuffersPerShaderStage"),
|
|
3599
|
+
maxStorageBufferBindingSize: readGpuLimit(adapter, device, "maxStorageBufferBindingSize")
|
|
3600
|
+
}),
|
|
3601
|
+
configuredWorkgroupSize: WORKGROUP_SIZE
|
|
3602
|
+
});
|
|
3603
|
+
}
|
|
3604
|
+
function createGpuParallelismCounters() {
|
|
3605
|
+
return {
|
|
3606
|
+
directDispatches: 0,
|
|
3607
|
+
directWorkgroups: 0,
|
|
3608
|
+
directShaderInvocations: 0,
|
|
3609
|
+
multiWorkgroupDispatches: 0,
|
|
3610
|
+
largestDirectWorkgroupsPerDispatch: 0,
|
|
3611
|
+
indirectDispatches: 0,
|
|
3612
|
+
estimatedIndirectWorkgroupsUpperBound: 0,
|
|
3613
|
+
estimatedIndirectShaderInvocationsUpperBound: 0,
|
|
3614
|
+
indirectDispatchesWithMultiWorkgroupCapacity: 0,
|
|
3615
|
+
largestEstimatedIndirectWorkgroupsPerDispatch: 0
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3618
|
+
function countDispatchWorkgroups(groups) {
|
|
3619
|
+
return groups.reduce((product, value) => {
|
|
3620
|
+
const numeric = Number(value ?? 1);
|
|
3621
|
+
const count = Number.isFinite(numeric) ? Math.max(1, Math.trunc(numeric)) : 1;
|
|
3622
|
+
return product * count;
|
|
3623
|
+
}, 1);
|
|
3624
|
+
}
|
|
3625
|
+
function recordDirectDispatch(parallelism, groups, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3626
|
+
const workgroups = countDispatchWorkgroups(groups);
|
|
3627
|
+
parallelism.directDispatches += 1;
|
|
3628
|
+
parallelism.directWorkgroups += workgroups;
|
|
3629
|
+
parallelism.directShaderInvocations += workgroups * invocationsPerWorkgroup;
|
|
3630
|
+
parallelism.largestDirectWorkgroupsPerDispatch = Math.max(
|
|
3631
|
+
parallelism.largestDirectWorkgroupsPerDispatch,
|
|
3632
|
+
workgroups
|
|
3633
|
+
);
|
|
3634
|
+
if (workgroups > 1) {
|
|
3635
|
+
parallelism.multiWorkgroupDispatches += 1;
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
function recordIndirectDispatch(parallelism, estimatedWorkgroupsUpperBound, invocationsPerWorkgroup = WORKGROUP_SIZE) {
|
|
3639
|
+
const workgroups = Math.max(1, Math.trunc(Number(estimatedWorkgroupsUpperBound) || 1));
|
|
3640
|
+
parallelism.indirectDispatches += 1;
|
|
3641
|
+
parallelism.estimatedIndirectWorkgroupsUpperBound += workgroups;
|
|
3642
|
+
parallelism.estimatedIndirectShaderInvocationsUpperBound += workgroups * invocationsPerWorkgroup;
|
|
3643
|
+
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch = Math.max(
|
|
3644
|
+
parallelism.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3645
|
+
workgroups
|
|
3646
|
+
);
|
|
3647
|
+
if (workgroups > 1) {
|
|
3648
|
+
parallelism.indirectDispatchesWithMultiWorkgroupCapacity += 1;
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
function createGpuParallelismDiagnostics(adapterDiagnostics, counters) {
|
|
3652
|
+
const totalEstimatedWorkgroupsUpperBound = counters.directWorkgroups + counters.estimatedIndirectWorkgroupsUpperBound;
|
|
3653
|
+
const totalEstimatedShaderInvocationsUpperBound = counters.directShaderInvocations + counters.estimatedIndirectShaderInvocationsUpperBound;
|
|
3654
|
+
const exposesMultiWorkgroupParallelism = counters.multiWorkgroupDispatches > 0 || counters.indirectDispatchesWithMultiWorkgroupCapacity > 0;
|
|
3655
|
+
return Object.freeze({
|
|
3656
|
+
...adapterDiagnostics,
|
|
3657
|
+
directDispatches: counters.directDispatches,
|
|
3658
|
+
directWorkgroups: counters.directWorkgroups,
|
|
3659
|
+
directShaderInvocations: counters.directShaderInvocations,
|
|
3660
|
+
multiWorkgroupDispatches: counters.multiWorkgroupDispatches,
|
|
3661
|
+
largestDirectWorkgroupsPerDispatch: counters.largestDirectWorkgroupsPerDispatch,
|
|
3662
|
+
indirectDispatches: counters.indirectDispatches,
|
|
3663
|
+
estimatedIndirectWorkgroupsUpperBound: counters.estimatedIndirectWorkgroupsUpperBound,
|
|
3664
|
+
estimatedIndirectShaderInvocationsUpperBound: counters.estimatedIndirectShaderInvocationsUpperBound,
|
|
3665
|
+
indirectDispatchesWithMultiWorkgroupCapacity: counters.indirectDispatchesWithMultiWorkgroupCapacity,
|
|
3666
|
+
largestEstimatedIndirectWorkgroupsPerDispatch: counters.largestEstimatedIndirectWorkgroupsPerDispatch,
|
|
3667
|
+
totalEstimatedWorkgroupsUpperBound,
|
|
3668
|
+
totalEstimatedShaderInvocationsUpperBound,
|
|
3669
|
+
exposesMultiWorkgroupParallelism,
|
|
3670
|
+
likelyUsesMoreThanOnePhysicalGpuCore: null,
|
|
3671
|
+
coreUtilizationStatus: "not-exposed-by-webgpu"
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
3674
|
+
function createEnvironmentMapSnapshot(environmentMap) {
|
|
3675
|
+
return Object.freeze({
|
|
3676
|
+
enabled: environmentMap.enabled,
|
|
3677
|
+
width: environmentMap.width,
|
|
3678
|
+
height: environmentMap.height,
|
|
3679
|
+
projection: environmentMap.projection,
|
|
3680
|
+
intensity: environmentMap.intensity,
|
|
3681
|
+
rotationRadians: environmentMap.rotationRadians,
|
|
3682
|
+
ambientStrength: environmentMap.ambientStrength
|
|
3683
|
+
});
|
|
3684
|
+
}
|
|
3108
3685
|
async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
3109
3686
|
assertAnalyticDisplayQualityPolicy(options);
|
|
3110
3687
|
const constants = getGpuUsageConstants();
|
|
@@ -3124,6 +3701,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3124
3701
|
throw new Error("Unable to acquire a WebGPU adapter for wavefront path tracing.");
|
|
3125
3702
|
}
|
|
3126
3703
|
const device = await adapter.requestDevice(createWavefrontDeviceDescriptor(adapter, options));
|
|
3704
|
+
const gpuAdapterParallelism = createGpuAdapterParallelismDiagnostics(adapter, device);
|
|
3127
3705
|
const context = canvas.getContext("webgpu");
|
|
3128
3706
|
if (!context || typeof context.configure !== "function") {
|
|
3129
3707
|
throw new Error("Canvas WebGPU context does not support configure().");
|
|
@@ -3151,6 +3729,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3151
3729
|
const rayQueueBytes = config.tilePixelCapacity * RAY_RECORD_BYTES;
|
|
3152
3730
|
const hitBytes = config.tilePixelCapacity * HIT_RECORD_BYTES;
|
|
3153
3731
|
const accumulationBytes = config.tilePixelCapacity * ACCUMULATION_RECORD_BYTES;
|
|
3732
|
+
const pathVertexBytes = config.tilePixelCapacity * (config.maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
3154
3733
|
const activeQueue = createBuffer(device, bufferUsage, rayQueueBytes, "plasius.wavefront.activeQueue");
|
|
3155
3734
|
const nextQueue = createBuffer(device, bufferUsage, rayQueueBytes, "plasius.wavefront.nextQueue");
|
|
3156
3735
|
const hitBuffer = createBuffer(device, bufferUsage, hitBytes, "plasius.wavefront.hitBuffer");
|
|
@@ -3160,6 +3739,12 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3160
3739
|
accumulationBytes,
|
|
3161
3740
|
"plasius.wavefront.accumulation"
|
|
3162
3741
|
);
|
|
3742
|
+
const pathVertexBuffer = createBuffer(
|
|
3743
|
+
device,
|
|
3744
|
+
bufferUsage,
|
|
3745
|
+
pathVertexBytes,
|
|
3746
|
+
"plasius.wavefront.pathVertices"
|
|
3747
|
+
);
|
|
3163
3748
|
const sceneObjectBuffer = createBuffer(
|
|
3164
3749
|
device,
|
|
3165
3750
|
constants.buffer.STORAGE | constants.buffer.COPY_DST,
|
|
@@ -3214,9 +3799,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3214
3799
|
CONFIG_BUFFER_BYTES,
|
|
3215
3800
|
Number.isFinite(uniformOffsetAlignment) && uniformOffsetAlignment > 0 ? uniformOffsetAlignment : CONFIG_BUFFER_BYTES
|
|
3216
3801
|
);
|
|
3802
|
+
const outputConfigSlotCount = config.deferredPathResolve ? 0 : tiles.length;
|
|
3217
3803
|
const frameConfigSlotCount = Math.max(
|
|
3218
3804
|
1,
|
|
3219
|
-
tiles.length * config.samplesPerPixel +
|
|
3805
|
+
tiles.length * config.samplesPerPixel + outputConfigSlotCount + (config.denoise ? 1 : 0)
|
|
3220
3806
|
);
|
|
3221
3807
|
const configBuffer = createBuffer(
|
|
3222
3808
|
device,
|
|
@@ -3294,6 +3880,12 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3294
3880
|
magFilter: "nearest",
|
|
3295
3881
|
minFilter: "nearest"
|
|
3296
3882
|
});
|
|
3883
|
+
const environmentMapResource = createEnvironmentMapResource(
|
|
3884
|
+
device,
|
|
3885
|
+
constants,
|
|
3886
|
+
config.environmentMap,
|
|
3887
|
+
config.environmentColor
|
|
3888
|
+
);
|
|
3297
3889
|
const traceBindGroupLayout = device.createBindGroupLayout({
|
|
3298
3890
|
label: "plasius.wavefront.traceBindGroupLayout",
|
|
3299
3891
|
entries: [
|
|
@@ -3320,7 +3912,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3320
3912
|
visibility: constants.shader.COMPUTE,
|
|
3321
3913
|
storageTexture: { access: "write-only", format: "rgba16float" }
|
|
3322
3914
|
},
|
|
3323
|
-
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } }
|
|
3915
|
+
{ binding: 19, visibility: constants.shader.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
3916
|
+
{ binding: 20, visibility: constants.shader.COMPUTE, texture: { sampleType: "float" } },
|
|
3917
|
+
{ binding: 21, visibility: constants.shader.COMPUTE, sampler: { type: "filtering" } },
|
|
3918
|
+
{ binding: 22, visibility: constants.shader.COMPUTE, buffer: { type: "storage" } }
|
|
3324
3919
|
]
|
|
3325
3920
|
});
|
|
3326
3921
|
const accelerationBindGroupLayout = device.createBindGroupLayout({
|
|
@@ -3494,7 +4089,10 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3494
4089
|
{ binding: 8, resource: { buffer: triangleBuffer } },
|
|
3495
4090
|
{ binding: 9, resource: { buffer: bvhNodeBuffer } },
|
|
3496
4091
|
{ binding: 16, resource: radianceView },
|
|
3497
|
-
{ binding: 19, resource: { buffer: environmentPortalBuffer } }
|
|
4092
|
+
{ binding: 19, resource: { buffer: environmentPortalBuffer } },
|
|
4093
|
+
{ binding: 20, resource: environmentMapResource.view },
|
|
4094
|
+
{ binding: 21, resource: environmentMapResource.sampler },
|
|
4095
|
+
{ binding: 22, resource: { buffer: pathVertexBuffer } }
|
|
3498
4096
|
]
|
|
3499
4097
|
});
|
|
3500
4098
|
}
|
|
@@ -3583,6 +4181,11 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3583
4181
|
let frame = 0;
|
|
3584
4182
|
let accelerationBuilt = !config.gpuAccelerationBuildRequired;
|
|
3585
4183
|
let accelerationBuildCount = 0;
|
|
4184
|
+
let activeCameraOptions = options.camera ?? DEFAULT_CAMERA;
|
|
4185
|
+
let lastGpuParallelism = createGpuParallelismDiagnostics(
|
|
4186
|
+
gpuAdapterParallelism,
|
|
4187
|
+
createGpuParallelismCounters()
|
|
4188
|
+
);
|
|
3586
4189
|
function createFrameConfigWriter(frameIndex) {
|
|
3587
4190
|
let slot = 0;
|
|
3588
4191
|
return (tile, buildRange = {}) => {
|
|
@@ -3599,7 +4202,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3599
4202
|
return offset;
|
|
3600
4203
|
};
|
|
3601
4204
|
}
|
|
3602
|
-
function dispatchGpuAccelerationBuild(frameIndex) {
|
|
4205
|
+
function dispatchGpuAccelerationBuild(frameIndex, parallelism) {
|
|
3603
4206
|
if (!config.gpuAccelerationBuildRequired || accelerationBuilt) {
|
|
3604
4207
|
return false;
|
|
3605
4208
|
}
|
|
@@ -3638,24 +4241,32 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3638
4241
|
});
|
|
3639
4242
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [0]);
|
|
3640
4243
|
passEncoder.setPipeline(pipelines.prepareMeshTrianglesAndLeaves);
|
|
3641
|
-
|
|
4244
|
+
const prepareWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4245
|
+
passEncoder.dispatchWorkgroups(prepareWorkgroups);
|
|
4246
|
+
recordDirectDispatch(parallelism, [prepareWorkgroups]);
|
|
3642
4247
|
passEncoder.setPipeline(pipelines.sortBvhLeafRefs);
|
|
3643
4248
|
for (let stageIndex = 0; stageIndex < config.bvhSortStages.length; stageIndex += 1) {
|
|
3644
4249
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [
|
|
3645
4250
|
(stageIndex + 1) * configBufferStride
|
|
3646
4251
|
]);
|
|
3647
|
-
|
|
4252
|
+
const sortWorkgroups = Math.ceil(config.bvhLeafSortCapacity / WORKGROUP_SIZE);
|
|
4253
|
+
passEncoder.dispatchWorkgroups(sortWorkgroups);
|
|
4254
|
+
recordDirectDispatch(parallelism, [sortWorkgroups]);
|
|
3648
4255
|
}
|
|
3649
4256
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [0]);
|
|
3650
4257
|
passEncoder.setPipeline(pipelines.writeSortedBvhLeaves);
|
|
3651
|
-
|
|
4258
|
+
const leafWriteWorkgroups = Math.ceil(config.triangleCount / WORKGROUP_SIZE);
|
|
4259
|
+
passEncoder.dispatchWorkgroups(leafWriteWorkgroups);
|
|
4260
|
+
recordDirectDispatch(parallelism, [leafWriteWorkgroups]);
|
|
3652
4261
|
passEncoder.setPipeline(pipelines.buildBvhInternalLevel);
|
|
3653
4262
|
for (let levelIndex = 0; levelIndex < config.bvhBuildLevels.length; levelIndex += 1) {
|
|
3654
4263
|
const buildLevel = config.bvhBuildLevels[levelIndex];
|
|
3655
4264
|
passEncoder.setBindGroup(0, bvhBuildBindGroup, [
|
|
3656
4265
|
(buildLevelConfigStart + levelIndex) * configBufferStride
|
|
3657
4266
|
]);
|
|
3658
|
-
|
|
4267
|
+
const levelWorkgroups = Math.ceil(buildLevel.count / WORKGROUP_SIZE);
|
|
4268
|
+
passEncoder.dispatchWorkgroups(levelWorkgroups);
|
|
4269
|
+
recordDirectDispatch(parallelism, [levelWorkgroups]);
|
|
3659
4270
|
}
|
|
3660
4271
|
passEncoder.end();
|
|
3661
4272
|
device.queue.submit([encoder.finish()]);
|
|
@@ -3663,7 +4274,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3663
4274
|
accelerationBuildCount += 1;
|
|
3664
4275
|
return true;
|
|
3665
4276
|
}
|
|
3666
|
-
function encodeTileSample(encoder, tile, configOffset) {
|
|
4277
|
+
function encodeTileSample(encoder, tile, configOffset, parallelism) {
|
|
3667
4278
|
const generatePass = encoder.beginComputePass({
|
|
3668
4279
|
label: "plasius.wavefront.generatePrimaryRaysPass"
|
|
3669
4280
|
});
|
|
@@ -3671,6 +4282,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3671
4282
|
generatePass.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3672
4283
|
generatePass.setPipeline(pipelines.generatePrimaryRays);
|
|
3673
4284
|
generatePass.dispatchWorkgroups(tileWorkgroups);
|
|
4285
|
+
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
3674
4286
|
generatePass.end();
|
|
3675
4287
|
for (let bounceIndex = 0; bounceIndex < config.maxDepth; bounceIndex += 1) {
|
|
3676
4288
|
encoder.copyBufferToBuffer(
|
|
@@ -3686,14 +4298,17 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3686
4298
|
passEncoder.setBindGroup(0, bindGroups[bounceIndex % 2], [configOffset]);
|
|
3687
4299
|
passEncoder.setPipeline(pipelines.intersectActiveQueue);
|
|
3688
4300
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4301
|
+
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
3689
4302
|
passEncoder.setPipeline(pipelines.resolveSurfaceRecords);
|
|
3690
4303
|
passEncoder.dispatchWorkgroupsIndirect(activeDispatchBuffer, 0);
|
|
4304
|
+
recordIndirectDispatch(parallelism, tileWorkgroups);
|
|
3691
4305
|
passEncoder.setPipeline(pipelines.compactAndSwapQueues);
|
|
3692
4306
|
passEncoder.dispatchWorkgroups(1);
|
|
4307
|
+
recordDirectDispatch(parallelism, [1], 1);
|
|
3693
4308
|
passEncoder.end();
|
|
3694
4309
|
}
|
|
3695
4310
|
}
|
|
3696
|
-
function encodeTileOutput(encoder, tile, configOffset) {
|
|
4311
|
+
function encodeTileOutput(encoder, tile, configOffset, parallelism) {
|
|
3697
4312
|
const passEncoder = encoder.beginComputePass({
|
|
3698
4313
|
label: "plasius.wavefront.outputPass"
|
|
3699
4314
|
});
|
|
@@ -3701,25 +4316,30 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3701
4316
|
passEncoder.setBindGroup(0, bindGroups[0], [configOffset]);
|
|
3702
4317
|
passEncoder.setPipeline(pipelines.accumulateTerminalRadiance);
|
|
3703
4318
|
passEncoder.dispatchWorkgroups(tileWorkgroups);
|
|
4319
|
+
recordDirectDispatch(parallelism, [tileWorkgroups]);
|
|
3704
4320
|
passEncoder.end();
|
|
3705
4321
|
}
|
|
3706
|
-
function encodeDenoise(encoder, configOffset) {
|
|
4322
|
+
function encodeDenoise(encoder, configOffset, parallelism) {
|
|
3707
4323
|
if (!config.denoise) {
|
|
3708
4324
|
return;
|
|
3709
4325
|
}
|
|
4326
|
+
const denoiseWorkgroupsX = Math.ceil(config.width / 8);
|
|
4327
|
+
const denoiseWorkgroupsY = Math.ceil(config.height / 8);
|
|
3710
4328
|
const radiancePass = encoder.beginComputePass({
|
|
3711
4329
|
label: "plasius.wavefront.denoiseRadiancePass"
|
|
3712
4330
|
});
|
|
3713
4331
|
radiancePass.setBindGroup(0, denoiseRadianceBindGroup, [configOffset]);
|
|
3714
4332
|
radiancePass.setPipeline(pipelines.denoiseLinearRadiance);
|
|
3715
|
-
radiancePass.dispatchWorkgroups(
|
|
4333
|
+
radiancePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
4334
|
+
recordDirectDispatch(parallelism, [denoiseWorkgroupsX, denoiseWorkgroupsY]);
|
|
3716
4335
|
radiancePass.end();
|
|
3717
4336
|
const resolvePass = encoder.beginComputePass({
|
|
3718
4337
|
label: "plasius.wavefront.denoiseResolvePass"
|
|
3719
4338
|
});
|
|
3720
4339
|
resolvePass.setBindGroup(0, denoiseResolveBindGroup, [configOffset]);
|
|
3721
4340
|
resolvePass.setPipeline(pipelines.resolveDenoisedOutputImage);
|
|
3722
|
-
resolvePass.dispatchWorkgroups(
|
|
4341
|
+
resolvePass.dispatchWorkgroups(denoiseWorkgroupsX, denoiseWorkgroupsY);
|
|
4342
|
+
recordDirectDispatch(parallelism, [denoiseWorkgroupsX, denoiseWorkgroupsY]);
|
|
3723
4343
|
resolvePass.end();
|
|
3724
4344
|
}
|
|
3725
4345
|
function encodePresent(encoder) {
|
|
@@ -3740,41 +4360,68 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3740
4360
|
passEncoder.draw(3);
|
|
3741
4361
|
passEncoder.end();
|
|
3742
4362
|
}
|
|
3743
|
-
function dispatchFrame(frameIndex) {
|
|
4363
|
+
function dispatchFrame(frameIndex, parallelism) {
|
|
3744
4364
|
const writeFrameConfig = createFrameConfigWriter(frameIndex);
|
|
3745
|
-
|
|
3746
|
-
|
|
4365
|
+
let submissionCount = 0;
|
|
4366
|
+
let encodedFramePasses = 0;
|
|
4367
|
+
let encoder = device.createCommandEncoder({
|
|
4368
|
+
label: `plasius.wavefront.frame.${frameIndex}.batched.${submissionCount + 1}`
|
|
3747
4369
|
});
|
|
4370
|
+
function submitCurrentEncoder() {
|
|
4371
|
+
if (encodedFramePasses <= 0) {
|
|
4372
|
+
return;
|
|
4373
|
+
}
|
|
4374
|
+
device.queue.submit([encoder.finish()]);
|
|
4375
|
+
submissionCount += 1;
|
|
4376
|
+
encodedFramePasses = 0;
|
|
4377
|
+
encoder = device.createCommandEncoder({
|
|
4378
|
+
label: `plasius.wavefront.frame.${frameIndex}.batched.${submissionCount + 1}`
|
|
4379
|
+
});
|
|
4380
|
+
}
|
|
4381
|
+
function reserveEncoder(passCount = 1) {
|
|
4382
|
+
if (encodedFramePasses > 0 && encodedFramePasses + passCount > config.maxFramePassesPerSubmission) {
|
|
4383
|
+
submitCurrentEncoder();
|
|
4384
|
+
}
|
|
4385
|
+
encodedFramePasses += passCount;
|
|
4386
|
+
return encoder;
|
|
4387
|
+
}
|
|
3748
4388
|
for (const tile of tiles) {
|
|
3749
4389
|
for (let sampleIndex = 0; sampleIndex < config.samplesPerPixel; sampleIndex += 1) {
|
|
3750
4390
|
const configOffset = writeFrameConfig(tile, {
|
|
3751
4391
|
sampleIndex,
|
|
3752
4392
|
sampleWeight: 1 / config.samplesPerPixel
|
|
3753
4393
|
});
|
|
3754
|
-
encodeTileSample(
|
|
4394
|
+
encodeTileSample(reserveEncoder(), tile, configOffset, parallelism);
|
|
4395
|
+
if (config.deferredPathResolve) {
|
|
4396
|
+
encodeTileOutput(reserveEncoder(), tile, configOffset, parallelism);
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
if (!config.deferredPathResolve) {
|
|
4400
|
+
const outputConfigOffset = writeFrameConfig(tile, {
|
|
4401
|
+
sampleIndex: 0,
|
|
4402
|
+
sampleWeight: 1 / config.samplesPerPixel
|
|
4403
|
+
});
|
|
4404
|
+
encodeTileOutput(reserveEncoder(), tile, outputConfigOffset, parallelism);
|
|
3755
4405
|
}
|
|
3756
|
-
const outputConfigOffset = writeFrameConfig(tile, {
|
|
3757
|
-
sampleIndex: 0,
|
|
3758
|
-
sampleWeight: 1 / config.samplesPerPixel
|
|
3759
|
-
});
|
|
3760
|
-
encodeTileOutput(encoder, tile, outputConfigOffset);
|
|
3761
4406
|
}
|
|
3762
4407
|
if (config.denoise) {
|
|
3763
4408
|
const denoiseConfigOffset = writeFrameConfig(
|
|
3764
4409
|
{ x: 0, y: 0, width: config.width, height: config.height },
|
|
3765
4410
|
{ sampleIndex: 0, sampleWeight: 1 / config.samplesPerPixel }
|
|
3766
4411
|
);
|
|
3767
|
-
encodeDenoise(
|
|
4412
|
+
encodeDenoise(reserveEncoder(), denoiseConfigOffset, parallelism);
|
|
3768
4413
|
}
|
|
3769
|
-
encodePresent(
|
|
3770
|
-
|
|
3771
|
-
return
|
|
4414
|
+
encodePresent(reserveEncoder());
|
|
4415
|
+
submitCurrentEncoder();
|
|
4416
|
+
return submissionCount;
|
|
3772
4417
|
}
|
|
3773
4418
|
function renderOnce() {
|
|
3774
4419
|
frame += 1;
|
|
3775
4420
|
const frameIndex = frame + config.frameIndex;
|
|
3776
|
-
const
|
|
3777
|
-
const
|
|
4421
|
+
const parallelismCounters = createGpuParallelismCounters();
|
|
4422
|
+
const accelerationBuildSubmitted = dispatchGpuAccelerationBuild(frameIndex, parallelismCounters);
|
|
4423
|
+
const frameSubmissionCount = dispatchFrame(frameIndex, parallelismCounters);
|
|
4424
|
+
lastGpuParallelism = createGpuParallelismDiagnostics(gpuAdapterParallelism, parallelismCounters);
|
|
3778
4425
|
return Object.freeze({
|
|
3779
4426
|
frame,
|
|
3780
4427
|
width: config.width,
|
|
@@ -3783,6 +4430,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3783
4430
|
tiles: tiles.length,
|
|
3784
4431
|
tileSize: config.tileSize,
|
|
3785
4432
|
samplesPerPixel: config.samplesPerPixel,
|
|
4433
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
3786
4434
|
screenRays: config.width * config.height,
|
|
3787
4435
|
primaryRays: config.width * config.height * config.samplesPerPixel,
|
|
3788
4436
|
sceneObjectCount: config.sceneObjectCount,
|
|
@@ -3790,6 +4438,8 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3790
4438
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3791
4439
|
environmentPortalCount: config.environmentPortalCount,
|
|
3792
4440
|
environmentPortalMode: config.environmentPortalMode,
|
|
4441
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
4442
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
3793
4443
|
bvhNodeCount: config.bvhNodeCount,
|
|
3794
4444
|
displayQuality: config.displayQuality,
|
|
3795
4445
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
@@ -3799,6 +4449,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3799
4449
|
accelerationBuildCount,
|
|
3800
4450
|
commandSubmissions: frameSubmissionCount + (accelerationBuildSubmitted ? 1 : 0),
|
|
3801
4451
|
frameConfigSlots: frameConfigSlotCount,
|
|
4452
|
+
gpuParallelism: lastGpuParallelism,
|
|
3802
4453
|
memory: config.memory
|
|
3803
4454
|
});
|
|
3804
4455
|
}
|
|
@@ -3869,11 +4520,29 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3869
4520
|
samplesPerPixel: config.samplesPerPixel,
|
|
3870
4521
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
3871
4522
|
sceneObjects: packedScene.objects,
|
|
4523
|
+
camera: activeCameraOptions,
|
|
3872
4524
|
frameIndex: config.frameIndex
|
|
3873
4525
|
});
|
|
3874
4526
|
device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
|
|
3875
4527
|
return config;
|
|
3876
4528
|
}
|
|
4529
|
+
function updateCamera(cameraOptions = {}) {
|
|
4530
|
+
activeCameraOptions = cameraOptions;
|
|
4531
|
+
config = createWavefrontPathTracingComputeConfig({
|
|
4532
|
+
...options,
|
|
4533
|
+
canvas,
|
|
4534
|
+
width: config.width,
|
|
4535
|
+
height: config.height,
|
|
4536
|
+
maxDepth: config.maxDepth,
|
|
4537
|
+
tileSize: config.tileSize,
|
|
4538
|
+
samplesPerPixel: config.samplesPerPixel,
|
|
4539
|
+
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
4540
|
+
sceneObjects: packedScene.objects,
|
|
4541
|
+
camera: activeCameraOptions,
|
|
4542
|
+
frameIndex: config.frameIndex
|
|
4543
|
+
});
|
|
4544
|
+
return config;
|
|
4545
|
+
}
|
|
3877
4546
|
function getSnapshot() {
|
|
3878
4547
|
return Object.freeze({
|
|
3879
4548
|
frame,
|
|
@@ -3883,11 +4552,14 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3883
4552
|
tiles: tiles.length,
|
|
3884
4553
|
tileSize: config.tileSize,
|
|
3885
4554
|
samplesPerPixel: config.samplesPerPixel,
|
|
4555
|
+
maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
|
|
3886
4556
|
sceneObjectCount: config.sceneObjectCount,
|
|
3887
4557
|
triangleCount: config.triangleCount,
|
|
3888
4558
|
emissiveTriangleCount: config.emissiveTriangleCount,
|
|
3889
4559
|
environmentPortalCount: config.environmentPortalCount,
|
|
3890
4560
|
environmentPortalMode: config.environmentPortalMode,
|
|
4561
|
+
environmentMap: createEnvironmentMapSnapshot(config.environmentMap),
|
|
4562
|
+
deferredPathResolve: config.deferredPathResolve,
|
|
3891
4563
|
bvhNodeCount: config.bvhNodeCount,
|
|
3892
4564
|
displayQuality: config.displayQuality,
|
|
3893
4565
|
accelerationBuildMode: config.accelerationBuildMode,
|
|
@@ -3895,6 +4567,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3895
4567
|
accelerationBuilt,
|
|
3896
4568
|
accelerationBuildCount,
|
|
3897
4569
|
frameConfigSlots: frameConfigSlotCount,
|
|
4570
|
+
gpuParallelism: lastGpuParallelism,
|
|
3898
4571
|
memory: config.memory
|
|
3899
4572
|
});
|
|
3900
4573
|
}
|
|
@@ -3903,6 +4576,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3903
4576
|
nextQueue.destroy?.();
|
|
3904
4577
|
hitBuffer.destroy?.();
|
|
3905
4578
|
accumulationBuffer.destroy?.();
|
|
4579
|
+
pathVertexBuffer.destroy?.();
|
|
3906
4580
|
sceneObjectBuffer.destroy?.();
|
|
3907
4581
|
triangleBuffer.destroy?.();
|
|
3908
4582
|
bvhNodeBuffer.destroy?.();
|
|
@@ -3918,6 +4592,9 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3918
4592
|
radianceTexture.destroy?.();
|
|
3919
4593
|
denoiseScratchTexture.destroy?.();
|
|
3920
4594
|
outputTexture.destroy?.();
|
|
4595
|
+
if (environmentMapResource.ownsTexture) {
|
|
4596
|
+
environmentMapResource.texture?.destroy?.();
|
|
4597
|
+
}
|
|
3921
4598
|
context.unconfigure?.();
|
|
3922
4599
|
}
|
|
3923
4600
|
return Object.freeze({
|
|
@@ -3930,6 +4607,7 @@ async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
3930
4607
|
renderFrame,
|
|
3931
4608
|
readOutputProbe,
|
|
3932
4609
|
updateSceneObjects,
|
|
4610
|
+
updateCamera,
|
|
3933
4611
|
getSnapshot,
|
|
3934
4612
|
destroy
|
|
3935
4613
|
});
|