feat: optimize slabs render performance by rendering less not visible tiles
Improve performance in Greenfield by 6%
This commit is contained in:
parent
7d699f24bb
commit
2fbfc18d2e
8 changed files with 117 additions and 53 deletions
|
|
@ -281,9 +281,14 @@ export class BasePlaygroundScene {
|
|||
|
||||
addKeyboardShortcuts () {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.code === 'KeyR' && !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
|
||||
this.controls?.reset()
|
||||
this.resetCamera()
|
||||
if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
|
||||
if (e.code === 'KeyR') {
|
||||
this.controls?.reset()
|
||||
this.resetCamera()
|
||||
}
|
||||
if (e.code === 'KeyE') {
|
||||
worldView?.setBlockStateId(this.targetPos, this.world.getBlockStateId(this.targetPos))
|
||||
}
|
||||
}
|
||||
})
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import * as scenes from './scenes'
|
|||
|
||||
const qsScene = new URLSearchParams(window.location.search).get('scene')
|
||||
const Scene: typeof BasePlaygroundScene = qsScene ? scenes[qsScene] : scenes.main
|
||||
playgroundGlobalUiState.scenes = ['main', 'railsCobweb', 'floorRandom', 'lightingStarfield', 'transparencyIssue', 'entities', 'frequentUpdates']
|
||||
playgroundGlobalUiState.scenes = ['main', 'railsCobweb', 'floorRandom', 'lightingStarfield', 'transparencyIssue', 'entities', 'frequentUpdates', 'slabsOptimization']
|
||||
playgroundGlobalUiState.selected = qsScene ?? 'main'
|
||||
|
||||
const scene = new Scene()
|
||||
|
|
|
|||
|
|
@ -7,3 +7,4 @@ export { default as transparencyIssue } from './transparencyIssue'
|
|||
export { default as rotationIssue } from './rotationIssue'
|
||||
export { default as entities } from './entities'
|
||||
export { default as frequentUpdates } from './frequentUpdates'
|
||||
export { default as slabsOptimization } from './slabsOptimization'
|
||||
|
|
|
|||
15
prismarine-viewer/examples/scenes/slabsOptimization.ts
Normal file
15
prismarine-viewer/examples/scenes/slabsOptimization.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { BasePlaygroundScene } from '../baseScene'
|
||||
|
||||
export default class extends BasePlaygroundScene {
|
||||
expectedNumberOfFaces = 30
|
||||
|
||||
setupWorld () {
|
||||
this.addWorldBlock(0, 1, 0, 'stone_slab')
|
||||
this.addWorldBlock(0, 0, 0, 'stone')
|
||||
this.addWorldBlock(0, -1, 0, 'stone_slab', { type: 'top', waterlogged: false })
|
||||
this.addWorldBlock(0, -1, -1, 'stone_slab', { type: 'top', waterlogged: false })
|
||||
this.addWorldBlock(0, -1, 1, 'stone_slab', { type: 'top', waterlogged: false })
|
||||
this.addWorldBlock(-1, -1, 0, 'stone_slab', { type: 'top', waterlogged: false })
|
||||
this.addWorldBlock(1, -1, 0, 'stone_slab', { type: 'top', waterlogged: false })
|
||||
}
|
||||
}
|
||||
|
|
@ -196,13 +196,53 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ
|
|||
}
|
||||
}
|
||||
|
||||
let needRecompute = false
|
||||
const identicalCull = (currentElement: BlockElement, neighbor: Block, direction: Vec3) => {
|
||||
const dirStr = `${direction.x},${direction.y},${direction.z}`
|
||||
const lookForOppositeSide = {
|
||||
'0,1,0': 'down',
|
||||
'0,-1,0': 'up',
|
||||
'1,0,0': 'east',
|
||||
'-1,0,0': 'west',
|
||||
'0,0,1': 'south',
|
||||
'0,0,-1': 'north',
|
||||
}[dirStr]!
|
||||
const elemCompareForm = {
|
||||
'0,1,0': (e: BlockElement) => `${e.from[0]},${e.from[2]}:${e.to[0]},${e.to[2]}`,
|
||||
'0,-1,0': (e: BlockElement) => `${e.to[0]},${e.to[2]}:${e.from[0]},${e.from[2]}`,
|
||||
'1,0,0': (e: BlockElement) => `${e.from[2]},${e.from[1]}:${e.to[2]},${e.to[1]}`,
|
||||
'-1,0,0': (e: BlockElement) => `${e.to[2]},${e.to[1]}:${e.from[2]},${e.from[1]}`,
|
||||
'0,0,1': (e: BlockElement) => `${e.from[1]},${e.from[2]}:${e.to[1]},${e.to[2]}`,
|
||||
'0,0,-1': (e: BlockElement) => `${e.to[1]},${e.to[2]}:${e.from[1]},${e.from[2]}`,
|
||||
}[dirStr]!
|
||||
const elementEdgeValidator = {
|
||||
'0,1,0': (e: BlockElement) => currentElement.from[1] === 0 && e.to[2] === 16,
|
||||
'0,-1,0': (e: BlockElement) => currentElement.from[1] === 0 && e.to[2] === 16,
|
||||
'1,0,0': (e: BlockElement) => currentElement.from[0] === 0 && e.to[1] === 16,
|
||||
'-1,0,0': (e: BlockElement) => currentElement.from[0] === 0 && e.to[1] === 16,
|
||||
'0,0,1': (e: BlockElement) => currentElement.from[2] === 0 && e.to[0] === 16,
|
||||
'0,0,-1': (e: BlockElement) => currentElement.from[2] === 0 && e.to[0] === 16,
|
||||
}[dirStr]!
|
||||
const useVar = 0
|
||||
const models = neighbor.models?.map(m => m[useVar] ?? m[0]) ?? []
|
||||
// TODO we should support it! rewrite with optimizing general pipeline
|
||||
if (models.some(m => m.x || m.y || m.z)) return
|
||||
for (const model of models) {
|
||||
for (const element of model.elements ?? []) {
|
||||
// todo check alfa on texture
|
||||
if (element.faces[lookForOppositeSide]?.cullface && elemCompareForm(currentElement) === elemCompareForm(element) && elementEdgeValidator(element)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let needSectionRecomputeOnChange = false
|
||||
|
||||
function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: boolean, attr: MesherGeometryOutput, globalMatrix: any, globalShift: any, block: Block, biome: string) {
|
||||
const position = cursor
|
||||
// const key = `${position.x},${position.y},${position.z}`
|
||||
// if (!globalThis.allowedBlocks.includes(key)) return
|
||||
const cullIfIdentical = block.name.includes('glass')
|
||||
const cullIfIdentical = block.name.includes('glass') || block.name.includes('ice')
|
||||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const face in element.faces) {
|
||||
|
|
@ -211,12 +251,12 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO:
|
|||
const dir = matmul3(globalMatrix, elemFaces[face].dir)
|
||||
|
||||
if (eFace.cullface) {
|
||||
const neighbor = world.getBlock(cursor.plus(new Vec3(...dir)))
|
||||
const neighbor = world.getBlock(cursor.plus(new Vec3(...dir)), blockProvider, {})
|
||||
if (neighbor) {
|
||||
if (cullIfIdentical && neighbor.type === block.type) continue
|
||||
if (!neighbor.transparent && isCube(neighbor)) continue
|
||||
if (cullIfIdentical && neighbor.stateId === block.stateId) continue
|
||||
if (!neighbor.transparent && (isCube(neighbor) || identicalCull(element, neighbor, new Vec3(...dir)))) continue
|
||||
} else {
|
||||
needRecompute = true
|
||||
needSectionRecomputeOnChange = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
@ -391,7 +431,6 @@ const invisibleBlocks = new Set(['air', 'cave_air', 'void_air', 'barrier'])
|
|||
const isBlockWaterlogged = (block: Block) => block.getProperties().waterlogged === true || block.getProperties().waterlogged === 'true'
|
||||
|
||||
let unknownBlockModel: BlockModelPartsResolved
|
||||
let erroredBlockModel: BlockModelPartsResolved
|
||||
export function getSectionGeometry (sx, sy, sz, world: World) {
|
||||
let delayedRender = [] as Array<() => void>
|
||||
|
||||
|
|
@ -421,7 +460,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
|||
for (cursor.y = sy; cursor.y < sy + 16; cursor.y++) {
|
||||
for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) {
|
||||
for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
|
||||
const block = world.getBlock(cursor)!
|
||||
let block = world.getBlock(cursor, blockProvider, attr)!
|
||||
if (!invisibleBlocks.has(block.name)) {
|
||||
const highest = attr.highestBlocks[`${cursor.x},${cursor.z}`]
|
||||
if (!highest || highest.y < cursor.y) {
|
||||
|
|
@ -459,6 +498,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
|||
if (block.models && JSON.stringify(block._originalProperties) !== JSON.stringify(block._properties)) {
|
||||
// recompute models
|
||||
block.models = undefined
|
||||
block = world.getBlock(cursor, blockProvider, attr)!
|
||||
}
|
||||
} else {
|
||||
block._properties = block._originalProperties ?? block._properties
|
||||
|
|
@ -481,37 +521,6 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
|||
if (block.name !== 'water' && block.name !== 'lava' && !invisibleBlocks.has(block.name)) {
|
||||
// cache
|
||||
let { models } = block
|
||||
if (block.models === undefined) {
|
||||
const props = block.getProperties()
|
||||
try {
|
||||
// fixme
|
||||
if (world.preflat) {
|
||||
if (block.name === 'cobblestone_wall') {
|
||||
props.up = 'true'
|
||||
for (const key of ['north', 'south', 'east', 'west']) {
|
||||
const val = props[key]
|
||||
if (val === 'false' || val === 'true') {
|
||||
props[key] = val === 'true' ? 'low' : 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
models = blockProvider.getAllResolvedModels0_1({
|
||||
name: block.name,
|
||||
properties: props,
|
||||
}, world.preflat)! // fixme! this is a hack (also need a setting for all versions)
|
||||
if (!models.length) {
|
||||
console.debug('[mesher] block to render not found', block.name, props)
|
||||
models = null
|
||||
}
|
||||
} catch (err) {
|
||||
models ??= erroredBlockModel
|
||||
console.error(`Critical assets error. Unable to get block model for ${block.name}[${JSON.stringify(props)}]: ` + err.message, err.stack)
|
||||
attr.hadErrors = true
|
||||
}
|
||||
}
|
||||
block.models = models ?? null
|
||||
|
||||
models ??= unknownBlockModel
|
||||
|
||||
|
|
@ -607,7 +616,6 @@ export const setBlockStatesData = (blockstatesModels, blocksAtlas: any, _needTil
|
|||
globalThis.blockProvider = blockProvider
|
||||
if (useUnknownBlockModel) {
|
||||
unknownBlockModel = blockProvider.getAllResolvedModels0_1({ name: 'unknown', properties: {} })
|
||||
erroredBlockModel = blockProvider.getAllResolvedModels0_1({ name: 'errored', properties: {} })
|
||||
}
|
||||
|
||||
needTiles = _needTiles
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { BlockNames } from '../../../../../src/mcDataTypes'
|
||||
import { setup } from './mesherTester'
|
||||
|
||||
const addPositions = [
|
||||
|
|
@ -10,8 +11,10 @@ const addPositions = [
|
|||
[[0, 0, -1], 'stone'],
|
||||
] as const
|
||||
|
||||
const { mesherWorld, getGeometry, pos, mcData } = setup('1.18.1', addPositions as any)
|
||||
const { mesherWorld, getGeometry, pos, mcData } = setup('1.21.1', addPositions as any)
|
||||
|
||||
// mesherWorld.setBlockStateId(pos, mcData.blocksByName.soul_sand.defaultState)
|
||||
// mesherWorld.setBlockStateId(pos, 712)
|
||||
// mesherWorld.setBlockStateId(pos, mcData.blocksByName.stone_slab.defaultState)
|
||||
mesherWorld.setBlockStateId(pos, 11_225)
|
||||
|
||||
// console.log(getGeometry().centerTileNeighbors)
|
||||
console.log(getGeometry().centerTileNeighbors)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export class World {
|
|||
blockCache = {}
|
||||
biomeCache: { [id: number]: mcData.Biome }
|
||||
preflat: boolean
|
||||
erroredBlockModel: BlockModelPartsResolved
|
||||
|
||||
constructor (version) {
|
||||
this.Chunk = Chunks(version) as any
|
||||
|
|
@ -112,7 +113,7 @@ export class World {
|
|||
return this.getColumn(Math.floor(pos.x / 16) * 16, Math.floor(pos.z / 16) * 16)
|
||||
}
|
||||
|
||||
getBlock (pos: Vec3): WorldBlock | null {
|
||||
getBlock (pos: Vec3, blockProvider?, attr?): WorldBlock | null {
|
||||
// for easier testing
|
||||
if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
|
||||
const key = columnKey(Math.floor(pos.x / 16) * 16, Math.floor(pos.z / 16) * 16)
|
||||
|
|
@ -126,8 +127,7 @@ export class World {
|
|||
const stateId = column.getBlockStateId(locInChunk)
|
||||
|
||||
if (!this.blockCache[stateId]) {
|
||||
const b = column.getBlock(locInChunk)
|
||||
//@ts-expect-error
|
||||
const b = column.getBlock(locInChunk) as unknown as WorldBlock
|
||||
b.isCube = isCube(b.shapes)
|
||||
this.blockCache[stateId] = b
|
||||
Object.defineProperty(b, 'position', {
|
||||
|
|
@ -136,7 +136,6 @@ export class World {
|
|||
}
|
||||
})
|
||||
if (this.preflat) {
|
||||
//@ts-expect-error
|
||||
b._properties = {}
|
||||
|
||||
const namePropsStr = legacyJson.blocks[b.type + ':' + b.metadata] || findClosestLegacyBlockFallback(b.type, b.metadata, pos)
|
||||
|
|
@ -149,7 +148,6 @@ export class World {
|
|||
if (!isNaN(val)) val = parseInt(val, 10)
|
||||
return [key, val]
|
||||
}))
|
||||
//@ts-expect-error
|
||||
b._properties = newProperties
|
||||
}
|
||||
}
|
||||
|
|
@ -157,6 +155,40 @@ export class World {
|
|||
}
|
||||
|
||||
const block = this.blockCache[stateId]
|
||||
|
||||
if (block.models === undefined && blockProvider) {
|
||||
if (!attr) throw new Error('attr is required')
|
||||
const props = block.getProperties()
|
||||
try {
|
||||
// fixme
|
||||
if (this.preflat) {
|
||||
if (block.name === 'cobblestone_wall') {
|
||||
props.up = 'true'
|
||||
for (const key of ['north', 'south', 'east', 'west']) {
|
||||
const val = props[key]
|
||||
if (val === 'false' || val === 'true') {
|
||||
props[key] = val === 'true' ? 'low' : 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
block.models = blockProvider.getAllResolvedModels0_1({
|
||||
name: block.name,
|
||||
properties: props,
|
||||
}, this.preflat)! // fixme! this is a hack (also need a setting for all versions)
|
||||
if (!block.models!.length) {
|
||||
console.debug('[mesher] block to render not found', block.name, props)
|
||||
block.models = null
|
||||
}
|
||||
} catch (err) {
|
||||
this.erroredBlockModel ??= blockProvider.getAllResolvedModels0_1({ name: 'errored', properties: {} })
|
||||
block.models ??= this.erroredBlockModel
|
||||
console.error(`Critical assets error. Unable to get block model for ${block.name}[${JSON.stringify(props)}]: ` + err.message, err.stack)
|
||||
attr.hadErrors = true
|
||||
}
|
||||
}
|
||||
|
||||
if (block.name === 'flowing_water') block.name = 'water'
|
||||
if (block.name === 'flowing_lava') block.name = 'lava'
|
||||
// block.position = loc // it overrides position of all currently loaded blocks
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const dataTypeBundling = {
|
|||
processData (current, prev) {
|
||||
for (const block of current) {
|
||||
if (block.transparent) {
|
||||
const forceOpaque = block.name.includes('shulker_box') || block.name.match(/^double_.+_slab\d?$/)
|
||||
const forceOpaque = block.name.includes('shulker_box') || block.name.match(/^double_.+_slab\d?$/) || ['melon_block', 'lit_pumpkin', 'lit_redstone_ore', 'lit_furnace'].includes(block.name)
|
||||
|
||||
const prevBlock = prev?.find(x => x.name === block.name);
|
||||
if (forceOpaque || (prevBlock && !prevBlock.transparent)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue