minecraft-renderer 0.1.77 → 0.1.79
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/mesherWasm.js +23 -1
- package/dist/minecraft-renderer.js +86 -64
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +454 -432
- package/package.json +1 -1
- package/src/three/chunkMeshManager.ts +114 -36
- package/src/three/globalBlockBuffer.ts +8 -0
- package/src/three/globalLegacyBuffer.ts +257 -19
- package/src/three/legacyMultiDraw.ts +99 -0
- package/src/three/shaders/cubeBlockShader.ts +36 -1
- package/src/three/tests/chunkMeshManagerLegacy.test.ts +112 -4
- package/src/three/tests/globalLegacyBuffer.test.ts +163 -20
- package/src/three/tests/legacyMultiDraw.test.ts +109 -0
- package/src/three/tests/pr3PerFrameCpuCuts.test.ts +293 -0
- package/src/three/worldRendererThree.ts +20 -4
- package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +12 -1
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import * as nbt from 'prismarine-nbt'
|
|
|
4
4
|
import { Vec3 } from 'vec3'
|
|
5
5
|
import { MesherGeometryOutput } from '../mesher-shared/shared'
|
|
6
6
|
import { getShaderCubeResources, SHADER_CUBES_WORDS_PER_FACE } from '../wasm-mesher/bridge/shaderCubeBridge'
|
|
7
|
-
import { createCubeBlockMaterial, computeSectionOriginRel, setCubeSkyLevel, setCubeLightmapParams, type BlockLightmapParams } from './shaders/cubeBlockShader'
|
|
7
|
+
import { createCubeBlockMaterial, computeSectionOriginRel, setCubeSkyLevel, setCubeShadingTheme, setCubeLightmapParams, type BlockLightmapParams } from './shaders/cubeBlockShader'
|
|
8
8
|
import { computeCameraRelativeUniforms, createGlobalLegacyBlendMaterial, createGlobalLegacyBlockMaterial, createLegacyBlockMaterial, setLegacyCameraOrigin, setLegacySkyLevel, setLegacyLightmapParams, type RenderOrigin } from './shaders/legacyBlockShader'
|
|
9
9
|
import { LEGACY_SECTION_HALF_EXTENT, sectionIntersectsFrustum, setupLegacySectionMatrix, updateLegacySectionCullState } from './legacySectionCull'
|
|
10
10
|
import { createShaderCubeMesh, disposeShaderCubeMesh } from './shaderCubeMesh'
|
|
@@ -140,6 +140,9 @@ export class ChunkMeshManager {
|
|
|
140
140
|
private readonly _legacyCullBoxMin = new THREE.Vector3()
|
|
141
141
|
private readonly _legacyCullBoxMax = new THREE.Vector3()
|
|
142
142
|
private readonly _visibleSectionSpans: Array<{ key: string, distSq: number }> = []
|
|
143
|
+
/** Sections with geometry in global legacy opaque and/or blend buffers — cull/raycast scan only these. */
|
|
144
|
+
private readonly legacyCullSections = new Map<string, { worldX: number, worldY: number, worldZ: number }>()
|
|
145
|
+
private _lastCullFingerprint = ''
|
|
143
146
|
/** Drives per-frame cull + span rebuild; cleared after updateSectionCullAndSort. */
|
|
144
147
|
cullDirty = true
|
|
145
148
|
private readonly _lastCullCamPos = new THREE.Vector3()
|
|
@@ -266,6 +269,11 @@ export class ChunkMeshManager {
|
|
|
266
269
|
this.blockEntityLightRegistry.setSkyLevel(value)
|
|
267
270
|
}
|
|
268
271
|
|
|
272
|
+
setShadingTheme (theme: 'vanilla' | 'high-contrast', cardinalLight: string): void {
|
|
273
|
+
const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
|
|
274
|
+
if (cube) setCubeShadingTheme(cube, theme, cardinalLight)
|
|
275
|
+
}
|
|
276
|
+
|
|
269
277
|
/** Vanilla-like lightmap curve params (live tuning via window.setBlockLightmap). */
|
|
270
278
|
setBlockLightmapParams (params: BlockLightmapParams): void {
|
|
271
279
|
const cube = this.cubeShaderMaterial ?? (this.isShaderCubesGpuEnabled() ? this.getCubeShaderMaterial() : null)
|
|
@@ -379,6 +387,45 @@ export class ChunkMeshManager {
|
|
|
379
387
|
return this.activeSections.has(sectionKey)
|
|
380
388
|
}
|
|
381
389
|
|
|
390
|
+
private registerLegacyCullSection (sectionKey: string, worldX: number, worldY: number, worldZ: number): void {
|
|
391
|
+
this.legacyCullSections.set(sectionKey, { worldX, worldY, worldZ })
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private maybeUnregisterLegacyCullSection (sectionKey: string): void {
|
|
395
|
+
const inOpaque = this.globalLegacyBuffer?.hasSection(sectionKey) ?? false
|
|
396
|
+
const inBlend = this.globalLegacyBlendBuffer?.hasSection(sectionKey) ?? false
|
|
397
|
+
if (!inOpaque && !inBlend) {
|
|
398
|
+
this.legacyCullSections.delete(sectionKey)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private updatePooledLegacyCullState (
|
|
403
|
+
cameraWorldX: number,
|
|
404
|
+
cameraWorldY: number,
|
|
405
|
+
cameraWorldZ: number,
|
|
406
|
+
): void {
|
|
407
|
+
for (const poolEntry of this.activeSections.values()) {
|
|
408
|
+
const sectionKey = poolEntry.sectionKey
|
|
409
|
+
if (!sectionKey) continue
|
|
410
|
+
const sectionObject = this.sectionObjects[sectionKey]
|
|
411
|
+
if (!sectionObject) continue
|
|
412
|
+
|
|
413
|
+
updateLegacySectionCullState(
|
|
414
|
+
poolEntry.mesh,
|
|
415
|
+
sectionObject.worldX ?? 0,
|
|
416
|
+
sectionObject.worldY ?? 0,
|
|
417
|
+
sectionObject.worldZ ?? 0,
|
|
418
|
+
cameraWorldX,
|
|
419
|
+
cameraWorldY,
|
|
420
|
+
cameraWorldZ,
|
|
421
|
+
this._legacyCullFrustum,
|
|
422
|
+
this._legacyCullBox,
|
|
423
|
+
this._legacyCullBoxMin,
|
|
424
|
+
this._legacyCullBoxMax,
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
382
429
|
/**
|
|
383
430
|
* Shared section visibility + span groups for global legacy and cube buffers.
|
|
384
431
|
*/
|
|
@@ -389,8 +436,9 @@ export class ChunkMeshManager {
|
|
|
389
436
|
const visible = this._visibleSectionSpans
|
|
390
437
|
visible.length = 0
|
|
391
438
|
|
|
392
|
-
for (const [sectionKey
|
|
393
|
-
|
|
439
|
+
for (const [sectionKey] of this.legacyCullSections) {
|
|
440
|
+
const sectionObject = this.sectionObjects[sectionKey]
|
|
441
|
+
if (!sectionObject?.visible || sectionObject.worldX === undefined) continue
|
|
394
442
|
const { visible: inFrustum, distSq } = sectionIntersectsFrustum(
|
|
395
443
|
sectionObject.worldX,
|
|
396
444
|
sectionObject.worldY ?? 0,
|
|
@@ -408,13 +456,27 @@ export class ChunkMeshManager {
|
|
|
408
456
|
}
|
|
409
457
|
}
|
|
410
458
|
|
|
411
|
-
this.globalLegacyBuffer
|
|
412
|
-
this.globalLegacyBlendBuffer
|
|
413
|
-
|
|
459
|
+
const opaqueBuf = this.globalLegacyBuffer
|
|
460
|
+
const blendBuf = this.globalLegacyBlendBuffer
|
|
414
461
|
const gb = this.globalBlockBuffer
|
|
462
|
+
|
|
463
|
+
const opaqueKeys: string[] = []
|
|
464
|
+
for (const entry of visible) {
|
|
465
|
+
if (opaqueBuf?.hasSection(entry.key)) opaqueKeys.push(entry.key)
|
|
466
|
+
}
|
|
467
|
+
opaqueKeys.sort()
|
|
468
|
+
|
|
469
|
+
const blendVisible = visible.filter(v => blendBuf?.hasSection(v.key))
|
|
470
|
+
blendVisible.sort((a, b) => b.distSq - a.distSq)
|
|
471
|
+
const blendKeys = blendVisible.map(v => v.key)
|
|
472
|
+
|
|
473
|
+
const cubeVisibleKeys: string[] = []
|
|
474
|
+
const visibleSlots: Array<{ start: number, count: number }> = []
|
|
415
475
|
if (gb) {
|
|
416
|
-
const visibleSlots: Array<{ start: number, count: number }> = []
|
|
417
476
|
gb.forEachSectionSlot((key, slot) => {
|
|
477
|
+
const sectionObject = this.sectionObjects[key]
|
|
478
|
+
// Keep in sync with legacy gate (line 440).
|
|
479
|
+
if (!sectionObject?.visible) return
|
|
418
480
|
const entry = this.shaderSectionRaycastBoxes.get(key)
|
|
419
481
|
if (!entry) {
|
|
420
482
|
return
|
|
@@ -434,35 +496,39 @@ export class ChunkMeshManager {
|
|
|
434
496
|
if (!inFrustum) {
|
|
435
497
|
return
|
|
436
498
|
}
|
|
499
|
+
cubeVisibleKeys.push(key)
|
|
437
500
|
const drawStart = gb.getSectionDrawStart(key)
|
|
438
501
|
if (drawStart !== undefined) {
|
|
439
502
|
visibleSlots.push({ start: drawStart, count: slot.count })
|
|
440
503
|
}
|
|
441
504
|
})
|
|
442
|
-
const spans = buildVisibleCubeSpans(visibleSlots, gb.getHighWatermark())
|
|
443
|
-
gb.setVisibleSpans(spans)
|
|
444
505
|
}
|
|
506
|
+
cubeVisibleKeys.sort()
|
|
445
507
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
508
|
+
const fingerprint = [
|
|
509
|
+
opaqueKeys.join(','),
|
|
510
|
+
blendKeys.join(','),
|
|
511
|
+
cubeVisibleKeys.join(','),
|
|
512
|
+
opaqueBuf?.getLayoutVersion() ?? 0,
|
|
513
|
+
blendBuf?.getLayoutVersion() ?? 0,
|
|
514
|
+
gb?.getLayoutVersion() ?? 0,
|
|
515
|
+
].join('|')
|
|
451
516
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
)
|
|
517
|
+
if (fingerprint === this._lastCullFingerprint) {
|
|
518
|
+
this.updatePooledLegacyCullState(cameraWorldX, cameraWorldY, cameraWorldZ)
|
|
519
|
+
return
|
|
520
|
+
}
|
|
521
|
+
this._lastCullFingerprint = fingerprint
|
|
522
|
+
|
|
523
|
+
opaqueBuf?.updateDrawSpans(visible, 'opaque')
|
|
524
|
+
blendBuf?.updateDrawSpans(visible, 'sortedBlend')
|
|
525
|
+
|
|
526
|
+
if (gb) {
|
|
527
|
+
const spans = buildVisibleCubeSpans(visibleSlots, gb.getHighWatermark())
|
|
528
|
+
gb.setVisibleSpans(spans)
|
|
465
529
|
}
|
|
530
|
+
|
|
531
|
+
this.updatePooledLegacyCullState(cameraWorldX, cameraWorldY, cameraWorldZ)
|
|
466
532
|
}
|
|
467
533
|
|
|
468
534
|
markCullDirty (): void {
|
|
@@ -531,6 +597,8 @@ export class ChunkMeshManager {
|
|
|
531
597
|
if (!this.cubeShaderMaterial) {
|
|
532
598
|
this.cubeShaderMaterial = createCubeBlockMaterial()
|
|
533
599
|
this.syncCubeShaderUniforms()
|
|
600
|
+
const cfg = this.worldRenderer.worldRendererConfig
|
|
601
|
+
setCubeShadingTheme(this.cubeShaderMaterial, cfg.shadingTheme, cfg.cardinalLight)
|
|
534
602
|
}
|
|
535
603
|
return this.cubeShaderMaterial
|
|
536
604
|
}
|
|
@@ -602,13 +670,16 @@ export class ChunkMeshManager {
|
|
|
602
670
|
const wy = section.worldY
|
|
603
671
|
const wz = section.worldZ
|
|
604
672
|
if (wx !== undefined && wy !== undefined && wz !== undefined) {
|
|
605
|
-
this.getGlobalLegacyBuffer().addSection(
|
|
673
|
+
const added = this.getGlobalLegacyBuffer().addSection(
|
|
606
674
|
sectionKey,
|
|
607
675
|
{ positions, colors, skyLights, blockLights, uvs, indices },
|
|
608
676
|
wx,
|
|
609
677
|
wy,
|
|
610
678
|
wz,
|
|
611
679
|
)
|
|
680
|
+
if (added) {
|
|
681
|
+
this.registerLegacyCullSection(sectionKey, wx, wy, wz)
|
|
682
|
+
}
|
|
612
683
|
}
|
|
613
684
|
delete section.deferredLegacyOpaque
|
|
614
685
|
}
|
|
@@ -619,13 +690,16 @@ export class ChunkMeshManager {
|
|
|
619
690
|
const wy = section.worldY
|
|
620
691
|
const wz = section.worldZ
|
|
621
692
|
if (wx !== undefined && wy !== undefined && wz !== undefined) {
|
|
622
|
-
this.getGlobalLegacyBlendBuffer().addSection(
|
|
693
|
+
const added = this.getGlobalLegacyBlendBuffer().addSection(
|
|
623
694
|
sectionKey,
|
|
624
695
|
{ positions, colors, skyLights, blockLights, uvs, indices },
|
|
625
696
|
wx,
|
|
626
697
|
wy,
|
|
627
698
|
wz,
|
|
628
699
|
)
|
|
700
|
+
if (added) {
|
|
701
|
+
this.registerLegacyCullSection(sectionKey, wx, wy, wz)
|
|
702
|
+
}
|
|
629
703
|
}
|
|
630
704
|
delete section.deferredLegacyBlend
|
|
631
705
|
section.hasBlendMesh = false
|
|
@@ -653,15 +727,13 @@ export class ChunkMeshManager {
|
|
|
653
727
|
const far = raycaster.far
|
|
654
728
|
const halfExtent = LEGACY_SECTION_HALF_EXTENT + 0.01
|
|
655
729
|
const candidates: string[] = []
|
|
656
|
-
for (const [key
|
|
657
|
-
|
|
730
|
+
for (const [key] of this.legacyCullSections) {
|
|
731
|
+
const section = this.sectionObjects[key]
|
|
732
|
+
if (!section || section.worldX === undefined) continue
|
|
658
733
|
const dx = section.worldX - origin.x
|
|
659
734
|
const dy = (section.worldY ?? 0) - origin.y
|
|
660
735
|
const dz = (section.worldZ ?? 0) - origin.z
|
|
661
736
|
if (dx * dx + dy * dy + dz * dz > maxDistSq) continue
|
|
662
|
-
const inOpaque = this.globalLegacyBuffer?.hasSection(key) ?? false
|
|
663
|
-
const inBlend = this.globalLegacyBlendBuffer?.hasSection(key) ?? false
|
|
664
|
-
if (!inOpaque && !inBlend) continue
|
|
665
737
|
if (!sectionAabbIntersectsRay(
|
|
666
738
|
section.worldX,
|
|
667
739
|
section.worldY ?? 0,
|
|
@@ -905,7 +977,9 @@ export class ChunkMeshManager {
|
|
|
905
977
|
geometryData.sy,
|
|
906
978
|
geometryData.sz,
|
|
907
979
|
)
|
|
908
|
-
if (
|
|
980
|
+
if (added) {
|
|
981
|
+
this.registerLegacyCullSection(sectionKey, geometryData.sx, geometryData.sy, geometryData.sz)
|
|
982
|
+
} else {
|
|
909
983
|
const poolEntry = this.acquirePooledSectionMesh(sectionKey)
|
|
910
984
|
if (!poolEntry) return null
|
|
911
985
|
legacyMesh = this.uploadLegacyPooledMesh(poolEntry, geometryData, geometryData.sx, geometryData.sy, geometryData.sz)
|
|
@@ -953,7 +1027,9 @@ export class ChunkMeshManager {
|
|
|
953
1027
|
geometryData.sy,
|
|
954
1028
|
geometryData.sz,
|
|
955
1029
|
)
|
|
956
|
-
if (
|
|
1030
|
+
if (added) {
|
|
1031
|
+
this.registerLegacyCullSection(sectionKey, geometryData.sx, geometryData.sy, geometryData.sz)
|
|
1032
|
+
} else {
|
|
957
1033
|
console.warn(`ChunkMeshManager: blend invariant violation for section ${sectionKey}, using pooled mesh fallback`)
|
|
958
1034
|
const poolEntry = this.acquirePooledSectionMesh(sectionKey)
|
|
959
1035
|
if (!poolEntry) return null
|
|
@@ -1190,6 +1266,7 @@ export class ChunkMeshManager {
|
|
|
1190
1266
|
sectionObject.visible = true
|
|
1191
1267
|
}
|
|
1192
1268
|
delete this.waitingChunksToDisplay[chunkKey]
|
|
1269
|
+
this.markCullDirty()
|
|
1193
1270
|
}
|
|
1194
1271
|
|
|
1195
1272
|
// Re-check every parked entry; each has its own grace window via `ageMs`.
|
|
@@ -1352,6 +1429,7 @@ export class ChunkMeshManager {
|
|
|
1352
1429
|
this.globalBlockBuffer?.removeSection(sectionKey)
|
|
1353
1430
|
this.globalLegacyBuffer?.removeSection(sectionKey)
|
|
1354
1431
|
this.globalLegacyBlendBuffer?.removeSection(sectionKey)
|
|
1432
|
+
this.maybeUnregisterLegacyCullSection(sectionKey)
|
|
1355
1433
|
this.unregisterShaderSectionRaycastBox(sectionKey)
|
|
1356
1434
|
this.markCullDirty()
|
|
1357
1435
|
delete sectionObject.deferredLegacyOpaque
|
|
@@ -69,6 +69,7 @@ export class GlobalBlockBuffer {
|
|
|
69
69
|
private tierCAttrs: TierCAttrState | null = null
|
|
70
70
|
private tierCGl: WebGL2RenderingContext | null = null
|
|
71
71
|
private debugOverlay = false
|
|
72
|
+
private layoutVersion = 0
|
|
72
73
|
|
|
73
74
|
constructor (
|
|
74
75
|
material: THREE.ShaderMaterial,
|
|
@@ -225,6 +226,11 @@ export class GlobalBlockBuffer {
|
|
|
225
226
|
this.sectionSlots.set(sectionKey, slot)
|
|
226
227
|
this.markDirty(slot.start, slot.start + faceCount - 1)
|
|
227
228
|
this.mesh.geometry.instanceCount = this.highWatermark
|
|
229
|
+
this.layoutVersion++
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
getLayoutVersion (): number {
|
|
233
|
+
return this.layoutVersion
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
hasSection (sectionKey: string): boolean {
|
|
@@ -288,6 +294,7 @@ export class GlobalBlockBuffer {
|
|
|
288
294
|
this.insertFreeSlot(slot)
|
|
289
295
|
this.shrinkHighWatermark()
|
|
290
296
|
this.mesh.geometry.instanceCount = this.highWatermark
|
|
297
|
+
this.layoutVersion++
|
|
291
298
|
}
|
|
292
299
|
|
|
293
300
|
/** One interior-hole move per frame when fragmentation exceeds threshold; deferred shrink. */
|
|
@@ -318,6 +325,7 @@ export class GlobalBlockBuffer {
|
|
|
318
325
|
this.sectionSlots.set(section.key, { start: newStart, count: section.count })
|
|
319
326
|
this.markDirty(newStart, newStart + section.count - 1)
|
|
320
327
|
this.pendingMove = { key: section.key, oldStart, newStart, count: section.count }
|
|
328
|
+
this.layoutVersion++
|
|
321
329
|
}
|
|
322
330
|
|
|
323
331
|
uploadDirtyRange (): void {
|