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