summaryrefslogtreecommitdiff
path: root/src/client/shader.cpp
blob: bbb8727612862810c0fb91fde47236773a4cf7be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
/*
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 "irr_ptr.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"
#include "config.h"

#include <mt_opengl.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<std::unique_ptr<IShaderConstantSetter>> m_setters;

public:
	template <typename Factories>
	ShaderCallback(const Factories &factories)
	{
		for (auto &&factory : factories)
			m_setters.push_back(std::unique_ptr<IShaderConstantSetter>(factory->create()));
	}

	virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) override
	{
		video::IVideoDriver *driver = services->getVideoDriver();
		sanity_check(driver != NULL);

		for (auto &&setter : m_setters)
			setter->onSetConstants(services);
	}

	virtual void OnSetMaterial(const video::SMaterial& material) override
	{
		for (auto &&setter : m_setters)
			setter->onSetMaterial(material);
	}
};


/*
	MainShaderConstantSetter: Set basic constants required for almost everything
*/

class MainShaderConstantSetter : public IShaderConstantSetter
{
	CachedVertexShaderSetting<f32, 16> m_world_view_proj;
	CachedVertexShaderSetting<f32, 16> m_world;

	// Shadow-related
	CachedPixelShaderSetting<f32, 16> m_shadow_view_proj;
	CachedPixelShaderSetting<f32, 3> m_light_direction;
	CachedPixelShaderSetting<f32> m_texture_res;
	CachedPixelShaderSetting<f32> m_shadow_strength;
	CachedPixelShaderSetting<f32> m_time_of_day;
	CachedPixelShaderSetting<f32> m_shadowfar;
	CachedPixelShaderSetting<f32, 4> m_camera_pos;
	CachedPixelShaderSetting<s32> m_shadow_texture;
	CachedVertexShaderSetting<f32> m_perspective_bias0_vertex;
	CachedPixelShaderSetting<f32> m_perspective_bias0_pixel;
	CachedVertexShaderSetting<f32> m_perspective_bias1_vertex;
	CachedPixelShaderSetting<f32> m_perspective_bias1_pixel;
	CachedVertexShaderSetting<f32> m_perspective_zbias_vertex;
	CachedPixelShaderSetting<f32> m_perspective_zbias_pixel;

#if ENABLE_GLES
	// Modelview matrix
	CachedVertexShaderSetting<float, 16> m_world_view;
	// Texture matrix
	CachedVertexShaderSetting<float, 16> m_texture;
	// Normal matrix
	CachedVertexShaderSetting<float, 9> m_normal;
#endif

public:
	MainShaderConstantSetter() :
		  m_world_view_proj("mWorldViewProj")
		, m_world("mWorld")
		, m_shadow_view_proj("m_ShadowViewProj")
		, m_light_direction("v_LightDirection")
		, m_texture_res("f_textureresolution")
		, m_shadow_strength("f_shadow_strength")
		, m_time_of_day("f_timeofday")
		, m_shadowfar("f_shadowfar")
		, m_camera_pos("CameraPos")
		, m_shadow_texture("ShadowMapSampler")
		, m_perspective_bias0_vertex("xyPerspectiveBias0")
		, m_perspective_bias0_pixel("xyPerspectiveBias0")
		, m_perspective_bias1_vertex("xyPerspectiveBias1")
		, m_perspective_bias1_pixel("xyPerspectiveBias1")
		, m_perspective_zbias_vertex("zPerspectiveBias")
		, m_perspective_zbias_pixel("zPerspectiveBias")
#if ENABLE_GLES
		, m_world_view("mWorldView")
		, m_texture("mTexture")
		, m_normal("mNormal")
#endif
	{}
	~MainShaderConstantSetter() = default;

	virtual void onSetConstants(video::IMaterialRendererServices *services) override
	{
		video::IVideoDriver *driver = services->getVideoDriver();
		sanity_check(driver);

		// Set world matrix
		core::matrix4 world = driver->getTransform(video::ETS_WORLD);
		m_world.set(*reinterpret_cast<float(*)[16]>(world.pointer()), services);

		// Set clip matrix
		core::matrix4 worldView;
		worldView = driver->getTransform(video::ETS_VIEW);
		worldView *= world;

		core::matrix4 worldViewProj;
		worldViewProj = driver->getTransform(video::ETS_PROJECTION);
		worldViewProj *= worldView;
		m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services);

#if ENABLE_GLES
		core::matrix4 texture = driver->getTransform(video::ETS_TEXTURE_0);
		m_world_view.set(*reinterpret_cast<float(*)[16]>(worldView.pointer()), services);
		m_texture.set(*reinterpret_cast<float(*)[16]>(texture.pointer()), services);

