/* Minetest Copyright (C) 2010-2013 celeron55, Perttu Ahola Copyright (C) 2017 nerzhul, Loic Blot This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "fontengine.h" #include "client.h" #include "clouds.h" #include "util/numeric.h" #include "guiscalingfilter.h" #include "localplayer.h" #include "client/hud.h" #include "camera.h" #include "minimap.h" #include "clientmap.h" #include "renderingengine.h" #include "render/core.h" #include "render/factory.h" #include "inputhandler.h" #include "gettext.h" #include "filesys.h" #include "../gui/guiSkin.h" #include "irrlicht_changes/static_text.h" #include "irr_ptr.h" RenderingEngine *RenderingEngine::s_singleton = nullptr; const video::SColor RenderingEngine::MENU_SKY_COLOR = video::SColor(255, 140, 186, 250); const float RenderingEngine::BASE_BLOOM_STRENGTH = 1.0f; static gui::GUISkin *createSkin(gui::IGUIEnvironment *environment, gui::EGUI_SKIN_TYPE type, video::IVideoDriver *driver) { gui::GUISkin *skin = new gui::GUISkin(type, driver); gui::IGUIFont *builtinfont = environment->getBuiltInFont(); gui::IGUIFontBitmap *bitfont = nullptr; if (builtinfont && builtinfont->getType() == gui::EGFT_BITMAP) bitfont = (gui::IGUIFontBitmap*)builtinfont; gui::IGUISpriteBank *bank = 0; skin->setFont(builtinfont); if (bitfont) bank = bitfont->getSpriteBank(); skin->setSpriteBank(bank); return skin; } static std::optional chooseVideoDriver() { auto &&configured_name = g_settings->get("video_driver"); if (configured_name.empty()) return std::nullopt; auto &&drivers = RenderingEngine::getSupportedVideoDrivers(); for (auto driver: drivers) { auto &&info = RenderingEngine::getVideoDriverInfo(driver); if (!strcasecmp(configured_name.c_str(), info.name.c_str())) return driver; } errorstream << "Invalid video_driver specified: " << configured_name << std::endl; return std::nullopt; } static irr::IrrlichtDevice *createDevice(SIrrlichtCreationParameters params, std::optional requested_driver) { if (requested_driver) { params.DriverType = *requested_driver; if (auto *device = createDeviceEx(params)) return device; errorstream << "Failed to initialize the " << RenderingEngine::getVideoDriverInfo(*requested_driver).friendly_name << " video driver" << std::endl; } sanity_check(requested_driver != video::EDT_NULL); // try to find any working video driver for (auto fallback_driver: RenderingEngine::getSupportedVideoDrivers()) { if (fallback_driver == video::EDT_NULL || fallback_driver == requested_driver) continue; params.DriverType = fallback_driver; if (auto *device = createDeviceEx(params)) return device; } throw std::runtime_error("Could not initialize the device with any supported video driver"); } RenderingEngine::RenderingEngine(IEventReceiver *receiver) { sanity_check(!s_singleton); // Resolution selection bool fullscreen = g_settings->getBool("fullscreen"); #ifdef __ANDROID__ u16 screen_w = 0, screen_h = 0; bool window_maximized = false; #else u16 screen_w = std::max(g_settings->getU16("screen_w"), 1); u16 screen_h = std::max(g_settings->getU16("screen_h"), 1); bool window_maximized = g_settings->getBool("window_maximized"); #endif // bpp, fsaa, vsync bool vsync = g_settings->getBool("vsync"); bool enable_fsaa = g_settings->get("antialiasing") == "fsaa"; u16 fsaa = enable_fsaa ? MYMAX(2, g_settings->getU16("fsaa")) : 0; // Determine driver auto driverType = chooseVideoDriver(); SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); if (tracestream) params.LoggingLevel = irr::ELL_DEBUG; params.WindowSize = core::dimension2d(screen_w, screen_h); params.AntiAlias = fsaa; params.Fullscreen = fullscreen; params.WindowMaximized = window_maximized; params.WindowResizable = 1; // 1 means always (required for window_maximized) params.Stencilbuffer = false; params.Vsync = vsync; params.EventReceiver = receiver; #ifdef __ANDROID__ params.PrivateData = porting::app_global; #endif // there is no standardized path for these on desktop std::string rel_path = std::string("client") + DIR_DELIM + "shaders" + DIR_DELIM + "Irrlicht"; params.OGLES2ShaderPath = (porting::path_share + DIR_DELIM + rel_path + DIR_DELIM).c_str(); m_device = createDevice(params, driverType); driver = m_device->getVideoDriver(); infostream << "Using the " << RenderingEngine::getVideoDriverInfo(driver->getDriverType()).friendly_name << " video driver" << std::endl; s_singleton = this; auto skin = createSkin(m_device->getGUIEnvironment(), gui::EGST_WINDOWS_METALLIC, driver); m_device->getGUIEnvironment()->setSkin(skin); skin->drop(); } RenderingEngine::~RenderingEngine() { core.reset(); m_device->closeDevice(); s_singleton = nullptr; } v2u32 RenderingEngine::_getWindowSize() const { if (core) return core->getVirtualSize(); return m_device->getVideoDriver()->getScreenSize(); } void RenderingEngine::setResizable(bool resize) { m_device->setResizable(resize); } void RenderingEngine::removeMesh(const scene::IMesh* mesh) { m_device->getSceneManager()->getMeshCache()->removeMesh(mesh); } void RenderingEngine::cleanupMeshCache() { auto mesh_cache = m_device->getSceneManager()->getMeshCache(); while (mesh_cache->getMeshCount() != 0) { if (scene::IAnimatedMesh *mesh = mesh_cache->getMeshByIndex(0)) mesh_cache->removeMesh(mesh); } } bool RenderingEngine::setupTopLevelWindow() { return setWindowIcon(); } bool RenderingEngine::setWindowIcon() { irr_ptr img(driver->createImageFromFile( (porting::path_share + "/textures/base/pack/logo.png").c_str())); if (!img) { warningstream << "Could not load icon file." << std::endl; return false; } return m_device->setWindowIcon(img.get()); } /* Draws a screen with a single text on it. Text will be removed when the screen is drawn the next time. Additionally, a progressbar can be drawn when percent is set between 0 and 100. */ void RenderingEngine::draw_load_screen(const std::wstring &text, gui::IGUIEnvironment *guienv, ITextureSource *tsrc, float dtime, int percent, bool sky) { v2u32 screensize = getWindowSize(); v2s32 textsize(g_fontengine->getTextWidth(text), g_fontengine->getLineHeight()); v2s32 center(screensize.X / 2, screensize.Y / 2); core::rect textrect(center - textsize / 2, center + textsize / 2); gui::IGUIStaticText *guitext = gui::StaticText::add(guienv, text, textrect, false, false); guitext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT); if (sky && g_settings->getBool("menu_clouds")) { g_menuclouds->step(dtime * 3); g_menuclouds->render(); get_video_driver()->beginScene(true, true, RenderingEngine::MENU_SKY_COLOR); g_menucloudsmgr->drawAll(); } else if (sky) get_video_driver()->beginScene(true, true, RenderingEngine::MENU_SKY_COLOR); else get_video_driver()->beginScene(true, true, video::SColor(255, 0, 0, 0)); // draw progress bar if ((percent >= 0) && (percent <= 100)) { video::ITexture *progress_img = tsrc->getTexture("progress_bar.png"); video::ITexture *progress_img_bg = tsrc->getTexture("progress_bar_bg.png"); if (progress_img && progress_img_bg) { #ifndef __ANDROID__ const core::dimension2d &img_size = progress_img_bg->getSize(); float density = g_settings->getFloat("gui_scaling", 0.5f, 20.0f) * getDisplayDensity(); u32 imgW = rangelim(img_size.Width, 200, 600) * density; u32 imgH = rangelim(img_size.Height, 24, 72) * density; #else const core::dimension2d img_size(256, 48); float imgRatio = (float)img_size.Height / img_size.Width; u32 imgW = screensize.X / 2.2f; u32 imgH = floor(imgW * imgRatio); #endif v2s32 img_pos((screensize.X - imgW) / 2, (screensize.Y - imgH) / 2); draw2DImageFilterScaled(get_video_driver(), progress_img_bg, core::rect(img_pos.X, img_pos.Y, img_pos.X + imgW, img_pos.Y + imgH), core::rect(0, 0, img_size.Width, img_size.Height), 0, 0, true); draw2DImageFilterScaled(get_video_driver(), progress_img, core::rect(img_pos.X, img_pos.Y, img_pos.X + (percent * imgW) / 100, img_pos.Y + imgH), core::rect(0, 0, (percent * img_size.Width) / 100, img_size.Height), 0, 0, true); } } guienv->drawAll(); get_video_driver()->endScene(); guitext->remove(); } std::vector RenderingEngine::getSupportedVideoDrivers() { // Only check these drivers. We do not support software and D3D in any capacity. // Order by preference (best first) static const video::E_DRIVER_TYPE glDrivers[] = { video::EDT_OPENGL, video::EDT_OPENGL3, video::EDT_OGLES2, video::EDT_OGLES1, video::EDT_NULL, }; std::vector drivers; for (video::E_DRIVER_TYPE driver: glDrivers) { if (IrrlichtDevice::isDriverSupported(driver)) drivers.push_back(driver); } return drivers; } void RenderingEngine::initialize(Client *client, Hud *hud) { const std::string &draw_mode = g_settings->get("3d_mode"); core.reset(createRenderingCore(draw_mode, m_device, client, hud)); } void RenderingEngine::finalize() { core.reset(); } void RenderingEngine::draw_scene(video::SColor skycolor, bool show_hud, bool draw_wield_tool, bool draw_crosshair) { core->draw(skycolor, show_hud, draw_wield_tool, draw_crosshair); } const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_TYPE type) { static const std::unordered_map driver_info_map = { {(int)video::EDT_NULL, {"null", "NULL Driver"}}, {(int)video::EDT_OPENGL, {"opengl", "OpenGL"}}, {(int)video::EDT_OPENGL3, {"opengl3", "OpenGL 3+"}}, {(int)video::EDT_OGLES1, {"ogles1", "OpenGL ES1"}}, {(int)video::EDT_OGLES2, {"ogles2", "OpenGL ES2"}}, }; return driver_info_map.at((int)type); } float RenderingEngine::getDisplayDensity() { #ifndef __ANDROID__ static float cached_display_density = [&] { float dpi = get_raw_device()->getDisplayDensity(); // fall back to manually specified dpi if (dpi == 0.0f) dpi = g_settings->getFloat("screen_dpi"); return dpi / 96.0f; }(); return std::max(cached_display_density * g_settings->getFloat("display_density_factor"), 0.5f); #else // __ANDROID__ return porting::getDisplayDensity(); #endif // __ANDROID__ } void RenderingEngine::autosaveScreensizeAndCo( const irr::core::dimension2d initial_screen_size, const bool initial_window_maximized) { if (!g_settings->getBool("autosave_screensize")) return; // Note: If the screensize or similar hasn't changed (i.e. it's the same as // the setting was when minetest started, as given by the initial_* parameters), // we do not want to save the thing. This allows users to also manually change // the settings. // Screen size const irr::core::dimension2d current_screen_size = RenderingEngine::get_video_driver()->getScreenSize(); // Don't replace good value with (0, 0) if (current_screen_size != irr::core::dimension2d(0, 0) && current_screen_size != initial_screen_size) { g_settings->setU16("screen_w", current_screen_size.Width); g_settings->setU16("screen_h", current_screen_size.Height); } // Window maximized const bool is_window_maximized = RenderingEngine::get_raw_device() ->isWindowMaximized(); if (is_window_maximized != initial_window_maximized) g_settings->setBool("window_maximized", is_window_maximized); }