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  : LastRefresh()
47 {
48 
49 }
50 
52 {
53  Clear();
54 }
55 
56 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 
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 
70 void C4Shader::AddVertexSlice(int iPos, const char *szText)
71 {
72  AddSlice(VertexSlices, iPos, szText, nullptr, 0, 0);
73 }
74 
75 void C4Shader::AddFragmentSlice(int iPos, const char *szText)
76 {
77  AddSlice(FragmentSlices, iPos, szText, nullptr, 0, 0);
78 }
79 
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 
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 
90 bool C4Shader::LoadFragmentSlices(C4GroupSet *pGroups, const char *szFile)
91 {
92  return LoadSlices(FragmentSlices, pGroups, szFile);
93 }
94 
95 bool C4Shader::LoadVertexSlices(C4GroupSet *pGroups, const char *szFile)
96 {
97  return LoadSlices(VertexSlices, pGroups, szFile);
98 }
99 
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 
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 
116 void C4Shader::LoadScriptSlice(int id)
117 {
118  auto& s = ScriptShader.shaders.at(id);
119  switch (s.type)
120  {
122  AddVertexSlices(Name.getData(), s.source.c_str(), FormatString("[script %d]", id).getData());
123  break;
125  AddFragmentSlices(Name.getData(), s.source.c_str(), FormatString("[script %d]", id).getData());
126  break;
127  }
128 }
129 
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 
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;
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
212  if(*pPos != ')') { pPos++; continue; }
213  pPos++;
214 
215  // Make sure an opening brace follows
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 
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 
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 
323 {
324  VertexSlices.clear();
325  FragmentSlices.clear();
326  iTexCoords = 0;
327  // Script slices
328  ScriptSlicesLoaded = false;
329  Categories.clear();
330  ScriptShaders.clear();
331 }
332 
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 
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 
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 
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
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 
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 
664 
665 #ifndef USE_CONSOLE
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 
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 
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
718 
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 
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 
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 
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 
821 {
822  uniformStack = std::stack<UniformMap>();
823  uniformStack.emplace();
824 }
825 
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 }
char * GetFilename(char *szPath)
Definition: StdFile.cpp:45
const char * getData() const
Definition: StdBuf.h:442
bool ShaderLog(const char *szMessage)
Definition: C4Log.cpp:341
void AddFragmentSlices(const char *szWhat, const char *szText, const char *szSource="", int iFileTime=0)
Definition: C4Shader.cpp:85
int32_t DebugOpenGL
Definition: C4Config.h:117
void SetUniform1i(int iUniform, int iX) const
Definition: C4Shader.h:209
const int C4Shader_Version
Definition: C4Shader.h:36
C4Config Config
Definition: C4Config.cpp:833
const int C4Shader_PositionInit
Definition: C4Shader.h:45
const int C4Shader_PositionColor
Definition: C4Shader.h:51
void SetScriptCategories(const std::vector< std::string > &categories)
Definition: C4Shader.cpp:100
bool GetProperty(C4PropertyName k, C4Value *pResult) const
Definition: C4PropList.h:101
int SGetLine(const char *szText, const char *cpPosition)
Definition: Standard.cpp:473
void AddFragmentSlice(int iPos, const char *szText)
Definition: C4Shader.cpp:75
void AddVertexSlices(const char *szWhat, const char *szText, const char *szSource="", int iFileTime=0)
Definition: C4Shader.cpp:80
bool LoadFragmentSlices(C4GroupSet *pGroupSet, const char *szFile)
Definition: C4Shader.cpp:90
const char * Name
Definition: C4Shader.cpp:26
void Apply(C4ShaderCall &call)
Definition: C4Shader.cpp:826
void AddDefine(const char *name)
Definition: C4Shader.cpp:63
const int C4Shader_Vertex_PositionPos
Definition: C4Shader.h:59
const int C4Shader_PositionLight
Definition: C4Shader.h:50
void Format(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:174
const int C4Shader_Vertex_NormalPos
Definition: C4Shader.h:57
const int C4Shader_Vertex_ColorPos
Definition: C4Shader.h:58
void ClearSlices()
Definition: C4Shader.cpp:322
#define _MAX_PATH
bool SEqual(const char *szStr1, const char *szStr2)
Definition: Standard.h:93
const int C4Shader_PositionTexture
Definition: C4Shader.h:47
void AppendFormat(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:190
bool GetParentPath(const char *szFilename, char *szBuffer)
Definition: StdFile.cpp:189
void AppendChar(char cChar)
Definition: StdBuf.h:588
C4ConfigGraphics Graphics
Definition: C4Config.h:253
int FileTime(const char *fname)
bool Refresh()
Definition: C4Shader.cpp:443
static bool IsLogging()
Definition: C4Shader.cpp:663
C4Shader()
Definition: C4Shader.cpp:45
StdStrBuf GetFullName() const
Definition: C4Group.cpp:2078
void Clear()
Definition: C4Shader.cpp:333
bool Open(const char *szGroupName, bool fCreate=false)
Definition: C4Group.cpp:514
C4Group * FindEntry(const char *szWildcard, int32_t *pPriority=nullptr, int32_t *pID=nullptr)
Definition: C4GroupSet.cpp:175
const int C4Shader_PositionMaterial
Definition: C4Shader.h:48
~C4Shader()
Definition: C4Shader.cpp:51
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:519
bool Remove(int id)
Definition: C4Shader.cpp:740
const int C4Shader_PositionNormal
Definition: C4Shader.h:49
void Finish()
Definition: C4Shader.cpp:703
const int C4ShaderCall_MaxUnits
Definition: C4Shader.h:42
bool Close()
Definition: C4Group.cpp:755
bool LoadVertexSlices(C4GroupSet *pGroupSet, const char *szFile)
Definition: C4Shader.cpp:95
bool HaveUniform(int iUniform) const
Definition: C4Shader.h:122
const int C4Shader_Vertex_TexCoordPos
Definition: C4Shader.h:56
const int C4Shader_PositionFinish
Definition: C4Shader.h:52
bool FileExists(const char *szFileName)
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:346
C4ScriptShader ScriptShader
Definition: C4Shader.cpp:717
#define SKIP_WHITESPACE
bool ShaderLogF(const char *strMessage...)
Definition: C4Log.cpp:354
std::set< int > GetShaderIDs(const std::vector< std::string > &cats)
Definition: C4Shader.cpp:719
bool SEqual2(const char *szStr1, const char *szStr2)
Definition: Standard.cpp:198
GLint AllocTexUnit(int iUniform)
Definition: C4Shader.cpp:666
void ObjectLabel(uint32_t identifier, uint32_t name, int32_t length, const char *label)
Definition: C4DrawGL.cpp:274
size_t getLength() const
Definition: StdBuf.h:445
CStdGL * pGL
Definition: C4DrawGL.cpp:905
const char * AtRelativePath(const char *szFilename)
Definition: C4Config.cpp:656
#define DirSep
void Start()
Definition: C4Shader.cpp:689
const uint32_t C4Shader_RefreshInterval
Definition: C4Shader.cpp:23
int Add(const std::string &shaderName, ShaderType type, const std::string &source)
Definition: C4Shader.cpp:728
uint32_t AsInt() const
const int C4Shader_PositionCoordinate
Definition: C4Shader.h:46
const int C4Shader_LastPosition
Definition: C4Shader.h:53
void Copy()
Definition: StdBuf.h:467
#define s
void AddVertexSlice(int iPos, const char *szText)
Definition: C4Shader.cpp:70
C4Application Application
Definition: C4Globals.cpp:44
static C4TimeMilliseconds Now()
std::unique_ptr< Popper > Push(C4PropList *proplist)
Definition: C4Shader.cpp:755
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270