summaryrefslogtreecommitdiff
path: root/src/client/shader.cpp
diff options
context:
space:
mode:
authorQuentin Bazin <quent42340@gmail.com>2018-11-28 20:01:49 +0100
committerSmallJoker <SmallJoker@users.noreply.github.com>2018-11-28 20:01:49 +0100
commit5f1cd555cd9d1c64426e173b30b5b792d211c835 (patch)
tree2c8508467d3bf28d549cce2d25144fa8ef42beae /src/client/shader.cpp
parentddd9317b733857630499972179caebc236b4d991 (diff)
downloadminetest-5f1cd555cd9d1c64426e173b30b5b792d211c835.tar.gz
minetest-5f1cd555cd9d1c64426e173b30b5b792d211c835.tar.bz2
minetest-5f1cd555cd9d1c64426e173b30b5b792d211c835.zip
Move client-specific files to 'src/client' (#7902)
Update Android.mk Remove 'src/client' from include_directories
Diffstat (limited to 'src/client/shader.cpp')
-rw-r--r--src/client/shader.cpp873
1 files changed, 873 insertions, 0 deletions
diff --git a/src/client/shader.cpp b/src/client/shader.cpp
new file mode 100644
index 000000000..3b49a36ba
--- /dev/null
+++ b/src/client/shader.cpp
@@ -0,0 +1,873 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Kahrl <kahrl@gmx.net>
+
+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 <fstream>
+#include <iterator>
+#include "shader.h"
+#include "irrlichttypes_extrabloated.h"
+#include "debug.h"
+#include "filesys.h"
+#include "util/container.h"
+#include "util/thread.h"
+#include "settings.h"
+#include <ICameraSceneNode.h>
+#include <IGPUProgrammingServices.h>
+#include <IMaterialRenderer.h>
+#include <IMaterialRendererServices.h>
+#include <IShaderConstantSetCallBack.h>
+#include "client/renderingengine.h"
+#include "EShaderTypes.h"
+#include "log.h"
+#include "gamedef.h"
+#include "client/tile.h"
+
+/*
+ A cache from shader name to shader path
+*/
+MutexedMap<std::string, std::string> g_shadername_to_path_cache;
+
+/*
+ Gets the path to a shader by first checking if the file
+ name_of_shader/filename
+ exists in shader_path and if not, using the data path.
+
+ If not found, returns "".
+
+ Utilizes a thread-safe cache.
+*/
+std::string getShaderPath(const std::string &name_of_shader,
+ const std::string &filename)
+{
+ std::string combined = name_of_shader + DIR_DELIM + filename;
+ std::string fullpath;
+ /*
+ Check from cache
+ */
+ bool incache = g_shadername_to_path_cache.get(combined, &fullpath);
+ if(incache)
+ return fullpath;
+
+ /*
+ Check from shader_path
+ */
+ std::string shader_path = g_settings->get("shader_path");
+ if (!shader_path.empty()) {
+ std::string testpath = shader_path + DIR_DELIM + combined;
+ if(fs::PathExists(testpath))
+ fullpath = testpath;
+ }
+
+ /*
+ Check from default data directory
+ */
+ if (fullpath.empty()) {
+ std::string rel_path = std::string("client") + DIR_DELIM
+ + "shaders" + DIR_DELIM
+ + name_of_shader + DIR_DELIM
+ + filename;
+ std::string testpath = porting::path_share + DIR_DELIM + rel_path;
+ if(fs::PathExists(testpath))
+ fullpath = testpath;
+ }
+
+ // Add to cache (also an empty result is cached)
+ g_shadername_to_path_cache.set(combined, fullpath);
+
+ // Finally return it
+ return fullpath;
+}
+
+/*
+ SourceShaderCache: A cache used for storing source shaders.
+*/
+
+class SourceShaderCache
+{
+public:
+ void insert(const std::string &name_of_shader, const std::string &filename,
+ const std::string &program, bool prefer_local)
+ {
+ std::string combined = name_of_shader + DIR_DELIM + filename;
+ // Try to use local shader instead if asked to
+ if(prefer_local){
+ std::string path = getShaderPath(name_of_shader, filename);
+ if(!path.empty()){
+ std::string p = readFile(path);
+ if (!p.empty()) {
+ m_programs[combined] = p;
+ return;
+ }
+ }
+ }
+ m_programs[combined] = program;
+ }
+
+ std::string get(const std::string &name_of_shader,
+ const std::string &filename)
+ {
+ std::string combined = name_of_shader + DIR_DELIM + filename;
+ StringMap::iterator n = m_programs.find(combined);
+ if (n != m_programs.end())
+ return n->second;
+ return "";
+ }
+
+ // Primarily fetches from cache, secondarily tries to read from filesystem
+ std::string getOrLoad(const std::string &name_of_shader,
+ const std::string &filename)
+ {
+ std::string combined = name_of_shader + DIR_DELIM + filename;
+ StringMap::iterator n = m_programs.find(combined);
+ if (n != m_programs.end())
+ return n->second;
+ std::string path = getShaderPath(name_of_shader, filename);
+ if (path.empty()) {
+ infostream << "SourceShaderCache::getOrLoad(): No path found for \""
+ << combined << "\"" << std::endl;
+ return "";
+ }
+ infostream << "SourceShaderCache::getOrLoad(): Loading path \""
+ << path << "\"" << std::endl;
+ std::string p = readFile(path);
+ if (!p.empty()) {
+ m_programs[combined] = p;
+ return p;
+ }
+ return "";
+ }
+private:
+ StringMap m_programs;
+
+ std::string readFile(const std::string &path)
+ {
+ std::ifstream is(path.c_str(), std::ios::binary);
+ if(!is.is_open())
+ return "";
+ std::ostringstream tmp_os;
+ tmp_os << is.rdbuf();
+ return tmp_os.str();
+ }
+};
+
+
+/*
+ ShaderCallback: Sets constants that can be used in shaders
+*/
+
+class ShaderCallback : public video::IShaderConstantSetCallBack
+{
+ std::vector<IShaderConstantSetter*> m_setters;
+
+public:
+ ShaderCallback(const std::vector<IShaderConstantSetterFactory *> &factories)
+ {
+ for (IShaderConstantSetterFactory *factory : factories)
+ m_setters.push_back(factory->create());
+ }
+
+ ~ShaderCallback()
+ {
+ for (IShaderConstantSetter *setter : m_setters)
+ delete setter;
+ }
+
+ virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData)
+ {
+ video::IVideoDriver *driver = services->getVideoDriver();
+ sanity_check(driver != NULL);
+
+ bool is_highlevel = userData;
+
+ for (IShaderConstantSetter *setter : m_setters)
+ setter->onSetConstants(services, is_highlevel);
+ }
+};
+
+
+/*
+ MainShaderConstantSetter: Set basic constants required for almost everything
+*/
+
+class MainShaderConstantSetter : public IShaderConstantSetter
+{
+ CachedVertexShaderSetting<float, 16> m_world_view_proj;
+ CachedVertexShaderSetting<float, 16> m_world;
+
+public:
+ MainShaderConstantSetter() :
+ m_world_view_proj("mWorldViewProj"),
+ m_world("mWorld")
+ {}
+ ~MainShaderConstantSetter() = default;
+
+ virtual void onSetConstants(video::IMaterialRendererServices *services,
+ bool is_highlevel)
+ {
+ video::IVideoDriver *driver = services->getVideoDriver();
+ sanity_check(driver);
+
+ // Set clip matrix
+ core::matrix4 worldViewProj;
+ worldViewProj = driver->getTransform(video::ETS_PROJECTION);
+ worldViewProj *= driver->getTransform(video::ETS_VIEW);
+ worldViewProj *= driver->getTransform(video::ETS_WORLD);
+ if (is_highlevel)
+ m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services);
+ else
+ services->setVertexShaderConstant(worldViewProj.pointer(), 0, 4);
+
+ // Set world matrix
+ core::matrix4 world = driver->getTransform(video::ETS_WORLD);
+ if (is_highlevel)
+ m_world.set(*reinterpret_cast<float(*)[16]>(world.pointer()), services);
+ else
+ services->setVertexShaderConstant(world.pointer(), 4, 4);
+
+ }
+};
+
+
+class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory
+{
+public:
+ virtual IShaderConstantSetter* create()
+ { return new MainShaderConstantSetter(); }
+};
+
+
+/*
+ ShaderSource
+*/
+
+class ShaderSource : public IWritableShaderSource
+{
+public:
+ ShaderSource();
+ ~ShaderSource();
+
+ /*
+ - If shader material specified by name is found from cache,
+ return the cached id.
+ - Otherwise generate the shader material, add to cache and return id.
+
+ The id 0 points to a null shader. Its material is EMT_SOLID.
+ */
+ u32 getShaderIdDirect(const std::string &name,
+ const u8 material_type, const u8 drawtype);
+
+ /*
+ If shader specified by the name pointed by the id doesn't
+ exist, create it, then return id.
+
+ Can be called from any thread. If called from some other thread
+ and not found in cache, the call is queued to the main thread
+ for processing.
+ */
+
+ u32 getShader(const std::string &name,
+ const u8 material_type, const u8 drawtype);
+
+ ShaderInfo getShaderInfo(u32 id);
+
+ // Processes queued shader requests from other threads.
+ // Shall be called from the main thread.
+ void processQueue();
+
+ // Insert a shader program into the cache without touching the
+ // filesystem. Shall be called from the main thread.
+ void insertSourceShader(const std::string &name_of_shader,
+ const std::string &filename, const std::string &program);
+
+ // Rebuild shaders from the current set of source shaders
+ // Shall be called from the main thread.
+ void rebuildShaders();
+
+ void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter)
+ {
+ m_setter_factories.push_back(setter);
+ }
+
+private:
+
+ // The id of the thread that is allowed to use irrlicht directly
+ std::thread::id m_main_thread;
+
+ // Cache of source shaders
+ // This should be only accessed from the main thread
+ SourceShaderCache m_sourcecache;
+
+ // A shader id is index in this array.
+ // The first position contains a dummy shader.
+ std::vector<ShaderInfo> m_shaderinfo_cache;
+ // The former container is behind this mutex
+ std::mutex m_shaderinfo_cache_mutex;
+
+ // Queued shader fetches (to be processed by the main thread)
+ RequestQueue<std::string, u32, u8, u8> m_get_shader_queue;
+
+ // Global constant setter factories
+ std::vector<IShaderConstantSetterFactory *> m_setter_factories;
+
+ // Shader callbacks
+ std::vector<ShaderCallback *> m_callbacks;
+};
+
+IWritableShaderSource *createShaderSource()
+{
+ return new ShaderSource();
+}
+
+/*
+ Generate shader given the shader name.
+*/
+ShaderInfo generate_shader(const std::string &name,
+ u8 material_type, u8 drawtype, std::vector<ShaderCallback *> &callbacks,
+ const std::vector<IShaderConstantSetterFactory *> &setter_factories,
+ SourceShaderCache *sourcecache);
+
+/*
+ Load shader programs
+*/
+void load_shaders(const std::string &name, SourceShaderCache *sourcecache,
+ video::E_DRIVER_TYPE drivertype, bool enable_shaders,
+ std::string &vertex_program, std::string &pixel_program,
+ std::string &geometry_program, bool &is_highlevel);
+
+ShaderSource::ShaderSource()
+{
+ m_main_thread = std::this_thread::get_id();
+
+ // Add a dummy ShaderInfo as the first index, named ""
+ m_shaderinfo_cache.emplace_back();
+
+ // Add main global constant setter
+ addShaderConstantSetterFactory(new MainShaderConstantSetterFactory());
+}
+
+ShaderSource::~ShaderSource()
+{
+ for (ShaderCallback *callback : m_callbacks) {
+ delete callback;
+ }
+ for (IShaderConstantSetterFactory *setter_factorie : m_setter_factories) {
+ delete setter_factorie;
+ }
+}
+
+u32 ShaderSource::getShader(const std::string &name,
+ const u8 material_type, const u8 drawtype)
+{
+ /*
+ Get shader
+ */
+
+ if (std::this_thread::get_id() == m_main_thread) {
+ return getShaderIdDirect(name, material_type, drawtype);
+ }
+
+ /*errorstream<<"getShader(): 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_shader_queue.add(name, 0, 0, &result_queue);
+
+ /* infostream<<"Waiting for shader from main thread, name=\""
+ <<name<<"\""<<std::endl;*/
+
+ while(true) {
+ GetResult<std::string, u32, u8, u8>
+ result = result_queue.pop_frontNoEx();
+
+ if (result.key == name) {
+ return result.item;
+ }
+
+ errorstream << "Got shader with invalid name: " << result.key << std::endl;
+ }
+
+ infostream << "getShader(): Failed" << std::endl;
+
+ return 0;
+}
+
+/*
+ This method generates all the shaders
+*/
+u32 ShaderSource::getShaderIdDirect(const std::string &name,
+ const u8 material_type, const u8 drawtype)
+{
+ //infostream<<"getShaderIdDirect(): name=\""<<name<<"\""<<std::endl;
+
+ // Empty name means shader 0
+ if (name.empty()) {
+ infostream<<"getShaderIdDirect(): name is empty"<<std::endl;
+ return 0;
+ }
+
+ // Check if already have such instance
+ for(u32 i=0; i<m_shaderinfo_cache.size(); i++){
+ ShaderInfo *info = &m_shaderinfo_cache[i];
+ if(info->name == name && info->material_type == material_type &&
+ info->drawtype == drawtype)
+ return i;
+ }
+
+ /*
+ Calling only allowed from main thread
+ */
+ if (std::this_thread::get_id() != m_main_thread) {
+ errorstream<<"ShaderSource::getShaderIdDirect() "
+ "called not from main thread"<<std::endl;
+ return 0;
+ }
+
+ ShaderInfo info = generate_shader(name, material_type, drawtype,
+ m_callbacks, m_setter_factories, &m_sourcecache);
+
+ /*
+ Add shader to caches (add dummy shaders too)
+ */
+
+ MutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+ u32 id = m_shaderinfo_cache.size();
+ m_shaderinfo_cache.push_back(info);
+
+ infostream<<"getShaderIdDirect(): "
+ <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;
+
+ return id;
+}
+
+
+ShaderInfo ShaderSource::getShaderInfo(u32 id)
+{
+ MutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+ if(id >= m_shaderinfo_cache.size())
+ return ShaderInfo();
+
+ return m_shaderinfo_cache[id];
+}
+
+void ShaderSource::processQueue()
+{
+
+
+}
+
+void ShaderSource::insertSourceShader(const std::string &name_of_shader,
+ const std::string &filename, const std::string &program)
+{
+ /*infostream<<"ShaderSource::insertSourceShader(): "
+ "name_of_shader=\""<<name_of_shader<<"\", "
+ "filename=\""<<filename<<"\""<<std::endl;*/
+
+ sanity_check(std::this_thread::get_id() == m_main_thread);
+
+ m_sourcecache.insert(name_of_shader, filename, program, true);
+}
+
+void ShaderSource::rebuildShaders()
+{
+ MutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+ /*// Oh well... just clear everything, they'll load sometime.
+ m_shaderinfo_cache.clear();
+ m_name_to_id.clear();*/
+
+ /*
+ FIXME: Old shader materials can't be deleted in Irrlicht,
+ or can they?
+ (This would be nice to do in the destructor too)
+ */
+
+ // Recreate shaders
+ for (ShaderInfo &i : m_shaderinfo_cache) {
+ ShaderInfo *info = &i;
+ if (!info->name.empty()) {
+ *info = generate_shader(info->name, info->material_type,
+ info->drawtype, m_callbacks,
+ m_setter_factories, &m_sourcecache);
+ }
+ }
+}
+
+
+ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtype,
+ std::vector<ShaderCallback *> &callbacks,
+ const std::vector<IShaderConstantSetterFactory *> &setter_factories,
+ SourceShaderCache *sourcecache)
+{
+ ShaderInfo shaderinfo;
+ shaderinfo.name = name;
+ shaderinfo.material_type = material_type;
+ shaderinfo.drawtype = drawtype;
+ shaderinfo.material = video::EMT_SOLID;
+ switch (material_type) {
+ case TILE_MATERIAL_OPAQUE:
+ case TILE_MATERIAL_LIQUID_OPAQUE:
+ shaderinfo.base_material = video::EMT_SOLID;
+ break;
+ case TILE_MATERIAL_ALPHA:
+ case TILE_MATERIAL_LIQUID_TRANSPARENT:
+ shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+ break;
+ case TILE_MATERIAL_BASIC:
+ case TILE_MATERIAL_WAVING_LEAVES:
+ case TILE_MATERIAL_WAVING_PLANTS:
+ shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+ break;
+ }
+
+ bool enable_shaders = g_settings->getBool("enable_shaders");
+ if (!enable_shaders)
+ return shaderinfo;
+
+ video::IVideoDriver *driver = RenderingEngine::get_video_driver();
+
+ video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
+ if(!gpu){
+ errorstream<<"generate_shader(): "
+ "failed to generate \""<<name<<"\", "
+ "GPU programming not supported."
+ <<std::endl;
+ return shaderinfo;
+ }
+
+ // Choose shader language depending on driver type and settings
+ // Then load shaders
+ std::string vertex_program;
+ std::string pixel_program;
+ std::string geometry_program;
+ bool is_highlevel;
+ load_shaders(name, sourcecache, driver->getDriverType(),
+ enable_shaders, vertex_program, pixel_program,
+ geometry_program, is_highlevel);
+ // Check hardware/driver support
+ if (!vertex_program.empty() &&
+ !driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
+ !driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1)){
+ infostream<<"generate_shader(): vertex shaders disabled "
+ "because of missing driver/hardware support."
+ <<std::endl;
+ vertex_program = "";
+ }
+ if (!pixel_program.empty() &&
+ !driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
+ !driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1)){
+ infostream<<"generate_shader(): pixel shaders disabled "
+ "because of missing driver/hardware support."
+ <<std::endl;
+ pixel_program = "";
+ }
+ if (!geometry_program.empty() &&
+ !driver->queryFeature(video::EVDF_GEOMETRY_SHADER)){
+ infostream<<"generate_shader(): geometry shaders disabled "
+ "because of missing driver/hardware support."
+ <<std::endl;
+ geometry_program = "";
+ }
+
+ // If no shaders are used, don't make a separate material type
+ if (vertex_program.empty() && pixel_program.empty() && geometry_program.empty())
+ return shaderinfo;
+
+ // Create shaders header
+ std::string shaders_header = "#version 120\n";
+
+ static const char* drawTypes[] = {
+ "NDT_NORMAL",
+ "NDT_AIRLIKE",
+ "NDT_LIQUID",
+ "NDT_FLOWINGLIQUID",
+ "NDT_GLASSLIKE",
+ "NDT_ALLFACES",
+ "NDT_ALLFACES_OPTIONAL",
+ "NDT_TORCHLIKE",
+ "NDT_SIGNLIKE",
+ "NDT_PLANTLIKE",
+ "NDT_FENCELIKE",
+ "NDT_RAILLIKE",
+ "NDT_NODEBOX",
+ "NDT_GLASSLIKE_FRAMED",
+ "NDT_FIRELIKE",
+ "NDT_GLASSLIKE_FRAMED_OPTIONAL",
+ "NDT_PLANTLIKE_ROOTED",
+ };
+
+ for (int i = 0; i < 14; i++){
+ shaders_header += "#define ";
+ shaders_header += drawTypes[i];
+ shaders_header += " ";
+ shaders_header += itos(i);
+ shaders_header += "\n";
+ }
+
+ static const char* materialTypes[] = {
+ "TILE_MATERIAL_BASIC",
+ "TILE_MATERIAL_ALPHA",
+ "TILE_MATERIAL_LIQUID_TRANSPARENT",
+ "TILE_MATERIAL_LIQUID_OPAQUE",
+ "TILE_MATERIAL_WAVING_LEAVES",
+ "TILE_MATERIAL_WAVING_PLANTS",
+ "TILE_MATERIAL_OPAQUE"
+ };
+
+ for (int i = 0; i < 7; i++){
+ shaders_header += "#define ";
+ shaders_header += materialTypes[i];
+ shaders_header += " ";
+ shaders_header += itos(i);
+ shaders_header += "\n";
+ }
+
+ shaders_header += "#define MATERIAL_TYPE ";
+ shaders_header += itos(material_type);
+ shaders_header += "\n";
+ shaders_header += "#define DRAW_TYPE ";
+ shaders_header += itos(drawtype);
+ shaders_header += "\n";
+
+ if (g_settings->getBool("generate_normalmaps")) {
+ shaders_header += "#define GENERATE_NORMALMAPS 1\n";
+ } else {
+ shaders_header += "#define GENERATE_NORMALMAPS 0\n";
+ }
+ shaders_header += "#define NORMALMAPS_STRENGTH ";
+ shaders_header += ftos(g_settings->getFloat("normalmaps_strength"));
+ shaders_header += "\n";
+ float sample_step;
+ int smooth = (int)g_settings->getFloat("normalmaps_smooth");
+ switch (smooth){
+ case 0:
+ sample_step = 0.0078125; // 1.0 / 128.0
+ break;
+ case 1:
+ sample_step = 0.00390625; // 1.0 / 256.0
+ break;
+ case 2:
+ sample_step = 0.001953125; // 1.0 / 512.0
+ break;
+ default:
+ sample_step = 0.0078125;
+ break;
+ }
+ shaders_header += "#define SAMPLE_STEP ";
+ shaders_header += ftos(sample_step);
+ shaders_header += "\n";
+
+ if (g_settings->getBool("enable_bumpmapping"))
+ shaders_header += "#define ENABLE_BUMPMAPPING\n";
+
+ if (g_settings->getBool("enable_parallax_occlusion")){
+ int mode = g_settings->getFloat("parallax_occlusion_mode");
+ float scale = g_settings->getFloat("parallax_occlusion_scale");
+ float bias = g_settings->getFloat("parallax_occlusion_bias");
+ int iterations = g_settings->getFloat("parallax_occlusion_iterations");
+ shaders_header += "#define ENABLE_PARALLAX_OCCLUSION\n";
+ shaders_header += "#define PARALLAX_OCCLUSION_MODE ";
+ shaders_header += itos(mode);
+ shaders_header += "\n";
+ shaders_header += "#define PARALLAX_OCCLUSION_SCALE ";
+ shaders_header += ftos(scale);
+ shaders_header += "\n";
+ shaders_header += "#define PARALLAX_OCCLUSION_BIAS ";
+ shaders_header += ftos(bias);
+ shaders_header += "\n";
+ shaders_header += "#define PARALLAX_OCCLUSION_ITERATIONS ";
+ shaders_header += itos(iterations);
+ shaders_header += "\n";
+ }
+
+ shaders_header += "#define USE_NORMALMAPS ";
+ if (g_settings->getBool("enable_bumpmapping") || g_settings->getBool("enable_parallax_occlusion"))
+ shaders_header += "1\n";
+ else
+ shaders_header += "0\n";
+
+ if (g_settings->getBool("enable_waving_water")){
+ shaders_header += "#define ENABLE_WAVING_WATER 1\n";
+ shaders_header += "#define WATER_WAVE_HEIGHT ";
+ shaders_header += ftos(g_settings->getFloat("water_wave_height"));
+ shaders_header += "\n";
+ shaders_header += "#define WATER_WAVE_LENGTH ";
+ shaders_header += ftos(g_settings->getFloat("water_wave_length"));
+ shaders_header += "\n";
+ shaders_header += "#define WATER_WAVE_SPEED ";
+ shaders_header += ftos(g_settings->getFloat("water_wave_speed"));
+ shaders_header += "\n";
+ } else{
+ shaders_header += "#define ENABLE_WAVING_WATER 0\n";
+ }
+
+ shaders_header += "#define ENABLE_WAVING_LEAVES ";
+ if (g_settings->getBool("enable_waving_leaves"))
+ shaders_header += "1\n";
+ else
+ shaders_header += "0\n";
+
+ shaders_header += "#define ENABLE_WAVING_PLANTS ";
+ if (g_settings->getBool("enable_waving_plants"))
+ shaders_header += "1\n";
+ else
+ shaders_header += "0\n";
+
+ if (g_settings->getBool("tone_mapping"))
+ shaders_header += "#define ENABLE_TONE_MAPPING\n";
+
+ shaders_header += "#define FOG_START ";
+ shaders_header += ftos(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f));
+ shaders_header += "\n";
+
+ // Call addHighLevelShaderMaterial() or addShaderMaterial()
+ const c8* vertex_program_ptr = 0;
+ const c8* pixel_program_ptr = 0;
+ const c8* geometry_program_ptr = 0;
+ if (!vertex_program.empty()) {
+ vertex_program = shaders_header + vertex_program;
+ vertex_program_ptr = vertex_program.c_str();
+ }
+ if (!pixel_program.empty()) {
+ pixel_program = shaders_header + pixel_program;
+ pixel_program_ptr = pixel_program.c_str();
+ }
+ if (!geometry_program.empty()) {
+ geometry_program = shaders_header + geometry_program;
+ geometry_program_ptr = geometry_program.c_str();
+ }
+ ShaderCallback *cb = new ShaderCallback(setter_factories);
+ s32 shadermat = -1;
+ if(is_highlevel){
+ infostream<<"Compiling high level shaders for "<<name<<std::endl;
+ shadermat = gpu->addHighLevelShaderMaterial(
+ vertex_program_ptr, // Vertex shader program
+ "vertexMain", // Vertex shader entry point
+ video::EVST_VS_1_1, // Vertex shader version
+ pixel_program_ptr, // Pixel shader program
+ "pixelMain", // Pixel shader entry point
+ video::EPST_PS_1_2, // Pixel shader version
+ geometry_program_ptr, // Geometry shader program
+ "geometryMain", // Geometry shader entry point
+ video::EGST_GS_4_0, // Geometry shader version
+ scene::EPT_TRIANGLES, // Geometry shader input
+ scene::EPT_TRIANGLE_STRIP, // Geometry shader output
+ 0, // Support maximum number of vertices
+ cb, // Set-constant callback
+ shaderinfo.base_material, // Base material
+ 1 // Userdata passed to callback
+ );
+ if(shadermat == -1){
+ errorstream<<"generate_shader(): "
+ "failed to generate \""<<name<<"\", "
+ "addHighLevelShaderMaterial failed."
+ <<std::endl;
+ dumpShaderProgram(warningstream, "Vertex", vertex_program);
+ dumpShaderProgram(warningstream, "Pixel", pixel_program);
+ dumpShaderProgram(warningstream, "Geometry", geometry_program);
+ delete cb;
+ return shaderinfo;
+ }
+ }
+ else{
+ infostream<<"Compiling assembly shaders for "<<name<<std::endl;
+ shadermat = gpu->addShaderMaterial(
+ vertex_program_ptr, // Vertex shader program
+ pixel_program_ptr, // Pixel shader program
+ cb, // Set-constant callback
+ shaderinfo.base_material, // Base material
+ 0 // Userdata passed to callback
+ );
+
+ if(shadermat == -1){
+ errorstream<<"generate_shader(): "
+ "failed to generate \""<<name<<"\", "
+ "addShaderMaterial failed."
+ <<std::endl;
+ dumpShaderProgram(warningstream, "Vertex", vertex_program);
+ dumpShaderProgram(warningstream,"Pixel", pixel_program);
+ delete cb;
+ return shaderinfo;
+ }
+ }
+ callbacks.push_back(cb);
+
+ // HACK, TODO: investigate this better
+ // Grab the material renderer once more so minetest doesn't crash on exit
+ driver->getMaterialRenderer(shadermat)->grab();
+
+ // Apply the newly created material type
+ shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat;
+ return shaderinfo;
+}
+
+void load_shaders(const std::string &name, SourceShaderCache *sourcecache,
+ video::E_DRIVER_TYPE drivertype, bool enable_shaders,
+ std::string &vertex_program, std::string &pixel_program,
+ std::string &geometry_program, bool &is_highlevel)
+{
+ vertex_program = "";
+ pixel_program = "";
+ geometry_program = "";
+ is_highlevel = false;
+
+ if(enable_shaders){
+ // Look for high level shaders
+ if(drivertype == video::EDT_DIRECT3D9){
+ // Direct3D 9: HLSL
+ // (All shaders in one file)
+ vertex_program = sourcecache->getOrLoad(name, "d3d9.hlsl");
+ pixel_program = vertex_program;
+ geometry_program = vertex_program;
+ }
+ else if(drivertype == video::EDT_OPENGL){
+ // OpenGL: GLSL
+ vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.glsl");
+ pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.glsl");
+ geometry_program = sourcecache->getOrLoad(name, "opengl_geometry.glsl");
+ }
+ if (!vertex_program.empty() || !pixel_program.empty() || !geometry_program.empty()){
+ is_highlevel = true;
+ return;
+ }
+ }
+
+}
+
+void dumpShaderProgram(std::ostream &output_stream,
+ const std::string &program_type, const std::string &program)
+{
+ output_stream << program_type << " shader program:" << std::endl <<
+ "----------------------------------" << std::endl;
+ size_t pos = 0;
+ size_t prev = 0;
+ s16 line = 1;
+ while ((pos = program.find('\n', prev)) != std::string::npos) {
+ output_stream << line++ << ": "<< program.substr(prev, pos - prev) <<
+ std::endl;
+ prev = pos + 1;
+ }
+ output_stream << line << ": " << program.substr(prev) << std::endl <<
+ "End of " << program_type << " shader program." << std::endl <<
+ " " << std::endl;
+}