]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/client/clientmap.cpp
Merge branch 'master' of https://github.com/minetest/minetest
[dragonfireclient.git] / src / client / clientmap.cpp
index 3e4ab2e94aefed7ebf7f048b16c8fac1bc66359c..51f0f6896774dc10a64cb622c0b5499f492a5586 100644 (file)
@@ -31,17 +31,57 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <algorithm>
 #include "client/renderingengine.h"
 
+// struct MeshBufListList
+void MeshBufListList::clear()
+{
+       for (auto &list : lists)
+               list.clear();
+}
+
+void MeshBufListList::add(scene::IMeshBuffer *buf, v3s16 position, u8 layer)
+{
+       // Append to the correct layer
+       std::vector<MeshBufList> &list = lists[layer];
+       const video::SMaterial &m = buf->getMaterial();
+       for (MeshBufList &l : list) {
+               // comparing a full material is quite expensive so we don't do it if
+               // not even first texture is equal
+               if (l.m.TextureLayer[0].Texture != m.TextureLayer[0].Texture)
+                       continue;
+
+               if (l.m == m) {
+                       l.bufs.emplace_back(position, buf);
+                       return;
+               }
+       }
+       MeshBufList l;
+       l.m = m;
+       l.bufs.emplace_back(position, buf);
+       list.emplace_back(l);
+}
+
+// ClientMap
+
 ClientMap::ClientMap(
                Client *client,
+               RenderingEngine *rendering_engine,
                MapDrawControl &control,
                s32 id
 ):
-       Map(dout_client, client),
-       scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
-               RenderingEngine::get_scene_manager(), id),
+       Map(client),
+       scene::ISceneNode(rendering_engine->get_scene_manager()->getRootSceneNode(),
+               rendering_engine->get_scene_manager(), id),
        m_client(client),
-       m_control(control)
+       m_rendering_engine(rendering_engine),
+       m_control(control),
+       m_drawlist(MapBlockComparer(v3s16(0,0,0)))
 {
+
+       /*
+        * @Liso: Sadly C++ doesn't have introspection, so the only way we have to know
+        * the class is whith a name ;) Name property cames from ISceneNode base class.
+        */
+       Name = "ClientMap";
        m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000,
                        BS*1000000,BS*1000000,BS*1000000);
 
@@ -57,9 +97,32 @@ ClientMap::ClientMap(
        m_cache_trilinear_filter  = g_settings->getBool("trilinear_filter");
        m_cache_bilinear_filter   = g_settings->getBool("bilinear_filter");
        m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter");
+       m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance");
 
 }
 
+void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset)
+{
+       v3s16 previous_node = floatToInt(m_camera_position, BS) + m_camera_offset;
+       v3s16 previous_block = getContainerPos(previous_node, MAP_BLOCKSIZE);
+
+       m_camera_position = pos;
+       m_camera_direction = dir;
+       m_camera_fov = fov;
+       m_camera_offset = offset;
+
+       v3s16 current_node = floatToInt(m_camera_position, BS) + m_camera_offset;
+       v3s16 current_block = getContainerPos(current_node, MAP_BLOCKSIZE);
+
+       // reorder the blocks when camera crosses block boundary
+       if (previous_block != current_block)
+               m_needs_update_drawlist = true;
+
+       // reorder transparent meshes when camera crosses node boundary
+       if (previous_node != current_node)
+               m_needs_update_transparent_meshes = true;
+}
+
 MapSector * ClientMap::emergeSector(v2s16 p2d)
 {
        // Check that it doesn't exist already
@@ -83,12 +146,21 @@ void ClientMap::OnRegisterSceneNode()
        }
 
        ISceneNode::OnRegisterSceneNode();
+
+       if (!m_added_to_shadow_renderer) {
+               m_added_to_shadow_renderer = true;
+               if (auto shadows = m_rendering_engine->get_shadow_renderer())
+                       shadows->addNodeToShadowList(this);
+       }
 }
 
 void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes,
