minecraft-renderer 0.1.100 → 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/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
|
+
})
|