/* Minetest Copyright (C) 2013 celeron55, Perttu Ahola Copyright (C) 2013 Kahrl 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 "shader.h" #include "irrlichttypes_extrabloated.h" #include "debug.h" #include "main.h" // for g_settings #include "filesys.h" #include "util/container.h" #include "util/thread.h" #include "settings.h" #include #include #include #include #include #include #include "EShaderTypes.h" #include "log.h" #include "gamedef.h" #include "strfnd.h" // trim() /* A cache from shader name to shader path */ MutexedMap 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 != "") { std::string testpath = shader_path + DIR_DELIM + combined; if(fs::PathExists(testpath)) fullpath = testpath; } /* Check from default data directory */ if(fullpath == "") { 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 != ""){ std::string p = readFile(path); if(p != ""){ 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; std::map::iterator n; 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; std::map::iterator n; n = m_programs.find(combined); if(n != m_programs.end()) return n->second; std::string path = getShaderPath(name_of_shader, filename); if(path == ""){ infostream<<"SourceShaderCache::getOrLoad(): No path found for \"" < 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 IShaderConstantSetterRegistry { public: virtual ~IShaderConstantSetterRegistry(){}; virtual void onSetConstants(video::IMaterialRendererServices *services, bool is_highlevel, const std::string &name) = 0; }; class ShaderCallback : public video::IShaderConstantSetCallBack { IShaderConstantSetterRegistry *m_scsr; std::string m_name; public: ShaderCallback(IShaderConstantSetterRegistry *scsr, const std::string &name): m_scsr(scsr), m_name(name) {} ~ShaderCallback() {} virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) { video::IVideoDriver *driver = services->getVideoDriver(); assert(driver); bool is_highlevel = userData; m_scsr->onSetConstants(services, is_highlevel, m_name); } }; /* MainShaderConstantSetter: Set basic constants required for almost everything */ class MainShaderConstantSetter : public IShaderConstantSetter { public: MainShaderConstantSetter(IrrlichtDevice *device): m_device(device) {} ~MainShaderConstantSetter() {} virtual void onSetConstants(video::IMaterialRendererServices *services, bool is_highlevel) { video::IVideoDriver *driver = services->getVideoDriver(); assert(driver); // set inverted world matrix core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD); invWorld.makeInverse(); if(is_highlevel) services->setVertexShaderConstant("mInvWorld", invWorld.pointer(), 16); else services->setVertexShaderConstant(invWorld.pointer(), 0, 4); // 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) services->setVertexShaderConstant("mWorldViewProj", worldViewProj.pointer(), 16); else services->setVertexShaderConstant(worldViewProj.pointer(), 4, 4); // set transposed world matrix core::matrix4 world = driver->getTransform(video::ETS_WORLD); world = world.getTransposed(); if(is_highlevel) services->setVertexShaderConstant("mTransWorld", world.pointer(), 16); else services->setVertexShaderConstant(world.pointer(), 8, 4); } private: IrrlichtDevice *m_device; }; /* ShaderSource */ class ShaderSource : public IWritableShaderSource, public IShaderConstantSetterRegistry { public: ShaderSource(IrrlichtDevice *device); ~ShaderSource(); /* Gets a shader material id from cache or - if main thread, from getShaderIdDirect - if other thread, adds to request queue and waits for main thread */ u32 getShaderId(const std::string &name); /* - 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); // Finds out the name of a cached shader. std::string getShaderName(u32 id); /* If shader specified by the name pointed by the id doesn't exist, create it, then return the cached shader. 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. */ ShaderInfo getShader(u32 id); ShaderInfo getShader(const std::string &name) { return getShader(getShaderId(name)); } // 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 addGlobalConstantSetter(IShaderConstantSetter *setter) { m_global_setters.push_back(setter); } void onSetConstants(video::IMaterialRendererServices *services, bool is_highlevel, const std::string &name); private: // The id of the thread that is allowed to use irrlicht directly threadid_t m_main_thread; // The irrlicht device IrrlichtDevice *m_device; // The set-constants callback ShaderCallback *m_shader_callback; // 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 m_shaderinfo_cache; // Maps a shader name to an index in the former. std::map m_name_to_id; // The two former containers are behind this mutex JMutex m_shaderinfo_cache_mutex; // Queued shader fetches (to be processed by the main thread) RequestQueue m_get_shader_queue; // Global constant setters // TODO: Delete these in the destructor std::vector m_global_setters; }; IWritableShaderSource* createShaderSource(IrrlichtDevice *device) { return new ShaderSource(device); } /* Generate shader given the shader name. */ ShaderInfo generate_shader(std::string name, IrrlichtDevice *device, video::IShaderConstantSetCallBack *callback, SourceShaderCache *sourcecache); /* Load shader programs */ void load_shaders(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(IrrlichtDevice *device): m_device(device) { assert(m_device); m_shader_callback = new ShaderCallback(this, "default"); m_shaderinfo_cache_mutex.Init(); m_main_thread = get_current_thread_id(); // Add a dummy ShaderInfo as the first index, named "" m_shaderinfo_cache.push_back(ShaderInfo()); m_name_to_id[""] = 0; // Add main global constant setter addGlobalConstantSetter(new MainShaderConstantSetter(device)); } ShaderSource::~ShaderSource() { //m_shader_callback->drop(); for (std::vector::iterator iter = m_global_setters.begin(); iter != m_global_setters.end(); iter++) { delete *iter; } m_global_setters.clear(); } u32 ShaderSource::getShaderId(const std::string &name) { //infostream<<"getShaderId(): \""<::iterator n; n = m_name_to_id.find(name); if(n != m_name_to_id.end()) return n->second; } /* Get shader */ if(get_current_thread_id() == m_main_thread){ return getShaderIdDirect(name); } else { infostream<<"getShaderId(): Queued: name=\""< result_queue; // Throw a request in m_get_shader_queue.add(name, 0, 0, &result_queue); infostream<<"Waiting for shader from main thread, name=\"" < result = result_queue.pop_front(1000); // Check that at least something worked OK assert(result.key == name); return result.item; } catch(ItemNotFoundException &e){ infostream<<"Waiting for shader timed out."<::iterator n; n = m_name_to_id.find(name); if(n != m_name_to_id.end()){ /*infostream<<"getShaderIdDirect(): \""<second; } } /*infostream<<"getShaderIdDirect(): \""<= m_shaderinfo_cache.size()){ errorstream<<"ShaderSource::getShaderName(): id="<= m_shaderinfo_cache.size()=" <= m_shaderinfo_cache.size()) return ShaderInfo(); return m_shaderinfo_cache[id]; } void ShaderSource::processQueue() { /* Fetch shaders */ if(!m_get_shader_queue.empty()){ GetRequest request = m_get_shader_queue.pop(); /*infostream<<"ShaderSource::processQueue(): " <<"got shader request with " <<"name=\""< result; result.key = request.key; result.callers = request.callers; result.item = getShaderIdDirect(request.key); request.dest->push_back(result); } } void ShaderSource::insertSourceShader(const std::string &name_of_shader, const std::string &filename, const std::string &program) { /*infostream<<"ShaderSource::insertSourceShader(): " "name_of_shader=\""<name != ""){ *info = generate_shader(info->name, m_device, m_shader_callback, &m_sourcecache); } } } void ShaderSource::onSetConstants(video::IMaterialRendererServices *services, bool is_highlevel, const std::string &name) { for(u32 i=0; ionSetConstants(services, is_highlevel); } } ShaderInfo generate_shader(std::string name, IrrlichtDevice *device, video::IShaderConstantSetCallBack *callback, SourceShaderCache *sourcecache) { /*infostream<<"generate_shader(): " "\""<getOrLoad(name, "base.txt")); for(s32 i = 0; video::sBuiltInMaterialTypeNames[i] != 0; i++){ if(video::sBuiltInMaterialTypeNames[i] == base_material_name){ shaderinfo.material = (video::E_MATERIAL_TYPE) i; break; } } // 0 = off, 1 = assembly shaders only, 2 = highlevel or assembly bool enable_shaders = g_settings->getBool("enable_shaders"); if(!enable_shaders) return shaderinfo; video::IVideoDriver* driver = device->getVideoDriver(); assert(driver); video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices(); if(!gpu){ errorstream<<"generate_shader(): " "failed to generate \""<getDriverType(), enable_shaders, vertex_program, pixel_program, geometry_program, is_highlevel); // Check hardware/driver support if(vertex_program != "" && !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." <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." <queryFeature(video::EVDF_GEOMETRY_SHADER)){ infostream<<"generate_shader(): geometry shaders disabled " "because of missing driver/hardware support." <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_1, // 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 callback, // Set-constant callback shaderinfo.material, // Base material 1 // Userdata passed to callback ); if(shadermat == -1){ errorstream<<"generate_shader(): " "failed to generate \""<addShaderMaterial( vertex_program_ptr, // Vertex shader program pixel_program_ptr, // Pixel shader program callback, // Set-constant callback shaderinfo.material, // Base material 0 // Userdata passed to callback ); if(shadermat == -1){ errorstream<<"generate_shader(): " "failed to generate \""<getMaterialRenderer(shadermat)->grab(); // Apply the newly created material type shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat; return shaderinfo; } void load_shaders(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 != "" || pixel_program != "" || geometry_program != ""){ is_highlevel = true; return; } } if(enable_shaders){ // Look for assembly shaders if(drivertype == video::EDT_DIRECT3D8){ // Direct3D 8 assembly shaders vertex_program = sourcecache->getOrLoad(name, "d3d8_vertex.asm"); pixel_program = sourcecache->getOrLoad(name, "d3d8_pixel.asm"); } else if(drivertype == video::EDT_DIRECT3D9){ // Direct3D 9 assembly shaders vertex_program = sourcecache->getOrLoad(name, "d3d9_vertex.asm"); pixel_program = sourcecache->getOrLoad(name, "d3d9_pixel.asm"); } else if(drivertype == video::EDT_OPENGL){ // OpenGL assembly shaders vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.asm"); pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.asm"); } if(vertex_program != "" || pixel_program != "") return; } }