@hello-terrain/three 0.0.0-alpha.6 → 0.0.0-alpha.7

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/dist/index.cjs CHANGED
@@ -276,42 +276,330 @@ class TerrainMesh extends webgpu.InstancedMesh {
276
276
  }
277
277
  }
278
278
 
279
+ function getDeviceComputeLimits(renderer) {
280
+ const backend = renderer.backend;
281
+ const limits = backend?.device?.limits;
282
+ return {
283
+ maxWorkgroupSizeX: limits?.maxComputeWorkgroupSizeX ?? 256,
284
+ maxWorkgroupSizeY: limits?.maxComputeWorkgroupSizeY ?? 256,
285
+ maxWorkgroupInvocations: limits?.maxComputeWorkgroupInvocations ?? 256
286
+ };
287
+ }
288
+
279
289
  const WORKGROUP_X = 16;
280
290
  const WORKGROUP_Y = 16;
281
- function compileComputePipeline(stages, width, bindings) {
282
- const workgroupSize = [WORKGROUP_X, WORKGROUP_Y, 1];
283
- const dispatchX = Math.ceil(width / WORKGROUP_X);
284
- const dispatchY = Math.ceil(width / WORKGROUP_Y);
291
+ function compileComputePipeline(stages, width, options) {
292
+ const bindings = options?.bindings;
293
+ const preferredWorkgroup = options?.workgroupSize ?? [
294
+ WORKGROUP_X,
295
+ WORKGROUP_Y
296
+ ];
297
+ const preferSingleKernelWhenPossible = options?.preferSingleKernelWhenPossible ?? true;
285
298
  const uInstanceCount = tsl.uniform(0, "uint");
286
- const computeShader = tsl.Fn(() => {
287
- const fWidth = tsl.float(width);
288
- const activeIndex = tsl.globalId.z;
289
- const nodeIndex = tsl.int(activeIndex).toVar();
290
- const iWidth = tsl.int(width);
291
- const ix = tsl.int(tsl.globalId.x);
292
- const iy = tsl.int(tsl.globalId.y);
293
- const texelSize = tsl.vec2(1, 1).div(fWidth);
294
- const localCoordinates = tsl.vec2(tsl.globalId.x, tsl.globalId.y);
295
- const localUVCoords = localCoordinates.div(fWidth);
296
- const verticesPerNode = iWidth.mul(iWidth);
297
- const globalIndex = tsl.int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
298
- const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(tsl.uint(activeIndex).lessThan(uInstanceCount)).toVar();
299
- for (let i = 0; i < stages.length; i++) {
300
- if (i > 0) {
301
- tsl.workgroupBarrier();
299
+ let singleKernel;
300
+ const stagedKernelCache = /* @__PURE__ */ new Map();
301
+ function canRunSingleKernel(widthValue, limits) {
302
+ return widthValue <= limits.maxWorkgroupSizeX && widthValue <= limits.maxWorkgroupSizeY && widthValue * widthValue <= limits.maxWorkgroupInvocations;
303
+ }
304
+ function clampWorkgroupToLimits(requested, limits) {
305
+ let x = Math.max(1, Math.floor(requested[0]));
306
+ let y = Math.max(1, Math.floor(requested[1]));
307
+ x = Math.min(x, limits.maxWorkgroupSizeX);
308
+ y = Math.min(y, limits.maxWorkgroupSizeY);
309
+ y = Math.min(
310
+ y,
311
+ Math.max(1, Math.floor(limits.maxWorkgroupInvocations / x))
312
+ );
313
+ x = Math.min(
314
+ x,
315
+ Math.max(1, Math.floor(limits.maxWorkgroupInvocations / y))
316
+ );
317
+ return [x, y];
318
+ }
319
+ function buildSingleKernel(workgroupSize) {
320
+ return tsl.Fn(() => {
321
+ bindings?.forEach((b) => b.toVar());
322
+ const fWidth = tsl.float(width);
323
+ const activeIndex = tsl.globalId.z;
324
+ const nodeIndex = tsl.int(activeIndex).toVar();
325
+ const iWidth = tsl.int(width);
326
+ const ix = tsl.int(tsl.globalId.x);
327
+ const iy = tsl.int(tsl.globalId.y);
328
+ const texelSize = tsl.vec2(1, 1).div(fWidth);
329
+ const localCoordinates = tsl.vec2(tsl.globalId.x, tsl.globalId.y);
330
+ const localUVCoords = localCoordinates.div(fWidth);
331
+ const verticesPerNode = iWidth.mul(iWidth);
332
+ const globalIndex = tsl.int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
333
+ const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(tsl.uint(activeIndex).lessThan(uInstanceCount)).toVar();
334
+ for (let i = 0; i < stages.length; i++) {
335
+ if (i > 0) {
336
+ tsl.workgroupBarrier();
337
+ }
338
+ tsl.If(inBounds, () => {
339
+ stages[i](
340
+ nodeIndex,
341
+ globalIndex,
342
+ localUVCoords,
343
+ localCoordinates,
344
+ texelSize
345
+ );
346
+ });
302
347
  }
303
- tsl.If(inBounds, () => {
304
- stages[i](nodeIndex, globalIndex, localUVCoords, localCoordinates, texelSize);
305
- });
306
- }
307
- })().computeKernel(workgroupSize);
348
+ })().computeKernel(workgroupSize);
349
+ }
350
+ function buildStagedKernels(workgroupSize) {
351
+ return stages.map(
352
+ (stage) => tsl.Fn(() => {
353
+ bindings?.forEach((b) => b.toVar());
354
+ const fWidth = tsl.float(width);
355
+ const activeIndex = tsl.globalId.z;
356
+ const nodeIndex = tsl.int(activeIndex).toVar();
357
+ const iWidth = tsl.int(width);
358
+ const ix = tsl.int(tsl.globalId.x);
359
+ const iy = tsl.int(tsl.globalId.y);
360
+ const texelSize = tsl.vec2(1, 1).div(fWidth);
361
+ const localCoordinates = tsl.vec2(tsl.globalId.x, tsl.globalId.y);
362
+ const localUVCoords = localCoordinates.div(fWidth);
363
+ const verticesPerNode = iWidth.mul(iWidth);
364
+ const globalIndex = tsl.int(nodeIndex).mul(verticesPerNode).add(iy.mul(iWidth).add(ix));
365
+ const inBounds = ix.lessThan(iWidth).and(iy.lessThan(iWidth)).and(tsl.uint(activeIndex).lessThan(uInstanceCount)).toVar();
366
+ tsl.If(inBounds, () => {
367
+ stage(
368
+ nodeIndex,
369
+ globalIndex,
370
+ localUVCoords,
371
+ localCoordinates,
372
+ texelSize
373
+ );
374
+ });
375
+ })().computeKernel(workgroupSize)
376
+ );
377
+ }
308
378
  function execute(renderer, instanceCount) {
379
+ const limits = getDeviceComputeLimits(renderer);
380
+ const canUseSingleKernel = preferSingleKernelWhenPossible && canRunSingleKernel(width, limits);
309
381
  uInstanceCount.value = instanceCount;
310
- renderer.compute(computeShader, [dispatchX, dispatchY, instanceCount]);
382
+ if (canUseSingleKernel) {
383
+ if (!singleKernel) {
384
+ singleKernel = buildSingleKernel([width, width, 1]);
385
+ }
386
+ renderer.compute(singleKernel, [1, 1, instanceCount]);
387
+ return;
388
+ }
389
+ const [workgroupX, workgroupY] = clampWorkgroupToLimits(
390
+ preferredWorkgroup,
391
+ limits
392
+ );
393
+ const cacheKey = `${workgroupX}x${workgroupY}`;
394
+ let stagedKernels = stagedKernelCache.get(cacheKey);
395
+ if (!stagedKernels) {
396
+ stagedKernels = buildStagedKernels([workgroupX, workgroupY, 1]);
397
+ stagedKernelCache.set(cacheKey, stagedKernels);
398
+ }
399
+ const dispatchX = Math.ceil(width / workgroupX);
400
+ const dispatchY = Math.ceil(width / workgroupY);
401
+ for (const kernel of stagedKernels) {
402
+ renderer.compute(kernel, [dispatchX, dispatchY, instanceCount]);
403
+ }
311
404
  }
312
405
  return { execute };
313
406
  }
314
407
 
408
+ function resolveType(format) {
409
+ return format === "rgba16float" ? three.HalfFloatType : three.FloatType;
410
+ }
411
+ function resolveFilter(mode) {
412
+ return mode === "linear" ? three.LinearFilter : three.NearestFilter;
413
+ }
414
+ function configureStorageTexture(texture, format, filter) {
415
+ texture.format = three.RGBAFormat;
416
+ texture.type = resolveType(format);
417
+ texture.magFilter = resolveFilter(filter);
418
+ texture.minFilter = resolveFilter(filter);
419
+ texture.wrapS = three.ClampToEdgeWrapping;
420
+ texture.wrapT = three.ClampToEdgeWrapping;
421
+ texture.generateMipmaps = false;
422
+ texture.needsUpdate = true;
423
+ }
424
+ function ArrayTextureBackend(edgeVertexCount, tileCount, options) {
425
+ let currentEdgeVertexCount = edgeVertexCount;
426
+ let currentTileCount = tileCount;
427
+ const texture = new webgpu.StorageArrayTexture(
428
+ edgeVertexCount,
429
+ edgeVertexCount,
430
+ tileCount
431
+ );
432
+ configureStorageTexture(texture, options.format, options.filter);
433
+ return {
434
+ backendType: "array-texture",
435
+ get edgeVertexCount() {
436
+ return currentEdgeVertexCount;
437
+ },
438
+ get tileCount() {
439
+ return currentTileCount;
440
+ },
441
+ texture,
442
+ uv(ix, iy, _tileIndex) {
443
+ return tsl.vec2(ix.toFloat(), iy.toFloat());
444
+ },
445
+ texel(ix, iy, tileIndex) {
446
+ return tsl.ivec3(ix, iy, tileIndex);
447
+ },
448
+ resize(width, height, nextTileCount) {
449
+ currentEdgeVertexCount = width;
450
+ currentTileCount = nextTileCount;
451
+ texture.setSize(width, height, nextTileCount);
452
+ texture.needsUpdate = true;
453
+ }
454
+ };
455
+ }
456
+ function atlasCoord(tilesPerRow, edgeVertexCount, ix, iy, tileIndex) {
457
+ const tilesPerRowNode = tsl.int(tilesPerRow);
458
+ const edge = tsl.int(edgeVertexCount);
459
+ const tile = tsl.int(tileIndex);
460
+ const col = tile.mod(tilesPerRowNode);
461
+ const row = tile.div(tilesPerRowNode);
462
+ const atlasX = col.mul(edge).add(tsl.int(ix));
463
+ const atlasY = row.mul(edge).add(tsl.int(iy));
464
+ return { atlasX, atlasY };
465
+ }
466
+ function AtlasBackend(edgeVertexCount, tileCount, options) {
467
+ let currentEdgeVertexCount = edgeVertexCount;
468
+ let currentTileCount = tileCount;
469
+ let tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(tileCount)));
470
+ const atlasSize = tilesPerRow * edgeVertexCount;
471
+ const texture = new webgpu.StorageTexture(atlasSize, atlasSize);
472
+ configureStorageTexture(texture, options.format, options.filter);
473
+ return {
474
+ backendType: "atlas",
475
+ get edgeVertexCount() {
476
+ return currentEdgeVertexCount;
477
+ },
478
+ get tileCount() {
479
+ return currentTileCount;
480
+ },
481
+ texture,
482
+ uv(ix, iy, tileIndex) {
483
+ const { atlasX, atlasY } = atlasCoord(
484
+ tilesPerRow,
485
+ currentEdgeVertexCount,
486
+ ix,
487
+ iy,
488
+ tileIndex
489
+ );
490
+ const currentAtlasSize = tsl.float(tilesPerRow * currentEdgeVertexCount);
491
+ return tsl.vec2(
492
+ atlasX.toFloat().add(0.5).div(currentAtlasSize),
493
+ atlasY.toFloat().add(0.5).div(currentAtlasSize)
494
+ );
495
+ },
496
+ texel(ix, iy, tileIndex) {
497
+ const { atlasX, atlasY } = atlasCoord(
498
+ tilesPerRow,
499
+ currentEdgeVertexCount,
500
+ ix,
501
+ iy,
502
+ tileIndex
503
+ );
504
+ return tsl.ivec2(atlasX, atlasY);
505
+ },
506
+ resize(width, height, nextTileCount) {
507
+ currentEdgeVertexCount = width;
508
+ currentTileCount = nextTileCount;
509
+ tilesPerRow = Math.max(1, Math.ceil(Math.sqrt(nextTileCount)));
510
+ const nextAtlasSize = tilesPerRow * width;
511
+ const image = texture.image;
512
+ image.width = nextAtlasSize;
513
+ image.height = nextAtlasSize;
514
+ texture.needsUpdate = true;
515
+ }
516
+ };
517
+ }
518
+ function Texture3DBackend(edgeVertexCount, tileCount, options) {
519
+ let currentEdgeVertexCount = edgeVertexCount;
520
+ let currentTileCount = tileCount;
521
+ const texture = new webgpu.StorageArrayTexture(
522
+ edgeVertexCount,
523
+ edgeVertexCount,
524
+ tileCount
525
+ );
526
+ configureStorageTexture(texture, options.format, options.filter);
527
+ return {
528
+ backendType: "texture-3d",
529
+ get edgeVertexCount() {
530
+ return currentEdgeVertexCount;
531
+ },
532
+ get tileCount() {
533
+ return currentTileCount;
534
+ },
535
+ texture,
536
+ uv(ix, iy, _tileIndex) {
537
+ return tsl.vec2(ix.toFloat(), iy.toFloat());
538
+ },
539
+ texel(ix, iy, tileIndex) {
540
+ return tsl.ivec3(ix, iy, tileIndex);
541
+ },
542
+ resize(width, height, nextTileCount) {
543
+ currentEdgeVertexCount = width;
544
+ currentTileCount = nextTileCount;
545
+ texture.setSize(width, height, nextTileCount);
546
+ texture.needsUpdate = true;
547
+ }
548
+ };
549
+ }
550
+ function tryGetDeviceLimits(renderer) {
551
+ const backend = renderer;
552
+ return backend.backend?.device?.limits ?? {};
553
+ }
554
+ function createTerrainFieldStorage(edgeVertexCount, tileCount, renderer, options = {}) {
555
+ const filter = options.filter ?? "nearest";
556
+ const format = options.format ?? "rgba16float";
557
+ const forcedBackend = options.backend;
558
+ if (forcedBackend === "atlas") {
559
+ return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
560
+ }
561
+ if (forcedBackend === "texture-3d") {
562
+ return Texture3DBackend(edgeVertexCount, tileCount, { filter, format });
563
+ }
564
+ if (forcedBackend === "array-texture") {
565
+ return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
566
+ }
567
+ const DEFAULT_MAX_TEXTURE_ARRAY_LAYERS = 256;
568
+ const maxLayers = renderer ? tryGetDeviceLimits(renderer).maxTextureArrayLayers ?? DEFAULT_MAX_TEXTURE_ARRAY_LAYERS : DEFAULT_MAX_TEXTURE_ARRAY_LAYERS;
569
+ if (tileCount > maxLayers) {
570
+ return AtlasBackend(edgeVertexCount, tileCount, { filter, format });
571
+ }
572
+ return ArrayTextureBackend(edgeVertexCount, tileCount, { filter, format });
573
+ }
574
+ function storeTerrainField(storage, ix, iy, tileIndex, value) {
575
+ if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
576
+ return tsl.textureStore(
577
+ storage.texture,
578
+ tsl.uvec3(tsl.int(ix), tsl.int(iy), tsl.int(tileIndex)),
579
+ value
580
+ );
581
+ }
582
+ return tsl.textureStore(storage.texture, storage.texel(ix, iy, tileIndex), value);
583
+ }
584
+ function loadTerrainField(storage, ix, iy, tileIndex) {
585
+ if (storage.backendType === "array-texture" || storage.backendType === "texture-3d") {
586
+ return tsl.textureLoad(storage.texture, tsl.ivec2(tsl.int(ix), tsl.int(iy)), tsl.int(0)).depth(
587
+ tsl.int(tileIndex)
588
+ );
589
+ }
590
+ return tsl.textureLoad(storage.texture, storage.texel(ix, iy, tileIndex), tsl.int(0));
591
+ }
592
+ function loadTerrainFieldElevation(storage, ix, iy, tileIndex) {
593
+ return loadTerrainField(storage, ix, iy, tileIndex).r;
594
+ }
595
+ function loadTerrainFieldNormal(storage, ix, iy, tileIndex) {
596
+ const sample = loadTerrainField(storage, ix, iy, tileIndex);
597
+ return tsl.vec2(sample.g, sample.b);
598
+ }
599
+ function packTerrainFieldSample(height, normalXZ, extra = tsl.float(0)) {
600
+ return tsl.vec4(height, normalXZ.x, normalXZ.y, extra);
601
+ }
602
+
315
603
  const createElevation = (tile, uniforms, elevationFn) => {
316
604
  return function perVertexElevation(nodeIndex, localCoordinates) {
317
605
  const ix = tsl.int(localCoordinates.x);
@@ -333,7 +621,7 @@ const createElevation = (tile, uniforms, elevationFn) => {
333
621
  });
334
622
  };
