mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/LCE-Revelations.git
synced 2026-06-05 08:25:32 +00:00
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:
@@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user