minecraft-renderer 0.1.99 → 0.1.101
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 +170 -170
- package/dist/minecraft-renderer.js +9 -9
- package/dist/minecraft-renderer.js.meta.json +1 -1
- package/dist/threeWorker.js +16 -16
- package/package.json +1 -1
- package/src/three/cubeMultiDraw.ts +12 -0
- package/src/three/tests/cubeMultiDraw.test.ts +118 -0
- package/src/wasm-mesher/bridge/geometryIndices.ts +17 -0
- package/src/wasm-mesher/bridge/render-from-wasm.ts +3 -4
- package/src/wasm-mesher/tests/geometryIndices.test.ts +71 -0
- package/src/wasm-mesher/worker/mesherWasm.ts +5 -5
package/package.json
CHANGED
|
@@ -45,6 +45,17 @@ export function createCubeMultiDrawScratch(): CubeMultiDrawScratch {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** Grow scratch arrays when culling leaves more disjoint spans than MAX_CUBE_SPANS. */
|
|
49
|
+
function ensureScratchCapacity(scratch: CubeMultiDrawScratch, minLength: number): void {
|
|
50
|
+
if (scratch.counts.length >= minLength) return
|
|
51
|
+
let newLen = scratch.counts.length
|
|
52
|
+
while (newLen < minLength) newLen *= 2
|
|
53
|
+
scratch.firsts = new Int32Array(newLen)
|
|
54
|
+
scratch.counts = new Int32Array(newLen)
|
|
55
|
+
scratch.instanceCounts = new Int32Array(newLen)
|
|
56
|
+
scratch.baseInstances = new Int32Array(newLen)
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
export function detectMultiDrawCaps(gl: WebGL2RenderingContext): MultiDrawCaps {
|
|
49
60
|
const tierA = gl.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance')
|
|
50
61
|
if (tierA) {
|
|
@@ -82,6 +93,7 @@ export function drawCubeSpans(
|
|
|
82
93
|
|
|
83
94
|
if (caps.tier === 'A' && caps.ext) {
|
|
84
95
|
const ext = caps.ext as MultiDrawInstancedExt
|
|
96
|
+
ensureScratchCapacity(scratch, drawCount)
|
|
85
97
|
for (let i = 0; i < drawCount; i++) {
|
|
86
98
|
const span = spans[i]!
|
|
87
99
|
scratch.firsts[i] = 0
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { expect, test, vi } from 'vitest'
|
|
3
|
+
import { buildVisibleCubeSpans, MAX_CUBE_SPANS } from '../cubeDrawSpans'
|
|
4
|
+
import { createCubeMultiDrawScratch, detectMultiDrawCaps, drawCubeSpans } from '../cubeMultiDraw'
|
|
5
|
+
import { VERTICES_PER_FACE } from '../shaders/cubeBlockShader'
|
|
6
|
+
|
|
7
|
+
type MultiDrawArgs = [number, Int32Array, number, Int32Array, number, Int32Array, number, Int32Array, number, number]
|
|
8
|
+
|
|
9
|
+
function makeMockGl(opts?: { tier?: 'A' | 'B' }): WebGL2RenderingContext {
|
|
10
|
+
const multiDrawArraysInstancedBaseInstanceWEBGL = vi.fn((...args: MultiDrawArgs) => {
|
|
11
|
+
const [, firsts, , counts, , instanceCounts, , baseInstances, , drawCount] = args
|
|
12
|
+
// WebGL raises INVALID_OPERATION and draws nothing when an argument array is shorter than drawCount.
|
|
13
|
+
for (const array of [firsts, counts, instanceCounts, baseInstances]) {
|
|
14
|
+
if (array.length < drawCount) throw new Error(`argument array of length ${array.length} for drawCount ${drawCount}`)
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
const drawArraysInstancedBaseInstanceWEBGL = vi.fn()
|
|
18
|
+
return {
|
|
19
|
+
TRIANGLES: 0x0004,
|
|
20
|
+
getExtension: (name: string) => {
|
|
21
|
+
if (opts?.tier === 'A' && name === 'WEBGL_multi_draw_instanced_base_vertex_base_instance') {
|
|
22
|
+
return { multiDrawArraysInstancedBaseInstanceWEBGL }
|
|
23
|
+
}
|
|
24
|
+
if (opts?.tier === 'B' && name === 'WEBGL_draw_instanced_base_vertex_base_instance') {
|
|
25
|
+
return { drawArraysInstancedBaseInstanceWEBGL }
|
|
26
|
+
}
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
} as unknown as WebGL2RenderingContext
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Visible slots with a one-instance hole between each, so no two merge into one span. */
|
|
33
|
+
function scatteredSpans(count: number) {
|
|
34
|
+
const slots = Array.from({ length: count }, (_, i) => ({ start: i * 3, count: 2 }))
|
|
35
|
+
return buildVisibleCubeSpans(slots, count * 3)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
test('drawCubeSpans: tier A submits one instanced draw per span', () => {
|
|
39
|
+
const gl = makeMockGl({ tier: 'A' })
|
|
40
|
+
const caps = detectMultiDrawCaps(gl)
|
|
41
|
+
const scratch = createCubeMultiDrawScratch()
|
|
42
|
+
const spans = [
|
|
43
|
+
{ start: 4, count: 2 },
|
|
44
|
+
{ start: 10, count: 5 }
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
drawCubeSpans(gl, caps, spans, scratch)
|
|
48
|
+
|
|
49
|
+
expect(Array.from(scratch.firsts.subarray(0, 2))).toEqual([0, 0])
|
|
50
|
+
expect(Array.from(scratch.counts.subarray(0, 2))).toEqual([VERTICES_PER_FACE, VERTICES_PER_FACE])
|
|
51
|
+
expect(Array.from(scratch.instanceCounts.subarray(0, 2))).toEqual([2, 5])
|
|
52
|
+
expect(Array.from(scratch.baseInstances.subarray(0, 2))).toEqual([4, 10])
|
|
53
|
+
|
|
54
|
+
const ext = caps.ext as { multiDrawArraysInstancedBaseInstanceWEBGL: ReturnType<typeof vi.fn> }
|
|
55
|
+
expect(ext.multiDrawArraysInstancedBaseInstanceWEBGL).toHaveBeenCalledTimes(1)
|
|
56
|
+
expect(ext.multiDrawArraysInstancedBaseInstanceWEBGL).toHaveBeenCalledWith(
|
|
57
|
+
gl.TRIANGLES,
|
|
58
|
+
scratch.firsts,
|
|
59
|
+
0,
|
|
60
|
+
scratch.counts,
|
|
61
|
+
0,
|
|
62
|
+
scratch.instanceCounts,
|
|
63
|
+
0,
|
|
64
|
+
scratch.baseInstances,
|
|
65
|
+
0,
|
|
66
|
+
2
|
|
67
|
+
)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('drawCubeSpans: tier A grows scratch past MAX_CUBE_SPANS for fragmented visible sets', () => {
|
|
71
|
+
const gl = makeMockGl({ tier: 'A' })
|
|
72
|
+
const caps = detectMultiDrawCaps(gl)
|
|
73
|
+
const scratch = createCubeMultiDrawScratch()
|
|
74
|
+
expect(scratch.counts.length).toBe(MAX_CUBE_SPANS)
|
|
75
|
+
|
|
76
|
+
const spans = scatteredSpans(MAX_CUBE_SPANS + 6)
|
|
77
|
+
expect(spans.length).toBe(MAX_CUBE_SPANS + 6)
|
|
78
|
+
|
|
79
|
+
drawCubeSpans(gl, caps, spans, scratch)
|
|
80
|
+
|
|
81
|
+
for (const array of [scratch.firsts, scratch.counts, scratch.instanceCounts, scratch.baseInstances]) {
|
|
82
|
+
expect(array.length).toBeGreaterThanOrEqual(spans.length)
|
|
83
|
+
}
|
|
84
|
+
const last = spans.length - 1
|
|
85
|
+
expect(scratch.instanceCounts[last]).toBe(spans[last]!.count)
|
|
86
|
+
expect(scratch.baseInstances[last]).toBe(spans[last]!.start)
|
|
87
|
+
const ext = caps.ext as { multiDrawArraysInstancedBaseInstanceWEBGL: ReturnType<typeof vi.fn> }
|
|
88
|
+
expect(ext.multiDrawArraysInstancedBaseInstanceWEBGL.mock.calls[0]![9]).toBe(spans.length)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('drawCubeSpans: tier A keeps grown scratch when the visible set shrinks and grows again', () => {
|
|
92
|
+
const gl = makeMockGl({ tier: 'A' })
|
|
93
|
+
const caps = detectMultiDrawCaps(gl)
|
|
94
|
+
const scratch = createCubeMultiDrawScratch()
|
|
95
|
+
|
|
96
|
+
drawCubeSpans(gl, caps, scatteredSpans(MAX_CUBE_SPANS + 1), scratch)
|
|
97
|
+
const grown = scratch.counts
|
|
98
|
+
drawCubeSpans(gl, caps, scatteredSpans(3), scratch)
|
|
99
|
+
expect(scratch.counts).toBe(grown)
|
|
100
|
+
drawCubeSpans(gl, caps, scatteredSpans(grown.length * 2 + 1), scratch)
|
|
101
|
+
|
|
102
|
+
expect(scratch.counts.length).toBeGreaterThanOrEqual(grown.length * 2 + 1)
|
|
103
|
+
const ext = caps.ext as { multiDrawArraysInstancedBaseInstanceWEBGL: ReturnType<typeof vi.fn> }
|
|
104
|
+
expect(ext.multiDrawArraysInstancedBaseInstanceWEBGL).toHaveBeenCalledTimes(3)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('drawCubeSpans: tier B issues one base-instance draw per span', () => {
|
|
108
|
+
const gl = makeMockGl({ tier: 'B' })
|
|
109
|
+
const caps = detectMultiDrawCaps(gl)
|
|
110
|
+
const scratch = createCubeMultiDrawScratch()
|
|
111
|
+
const spans = scatteredSpans(MAX_CUBE_SPANS + 1)
|
|
112
|
+
|
|
113
|
+
drawCubeSpans(gl, caps, spans, scratch)
|
|
114
|
+
|
|
115
|
+
const ext = caps.ext as { drawArraysInstancedBaseInstanceWEBGL: ReturnType<typeof vi.fn> }
|
|
116
|
+
expect(ext.drawArraysInstancedBaseInstanceWEBGL).toHaveBeenCalledTimes(spans.length)
|
|
117
|
+
expect(ext.drawArraysInstancedBaseInstanceWEBGL).toHaveBeenLastCalledWith(gl.TRIANGLES, 0, VERTICES_PER_FACE, 2, (spans.length - 1) * 3)
|
|
118
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
export function createGeometryIndexArray(indices: readonly number[]): Uint16Array | Uint32Array {
|
|
3
|
+
let using32Array = false
|
|
4
|
+
for (let index = 0; index < indices.length; index++) {
|
|
5
|
+
if (indices[index] > 65535) {
|
|
6
|
+
using32Array = true
|
|
7
|
+
break
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return using32Array ? new Uint32Array(indices) : new Uint16Array(indices)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function appendGeometryIndices(target: number[], source: readonly number[], vertexOffset: number): void {
|
|
14
|
+
for (let index = 0; index < source.length; index++) {
|
|
15
|
+
target.push(source[index] + vertexOffset)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -19,6 +19,7 @@ import { resolveBlockPropertiesForMeshing } from '../../mesher-shared/blockPrope
|
|
|
19
19
|
import { isSemiTransparentBlockName } from '../../mesher-shared/models'
|
|
20
20
|
import { faceIsCulled } from '../../mesher-shared/faceOcclusion'
|
|
21
21
|
import { buildShaderCubesFromWords, getShaderCubeResources, tryBuildShaderCubeInstances } from './shaderCubeBridge'
|
|
22
|
+
import { appendGeometryIndices } from './geometryIndices'
|
|
22
23
|
import { getSideShading, vertexLightFromAo } from '../../mesher-shared/vertexShading'
|
|
23
24
|
import tintsJson from 'minecraft-data/minecraft-data/data/pc/1.16.2/tints.json'
|
|
24
25
|
|
|
@@ -986,10 +987,8 @@ export function renderWasmOutputToGeometry(
|
|
|
986
987
|
blendSkyLights = liquidSkyLights.concat(blockBlendSkyLights)
|
|
987
988
|
blendBlockLights = liquidBlockLights.concat(blockBlendBlockLights)
|
|
988
989
|
blendUvs = liquidUvs.concat(blockBlendUvs)
|
|
989
|
-
blendIndices
|
|
990
|
-
|
|
991
|
-
blendIndices.push(idx + liquidVertexCount)
|
|
992
|
-
}
|
|
990
|
+
appendGeometryIndices(blendIndices, liquidIndices, 0)
|
|
991
|
+
appendGeometryIndices(blendIndices, blockBlendIndices, liquidVertexCount)
|
|
993
992
|
}
|
|
994
993
|
|
|
995
994
|
const shaderCubes = buildShaderCubesFromWords(shaderWordBuffer)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { expect, test } from 'vitest'
|
|
3
|
+
import { appendGeometryIndices, createGeometryIndexArray } from '../bridge/geometryIndices'
|
|
4
|
+
|
|
5
|
+
function expectSameValues(actual: ArrayLike<number>, expected: ArrayLike<number>): void {
|
|
6
|
+
expect(actual.length).toBe(expected.length)
|
|
7
|
+
let firstMismatch = -1
|
|
8
|
+
for (let index = 0; index < expected.length; index++) {
|
|
9
|
+
if (actual[index] !== expected[index]) {
|
|
10
|
+
firstMismatch = index
|
|
11
|
+
break
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
expect(firstMismatch, 'First differing index; -1 means all values match').toBe(-1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test('creates lossless index buffers without depending on argument count', () => {
|
|
18
|
+
const cases: Array<{ input: number[]; using32Array: boolean }> = [
|
|
19
|
+
{ input: [], using32Array: false },
|
|
20
|
+
{ input: [0, 65535, 7], using32Array: false },
|
|
21
|
+
{ input: [0, 65536, 7], using32Array: true }
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
for (const { input, using32Array } of cases) {
|
|
25
|
+
const inputBefore = input.slice()
|
|
26
|
+
const result = createGeometryIndexArray(input)
|
|
27
|
+
expect(result).toBeInstanceOf(using32Array ? Uint32Array : Uint16Array)
|
|
28
|
+
expect(Array.from(result)).toEqual(inputBefore)
|
|
29
|
+
expect(input).toEqual(inputBefore)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const source = new Array<number>(1_000_000).fill(65535)
|
|
33
|
+
source[0] = 0
|
|
34
|
+
source[500_000] = 123
|
|
35
|
+
const sourceBeforeSmall = source.slice()
|
|
36
|
+
const smallValues = createGeometryIndexArray(source)
|
|
37
|
+
expect(smallValues).toBeInstanceOf(Uint16Array)
|
|
38
|
+
expectSameValues(smallValues, sourceBeforeSmall)
|
|
39
|
+
expectSameValues(source, sourceBeforeSmall)
|
|
40
|
+
|
|
41
|
+
source[source.length - 1] = 65536
|
|
42
|
+
const sourceBeforeLarge = source.slice()
|
|
43
|
+
const largeValue = createGeometryIndexArray(source)
|
|
44
|
+
expect(largeValue).toBeInstanceOf(Uint32Array)
|
|
45
|
+
expectSameValues(largeValue, sourceBeforeLarge)
|
|
46
|
+
expectSameValues(source, sourceBeforeLarge)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('appends large liquid indices before rebased block indices', () => {
|
|
50
|
+
const liquidIndices = new Array<number>(1_000_000).fill(0)
|
|
51
|
+
liquidIndices[0] = 2
|
|
52
|
+
liquidIndices[liquidIndices.length - 1] = 1
|
|
53
|
+
const blockBlendIndices = [0, 2, 1]
|
|
54
|
+
const liquidVertexCount = 3
|
|
55
|
+
const blendIndices: number[] = []
|
|
56
|
+
const liquidIndicesBefore = liquidIndices.slice()
|
|
57
|
+
const blockBlendIndicesBefore = blockBlendIndices.slice()
|
|
58
|
+
|
|
59
|
+
appendGeometryIndices(blendIndices, liquidIndices, 0)
|
|
60
|
+
appendGeometryIndices(blendIndices, blockBlendIndices, liquidVertexCount)
|
|
61
|
+
|
|
62
|
+
expect(blendIndices.length).toBe(liquidIndicesBefore.length + blockBlendIndicesBefore.length)
|
|
63
|
+
expectSameValues(blendIndices.slice(0, liquidIndicesBefore.length), liquidIndicesBefore)
|
|
64
|
+
expect(blendIndices.slice(liquidIndicesBefore.length)).toEqual([3, 5, 4])
|
|
65
|
+
expect(blockBlendIndices).toEqual(blockBlendIndicesBefore)
|
|
66
|
+
expectSameValues(liquidIndices, liquidIndicesBefore)
|
|
67
|
+
|
|
68
|
+
const snapshot = blendIndices.slice()
|
|
69
|
+
appendGeometryIndices(blendIndices, [], liquidVertexCount)
|
|
70
|
+
expectSameValues(blendIndices, snapshot)
|
|
71
|
+
})
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Vec3 } from 'vec3'
|
|
3
3
|
import { convertChunkToWasm, getBlockMeta, type ChunkConversionResult } from '../bridge/convertChunk'
|
|
4
4
|
import { extractColumnHeightmap, splitColumnWasmOutputToSections } from '../bridge/render-from-wasm'
|
|
5
|
+
import { createGeometryIndexArray } from '../bridge/geometryIndices'
|
|
5
6
|
import { setBlockStatesData as setMesherData } from '../../mesher-shared/models'
|
|
6
7
|
import { defaultMesherConfig, type MesherGeometryOutput, SECTION_HEIGHT } from '../../mesher-shared/shared'
|
|
7
8
|
import { SHADER_CUBES_FORMAT_VERSION } from '../../mesher-shared/shaderCubeFormat'
|
|
@@ -1493,8 +1494,8 @@ function processColumnTick() {
|
|
|
1493
1494
|
const hasLegacyMesh = hasOpaqueMesh || hasBlendMesh
|
|
1494
1495
|
const hasShaderCubes = (exported?.shaderCubes?.count ?? 0) > 0
|
|
1495
1496
|
if (exported && (hasLegacyMesh || hasShaderCubes)) {
|
|
1496
|
-
const
|
|
1497
|
-
const using32Array =
|
|
1497
|
+
const indices = createGeometryIndexArray(exported.geometry.indices)
|
|
1498
|
+
const using32Array = indices instanceof Uint32Array
|
|
1498
1499
|
geometry = {
|
|
1499
1500
|
sectionYNumber: (sy - (config?.worldMinY || 0)) >> 4,
|
|
1500
1501
|
chunkKey: worldColumnKey(sx, sz),
|
|
@@ -1513,7 +1514,7 @@ function processColumnTick() {
|
|
|
1513
1514
|
skyLights: new Float32Array(exported.geometry.skyLights),
|
|
1514
1515
|
blockLights: new Float32Array(exported.geometry.blockLights),
|
|
1515
1516
|
uvs: new Float32Array(exported.geometry.uvs),
|
|
1516
|
-
indices
|
|
1517
|
+
indices,
|
|
1517
1518
|
indicesCount: exported.geometry.indices.length,
|
|
1518
1519
|
using32Array,
|
|
1519
1520
|
tiles: {},
|
|
@@ -1537,7 +1538,6 @@ function processColumnTick() {
|
|
|
1537
1538
|
}
|
|
1538
1539
|
}
|
|
1539
1540
|
if (exported.blendGeometry && hasBlendMesh) {
|
|
1540
|
-
const blendMax = Math.max(...exported.blendGeometry.indices)
|
|
1541
1541
|
geometry.blend = {
|
|
1542
1542
|
positions: new Float32Array(exported.blendGeometry.positions),
|
|
1543
1543
|
normals: new Float32Array(exported.blendGeometry.normals),
|
|
@@ -1545,7 +1545,7 @@ function processColumnTick() {
|
|
|
1545
1545
|
skyLights: new Float32Array(exported.blendGeometry.skyLights),
|
|
1546
1546
|
blockLights: new Float32Array(exported.blendGeometry.blockLights),
|
|
1547
1547
|
uvs: new Float32Array(exported.blendGeometry.uvs),
|
|
1548
|
-
indices:
|
|
1548
|
+
indices: createGeometryIndexArray(exported.blendGeometry.indices)
|
|
1549
1549
|
}
|
|
1550
1550
|
}
|
|
1551
1551
|
transferable = [
|