335
623
  };
336
- const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount, positionLocal) => tsl.Fn(() => {
624
+ const readElevationFieldAtPositionLocal = (terrainFieldStorage, edgeVertexCount, positionLocal) => tsl.Fn(() => {
337
625
  const nodeIndex = tsl.int(tsl.instanceIndex);
338
626
  const intEdge = tsl.int(edgeVertexCount);
339
627
  const innerSegments = tsl.int(edgeVertexCount).sub(3);
@@ -345,10 +633,12 @@ const readElevationFieldAtPositionLocal = (elevationFieldBuffer, edgeVertexCount
345
633
  const y = v.mul(fInnerSegments).round().toInt().add(tsl.int(1));
346
634
  const xClamped = tsl.min(tsl.max(x, tsl.int(0)), last);
347
635
  const yClamped = tsl.min(tsl.max(y, tsl.int(0)), last);
348
- const verticesPerNode = intEdge.mul(intEdge);
349
- const perNodeVertexIndex = yClamped.mul(intEdge).add(xClamped);
350
- const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(perNodeVertexIndex);
351
- return elevationFieldBuffer.element(globalVertexIndex);
636
+ return loadTerrainFieldElevation(
637
+ terrainFieldStorage,
638
+ xClamped,
639
+ yClamped,
640
+ nodeIndex
641
+ );
352
642
  });
353
643
 
354
644
  function createTileCompute(leafStorage, uniforms) {
@@ -1241,21 +1531,19 @@ const elevationFieldStageTask = work.task((get, work) => {
1241
1531
  });
1242
1532
  }).displayName("elevationFieldStageTask");
1243
1533
 
1244
- const createNormalFieldContextTask = work.task((get, work) => {
1245
- const edgeVertexCount = get(innerTileSegments) + 3;
1246
- const verticesPerNode = edgeVertexCount * edgeVertexCount;
1247
- const totalElements = get(maxNodes) * verticesPerNode;
1248
- return work(() => {
1249
- const data = new Uint32Array(totalElements);
1250
- const attribute = new webgpu.StorageBufferAttribute(data, 1);
1251
- const node = tsl.storage(attribute, "uint", totalElements);
1252
- return {
1253
- data,
1254
- attribute,
1255
- node
1256
- };
1257
- });
1258
- }).displayName("createNormalFieldContextTask");
1534
+ const createTerrainFieldTextureTask = work.task(
1535
+ (get, work, { resources }) => {
1536
+ const edgeVertexCount = get(innerTileSegments) + 3;
1537
+ const maxNodesValue = get(maxNodes);
1538
+ return work(
1539
+ () => createTerrainFieldStorage(
1540
+ edgeVertexCount,
1541
+ maxNodesValue,
1542
+ resources?.renderer
1543
+ )
1544
+ );
1545
+ }
1546
+ ).displayName("createTerrainFieldTextureTask");
1259
1547
  function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
1260
1548
  return tsl.Fn(
1261
1549
  ([nodeIndex, tileSize, ix, iy, elevationScale]) => {
@@ -1280,10 +1568,10 @@ function createNormalFromElevationField(elevationFieldNode, edgeVertexCount) {
1280
1568
  }
1281
1569
  );
1282
1570
  }
1283
- const normalFieldStageTask = work.task((get, work) => {
1571
+ const terrainFieldStageTask = work.task((get, work) => {
1284
1572
  const upstream = get(elevationFieldStageTask);
1285
1573
  const elevationFieldContext = get(createElevationFieldContextTask);
1286
- const normalFieldContext = get(createNormalFieldContextTask);
1574
+ const terrainFieldStorage = get(createTerrainFieldTextureTask);
1287
1575
  const tileEdgeVertexCount = get(innerTileSegments) + 3;
1288
1576
  const tile = get(tileNodesTask);
1289
1577
  const uniforms = get(createUniformsTask);
@@ -1298,6 +1586,7 @@ const normalFieldStageTask = work.task((get, work) => {
1298
1586
  const ix = tsl.int(localCoordinates.x);
1299
1587
  const iy = tsl.int(localCoordinates.y);
1300
1588
  const tileSize = tile.tileSize(nodeIndex);
1589
+ const height = elevationFieldContext.node.element(globalVertexIndex);
1301
1590
  const normalXZ = computeNormal(
1302
1591
  nodeIndex,
1303
1592
  tileSize,
@@ -1305,58 +1594,60 @@ const normalFieldStageTask = work.task((get, work) => {
1305
1594
  iy,
1306
1595
  uniforms.uElevationScale
1307
1596
  );
1308
- normalFieldContext.node.element(globalVertexIndex).assign(tsl.packHalf2x16(normalXZ));
1597
+ storeTerrainField(
1598
+ terrainFieldStorage,
1599
+ ix,
1600
+ iy,
1601
+ nodeIndex,
1602
+ packTerrainFieldSample(height, normalXZ)
1603
+ );
1309
1604
  }
1310
1605
  ];
1311
1606
  });
1312
- }).displayName("normalFieldStageTask");
1607
+ }).displayName("terrainFieldStageTask");
1313
1608
 
1314
1609
  const compileComputeTask = work.task((get, work) => {
1315
- const pipeline = get(normalFieldStageTask);
1610
+ const pipeline = get(terrainFieldStageTask);
1316
1611
  const edgeVertexCount = get(innerTileSegments) + 3;
1317
- return work(() => compileComputePipeline(pipeline, edgeVertexCount));
1318
- }).displayName("compileComputeTask");
1319
- const executeComputeTask = work.task((get, work, { resources }) => {
1320
- const { execute } = get(compileComputeTask);
1321
- const leafState = get(leafGpuBufferTask);
1322
1612
  return work(
1323
- () => resources?.renderer ? execute(resources.renderer, leafState.count) : () => {
1324
- }
1613
+ () => compileComputePipeline(pipeline, edgeVertexCount, {
1614
+ preferSingleKernelWhenPossible: false
1615
+ })
1325
1616
  );
1326
- }).displayName("executeComputeTask").lane("gpu");
1617
+ }).displayName("compileComputeTask");
1618
+ const executeComputeTask = work.task(
1619
+ (get, work, { resources }) => {
1620
+ const { execute } = get(compileComputeTask);
1621
+ const leafState = get(leafGpuBufferTask);
1622
+ return work(
1623
+ () => resources?.renderer ? execute(resources.renderer, leafState.count) : () => {
1624
+ }
1625
+ );
1626
+ }
1627
+ ).displayName("executeComputeTask").lane("gpu");
1327
1628
  function createComputePipelineTasks(leafStageTask) {
1328
1629
  const compile = work.task((get, work) => {
1329
1630
  const pipeline = get(leafStageTask);
1330
1631
  const edgeVertexCount = get(innerTileSegments) + 3;
1331
- return work(() => compileComputePipeline(pipeline, edgeVertexCount));
1632
+ return work(
1633
+ () => compileComputePipeline(pipeline, edgeVertexCount, {
1634
+ preferSingleKernelWhenPossible: false
1635
+ })
1636
+ );
1332
1637
  }).displayName("compileComputeTask");
1333
- const execute = work.task((get, work, { resources }) => {
1334
- const { execute: run } = get(compile);
1335
- const leafState = get(leafGpuBufferTask);
1336
- return work(() => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
1337
- });
1338
- }).displayName("executeComputeTask").lane("gpu");
1638
+ const execute = work.task(
1639
+ (get, work, { resources }) => {
1640
+ const { execute: run } = get(compile);
1641
+ const leafState = get(leafGpuBufferTask);
1642
+ return work(
1643
+ () => resources?.renderer ? run(resources.renderer, leafState.count) : () => {
1644
+ }
1645
+ );
1646
+ }
1647
+ ).displayName("executeComputeTask").lane("gpu");
1339
1648
  return { compile, execute };
