783a032afd4330243c072c44e1d78ea06e1842ff
[occt.git] / src / OpenGl / OpenGl_ShaderObject.cxx
1 // Created on: 2013-09-19
2 // Created by: Denis BOGOLEPOV
3 // Copyright (c) 2013-2014 OPEN CASCADE SAS
4 //
5 // This file is part of Open CASCADE Technology software library.
6 //
7 // This library is free software; you can redistribute it and/or modify it under
8 // the terms of the GNU Lesser General Public License version 2.1 as published
9 // by the Free Software Foundation, with special exception defined in the file
10 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11 // distribution for complete text of the license and disclaimer of any warranty.
12 //
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
15
16 #include <Graphic3d_ShaderObject.hxx>
17 #include <Message_Messenger.hxx>
18 #include <OpenGl_Context.hxx>
19 #include <OpenGl_ShaderObject.hxx>
20 #include <OSD_File.hxx>
21 #include <OSD_Path.hxx>
22 #include <OSD_Process.hxx>
23 #include <OSD_Protection.hxx>
24 #include <Standard_Assert.hxx>
25 #include <TCollection_AsciiString.hxx>
26 #include <TCollection_ExtendedString.hxx>
27
28 #ifdef _WIN32
29   #include <malloc.h> // for alloca()
30 #endif
31
32 IMPLEMENT_STANDARD_RTTIEXT(OpenGl_ShaderObject,OpenGl_Resource)
33
34 //! Puts line numbers to the output of GLSL program source code.
35 static TCollection_AsciiString putLineNumbers (const TCollection_AsciiString& theSource)
36 {
37   std::stringstream aStream;
38   theSource.Print (aStream);
39   std::string aLine;
40   Standard_Integer aLineNumber = 1;
41   TCollection_AsciiString aResultSource;
42   while (std::getline (aStream, aLine))
43   {
44     TCollection_AsciiString anAsciiString = TCollection_AsciiString (aLine.c_str());
45     anAsciiString.Prepend (TCollection_AsciiString ("\n") + TCollection_AsciiString (aLineNumber) + ": ");
46     aResultSource += anAsciiString;
47     aLineNumber++;
48   }
49   return aResultSource;
50 }
51
52 // =======================================================================
53 // function : CreateFromSource
54 // purpose  :
55 // =======================================================================
56 Handle(Graphic3d_ShaderObject) OpenGl_ShaderObject::CreateFromSource (TCollection_AsciiString& theSource,
57                                                                       Graphic3d_TypeOfShaderObject theType,
58                                                                       const ShaderVariableList& theUniforms,
59                                                                       const ShaderVariableList& theStageInOuts,
60                                                                       const TCollection_AsciiString& theInName,
61                                                                       const TCollection_AsciiString& theOutName,
62                                                                       Standard_Integer theNbGeomInputVerts)
63 {
64   if (theSource.IsEmpty())
65   {
66     return Handle(Graphic3d_ShaderObject)();
67   }
68
69   TCollection_AsciiString aSrcUniforms, aSrcInOuts, aSrcInStructs, aSrcOutStructs;
70   for (ShaderVariableList::Iterator anUniformIter (theUniforms); anUniformIter.More(); anUniformIter.Next())
71   {
72     const ShaderVariable& aVar = anUniformIter.Value();
73     if ((aVar.Stages & theType) != 0)
74     {
75       aSrcUniforms += TCollection_AsciiString("\nuniform ") + aVar.Name + ";";
76     }
77   }
78   for (ShaderVariableList::Iterator aVarListIter (theStageInOuts); aVarListIter.More(); aVarListIter.Next())
79   {
80     const ShaderVariable& aVar = aVarListIter.Value();
81     Standard_Integer aStageLower = IntegerLast(), aStageUpper = IntegerFirst();
82     Standard_Integer aNbStages = 0;
83     for (Standard_Integer aStageIter = Graphic3d_TOS_VERTEX; aStageIter <= (Standard_Integer )Graphic3d_TOS_COMPUTE; aStageIter = aStageIter << 1)
84     {
85       if ((aVar.Stages & aStageIter) != 0)
86       {
87         ++aNbStages;
88         aStageLower = Min (aStageLower, aStageIter);
89         aStageUpper = Max (aStageUpper, aStageIter);
90       }
91     }
92     if ((Standard_Integer )theType < aStageLower
93      || (Standard_Integer )theType > aStageUpper)
94     {
95       continue;
96     }
97
98     const Standard_Boolean hasGeomStage = theNbGeomInputVerts > 0
99                                        && aStageLower <  Graphic3d_TOS_GEOMETRY
100                                        && aStageUpper >= Graphic3d_TOS_GEOMETRY;
101     const Standard_Boolean isAllStagesVar = aStageLower == Graphic3d_TOS_VERTEX
102                                          && aStageUpper == Graphic3d_TOS_FRAGMENT;
103     if (hasGeomStage
104     || !theInName.IsEmpty()
105     || !theOutName.IsEmpty())
106     {
107       if (aSrcInStructs.IsEmpty()
108        && aSrcOutStructs.IsEmpty()
109        && isAllStagesVar)
110       {
111         if (theType == aStageLower)
112         {
113           aSrcOutStructs = "\nout VertexData\n{";
114         }
115         else if (theType == aStageUpper)
116         {
117           aSrcInStructs = "\nin VertexData\n{";
118         }
119         else // requires theInName/theOutName
120         {
121           aSrcInStructs  = "\nin  VertexData\n{";
122           aSrcOutStructs = "\nout VertexData\n{";
123         }
124       }
125     }
126
127     if (isAllStagesVar
128      && (!aSrcInStructs.IsEmpty()
129       || !aSrcOutStructs.IsEmpty()))
130     {
131       if (!aSrcInStructs.IsEmpty())
132       {
133         aSrcInStructs  += TCollection_AsciiString("\n  ") + aVar.Name + ";";
134       }
135       if (!aSrcOutStructs.IsEmpty())
136       {
137         aSrcOutStructs += TCollection_AsciiString("\n  ") + aVar.Name + ";";
138       }
139     }
140     else
141     {
142       if (theType == aStageLower)
143       {
144         aSrcInOuts += TCollection_AsciiString("\nTHE_SHADER_OUT ") + aVar.Name + ";";
145       }
146       else if (theType == aStageUpper)
147       {
148         aSrcInOuts += TCollection_AsciiString("\nTHE_SHADER_IN ") + aVar.Name + ";";
149       }
150     }
151   }
152
153   if (theType == Graphic3d_TOS_GEOMETRY)
154   {
155     aSrcUniforms.Prepend (TCollection_AsciiString()
156                         + "\nlayout (triangles) in;"
157                           "\nlayout (triangle_strip, max_vertices = " + theNbGeomInputVerts + ") out;");
158   }
159   if (!aSrcInStructs.IsEmpty()
160    && theType == Graphic3d_TOS_GEOMETRY)
161   {
162     aSrcInStructs  += TCollection_AsciiString ("\n} ") + theInName  + "[" + theNbGeomInputVerts + "];";
163   }
164   else if (!aSrcInStructs.IsEmpty())
165   {
166     aSrcInStructs += "\n}";
167     if (!theInName.IsEmpty())
168     {
169       aSrcInStructs += " ";
170       aSrcInStructs += theInName;
171     }
172     aSrcInStructs += ";";
173   }
174   if (!aSrcOutStructs.IsEmpty())
175   {
176     aSrcOutStructs += "\n}";
177     if (!theOutName.IsEmpty())
178     {
179       aSrcOutStructs += " ";
180       aSrcOutStructs += theOutName;
181     }
182     aSrcOutStructs += ";";
183   }
184
185   theSource.Prepend (aSrcUniforms + aSrcInStructs + aSrcOutStructs + aSrcInOuts);
186   return Graphic3d_ShaderObject::CreateFromSource (theType, theSource);
187 }
188
189 // =======================================================================
190 // function : OpenGl_ShaderObject
191 // purpose  : Creates uninitialized shader object
192 // =======================================================================
193 OpenGl_ShaderObject::OpenGl_ShaderObject (GLenum theType)
194 : myType     (theType),
195   myShaderID (NO_SHADER)
196 {
197   //
198 }
199
200 // =======================================================================
201 // function : ~OpenGl_ShaderObject
202 // purpose  : Releases resources of shader object
203 // =======================================================================
204 OpenGl_ShaderObject::~OpenGl_ShaderObject()
205 {
206   Release (NULL);
207 }
208
209 // =======================================================================
210 // function : LoadAndCompile
211 // purpose  :
212 // =======================================================================
213 Standard_Boolean OpenGl_ShaderObject::LoadAndCompile (const Handle(OpenGl_Context)& theCtx,
214                                                       const TCollection_AsciiString& theSource,
215                                                       bool theIsVerbose,
216                                                       bool theToPrintSource)
217 {
218   if (!theIsVerbose)
219   {
220     return LoadSource (theCtx, theSource)
221         && Compile (theCtx);
222   }
223
224   if (!LoadSource (theCtx, theSource))
225   {
226     if (theToPrintSource)
227     {
228       theCtx->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, theSource);
229     }
230     theCtx->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, "Error! Failed to set shader source");
231     return false;
232   }
233
234   if (!Compile (theCtx))
235   {
236     if (theToPrintSource)
237     {
238       theCtx->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, putLineNumbers (theSource));
239     }
240     TCollection_AsciiString aLog;
241     FetchInfoLog (theCtx, aLog);
242     if (aLog.IsEmpty())
243     {
244       aLog = "Compilation log is empty.";
245     }
246     theCtx->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH,
247                          TCollection_AsciiString ("Failed to compile shader object. Compilation log:\n") + aLog);
248     return false;
249   }
250   else if (theCtx->caps->glslWarnings)
251   {
252     TCollection_AsciiString aLog;
253     FetchInfoLog (theCtx, aLog);
254     if (!aLog.IsEmpty()
255      && !aLog.IsEqual ("No errors.\n"))
256     {
257       theCtx->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PORTABILITY, 0, GL_DEBUG_SEVERITY_LOW,
258                            TCollection_AsciiString ("Shader compilation log:\n") + aLog);
259     }
260   }
261   return true;
262 }
263
264 // =======================================================================
265 // function : LoadSource
266 // purpose  : Loads shader source code
267 // =======================================================================
268 Standard_Boolean OpenGl_ShaderObject::LoadSource (const Handle(OpenGl_Context)&  theCtx,
269                                                   const TCollection_AsciiString& theSource)
270 {
271   if (myShaderID == NO_SHADER)
272   {
273     return Standard_False;
274   }
275
276   const GLchar* aLines = theSource.ToCString();
277   theCtx->core20fwd->glShaderSource (myShaderID, 1, &aLines, NULL);
278   return Standard_True;
279 }
280
281 // =======================================================================
282 // function : Compile
283 // purpose  : Compiles the shader object
284 // =======================================================================
285 Standard_Boolean OpenGl_ShaderObject::Compile (const Handle(OpenGl_Context)& theCtx)
286 {
287   if (myShaderID == NO_SHADER)
288   {
289     return Standard_False;
290   }
291
292   // Try to compile shader
293   theCtx->core20fwd->glCompileShader (myShaderID);
294
295   // Check compile status
296   GLint aStatus = GL_FALSE;
297   theCtx->core20fwd->glGetShaderiv (myShaderID, GL_COMPILE_STATUS, &aStatus);
298   return aStatus != GL_FALSE;
299 }
300
301 // =======================================================================
302 // function : FetchInfoLog
303 // purpose  : Fetches information log of the last compile operation
304 // =======================================================================
305 Standard_Boolean OpenGl_ShaderObject::FetchInfoLog (const Handle(OpenGl_Context)& theCtx,
306                                                     TCollection_AsciiString&      theLog)
307 {
308   if (myShaderID == NO_SHADER)
309   {
310     return Standard_False;
311   }
312
313   // Load information log of the compiler
314   GLint aLength = 0;
315   theCtx->core20fwd->glGetShaderiv (myShaderID, GL_INFO_LOG_LENGTH, &aLength);
316   if (aLength > 0)
317   {
318     GLchar* aLog = (GLchar*) alloca (aLength);
319     memset (aLog, 0, aLength);
320     theCtx->core20fwd->glGetShaderInfoLog (myShaderID, aLength, NULL, aLog);
321     theLog = aLog;
322   }
323
324   return Standard_True;
325 }
326
327 // =======================================================================
328 // function : Create
329 // purpose  : Creates new empty shader object of specified type
330 // =======================================================================
331 Standard_Boolean OpenGl_ShaderObject::Create (const Handle(OpenGl_Context)& theCtx)
332 {
333   if (myShaderID == NO_SHADER
334    && theCtx->core20fwd != NULL)
335   {
336     myShaderID = theCtx->core20fwd->glCreateShader (myType);
337   }
338
339   return myShaderID != NO_SHADER;
340 }
341
342 // =======================================================================
343 // function : Release
344 // purpose  : Destroys shader object
345 // =======================================================================
346 void OpenGl_ShaderObject::Release (OpenGl_Context* theCtx)
347 {
348   if (myShaderID == NO_SHADER)
349   {
350     return;
351   }
352
353   Standard_ASSERT_RETURN (theCtx != NULL,
354     "OpenGl_ShaderObject destroyed without GL context! Possible GPU memory leakage...",);
355
356   if (theCtx->core20fwd != NULL
357    && theCtx->IsValid())
358   {
359     theCtx->core20fwd->glDeleteShader (myShaderID);
360   }
361   myShaderID = NO_SHADER;
362 }
363
364 //! Return GLSL shader stage file extension.
365 static const char* getShaderExtension (GLenum theType)
366 {
367   switch (theType)
368   {
369     case GL_VERTEX_SHADER:          return ".vs";
370     case GL_FRAGMENT_SHADER:        return ".fs";
371     case GL_GEOMETRY_SHADER:        return ".gs";
372     case GL_TESS_CONTROL_SHADER:    return ".tcs";
373     case GL_TESS_EVALUATION_SHADER: return ".tes";
374     case GL_COMPUTE_SHADER:         return ".cs";
375   }
376   return ".glsl";
377 }
378
379 //! Expand substring with additional tail.
380 static void insertSubString (TCollection_AsciiString& theString,
381                              const char& thePattern,
382                              const TCollection_AsciiString& theSubstitution)
383 {
384   const int aSubLen = theSubstitution.Length();
385   for (int aCharIter = 1, aNbChars = theString.Length(); aCharIter <= aNbChars; ++aCharIter)
386   {
387     if (theString.Value (aCharIter) == thePattern)
388     {
389       theString.Insert (aCharIter + 1, theSubstitution);
390       aCharIter += aSubLen;
391       aNbChars  += aSubLen;
392     }
393   }
394 }
395
396 //! Dump GLSL shader source code into file.
397 static bool dumpShaderSource (const TCollection_AsciiString& theFileName,
398                               const TCollection_AsciiString& theSource,
399                               bool theToBeautify)
400 {
401   OSD_File aFile (theFileName);
402   aFile.Build (OSD_WriteOnly, OSD_Protection());
403   TCollection_AsciiString aSource = theSource;
404   if (theToBeautify)
405   {
406     insertSubString (aSource, ';', "\n");
407     insertSubString (aSource, '{', "\n");
408     insertSubString (aSource, '}', "\n");
409   }
410   if (!aFile.IsOpen())
411   {
412     Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: File '") + theFileName + "' cannot be opened to save shader", Message_Fail);
413     return false;
414   }
415
416   if (aSource.Length() > 0)
417   {
418     aFile.Write (aSource.ToCString(), aSource.Length());
419   }
420   aFile.Close();
421   Message::DefaultMessenger()->Send (TCollection_AsciiString ("Shader source dumped into '") + theFileName + "'", Message_Warning);
422   return true;
423 }
424
425 //! Read GLSL shader source code from file dump.
426 static bool restoreShaderSource (TCollection_AsciiString& theSource,
427                                  const TCollection_AsciiString& theFileName)
428 {
429   OSD_File aFile (theFileName);
430   aFile.Open (OSD_ReadOnly, OSD_Protection());
431   if (!aFile.IsOpen())
432   {
433     Message::DefaultMessenger()->Send (TCollection_AsciiString ("File '") + theFileName + "' cannot be opened to load shader", Message_Fail);
434     return false;
435   }
436
437   const Standard_Integer aSize = (Standard_Integer )aFile.Size();
438   if (aSize > 0)
439   {
440     theSource = TCollection_AsciiString (aSize, '\0');
441     aFile.Read (theSource, aSize);
442   }
443   aFile.Close();
444   Message::DefaultMessenger()->Send (TCollection_AsciiString ("Restored shader dump from '") + theFileName + "'", Message_Warning);
445   return true;
446 }
447
448 // =======================================================================
449 // function : updateDebugDump
450 // purpose  :
451 // =======================================================================
452 Standard_Boolean OpenGl_ShaderObject::updateDebugDump (const Handle(OpenGl_Context)& theCtx,
453                                                        const TCollection_AsciiString& theProgramId,
454                                                        const TCollection_AsciiString& theFolder,
455                                                        Standard_Boolean theToBeautify,
456                                                        Standard_Boolean theToReset)
457 {
458   const TCollection_AsciiString aFileName = theFolder + "/" + theProgramId + getShaderExtension (myType);
459   if (!theToReset)
460   {
461     OSD_File aFile (aFileName);
462     if (aFile.Exists())
463     {
464       const Quantity_Date aDate = aFile.AccessMoment();
465       if (aDate > myDumpDate)
466       {
467         TCollection_AsciiString aNewSource;
468         if (restoreShaderSource (aNewSource, aFileName))
469         {
470           LoadAndCompile (theCtx, aNewSource);
471           return Standard_True;
472         }
473       }
474       return Standard_False;
475     }
476   }
477
478   bool isDumped = false;
479   if (myShaderID != NO_SHADER)
480   {
481     GLint aLength = 0;
482     theCtx->core20fwd->glGetShaderiv (myShaderID, GL_SHADER_SOURCE_LENGTH, &aLength);
483     if (aLength > 0)
484     {
485       TCollection_AsciiString aSource (aLength - 1, '\0');
486       theCtx->core20fwd->glGetShaderSource (myShaderID, aLength, NULL, (GLchar* )aSource.ToCString());
487       dumpShaderSource (aFileName, aSource, theToBeautify);
488       isDumped = true;
489     }
490   }
491   if (!isDumped)
492   {
493     dumpShaderSource (aFileName, "", false);
494   }
495   myDumpDate = OSD_Process().SystemDate();
496   return Standard_False;
497 }