Performance optimizations: sound caching, renderer culling, entity and lighting improvements

- Sound engine: cache filesystem probe results to avoid repeated file existence checks; add MA_SOUND_FLAG_DECODE for pre-decoded playback
- Level renderer: column-level frustum culling, compact visible chunk lists to skip empty iteration, lightweight second-pass render path, early-out for non-dirty chunks, scaled recheck period at high render distances
- Entity: cache shared_from_this() to reduce reference counting overhead in move() and checkInsideTiles()
- Level: skip checkLight() when tile light properties unchanged; enable entity locking on all platforms (not just Vita)
- LevelChunk: only rescan min height when the minimum column changes; defer lightGap processing
- LivingEntity: use raw pointer cast instead of dynamic_pointer_cast; cache friction tile lookup
- ServerPlayerGameMode: return whether block was destroyed to avoid redundant tile update packets
This commit is contained in:
Revela
2026-03-18 17:22:04 -05:00
parent 80ba2e0762
commit 0ba923f085
12 changed files with 311 additions and 170 deletions

View File

@@ -157,6 +157,11 @@ LevelRenderer::LevelRenderer(Minecraft *mc, Textures *textures)
dirtyChunkPresent = false;
lastDirtyChunkFound = 0;
visibleLists_layer0 = nullptr;
visibleLists_layer1 = nullptr;
visibleCount_layer0 = 0;
visibleCount_layer1 = 0;
this->mc = mc;
this->textures = textures;
@@ -455,6 +460,12 @@ void LevelRenderer::allChanged(int playerIndex)
// delete sortedChunks[playerIndex]; // 4J - removed - not sorting our chunks anymore
}
// Free old visible chunk lists
delete[] visibleLists_layer0;
delete[] visibleLists_layer1;
visibleLists_layer0 = nullptr;
visibleLists_layer1 = nullptr;
chunks[playerIndex] = ClipChunkArray(xChunks * yChunks * zChunks);
// sortedChunks[playerIndex] = new vector<Chunk *>(xChunks * yChunks * zChunks); // 4J - removed - not sorting our chunks anymore
int id = 0;
@@ -487,6 +498,13 @@ void LevelRenderer::allChanged(int playerIndex)
}
nonStackDirtyChunksAdded();
// Allocate visible chunk lists (worst case: all chunks visible)
int totalChunkCount = xChunks * yChunks * zChunks;
visibleLists_layer0 = new int[totalChunkCount];
visibleLists_layer1 = new int[totalChunkCount];
visibleCount_layer0 = 0;
visibleCount_layer1 = 0;
if (level != nullptr)
{
shared_ptr<Entity> player = mc->cameraTargetPlayer;
@@ -705,42 +723,42 @@ int LevelRenderer::render(shared_ptr<LivingEntity> player, int layer, double alp
{
int playerIndex = mc->player->GetXboxPad();
// 4J - added - if the number of players has changed, we need to rebuild things for the new draw distance this will require
if( lastPlayerCount[playerIndex] != activePlayers() )
{
allChanged();
}
else if (mc->options->viewDistance != lastViewDistance)
{
allChanged();
}
// Only check allChanged/resortChunks on layer 0 — they only need to run once per frame
if (layer == 0)
{
// 4J - added - if the number of players has changed, we need to rebuild things for the new draw distance this will require
if( lastPlayerCount[playerIndex] != activePlayers() )
{
allChanged();
}
else if (mc->options->viewDistance != lastViewDistance)
{
allChanged();
}
totalChunks = 0;
offscreenChunks = 0;
occludedChunks = 0;
renderedChunks = 0;
emptyChunks = 0;
double xd = player->x - xOld[playerIndex];
double yd = player->y - yOld[playerIndex];
double zd = player->z - zOld[playerIndex];
if (xd * xd + yd * yd + zd * zd > 4 * 4)
{
xOld[playerIndex] = player->x;
yOld[playerIndex] = player->y;
zOld[playerIndex] = player->z;
resortChunks(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z));
}
}
double xOff = player->xOld + (player->x - player->xOld) * alpha;
double yOff = player->yOld + (player->y - player->yOld) * alpha;
double zOff = player->zOld + (player->z - player->zOld) * alpha;
double xd = player->x - xOld[playerIndex];
double yd = player->y - yOld[playerIndex];
double zd = player->z - zOld[playerIndex];
if (xd * xd + yd * yd + zd * zd > 4 * 4)
{
xOld[playerIndex] = player->x;
yOld[playerIndex] = player->y;
zOld[playerIndex] = player->z;
resortChunks(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z));
// sort(sortedChunks[playerIndex]->begin(),sortedChunks[playerIndex]->end(), DistanceChunkSorter(player)); // 4J - removed - not sorting our chunks anymore
}
Lighting::turnOff();
int count = renderChunks(0, static_cast<int>(chunks[playerIndex].length), layer, alpha);
@@ -749,6 +767,38 @@ int LevelRenderer::render(shared_ptr<LivingEntity> player, int layer, double alp
}
// Lightweight render path for the second layer 1 pass — skips allChanged/resortChunks checks
// and just does GL setup + visible list iteration + cleanup.
// Assumes Lighting::turnOff() was already called by the prior render() invocation.
void LevelRenderer::renderChunksDirect(int layer, double alpha)
{
shared_ptr<LivingEntity> player = mc->cameraTargetPlayer;
if (player == nullptr) return;
mc->gameRenderer->turnOnLightLayer(alpha);
double xOff = player->xOld + (player->x - player->xOld) * alpha;
double yOff = player->yOld + (player->y - player->yOld) * alpha;
double zOff = player->zOld + (player->z - player->zOld) * alpha;
glPushMatrix();
glTranslatef(static_cast<float>(-xOff), static_cast<float>(-yOff), static_cast<float>(-zOff));
int *lists = (layer == 0) ? visibleLists_layer0 : visibleLists_layer1;
int numVisible = (layer == 0) ? visibleCount_layer0 : visibleCount_layer1;
bool first = true;
if (lists != nullptr)
{
for (int i = 0; i < numVisible; i++)
{
if (RenderManager.CBuffCall(lists[i], first))
first = false;
}
}
glPopMatrix();
mc->gameRenderer->turnOffLightLayer(alpha);
}
#ifdef __PSVITA__
#include <stdlib.h>
@@ -819,23 +869,35 @@ int LevelRenderer::renderChunks(int from, int to, int layer, double alpha)
bool first = true;
int count = 0;
ClipChunk *pClipChunk = chunks[playerIndex].data;
unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer;
for( int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++ )
// Use compact visible lists built during cull() instead of iterating all chunks
int *lists = (layer == 0) ? visibleLists_layer0 : visibleLists_layer1;
int numVisible = (layer == 0) ? visibleCount_layer0 : visibleCount_layer1;
if (lists != nullptr)
{
if( !pClipChunk->visible ) continue; // This will be set if the chunk isn't visible, or isn't compiled, or has both empty flags set
if( pClipChunk->globalIdx == -1 ) continue; // Not sure if we should ever encounter this... TODO check
if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) continue; // Check that this particular layer isn't empty
// List can be calculated directly from the chunk's global idex
int list = pClipChunk->globalIdx * 2 + layer;
list += chunkLists;
if(RenderManager.CBuffCall(list, first))
for (int i = 0; i < numVisible; i++)
{
first = false;
if (RenderManager.CBuffCall(lists[i], first))
first = false;
count++;
}
}
else
{
// Fallback: iterate all chunks (before visible lists are allocated)
ClipChunk *pClipChunk = chunks[playerIndex].data;
unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer;
for( int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++ )
{
if( !pClipChunk->visible ) continue;
if( pClipChunk->globalIdx == -1 ) continue;
if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) continue;
int list = pClipChunk->globalIdx * 2 + layer;
list += chunkLists;
if(RenderManager.CBuffCall(list, first))
first = false;
count++;
}
count++;
}
#ifdef __PSVITA__
@@ -1968,6 +2030,9 @@ bool LevelRenderer::updateDirtyChunks()
for( int y = 0; y < CHUNK_Y_COUNT; y++ )
{
ClipChunk *pClipChunk = &chunks[p][(z * yChunks + y) * xChunks + x];
// Early-out for non-dirty chunks - avoids distance calculation for the vast majority at steady state
if( !(globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_DIRTY) )
continue;
// Get distance to this chunk - deliberately not calling the chunk's method of doing this to avoid overheads (passing entitie, type conversion etc.) that this involves
int xd = pClipChunk->xm - px;
int yd = pClipChunk->ym - py;
@@ -2163,7 +2228,9 @@ bool LevelRenderer::updateDirtyChunks()
else
{
// Nothing to do - clear flags that there are things to process, unless it's been a while since we found any dirty chunks in which case force a check next time through
if( ( System::currentTimeMillis() - lastDirtyChunkFound ) > FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS )
// Scale recheck period with render distance to reduce wasted full-scans at high distances
int recheckPeriod = (xChunks >= 60) ? 1000 : (xChunks >= 40) ? 500 : FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS;
if( ( System::currentTimeMillis() - lastDirtyChunkFound ) > recheckPeriod )
{
dirtyChunkPresent = true;
}
@@ -2564,32 +2631,81 @@ void LevelRenderer::cull(Culler *culler, float a)
fdraw[i * 4 + 3] = static_cast<float>(fd->m_Frustum[i][3] + (fx * -fc->xOff) + (fy * -fc->yOff) + (fz * -fc->zOff));
}
ClipChunk *pClipChunk = chunks[playerIndex].data;
int vis = 0;
int total = 0;
int numWrong = 0;
for (unsigned int i = 0; i < chunks[playerIndex].length; i++)
// Reset visible chunk lists for this frame
visibleCount_layer0 = 0;
visibleCount_layer1 = 0;
// Column-level frustum culling: test one AABB per XZ column before testing individual Y chunks.
// At dist 64 this reduces ~278K clip() calls to ~17K column tests + per-chunk tests only for visible columns.
for (int x = 0; x < xChunks; x++)
{
unsigned char flags = pClipChunk->globalIdx == -1 ? 0 : globalChunkFlags[ pClipChunk->globalIdx ];
for (int z = 0; z < zChunks; z++)
{
// Build column AABB from bottom and top chunks in this column
ClipChunk *bottomChunk = &chunks[playerIndex][(z * yChunks + 0) * xChunks + x];
ClipChunk *topChunk = &chunks[playerIndex][(z * yChunks + (yChunks - 1)) * xChunks + x];
float columnAABB[6] = {
bottomChunk->aabb[0], bottomChunk->aabb[1], bottomChunk->aabb[2], // minX, minY, minZ
bottomChunk->aabb[3], topChunk->aabb[4], bottomChunk->aabb[5] // maxX, maxY(top), maxZ
};
// Always perform frustum cull test
bool clipres = clip(pClipChunk->aabb, fdraw);
// Test entire column against frustum
if (!clip(columnAABB, fdraw))
{
// Entire column outside frustum — mark all Y chunks invisible
for (int y = 0; y < yChunks; y++)
{
ClipChunk *pClipChunk = &chunks[playerIndex][(z * yChunks + y) * xChunks + x];
pClipChunk->visible = false;
}
continue;
}
if ( (flags & CHUNK_FLAG_COMPILED ) && ( ( flags & CHUNK_FLAG_EMPTYBOTH ) != CHUNK_FLAG_EMPTYBOTH ) )
{
pClipChunk->visible = clipres;
if( pClipChunk->visible ) vis++;
total++;
// Column is (partially) in frustum — test individual chunks
for (int y = 0; y < yChunks; y++)
{
ClipChunk *pClipChunk = &chunks[playerIndex][(z * yChunks + y) * xChunks + x];
unsigned char flags = pClipChunk->globalIdx == -1 ? 0 : globalChunkFlags[ pClipChunk->globalIdx ];
// Skip frustum test for confirmed-empty compiled chunks - they have nothing to render
if ((flags & CHUNK_FLAG_COMPILED) && (flags & CHUNK_FLAG_EMPTYBOTH) == CHUNK_FLAG_EMPTYBOTH)
{
pClipChunk->visible = false;
continue;
}
bool clipres = clip(pClipChunk->aabb, fdraw);
if ( (flags & CHUNK_FLAG_COMPILED ) && ( ( flags & CHUNK_FLAG_EMPTYBOTH ) != CHUNK_FLAG_EMPTYBOTH ) )
{
pClipChunk->visible = clipres;
if( pClipChunk->visible ) vis++;
total++;
}
else if (clipres)
{
pClipChunk->visible = true;
}
else
{
pClipChunk->visible = false;
}
// Build compact visible chunk lists for renderChunks()
if (pClipChunk->visible && pClipChunk->globalIdx != -1 && visibleLists_layer0 != nullptr)
{
int list = pClipChunk->globalIdx * 2 + chunkLists;
if (!((flags & CHUNK_FLAG_EMPTY0) == CHUNK_FLAG_EMPTY0))
visibleLists_layer0[visibleCount_layer0++] = list;
if (!((flags & CHUNK_FLAG_EMPTY1) == CHUNK_FLAG_EMPTY1))
visibleLists_layer1[visibleCount_layer1++] = list + 1;
}
}
}
else if (clipres)
{
pClipChunk->visible = true;
}
else
{
pClipChunk->visible = false;
}
pClipChunk++;
}
}