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