mirror of
https://github.com/minetest/minetest.git
synced 2025-01-07 22:07:30 +01:00
Group sparse mesh buffers over entire scene for rendering
This commit is contained in:
parent
612d4f9656
commit
d2a7875b5b
@ -1856,6 +1856,10 @@ mesh_generation_interval (Mapblock mesh generation delay) int 0 0 50
|
|||||||
# Value of 0 (default) will let Luanti autodetect the number of available threads.
|
# Value of 0 (default) will let Luanti autodetect the number of available threads.
|
||||||
mesh_generation_threads (Mapblock mesh generation threads) int 0 0 8
|
mesh_generation_threads (Mapblock mesh generation threads) int 0 0 8
|
||||||
|
|
||||||
|
# All mesh buffers with less than this number of vertices will be merged
|
||||||
|
# during map rendering. This improves rendering performance.
|
||||||
|
mesh_buffer_min_vertices (Minimum vertex count for mesh buffers) int 100 0 1000
|
||||||
|
|
||||||
# True = 256
|
# True = 256
|
||||||
# False = 128
|
# False = 128
|
||||||
# Usable to make minimap smoother on slower machines.
|
# Usable to make minimap smoother on slower machines.
|
||||||
|
@ -1144,6 +1144,10 @@ CNullDriver::SHWBufferLink *CNullDriver::getBufferLink(const scene::IIndexBuffer
|
|||||||
//! Update all hardware buffers, remove unused ones
|
//! Update all hardware buffers, remove unused ones
|
||||||
void CNullDriver::updateAllHardwareBuffers()
|
void CNullDriver::updateAllHardwareBuffers()
|
||||||
{
|
{
|
||||||
|
// FIXME: this method can take a lot of time just doing the refcount
|
||||||
|
// checks and iteration (too much pointer chasing?) for
|
||||||
|
// large buffer counts (e.g. 50000)
|
||||||
|
|
||||||
auto it = HWBufferList.begin();
|
auto it = HWBufferList.begin();
|
||||||
while (it != HWBufferList.end()) {
|
while (it != HWBufferList.end()) {
|
||||||
SHWBufferLink *Link = *it;
|
SHWBufferLink *Link = *it;
|
||||||
|
@ -16,13 +16,13 @@ void OpenGLVBO::upload(const void *data, size_t size, size_t offset,
|
|||||||
GLenum usage, bool mustShrink)
|
GLenum usage, bool mustShrink)
|
||||||
{
|
{
|
||||||
bool newBuffer = false;
|
bool newBuffer = false;
|
||||||
|
assert(!(mustShrink && offset > 0)); // forbidden usage
|
||||||
if (!m_name) {
|
if (!m_name) {
|
||||||
GL.GenBuffers(1, &m_name);
|
GL.GenBuffers(1, &m_name);
|
||||||
if (!m_name)
|
if (!m_name)
|
||||||
return;
|
return;
|
||||||
newBuffer = true;
|
newBuffer = true;
|
||||||
} else if (size > m_size || mustShrink) {
|
} else if (size > m_size || mustShrink) {
|
||||||
// note: mustShrink && offset > 0 is forbidden
|
|
||||||
newBuffer = size != m_size;
|
newBuffer = size != m_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ namespace video
|
|||||||
/// OpenGL 3+ driver
|
/// OpenGL 3+ driver
|
||||||
///
|
///
|
||||||
/// For OpenGL 3.2 and higher. Compatibility profile is required currently.
|
/// For OpenGL 3.2 and higher. Compatibility profile is required currently.
|
||||||
class COpenGL3Driver : public COpenGL3DriverBase
|
class COpenGL3Driver final : public COpenGL3DriverBase
|
||||||
{
|
{
|
||||||
friend IVideoDriver *createOpenGL3Driver(const SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager);
|
friend IVideoDriver *createOpenGL3Driver(const SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager);
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ namespace video
|
|||||||
/// OpenGL ES 2+ driver
|
/// OpenGL ES 2+ driver
|
||||||
///
|
///
|
||||||
/// For OpenGL ES 2.0 and higher.
|
/// For OpenGL ES 2.0 and higher.
|
||||||
class COpenGLES2Driver : public COpenGL3DriverBase
|
class COpenGLES2Driver final : public COpenGL3DriverBase
|
||||||
{
|
{
|
||||||
friend IVideoDriver *createOGLES2Driver(const SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager);
|
friend IVideoDriver *createOGLES2Driver(const SIrrlichtCreationParameters ¶ms, io::IFileSystem *io, IContextManager *contextManager);
|
||||||
|
|
||||||
|
@ -22,16 +22,27 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// A helper struct
|
// data structure that groups block meshes by material
|
||||||
struct MeshBufListMaps
|
struct MeshBufListMaps
|
||||||
{
|
{
|
||||||
using MeshBufListMap = std::unordered_map<
|
// first = block pos
|
||||||
video::SMaterial,
|
using MeshBuf = std::pair<v3s16, scene::IMeshBuffer*>;
|
||||||
std::vector<std::pair<v3s16, scene::IMeshBuffer *>>
|
|
||||||
>;
|
using MeshBufList = std::vector<MeshBuf>;
|
||||||
|
|
||||||
|
using MeshBufListMap = std::unordered_map<video::SMaterial, MeshBufList>;
|
||||||
|
|
||||||
std::array<MeshBufListMap, MAX_TILE_LAYERS> maps;
|
std::array<MeshBufListMap, MAX_TILE_LAYERS> maps;
|
||||||
|
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
for (auto &map : maps) {
|
||||||
|
if (!map.empty())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void clear()
|
void clear()
|
||||||
{
|
{
|
||||||
for (auto &map : maps)
|
for (auto &map : maps)
|
||||||
@ -48,7 +59,67 @@ namespace {
|
|||||||
auto &bufs = map[m]; // default constructs if non-existent
|
auto &bufs = map[m]; // default constructs if non-existent
|
||||||
bufs.emplace_back(position, buf);
|
bufs.emplace_back(position, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addFromBlock(v3s16 block_pos, MapBlockMesh *block_mesh,
|
||||||
|
video::IVideoDriver *driver);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// reference to a mesh buffer used when rendering the map.
|
||||||
|
struct DrawDescriptor {
|
||||||
|
v3f m_pos; // world translation
|
||||||
|
bool m_reuse_material:1;
|
||||||
|
bool m_use_partial_buffer:1;
|
||||||
|
union {
|
||||||
|
scene::IMeshBuffer *m_buffer;
|
||||||
|
const PartialMeshBuffer *m_partial_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
DrawDescriptor(v3f pos, scene::IMeshBuffer *buffer, bool reuse_material = true) :
|
||||||
|
m_pos(pos), m_reuse_material(reuse_material), m_use_partial_buffer(false),
|
||||||
|
m_buffer(buffer)
|
||||||
|
{}
|
||||||
|
|
||||||
|
DrawDescriptor(v3f pos, const PartialMeshBuffer *buffer) :
|
||||||
|
m_pos(pos), m_reuse_material(false), m_use_partial_buffer(true),
|
||||||
|
m_partial_buffer(buffer)
|
||||||
|
{}
|
||||||
|
|
||||||
|
video::SMaterial &getMaterial();
|
||||||
|
/// @return number of vertices drawn
|
||||||
|
u32 draw(video::IVideoDriver* driver);
|
||||||
|
};
|
||||||
|
|
||||||
|
using DrawDescriptorList = std::vector<DrawDescriptor>;
|
||||||
|
|
||||||
|
/// @brief Append vertices to a mesh buffer
|
||||||
|
/// @note does not update bounding box!
|
||||||
|
void appendToMeshBuffer(scene::SMeshBuffer *dst, const scene::IMeshBuffer *src, v3f translate)
|
||||||
|
{
|
||||||
|
const size_t vcount = dst->Vertices->Data.size();
|
||||||
|
const size_t icount = dst->Indices->Data.size();
|
||||||
|
|
||||||
|
assert(src->getVertexType() == video::EVT_STANDARD);
|
||||||
|
const auto vptr = static_cast<const video::S3DVertex*>(src->getVertices());
|
||||||
|
dst->Vertices->Data.insert(dst->Vertices->Data.end(),
|
||||||
|
vptr, vptr + src->getVertexCount());
|
||||||
|
// apply translation
|
||||||
|
for (size_t j = vcount; j < dst->Vertices->Data.size(); j++)
|
||||||
|
dst->Vertices->Data[j].Pos += translate;
|
||||||
|
|
||||||
|
const auto iptr = src->getIndices();
|
||||||
|
dst->Indices->Data.insert(dst->Indices->Data.end(),
|
||||||
|
iptr, iptr + src->getIndexCount());
|
||||||
|
// fixup indices
|
||||||
|
if (vcount != 0) {
|
||||||
|
for (size_t j = icount; j < dst->Indices->Data.size(); j++)
|
||||||
|
dst->Indices->Data[j] += vcount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T subtract_or_zero(T a, T b) {
|
||||||
|
return b >= a ? T(0) : (a - b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -90,8 +161,7 @@ ClientMap::ClientMap(
|
|||||||
* the class is whith a name ;) Name property cames from ISceneNode base class.
|
* the class is whith a name ;) Name property cames from ISceneNode base class.
|
||||||
*/
|
*/
|
||||||
Name = "ClientMap";
|
Name = "ClientMap";
|
||||||
m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000,
|
setAutomaticCulling(scene::EAC_OFF);
|
||||||
BS*1000000,BS*1000000,BS*1000000);
|
|
||||||
|
|
||||||
for (const auto &name : ClientMap_settings)
|
for (const auto &name : ClientMap_settings)
|
||||||
g_settings->registerChangedCallback(name, on_settings_changed, this);
|
g_settings->registerChangedCallback(name, on_settings_changed, this);
|
||||||
@ -299,7 +369,7 @@ void ClientMap::updateDrawList()
|
|||||||
}
|
}
|
||||||
|
|
||||||
const v3s16 camera_block = getContainerPos(cam_pos_nodes, MAP_BLOCKSIZE);
|
const v3s16 camera_block = getContainerPos(cam_pos_nodes, MAP_BLOCKSIZE);
|
||||||
m_drawlist = std::map<v3s16, MapBlock*, MapBlockComparer>(MapBlockComparer(camera_block));
|
m_drawlist = decltype(m_drawlist)(MapBlockComparer(camera_block));
|
||||||
|
|
||||||
auto is_frustum_culled = m_client->getCamera()->getFrustumCuller();
|
auto is_frustum_culled = m_client->getCamera()->getFrustumCuller();
|
||||||
|
|
||||||
@ -692,23 +762,132 @@ void ClientMap::touchMapBlocks()
|
|||||||
g_profiler->avg("MapBlocks loaded [#]", blocks_loaded);
|
g_profiler->avg("MapBlocks loaded [#]", blocks_loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MeshBufListMaps::addFromBlock(v3s16 block_pos, MapBlockMesh *block_mesh,
|
||||||
|
video::IVideoDriver *driver)
|
||||||
|
{
|
||||||
|
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
|
||||||
|
scene::IMesh *mesh = block_mesh->getMesh(layer);
|
||||||
|
assert(mesh);
|
||||||
|
|
||||||
|
u32 c = mesh->getMeshBufferCount();
|
||||||
|
for (u32 i = 0; i < c; i++) {
|
||||||
|
scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
|
||||||
|
|
||||||
|
auto &material = buf->getMaterial();
|
||||||
|
auto *rnd = driver->getMaterialRenderer(material.MaterialType);
|
||||||
|
bool transparent = rnd && rnd->isTransparent();
|
||||||
|
if (!transparent)
|
||||||
|
add(buf, block_pos, layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a list of mesh buffers into the draw order, while potentially
|
||||||
|
* merging some.
|
||||||
|
* @param src buffer list
|
||||||
|
* @param dst draw order
|
||||||
|
* @param get_world_pos returns translation for a buffer
|
||||||
|
* @param buffer_trash output container for temporary mesh buffers
|
||||||
|
* @return number of buffers that were merged
|
||||||
|
*/
|
||||||
|
template <typename F, typename C>
|
||||||
|
static u32 transformBuffersToDrawOrder(
|
||||||
|
const MeshBufListMaps::MeshBufList &src, DrawDescriptorList &draw_order,
|
||||||
|
F get_world_pos, C &buffer_trash)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This is a tradeoff between time spent merging buffers and time spent
|
||||||
|
* due to excess drawcalls.
|
||||||
|
* Testing has shown that the ideal value is in the low hundreds, as extra
|
||||||
|
* CPU work quickly eats up the benefits.
|
||||||
|
* In MTG landscape scenes this was found to save around 20-40% of drawcalls.
|
||||||
|
*
|
||||||
|
* NOTE: if you attempt to test this with quicktune, it won't give you valid
|
||||||
|
* results since HW buffers stick around and Irrlicht handles large amounts
|
||||||
|
* inefficiently.
|
||||||
|
*
|
||||||
|
* TODO: as a next step we should cache merged meshes, so they do not need
|
||||||
|
* to be re-built *and* can be kept in GPU memory.
|
||||||
|
*/
|
||||||
|
const u32 target_min_vertices = g_settings->getU32("mesh_buffer_min_vertices");
|
||||||
|
|
||||||
|
const auto draw_order_pre = draw_order.size();
|
||||||
|
auto *driver = RenderingEngine::get_video_driver();
|
||||||
|
|
||||||
|
// check if we can even merge anything
|
||||||
|
u32 can_merge = 0;
|
||||||
|
u32 total_vtx = 0, total_idx = 0;
|
||||||
|
for (auto &pair : src) {
|
||||||
|
if (pair.second->getVertexCount() < target_min_vertices) {
|
||||||
|
can_merge++;
|
||||||
|
total_vtx += pair.second->getVertexCount();
|
||||||
|
total_idx += pair.second->getIndexCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scene::SMeshBuffer *tmp = nullptr;
|
||||||
|
const auto &finish_buf = [&] () {
|
||||||
|
if (tmp) {
|
||||||
|
draw_order.emplace_back(v3f(0), tmp);
|
||||||
|
total_vtx = subtract_or_zero(total_vtx, tmp->getVertexCount());
|
||||||
|
total_idx = subtract_or_zero(total_idx, tmp->getIndexCount());
|
||||||
|
|
||||||
|
// Upload buffer here explicitly to give the driver some
|
||||||
|
// extra time to get it ready before drawing.
|
||||||
|
tmp->setHardwareMappingHint(scene::EHM_STREAM);
|
||||||
|
driver->updateHardwareBuffer(tmp->getVertexBuffer());
|
||||||
|
driver->updateHardwareBuffer(tmp->getIndexBuffer());
|
||||||
|
}
|
||||||
|
tmp = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// iterate in reverse to get closest blocks first
|
||||||
|
for (auto it = src.rbegin(); it != src.rend(); ++it) {
|
||||||
|
v3f translate = get_world_pos(it->first);
|
||||||
|
auto *buf = it->second;
|
||||||
|
if (can_merge < 2 || buf->getVertexCount() >= target_min_vertices) {
|
||||||
|
draw_order.emplace_back(translate, buf);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool new_buffer = false;
|
||||||
|
if (!tmp)
|
||||||
|
new_buffer = true;
|
||||||
|
else if (tmp->getVertexCount() + buf->getVertexCount() > U16_MAX)
|
||||||
|
new_buffer = true;
|
||||||
|
if (new_buffer) {
|
||||||
|
finish_buf();
|
||||||
|
tmp = new scene::SMeshBuffer();
|
||||||
|
buffer_trash.push_back(tmp);
|
||||||
|
assert(tmp->getPrimitiveType() == buf->getPrimitiveType());
|
||||||
|
tmp->Material = buf->getMaterial();
|
||||||
|
// preallocate
|
||||||
|
tmp->Vertices->Data.reserve(total_vtx);
|
||||||
|
tmp->Indices->Data.reserve(total_idx);
|
||||||
|
}
|
||||||
|
appendToMeshBuffer(tmp, buf, translate);
|
||||||
|
}
|
||||||
|
finish_buf();
|
||||||
|
|
||||||
|
// first call needs to set the material
|
||||||
|
if (draw_order.size() > draw_order_pre)
|
||||||
|
draw_order[draw_order_pre].m_reuse_material = false;
|
||||||
|
|
||||||
|
return can_merge < 2 ? 0 : can_merge;
|
||||||
|
}
|
||||||
|
|
||||||
void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
|
void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
|
||||||
{
|
{
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
|
||||||
bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
|
const bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
|
||||||
|
|
||||||
std::string prefix;
|
std::string prefix;
|
||||||
if (pass == scene::ESNRP_SOLID)
|
if (pass == scene::ESNRP_SOLID)
|
||||||
prefix = "renderMap(SOLID): ";
|
prefix = "renderMap(SOLID): ";
|
||||||
else
|
else
|
||||||
prefix = "renderMap(TRANSPARENT): ";
|
prefix = "renderMap(TRANS): ";
|
||||||
|
|
||||||
/*
|
|
||||||
This is called two times per frame, reset on the non-transparent one
|
|
||||||
*/
|
|
||||||
if (pass == scene::ESNRP_SOLID)
|
|
||||||
m_last_drawn_sectors.clear();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Get animation parameters
|
Get animation parameters
|
||||||
@ -719,16 +898,16 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
|
|||||||
|
|
||||||
const v3f camera_position = m_camera_position;
|
const v3f camera_position = m_camera_position;
|
||||||
|
|
||||||
/*
|
const auto mesh_grid = m_client->getMeshGrid();
|
||||||
Get all blocks and draw all visible ones
|
// Gets world position from block map position
|
||||||
*/
|
const auto get_block_wpos = [&] (v3s16 pos) -> v3f {
|
||||||
|
return intToFloat(mesh_grid.getMeshPos(pos) * MAP_BLOCKSIZE - m_camera_offset, BS);
|
||||||
|
};
|
||||||
|
|
||||||
u32 vertex_count = 0;
|
u32 merged_count = 0;
|
||||||
u32 drawcall_count = 0;
|
|
||||||
|
|
||||||
// For limiting number of mesh animations per frame
|
// For limiting number of mesh animations per frame
|
||||||
u32 mesh_animate_count = 0;
|
u32 mesh_animate_count = 0;
|
||||||
//u32 mesh_animate_count_far = 0;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Update transparent meshes
|
Update transparent meshes
|
||||||
@ -737,18 +916,18 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
|
|||||||
updateTransparentMeshBuffers();
|
updateTransparentMeshBuffers();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Draw the selected MapBlocks
|
Collect everything we need to draw
|
||||||
*/
|
*/
|
||||||
|
TimeTaker tt_collect("");
|
||||||
|
|
||||||
MeshBufListMaps grouped_buffers;
|
MeshBufListMaps grouped_buffers;
|
||||||
std::vector<DrawDescriptor> draw_order;
|
std::vector<scene::IMeshBuffer*> buffer_trash;
|
||||||
video::SMaterial previous_material;
|
DrawDescriptorList draw_order;
|
||||||
|
|
||||||
auto is_frustum_culled = m_client->getCamera()->getFrustumCuller();
|
auto is_frustum_culled = m_client->getCamera()->getFrustumCuller();
|
||||||
|
|
||||||
const MeshGrid mesh_grid = m_client->getMeshGrid();
|
|
||||||
for (auto &i : m_drawlist) {
|
for (auto &i : m_drawlist) {
|
||||||
v3s16 block_pos = i.first;
|
const v3s16 block_pos = i.first;
|
||||||
MapBlock *block = i.second;
|
MapBlock *block = i.second;
|
||||||
MapBlockMesh *block_mesh = block->mesh;
|
MapBlockMesh *block_mesh = block->mesh;
|
||||||
|
|
||||||
@ -789,49 +968,28 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
|
|||||||
// In transparent pass, the mesh will give us
|
// In transparent pass, the mesh will give us
|
||||||
// the partial buffers in the correct order
|
// the partial buffers in the correct order
|
||||||
for (auto &buffer : block_mesh->getTransparentBuffers())
|
for (auto &buffer : block_mesh->getTransparentBuffers())
|
||||||
draw_order.emplace_back(block_pos, &buffer);
|
draw_order.emplace_back(get_block_wpos(block_pos), &buffer);
|
||||||
}
|
} else {
|
||||||
else {
|
// Otherwise, group them
|
||||||
// otherwise, group buffers across meshes
|
grouped_buffers.addFromBlock(block_pos, block_mesh, driver);
|
||||||
// using MeshBufListMaps
|
|
||||||
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
|
|
||||||
scene::IMesh *mesh = block_mesh->getMesh(layer);
|
|
||||||
assert(mesh);
|
|
||||||
|
|
||||||
u32 c = mesh->getMeshBufferCount();
|
|
||||||
for (u32 i = 0; i < c; i++) {
|
|
||||||
scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
|
|
||||||
|
|
||||||
video::SMaterial& material = buf->getMaterial();
|
|
||||||
video::IMaterialRenderer* rnd =
|
|
||||||
driver->getMaterialRenderer(material.MaterialType);
|
|
||||||
bool transparent = (rnd && rnd->isTransparent());
|
|
||||||
if (!transparent) {
|
|
||||||
if (buf->getVertexCount() == 0)
|
|
||||||
errorstream << "Block [" << analyze_block(block)
|
|
||||||
<< "] contains an empty meshbuf" << std::endl;
|
|
||||||
|
|
||||||
grouped_buffers.add(buf, block_pos, layer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture draw order for all solid meshes
|
assert(!is_transparent_pass || grouped_buffers.empty());
|
||||||
for (auto &map : grouped_buffers.maps) {
|
for (auto &map : grouped_buffers.maps) {
|
||||||
for (auto &list : map) {
|
for (auto &list : map) {
|
||||||
// iterate in reverse to draw closest blocks first
|
merged_count += transformBuffersToDrawOrder(
|
||||||
for (auto it = list.second.rbegin(); it != list.second.rend(); ++it) {
|
list.second, draw_order, get_block_wpos, buffer_trash);
|
||||||
draw_order.emplace_back(it->first, it->second, it != list.second.rbegin());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeTaker draw("Drawing mesh buffers");
|
g_profiler->avg(prefix + "collecting [ms]", tt_collect.stop(true));
|
||||||
|
|
||||||
|
TimeTaker tt_draw("");
|
||||||
|
|
||||||
core::matrix4 m; // Model matrix
|
core::matrix4 m; // Model matrix
|
||||||
v3f offset = intToFloat(m_camera_offset, BS);
|
u32 vertex_count = 0;
|
||||||
|
u32 drawcall_count = 0;
|
||||||
u32 material_swaps = 0;
|
u32 material_swaps = 0;
|
||||||
|
|
||||||
// Render all mesh buffers in order
|
// Render all mesh buffers in order
|
||||||
@ -867,18 +1025,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
|
|||||||
material.TextureLayers[ShadowRenderer::TEXTURE_LAYER_SHADOW].Texture = nullptr;
|
material.TextureLayers[ShadowRenderer::TEXTURE_LAYER_SHADOW].Texture = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
v3f block_wpos = intToFloat(mesh_grid.getMeshPos(descriptor.m_pos) * MAP_BLOCKSIZE, BS);
|
m.setTranslation(descriptor.m_pos);
|
||||||
m.setTranslation(block_wpos - offset);
|
|
||||||
|
|
||||||
driver->setTransform(video::ETS_WORLD, m);
|
driver->setTransform(video::ETS_WORLD, m);
|
||||||
|
|
||||||
vertex_count += descriptor.draw(driver);
|
vertex_count += descriptor.draw(driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
|
g_profiler->avg(prefix + "draw meshes [ms]", tt_draw.stop(true));
|
||||||
|
|
||||||
// Log only on solid pass because values are the same
|
|
||||||
if (pass == scene::ESNRP_SOLID) {
|
if (pass == scene::ESNRP_SOLID) {
|
||||||
g_profiler->avg("renderMap(): animated meshes [#]", mesh_animate_count);
|
g_profiler->avg("renderMap(): animated meshes [#]", mesh_animate_count);
|
||||||
|
g_profiler->avg(prefix + "merged buffers [#]", merged_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pass == scene::ESNRP_TRANSPARENT) {
|
if (pass == scene::ESNRP_TRANSPARENT) {
|
||||||
@ -888,6 +1045,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
|
|||||||
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
|
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
|
||||||
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
|
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
|
||||||
g_profiler->avg(prefix + "material swaps [#]", material_swaps);
|
g_profiler->avg(prefix + "material swaps [#]", material_swaps);
|
||||||
|
|
||||||
|
for (auto &x : buffer_trash)
|
||||||
|
x->drop();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
|
static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
|
||||||
@ -1096,12 +1256,15 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
|
|||||||
else
|
else
|
||||||
prefix = "renderMap(SHADOW SOLID): ";
|
prefix = "renderMap(SHADOW SOLID): ";
|
||||||
|
|
||||||
u32 drawcall_count = 0;
|
const auto mesh_grid = m_client->getMeshGrid();
|
||||||
u32 vertex_count = 0;
|
// Gets world position from block map position
|
||||||
|
const auto get_block_wpos = [&] (v3s16 pos) -> v3f {
|
||||||
|
return intToFloat(mesh_grid.getMeshPos(pos) * MAP_BLOCKSIZE - m_camera_offset, BS);
|
||||||
|
};
|
||||||
|
|
||||||
MeshBufListMaps grouped_buffers;
|
MeshBufListMaps grouped_buffers;
|
||||||
std::vector<DrawDescriptor> draw_order;
|
std::vector<scene::IMeshBuffer*> buffer_trash;
|
||||||
|
DrawDescriptorList draw_order;
|
||||||
|
|
||||||
std::size_t count = 0;
|
std::size_t count = 0;
|
||||||
std::size_t meshes_per_frame = m_drawlist_shadow.size() / total_frames + 1;
|
std::size_t meshes_per_frame = m_drawlist_shadow.size() / total_frames + 1;
|
||||||
@ -1113,7 +1276,6 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MeshGrid mesh_grid = m_client->getMeshGrid();
|
|
||||||
for (const auto &i : m_drawlist_shadow) {
|
for (const auto &i : m_drawlist_shadow) {
|
||||||
// only process specific part of the list & break early
|
// only process specific part of the list & break early
|
||||||
++count;
|
++count;
|
||||||
@ -1136,52 +1298,25 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
|
|||||||
// In transparent pass, the mesh will give us
|
// In transparent pass, the mesh will give us
|
||||||
// the partial buffers in the correct order
|
// the partial buffers in the correct order
|
||||||
for (auto &buffer : block->mesh->getTransparentBuffers())
|
for (auto &buffer : block->mesh->getTransparentBuffers())
|
||||||
draw_order.emplace_back(block_pos, &buffer);
|
draw_order.emplace_back(get_block_wpos(block_pos), &buffer);
|
||||||
}
|
} else {
|
||||||
else {
|
// Otherwise, group them
|
||||||
// otherwise, group buffers across meshes
|
grouped_buffers.addFromBlock(block_pos, block->mesh, driver);
|
||||||
// using MeshBufListMaps
|
|
||||||
MapBlockMesh *mapBlockMesh = block->mesh;
|
|
||||||
assert(mapBlockMesh);
|
|
||||||
|
|
||||||
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
|
|
||||||
scene::IMesh *mesh = mapBlockMesh->getMesh(layer);
|
|
||||||
assert(mesh);
|
|
||||||
|
|
||||||
u32 c = mesh->getMeshBufferCount();
|
|
||||||
for (u32 i = 0; i < c; i++) {
|
|
||||||
scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
|
|
||||||
|
|
||||||
video::SMaterial &mat = buf->getMaterial();
|
|
||||||
auto rnd = driver->getMaterialRenderer(mat.MaterialType);
|
|
||||||
bool transparent = rnd && rnd->isTransparent();
|
|
||||||
if (!transparent)
|
|
||||||
grouped_buffers.add(buf, block_pos, layer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 buffer_count = 0;
|
|
||||||
for (auto &map : grouped_buffers.maps)
|
|
||||||
for (auto &list : map)
|
|
||||||
buffer_count += list.second.size();
|
|
||||||
|
|
||||||
draw_order.reserve(draw_order.size() + buffer_count);
|
|
||||||
|
|
||||||
// Capture draw order for all solid meshes
|
|
||||||
for (auto &map : grouped_buffers.maps) {
|
for (auto &map : grouped_buffers.maps) {
|
||||||
for (auto &list : map) {
|
for (auto &list : map) {
|
||||||
// iterate in reverse to draw closest blocks first
|
transformBuffersToDrawOrder(
|
||||||
for (auto it = list.second.rbegin(); it != list.second.rend(); ++it)
|
list.second, draw_order, get_block_wpos, buffer_trash);
|
||||||
draw_order.emplace_back(it->first, it->second, it != list.second.rbegin());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeTaker draw("Drawing shadow mesh buffers");
|
TimeTaker draw("");
|
||||||
|
|
||||||
core::matrix4 m; // Model matrix
|
core::matrix4 m; // Model matrix
|
||||||
v3f offset = intToFloat(m_camera_offset, BS);
|
u32 drawcall_count = 0;
|
||||||
|
u32 vertex_count = 0;
|
||||||
u32 material_swaps = 0;
|
u32 material_swaps = 0;
|
||||||
|
|
||||||
// Render all mesh buffers in order
|
// Render all mesh buffers in order
|
||||||
@ -1221,8 +1356,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
|
|||||||
++material_swaps;
|
++material_swaps;
|
||||||
}
|
}
|
||||||
|
|
||||||
v3f block_wpos = intToFloat(mesh_grid.getMeshPos(descriptor.m_pos) * MAP_BLOCKSIZE, BS);
|
m.setTranslation(descriptor.m_pos);
|
||||||
m.setTranslation(block_wpos - offset);
|
|
||||||
|
|
||||||
driver->setTransform(video::ETS_WORLD, m);
|
driver->setTransform(video::ETS_WORLD, m);
|
||||||
vertex_count += descriptor.draw(driver);
|
vertex_count += descriptor.draw(driver);
|
||||||
@ -1232,12 +1366,16 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
|
|||||||
video::SMaterial clean;
|
video::SMaterial clean;
|
||||||
clean.BlendOperation = video::EBO_ADD;
|
clean.BlendOperation = video::EBO_ADD;
|
||||||
driver->setMaterial(clean); // reset material to defaults
|
driver->setMaterial(clean); // reset material to defaults
|
||||||
|
// FIXME: why is this here?
|
||||||
driver->draw3DLine(v3f(), v3f(), video::SColor(0));
|
driver->draw3DLine(v3f(), v3f(), video::SColor(0));
|
||||||
|
|
||||||
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
|
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
|
||||||
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
|
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
|
||||||
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
|
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
|
||||||
g_profiler->avg(prefix + "material swaps [#]", material_swaps);
|
g_profiler->avg(prefix + "material swaps [#]", material_swaps);
|
||||||
|
|
||||||
|
for (auto &x : buffer_trash)
|
||||||
|
x->drop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1347,12 +1485,12 @@ void ClientMap::updateTransparentMeshBuffers()
|
|||||||
m_needs_update_transparent_meshes = false;
|
m_needs_update_transparent_meshes = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
video::SMaterial &ClientMap::DrawDescriptor::getMaterial()
|
video::SMaterial &DrawDescriptor::getMaterial()
|
||||||
{
|
{
|
||||||
return (m_use_partial_buffer ? m_partial_buffer->getBuffer() : m_buffer)->getMaterial();
|
return (m_use_partial_buffer ? m_partial_buffer->getBuffer() : m_buffer)->getMaterial();
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 ClientMap::DrawDescriptor::draw(video::IVideoDriver* driver)
|
u32 DrawDescriptor::draw(video::IVideoDriver* driver)
|
||||||
{
|
{
|
||||||
if (m_use_partial_buffer) {
|
if (m_use_partial_buffer) {
|
||||||
m_partial_buffer->draw(driver);
|
m_partial_buffer->draw(driver);
|
||||||
|
@ -115,7 +115,6 @@ private:
|
|||||||
// update the vertex order in transparent mesh buffers
|
// update the vertex order in transparent mesh buffers
|
||||||
void updateTransparentMeshBuffers();
|
void updateTransparentMeshBuffers();
|
||||||
|
|
||||||
|
|
||||||
// Orders blocks by distance to the camera
|
// Orders blocks by distance to the camera
|
||||||
class MapBlockComparer
|
class MapBlockComparer
|
||||||
{
|
{
|
||||||
@ -133,30 +132,6 @@ private:
|
|||||||
v3s16 m_camera_block;
|
v3s16 m_camera_block;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// reference to a mesh buffer used when rendering the map.
|
|
||||||
struct DrawDescriptor {
|
|
||||||
v3s16 m_pos;
|
|
||||||
union {
|
|
||||||
scene::IMeshBuffer *m_buffer;
|
|
||||||
const PartialMeshBuffer *m_partial_buffer;
|
|
||||||
};
|
|
||||||
bool m_reuse_material:1;
|
|
||||||
bool m_use_partial_buffer:1;
|
|
||||||
|
|
||||||
DrawDescriptor(v3s16 pos, scene::IMeshBuffer *buffer, bool reuse_material) :
|
|
||||||
m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material), m_use_partial_buffer(false)
|
|
||||||
{}
|
|
||||||
|
|
||||||
DrawDescriptor(v3s16 pos, const PartialMeshBuffer *buffer) :
|
|
||||||
m_pos(pos), m_partial_buffer(buffer), m_reuse_material(false), m_use_partial_buffer(true)
|
|
||||||
{}
|
|
||||||
|
|
||||||
video::SMaterial &getMaterial();
|
|
||||||
/// @return index count
|
|
||||||
u32 draw(video::IVideoDriver* driver);
|
|
||||||
};
|
|
||||||
|
|
||||||
Client *m_client;
|
Client *m_client;
|
||||||
RenderingEngine *m_rendering_engine;
|
RenderingEngine *m_rendering_engine;
|
||||||
|
|
||||||
@ -177,8 +152,6 @@ private:
|
|||||||
std::map<v3s16, MapBlock*> m_drawlist_shadow;
|
std::map<v3s16, MapBlock*> m_drawlist_shadow;
|
||||||
bool m_needs_update_drawlist;
|
bool m_needs_update_drawlist;
|
||||||
|
|
||||||
std::set<v2s16> m_last_drawn_sectors;
|
|
||||||
|
|
||||||
bool m_cache_trilinear_filter;
|
bool m_cache_trilinear_filter;
|
||||||
bool m_cache_bilinear_filter;
|
bool m_cache_bilinear_filter;
|
||||||
bool m_cache_anistropic_filter;
|
bool m_cache_anistropic_filter;
|
||||||
|
@ -104,11 +104,11 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
|
|||||||
os << std::fixed
|
os << std::fixed
|
||||||
<< PROJECT_NAME_C " " << g_version_hash
|
<< PROJECT_NAME_C " " << g_version_hash
|
||||||
<< " | FPS: " << fps
|
<< " | FPS: " << fps
|
||||||
<< std::setprecision(0)
|
<< std::setprecision(fps >= 100 ? 1 : 0)
|
||||||
<< " | drawtime: " << m_drawtime_avg << "ms"
|
<< " | drawtime: " << m_drawtime_avg << "ms"
|
||||||
<< std::setprecision(1)
|
<< std::setprecision(1)
|
||||||
<< " | dtime jitter: "
|
<< " | dtime jitter: "
|
||||||
<< (stats.dtime_jitter.max_fraction * 100.0) << "%"
|
<< (stats.dtime_jitter.max_fraction * 100.0f) << "%"
|
||||||
<< std::setprecision(1)
|
<< std::setprecision(1)
|
||||||
<< " | view range: "
|
<< " | view range: "
|
||||||
<< (draw_control->range_all ? "All" : itos(draw_control->wanted_range))
|
<< (draw_control->range_all ? "All" : itos(draw_control->wanted_range))
|
||||||
|
@ -83,10 +83,13 @@ struct TileLayer
|
|||||||
|
|
||||||
void applyMaterialOptionsWithShaders(video::SMaterial &material) const;
|
void applyMaterialOptionsWithShaders(video::SMaterial &material) const;
|
||||||
|
|
||||||
|
/// @return is this layer semi-transparent?
|
||||||
bool isTransparent() const
|
bool isTransparent() const
|
||||||
{
|
{
|
||||||
|
// see also: the mapping in ShaderSource::generateShader()
|
||||||
switch (material_type) {
|
switch (material_type) {
|
||||||
case TILE_MATERIAL_ALPHA:
|
case TILE_MATERIAL_ALPHA:
|
||||||
|
case TILE_MATERIAL_PLAIN_ALPHA:
|
||||||
case TILE_MATERIAL_LIQUID_TRANSPARENT:
|
case TILE_MATERIAL_LIQUID_TRANSPARENT:
|
||||||
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
|
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
|
||||||
return true;
|
return true;
|
||||||
|
@ -104,6 +104,7 @@ void set_default_settings()
|
|||||||
settings->setDefault("sound_extensions_blacklist", "");
|
settings->setDefault("sound_extensions_blacklist", "");
|
||||||
settings->setDefault("mesh_generation_interval", "0");
|
settings->setDefault("mesh_generation_interval", "0");
|
||||||
settings->setDefault("mesh_generation_threads", "0");
|
settings->setDefault("mesh_generation_threads", "0");
|
||||||
|
settings->setDefault("mesh_buffer_min_vertices", "100");
|
||||||
settings->setDefault("free_move", "false");
|
settings->setDefault("free_move", "false");
|
||||||
settings->setDefault("pitch_move", "false");
|
settings->setDefault("pitch_move", "false");
|
||||||
settings->setDefault("fast_move", "false");
|
settings->setDefault("fast_move", "false");
|
||||||
|
@ -121,7 +121,7 @@ int Profiler::print(std::ostream &o, u32 page, u32 pagecount)
|
|||||||
{
|
{
|
||||||
GraphValues values;
|
GraphValues values;
|
||||||
getPage(values, page, pagecount);
|
getPage(values, page, pagecount);
|
||||||
char buffer[50];
|
char buffer[128];
|
||||||
|
|
||||||
for (const auto &i : values) {
|
for (const auto &i : values) {
|
||||||
o << " " << i.first << " ";
|
o << " " << i.first << " ";
|
||||||
@ -132,7 +132,7 @@ int Profiler::print(std::ostream &o, u32 page, u32 pagecount)
|
|||||||
|
|
||||||
{
|
{
|
||||||
// Padding
|
// Padding
|
||||||
s32 space = std::max(0, 44 - (s32)i.first.size());
|
s32 space = std::max(0, 46 - (s32)i.first.size());
|
||||||
memset(buffer, '_', space);
|
memset(buffer, '_', space);
|
||||||
buffer[space] = '\0';
|
buffer[space] = '\0';
|
||||||
o << buffer;
|
o << buffer;
|
||||||
|
Loading…
Reference in New Issue
Block a user