perf(render): improve chunk scheduling and refine rebuild profiling

This commit is contained in:
MatthewBeshay 2026-03-29 15:52:55 +11:00 committed by Tropical
parent 47b7d90835
commit a3f7f7d03c
5 changed files with 171 additions and 122 deletions

View file

@ -7,6 +7,7 @@
#include "../../Minecraft.World/Headers/net.minecraft.world.level.tile.h"
#include "../../Minecraft.World/Headers/net.minecraft.world.level.tile.entity.h"
#include "LevelRenderer.h"
#include "../Utils/FrameProfiler.h"
#ifdef __PS3__
#include "../Platform/PS3/SPU_Tasks/ChunkUpdate/ChunkRebuildData.h"
@ -252,94 +253,97 @@ void Chunk::rebuild() {
// into this category. By far the largest category of these are tiles in
// solid regions of rock.
bool empty = true;
for (int yy = y0; yy < y1; yy++) {
for (int zz = 0; zz < 16; zz++) {
for (int xx = 0; xx < 16; xx++) {
// 4J Stu - tile data is ordered in 128 blocks of full width,
// lower 128 then upper 128
int indexY = yy;
int offset = 0;
if (indexY >= Level::COMPRESSED_CHUNK_SECTION_HEIGHT) {
indexY -= Level::COMPRESSED_CHUNK_SECTION_HEIGHT;
offset = Level::COMPRESSED_CHUNK_SECTION_TILES;
}
unsigned char tileId =
tileIds[offset + (((xx + 0) << 11) | ((zz + 0) << 7) |
(indexY + 0))];
if (tileId > 0) empty = false;
// Don't bother trying to work out neighbours for this tile if
// we are at the edge of the chunk - apart from the very bottom
// of the world where we shouldn't ever be able to see
if (yy == (Level::maxBuildHeight - 1)) continue;
if ((xx == 0) || (xx == 15)) continue;
if ((zz == 0) || (zz == 15)) continue;
// Establish whether this tile and its neighbours are all made
// of rock, dirt, unbreakable tiles, or have already been
// determined to meet this criteria themselves and have a tile
// of 255 set.
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
tileId = tileIds[offset + (((xx - 1) << 11) | ((zz + 0) << 7) |
(indexY + 0))];
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
tileId = tileIds[offset + (((xx + 1) << 11) | ((zz + 0) << 7) |
(indexY + 0))];
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
tileId = tileIds[offset + (((xx + 0) << 11) | ((zz - 1) << 7) |
(indexY + 0))];
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
tileId = tileIds[offset + (((xx + 0) << 11) | ((zz + 1) << 7) |
(indexY + 0))];
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
// Treat the bottom of the world differently - we shouldn't ever
// be able to look up at this, so consider tiles as invisible if
// they are surrounded on sides other than the bottom
if (yy > 0) {
int indexYMinusOne = yy - 1;
int yMinusOneOffset = 0;
if (indexYMinusOne >=
Level::COMPRESSED_CHUNK_SECTION_HEIGHT) {
indexYMinusOne -=
Level::COMPRESSED_CHUNK_SECTION_HEIGHT;
yMinusOneOffset = Level::COMPRESSED_CHUNK_SECTION_TILES;
{
FRAME_PROFILE_SCOPE(ChunkPrepass);
for (int yy = y0; yy < y1; yy++) {
for (int zz = 0; zz < 16; zz++) {
for (int xx = 0; xx < 16; xx++) {
// 4J Stu - tile data is ordered in 128 blocks of full width,
// lower 128 then upper 128
int indexY = yy;
int offset = 0;
if (indexY >= Level::COMPRESSED_CHUNK_SECTION_HEIGHT) {
indexY -= Level::COMPRESSED_CHUNK_SECTION_HEIGHT;
offset = Level::COMPRESSED_CHUNK_SECTION_TILES;
}
tileId = tileIds[yMinusOneOffset + (((xx + 0) << 11) |
((zz + 0) << 7) |
indexYMinusOne)];
if (!((tileId == Tile::stone_Id) ||
(tileId == Tile::dirt_Id) ||
unsigned char tileId =
tileIds[offset + (((xx + 0) << 11) | ((zz + 0) << 7) |
(indexY + 0))];
if (tileId > 0) empty = false;
// Don't bother trying to work out neighbours for this tile if
// we are at the edge of the chunk - apart from the very bottom
// of the world where we shouldn't ever be able to see
if (yy == (Level::maxBuildHeight - 1)) continue;
if ((xx == 0) || (xx == 15)) continue;
if ((zz == 0) || (zz == 15)) continue;
// Establish whether this tile and its neighbours are all made
// of rock, dirt, unbreakable tiles, or have already been
// determined to meet this criteria themselves and have a tile
// of 255 set.
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
tileId = tileIds[offset + (((xx - 1) << 11) | ((zz + 0) << 7) |
(indexY + 0))];
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
tileId = tileIds[offset + (((xx + 1) << 11) | ((zz + 0) << 7) |
(indexY + 0))];
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
tileId = tileIds[offset + (((xx + 0) << 11) | ((zz - 1) << 7) |
(indexY + 0))];
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
tileId = tileIds[offset + (((xx + 0) << 11) | ((zz + 1) << 7) |
(indexY + 0))];
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
// Treat the bottom of the world differently - we shouldn't ever
// be able to look up at this, so consider tiles as invisible if
// they are surrounded on sides other than the bottom
if (yy > 0) {
int indexYMinusOne = yy - 1;
int yMinusOneOffset = 0;
if (indexYMinusOne >=
Level::COMPRESSED_CHUNK_SECTION_HEIGHT) {
indexYMinusOne -=
Level::COMPRESSED_CHUNK_SECTION_HEIGHT;
yMinusOneOffset = Level::COMPRESSED_CHUNK_SECTION_TILES;
}
tileId = tileIds[yMinusOneOffset + (((xx + 0) << 11) |
((zz + 0) << 7) |
indexYMinusOne)];
if (!((tileId == Tile::stone_Id) ||
(tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
}
int indexYPlusOne = yy + 1;
int yPlusOneOffset = 0;
if (indexYPlusOne >= Level::COMPRESSED_CHUNK_SECTION_HEIGHT) {
indexYPlusOne -= Level::COMPRESSED_CHUNK_SECTION_HEIGHT;
yPlusOneOffset = Level::COMPRESSED_CHUNK_SECTION_TILES;
}
tileId =
tileIds[yPlusOneOffset + (((xx + 0) << 11) |
((zz + 0) << 7) | indexYPlusOne)];
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
}
int indexYPlusOne = yy + 1;
int yPlusOneOffset = 0;
if (indexYPlusOne >= Level::COMPRESSED_CHUNK_SECTION_HEIGHT) {
indexYPlusOne -= Level::COMPRESSED_CHUNK_SECTION_HEIGHT;
yPlusOneOffset = Level::COMPRESSED_CHUNK_SECTION_TILES;
}
tileId =
tileIds[yPlusOneOffset + (((xx + 0) << 11) |
((zz + 0) << 7) | indexYPlusOne)];
if (!((tileId == Tile::stone_Id) || (tileId == Tile::dirt_Id) ||
(tileId == Tile::unbreakable_Id) || (tileId == 255)))
continue;
// This tile is surrounded. Flag it as not requiring to be
// rendered by setting its id to 255.
tileIds[offset + (((xx + 0) << 11) | ((zz + 0) << 7) |
(indexY + 0))] = 0xff;
// This tile is surrounded. Flag it as not requiring to be
// rendered by setting its id to 255.
tileIds[offset + (((xx + 0) << 11) | ((zz + 0) << 7) |
(indexY + 0))] = 0xff;
}
}
}
}
@ -752,7 +756,7 @@ void Chunk::rebuild_SPU() {
if (currentLayer == 0 &&
Tile::tiles[tileId]->isEntityTile()) {
std::shared_ptr<TileEntity> et =
region.getTileEntity(x, y, z);
region->getTileEntity(x, y, z);
if (TileEntityRenderDispatcher::instance
->hasRenderer(et)) {
renderableTileEntities.push_back(et);
@ -770,7 +774,7 @@ void Chunk::rebuild_SPU() {
} else if (renderLayer == currentLayer) {
// if(currentLayer == 0)
// numRenderedLayer0++;
rendered |= tileRenderer.tesselateInWorld(
rendered |= tileRenderer->tesselateInWorld(
tile, x, y, z);
}
}

View file

@ -11,6 +11,7 @@
#include "../../../Minecraft.World/Headers/net.minecraft.h"
#include "../../../Minecraft.World/Headers/net.minecraft.world.h"
#include "../Tesselator.h"
#include "../../Utils/FrameProfiler.h"
#include "EntityTileRenderer.h"
#include "../../GameState/Options.h"
@ -267,7 +268,12 @@ bool TileRenderer::tesselateInWorld(
{
Tesselator* t = Tesselator::getInstance();
int shape = tt->getRenderShape();
tt->updateShape(level, x, y, z, forceData, forceEntity);
if (shape == Tile::SHAPE_BLOCK) {
FRAME_PROFILE_SCOPE(ChunkBlockShape);
tt->updateShape(level, x, y, z, forceData, forceEntity);
} else {
tt->updateShape(level, x, y, z, forceData, forceEntity);
}
// AP - now that the culling is done earlier we don't need to call setShape
// until later on (only for SHAPE_BLOCK)
if (shape != Tile::SHAPE_BLOCK) {
@ -278,6 +284,11 @@ bool TileRenderer::tesselateInWorld(
bool retVal = false;
switch (shape) {
case Tile::SHAPE_BLOCK: {
{
FRAME_PROFILE_SCOPE(ChunkBlockShape);
setShape(tt);
}
// 4J - added these faceFlags so we can detect whether this block is
// going to have no visible faces and early out the original code
// checked noCulling and shouldRenderFace directly where faceFlags
@ -290,6 +301,7 @@ bool TileRenderer::tesselateInWorld(
if (noCulling) {
faceFlags = 0x3f;
} else {
FRAME_PROFILE_SCOPE(ChunkBlockFaceCull);
// these block types can take advantage of a faster version of
// shouldRenderFace there are others but this is an easy check
// which covers the majority Note: This now covers rock, grass,
@ -319,9 +331,6 @@ bool TileRenderer::tesselateInWorld(
break;
}
// now we need to set the shape
setShape(tt);
retVal = tesselateBlockInWorld(tt, x, y, z, faceFlags);
} break;
case Tile::SHAPE_TREE:
@ -4828,9 +4837,11 @@ bool TileRenderer::tesselateBlockInWorld(Tile* tt, int x, int y, int z) {
if (Tile::lightEmission[tt->id] ==
0) // 4J - TODO/remove (Minecraft::useAmbientOcclusion())
{
FRAME_PROFILE_SCOPE(ChunkBlockLighting);
return tesselateBlockInWorldWithAmbienceOcclusionTexLighting(
tt, x, y, z, r, g, b, 0, smoothShapeLighting);
} else {
FRAME_PROFILE_SCOPE(ChunkBlockLighting);
return tesselateBlockInWorld(tt, x, y, z, r, g, b);
}
}
@ -4856,9 +4867,11 @@ bool TileRenderer::tesselateBlockInWorld(Tile* tt, int x, int y, int z,
if (Tile::lightEmission[tt->id] ==
0) // 4J - TODO/remove (Minecraft::useAmbientOcclusion())
{
FRAME_PROFILE_SCOPE(ChunkBlockLighting);
return tesselateBlockInWorldWithAmbienceOcclusionTexLighting(
tt, x, y, z, r, g, b, faceFlags, smoothShapeLighting);
} else {
FRAME_PROFILE_SCOPE(ChunkBlockLighting);
return tesselateBlockInWorld(tt, x, y, z, r, g, b);
}
}
@ -7049,6 +7062,7 @@ bool TileRenderer::tesselateDoorInWorld(Tile* tt, int x, int y, int z) {
void TileRenderer::renderFaceDown(Tile* tt, double x, double y, double z,
Icon* tex) {
FRAME_PROFILE_SCOPE(ChunkBlockEmit);
Tesselator* t = Tesselator::getInstance();
if (hasFixedTexture()) tex = fixedTexture;
@ -7167,6 +7181,7 @@ void TileRenderer::renderFaceDown(Tile* tt, double x, double y, double z,
void TileRenderer::renderFaceUp(Tile* tt, double x, double y, double z,
Icon* tex) {
FRAME_PROFILE_SCOPE(ChunkBlockEmit);
Tesselator* t = Tesselator::getInstance();
if (hasFixedTexture()) tex = fixedTexture;
@ -7286,6 +7301,7 @@ void TileRenderer::renderFaceUp(Tile* tt, double x, double y, double z,
void TileRenderer::renderNorth(Tile* tt, double x, double y, double z,
Icon* tex) {
FRAME_PROFILE_SCOPE(ChunkBlockEmit);
Tesselator* t = Tesselator::getInstance();
if (hasFixedTexture()) tex = fixedTexture;
@ -7410,6 +7426,7 @@ void TileRenderer::renderNorth(Tile* tt, double x, double y, double z,
void TileRenderer::renderSouth(Tile* tt, double x, double y, double z,
Icon* tex) {
FRAME_PROFILE_SCOPE(ChunkBlockEmit);
Tesselator* t = Tesselator::getInstance();
if (hasFixedTexture()) tex = fixedTexture;
@ -7534,6 +7551,7 @@ void TileRenderer::renderSouth(Tile* tt, double x, double y, double z,
void TileRenderer::renderWest(Tile* tt, double x, double y, double z,
Icon* tex) {
FRAME_PROFILE_SCOPE(ChunkBlockEmit);
Tesselator* t = Tesselator::getInstance();
if (hasFixedTexture()) tex = fixedTexture;
@ -7658,6 +7676,7 @@ void TileRenderer::renderWest(Tile* tt, double x, double y, double z,
void TileRenderer::renderEast(Tile* tt, double x, double y, double z,
Icon* tex) {
FRAME_PROFILE_SCOPE(ChunkBlockEmit);
Tesselator* t = Tesselator::getInstance();
if (hasFixedTexture()) tex = fixedTexture;

View file

@ -1,5 +1,6 @@
#include <thread>
#include <chrono>
#include <array>
#include "../Platform/stdafx.h"
#include "LevelRenderer.h"
@ -904,11 +905,12 @@ int LevelRenderer::renderChunks(int from, int to, int layer, double alpha) {
int list = chunk->globalIdx * 2 + layer;
list += chunkLists;
// 4jcraft: replaced glPushMatrix/glTranslatef/glPopMatrix per chunk
// no more full MVP upload per chunk, can also be bkwards compat
RenderManager.SetChunkOffset((float)chunk->chunk->x,
(float)chunk->chunk->y,
(float)chunk->chunk->z);
// 4jcraft: replaced glPushMatrix/glTranslatef/glPopMatrix per
// chunk no more full MVP upload per chunk, can also be bkwards
// compat
RenderManager.SetChunkOffset((float)chunk->chunk->x,
(float)chunk->chunk->y,
(float)chunk->chunk->z);
if (RenderManager.CBuffCall(list, first)) {
first = false;
@ -1964,7 +1966,41 @@ void LevelRenderer::renderAdvancedClouds(float alpha) {
bool LevelRenderer::updateDirtyChunks() {
#ifdef _LARGE_WORLDS
std::list<std::pair<ClipChunk*, int> > nearestClipChunks;
struct NearestClipChunkSet {
std::array<std::pair<ClipChunk*, int>, MAX_CONCURRENT_CHUNK_REBUILDS>
items;
int count = 0;
bool empty() const noexcept { return count == 0; }
int size() const noexcept { return count; }
bool wouldAccept(int distSqWeighted) const noexcept {
return (count < MAX_CONCURRENT_CHUNK_REBUILDS) ||
(distSqWeighted < items[count - 1].second);
}
void insert(ClipChunk* chunk, int distSqWeighted) noexcept {
int pos = 0;
while ((pos < count) && (items[pos].second <= distSqWeighted)) {
++pos;
}
if ((count == MAX_CONCURRENT_CHUNK_REBUILDS) &&
(pos >= MAX_CONCURRENT_CHUNK_REBUILDS)) {
return;
}
const int newCount =
(count < MAX_CONCURRENT_CHUNK_REBUILDS)
? (count + 1)
: MAX_CONCURRENT_CHUNK_REBUILDS;
for (int i = newCount - 1; i > pos; --i) {
items[i] = items[i - 1];
}
items[pos] = std::pair<ClipChunk*, int>(chunk, distSqWeighted);
count = newCount;
}
} nearestClipChunks;
#endif
ClipChunk* nearChunk = NULL; // Nearest chunk that is dirty
@ -2090,11 +2126,6 @@ bool LevelRenderer::updateDirtyChunks() {
veryNearCount = g_findNearestChunkDataIn.veryNearCount;
#else // __PS3__
#ifdef _LARGE_WORLDS
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
for (int p = 0; p < XUSER_MAX_COUNT; p++) {
// It's possible that the localplayers member can be set to NULL on
@ -2150,17 +2181,9 @@ bool LevelRenderer::updateDirtyChunks() {
considered++;
// Is this chunk nearer than our nearest?
#ifdef _LARGE_WORLDS
bool isNearer = nearestClipChunks.empty();
AUTO_VAR(itNearest, nearestClipChunks.begin());
for (; itNearest != nearestClipChunks.end();
++itNearest) {
isNearer =
distSqWeighted < itNearest->second;
if (isNearer) break;
}
isNearer =
isNearer || (nearestClipChunks.size() <
maxNearestChunks);
bool isNearer =
nearestClipChunks.wouldAccept(
distSqWeighted);
#else
bool isNearer = distSqWeighted < minDistSq;
#endif
@ -2203,13 +2226,7 @@ bool LevelRenderer::updateDirtyChunks() {
minDistSq = distSqWeighted;
#ifdef _LARGE_WORLDS
nearestClipChunks.insert(
itNearest,
std::pair<ClipChunk*, int>(
nearChunk, minDistSq));
if (nearestClipChunks.size() >
maxNearestChunks) {
nearestClipChunks.pop_back();
}
nearChunk, minDistSq);
#endif
} else {
chunk->clearDirty();
@ -2249,9 +2266,8 @@ bool LevelRenderer::updateDirtyChunks() {
int index = 0;
{
FRAME_PROFILE_SCOPE(ChunkRebuildSchedule);
for (AUTO_VAR(it, nearestClipChunks.begin());
it != nearestClipChunks.end(); ++it) {
chunk = it->first->chunk;
for (int i = 0; i < nearestClipChunks.size(); ++i) {
chunk = nearestClipChunks.items[i].first->chunk;
// If this chunk is very near, then move the renderer into a
// deferred mode. This won't commit any command buffers for
// rendering until we call CBuffDeferredModeEnd(), allowing us to

View file

@ -39,6 +39,11 @@ constexpr std::array<FrameProfiler::BucketDescriptor, kBucketCount>
{Bucket::ChunkDirtyScan, "chunkDirtyScan"},
{Bucket::ChunkRebuildSchedule, "chunkRebuildSchedule"},
{Bucket::ChunkRebuildBody, "chunkRebuildBody"},
{Bucket::ChunkPrepass, "chunkPrepass"},
{Bucket::ChunkBlockShape, "chunkBlockShape"},
{Bucket::ChunkBlockFaceCull, "chunkBlockFaceCull"},
{Bucket::ChunkBlockLighting, "chunkBlockLighting"},
{Bucket::ChunkBlockEmit, "chunkBlockEmit"},
{Bucket::Entity, "entities"},
{Bucket::Particle, "particles"},
{Bucket::WeatherSky, "weather"},

View file

@ -18,6 +18,11 @@ public:
ChunkDirtyScan,
ChunkRebuildSchedule,
ChunkRebuildBody,
ChunkPrepass,
ChunkBlockShape,
ChunkBlockFaceCull,
ChunkBlockLighting,
ChunkBlockEmit,
Entity,
Particle,
WeatherSky,