diff --git a/targets/minecraft/client/renderer/Chunk.cpp b/targets/minecraft/client/renderer/Chunk.cpp index fe581b473..5d475fc14 100644 --- a/targets/minecraft/client/renderer/Chunk.cpp +++ b/targets/minecraft/client/renderer/Chunk.cpp @@ -13,11 +13,10 @@ #include #include -#include "platform/sdl2/Render.h" #include "LevelRenderer.h" -#include "app/linux/Stubs/winapi_stubs.h" -#include "app/include/FrameProfiler.h" #include "TileRenderer.h" +#include "app/include/FrameProfiler.h" +#include "app/linux/Stubs/winapi_stubs.h" #include "minecraft/client/renderer/Tesselator.h" #include "minecraft/client/renderer/culling/Culler.h" #include "minecraft/client/renderer/tileentity/TileEntityRenderDispatcher.h" @@ -29,6 +28,7 @@ #include "minecraft/world/level/tile/Tile.h" #include "minecraft/world/level/tile/entity/TileEntity.h" #include "minecraft/world/phys/AABB.h" +#include "platform/sdl2/Render.h" int Chunk::updates = 0; @@ -174,6 +174,11 @@ void Chunk::setPos(int x, int y, int z) { levelRenderer->m_csDirtyChunks); unsigned char refCount = levelRenderer->incGlobalChunkRefCount(x, y, z, level); + if ((clipChunk->globalIdx >= 0) && + ((size_t)clipChunk->globalIdx < + levelRenderer->m_globalClipChunks.size())) { + levelRenderer->m_globalClipChunks[clipChunk->globalIdx] = clipChunk; + } // printf("\t\t [inc] refcount %d at %d, %d, %d\n",refCount,x,y,z); // int idx = levelRenderer->getGlobalIndexForChunk(x, y, z, level); @@ -223,8 +228,15 @@ void Chunk::makeCopyForRebuild(Chunk* source) { this->globalRenderableTileEntities = source->globalRenderableTileEntities; this->globalRenderableTileEntities_cs = source->globalRenderableTileEntities_cs; + this->buildContext = source->buildContext; } +void Chunk::setBuildContext(const ChunkBuildContext& ctx) { + buildContext = ctx; +} + +const ChunkBuildContext& Chunk::getBuildContext() const { return buildContext; } + void Chunk::rebuild() { // if (!dirty) return; @@ -285,8 +297,8 @@ void Chunk::rebuild() { 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); + TileRenderer* tileRenderer = new TileRenderer( + region, this->x, this->y, this->z, tileIds, &buildContext); // 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 @@ -542,10 +554,14 @@ void Chunk::rebuild() { bb = {bounds.boundingBox[0], bounds.boundingBox[1], bounds.boundingBox[2], bounds.boundingBox[3], bounds.boundingBox[4], bounds.boundingBox[5]}; - uint64_t conn = computeConnectivity(tileIds); // pass tileIds int globalIdx = levelRenderer->getGlobalIndexForChunk(this->x, this->y, this->z, level); +#if defined(OCCLUSION_MODE_BFS) + uint64_t conn = computeConnectivity(tileIds); // pass tileIds levelRenderer->setGlobalChunkConnectivity(globalIdx, conn); +#else + levelRenderer->setGlobalChunkConnectivity(globalIdx, ~0ULL); +#endif delete tileRenderer; delete region; @@ -590,6 +606,7 @@ float Chunk::squishedDistanceToSqr(std::shared_ptr player) { return xd * xd + yd * yd + zd * zd; } +#if defined(OCCLUSION_MODE_BFS) uint64_t Chunk::computeConnectivity(const uint8_t* tileIds) { const int W = 16; const int H = 16; @@ -726,6 +743,7 @@ uint64_t Chunk::computeConnectivity(const uint8_t* tileIds) { return result; } +#endif void Chunk::reset() { if (assigned) { int oldKey = -1; @@ -742,6 +760,9 @@ void Chunk::reset() { //%d\n",refCount,x,y,z); if (refCount == 0 && oldKey != -1) { retireRenderableTileEntities = true; + if ((size_t)oldKey < levelRenderer->m_globalClipChunks.size()) { + levelRenderer->m_globalClipChunks[oldKey] = nullptr; + } int lists = oldKey * 2; if (lists >= 0) { lists += levelRenderer->chunkLists; diff --git a/targets/minecraft/client/renderer/Chunk.h b/targets/minecraft/client/renderer/Chunk.h index d54b21ad2..ae48fbcaf 100644 --- a/targets/minecraft/client/renderer/Chunk.h +++ b/targets/minecraft/client/renderer/Chunk.h @@ -17,6 +17,14 @@ class Entity; class Chunk; class Culler; +struct ChunkBuildContext { + int64_t nowMs = 0; + uint32_t batchId = 0; + uint16_t veryNearCount = 0; + bool onlyRebuild = false; + bool deferredAtomic = false; +}; + class ClipChunk { public: Chunk* chunk; @@ -57,7 +65,9 @@ public: int xm, ym, zm; AABB bb; ClipChunk* clipChunk; +#if defined(OCCLUSION_MODE_BFS) uint64_t computeConnectivity(const uint8_t* tileIds); +#endif int id; // public: // std::vector > renderableTileEntities; @@ -67,6 +77,7 @@ private: LevelRenderer::rteMap* globalRenderableTileEntities; std::mutex* globalRenderableTileEntities_cs; bool assigned; + ChunkBuildContext buildContext; public: Chunk(Level* level, LevelRenderer::rteMap& globalRenderableTileEntities, @@ -84,6 +95,8 @@ private: public: void makeCopyForRebuild(Chunk* source); + void setBuildContext(const ChunkBuildContext& ctx); + const ChunkBuildContext& getBuildContext() const; void rebuild(); float distanceToSqr(std::shared_ptr player) const; float squishedDistanceToSqr(std::shared_ptr player); diff --git a/targets/minecraft/client/renderer/GameRenderer.cpp b/targets/minecraft/client/renderer/GameRenderer.cpp index 744e27a79..dd94823f1 100644 --- a/targets/minecraft/client/renderer/GameRenderer.cpp +++ b/targets/minecraft/client/renderer/GameRenderer.cpp @@ -6,24 +6,18 @@ #include #include -#include "platform/PlatformTypes.h" -#include "platform/sdl2/Input.h" -#include "platform/sdl2/Render.h" #include "BossMobGuiInfo.h" #include "Chunk.h" #include "ItemInHandRenderer.h" #include "LevelRenderer.h" +#include "Tesselator.h" #include "app/common/App_enums.h" -#include "platform/ShutdownManager.h" #include "app/common/src/Colours/ColourTable.h" -#include "app/linux/LinuxGame.h" -#include "app/linux/Stubs/winapi_stubs.h" #include "app/include/BufferedImage.h" #include "app/include/FrameProfiler.h" #include "app/include/stubs.h" -#include "Tesselator.h" -#include "minecraft/world/level/storage/ConsoleSaveFileIO/compression.h" - +#include "app/linux/LinuxGame.h" +#include "app/linux/Stubs/winapi_stubs.h" #include "java/Class.h" #include "java/FloatBuffer.h" #include "java/JavaMath.h" @@ -78,10 +72,15 @@ #include "minecraft/world/level/chunk/SparseLightStorage.h" #include "minecraft/world/level/dimension/Dimension.h" #include "minecraft/world/level/material/Material.h" +#include "minecraft/world/level/storage/ConsoleSaveFileIO/compression.h" #include "minecraft/world/level/tile/Tile.h" #include "minecraft/world/phys/AABB.h" #include "minecraft/world/phys/HitResult.h" #include "minecraft/world/phys/Vec3.h" +#include "platform/PlatformTypes.h" +#include "platform/ShutdownManager.h" +#include "platform/sdl2/Input.h" +#include "platform/sdl2/Render.h" bool GameRenderer::anaglyph3d = false; int GameRenderer::anaglyphPass = 0; @@ -1115,6 +1114,7 @@ void GameRenderer::FinishedReassigning() { m_csDeleteStack.unlock(); } int GameRenderer::runUpdate(void* lpParam) { Minecraft* minecraft = Minecraft::GetInstance(); + int64_t updatePassToken = 0; Tesselator::CreateNewThreadStorage(1024 * 1024); Compression::UseDefaultThreadStorage(); RenderManager.InitialiseContext(); @@ -1150,8 +1150,10 @@ int GameRenderer::runUpdate(void* lpParam) { int count = 0; static const int MAX_DEFERRED_UPDATES = 10; bool shouldContinue = false; + const int64_t tickNowMs = ++updatePassToken; do { - shouldContinue = minecraft->levelRenderer->updateDirtyChunks(); + shouldContinue = + minecraft->levelRenderer->updateDirtyChunks(tickNowMs); count++; } while (shouldContinue && count < MAX_DEFERRED_UPDATES); @@ -1421,7 +1423,8 @@ void GameRenderer::renderLevel(float a, int64_t until) { int visibleWaterChunks = levelRenderer->render(cameraEntity, 1, a, updateChunks); - RenderManager.StateSetBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + RenderManager.StateSetBlendFunc(GL_SRC_ALPHA, + GL_ONE_MINUS_SRC_ALPHA); if (visibleWaterChunks > 0) { levelRenderer->render( diff --git a/targets/minecraft/client/renderer/LevelRenderer.cpp b/targets/minecraft/client/renderer/LevelRenderer.cpp index e427202df..d3c7e7478 100644 --- a/targets/minecraft/client/renderer/LevelRenderer.cpp +++ b/targets/minecraft/client/renderer/LevelRenderer.cpp @@ -11,27 +11,24 @@ #include #include +#include #include #include #include #include #include -#include "platform/PlatformTypes.h" -#include "platform/sdl2/Input.h" -#include "platform/sdl2/Render.h" #include "Chunk.h" #include "GameRenderer.h" +#include "Tesselator.h" #include "app/common/App_enums.h" #include "app/common/src/Audio/SoundEngine.h" #include "app/common/src/Colours/ColourTable.h" #include "app/common/src/Console_Debug_enum.h" -#include "app/linux/LinuxGame.h" #include "app/include/FrameProfiler.h" #include "app/include/MobSkinMemTextureProcessor.h" #include "app/include/stubs.h" -#include "Tesselator.h" -#include "util/StringHelpers.h" +#include "app/linux/LinuxGame.h" #include "java/Class.h" #include "java/JavaMath.h" #include "java/Random.h" @@ -111,10 +108,19 @@ #include "minecraft/world/phys/AABB.h" #include "minecraft/world/phys/HitResult.h" #include "minecraft/world/phys/Vec3.h" +#include "platform/PlatformTypes.h" +#include "platform/sdl2/Input.h" +#include "platform/sdl2/Render.h" +#include "util/StringHelpers.h" class Icon; class ItemInstance; +namespace { +std::atomic s_chunkBuildBatchCounter{0}; +std::atomic s_dirtyChunkPassToken{0}; +} // namespace + // #define DISABLE_SPU_CODE ResourceLocation LevelRenderer::MOON_LOCATION = @@ -216,6 +222,7 @@ LevelRenderer::LevelRenderer(Minecraft* mc, Textures* textures) { dirtyChunkPresent = false; lastDirtyChunkFound = 0; + m_dirtyChunksRequireFullScan = false; this->mc = mc; this->textures = textures; @@ -230,6 +237,8 @@ LevelRenderer::LevelRenderer(Minecraft* mc, Textures* textures) { globalChunkConnectivity = new uint64_t[getGlobalChunkCount()]; memset(globalChunkConnectivity, 0xFF, getGlobalChunkCount() * sizeof(uint64_t)); // 0xFF >> Fully open + m_globalClipChunks = + std::vector(getGlobalChunkCount(), nullptr); starList = MemoryTracker::genLists(4); @@ -1699,7 +1708,14 @@ void LevelRenderer::renderAdvancedClouds(float alpha) { RenderManager.StateSetEnableViewportClipPlanes(false); } -bool LevelRenderer::updateDirtyChunks() { +bool LevelRenderer::updateDirtyChunks(int64_t cachedNowMs) { + const int64_t nowMs = + (cachedNowMs >= 0) + ? cachedNowMs + : (s_dirtyChunkPassToken.fetch_add(1, std::memory_order_relaxed) + + 1); + const uint32_t chunkBuildBatchId = + s_chunkBuildBatchCounter.fetch_add(1, std::memory_order_relaxed) + 1; #if defined(_LARGE_WORLDS) struct NearestClipChunkSet { std::array, MAX_CONCURRENT_CHUNK_REBUILDS> @@ -1740,6 +1756,7 @@ bool LevelRenderer::updateDirtyChunks() { ClipChunk* nearChunk = nullptr; // Nearest chunk that is dirty int veryNearCount = 0; int minDistSq = 0x7fffffff; // Distances to this chunk + bool onlyRebuild = false; std::unique_lock dirtyChunksLock(m_csDirtyChunks); // Set a flag if we should only rebuild existing chunks, not create anything @@ -1756,11 +1773,165 @@ bool LevelRenderer::updateDirtyChunks() { } throttle++; */ - bool onlyRebuild = (memAlloc >= MAX_COMMANDBUFFER_ALLOCATIONS); + onlyRebuild = (memAlloc >= MAX_COMMANDBUFFER_ALLOCATIONS); // Move any dirty chunks stored in the lock free stack into global flags int index = 0; + bool hasNonStackDirtySignal = false; + std::vector poppedDirtyIndices; + + // We want to pop all the dirty chunk indices from the stack + auto evaluateSparseDirtyClipChunk = [&](ClipChunk* pClipChunk) { + if (pClipChunk == nullptr) return; + if (pClipChunk->globalIdx < 0) return; + + unsigned char flags = globalChunkFlags[pClipChunk->globalIdx]; + if (!(flags & CHUNK_FLAG_DIRTY)) return; + + Chunk* candidateChunk = pClipChunk->chunk; + if ((candidateChunk == nullptr) || + (candidateChunk->level == nullptr)) + return; + + int bestDistSq = INT_MAX; + int bestDistWeighted = INT_MAX; + bool hasRelevantPlayer = false; + + for (int p = 0; p < XUSER_MAX_COUNT; ++p) { + std::shared_ptr player = mc->localplayers[p]; + if (player == nullptr) continue; + if (level[p] != candidateChunk->level) continue; + + hasRelevantPlayer = true; + int xd = candidateChunk->xm - (int)player->x; + int yd = candidateChunk->ym - (int)player->y; + int zd = candidateChunk->zm - (int)player->z; + int distSq = xd * xd + yd * yd + zd * zd; + int distSqWeighted = xd * xd + yd * yd * 4 + zd * zd; + + if (distSq < bestDistSq) bestDistSq = distSq; + if (distSqWeighted < bestDistWeighted) + bestDistWeighted = distSqWeighted; + } + + if (!hasRelevantPlayer) return; + + if ((!onlyRebuild) || (flags & CHUNK_FLAG_COMPILED) || + (bestDistSq < 20 * 20)) { +#if defined(_LARGE_WORLDS) + bool isNearer = nearestClipChunks.wouldAccept(bestDistWeighted); +#else + bool isNearer = bestDistWeighted < minDistSq; +#endif + +#if defined(_CRITICAL_CHUNKS) + bool isCritical = (globalChunkFlags[pClipChunk->globalIdx] & + CHUNK_FLAG_CRITICAL) != 0; + if ((!veryNearCount && isNearer) || + ((bestDistSq < 20 * 20) && isCritical)) +#else + if (isNearer) +#endif + { + int chunkY = candidateChunk->y / CHUNK_SIZE; + if ((chunkY >= 0) && (chunkY < CHUNK_Y_COUNT)) { + LevelChunk* lc = candidateChunk->level->getChunkAt( + candidateChunk->x, candidateChunk->z); + if (!lc->isRenderChunkEmpty(chunkY * CHUNK_SIZE)) { + nearChunk = pClipChunk; + minDistSq = bestDistWeighted; +#if defined(_LARGE_WORLDS) + nearestClipChunks.insert(nearChunk, minDistSq); +#endif + } else { + candidateChunk->clearDirty(); + globalChunkFlags[pClipChunk->globalIdx] |= + CHUNK_FLAG_EMPTYBOTH; + } + } + } + +#if defined(_CRITICAL_CHUNKS) + if ((bestDistSq < 20 * 20) && + (globalChunkFlags[pClipChunk->globalIdx] & + CHUNK_FLAG_CRITICAL)) +#else + if (bestDistSq < 20 * 20) +#endif + { + veryNearCount++; + } + } + }; + + auto evaluateDirtyClipChunkForPlayer = [&](ClipChunk* pClipChunk, + int px, int py, int pz) { + if (pClipChunk == nullptr) return; + if (pClipChunk->globalIdx < 0) return; + + unsigned char flags = globalChunkFlags[pClipChunk->globalIdx]; + if (!(flags & CHUNK_FLAG_DIRTY)) return; + + Chunk* candidateChunk = pClipChunk->chunk; + if ((candidateChunk == nullptr) || + (candidateChunk->level == nullptr)) + return; + + int xd = candidateChunk->xm - px; + int yd = candidateChunk->ym - py; + int zd = candidateChunk->zm - pz; + int distSq = xd * xd + yd * yd + zd * zd; + int distSqWeighted = xd * xd + yd * yd * 4 + zd * zd; + + if ((!onlyRebuild) || (flags & CHUNK_FLAG_COMPILED) || + (distSq < 20 * 20)) { +#if defined(_LARGE_WORLDS) + bool isNearer = nearestClipChunks.wouldAccept(distSqWeighted); +#else + bool isNearer = distSqWeighted < minDistSq; +#endif + +#if defined(_CRITICAL_CHUNKS) + if ((!veryNearCount && isNearer) || + (distSq < 20 * 20 && + (globalChunkFlags[pClipChunk->globalIdx] & + CHUNK_FLAG_CRITICAL))) +#else + if (isNearer) +#endif + { + int chunkY = candidateChunk->y / CHUNK_SIZE; + if ((chunkY >= 0) && (chunkY < CHUNK_Y_COUNT)) { + LevelChunk* lc = candidateChunk->level->getChunkAt( + candidateChunk->x, candidateChunk->z); + if (!lc->isRenderChunkEmpty(chunkY * CHUNK_SIZE)) { + nearChunk = pClipChunk; + minDistSq = distSqWeighted; +#if defined(_LARGE_WORLDS) + nearestClipChunks.insert(nearChunk, minDistSq); +#endif + } else { + candidateChunk->clearDirty(); + globalChunkFlags[pClipChunk->globalIdx] |= + CHUNK_FLAG_EMPTYBOTH; + } + } + } + +#if defined(_CRITICAL_CHUNKS) + if (distSq < 20 * 20 && + ((globalChunkFlags[pClipChunk->globalIdx] & + CHUNK_FLAG_CRITICAL))) +#else + if (distSq < 20 * 20) +#endif + { + veryNearCount++; + } + } + }; + do { // See comment on dirtyChunksLockFreeStack.Push() regarding details // of this casting/subtracting -2. @@ -1775,7 +1946,10 @@ bool LevelRenderer::updateDirtyChunks() { true; // 1 is a special value passed to let this thread // know that a chunk which isn't on this stack has // been set to dirty - else if (index > 1) { + if (index == 1) { + hasNonStackDirtySignal = true; + m_dirtyChunksRequireFullScan = true; + } else if (index > 1) { int i2 = index - 2; if (i2 >= DIMENSION_OFFSETS[2]) { i2 -= DIMENSION_OFFSETS[2]; @@ -1798,148 +1972,69 @@ bool LevelRenderer::updateDirtyChunks() { #endif dirtyChunkPresent = true; + poppedDirtyIndices.push_back(index - 2); } } while (index); // Only bother searching round all the chunks if we have some dirty // chunk(s) if (dirtyChunkPresent) { - lastDirtyChunkFound = System::currentTimeMillis(); + lastDirtyChunkFound = nowMs; - // Find nearest chunk that is dirty - 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 So take a reference to the player object now. As it is a - // shared_ptr it should live as long as we need it - std::shared_ptr player = mc->localplayers[p]; - if (player == nullptr) continue; - if (chunks[p].empty()) continue; - if (level[p] == nullptr) continue; - if (chunks[p].size() != xChunks * zChunks * CHUNK_Y_COUNT) - continue; - int px = (int)player->x; - int py = (int)player->y; - int pz = (int)player->z; + bool useSparsePath = (!m_dirtyChunksRequireFullScan) && + (!hasNonStackDirtySignal) && + (!poppedDirtyIndices.empty()); - // app.DebugPrintf("!! %d %d %d, %d %d %d - //{%d,%d} - //",px,py,pz,stackChunkDirty,nonStackChunkDirty,onlyRebuild, - // xChunks, zChunks); + if (useSparsePath) { + std::sort(poppedDirtyIndices.begin(), poppedDirtyIndices.end()); + poppedDirtyIndices.erase(std::unique(poppedDirtyIndices.begin(), + poppedDirtyIndices.end()), + poppedDirtyIndices.end()); - int considered = 0; - int wouldBeNearButEmpty = 0; - for (int x = 0; x < xChunks; x++) { - for (int z = 0; z < zChunks; z++) { - for (int y = 0; y < CHUNK_Y_COUNT; y++) { - 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 + for (int dirtyIdx : poppedDirtyIndices) { + if (dirtyIdx < 0) continue; + if ((size_t)dirtyIdx >= m_globalClipChunks.size()) continue; + evaluateSparseDirtyClipChunk(m_globalClipChunks[dirtyIdx]); + } + } else { + bool sawAnyDirtyFlag = false; + for (int p = 0; p < XUSER_MAX_COUNT; p++) { + std::shared_ptr player = mc->localplayers[p]; + if (player == nullptr) continue; + if (chunks[p].empty()) continue; + if (level[p] == nullptr) continue; + if (chunks[p].size() != xChunks * zChunks * CHUNK_Y_COUNT) + continue; - if (globalChunkFlags[pClipChunk->globalIdx] & - CHUNK_FLAG_DIRTY) { - if ((!onlyRebuild) || - globalChunkFlags[pClipChunk->globalIdx] & - CHUNK_FLAG_COMPILED || - (distSq < - 20 * 20)) // 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 - { - considered++; - // Is this chunk nearer than our nearest? -#if defined(_LARGE_WORLDS) - bool isNearer = - nearestClipChunks.wouldAccept( - distSqWeighted); -#else - bool isNearer = distSqWeighted < minDistSq; -#endif + const int px = (int)player->x; + const int py = (int)player->y; + const int pz = (int)player->z; -#if defined(_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; -#if defined(_LARGE_WORLDS) - nearestClipChunks.insert(nearChunk, - minDistSq); -#endif - } else { - chunk->clearDirty(); - globalChunkFlags[pClipChunk - ->globalIdx] |= - CHUNK_FLAG_EMPTYBOTH; - wouldBeNearButEmpty++; - } - } + // app.DebugPrintf("!! %d %d %d, %d %d %d + //{%d,%d} + //",px,py,pz,stackChunkDirty,nonStackChunkDirty,onlyRebuild, + // xChunks, zChunks); -#if defined(_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++; - } + for (int x = 0; x < xChunks; x++) { + for (int z = 0; z < zChunks; z++) { + for (int y = 0; y < CHUNK_Y_COUNT; y++) { + ClipChunk* pClipChunk = + &chunks[p][(z * yChunks + y) * xChunks + x]; + if (pClipChunk->globalIdx < 0) continue; + if (globalChunkFlags[pClipChunk->globalIdx] & + CHUNK_FLAG_DIRTY) { + sawAnyDirtyFlag = true; } + evaluateDirtyClipChunkForPlayer(pClipChunk, px, + py, pz); } } } } - // app.DebugPrintf("[%d,%d,%d]\n",nearestClipChunks.empty(),considered,wouldBeNearButEmpty); + + if (!sawAnyDirtyFlag) { + m_dirtyChunksRequireFullScan = false; + } } } } @@ -1973,7 +2068,15 @@ bool LevelRenderer::updateDirtyChunks() { // whilst we are doing this copy. The copy will then be // guaranteed to be consistent whilst rebuilding takes place // outside of that lock. + const ChunkBuildContext buildContext = { + nowMs, + chunkBuildBatchId, + static_cast(std::min(veryNearCount, 0xffff)), + onlyRebuild, + veryNearCount > 0, + }; permaChunk[index].makeCopyForRebuild(chunk); + permaChunk[index].setBuildContext(buildContext); ++index; } dirtyChunksLock.unlock(); @@ -2064,7 +2167,15 @@ bool LevelRenderer::updateDirtyChunks() { // means that any chunks can't be repositioned whilst we are doing // this copy. The copy will then be guaranteed to be consistent // whilst rebuilding takes place outside of that lock. + const ChunkBuildContext buildContext = { + nowMs, + chunkBuildBatchId, + static_cast(std::min(veryNearCount, 0xffff)), + onlyRebuild, + veryNearCount > 0, + }; permaChunk.makeCopyForRebuild(chunk); + permaChunk.setBuildContext(buildContext); dirtyChunksLock.unlock(); } // static int64_t totalTime = 0; @@ -2086,8 +2197,8 @@ bool LevelRenderer::updateDirtyChunks() { // 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) { + if ((nowMs - lastDirtyChunkFound) > + FORCE_DIRTY_CHUNK_CHECK_PERIOD_PASSES) { dirtyChunkPresent = true; } else { dirtyChunkPresent = false; @@ -2323,6 +2434,12 @@ void LevelRenderer::setDirty(int x0, int y0, int z0, int x1, int y1, int z1, int _y1 = Mth::intFloorDiv(y1, CHUNK_SIZE); int _z1 = Mth::intFloorDiv(z1, CHUNK_XZSIZE); +#if !defined(_CRITICAL_CHUNKS) + std::vector pendingDirtyIndices; + pendingDirtyIndices.reserve( + ((_x1 - _x0 + 1) * (_y1 - _y0 + 1) * (_z1 - _z0 + 1))); +#endif + for (int x = _x0; x <= _x1; x++) { for (int y = _y0; y <= _y1; y++) { for (int z = _z0; z <= _z1; z++) { @@ -2393,8 +2510,8 @@ void LevelRenderer::setDirty(int x0, int y0, int z0, int x1, int y1, int z1, dirtyChunksLockFreeStack.Push((int*)(index)); #else - dirtyChunksLockFreeStack.Push( - (int*)(intptr_t)(uintptr_t)(index + 2)); + pendingDirtyIndices.push_back( + (intptr_t)(uintptr_t)(index + 2)); #endif } // setGlobalChunkFlag(x * 16, y * @@ -2402,6 +2519,19 @@ void LevelRenderer::setDirty(int x0, int y0, int z0, int x1, int y1, int z1, } } } + +#if !defined(_CRITICAL_CHUNKS) + if (!pendingDirtyIndices.empty()) { + std::sort(pendingDirtyIndices.begin(), pendingDirtyIndices.end()); + pendingDirtyIndices.erase( + std::unique(pendingDirtyIndices.begin(), pendingDirtyIndices.end()), + pendingDirtyIndices.end()); + + for (intptr_t encodedIndex : pendingDirtyIndices) { + dirtyChunksLockFreeStack.Push((int*)encodedIndex); + } + } +#endif } void LevelRenderer::tileChanged(int x, int y, int z) { diff --git a/targets/minecraft/client/renderer/LevelRenderer.h b/targets/minecraft/client/renderer/LevelRenderer.h index c66590063..7381f543b 100644 --- a/targets/minecraft/client/renderer/LevelRenderer.h +++ b/targets/minecraft/client/renderer/LevelRenderer.h @@ -1,15 +1,15 @@ #pragma once +#include "OffsettedRenderList.h" #include "app/include/NetTypes.h" #include "app/include/SkinBox.h" #include "app/include/XboxStubs.h" -#include "OffsettedRenderList.h" -#include "platform/C4JThread.h" -#include "util/Definitions.h" #include "java/JavaIntHash.h" #include "minecraft/core/particles/ParticleTypes.h" #include "minecraft/world/level/Level.h" #include "minecraft/world/level/LevelListener.h" #include "minecraft/world/phys/AABB.h" +#include "platform/C4JThread.h" +#include "util/Definitions.h" class ClipChunk; class HitResult; @@ -119,7 +119,7 @@ public: void renderClouds(float alpha); bool isInCloud(double x, double y, double z, float alpha); void renderAdvancedClouds(float alpha); - bool updateDirtyChunks(); + bool updateDirtyChunks(int64_t cachedNowMs = -1); public: void renderHit(std::shared_ptr player, HitResult* h, int mode, @@ -225,6 +225,8 @@ private: uint64_t getGlobalChunkConnectivity(int index); std::vector m_bfsGrid; std::vector m_bfsVisitedFaces[4]; + std::vector m_globalClipChunks; + bool m_dirtyChunksRequireFullScan; std::unordered_map destroyingBlocks; Icon** breakingTextures; @@ -355,7 +357,7 @@ public: bool dirtyChunkPresent; int64_t lastDirtyChunkFound; - static const int FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS = 250; + static const int FORCE_DIRTY_CHUNK_CHECK_PERIOD_PASSES = 64; #if defined(_LARGE_WORLDS) static const int MAX_CONCURRENT_CHUNK_REBUILDS = 4; diff --git a/targets/minecraft/client/renderer/TileRenderer.cpp b/targets/minecraft/client/renderer/TileRenderer.cpp index 0dbeb2994..a60017fda 100644 --- a/targets/minecraft/client/renderer/TileRenderer.cpp +++ b/targets/minecraft/client/renderer/TileRenderer.cpp @@ -13,13 +13,13 @@ #include #include -#include "platform/sdl2/Render.h" +#include "Chunk.h" #include "EntityTileRenderer.h" #include "GameRenderer.h" +#include "Tesselator.h" #include "app/common/App_enums.h" #include "app/common/src/Colours/ColourTable.h" #include "app/include/FrameProfiler.h" -#include "Tesselator.h" #include "minecraft/Direction.h" #include "minecraft/Facing.h" #include "minecraft/SharedConstants.h" @@ -69,6 +69,7 @@ #include "minecraft/world/level/tile/piston/PistonBaseTile.h" #include "minecraft/world/level/tile/piston/PistonExtensionTile.h" #include "minecraft/world/phys/Vec3.h" +#include "platform/sdl2/Render.h" bool TileRenderer::fancy = true; @@ -101,6 +102,7 @@ void TileRenderer::_init() { yMin = 0; zMin = 0; cache = nullptr; + buildContext = nullptr; } bool TileRenderer::isTranslucentAt(LevelSource* level, int x, int y, int z) { @@ -204,7 +206,8 @@ int TileRenderer::getLightColor(Tile* tt, LevelSource* level, int x, int y, } TileRenderer::TileRenderer(LevelSource* level, int xMin, int yMin, int zMin, - unsigned char* tileIds) { + unsigned char* tileIds, + const ChunkBuildContext* buildContext) { this->level = level; _init(); this->xMin = xMin; @@ -214,6 +217,7 @@ TileRenderer::TileRenderer(LevelSource* level, int xMin, int yMin, int zMin, this->yMin2 = yMin - 2; this->zMin2 = zMin - 2; this->tileIds = tileIds; + this->buildContext = buildContext; cache = new unsigned int[32 * 32 * 32]; memset(cache, 0, 32 * 32 * 32 * sizeof(unsigned int)); } @@ -232,6 +236,10 @@ TileRenderer::TileRenderer() { _init(); } +void TileRenderer::setBuildContext(const ChunkBuildContext* buildContext) { + this->buildContext = buildContext; +} + void TileRenderer::setFixedTexture(Icon* fixedTexture) { this->fixedTexture = fixedTexture; } @@ -5254,10 +5262,10 @@ bool TileRenderer::tesselateBlockInWorldWithAmbienceOcclusionTexLighting( ll0yZ = getShadeBrightness(tt, level, pX, pY, pZ + 1); llXy0 = getShadeBrightness(tt, level, pX + 1, pY, pZ); - bool llTransXy0 = Tile::transculent[level->getTile(pX + 1, pY - 1, pZ)]; - bool llTransxy0 = Tile::transculent[level->getTile(pX - 1, pY - 1, pZ)]; - bool llTrans0yZ = Tile::transculent[level->getTile(pX, pY - 1, pZ + 1)]; - bool llTrans0yz = Tile::transculent[level->getTile(pX, pY - 1, pZ - 1)]; + bool llTransXy0 = isTranslucentAt(level, pX + 1, pY - 1, pZ); + bool llTransxy0 = isTranslucentAt(level, pX - 1, pY - 1, pZ); + bool llTrans0yZ = isTranslucentAt(level, pX, pY - 1, pZ + 1); + bool llTrans0yz = isTranslucentAt(level, pX, pY - 1, pZ - 1); if (llTrans0yz || llTransxy0) { llxyz = getShadeBrightness(tt, level, pX - 1, pY, pZ - 1); @@ -5346,10 +5354,10 @@ bool TileRenderer::tesselateBlockInWorldWithAmbienceOcclusionTexLighting( ll0Yz = getShadeBrightness(tt, level, pX, pY, pZ - 1); ll0YZ = getShadeBrightness(tt, level, pX, pY, pZ + 1); - bool llTransXY0 = Tile::transculent[level->getTile(pX + 1, pY + 1, pZ)]; - bool llTransxY0 = Tile::transculent[level->getTile(pX - 1, pY + 1, pZ)]; - bool llTrans0YZ = Tile::transculent[level->getTile(pX, pY + 1, pZ + 1)]; - bool llTrans0Yz = Tile::transculent[level->getTile(pX, pY + 1, pZ - 1)]; + bool llTransXY0 = isTranslucentAt(level, pX + 1, pY + 1, pZ); + bool llTransxY0 = isTranslucentAt(level, pX - 1, pY + 1, pZ); + bool llTrans0YZ = isTranslucentAt(level, pX, pY + 1, pZ + 1); + bool llTrans0Yz = isTranslucentAt(level, pX, pY + 1, pZ - 1); if (llTrans0Yz || llTransxY0) { llxYz = getShadeBrightness(tt, level, pX - 1, pY, pZ - 1); @@ -5429,10 +5437,10 @@ bool TileRenderer::tesselateBlockInWorldWithAmbienceOcclusionTexLighting( cc0Yz = getLightColor(tt, level, pX, pY + 1, pZ); ccX0z = getLightColor(tt, level, pX + 1, pY, pZ); - bool llTransX0z = Tile::transculent[level->getTile(pX + 1, pY, pZ - 1)]; - bool llTransx0z = Tile::transculent[level->getTile(pX - 1, pY, pZ - 1)]; - bool llTrans0Yz = Tile::transculent[level->getTile(pX, pY + 1, pZ - 1)]; - bool llTrans0yz = Tile::transculent[level->getTile(pX, pY - 1, pZ - 1)]; + bool llTransX0z = isTranslucentAt(level, pX + 1, pY, pZ - 1); + bool llTransx0z = isTranslucentAt(level, pX - 1, pY, pZ - 1); + bool llTrans0Yz = isTranslucentAt(level, pX, pY + 1, pZ - 1); + bool llTrans0yz = isTranslucentAt(level, pX, pY - 1, pZ - 1); if (llTransx0z || llTrans0yz) { llxyz = getShadeBrightness(tt, level, pX - 1, pY - 1, pZ); @@ -5597,10 +5605,10 @@ bool TileRenderer::tesselateBlockInWorldWithAmbienceOcclusionTexLighting( cc0yZ = getLightColor(tt, level, pX, pY - 1, pZ); cc0YZ = getLightColor(tt, level, pX, pY + 1, pZ); - bool llTransX0Z = Tile::transculent[level->getTile(pX + 1, pY, pZ + 1)]; - bool llTransx0Z = Tile::transculent[level->getTile(pX - 1, pY, pZ + 1)]; - bool llTrans0YZ = Tile::transculent[level->getTile(pX, pY + 1, pZ + 1)]; - bool llTrans0yZ = Tile::transculent[level->getTile(pX, pY - 1, pZ + 1)]; + bool llTransX0Z = isTranslucentAt(level, pX + 1, pY, pZ + 1); + bool llTransx0Z = isTranslucentAt(level, pX - 1, pY, pZ + 1); + bool llTrans0YZ = isTranslucentAt(level, pX, pY + 1, pZ + 1); + bool llTrans0yZ = isTranslucentAt(level, pX, pY - 1, pZ + 1); if (llTransx0Z || llTrans0yZ) { llxyZ = getShadeBrightness(tt, level, pX - 1, pY - 1, pZ); @@ -5765,10 +5773,10 @@ bool TileRenderer::tesselateBlockInWorldWithAmbienceOcclusionTexLighting( ccx0Z = getLightColor(tt, level, pX, pY, pZ + 1); ccxY0 = getLightColor(tt, level, pX, pY + 1, pZ); - bool llTransxY0 = Tile::transculent[level->getTile(pX - 1, pY + 1, pZ)]; - bool llTransxy0 = Tile::transculent[level->getTile(pX - 1, pY - 1, pZ)]; - bool llTransx0z = Tile::transculent[level->getTile(pX - 1, pY, pZ - 1)]; - bool llTransx0Z = Tile::transculent[level->getTile(pX - 1, pY, pZ + 1)]; + bool llTransxY0 = isTranslucentAt(level, pX - 1, pY + 1, pZ); + bool llTransxy0 = isTranslucentAt(level, pX - 1, pY - 1, pZ); + bool llTransx0z = isTranslucentAt(level, pX - 1, pY, pZ - 1); + bool llTransx0Z = isTranslucentAt(level, pX - 1, pY, pZ + 1); if (llTransx0z || llTransxy0) { llxyz = getShadeBrightness(tt, level, pX, pY - 1, pZ - 1); @@ -5929,10 +5937,10 @@ bool TileRenderer::tesselateBlockInWorldWithAmbienceOcclusionTexLighting( ccX0Z = getLightColor(tt, level, pX, pY, pZ + 1); ccXY0 = getLightColor(tt, level, pX, pY + 1, pZ); - bool llTransXY0 = Tile::transculent[level->getTile(pX + 1, pY + 1, pZ)]; - bool llTransXy0 = Tile::transculent[level->getTile(pX + 1, pY - 1, pZ)]; - bool llTransX0Z = Tile::transculent[level->getTile(pX + 1, pY, pZ + 1)]; - bool llTransX0z = Tile::transculent[level->getTile(pX + 1, pY, pZ - 1)]; + bool llTransXY0 = isTranslucentAt(level, pX + 1, pY + 1, pZ); + bool llTransXy0 = isTranslucentAt(level, pX + 1, pY - 1, pZ); + bool llTransX0Z = isTranslucentAt(level, pX + 1, pY, pZ + 1); + bool llTransX0z = isTranslucentAt(level, pX + 1, pY, pZ - 1); if (llTransXy0 || llTransX0z) { llXyz = getShadeBrightness(tt, level, pX, pY - 1, pZ - 1); diff --git a/targets/minecraft/client/renderer/TileRenderer.h b/targets/minecraft/client/renderer/TileRenderer.h index ab0d34771..19c676029 100644 --- a/targets/minecraft/client/renderer/TileRenderer.h +++ b/targets/minecraft/client/renderer/TileRenderer.h @@ -30,6 +30,7 @@ class BeaconTile; class HopperTile; class Icon; class Minecraft; +struct ChunkBuildContext; class TileRenderer { friend class FallingTileRenderer; @@ -62,6 +63,7 @@ public: bool isTranslucentAt(LevelSource* level, int x, int y, int z); unsigned int* cache; unsigned char* tileIds; + const ChunkBuildContext* buildContext; static const unsigned int cache_getLightColor_valid = 0x80000000; static const unsigned int cache_isTranslucentAt_valid = 0x40000000; static const unsigned int cache_isSolidBlockingTile_valid = 0x20000000; @@ -74,10 +76,12 @@ public: public: TileRenderer(LevelSource* level, int xMin, int yMin, int zMin, - unsigned char* tileIds); + unsigned char* tileIds, + const ChunkBuildContext* buildContext = nullptr); TileRenderer(LevelSource* level); TileRenderer(); ~TileRenderer(); + void setBuildContext(const ChunkBuildContext* buildContext); void setFixedTexture(Icon* fixedTexture); void clearFixedTexture(); bool hasFixedTexture();