This may improve performance substantially if there are many meshes with "few" vertices that would otherwise be retransmitted to the GPU every frame. In testing, this does not seem to decrease performance, even if as few as 4 vertices are used (e.g. particles).
387 lines
12 KiB
C++
387 lines
12 KiB
C++
/*
|
|
Minetest
|
|
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
|
Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
|
|
|
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 <optional>
|
|
#include <irrlicht.h>
|
|
#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<video::E_DRIVER_TYPE> 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<video::E_DRIVER_TYPE> 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<u16>(g_settings->getU16("screen_w"), 1);
|
|
u16 screen_h = std::max<u16>(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<u32>(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;
|
|
|
|
// This changes the minimum allowed number of vertices in a VBO. Default is 500.
|
|
driver->setMinHardwareBufferVertexCount(4);
|
|
|
|
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<video::IImage> 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<s32> 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<u32> &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<u32> 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<s32>(img_pos.X, img_pos.Y,
|
|
img_pos.X + imgW,
|
|
img_pos.Y + imgH),
|
|
core::rect<s32>(0, 0, img_size.Width,
|
|
img_size.Height),
|
|
0, 0, true);
|
|
|
|
draw2DImageFilterScaled(get_video_driver(), progress_img,
|
|
core::rect<s32>(img_pos.X, img_pos.Y,
|
|
img_pos.X + (percent * imgW) / 100,
|
|
img_pos.Y + imgH),
|
|
core::rect<s32>(0, 0,
|
|
(percent * img_size.Width) / 100,
|
|
img_size.Height),
|
|
0, 0, true);
|
|
}
|
|
}
|
|
|
|
guienv->drawAll();
|
|
get_video_driver()->endScene();
|
|
guitext->remove();
|
|
}
|
|
|
|
std::vector<video::E_DRIVER_TYPE> 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<video::E_DRIVER_TYPE> 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<int, VideoDriverInfo> 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<u32> 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<u32> current_screen_size =
|
|
RenderingEngine::get_video_driver()->getScreenSize();
|
|
// Don't replace good value with (0, 0)
|
|
if (current_screen_size != irr::core::dimension2d<u32>(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);
|
|
}
|