1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2014-2016, The OpenClonk Team and contributors
5  *
6  * Distributed under the terms of the ISC license; see accompanying file
7  * "COPYING" for details.
8  *
9  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10  * See accompanying file "TRADEMARK" for details.
11  *
12  * To redistribute this file separately, substitute the full license texts
13  * for the above references.
14  */
15 
16 #include "C4Include.h"
17 #include "C4ForbidLibraryCompilation.h"
18 #include "graphics/C4Shader.h"
19 #include "game/C4Application.h"
20 #include "graphics/C4DrawGL.h"
21 
22 // How often we check whether shader files got updated
23 const uint32_t C4Shader_RefreshInterval = 1000; // ms
24 
25 struct C4ShaderPosName {
26 	int Position; const char *Name;
27 };
28 
29 C4ShaderPosName C4SH_PosNames[] = {
30 	{ C4Shader_PositionInit,		 "init" },
31 	{ C4Shader_PositionCoordinate,	 "coordinate" },
32 	{ C4Shader_PositionTexture,		 "texture" },
33 	{ C4Shader_PositionMaterial,	 "material" },
34 	{ C4Shader_PositionNormal,		 "normal" },
35 	{ C4Shader_PositionLight,		 "light" },
36 	{ C4Shader_PositionColor,		 "color" },
37 	{ C4Shader_PositionFinish,		 "finish" },
38 
39 	{ C4Shader_Vertex_TexCoordPos,	        "texcoord" },
40 	{ C4Shader_Vertex_NormalPos,            "normal" },
41 	{ C4Shader_Vertex_ColorPos,             "color" },
42 	{ C4Shader_Vertex_PositionPos,          "position" }
43 };
44 
C4Shader()45 C4Shader::C4Shader()
46 	: LastRefresh()
47 {
48 
49 }
50 
~C4Shader()51 C4Shader::~C4Shader()
52 {
53 	Clear();
54 }
55 
GetSourceFileId(const char * file) const56 int C4Shader::GetSourceFileId(const char *file) const
57 {
58 	auto it = std::find(SourceFiles.begin(), SourceFiles.end(), file);
59 	if (it == SourceFiles.end()) return -1;
60 	return std::distance(SourceFiles.begin(), it);
61 }
62 
AddDefine(const char * name)63 void C4Shader::AddDefine(const char* name)
64 {
65 	StdStrBuf define = FormatString("#define %s", name);
66 	AddVertexSlice(-1, define.getData());
67 	AddFragmentSlice(-1, define.getData());
68 }
69 
AddVertexSlice(int iPos,const char * szText)70 void C4Shader::AddVertexSlice(int iPos, const char *szText)
71 {
72 	AddSlice(VertexSlices, iPos, szText, nullptr, 0, 0);
73 }
74 
AddFragmentSlice(int iPos,const char * szText)75 void C4Shader::AddFragmentSlice(int iPos, const char *szText)
76 {
77 	AddSlice(FragmentSlices, iPos, szText, nullptr, 0, 0);
78 }
79 
AddVertexSlices(const char * szWhat,const char * szText,const char * szSource,int iSourceTime)80 void C4Shader::AddVertexSlices(const char *szWhat, const char *szText, const char *szSource, int iSourceTime)
81 {
82 	AddSlices(VertexSlices, szWhat, szText, szSource, iSourceTime);
83 }
84 
AddFragmentSlices(const char * szWhat,const char * szText,const char * szSource,int iSourceTime)85 void C4Shader::AddFragmentSlices(const char *szWhat, const char *szText, const char *szSource, int iSourceTime)
86 {
87 	AddSlices(FragmentSlices, szWhat, szText, szSource, iSourceTime);
88 }
89 
LoadFragmentSlices(C4GroupSet * pGroups,const char * szFile)90 bool C4Shader::LoadFragmentSlices(C4GroupSet *pGroups, const char *szFile)
91 {
92 	return LoadSlices(FragmentSlices, pGroups, szFile);
93 }
94 
LoadVertexSlices(C4GroupSet * pGroups,const char * szFile)95 bool C4Shader::LoadVertexSlices(C4GroupSet *pGroups, const char *szFile)
96 {
97 	return LoadSlices(VertexSlices, pGroups, szFile);
98 }
99 
SetScriptCategories(const std::vector<std::string> & categories)100 void C4Shader::SetScriptCategories(const std::vector<std::string>& categories)
101 {
102 	assert(!ScriptSlicesLoaded && "Can't change shader categories after initialization");
103 	Categories = categories;
104 }
105 
LoadScriptSlices()106 void C4Shader::LoadScriptSlices()
107 {
108 	ScriptShaders = ScriptShader.GetShaderIDs(Categories);
109 	for (auto& id : ScriptShaders)
110 	{
111 		LoadScriptSlice(id);
112 	}
113 	ScriptSlicesLoaded = true;
114 }
115 
LoadScriptSlice(int id)116 void C4Shader::LoadScriptSlice(int id)
117 {
118 	auto& s = ScriptShader.shaders.at(id);
119 	switch (s.type)
120 	{
121 	case C4ScriptShader::VertexShader:
122 		AddVertexSlices(Name.getData(), s.source.c_str(), FormatString("[script %d]", id).getData());
123 		break;
124 	case C4ScriptShader::FragmentShader:
125 		AddFragmentSlices(Name.getData(), s.source.c_str(), FormatString("[script %d]", id).getData());
126 		break;
127 	}
128 }
129 
AddSlice(ShaderSliceList & slices,int iPos,const char * szText,const char * szSource,int line,int iSourceTime)130 void C4Shader::AddSlice(ShaderSliceList& slices, int iPos, const char *szText, const char *szSource, int line, int iSourceTime)
131 {
132 	ShaderSlice Slice;
133 	Slice.Position = iPos;
134 	Slice.Text.Copy(szText);
135 	Slice.Source = szSource;
136 	Slice.SourceTime = iSourceTime;
137 	Slice.SourceLine = line;
138 	slices.push_back(Slice);
139 }
140 
AddSlices(ShaderSliceList & slices,const char * szWhat,const char * szText,const char * szSource,int iSourceTime)141 void C4Shader::AddSlices(ShaderSliceList& slices, const char *szWhat, const char *szText, const char *szSource, int iSourceTime)
142 {
143 	if (std::find(SourceFiles.cbegin(), SourceFiles.cend(), szSource) == SourceFiles.cend())
144 		SourceFiles.emplace_back(szSource);
145 
146 	const char *pStart = szText, *pPos = szText;
147 	int iDepth = -1;
148 	int iPosition = -1;
149 	bool fGotContent = false; // Anything in the slice apart from comments and white-space?
150 
151 #define SKIP_WHITESPACE do { while(isspace(*pPos)) { ++pPos; } } while (0)
152 
153 	// Find slices
154 	while(*pPos) {
155 		// Comment? Might seem silly, but we don't want to get confused by braces in comments...
156 		if (*pPos == '/' && *(pPos + 1) == '/') {
157 			pPos += 2;
158 			while (*pPos && *pPos != '\n') pPos++;
159 			continue;
160 		}
161 		if (*pPos == '/' && *(pPos + 1) == '*') {
162 			pPos += 2;
163 			while (*pPos && (*pPos != '*' || *(pPos + 1) != '/'))
164 			{
165 				pPos++;
166 			}
167 			if (*pPos) pPos += 2;
168 			continue;
169 		}
170 
171 		// Opening brace?
172 		if (*pPos == '{') {
173 			iDepth++; pPos++;
174 			continue;
175 		}
176 		if (*pPos == '}') {
177 			// End of slice?
178 			if (iPosition != -1 && !iDepth) {
179 
180 				// Have a new slice!
181 				if (fGotContent)
182 				{
183 					StdStrBuf Str; Str.Copy(pStart, pPos - pStart);
184 					AddSlice(slices, iPosition, Str.getData(), szSource, SGetLine(szText, pStart), iSourceTime);
185 				}
186 
187 				iPosition = -1;
188 				pStart = pPos+1;
189 				fGotContent = false;
190 			}
191 			if (iDepth >= 0)
192 				iDepth--;
193 			pPos++;
194 			continue;
195 		}
196 
197 		// New slice? We need a newline followed by "slice". Don't do
198 		// the depth check, so that we also recognize slices inside
199 		// an ifdefed-out "void main() {" block.
200 		if (*pPos == '\n') {
201 			if (SEqual2(pPos+1, "slice") && !isalnum(*(pPos+6))) {
202 				const char *pSliceEnd = pPos; pPos += 6;
203 				SKIP_WHITESPACE;
204 				if(*pPos != '(') { pPos++; continue; }
205 				pPos++;
206 
207 				// Now let's parse the position
208 				iPosition = ParsePosition(szWhat, &pPos);
209 				if (iPosition != -1) {
210 					// Make sure a closing parenthesis
211 					SKIP_WHITESPACE;
212 					if(*pPos != ')') { pPos++; continue; }
213 					pPos++;
214 
215 					// Make sure an opening brace follows
216 					SKIP_WHITESPACE;
217 					if (*pPos == '{') {
218 
219 						// Add code before "slice" as new slice
220 						if (fGotContent)
221 						{
222 							StdStrBuf Str; Str.Copy(pStart, pSliceEnd - pStart);
223 							AddSlice(slices, -1, Str.getData(), szSource, SGetLine(szText, pSliceEnd), iSourceTime);
224 						}
225 
226 						iDepth = 0;
227 						pStart = pPos+1;
228 						fGotContent = false;
229 					} else {
230 						ShaderLogF("  gl: Missing opening brace in %s!", szWhat);
231 					}
232 					pPos++;
233 					continue;
234 				}
235 			}
236 		}
237 
238 		// Otherwise: Continue
239 		if (!isspace(*pPos)) fGotContent = true;
240 		pPos++;
241 	}
242 
243 	// Add final slice
244 	if (fGotContent)
245 	{
246 		StdStrBuf Str; Str.Copy(pStart, pPos - pStart);
247 		AddSlice(slices, iPosition, Str.getData(), szSource, SGetLine(szText, pStart), iSourceTime);
248 	}
249 #undef SKIP_WHITESPACE
250 }
251 
ParsePosition(const char * szWhat,const char ** ppPos)252 int C4Shader::ParsePosition(const char *szWhat, const char **ppPos)
253 {
254 	const char *pPos = *ppPos;
255 	while (isspace(*pPos)) pPos++;
256 
257 	// Expect a name
258 	const char *pStart = pPos;
259 	while (isalnum(*pPos)) pPos++;
260 	StdStrBuf Name; Name.Copy(pStart, pPos - pStart);
261 
262 	// Lookup name
263 	int iPosition = -1;
264 	for (auto & PosName : C4SH_PosNames) {
265 		if (SEqual(Name.getData(), PosName.Name)) {
266 			iPosition = PosName.Position;
267 			break;
268 		}
269 	}
270 	if (iPosition == -1) {
271 		ShaderLogF("  gl: Unknown slice position in %s: %s", szWhat, Name.getData());
272 		return -1;
273 	}
274 
275 	// Add modifier
276 	while (isspace(*pPos)) pPos++;
277 	if (*pPos == '+') {
278 		int iMod, iModLen;
279 		if (!sscanf(pPos+1, "%d%n", &iMod, &iModLen)) {
280 			ShaderLogF("  gl: Invalid slice modifier in %s", szWhat);
281 			return -1;
282 		}
283 		iPosition += iMod;
284 		pPos += 1+iModLen;
285 	}
286 	if (*pPos == '-') {
287 		int iMod, iModLen;
288 		if (!sscanf(pPos+1, "%d%n", &iMod, &iModLen)) {
289 			ShaderLogF("  gl: Invalid slice modifier in %s", szWhat);
290 			return -1;
291 		}
292 		iPosition -= iMod;
293 		pPos += 1+iModLen;
294 	}
295 
296 	// Everything okay!
297 	*ppPos = pPos;
298 	return iPosition;
299 }
300 
LoadSlices(ShaderSliceList & slices,C4GroupSet * pGroups,const char * szFile)301 bool C4Shader::LoadSlices(ShaderSliceList& slices, C4GroupSet *pGroups, const char *szFile)
302 {
303 	// Search for our shaders
304 	C4Group *pGroup = pGroups->FindEntry(szFile);
305 	if(!pGroup) return false;
306 	// Load it, save the path for later reloading
307 	StdStrBuf Shader;
308 	if(!pGroup->LoadEntryString(szFile, &Shader))
309 		return false;
310 	// If it physically exists, save back creation time so we
311 	// can automatically reload it if it changes
312 	StdStrBuf Source = FormatString("%s" DirSep "%s", pGroup->GetFullName().getData(), szFile);
313 	int iSourceTime = 0;
314 	if(FileExists(Source.getData()))
315 		iSourceTime = FileTime(Source.getData());
316 	// Load
317 	StdStrBuf What = FormatString("file %s", Config.AtRelativePath(Source.getData()));
318 	AddSlices(slices, What.getData(), Shader.getData(), Source.getData(), iSourceTime);
319 	return true;
320 }
321 
ClearSlices()322 void C4Shader::ClearSlices()
323 {
324 	VertexSlices.clear();
325 	FragmentSlices.clear();
326 	iTexCoords = 0;
327 	// Script slices
328 	ScriptSlicesLoaded = false;
329 	Categories.clear();
330 	ScriptShaders.clear();
331 }
332 
Clear()333 void C4Shader::Clear()
334 {
335 #ifndef USE_CONSOLE
336 	if (!hProg) return;
337 	// Need to be detached, then deleted
338 	glDeleteProgram(hProg);
339 	hProg = 0;
340 	// Clear uniform data
341 	Uniforms.clear();
342 	Attributes.clear();
343 #endif
344 }
345 
Init(const char * szWhat,const char ** szUniforms,const char ** szAttributes)346 bool C4Shader::Init(const char *szWhat, const char **szUniforms, const char **szAttributes)
347 {
348 	Name.Copy(szWhat);
349 	LastRefresh = C4TimeMilliseconds::Now();
350 
351 	if (!ScriptSlicesLoaded)
352 	{
353 		Categories.emplace_back(szWhat);
354 		LoadScriptSlices();
355 	}
356 
357 	StdStrBuf VertexShader = Build(VertexSlices, true),
358 		FragmentShader = Build(FragmentSlices, true);
359 
360 	// Dump
361 	if (C4Shader::IsLogging())
362 	{
363 		ShaderLogF("******** Vertex shader for %s:", szWhat);
364 		ShaderLog(VertexShader.getData());
365 		ShaderLogF("******** Fragment shader for %s:", szWhat);
366 		ShaderLog(FragmentShader.getData());
367 	}
368 
369 #ifndef USE_CONSOLE
370 	// Attempt to create shaders
371 	const GLuint hVert = Create(GL_VERTEX_SHADER,
372 	                            FormatString("%s vertex shader", szWhat).getData(),
373 	                            VertexShader.getData());
374 	const GLuint hFrag = Create(GL_FRAGMENT_SHADER,
375 	                            FormatString("%s fragment shader", szWhat).getData(),
376 	                            FragmentShader.getData());
377 
378 	if(!hFrag || !hVert)
379 	{
380 		if (hFrag) glDeleteShader(hFrag);
381 		if (hVert) glDeleteShader(hVert);
382 		return false;
383 	}
384 
385 	// Link program
386 	const GLuint hNewProg = glCreateProgram();
387 	pGL->ObjectLabel(GL_PROGRAM, hNewProg, -1, szWhat);
388 	glAttachShader(hNewProg, hVert);
389 	glAttachShader(hNewProg, hFrag);
390 	glLinkProgram(hNewProg);
391 	// Delete vertex and fragment shader after we linked the program
392 	glDeleteShader(hFrag);
393 	glDeleteShader(hVert);
394 
395 	// Link successful?
396 	DumpInfoLog(FormatString("%s shader program", szWhat).getData(), hNewProg, true);
397 	GLint status;
398 	glGetProgramiv(hNewProg, GL_LINK_STATUS, &status);
399 	if(status != GL_TRUE) {
400 		glDeleteProgram(hNewProg);
401 		ShaderLogF("  gl: Failed to link %s shader!", szWhat);
402 		return false;
403 	}
404 	ShaderLogF("  gl: %s shader linked successfully", szWhat);
405 
406 	// Everything successful, delete old shader
407 	if (hProg != 0) glDeleteProgram(hProg);
408 	hProg = hNewProg;
409 
410 	// Allocate uniform and attribute arrays
411 	int iUniformCount = 0;
412 	if (szUniforms != nullptr)
413 		while (szUniforms[iUniformCount])
414 			iUniformCount++;
415 	Uniforms.resize(iUniformCount);
416 
417 	int iAttributeCount = 0;
418 	if (szAttributes != nullptr)
419 		while (szAttributes[iAttributeCount])
420 			iAttributeCount++;
421 	Attributes.resize(iAttributeCount);
422 
423 	// Get uniform and attribute locations. Note this is expected to fail for a few of them
424 	// because the respective uniforms got optimized out!
425 	for (int i = 0; i < iUniformCount; i++) {
426 		Uniforms[i].address = glGetUniformLocation(hProg, szUniforms[i]);
427 		Uniforms[i].name = szUniforms[i];
428 		ShaderLogF("Uniform %s = %d", szUniforms[i], Uniforms[i].address);
429 	}
430 
431 	for (int i = 0; i < iAttributeCount; i++) {
432 		Attributes[i].address = glGetAttribLocation(hProg, szAttributes[i]);
433 		Attributes[i].name = szAttributes[i];
434 		ShaderLogF("Attribute %s = %d", szAttributes[i], Attributes[i].address);
435 	}
436 
437 #endif
438 
439 	return true;
440 }
441 
442 
Refresh()443 bool C4Shader::Refresh()
444 {
445 	// Update last refresh. Keep a local copy around though to identify added script shaders.
446 	LastRefresh = C4TimeMilliseconds::Now();
447 
448 	auto next = ScriptShader.GetShaderIDs(Categories);
449 	std::set<int> toAdd, toRemove;
450 	std::set_difference(ScriptShaders.begin(), ScriptShaders.end(), next.begin(), next.end(), std::inserter(toRemove, toRemove.end()));
451 	std::set_difference(next.begin(), next.end(), ScriptShaders.begin(), ScriptShaders.end(), std::inserter(toAdd, toAdd.end()));
452 	ScriptShaders = next;
453 
454 	auto removeSlices = [&](ShaderSliceList::iterator& pSlice)
455 	{
456 		StdCopyStrBuf Source = pSlice->Source;
457 
458 		// Okay, remove all slices that came from this file
459 		ShaderSliceList::iterator pNext;
460 		for (; pSlice != FragmentSlices.end(); pSlice = pNext)
461 		{
462 			pNext = pSlice; pNext++;
463 			if (SEqual(pSlice->Source.getData(), Source.getData()))
464 				FragmentSlices.erase(pSlice);
465 		}
466 	};
467 
468 	// Find slices where the source file has updated.
469 	std::vector<StdCopyStrBuf> sourcesToUpdate;
470 	for (ShaderSliceList::iterator pSlice = FragmentSlices.begin(); pSlice != FragmentSlices.end(); pSlice++)
471 		if (pSlice->Source.getLength())
472 		{
473 			if (pSlice->Source.BeginsWith("[script "))
474 			{
475 				// TODO: Maybe save id instead of parsing the string here.
476 				int sid = -1;
477 				sscanf(pSlice->Source.getData(), "[script %d", &sid);
478 				if (toRemove.find(sid) != toRemove.end())
479 					removeSlices(pSlice);
480 				// Note: script slices don't change, so we don't have to handle updates like for files.
481 			}
482 			else if (FileExists(pSlice->Source.getData()) &&
483 			         FileTime(pSlice->Source.getData()) > pSlice->SourceTime)
484 			{
485 				sourcesToUpdate.push_back(pSlice->Source);
486 				removeSlices(pSlice);
487 			}
488 		}
489 
490 	// Anything to do?
491 	if (toAdd.size() == 0 && toRemove.size() == 0 && sourcesToUpdate.size() == 0)
492 		return true;
493 
494 	// Process file reloading.
495 	for (auto& Source : sourcesToUpdate)
496 	{
497 		char szParentPath[_MAX_PATH+1]; C4Group Group;
498 		StdStrBuf Shader;
499 		GetParentPath(Source.getData(),szParentPath);
500 		if(!Group.Open(szParentPath) ||
501 		   !Group.LoadEntryString(GetFilename(Source.getData()),&Shader) ||
502 		   !Group.Close())
503 		{
504 			ShaderLogF("  gl: Failed to refresh %s shader from %s!", Name.getData(), Source.getData());
505 			return false;
506 		}
507 
508 		// Load slices
509 		int iSourceTime = FileTime(Source.getData());
510 		StdStrBuf WhatSrc = FormatString("file %s", Config.AtRelativePath(Source.getData()));
511 		AddFragmentSlices(WhatSrc.getData(), Shader.getData(), Source.getData(), iSourceTime);
512 	}
513 
514 	// Process new script slices.
515 	for (int id : toAdd)
516 	{
517 		LoadScriptSlice(id);
518 	}
519 
520 #ifndef USE_CONSOLE
521 	std::vector<const char*> UniformNames(Uniforms.size() + 1);
522 	for (std::size_t i = 0; i < Uniforms.size(); ++i)
523 		UniformNames[i] = Uniforms[i].name;
524 	UniformNames[Uniforms.size()] = nullptr;
525 
526 	std::vector<const char*> AttributeNames(Attributes.size() + 1);
527 	for (std::size_t i = 0; i < Attributes.size(); ++i)
528 		AttributeNames[i] = Attributes[i].name;
529 	AttributeNames[Attributes.size()] = nullptr;
530 #endif
531 
532 	// Reinitialise
533 	StdCopyStrBuf What(Name);
534 	if (!Init(What.getData(),
535 #ifndef USE_CONSOLE
536 		&UniformNames[0],
537 		&AttributeNames[0]
538 #else
539 		nullptr,
540 		nullptr
541 #endif
542 		))
543 		return false;
544 
545 	return true;
546 }
547 
Build(const ShaderSliceList & Slices,bool fDebug)548 StdStrBuf C4Shader::Build(const ShaderSliceList &Slices, bool fDebug)
549 {
550 	// At the start of the shader set the #version and number of
551 	// available uniforms
552 	StdStrBuf Buf;
553 #ifndef USE_CONSOLE
554 	GLint iMaxFrags = 0, iMaxVerts = 0;
555 	glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &iMaxFrags);
556 	glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &iMaxVerts);
557 #else
558 	int iMaxFrags = INT_MAX, iMaxVerts = INT_MAX;
559 #endif
560 	Buf.Format("#version %d\n"
561 	           "#define MAX_FRAGMENT_UNIFORM_COMPONENTS %d\n"
562 	           "#define MAX_VERTEX_UNIFORM_COMPONENTS %d\n",
563 	           C4Shader_Version, iMaxFrags, iMaxVerts);
564 
565 	// Put slices
566 	int iPos = -1, iNextPos = -1;
567 	do
568 	{
569 		iPos = iNextPos; iNextPos = C4Shader_LastPosition+1;
570 		// Add all slices at the current level
571 		if (fDebug && iPos > 0)
572 			Buf.AppendFormat("\t// Position %d:\n", iPos);
573 		for (const auto & Slice : Slices)
574 		{
575 			if (Slice.Position < iPos) continue;
576 			if (Slice.Position > iPos)
577 			{
578 				iNextPos = std::min(iNextPos, Slice.Position);
579 				continue;
580 			}
581 			// Same position - add slice!
582 			if (fDebug)
583 			{
584 				if (Slice.Source.getLength())
585 				{
586 					// GLSL below 3.30 consider the line after a #line N directive to be N + 1; 3.30 and higher consider it N
587 					Buf.AppendFormat("\t// Slice from %s:\n#line %d %d\n", Slice.Source.getData(), Slice.SourceLine - (C4Shader_Version < 330), GetSourceFileId(Slice.Source.getData()) + 1);
588 				}
589 				else
590 					Buf.Append("\t// Built-in slice:\n#line 1 0\n");
591 				}
592 			Buf.Append(Slice.Text);
593 			if (Buf[Buf.getLength()-1] != '\n')
594 				Buf.AppendChar('\n');
595 		}
596 		// Add seperator - only priority (-1) is top-level
597 		if (iPos == -1) {
598 			Buf.Append("void main() {\n");
599 		}
600 	}
601 	while (iNextPos <= C4Shader_LastPosition);
602 
603 	// Terminate
604 	Buf.Append("}\n");
605 
606 	Buf.Append("// File number to name mapping:\n//\t  0: <built-in shader code>\n");
607 	for (int i = 0; i < SourceFiles.size(); ++i)
608 		Buf.AppendFormat("//\t%3d: %s\n", i + 1, SourceFiles[i].c_str());
609 	return Buf;
610 }
611 
612 #ifndef USE_CONSOLE
Create(GLenum iShaderType,const char * szWhat,const char * szShader)613 GLuint C4Shader::Create(GLenum iShaderType, const char *szWhat, const char *szShader)
614 {
615 	// Create shader
616 	GLuint hShader = glCreateShader(iShaderType);
617 	pGL->ObjectLabel(GL_SHADER, hShader, -1, szWhat);
618 
619 	// Compile
620 	glShaderSource(hShader, 1, &szShader, nullptr);
621 	glCompileShader(hShader);
622 
623 	// Dump any information to log
624 	DumpInfoLog(szWhat, hShader, false);
625 
626 	// Success?
627 	int status;
628 	glGetShaderiv(hShader, GL_COMPILE_STATUS, &status);
629 	if (status == GL_TRUE)
630 		return hShader;
631 
632 	// Did not work :/
633 	glDeleteShader(hShader);
634 	return 0;
635 }
636 
DumpInfoLog(const char * szWhat,GLuint hShader,bool forProgram)637 void C4Shader::DumpInfoLog(const char *szWhat, GLuint hShader, bool forProgram)
638 {
639 	// Get length of info line
640 	GLint iLength = 0;
641 	if (forProgram)
642 		glGetProgramiv(hShader, GL_INFO_LOG_LENGTH, &iLength);
643 	else
644 		glGetShaderiv(hShader, GL_INFO_LOG_LENGTH, &iLength);
645 	if(iLength <= 1) return;
646 
647 	// Allocate buffer, get data
648 	std::vector<char> buf(iLength + 1);
649 	int iActualLength = 0;
650 	if (forProgram)
651 		glGetProgramInfoLog(hShader, iLength, &iActualLength, &buf[0]);
652 	else
653 		glGetShaderInfoLog(hShader, iLength, &iActualLength, &buf[0]);
654 	if(iActualLength > iLength || iActualLength <= 0) return;
655 
656 	// Terminate, log
657 	buf[iActualLength] = '\0';
658 	ShaderLogF("  gl: Compiling %s:", szWhat);
659 	ShaderLog(&buf[0]);
660 }
661 #endif
662 
IsLogging()663 bool C4Shader::IsLogging() { return Config.Graphics.DebugOpenGL != 0 || !!Application.isEditor; }
664 
665 #ifndef USE_CONSOLE
AllocTexUnit(int iUniform)666 GLint C4ShaderCall::AllocTexUnit(int iUniform)
667 {
668 
669 	// Want to bind uniform automatically? If not, the caller will take
670 	// care of it.
671 	if (iUniform >= 0) {
672 
673 		// If uniform isn't used, we should skip this. Also check texunit range.
674 		if (!pShader->HaveUniform(iUniform)) return 0;
675 		assert(iUnits < C4ShaderCall_MaxUnits);
676 		if (iUnits >= C4ShaderCall_MaxUnits) return 0;
677 
678 		// Set the uniform
679 		SetUniform1i(iUniform, iUnits);
680 	}
681 
682 	// Activate the texture
683 	GLint hTex = GL_TEXTURE0 + iUnits;
684 	glActiveTexture(hTex);
685 	iUnits++;
686 	return hTex;
687 }
688 
Start()689 void C4ShaderCall::Start()
690 {
691 	assert(!fStarted);
692 	assert(pShader->hProg != 0); // Shader must be initialized
693 
694 	// Possibly refresh shader
695 	if (ScriptShader.LastUpdate > pShader->LastRefresh || C4TimeMilliseconds::Now() > pShader->LastRefresh + C4Shader_RefreshInterval)
696 		const_cast<C4Shader *>(pShader)->Refresh();
697 
698 	// Activate shader
699 	glUseProgram(pShader->hProg);
700 	fStarted = true;
701 }
702 
Finish()703 void C4ShaderCall::Finish()
704 {
705 	// Remove shader
706 	if (fStarted) {
707 		glUseProgram(0);
708 	}
709 
710 	iUnits = 0;
711 	fStarted = false;
712 }
713 
714 #endif
715 
716 // global instance
717 C4ScriptShader ScriptShader;
718 
GetShaderIDs(const std::vector<std::string> & cats)719 std::set<int> C4ScriptShader::GetShaderIDs(const std::vector<std::string>& cats)
720 {
721 	std::set<int> result;
722 	for (auto& cat : cats)
723 		for (auto& id : categories[cat])
724 			result.emplace(id);
725 	return result;
726 }
727 
Add(const std::string & shaderName,ShaderType type,const std::string & source)728 int C4ScriptShader::Add(const std::string& shaderName, ShaderType type, const std::string& source)
729 {
730 	int id = NextID++;
731 	LastUpdate = C4TimeMilliseconds::Now().AsInt();
732 	// Hack: Always prepend a newline as the slice parser doesn't recognize
733 	// slices that don't begin with a newline.
734 	auto nsource = "\n" + source;
735 	shaders.emplace(std::make_pair(id, ShaderInstance{type, nsource}));
736 	categories[shaderName].emplace(id);
737 	return id;
738 }
739 
Remove(int id)740 bool C4ScriptShader::Remove(int id)
741 {
742 	// We have to perform a rather inefficient full search. We'll have to see
743 	// whether this turns out to be a performance issue.
744 	if (shaders.erase(id))
745 	{
746 		for (auto& kv : categories)
747 			if (kv.second.erase(id))
748 				break; // each id can appear in one category only
749 		LastUpdate = C4TimeMilliseconds::Now().AsInt();
750 		return true;
751 	}
752 	return false;
753 }
754 
Push(C4PropList * proplist)755 std::unique_ptr<C4ScriptUniform::Popper> C4ScriptUniform::Push(C4PropList* proplist)
756 {
757 #ifdef USE_CONSOLE
758 	return std::unique_ptr<C4ScriptUniform::Popper>();
759 #else
760 	C4Value ulist;
761 	if (!proplist->GetProperty(P_Uniforms, &ulist) || ulist.GetType() != C4V_PropList)
762 		return std::unique_ptr<C4ScriptUniform::Popper>();
763 
764 	uniformStack.emplace();
765 	auto& uniforms = uniformStack.top();
766 	Uniform u;
767 	for (const C4Property* prop : *ulist.getPropList())
768 	{
769 		if (!prop->Key) continue;
770 		switch (prop->Value.GetType())
771 		{
772 		case C4V_Int:
773 			u.type = GL_INT;
774 			u.intVec[0] = prop->Value._getInt();
775 			break;
776 		case C4V_Array:
777 		{
778 			auto array = prop->Value._getArray();
779 			switch (array->GetSize())
780 			{
781 			case 1: u.type = GL_INT; break;
782 			case 2: u.type = GL_INT_VEC2; break;
783 			case 3: u.type = GL_INT_VEC3; break;
784 			case 4: u.type = GL_INT_VEC4; break;
785 			default: continue;
786 			}
787 			for (int32_t i = 0; i < array->GetSize(); i++)
788 			{
789 				auto& item = array->_GetItem(i);
790 				switch (item.GetType())
791 				{
792 				case C4V_Int:
793 					u.intVec[i] = item._getInt();
794 					break;
795 				default:
796 					goto skip;
797 				}
798 			}
799 			break;
800 		}
801 		default:
802 			continue;
803 		}
804 		// Uniform is now filled properly. Note that array contents are undefined for higher slots
805 		// when "type" only requires a smaller array.
806 		uniforms.insert({prop->Key->GetCStr(), u});
807 skip:;
808 	}
809 	// Debug
810 	/*
811 	for (auto& p : uniforms)
812 	{
813 		LogF("Uniform %s (type %d) = %d %d %d %d", p.first.c_str(), p.second.type, p.second.intVec[0], p.second.intVec[1], p.second.intVec[2], p.second.intVec[3]);
814 	}
815 	*/
816 	return std::make_unique<C4ScriptUniform::Popper>(this);
817 #endif
818 }
819 
Clear()820 void C4ScriptUniform::Clear()
821 {
822 	uniformStack = std::stack<UniformMap>();
823 	uniformStack.emplace();
824 }
825 
Apply(C4ShaderCall & call)826 void C4ScriptUniform::Apply(C4ShaderCall& call)
827 {
828 #ifndef USE_CONSOLE
829 	for (auto& p : uniformStack.top())
830 	{
831 		// The existing SetUniform* methods only work for pre-defined indexed uniforms. The script
832 		// uniforms are unknown at shader compile time, so we have to use OpenGL functions directly
833 		// here.
834 		GLint loc = glGetUniformLocation(call.pShader->hProg, p.first.c_str());
835 		// Is this uniform defined in the shader?
836 		if (loc == -1) continue;
837 		auto& intVec = p.second.intVec;
838 		switch (p.second.type)
839 		{
840 		case GL_INT:      glUniform1iv(loc, 1, intVec); break;
841 		case GL_INT_VEC2: glUniform2iv(loc, 1, intVec); break;
842 		case GL_INT_VEC3: glUniform3iv(loc, 1, intVec); break;
843 		case GL_INT_VEC4: glUniform4iv(loc, 1, intVec); break;
844 		default:
845 			assert(false && "unsupported uniform type");
846 		}
847 	}
848 #endif
849 }
850