mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-08-20 09:57:09 +00:00
optimisation
Chunk.cpp/h: - thread_local occluder table via std::array<bool,256> IIFE; queries isSolidRender() for all 255 tile types instead of only stone/dirt/bedrock - Null-check getChunkAt() early-out with correct COMPILED/NOTSKYLIT/EMPTY flags - Defer Region + TileRenderer construction past empty-check to skip 9 chunk lookups and 128KB memset for empty chunks (common post-teleport) - Replace Win32 TLS API (TlsAlloc/TlsSetValue/TlsGetValue) + new/delete with static thread_local array for per-thread tileIds storage LevelRenderer.cpp: - Rewrite updateDirtyChunks nearest-chunk search as linear ClipChunk scan; dirty-flag checked before any distance work, batch-clear empty dirty chunks upfront via isRenderChunkEmpty to shrink dirty set across frames - const on all distance/flag locals Region.h/cpp: - Stack buffer flatChunks_stack[16] for common render-chunk regions (4x4), std::unique_ptr<LevelChunk*[]> heap fallback for large pathfinding regions; eliminates per-rebuild heap alloc/free on the hot path - Deleted copy/move ops; std::fill_n for null-init; static constexpr constant TileRenderer.h/cpp: - static thread_local cache array replaces per-instance new/delete - Remove dead getLightColorCount, cacheOwned, conditional delete[] - static constexpr cache size; defaulted destructor Level.cpp: Region constructed directly on stack (no copy) stdafx.h: added <array>
This commit is contained in:
parent
7d1d1599f2
commit
68efba88bd
|
|
@ -22,24 +22,21 @@
|
|||
int Chunk::updates = 0;
|
||||
|
||||
#ifdef _LARGE_WORLDS
|
||||
DWORD Chunk::tlsIdx = TlsAlloc();
|
||||
static thread_local unsigned char s_tlsTileIds[16 * 16 * Level::maxBuildHeight];
|
||||
|
||||
void Chunk::CreateNewThreadStorage()
|
||||
{
|
||||
unsigned char *tileIds = new unsigned char[16 * 16 * Level::maxBuildHeight];
|
||||
TlsSetValue(tlsIdx, tileIds);
|
||||
// No-op: thread_local handles per-thread allocation automatically
|
||||
}
|
||||
|
||||
void Chunk::ReleaseThreadStorage()
|
||||
{
|
||||
unsigned char *tileIds = static_cast<unsigned char *>(TlsGetValue(tlsIdx));
|
||||
delete tileIds;
|
||||
// No-op: thread_local handles per-thread cleanup automatically
|
||||
}
|
||||
|
||||
unsigned char *Chunk::GetTileIdsStorage()
|
||||
{
|
||||
unsigned char *tileIds = static_cast<unsigned char *>(TlsGetValue(tlsIdx));
|
||||
return tileIds;
|
||||
return s_tlsTileIds;
|
||||
}
|
||||
#else
|
||||
// 4J Stu - Don't want this when multi-threaded
|
||||
|
|
@ -234,33 +231,50 @@ void Chunk::rebuild()
|
|||
static unsigned char tileIds[16 * 16 * Level::maxBuildHeight];
|
||||
#endif
|
||||
byteArray tileArray = byteArray(tileIds, 16 * 16 * Level::maxBuildHeight);
|
||||
level->getChunkAt(x,z)->getBlockData(tileArray); // 4J - TODO - now our data has been re-arranged, we could just extra the vertical slice of this chunk rather than the whole thing
|
||||
|
||||
LevelSource *region = new Region(level, x0 - r, y0 - r, z0 - r, x1 + r, y1 + r, z1 + r, r);
|
||||
TileRenderer *tileRenderer = new TileRenderer(region, this->x, this->y, this->z, tileIds);
|
||||
|
||||
// AP - added a caching system for Chunk::rebuild to take advantage of
|
||||
// Basically we're storing of copy of the tileIDs array inside the region so that calls to Region::getTile can grab data
|
||||
// more quickly from this array rather than calling CompressedTileStorage. On the Vita the total thread time spent in
|
||||
// Region::getTile went from 20% to 4%.
|
||||
#ifdef __PSVITA__
|
||||
int xc = x >> 4;
|
||||
int zc = z >> 4;
|
||||
((Region*)region)->setCachedTiles(tileIds, xc, zc);
|
||||
#endif
|
||||
LevelChunk *sourceChunk = level->getChunkAt(x,z);
|
||||
if( sourceChunk == nullptr )
|
||||
{
|
||||
// Level chunk not loaded yet - treat as empty
|
||||
for (int currentLayer = 0; currentLayer < 2; currentLayer++)
|
||||
{
|
||||
levelRenderer->setGlobalChunkFlag(this->x, this->y, this->z, level, LevelRenderer::CHUNK_FLAG_EMPTY0, currentLayer);
|
||||
RenderManager.CBuffClear(lists + currentLayer);
|
||||
}
|
||||
levelRenderer->setGlobalChunkFlag(this->x, this->y, this->z, level, LevelRenderer::CHUNK_FLAG_NOTSKYLIT);
|
||||
levelRenderer->setGlobalChunkFlag(this->x, this->y, this->z, level, LevelRenderer::CHUNK_FLAG_COMPILED);
|
||||
PIXEndNamedEvent();
|
||||
PIXEndNamedEvent();
|
||||
return;
|
||||
}
|
||||
sourceChunk->getBlockData(tileArray); // 4J - TODO - now our data has been re-arranged, we could just extra the vertical slice of this chunk rather than the whole thing
|
||||
|
||||
// We now go through the vertical section of this level chunk that we are interested in and try and establish
|
||||
// (1) if it is completely empty
|
||||
// (2) if any of the tiles can be quickly determined to not need rendering because they are in the middle of other tiles and
|
||||
// so can't be seen. A large amount (> 60% in tests) of tiles that call tesselateInWorld in the unoptimised version
|
||||
// of this function fall into this category. By far the largest category of these are tiles in solid regions of rock.
|
||||
// Build a lookup table for occluder tile IDs to replace repeated 4-way comparisons with a single table lookup per neighbor, eliminating ~24 branch comparisons per interior tile.
|
||||
bool isOccluder[256];
|
||||
std::memset(isOccluder, 0, sizeof(isOccluder));
|
||||
isOccluder[Tile::stone_Id] = true;
|
||||
isOccluder[Tile::dirt_Id] = true;
|
||||
isOccluder[Tile::unbreakable_Id] = true;
|
||||
isOccluder[255] = true;
|
||||
// Build the occluder lookup from Tile::isSolidRender() so that ALL opaque full-cube blocks act as occluders,
|
||||
// not just stone/dirt/bedrock. This catches sand, gravel, ores, cobblestone, sandstone, netherrack, wool, etc.
|
||||
// which dramatically reduces tesselation work for underground/terrain-heavy chunks.
|
||||
//
|
||||
// thread_local: the table is built once per thread and reused across all subsequent rebuild() calls on that
|
||||
// thread. Without it, a plain local would redo 255 virtual dispatches (isSolidRender) every single call -
|
||||
// rebuild() runs up to MAX_CONCURRENT_CHUNK_REBUILDS times per updateDirtyChunks(), ~10 times per frame,
|
||||
// so hundreds of redundant vtable lookups per second for data that never changes at runtime. A plain static
|
||||
// would avoid that but introduces a data race: multiple rebuild threads run concurrently and the C++ static
|
||||
// init guard (mutex) would serialize them on every entry just to check the "already initialized" flag.
|
||||
// thread_local gives each thread its own copy with zero synchronization after the first call.
|
||||
static thread_local auto isOccluder = [] {
|
||||
std::array<bool, 256> table{};
|
||||
for (int id = 1; id < 256; id++)
|
||||
{
|
||||
Tile *tile = Tile::tiles[id];
|
||||
if (tile != nullptr && tile->isSolidRender())
|
||||
table[id] = true;
|
||||
}
|
||||
table[255] = true; // already-marked-invisible sentinel
|
||||
return table;
|
||||
}();
|
||||
|
||||
bool empty = true;
|
||||
for( int yy = y0; yy < y1; yy++ )
|
||||
|
|
@ -330,14 +344,31 @@ void Chunk::rebuild()
|
|||
levelRenderer->setGlobalChunkFlag(this->x, this->y, this->z, level, LevelRenderer::CHUNK_FLAG_EMPTY0, currentLayer);
|
||||
RenderManager.CBuffClear(lists + currentLayer);
|
||||
}
|
||||
levelRenderer->setGlobalChunkFlag(this->x, this->y, this->z, level, LevelRenderer::CHUNK_FLAG_NOTSKYLIT);
|
||||
levelRenderer->setGlobalChunkFlag(this->x, this->y, this->z, level, LevelRenderer::CHUNK_FLAG_COMPILED);
|
||||
|
||||
delete region;
|
||||
delete tileRenderer;
|
||||
PIXEndNamedEvent(); // match "Rebuilding chunk" event
|
||||
return;
|
||||
}
|
||||
// 4J - optimisation ends
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Construct Region and TileRenderer only for non-empty chunks.
|
||||
// This is deferred to here so that empty chunks (common after teleport) skip
|
||||
// the Region construction (9 chunk lookups) and TileRenderer 128KB cache memset.
|
||||
Region region(level, x0 - r, y0 - r, z0 - r, x1 + r, y1 + r, z1 + r, r);
|
||||
TileRenderer tileRenderer(®ion, this->x, this->y, this->z, tileIds);
|
||||
|
||||
// AP - added a caching system for Chunk::rebuild to take advantage of
|
||||
// Basically we're storing of copy of the tileIDs array inside the region so that calls to Region::getTile can grab data
|
||||
// more quickly from this array rather than calling CompressedTileStorage. On the Vita the total thread time spent in
|
||||
// Region::getTile went from 20% to 4%.
|
||||
#ifdef __PSVITA__
|
||||
int xc = x >> 4;
|
||||
int zc = z >> 4;
|
||||
region.setCachedTiles(tileIds, xc, zc);
|
||||
#endif
|
||||
|
||||
PIXBeginNamedEvent(0,"Rebuild section C");
|
||||
Tesselator::Bounds bounds; // 4J MGH - added
|
||||
{
|
||||
|
|
@ -374,7 +405,7 @@ void Chunk::rebuild()
|
|||
}
|
||||
|
||||
// 4J - get tile from those copied into our local array in earlier optimisation
|
||||
unsigned char tileId = tileIds[ offset + ( ( ( x - x0 ) << 11 ) | ( ( z - z0 ) << 7 ) | indexY) ];
|
||||
const unsigned char tileId = tileIds[ offset + ( ( ( x - x0 ) << 11 ) | ( ( z - z0 ) << 7 ) | indexY) ];
|
||||
// If flagged as not visible, drop out straight away
|
||||
if( tileId == 0xff ) [[unlikely]] continue;
|
||||
// int tileId = region->getTile(x,y,z);
|
||||
|
|
@ -406,7 +437,7 @@ void Chunk::rebuild()
|
|||
Tile *tile = Tile::tiles[tileId];
|
||||
if (currentLayer == 0 && tile->isEntityTile())
|
||||
{
|
||||
shared_ptr<TileEntity> et = region->getTileEntity(x, y, z);
|
||||
shared_ptr<TileEntity> et = region.getTileEntity(x, y, z);
|
||||
if (TileEntityRenderDispatcher::instance->hasRenderer(et))
|
||||
{
|
||||
renderableTileEntities.push_back(et);
|
||||
|
|
@ -420,7 +451,7 @@ void Chunk::rebuild()
|
|||
}
|
||||
else if (renderLayer == currentLayer)
|
||||
{
|
||||
rendered |= tileRenderer->tesselateInWorld(tile, x, y, z);
|
||||
rendered |= tileRenderer.tesselateInWorld(tile, x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -480,9 +511,6 @@ void Chunk::rebuild()
|
|||
bounds.boundingBox[3], bounds.boundingBox[4], bounds.boundingBox[5]);
|
||||
}
|
||||
|
||||
delete tileRenderer;
|
||||
delete region;
|
||||
|
||||
PIXEndNamedEvent();
|
||||
PIXBeginNamedEvent(0,"Rebuild section D");
|
||||
// 4J - have rewritten the way that tile entities are stored globally to make it work more easily with split screen. Chunks are now
|
||||
|
|
|
|||
|
|
@ -30,9 +30,8 @@ public:
|
|||
static LevelRenderer *levelRenderer;
|
||||
private:
|
||||
#ifndef _LARGE_WORLDS
|
||||
static Tesselator *t;
|
||||
static Tesselator *t;
|
||||
#else
|
||||
static DWORD tlsIdx;
|
||||
public:
|
||||
static void CreateNewThreadStorage();
|
||||
static void ReleaseThreadStorage();
|
||||
|
|
|
|||
|
|
@ -1943,7 +1943,11 @@ bool LevelRenderer::updateDirtyChunks()
|
|||
int maxNearestChunks = MAX_CONCURRENT_CHUNK_REBUILDS;
|
||||
// 4J Stu - On XboxOne we should cut this down if in a constrained state so the saving threads get more time
|
||||
#endif
|
||||
// Find nearest chunk that is dirty
|
||||
// Find nearest chunk that is dirty.
|
||||
// Rewritten for performance: linear scan of clip chunks (cache-friendly), with dirty flag
|
||||
// checked BEFORE computing distances. The old triple-nested x/z/y loop computed distances
|
||||
// for every chunk (~20K) regardless of dirty state. Now only dirty chunks (typically <1%)
|
||||
// pay the cost of distance computation and nearest-selection.
|
||||
for( int p = 0; p < XUSER_MAX_COUNT; p++ )
|
||||
{
|
||||
// It's possible that the localplayers member can be set to nullptr on the main thread when a player chooses to exit the game
|
||||
|
|
@ -1957,96 +1961,91 @@ bool LevelRenderer::updateDirtyChunks()
|
|||
int py = static_cast<int>(player->y);
|
||||
int pz = static_cast<int>(player->z);
|
||||
|
||||
// app.DebugPrintf("!! %d %d %d, %d %d %d {%d,%d} ",px,py,pz,stackChunkDirty,nonStackChunkDirty,onlyRebuild, xChunks, zChunks);
|
||||
|
||||
int considered = 0;
|
||||
int wouldBeNearButEmpty = 0;
|
||||
for( int x = 0; x < xChunks; x++ )
|
||||
int numClipChunks = static_cast<int>(chunks[p].length);
|
||||
ClipChunk *pClipChunk = chunks[p].data;
|
||||
for( int i = 0; i < numClipChunks; i++, pClipChunk++ )
|
||||
{
|
||||
for( int z = 0; z < zChunks; z++ )
|
||||
// Fast reject: skip non-dirty chunks immediately before any distance work.
|
||||
// globalIdx can be -1 for unassigned chunks.
|
||||
const int gIdx = pClipChunk->globalIdx;
|
||||
if (gIdx < 0)
|
||||
continue;
|
||||
const unsigned char flags = globalChunkFlags[gIdx];
|
||||
if (!(flags & CHUNK_FLAG_DIRTY))
|
||||
continue;
|
||||
|
||||
// Batch-clear empty chunks upfront. After teleport, thousands of sky/void chunks
|
||||
// are dirty. Clearing them all in one scan (rather than ~8 per call) dramatically
|
||||
// reduces the dirty set for subsequent iterations.
|
||||
Chunk *chunk = pClipChunk->chunk;
|
||||
if (chunk == nullptr)
|
||||
continue;
|
||||
const int ySlice = (pClipChunk->ym - (CHUNK_SIZE / 2)) / CHUNK_SIZE;
|
||||
LevelChunk *lc = level[p]->getChunkAt(chunk->x, chunk->z);
|
||||
if (lc == nullptr || lc->isRenderChunkEmpty(ySlice * 16))
|
||||
{
|
||||
for( int y = 0; y < CHUNK_Y_COUNT; y++ )
|
||||
chunk->clearDirty();
|
||||
globalChunkFlags[gIdx] |= CHUNK_FLAG_EMPTYBOTH;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Non-empty dirty chunk — now compute distance
|
||||
const int xd = pClipChunk->xm - px;
|
||||
const int yd = pClipChunk->ym - py;
|
||||
const int zd = pClipChunk->zm - pz;
|
||||
const int distSq = xd * xd + yd * yd + zd * zd;
|
||||
const int distSqWeighted = distSq + 3 * yd * yd; // Extra y weighting to prioritise things in same x/z plane as player first
|
||||
|
||||
if( (!onlyRebuild) ||
|
||||
(flags & CHUNK_FLAG_COMPILED) ||
|
||||
( distSq < 96 * 96 ) ) // Always rebuild really near things or else building (say) at tower up into empty blocks when we are low on memory will not create render data
|
||||
{
|
||||
// Is this chunk nearer than our nearest?
|
||||
#ifdef _LARGE_WORLDS
|
||||
bool isNearer = nearestClipChunks.empty();
|
||||
auto itNearest = nearestClipChunks.begin();
|
||||
for(; itNearest != nearestClipChunks.end(); ++itNearest)
|
||||
{
|
||||
ClipChunk *pClipChunk = &chunks[p][(z * yChunks + y) * xChunks + x];
|
||||
// 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;
|
||||
int zd = pClipChunk->zm - pz;
|
||||
int distSq = xd * xd + yd * yd + zd * zd;
|
||||
int distSqWeighted = xd * xd + yd * yd * 4 + zd * zd; // Weighting against y to prioritise things in same x/z plane as player first
|
||||
isNearer = distSqWeighted < itNearest->second;
|
||||
if(isNearer) break;
|
||||
}
|
||||
isNearer = isNearer || (nearestClipChunks.size() < maxNearestChunks);
|
||||
#else
|
||||
bool isNearer = distSqWeighted < minDistSq;
|
||||
#endif
|
||||
|
||||
if( globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_DIRTY )
|
||||
#ifdef _CRITICAL_CHUNKS
|
||||
// AP - this will make sure that if a deferred grouping has started, only critical chunks go into that
|
||||
// grouping, even if a non-critical chunk is closer.
|
||||
if( (!veryNearCount && isNearer) ||
|
||||
(distSq < 20 * 20 && (flags & CHUNK_FLAG_CRITICAL)) )
|
||||
#else
|
||||
if( isNearer )
|
||||
#endif
|
||||
{
|
||||
// Non-empty (already confirmed above), add to nearest set
|
||||
nearChunk = pClipChunk;
|
||||
minDistSq = distSqWeighted;
|
||||
#ifdef _LARGE_WORLDS
|
||||
nearestClipChunks.insert(itNearest, std::make_pair(nearChunk, minDistSq) );
|
||||
if(nearestClipChunks.size() > maxNearestChunks)
|
||||
{
|
||||
if( (!onlyRebuild) ||
|
||||
globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_COMPILED ||
|
||||
( distSq < 96 * 96 ) ) // Always rebuild really near things or else building (say) at tower up into empty blocks when we are low on memory will not create render data
|
||||
{ // distSq adjusted from 20 * 20 to 96 * 96 - updated by detectiveren
|
||||
considered++;
|
||||
// Is this chunk nearer than our nearest?
|
||||
#ifdef _LARGE_WORLDS
|
||||
bool isNearer = nearestClipChunks.empty();
|
||||
auto itNearest = nearestClipChunks.begin();
|
||||
for(; itNearest != nearestClipChunks.end(); ++itNearest)
|
||||
{
|
||||
isNearer = distSqWeighted < itNearest->second;
|
||||
if(isNearer) break;
|
||||
}
|
||||
isNearer = isNearer || (nearestClipChunks.size() < maxNearestChunks);
|
||||
#else
|
||||
bool isNearer = distSqWeighted < minDistSq;
|
||||
#endif
|
||||
|
||||
#ifdef _CRITICAL_CHUNKS
|
||||
// AP - this will make sure that if a deferred grouping has started, only critical chunks go into that
|
||||
// grouping, even if a non-critical chunk is closer.
|
||||
if( (!veryNearCount && isNearer) ||
|
||||
(distSq < 20 * 20 && (globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_CRITICAL)) )
|
||||
#else
|
||||
if( isNearer )
|
||||
#endif
|
||||
{
|
||||
// At this point we've got a chunk that we would like to consider for rendering, at least based on its proximity to the player(s).
|
||||
// Its *quite* quick to generate empty render data for render chunks, but if we let the rebuilding do that then the after rebuilding we will have
|
||||
// to start searching for the next nearest chunk from scratch again. Instead, its better to detect empty chunks at this stage, flag them up as not dirty
|
||||
// (and empty), and carry on. The levelchunk's isRenderChunkEmpty method can be quite optimal as it can make use of the chunk's data compression to detect
|
||||
// emptiness without actually testing as many data items as uncompressed data would.
|
||||
Chunk *chunk = pClipChunk->chunk;
|
||||
LevelChunk *lc = level[p]->getChunkAt(chunk->x,chunk->z);
|
||||
if( !lc->isRenderChunkEmpty(y * 16) )
|
||||
{
|
||||
nearChunk = pClipChunk;
|
||||
minDistSq = distSqWeighted;
|
||||
#ifdef _LARGE_WORLDS
|
||||
nearestClipChunks.insert(itNearest, std::make_pair(nearChunk, minDistSq) );
|
||||
if(nearestClipChunks.size() > maxNearestChunks)
|
||||
{
|
||||
nearestClipChunks.pop_back();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
chunk->clearDirty();
|
||||
globalChunkFlags[ pClipChunk->globalIdx ] |= CHUNK_FLAG_EMPTYBOTH;
|
||||
wouldBeNearButEmpty++;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _CRITICAL_CHUNKS
|
||||
// AP - is the chunk near and also critical
|
||||
if( distSq < 20 * 20 && ((globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_CRITICAL)) )
|
||||
#else
|
||||
if( distSq < 20 * 20 )
|
||||
#endif
|
||||
{
|
||||
veryNearCount++;
|
||||
}
|
||||
}
|
||||
nearestClipChunks.pop_back();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _CRITICAL_CHUNKS
|
||||
// AP - is the chunk near and also critical
|
||||
if( distSq < 20 * 20 && (flags & CHUNK_FLAG_CRITICAL) )
|
||||
#else
|
||||
if( distSq < 20 * 20 )
|
||||
#endif
|
||||
{
|
||||
veryNearCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// app.DebugPrintf("[%d,%d,%d]\n",nearestClipChunks.empty(),considered,wouldBeNearButEmpty);
|
||||
}
|
||||
#endif // __PS3__
|
||||
PIXEndNamedEvent();
|
||||
|
|
@ -2193,7 +2192,11 @@ bool LevelRenderer::updateDirtyChunks()
|
|||
return true;
|
||||
}
|
||||
|
||||
if( nearChunk ) destroyedTileManager->updatedChunkAt(chunk->level, chunk->x, chunk->y, chunk->z, veryNearCount );
|
||||
if( nearChunk )
|
||||
{
|
||||
destroyedTileManager->updatedChunkAt(chunk->level, chunk->x, chunk->y, chunk->z, veryNearCount );
|
||||
return dirtyChunkPresent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,6 +142,9 @@ int TileRenderer::getLightColor( Tile *tt, LevelSource *level, int x, int y, int
|
|||
return tt->getLightColor(level, x, y, z);
|
||||
}
|
||||
|
||||
static constexpr int TILE_RENDERER_CACHE_SIZE = 32 * 32 * 32;
|
||||
static thread_local unsigned int s_tlsCache[TILE_RENDERER_CACHE_SIZE];
|
||||
|
||||
TileRenderer::TileRenderer( LevelSource* level, int xMin, int yMin, int zMin, unsigned char *tileIds )
|
||||
{
|
||||
this->level = level;
|
||||
|
|
@ -153,14 +156,11 @@ TileRenderer::TileRenderer( LevelSource* level, int xMin, int yMin, int zMin, un
|
|||
this->yMin2 = yMin-2;
|
||||
this->zMin2 = zMin-2;
|
||||
this->tileIds = tileIds;
|
||||
cache = new unsigned int[32*32*32];
|
||||
XMemSet(cache,0,32*32*32*sizeof(unsigned int));
|
||||
cache = s_tlsCache;
|
||||
std::memset(cache, 0, TILE_RENDERER_CACHE_SIZE * sizeof(unsigned int));
|
||||
}
|
||||
|
||||
TileRenderer::~TileRenderer()
|
||||
{
|
||||
delete cache;
|
||||
}
|
||||
TileRenderer::~TileRenderer() = default;
|
||||
|
||||
TileRenderer::TileRenderer( LevelSource* level )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ typedef XUID GameSessionUID;
|
|||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
|
|
|||
|
|
@ -12,14 +12,7 @@
|
|||
|
||||
Region::~Region()
|
||||
{
|
||||
for(unsigned int i = 0; i < chunks->length; ++i)
|
||||
{
|
||||
LevelChunkArray *lca = (*chunks)[i];
|
||||
delete [] lca->data;
|
||||
delete lca;
|
||||
}
|
||||
delete [] chunks->data;
|
||||
delete chunks;
|
||||
// flatChunksHeap automatically freed by unique_ptr
|
||||
|
||||
// AP - added a caching system for Chunk::rebuild to take advantage of
|
||||
if( CachedTiles )
|
||||
|
|
@ -37,7 +30,21 @@ Region::Region(Level *level, int x1, int y1, int z1, int x2, int y2, int z2, int
|
|||
int xc2 = (x2 + r) >> 4;
|
||||
int zc2 = (z2 + r) >> 4;
|
||||
|
||||
chunks = new LevelChunk2DArray(xc2 - xc1 + 1, zc2 - zc1 + 1);
|
||||
chunksDimX = xc2 - xc1 + 1;
|
||||
chunksDimZ = zc2 - zc1 + 1;
|
||||
int totalChunks = chunksDimX * chunksDimZ;
|
||||
|
||||
// Use stack buffer for common small regions (render chunks), heap for rare large ones (pathfinding)
|
||||
if( totalChunks <= MAX_STACK_CHUNKS )
|
||||
{
|
||||
flatChunks = flatChunks_stack;
|
||||
}
|
||||
else
|
||||
{
|
||||
flatChunksHeap = std::make_unique<LevelChunk*[]>(totalChunks);
|
||||
flatChunks = flatChunksHeap.get();
|
||||
}
|
||||
std::fill_n(flatChunks, totalChunks, nullptr);
|
||||
|
||||
allEmpty = true;
|
||||
for (int xc = xc1; xc <= xc2; xc++)
|
||||
|
|
@ -47,8 +54,7 @@ Region::Region(Level *level, int x1, int y1, int z1, int x2, int y2, int z2, int
|
|||
LevelChunk *chunk = level->getChunk(xc, zc);
|
||||
if(chunk != nullptr)
|
||||
{
|
||||
LevelChunkArray *lca = (*chunks)[xc - xc1];
|
||||
lca->data[zc - zc1] = chunk;
|
||||
flatChunks[(xc - xc1) * chunksDimZ + (zc - zc1)] = chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,8 +62,7 @@ Region::Region(Level *level, int x1, int y1, int z1, int x2, int y2, int z2, int
|
|||
{
|
||||
for (int zc = (z1 >> 4); zc <= (z2 >> 4); zc++)
|
||||
{
|
||||
LevelChunkArray *lca = (*chunks)[xc - xc1];
|
||||
LevelChunk *chunk = lca->data[zc - zc1];
|
||||
LevelChunk *chunk = flatChunks[(xc - xc1) * chunksDimZ + (zc - zc1)];
|
||||
if (chunk != nullptr)
|
||||
{
|
||||
if (!chunk->isYSpaceEmpty(y1, y2))
|
||||
|
|
@ -105,12 +110,12 @@ int Region::getTile(int x, int y, int z)
|
|||
xc -= xc1;
|
||||
zc -= zc1;
|
||||
|
||||
if (xc < 0 || xc >= static_cast<int>(chunks->length) || zc < 0 || zc >= static_cast<int>((*chunks)[xc]->length))
|
||||
if (xc < 0 || xc >= chunksDimX || zc < 0 || zc >= chunksDimZ)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LevelChunk *lc = (*chunks)[xc]->data[zc];
|
||||
LevelChunk *lc = flatChunks[xc * chunksDimZ + zc];
|
||||
if (lc == nullptr) return 0;
|
||||
|
||||
return lc->getTile(x & 15, y, z & 15);
|
||||
|
|
@ -137,12 +142,12 @@ LevelChunk* Region::getLevelChunk(int x, int y, int z)
|
|||
int xc = (x >> 4) - xc1;
|
||||
int zc = (z >> 4) - zc1;
|
||||
|
||||
if (xc < 0 || xc >= static_cast<int>(chunks->length) || zc < 0 || zc >= static_cast<int>((*chunks)[xc]->length))
|
||||
if (xc < 0 || xc >= chunksDimX || zc < 0 || zc >= chunksDimZ)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LevelChunk *lc = (*chunks)[xc]->data[zc];
|
||||
LevelChunk *lc = flatChunks[xc * chunksDimZ + zc];
|
||||
return lc;
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +158,15 @@ shared_ptr<TileEntity> Region::getTileEntity(int x, int y, int z)
|
|||
int xc = (x >> 4) - xc1;
|
||||
int zc = (z >> 4) - zc1;
|
||||
|
||||
return (*chunks)[xc]->data[zc]->getTileEntity(x & 15, y, z & 15);
|
||||
if (xc < 0 || xc >= chunksDimX || zc < 0 || zc >= chunksDimZ)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LevelChunk *lc = flatChunks[xc * chunksDimZ + zc];
|
||||
if (lc == nullptr) return nullptr;
|
||||
|
||||
return lc->getTileEntity(x & 15, y, z & 15);
|
||||
}
|
||||
|
||||
int Region::getLightColor(int x, int y, int z, int emitt, int tileId/*=-1*/)
|
||||
|
|
@ -228,7 +241,15 @@ int Region::getRawBrightness(int x, int y, int z, bool propagate)
|
|||
int xc = (x >> 4) - xc1;
|
||||
int zc = (z >> 4) - zc1;
|
||||
|
||||
return (*chunks)[xc]->data[zc]->getRawBrightness(x & 15, y, z & 15, level->skyDarken);
|
||||
if (xc < 0 || xc >= chunksDimX || zc < 0 || zc >= chunksDimZ)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LevelChunk *lc = flatChunks[xc * chunksDimZ + zc];
|
||||
if (lc == nullptr) return 0;
|
||||
|
||||
return lc->getRawBrightness(x & 15, y, z & 15, level->skyDarken);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -239,7 +260,15 @@ int Region::getData(int x, int y, int z)
|
|||
int xc = (x >> 4) - xc1;
|
||||
int zc = (z >> 4) - zc1;
|
||||
|
||||
return (*chunks)[xc]->data[zc]->getData(x & 15, y, z & 15);
|
||||
if (xc < 0 || xc >= chunksDimX || zc < 0 || zc >= chunksDimZ)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
LevelChunk *lc = flatChunks[xc * chunksDimZ + zc];
|
||||
if (lc == nullptr) return 0;
|
||||
|
||||
return lc->getData(x & 15, y, z & 15);
|
||||
}
|
||||
|
||||
Material *Region::getMaterial(int x, int y, int z)
|
||||
|
|
@ -321,7 +350,7 @@ int Region::getBrightnessPropagate(LightLayer::variety layer, int x, int y, int
|
|||
// 4J Stu - The java LightLayer was an enum class type with a member "surrounding" which is what we
|
||||
// were returning here. Surrounding has the same value as the enum value in our C++ code, so just cast
|
||||
// it to an int
|
||||
return (int)layer;
|
||||
return static_cast<int>(layer);
|
||||
}
|
||||
if (layer == LightLayer::Sky && level->dimension->hasCeiling)
|
||||
{
|
||||
|
|
@ -346,7 +375,15 @@ int Region::getBrightnessPropagate(LightLayer::variety layer, int x, int y, int
|
|||
int xc = (x >> 4) - xc1;
|
||||
int zc = (z >> 4) - zc1;
|
||||
|
||||
return (*chunks)[xc]->data[zc]->getBrightness(layer, x & 15, y, z & 15);
|
||||
if (xc < 0 || xc >= chunksDimX || zc < 0 || zc >= chunksDimZ)
|
||||
{
|
||||
return static_cast<int>(layer);
|
||||
}
|
||||
|
||||
LevelChunk *lc = flatChunks[xc * chunksDimZ + zc];
|
||||
if (lc == nullptr) return static_cast<int>(layer);
|
||||
|
||||
return lc->getBrightness(layer, x & 15, y, z & 15);
|
||||
}
|
||||
|
||||
// 4J - brought forward from 1.8.2
|
||||
|
|
@ -359,12 +396,20 @@ int Region::getBrightness(LightLayer::variety layer, int x, int y, int z)
|
|||
// 4J Stu - The java LightLayer was an enum class type with a member "surrounding" which is what we
|
||||
// were returning here. Surrounding has the same value as the enum value in our C++ code, so just cast
|
||||
// it to an int
|
||||
return (int)layer;
|
||||
return static_cast<int>(layer);
|
||||
}
|
||||
int xc = (x >> 4) - xc1;
|
||||
int zc = (z >> 4) - zc1;
|
||||
|
||||
return (*chunks)[xc]->data[zc]->getBrightness(layer, x & 15, y, z & 15);
|
||||
if (xc < 0 || xc >= chunksDimX || zc < 0 || zc >= chunksDimZ)
|
||||
{
|
||||
return static_cast<int>(layer);
|
||||
}
|
||||
|
||||
LevelChunk *lc = flatChunks[xc * chunksDimZ + zc];
|
||||
if (lc == nullptr) return static_cast<int>(layer);
|
||||
|
||||
return lc->getBrightness(layer, x & 15, y, z & 15);
|
||||
}
|
||||
|
||||
int Region::getMaxBuildHeight()
|
||||
|
|
|
|||
|
|
@ -9,8 +9,12 @@ class BiomeSource;
|
|||
class Region : public LevelSource
|
||||
{
|
||||
private:
|
||||
static constexpr int MAX_STACK_CHUNKS = 16; // 4x4 covers the common render-chunk case (r=1)
|
||||
int xc1, zc1;
|
||||
LevelChunk2DArray *chunks;
|
||||
LevelChunk *flatChunks_stack[MAX_STACK_CHUNKS];
|
||||
LevelChunk **flatChunks; // non-owning: points to flatChunks_stack or flatChunksHeap's buffer
|
||||
std::unique_ptr<LevelChunk*[]> flatChunksHeap; // owns heap allocation for large regions
|
||||
int chunksDimX, chunksDimZ;
|
||||
Level *level;
|
||||
bool allEmpty;
|
||||
|
||||
|
|
@ -21,6 +25,13 @@ private:
|
|||
public:
|
||||
Region(Level *level, int x1, int y1, int z1, int x2, int y2, int z2, int r);
|
||||
virtual ~Region();
|
||||
|
||||
// Non-copyable/movable: flatChunks may point to internal stack buffer
|
||||
Region(const Region&) = delete;
|
||||
Region& operator=(const Region&) = delete;
|
||||
Region(Region&&) = delete;
|
||||
Region& operator=(Region&&) = delete;
|
||||
|
||||
bool isAllEmpty();
|
||||
int getTile(int x, int y, int z);
|
||||
shared_ptr<TileEntity> getTileEntity(int x, int y, int z);
|
||||
|
|
|
|||
Loading…
Reference in a new issue