		core::matrix4 normal;
		worldView.getTransposed(normal);
		sanity_check(normal.makeInverse());
		float m[9] = {
			normal[0], normal[1], normal[2],
			normal[4], normal[5], normal[6],
			normal[8], normal[9], normal[10],
		};
		m_normal.set(m, services);
#endif

		// Set uniforms for Shadow shader
		if (ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer()) {
			const auto &light = shadow->getDirectionalLight();

			core::matrix4 shadowViewProj = light.getProjectionMatrix();
			shadowViewProj *= light.getViewMatrix();
			m_shadow_view_proj.set(shadowViewProj.pointer(), services);

			f32 v_LightDirection[3];
			light.getDirection().getAs3Values(v_LightDirection);
			m_light_direction.set(v_LightDirection, services);

			f32 TextureResolution = light.getMapResolution();
			m_texture_res.set(&TextureResolution, services);

			f32 ShadowStrength = shadow->getShadowStrength();
			m_shadow_strength.set(&ShadowStrength, services);

			f32 timeOfDay = shadow->getTimeOfDay();
			m_time_of_day.set(&timeOfDay, services);

			f32 shadowFar = shadow->getMaxShadowFar();
			m_shadowfar.set(&shadowFar, services);

			f32 cam_pos[4];
			shadowViewProj.transformVect(cam_pos, light.getPlayerPos());
			m_camera_pos.set(cam_pos, services);

			// I dont like using this hardcoded value. maybe something like
			// MAX_TEXTURE - 1 or somthing like that??
			s32 TextureLayerID = 3;
			m_shadow_texture.set(&TextureLayerID, services);

			f32 bias0 = shadow->getPerspectiveBiasXY();
			m_perspective_bias0_vertex.set(&bias0, services);
			m_perspective_bias0_pixel.set(&bias0, services);
			f32 bias1 = 1.0f - bias0 + 1e-5f;
			m_perspective_bias1_vertex.set(&bias1, services);
			m_perspective_bias1_pixel.set(&bias1, services);
			f32 zbias = shadow->getPerspectiveBiasZ();
			m_perspective_zbias_vertex.set(&zbias, services);
			m_perspective_zbias_pixel.set(&zbias, services);
		}
	}
};


class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory
{
public:
	virtual IShaderConstantSetter* create()
		{ return new MainShaderConstantSetter(); }
};


/*
	ShaderSource
*/

