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