3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 Copyright (C) 2013 Kahrl <kahrl@gmx.net>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "irrlichttypes_extrabloated.h"
28 #include "util/container.h"
29 #include "util/thread.h"
31 #include <ICameraSceneNode.h>
32 #include <IGPUProgrammingServices.h>
33 #include <IMaterialRenderer.h>
34 #include <IMaterialRendererServices.h>
35 #include <IShaderConstantSetCallBack.h>
36 #include "client/renderingengine.h"
37 #include "EShaderTypes.h"
40 #include "client/tile.h"
44 #ifdef _IRR_COMPILE_WITH_OGLES1_
47 #include <GLES2/gl2.h>
53 #define GL_SILENCE_DEPRECATION
54 #include <OpenGL/gl.h>
59 A cache from shader name to shader path
61 MutexedMap<std::string, std::string> g_shadername_to_path_cache;
64 Gets the path to a shader by first checking if the file
65 name_of_shader/filename
66 exists in shader_path and if not, using the data path.
68 If not found, returns "".
70 Utilizes a thread-safe cache.
72 std::string getShaderPath(const std::string &name_of_shader,
73 const std::string &filename)
75 std::string combined = name_of_shader + DIR_DELIM + filename;
80 bool incache = g_shadername_to_path_cache.get(combined, &fullpath);
85 Check from shader_path
87 std::string shader_path = g_settings->get("shader_path");
88 if (!shader_path.empty()) {
89 std::string testpath = shader_path + DIR_DELIM + combined;
90 if(fs::PathExists(testpath))
95 Check from default data directory
97 if (fullpath.empty()) {
98 std::string rel_path = std::string("client") + DIR_DELIM
99 + "shaders" + DIR_DELIM
100 + name_of_shader + DIR_DELIM
102 std::string testpath = porting::path_share + DIR_DELIM + rel_path;
103 if(fs::PathExists(testpath))
107 // Add to cache (also an empty result is cached)
108 g_shadername_to_path_cache.set(combined, fullpath);
115 SourceShaderCache: A cache used for storing source shaders.
118 class SourceShaderCache
121 void insert(const std::string &name_of_shader, const std::string &filename,
122 const std::string &program, bool prefer_local)
124 std::string combined = name_of_shader + DIR_DELIM + filename;
125 // Try to use local shader instead if asked to
127 std::string path = getShaderPath(name_of_shader, filename);
129 std::string p = readFile(path);
131 m_programs[combined] = p;
136 m_programs[combined] = program;
139 std::string get(const std::string &name_of_shader,
140 const std::string &filename)
142 std::string combined = name_of_shader + DIR_DELIM + filename;
143 StringMap::iterator n = m_programs.find(combined);
144 if (n != m_programs.end())
149 // Primarily fetches from cache, secondarily tries to read from filesystem
150 std::string getOrLoad(const std::string &name_of_shader,
151 const std::string &filename)
153 std::string combined = name_of_shader + DIR_DELIM + filename;
154 StringMap::iterator n = m_programs.find(combined);
155 if (n != m_programs.end())
157 std::string path = getShaderPath(name_of_shader, filename);
159 infostream << "SourceShaderCache::getOrLoad(): No path found for \""
160 << combined << "\"" << std::endl;
163 infostream << "SourceShaderCache::getOrLoad(): Loading path \""
164 << path << "\"" << std::endl;
165 std::string p = readFile(path);
167 m_programs[combined] = p;
173 StringMap m_programs;
175 std::string readFile(const std::string &path)
177 std::ifstream is(path.c_str(), std::ios::binary);
180 std::ostringstream tmp_os;
181 tmp_os << is.rdbuf();
188 ShaderCallback: Sets constants that can be used in shaders
191 class ShaderCallback : public video::IShaderConstantSetCallBack
193 std::vector<std::unique_ptr<IShaderConstantSetter>> m_setters;
196 template <typename Factories>
197 ShaderCallback(const Factories &factories)
199 for (auto &&factory : factories)
200 m_setters.push_back(std::unique_ptr<IShaderConstantSetter>(factory->create()));
203 virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) override
205 video::IVideoDriver *driver = services->getVideoDriver();
206 sanity_check(driver != NULL);
208 for (auto &&setter : m_setters)
209 setter->onSetConstants(services);
212 virtual void OnSetMaterial(const video::SMaterial& material) override
214 for (auto &&setter : m_setters)
215 setter->onSetMaterial(material);
221 MainShaderConstantSetter: Set basic constants required for almost everything
224 class MainShaderConstantSetter : public IShaderConstantSetter
226 CachedVertexShaderSetting<float, 16> m_world_view_proj;
227 CachedVertexShaderSetting<float, 16> m_world;
230 CachedVertexShaderSetting<float, 16> m_world_view;
232 CachedVertexShaderSetting<float, 16> m_texture;
234 CachedVertexShaderSetting<float, 9> m_normal;
238 MainShaderConstantSetter() :
239 m_world_view_proj("mWorldViewProj")
242 , m_world_view("mWorldView")
243 , m_texture("mTexture")
244 , m_normal("mNormal")
247 ~MainShaderConstantSetter() = default;
249 virtual void onSetConstants(video::IMaterialRendererServices *services) override
251 video::IVideoDriver *driver = services->getVideoDriver();
252 sanity_check(driver);
255 core::matrix4 world = driver->getTransform(video::ETS_WORLD);
256 m_world.set(*reinterpret_cast<float(*)[16]>(world.pointer()), services);
259 core::matrix4 worldView;
260 worldView = driver->getTransform(video::ETS_VIEW);
263 core::matrix4 worldViewProj;
264 worldViewProj = driver->getTransform(video::ETS_PROJECTION);
265 worldViewProj *= worldView;
266 m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services);
269 core::matrix4 texture = driver->getTransform(video::ETS_TEXTURE_0);
270 m_world_view.set(*reinterpret_cast<float(*)[16]>(worldView.pointer()), services);
271 m_texture.set(*reinterpret_cast<float(*)[16]>(texture.pointer()), services);
273 core::matrix4 normal;
274 worldView.getTransposed(normal);
275 sanity_check(normal.makeInverse());
277 normal[0], normal[1], normal[2],
278 normal[4], normal[5], normal[6],
279 normal[8], normal[9], normal[10],
281 m_normal.set(m, services);
287 class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory
290 virtual IShaderConstantSetter* create()
291 { return new MainShaderConstantSetter(); }
299 class ShaderSource : public IWritableShaderSource
305 - If shader material specified by name is found from cache,
306 return the cached id.
307 - Otherwise generate the shader material, add to cache and return id.
309 The id 0 points to a null shader. Its material is EMT_SOLID.
311 u32 getShaderIdDirect(const std::string &name,
312 MaterialType material_type, NodeDrawType drawtype) override;
315 If shader specified by the name pointed by the id doesn't
316 exist, create it, then return id.
318 Can be called from any thread. If called from some other thread
319 and not found in cache, the call is queued to the main thread
323 u32 getShader(const std::string &name,
324 MaterialType material_type, NodeDrawType drawtype) override;
326 ShaderInfo getShaderInfo(u32 id) override;
328 // Processes queued shader requests from other threads.
329 // Shall be called from the main thread.
330 void processQueue() override;
332 // Insert a shader program into the cache without touching the
333 // filesystem. Shall be called from the main thread.
334 void insertSourceShader(const std::string &name_of_shader,
335 const std::string &filename, const std::string &program) override;
337 // Rebuild shaders from the current set of source shaders
338 // Shall be called from the main thread.
339 void rebuildShaders() override;
341 void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) override
343 m_setter_factories.push_back(std::unique_ptr<IShaderConstantSetterFactory>(setter));
348 // The id of the thread that is allowed to use irrlicht directly
349 std::thread::id m_main_thread;
351 // Cache of source shaders
352 // This should be only accessed from the main thread
353 SourceShaderCache m_sourcecache;
355 // A shader id is index in this array.
356 // The first position contains a dummy shader.
357 std::vector<ShaderInfo> m_shaderinfo_cache;
358 // The former container is behind this mutex
359 std::mutex m_shaderinfo_cache_mutex;
361 // Queued shader fetches (to be processed by the main thread)
362 RequestQueue<std::string, u32, u8, u8> m_get_shader_queue;
364 // Global constant setter factories
365 std::vector<std::unique_ptr<IShaderConstantSetterFactory>> m_setter_factories;
367 // Generate shader given the shader name.
368 ShaderInfo generateShader(const std::string &name,
369 MaterialType material_type, NodeDrawType drawtype);
372 IWritableShaderSource *createShaderSource()
374 return new ShaderSource();
377 ShaderSource::ShaderSource()
379 m_main_thread = std::this_thread::get_id();
381 // Add a dummy ShaderInfo as the first index, named ""
382 m_shaderinfo_cache.emplace_back();
384 // Add main global constant setter
385 addShaderConstantSetterFactory(new MainShaderConstantSetterFactory());
388 u32 ShaderSource::getShader(const std::string &name,
389 MaterialType material_type, NodeDrawType drawtype)
395 if (std::this_thread::get_id() == m_main_thread) {
396 return getShaderIdDirect(name, material_type, drawtype);
399 /*errorstream<<"getShader(): Queued: name=\""<<name<<"\""<<std::endl;*/
401 // We're gonna ask the result to be put into here
403 static ResultQueue<std::string, u32, u8, u8> result_queue;
405 // Throw a request in
406 m_get_shader_queue.add(name, 0, 0, &result_queue);
408 /* infostream<<"Waiting for shader from main thread, name=\""
409 <<name<<"\""<<std::endl;*/
412 GetResult<std::string, u32, u8, u8>
413 result = result_queue.pop_frontNoEx();
415 if (result.key == name) {
419 errorstream << "Got shader with invalid name: " << result.key << std::endl;
422 infostream << "getShader(): Failed" << std::endl;
428 This method generates all the shaders
430 u32 ShaderSource::getShaderIdDirect(const std::string &name,
431 MaterialType material_type, NodeDrawType drawtype)
433 //infostream<<"getShaderIdDirect(): name=\""<<name<<"\""<<std::endl;
435 // Empty name means shader 0
437 infostream<<"getShaderIdDirect(): name is empty"<<std::endl;
441 // Check if already have such instance
442 for(u32 i=0; i<m_shaderinfo_cache.size(); i++){
443 ShaderInfo *info = &m_shaderinfo_cache[i];
444 if(info->name == name && info->material_type == material_type &&
445 info->drawtype == drawtype)
450 Calling only allowed from main thread
452 if (std::this_thread::get_id() != m_main_thread) {
453 errorstream<<"ShaderSource::getShaderIdDirect() "
454 "called not from main thread"<<std::endl;
458 ShaderInfo info = generateShader(name, material_type, drawtype);
461 Add shader to caches (add dummy shaders too)
464 MutexAutoLock lock(m_shaderinfo_cache_mutex);
466 u32 id = m_shaderinfo_cache.size();
467 m_shaderinfo_cache.push_back(info);
469 infostream<<"getShaderIdDirect(): "
470 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;
476 ShaderInfo ShaderSource::getShaderInfo(u32 id)
478 MutexAutoLock lock(m_shaderinfo_cache_mutex);
480 if(id >= m_shaderinfo_cache.size())
483 return m_shaderinfo_cache[id];
486 void ShaderSource::processQueue()
492 void ShaderSource::insertSourceShader(const std::string &name_of_shader,
493 const std::string &filename, const std::string &program)
495 /*infostream<<"ShaderSource::insertSourceShader(): "
496 "name_of_shader=\""<<name_of_shader<<"\", "
497 "filename=\""<<filename<<"\""<<std::endl;*/
499 sanity_check(std::this_thread::get_id() == m_main_thread);
501 m_sourcecache.insert(name_of_shader, filename, program, true);
504 void ShaderSource::rebuildShaders()
506 MutexAutoLock lock(m_shaderinfo_cache_mutex);
508 /*// Oh well... just clear everything, they'll load sometime.
509 m_shaderinfo_cache.clear();
510 m_name_to_id.clear();*/
513 FIXME: Old shader materials can't be deleted in Irrlicht,
515 (This would be nice to do in the destructor too)
519 for (ShaderInfo &i : m_shaderinfo_cache) {
520 ShaderInfo *info = &i;
521 if (!info->name.empty()) {
522 *info = generateShader(info->name, info->material_type, info->drawtype);
528 ShaderInfo ShaderSource::generateShader(const std::string &name,
529 MaterialType material_type, NodeDrawType drawtype)
531 ShaderInfo shaderinfo;
532 shaderinfo.name = name;
533 shaderinfo.material_type = material_type;
534 shaderinfo.drawtype = drawtype;
535 switch (material_type) {
536 case TILE_MATERIAL_OPAQUE:
537 case TILE_MATERIAL_LIQUID_OPAQUE:
538 case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
539 shaderinfo.base_material = video::EMT_SOLID;
541 case TILE_MATERIAL_ALPHA:
542 case TILE_MATERIAL_PLAIN_ALPHA:
543 case TILE_MATERIAL_LIQUID_TRANSPARENT:
544 case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
545 shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
547 case TILE_MATERIAL_BASIC:
548 case TILE_MATERIAL_PLAIN:
549 case TILE_MATERIAL_WAVING_LEAVES:
550 case TILE_MATERIAL_WAVING_PLANTS:
551 case TILE_MATERIAL_WAVING_LIQUID_BASIC:
552 shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
555 shaderinfo.material = shaderinfo.base_material;
557 bool enable_shaders = g_settings->getBool("enable_shaders");
561 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
562 if (!driver->queryFeature(video::EVDF_ARB_GLSL)) {
563 errorstream << "Shaders are enabled but GLSL is not supported by the driver\n";
566 video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
568 // Create shaders header
569 bool use_gles = false;
571 use_gles = driver->getDriverType() == video::EDT_OGLES2;
573 std::stringstream shaders_header;
576 << std::showpoint // for GLSL ES
578 std::string vertex_header, fragment_header, geometry_header;
580 shaders_header << R"(
584 uniform highp mat4 mWorldView;
585 uniform highp mat4 mWorldViewProj;
586 uniform mediump mat4 mTexture;
587 uniform mediump mat3 mNormal;
589 attribute highp vec4 inVertexPosition;
590 attribute lowp vec4 inVertexColor;
591 attribute mediump vec4 inTexCoord0;
592 attribute mediump vec3 inVertexNormal;
593 attribute mediump vec4 inVertexTangent;
594 attribute mediump vec4 inVertexBinormal;
596 fragment_header = R"(
597 precision mediump float;
600 shaders_header << R"(
607 #define mWorldView gl_ModelViewMatrix
608 #define mWorldViewProj gl_ModelViewProjectionMatrix
609 #define mTexture (gl_TextureMatrix[0])
610 #define mNormal gl_NormalMatrix
612 #define inVertexPosition gl_Vertex
613 #define inVertexColor gl_Color
614 #define inTexCoord0 gl_MultiTexCoord0
615 #define inVertexNormal gl_Normal
616 #define inVertexTangent gl_MultiTexCoord1
617 #define inVertexBinormal gl_MultiTexCoord2
621 bool use_discard = use_gles;
623 // For renderers that should use discard instead of GL_ALPHA_TEST
624 const char* gl_renderer = (const char*)glGetString(GL_RENDERER);
625 if (strstr(gl_renderer, "GC7000"))
628 if (use_discard && shaderinfo.base_material != video::EMT_SOLID)
629 shaders_header << "#define USE_DISCARD 1\n";
631 #define PROVIDE(constant) shaders_header << "#define " #constant " " << (int)constant << "\n"
634 PROVIDE(NDT_AIRLIKE);
636 PROVIDE(NDT_FLOWINGLIQUID);
637 PROVIDE(NDT_GLASSLIKE);
638 PROVIDE(NDT_ALLFACES);
639 PROVIDE(NDT_ALLFACES_OPTIONAL);
640 PROVIDE(NDT_TORCHLIKE);
641 PROVIDE(NDT_SIGNLIKE);
642 PROVIDE(NDT_PLANTLIKE);
643 PROVIDE(NDT_FENCELIKE);
644 PROVIDE(NDT_RAILLIKE);
645 PROVIDE(NDT_NODEBOX);
646 PROVIDE(NDT_GLASSLIKE_FRAMED);
647 PROVIDE(NDT_FIRELIKE);
648 PROVIDE(NDT_GLASSLIKE_FRAMED_OPTIONAL);
649 PROVIDE(NDT_PLANTLIKE_ROOTED);
651 PROVIDE(TILE_MATERIAL_BASIC);
652 PROVIDE(TILE_MATERIAL_ALPHA);
653 PROVIDE(TILE_MATERIAL_LIQUID_TRANSPARENT);
654 PROVIDE(TILE_MATERIAL_LIQUID_OPAQUE);
655 PROVIDE(TILE_MATERIAL_WAVING_LEAVES);
656 PROVIDE(TILE_MATERIAL_WAVING_PLANTS);
657 PROVIDE(TILE_MATERIAL_OPAQUE);
658 PROVIDE(TILE_MATERIAL_WAVING_LIQUID_BASIC);
659 PROVIDE(TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT);
660 PROVIDE(TILE_MATERIAL_WAVING_LIQUID_OPAQUE);
661 PROVIDE(TILE_MATERIAL_PLAIN);
662 PROVIDE(TILE_MATERIAL_PLAIN_ALPHA);
666 shaders_header << "#define MATERIAL_TYPE " << (int)material_type << "\n";
667 shaders_header << "#define DRAW_TYPE " << (int)drawtype << "\n";
669 bool enable_waving_water = g_settings->getBool("enable_waving_water");
670 shaders_header << "#define ENABLE_WAVING_WATER " << enable_waving_water << "\n";
671 if (enable_waving_water) {
672 shaders_header << "#define WATER_WAVE_HEIGHT " << g_settings->getFloat("water_wave_height") << "\n";
673 shaders_header << "#define WATER_WAVE_LENGTH " << g_settings->getFloat("water_wave_length") << "\n";
674 shaders_header << "#define WATER_WAVE_SPEED " << g_settings->getFloat("water_wave_speed") << "\n";
677 shaders_header << "#define ENABLE_WAVING_LEAVES " << g_settings->getBool("enable_waving_leaves") << "\n";
678 shaders_header << "#define ENABLE_WAVING_PLANTS " << g_settings->getBool("enable_waving_plants") << "\n";
679 shaders_header << "#define ENABLE_TONE_MAPPING " << g_settings->getBool("tone_mapping") << "\n";
681 shaders_header << "#define FOG_START " << core::clamp(g_settings->getFloat("fog_start"), 0.0f, 0.99f) << "\n";
683 std::string common_header = shaders_header.str();
685 std::string vertex_shader = m_sourcecache.getOrLoad(name, "opengl_vertex.glsl");
686 std::string fragment_shader = m_sourcecache.getOrLoad(name, "opengl_fragment.glsl");
687 std::string geometry_shader = m_sourcecache.getOrLoad(name, "opengl_geometry.glsl");
689 vertex_shader = common_header + vertex_header + vertex_shader;
690 fragment_shader = common_header + fragment_header + fragment_shader;
691 const char *geometry_shader_ptr = nullptr; // optional
692 if (!geometry_shader.empty()) {
693 geometry_shader = common_header + geometry_header + geometry_shader;
694 geometry_shader_ptr = geometry_shader.c_str();
697 irr_ptr<ShaderCallback> cb{new ShaderCallback(m_setter_factories)};
698 infostream<<"Compiling high level shaders for "<<name<<std::endl;
699 s32 shadermat = gpu->addHighLevelShaderMaterial(
700 vertex_shader.c_str(), nullptr, video::EVST_VS_1_1,
701 fragment_shader.c_str(), nullptr, video::EPST_PS_1_1,
702 geometry_shader_ptr, nullptr, video::EGST_GS_4_0, scene::EPT_TRIANGLES, scene::EPT_TRIANGLES, 0,
703 cb.get(), shaderinfo.base_material, 1);
704 if (shadermat == -1) {
705 errorstream<<"generate_shader(): "
706 "failed to generate \""<<name<<"\", "
707 "addHighLevelShaderMaterial failed."
709 dumpShaderProgram(warningstream, "Vertex", vertex_shader);
710 dumpShaderProgram(warningstream, "Fragment", fragment_shader);
711 dumpShaderProgram(warningstream, "Geometry", geometry_shader);
715 // Apply the newly created material type
716 shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat;
720 void dumpShaderProgram(std::ostream &output_stream,
721 const std::string &program_type, const std::string &program)
723 output_stream << program_type << " shader program:" << std::endl <<
724 "----------------------------------" << std::endl;
728 while ((pos = program.find('\n', prev)) != std::string::npos) {
729 output_stream << line++ << ": "<< program.substr(prev, pos - prev) <<
733 output_stream << line << ": " << program.substr(prev) << std::endl <<
734 "End of " << program_type << " shader program." << std::endl <<