class ShaderSource : public IWritableShaderSource
{
public:
	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,
		MaterialType material_type, NodeDrawType drawtype) override;

	/*
		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,
		MaterialType material_type, NodeDrawType drawtype) override;

	ShaderInfo getShaderInfo(u32 id) override;

	// Processes queued shader requests from other threads.
	// Shall be called from the main thread.
	void processQueue() override;

	// 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) override;

	// Rebuild shaders from the current set of source shaders
	// Shall be called from the main thread.
	void rebuildShaders() override;

	void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) override
	{
		m_setter_factories.push_back(std::unique_ptr<IShaderConstantSetterFactory>(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<std::unique_ptr<IShaderConstantSetterFactory>> m_setter_factories;

	// Generate shader given the shader name.
	ShaderInfo generateShader(const std::string &name,
			MaterialType material_type, NodeDrawType drawtype);
};

IWritableShaderSource *createShaderSource()
{
	return new ShaderSource();
}

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());
}

u32 ShaderSource::getShader(const std::string &name,
		MaterialType material_type, NodeDrawType 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,
		MaterialType material_type, NodeDrawType 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 = generateShader(name, material_type, drawtype);

	/*
		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 = generateShader(info->name, info->material_type, info->drawtype);
		}
	}
}


ShaderInfo ShaderSource::generateShader(const std::string &name,
		MaterialType material_type, NodeDrawType drawtype)
{
	ShaderInfo shaderinfo;
	shaderinfo.name = name;
	shaderinfo.material_type = material_type;
	shaderinfo.drawtype = drawtype;
	switch (material_type) {
	case TILE_MATERIAL_OPAQUE:
	case TILE_MATERIAL_LIQUID_OPAQUE:
	case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
		shaderinfo.base_material = video::EMT_SOLID;
		break;
	case TILE_MATERIAL_ALPHA:
	case TILE_MATERIAL_PLAIN_ALPHA:
	case TILE_MATERIAL_LIQUID_TRANSPARENT:
	case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
		shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
		break;
	case TILE_MATERIAL_BASIC:
	case TILE_MATERIAL_PLAIN:
	case TILE_MATERIAL_WAVING_LEAVES:
	case TILE_MATERIAL_WAVING_PLANTS:
	case TILE_MATERIAL_WAVING_LIQUID_BASIC:
		shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
		break;
	}
	shaderinfo.material = shaderinfo.base_material;

	bool enable_shaders = g_settings->getBool("enable_shaders");
	if (!enable_shaders)
		return shaderinfo;

	video::IVideoDriver *driver = RenderingEngine::get_video_driver();
	if (!driver->queryFeature(video::EVDF_ARB_GLSL)) {
		errorstream << "Shaders are enabled but GLSL is not supported by the driver\n";
		return shaderinfo;
	}
	video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();

	// Create shaders header
	bool use_gles = false;
#if ENABLE_GLES
	use_gles = driver->getDriverType() == video::EDT_OGLES2;
#endif
	std::stringstream shaders_header;
	shaders_header
		<< std::noboolalpha
		<< std::showpoint // for GLSL ES
		;
	std::string vertex_header, fragment_header, geometry_header;
	if (use_gles) {
		shaders_header << R"(
			#version 100
		)";
		vertex_header = R"(
			precision mediump float;

			uniform highp mat4 mWorldView;
			uniform highp mat4 mWorldViewProj;
			uniform mediump mat4 mTexture;
			uniform mediump mat3 mNormal;

			attribute highp vec4 inVertexPosition;
			attribute lowp vec4 inVertexColor;
			attribute mediump vec4 inTexCoord0;
			attribute mediump vec3 inVertexNormal;
			attribute mediump vec4 inVertexTangent;
			attribute mediump vec4 inVertexBinormal;
		)";
		fragment_header = R"(
			precision mediump float;
		)";
	} else {
		shaders_header << R"(
			#version 120
			#define lowp
			#define mediump
			#define highp
		)";
		vertex_header = R"(
			#define mWorldView gl_ModelViewMatrix
			#define mWorldViewProj gl_ModelViewProjectionMatrix
			#define mTexture (gl_TextureMatrix[0])
			#define mNormal gl_NormalMatrix

			#define inVertexPosition gl_Vertex
			#define inVertexColor gl_Color
			#define inTexCoord0 gl_MultiTexCoord0
			#define inVertexNormal gl_Normal
			#define inVertexTangent gl_MultiTexCoord1
			#define inVertexBinormal gl_MultiTexCoord2
		)";
	}

	// Since this is the first time we're using the GL bindings be extra careful.
	// This should be removed before 5.6.0 or similar.
	if (!GL.GetString) {
		errorstream << "OpenGL procedures were not loaded correctly, "
			"please open a bug report with details about your platform/OS." << std::endl;
		abort();
	}

	bool use_discard = use_gles;
	// For renderers that should use discard instead of GL_ALPHA_TEST
	const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER));
	if (strstr(renderer, "GC7000"))
		use_discard = true;
	if (use_discard) {
		if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL)
			shaders_header << "#define USE_DISCARD 1\n";
		else if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF)
			shaders_header << "#define USE_DISCARD_REF 1\n";
	}

#define PROVIDE(constant) shaders_header << "#define " #constant " " << (int)constant << "\n"

	PROVIDE(NDT_NORMAL);
	PROVIDE(NDT_AIRLIKE);
	PROVIDE(NDT_LIQUID);
	PROVIDE(NDT_FLOWINGLIQUID);
	PROVIDE(NDT_GLASSLIKE);
	PROVIDE(NDT_ALLFACES);
	PROVIDE(NDT_ALLFACES_OPTIONAL);
	PROVIDE(NDT_TORCHLIKE);
	PROVIDE(NDT_SIGNLIKE);
	PROVIDE(NDT_PLANTLIKE);
	PROVIDE(NDT_FENCELIKE);
	PROVIDE(NDT_RAILLIKE);
	PROVIDE(NDT_NODEBOX);
	PROVIDE(NDT_GLASSLIKE_FRAMED);
	PROVIDE(NDT_FIRELIKE);
	PROVIDE(NDT_GLASSLIKE_FRAMED_OPTIONAL);
	PROVIDE(NDT_PLANTLIKE_ROOTED);

	PROVIDE(TILE_MATERIAL_BASIC);
	PROVIDE(TILE_MATERIAL_ALPHA);
	PROVIDE(TILE_MATERIAL_LIQUID_TRANSPARENT);
	PROVIDE(TILE_MATERIAL_LIQUID_OPAQUE);
	PROVIDE(TILE_MATERIAL_WAVING_LEAVES);
	PROVIDE(TILE_MATERIAL_WAVING_PLANTS);
	PROVIDE(TILE_MATERIAL_OPAQUE);
	PROVIDE(TILE_MATERIAL_WAVING_LIQUID_BASIC);
	PROVIDE(TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT);
	PROVIDE(TILE_MATERIAL_WAVING_LIQUID_OPAQUE);
	PROVIDE(TILE_MATERIAL_PLAIN);
	PROVIDE(TILE_MATERIAL_PLAIN_ALPHA);

#undef PROVIDE

	shaders_header << "#define MATERIAL_TYPE " << (int)material_type << "\n";
	shaders_header << "#define DRAW_TYPE " << (int)drawtype << "\n";

	bool enable_waving_water = g_settings->getBool("enable_waving_water");
	shaders_header << "#define ENABLE_WAVING_WATER " << enable_waving_water << "\n";
	if (enable_waving_water) {
		shaders_header << "#define WATER_WAVE_HEIGHT " << g_settings->getFloat("water_wave_height") << "\n";
		shaders_header << "#define WATER_WAVE_LENGTH " << g_settings->getFloat("water_wave_length") << "\n";
		shaders_header << "#define WATER_WAVE_SPEED " << g_settings->getFloat("water_wave_speed") << "\n";
	}

	shaders_header << "#define ENABLE_WAVING_LEAVES " << g_settings->getBool("enable_waving_leaves") << "\n";
	shaders_header << "#define ENABLE_WAVING_PLANTS " << g_settings->getBool("enable_waving_plants") << "\n";
	shaders_header << "#define ENABLE_TONE_MAPPING " << g_settings->getBool("tone_mapping") << "\n";

	shaders_header << "#define FOG_START " << core::clamp(g_settings->getFloat("fog_start"), 0.0f, 0.99f) << "\n";

	if (g_settings->getBool("enable_dynamic_shadows")) {
		shaders_header << "#define ENABLE_DYNAMIC_SHADOWS 1\n";
		if (g_settings->getBool("shadow_map_color"))
			shaders_header << "#define COLORED_SHADOWS 1\n";

		if (g_settings->getBool("shadow_poisson_filter"))
			shaders_header << "#define POISSON_FILTER 1\n";

		s32 shadow_filter = g_settings->getS32("shadow_filters");
		shaders_header << "#define SHADOW_FILTER " << shadow_filter << "\n";

		float shadow_soft_radius = g_settings->getFloat("shadow_soft_radius");
		if (shadow_soft_radius < 1.0f)
			shadow_soft_radius = 1.0f;
		shaders_header << "#define SOFTSHADOWRADIUS " << shadow_soft_radius << "\n";
	}

	std::string common_header = shaders_header.str();

	std::string vertex_shader = m_sourcecache.getOrLoad(name, "opengl_vertex.glsl");
	std::string fragment_shader = m_sourcecache.getOrLoad(name, "opengl_fragment.glsl");
	std::string geometry_shader = m_sourcecache.getOrLoad(name, "opengl_geometry.glsl");

	vertex_shader = common_header + vertex_header + vertex_shader;
	fragment_shader = common_header + fragment_header + fragment_shader;
	const char *geometry_shader_ptr = nullptr; // optional
	if (!geometry_shader.empty()) {
		geometry_shader = common_header + geometry_header + geometry_shader;
		geometry_shader_ptr = geometry_shader.c_str();
	}

	irr_ptr<ShaderCallback> cb{new ShaderCallback(m_setter_factories)};
	infostream<<"Compiling high level shaders for "<<name<<std::endl;
	s32 shadermat = gpu->addHighLevelShaderMaterial(
		vertex_shader.c_str(), nullptr, video::EVST_VS_1_1,
		fragment_shader.c_str(), nullptr, video::EPST_PS_1_1,
		geometry_shader_ptr, nullptr, video::EGST_GS_4_0, scene::EPT_TRIANGLES, scene::EPT_TRIANGLES, 0,
		cb.get(), shaderinfo.base_material,  1);
	if (shadermat == -1) {
		errorstream<<"generate_shader(): "
				"failed to generate \""<<name<<"\", "
				"addHighLevelShaderMaterial failed."
				<<std::endl;
		dumpShaderProgram(warningstream, "Vertex", vertex_shader);
		dumpShaderProgram(warningstream, "Fragment", fragment_shader);
		dumpShaderProgram(warningstream, "Geometry", geometry_shader);
		return shaderinfo;
	}

	// Apply the newly created material type
	shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat;
	return shaderinfo;
}

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;
}