1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 
17 #ifndef INC_StdMeshMaterial
18 #define INC_StdMeshMaterial
19 
20 #include "graphics/C4Shader.h"
21 #include "graphics/C4Surface.h"
22 
23 #include <tuple>
24 
25 // TODO: Support more features of OGRE material scripts
26 // Refer to http://www.ogre3d.org/docs/manual/manual_14.html
27 
28 class StdMeshMaterialParserCtx;
29 
30 class StdMeshMaterialError: public std::exception
31 {
32 public:
33 	StdMeshMaterialError(const StdStrBuf& message, const char* file, unsigned int line);
34 	~StdMeshMaterialError() throw() override = default;
35 
what()36 	const char* what() const throw() override { return Buf.getData(); }
37 
38 protected:
39 	StdCopyStrBuf Buf;
40 };
41 
42 class StdMeshMaterialShaderParameter
43 {
44 public:
45 	enum Type {
46 		AUTO,
47 		AUTO_TEXTURE_MATRIX, // Texture matrix for the i-th texture
48 		INT,
49 		FLOAT,
50 		FLOAT2,
51 		FLOAT3,
52 		FLOAT4,
53 		MATRIX_4X4
54 	};
55 
56 	enum Auto {
57 		// TODO: OGRE auto values
58 		AUTO_DUMMY
59 	};
60 
61 	StdMeshMaterialShaderParameter(); // type=FLOAT, value uninitialized
62 	StdMeshMaterialShaderParameter(Type type); // value uninitialized
63 	StdMeshMaterialShaderParameter(const StdMeshMaterialShaderParameter& other);
64 	StdMeshMaterialShaderParameter(StdMeshMaterialShaderParameter &&other);
65 	~StdMeshMaterialShaderParameter();
66 
67 	StdMeshMaterialShaderParameter& operator=(const StdMeshMaterialShaderParameter& other);
68 	StdMeshMaterialShaderParameter& operator=(StdMeshMaterialShaderParameter &&other);
69 
GetType()70 	Type GetType() const { return type; }
71 	void SetType(Type type); // changes type, new value is uninitialized
72 
73 	// Getters
GetAuto()74 	Auto GetAuto() const { assert(type == AUTO); return a; }
GetInt()75 	int GetInt() const { assert(type == INT || type == AUTO_TEXTURE_MATRIX); return i; }
GetFloat()76 	float GetFloat() const { assert(type == FLOAT); return f[0]; }
GetFloatv()77 	const float* GetFloatv() const { assert(type == FLOAT2 || type == FLOAT3 || type == FLOAT4); return f; }
GetMatrix()78 	const float* GetMatrix() const { assert(type == MATRIX_4X4); return matrix; }
79 
80 	// Setters
GetAuto()81 	Auto& GetAuto() { assert(type == AUTO); return a; }
GetInt()82 	int& GetInt() { assert(type == INT || type == AUTO_TEXTURE_MATRIX); return i; }
GetFloat()83 	float& GetFloat() { assert(type == FLOAT); return f[0]; }
GetFloatv()84 	float* GetFloatv() { assert(type == FLOAT2 || type == FLOAT3 || type == FLOAT4); return f; }
GetMatrix()85 	float* GetMatrix() { assert(type == MATRIX_4X4); return matrix; }
86 private:
87 	void CopyShallow(const StdMeshMaterialShaderParameter& other);
88 	void CopyDeep(const StdMeshMaterialShaderParameter& other);
89 	void Move(StdMeshMaterialShaderParameter &&other);
90 
91 	Type type{FLOAT4};
92 
93 	union {
94 		Auto a;
95 		int i;
96 		float f[4];
97 		float* matrix; // 16 floats, row-major order
98 	};
99 };
100 
101 class StdMeshMaterialShaderParameters
102 {
103 public:
104 	StdMeshMaterialShaderParameters();
105 
106 	void Load(StdMeshMaterialParserCtx& ctx);
107 
108 	StdMeshMaterialShaderParameter& AddParameter(const char* name, StdMeshMaterialShaderParameter::Type type);
109 
110 	std::vector<std::pair<StdCopyStrBuf, StdMeshMaterialShaderParameter> > NamedParameters;
111 private:
112 	StdMeshMaterialShaderParameter LoadConstParameter(StdMeshMaterialParserCtx& ctx);
113 	StdMeshMaterialShaderParameter LoadAutoParameter(StdMeshMaterialParserCtx& ctx);
114 };
115 
116 enum StdMeshMaterialShaderType {
117 	SMMS_FRAGMENT,
118 	SMMS_VERTEX,
119 	SMMS_GEOMETRY
120 };
121 
122 // Interface to load additional resources.
123 // Given a texture filename occuring in the
124 // material script, this should load the texture from wherever the material
125 // script is actually loaded, for example from a C4Group.
126 // Given a shader filename, this should load the shader text.
127 class StdMeshMaterialLoader
128 {
129 public:
130 	virtual C4Surface* LoadTexture(const char* filename) = 0;
131 	virtual StdStrBuf LoadShaderCode(const char* filename) = 0;
132 	virtual void AddShaderSlices(C4Shader& shader, int ssc) = 0; // add default shader slices
133 	virtual ~StdMeshMaterialLoader() = default;
134 };
135 
136 // This is just a container class to hold the shader code; the C4Shader
137 // objects are later created from that code by mixing them with the default
138 // slices.
139 class StdMeshMaterialShader
140 {
141 public:
StdMeshMaterialShader(const char * filename,const char * name,const char * language,StdMeshMaterialShaderType,const char * code)142 	StdMeshMaterialShader(const char* filename, const char* name, const char* language, StdMeshMaterialShaderType /* type */, const char* code):
143 		Filename(filename), Name(name), Language(language), Code(code)
144 	{}
145 
GetFilename()146 	const char* GetFilename() const { return Filename.getData(); }
GetCode()147 	const char* GetCode() const { return Code.getData(); }
148 
149 private:
150 	StdCopyStrBuf Filename;
151 	StdCopyStrBuf Name;
152 	StdCopyStrBuf Language;
153 	StdCopyStrBuf Code;
154 };
155 
156 class StdMeshMaterialProgram
157 {
158 public:
159 	StdMeshMaterialProgram(const char* name, const StdMeshMaterialShader* fragment_shader, const StdMeshMaterialShader* vertex_shader, const StdMeshMaterialShader* geometry_shader);
160 	bool AddParameterNames(const StdMeshMaterialShaderParameters& parameters); // returns true if some parameter names were not yet registered.
161 
IsCompiled()162 	bool IsCompiled() const { return Shader.Initialised(); }
163 	bool Compile(StdMeshMaterialLoader& loader);
164 
165 	const C4Shader* GetShader(int ssc) const;
166 	int GetParameterIndex(const char* name) const;
167 
GetFragmentShader()168 	const StdMeshMaterialShader* GetFragmentShader() const { return FragmentShader; }
GetVertexShader()169 	const StdMeshMaterialShader* GetVertexShader() const { return VertexShader; }
GetGeometryShader()170 	const StdMeshMaterialShader* GetGeometryShader() const { return GeometryShader; }
171 private:
172 	bool CompileShader(StdMeshMaterialLoader& loader, C4Shader& shader, int ssc);
173 
174 	// Human-readable program name
175 	const StdCopyStrBuf Name;
176 
177 	// Program components
178 	const StdMeshMaterialShader* FragmentShader;
179 	const StdMeshMaterialShader* VertexShader;
180 	const StdMeshMaterialShader* GeometryShader;
181 
182 	// Compiled shaders
183 	C4Shader Shader;
184 	C4Shader ShaderMod2;
185 	C4Shader ShaderLight;
186 	C4Shader ShaderLightMod2;
187 
188 	// Filled as program references are encountered;
189 	std::vector<StdCopyStrBuf> ParameterNames;
190 };
191 
192 class StdMeshMaterialTextureUnit
193 {
194 public:
195 	enum TexAddressModeType
196 	{
197 		AM_Wrap,
198 		AM_Clamp,
199 		AM_Mirror,
200 		AM_Border
201 	};
202 
203 	enum FilteringType
204 	{
205 		F_None,
206 		F_Point,
207 		F_Linear,
208 		F_Anisotropic
209 	};
210 
211 	enum BlendOpType
212 	{
213 		BO_Replace,
214 		BO_Add,
215 		BO_Modulate,
216 		BO_AlphaBlend
217 	};
218 
219 	enum BlendOpExType
220 	{
221 		BOX_Source1,
222 		BOX_Source2,
223 		BOX_Modulate,
224 		BOX_ModulateX2,
225 		BOX_ModulateX4,
226 		BOX_Add,
227 		BOX_AddSigned,
228 		BOX_AddSmooth,
229 		BOX_Subtract,
230 		BOX_BlendDiffuseAlpha,
231 		BOX_BlendTextureAlpha,
232 		BOX_BlendCurrentAlpha,
233 		BOX_BlendManual,
234 		BOX_Dotproduct,
235 		BOX_BlendDiffuseColor
236 	};
237 
238 	enum BlendOpSourceType
239 	{
240 		BOS_Current,
241 		BOS_Texture,
242 		BOS_Diffuse,
243 		BOS_Specular,
244 		BOS_PlayerColor, // not specified in ogre, added in OpenClonk
245 		BOS_Manual
246 	};
247 
248 	struct Transformation
249 	{
250 		enum Type
251 		{
252 			T_SCROLL,
253 			T_SCROLL_ANIM,
254 			T_ROTATE,
255 			T_ROTATE_ANIM,
256 			T_SCALE,
257 			T_TRANSFORM,
258 			T_WAVE_XFORM
259 		};
260 
261 		enum XFormType
262 		{
263 			XF_SCROLL_X,
264 			XF_SCROLL_Y,
265 			XF_ROTATE,
266 			XF_SCALE_X,
267 			XF_SCALE_Y
268 		};
269 
270 		enum WaveType
271 		{
272 			W_SINE,
273 			W_TRIANGLE,
274 			W_SQUARE,
275 			W_SAWTOOTH,
276 			W_INVERSE_SAWTOOTH
277 		};
278 
279 		Type TransformType;
280 
281 		union
282 		{
283 			struct { float X; float Y; } Scroll;
284 			struct { float XSpeed; float YSpeed; } ScrollAnim;
285 			struct { float Angle; } Rotate;
286 			struct { float RevsPerSec; } RotateAnim;
287 			struct { float X; float Y; } Scale;
288 			struct { float M[16]; } Transform;
289 			struct { XFormType XForm; WaveType Wave; float Base; float Frequency; float Phase; float Amplitude; } WaveXForm;
290 		};
291 
GetScrollXTransformation292 		double GetScrollX(double t) const { assert(TransformType == T_SCROLL_ANIM); return ScrollAnim.XSpeed * t; }
GetScrollYTransformation293 		double GetScrollY(double t) const { assert(TransformType == T_SCROLL_ANIM); return ScrollAnim.YSpeed * t; }
GetRotateTransformation294 		double GetRotate(double t) const { assert(TransformType == T_ROTATE_ANIM); return fmod(RotateAnim.RevsPerSec * t, 1.0) * 360.0; }
295 		double GetWaveXForm(double t) const;
296 	};
297 
298 	// Ref-counted texture. When a meterial inherits from one which contains
299 	// a TextureUnit, then they will share the same C4TexRef.
300 	class Tex
301 	{
302 	public:
303 		Tex(C4Surface* Surface); // Takes ownership
304 		~Tex();
305 
306 		unsigned int RefCount;
307 
308 		// TODO: Note this cannot be C4Surface here, because C4Surface
309 		// does not have a virtual destructor, so we couldn't delete it
310 		// properly in that case. I am a bit annoyed that this
311 		// currently requires a cross-ref to lib/texture. I think
312 		// C4Surface should go away and the file loading/saving
313 		// should be free functions instead. I also think the file
314 		// loading/saving should be decoupled from the surfaces, so we
315 		// can skip the surface here and simply use a C4TexRef. armin.
316 		C4Surface* Surf;
317 		C4TexRef& Texture;
318 	};
319 
320 	// Simple wrapper which handles refcounting of Tex
321 	class TexPtr
322 	{
323 	public:
324 		TexPtr(C4Surface* Surface);
325 		TexPtr(const TexPtr& other);
326 		~TexPtr();
327 
328 		TexPtr& operator=(const TexPtr& other);
329 
330 		Tex* pTex;
331 	};
332 
333 	StdMeshMaterialTextureUnit();
334 
335 	void LoadTexture(StdMeshMaterialParserCtx& ctx, const char* texname);
336 	void Load(StdMeshMaterialParserCtx& ctx);
337 
HasTexture()338 	bool HasTexture() const { return !Textures.empty(); }
GetNumTextures()339 	size_t GetNumTextures() const { return Textures.size(); }
GetTexture(unsigned int i)340 	const C4TexRef& GetTexture(unsigned int i) const { return Textures[i].pTex->Texture; }
HasFrameAnimation()341 	bool HasFrameAnimation() const { return Duration > 0; }
HasTexCoordAnimation()342 	bool HasTexCoordAnimation() const { return !Transformations.empty(); }
343 
344 	StdCopyStrBuf Name;
345 	float Duration{0.0f}; // Duration of texture animation, if any.
346 
347 	TexAddressModeType TexAddressMode{AM_Wrap};
348 	float TexBorderColor[4];
349 	FilteringType Filtering[3]; // min, max, mipmap
350 
351 	BlendOpExType ColorOpEx{BOX_Modulate};
352 	BlendOpSourceType ColorOpSources[2];
353 	float ColorOpManualFactor{0.0f};
354 	float ColorOpManualColor1[3];
355 	float ColorOpManualColor2[3];
356 
357 	BlendOpExType AlphaOpEx{BOX_Modulate};
358 	BlendOpSourceType AlphaOpSources[2];
359 	float AlphaOpManualFactor{0.0f};
360 	float AlphaOpManualAlpha1;
361 	float AlphaOpManualAlpha2;
362 
363 	// Transformations to be applied to texture coordinates in order
364 	std::vector<Transformation> Transformations;
365 
366 private:
367 	std::vector<TexPtr> Textures;
368 };
369 
370 class StdMeshMaterialPass
371 {
372 public:
373 	enum CullHardwareType
374 	{
375 		CH_Clockwise,
376 		CH_CounterClockwise,
377 		CH_None
378 	};
379 
380 	enum SceneBlendType
381 	{
382 		SB_One,
383 		SB_Zero,
384 		SB_DestColor,
385 		SB_SrcColor,
386 		SB_OneMinusDestColor,
387 		SB_OneMinusSrcColor,
388 		SB_DestAlpha,
389 		SB_SrcAlpha,
390 		SB_OneMinusDestAlpha,
391 		SB_OneMinusSrcAlpha
392 	};
393 
394 	enum DepthFunctionType
395 	{
396 		DF_AlwaysFail,
397 		DF_AlwaysPass,
398 		DF_Less,
399 		DF_LessEqual,
400 		DF_Equal,
401 		DF_NotEqual,
402 		DF_GreaterEqual,
403 		DF_Greater
404 	};
405 
406 	StdMeshMaterialPass();
407 	void Load(StdMeshMaterialParserCtx& ctx);
408 
IsOpaque()409 	bool IsOpaque() const { return SceneBlendFactors[1] == SB_Zero; }
410 
411 	StdCopyStrBuf Name;
412 	std::vector<StdMeshMaterialTextureUnit> TextureUnits;
413 
414 	float Ambient[4];
415 	float Diffuse[4];
416 	float Specular[4];
417 	float Emissive[4];
418 	float Shininess;
419 
420 	bool DepthCheck{true};
421 	bool DepthWrite{true};
422 
423 	CullHardwareType CullHardware{CH_Clockwise};
424 	SceneBlendType SceneBlendFactors[2];
425 	DepthFunctionType AlphaRejectionFunction;
426 	float AlphaRejectionValue;
427 	bool AlphaToCoverage;
428 
429 	struct ShaderInstance
430 	{
431 		// This points into the StdMeshMatManager maps
432 		const StdMeshMaterialShader* Shader;
433 		// Parameters for this instance
434 		StdMeshMaterialShaderParameters Parameters;
435 	};
436 
437 	class ProgramInstance
438 	{
439 	public:
440 		ProgramInstance(const StdMeshMaterialProgram* program, const ShaderInstance* fragment_instance, const ShaderInstance* vertex_instance, const ShaderInstance* geometry_instance);
441 
442 		// This points into the StdMeshMatManager map
443 		const StdMeshMaterialProgram* const Program;
444 
445 		// Parameters for this instance
446 		struct ParameterRef {
447 			const StdMeshMaterialShaderParameter* Parameter;
448 			int UniformIndex; // Index into parameter table for this program
449 		};
450 
451 		std::vector<ParameterRef> Parameters;
452 
453 	private:
454 		void LoadParameterRefs(const ShaderInstance* instance);
455 	};
456 
457 	ShaderInstance FragmentShader;
458 	ShaderInstance VertexShader;
459 	ShaderInstance GeometryShader;
460 
461 	// This is a shared_ptr and not a unique_ptr so that this class is
462 	// copyable, so it can be inherited. However, when the inherited
463 	// material is prepared, the ProgramInstance will be overwritten
464 	// anyway, so in that sense a unique_ptr would be enough. We could
465 	// change it and make this class only movable (not copyable), and
466 	// provide inheritance by copying all other fields, and letting
467 	// PrepareMaterial fill the program instance.
468 	std::shared_ptr<ProgramInstance> Program;
469 
470 private:
471 	void LoadShaderRef(StdMeshMaterialParserCtx& ctx, StdMeshMaterialShaderType type);
472 };
473 
474 class StdMeshMaterialTechnique
475 {
476 public:
477 	StdMeshMaterialTechnique();
478 
479 	void Load(StdMeshMaterialParserCtx& ctx);
480 
481 	bool IsOpaque() const;
482 
483 	StdCopyStrBuf Name;
484 	std::vector<StdMeshMaterialPass> Passes;
485 
486 	// Filled in by gfx implementation: Whether this technique is available on
487 	// the hardware and gfx engine (DX/GL) we are running on
488 	bool Available{false};
489 };
490 
491 class StdMeshMaterial
492 {
493 public:
494 	StdMeshMaterial();
495 	void Load(StdMeshMaterialParserCtx& ctx);
496 
IsOpaque()497 	bool IsOpaque() const { assert(BestTechniqueIndex >= 0); return Techniques[BestTechniqueIndex].IsOpaque(); }
498 
499 	// Location the Material was loaded from
500 	StdCopyStrBuf FileName;
501 	unsigned int Line{0};
502 
503 	// Material name
504 	StdCopyStrBuf Name;
505 
506 	// Not currently used in Clonk, but don't fail when we see this in a
507 	// Material script:
508 	bool ReceiveShadows{true};
509 
510 	// Available techniques
511 	std::vector<StdMeshMaterialTechnique> Techniques;
512 
513 	// Filled in by gfx implementation: Best technique to use
514 	int BestTechniqueIndex{-1}; // Don't use a pointer into the Technique vector to save us from implementing a copyctor
515 };
516 
517 class StdMeshMatManager
518 {
519 	friend class StdMeshMaterialUpdate;
520 private:
521 	typedef std::map<StdCopyStrBuf, StdMeshMaterial> MaterialMap;
522 
523 public:
524 	enum ShaderLoadFlag {
525 		SMM_AcceptExisting = 1,
526 		SMM_ForceReload = 2
527 	};
528 
529 	class Iterator
530 	{
531 		friend class StdMeshMatManager;
532 	public:
Iterator(const MaterialMap::iterator & iter)533 		Iterator(const MaterialMap::iterator& iter): iter_(iter) {}
534 		Iterator(const Iterator& iter) = default;
535 
536 		Iterator operator=(const Iterator& iter) { iter_ = iter.iter_; return *this; }
537 		Iterator& operator++() { ++iter_; return *this; }
538 		bool operator==(const Iterator& other) const { return iter_ == other.iter_; }
539 		bool operator!=(const Iterator& other) const { return iter_ != other.iter_; }
540 
541 		const StdMeshMaterial& operator*() const { return iter_->second; }
542 		const StdMeshMaterial* operator->() const { return &iter_->second; }
543 	private:
544 		MaterialMap::iterator iter_;
545 	};
546 
547 	// Remove all materials from manager. Make sure there is no StdMesh
548 	// referencing any out there before calling this.
549 	void Clear();
550 
551 	// Parse a material script file, and add the materials to the manager.
552 	// filename may be nullptr if the source is not a file. It will only be used
553 	// for error messages.
554 	// Throws StdMeshMaterialError.
555 	// Returns a set of all loaded materials.
556 	std::set<StdCopyStrBuf> Parse(const char* mat_script, const char* filename, StdMeshMaterialLoader& loader);
557 
558 	// Get material by name. nullptr if there is no such material with this name.
559 	const StdMeshMaterial* GetMaterial(const char* material_name) const;
560 
Begin()561 	Iterator Begin() { return Iterator(Materials.begin()); }
End()562 	Iterator End() { return Iterator(Materials.end()); }
563 	void Remove(const StdStrBuf& name, class StdMeshMaterialUpdate* update);
564 	Iterator Remove(const Iterator& iter, class StdMeshMaterialUpdate* update);
565 
566 	const StdMeshMaterialShader* AddShader(const char* filename, const char* name, const char* language, StdMeshMaterialShaderType type, const char* text, uint32_t load_flags); // if load_flags & SMM_AcceptExisting, the function returns the existing shader, otherwise returns nullptr.
567 	const StdMeshMaterialProgram* AddProgram(const char* name, StdMeshMaterialLoader& loader, const StdMeshMaterialPass::ShaderInstance& fragment_shader, const StdMeshMaterialPass::ShaderInstance& vertex_shader, const StdMeshMaterialPass::ShaderInstance& geometry_shader); // returns nullptr if shader code cannot be compiled
568 
569 	const StdMeshMaterialShader* GetFragmentShader(const char* name) const;
570 	const StdMeshMaterialShader* GetVertexShader(const char* name) const;
571 	const StdMeshMaterialShader* GetGeometryShader(const char* name) const;
572 private:
573 	MaterialMap Materials;
574 
575 	// Shader code for custom shaders.
576 	typedef std::map<StdCopyStrBuf, std::unique_ptr<StdMeshMaterialShader>> ShaderMap;
577 	ShaderMap FragmentShaders;
578 	ShaderMap VertexShaders;
579 	ShaderMap GeometryShaders;
580 
581 	// Linked programs
582 	typedef std::map<std::tuple<const StdMeshMaterialShader*, const StdMeshMaterialShader*, const StdMeshMaterialShader*>, std::unique_ptr<StdMeshMaterialProgram> > ProgramMap;
583 	ProgramMap Programs;
584 };
585 
586 extern StdMeshMatManager MeshMaterialManager;
587 
588 #endif
589