X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fclient%2Ftile.cpp;h=a31e3aca16b4783be167bff6a1cf02088d68fc84;hb=6fedee16f098549ffaee188b02b777239513abc3;hp=86ca7d42221daf9dd0b7fe90490efab286efef49;hpb=5a59ad230744a84a1474e8c3b97cf3d9080536d7;p=dragonfireclient.git diff --git a/src/client/tile.cpp b/src/client/tile.cpp index 86ca7d422..a31e3aca1 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -19,28 +19,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "tile.h" +#include #include +#include #include "util/string.h" #include "util/container.h" #include "util/thread.h" -#include "util/numeric.h" -#include "irrlichttypes_extrabloated.h" -#include "debug.h" #include "filesys.h" #include "settings.h" #include "mesh.h" -#include "log.h" #include "gamedef.h" #include "util/strfnd.h" -#include "util/string.h" // for parseColorString() #include "imagefilters.h" #include "guiscalingfilter.h" -#include "nodedef.h" - - -#ifdef __ANDROID__ -#include -#endif +#include "renderingengine.h" /* A cache from texture name to texture path @@ -89,19 +81,15 @@ static bool replace_ext(std::string &path, const char *ext) std::string getImagePath(std::string path) { // A NULL-ended list of possible image extensions - const char *extensions[] = { - "png", "jpg", "bmp", "tga", - "pcx", "ppm", "psd", "wal", "rgb", - NULL - }; - // If there is no extension, add one - if (removeStringEnd(path, extensions) == "") + const char *extensions[] = { "png", "jpg", "bmp", "tga", NULL }; + // If there is no extension, assume PNG + if (removeStringEnd(path, extensions).empty()) path = path + ".png"; // Check paths until something is found to exist const char **ext = extensions; do{ bool r = replace_ext(path, *ext); - if (r == false) + if (!r) return ""; if (fs::PathExists(path)) return path; @@ -121,9 +109,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 = ""; + 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 */ @@ -134,23 +127,27 @@ std::string getTexturePath(const std::string &filename) /* Check from texture_path */ - const std::string &texture_path = g_settings->get("texture_path"); - if (texture_path != "") { - 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; } /* Check from default data directory */ - if (fullpath == "") + if (fullpath.empty()) { std::string base_path = porting::path_share + DIR_DELIM + "textures" + DIR_DELIM + "base" + DIR_DELIM + "pack"; 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) @@ -192,14 +189,12 @@ class SourceImageCache { public: ~SourceImageCache() { - for (std::map::iterator iter = m_images.begin(); - iter != m_images.end(); ++iter) { - iter->second->drop(); + for (auto &m_image : m_images) { + m_image.second->drop(); } m_images.clear(); } - void insert(const std::string &name, video::IImage *img, - bool prefer_local, video::IVideoDriver *driver) + void insert(const std::string &name, video::IImage *img, bool prefer_local) { assert(img); // Pre-condition // Remove old image @@ -214,10 +209,13 @@ 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 != ""){ - video::IImage *img2 = driver->createImageFromFile(path.c_str()); + 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){ toadd = img2; need_to_grab = false; @@ -238,7 +236,7 @@ class SourceImageCache return NULL; } // Primarily fetches from cache, secondarily tries to read from filesystem - video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device) + video::IImage *getOrLoad(const std::string &name) { std::map::iterator n; n = m_images.find(name); @@ -246,9 +244,9 @@ class SourceImageCache n->second->grab(); // Grab for caller return n->second; } - video::IVideoDriver* driver = device->getVideoDriver(); + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); std::string path = getTexturePath(name); - if (path == ""){ + if (path.empty()) { infostream<<"SourceImageCache::getOrLoad(): No path found for \"" < m_source_image_existence; @@ -410,7 +401,7 @@ class TextureSource : public IWritableTextureSource // Maps a texture name to an index in the former. std::map m_name_to_id; // The two former containers are behind this mutex - Mutex m_textureinfo_cache_mutex; + std::mutex m_textureinfo_cache_mutex; // Queued texture fetches (to be processed by the main thread) RequestQueue m_get_texture_queue; @@ -419,63 +410,56 @@ class TextureSource : public IWritableTextureSource // but can't be deleted because the ITexture* might still be used std::vector m_texture_trash; + // Maps image file names to loaded palettes. + std::unordered_map m_palettes; + // Cached settings needed for making textures from meshes + bool m_setting_mipmap; bool m_setting_trilinear_filter; bool m_setting_bilinear_filter; - bool m_setting_anisotropic_filter; }; -IWritableTextureSource* createTextureSource(IrrlichtDevice *device) +IWritableTextureSource *createTextureSource() { - return new TextureSource(device); + return new TextureSource(); } -TextureSource::TextureSource(IrrlichtDevice *device): - m_device(device) +TextureSource::TextureSource() { - assert(m_device); // Pre-condition - - m_main_thread = thr_get_current_thread_id(); + m_main_thread = std::this_thread::get_id(); // Add a NULL TextureInfo as the first index, named "" - m_textureinfo_cache.push_back(TextureInfo("")); + m_textureinfo_cache.emplace_back(""); m_name_to_id[""] = 0; // Cache some settings // Note: Since this is only done once, the game must be restarted // for these settings to take effect + m_setting_mipmap = g_settings->getBool("mip_map"); m_setting_trilinear_filter = g_settings->getBool("trilinear_filter"); m_setting_bilinear_filter = g_settings->getBool("bilinear_filter"); - m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter"); } TextureSource::~TextureSource() { - video::IVideoDriver* driver = m_device->getVideoDriver(); + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); unsigned int textures_before = driver->getTextureCount(); - for (std::vector::iterator iter = - m_textureinfo_cache.begin(); - iter != m_textureinfo_cache.end(); ++iter) - { + for (const auto &iter : m_textureinfo_cache) { //cleanup texture - if (iter->texture) - driver->removeTexture(iter->texture); + if (iter.texture) + driver->removeTexture(iter.texture); } m_textureinfo_cache.clear(); - for (std::vector::iterator iter = - m_texture_trash.begin(); iter != m_texture_trash.end(); - ++iter) { - video::ITexture *t = *iter; - + for (auto t : m_texture_trash) { //cleanup trashed texture driver->removeTexture(t); } - infostream << "~TextureSource() "<< textures_before << "/" - << driver->getTextureCount() << std::endl; + infostream << "~TextureSource() before cleanup: "<< textures_before + << " after: " << driver->getTextureCount() << std::endl; } u32 TextureSource::getTextureId(const std::string &name) @@ -498,43 +482,35 @@ u32 TextureSource::getTextureId(const std::string &name) /* Get texture */ - if (thr_is_current_thread(m_main_thread)) - { + if (std::this_thread::get_id() == m_main_thread) { return generateTexture(name); } - else - { - infostream<<"getTextureId(): Queued: name=\""< result_queue; - // Throw a request in - m_get_texture_queue.add(name, 0, 0, &result_queue); + infostream<<"getTextureId(): Queued: name=\""< result_queue; - try - { - while(true) { - // Wait result for a second - GetResult - 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 + result = result_queue.pop_front(1000); + + if (result.key == name) { + return result.item; } } - catch(ItemNotFoundException &e) - { - errorstream<<"Waiting for texture " << name << " timed out."<getVideoDriver(); + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); sanity_check(driver); video::IImage *img = generateImage(name); @@ -620,7 +596,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 @@ -679,7 +655,69 @@ video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id) video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id) { - return getTexture(name + "^[applyfiltersformesh", id); + static thread_local bool filter_needed = + g_settings->getBool("texture_clean_transparent") || m_setting_mipmap || + ((m_setting_trilinear_filter || m_setting_bilinear_filter) && + g_settings->getS32("texture_min_size") > 1); + // Avoid duplicating texture if it won't actually change + if (filter_needed) + return getTexture(name + "^[applyfiltersformesh", id); + return getTexture(name, id); +} + +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.empty()) + return NULL; + + auto it = m_palettes.find(name); + if (it == m_palettes.end()) { + // Create palette + video::IImage *img = generateImage(name); + if (!img) { + warningstream << "TextureSource::getPalette(): palette \"" << name + << "\" could not be loaded." << std::endl; + return NULL; + } + Palette new_palette; + u32 w = img->getDimension().Width; + u32 h = img->getDimension().Height; + // Real area of the image + u32 area = h * w; + if (area == 0) + return NULL; + if (area > 256) { + warningstream << "TextureSource::getPalette(): the specified" + << " palette image \"" << name << "\" is larger than 256" + << " pixels, using the first 256." << std::endl; + area = 256; + } else if (256 % area != 0) + warningstream << "TextureSource::getPalette(): the " + << "specified palette image \"" << name << "\" does not " + << "contain power of two pixels." << std::endl; + // We stretch the palette so it will fit 256 values + // This many param2 values will have the same color + u32 step = 256 / area; + // For each pixel in the image + for (u32 i = 0; i < area; i++) { + video::SColor c = img->getPixel(i % w, i / w); + // Fill in palette with 'step' colors + for (u32 j = 0; j < step; j++) + new_palette.push_back(c); + } + img->drop(); + // Fill in remaining elements + while (new_palette.size() < 256) + new_palette.emplace_back(0xFFFFFFFF); + m_palettes[name] = new_palette; + it = m_palettes.find(name); + } + if (it != m_palettes.end()) + return &((*it).second); + return NULL; } void TextureSource::processQueue() @@ -706,9 +744,9 @@ void TextureSource::insertSourceImage(const std::string &name, video::IImage *im { //infostream<<"TextureSource::insertSourceImage(): name="<getVideoDriver()); + m_sourcecache.insert(name, img, true); m_source_image_existence.set(name, true); } @@ -716,234 +754,147 @@ void TextureSource::rebuildImagesAndTextures() { MutexAutoLock lock(m_textureinfo_cache_mutex); - video::IVideoDriver* driver = m_device->getVideoDriver(); + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); sanity_check(driver); + infostream << "TextureSource: recreating " << m_textureinfo_cache.size() + << " textures" << std::endl; + // Recreate textures - for (u32 i=0; iname); -#ifdef __ANDROID__ + for (TextureInfo &ti : m_textureinfo_cache) { + video::IImage *img = generateImage(ti.name); +#if ENABLE_GLES img = Align2Npot2(img, driver); - sanity_check(img->getDimension().Height == npot2(img->getDimension().Height)); - sanity_check(img->getDimension().Width == npot2(img->getDimension().Width)); #endif // Create texture from resulting image video::ITexture *t = NULL; if (img) { - t = driver->addTexture(ti->name.c_str(), img); - guiScalingCache(io::path(ti->name.c_str()), driver, img); + t = driver->addTexture(ti.name.c_str(), img); + guiScalingCache(io::path(ti.name.c_str()), driver, img); img->drop(); } - video::ITexture *t_old = ti->texture; + video::ITexture *t_old = ti.texture; // Replace texture - ti->texture = t; + ti.texture = t; if (t_old) m_texture_trash.push_back(t_old); } } -video::ITexture* TextureSource::generateTextureFromMesh( - const TextureFromMeshParams ¶ms) +inline static void applyShadeFactor(video::SColor &color, u32 factor) { - video::IVideoDriver *driver = m_device->getVideoDriver(); - 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 screen = driver->getScreenSize(); - - // Render scene - driver->beginScene(true, true, video::SColor(0,0,0,0)); - driver->clearZBuffer(); - smgr->drawAll(); - - core::dimension2d partsize(screen.Width * scaling,screen.Height * scaling); - - irr::video::IImage* rawImage = - driver->createImage(irr::video::ECF_A8R8G8B8, partsize); - - u8* pixels = static_cast(rawImage->lock()); - if (!pixels) - { - rawImage->drop(); - return NULL; - } - - core::rect 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(); - - guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image); - - video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image); - inventory_image->drop(); + u32 f = core::clamp(factor, 0, 256); + color.setRed(color.getRed() * f / 256); + color.setGreen(color.getGreen() * f / 256); + color.setBlue(color.getBlue() * f / 256); +} - if (rtt == NULL) { - errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl; - return NULL; +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(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::IVideoDriver *driver = RenderingEngine::get_video_driver(); + + 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(image->getData()); + }; + auto free_image = [] (video::IImage *image) -> void { + 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) == false) - { - static bool warned = false; - if (!warned) - { - errorstream<<"TextureSource::generateTextureFromMesh(): " - <<"EVDF_RENDER_TO_TARGET not supported."<createImage(video::ECF_A8R8G8B8, {cube_size, cube_size}); + sanity_check(result->getPitch() == 4 * cube_size); + result->fill(video::SColor(0x00000000u)); + u32 *target = reinterpret_cast(result->getData()); + + // 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 offsets) -> void { + u32 brightness = core::clamp(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."<setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) { - driver->removeTexture(rtt); - errorstream<<"TextureSource::generateTextureFromMesh(): " - <<"failed to set render target"<getSceneManager(); - 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}, + }); + + return result; } video::IImage* TextureSource::generateImage(const std::string &name) @@ -1002,10 +953,6 @@ video::IImage* TextureSource::generateImage(const std::string &name) baseimg = generateImage(name.substr(0, last_separator_pos)); } - - video::IVideoDriver* driver = m_device->getVideoDriver(); - sanity_check(driver); - /* Parse out the last part of the name of the image and act according to it @@ -1051,51 +998,41 @@ video::IImage* TextureSource::generateImage(const std::string &name) return baseimg; } -#ifdef __ANDROID__ -#include +#if ENABLE_GLES + /** * Check and align image to npot2 if required by hardware * @param image image to check for npot2 alignment * @param driver driver to use for image operations * @return image or copy of image aligned to npot2 */ -video::IImage * Align2Npot2(video::IImage * image, - video::IVideoDriver* driver) +video::IImage *Align2Npot2(video::IImage *image, + video::IVideoDriver *driver) { - if (image == NULL) { + if (image == NULL) return image; - } - - core::dimension2d dim = image->getDimension(); - std::string extensions = (char*) glGetString(GL_EXTENSIONS); - if (extensions.find("GL_OES_texture_npot") != std::string::npos) { + if (driver->queryFeature(video::EVDF_TEXTURE_NPOT)) return image; - } + core::dimension2d 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(width, height)); - if (targetimage != NULL) { + if (targetimage != NULL) image->copyToScaling(targetimage); - } image->drop(); return targetimage; } @@ -1123,29 +1060,28 @@ bool TextureSource::generateImagePart(std::string part_of_name, video::IImage *& baseimg) { const char escape = '\\'; // same as in generateImage() - video::IVideoDriver* driver = m_device->getVideoDriver(); + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); sanity_check(driver); // Stuff starting with [ are special commands - if (part_of_name.size() == 0 || part_of_name[0] != '[') - { - video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device); -#ifdef __ANDROID__ + if (part_of_name.empty() || part_of_name[0] != '[') { + video::IImage *image = m_sourcecache.getOrLoad(part_of_name); +#if ENABLE_GLES image = Align2Npot2(image, driver); #endif if (image == NULL) { - if (part_of_name != "") { - if (part_of_name.find("_normal.png") == std::string::npos){ - errorstream<<"generateImage(): Could not load image \"" - <getVideoDriver()-> + video::IImage *scaled_image = RenderingEngine::get_video_driver()-> createImage(video::ECF_A8R8G8B8, dim_dst); image->copyToScaling(scaled_image); @@ -1210,7 +1146,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, scaled_image->drop(); } else { // Upscale base image - video::IImage* scaled_base = m_device->getVideoDriver()-> + video::IImage *scaled_base = RenderingEngine::get_video_driver()-> createImage(video::ECF_A8R8G8B8, dim); baseimg->copyToScaling(scaled_base); baseimg->drop(); @@ -1246,11 +1182,22 @@ bool TextureSource::generateImagePart(std::string part_of_name, } // Crack image number and overlay option + // Format: crack[o][:]:: 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 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) { /* @@ -1260,12 +1207,12 @@ bool TextureSource::generateImagePart(std::string part_of_name, horizontally tiled. */ video::IImage *img_crack = m_sourcecache.getOrLoad( - "crack_anylength.png", m_device); + "crack_anylength.png"); if (img_crack) { draw_crack(img_crack, baseimg, use_overlay, frame_count, - progression, driver); + progression, driver, tiles); img_crack->drop(); } } @@ -1285,7 +1232,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); baseimg->fill(video::SColor(0,0,0,0)); } - while (sf.at_end() == false) { + while (!sf.at_end()) { u32 x = stoi(sf.next(",")); u32 y = stoi(sf.next("=")); std::string filename = unescape_string(sf.next_esc(":", escape), escape); @@ -1295,8 +1242,6 @@ bool TextureSource::generateImagePart(std::string part_of_name, video::IImage *img = generateImage(filename); if (img) { core::dimension2d dim = img->getDimension(); - infostream<<"Size "< pos_base(x, y); video::IImage *img2 = driver->createImage(video::ECF_A8R8G8B8, dim); @@ -1470,89 +1415,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 @@ -1722,8 +1592,18 @@ bool TextureSource::generateImagePart(std::string part_of_name, */ else if (str_starts_with(part_of_name, "[applyfiltersformesh")) { - // Apply the "clean transparent" filter, if configured. - if (g_settings->getBool("texture_clean_transparent")) + /* IMPORTANT: When changing this, getTextureForMesh() needs to be + * updated too. */ + + if (!baseimg) { + errorstream << "generateImagePart(): baseimg == NULL " + << "for part_of_name=\"" << part_of_name + << "\", cancelling." << std::endl; + return false; + } + + // Apply the "clean transparent" filter, if needed + if (m_setting_mipmap || g_settings->getBool("texture_clean_transparent")) imageCleanTransparent(baseimg, 127); /* Upscale textures to user's requested minimum size. This is a trick to make @@ -1732,7 +1612,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 dim = baseimg->getDimension(); @@ -1782,7 +1663,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, u32 height = stoi(sf.next("")); core::dimension2d dim(width, height); - video::IImage* image = m_device->getVideoDriver()-> + video::IImage *image = RenderingEngine::get_video_driver()-> createImage(video::ECF_A8R8G8B8, dim); baseimg->copyToScaling(image); baseimg->drop(); @@ -1838,13 +1719,13 @@ bool TextureSource::generateImagePart(std::string part_of_name, std::string mode = sf.next(""); u32 mask = 0; - if (mode.find("a") != std::string::npos) + if (mode.find('a') != std::string::npos) mask |= 0xff000000UL; - if (mode.find("r") != std::string::npos) + if (mode.find('r') != std::string::npos) mask |= 0x00ff0000UL; - if (mode.find("g") != std::string::npos) + if (mode.find('g') != std::string::npos) mask |= 0x0000ff00UL; - if (mode.find("b") != std::string::npos) + if (mode.find('b') != std::string::npos) mask |= 0x000000ffUL; core::dimension2d dim = baseimg->getDimension(); @@ -1910,6 +1791,24 @@ bool TextureSource::generateImagePart(std::string part_of_name, return true; } +/* + Calculate the color of a single pixel drawn on top of another pixel. + + This is a little more complicated than just video::SColor::getInterpolated + because getInterpolated does not handle alpha correctly. For example, a + pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a + pixel with alpha=160, while getInterpolated would yield alpha=96. +*/ +static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio) +{ + if (dst_c.getAlpha() == 0) + return src_c; + video::SColor out_c = src_c.getInterpolated(dst_c, (float)ratio / 255.0f); + out_c.setAlpha(dst_c.getAlpha() + (255 - dst_c.getAlpha()) * + src_c.getAlpha() * ratio / (255 * 255)); + return out_c; +} + /* Draw an image on top of an another one, using the alpha channel of the source image @@ -1929,7 +1828,7 @@ static void blit_with_alpha(video::IImage *src, video::IImage *dst, s32 dst_y = dst_pos.Y + y0; video::SColor src_c = src->getPixel(src_x, src_y); video::SColor dst_c = dst->getPixel(dst_x, dst_y); - dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst_c = blitPixel(src_c, dst_c, src_c.getAlpha()); dst->setPixel(dst_x, dst_y, dst_c); } } @@ -1952,7 +1851,7 @@ static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, video::SColor dst_c = dst->getPixel(dst_x, dst_y); if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0) { - dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f); + dst_c = blitPixel(src_c, dst_c, src_c.getAlpha()); dst->setPixel(dst_x, dst_y, dst_c); } } @@ -2068,74 +1967,78 @@ static void apply_mask(video::IImage *mask, video::IImage *dst, } } +video::IImage *create_crack_image(video::IImage *crack, s32 frame_index, + core::dimension2d size, u8 tiles, video::IVideoDriver *driver) +{ + core::dimension2d strip_size = crack->getDimension(); + core::dimension2d frame_size(strip_size.Width, strip_size.Width); + core::dimension2d 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 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 dim_dst = dst->getDimension(); - // Dimension of original image - core::dimension2d 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 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 dim_crack_scaled( + core::dimension2d 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(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) @@ -2182,9 +2085,8 @@ u32 parseImageTransform(const std::string& s) pos++; break; } - else if (!(name_i.empty()) && - lowercase(s.substr(pos, name_i.size())) == name_i) - { + + if (!(name_i.empty()) && lowercase(s.substr(pos, name_i.size())) == name_i) { transform = i; pos += name_i.size(); break; @@ -2211,8 +2113,8 @@ core::dimension2d imageTransformDimension(u32 transform, core::dimension2d< { if (transform % 2 == 0) return dim; - else - return core::dimension2d(dim.Height, dim.Width); + + return core::dimension2d(dim.Height, dim.Width); } void imageTransform(u32 transform, video::IImage *src, video::IImage *dst) @@ -2267,12 +2169,12 @@ video::ITexture* TextureSource::getNormalTexture(const std::string &name) std::string fname_base = name; static const char *normal_ext = "_normal.png"; static const u32 normal_ext_size = strlen(normal_ext); - size_t pos = fname_base.find("."); + size_t pos = fname_base.find('.'); std::string fname_normal = fname_base.substr(0, pos) + normal_ext; if (isKnownSourceImage(fname_normal)) { // look for image extension and replace it size_t i = 0; - while ((i = fname_base.find(".", i)) != std::string::npos) { + while ((i = fname_base.find('.', i)) != std::string::npos) { fname_base.replace(i, 4, normal_ext); i += normal_ext_size; } @@ -2283,12 +2185,17 @@ video::ITexture* TextureSource::getNormalTexture(const std::string &name) video::SColor TextureSource::getTextureAverageColor(const std::string &name) { - video::IVideoDriver *driver = m_device->getVideoDriver(); + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); video::SColor c(0, 0, 0, 0); video::ITexture *texture = getTexture(name); + if (!texture) + return c; video::IImage *image = driver->createImage(texture, core::position2d(0, 0), texture->getOriginalSize()); + if (!image) + return c; + u32 total = 0; u32 tR = 0; u32 tG = 0; @@ -2326,15 +2233,21 @@ video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present) if (isKnownSourceImage(tname)) { return getTexture(tname); - } else { - video::IVideoDriver *driver = m_device->getVideoDriver(); - video::IImage *flags_image = driver->createImage( - video::ECF_A8R8G8B8, core::dimension2d(1, 1)); - sanity_check(flags_image != NULL); - video::SColor c(255, normalmap_present ? 255 : 0, 0, 0); - flags_image->setPixel(0, 0, c); - insertSourceImage(tname, flags_image); - flags_image->drop(); - return getTexture(tname); } + + video::IVideoDriver *driver = RenderingEngine::get_video_driver(); + video::IImage *flags_image = driver->createImage( + video::ECF_A8R8G8B8, core::dimension2d(1, 1)); + sanity_check(flags_image != NULL); + video::SColor c(255, normalmap_present ? 255 : 0, 0, 0); + flags_image->setPixel(0, 0, c); + insertSourceImage(tname, flags_image); + flags_image->drop(); + return getTexture(tname); + +} + +std::vector getTextureDirs() +{ + return fs::GetRecursiveDirs(g_settings->get("texture_path")); }