]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/client/tile.cpp
Textures: Load base pack only as last fallback (#8974)
[dragonfireclient.git] / src / client / tile.cpp
index 150e621d93969eb8dc284c799854e9e857e7c80f..3d9e2470a9cae8af9e04d19d9cb88d80f5882536 100644 (file)
@@ -19,7 +19,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "tile.h"
 
+#include <algorithm>
 #include <ICameraSceneNode.h>
+#include <IrrCompileConfig.h>
 #include "util/string.h"
 #include "util/container.h"
 #include "util/thread.h"
@@ -33,8 +35,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "renderingengine.h"
 
 
-#ifdef __ANDROID__
+#if ENABLE_GLES
+#ifdef _IRR_COMPILE_WITH_OGLES1_
 #include <GLES/gl.h>
+#else
+#include <GLES2/gl2.h>
+#endif
 #endif
 
 /*
@@ -116,9 +122,14 @@ std::string getImagePath(std::string path)
 
        Utilizes a thread-safe cache.
 */
-std::string getTexturePath(const std::string &filename)
+std::string getTexturePath(const std::string &filename, bool *is_base_pack)
 {
        std::string fullpath;
+
+       // This can set a wrong value on cached textures, but is irrelevant because
+       // is_base_pack is only passed when initializing the textures the first time
+       if (is_base_pack)
+               *is_base_pack = false;
        /*
                Check from cache
        */
@@ -129,11 +140,13 @@ std::string getTexturePath(const std::string &filename)
        /*
                Check from texture_path
        */
-       const std::string &texture_path = g_settings->get("texture_path");
-       if (!texture_path.empty()) {
-               std::string testpath = texture_path + DIR_DELIM + filename;
+       for (const auto &path : getTextureDirs()) {
+               std::string testpath = path + DIR_DELIM;
+               testpath.append(filename);
                // Check all filename extensions. Returns "" if not found.
                fullpath = getImagePath(testpath);
+               if (!fullpath.empty())
+                       break;
        }
 
        /*
@@ -146,6 +159,8 @@ std::string getTexturePath(const std::string &filename)
                std::string testpath = base_path + DIR_DELIM + filename;
                // Check all filename extensions. Returns "" if not found.
                fullpath = getImagePath(testpath);
+               if (is_base_pack && !fullpath.empty())
+                       *is_base_pack = true;
        }
 
        // Add to cache (also an empty result is cached)
@@ -207,9 +222,11 @@ class SourceImageCache
                bool need_to_grab = true;
 
                // Try to use local texture instead if asked to
-               if (prefer_local){
-                       std::string path = getTexturePath(name);
-                       if (!path.empty()) {
+               if (prefer_local) {
+                       bool is_base_pack;
+                       std::string path = getTexturePath(name, &is_base_pack);
+                       // Ignore base pack
+                       if (!path.empty() && !is_base_pack) {
                                video::IImage *img2 = RenderingEngine::get_video_driver()->
                                        createImageFromFile(path.c_str());
                                if (img2){
@@ -361,12 +378,6 @@ class TextureSource : public IWritableTextureSource
        // Shall be called from the main thread.
        void rebuildImagesAndTextures();
 
-       // Render a mesh to a texture.
-       // Returns NULL if render-to-texture failed.
-       // Shall be called from the main thread.
-       video::ITexture* generateTextureFromMesh(
-                       const TextureFromMeshParams &params);
-
        video::ITexture* getNormalTexture(const std::string &name);
        video::SColor getTextureAverageColor(const std::string &name);
        video::ITexture *getShaderFlagsTexture(bool normamap_present);
@@ -484,43 +495,35 @@ u32 TextureSource::getTextureId(const std::string &name)
        /*
                Get texture
        */
-       if (std::this_thread::get_id() == m_main_thread)
-       {
+       if (std::this_thread::get_id() == m_main_thread) {
                return generateTexture(name);
        }
-       else
-       {
-               infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
 
-               // We're gonna ask the result to be put into here
-               static ResultQueue<std::string, u32, u8, u8> result_queue;
 
-               // Throw a request in
-               m_get_texture_queue.add(name, 0, 0, &result_queue);
+       infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
 
-               /*infostream<<"Waiting for texture from main thread, name=\""
-                               <<name<<"\""<<std::endl;*/
+       // We're gonna ask the result to be put into here
+       static ResultQueue<std::string, u32, u8, u8> result_queue;
 
-               try
-               {
-                       while(true) {
-                               // Wait result for a second
-                               GetResult<std::string, u32, u8, u8>
-                                       result = result_queue.pop_front(1000);
+       // Throw a request in
+       m_get_texture_queue.add(name, 0, 0, &result_queue);
 
-                               if (result.key == name) {
-                                       return result.item;
-                               }
+       try {
+               while(true) {
+                       // Wait result for a second
+                       GetResult<std::string, u32, u8, u8>
+                               result = result_queue.pop_front(1000);
+
+                       if (result.key == name) {
+                               return result.item;
                        }
                }
-               catch(ItemNotFoundException &e)
-               {
-                       errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
-                       return 0;
-               }
+       } catch(ItemNotFoundException &e) {
+               errorstream << "Waiting for texture " << name << " timed out." << std::endl;
+               return 0;
        }
 
-       infostream<<"getTextureId(): Failed"<<std::endl;
+       infostream << "getTextureId(): Failed" << std::endl;
 
        return 0;
 }
@@ -553,7 +556,7 @@ static void apply_mask(video::IImage *mask, video::IImage *dst,
 // Draw or overlay a crack
 static void draw_crack(video::IImage *crack, video::IImage *dst,
                bool use_overlay, s32 frame_count, s32 progression,
-               video::IVideoDriver *driver);
+               video::IVideoDriver *driver, u8 tiles = 1);
 
 // Brighten image
 void brighten(video::IImage *image);
@@ -606,7 +609,7 @@ u32 TextureSource::generateTexture(const std::string &name)
        video::ITexture *tex = NULL;
 
        if (img != NULL) {
-#ifdef __ANDROID__
+#if ENABLE_GLES
                img = Align2Npot2(img, driver);
 #endif
                // Create texture from resulting image
@@ -673,7 +676,7 @@ Palette* TextureSource::getPalette(const std::string &name)
        // Only the main thread may load images
        sanity_check(std::this_thread::get_id() == m_main_thread);
 
-       if (name == "")
+       if (name.empty())
                return NULL;
 
        auto it = m_palettes.find(name);
@@ -763,7 +766,7 @@ void TextureSource::rebuildImagesAndTextures()
        // Recreate textures
        for (TextureInfo &ti : m_textureinfo_cache) {
                video::IImage *img = generateImage(ti.name);
-#ifdef __ANDROID__
+#if ENABLE_GLES
                img = Align2Npot2(img, driver);
 #endif
                // Create texture from resulting image
@@ -782,205 +785,121 @@ void TextureSource::rebuildImagesAndTextures()
        }
 }
 
-video::ITexture* TextureSource::generateTextureFromMesh(
-               const TextureFromMeshParams &params)
+inline static void applyShadeFactor(video::SColor &color, u32 factor)
 {
-       video::IVideoDriver *driver = RenderingEngine::get_video_driver();
-       sanity_check(driver);
-
-#ifdef __ANDROID__
-       const GLubyte* renderstr = glGetString(GL_RENDERER);
-       std::string renderer((char*) renderstr);
-
-       // use no render to texture hack
-       if (
-               (renderer.find("Adreno") != std::string::npos) ||
-               (renderer.find("Mali") != std::string::npos) ||
-               (renderer.find("Immersion") != std::string::npos) ||
-               (renderer.find("Tegra") != std::string::npos) ||
-               g_settings->getBool("inventory_image_hack")
-               ) {
-               // Get a scene manager
-               scene::ISceneManager *smgr_main = m_device->getSceneManager();
-               sanity_check(smgr_main);
-               scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
-               sanity_check(smgr);
-
-               const float scaling = 0.2;
-
-               scene::IMeshSceneNode* meshnode =
-                               smgr->addMeshSceneNode(params.mesh, NULL,
-                                               -1, v3f(0,0,0), v3f(0,0,0),
-                                               v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
-               meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
-               meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
-               meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
-               meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
-               meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
-
-               scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
-                               params.camera_position, params.camera_lookat);
-               // second parameter of setProjectionMatrix (isOrthogonal) is ignored
-               camera->setProjectionMatrix(params.camera_projection_matrix, false);
-
-               smgr->setAmbientLight(params.ambient_light);
-               smgr->addLightSceneNode(0,
-                               params.light_position,
-                               params.light_color,
-                               params.light_radius*scaling);
-
-               core::dimension2d<u32> screen = driver->getScreenSize();
-
-               // Render scene
-               driver->beginScene(true, true, video::SColor(0,0,0,0));
-               driver->clearZBuffer();
-               smgr->drawAll();
-
-               core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
-
-               irr::video::IImage* rawImage =
-                               driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
-
-               u8* pixels = static_cast<u8*>(rawImage->lock());
-               if (!pixels)
-               {
-                       rawImage->drop();
-                       return NULL;
-               }
-
-               core::rect<s32> source(
-                               screen.Width /2 - (screen.Width  * (scaling / 2)),
-                               screen.Height/2 - (screen.Height * (scaling / 2)),
-                               screen.Width /2 + (screen.Width  * (scaling / 2)),
-                               screen.Height/2 + (screen.Height * (scaling / 2))
-                       );
-
-               glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
-                               partsize.Width, partsize.Height, GL_RGBA,
-                               GL_UNSIGNED_BYTE, pixels);
-
-               driver->endScene();
-
-               // Drop scene manager
-               smgr->drop();
-
-               unsigned int pixelcount = partsize.Width*partsize.Height;
-
-               u8* runptr = pixels;
-               for (unsigned int i=0; i < pixelcount; i++) {
-
-                       u8 B = *runptr;
-                       u8 G = *(runptr+1);
-                       u8 R = *(runptr+2);
-                       u8 A = *(runptr+3);
-
-                       //BGRA -> RGBA
-                       *runptr = R;
-                       runptr ++;
-                       *runptr = G;
-                       runptr ++;
-                       *runptr = B;
-                       runptr ++;
-                       *runptr = A;
-                       runptr ++;
-               }
-
-               video::IImage* inventory_image =
-                               driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
-
-               rawImage->copyToScaling(inventory_image);
-               rawImage->drop();
+       u32 f = core::clamp<u32>(factor, 0, 256);
+       color.setRed(color.getRed() * f / 256);
+       color.setGreen(color.getGreen() * f / 256);
+       color.setBlue(color.getBlue() * f / 256);
+}
 
-               guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
+static video::IImage *createInventoryCubeImage(
+       video::IImage *top, video::IImage *left, video::IImage *right)
+{
+       core::dimension2du size_top = top->getDimension();
+       core::dimension2du size_left = left->getDimension();
+       core::dimension2du size_right = right->getDimension();
+
+       u32 size = npot2(std::max({
+                       size_top.Width, size_top.Height,
+                       size_left.Width, size_left.Height,
+                       size_right.Width, size_right.Height,
+       }));
+
+       // It must be divisible by 4, to let everything work correctly.
+       // But it is a power of 2, so being at least 4 is the same.
+       // And the resulting texture should't be too large as well.
+       size = core::clamp<u32>(size, 4, 64);
+
+       // With such parameters, the cube fits exactly, touching each image line
+       // from `0` to `cube_size - 1`. (Note that division is exact here).
+       u32 cube_size = 9 * size;
+       u32 offset = size / 2;
 
-               video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
-               inventory_image->drop();
+       video::IVideoDriver *driver = RenderingEngine::get_video_driver();
 
-               if (rtt == NULL) {
-                       errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
-                       return NULL;
+       auto lock_image = [size, driver] (video::IImage *&image) -> const u32 * {
+               image->grab();
+               core::dimension2du dim = image->getDimension();
+               video::ECOLOR_FORMAT format = image->getColorFormat();
+               if (dim.Width != size || dim.Height != size || format != video::ECF_A8R8G8B8) {
+                       video::IImage *scaled = driver->createImage(video::ECF_A8R8G8B8, {size, size});
+                       image->copyToScaling(scaled);
+                       image->drop();
+                       image = scaled;
                }
+               sanity_check(image->getPitch() == 4 * size);
+               return reinterpret_cast<u32 *>(image->lock());
+       };
+       auto free_image = [] (video::IImage *image) -> void {
+               image->unlock();
+               image->drop();
+       };
 
-               driver->makeColorKeyTexture(rtt, v2s32(0,0));
-
-               if (params.delete_texture_on_shutdown)
-                       m_texture_trash.push_back(rtt);
-
-               return rtt;
-       }
-#endif
-
-       if (!driver->queryFeature(video::EVDF_RENDER_TO_TARGET)) {
-               static bool warned = false;
-               if (!warned)
-               {
-                       errorstream<<"TextureSource::generateTextureFromMesh(): "
-                               <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
-                       warned = true;
+       video::IImage *result = driver->createImage(video::ECF_A8R8G8B8, {cube_size, cube_size});
+       sanity_check(result->getPitch() == 4 * cube_size);
+       result->fill(video::SColor(0x00000000u));
+       u32 *target = reinterpret_cast<u32 *>(result->lock());
+
+       // Draws single cube face
+       // `shade_factor` is face brightness, in range [0.0, 1.0]
+       // (xu, xv, x1; yu, yv, y1) form coordinate transformation matrix
+       // `offsets` list pixels to be drawn for single source pixel
+       auto draw_image = [=] (video::IImage *image, float shade_factor,
+                       s16 xu, s16 xv, s16 x1,
+                       s16 yu, s16 yv, s16 y1,
+                       std::initializer_list<v2s16> offsets) -> void {
+               u32 brightness = core::clamp<u32>(256 * shade_factor, 0, 256);
+               const u32 *source = lock_image(image);
+               for (u16 v = 0; v < size; v++) {
+                       for (u16 u = 0; u < size; u++) {
+                               video::SColor pixel(*source);
+                               applyShadeFactor(pixel, brightness);
+                               s16 x = xu * u + xv * v + x1;
+                               s16 y = yu * u + yv * v + y1;
+                               for (const auto &off : offsets)
+                                       target[(y + off.Y) * cube_size + (x + off.X) + offset] = pixel.color;
+                               source++;
+                       }
                }
-               return NULL;
-       }
-
-       // Create render target texture
-       video::ITexture *rtt = driver->addRenderTargetTexture(
-                       params.dim, params.rtt_texture_name.c_str(),
-                       video::ECF_A8R8G8B8);
-       if (rtt == NULL)
-       {
-               errorstream<<"TextureSource::generateTextureFromMesh(): "
-                       <<"addRenderTargetTexture returned NULL."<<std::endl;
-               return NULL;
-       }
-
-       // Set render target
-       if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
-               driver->removeTexture(rtt);
-               errorstream<<"TextureSource::generateTextureFromMesh(): "
-                       <<"failed to set render target"<<std::endl;
-               return NULL;
-       }
+               free_image(image);
+       };
 
-       // Get a scene manager
-       scene::ISceneManager *smgr_main = RenderingEngine::get_scene_manager();
-       assert(smgr_main);
-       scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
-       assert(smgr);
-
-       scene::IMeshSceneNode* meshnode =
-                       smgr->addMeshSceneNode(params.mesh, NULL,
-                                       -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
-       meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
-       meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
-       meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
-       meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
-       meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
-
-       scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
-                       params.camera_position, params.camera_lookat);
-       // second parameter of setProjectionMatrix (isOrthogonal) is ignored
-       camera->setProjectionMatrix(params.camera_projection_matrix, false);
-
-       smgr->setAmbientLight(params.ambient_light);
-       smgr->addLightSceneNode(0,
-                       params.light_position,
-                       params.light_color,
-                       params.light_radius);
-
-       // Render scene
-       driver->beginScene(true, true, video::SColor(0,0,0,0));
-       smgr->drawAll();
-       driver->endScene();
-
-       // Drop scene manager
-       smgr->drop();
-
-       // Unset render target
-       driver->setRenderTarget(0, false, true, video::SColor(0,0,0,0));
-
-       if (params.delete_texture_on_shutdown)
-               m_texture_trash.push_back(rtt);
-
-       return rtt;
+       draw_image(top, 1.000000f,
+                       4, -4, 4 * (size - 1),
+                       2, 2, 0,
+                       {
+                                               {2, 0}, {3, 0}, {4, 0}, {5, 0},
+                               {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {6, 1}, {7, 1},
+                                               {2, 2}, {3, 2}, {4, 2}, {5, 2},
+                       });
+
+       draw_image(left, 0.836660f,
+                       4, 0, 0,
+                       2, 5, 2 * size,
+                       {
+                               {0, 0}, {1, 0},
+                               {0, 1}, {1, 1}, {2, 1}, {3, 1},
+                               {0, 2}, {1, 2}, {2, 2}, {3, 2},
+                               {0, 3}, {1, 3}, {2, 3}, {3, 3},
+                               {0, 4}, {1, 4}, {2, 4}, {3, 4},
+                                               {2, 5}, {3, 5},
+                       });
+
+       draw_image(right, 0.670820f,
+                       4, 0, 4 * size,
+                       -2, 5, 4 * size - 2,
+                       {
+                                               {2, 0}, {3, 0},
+                               {0, 1}, {1, 1}, {2, 1}, {3, 1},
+                               {0, 2}, {1, 2}, {2, 2}, {3, 2},
+                               {0, 3}, {1, 3}, {2, 3}, {3, 3},
+                               {0, 4}, {1, 4}, {2, 4}, {3, 4},
+                               {0, 5}, {1, 5},
+                       });
+
+       result->unlock();
+       return result;
 }
 
 video::IImage* TextureSource::generateImage(const std::string &name)
@@ -1084,8 +1003,30 @@ video::IImage* TextureSource::generateImage(const std::string &name)
        return baseimg;
 }
 
-#ifdef __ANDROID__
-#include <GLES/gl.h>
+#if ENABLE_GLES
+
+
+static inline u16 get_GL_major_version()
+{
+       const GLubyte *gl_version = glGetString(GL_VERSION);
+       return (u16) (gl_version[0] - '0');
+}
+
+/**
+ * Check if hardware requires npot2 aligned textures
+ * @return true if alignment NOT(!) requires, false otherwise
+ */
+
+bool hasNPotSupport()
+{
+       // Only GLES2 is trusted to correctly report npot support
+       // Note: we cache the boolean result, the GL context will never change.
+       static const bool supported = get_GL_major_version() > 1 &&
+               glGetString(GL_EXTENSIONS) &&
+               strstr((char *)glGetString(GL_EXTENSIONS), "GL_OES_texture_npot");
+       return supported;
+}
+
 /**
  * Check and align image to npot2 if required by hardware
  * @param image image to check for npot2 alignment
@@ -1093,52 +1034,33 @@ video::IImage* TextureSource::generateImage(const std::string &name)
  * @return image or copy of image aligned to npot2
  */
 
-inline u16 get_GL_major_version()
-{
-       const GLubyte *gl_version = glGetString(GL_VERSION);
-       return (u16) (gl_version[0] - '0');
-}
-
 video::IImage * Align2Npot2(video::IImage * image,
                video::IVideoDriver* driver)
 {
-       if (image == NULL) {
+       if (image == NULL)
                return image;
-       }
 
-       core::dimension2d<u32> dim = image->getDimension();
-
-       std::string extensions = (char*) glGetString(GL_EXTENSIONS);
-
-       // Only GLES2 is trusted to correctly report npot support
-       if (get_GL_major_version() > 1 &&
-                       extensions.find("GL_OES_texture_npot") != std::string::npos) {
+       if (hasNPotSupport())
                return image;
-       }
 
+       core::dimension2d<u32> dim = image->getDimension();
        unsigned int height = npot2(dim.Height);
        unsigned int width  = npot2(dim.Width);
 
-       if ((dim.Height == height) &&
-                       (dim.Width == width)) {
+       if (dim.Height == height && dim.Width == width)
                return image;
-       }
 
-       if (dim.Height > height) {
+       if (dim.Height > height)
                height *= 2;
-       }
-
-       if (dim.Width > width) {
+       if (dim.Width > width)
                width *= 2;
-       }
 
        video::IImage *targetimage =
                        driver->createImage(video::ECF_A8R8G8B8,
                                        core::dimension2d<u32>(width, height));
 
-       if (targetimage != NULL) {
+       if (targetimage != NULL)
                image->copyToScaling(targetimage);
-       }
        image->drop();
        return targetimage;
 }
@@ -1172,7 +1094,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
        // Stuff starting with [ are special commands
        if (part_of_name.empty() || part_of_name[0] != '[') {
                video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
-#ifdef __ANDROID__
+#if ENABLE_GLES
                image = Align2Npot2(image, driver);
 #endif
                if (image == NULL) {
@@ -1288,11 +1210,22 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                        }
 
                        // Crack image number and overlay option
+                       // Format: crack[o][:<tiles>]:<frame_count>:<frame>
                        bool use_overlay = (part_of_name[6] == 'o');
                        Strfnd sf(part_of_name);
                        sf.next(":");
                        s32 frame_count = stoi(sf.next(":"));
                        s32 progression = stoi(sf.next(":"));
+                       s32 tiles = 1;
+                       // Check whether there is the <tiles> argument, that is,
+                       // whether there are 3 arguments. If so, shift values
+                       // as the first and not the last argument is optional.
+                       auto s = sf.next(":");
+                       if (!s.empty()) {
+                               tiles = frame_count;
+                               frame_count = progression;
+                               progression = stoi(s);
+                       }
 
                        if (progression >= 0) {
                                /*
@@ -1307,7 +1240,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                                if (img_crack) {
                                        draw_crack(img_crack, baseimg,
                                                use_overlay, frame_count,
-                                               progression, driver);
+                                               progression, driver, tiles);
                                        img_crack->drop();
                                }
                        }
@@ -1512,89 +1445,14 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                                return true;
                        }
 
-#ifdef __ANDROID__
-                       assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
-                       assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
-
-                       assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
-                       assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
-
-                       assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
-                       assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
-#endif
-
-                       // Create textures from images
-                       video::ITexture *texture_top = driver->addTexture(
-                                       (imagename_top + "__temp__").c_str(), img_top);
-                       video::ITexture *texture_left = driver->addTexture(
-                                       (imagename_left + "__temp__").c_str(), img_left);
-                       video::ITexture *texture_right = driver->addTexture(
-                                       (imagename_right + "__temp__").c_str(), img_right);
-                       FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
+                       baseimg = createInventoryCubeImage(img_top, img_left, img_right);
 
-                       // Drop images
+                       // Face images are not needed anymore
                        img_top->drop();
                        img_left->drop();
                        img_right->drop();
 
-                       /*
-                               Draw a cube mesh into a render target texture
-                       */
-                       scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
-                       setMeshColor(cube, video::SColor(255, 255, 255, 255));
-                       cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
-                       cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
-                       cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
-                       cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
-                       cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
-                       cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
-
-                       TextureFromMeshParams params;
-                       params.mesh = cube;
-                       params.dim.set(64, 64);
-                       params.rtt_texture_name = part_of_name + "_RTT";
-                       // We will delete the rtt texture ourselves
-                       params.delete_texture_on_shutdown = false;
-                       params.camera_position.set(0, 1.0, -1.5);
-                       params.camera_position.rotateXZBy(45);
-                       params.camera_lookat.set(0, 0, 0);
-                       // Set orthogonal projection
-                       params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
-                                       1.65, 1.65, 0, 100);
-
-                       params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
-                       params.light_position.set(10, 100, -50);
-                       params.light_color.set(1.0, 0.5, 0.5, 0.5);
-                       params.light_radius = 1000;
-
-                       video::ITexture *rtt = generateTextureFromMesh(params);
-
-                       // Drop mesh
-                       cube->drop();
-
-                       // Free textures
-                       driver->removeTexture(texture_top);
-                       driver->removeTexture(texture_left);
-                       driver->removeTexture(texture_right);
-
-                       if (rtt == NULL) {
-                               baseimg = generateImage(imagename_top);
-                               return true;
-                       }
-
-                       // Create image of render target
-                       video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
-                       FATAL_ERROR_IF(!image, "Could not create image of render target");
-
-                       // Cleanup texture
-                       driver->removeTexture(rtt);
-
-                       baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
-
-                       if (image) {
-                               image->copyTo(baseimg);
-                               image->drop();
-                       }
+                       return true;
                }
                /*
                        [lowpart:percent:filename
@@ -1774,7 +1632,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
                         * mix high- and low-res textures, or for mods with least-common-denominator
                         * textures that don't have the resources to offer high-res alternatives.
                         */
-                       s32 scaleto = g_settings->getS32("texture_min_size");
+                       const bool filter = m_setting_trilinear_filter || m_setting_bilinear_filter;
+                       const s32 scaleto = filter ? g_settings->getS32("texture_min_size") : 1;
                        if (scaleto > 1) {
                                const core::dimension2d<u32> dim = baseimg->getDimension();
 
@@ -2110,74 +1969,78 @@ static void apply_mask(video::IImage *mask, video::IImage *dst,
        }
 }
 
+video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
+               core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
+{
+       core::dimension2d<u32> strip_size = crack->getDimension();
+       core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
+       core::dimension2d<u32> tile_size(size / tiles);
+       s32 frame_count = strip_size.Height / strip_size.Width;
+       if (frame_index >= frame_count)
+               frame_index = frame_count - 1;
+       core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
+       video::IImage *result = nullptr;
+
+// extract crack frame
+       video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
+       if (!crack_tile)
+               return nullptr;
+       if (tile_size == frame_size) {
+               crack->copyTo(crack_tile, v2s32(0, 0), frame);
+       } else {
+               video::IImage *crack_frame = driver->createImage(video::ECF_A8R8G8B8, frame_size);
+               if (!crack_frame)
+                       goto exit__has_tile;
+               crack->copyTo(crack_frame, v2s32(0, 0), frame);
+               crack_frame->copyToScaling(crack_tile);
+               crack_frame->drop();
+       }
+       if (tiles == 1)
+               return crack_tile;
+
+// tile it
+       result = driver->createImage(video::ECF_A8R8G8B8, size);
+       if (!result)
+               goto exit__has_tile;
+       result->fill({});
+       for (u8 i = 0; i < tiles; i++)
+               for (u8 j = 0; j < tiles; j++)
+                       crack_tile->copyTo(result, v2s32(i * tile_size.Width, j * tile_size.Height));
+
+exit__has_tile:
+       crack_tile->drop();
+       return result;
+}
+
 static void draw_crack(video::IImage *crack, video::IImage *dst,
                bool use_overlay, s32 frame_count, s32 progression,
-               video::IVideoDriver *driver)
+               video::IVideoDriver *driver, u8 tiles)
 {
        // Dimension of destination image
        core::dimension2d<u32> dim_dst = dst->getDimension();
-       // Dimension of original image
-       core::dimension2d<u32> dim_crack = crack->getDimension();
-       // Count of crack stages
-       s32 crack_count = dim_crack.Height / dim_crack.Width;
        // Limit frame_count
        if (frame_count > (s32) dim_dst.Height)
                frame_count = dim_dst.Height;
        if (frame_count < 1)
                frame_count = 1;
-       // Limit progression
-       if (progression > crack_count-1)
-               progression = crack_count-1;
-       // Dimension of a single crack stage
-       core::dimension2d<u32> dim_crack_cropped(
-               dim_crack.Width,
-               dim_crack.Width
-       );
        // Dimension of the scaled crack stage,
        // which is the same as the dimension of a single destination frame
-       core::dimension2d<u32> dim_crack_scaled(
+       core::dimension2d<u32> frame_size(
                dim_dst.Width,
                dim_dst.Height / frame_count
        );
-       // Create cropped and scaled crack images
-       video::IImage *crack_cropped = driver->createImage(
-                       video::ECF_A8R8G8B8, dim_crack_cropped);
-       video::IImage *crack_scaled = driver->createImage(
-                       video::ECF_A8R8G8B8, dim_crack_scaled);
+       video::IImage *crack_scaled = create_crack_image(crack, progression,
+                       frame_size, tiles, driver);
+       if (!crack_scaled)
+               return;
 
-       if (crack_cropped && crack_scaled)
-       {
-               // Crop crack image
-               v2s32 pos_crack(0, progression*dim_crack.Width);
-               crack->copyTo(crack_cropped,
-                               v2s32(0,0),
-                               core::rect<s32>(pos_crack, dim_crack_cropped));
-               // Scale crack image by copying
-               crack_cropped->copyToScaling(crack_scaled);
-               // Copy or overlay crack image onto each frame
-               for (s32 i = 0; i < frame_count; ++i)
-               {
-                       v2s32 dst_pos(0, dim_crack_scaled.Height * i);
-                       if (use_overlay)
-                       {
-                               blit_with_alpha_overlay(crack_scaled, dst,
-                                               v2s32(0,0), dst_pos,
-                                               dim_crack_scaled);
-                       }
-                       else
-                       {
-                               blit_with_alpha(crack_scaled, dst,
-                                               v2s32(0,0), dst_pos,
-                                               dim_crack_scaled);
-                       }
-               }
+       auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
+       for (s32 i = 0; i < frame_count; ++i) {
+               v2s32 dst_pos(0, frame_size.Height * i);
+               blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
        }
 
-       if (crack_scaled)
-               crack_scaled->drop();
-
-       if (crack_cropped)
-               crack_cropped->drop();
+       crack_scaled->drop();
 }
 
 void brighten(video::IImage *image)
@@ -2380,3 +2243,8 @@ video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
        return getTexture(tname);
 
 }
+
+std::vector<std::string> getTextureDirs()
+{
+       return fs::GetRecursiveDirs(g_settings->get("texture_path"));
+}