minecraft-renderer 0.1.72 → 0.1.73
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/README.md +1 -1
- package/dist/mesher.js +81 -81
- package/dist/mesher.js.map +3 -3
- package/dist/mesherWasm.js +1183 -943
- package/dist/minecraft-renderer.js +249 -78
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +1732 -1001
- package/package.json +3 -3
- package/src/graphicsBackend/rendererDefaultOptions.ts +2 -7
- package/src/graphicsBackend/rendererOptionsSync.ts +1 -1
- package/src/lib/bakeLegacyLight.ts +17 -0
- package/src/lib/blockEntityLightRegistry.test.ts +18 -0
- package/src/lib/blockEntityLightRegistry.ts +75 -0
- package/src/lib/blockEntityLighting.test.ts +30 -0
- package/src/lib/blockEntityLighting.ts +53 -0
- package/src/lib/worldrendererCommon.ts +14 -6
- package/src/mesher-shared/blockEntityMetadata.test.ts +33 -0
- package/src/mesher-shared/blockEntityMetadata.ts +19 -3
- package/src/mesher-shared/exportedGeometryTypes.ts +11 -0
- package/src/mesher-shared/models.ts +161 -92
- package/src/mesher-shared/shared.ts +15 -4
- package/src/mesher-shared/tests/liquidQuadInvariant.test.ts +40 -0
- package/src/mesher-shared/world.ts +12 -0
- package/src/mesher-shared/worldLighting.test.ts +54 -0
- package/src/playground/baseScene.ts +1 -1
- package/src/three/bannerRenderer.ts +10 -3
- package/src/three/chunkMeshManager.ts +663 -69
- package/src/three/cubeDrawSpans.ts +74 -0
- package/src/three/cubeMultiDraw.ts +119 -0
- package/src/three/documentRenderer.ts +0 -2
- package/src/three/entities.ts +5 -6
- package/src/three/entity/EntityMesh.ts +7 -5
- package/src/three/entity/gltfAnimationUtils.ts +5 -3
- package/src/three/globalBlockBuffer.ts +208 -12
- package/src/three/globalLegacyBuffer.ts +701 -0
- package/src/three/itemMesh.ts +5 -2
- package/src/three/legacySectionCull.ts +85 -0
- package/src/three/modules/sciFiWorldReveal.ts +347 -703
- package/src/three/modules/starfield.ts +3 -2
- package/src/three/sectionRaycastAabb.ts +25 -0
- package/src/three/shaders/cubeBlockShader.ts +80 -17
- package/src/three/shaders/legacyBlockShader.ts +292 -0
- package/src/three/skyboxRenderer.ts +1 -1
- package/src/three/tests/chunkMeshManagerLegacy.test.ts +286 -0
- package/src/three/tests/cubeDrawSpans.test.ts +73 -0
- package/src/three/tests/globalLegacyBuffer.test.ts +360 -0
- package/src/three/tests/legacySectionCull.test.ts +80 -0
- package/src/three/tests/signTextureCache.test.ts +83 -0
- package/src/three/threeJsMedia.ts +2 -2
- package/src/three/waypointSprite.ts +2 -2
- package/src/three/world/cursorBlock.ts +1 -0
- package/src/three/world/vr.ts +2 -2
- package/src/three/worldGeometryExport.ts +83 -26
- package/src/three/worldRendererThree.ts +94 -25
- package/src/wasm-mesher/bridge/render-from-wasm.ts +214 -72
- package/src/wasm-mesher/bridge/shaderCubeBridge.ts +18 -6
- package/src/wasm-mesher/runtime-build/wasm_mesher_bg.wasm +0 -0
- package/src/wasm-mesher/tests/sectionRaycastAabb.test.ts +20 -0
- package/src/wasm-mesher/tests/shaderCubeInstances.test.ts +67 -5
- package/src/wasm-mesher/worker/mesherWasm.ts +70 -14
- package/src/wasm-mesher/worker/mesherWasmLightDirty.test.ts +11 -0
- package/src/wasm-mesher/worker/mesherWasmLightDirty.ts +15 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import {
|
|
3
|
+
FULL_DRAW_VISIBLE_FRACTION,
|
|
4
|
+
MAX_OPAQUE_SPANS,
|
|
5
|
+
SPAN_GAP_TOLERANCE_QUADS,
|
|
6
|
+
} from './globalLegacyBuffer'
|
|
7
|
+
|
|
8
|
+
export const MAX_CUBE_SPANS = MAX_OPAQUE_SPANS
|
|
9
|
+
export const SPAN_GAP_TOLERANCE_FACES = SPAN_GAP_TOLERANCE_QUADS
|
|
10
|
+
export { FULL_DRAW_VISIBLE_FRACTION }
|
|
11
|
+
|
|
12
|
+
export type CubeDrawSpan = { start: number, count: number }
|
|
13
|
+
|
|
14
|
+
export type VisibleCubeSlot = { start: number, count: number }
|
|
15
|
+
|
|
16
|
+
function mergeCubeSpans (spans: CubeDrawSpan[]): void {
|
|
17
|
+
if (spans.length < 2) return
|
|
18
|
+
let i = 0
|
|
19
|
+
while (i < spans.length - 1) {
|
|
20
|
+
const cur = spans[i]!
|
|
21
|
+
const next = spans[i + 1]!
|
|
22
|
+
const gap = next.start - (cur.start + cur.count)
|
|
23
|
+
if (gap <= SPAN_GAP_TOLERANCE_FACES) {
|
|
24
|
+
cur.count = next.start + next.count - cur.start
|
|
25
|
+
spans.splice(i + 1, 1)
|
|
26
|
+
} else {
|
|
27
|
+
i++
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function capCubeSpans (spans: CubeDrawSpan[]): void {
|
|
33
|
+
while (spans.length > MAX_CUBE_SPANS) {
|
|
34
|
+
let bestIdx = 0
|
|
35
|
+
let bestGap = Infinity
|
|
36
|
+
for (let i = 0; i < spans.length - 1; i++) {
|
|
37
|
+
const gap = spans[i + 1]!.start - (spans[i]!.start + spans[i]!.count)
|
|
38
|
+
if (gap < bestGap) {
|
|
39
|
+
bestGap = gap
|
|
40
|
+
bestIdx = i
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const cur = spans[bestIdx]!
|
|
44
|
+
const next = spans[bestIdx + 1]!
|
|
45
|
+
cur.count = next.start + next.count - cur.start
|
|
46
|
+
spans.splice(bestIdx + 1, 1)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Merge/cap visible face-instance ranges for WEBGL_multi_draw (option B).
|
|
52
|
+
* Instance indices — no *6 scaling on span values.
|
|
53
|
+
*/
|
|
54
|
+
export function buildVisibleCubeSpans (
|
|
55
|
+
visibleSlots: VisibleCubeSlot[],
|
|
56
|
+
highWatermark: number,
|
|
57
|
+
): CubeDrawSpan[] {
|
|
58
|
+
if (visibleSlots.length === 0 || highWatermark === 0) return []
|
|
59
|
+
|
|
60
|
+
let visibleFaceCount = 0
|
|
61
|
+
for (const slot of visibleSlots) {
|
|
62
|
+
visibleFaceCount += slot.count
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (visibleFaceCount >= highWatermark * FULL_DRAW_VISIBLE_FRACTION) {
|
|
66
|
+
return [{ start: 0, count: highWatermark }]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const spans = visibleSlots.map(s => ({ start: s.start, count: s.count }))
|
|
70
|
+
spans.sort((a, b) => a.start - b.start)
|
|
71
|
+
mergeCubeSpans(spans)
|
|
72
|
+
capCubeSpans(spans)
|
|
73
|
+
return spans
|
|
74
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import type { CubeDrawSpan } from './cubeDrawSpans'
|
|
3
|
+
import { MAX_CUBE_SPANS } from './cubeDrawSpans'
|
|
4
|
+
import { VERTICES_PER_FACE } from './shaders/cubeBlockShader'
|
|
5
|
+
|
|
6
|
+
export type MultiDrawTier = 'A' | 'B' | 'C'
|
|
7
|
+
|
|
8
|
+
export type MultiDrawCaps = {
|
|
9
|
+
tier: MultiDrawTier
|
|
10
|
+
ext: MultiDrawInstancedExt | DrawInstancedBaseExt | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type MultiDrawInstancedExt = {
|
|
14
|
+
multiDrawArraysInstancedBaseInstanceWEBGL: (
|
|
15
|
+
mode: number,
|
|
16
|
+
firsts: Int32Array,
|
|
17
|
+
firstsOffset: number,
|
|
18
|
+
counts: Int32Array,
|
|
19
|
+
countsOffset: number,
|
|
20
|
+
instanceCounts: Int32Array,
|
|
21
|
+
instanceCountsOffset: number,
|
|
22
|
+
baseInstances: Int32Array,
|
|
23
|
+
baseInstancesOffset: number,
|
|
24
|
+
drawCount: number,
|
|
25
|
+
) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type DrawInstancedBaseExt = {
|
|
29
|
+
drawArraysInstancedBaseInstanceWEBGL: (
|
|
30
|
+
mode: number,
|
|
31
|
+
first: number,
|
|
32
|
+
count: number,
|
|
33
|
+
instanceCount: number,
|
|
34
|
+
baseInstance: number,
|
|
35
|
+
) => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type CubeMultiDrawScratch = {
|
|
39
|
+
firsts: Int32Array
|
|
40
|
+
counts: Int32Array
|
|
41
|
+
instanceCounts: Int32Array
|
|
42
|
+
baseInstances: Int32Array
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createCubeMultiDrawScratch (): CubeMultiDrawScratch {
|
|
46
|
+
return {
|
|
47
|
+
firsts: new Int32Array(MAX_CUBE_SPANS),
|
|
48
|
+
counts: new Int32Array(MAX_CUBE_SPANS),
|
|
49
|
+
instanceCounts: new Int32Array(MAX_CUBE_SPANS),
|
|
50
|
+
baseInstances: new Int32Array(MAX_CUBE_SPANS),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function detectMultiDrawCaps (gl: WebGL2RenderingContext): MultiDrawCaps {
|
|
55
|
+
const tierA = gl.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance')
|
|
56
|
+
if (tierA) {
|
|
57
|
+
return { tier: 'A', ext: tierA as MultiDrawInstancedExt }
|
|
58
|
+
}
|
|
59
|
+
const tierB = gl.getExtension('WEBGL_draw_instanced_base_vertex_base_instance')
|
|
60
|
+
if (tierB) {
|
|
61
|
+
return { tier: 'B', ext: tierB as DrawInstancedBaseExt }
|
|
62
|
+
}
|
|
63
|
+
return { tier: 'C', ext: null }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let tierLogged = false
|
|
67
|
+
|
|
68
|
+
export function logMultiDrawTierOnce (tier: MultiDrawTier, debug: boolean): void {
|
|
69
|
+
if (tierLogged || !debug) return
|
|
70
|
+
tierLogged = true
|
|
71
|
+
console.info('[globalBlockBuffer] cube multi_draw tier', tier)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Issue instanced cube draws for visible spans. Tier C delegates to buffer-owned VAO path.
|
|
76
|
+
*/
|
|
77
|
+
export function drawCubeSpans (
|
|
78
|
+
gl: WebGL2RenderingContext,
|
|
79
|
+
caps: MultiDrawCaps,
|
|
80
|
+
spans: readonly CubeDrawSpan[],
|
|
81
|
+
scratch: CubeMultiDrawScratch,
|
|
82
|
+
tierCDraw?: (gl: WebGL2RenderingContext, spans: readonly CubeDrawSpan[]) => void,
|
|
83
|
+
): void {
|
|
84
|
+
const drawCount = spans.length
|
|
85
|
+
if (drawCount === 0) return
|
|
86
|
+
|
|
87
|
+
const mode = gl.TRIANGLES
|
|
88
|
+
|
|
89
|
+
if (caps.tier === 'A' && caps.ext) {
|
|
90
|
+
const ext = caps.ext as MultiDrawInstancedExt
|
|
91
|
+
for (let i = 0; i < drawCount; i++) {
|
|
92
|
+
const span = spans[i]!
|
|
93
|
+
scratch.firsts[i] = 0
|
|
94
|
+
scratch.counts[i] = VERTICES_PER_FACE
|
|
95
|
+
scratch.instanceCounts[i] = span.count
|
|
96
|
+
scratch.baseInstances[i] = span.start
|
|
97
|
+
}
|
|
98
|
+
ext.multiDrawArraysInstancedBaseInstanceWEBGL(
|
|
99
|
+
mode,
|
|
100
|
+
scratch.firsts, 0,
|
|
101
|
+
scratch.counts, 0,
|
|
102
|
+
scratch.instanceCounts, 0,
|
|
103
|
+
scratch.baseInstances, 0,
|
|
104
|
+
drawCount,
|
|
105
|
+
)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (caps.tier === 'B' && caps.ext) {
|
|
110
|
+
const ext = caps.ext as DrawInstancedBaseExt
|
|
111
|
+
for (let i = 0; i < drawCount; i++) {
|
|
112
|
+
const span = spans[i]!
|
|
113
|
+
ext.drawArraysInstancedBaseInstanceWEBGL(mode, 0, VERTICES_PER_FACE, span.count, span.start)
|
|
114
|
+
}
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
tierCDraw?.(gl, spans)
|
|
119
|
+
}
|
package/src/three/entities.ts
CHANGED
|
@@ -265,7 +265,7 @@ export class Entities {
|
|
|
265
265
|
}
|
|
266
266
|
debugMode: string
|
|
267
267
|
onSkinUpdate: () => void
|
|
268
|
-
clock = new THREE.
|
|
268
|
+
clock = new THREE.Timer()
|
|
269
269
|
currentlyRendering = true
|
|
270
270
|
cachedMapsImages = {} as Record<number, string>
|
|
271
271
|
itemFrameMaps = {} as Record<number, Array<THREE.Mesh<THREE.PlaneGeometry, THREE.MeshLambertMaterial>>>
|
|
@@ -405,6 +405,7 @@ export class Entities {
|
|
|
405
405
|
this.setRendering(renderEntitiesConfig)
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
+
this.clock.update(performance.now())
|
|
408
409
|
const dt = Math.min(this.clock.getDelta(), 1 / 30)
|
|
409
410
|
const botPos = this.worldRenderer.viewerChunkPosition
|
|
410
411
|
const VISIBLE_DISTANCE = 10 * 10
|
|
@@ -678,7 +679,6 @@ export class Entities {
|
|
|
678
679
|
earsTexture.magFilter = THREE.NearestFilter
|
|
679
680
|
earsTexture.minFilter = THREE.NearestFilter
|
|
680
681
|
earsTexture.needsUpdate = true
|
|
681
|
-
//@ts-expect-error
|
|
682
682
|
playerObject.ears.map = earsTexture
|
|
683
683
|
playerObject.ears.visible = true
|
|
684
684
|
} else {
|
|
@@ -706,10 +706,8 @@ export class Entities {
|
|
|
706
706
|
capeTexture.magFilter = THREE.NearestFilter
|
|
707
707
|
capeTexture.minFilter = THREE.NearestFilter
|
|
708
708
|
capeTexture.needsUpdate = true
|
|
709
|
-
//@ts-expect-error
|
|
710
709
|
playerObject.cape.map = capeTexture
|
|
711
710
|
playerObject.cape.visible = true
|
|
712
|
-
//@ts-expect-error
|
|
713
711
|
playerObject.elytra.map = capeTexture
|
|
714
712
|
this.onSkinUpdate?.()
|
|
715
713
|
|
|
@@ -902,9 +900,10 @@ export class Entities {
|
|
|
902
900
|
// mesh.position.set(targetPos.x + 0.5 + 2, targetPos.y + 0.5, targetPos.z + 0.5)
|
|
903
901
|
// viewer.scene.add(mesh)
|
|
904
902
|
if (entity.name === 'item') {
|
|
905
|
-
const
|
|
903
|
+
const itemTimer = new THREE.Timer()
|
|
906
904
|
mesh.onBeforeRender = () => {
|
|
907
|
-
|
|
905
|
+
itemTimer.update(performance.now())
|
|
906
|
+
const delta = itemTimer.getDelta()
|
|
908
907
|
mesh!.rotation.y += delta
|
|
909
908
|
}
|
|
910
909
|
}
|
|
@@ -261,7 +261,7 @@ export function getMesh(
|
|
|
261
261
|
let textureHeight = jsonModel.textureheight ?? 64
|
|
262
262
|
let textureOffset: number[] | undefined
|
|
263
263
|
const useBlockTexture = texture.startsWith('block:')
|
|
264
|
-
const blocksTexture = worldRenderer?.material.map
|
|
264
|
+
const blocksTexture = worldRenderer?.material.map as THREE.Texture<HTMLImageElement | ImageBitmap> | null | undefined
|
|
265
265
|
if (useBlockTexture) {
|
|
266
266
|
if (!worldRenderer) throw new Error('worldRenderer is required for block textures')
|
|
267
267
|
const blockName = texture.slice(6)
|
|
@@ -369,13 +369,15 @@ export function getMesh(
|
|
|
369
369
|
material.map = loadedTexture
|
|
370
370
|
}, () => {
|
|
371
371
|
// This callback runs after the texture is fully loaded
|
|
372
|
-
const
|
|
372
|
+
const map = material.map as THREE.Texture<HTMLImageElement | ImageBitmap> | null
|
|
373
|
+
if (!map) return
|
|
374
|
+
const actualWidth = map.image.width
|
|
373
375
|
if (actualWidth && textureWidth !== actualWidth) {
|
|
374
|
-
|
|
376
|
+
map.repeat.x = textureWidth / actualWidth
|
|
375
377
|
}
|
|
376
|
-
const actualHeight =
|
|
378
|
+
const actualHeight = map.image.height
|
|
377
379
|
if (actualHeight && textureHeight !== actualHeight) {
|
|
378
|
-
|
|
380
|
+
map.repeat.y = textureHeight / actualHeight
|
|
379
381
|
}
|
|
380
382
|
material.needsUpdate = true
|
|
381
383
|
})
|
|
@@ -10,7 +10,7 @@ export interface AnimationState {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export class AnimationManager {
|
|
13
|
-
private readonly
|
|
13
|
+
private readonly timer = new THREE.Timer()
|
|
14
14
|
private readonly animatedObjects = new Set<THREE.Object3D>()
|
|
15
15
|
state!: AnimationState
|
|
16
16
|
|
|
@@ -47,7 +47,8 @@ export class AnimationManager {
|
|
|
47
47
|
if (child instanceof THREE.Mesh || child instanceof THREE.Line || child instanceof THREE.Points || child instanceof THREE.Sprite) {
|
|
48
48
|
const originalOnBeforeRender = child.onBeforeRender
|
|
49
49
|
child.onBeforeRender = (renderer, scene, camera, geometry, material, group) => {
|
|
50
|
-
|
|
50
|
+
this.timer.update(performance.now())
|
|
51
|
+
const delta = this.timer.getDelta()
|
|
51
52
|
mixer.update(delta)
|
|
52
53
|
// Call original onBeforeRender if it existed
|
|
53
54
|
originalOnBeforeRender?.(renderer, scene, camera, geometry, material, group)
|
|
@@ -123,7 +124,8 @@ export class AnimationManager {
|
|
|
123
124
|
* Gets the current clock delta (useful for manual updates)
|
|
124
125
|
*/
|
|
125
126
|
getDelta(): number {
|
|
126
|
-
|
|
127
|
+
this.timer.update(performance.now())
|
|
128
|
+
return this.timer.getDelta()
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
|
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import * as THREE from 'three'
|
|
3
|
-
import {
|
|
3
|
+
import type { CubeDrawSpan } from './cubeDrawSpans'
|
|
4
|
+
import {
|
|
5
|
+
createCubeMultiDrawScratch,
|
|
6
|
+
detectMultiDrawCaps,
|
|
7
|
+
drawCubeSpans,
|
|
8
|
+
logMultiDrawTierOnce,
|
|
9
|
+
type CubeMultiDrawScratch,
|
|
10
|
+
type MultiDrawCaps,
|
|
11
|
+
} from './cubeMultiDraw'
|
|
12
|
+
import { VERTICES_PER_FACE, computeSectionOriginRel } from './shaders/cubeBlockShader'
|
|
13
|
+
import { computeCameraRelativeUniforms, type RenderOrigin } from './shaders/legacyBlockShader'
|
|
4
14
|
import { packWord2Empty } from '../wasm-mesher/bridge/shaderCubeBridge'
|
|
5
15
|
|
|
16
|
+
type WebGLRendererInternals = THREE.WebGLRenderer & {
|
|
17
|
+
properties: {
|
|
18
|
+
get: (material: THREE.Material) => { currentProgram: { program: WebGLProgram } }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type TierCAttrBinding = { loc: number, buffer: WebGLBuffer }
|
|
23
|
+
type TierCAttrState = {
|
|
24
|
+
w0: TierCAttrBinding
|
|
25
|
+
w1: TierCAttrBinding
|
|
26
|
+
w2: TierCAttrBinding
|
|
27
|
+
w3: TierCAttrBinding
|
|
28
|
+
}
|
|
29
|
+
|
|
6
30
|
// Linear growth (NOT doubling) to keep iOS allocation spikes bounded to one increment.
|
|
7
31
|
// Reference: prismarine-web-client PR #90 (webgl) and #120 (webgpu) both grow by +1M faces.
|
|
8
32
|
const INITIAL_CAPACITY_FACES = 512_000 // ~8 MB up front (4 words × 4 B), well under 1M
|
|
@@ -20,7 +44,7 @@ export type GlobalBlockBufferShaderData = {
|
|
|
20
44
|
|
|
21
45
|
/**
|
|
22
46
|
* Single GPU instanced mesh for all shader-cube faces in the world.
|
|
23
|
-
* Camera-relative positioning via
|
|
47
|
+
* Camera-relative positioning via u_originDelta + u_sectionOriginRel; no sceneOrigin tracking.
|
|
24
48
|
*/
|
|
25
49
|
export class GlobalBlockBuffer {
|
|
26
50
|
readonly mesh: THREE.Mesh<THREE.InstancedBufferGeometry, THREE.ShaderMaterial>
|
|
@@ -35,6 +59,13 @@ export class GlobalBlockBuffer {
|
|
|
35
59
|
private highWatermark = 0
|
|
36
60
|
private pendingRanges: Array<{ start: number, end: number }> = []
|
|
37
61
|
private pendingMove: PendingMove | null = null
|
|
62
|
+
private visibleSpans: CubeDrawSpan[] = []
|
|
63
|
+
private readonly _drawScratch: CubeMultiDrawScratch = createCubeMultiDrawScratch()
|
|
64
|
+
private multiDrawCaps: MultiDrawCaps | null = null
|
|
65
|
+
private tierCVao: WebGLVertexArrayObject | null = null
|
|
66
|
+
private tierCAttrs: TierCAttrState | null = null
|
|
67
|
+
private tierCGl: WebGL2RenderingContext | null = null
|
|
68
|
+
private debugOverlay = false
|
|
38
69
|
|
|
39
70
|
constructor (
|
|
40
71
|
material: THREE.ShaderMaterial,
|
|
@@ -70,6 +101,73 @@ export class GlobalBlockBuffer {
|
|
|
70
101
|
this.mesh.matrix.identity()
|
|
71
102
|
this.mesh.position.set(0, 0, 0)
|
|
72
103
|
scene.add(this.mesh)
|
|
104
|
+
|
|
105
|
+
this.mesh.onAfterRender = (renderer, _scene, _camera, _geometry, material) => {
|
|
106
|
+
if (this.visibleSpans.length === 0) return
|
|
107
|
+
const shaderMaterial = material as THREE.ShaderMaterial
|
|
108
|
+
const gl = renderer.getContext() as WebGL2RenderingContext
|
|
109
|
+
if (!this.multiDrawCaps) {
|
|
110
|
+
this.multiDrawCaps = detectMultiDrawCaps(gl)
|
|
111
|
+
logMultiDrawTierOnce(this.multiDrawCaps.tier, this.debugOverlay)
|
|
112
|
+
}
|
|
113
|
+
drawCubeSpans(
|
|
114
|
+
gl,
|
|
115
|
+
this.multiDrawCaps,
|
|
116
|
+
this.visibleSpans,
|
|
117
|
+
this._drawScratch,
|
|
118
|
+
this.multiDrawCaps.tier === 'C'
|
|
119
|
+
? (g, spans) => this.drawTierCSpans(g, spans, renderer, shaderMaterial)
|
|
120
|
+
: undefined,
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
setDebugOverlay (enabled: boolean): void {
|
|
126
|
+
this.debugOverlay = enabled
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Suppress three's full-buffer instanced draw; onAfterRender issues visible spans only.
|
|
131
|
+
* setDrawRange(0,0) skips bindingStates.setup in r0.184 — use 6 verts + instanceCount=0
|
|
132
|
+
* so program/VAO stay bound while renderInstances no-ops at primcount===0.
|
|
133
|
+
*/
|
|
134
|
+
suppressThreeDraw (): void {
|
|
135
|
+
const geometry = this.mesh.geometry
|
|
136
|
+
geometry.setDrawRange(0, VERTICES_PER_FACE)
|
|
137
|
+
geometry.instanceCount = 0
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
setVisibleSpans (spans: CubeDrawSpan[]): void {
|
|
141
|
+
this.visibleSpans = spans
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
getVisibleSpans (): readonly CubeDrawSpan[] {
|
|
145
|
+
return this.visibleSpans
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
forEachSectionSlot (cb: (key: string, slot: { start: number, count: number }) => void): void {
|
|
149
|
+
for (const [key, slot] of this.sectionSlots) {
|
|
150
|
+
cb(key, slot)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
getSectionDrawStart (sectionKey: string): number | undefined {
|
|
155
|
+
const slot = this.sectionSlots.get(sectionKey)
|
|
156
|
+
if (!slot) return undefined
|
|
157
|
+
if (this.pendingMove?.key === sectionKey) return this.pendingMove.oldStart
|
|
158
|
+
return slot.start
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getHighWatermark (): number {
|
|
162
|
+
return this.highWatermark
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
hasPendingUploads (): boolean {
|
|
166
|
+
return this.pendingRanges.length > 0
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getPendingMove (): PendingMove | null {
|
|
170
|
+
return this.pendingMove
|
|
73
171
|
}
|
|
74
172
|
|
|
75
173
|
addSection (sectionKey: string, words: Uint32Array, faceCount: number): void {
|
|
@@ -213,8 +311,8 @@ export class GlobalBlockBuffer {
|
|
|
213
311
|
|
|
214
312
|
for (const name of ['a_w0', 'a_w1', 'a_w2', 'a_w3'] as const) {
|
|
215
313
|
const attr = geometry.getAttribute(name) as THREE.InstancedBufferAttribute
|
|
216
|
-
attr.
|
|
217
|
-
attr.
|
|
314
|
+
attr.clearUpdateRanges()
|
|
315
|
+
attr.addUpdateRange(offset, count)
|
|
218
316
|
attr.needsUpdate = true
|
|
219
317
|
}
|
|
220
318
|
|
|
@@ -222,18 +320,20 @@ export class GlobalBlockBuffer {
|
|
|
222
320
|
else r.start = offset + count
|
|
223
321
|
}
|
|
224
322
|
|
|
225
|
-
setCameraOrigin (x: number, y: number, z: number): void {
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
const iz = Math.floor(z)
|
|
230
|
-
const u = this.mesh.material.uniforms.u_cameraOrigin
|
|
323
|
+
setCameraOrigin (renderOrigin: RenderOrigin, x: number, y: number, z: number): void {
|
|
324
|
+
const { originDelta, cameraOriginFrac } = computeCameraRelativeUniforms(renderOrigin, x, y, z)
|
|
325
|
+
const sectionOriginRel = computeSectionOriginRel(renderOrigin)
|
|
326
|
+
const u = this.mesh.material.uniforms.u_originDelta
|
|
231
327
|
if (u?.value?.set) {
|
|
232
|
-
u.value.set(
|
|
328
|
+
u.value.set(originDelta.x, originDelta.y, originDelta.z)
|
|
233
329
|
}
|
|
234
330
|
const uf = this.mesh.material.uniforms.u_cameraOriginFrac
|
|
235
331
|
if (uf?.value?.set) {
|
|
236
|
-
uf.value.set(x
|
|
332
|
+
uf.value.set(cameraOriginFrac.x, cameraOriginFrac.y, cameraOriginFrac.z)
|
|
333
|
+
}
|
|
334
|
+
const us = this.mesh.material.uniforms.u_sectionOriginRel
|
|
335
|
+
if (us?.value?.set) {
|
|
336
|
+
us.value.set(sectionOriginRel.x, sectionOriginRel.y, sectionOriginRel.z)
|
|
237
337
|
}
|
|
238
338
|
}
|
|
239
339
|
|
|
@@ -243,19 +343,114 @@ export class GlobalBlockBuffer {
|
|
|
243
343
|
this.highWatermark = 0
|
|
244
344
|
this.pendingRanges.length = 0
|
|
245
345
|
this.pendingMove = null
|
|
346
|
+
this.visibleSpans = []
|
|
246
347
|
this.w0.fill(0)
|
|
247
348
|
this.w1.fill(0)
|
|
248
349
|
this.w2.fill(EMPTY_W2)
|
|
249
350
|
this.w3.fill(0)
|
|
250
351
|
this.mesh.geometry.instanceCount = 0
|
|
352
|
+
this.invalidateTierCVao()
|
|
251
353
|
}
|
|
252
354
|
|
|
253
355
|
dispose (): void {
|
|
356
|
+
this.invalidateTierCVao()
|
|
254
357
|
this.mesh.parent?.remove(this.mesh)
|
|
255
358
|
this.mesh.geometry.dispose()
|
|
256
359
|
this.reset()
|
|
257
360
|
}
|
|
258
361
|
|
|
362
|
+
private invalidateTierCVao (): void {
|
|
363
|
+
if (this.tierCVao && this.tierCGl) {
|
|
364
|
+
this.tierCGl.deleteVertexArray(this.tierCVao)
|
|
365
|
+
}
|
|
366
|
+
this.tierCVao = null
|
|
367
|
+
this.tierCAttrs = null
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private drawTierCSpans (
|
|
371
|
+
gl: WebGL2RenderingContext,
|
|
372
|
+
spans: readonly CubeDrawSpan[],
|
|
373
|
+
renderer: THREE.WebGLRenderer,
|
|
374
|
+
material: THREE.ShaderMaterial,
|
|
375
|
+
): void {
|
|
376
|
+
this.ensureTierCVao(gl, renderer, material)
|
|
377
|
+
if (!this.tierCVao || !this.tierCAttrs) return
|
|
378
|
+
|
|
379
|
+
const attrs = this.tierCAttrs
|
|
380
|
+
gl.bindVertexArray(this.tierCVao)
|
|
381
|
+
for (const span of spans) {
|
|
382
|
+
const byteOffset = span.start * 4
|
|
383
|
+
const repointWord = (binding: TierCAttrBinding) => {
|
|
384
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, binding.buffer)
|
|
385
|
+
gl.vertexAttribIPointer(binding.loc, 1, gl.UNSIGNED_INT, 4, byteOffset)
|
|
386
|
+
}
|
|
387
|
+
repointWord(attrs.w0)
|
|
388
|
+
repointWord(attrs.w1)
|
|
389
|
+
repointWord(attrs.w2)
|
|
390
|
+
repointWord(attrs.w3)
|
|
391
|
+
gl.drawArraysInstanced(gl.TRIANGLES, 0, VERTICES_PER_FACE, span.count)
|
|
392
|
+
}
|
|
393
|
+
gl.bindVertexArray(null)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private ensureTierCVao (
|
|
397
|
+
gl: WebGL2RenderingContext,
|
|
398
|
+
renderer: THREE.WebGLRenderer,
|
|
399
|
+
material: THREE.ShaderMaterial,
|
|
400
|
+
): void {
|
|
401
|
+
this.tierCGl = gl
|
|
402
|
+
const internals = renderer as WebGLRendererInternals
|
|
403
|
+
const materialProps = internals.properties.get(material) as { currentProgram: { program: WebGLProgram } }
|
|
404
|
+
const program = materialProps.currentProgram.program
|
|
405
|
+
|
|
406
|
+
const w0Loc = gl.getAttribLocation(program, 'a_w0')
|
|
407
|
+
const w1Loc = gl.getAttribLocation(program, 'a_w1')
|
|
408
|
+
const w2Loc = gl.getAttribLocation(program, 'a_w2')
|
|
409
|
+
const w3Loc = gl.getAttribLocation(program, 'a_w3')
|
|
410
|
+
|
|
411
|
+
const readBoundBuffer = (loc: number): WebGLBuffer | null => {
|
|
412
|
+
if (loc < 0) return null
|
|
413
|
+
return gl.getVertexAttrib(loc, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING) as WebGLBuffer | null
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const liveW0 = readBoundBuffer(w0Loc)
|
|
417
|
+
if (this.tierCVao && this.tierCAttrs && liveW0 && this.tierCAttrs.w0.buffer !== liveW0) {
|
|
418
|
+
this.invalidateTierCVao()
|
|
419
|
+
}
|
|
420
|
+
if (this.tierCVao) return
|
|
421
|
+
|
|
422
|
+
const w0Buf = readBoundBuffer(w0Loc)
|
|
423
|
+
const w1Buf = readBoundBuffer(w1Loc)
|
|
424
|
+
const w2Buf = readBoundBuffer(w2Loc)
|
|
425
|
+
const w3Buf = readBoundBuffer(w3Loc)
|
|
426
|
+
if (!w0Buf || !w1Buf || !w2Buf || !w3Buf) return
|
|
427
|
+
|
|
428
|
+
const vao = gl.createVertexArray()
|
|
429
|
+
if (!vao) return
|
|
430
|
+
|
|
431
|
+
gl.bindVertexArray(vao)
|
|
432
|
+
|
|
433
|
+
const bindInstancedWord = (loc: number, buffer: WebGLBuffer) => {
|
|
434
|
+
gl.enableVertexAttribArray(loc)
|
|
435
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
436
|
+
gl.vertexAttribIPointer(loc, 1, gl.UNSIGNED_INT, 4, 0)
|
|
437
|
+
gl.vertexAttribDivisor(loc, 1)
|
|
438
|
+
}
|
|
439
|
+
bindInstancedWord(w0Loc, w0Buf)
|
|
440
|
+
bindInstancedWord(w1Loc, w1Buf)
|
|
441
|
+
bindInstancedWord(w2Loc, w2Buf)
|
|
442
|
+
bindInstancedWord(w3Loc, w3Buf)
|
|
443
|
+
|
|
444
|
+
gl.bindVertexArray(null)
|
|
445
|
+
this.tierCVao = vao
|
|
446
|
+
this.tierCAttrs = {
|
|
447
|
+
w0: { loc: w0Loc, buffer: w0Buf },
|
|
448
|
+
w1: { loc: w1Loc, buffer: w1Buf },
|
|
449
|
+
w2: { loc: w2Loc, buffer: w2Buf },
|
|
450
|
+
w3: { loc: w3Loc, buffer: w3Buf },
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
259
454
|
private markDirty (start: number, end: number): void {
|
|
260
455
|
this.pendingRanges.push({ start, end })
|
|
261
456
|
this.pendingRanges.sort((a, b) => a.start - b.start)
|
|
@@ -462,5 +657,6 @@ export class GlobalBlockBuffer {
|
|
|
462
657
|
mkAttr(this.w3, 'a_w3')
|
|
463
658
|
|
|
464
659
|
this.pendingRanges.length = 0
|
|
660
|
+
this.invalidateTierCVao()
|
|
465
661
|
}
|
|
466
662
|
}
|