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 CachedPixelShaderSetting<float, 16> m_shadow_view_proj;
231 CachedPixelShaderSetting<float, 3> m_light_direction;
232 CachedPixelShaderSetting<float> m_texture_res;
233 CachedPixelShaderSetting<float> m_shadow_strength;
234 CachedPixelShaderSetting<float> m_time_of_day;
235 CachedPixelShaderSetting<float> m_shadowfar;
236 CachedPixelShaderSetting<s32> m_shadow_texture;
240 CachedVertexShaderSetting<float, 16> m_world_view;
242 CachedVertexShaderSetting<float, 16> m_texture;
244 CachedVertexShaderSetting<float, 9> m_normal;
248 MainShaderConstantSetter() :
249 m_world_view_proj("mWorldViewProj")
252 , m_world_view("mWorldView")
253 , m_texture("mTexture")
254 , m_normal("mNormal")
256 , m_shadow_view_proj("m_ShadowViewProj")
257 , m_light_direction("v_LightDirection")
258 , m_texture_res("f_textureresolution")
259 , m_shadow_strength("f_shadow_strength")
260 , m_time_of_day("f_timeofday")
261 , m_shadowfar("f_shadowfar")
262 , m_shadow_texture("ShadowMapSampler")
264 ~MainShaderConstantSetter() = default;
266 virtual void onSetConstants(video::IMaterialRendererServices *services) override
268 video::IVideoDriver *driver = services->getVideoDriver();
269 sanity_check(driver);
272 core::matrix4 world = driver->getTransform(video::ETS_WORLD);
273 m_world.set(*reinterpret_cast<float(*)[16]>(world.pointer()), services);
276 core::matrix4 worldView;
277 worldView = driver->getTransform(video::ETS_VIEW);
280 core::matrix4 worldViewProj;
281 worldViewProj = driver->getTransform(video::ETS_PROJECTION);
282 worldViewProj *= worldView;
283 m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services);
286 core::matrix4 texture = driver->getTransform(video::ETS_TEXTURE_0);
287 m_world_view.set(*reinterpret_cast<float(*)[16]>(worldView.pointer()), services);
288 m_texture.set(*reinterpret_cast<float(*)[16]>(texture.pointer()), services);
290 core::matrix4 normal;
291 worldView.getTransposed(normal);
292 sanity_check(normal.makeInverse());
294 normal[0], normal[1], normal[2],
295 normal[4], normal[5], normal[6],
296 normal[8], normal[9], normal[10],
298 m_normal.set(m, services);
301 // Set uniforms for Shadow shader
302 if (ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer()) {
303 const auto &light = shadow->getDirectionalLight();
305 core::matrix4 shadowViewProj = light.getProjectionMatrix();
306 shadowViewProj *= light.getViewMatrix();
307 m_shadow_view_proj.set(shadowViewProj.pointer(), services);
309 float v_LightDirection[3];
310 light.getDirection().getAs3Values(v_LightDirection);
311 m_light_direction.set(v_LightDirection, services);
313 float TextureResolution = light.getMapResolution();
314 m_texture_res.set(&TextureResolution, services);
316 float ShadowStrength = shadow->getShadowStrength();
317 m_shadow_strength.set(&ShadowStrength, services);
319 float timeOfDay = shadow->getTimeOfDay();
320 m_time_of_day.set(&timeOfDay, services);
322 float shadowFar = shadow->getMaxShadowFar();
323 m_shadowfar.set(&shadowFar, services);
325 // I dont like using this hardcoded value. maybe something like
326 // MAX_TEXTURE - 1 or somthing like that??
327 s32 TextureLayerID = 3;
328 m_shadow_texture.set(&TextureLayerID, services);
334 class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory
337 virtual IShaderConstantSetter* create()
338 { return new MainShaderConstantSetter(); }
346 class ShaderSource : public IWritableShaderSource
352 - If shader material specified by name is found from cache,
353 return the cached id.
354 - Otherwise generate the shader material, add to cache and return id.
356 The id 0 points to a null shader. Its material is EMT_SOLID.
358 u32 getShaderIdDirect(const std::string &name,
359 MaterialType material_type, NodeDrawType drawtype) override;
362 If shader specified by the name pointed by the id doesn't
363 exist, create it, then return id.
365 Can be called from any thread. If called from some other thread
366 and not found in cache, the call is queued to the main thread
370 u32 getShader(const std::string &name,
371 MaterialType material_type, NodeDrawType drawtype) override;
373 ShaderInfo getShaderInfo(u32 id) override;
375 // Processes queued shader requests from other threads.
376 // Shall be called from the main thread.
377 void processQueue() override;
379 // Insert a shader program into the cache without touching the
380 // filesystem. Shall be called from the main thread.
381 void insertSourceShader(const std::string &name_of_shader,
382 const std::string &filename, const std::string &program) override;
384 // Rebuild shaders from the current set of source shaders
385 // Shall be called from the main thread.
386 void rebuildShaders() override;
388 void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) override
390 m_setter_factories.push_back(std::unique_ptr<IShaderConstantSetterFactory>(setter));
395 // The id of the thread that is allowed to use irrlicht directly
396 std::thread::id m_main_thread;
398 // Cache of source shaders
399 // This should be only accessed from the main thread
400 SourceShaderCache m_sourcecache;
402 // A shader id is index in this array.
403 // The first position contains a dummy shader.
404 std::vector<ShaderInfo> m_shaderinfo_cache;
405 // The former container is behind this mutex
406 std::mutex m_shaderinfo_cache_mutex;
408 // Queued shader fetches (to be processed by the main thread)
409 RequestQueue<std::string, u32, u8, u8> m_get_shader_queue;
411 // Global constant setter factories
412 std::vector<std::unique_ptr<IShaderConstantSetterFactory>> m_setter_factories;
414 // Generate shader given the shader name.
415 ShaderInfo generateShader(const std::string &name,
416 MaterialType material_type, NodeDrawType drawtype);
419 IWritableShaderSource *createShaderSource()
421 return new ShaderSource();
424 ShaderSource::ShaderSource()
426 m_main_thread = std::this_thread::get_id();
428 // Add a dummy ShaderInfo as the first index, named ""
429 m_shaderinfo_cache.emplace_back();
431 // Add main global constant setter
432 addShaderConstantSetterFactory(new MainShaderConstantSetterFactory());
435 u32 ShaderSource::getShader(const std::string &name,
436 MaterialType material_type, NodeDrawType drawtype)
442 if (std::this_thread::get_id() == m_main_thread) {
443 return getShaderIdDirect(name, material_type, drawtype);
446 /*errorstream<<"getShader(): Queued: name=\""<<name<<"\""<<std::endl;*/
448 // We're gonna ask the result to be put into here
450 static ResultQueue<std::string, u32, u8, u8> result_queue;
452 // Throw a request in
453 m_get_shader_queue.add(name, 0, 0, &result_queue);
455 /* infostream<<"Waiting for shader from main thread, name=\""
456 <<name<<"\""<<std::endl;*/
459 GetResult<std::string, u32, u8, u8>
460 result = result_queue.pop_frontNoEx();
462 if (result.key == name) {
466 errorstream << "Got shader with invalid name: " << result.key << std::endl;
469 infostream << "getShader(): Failed" << std::endl;
475 This method generates all the shaders
477 u32 ShaderSource::getShaderIdDirect(const std::string &name,
478 MaterialType material_type, NodeDrawType drawtype)
480 //infostream<<"getShaderIdDirect(): name=\""<<name<<"\""<<std::endl;
482 // Empty name means shader 0
484 infostream<<"getShaderIdDirect(): name is empty"<<std::endl;
488 // Check if already have such instance
489 for(u32 i=0; i<m_shaderinfo_cache.size(); i++){
490 ShaderInfo *info = &m_shaderinfo_cache[i];
491 if(info->name == name && info->material_type == material_type &&
492 info->drawtype == drawtype)
497 Calling only allowed from main thread
499 if (std::this_thread::get_id() != m_main_thread) {
500 errorstream<<"ShaderSource::getShaderIdDirect() "
501 "called not from main thread"<<std::endl;
505 ShaderInfo info = generateShader(name, material_type, drawtype);
508 Add shader to caches (add dummy shaders too)
511 MutexAutoLock lock(m_shaderinfo_cache_mutex);
513 u32 id = m_shaderinfo_cache.size();
514 m_shaderinfo_cache.push_back(info);
516 infostream<<"getShaderIdDirect(): "
517 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;
523 ShaderInfo ShaderSource::getShaderInfo(u32 id)
525 MutexAutoLock lock(m_shaderinfo_cache_mutex);
527 if(id >= m_shaderinfo_cache.size())
530 return m_shaderinfo_cache[id];
533 void ShaderSource::processQueue()
539 void ShaderSource::insertSourceShader(const std::string &name_of_shader,
540 const std::string &filename, const std::string &program)
542 /*infostream<<"ShaderSource::insertSourceShader(): "
543 "name_of_shader=\""<<name_of_shader<<"\", "
544 "filename=\""<<filename<<"\""<<std::endl;*/
546 sanity_check(std::this_thread::get_id() == m_main_thread);
548 m_sourcecache.insert(name_of_shader, filename, program, true);
551 void ShaderSource::rebuildShaders()
553 MutexAutoLock lock(m_shaderinfo_cache_mutex);
555 /*// Oh well... just clear everything, they'll load sometime.
556 m_shaderinfo_cache.clear();
557 m_name_to_id.clear();*/
560 FIXME: Old shader materials can't be deleted in Irrlicht,
562 (This would be nice to do in the destructor too)
566 for (ShaderInfo &i : m_shaderinfo_cache) {
567 ShaderInfo *info = &i;
568 if (!info->name.empty()) {
569 *info = generateShader(info->name, info->material_type, info->drawtype);
575 ShaderInfo ShaderSource::generateShader(const std::string &name,
576 MaterialType material_type, NodeDrawType drawtype)
578 ShaderInfo shaderinfo;
579 shaderinfo.name = name;
580 shaderinfo.material_type = material_type;
581 shaderinfo.drawtype = drawtype;
582 switch (material_type) {
583 case TILE_MATERIAL_OPAQUE:
584 case TILE_MATERIAL_LIQUID_OPAQUE:
585 case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
586 shaderinfo.base_material = video::EMT_SOLID;
588 case TILE_MATERIAL_ALPHA:
589 case TILE_MATERIAL_PLAIN_ALPHA:
590 case TILE_MATERIAL_LIQUID_TRANSPARENT:
591 case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
592 shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
594 case TILE_MATERIAL_BASIC:
595 case TILE_MATERIAL_PLAIN:
596 case TILE_MATERIAL_WAVING_LEAVES:
597 case TILE_MATERIAL_WAVING_PLANTS:
598 case TILE_MATERIAL_WAVING_LIQUID_BASIC:
599 shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
602 shaderinfo.material = shaderinfo.base_material;
604 bool enable_shaders = g_settings->getBool("enable_shaders");
608 video::IVideoDriver *driver = RenderingEngine::get_video_driver();
609 if (!driver->queryFeature(video::EVDF_ARB_GLSL)) {
610 errorstream << "Shaders are enabled but GLSL is not supported by the driver\n";
613 video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
615 // Create shaders header
616 bool use_gles = false;
618 use_gles = driver->getDriverType() == video::EDT_OGLES2;
620 std::stringstream shaders_header;
623 << std::showpoint // for GLSL ES
625 std::string vertex_header, fragment_header, geometry_header;
627 shaders_header << R"(
631 precision mediump float;
633 uniform highp mat4 mWorldView;
634 uniform highp mat4 mWorldViewProj;
635 uniform mediump mat4 mTexture;
636 uniform mediump mat3 mNormal;
638 attribute highp vec4 inVertexPosition;
639 attribute lowp vec4 inVertexColor;
640 attribute mediump vec4 inTexCoord0;
641 attribute mediump vec3 inVertexNormal;
642 attribute mediump vec4 inVertexTangent;
643 attribute mediump vec4 inVertexBinormal;
645 fragment_header = R"(
646 precision mediump float;
649 shaders_header << R"(
656 #define mWorldView gl_ModelViewMatrix
657 #define mWorldViewProj gl_ModelViewProjectionMatrix
658 #define mTexture (gl_TextureMatrix[0])
659 #define mNormal gl_NormalMatrix
661 #define inVertexPosition gl_Vertex
662 #define inVertexColor gl_Color
663 #define inTexCoord0 gl_MultiTexCoord0
664 #define inVertexNormal gl_Normal
665 #define inVertexTangent gl_MultiTexCoord1
666 #define inVertexBinormal gl_MultiTexCoord2
670 bool use_discard = use_gles;
672 // For renderers that should use discard instead of GL_ALPHA_TEST
673 const char* gl_renderer = (const char*)glGetString(GL_RENDERER);
674 if (strstr(gl_renderer, "GC7000"))
678 if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL)
679 shaders_header << "#define USE_DISCARD 1\n";
680 else if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF)
681 shaders_header << "#define USE_DISCARD_REF 1\n";
684 #define PROVIDE(constant) shaders_header << "#define " #constant " " << (int)constant << "\n"
687 PROVIDE(NDT_AIRLIKE);
689 PROVIDE(NDT_FLOWINGLIQUID);
690 PROVIDE(NDT_GLASSLIKE);
691 PROVIDE(NDT_ALLFACES);
692 PROVIDE(NDT_ALLFACES_OPTIONAL);
693 PROVIDE(NDT_TORCHLIKE);
694 PROVIDE(NDT_SIGNLIKE);
695 PROVIDE(NDT_PLANTLIKE);
696 PROVIDE(NDT_FENCELIKE);
697 PROVIDE(NDT_RAILLIKE);
698 PROVIDE(NDT_NODEBOX);
699 PROVIDE(NDT_GLASSLIKE_FRAMED);
700 PROVIDE(NDT_FIRELIKE);
701 PROVIDE(NDT_GLASSLIKE_FRAMED_OPTIONAL);
702 PROVIDE(NDT_PLANTLIKE_ROOTED);
704 PROVIDE(TILE_MATERIAL_BASIC);
705 PROVIDE(TILE_MATERIAL_ALPHA);
706 PROVIDE(TILE_MATERIAL_LIQUID_TRANSPARENT);
707 PROVIDE(TILE_MATERIAL_LIQUID_OPAQUE);
708 PROVIDE(TILE_MATERIAL_WAVING_LEAVES);
709 PROVIDE(TILE_MATERIAL_WAVING_PLANTS);
710 PROVIDE(TILE_MATERIAL_OPAQUE);
711 PROVIDE(TILE_MATERIAL_WAVING_LIQUID_BASIC);
712 PROVIDE(TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT);
713 PROVIDE(TILE_MATERIAL_WAVING_LIQUID_OPAQUE);
714 PROVIDE(TILE_MATERIAL_PLAIN);
715 PROVIDE(TILE_MATERIAL_PLAIN_ALPHA);
719 shaders_header << "#define MATERIAL_TYPE " << (int)material_type << "\n";
720 shaders_header << "#define DRAW_TYPE " << (int)drawtype << "\n";
722 bool enable_waving_water = g_settings->getBool("enable_waving_water");
723 shaders_header << "#define ENABLE_WAVING_WATER " << enable_waving_water << "\n";
724 if (enable_waving_water) {
725 shaders_header << "#define WATER_WAVE_HEIGHT " << g_settings->getFloat("water_wave_height") << "\n";
726 shaders_header << "#define WATER_WAVE_LENGTH " << g_settings->getFloat("water_wave_length") << "\n";
727 shaders_header << "#define WATER_WAVE_SPEED " << g_settings->getFloat("water_wave_speed") << "\n";
730 shaders_header << "#define ENABLE_WAVING_LEAVES " << g_settings->getBool("enable_waving_leaves") << "\n";
731 shaders_header << "#define ENABLE_WAVING_PLANTS " << g_settings->getBool("enable_waving_plants") << "\n";
732 shaders_header << "#define ENABLE_TONE_MAPPING " << g_settings->getBool("tone_mapping") << "\n";
734 shaders_header << "#define FOG_START " << core::clamp(g_settings->getFloat("fog_start"), 0.0f, 0.99f) << "\n";
736 if (g_settings->getBool("enable_dynamic_shadows")) {
737 shaders_header << "#define ENABLE_DYNAMIC_SHADOWS 1\n";
738 if (g_settings->getBool("shadow_map_color"))
739 shaders_header << "#define COLORED_SHADOWS 1\n";
741 if (g_settings->getBool("shadow_poisson_filter"))
742 shaders_header << "#define POISSON_FILTER 1\n";
744 s32 shadow_filter = g_settings->getS32("shadow_filters");
745 shaders_header << "#define SHADOW_FILTER " << shadow_filter << "\n";
747 float shadow_soft_radius = g_settings->getFloat("shadow_soft_radius");
748 if (shadow_soft_radius < 1.0f)
749 shadow_soft_radius = 1.0f;
750 shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n";
753 std::string common_header = shaders_header.str();
755 std::string vertex_shader = m_sourcecache.getOrLoad(name, "opengl_vertex.glsl");
756 std::string fragment_shader = m_sourcecache.getOrLoad(name, "opengl_fragment.glsl");
757 std::string geometry_shader = m_sourcecache.getOrLoad(name, "opengl_geometry.glsl");
759 vertex_shader = common_header + vertex_header + vertex_shader;
760 fragment_shader = common_header + fragment_header + fragment_shader;
761 const char *geometry_shader_ptr = nullptr; // optional
762 if (!geometry_shader.empty()) {
763 geometry_shader = common_header + geometry_header + geometry_shader;
764 geometry_shader_ptr = geometry_shader.c_str();
767 irr_ptr<ShaderCallback> cb{new ShaderCallback(m_setter_factories)};
768 infostream<<"Compiling high level shaders for "<<name<<std::endl;
769 s32 shadermat = gpu->addHighLevelShaderMaterial(
770 vertex_shader.c_str(), nullptr, video::EVST_VS_1_1,
771 fragment_shader.c_str(), nullptr, video::EPST_PS_1_1,
772 geometry_shader_ptr, nullptr, video::EGST_GS_4_0, scene::EPT_TRIANGLES, scene::EPT_TRIANGLES, 0,
773 cb.get(), shaderinfo.base_material, 1);
774 if (shadermat == -1) {
775 errorstream<<"generate_shader(): "
776 "failed to generate \""<<name<<"\", "
777 "addHighLevelShaderMaterial failed."
779 dumpShaderProgram(warningstream, "Vertex", vertex_shader);
780 dumpShaderProgram(warningstream, "Fragment", fragment_shader);
781 dumpShaderProgram(warningstream, "Geometry", geometry_shader);
785 // Apply the newly created material type
786 shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat;
790 void dumpShaderProgram(std::ostream &output_stream,
791 const std::string &program_type, const std::string &program)
793 output_stream << program_type << " shader program:" << std::endl <<
794 "----------------------------------" << std::endl;
798 while ((pos = program.find('\n', prev)) != std::string::npos) {
799 output_stream << line++ << ": "<< program.substr(prev, pos - prev) <<
803 output_stream << line << ": " << program.substr(prev) << std::endl <<
804 "End of " << program_type << " shader program." << std::endl <<