1340
1649
  }
1341
1650
 
1342
- const textureSpaceToVectorSpace = tsl.Fn(([value]) => {
1343
- return tsl.remap(value, tsl.float(0), tsl.float(1), tsl.float(-1), tsl.float(1));
1344
- });
1345
- const vectorSpaceToTextureSpace = tsl.Fn(([value]) => {
1346
- return tsl.remap(value, tsl.float(-1), tsl.float(1), tsl.float(0), tsl.float(1));
1347
- });
1348
- const blendAngleCorrectedNormals = tsl.Fn(([n1, n2]) => {
1349
- const t = tsl.vec3(n1.x, n1.y, n1.z.add(1));
1350
- const u = tsl.vec3(n2.x.negate(), n2.y.negate(), n2.z);
1351
- const r = t.mul(tsl.dot(t, u)).sub(u.mul(t.z)).normalize();
1352
- return r;
1353
- });
1354
- const deriveNormalZ = tsl.Fn(([normalXY]) => {
1355
- const xy = normalXY.toVar();
1356
- const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
1357
- return tsl.vec3(xy.x, xy.y, z);
1358
- });
1359
-
1360
1651
  const isSkirtVertex = tsl.Fn(([segments]) => {
1361
1652
  const segmentsNode = typeof segments === "number" ? tsl.int(segments) : segments;
1362
1653
  const vIndex = tsl.int(tsl.vertexIndex);
@@ -1398,37 +1689,40 @@ function createTileBaseWorldPosition(leafStorage, terrainUniforms) {
1398
1689
  return tsl.vec3(worldX, rootOrigin.y, worldZ);
1399
1690
  });
1400
1691
  }
