Generalize mesh chunking, and make it configurable. (#13179)

* Generalize mesh chunking. Set 3x3x3 chunks.

* Make mesh chunk size configurable... Default to 1 (off).

* Extract all mesh grid maths into a dedicated class

---------

Co-authored-by: x2048 <codeforsmile@gmail.com>
This commit is contained in:
lhofhansl 2023-02-08 13:42:12 -08:00 committed by GitHub
parent 56d2567b5d
commit d3a6ee00e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 116 additions and 116 deletions

@ -1713,6 +1713,13 @@ world_aligned_mode (World-aligned textures mode) enum enable disable,enable,forc
# Warning: This option is EXPERIMENTAL!
autoscale_mode (Autoscaling mode) enum disable disable,enable,force
# Side length of a cube of map blocks that the client will consider together
# when generating meshes.
# Larger values increase the utilization of the GPU by reducing the number of
# draw calls, benefiting especially high-end GPUs.
# Systems with a low-end GPU (or no GPU) would benefit from smaller values.
client_mesh_chunk (Client Mesh Chunksize) int 1 1 16
[**Font]
font_bold (Font bold by default) bool false

@ -143,6 +143,7 @@ Client::Client(
}
m_cache_save_interval = g_settings->getU16("server_map_save_interval");
m_mesh_grid = { g_settings->getU16("client_mesh_chunk") };
}
void Client::migrateModStorage()
@ -564,7 +565,7 @@ void Client::step(float dtime)
MapBlock *block = sector->getBlockNoCreateNoEx(r.p.Y);
// The block in question is not visible (perhaps it is culled at the server),
// create a blank block just to hold the 2x2x2 mesh.
// create a blank block just to hold the chunk's mesh.
// If the block becomes visible later it will replace the blank block.
if (!block && r.mesh)
block = sector->createBlankBlock(r.p.Y);
@ -607,10 +608,10 @@ void Client::step(float dtime)
v3s16 ofs;
// See also mapblock_mesh.cpp for the code that creates the array of minimap blocks.
for (ofs.Z = 0; ofs.Z <= 1; ofs.Z++)
for (ofs.Y = 0; ofs.Y <= 1; ofs.Y++)
for (ofs.X = 0; ofs.X <= 1; ofs.X++) {
size_t i = ofs.Z * 4 + ofs.Y * 2 + ofs.X;
for (ofs.Z = 0; ofs.Z < m_mesh_grid.cell_size; ofs.Z++)
for (ofs.Y = 0; ofs.Y < m_mesh_grid.cell_size; ofs.Y++)
for (ofs.X = 0; ofs.X < m_mesh_grid.cell_size; ofs.X++) {
size_t i = m_mesh_grid.getOffsetIndex(ofs);
if (i < minimap_mapblocks.size() && minimap_mapblocks[i])
m_minimap->addBlock(r.p + ofs, minimap_mapblocks[i]);
}

@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "network/peerhandler.h"
#include "gameparams.h"
#include <fstream>
#include "util/numeric.h"
#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f
@ -437,6 +438,11 @@ public:
{
return m_env.getLocalPlayer()->formspec_prepend;
}
inline MeshGrid getMeshGrid()
{
return m_mesh_grid;
}
private:
void loadMods();
@ -602,4 +608,7 @@ private:
u32 m_csm_restriction_noderange = 8;
std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
// The number of blocks the client will combine for mesh generation.
MeshGrid m_mesh_grid;
};