-               v3s16 *p_blocks_min, v3s16 *p_blocks_max)
+               v3s16 *p_blocks_min, v3s16 *p_blocks_max, float range)
 {
-       v3s16 box_nodes_d = m_control.wanted_range * v3s16(1, 1, 1);
+       if (range <= 0.0f)
+               range = m_control.wanted_range;
+
+       v3s16 box_nodes_d = range * v3s16(1, 1, 1);
        // Define p_nodes_min/max as v3s32 because 'cam_pos_nodes -/+ box_nodes_d'
        // can exceed the range of v3s16 when a large view range is used near the
        // world edges.
@@ -116,26 +188,32 @@ void ClientMap::updateDrawList()
 {
        ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
 
+       m_needs_update_drawlist = false;
+
        for (auto &i : m_drawlist) {
                MapBlock *block = i.second;
                block->refDrop();
        }
        m_drawlist.clear();
 
-       v3f camera_position = m_camera_position;
-       v3f camera_direction = m_camera_direction;
-       f32 camera_fov = m_camera_fov;
+       const v3f camera_position = m_camera_position;
+       const v3f camera_direction = m_camera_direction;
 
        // Use a higher fov to accomodate faster camera movements.
        // Blocks are cropped better when they are drawn.
-       // Or maybe they aren't? Well whatever.
-       camera_fov *= 1.2;
+       const f32 camera_fov = m_camera_fov * 1.1f;
 
        v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
+
        v3s16 p_blocks_min;
        v3s16 p_blocks_max;
        getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max);
 
+       // Read the vision range, unless unlimited range is enabled.
+       float range = m_control.range_all ? 1e7 : m_control.wanted_range;
+
+       // Number of blocks currently loaded by the client
+       u32 blocks_loaded = 0;
        // Number of blocks with mesh in rendering range
        u32 blocks_in_range_with_mesh = 0;
        // Number of blocks occlusion culled
@@ -144,13 +222,16 @@ void ClientMap::updateDrawList()
        // No occlusion culling when free_move is on and camera is
        // inside ground
        bool occlusion_culling_enabled = true;
-       if (g_settings->getBool("free_move") && g_settings->getBool("noclip")) {
+       if ((g_settings->getBool("free_move") && g_settings->getBool("noclip")) || g_settings->getBool("freecam")) {
                MapNode n = getNode(cam_pos_nodes);
                if (n.getContent() == CONTENT_IGNORE ||
                                m_nodedef->get(n).solidness == 2)
                        occlusion_culling_enabled = false;
        }
 
+       v3s16 camera_block = getContainerPos(cam_pos_nodes, MAP_BLOCKSIZE);
+       m_drawlist = std::map<v3s16, MapBlock*, MapBlockComparer>(MapBlockComparer(camera_block));
+
        // Uncomment to debug occluded blocks in the wireframe mode
        // TODO: Include this as a flag for an extended debugging setting
        //if (occlusion_culling_enabled && m_control.show_wireframe)
@@ -160,6 +241,7 @@ void ClientMap::updateDrawList()
                MapSector *sector = sector_it.second;
                v2s16 sp = sector->getPos();
 
+               blocks_loaded += sector->size();
                if (!m_control.range_all) {
                        if (sp.X < p_blocks_min.X || sp.X > p_blocks_max.X ||
                                        sp.Y < p_blocks_min.Z || sp.Y > p_blocks_max.Z)
@@ -181,42 +263,39 @@ void ClientMap::updateDrawList()
                                if not seen on display
                        */
 
-                       if (block->mesh)
-                               block->mesh->updateCameraOffset(m_camera_offset);
-
-                       float range = 100000 * BS;
-                       if (!m_control.range_all)
-                               range = m_control.wanted_range * BS;
-
-                       float d = 0.0;
-                       if (!isBlockInSight(block->getPos(), camera_position,
-                                       camera_direction, camera_fov, range, &d))
+                       if (!block->mesh) {
+                               // Ignore if mesh doesn't exist
                                continue;
+                       }
 
+                       v3s16 block_coord = block->getPos();
+                       v3s16 block_position = block->getPosRelative() + MAP_BLOCKSIZE / 2;
 
-                       /*
-                               Ignore if mesh doesn't exist
-                       */
-                       if (!block->mesh)
-                               continue;
+                       // First, perform a simple distance check, with a padding of one extra block.
+                       if (!m_control.range_all &&
+                                       block_position.getDistanceFrom(cam_pos_nodes) > range + MAP_BLOCKSIZE)
+                               continue; // Out of range, skip.
 
+                       // Keep the block alive as long as it is in range.
+                       block->resetUsageTimer();
                        blocks_in_range_with_mesh++;
 
-                       /*
-                               Occlusion culling
-                       */
+                       // Frustum culling
+                       float d = 0.0;
+                       if (!isBlockInSight(block_coord, camera_position,
+                                       camera_direction, camera_fov, range * BS, &d))
+                               continue;
+
+                       // Occlusion culling
                        if ((!m_control.range_all && d > m_control.wanted_range * BS) ||
                                        (occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes))) {
                                blocks_occlusion_culled++;
                                continue;
                        }
 
-                       // This block is in range. Reset usage timer.
-                       block->resetUsageTimer();
-
                        // Add to set
                        block->refGrab();
-                       m_drawlist[block->getPos()] = block;
+                       m_drawlist[block_coord] = block;
 
                        sector_blocks_drawn++;
                } // foreach sectorblocks
@@ -228,52 +307,9 @@ void ClientMap::updateDrawList()
        g_profiler->avg("MapBlock meshes in range [#]", blocks_in_range_with_mesh);
        g_profiler->avg("MapBlocks occlusion culled [#]", blocks_occlusion_culled);
        g_profiler->avg("MapBlocks drawn [#]", m_drawlist.size());
+       g_profiler->avg("MapBlocks loaded [#]", blocks_loaded);
 }
 