1401
- function createTileElevation(terrainUniforms, elevationFieldBufferNode) {
1402
- if (!elevationFieldBufferNode) return tsl.float(0);
1692
+ function createTileElevation(terrainUniforms, terrainFieldStorage) {
1693
+ if (!terrainFieldStorage) return tsl.float(0);
1403
1694
  const edgeVertexCount = terrainUniforms.uInnerTileSegments.add(3);
1404
1695
  return readElevationFieldAtPositionLocal(
1405
- elevationFieldBufferNode,
1696
+ terrainFieldStorage,
1406
1697
  edgeVertexCount,
1407
1698
  tsl.positionLocal
1408
1699
  )().mul(
1409
1700
  terrainUniforms.uElevationScale
1410
1701
  );
1411
1702
  }
1412
- function createNormalAssignment(terrainUniforms, normalFieldBufferNode) {
1413
- if (!normalFieldBufferNode) return;
1703
+ function createNormalAssignment(terrainUniforms, terrainFieldStorage) {
1704
+ if (!terrainFieldStorage) return;
1414
1705
  const nodeIndex = tsl.int(tsl.instanceIndex);
1415
- const intEdge = tsl.int(terrainUniforms.uInnerTileSegments.add(3));
1416
- const verticesPerNode = intEdge.mul(intEdge);
1417
- const globalVertexIndex = nodeIndex.mul(verticesPerNode).add(tsl.int(tsl.vertexIndex));
1418
- const packed = normalFieldBufferNode.element(globalVertexIndex);
1419
- const normalXZ = tsl.unpackHalf2x16(packed);
1420
- const reconstructed = deriveNormalZ(normalXZ);
1421
- tsl.normalLocal.assign(tsl.vec3(reconstructed.x, reconstructed.z, reconstructed.y));
1706
+ const edgeVertexCount = tsl.int(terrainUniforms.uInnerTileSegments.add(3));
1707
+ const localVertexIndex = tsl.int(tsl.vertexIndex);
1708
+ const ix = localVertexIndex.mod(edgeVertexCount);
1709
+ const iy = localVertexIndex.div(edgeVertexCount);
1710
+ const normalXZ = loadTerrainFieldNormal(terrainFieldStorage, ix, iy, nodeIndex);
1711
+ const nx = normalXZ.x;
1712
+ const nz = normalXZ.y;
1713
+ const nySq = tsl.float(1).sub(nx.mul(nx)).sub(nz.mul(nz)).max(tsl.float(0));
1714
+ const ny = nySq.sqrt();
1715
+ tsl.normalLocal.assign(tsl.vec3(nx, ny, nz));
1422
1716
  }
1423
- function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBufferNode, normalFieldBufferNode) {
1717
+ function createTileWorldPosition(leafStorage, terrainUniforms, terrainFieldStorage) {
1424
1718
  const baseWorldPosition = createTileBaseWorldPosition(leafStorage, terrainUniforms);
1425
1719
  return tsl.Fn(() => {
1426
1720
  const base = baseWorldPosition();
1427
- const yElevation = createTileElevation(terrainUniforms, elevationFieldBufferNode);
1721
+ const yElevation = createTileElevation(terrainUniforms, terrainFieldStorage);
1428
1722
  const skirtVertex = isSkirtVertex(terrainUniforms.uInnerTileSegments);
1429
1723
  const skirtY = base.y.add(yElevation).sub(terrainUniforms.uSkirtScale.toVar());
1430
1724
  const worldY = tsl.select(skirtVertex, skirtY, base.y.add(yElevation));
1431
- createNormalAssignment(terrainUniforms, normalFieldBufferNode);
1725
+ createNormalAssignment(terrainUniforms, terrainFieldStorage);
1432
1726
  return tsl.vec3(base.x, worldY, base.z);
1433
1727
  })();
1434
1728
  }
@@ -1436,20 +1730,18 @@ function createTileWorldPosition(leafStorage, terrainUniforms, elevationFieldBuf
1436
1730
  const positionNodeTask = work.task((get, work) => {
1437
1731
  const leafStorage = get(leafStorageTask);
1438
1732
  const terrainUniforms = get(createUniformsTask);
1439
- const elevationFieldContext = get(createElevationFieldContextTask);
1440
- const normalFieldContext = get(createNormalFieldContextTask);
1733
+ const terrainFieldStorage = get(createTerrainFieldTextureTask);
1441
1734
  return work(
1442
1735
  () => createTileWorldPosition(
1443
1736
  leafStorage,
1444
1737
  terrainUniforms,
1445
- elevationFieldContext.node,
1446
- normalFieldContext.node
1738
+ terrainFieldStorage
1447
1739
  )
1448
1740
  );
1449
1741
  }).displayName("positionNodeTask");
1450
1742
 
1451
1743
  function terrainGraph() {
1452
- return work.graph().add(instanceIdTask).add(quadtreeConfigTask).add(quadtreeUpdateTask).add(leafStorageTask).add(surfaceTask).add(leafGpuBufferTask).add(createUniformsTask).add(updateUniformsTask).add(positionNodeTask).add(createElevationFieldContextTask).add(tileNodesTask).add(createNormalFieldContextTask).add(elevationFieldStageTask).add(normalFieldStageTask).add(compileComputeTask).add(executeComputeTask);
1744
+ return work.graph().add(instanceIdTask).add(quadtreeConfigTask).add(quadtreeUpdateTask).add(leafStorageTask).add(surfaceTask).add(leafGpuBufferTask).add(createUniformsTask).add(updateUniformsTask).add(positionNodeTask).add(createElevationFieldContextTask).add(tileNodesTask).add(createTerrainFieldTextureTask).add(elevationFieldStageTask).add(terrainFieldStageTask).add(compileComputeTask).add(executeComputeTask);
1453
1745
  }
1454
1746
  const terrainTasks = {
1455
1747
  instanceId: instanceIdTask,
@@ -1463,13 +1755,31 @@ const terrainTasks = {
1463
1755
  positionNode: positionNodeTask,
1464
1756
  createElevationFieldContext: createElevationFieldContextTask,
1465
1757
  createTileNodes: tileNodesTask,
1466
- createNormalFieldContext: createNormalFieldContextTask,
1758
+ createTerrainFieldTexture: createTerrainFieldTextureTask,
1467
1759
  elevationFieldStage: elevationFieldStageTask,
1468
- normalFieldStage: normalFieldStageTask,
1760
+ terrainFieldStage: terrainFieldStageTask,
1469
1761
  compileCompute: compileComputeTask,
1470
1762
  executeCompute: executeComputeTask
1471
1763
  };
1472
1764
 
1765
+ const textureSpaceToVectorSpace = tsl.Fn(([value]) => {
1766
+ return tsl.remap(value, tsl.float(0), tsl.float(1), tsl.float(-1), tsl.float(1));
1767
+ });
1768
+ const vectorSpaceToTextureSpace = tsl.Fn(([value]) => {
1769
+ return tsl.remap(value, tsl.float(-1), tsl.float(1), tsl.float(0), tsl.float(1));
1770
+ });
1771
+ const blendAngleCorrectedNormals = tsl.Fn(([n1, n2]) => {
1772
+ const t = tsl.vec3(n1.x, n1.y, n1.z.add(1));
1773
+ const u = tsl.vec3(n2.x.negate(), n2.y.negate(), n2.z);
1774
+ const r = t.mul(tsl.dot(t, u)).sub(u.mul(t.z)).normalize();
1775
+ return r;
1776
+ });
1777
+ const deriveNormalZ = tsl.Fn(([normalXY]) => {
1778
+ const xy = normalXY.toVar();
1779
+ const z = xy.x.mul(xy.x).add(xy.y.mul(xy.y)).oneMinus().max(0).sqrt();
1780
+ return tsl.vec3(xy.x, xy.y, z);
1781
+ });
1782
+
1473
1783
  const vGlobalVertexIndex = /* @__PURE__ */ tsl.varyingProperty("int", "vGlobalVertexIndex");
1474
1784
  const vElevation = /* @__PURE__ */ tsl.varyingProperty("f32", "vElevation");
1475
1785
 
@@ -1504,9 +1814,12 @@ const voronoiCells = tsl.Fn((params) => {
1504
1814
  return k;
1505
1815
  });
1506
1816
 
1817
+ exports.ArrayTextureBackend = ArrayTextureBackend;
1818
+ exports.AtlasBackend = AtlasBackend;
1507
1819
  exports.Dir = Dir;
1508
1820
  exports.TerrainGeometry = TerrainGeometry;
1509
1821
  exports.TerrainMesh = TerrainMesh;
1822
+ exports.Texture3DBackend = Texture3DBackend;
1510
1823
  exports.U32_EMPTY = U32_EMPTY;
1511
1824
  exports.allocLeafSet = allocLeafSet;
1512
1825
  exports.allocSeamTable = allocSeamTable;
@@ -1520,9 +1833,10 @@ exports.createCubeSphereSurface = createCubeSphereSurface;
1520
1833
  exports.createElevationFieldContextTask = createElevationFieldContextTask;
1521
1834
  exports.createFlatSurface = createFlatSurface;
1522
1835
  exports.createInfiniteFlatSurface = createInfiniteFlatSurface;
1523
- exports.createNormalFieldContextTask = createNormalFieldContextTask;
1524
1836
  exports.createSpatialIndex = createSpatialIndex;
1525
1837
  exports.createState = createState;
1838
+ exports.createTerrainFieldStorage = createTerrainFieldStorage;
1839
+ exports.createTerrainFieldTextureTask = createTerrainFieldTextureTask;
1526
1840
  exports.createTerrainUniforms = createTerrainUniforms;
1527
1841
  exports.createUniformsTask = createUniformsTask;
1528
1842
  exports.deriveNormalZ = deriveNormalZ;
@@ -1530,16 +1844,20 @@ exports.elevationFieldStageTask = elevationFieldStageTask;
1530
1844
  exports.elevationFn = elevationFn;
1531
1845
  exports.elevationScale = elevationScale;
1532
1846
  exports.executeComputeTask = executeComputeTask;
1847
+ exports.getDeviceComputeLimits = getDeviceComputeLimits;
1533
1848
  exports.innerTileSegments = innerTileSegments;
1534
1849
  exports.instanceIdTask = instanceIdTask;
1535
1850
  exports.isSkirtUV = isSkirtUV;
1536
1851
  exports.isSkirtVertex = isSkirtVertex;
1537
1852
  exports.leafGpuBufferTask = leafGpuBufferTask;
1538
1853
  exports.leafStorageTask = leafStorageTask;
1854
+ exports.loadTerrainField = loadTerrainField;
1855
+ exports.loadTerrainFieldElevation = loadTerrainFieldElevation;
1856
+ exports.loadTerrainFieldNormal = loadTerrainFieldNormal;
1539
1857
  exports.maxLevel = maxLevel;
1540
1858
  exports.maxNodes = maxNodes;
1541
- exports.normalFieldStageTask = normalFieldStageTask;
1542
1859
  exports.origin = origin;
1860
+ exports.packTerrainFieldSample = packTerrainFieldSample;
1543
1861
  exports.positionNodeTask = positionNodeTask;
1544
1862
  exports.quadtreeConfigTask = quadtreeConfigTask;
1545
1863
  exports.quadtreeUpdate = quadtreeUpdate;
@@ -1548,8 +1866,10 @@ exports.resetLeafSet = resetLeafSet;
1548
1866
  exports.resetSeamTable = resetSeamTable;
1549
1867
  exports.rootSize = rootSize;
1550
1868
  exports.skirtScale = skirtScale;
1869
+ exports.storeTerrainField = storeTerrainField;
1551
1870
  exports.surface = surface;
1552
1871
  exports.surfaceTask = surfaceTask;
1872
+ exports.terrainFieldStageTask = terrainFieldStageTask;
1553
1873
  exports.terrainGraph = terrainGraph;
1554
1874
  exports.terrainTasks = terrainTasks;
1555
1875
  exports.textureSpaceToVectorSpace = textureSpaceToVectorSpace;