perf(render): optimise tile entity cleanup and profiling

This commit is contained in:
MatthewBeshay 2026-03-30 09:26:35 +11:00 committed by Tropical
parent 51ad1434db
commit c6fa51d592
11 changed files with 213 additions and 152 deletions

View file

@ -8,6 +8,7 @@
#include "../../Minecraft.World/Headers/net.minecraft.world.level.tile.entity.h"
#include "LevelRenderer.h"
#include "../Utils/FrameProfiler.h"
#include <unordered_set>
#ifdef __PS3__
#include "../Platform/PS3/SPU_Tasks/ChunkUpdate/ChunkRebuildData.h"
@ -36,6 +37,66 @@ Tesselator* Chunk::t = Tesselator::getInstance();
#endif
LevelRenderer* Chunk::levelRenderer;
void Chunk::reconcileRenderableTileEntities(
const std::vector<std::shared_ptr<TileEntity> >& renderableTileEntities) {
int key =
levelRenderer->getGlobalIndexForChunk(this->x, this->y, this->z, level);
AUTO_VAR(it, globalRenderableTileEntities->find(key));
if (!renderableTileEntities.empty()) {
std::unordered_set<TileEntity*> currentRenderableTileEntitySet;
currentRenderableTileEntitySet.reserve(renderableTileEntities.size());
for (size_t i = 0; i < renderableTileEntities.size(); i++) {
currentRenderableTileEntitySet.insert(renderableTileEntities[i].get());
}
if (it != globalRenderableTileEntities->end()) {
LevelRenderer::RenderableTileEntityBucket& existingBucket =
it->second;
for (AUTO_VAR(it2, existingBucket.tiles.begin());
it2 != existingBucket.tiles.end(); it2++) {
TileEntity* tileEntity = (*it2).get();
if (currentRenderableTileEntitySet.find(tileEntity) ==
currentRenderableTileEntitySet.end()) {
(*it2)->setRenderRemoveStage(
TileEntity::e_RenderRemoveStageFlaggedAtChunk);
levelRenderer->queueRenderableTileEntityForRemoval_Locked(
key, tileEntity);
} else {
(*it2)->setRenderRemoveStage(
TileEntity::e_RenderRemoveStageKeep);
}
}
for (size_t i = 0; i < renderableTileEntities.size(); i++) {
renderableTileEntities[i]->setRenderRemoveStage(
TileEntity::e_RenderRemoveStageKeep);
if (existingBucket.indexByTile.find(renderableTileEntities[i].get()) ==
existingBucket.indexByTile.end()) {
levelRenderer->addRenderableTileEntity_Locked(
key, renderableTileEntities[i]);
}
}
} else {
for (size_t i = 0; i < renderableTileEntities.size(); i++) {
renderableTileEntities[i]->setRenderRemoveStage(
TileEntity::e_RenderRemoveStageKeep);
levelRenderer->addRenderableTileEntity_Locked(
key, renderableTileEntities[i]);
}
}
} else if (it != globalRenderableTileEntities->end()) {
for (AUTO_VAR(it2, it->second.tiles.begin());
it2 != it->second.tiles.end();
it2++) {
(*it2)->setRenderRemoveStage(
TileEntity::e_RenderRemoveStageFlaggedAtChunk);
levelRenderer->queueRenderableTileEntityForRemoval_Locked(key,
(*it2).get());
}
}
}
// TODO - 4J see how input entity vector is set up and decide what way is best
// to pass this to the function
Chunk::Chunk(Level* level, LevelRenderer::rteMap& globalRenderableTileEntities,
@ -519,57 +580,8 @@ void Chunk::rebuild() {
// from the dimension and chunk position (using same index as is used for
// global flags)
#if 1
int key =
levelRenderer->getGlobalIndexForChunk(this->x, this->y, this->z, level);
EnterCriticalSection(globalRenderableTileEntities_cs);
if (renderableTileEntities.size()) {
AUTO_VAR(it, globalRenderableTileEntities->find(key));
if (it != globalRenderableTileEntities->end()) {
// We've got some renderable tile entities that we want associated
// with this chunk, and an existing list of things that used to be.
// We need to flag any that we don't need any more to be removed,
// keep those that we do, and add any new ones
// First pass - flag everything already existing to be removed
for (AUTO_VAR(it2, it->second.begin()); it2 != it->second.end();
it2++) {
(*it2)->setRenderRemoveStage(
TileEntity::e_RenderRemoveStageFlaggedAtChunk);
}
// Now go through the current list. If these are already in the
// list, then unflag the remove flag. If they aren't, then add
for (int i = 0; i < renderableTileEntities.size(); i++) {
AUTO_VAR(it2, find(it->second.begin(), it->second.end(),
renderableTileEntities[i]));
if (it2 == it->second.end()) {
(*globalRenderableTileEntities)[key].push_back(
renderableTileEntities[i]);
} else {
(*it2)->setRenderRemoveStage(
TileEntity::e_RenderRemoveStageKeep);
}
}
} else {
// Easy case - nothing already existing for this chunk. Add them all
// in.
for (int i = 0; i < renderableTileEntities.size(); i++) {
(*globalRenderableTileEntities)[key].push_back(
renderableTileEntities[i]);
}
}
} else {
// Another easy case - we don't want any renderable tile entities
// associated with this chunk. Flag all to be removed.
AUTO_VAR(it, globalRenderableTileEntities->find(key));
if (it != globalRenderableTileEntities->end()) {
for (AUTO_VAR(it2, it->second.begin()); it2 != it->second.end();
it2++) {
(*it2)->setRenderRemoveStage(
TileEntity::e_RenderRemoveStageFlaggedAtChunk);
}
}
}
reconcileRenderableTileEntities(renderableTileEntities);
LeaveCriticalSection(globalRenderableTileEntities_cs);
PIXEndNamedEvent();
#else
@ -831,57 +843,8 @@ void Chunk::rebuild_SPU() {
// from the dimension and chunk position (using same index as is used for
// global flags)
#if 1
int key =
levelRenderer->getGlobalIndexForChunk(this->x, this->y, this->z, level);
EnterCriticalSection(globalRenderableTileEntities_cs);
if (renderableTileEntities.size()) {
AUTO_VAR(it, globalRenderableTileEntities->find(key));
if (it != globalRenderableTileEntities->end()) {
// We've got some renderable tile entities that we want associated
// with this chunk, and an existing list of things that used to be.
// We need to flag any that we don't need any more to be removed,
// keep those that we do, and add any new ones
// First pass - flag everything already existing to be removed
for (AUTO_VAR(it2, it->second.begin()); it2 != it->second.end();
it2++) {
(*it2)->setRenderRemoveStage(
TileEntity::e_RenderRemoveStageFlaggedAtChunk);
}
// Now go through the current list. If these are already in the
// list, then unflag the remove flag. If they aren't, then add
for (int i = 0; i < renderableTileEntities.size(); i++) {
AUTO_VAR(it2, find(it->second.begin(), it->second.end(),
renderableTileEntities[i]));
if (it2 == it->second.end()) {
(*globalRenderableTileEntities)[key].push_back(
renderableTileEntities[i]);
} else {
(*it2)->setRenderRemoveStage(
TileEntity::e_RenderRemoveStageKeep);
}
}
} else {
// Easy case - nothing already existing for this chunk. Add them all
// in.
for (int i = 0; i < renderableTileEntities.size(); i++) {
(*globalRenderableTileEntities)[key].push_back(
renderableTileEntities[i]);
}
}
} else {
// Another easy case - we don't want any renderable tile entities
// associated with this chunk. Flag all to be removed.
AUTO_VAR(it, globalRenderableTileEntities->find(key));
if (it != globalRenderableTileEntities->end()) {
for (AUTO_VAR(it2, it->second.begin()); it2 != it->second.end();
it2++) {
(*it2)->setRenderRemoveStage(
TileEntity::e_RenderRemoveStageFlaggedAtChunk);
}
}
}
reconcileRenderableTileEntities(renderableTileEntities);
LeaveCriticalSection(globalRenderableTileEntities_cs);
#else
// Find the removed ones:
@ -1108,15 +1071,19 @@ uint64_t Chunk::computeConnectivity(const uint8_t* tileIds) {
}
void Chunk::reset() {
if (assigned) {
int oldKey = -1;
bool retireRenderableTileEntities = false;
EnterCriticalSection(&levelRenderer->m_csDirtyChunks);
oldKey = levelRenderer->getGlobalIndexForChunk(x, y, z, level);
unsigned char refCount =
levelRenderer->decGlobalChunkRefCount(x, y, z, level);
assigned = false;
// printf("\t\t [dec] refcount %d at %d, %d,
//%d\n",refCount,x,y,z);
if (refCount == 0) {
int lists =
levelRenderer->getGlobalIndexForChunk(x, y, z, level) * 2;
if (refCount == 0 && oldKey != -1) {
retireRenderableTileEntities = true;
int lists = oldKey * 2;
if (lists >= 0) {
lists += levelRenderer->chunkLists;
for (int i = 0; i < 2; i++) {
@ -1128,6 +1095,10 @@ void Chunk::reset() {
}
}
LeaveCriticalSection(&levelRenderer->m_csDirtyChunks);
if (retireRenderableTileEntities) {
levelRenderer->retireRenderableTileEntitiesForChunkKey(oldKey);
}
}
clipChunk->visible = false;

View file

@ -69,6 +69,8 @@ public:
private:
void translateToPos();
void reconcileRenderableTileEntities(
const std::vector<std::shared_ptr<TileEntity> >& renderableTileEntities);
public:
void makeCopyForRebuild(Chunk* source);

View file

@ -410,8 +410,12 @@ void LevelRenderer::setLevel(int playerIndex, MultiPlayerLevel* level) {
// tile entities in the world dissappear We should only do this when
// actually exiting the game, so only when the primary player sets there
// level to NULL
if (playerIndex == ProfileManager.GetPrimaryPad())
if (playerIndex == ProfileManager.GetPrimaryPad()) {
EnterCriticalSection(&m_csRenderableTileEntities);
renderableTileEntities.clear();
m_renderableTileEntitiesPendingRemoval.clear();
LeaveCriticalSection(&m_csRenderableTileEntities);
}
}
}
@ -641,34 +645,13 @@ void LevelRenderer::renderEntities(Vec3* cam, Culler* culler, float a) {
// Don't render if it isn't in the same dimension as this player
if (!isGlobalIndexInSameDimension(idx, level[playerIndex])) continue;
for (AUTO_VAR(it2, it->second.begin()); it2 != it->second.end();
for (AUTO_VAR(it2, it->second.tiles.begin());
it2 != it->second.tiles.end();
it2++) {
TileEntityRenderDispatcher::instance->render(*it2, a);
}
}
// Now consider if any of these renderable tile entities have been flagged
// for removal, and if so, remove
for (AUTO_VAR(it, renderableTileEntities.begin());
it != renderableTileEntities.end();) {
int idx = it->first;
for (AUTO_VAR(it2, it->second.begin()); it2 != it->second.end();) {
// If it has been flagged for removal, remove
if ((*it2)->shouldRemoveForRender()) {
it2 = it->second.erase(it2);
} else {
it2++;
}
}
// If there aren't any entities left for this key, then delete the key
if (it->second.size() == 0) {
it = renderableTileEntities.erase(it);
} else {
it++;
}
}
LeaveCriticalSection(&m_csRenderableTileEntities);
@ -4181,16 +4164,87 @@ unsigned char LevelRenderer::decGlobalChunkRefCount(int x, int y, int z,
}
}
void LevelRenderer::queueRenderableTileEntityForRemoval_Locked(
int key, TileEntity* tileEntity) {
m_renderableTileEntitiesPendingRemoval[key].insert(tileEntity);
}
void LevelRenderer::addRenderableTileEntity_Locked(
int key, const std::shared_ptr<TileEntity>& tileEntity) {
RenderableTileEntityBucket& bucket = renderableTileEntities[key];
TileEntity* tileEntityPtr = tileEntity.get();
if (bucket.indexByTile.find(tileEntityPtr) != bucket.indexByTile.end()) {
return;
}
size_t index = bucket.tiles.size();
bucket.tiles.push_back(tileEntity);
bucket.indexByTile.insert(std::make_pair(tileEntityPtr, index));
}
void LevelRenderer::eraseRenderableTileEntity_Locked(
RenderableTileEntityBucket& bucket, TileEntity* tileEntity) {
auto it = bucket.indexByTile.find(tileEntity);
if (it == bucket.indexByTile.end()) {
return;
}
size_t index = it->second;
size_t lastIndex = bucket.tiles.size() - 1;
if (index != lastIndex) {
std::shared_ptr<TileEntity> moved = bucket.tiles[lastIndex];
bucket.tiles[index] = moved;
bucket.indexByTile[moved.get()] = index;
}
bucket.tiles.pop_back();
bucket.indexByTile.erase(it);
}
void LevelRenderer::retireRenderableTileEntitiesForChunkKey(int key) {
if (key == -1) return;
EnterCriticalSection(&m_csRenderableTileEntities);
renderableTileEntities.erase(key);
m_renderableTileEntitiesPendingRemoval.erase(key);
LeaveCriticalSection(&m_csRenderableTileEntities);
}
// 4J added
void LevelRenderer::fullyFlagRenderableTileEntitiesToBeRemoved() {
FRAME_PROFILE_SCOPE(RenderableTileEntityCleanup);
EnterCriticalSection(&m_csRenderableTileEntities);
AUTO_VAR(itChunkEnd, renderableTileEntities.end());
for (AUTO_VAR(it, renderableTileEntities.begin()); it != itChunkEnd; it++) {
AUTO_VAR(itTEEnd, it->second.end());
for (AUTO_VAR(it2, it->second.begin()); it2 != itTEEnd; it2++) {
(*it2)->upgradeRenderRemoveStage();
if (m_renderableTileEntitiesPendingRemoval.empty()) {
LeaveCriticalSection(&m_csRenderableTileEntities);
return;
}
auto itKeyEnd = m_renderableTileEntitiesPendingRemoval.end();
for (auto itKey = m_renderableTileEntitiesPendingRemoval.begin();
itKey != itKeyEnd; itKey++) {
auto itChunk = renderableTileEntities.find(itKey->first);
if (itChunk == renderableTileEntities.end()) continue;
RenderableTileEntityBucket& bucket = itChunk->second;
for (AUTO_VAR(itPending, itKey->second.begin());
itPending != itKey->second.end(); itPending++) {
if (bucket.indexByTile.find(*itPending) == bucket.indexByTile.end()) {
continue;
}
if (!(*itPending)->finalizeRenderRemoveStage()) {
continue;
}
eraseRenderableTileEntity_Locked(bucket, *itPending);
}
if (bucket.tiles.empty()) {
renderableTileEntities.erase(itChunk);
}
}
m_renderableTileEntitiesPendingRemoval.clear();
LeaveCriticalSection(&m_csRenderableTileEntities);
}

View file

@ -10,6 +10,7 @@
#ifdef __PS3__
#include "../Platform/PS3/PS3Extras/C4JSpursJob.h"
#endif
#include <unordered_set>
class MultiPlayerLevel;
class Textures;
class Chunk;
@ -163,8 +164,13 @@ public:
void destroyTileProgress(int id, int x, int y, int z, int progress);
void registerTextures(IconRegister* iconRegister);
typedef std::unordered_map<int, std::vector<std::shared_ptr<TileEntity> >,
IntKeyHash, IntKeyEq>
struct RenderableTileEntityBucket {
std::vector<std::shared_ptr<TileEntity> > tiles;
std::unordered_map<TileEntity*, size_t> indexByTile;
};
typedef std::unordered_map<int, RenderableTileEntityBucket, IntKeyHash,
IntKeyEq>
rteMap;
private:
@ -176,6 +182,10 @@ private:
rteMap renderableTileEntities; // 4J - changed - was
// std::vector<std::shared_ptr<TileEntity>,
// now hashed by chunk so we can find them
typedef std::unordered_set<TileEntity*> rtePendingRemovalSet;
typedef std::unordered_map<int, rtePendingRemovalSet, IntKeyHash, IntKeyEq>
rtePendingRemovalMap;
rtePendingRemovalMap m_renderableTileEntitiesPendingRemoval;
CRITICAL_SECTION m_csRenderableTileEntities;
MultiPlayerLevel* level[4]; // 4J - now one per player
Textures* textures;
@ -214,6 +224,14 @@ private:
std::unordered_map<int, BlockDestructionProgress*> destroyingBlocks;
Icon** breakingTextures;
void addRenderableTileEntity_Locked(
int key, const std::shared_ptr<TileEntity>& tileEntity);
void eraseRenderableTileEntity_Locked(
RenderableTileEntityBucket& bucket, TileEntity* tileEntity);
void queueRenderableTileEntityForRemoval_Locked(int key,
TileEntity* tileEntity);
void retireRenderableTileEntitiesForChunkKey(int key);
public:
void fullyFlagRenderableTileEntitiesToBeRemoved(); // 4J added

View file

@ -44,6 +44,8 @@ constexpr std::array<FrameProfiler::BucketDescriptor, kBucketCount>
{Bucket::ChunkBlockFaceCull, "chunkBlockFaceCull"},
{Bucket::ChunkBlockLighting, "chunkBlockLighting"},
{Bucket::ChunkBlockEmit, "chunkBlockEmit"},
{Bucket::RenderableTileEntityCleanup, "renderableTileEntityCleanup"},
{Bucket::TileEntityUnloadCleanup, "tileEntityUnloadCleanup"},
{Bucket::Entity, "entities"},
{Bucket::Particle, "particles"},
{Bucket::WeatherSky, "weather"},
@ -133,8 +135,7 @@ inline void recordWorkerBucket(Bucket bucket, std::uint64_t elapsedNs) noexcept
return t_threadState.frameScopeDepth != 0;
}
FRAME_PROFILER_NOINLINE
[[nodiscard]] bool computeEnabled() noexcept {
FRAME_PROFILER_NOINLINE bool computeEnabled() noexcept {
const char* const envValue = std::getenv("C4J_FRAME_PROFILER");
if (envValue == nullptr) return true;
return !envSaysDisabled(envValue);

View file

@ -23,6 +23,8 @@ public:
ChunkBlockFaceCull,
ChunkBlockLighting,
ChunkBlockEmit,
RenderableTileEntityCleanup,
TileEntityUnloadCleanup,
Entity,
Particle,
WeatherSky,

View file

@ -187,6 +187,15 @@ void TileEntity::upgradeRenderRemoveStage() {
}
}
bool TileEntity::finalizeRenderRemoveStage() {
if (renderRemoveStage == e_RenderRemoveStageFlaggedAtChunk) {
renderRemoveStage = e_RenderRemoveStageRemove;
return true;
}
return renderRemoveStage == e_RenderRemoveStageRemove;
}
// 4J Added
void TileEntity::clone(std::shared_ptr<TileEntity> tileEntity) {
tileEntity->level = this->level;
@ -195,4 +204,4 @@ void TileEntity::clone(std::shared_ptr<TileEntity> tileEntity) {
tileEntity->z = this->z;
tileEntity->data = this->data;
tileEntity->tile = this->tile;
}
}

View file

@ -49,6 +49,7 @@ public:
void setRenderRemoveStage(unsigned char stage); // 4J added
void upgradeRenderRemoveStage(); // 4J added
bool finalizeRenderRemoveStage(); // 4J added
bool shouldRemoveForRender(); // 4J added
virtual Level* getLevel();
@ -77,4 +78,4 @@ public:
protected:
void clone(std::shared_ptr<TileEntity> tileEntity);
};
};

View file

@ -38,6 +38,7 @@
#include "../../Minecraft.Client/Platform/Common/DLC/DLCPack.h"
#include "../../Minecraft.Client/Platform/PS3/PS3Extras/ShutdownManager.h"
#include "../../Minecraft.Client/MinecraftServer.h"
#include "../../Minecraft.Client/Utils/FrameProfiler.h"
#include <cmath>
#include <cstdint>
#include <limits>
@ -2206,19 +2207,11 @@ void Level::tickEntities() {
// 4J-PB - Stuart - check this is correct here
if (!tileEntitiesToUnload.empty()) {
// tileEntityList.removeAll(tileEntitiesToUnload);
FRAME_PROFILE_SCOPE(TileEntityUnloadCleanup);
for (AUTO_VAR(it, tileEntityList.begin());
it != tileEntityList.end();) {
bool found = false;
for (AUTO_VAR(it2, tileEntitiesToUnload.begin());
it2 != tileEntitiesToUnload.end(); it2++) {
if ((*it) == (*it2)) {
found = true;
break;
}
}
if (found) {
if (tileEntitiesToUnload.find(*it) != tileEntitiesToUnload.end()) {
if (isClientSide) {
__debugbreak();
}
@ -2731,7 +2724,9 @@ void Level::removeTileEntity(int x, int y, int z) {
}
void Level::markForRemoval(std::shared_ptr<TileEntity> entity) {
tileEntitiesToUnload.push_back(entity);
EnterCriticalSection(&m_tileEntityListCS);
tileEntitiesToUnload.insert(entity);
LeaveCriticalSection(&m_tileEntityListCS);
}
bool Level::isSolidRenderTile(int x, int y, int z) {

View file

@ -10,6 +10,7 @@
#include "../WorldGen/Biomes/Biome.h"
#include "../Util/C4JThread.h"
#include <cstdint>
#include <unordered_set>
#ifdef __PSVITA__
#include "../../Minecraft.Client/Platform/PSVita/PSVitaExtras/CustomSet.h"
@ -118,7 +119,7 @@ public:
private:
std::vector<std::shared_ptr<TileEntity> > pendingTileEntities;
std::vector<std::shared_ptr<TileEntity> > tileEntitiesToUnload;
std::unordered_set<std::shared_ptr<TileEntity> > tileEntitiesToUnload;
bool updatingTileEntities;
public:
@ -665,3 +666,4 @@ public:
bool canCreateMore(eINSTANCEOF type, ESPAWN_TYPE spawnType);
};
#include <unordered_set>

View file

@ -1558,13 +1558,19 @@ void LevelChunk::unload(bool unloadTileEntities) // 4J - added parameter
{
loaded = false;
if (unloadTileEntities) {
std::vector<std::shared_ptr<TileEntity> > tileEntitiesToRemove;
EnterCriticalSection(&m_csTileEntities);
for (AUTO_VAR(it, tileEntities.begin()); it != tileEntities.end();
it++) {
// 4J-PB -m 1.7.3 was it->second->setRemoved();
level->markForRemoval(it->second);
tileEntitiesToRemove.push_back(it->second);
}
LeaveCriticalSection(&m_csTileEntities);
AUTO_VAR(itEnd, tileEntitiesToRemove.end());
for (AUTO_VAR(it, tileEntitiesToRemove.begin()); it != itEnd; it++) {
// 4J-PB -m 1.7.3 was it->second->setRemoved();
level->markForRemoval(*it);
}
}
#ifdef _ENTITIES_RW_SECTION