@plasius/gpu-renderer 0.2.57 → 0.2.58
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 +17 -0
- package/README.md +9 -0
- package/dist/index.cjs +5684 -5635
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5684 -5635
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.d.ts +5 -1
- package/src/wavefront-compute.js +4 -5
- package/src/wavefront-config.js +8 -2
- package/src/wavefront-instance-shader.js +12 -10
- package/src/wavefront-instance-texture.js +43 -0
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -1050,8 +1050,10 @@ export interface WavefrontPathTracingMemoryEstimate {
|
|
|
1050
1050
|
readonly pathVertexBytes: number;
|
|
1051
1051
|
readonly sceneObjectBytes: number;
|
|
1052
1052
|
readonly triangleBytes: number;
|
|
1053
|
-
/**
|
|
1053
|
+
/** Logical 144-byte records, separate from totalHotBufferBytes and before texture row padding. */
|
|
1054
1054
|
readonly instanceBytes: number;
|
|
1055
|
+
/** Requested instance texture storage including row padding. */
|
|
1056
|
+
readonly instanceTextureBytes: number;
|
|
1055
1057
|
readonly materialTableBytes: number;
|
|
1056
1058
|
readonly bvhNodeBytes: number;
|
|
1057
1059
|
readonly bvhCombinedBytes: number;
|
|
@@ -1417,6 +1419,8 @@ export function estimateWavefrontPathTracingMemory(options?: {
|
|
|
1417
1419
|
maxDepth?: number;
|
|
1418
1420
|
sceneObjectCapacity?: number;
|
|
1419
1421
|
triangleCapacity?: number;
|
|
1422
|
+
instanceBytes?: number;
|
|
1423
|
+
instanceTextureBytes?: number;
|
|
1420
1424
|
materialCapacity?: number;
|
|
1421
1425
|
bvhNodeCapacity?: number;
|
|
1422
1426
|
bvhLeafSortCapacity?: number;
|
package/src/wavefront-compute.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { uploadWavefrontBuffer } from './wavefront-upload.js';
|
|
2
|
+
import { planInstanceTexture, uploadInstanceTexture } from './wavefront-instance-texture.js';
|
|
2
3
|
import { createGpuAllocationTracker, openGpuAllocationErrorScopes } from './gpu-allocation-budget.js';
|
|
3
4
|
import { createWavefrontProgressiveResources } from './wavefront-progressive.js';
|
|
4
5
|
import {
|
|
@@ -242,12 +243,10 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
242
243
|
const memory = config.memory;
|
|
243
244
|
let instanceTexture = null, instanceView = null;
|
|
244
245
|
if(config.instancing) {
|
|
245
|
-
const
|
|
246
|
-
instanceTexture=device.createTexture({label:"plasius.wavefront.instances",size:{width:
|
|
246
|
+
const layout=planInstanceTexture(config.instancing.instanceCount,device.limits?.maxTextureDimension2D);
|
|
247
|
+
instanceTexture=device.createTexture({label:"plasius.wavefront.instances",size:{width:layout.width,height:layout.height},
|
|
247
248
|
format:"rgba32uint",usage:constants.texture.TEXTURE_BINDING|constants.texture.COPY_DST});
|
|
248
|
-
|
|
249
|
-
for(let i=0;i<count;i++) upload.set(source.subarray(i*144,(i+1)*144),i*256);
|
|
250
|
-
device.queue.writeTexture({texture:instanceTexture},upload,{bytesPerRow:256,rowsPerImage:count},{width:9,height:count,depthOrArrayLayers:1});
|
|
249
|
+
await uploadInstanceTexture(device,instanceTexture,config.instancing.buffer,layout);
|
|
251
250
|
instanceView=instanceTexture.createView();
|
|
252
251
|
}
|
|
253
252
|
const activeQueue = createBuffer(device, bufferUsage, memory.queueBytes, "plasius.wavefront.activeQueue");
|
package/src/wavefront-config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { planInstanceTexture } from './wavefront-instance-texture.js';
|
|
2
|
+
import { WAVEFRONT_INSTANCE_BYTES, prepareWavefrontInstances, buildWavefrontInstances } from './wavefront-instances.js';
|
|
2
3
|
import {
|
|
3
4
|
ACCUMULATION_RECORD_BYTES,
|
|
4
5
|
BVH_LEAF_REF_RECORD_BYTES,
|
|
@@ -359,6 +360,7 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
359
360
|
const pathVertexBytes = tilePixelCapacity * (maxDepth + 1) * PATH_VERTEX_RECORD_BYTES;
|
|
360
361
|
const sceneObjectBytes = sceneObjectCapacity * SCENE_OBJECT_RECORD_BYTES;
|
|
361
362
|
const instanceBytes = readNonNegativeInteger("instanceBytes", options.instanceBytes, 0);
|
|
363
|
+
const instanceTextureBytes = readNonNegativeInteger("instanceTextureBytes", options.instanceTextureBytes, instanceBytes);
|
|
362
364
|
const triangleBytes = allocatedRecordBytes(triangleCapacity, TRIANGLE_RECORD_BYTES);
|
|
363
365
|
const materialTableBytes = 0;
|
|
364
366
|
const bvhNodeBytes = bvhNodeCapacity * BVH_NODE_RECORD_BYTES;
|
|
@@ -434,6 +436,7 @@ export function estimateWavefrontPathTracingMemory(options = {}) {
|
|
|
434
436
|
sceneObjectBytes,
|
|
435
437
|
triangleBytes,
|
|
436
438
|
instanceBytes,
|
|
439
|
+
instanceTextureBytes,
|
|
437
440
|
materialTableBytes,
|
|
438
441
|
bvhNodeBytes,
|
|
439
442
|
bvhCombinedBytes,
|
|
@@ -465,6 +468,7 @@ export function estimateWavefrontPathTracingMemoryForConfig(config, limits = {})
|
|
|
465
468
|
sceneObjectCapacity: config.sceneObjectCapacity,
|
|
466
469
|
triangleCapacity: config.triangleCapacity,
|
|
467
470
|
instanceBytes: config.instancing?.instanceBytes ?? 0,
|
|
471
|
+
instanceTextureBytes: config.instancing ? planInstanceTexture(config.instancing.instanceCount, limits.maxTextureDimension2D).bytes : 0,
|
|
468
472
|
bvhNodeCapacity: config.bvhNodeCapacity,
|
|
469
473
|
bvhLeafSortCapacity: config.bvhLeafSortCapacity,
|
|
470
474
|
emissiveTriangleCapacity: config.emissiveTriangleCapacity,
|
|
@@ -519,7 +523,9 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
|
|
|
519
523
|
const meshSourceShape = estimateMeshSourceShape(meshes);
|
|
520
524
|
if(options.memoryBudgetBytes !== undefined) {
|
|
521
525
|
if(!Number.isSafeInteger(options.memoryBudgetBytes)||options.memoryBudgetBytes<=0) throw new Error("GPU memory budget must be a positive safe integer.");
|
|
522
|
-
const
|
|
526
|
+
const count = options.instances?.length ?? 0;
|
|
527
|
+
const instanceMinimumBytes = count * WAVEFRONT_INSTANCE_BYTES + Math.max(0, count * 2 - 1) * BVH_NODE_RECORD_BYTES;
|
|
528
|
+
const geometryMinimumBytes = meshSourceShape.triangleCount * TRIANGLE_RECORD_BYTES + instanceMinimumBytes;
|
|
523
529
|
if(!Number.isSafeInteger(geometryMinimumBytes)||geometryMinimumBytes>options.memoryBudgetBytes) throw new Error("Unique geometry exceeds the GPU memory budget before acceleration construction.");
|
|
524
530
|
}
|
|
525
531
|
const instancePlan = options.instances === undefined ? null : prepareWavefrontInstances(meshes, options.instances);
|
|
@@ -9,17 +9,19 @@ struct MeshInstance {
|
|
|
9
9
|
};
|
|
10
10
|
@group(0) @binding(47) var meshInstances: texture_2d<u32>;
|
|
11
11
|
fn read_mesh_instance(index: u32) -> MeshInstance {
|
|
12
|
-
let
|
|
13
|
-
let
|
|
12
|
+
let recordsPerRow = textureDimensions(meshInstances).x / 9u;
|
|
13
|
+
let x = i32((index % recordsPerRow) * 9u);
|
|
14
|
+
let y = i32(index / recordsPerRow);
|
|
15
|
+
let metadata = textureLoad(meshInstances, vec2<i32>(x + 8, y), 0);
|
|
14
16
|
return MeshInstance(
|
|
15
|
-
mat4x4<f32>(bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(
|
|
16
|
-
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(1, y), 0)),
|
|
17
|
-
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(2, y), 0)),
|
|
18
|
-
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(3, y), 0))),
|
|
19
|
-
mat4x4<f32>(bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(4, y), 0)),
|
|
20
|
-
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(5, y), 0)),
|
|
21
|
-
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(6, y), 0)),
|
|
22
|
-
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(7, y), 0))),
|
|
17
|
+
mat4x4<f32>(bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(x, y), 0)),
|
|
18
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(x + 1, y), 0)),
|
|
19
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(x + 2, y), 0)),
|
|
20
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(x + 3, y), 0))),
|
|
21
|
+
mat4x4<f32>(bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(x + 4, y), 0)),
|
|
22
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(x + 5, y), 0)),
|
|
23
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(x + 6, y), 0)),
|
|
24
|
+
bitcast<vec4<f32>>(textureLoad(meshInstances, vec2<i32>(x + 7, y), 0))),
|
|
23
25
|
metadata.x, metadata.y, bitcast<f32>(metadata.z), 0u);
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { waitForSubmittedGpuWork } from './wavefront-gpu-synchronization.js';
|
|
2
|
+
import { WAVEFRONT_INSTANCE_BYTES } from './wavefront-instances.js';
|
|
3
|
+
|
|
4
|
+
const TEXELS_PER_RECORD = WAVEFRONT_INSTANCE_BYTES / 16;
|
|
5
|
+
|
|
6
|
+
/** Whole records never cross rows; limits come from the granted GPU device. */
|
|
7
|
+
export function planInstanceTexture(count, maxDimension = 8192) {
|
|
8
|
+
if (!Number.isSafeInteger(count) || count < 1 || count >= 0xffffffff) {
|
|
9
|
+
throw new Error('Instance count must fit unsigned shader addressing.');
|
|
10
|
+
}
|
|
11
|
+
if (!Number.isSafeInteger(maxDimension) || maxDimension < TEXELS_PER_RECORD) {
|
|
12
|
+
throw new Error('GPU instance texture dimension cannot hold one record.');
|
|
13
|
+
}
|
|
14
|
+
const recordsPerRow = Math.min(count, Math.floor(maxDimension / TEXELS_PER_RECORD));
|
|
15
|
+
const width = recordsPerRow * TEXELS_PER_RECORD, height = Math.ceil(count / recordsPerRow);
|
|
16
|
+
if (height > maxDimension) throw new Error('GPU instance texture dimension limit exceeded.');
|
|
17
|
+
const bytes = width * height * 16;
|
|
18
|
+
if (!Number.isSafeInteger(bytes)) throw new Error('Instance texture byte size is out of range.');
|
|
19
|
+
return Object.freeze({ count, recordsPerRow, width, height, bytes });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Queue writes have no 256-byte row-alignment requirement. Bound staging by rows. */
|
|
23
|
+
export async function uploadInstanceTexture(device, texture, buffer, layout, { chunkBytes = 8 * 1024 * 1024 } = {}) {
|
|
24
|
+
const bytesPerRow = layout.width * 16;
|
|
25
|
+
if (!Number.isSafeInteger(chunkBytes) || chunkBytes < bytesPerRow) {
|
|
26
|
+
throw new Error('Instance upload chunk must hold at least one texture row.');
|
|
27
|
+
}
|
|
28
|
+
if (buffer.byteLength !== layout.count * WAVEFRONT_INSTANCE_BYTES) {
|
|
29
|
+
throw new Error('Instance upload does not match its record count.');
|
|
30
|
+
}
|
|
31
|
+
const source = new Uint8Array(buffer), rowsPerChunk = Math.floor(chunkBytes / bytesPerRow);
|
|
32
|
+
for (let y = 0; y < layout.height; y += rowsPerChunk) {
|
|
33
|
+
const rows = Math.min(rowsPerChunk, layout.height - y), offset = y * bytesPerRow;
|
|
34
|
+
const length = rows * bytesPerRow;
|
|
35
|
+
let chunk = source.subarray(offset, offset + length);
|
|
36
|
+
if (chunk.byteLength < length) {
|
|
37
|
+
const padded = new Uint8Array(length); padded.set(chunk); chunk = padded;
|
|
38
|
+
}
|
|
39
|
+
device.queue.writeTexture({ texture, origin: { x: 0, y, z: 0 } }, chunk,
|
|
40
|
+
{ bytesPerRow, rowsPerImage: rows }, { width: layout.width, height: rows, depthOrArrayLayers: 1 });
|
|
41
|
+
await waitForSubmittedGpuWork(device, { timeoutMs: 10000, maxWaitMs: 10000, allowTimeout: false });
|
|
42
|
+
}
|
|
43
|
+
}
|