diff --git a/Minecraft.Client/Chunk.cpp b/Minecraft.Client/Chunk.cpp index 850b79fb3..e8170f770 100644 --- a/Minecraft.Client/Chunk.cpp +++ b/Minecraft.Client/Chunk.cpp @@ -254,6 +254,14 @@ void Chunk::rebuild() // (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; + bool empty = true; for( int yy = y0; yy < y1; yy++ ) { @@ -279,17 +287,12 @@ void Chunk::rebuild() 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; + // Establish whether this tile and its neighbours are all occluders using lookup table + if( !isOccluder[tileId] ) continue; + if( !isOccluder[ tileIds[ offset + ( ( ( xx - 1 ) << 11 ) | ( ( zz + 0 ) << 7 ) | ( indexY + 0 )) ] ] ) continue; + if( !isOccluder[ tileIds[ offset + ( ( ( xx + 1 ) << 11 ) | ( ( zz + 0 ) << 7 ) | ( indexY + 0 )) ] ] ) continue; + if( !isOccluder[ tileIds[ offset + ( ( ( xx + 0 ) << 11 ) | ( ( zz - 1 ) << 7 ) | ( indexY + 0 )) ] ] ) continue; + if( !isOccluder[ tileIds[ offset + ( ( ( xx + 0 ) << 11 ) | ( ( zz + 1 ) << 7 ) | ( indexY + 0 )) ] ] ) 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 ) @@ -301,8 +304,7 @@ void Chunk::rebuild() 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; + if( !isOccluder[ tileIds[ yMinusOneOffset + ( ( ( xx + 0 ) << 11 ) | ( ( zz + 0 ) << 7 ) | indexYMinusOne ) ] ] ) continue; } int indexYPlusOne = yy + 1; int yPlusOneOffset = 0; @@ -311,8 +313,7 @@ void Chunk::rebuild() 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; + if( !isOccluder[ tileIds[ yPlusOneOffset + ( ( ( xx + 0 ) << 11 ) | ( ( zz + 0 ) << 7 ) | indexYPlusOne ) ] ] ) 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; @@ -375,9 +376,9 @@ 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) ]; // If flagged as not visible, drop out straight away - if( tileId == 0xff ) continue; + if( tileId == 0xff ) [[unlikely]] continue; // int tileId = region->getTile(x,y,z); - if (tileId > 0) + if (tileId > 0) [[unlikely]] { if (!started) { diff --git a/Minecraft.Client/LevelRenderer.cpp b/Minecraft.Client/LevelRenderer.cpp index 594ff4ea9..7b00bf783 100644 --- a/Minecraft.Client/LevelRenderer.cpp +++ b/Minecraft.Client/LevelRenderer.cpp @@ -805,8 +805,8 @@ int LevelRenderer::renderChunks(int from, int to, int layer, double alpha) unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer; for( int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++ ) { - if( !pClipChunk->visible ) continue; // This will be set if the chunk isn't visible, or isn't compiled, or has both empty flags set - if( pClipChunk->globalIdx == -1 ) continue; // Not sure if we should ever encounter this... TODO check + if( !pClipChunk->visible ) [[likely]] continue; // This will be set if the chunk isn't visible, or isn't compiled, or has both empty flags set + if( pClipChunk->globalIdx == -1 ) [[unlikely]] continue; // Not sure if we should ever encounter this... TODO check if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) continue; // Check that this particular layer isn't empty // List can be calculated directly from the chunk's global idex @@ -2426,18 +2426,23 @@ void LevelRenderer::setTilesDirty(int x0, int y0, int z0, int x1, int y1, int z1 setDirty(x0 - 1, y0 - 1, z0 - 1, x1 + 1, y1 + 1, z1 + 1, level); } -bool inline clip(float *bb, float *frustum) +bool inline clip(float * __restrict bb, float * __restrict frustum) { + // Pre-load AABB corners to avoid repeated memory loads + const float x0 = bb[0], y0 = bb[1], z0 = bb[2]; + const float x1 = bb[3], y1 = bb[4], z1 = bb[5]; + for (int i = 0; i < 6; ++i, frustum += 4) { - if (frustum[0] * (bb[0]) + frustum[1] * (bb[1]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue; - if (frustum[0] * (bb[3]) + frustum[1] * (bb[1]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue; - if (frustum[0] * (bb[0]) + frustum[1] * (bb[4]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue; - if (frustum[0] * (bb[3]) + frustum[1] * (bb[4]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue; - if (frustum[0] * (bb[0]) + frustum[1] * (bb[1]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue; - if (frustum[0] * (bb[3]) + frustum[1] * (bb[1]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue; - if (frustum[0] * (bb[0]) + frustum[1] * (bb[4]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue; - if (frustum[0] * (bb[3]) + frustum[1] * (bb[4]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue; + const float a = frustum[0], b = frustum[1], c = frustum[2], d = frustum[3]; + if (a * x0 + b * y0 + c * z0 + d > 0) continue; + if (a * x1 + b * y0 + c * z0 + d > 0) continue; + if (a * x0 + b * y1 + c * z0 + d > 0) continue; + if (a * x1 + b * y1 + c * z0 + d > 0) continue; + if (a * x0 + b * y0 + c * z1 + d > 0) continue; + if (a * x1 + b * y0 + c * z1 + d > 0) continue; + if (a * x0 + b * y1 + c * z1 + d > 0) continue; + if (a * x1 + b * y1 + c * z1 + d > 0) continue; return false; } diff --git a/Minecraft.Client/Tesselator.cpp b/Minecraft.Client/Tesselator.cpp index 7bae7aca9..55e542398 100644 --- a/Minecraft.Client/Tesselator.cpp +++ b/Minecraft.Client/Tesselator.cpp @@ -310,20 +310,16 @@ void Tesselator::color(int r, int g, int b) void Tesselator::color(int r, int g, int b, int a) { - if (_noColor) return; + if (_noColor) return; - if (r > 255) r = 255; - if (g > 255) g = 255; - if (b > 255) b = 255; - if (a > 255) a = 255; - if (r < 0) r = 0; - if (g < 0) g = 0; - if (b < 0) b = 0; - if (a < 0) a = 0; + r = std::clamp(r, 0, 255); + g = std::clamp(g, 0, 255); + b = std::clamp(b, 0, 255); + a = std::clamp(a, 0, 255); - hasColor = true; + hasColor = true; // 4J - removed little-endian option - col = (r << 24) | (g << 16) | (b << 8) | (a); + col = (r << 24) | (g << 16) | (b << 8) | (a); } void Tesselator::color(byte r, byte g, byte b) @@ -381,26 +377,15 @@ void Tesselator::packCompactQuad() m_iz[i] += 16 * 128; } // Find min x/y/z - unsigned int minx = m_ix[0]; - unsigned int miny = m_iy[0]; - unsigned int minz = m_iz[0]; - for( int i = 1; i < 4; i++ ) - { - if( m_ix[i] < minx ) minx = m_ix[i]; - if( m_iy[i] < miny ) miny = m_iy[i]; - if( m_iz[i] < minz ) minz = m_iz[i]; - } + unsigned int minx = std::min({m_ix[0], m_ix[1], m_ix[2], m_ix[3]}); + unsigned int miny = std::min({m_iy[0], m_iy[1], m_iy[2], m_iy[3]}); + unsigned int minz = std::min({m_iz[0], m_iz[1], m_iz[2], m_iz[3]}); // Everything has been scaled by a factor of 128 to get it into an int, and so // the minimum now should be in the range of (0->32) * 128. Get the base x/y/z // that our quad will be referenced from now, which can be stored in 5 bits - unsigned int basex = ( minx >> 7 ); - unsigned int basey = ( miny >> 7 ); - unsigned int basez = ( minz >> 7 ); - // If the min is 32, then this whole quad must be in that plane - make the min 15 instead so - // we can still offset from that with our delta to get to the exact edge - if( basex == 32 ) basex = 31; - if( basey == 32 ) basey = 31; - if( basez == 32 ) basez = 31; + unsigned int basex = std::min(minx >> 7, 31u); + unsigned int basey = std::min(miny >> 7, 31u); + unsigned int basez = std::min(minz >> 7, 31u); // Now get deltas to each vertex - these have an 8-bit range so they can span a // full unit range from the base position for( int i = 0; i < 4; i++ ) @@ -420,28 +405,18 @@ void Tesselator::packCompactQuad() data[0] |= ( basex << 26 ) | ( basey << 21 )| ( basez << 16 ); // Now process UVs. First find min & max U & V - unsigned int minu = m_u[0]; - unsigned int minv = m_v[0]; - unsigned int maxu = m_u[0]; - unsigned int maxv = m_v[0]; - - for( int i = 1; i < 4; i++ ) - { - if( m_u[i] < minu ) minu = m_u[i]; - if( m_v[i] < minv ) minv = m_v[i]; - if( m_u[i] > maxu ) maxu = m_u[i]; - if( m_v[i] > maxv ) maxv = m_v[i]; - } + unsigned int minu = std::min({m_u[0], m_u[1], m_u[2], m_u[3]}); + unsigned int minv = std::min({m_v[0], m_v[1], m_v[2], m_v[3]}); + unsigned int maxu = std::max({m_u[0], m_u[1], m_u[2], m_u[3]}); + unsigned int maxv = std::max({m_v[0], m_v[1], m_v[2], m_v[3]}); // In nearly all cases, all our UVs should be axis aligned for this quad. So the only values they should // have in each dimension should be the min/max. We're going to store: // (1) minu/maxu (16 bits each, only actuall needs to store 14 bits to get a 0 to 2 range for each // (2) du/dv ( ie maxu-minu, maxv-minv) - 8 bits each, to store a range of 0 to 15.9375 texels. This // should be enough to map the full UV range of a single 16x16 region of the terrain texture, since // we always pull UVs in by 1/16th of their range at the sides - unsigned int du = maxu - minu; - unsigned int dv = maxv - minv; - if( du > 255 ) du = 255; - if( dv > 255 ) dv = 255; + unsigned int du = std::min(maxu - minu, 255u); + unsigned int dv = std::min(maxv - minv, 255u); // Check if this quad has UVs that can be referenced this way. This should only happen for flowing water // and lava, where the texture coordinates are rotated for the top surface of the tile. bool axisAligned = true; diff --git a/Minecraft.Client/Tesselator.h b/Minecraft.Client/Tesselator.h index 72a458c22..9337fd90b 100644 --- a/Minecraft.Client/Tesselator.h +++ b/Minecraft.Client/Tesselator.h @@ -93,35 +93,21 @@ public: } void addVert(float x, float y, float z) { - if(x < boundingBox[0]) - boundingBox[0] = x; - if(y < boundingBox[1]) - boundingBox[1] = y; - if(z < boundingBox[2]) - boundingBox[2] = z; - - if(x > boundingBox[3]) - boundingBox[3] = x; - if(y > boundingBox[4]) - boundingBox[4] = y; - if(z > boundingBox[5]) - boundingBox[5] = z; + boundingBox[0] = std::min(boundingBox[0], x); + boundingBox[1] = std::min(boundingBox[1], y); + boundingBox[2] = std::min(boundingBox[2], z); + boundingBox[3] = std::max(boundingBox[3], x); + boundingBox[4] = std::max(boundingBox[4], y); + boundingBox[5] = std::max(boundingBox[5], z); } - void addBounds(Bounds& ob) + void addBounds(const Bounds& ob) { - if(ob.boundingBox[0] < boundingBox[0]) - boundingBox[0] = ob.boundingBox[0]; - if(ob.boundingBox[1] < boundingBox[1]) - boundingBox[1] = ob.boundingBox[1]; - if(ob.boundingBox[2] < boundingBox[2]) - boundingBox[2] = ob.boundingBox[2]; - - if(ob.boundingBox[3] > boundingBox[3]) - boundingBox[3] = ob.boundingBox[3]; - if(ob.boundingBox[4] > boundingBox[4]) - boundingBox[4] = ob.boundingBox[4]; - if(ob.boundingBox[5] > boundingBox[5]) - boundingBox[5] = ob.boundingBox[5]; + boundingBox[0] = std::min(boundingBox[0], ob.boundingBox[0]); + boundingBox[1] = std::min(boundingBox[1], ob.boundingBox[1]); + boundingBox[2] = std::min(boundingBox[2], ob.boundingBox[2]); + boundingBox[3] = std::max(boundingBox[3], ob.boundingBox[3]); + boundingBox[4] = std::max(boundingBox[4], ob.boundingBox[4]); + boundingBox[5] = std::max(boundingBox[5], ob.boundingBox[5]); } float boundingBox[6]; // 4J MGH added