vtkOpenGLShaderCache.cxx 11.3 KB
Newer Older
1 2 3
/*=========================================================================

  Program:   Visualization Toolkit
4
  Module:    vtkOpenGLShaderCache.cxx
5 6 7 8 9 10 11 12 13 14

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
15
#include "vtkOpenGLShaderCache.h"
Ken Martin's avatar
Ken Martin committed
16
#include "vtk_glew.h"
17

18
#include "vtkObjectFactory.h"
19
#include "vtkOpenGLError.h"
20 21 22
#include "vtkOpenGLRenderWindow.h"
#include "vtkShader.h"
#include "vtkShaderProgram.h"
Ken Martin's avatar
Ken Martin committed
23
#include "vtkOpenGLHelper.h"
24

Sean McBride's avatar
Sean McBride committed
25
#include <cmath>
26
#include <sstream>
27 28 29



Ken Martin's avatar
Ken Martin committed
30
#include "vtksys/MD5.h"
31

32
class vtkOpenGLShaderCache::Private
33 34 35 36 37
{
public:
  vtksysMD5* md5;

  // map of hash to shader program structs
38
  std::map<std::string, vtkShaderProgram *> ShaderPrograms;
39 40 41 42 43 44 45 46 47 48 49 50

  Private()
  {
  md5 = vtksysMD5_New();
  }

  ~Private()
  {
  vtksysMD5_Delete(this->md5);
  }

  //-----------------------------------------------------------------------------
51 52 53 54
  void ComputeMD5(const char* content,
                  const char* content2,
                  const char* content3,
                  std::string &hash)
55 56 57 58 59 60
  {
    unsigned char digest[16];
    char md5Hash[33];
    md5Hash[32] = '\0';

    vtksysMD5_Initialize(this->md5);
61
    if (content)
62
    {
63 64 65
      vtksysMD5_Append(this->md5,
        reinterpret_cast<const unsigned char *>(content),
        (int)strlen(content));
66
    }
67
    if (content2)
68
    {
69 70 71
      vtksysMD5_Append(this->md5,
        reinterpret_cast<const unsigned char *>(content2),
        (int)strlen(content2));
72
    }
73
    if (content3)
74
    {
75 76 77
      vtksysMD5_Append(this->md5,
        reinterpret_cast<const unsigned char *>(content3),
        (int)strlen(content3));
78
    }
79 80 81 82 83 84 85 86 87 88
    vtksysMD5_Finalize(this->md5, digest);
    vtksysMD5_DigestToHex(digest, md5Hash);

    hash = md5Hash;
  }


};

// ----------------------------------------------------------------------------
89
vtkStandardNewMacro(vtkOpenGLShaderCache);
90 91

// ----------------------------------------------------------------------------
92
vtkOpenGLShaderCache::vtkOpenGLShaderCache() : Internal(new Private)
93
{
94
  this->LastShaderBound  = nullptr;
95 96 97
  this->OpenGLMajorVersion = 0;
  this->OpenGLMinorVersion = 0;

98 99 100
}

// ----------------------------------------------------------------------------
101
vtkOpenGLShaderCache::~vtkOpenGLShaderCache()
102
{
103 104
  typedef std::map<std::string,vtkShaderProgram*>::const_iterator SMapIter;
  SMapIter iter = this->Internal->ShaderPrograms.begin();
105
  for ( ; iter != this->Internal->ShaderPrograms.end(); ++iter)
106
  {
107
    iter->second->Delete();
108
  }
109

110 111 112
  delete this->Internal;
}

luz.paz's avatar
luz.paz committed
113
// perform System and Output replacements
114 115 116 117
unsigned int vtkOpenGLShaderCache::ReplaceShaderValues(
  std::string &VSSource,
  std::string &FSSource,
  std::string &GSSource)
118
{
119
  // first handle renaming any Fragment shader inputs
luz.paz's avatar
luz.paz committed
120
  // if we have a geometry shader. By default fragment shaders
121 122 123
  // assume their inputs come from a Vertex Shader. When we
  // have a Geometry shader we rename the frament shader inputs
  // to come from the geometry shader
124
  if (!GSSource.empty())
125
  {
126
    vtkShaderProgram::Substitute(FSSource,"VSOut","GSOut");
127
  }
128

Ken Martin's avatar
Ken Martin committed
129
#if GL_ES_VERSION_3_0 == 1
130
  std::string version = "#version 300 es\n";
Ken Martin's avatar
Ken Martin committed
131
#else
132
  if (!this->OpenGLMajorVersion)
133
  {
134 135 136 137 138 139 140 141 142 143
    this->OpenGLMajorVersion = 3;
    this->OpenGLMinorVersion = 2;
    glGetIntegerv(GL_MAJOR_VERSION, & this->OpenGLMajorVersion);
    glGetIntegerv(GL_MINOR_VERSION, & this->OpenGLMinorVersion);
  }

  std::string version = "#version 150\n";
  if (this->OpenGLMajorVersion == 3 && this->OpenGLMinorVersion == 1)
  {
    version = "#version 140\n";
144
  }
145 146
#endif

147
  vtkShaderProgram::Substitute(VSSource,"//VTK::System::Dec",
148
    version +
149
    "#ifndef GL_ES\n"
150 151 152 153
    "#define highp\n"
    "#define mediump\n"
    "#define lowp\n"
    "#endif // GL_ES\n"
154 155
    "#define attribute in\n"  // to be safe
    "#define varying out\n" // to be safe
156
    );
157

158
  vtkShaderProgram::Substitute(FSSource,"//VTK::System::Dec",
159
    version +
160 161 162 163 164 165 166 167 168 169
    "#ifdef GL_ES\n"
    "#ifdef GL_FRAGMENT_PRECISION_HIGH\n"
    "precision highp float;\n"
    "precision highp sampler2D;\n"
    "precision highp sampler3D;\n"
    "#else\n"
    "precision mediump float;\n"
    "precision mediump sampler2D;\n"
    "precision mediump sampler3D;\n"
    "#endif\n"
170 171 172 173 174 175 176 177 178
    "#define texelFetchBuffer texelFetch\n"
    "#define texture1D texture\n"
    "#define texture2D texture\n"
    "#define texture3D texture\n"
    "#else // GL_ES\n"
    "#define highp\n"
    "#define mediump\n"
    "#define lowp\n"
    "#if __VERSION__ == 150\n"
179 180 181 182
    "#define texelFetchBuffer texelFetch\n"
    "#define texture1D texture\n"
    "#define texture2D texture\n"
    "#define texture3D texture\n"
183 184
    "#endif\n"
    "#endif // GL_ES\n"
185
    "#define varying in\n" // to be safe
186
    );
187

188
  vtkShaderProgram::Substitute(GSSource,"//VTK::System::Dec",
189
    version +
190 191 192 193 194 195
    "#ifdef GL_ES\n"
    "#ifdef GL_FRAGMENT_PRECISION_HIGH\n"
    "precision highp float;\n"
    "#else\n"
    "precision mediump float;\n"
    "#endif\n"
196 197 198 199 200
    "#else // GL_ES\n"
    "#define highp\n"
    "#define mediump\n"
    "#define lowp\n"
    "#endif // GL_ES\n"
201
    );
202

203 204 205 206
  unsigned int count = 0;
  std::string fragDecls;
  bool done = false;
  while (!done)
207
  {
208 209 210 211 212 213 214 215
    std::ostringstream src;
    std::ostringstream dst;
    src << "gl_FragData[" << count << "]";
    // this naming has to match the bindings
    // in vtkOpenGLShaderProgram.cxx
    dst << "fragOutput" << count;
    done = !vtkShaderProgram::Substitute(FSSource, src.str(),dst.str());
    if (!done)
216
    {
217
#if GL_ES_VERSION_3_0
218 219 220 221
      src.str("");
      src.clear();
      src << count;
      fragDecls += "layout(location = " + src.str() + ") ";
222
#endif
223 224
      fragDecls += "out vec4 " + dst.str() + ";\n";
      count++;
225 226
    }
  }
227 228
  vtkShaderProgram::Substitute(FSSource,"//VTK::Output::Dec",fragDecls);
  return count;
229 230 231
}

vtkShaderProgram *vtkOpenGLShaderCache::ReadyShaderProgram(
David C. Lonie's avatar
David C. Lonie committed
232 233
    std::map<vtkShader::Type,vtkShader *> shaders,
    vtkTransformFeedback *cap)
234 235 236 237 238 239 240 241 242 243 244 245 246
{
  std::string VSSource = shaders[vtkShader::Vertex]->GetSource();
  std::string FSSource = shaders[vtkShader::Fragment]->GetSource();
  std::string GSSource = shaders[vtkShader::Geometry]->GetSource();

  unsigned int count =
    this->ReplaceShaderValues(VSSource,FSSource,GSSource);
  shaders[vtkShader::Vertex]->SetSource(VSSource);
  shaders[vtkShader::Fragment]->SetSource(FSSource);
  shaders[vtkShader::Geometry]->SetSource(GSSource);

  vtkShaderProgram *shader = this->GetShaderProgram(shaders);
  shader->SetNumberOfOutputs(count);
247

David C. Lonie's avatar
David C. Lonie committed
248
  return this->ReadyShaderProgram(shader, cap);
249 250
}

251
// return nullptr if there is an issue
252
vtkShaderProgram *vtkOpenGLShaderCache::ReadyShaderProgram(
David C. Lonie's avatar
David C. Lonie committed
253 254
  const char *vertexCode, const char *fragmentCode, const char *geometryCode,
  vtkTransformFeedback *cap)
255 256
{
  // perform system wide shader replacements
luz.paz's avatar
luz.paz committed
257
  // desktops to not use precision statements
258 259 260 261 262 263 264 265 266 267 268
  std::string VSSource = vertexCode;
  std::string FSSource = fragmentCode;
  std::string GSSource = geometryCode;

  unsigned int count =
    this->ReplaceShaderValues(VSSource,FSSource,GSSource);
  vtkShaderProgram *shader =
    this->GetShaderProgram(
      VSSource.c_str(), FSSource.c_str(), GSSource.c_str());
  shader->SetNumberOfOutputs(count);

David C. Lonie's avatar
David C. Lonie committed
269
  return this->ReadyShaderProgram(shader, cap);
270 271
}

272
// return nullptr if there is an issue
273
vtkShaderProgram *vtkOpenGLShaderCache::ReadyShaderProgram(
David C. Lonie's avatar
David C. Lonie committed
274
    vtkShaderProgram *shader, vtkTransformFeedback *cap)
275
{
276
  if (!shader)
277
  {
278
    return nullptr;
279
  }
280

David C. Lonie's avatar
David C. Lonie committed
281
  if (shader->GetTransformFeedback() != cap)
282
  {
David C. Lonie's avatar
David C. Lonie committed
283
    this->ReleaseCurrentShader();
284
    shader->ReleaseGraphicsResources(nullptr);
David C. Lonie's avatar
David C. Lonie committed
285
    shader->SetTransformFeedback(cap);
286
  }
David C. Lonie's avatar
David C. Lonie committed
287

288
  // compile if needed
289
  if (!shader->GetCompiled() && !shader->CompileShader())
290
  {
291
    return nullptr;
292
  }
293 294 295

  // bind if needed
  if (!this->BindShader(shader))
296
  {
297
    return nullptr;
298
  }
299 300 301 302

  return shader;
}

303 304
vtkShaderProgram *vtkOpenGLShaderCache::GetShaderProgram(
  std::map<vtkShader::Type,vtkShader *> shaders)
305
{
306 307 308 309 310 311 312 313 314 315 316
  // compute the MD5 and the check the map
  std::string result;
  this->Internal->ComputeMD5(
    shaders[vtkShader::Vertex]->GetSource().c_str(),
    shaders[vtkShader::Fragment]->GetSource().c_str(),
    shaders[vtkShader::Geometry]->GetSource().c_str(), result);

  // does it already exist?
  typedef std::map<std::string,vtkShaderProgram*>::const_iterator SMapIter;
  SMapIter found = this->Internal->ShaderPrograms.find(result);
  if (found == this->Internal->ShaderPrograms.end())
317
  {
318 319 320 321 322 323 324 325
    // create one
    vtkShaderProgram *sps = vtkShaderProgram::New();
    sps->SetVertexShader(shaders[vtkShader::Vertex]);
    sps->SetFragmentShader(shaders[vtkShader::Fragment]);
    sps->SetGeometryShader(shaders[vtkShader::Geometry]);
    sps->SetMD5Hash(result); // needed?
    this->Internal->ShaderPrograms.insert(std::make_pair(result, sps));
    return sps;
326
  }
327
  else
328
  {
329
    return found->second;
330
  }
331 332
}

333
vtkShaderProgram *vtkOpenGLShaderCache::GetShaderProgram(
334 335 336
  const char *vertexCode,
  const char *fragmentCode,
  const char *geometryCode)
337 338 339
{
  // compute the MD5 and the check the map
  std::string result;
340
  this->Internal->ComputeMD5(vertexCode, fragmentCode, geometryCode, result);
341 342

  // does it already exist?
343
  typedef std::map<std::string,vtkShaderProgram*>::const_iterator SMapIter;
344 345
  SMapIter found = this->Internal->ShaderPrograms.find(result);
  if (found == this->Internal->ShaderPrograms.end())
346
  {
347
    // create one
348 349 350
    vtkShaderProgram *sps = vtkShaderProgram::New();
    sps->GetVertexShader()->SetSource(vertexCode);
    sps->GetFragmentShader()->SetSource(fragmentCode);
351
    if (geometryCode != nullptr)
352
    {
353
      sps->GetGeometryShader()->SetSource(geometryCode);
354
    }
355
    sps->SetMD5Hash(result); // needed?
356 357
    this->Internal->ShaderPrograms.insert(std::make_pair(result, sps));
    return sps;
358
  }
359
  else
360
  {
361
    return found->second;
362
  }
363 364
}

365
void vtkOpenGLShaderCache::ReleaseGraphicsResources(vtkWindow *win)
366
{
367 368 369
  // NOTE:
  // In the current implementation as of October 26th, if a shader
  // program is created by ShaderCache then it should make sure
luz.paz's avatar
luz.paz committed
370
  // that it releases the graphics resources used by these programs.
371 372 373 374
  // It is not wisely for callers to do that since then they would
  // have to loop over all the programs were in use and invoke
  // release graphics resources individually.

375
  this->ReleaseCurrentShader();
376 377 378

  typedef std::map<std::string,vtkShaderProgram*>::const_iterator SMapIter;
  SMapIter iter = this->Internal->ShaderPrograms.begin();
379
  for ( ; iter != this->Internal->ShaderPrograms.end(); ++iter)
380
  {
381
    iter->second->ReleaseGraphicsResources(win);
382
  }
383
  this->OpenGLMajorVersion = 0;
384 385
}

386 387 388 389
void vtkOpenGLShaderCache::ReleaseCurrentShader()
{
  // release prior shader
  if (this->LastShaderBound)
390
  {
391
    this->LastShaderBound->Release();
392
    this->LastShaderBound = nullptr;
393
  }
394
}
395

396
int vtkOpenGLShaderCache::BindShader(vtkShaderProgram* shader)
397 398
{
  if (this->LastShaderBound == shader)
399
  {
400
    return 1;
401
  }
402 403 404

  // release prior shader
  if (this->LastShaderBound)
405
  {
406
    this->LastShaderBound->Release();
407
  }
408
  shader->Bind();
409 410 411 412 413 414
  this->LastShaderBound = shader;
  return 1;
}


// ----------------------------------------------------------------------------
415
void vtkOpenGLShaderCache::PrintSelf(ostream& os, vtkIndent indent)
416 417 418
{
  this->Superclass::PrintSelf(os,indent);
}