@ -304,6 +304,7 @@ void ClientMap::updateDrawList()
blocks_seen.getChunk(camera_block).getBits(camera_block) = 0x07; // mark all sides as visible
std::set<v3s16> shortlist;
MeshGrid mesh_grid = m_client->getMeshGrid();
// Recursively walk the space and pick mapblocks for drawing
while (blocks_to_consider.size() > 0) {
@ -330,7 +331,6 @@ void ClientMap::updateDrawList()
MapBlockMesh *mesh = block ? block->mesh : nullptr;
// Calculate the coordinates for range and frutum culling
v3f mesh_sphere_center;
f32 mesh_sphere_radius;
@ -376,13 +376,21 @@ void ClientMap::updateDrawList()
continue;
}
// Block meshes are stored in blocks where all coordinates are even (lowest bit set to 0)
// Add them to the de-dup set.
shortlist.emplace(block_coord.X & ~1, block_coord.Y & ~1, block_coord.Z & ~1);
// All other blocks we can grab and add to the keeplist right away.
if (block) {
m_keeplist.push_back(block);
if (mesh_grid.cell_size > 1) {
// Block meshes are stored in the corner block of a chunk
// (where all coordinate are divisible by the chunk size)
// Add them to the de-dup set.
shortlist.emplace(mesh_grid.getMeshPos(block_coord.X), mesh_grid.getMeshPos(block_coord.Y), mesh_grid.getMeshPos(block_coord.Z));
// All other blocks we can grab and add to the keeplist right away.
if (block) {
m_keeplist.push_back(block);
block->refGrab();
}
}
else if (mesh) {
// without mesh chunking we can add the block to the drawlist
block->refGrab();
m_drawlist.emplace(block_coord, block);
}
// Decide which sides to traverse next or to block away
@ -485,7 +493,7 @@ void ClientMap::updateDrawList()
g_profiler->avg("MapBlocks shortlist [#]", shortlist.size());
assert(m_drawlist.empty());
assert(m_drawlist.empty() || shortlist.empty());
for (auto pos : shortlist) {
MapBlock * block = getBlockNoCreateNoEx(pos);
if (block) {
@ -612,6 +620,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
auto is_frustum_culled = m_client->getCamera()->getFrustumCuller();
const MeshGrid mesh_grid = m_client->getMeshGrid();
for (auto &i : m_drawlist) {
v3s16 block_pos = i.first;
MapBlock *block = i.second;
@ -740,7 +749,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
material.TextureLayer[ShadowRenderer::TEXTURE_LAYER_SHADOW].Texture = nullptr;
}
v3f block_wpos = intToFloat(descriptor.m_pos / 8 * 8 * MAP_BLOCKSIZE, BS);
v3f block_wpos = intToFloat(mesh_grid.getMeshPos(descriptor.m_pos) * MAP_BLOCKSIZE, BS);
m.setTranslation(block_wpos - offset);
driver->setTransform(video::ETS_WORLD, m);
@ -978,6 +987,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
return;
}
const MeshGrid mesh_grid = m_client->getMeshGrid();
for (const auto &i : m_drawlist_shadow) {
// only process specific part of the list & break early
++count;
@ -1066,7 +1076,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
++material_swaps;
}
v3f block_wpos = intToFloat(descriptor.m_pos / 8 * 8 * MAP_BLOCKSIZE, BS);
v3f block_wpos = intToFloat(mesh_grid.getMeshPos(descriptor.m_pos) * MAP_BLOCKSIZE, BS);
m.setTranslation(block_wpos - offset);
driver->setTransform(video::ETS_WORLD, m);

@ -38,7 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
MeshMakeData::MeshMakeData(Client *client, bool use_shaders):
m_client(client),
m_use_shaders(use_shaders)
m_use_shaders(use_shaders),
m_mesh_grid(client->getMeshGrid()),
side_length(MAP_BLOCKSIZE * m_mesh_grid.cell_size)
{}
void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
@ -53,12 +55,11 @@ void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
m_vmanip.addArea(voxel_area);
}
void MeshMakeData::fillBlockData(const v3s16 &block_offset, MapNode *data)
void MeshMakeData::fillBlockData(const v3s16 &bp, MapNode *data)
{
v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
v3s16 bp = m_blockpos + block_offset;
v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE;
m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size);
}
@ -1179,18 +1180,18 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
v3s16 bp = data->m_blockpos;
// Only generate minimap mapblocks at even coordinates.
if (((bp.X | bp.Y | bp.Z) & 1) == 0 && data->m_client->getMinimap()) {
m_minimap_mapblocks.resize(8, nullptr);
if (data->m_mesh_grid.isMeshPos(bp) && data->m_client->getMinimap()) {
m_minimap_mapblocks.resize(data->m_mesh_grid.getCellVolume(), nullptr);
v3s16 ofs;
// See also client.cpp for the code that reads the array of minimap blocks.
for (ofs.Z = 0; ofs.Z <= 1; ofs.Z++)
for (ofs.Y = 0; ofs.Y <= 1; ofs.Y++)
for (ofs.X = 0; ofs.X <= 1; ofs.X++) {
for (ofs.Z = 0; ofs.Z < data->m_mesh_grid.cell_size; ofs.Z++)
for (ofs.Y = 0; ofs.Y < data->m_mesh_grid.cell_size; ofs.Y++)
for (ofs.X = 0; ofs.X < data->m_mesh_grid.cell_size; ofs.X++) {
v3s16 p = (bp + ofs) * MAP_BLOCKSIZE;
if (data->m_vmanip.getNodeNoEx(p).getContent() != CONTENT_IGNORE) {
MinimapMapblock *block = new MinimapMapblock;
m_minimap_mapblocks[ofs.Z * 4 + ofs.Y * 2 + ofs.X] = block;
m_minimap_mapblocks[data->m_mesh_grid.getOffsetIndex(ofs)] = block;
block->getMinimapNodes(&data->m_vmanip, p);
}
}
@ -1221,7 +1222,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
Convert FastFaces to MeshCollector
*/
v3f offset = intToFloat((data->m_blockpos - data->m_blockpos / 8 * 8) * MAP_BLOCKSIZE, BS);
v3f offset = intToFloat((data->m_blockpos - data->m_mesh_grid.getMeshPos(data->m_blockpos)) * MAP_BLOCKSIZE, BS);
MeshCollector collector(m_bounding_sphere_center, offset);
{
@ -1584,10 +1585,11 @@ std::unordered_map<v3s16, u8> get_solid_sides(MeshMakeData *data)
{
std::unordered_map<v3s16, u8> results;
v3s16 ofs;
const u16 mesh_chunk = data->side_length / MAP_BLOCKSIZE;
for (ofs.X = 0; ofs.X < 2; ofs.X++)
for (ofs.Y = 0; ofs.Y < 2; ofs.Y++)
for (ofs.Z = 0; ofs.Z < 2; ofs.Z++) {
for (ofs.X = 0; ofs.X < mesh_chunk; ofs.X++)
for (ofs.Y = 0; ofs.Y < mesh_chunk; ofs.Y++)
for (ofs.Z = 0; ofs.Z < mesh_chunk; ofs.Z++) {
v3s16 blockpos = data->m_blockpos + ofs;
v3s16 blockpos_nodes = blockpos * MAP_BLOCKSIZE;
const NodeDefManager *ndef = data->m_client->ndef();

@ -43,6 +43,7 @@ struct MeshMakeData
v3s16 m_blockpos = v3s16(-1337,-1337,-1337);
v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337);
bool m_smooth_lighting = false;
MeshGrid m_mesh_grid;
u16 side_length = MAP_BLOCKSIZE;
Client *m_client;
@ -54,7 +55,7 @@ struct MeshMakeData
Copy block data manually (to allow optimizations by the caller)
*/
void fillBlockDataBegin(const v3s16 &blockpos);
void fillBlockData(const v3s16 &block_offset, MapNode *data);
void fillBlockData(const v3s16 &bp, MapNode *data);
/*
Set the (node) position of a crack

@ -77,9 +77,11 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
MutexAutoLock lock(m_mutex);
// Mesh is placed at even positions at all coordinates
// (every 8-th block) and will cover 8 blocks
v3s16 mesh_position(p.X & ~1, p.Y & ~1, p.Z & ~1);
MeshGrid mesh_grid = m_client->getMeshGrid();
// Mesh is placed at the corner block of a chunk
// (where all coordinate are divisible by the chunk size)
v3s16 mesh_position(mesh_grid.getMeshPos(p));
/*
Mark the block as urgent if requested
*/
@ -99,14 +101,19 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
q->crack_level = m_client->getCrackLevel();
q->crack_pos = m_client->getCrackPos();
q->urgent |= urgent;
for (std::size_t i = 0; i < q->map_blocks.size(); i++) {
v3s16 pos;
int i = 0;
for (pos.X = q->p.X - 1; pos.X <= q->p.X + mesh_grid.cell_size; pos.X++)
for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + mesh_grid.cell_size; pos.Z++)
for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + mesh_grid.cell_size; pos.Y++) {
if (!q->map_blocks[i]) {
MapBlock *block = map->getBlockNoCreateNoEx(q->p + g_64dirs[i]);
MapBlock *block = map->getBlockNoCreateNoEx(pos);
if (block) {
block->refGrab();
q->map_blocks[i] = block;
}
}
i++;
}
return true;
}
@ -116,9 +123,12 @@ bool MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
Make a list of blocks necessary for mesh generation and lock the blocks in memory.
*/
std::vector<MapBlock *> map_blocks;
map_blocks.reserve(4*4*4);
for (v3s16 dp : g_64dirs) {
MapBlock *block = map->getBlockNoCreateNoEx(mesh_position + dp);
map_blocks.reserve((mesh_grid.cell_size+2)*(mesh_grid.cell_size+2)*(mesh_grid.cell_size+2));
v3s16 pos;
for (pos.X = mesh_position.X - 1; pos.X <= mesh_position.X + mesh_grid.cell_size; pos.X++)
for (pos.Z = mesh_position.Z - 1; pos.Z <= mesh_position.Z + mesh_grid.cell_size; pos.Z++)
for (pos.Y = mesh_position.Y - 1; pos.Y <= mesh_position.Y + mesh_grid.cell_size; pos.Y++) {
MapBlock *block = map->getBlockNoCreateNoEx(pos);
map_blocks.push_back(block);
if (block)
block->refGrab();
@ -182,13 +192,16 @@ void MeshUpdateQueue::fillDataFromMapBlocks(QueuedMeshUpdate *q)
{
MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders);
q->data = data;
data->side_length = 2 * MAP_BLOCKSIZE;
data->fillBlockDataBegin(q->p);
for (std::size_t i = 0; i < 64; i++) {
MapBlock *block = q->map_blocks[i];
data->fillBlockData(g_64dirs[i], block ? block->getData() : block_placeholder.data);
v3s16 pos;
int i = 0;
for (pos.X = q->p.X - 1; pos.X <= q->p.X + data->m_mesh_grid.cell_size; pos.X++)
for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + data->m_mesh_grid.cell_size; pos.Z++)
for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + data->m_mesh_grid.cell_size; pos.Y++) {
MapBlock *block = q->map_blocks[i++];
data->fillBlockData(pos, block ? block->getData() : block_placeholder.data);
}
data->setCrack(q->crack_level, q->crack_pos);

@ -185,6 +185,7 @@ void set_default_settings()
settings->setDefault("fps_max", "60");
settings->setDefault("fps_max_unfocused", "20");
settings->setDefault("viewing_range", "190");
settings->setDefault("client_mesh_chunk", "1");
#if ENABLE_GLES
settings->setDefault("near_plane", "0.1");
#endif

@ -110,78 +110,6 @@ const v3s16 g_27dirs[27] =
v3s16(0,0,0),
};
const v3s16 g_64dirs[64] =
{
// +right, +top, +back
v3s16( -1, -1, -1),
v3s16( -1, 0, -1),
v3s16( -1, 1, -1),
v3s16( -1, 2, -1),
v3s16( -1, -1, 0),
v3s16( -1, 0, 0),
v3s16( -1, 1, 0),
v3s16( -1, 2, 0),
v3s16( -1, -1, 1),
v3s16( -1, 0, 1),
v3s16( -1, 1, 1),
v3s16( -1, 2, 1),
v3s16( -1, -1, 2),
v3s16( -1, 0, 2),
v3s16( -1, 1, 2),
v3s16( -1, 2, 2),
v3s16( 0, -1, -1),
v3s16( 0, 0, -1),
v3s16( 0, 1, -1),
v3s16( 0, 2, -1),
v3s16( 0, -1, 0),
v3s16( 0, 0, 0),
v3s16( 0, 1, 0),
v3s16( 0, 2, 0),
v3s16( 0, -1, 1),
v3s16( 0, 0, 1),
v3s16( 0, 1, 1),
v3s16( 0, 2, 1),
v3s16( 0, -1, 2),
v3s16( 0, 0, 2),
v3s16( 0, 1, 2),
v3s16( 0, 2, 2),
v3s16( 1, -1, -1),
v3s16( 1, 0, -1),
v3s16( 1, 1, -1),
v3s16( 1, 2, -1),
v3s16( 1, -1, 0),
v3s16( 1, 0, 0),
v3s16( 1, 1, 0),
v3s16( 1, 2, 0),
v3s16( 1, -1, 1),
v3s16( 1, 0, 1),
v3s16( 1, 1, 1),
v3s16( 1, 2, 1),
v3s16( 1, -1, 2),
v3s16( 1, 0, 2),
v3s16( 1, 1, 2),
v3s16( 1, 2, 2),
v3s16( 2, -1, -1),
v3s16( 2, 0, -1),
v3s16( 2, 1, -1),
v3s16( 2, 2, -1),
v3s16( 2, -1, 0),
v3s16( 2, 0, 0),
v3s16( 2, 1, 0),
v3s16( 2, 2, 0),
v3s16( 2, -1, 1),
v3s16( 2, 0, 1),
v3s16( 2, 1, 1),
v3s16( 2, 2, 1),
v3s16( 2, -1, 2),
v3s16( 2, 0, 2),
v3s16( 2, 1, 2),
v3s16( 2, 2, 2),
};
const u8 wallmounted_to_facedir[6] = {
20,
0,

@ -31,9 +31,6 @@ extern const v3s16 g_26dirs[26];
// 26th is (0,0,0)
extern const v3s16 g_27dirs[27];
// all positions around an octablock in sector-first order
extern const v3s16 g_64dirs[64];
extern const u8 wallmounted_to_facedir[6];
extern const v3s16 wallmounted_dirs[8];

@ -145,6 +145,37 @@ inline v3s16 componentwise_max(const v3s16 &a, const v3s16 &b)
return v3s16(MYMAX(a.X, b.X), MYMAX(a.Y, b.Y), MYMAX(a.Z, b.Z));
}
/// @brief Describes a grid with given step, oirginating at (0,0,0)
struct MeshGrid {
u16 cell_size;
u32 getCellVolume() const { return cell_size * cell_size * cell_size; }
/// @brief returns closest step of the grid smaller than p
s16 getMeshPos(s16 p) const
{
return ((p - (p < 0) * (cell_size - 1)) / cell_size * cell_size);
}
/// @brief Returns coordinates of the origin of the grid cell containing p
v3s16 getMeshPos(v3s16 p) const
{
return v3s16(getMeshPos(p.X), getMeshPos(p.Y), getMeshPos(p.Z));
}
/// @brief Returns true if p is an origin of a cell in the grid.
bool isMeshPos(v3s16 &p) const
{
return ((p.X + p.Y + p.Z) % cell_size) == 0;
}
/// @brief Returns index of the given offset in a grid cell
/// All offset coordinates must be smaller than the size of the cell
u16 getOffsetIndex(v3s16 offset) const
{
return (offset.Z * cell_size + offset.Y) * cell_size + offset.X;
}
};
/** Returns \p f wrapped to the range [-360, 360]
*