-struct MeshBufList
-{
-       video::SMaterial m;
-       std::vector<scene::IMeshBuffer*> bufs;
-};
-
-struct MeshBufListList
-{
-       /*!
-        * Stores the mesh buffers of the world.
-        * The array index is the material's layer.
-        * The vector part groups vertices by material.
-        */
-       std::vector<MeshBufList> lists[MAX_TILE_LAYERS];
-
-       void clear()
-       {
-               for (auto &list : lists)
-                       list.clear();
-       }
-
-       void add(scene::IMeshBuffer *buf, u8 layer)
-       {
-               // Append to the correct layer
-               std::vector<MeshBufList> &list = lists[layer];
-               const video::SMaterial &m = buf->getMaterial();
-               for (MeshBufList &l : list) {
-                       // comparing a full material is quite expensive so we don't do it if
-                       // not even first texture is equal
-                       if (l.m.TextureLayer[0].Texture != m.TextureLayer[0].Texture)
-                               continue;
-
-                       if (l.m == m) {
-                               l.bufs.push_back(buf);
-                               return;
-                       }
-               }
-               MeshBufList l;
-               l.m = m;
-               l.bufs.push_back(buf);
-               list.push_back(l);
-       }
-};
-
 void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 {
        bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
@@ -293,45 +329,51 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
        /*
                Get animation parameters
        */
-       float animation_time = m_client->getAnimationTime();
-       int crack = m_client->getCrackLevel();
-       u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
+       const float animation_time = m_client->getAnimationTime();
+       const int crack = m_client->getCrackLevel();
+       const u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
 
-       v3f camera_position = m_camera_position;
-       v3f camera_direction = m_camera_direction;
-       f32 camera_fov = m_camera_fov;
+       const v3f camera_position = m_camera_position;
 
        /*
                Get all blocks and draw all visible ones
        */
 
        u32 vertex_count = 0;
+       u32 drawcall_count = 0;
 
        // For limiting number of mesh animations per frame
        u32 mesh_animate_count = 0;
        //u32 mesh_animate_count_far = 0;
 
+       /*
+               Update transparent meshes
+       */
+       if (is_transparent_pass)
+               updateTransparentMeshBuffers();
+
        /*
                Draw the selected MapBlocks
        */
 
-       MeshBufListList drawbufs;
+       MeshBufListList grouped_buffers;
+       std::vector<DrawDescriptor> draw_order;
+       video::SMaterial previous_material;
 
        for (auto &i : m_drawlist) {
+               v3s16 block_pos = i.first;
                MapBlock *block = i.second;
 
                // If the mesh of the block happened to get deleted, ignore it
                if (!block->mesh)
                        continue;
 
-               float d = 0.0;
-               if (!isBlockInSight(block->getPos(), camera_position,
-                               camera_direction, camera_fov, 100000 * BS, &d))
-                       continue;
+               v3f block_pos_r = intToFloat(block->getPosRelative() + MAP_BLOCKSIZE / 2, BS);
+               float d = camera_position.getDistanceFrom(block_pos_r);
+               d = MYMAX(0,d - BLOCK_MAX_RADIUS);
 
                // Mesh animation
                if (pass == scene::ESNRP_SOLID) {
-                       //MutexAutoLock lock(block->mesh_mutex);
                        MapBlockMesh *mapBlockMesh = block->mesh;
                        assert(mapBlockMesh);
                        // Pretty random but this should work somewhat nicely
@@ -351,9 +393,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                /*
                        Get the meshbuffers of the block
                */
-               {
-                       //MutexAutoLock lock(block->mesh_mutex);
-
+               if (is_transparent_pass) {
+                       // In transparent pass, the mesh will give us
+                       // the partial buffers in the correct order
+                       for (auto &buffer : block->mesh->getTransparentBuffers())
+                               draw_order.emplace_back(block_pos, &buffer);
+               }
+               else {
+                       // otherwise, group buffers across meshes
+                       // using MeshBufListList
                        MapBlockMesh *mapBlockMesh = block->mesh;
                        assert(mapBlockMesh);
 
@@ -367,48 +415,94 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
 
                                        video::SMaterial& material = buf->getMaterial();
                                        video::IMaterialRenderer* rnd =
-                                               driver->getMaterialRenderer(material.MaterialType);
+                                                       driver->getMaterialRenderer(material.MaterialType);
                                        bool transparent = (rnd && rnd->isTransparent());
-                                       if (transparent == is_transparent_pass) {
+                                       if (!transparent) {
                                                if (buf->getVertexCount() == 0)
                                                        errorstream << "Block [" << analyze_block(block)
-                                                               << "] contains an empty meshbuf" << std::endl;
-
-                                               material.setFlag(video::EMF_TRILINEAR_FILTER,
-                                                       m_cache_trilinear_filter);
-                                               material.setFlag(video::EMF_BILINEAR_FILTER,
-                                                       m_cache_bilinear_filter);
-                                               material.setFlag(video::EMF_ANISOTROPIC_FILTER,
-                                                       m_cache_anistropic_filter);
-                                               material.setFlag(video::EMF_WIREFRAME,
-                                                       m_control.show_wireframe);
-
-                                               drawbufs.add(buf, layer);
+                                                                       << "] contains an empty meshbuf" << std::endl;
+
+                                               grouped_buffers.add(buf, block_pos, layer);
                                        }
                                }
                        }
                }
        }
 
-       TimeTaker draw("Drawing mesh buffers");
-
-       // Render all layers in order
-       for (auto &lists : drawbufs.lists) {
+       // Capture draw order for all solid meshes
+       for (auto &lists : grouped_buffers.lists) {
                for (MeshBufList &list : lists) {
-                       // Check and abort if the machine is swapping a lot
-                       if (draw.getTimerTime() > 2000) {
-                               infostream << "ClientMap::renderMap(): Rendering took >2s, " <<
-                                               "returning." << std::endl;
-                               return;
+                       // iterate in reverse to draw closest blocks first
+                       for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it) {
+                               draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
                        }
-                       driver->setMaterial(list.m);
+               }
+       }
+
+       TimeTaker draw("Drawing mesh buffers");
+
+       core::matrix4 m; // Model matrix
+       v3f offset = intToFloat(m_camera_offset, BS);
+       u32 material_swaps = 0;
+
+       // Render all mesh buffers in order
+       drawcall_count += draw_order.size();
+
+       for (auto &descriptor : draw_order) {
+               scene::IMeshBuffer *buf;
+               
+               if (descriptor.m_use_partial_buffer) {
+                       descriptor.m_partial_buffer->beforeDraw();
+                       buf = descriptor.m_partial_buffer->getBuffer();
+               }
+               else {
+                       buf = descriptor.m_buffer;
+               }
+
+               // Check and abort if the machine is swapping a lot
+               if (draw.getTimerTime() > 2000) {
+                       infostream << "ClientMap::renderMap(): Rendering took >2s, " <<
+                                       "returning." << std::endl;
+                       return;
+               }
 
-                       for (scene::IMeshBuffer *buf : list.bufs) {
-                               driver->drawMeshBuffer(buf);
-                               vertex_count += buf->getVertexCount();
+               if (!descriptor.m_reuse_material) {
+                       auto &material = buf->getMaterial();
+
+                       // Apply filter settings
+                       material.setFlag(video::EMF_TRILINEAR_FILTER,
+                               m_cache_trilinear_filter);
+                       material.setFlag(video::EMF_BILINEAR_FILTER,
+                               m_cache_bilinear_filter);
+                       material.setFlag(video::EMF_ANISOTROPIC_FILTER,
+                               m_cache_anistropic_filter);
+                       material.setFlag(video::EMF_WIREFRAME,
+                               m_control.show_wireframe);
+
+                       // pass the shadow map texture to the buffer texture
+                       ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer();
+                       if (shadow && shadow->is_active()) {
+                               auto &layer = material.TextureLayer[3];
+                               layer.Texture = shadow->get_texture();
+                               layer.TextureWrapU = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE;
+                               layer.TextureWrapV = video::E_TEXTURE_CLAMP::ETC_CLAMP_TO_EDGE;
+                               // Do not enable filter on shadow texture to avoid visual artifacts
+                               // with colored shadows.
+                               // Filtering is done in shader code anyway
+                               layer.TrilinearFilter = false;
                        }
+                       driver->setMaterial(material);
+                       ++material_swaps;
                }
+
+               v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
+               m.setTranslation(block_wpos - offset);
+
+               driver->setTransform(video::ETS_WORLD, m);
+               driver->drawMeshBuffer(buf);
+               vertex_count += buf->getIndexCount();
        }
+
        g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
 
        // Log only on solid pass because values are the same
@@ -416,7 +510,13 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
                g_profiler->avg("renderMap(): animated meshes [#]", mesh_animate_count);
        }
 
+       if (pass == scene::ESNRP_TRANSPARENT) {
+               g_profiler->avg("renderMap(): transparent buffers [#]", draw_order.size());
+       }
+
        g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
+       g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
+       g_profiler->avg(prefix + "material swaps [#]", material_swaps);
 }
 
 static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
@@ -499,12 +599,12 @@ int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor,
        static v3f z_directions[50] = {
                v3f(-100, 0, 0)
        };
-       static f32 z_offsets[sizeof(z_directions)/sizeof(*z_directions)] = {
+       static f32 z_offsets[50] = {
                -1000,
        };
 
-       if(z_directions[0].X < -99){
-               for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
+       if (z_directions[0].X < -99) {
+               for (u32 i = 0; i < ARRLEN(z_directions); i++) {
                        // Assumes FOV of 72 and 16/9 aspect ratio
                        z_directions[i] = v3f(
                                0.02 * myrand_range(-100, 100),
@@ -520,7 +620,8 @@ int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor,
        if(sunlight_min_d > 35*BS)
                sunlight_min_d = 35*BS;
        std::vector<int> values;
-       for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
+       values.reserve(ARRLEN(z_directions));
+       for (u32 i = 0; i < ARRLEN(z_directions); i++) {
                v3f z_dir = z_directions[i];
                core::CMatrix4<f32> a;
                a.buildRotateFromTo(v3f(0,1,0), z_dir);
@@ -588,8 +689,8 @@ void ClientMap::renderPostFx(CameraMode cam_mode)
        // - Do not if player is in third person mode
        const ContentFeatures& features = m_nodedef->get(n);
        video::SColor post_effect_color = features.post_effect_color;
-       if(features.solidness == 2 && !(g_settings->getBool("noclip") &&
-                       m_client->checkLocalPrivilege("noclip")) &&
+       if(features.solidness == 2 && !((g_settings->getBool("noclip") || g_settings->getBool("freecam")) &&
+                       (m_client->checkLocalPrivilege("noclip") || g_settings->getBool("freecam"))) &&
                        cam_mode == CAMERA_MODE_FIRST)
        {
                post_effect_color = video::SColor(255, 0, 0, 0);
@@ -609,4 +710,257 @@ void ClientMap::PrintInfo(std::ostream &out)
        out<<"ClientMap: ";
 }
 
+void ClientMap::renderMapShadows(video::IVideoDriver *driver,
+               const video::SMaterial &material, s32 pass, int frame, int total_frames)
+{
+       bool is_transparent_pass = pass != scene::ESNRP_SOLID;
+       std::string prefix;
+       if (is_transparent_pass)
+               prefix = "renderMap(SHADOW TRANS): ";
+       else
+               prefix = "renderMap(SHADOW SOLID): ";
+
+       u32 drawcall_count = 0;
+       u32 vertex_count = 0;
+
+       MeshBufListList grouped_buffers;
+       std::vector<DrawDescriptor> draw_order;
+
+
+       int count = 0;
+       int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame;
+       int high_bound = is_transparent_pass ? m_drawlist_shadow.size() : m_drawlist_shadow.size() / total_frames * (frame + 1);
+
+       // transparent pass should be rendered in one go
+       if (is_transparent_pass && frame != total_frames - 1) {
+               return;
+       }
+
+       for (auto &i : m_drawlist_shadow) {
+               // only process specific part of the list & break early
+               ++count;
+               if (count <= low_bound)
+                       continue;
+               if (count > high_bound)
+                       break;
+
+               v3s16 block_pos = i.first;
+               MapBlock *block = i.second;
+
+               // If the mesh of the block happened to get deleted, ignore it
+               if (!block->mesh)
+                       continue;
+
+               /*
+                       Get the meshbuffers of the block
+               */
+               if (is_transparent_pass) {
+                       // In transparent pass, the mesh will give us
+                       // the partial buffers in the correct order
+                       for (auto &buffer : block->mesh->getTransparentBuffers())
+                               draw_order.emplace_back(block_pos, &buffer);
+               }
+               else {
+                       // otherwise, group buffers across meshes
+                       // using MeshBufListList
+                       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 &lists : grouped_buffers.lists)
+               for (MeshBufList &list : lists)
+                       buffer_count += list.bufs.size();
+       
+       draw_order.reserve(draw_order.size() + buffer_count);
+       
+       // Capture draw order for all solid meshes
+       for (auto &lists : grouped_buffers.lists) {
+               for (MeshBufList &list : lists) {
+                       // iterate in reverse to draw closest blocks first
+                       for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it)
+                               draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
+               }
+       }
+
+       TimeTaker draw("Drawing shadow mesh buffers");
+
+       core::matrix4 m; // Model matrix
+       v3f offset = intToFloat(m_camera_offset, BS);
+       u32 material_swaps = 0;
+
+       // Render all mesh buffers in order
+       drawcall_count += draw_order.size();
+
+       for (auto &descriptor : draw_order) {
+               scene::IMeshBuffer *buf;
+               
+               if (descriptor.m_use_partial_buffer) {
+                       descriptor.m_partial_buffer->beforeDraw();
+                       buf = descriptor.m_partial_buffer->getBuffer();
+               }
+               else {
+                       buf = descriptor.m_buffer;
+               }
+
+               // Check and abort if the machine is swapping a lot
+               if (draw.getTimerTime() > 1000) {
+                       infostream << "ClientMap::renderMapShadows(): Rendering "
+                                       "took >1s, returning." << std::endl;
+                       break;
+               }
+
+               if (!descriptor.m_reuse_material) {
+                       // override some material properties
+                       video::SMaterial local_material = buf->getMaterial();
+                       local_material.MaterialType = material.MaterialType;
+                       local_material.BackfaceCulling = material.BackfaceCulling;
+                       local_material.FrontfaceCulling = material.FrontfaceCulling;
+                       local_material.BlendOperation = material.BlendOperation;
+                       local_material.Lighting = false;
+                       driver->setMaterial(local_material);
+                       ++material_swaps;
+               }
+
+               v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
+               m.setTranslation(block_wpos - offset);
+
+               driver->setTransform(video::ETS_WORLD, m);
+               driver->drawMeshBuffer(buf);
+               vertex_count += buf->getIndexCount();
+       }
+
+       // restore the driver material state
+       video::SMaterial clean;
+       clean.BlendOperation = video::EBO_ADD;
+       driver->setMaterial(clean); // reset material to defaults
+       driver->draw3DLine(v3f(), v3f(), video::SColor(0));
+
+       g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
+       g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
+       g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
+       g_profiler->avg(prefix + "material swaps [#]", material_swaps);
+}
+
+/*
+       Custom update draw list for the pov of shadow light.
+*/
+void ClientMap::updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir, float radius, float length)
+{
+       ScopeProfiler sp(g_profiler, "CM::updateDrawListShadow()", SPT_AVG);
+
+       v3s16 cam_pos_nodes = floatToInt(shadow_light_pos, BS);
+       v3s16 p_blocks_min;
+       v3s16 p_blocks_max;
+       getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, radius + length);
+
+       std::vector<v2s16> blocks_in_range;
+
+       for (auto &i : m_drawlist_shadow) {
+               MapBlock *block = i.second;
+               block->refDrop();
+       }
+       m_drawlist_shadow.clear();
+
+       // Number of blocks currently loaded by the client
+       u32 blocks_loaded = 0;
+       // Number of blocks with mesh in rendering range
+       u32 blocks_in_range_with_mesh = 0;
+       // Number of blocks occlusion culled
+       u32 blocks_occlusion_culled = 0;
+
+       for (auto &sector_it : m_sectors) {
+               MapSector *sector = sector_it.second;
+               if (!sector)
+                       continue;
+               blocks_loaded += sector->size();
+
+               MapBlockVect sectorblocks;
+               sector->getBlocks(sectorblocks);
+
+               /*
+                       Loop through blocks in sector
+               */
+               for (MapBlock *block : sectorblocks) {
+                       if (!block->mesh) {
+                               // Ignore if mesh doesn't exist
+                               continue;
+                       }
+
+                       v3f block_pos = intToFloat(block->getPos() * MAP_BLOCKSIZE, BS);
+                       v3f projection = shadow_light_pos + shadow_light_dir * shadow_light_dir.dotProduct(block_pos - shadow_light_pos);
+                       if (projection.getDistanceFrom(block_pos) > radius)
+                               continue;
+
+                       blocks_in_range_with_mesh++;
+
+                       // This block is in range. Reset usage timer.
+                       block->resetUsageTimer();
+
+                       // Add to set
+                       if (m_drawlist_shadow.find(block->getPos()) == m_drawlist_shadow.end()) {
+                               block->refGrab();
+                               m_drawlist_shadow[block->getPos()] = block;
+                       }
+               }
+       }
+
+       g_profiler->avg("SHADOW MapBlock meshes in range [#]", blocks_in_range_with_mesh);
+       g_profiler->avg("SHADOW MapBlocks occlusion culled [#]", blocks_occlusion_culled);
+       g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size());
+       g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded);
+}
+
+void ClientMap::updateTransparentMeshBuffers()
+{
+       ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG);
+       u32 sorted_blocks = 0;
+       u32 unsorted_blocks = 0;
+       f32 sorting_distance_sq = pow(m_cache_transparency_sorting_distance * BS, 2.0f);
+
+
+       // Update the order of transparent mesh buffers in each mesh
+       for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) {
+               MapBlock* block = it->second;
+               if (!block->mesh)
+                       continue;
+               
+               if (m_needs_update_transparent_meshes || 
+                               block->mesh->getTransparentBuffers().size() == 0) {
+
+                       v3s16 block_pos = block->getPos();
+                       v3f block_pos_f = intToFloat(block_pos * MAP_BLOCKSIZE + MAP_BLOCKSIZE / 2, BS);
+                       f32 distance = m_camera_position.getDistanceFromSQ(block_pos_f);
+                       if (distance <= sorting_distance_sq) {
+                               block->mesh->updateTransparentBuffers(m_camera_position, block_pos);
+                               ++sorted_blocks;
+                       }
+                       else {
+                               block->mesh->consolidateTransparentBuffers();
+                               ++unsorted_blocks;
+                       }
+               }
+       }
+
+       g_profiler->avg("CM::Transparent Buffers - Sorted", sorted_blocks);
+       g_profiler->avg("CM::Transparent Buffers - Unsorted", unsorted_blocks);
+       m_needs_update_transparent_meshes = false;
+}