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
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
//@ts-nocheck
|
|
2
2
|
import * as THREE from 'three'
|
|
3
|
+
import {
|
|
4
|
+
createLegacyMultiDrawScratch,
|
|
5
|
+
detectLegacyMultiDrawCaps,
|
|
6
|
+
drawLegacySpans,
|
|
7
|
+
logLegacyMultiDrawTierOnce,
|
|
8
|
+
type LegacyDrawSpan,
|
|
9
|
+
type LegacyMultiDrawCaps,
|
|
10
|
+
type LegacyMultiDrawScratch,
|
|
11
|
+
} from './legacyMultiDraw'
|
|
3
12
|
import { computeCameraRelativeUniforms, type RenderOrigin } from './shaders/legacyBlockShader'
|
|
4
13
|
|
|
5
14
|
const VERTS_PER_QUAD = 4
|
|
@@ -11,6 +20,9 @@ const FLOATS_PER_LIGHT_VERT = 1
|
|
|
11
20
|
const DEFAULT_INITIAL_CAPACITY_QUADS = 128_000
|
|
12
21
|
const DEFAULT_GROWTH_INCREMENT_QUADS = 128_000
|
|
13
22
|
const MAX_UPLOAD_QUADS_PER_FRAME = 5_000
|
|
23
|
+
const FRAGMENTATION_THRESHOLD = 0.25
|
|
24
|
+
|
|
25
|
+
type PendingMove = { key: string, oldStart: number, newStart: number, count: number }
|
|
14
26
|
|
|
15
27
|
/** CPU bytes per allocated quad slot (all legacy vertex/index attrs). */
|
|
16
28
|
export const LEGACY_BYTES_PER_QUAD =
|
|
@@ -44,6 +56,8 @@ export type LegacySectionGeometryData = LegacySectionGeometry & {
|
|
|
44
56
|
sz: number
|
|
45
57
|
}
|
|
46
58
|
|
|
59
|
+
export type { LegacyDrawSpan } from './legacyMultiDraw'
|
|
60
|
+
|
|
47
61
|
/**
|
|
48
62
|
* Single GPU mesh for legacy quads (opaque+cutout or transparent blend).
|
|
49
63
|
* Camera-relative via per-vertex a_origin (relative to render origin) + u_originDelta uniforms.
|
|
@@ -67,6 +81,12 @@ export class GlobalLegacyBuffer {
|
|
|
67
81
|
private pendingRanges: Array<{ start: number, end: number }> = []
|
|
68
82
|
private readonly _spanScratch: Array<{ start: number, count: number }> = []
|
|
69
83
|
private renderOrigin: RenderOrigin = { x: 0, y: 0, z: 0 }
|
|
84
|
+
private layoutVersion = 0
|
|
85
|
+
private pendingMove: PendingMove | null = null
|
|
86
|
+
private visibleIndexSpans: LegacyDrawSpan[] = []
|
|
87
|
+
private readonly _drawScratch: LegacyMultiDrawScratch = createLegacyMultiDrawScratch()
|
|
88
|
+
private multiDrawCaps: LegacyMultiDrawCaps | null = null
|
|
89
|
+
private debugOverlay = false
|
|
70
90
|
|
|
71
91
|
constructor (
|
|
72
92
|
material: THREE.ShaderMaterial,
|
|
@@ -114,6 +134,38 @@ export class GlobalLegacyBuffer {
|
|
|
114
134
|
this.mesh.position.set(0, 0, 0)
|
|
115
135
|
scene.add(this.mesh)
|
|
116
136
|
this.syncDefaultDrawGroups()
|
|
137
|
+
|
|
138
|
+
this.mesh.onAfterRender = (renderer, _scene, _camera, _geometry, material) => {
|
|
139
|
+
if (this.visibleIndexSpans.length === 0) return
|
|
140
|
+
const gl = renderer.getContext() as WebGL2RenderingContext
|
|
141
|
+
if (!this.multiDrawCaps) {
|
|
142
|
+
this.multiDrawCaps = detectLegacyMultiDrawCaps(gl)
|
|
143
|
+
logLegacyMultiDrawTierOnce(this.multiDrawCaps.tier, this.debugOverlay)
|
|
144
|
+
}
|
|
145
|
+
drawLegacySpans(gl, this.multiDrawCaps, this.visibleIndexSpans, this._drawScratch)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setDebugOverlay (enabled: boolean): void {
|
|
150
|
+
this.debugOverlay = enabled
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Suppress three's full-buffer indexed draw; onAfterRender issues visible spans only.
|
|
155
|
+
* setDrawRange(0,0) skips bindingStates.setup — use a minimal non-zero range so
|
|
156
|
+
* program/VAO/ELEMENT_ARRAY_BUFFER stay bound while three draws ~nothing.
|
|
157
|
+
* Draws one triangle from index 0 (usually harmless; if quad 0 is culled, one stray tri).
|
|
158
|
+
*/
|
|
159
|
+
suppressThreeDraw (): void {
|
|
160
|
+
this.mesh.geometry.setDrawRange(0, 3)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
setVisibleIndexSpans (spans: LegacyDrawSpan[]): void {
|
|
164
|
+
this.visibleIndexSpans = spans
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
getVisibleIndexSpans (): readonly LegacyDrawSpan[] {
|
|
168
|
+
return this.visibleIndexSpans
|
|
117
169
|
}
|
|
118
170
|
|
|
119
171
|
private syncDefaultDrawGroups (): void {
|
|
@@ -171,14 +223,14 @@ export class GlobalLegacyBuffer {
|
|
|
171
223
|
this.uvs.set(geo.uvs, dstUvBase)
|
|
172
224
|
|
|
173
225
|
const originOff = dstFloatBase
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
const
|
|
226
|
+
const ox = sx - this.renderOrigin.x
|
|
227
|
+
const oy = sy - this.renderOrigin.y
|
|
228
|
+
const oz = sz - this.renderOrigin.z
|
|
177
229
|
for (let v = 0; v < vertCount; v++) {
|
|
178
230
|
const o = originOff + v * FLOATS_PER_VERT
|
|
179
|
-
this.aOrigin[o] =
|
|
180
|
-
this.aOrigin[o + 1] =
|
|
181
|
-
this.aOrigin[o + 2] =
|
|
231
|
+
this.aOrigin[o] = ox
|
|
232
|
+
this.aOrigin[o + 1] = oy
|
|
233
|
+
this.aOrigin[o + 2] = oz
|
|
182
234
|
}
|
|
183
235
|
|
|
184
236
|
const dstIndexBase = slot.start * INDICES_PER_QUAD
|
|
@@ -190,15 +242,60 @@ export class GlobalLegacyBuffer {
|
|
|
190
242
|
this.sectionSlots.set(sectionKey, slot)
|
|
191
243
|
this.markDirty(slot.start, slot.start + quadCount - 1)
|
|
192
244
|
this.syncDefaultDrawGroups()
|
|
245
|
+
this.layoutVersion++
|
|
193
246
|
return true
|
|
194
247
|
}
|
|
195
248
|
|
|
249
|
+
getLayoutVersion (): number {
|
|
250
|
+
return this.layoutVersion
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
getSectionDrawStart (sectionKey: string): number | undefined {
|
|
254
|
+
const slot = this.sectionSlots.get(sectionKey)
|
|
255
|
+
if (!slot) return undefined
|
|
256
|
+
if (this.pendingMove?.key === sectionKey) return this.pendingMove.oldStart
|
|
257
|
+
return slot.start
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
getPendingMove (): PendingMove | null {
|
|
261
|
+
return this.pendingMove
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** One interior-hole move per frame when fragmentation exceeds threshold; deferred shrink. */
|
|
265
|
+
compactStep (): void {
|
|
266
|
+
if (this.pendingMove) {
|
|
267
|
+
const { newStart, count } = this.pendingMove
|
|
268
|
+
if (this.rangeFullyUploaded(newStart, newStart + count - 1)) {
|
|
269
|
+
this.finalizePendingMove()
|
|
270
|
+
}
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (this.highWatermark === 0) return
|
|
275
|
+
const interiorFree = this.interiorFreeQuads()
|
|
276
|
+
if (interiorFree / this.highWatermark <= FRAGMENTATION_THRESHOLD) return
|
|
277
|
+
|
|
278
|
+
const section = this.findMovableSection(MAX_UPLOAD_QUADS_PER_FRAME)
|
|
279
|
+
if (!section) return
|
|
280
|
+
|
|
281
|
+
const hole = this.findLowestInteriorHole(section.start, section.count)
|
|
282
|
+
if (!hole) return
|
|
283
|
+
|
|
284
|
+
const reserved = this.reserveFreeSlotAt(hole.index, section.count)
|
|
285
|
+
const oldStart = section.start
|
|
286
|
+
const newStart = reserved.start
|
|
287
|
+
|
|
288
|
+
this.copySectionRange(oldStart, newStart, section.count)
|
|
289
|
+
this.sectionSlots.set(section.key, { start: newStart, count: section.count })
|
|
290
|
+
this.markDirty(newStart, newStart + section.count - 1)
|
|
291
|
+
this.pendingMove = { key: section.key, oldStart, newStart, count: section.count }
|
|
292
|
+
this.layoutVersion++
|
|
293
|
+
}
|
|
294
|
+
|
|
196
295
|
updateDrawSpans (visible: VisibleSectionSpan[], mode: 'opaque' | 'sortedBlend'): void {
|
|
197
|
-
|
|
198
|
-
geometry.clearGroups()
|
|
296
|
+
this.visibleIndexSpans = []
|
|
199
297
|
|
|
200
298
|
if (this.highWatermark === 0) {
|
|
201
|
-
geometry.setDrawRange(0, 0)
|
|
202
299
|
return
|
|
203
300
|
}
|
|
204
301
|
|
|
@@ -207,21 +304,30 @@ export class GlobalLegacyBuffer {
|
|
|
207
304
|
let visibleQuadCount = 0
|
|
208
305
|
|
|
209
306
|
for (const entry of visible) {
|
|
307
|
+
const drawStart = this.getSectionDrawStart(entry.key)
|
|
210
308
|
const slot = this.sectionSlots.get(entry.key)
|
|
211
|
-
if (!slot) continue
|
|
212
|
-
spans.push({ start:
|
|
309
|
+
if (drawStart === undefined || !slot) continue
|
|
310
|
+
spans.push({ start: drawStart, count: slot.count })
|
|
213
311
|
visibleQuadCount += slot.count
|
|
214
312
|
}
|
|
215
313
|
|
|
216
314
|
if (spans.length === 0) {
|
|
217
|
-
geometry.setDrawRange(0, 0)
|
|
218
315
|
return
|
|
219
316
|
}
|
|
220
317
|
|
|
318
|
+
const pushIndexSpan = (quadStart: number, quadCount: number): void => {
|
|
319
|
+
this.visibleIndexSpans.push({
|
|
320
|
+
indexStart: quadStart * INDICES_PER_QUAD,
|
|
321
|
+
indexCount: quadCount * INDICES_PER_QUAD,
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
|
|
221
325
|
if (mode === 'opaque') {
|
|
222
326
|
if (visibleQuadCount >= this.highWatermark * FULL_DRAW_VISIBLE_FRACTION) {
|
|
223
|
-
|
|
224
|
-
|
|
327
|
+
this.visibleIndexSpans.push({
|
|
328
|
+
indexStart: 0,
|
|
329
|
+
indexCount: this.highWatermark * INDICES_PER_QUAD,
|
|
330
|
+
})
|
|
225
331
|
return
|
|
226
332
|
}
|
|
227
333
|
|
|
@@ -230,18 +336,17 @@ export class GlobalLegacyBuffer {
|
|
|
230
336
|
this.capOpaqueSpans(spans)
|
|
231
337
|
|
|
232
338
|
for (const span of spans) {
|
|
233
|
-
|
|
339
|
+
pushIndexSpan(span.start, span.count)
|
|
234
340
|
}
|
|
235
341
|
} else {
|
|
236
342
|
visible.sort((a, b) => b.distSq - a.distSq)
|
|
237
343
|
for (const entry of visible) {
|
|
344
|
+
const drawStart = this.getSectionDrawStart(entry.key)
|
|
238
345
|
const slot = this.sectionSlots.get(entry.key)
|
|
239
|
-
if (!slot) continue
|
|
240
|
-
|
|
346
|
+
if (drawStart === undefined || !slot) continue
|
|
347
|
+
pushIndexSpan(drawStart, slot.count)
|
|
241
348
|
}
|
|
242
349
|
}
|
|
243
|
-
|
|
244
|
-
geometry.setDrawRange(0, this.highWatermark * INDICES_PER_QUAD)
|
|
245
350
|
}
|
|
246
351
|
|
|
247
352
|
private mergeOpaqueSpans (spans: Array<{ start: number, count: number }>): void {
|
|
@@ -328,6 +433,18 @@ export class GlobalLegacyBuffer {
|
|
|
328
433
|
const slot = this.sectionSlots.get(sectionKey)
|
|
329
434
|
if (!slot) return
|
|
330
435
|
|
|
436
|
+
if (this.pendingMove?.key === sectionKey) {
|
|
437
|
+
const { oldStart, count } = this.pendingMove
|
|
438
|
+
const oldIndexBase = oldStart * INDICES_PER_QUAD
|
|
439
|
+
const oldIndexLen = count * INDICES_PER_QUAD
|
|
440
|
+
for (let i = 0; i < oldIndexLen; i++) {
|
|
441
|
+
this.indices[oldIndexBase + i] = 0
|
|
442
|
+
}
|
|
443
|
+
this.markDirty(oldStart, oldStart + count - 1)
|
|
444
|
+
this.insertFreeSlot({ start: oldStart, count })
|
|
445
|
+
this.pendingMove = null
|
|
446
|
+
}
|
|
447
|
+
|
|
331
448
|
const dstIndexBase = slot.start * INDICES_PER_QUAD
|
|
332
449
|
const indexLen = slot.count * INDICES_PER_QUAD
|
|
333
450
|
for (let i = 0; i < indexLen; i++) {
|
|
@@ -339,6 +456,7 @@ export class GlobalLegacyBuffer {
|
|
|
339
456
|
this.insertFreeSlot(slot)
|
|
340
457
|
this.shrinkHighWatermark()
|
|
341
458
|
this.syncDefaultDrawGroups()
|
|
459
|
+
this.layoutVersion++
|
|
342
460
|
}
|
|
343
461
|
|
|
344
462
|
hasPendingUploads (): boolean {
|
|
@@ -505,6 +623,8 @@ export class GlobalLegacyBuffer {
|
|
|
505
623
|
this.freeList.length = 0
|
|
506
624
|
this.highWatermark = 0
|
|
507
625
|
this.pendingRanges.length = 0
|
|
626
|
+
this.pendingMove = null
|
|
627
|
+
this.visibleIndexSpans = []
|
|
508
628
|
this.syncDefaultDrawGroups()
|
|
509
629
|
}
|
|
510
630
|
|
|
@@ -585,7 +705,125 @@ export class GlobalLegacyBuffer {
|
|
|
585
705
|
}
|
|
586
706
|
}
|
|
587
707
|
|
|
708
|
+
private interiorFreeQuads (): number {
|
|
709
|
+
let total = 0
|
|
710
|
+
for (const slot of this.freeList) {
|
|
711
|
+
if (slot.start < this.highWatermark) total += slot.count
|
|
712
|
+
}
|
|
713
|
+
return total
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
private findMovableSection (maxCount: number): { key: string, start: number, count: number } | undefined {
|
|
717
|
+
const sections: Array<{ key: string, start: number, count: number }> = []
|
|
718
|
+
for (const [key, slot] of this.sectionSlots) {
|
|
719
|
+
sections.push({ key, start: slot.start, count: slot.count })
|
|
720
|
+
}
|
|
721
|
+
if (sections.length === 0) return undefined
|
|
722
|
+
|
|
723
|
+
sections.sort((a, b) => {
|
|
724
|
+
if (b.start !== a.start) return b.start - a.start
|
|
725
|
+
return (b.start + b.count) - (a.start + a.count)
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
const tailmost = sections[0]!
|
|
729
|
+
if (tailmost.count <= maxCount && this.findLowestInteriorHole(tailmost.start, tailmost.count)) {
|
|
730
|
+
return tailmost
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const candidates = sections
|
|
734
|
+
.filter(s => s.count <= maxCount)
|
|
735
|
+
.sort((a, b) => b.count - a.count)
|
|
736
|
+
|
|
737
|
+
for (const s of candidates) {
|
|
738
|
+
if (this.findLowestInteriorHole(s.start, s.count)) return s
|
|
739
|
+
}
|
|
740
|
+
return undefined
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
private findLowestInteriorHole (
|
|
744
|
+
sectionStart: number,
|
|
745
|
+
count: number,
|
|
746
|
+
): { start: number, count: number, index: number } | undefined {
|
|
747
|
+
for (let i = 0; i < this.freeList.length; i++) {
|
|
748
|
+
const slot = this.freeList[i]!
|
|
749
|
+
if (slot.start < sectionStart && slot.count >= count) {
|
|
750
|
+
return { start: slot.start, count: slot.count, index: i }
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return undefined
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
private reserveFreeSlotAt (index: number, count: number): { start: number, count: number } {
|
|
757
|
+
const slot = this.freeList[index]!
|
|
758
|
+
this.freeList.splice(index, 1)
|
|
759
|
+
if (slot.count === count) return { start: slot.start, count }
|
|
760
|
+
const used = { start: slot.start, count }
|
|
761
|
+
this.insertFreeSlot({ start: slot.start + count, count: slot.count - count })
|
|
762
|
+
return used
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
private copySectionRange (oldStart: number, newStart: number, quadCount: number): void {
|
|
766
|
+
const oldVertBase = oldStart * VERTS_PER_QUAD
|
|
767
|
+
const newVertBase = newStart * VERTS_PER_QUAD
|
|
768
|
+
const vertCount = quadCount * VERTS_PER_QUAD
|
|
769
|
+
const vertDelta = newVertBase - oldVertBase
|
|
770
|
+
|
|
771
|
+
const oldFloatBase = oldVertBase * FLOATS_PER_VERT
|
|
772
|
+
const newFloatBase = newVertBase * FLOATS_PER_VERT
|
|
773
|
+
const floatLen = vertCount * FLOATS_PER_VERT
|
|
774
|
+
this.positions.copyWithin(newFloatBase, oldFloatBase, oldFloatBase + floatLen)
|
|
775
|
+
this.colors.copyWithin(newFloatBase, oldFloatBase, oldFloatBase + floatLen)
|
|
776
|
+
this.aOrigin.copyWithin(newFloatBase, oldFloatBase, oldFloatBase + floatLen)
|
|
777
|
+
|
|
778
|
+
const oldLightBase = oldVertBase * FLOATS_PER_LIGHT_VERT
|
|
779
|
+
const newLightBase = newVertBase * FLOATS_PER_LIGHT_VERT
|
|
780
|
+
const lightLen = vertCount * FLOATS_PER_LIGHT_VERT
|
|
781
|
+
this.skyLights.copyWithin(newLightBase, oldLightBase, oldLightBase + lightLen)
|
|
782
|
+
this.blockLights.copyWithin(newLightBase, oldLightBase, oldLightBase + lightLen)
|
|
783
|
+
|
|
784
|
+
const oldUvBase = oldVertBase * FLOATS_PER_UV_VERT
|
|
785
|
+
const newUvBase = newVertBase * FLOATS_PER_UV_VERT
|
|
786
|
+
const uvLen = vertCount * FLOATS_PER_UV_VERT
|
|
787
|
+
this.uvs.copyWithin(newUvBase, oldUvBase, oldUvBase + uvLen)
|
|
788
|
+
|
|
789
|
+
const oldIndexBase = oldStart * INDICES_PER_QUAD
|
|
790
|
+
const newIndexBase = newStart * INDICES_PER_QUAD
|
|
791
|
+
const indexLen = quadCount * INDICES_PER_QUAD
|
|
792
|
+
for (let i = 0; i < indexLen; i++) {
|
|
793
|
+
this.indices[newIndexBase + i] = this.indices[oldIndexBase + i]! + vertDelta
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
private rangeFullyUploaded (start: number, end: number): boolean {
|
|
798
|
+
for (const r of this.pendingRanges) {
|
|
799
|
+
if (r.start <= end && r.end >= start) return false
|
|
800
|
+
}
|
|
801
|
+
return true
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
private finalizePendingMove (): void {
|
|
805
|
+
const move = this.pendingMove
|
|
806
|
+
if (!move) return
|
|
807
|
+
|
|
808
|
+
const { oldStart, count } = move
|
|
809
|
+
const oldIndexBase = oldStart * INDICES_PER_QUAD
|
|
810
|
+
const oldIndexLen = count * INDICES_PER_QUAD
|
|
811
|
+
for (let i = 0; i < oldIndexLen; i++) {
|
|
812
|
+
this.indices[oldIndexBase + i] = 0
|
|
813
|
+
}
|
|
814
|
+
this.insertFreeSlot({ start: oldStart, count })
|
|
815
|
+
this.shrinkHighWatermark()
|
|
816
|
+
if (oldStart < this.highWatermark) {
|
|
817
|
+
this.markDirty(oldStart, oldStart + count - 1)
|
|
818
|
+
}
|
|
819
|
+
this.syncDefaultDrawGroups()
|
|
820
|
+
this.pendingMove = null
|
|
821
|
+
this.layoutVersion++
|
|
822
|
+
}
|
|
823
|
+
|
|
588
824
|
private growCapacity (minQuads: number): void {
|
|
825
|
+
if (this.pendingMove) this.finalizePendingMove()
|
|
826
|
+
|
|
589
827
|
let newCap = this.capacityQuads
|
|
590
828
|
while (newCap < minQuads) newCap += this.growthIncrementQuads
|
|
591
829
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
import { MAX_OPAQUE_SPANS } from './globalLegacyBuffer'
|
|
3
|
+
|
|
4
|
+
export type LegacyMultiDrawTier = 'A' | 'B' | 'C'
|
|
5
|
+
|
|
6
|
+
export type LegacyDrawSpan = { indexStart: number, indexCount: number }
|
|
7
|
+
|
|
8
|
+
export type LegacyMultiDrawCaps = {
|
|
9
|
+
tier: LegacyMultiDrawTier
|
|
10
|
+
ext: MultiDrawElementsExt | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type MultiDrawElementsExt = {
|
|
14
|
+
multiDrawElementsWEBGL: (
|
|
15
|
+
mode: number,
|
|
16
|
+
counts: Int32Array,
|
|
17
|
+
countsOffset: number,
|
|
18
|
+
type: number,
|
|
19
|
+
offsets: Int32Array,
|
|
20
|
+
offsetsOffset: number,
|
|
21
|
+
drawCount: number,
|
|
22
|
+
) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type LegacyMultiDrawScratch = {
|
|
26
|
+
counts: Int32Array
|
|
27
|
+
offsets: Int32Array
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createLegacyMultiDrawScratch (): LegacyMultiDrawScratch {
|
|
31
|
+
return {
|
|
32
|
+
counts: new Int32Array(MAX_OPAQUE_SPANS),
|
|
33
|
+
offsets: new Int32Array(MAX_OPAQUE_SPANS),
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Grow scratch arrays when blend (uncapped) span count exceeds opaque cap. */
|
|
38
|
+
function ensureScratchCapacity (scratch: LegacyMultiDrawScratch, minLength: number): void {
|
|
39
|
+
if (scratch.counts.length >= minLength) return
|
|
40
|
+
let newLen = scratch.counts.length
|
|
41
|
+
while (newLen < minLength) newLen *= 2
|
|
42
|
+
scratch.counts = new Int32Array(newLen)
|
|
43
|
+
scratch.offsets = new Int32Array(newLen)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function detectLegacyMultiDrawCaps (gl: WebGL2RenderingContext): LegacyMultiDrawCaps {
|
|
47
|
+
const ext = gl.getExtension('WEBGL_multi_draw')
|
|
48
|
+
if (ext) {
|
|
49
|
+
return { tier: 'A', ext: ext as MultiDrawElementsExt }
|
|
50
|
+
}
|
|
51
|
+
return { tier: 'B', ext: null }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let tierLogged = false
|
|
55
|
+
|
|
56
|
+
export function logLegacyMultiDrawTierOnce (tier: LegacyMultiDrawTier, debug: boolean): void {
|
|
57
|
+
if (tierLogged || !debug) return
|
|
58
|
+
tierLogged = true
|
|
59
|
+
console.info('[globalLegacyBuffer] legacy multi_draw tier', tier)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Issue indexed legacy draws for visible spans. Tier B/C loop drawElements.
|
|
64
|
+
*/
|
|
65
|
+
export function drawLegacySpans (
|
|
66
|
+
gl: WebGL2RenderingContext,
|
|
67
|
+
caps: LegacyMultiDrawCaps,
|
|
68
|
+
spans: readonly LegacyDrawSpan[],
|
|
69
|
+
scratch: LegacyMultiDrawScratch,
|
|
70
|
+
): void {
|
|
71
|
+
const drawCount = spans.length
|
|
72
|
+
if (drawCount === 0) return
|
|
73
|
+
|
|
74
|
+
ensureScratchCapacity(scratch, drawCount)
|
|
75
|
+
|
|
76
|
+
const mode = gl.TRIANGLES
|
|
77
|
+
const type = gl.UNSIGNED_INT
|
|
78
|
+
|
|
79
|
+
for (let i = 0; i < drawCount; i++) {
|
|
80
|
+
const span = spans[i]!
|
|
81
|
+
scratch.counts[i] = span.indexCount
|
|
82
|
+
scratch.offsets[i] = span.indexStart * 4
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (caps.tier === 'A' && caps.ext) {
|
|
86
|
+
caps.ext.multiDrawElementsWEBGL(
|
|
87
|
+
mode,
|
|
88
|
+
scratch.counts, 0,
|
|
89
|
+
type,
|
|
90
|
+
scratch.offsets, 0,
|
|
91
|
+
drawCount,
|
|
92
|
+
)
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < drawCount; i++) {
|
|
97
|
+
gl.drawElements(mode, scratch.counts[i]!, type, scratch.offsets[i]!)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -199,6 +199,8 @@ uniform float u_skyLevel;
|
|
|
199
199
|
uniform float u_lightCurve;
|
|
200
200
|
uniform float u_minBrightness;
|
|
201
201
|
uniform float u_lightGamma;
|
|
202
|
+
uniform float u_shadingTheme; // 0 = vanilla, 1 = high-contrast
|
|
203
|
+
uniform float u_cardinalLight; // 0 = default, 1 = nether
|
|
202
204
|
|
|
203
205
|
in float v_blockLight;
|
|
204
206
|
in float v_skyLight;
|
|
@@ -248,6 +250,24 @@ void applyFog() {
|
|
|
248
250
|
|
|
249
251
|
${APPLY_LIGHTMAP_GLSL}
|
|
250
252
|
|
|
253
|
+
const vec3 FACE_NORMAL[6] = vec3[6](
|
|
254
|
+
vec3(0.0, 1.0, 0.0), // UP
|
|
255
|
+
vec3(0.0,-1.0, 0.0), // DOWN
|
|
256
|
+
vec3(1.0, 0.0, 0.0), // EAST
|
|
257
|
+
vec3(-1.0,0.0, 0.0), // WEST
|
|
258
|
+
vec3(0.0, 0.0, 1.0), // SOUTH
|
|
259
|
+
vec3(0.0, 0.0,-1.0) // NORTH
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
float sideShadingFromFaceId(int faceId, float theme, float cardinal) {
|
|
263
|
+
vec3 n = FACE_NORMAL[faceId];
|
|
264
|
+
float hc = 0.8 + 0.5 * max(0.0, 0.66*n.x + 0.66*n.y + 0.33*n.z);
|
|
265
|
+
float nether = 0.5 + abs(0.1*n.x + 0.4*n.y + 0.3*n.z);
|
|
266
|
+
float vanilla = 0.75 + 0.25*n.y + 0.05*(abs(n.z) - 3.0*abs(n.x));
|
|
267
|
+
float nonHc = mix(vanilla, nether, cardinal);
|
|
268
|
+
return mix(nonHc, hc, theme);
|
|
269
|
+
}
|
|
270
|
+
|
|
251
271
|
void main() {
|
|
252
272
|
// Atlas sample (pixelated, no filtering)
|
|
253
273
|
ivec2 atlasSize = textureSize(u_atlas, 0);
|
|
@@ -294,7 +314,9 @@ void main() {
|
|
|
294
314
|
|
|
295
315
|
float L = max(v_blockLight, min(v_skyLight, u_skyLevel));
|
|
296
316
|
float Lm = applyLightmap(L);
|
|
297
|
-
float
|
|
317
|
+
float aoFactor = mix(0.8 * v_ao + 0.2, v_ao, u_shadingTheme);
|
|
318
|
+
float side = sideShadingFromFaceId(v_faceId, u_shadingTheme, u_cardinalLight);
|
|
319
|
+
float brightness = Lm * aoFactor * side;
|
|
298
320
|
|
|
299
321
|
// Opaque full cubes: always alpha 1 (legacy uses cutout material; avoids seeing blocks behind)
|
|
300
322
|
FragColor = vec4(baseColor.rgb * tint * brightness, 1.0);
|
|
@@ -317,6 +339,8 @@ export function createCubeBlockMaterial(): THREE.ShaderMaterial {
|
|
|
317
339
|
u_tintPalette: { value: null },
|
|
318
340
|
u_debugMode: { value: 0 },
|
|
319
341
|
u_skyLevel: { value: 1.0 },
|
|
342
|
+
u_shadingTheme: { value: 1.0 },
|
|
343
|
+
u_cardinalLight: { value: 0.0 },
|
|
320
344
|
u_lightCurve: { value: DEFAULT_LIGHTMAP_PARAMS.curve },
|
|
321
345
|
u_minBrightness: { value: DEFAULT_LIGHTMAP_PARAMS.minBrightness },
|
|
322
346
|
u_lightGamma: { value: DEFAULT_LIGHTMAP_PARAMS.gamma },
|
|
@@ -344,6 +368,17 @@ export function setCubeSkyLevel (material: THREE.ShaderMaterial, value: number):
|
|
|
344
368
|
if (u) u.value = value
|
|
345
369
|
}
|
|
346
370
|
|
|
371
|
+
export function setCubeShadingTheme (
|
|
372
|
+
material: THREE.ShaderMaterial,
|
|
373
|
+
theme: 'vanilla' | 'high-contrast',
|
|
374
|
+
cardinalLight: string,
|
|
375
|
+
): void {
|
|
376
|
+
const t = material.uniforms.u_shadingTheme
|
|
377
|
+
if (t) t.value = theme === 'high-contrast' ? 1.0 : 0.0
|
|
378
|
+
const c = material.uniforms.u_cardinalLight
|
|
379
|
+
if (c) c.value = cardinalLight === 'nether' ? 1.0 : 0.0
|
|
380
|
+
}
|
|
381
|
+
|
|
347
382
|
export function setCubeLightmapParams (
|
|
348
383
|
material: THREE.ShaderMaterial,
|
|
349
384
|
params: BlockLightmapParams,
|