* 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>
# 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
}
m_cache_save_interval = g_settings->getU16("server_map_save_interval");
+ m_mesh_grid = { g_settings->getU16("client_mesh_chunk") };
}
void Client::migrateModStorage()
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);
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]);
}
#include "network/peerhandler.h"
#include "gameparams.h"
#include <fstream>
+#include "util/numeric.h"
#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f
{
return m_env.getLocalPlayer()->formspec_prepend;
}
+ inline MeshGrid getMeshGrid()
+ {
+ return m_mesh_grid;
+ }
+
private:
void loadMods();
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;
};
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) {
MapBlockMesh *mesh = block ? block->mesh : nullptr;
-
// Calculate the coordinates for range and frutum culling
v3f mesh_sphere_center;
f32 mesh_sphere_radius;
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
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) {
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;
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);
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;
++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);
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)
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);
}
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);
}
}
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);
{
{
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();
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;
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
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
*/
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;
}
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();
{
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);
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
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,
// 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];
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]
*