/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkDualDepthPeelingPass.cxx

  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.

=========================================================================*/

#include "vtkDualDepthPeelingPass.h"

#include "vtkOpenGLFramebufferObject.h"
#include "vtkInformation.h"
#include "vtkInformationKey.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkOpenGLActor.h"
#include "vtkOpenGLBufferObject.h"
#include "vtkOpenGLError.h"
#include "vtkOpenGLRenderUtilities.h"
#include "vtkOpenGLRenderWindow.h"
#include "vtkOpenGLShaderCache.h"
#include "vtkOpenGLVertexArrayObject.h"
#include "vtkRenderer.h"
#include "vtkRenderState.h"
#include "vtkRenderTimerLog.h"
#include "vtkShaderProgram.h"
#include "vtkTextureObject.h"
#include "vtkTypeTraits.h"

#include <algorithm>

// Define to print debug statements to the OpenGL CS stream (useful for e.g.
// apitrace debugging):
//#define ANNOTATE_STREAM

// Define to output details about each peel:
//#define DEBUG_PEEL

// Define to output details about each frame:
//#define DEBUG_FRAME

// Define to render the categorization of the initial volume-prepass pixel:
// - Pixels with no opaque or translucent geometry will be red.
// - Pixels with only opaque geometry will be green.
// - Pixels with only translucent geometry will be blue.
// - Pixels with both opaque and translucent geometry will be purple.
//#define DEBUG_VOLUME_PREPASS_PIXELS

// Recent OSX/ATI drivers perform some out-of-order execution that's causing
// the dFdx/dFdy calls to be conditionally executed. Specifically, it looks
// like the early returns when the depth is not on a current peel layer
// (Peeling pass, VTK::PreColor::Impl hook) are moved before the dFdx/dFdy
// calls used to compute normals. Disable the early returns on apple for now, I
// don't think most GPUs really benefit from them anyway at this point.
#ifdef __APPLE__
#define NO_PRECOLOR_EARLY_RETURN
#endif

using RenderEvent = vtkRenderTimerLog::ScopedEventLogger;

#define TIME_FUNCTION(functionName) \
  VTK_SCOPED_RENDER_EVENT(#functionName, this->Timer);

vtkStandardNewMacro(vtkDualDepthPeelingPass)
vtkCxxSetObjectMacro(vtkDualDepthPeelingPass, VolumetricPass, vtkRenderPass)

namespace
{
void annotate(const std::string &str)
{
#ifdef ANNOTATE_STREAM
  vtkOpenGLStaticCheckErrorMacro("Error before glDebug.")
  glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER,
                       GL_DEBUG_SEVERITY_NOTIFICATION,
                       0, str.size(), str.c_str());
  vtkOpenGLClearErrorMacro();
#else // ANNOTATE_STREAM
  (void)str;
#endif // ANNOTATE_STREAM
}
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::PrintSelf(std::ostream &os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::Render(const vtkRenderState *s)
{
  VTK_SCOPED_RENDER_EVENT("vtkDualDepthPeelingPass::Render",
                          s->GetRenderer()->GetRenderWindow()->GetRenderTimer());

  // Setup vtkOpenGLRenderPass
  this->PreRender(s);

  this->Initialize(s);
  this->Prepare();

  if (this->IsRenderingVolumes())
  {
    this->PeelVolumesOutsideTranslucentRange();
  }

#ifndef DEBUG_VOLUME_PREPASS_PIXELS
  while (!this->PeelingDone())
  {
    this->Peel();
  }
#endif // DEBUG_VOLUME_PREPASS_PIXELS

  this->Finalize();

  this->PostRender(s);
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::ReleaseGraphicsResources(vtkWindow* win)
{
  if (this->VolumetricPass)
  {
    this->VolumetricPass->ReleaseGraphicsResources(win);
  }

  this->FreeGLObjects();
}

//------------------------------------------------------------------------------
bool vtkDualDepthPeelingPass::PreReplaceShaderValues(
    std::string &vertexShader,
    std::string &geometryShader,
    std::string &fragmentShader,
    vtkAbstractMapper *mapper,
    vtkProp *prop)
{
  switch (this->CurrentPeelType)
  {
    case vtkDualDepthPeelingPass::TranslucentPeel:
      // Do nothing -- these are handled in the post-replacements.
      return true;
    case vtkDualDepthPeelingPass::VolumetricPeel:
      // Forward to volumetric implementation:
      return this->PreReplaceVolumetricShaderValues(vertexShader,
                                                    geometryShader,
                                                    fragmentShader,
                                                    mapper, prop);
    default:
      break;
  }
  return false;
}

//------------------------------------------------------------------------------
bool vtkDualDepthPeelingPass::PostReplaceShaderValues(
  std::string &vertexShader,
  std::string &geometryShader,
  std::string &fragmentShader,
  vtkAbstractMapper *mapper,
  vtkProp *prop)
{
  switch (this->CurrentPeelType)
  {
    case vtkDualDepthPeelingPass::TranslucentPeel:
      // Forward to translucent implementation:
      return this->PostReplaceTranslucentShaderValues(vertexShader,
                                                      geometryShader,
                                                      fragmentShader,
                                                      mapper, prop);
    case vtkDualDepthPeelingPass::VolumetricPeel:
      // Do nothing; these are handled in the pre-replacements.
      return true;

    default:
      break;
  }
  return false;
}

//------------------------------------------------------------------------------
bool vtkDualDepthPeelingPass::SetShaderParameters(vtkShaderProgram *program,
                                   vtkAbstractMapper *mapper, vtkProp *prop,
                                   vtkOpenGLVertexArrayObject *VAO)
{
  switch (this->CurrentPeelType)
  {
    case vtkDualDepthPeelingPass::TranslucentPeel:
      return this->SetTranslucentShaderParameters(program, mapper, prop, VAO);
    case vtkDualDepthPeelingPass::VolumetricPeel:
      return this->SetVolumetricShaderParameters(program, mapper, prop, VAO);
    default:
      break;
  }
  return false;
}

//------------------------------------------------------------------------------
vtkMTimeType vtkDualDepthPeelingPass::GetShaderStageMTime()
{
  return this->CurrentStageTimeStamp.GetMTime();
}

//------------------------------------------------------------------------------
bool vtkDualDepthPeelingPass::PostReplaceTranslucentShaderValues(
    std::string &, std::string &, std::string &fragmentShader,
    vtkAbstractMapper *, vtkProp *)
{
  switch (this->CurrentStage)
  {
    case vtkDualDepthPeelingPass::InitializingDepth:
      // Set gl_FragDepth if it isn't set already. It may have already been
      // replaced by the mapper, in which case the substitution will fail and
      // the previously set depth value will be used.
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::Depth::Impl",
            "gl_FragDepth = gl_FragCoord.z;");
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::DepthPeeling::Dec",
            "uniform sampler2D opaqueDepth;\n");
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::DepthPeeling::PreColor",
            "ivec2 pixel = ivec2(gl_FragCoord.xy);\n"
            "  float oDepth = texelFetch(opaqueDepth, pixel, 0).y;\n"
            "  if (oDepth != -1. && gl_FragDepth > oDepth)\n"
            "    { // Ignore fragments that are occluded by opaque geometry:\n"
            "    gl_FragData[1].xy = vec2(-1., oDepth);\n"
            "    return;\n"
            "    }\n"
            "  else\n"
            "    {\n"
            "    gl_FragData[1].xy = vec2(-gl_FragDepth, gl_FragDepth);\n"
            "    return;\n"
            "    }\n"
            );
      break;

    case vtkDualDepthPeelingPass::Peeling:
      // Set gl_FragDepth if it isn't set already. It may have already been
      // replaced by the mapper, in which case the substitution will fail and
      // the previously set depth value will be used.
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::Depth::Impl",
            "gl_FragDepth = gl_FragCoord.z;");
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::DepthPeeling::Dec",
            "uniform sampler2D lastFrontPeel;\n"
            "uniform sampler2D lastDepthPeel;\n");
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::DepthPeeling::PreColor",
            "  ivec2 pixelCoord = ivec2(gl_FragCoord.xy);\n"
            "  vec4 front = texelFetch(lastFrontPeel, pixelCoord, 0);\n"
            "  vec2 minMaxDepth = texelFetch(lastDepthPeel, pixelCoord, 0).xy;\n"
            "  float minDepth = -minMaxDepth.x;\n"
            "  float maxDepth = minMaxDepth.y;\n"
            "  // Use a tolerance when checking if we're on a current peel.\n"
            "  // Some OSX drivers compute slightly different fragment depths\n"
            "  // from one pass to the next. This value was determined\n"
            "  // through trial-and-error -- it may need to be increased at\n"
            "  // some point. See also the comment in vtkDepthPeelingPass's\n"
            "  // shader.\n"
            "  float epsilon = 0.000001;\n"
            "\n"
            "  // Default outputs (no data/change):\n"
            "  gl_FragData[0] = vec4(0.);\n"
            "  gl_FragData[1] = front;\n"
            "  gl_FragData[2].xy = vec2(-1.);\n"
            "\n"
            "  // Is this fragment outside the current peels?\n"
            "  if (gl_FragDepth < minDepth - epsilon ||\n"
            "      gl_FragDepth > maxDepth + epsilon)\n"
            "    {\n"
#ifndef NO_PRECOLOR_EARLY_RETURN
            "    return;\n"
#else
            "    // Early return removed to avoid instruction-reordering bug\n"
            "    // with dFdx/dFdy on OSX drivers.\n"
            "    // return;\n"
#endif
            "    }\n"
            "\n"
            "  // Is this fragment inside the current peels?\n"
            "  if (gl_FragDepth > minDepth + epsilon &&\n"
            "      gl_FragDepth < maxDepth - epsilon)\n"
            "    {\n"
            "    // Write out depth so this frag will be peeled later:\n"
            "    gl_FragData[2].xy = vec2(-gl_FragDepth, gl_FragDepth);\n"
#ifndef NO_PRECOLOR_EARLY_RETURN
            "    return;\n"
#else
            "    // Early return removed to avoid instruction-reordering bug\n"
            "    // with dFdx/dFdy on OSX drivers.\n"
            "    // return;\n"
#endif
            "    }\n"
            "\n"
            "  // Continue processing for fragments on the current peel:\n"
            );
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::DepthPeeling::Impl",
            "vec4 frag = gl_FragData[0];\n"
            "  // Default outputs (no data/change):\n"
            "\n"
            "  // This fragment is on a current peel:\n"
            "  if (gl_FragDepth >= minDepth - epsilon &&\n"
            "      gl_FragDepth <= minDepth + epsilon)\n"
            "    { // Front peel:\n"
            "    // Clear the back color:\n"
            "    gl_FragData[0] = vec4(0.);\n"
            "\n"
            "    // We store the front alpha value as (1-alpha) to allow MAX\n"
            "    // blending. This also means it is really initialized to 1,\n"
            "    // as it should be for under-blending.\n"
            "    front.a = 1. - front.a;\n"
            "\n"
            "    // Use under-blending to combine fragment with front color:\n"
            "    gl_FragData[1].rgb = front.a * frag.a * frag.rgb + front.rgb;\n"
            "    // Write out (1-alpha):\n"
            "    gl_FragData[1].a = 1. - (front.a * (1. - frag.a));\n"
            "    }\n"
#ifndef NO_PRECOLOR_EARLY_RETURN
            // just 'else' is ok. We'd return earlier in this case.
            "  else // (gl_FragDepth == maxDepth)\n"
#else
            // Need to explicitly test if this is the back peel, since early
            // returns are removed.
            "  else if (gl_FragDepth >= maxDepth - epsilon &&\n"
            "           gl_FragDepth <= maxDepth + epsilon)\n"
#endif
            "    { // Back peel:\n"
            "    // Dump premultiplied fragment, it will be blended later:\n"
            "    frag.rgb *= frag.a;\n"
            "    gl_FragData[0] = frag;\n"
            "    }\n"
#ifdef NO_PRECOLOR_EARLY_RETURN
            // Since the color outputs now get clobbered without the early
            // returns, reset them here.
            "  else\n"
            "    { // Need to clear the colors if not on a current peel.\n"
            "    gl_FragData[0] = vec4(0.);\n"
            "    gl_FragData[1] = front;\n"
            "    }\n"
#endif
            );
      break;

    case vtkDualDepthPeelingPass::AlphaBlending:
      // Set gl_FragDepth if it isn't set already. It may have already been
      // replaced by the mapper, in which case the substitution will fail and
      // the previously set depth value will be used.
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::Depth::Impl",
            "gl_FragDepth = gl_FragCoord.z;");
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::DepthPeeling::Dec",
            "uniform sampler2D lastDepthPeel;\n");
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::DepthPeeling::PreColor",
            "  ivec2 pixelCoord = ivec2(gl_FragCoord.xy);\n"
            "  vec2 minMaxDepth = texelFetch(lastDepthPeel, pixelCoord, 0).xy;\n"
            "  float minDepth = -minMaxDepth.x;\n"
            "  float maxDepth = minMaxDepth.y;\n"
            "\n"
            "  // Discard all fragments outside of the last set of peels:\n"
            "  if (gl_FragDepth < minDepth || gl_FragDepth > maxDepth)\n"
            "    {\n"
            "    discard;\n"
            "    }\n"
            );
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::DepthPeeling::Impl",
            "\n"
            "  // Pre-multiply alpha for depth peeling:\n"
            "  gl_FragData[0].rgb *= gl_FragData[0].a;\n"
            );
      break;

    default:
      break;
  }

  return true;
}

//------------------------------------------------------------------------------
bool vtkDualDepthPeelingPass::PreReplaceVolumetricShaderValues(
    std::string &, std::string &, std::string &fragmentShader,
    vtkAbstractMapper *, vtkProp *)
{
  const std::string rayInit =
    "  // Transform zStart and zEnd to texture_coordinates\n"
    "  mat4 NDCToTextureCoords = ip_inverseTextureDataAdjusted * in_inverseVolumeMatrix *\n"
    "    in_inverseModelViewMatrix * in_inverseProjectionMatrix;\n"
    "  \n"
    "  // Start point\n"
    "  vec4 startPoint = WindowToNDC(gl_FragCoord.x, gl_FragCoord.y, zStart);\n"
    "  startPoint = NDCToTextureCoords * startPoint;\n"
    "  startPoint /= startPoint.w;\n"
    "  \n"
    "  // startPoint could be located outside of the bounding box (bbox), this\n"
    "  // is the case in:\n"
    "  // 1. PeelVolumesOutside: Areas external to any geometry.\n"
    "  // 2. PeelVolumetricGeometry: Areas where the volume is contained within\n"
    "  // translucent geometry but the containing geometry lies outside of the bbox\n"
    "  // (startPoint is either in-front or behind the bbox depending on the viewpoint).\n"
    "\n"
    "  // Given that startPoint could be located either in-front, inside or behind the\n"
    "  // bbox (the ray exit is unknown hence it is not possible to use clamp() directly),\n"
    "  // the clamp is divided in these three zones:\n"
    "  // a. In-front: clamp to ip_textureCoords (bbox's texture coord).\n"
    "  // b. Inside: use startPoint directly as it is peeling within the bbox.\n"
    "  // c. Behind: discard by returning vec4(0.f).\n"
    "\n"
    "  // Initialize g_dataPos as if startPoint lies Inside (b.)\n"
    "  g_dataPos = startPoint.xyz;\n"
    "  bool isInsideBBox = !(any(greaterThan(startPoint.xyz, in_texMax)) ||\n"
    "    any(lessThan(startPoint.xyz, in_texMin)));\n"
    "  if (!isInsideBBox)\n"
    "  {\n"
    "    vec3 distStartTexCoord = ip_textureCoords.xyz - startPoint.xyz;\n"
    "    if (dot(distStartTexCoord, g_dirStep) < 0)\n"
    "    {\n"
    "      // startPoint lies behind the bounding box (c.)\n"
    "      return vec4(0.0);\n"
    "    }\n"
    "    // startPoint lies in-front (a.)\n"
    "    g_dataPos = ip_textureCoords.xyz;\n"
    "  }\n"
    "  g_dataPos += g_rayJitter;\n"
    "  \n"
    "  // End point\n"
    "  vec4 endPoint = WindowToNDC(gl_FragCoord.x, gl_FragCoord.y, zEnd);\n"
    "  endPoint = NDCToTextureCoords * endPoint;\n"
    "  endPoint /= endPoint.w;\n"
    "\n"
    "  // Compute the number of steps and reinitialize the step counter.\n"
    "  g_terminatePointMax = length(endPoint.xyz - g_dataPos.xyz) / length(g_dirStep);\n"
    "  g_currentT = 0.0;\n"
    "  g_fragColor = vec4(0.0);\n"
    "\n";

  switch (this->CurrentStage)
  {
    case vtkDualDepthPeelingPass::InitializingDepth:
      // At this point, both CopyOpaqueDepthBuffer and InitializeDepth have run.
      //
      // DepthSource (inner) has either:
      // a. Same as outer/DepthDestination, or
      // b. (-transGeoDepthMin, transGeoDepthMax)
      // (a) if no transparent geo in front of opaque, (b) otherwise.
      //
      // DepthDestination (outer) has (-1, opaqueDepth), or (-1, -1) if no
      // opaque geometry.
      //
      // All color buffers are empty, so we can draw directly to them. No input
      // passthrough or blending needed.
      //
      // We'll check both of the depth buffers:
      //
      // 1) If the inner.y < 0, there is no geometry here. Render volume from
      //    0 --> 1 into the back buffer.
      // 2) If the outer.x == -1 and inner.y < 0, we have only opaque geometry
      //    here. Render volumes from 0 --> outer.y into the back buffer.
      // 3) If the 'max' depth differs between the buffers, then peel:
      //    0 --> -inner.x into front buffer
      //    inner.y --> outer.y into back buffer. If outer.y < 0, replace with 1

      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::Termination::Init",
            "// Termination is defined somewhere else within the pass (CallWorker::Impl \n "
            "// and Ray::Init), so this tag is substituted for an empty implementation\n"
            "// to avoid unnecessary code.\n");

      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::DepthPeeling::Dec",
            "uniform sampler2D outerDepthTex;\n"
            "uniform sampler2D innerDepthTex;\n"
            );
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::CallWorker::Impl",
            "ivec2 pixelCoord = ivec2(gl_FragCoord.xy);\n"
            "  vec2 inner = texelFetch(innerDepthTex, pixelCoord, 0).xy;\n"
            "  vec2 outer = texelFetch(outerDepthTex, pixelCoord, 0).xy;\n"
            "\n"
            "  initializeRayCast();\n"
            "  vec4 front = vec4(0.f);\n"
            "  vec4 back = vec4(0.f);\n"
            "\n"
            "  // Check for the presence of opaque/trans geometry:\n"
            "  bool hasOpaqueGeometry = outer.y >= 0.f;\n"
            "  bool hasTranslucentGeometry = inner.x != -1.f;\n"
            "  bool hasAnyGeometry = hasOpaqueGeometry ||\n"
            "                        hasTranslucentGeometry;\n"
            "\n"
#ifndef DEBUG_VOLUME_PREPASS_PIXELS
            "  vec2 frontRange = vec2(1.f, -1.f);\n"
            "  vec2 backRange = vec2(1.f, -1.f);\n"
            "\n"
#endif // not DEBUG_VOLUME_PREPASS_PIXELS
            "  if (!hasAnyGeometry)\n"
            "  { // No opaque or translucent geometry\n"
#ifndef DEBUG_VOLUME_PREPASS_PIXELS
            "    backRange = vec2(0., 1.);\n"
#else // not DEBUG_VOLUME_PREPASS_PIXELS
            "    back = vec4(1.f, 0.f, 0.f, 1.f);\n"
#endif // not DEBUG_VOLUME_PREPASS_PIXELS
            "  }\n"
            "  else if (!hasTranslucentGeometry)\n"
            "  { // Opaque geometry only.\n"
#ifndef DEBUG_VOLUME_PREPASS_PIXELS
            "    float opaqueDepth = inner.y;\n"
            "    backRange = vec2(0.f, opaqueDepth);\n"
#else // not DEBUG_VOLUME_PREPASS_PIXELS
            "    back = vec4(0.f, 1.f, 0.f, 1.f);\n"
#endif // not DEBUG_VOLUME_PREPASS_PIXELS
            "  }\n"
            "  else // translucent geometry, maybe opaque, too:\n"
            "  {\n"
#ifndef DEBUG_VOLUME_PREPASS_PIXELS
            "    float opaqueDepth = hasOpaqueGeometry ? outer.y : 1.f;\n"
            "    frontRange = vec2(0.f, -inner.x);\n"
            "\n"
            "    if (front.a < g_opacityThreshold)\n"
            "    {\n"
            "      // The color returned by castRay() has alpha pre-multiplied,\n"
            "      // as required for back-blending.\n"
            "      backRange = vec2(inner.y, opaqueDepth);\n"
            "    }\n"
            "\n"
#else // not DEBUG_VOLUME_PREPASS_PIXELS
            "    float blue = hasOpaqueGeometry ? 1.f : 0.f;\n"
            "    back = vec4(blue, 0.f, 1.f, 1.f);\n"
#endif // not DEBUG_VOLUME_PREPASS_PIXELS
            "  }\n"
            "\n"
#ifndef DEBUG_VOLUME_PREPASS_PIXELS
            "  if (frontRange.x < frontRange.y)\n"
            "  {\n"
            "    front = castRay(frontRange.x, frontRange.y);\n"
            "  }\n"
            "  if (backRange.x < backRange.y)\n"
            "  {\n"
            "    back = castRay(backRange.x, backRange.y);\n"
            "  }\n"
            "\n"
#endif // not DEBUG_VOLUME_PREPASS_PIXELS
            "  gl_FragData[0] = back;\n"
            "  gl_FragData[1] = front;\n"
            );

      vtkShaderProgram::Substitute(fragmentShader,
            "//VTK::DepthPeeling::Ray::Init", rayInit);

      vtkShaderProgram::Substitute(fragmentShader, "//VTK::DepthPeeling::Ray::PathCheck",
            "  // Ensure end is not located before start. This could be the case\n"
            "  // if end lies outside of the volume's bounding box. In those cases\n"
            "  // a transparent color is returned.\n"
            "  vec3 rgrif = endPoint.xyz - g_dataPos.xyz;\n"
            "  if (dot(rgrif, g_dirStep) < 0)\n"
            "  {\n"
            "    return vec4(0.f);\n"
            "  }\n"
            );

      return true;

    case vtkDualDepthPeelingPass::Peeling:
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::DepthPeeling::Dec",
            "uniform sampler2D outerDepthTex;\n"
            "uniform sampler2D innerDepthTex;\n"
            "uniform sampler2D lastFrontColorTex;\n"
            "uniform sampler2D opaqueDepthTex;\n"
            );
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::CallWorker::Impl",
            "  ivec2 pixelCoord = ivec2(gl_FragCoord.xy);\n"
            "  vec2 innerDepths = texelFetch(innerDepthTex, pixelCoord, 0).xy;\n"
            "  vec2 outerDepths = texelFetch(outerDepthTex, pixelCoord, 0).xy;\n"
            "  vec4 lastFrontColor = texelFetch(lastFrontColorTex, pixelCoord, 0);\n"
            "\n"
            "  // Discard processed fragments\n"
            "  if (outerDepths.x == -1)\n"
            "  {\n"
            "    discard;\n"
            "  }\n"
            "\n"
            "  // Negate the near depths; they're negative for MAX blending:\n"
            "  float frontStartDepth = -outerDepths.x;\n"
            "  float frontEndDepth   = -innerDepths.x;\n"
            "  float backStartDepth  = innerDepths.y;\n"
            "  float backEndDepth    = outerDepths.y;\n"
            "\n"
            "  // Only record the back color (for occlusion queries) if the\n"
            "  // front/back ranges are the same:\n"
            "  bool onlyBack = frontStartDepth == backStartDepth &&\n"
            "                  frontEndDepth == backEndDepth;\n"
            "\n"
            "  // In the last peel, innerDepths may be (-1, -1) for most of the\n"
            "  // fragments. Casting a ray from [outerDepths.x, 1.0] would result\n"
            "  // in accumulating areas that have already been accounted for in\n"
            "  // former volume peels.  In this case frontEndDepth should be the\n"
            "  // outer max instead. Because of this, the back castRay() is also\n"
            "  // skipped.\n"
            "  bool noInnerDepths = innerDepths.x == -1.0;\n"
            "  if (noInnerDepths)\n"
            "  {\n"
            "    frontEndDepth = outerDepths.y;\n"
            "  }\n"
            "\n"
            "  // Peel passes set -1 in pixels that contain only opaque geometry,\n"
            "  // so the opaque depth is fetched in order to z-composite volumes\n"
            "  // with opaque goemetry. To do this, the end point of front is clamped\n"
            "  // to opaque-depth and back ray-cast is skipped altogether since it\n"
            "  // would be covered by opaque geometry anyway.\n"
            "  float oDepth = texelFetch(opaqueDepthTex, pixelCoord, 0).x;\n"
            "  bool endBehindOpaque = frontEndDepth >= oDepth;\n"
            "  float clampedFrontEnd = frontEndDepth;\n"
            "  if (endBehindOpaque)\n"
            "  {\n"
            "    clampedFrontEnd = clamp(frontEndDepth, oDepth, oDepth);\n"
            "  }\n"
            "  \n"
            "  initializeRayCast();\n"
            "  vec4 frontColor = vec4(0.f);\n"
            "  if (!onlyBack)\n"
            "  {\n"
            "    frontColor = castRay(frontStartDepth,\n"
            "                         clampedFrontEnd);\n"
            "  }\n"
            "\n"
            "  vec4 backColor = vec4(0.);\n"
            "  if (!endBehindOpaque && !noInnerDepths)"
            "  {\n"
            "    backColor = castRay(backStartDepth,\n"
            "                        backEndDepth);\n"
            "  }\n"
            "\n"
            "  // The color returned by castRay() has alpha pre-multiplied,\n"
            "  // as required for back-blending.\n"
            "  gl_FragData[0] = backColor;\n"
            "\n"
            "  // Front color is written with negated alpha for MAX blending:\n"
            "  lastFrontColor.a = 1. - lastFrontColor.a;\n"
            "\n"
            "  // Use under-blending to mix the front color on-the-fly:\n"
            "  // (note that frontColor.rgb is already multiplied by its\n"
            "  // alpha, this is done within castRay())\n"
            "  gl_FragData[1].rgb =\n"
            "    lastFrontColor.a * frontColor.rgb + lastFrontColor.rgb;\n"
            "\n"
            "  // Write out (1-alpha) for MAX blending:\n"
            "  gl_FragData[1].a = 1. - (lastFrontColor.a * (1. - frontColor.a));\n"
            );

      vtkShaderProgram::Substitute(fragmentShader,
            "//VTK::DepthPeeling::Ray::Init", rayInit);

      break;

    case vtkDualDepthPeelingPass::AlphaBlending:
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::DepthPeeling::Dec",
            "uniform sampler2D depthRangeTex;\n");
      vtkShaderProgram::Substitute(
            fragmentShader, "//VTK::CallWorker::Impl",
            "  ivec2 pixelCoord = ivec2(gl_FragCoord.xy);\n"
            "  vec2 depthRange = texelFetch(depthRangeTex, pixelCoord, 0).xy;\n"
            "\n"
            "  // Discard processed fragments\n"
            "  if (depthRange.x == -1.0)\n"
            "  {\n"
            "    discard;\n"
            "  }\n"
            "\n"
            "  float startDepth = -depthRange.x;\n"
            "  float endDepth = depthRange.y;\n"
            "\n"
            "  initializeRayCast();\n"
            "  vec4 color = castRay(startDepth, endDepth);\n"
            "\n"
            "  // The color returned by castRay() has alpha pre-multiplied,\n"
            "  // as required for back-blending.\n"
            "  gl_FragData[0] = color;\n"
            );
      break;

    default:
      break;
  }

  return true;
}

//------------------------------------------------------------------------------
bool vtkDualDepthPeelingPass::SetTranslucentShaderParameters(
    vtkShaderProgram *program, vtkAbstractMapper *, vtkProp *,
    vtkOpenGLVertexArrayObject *)
{
  switch (this->CurrentStage)
  {
    case vtkDualDepthPeelingPass::InitializingDepth:
      program->SetUniformi(
            "opaqueDepth",
            this->Textures[this->DepthDestination]->GetTextureUnit());
      break;

    case vtkDualDepthPeelingPass::Peeling:
      program->SetUniformi(
            "lastDepthPeel",
            this->Textures[this->DepthSource]->GetTextureUnit());
      program->SetUniformi(
            "frontDepthPeel",
            this->Textures[this->FrontSource]->GetTextureUnit());
      break;

    case vtkDualDepthPeelingPass::AlphaBlending:
      program->SetUniformi(
            "lastDepthPeel",
            this->Textures[this->DepthSource]->GetTextureUnit());
      break;

    default:
      break;
  }

  return true;
}

//------------------------------------------------------------------------------
bool vtkDualDepthPeelingPass::SetVolumetricShaderParameters(
    vtkShaderProgram *program, vtkAbstractMapper *, vtkProp *,
    vtkOpenGLVertexArrayObject *)
{
  switch (this->CurrentStage)
  {
    case vtkDualDepthPeelingPass::InitializingDepth:
      program->SetUniformi(
            "outerDepthTex",
            this->Textures[this->DepthDestination]->GetTextureUnit());
      program->SetUniformi(
            "innerDepthTex",
            this->Textures[this->DepthSource]->GetTextureUnit());
      return true;

    case vtkDualDepthPeelingPass::Peeling:
      program->SetUniformi(
            "outerDepthTex",
            this->Textures[this->DepthSource]->GetTextureUnit());
      program->SetUniformi(
            "innerDepthTex",
            this->Textures[this->DepthDestination]->GetTextureUnit());
      program->SetUniformi(
            "lastFrontColorTex",
            this->Textures[this->FrontSource]->GetTextureUnit());
      program->SetUniformi(
            "opaqueDepthTex", this->Textures[OpaqueDepth]->GetTextureUnit());
      break;

    case vtkDualDepthPeelingPass::AlphaBlending:
      program->SetUniformi(
            "depthRangeTex",
            this->Textures[this->DepthSource]->GetTextureUnit());
      break;

    default:
      break;
  }

  return true;
}

//------------------------------------------------------------------------------
vtkDualDepthPeelingPass::vtkDualDepthPeelingPass()
  : VolumetricPass(NULL),
    RenderState(NULL),
    CopyColorProgram(NULL),
    CopyColorVAO(NULL),
    CopyColorVBO(NULL),
    CopyDepthProgram(NULL),
    CopyDepthVAO(NULL),
    CopyDepthVBO(NULL),
    BackBlendProgram(NULL),
    BackBlendVAO(NULL),
    BackBlendVBO(NULL),
    BlendProgram(NULL),
    BlendVAO(NULL),
    BlendVBO(NULL),
    Framebuffer(NULL),
    FrontSource(FrontA),
    FrontDestination(FrontB),
    DepthSource(DepthA),
    DepthDestination(DepthB),
    CurrentStage(Inactive),
    CurrentPeelType(TranslucentPeel),
    LastPeelHadVolumes(false),
    CurrentPeel(0),
    TranslucentOcclusionQueryId(0),
    TranslucentWrittenPixels(0),
    VolumetricOcclusionQueryId(0),
    VolumetricWrittenPixels(0),
    OcclusionThreshold(0),
    TranslucentRenderCount(0),
    VolumetricRenderCount(0),
    SaveScissorTestState(false),
    CullFaceMode(0),
    CullFaceEnabled(false)
{
  std::fill(this->Textures, this->Textures + static_cast<int>(NumberOfTextures),
            static_cast<vtkTextureObject*>(NULL));
}

//------------------------------------------------------------------------------
vtkDualDepthPeelingPass::~vtkDualDepthPeelingPass()
{
  this->FreeGLObjects();

  if (this->VolumetricPass)
  {
    this->SetVolumetricPass(NULL);
  }
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::SetCurrentStage(ShaderStage stage)
{
  if (stage != this->CurrentStage)
  {
    this->CurrentStage = stage;
    this->CurrentStageTimeStamp.Modified();
  }
}

//------------------------------------------------------------------------------
// Delete the vtkObject subclass pointed at by ptr if it is set.
namespace {
template <typename T> void DeleteHelper(T *& ptr)
{
  if (ptr)
  {
    ptr->Delete();
    ptr = NULL;
  }
}
} // end anon namespace

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::FreeGLObjects()
{
  if (this->Framebuffer)
  {
    this->Framebuffer->Delete();
    this->Framebuffer = NULL;

    for (int i = 0; i < static_cast<int>(NumberOfTextures); ++i)
    {
      this->Textures[i]->Delete();
      this->Textures[i] = NULL;
    }
  }

  DeleteHelper(this->CopyColorVAO);
  DeleteHelper(this->CopyColorVBO);
  DeleteHelper(this->CopyDepthVAO);
  DeleteHelper(this->CopyDepthVBO);
  DeleteHelper(this->BackBlendVAO);
  DeleteHelper(this->BackBlendVBO);
  DeleteHelper(this->BlendVAO);
  DeleteHelper(this->BlendVBO);

  // don't delete the shader programs -- let the cache clean them up.
  this->CopyColorProgram = NULL;
  this->CopyDepthProgram = NULL;
  this->BackBlendProgram = NULL;
  this->BlendProgram = NULL;
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::RenderTranslucentPass()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::RenderTranslucentPass);
  this->TranslucentPass->Render(this->RenderState);
  ++this->TranslucentRenderCount;
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::RenderVolumetricPass()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::RenderVolumetricPass);
  this->VolumetricPass->Render(this->RenderState);
  ++this->VolumetricRenderCount;
  this->LastPeelHadVolumes =
      this->VolumetricPass->GetNumberOfRenderedProps() > 0;
}

//------------------------------------------------------------------------------
bool vtkDualDepthPeelingPass::IsRenderingVolumes()
{
  return this->VolumetricPass && this->LastPeelHadVolumes;
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::Initialize(const vtkRenderState *s)
{
  this->RenderState = s;
  this->LastPeelHadVolumes = true;

  // Get current viewport size:
  vtkRenderer *r = s->GetRenderer();
  if(s->GetFrameBuffer()==0)
  {
    // get the viewport dimensions
    r->GetTiledSizeAndOrigin(&this->ViewportWidth, &this->ViewportHeight,
                             &this->ViewportX, &this->ViewportY);
  }
  else
  {
    int size[2];
    s->GetWindowSize(size);
    this->ViewportWidth = size[0];
    this->ViewportHeight = size[1];
    this->ViewportX =0 ;
    this->ViewportY = 0;
  }

  this->Timer = r->GetRenderWindow()->GetRenderTimer();

  // The above code shouldn't touch the OpenGL command stream, so it's okay to
  // start the event here:
  TIME_FUNCTION(vtkDualDepthPeelingPass::Initialize);

  // See if we can reuse existing textures:
  if (this->Textures[Back] &&
      (static_cast<int>(this->Textures[Back]->GetHeight()) !=
       this->ViewportHeight ||
       static_cast<int>(this->Textures[Back]->GetWidth()) !=
       this->ViewportWidth))
  {
    this->FreeGLObjects();
  }

  // Allocate new textures if needed:
  if (!this->Framebuffer)
  {
    this->Framebuffer = vtkOpenGLFramebufferObject::New();

    std::generate(this->Textures,
                  this->Textures + static_cast<int>(NumberOfTextures),
                  &vtkTextureObject::New);

    this->InitColorTexture(this->Textures[BackTemp], s);
    this->InitColorTexture(this->Textures[Back], s);
    this->InitColorTexture(this->Textures[FrontA], s);
    this->InitColorTexture(this->Textures[FrontB], s);
    this->InitDepthTexture(this->Textures[DepthA], s);
    this->InitDepthTexture(this->Textures[DepthB], s);
    this->InitOpaqueDepthTexture(this->Textures[OpaqueDepth], s);

    this->InitFramebuffer(s);
  }
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::InitColorTexture(vtkTextureObject *tex,
                                               const vtkRenderState *s)
{
  tex->SetContext(static_cast<vtkOpenGLRenderWindow*>(
                    s->GetRenderer()->GetRenderWindow()));
  tex->SetFormat(GL_RGBA);
  tex->SetInternalFormat(GL_RGBA8);
  tex->Allocate2D(this->ViewportWidth, this->ViewportHeight, 4,
                  vtkTypeTraits<vtkTypeUInt8>::VTK_TYPE_ID);
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::InitDepthTexture(vtkTextureObject *tex,
                                               const vtkRenderState *s)
{
  tex->SetContext(static_cast<vtkOpenGLRenderWindow*>(
                    s->GetRenderer()->GetRenderWindow()));
  tex->SetFormat(GL_RG);
  tex->SetInternalFormat(GL_RG32F);
  tex->Allocate2D(this->ViewportWidth, this->ViewportHeight, 2,
                  vtkTypeTraits<vtkTypeFloat32>::VTK_TYPE_ID);
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::InitOpaqueDepthTexture(vtkTextureObject *tex,
                                                     const vtkRenderState *s)
{
  tex->SetContext(static_cast<vtkOpenGLRenderWindow*>(
                    s->GetRenderer()->GetRenderWindow()));
  tex->AllocateDepth(this->ViewportWidth, this->ViewportHeight,
                     vtkTextureObject::Float32);
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::InitFramebuffer(const vtkRenderState *s)
{
  this->Framebuffer->SetContext(static_cast<vtkOpenGLRenderWindow*>(
                                  s->GetRenderer()->GetRenderWindow()));

  // Save the current FBO bindings to restore them later.
  this->Framebuffer->SaveCurrentBindingsAndBuffers(GL_DRAW_FRAMEBUFFER);
  this->Framebuffer->Bind(GL_DRAW_FRAMEBUFFER);

  this->Framebuffer->AddColorAttachment(GL_DRAW_FRAMEBUFFER, BackTemp,
                                        this->Textures[BackTemp]);
  this->Framebuffer->AddColorAttachment(GL_DRAW_FRAMEBUFFER, Back,
                                        this->Textures[Back]);

  this->Framebuffer->AddColorAttachment(GL_DRAW_FRAMEBUFFER, FrontA,
                                        this->Textures[FrontA]);
  this->Framebuffer->AddColorAttachment(GL_DRAW_FRAMEBUFFER, FrontB,
                                        this->Textures[FrontB]);

  // The depth has to be treated like a color attachment, since it's a 2
  // component min-max texture.
  this->Framebuffer->AddColorAttachment(GL_DRAW_FRAMEBUFFER, DepthA,
                                        this->Textures[DepthA]);
  this->Framebuffer->AddColorAttachment(GL_DRAW_FRAMEBUFFER, DepthB,
                                        this->Textures[DepthB]);

  this->Framebuffer->RestorePreviousBindingsAndBuffers(GL_DRAW_FRAMEBUFFER);
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::Prepare()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::Prepare);

  // Since we're rendering into a temporary non-default framebuffer, we need to
  // remove the translation from the viewport and disable the scissor test;
  // otherwise we'll capture the wrong area of the rendered geometry.
  glViewport(0, 0,
             this->ViewportWidth, this->ViewportHeight);
  this->SaveScissorTestState = glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE;
  glDisable(GL_SCISSOR_TEST);

  glGetIntegerv(GL_CULL_FACE_MODE, &this->CullFaceMode);
  this->CullFaceEnabled = glIsEnabled(GL_CULL_FACE) == GL_TRUE;

  // Prevent vtkOpenGLActor from messing with the depth mask:
  size_t numProps = this->RenderState->GetPropArrayCount();
  for (size_t i = 0; i < numProps; ++i)
  {
    vtkProp *prop = this->RenderState->GetPropArray()[i];
    vtkInformation *info = prop->GetPropertyKeys();
    if (!info)
    {
      info = vtkInformation::New();
      prop->SetPropertyKeys(info);
      info->FastDelete();
    }
    info->Set(vtkOpenGLActor::GLDepthMaskOverride(), -1);
  }

  // Setup GL state:
  glDisable(GL_DEPTH_TEST);
  this->InitializeOcclusionQuery();
  this->CurrentPeel = 0;
  this->TranslucentRenderCount = 0;
  this->VolumetricRenderCount = 0;

  // Save the current FBO bindings to restore them later.
  this->Framebuffer->SaveCurrentBindingsAndBuffers(GL_DRAW_FRAMEBUFFER);
  this->Framebuffer->Bind(GL_DRAW_FRAMEBUFFER);

  // The source front buffer must be initialized, since it simply uses additive
  // blending.
  // The back-blending may discard fragments, so the back peel accumulator needs
  // initialization as well.
  unsigned int targets[2] = { static_cast<unsigned int>(Back),
                              static_cast<unsigned int>(this->FrontSource) };
  this->Framebuffer->ActivateDrawBuffers(targets, 2);
  glClearColor(0.f, 0.f, 0.f, 0.f);
  glClear(GL_COLOR_BUFFER_BIT);

  // Fill both depth buffers with -1, -1. This lets us discard fragments in
  // CopyOpaqueDepthBuffers, which gives a moderate performance boost.
  targets[0] = static_cast<unsigned int>(this->DepthSource);
  targets[1] = static_cast<unsigned int>(this->DepthDestination);
  this->Framebuffer->ActivateDrawBuffers(targets, 2);
  glClearColor(-1, -1, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT);

  // Pre-fill the depth buffer with opaque pass data:
  this->CopyOpaqueDepthBuffer();

  // Initialize the transparent depths for the peeling algorithm:
  this->InitializeDepth();
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::InitializeOcclusionQuery()
{
  glGenQueries(1, &this->TranslucentOcclusionQueryId);
  glGenQueries(1, &this->VolumetricOcclusionQueryId);

  int numPixels = this->ViewportHeight * this->ViewportWidth;
  this->OcclusionThreshold = numPixels * this->OcclusionRatio;
  this->TranslucentWrittenPixels = this->OcclusionThreshold + 1;
  this->VolumetricWrittenPixels = this->OcclusionThreshold + 1;
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::CopyOpaqueDepthBuffer()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::CopyOpaqueDepthBuffer);

  // Initialize the peeling depth buffer using the existing opaque depth buffer.
  // Note that the min component is stored as -depth, allowing
  // glBlendEquation = GL_MAX to be used during peeling.

  // Copy from the current (default) framebuffer's depth buffer into a texture:
  this->Framebuffer->RestorePreviousBindingsAndBuffers(GL_DRAW_FRAMEBUFFER);
  this->Textures[OpaqueDepth]->CopyFromFrameBuffer(
        this->ViewportX, this->ViewportY, 0, 0,
        this->ViewportWidth, this->ViewportHeight);
  this->Framebuffer->SaveCurrentBindingsAndBuffers(GL_DRAW_FRAMEBUFFER);
  this->Framebuffer->Bind(GL_DRAW_FRAMEBUFFER);

  // Fill both depth buffers with the opaque fragment depths. InitializeDepth
  // will compare translucent fragment depths with values in DepthDestination
  // and write to DepthSource using MAX blending, so we need both to have opaque
  // fragments (src/dst seem reversed because they're named for their usage in
  // PeelRender).
  unsigned int targets[2] = { static_cast<unsigned int>(this->DepthSource),
                              static_cast<unsigned int>(this->DepthDestination)
                            };
  this->Framebuffer->ActivateDrawBuffers(targets, 2);
  this->Textures[OpaqueDepth]->Activate();

  glDisable(GL_BLEND);

  typedef vtkOpenGLRenderUtilities GLUtil;

  vtkOpenGLRenderWindow *renWin = static_cast<vtkOpenGLRenderWindow*>(
        this->RenderState->GetRenderer()->GetRenderWindow());
  if (!this->CopyDepthProgram)
  {
    std::string fragShader = GLUtil::GetFullScreenQuadFragmentShaderTemplate();
    vtkShaderProgram::Substitute(
          fragShader, "//VTK::FSQ::Decl",
          "uniform float clearValue;\n"
          "uniform sampler2D oDepth;\n");
    vtkShaderProgram::Substitute(
          fragShader, "//VTK::FSQ::Impl",
          "  float d = texture2D(oDepth, texCoord).x;\n"
          "  if (d == clearValue)\n"
          "    { // If no depth value has been written, discard the frag:\n"
          "    discard;\n"
          "    }\n"
          "  gl_FragData[0] = gl_FragData[1] = vec4(-1, d, 0., 0.);\n"
          );
    this->CopyDepthProgram = renWin->GetShaderCache()->ReadyShaderProgram(
          GLUtil::GetFullScreenQuadVertexShader().c_str(),
          fragShader.c_str(),
          GLUtil::GetFullScreenQuadGeometryShader().c_str());
  }
  else
  {
    renWin->GetShaderCache()->ReadyShaderProgram(this->CopyDepthProgram);
  }

  if (!this->CopyDepthProgram)
  {
    return;
  }

  if (!this->CopyDepthVAO)
  {
    this->CopyDepthVBO = vtkOpenGLBufferObject::New();
    this->CopyDepthVAO = vtkOpenGLVertexArrayObject::New();
    GLUtil::PrepFullScreenVAO(this->CopyDepthVBO, this->CopyDepthVAO,
                              this->CopyDepthProgram);
  }

  // Get the clear value. We don't set this, so it should still be what the
  // opaque pass uses:
  GLfloat clearValue = 1.f;
  glGetFloatv(GL_DEPTH_CLEAR_VALUE, &clearValue);
  this->CopyDepthProgram->SetUniformf("clearValue", clearValue);
  this->CopyDepthProgram->SetUniformi(
        "oDepth", this->Textures[OpaqueDepth]->GetTextureUnit());

  this->CopyDepthVAO->Bind();

  annotate("Copying opaque depth!");
  GLUtil::DrawFullScreenQuad();
  annotate("Opaque depth copied!");

  this->CopyDepthVAO->Release();

  this->Textures[OpaqueDepth]->Deactivate();
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::InitializeDepth()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::InitializeDepth);

  // Add the translucent geometry to our depth peeling buffer:

  // We bind the back temporary buffer as render target 0 -- the data we
  // write to it isn't used, but this makes it easier to work with the existing
  // polydata shaders as they expect gl_FragData[0] to be RGBA. The front
  // destination buffer is cleared prior to peeling, so it's just a dummy
  // buffer at this point.
  unsigned int targets[2] = { static_cast<unsigned int>(BackTemp),
                              static_cast<unsigned int>(this->DepthSource)
                            };
  this->Framebuffer->ActivateDrawBuffers(targets, 2);

  this->SetCurrentStage(InitializingDepth);
  this->SetCurrentPeelType(TranslucentPeel);
  this->Textures[this->DepthDestination]->Activate();

  glEnable(GL_BLEND);
  glBlendEquation(GL_MAX);
  annotate("Initializing depth.");
  this->RenderTranslucentPass();
  annotate("Depth initialized");

  this->Textures[this->DepthDestination]->Deactivate();
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::PeelVolumesOutsideTranslucentRange()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::PeelVolumesOutsideTranslucentRange);

  // Enable the destination targets. Note that we're rendering directly into
  // the Back accumulation buffer and the FrontSource buffer, since we know
  // this is the first time these buffers will be drawn into.
  unsigned int targets[2] = { static_cast<unsigned int>(Back),
                              static_cast<unsigned int>(this->FrontSource)
                            };
  this->Framebuffer->ActivateDrawBuffers(targets, 2);

  // Cull back fragments of the volume's proxy geometry since they are
  // not necessary anyway.
  glCullFace(GL_BACK);
  glEnable(GL_CULL_FACE);

  this->SetCurrentStage(InitializingDepth);
  this->SetCurrentPeelType(VolumetricPeel);

  this->Textures[this->DepthSource]->Activate();
  this->Textures[this->DepthDestination]->Activate();

  annotate("Peeling volumes external to translucent geometry.");
  this->RenderVolumetricPass();
  annotate("External volume peel done.");

  glCullFace(this->CullFaceMode);
  glDisable(GL_CULL_FACE);

  this->Textures[this->DepthSource]->Deactivate();
  this->Textures[this->DepthDestination]->Deactivate();
}

//------------------------------------------------------------------------------
bool vtkDualDepthPeelingPass::PeelingDone()
{
  // Note that we do NOT check the volumetric occlusion info as an early
  // termination criterion. A volume may not exist for every slice, or may
  // only be found in the front slice (only the back is counted for occlusion
  // tests). This can lead to incorrect early termination of volume peeling.
  return this->CurrentPeel >= this->MaximumNumberOfPeels ||
         this->TranslucentWrittenPixels <= this->OcclusionThreshold;
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::Peel()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::Peel);

  this->InitializeTargetsForTranslucentPass();
  this->PeelTranslucentGeometry();
  this->StartTranslucentOcclusionQuery();
  this->BlendBackBuffer();
  this->EndTranslucentOcclusionQuery();
  this->SwapFrontBufferSourceDest();

  if (this->IsRenderingVolumes())
  {
    this->InitializeTargetsForVolumetricPass();
    this->PeelVolumetricGeometry();

    this->StartVolumetricOcclusionQuery();
    this->BlendBackBuffer();
    this->EndVolumetricOcclusionQuery();
    this->SwapFrontBufferSourceDest();
  }

  this->SwapDepthBufferSourceDest();

  ++this->CurrentPeel;

#ifdef DEBUG_PEEL
  std::cout << "Peel " << this->CurrentPeel << ": Pixels written: trans="
            << this->TranslucentWrittenPixels << " volume="
            << this->VolumetricWrittenPixels << " (threshold: "
            << this->OcclusionThreshold << ")\n";
#endif // DEBUG_PEEL
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::PrepareFrontDestination()
{
  // If we're not using volumes, clear the front destination buffer and just
  // let the shaders pass-through the colors from the previous peel.
  //
  // If we are rendering volumes, we can't rely on the shader pass-through,
  // since the volumetric and translucent geometry may not cover the same
  // pixels, and information would be lost if we simply cleared the front
  // buffer. In this case, we're essentially forcing a fullscreen pass-through
  // prior to the any actual rendering calls.
  if (!this->IsRenderingVolumes())
  {
    this->ClearFrontDestination();
  }
  else
  {
    this->CopyFrontSourceToFrontDestination();
  }
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::ClearFrontDestination()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::ClearFrontDestination);
  annotate("ClearFrontDestination()");
  this->Framebuffer->ActivateDrawBuffer(this->FrontDestination);
  glClearColor(0.f, 0.f, 0.f, 0.f);
  glClear(GL_COLOR_BUFFER_BIT);
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::CopyFrontSourceToFrontDestination()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::CopyFrontSourceToFrontDestination);

  this->Framebuffer->ActivateDrawBuffer(this->FrontDestination);

  glDisable(GL_BLEND);

  typedef vtkOpenGLRenderUtilities GLUtil;

  vtkOpenGLRenderWindow *renWin = static_cast<vtkOpenGLRenderWindow*>(
        this->RenderState->GetRenderer()->GetRenderWindow());
  if (!this->CopyColorProgram)
  {
    std::string fragShader = GLUtil::GetFullScreenQuadFragmentShaderTemplate();
    vtkShaderProgram::Substitute(
          fragShader, "//VTK::FSQ::Decl",
          "uniform sampler2D inTex;\n");
    vtkShaderProgram::Substitute(
          fragShader, "//VTK::FSQ::Impl",
          "  gl_FragData[0] = texture2D(inTex, texCoord);\n");
    this->CopyColorProgram = renWin->GetShaderCache()->ReadyShaderProgram(
          GLUtil::GetFullScreenQuadVertexShader().c_str(),
          fragShader.c_str(),
          GLUtil::GetFullScreenQuadGeometryShader().c_str());
  }
  else
  {
    renWin->GetShaderCache()->ReadyShaderProgram(this->CopyColorProgram);
  }

  if (!this->CopyColorProgram)
  {
    return;
  }

  if (!this->CopyColorVAO)
  {
    this->CopyColorVBO = vtkOpenGLBufferObject::New();
    this->CopyColorVAO = vtkOpenGLVertexArrayObject::New();
    GLUtil::PrepFullScreenVAO(this->CopyColorVBO, this->CopyColorVAO,
                              this->CopyColorProgram);
  }

  this->Textures[this->FrontSource]->Activate();
  this->CopyColorProgram->SetUniformi(
        "inTex", this->Textures[this->FrontSource]->GetTextureUnit());

  this->CopyColorVAO->Bind();

  annotate("Copying front texture src -> dst for pre-pass initialization!");
  GLUtil::DrawFullScreenQuad();
  annotate("Front texture copied!");

  this->CopyColorVAO->Release();

  this->Textures[this->FrontSource]->Deactivate();
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::InitializeTargetsForTranslucentPass()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::InitializeTargetsForTranslucentPass);

  // Initialize destination buffers to their minima, since we're MAX blending,
  // this ensures that valid outputs are captured.
  this->Framebuffer->ActivateDrawBuffer(BackTemp);
  glClearColor(0.f, 0.f, 0.f, 0.f);
  glClear(GL_COLOR_BUFFER_BIT);

  this->Framebuffer->ActivateDrawBuffer(this->DepthDestination);
  glClearColor(-1.f, -1.f, 0.f, 0.f);
  glClear(GL_COLOR_BUFFER_BIT);

  this->PrepareFrontDestination();
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::InitializeTargetsForVolumetricPass()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::InitializeTargetsForVolumetricPass);

  // Clear the back buffer to ensure that current fragments are captured for
  // later blending into the back accumulation buffer:
  this->Framebuffer->ActivateDrawBuffer(BackTemp);
  glClearColor(0.f, 0.f, 0.f, 0.f);
  glClear(GL_COLOR_BUFFER_BIT);

  this->PrepareFrontDestination();
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::PeelTranslucentGeometry()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::PeelTranslucentGeometry);

  // Enable the destination targets:
  unsigned int targets[3] = { static_cast<unsigned int>(BackTemp),
                              static_cast<unsigned int>(this->FrontDestination),
                              static_cast<unsigned int>(this->DepthDestination)
                            };
  this->Framebuffer->ActivateDrawBuffers(targets, 3);

  // Use MAX blending to capture peels:
  glEnable(GL_BLEND);
  glBlendEquation(GL_MAX);

  this->SetCurrentStage(Peeling);
  this->SetCurrentPeelType(TranslucentPeel);
  this->Textures[this->FrontSource]->Activate();
  this->Textures[this->DepthSource]->Activate();

  annotate("Start translucent peeling!");
  this->RenderTranslucentPass();
  annotate("Translucent peeling done!");

  this->Textures[this->FrontSource]->Deactivate();
  this->Textures[this->DepthSource]->Deactivate();
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::PeelVolumetricGeometry()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::PeelVolumeGeometry);

  // Enable the destination targets:
  unsigned int targets[2] = { static_cast<unsigned int>(BackTemp),
                              static_cast<unsigned int>(this->FrontDestination)
                            };
  this->Framebuffer->ActivateDrawBuffers(targets, 2);

  // Cull back fragments of the volume's proxy geometry since they are
  // not necessary anyway.
  glCullFace(GL_BACK);
  glEnable(GL_CULL_FACE);

  // Use MAX blending to capture peels:
  glEnable(GL_BLEND);
  glBlendEquation(GL_MAX);

  this->SetCurrentStage(Peeling);
  this->SetCurrentPeelType(VolumetricPeel);

  this->Textures[this->FrontSource]->Activate();
  this->Textures[this->DepthSource]->Activate();
  this->Textures[this->DepthDestination]->Activate();
  this->Textures[OpaqueDepth]->Activate();

  annotate("Start volumetric peeling!");
  this->RenderVolumetricPass();
  annotate("Volumetric peeling done!");

  this->Textures[this->FrontSource]->Deactivate();
  this->Textures[this->DepthSource]->Deactivate();
  this->Textures[this->DepthDestination]->Deactivate();
  this->Textures[OpaqueDepth]->Deactivate();

  glCullFace(this->CullFaceMode);
  glDisable(GL_CULL_FACE);
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::BlendBackBuffer()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::BlendBackBuffer);

  this->Framebuffer->ActivateDrawBuffer(Back);
  this->Textures[BackTemp]->Activate();

  /* For this step, we blend the last peel's back fragments into a back-
   * accumulation buffer. The full over-blending equations are:
   *
   * (f = front frag (incoming peel); b = back frag (current accum. buffer))
   *
   * a = f.a + (1. - f.a) * b.a
   *
   * if a == 0, C == (0, 0, 0). Otherwise,
   *
   * C = ( f.a * f.rgb + (1. - f.a) * b.a * b.rgb ) / a
   *
   * We use premultiplied alphas to save on computations, resulting in:
   *
   * [a * C] = [f.a * f.rgb] + (1 - f.a) * [ b.a * b.rgb ]
   * a = f.a + (1. - f.a) * b.a
   */

  glEnable(GL_BLEND);
  glBlendEquation(GL_FUNC_ADD);
  glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

  typedef vtkOpenGLRenderUtilities GLUtil;

  vtkOpenGLRenderWindow *renWin = static_cast<vtkOpenGLRenderWindow*>(
        this->RenderState->GetRenderer()->GetRenderWindow());
  if (!this->BackBlendProgram)
  {
    std::string fragShader = GLUtil::GetFullScreenQuadFragmentShaderTemplate();
    vtkShaderProgram::Substitute(
          fragShader, "//VTK::FSQ::Decl",
          "uniform sampler2D newPeel;\n"
          );
    vtkShaderProgram::Substitute(
          fragShader, "//VTK::FSQ::Impl",
          "  vec4 f = texture2D(newPeel, texCoord); // new frag\n"
          "  if (f.a == 0.)\n"
          "    {\n"
          "    discard;\n"
          "    }\n"
          "\n"
          "  gl_FragData[0] = f;\n"
          );
    this->BackBlendProgram = renWin->GetShaderCache()->ReadyShaderProgram(
          GLUtil::GetFullScreenQuadVertexShader().c_str(),
          fragShader.c_str(),
          GLUtil::GetFullScreenQuadGeometryShader().c_str());
  }
  else
  {
    renWin->GetShaderCache()->ReadyShaderProgram(this->BackBlendProgram);
  }

  if (!this->BackBlendProgram)
  {
    return;
  }

  if (!this->BackBlendVAO)
  {
    this->BackBlendVBO = vtkOpenGLBufferObject::New();
    this->BackBlendVAO = vtkOpenGLVertexArrayObject::New();
    GLUtil::PrepFullScreenVAO(this->BackBlendVBO, this->BackBlendVAO,
                              this->BackBlendProgram);
  }

  this->BackBlendProgram->SetUniformi(
        "newPeel", this->Textures[BackTemp]->GetTextureUnit());

  this->BackBlendVAO->Bind();

  annotate("Start blending back!");
  GLUtil::DrawFullScreenQuad();
  annotate("Back blended!");

  this->BackBlendVAO->Release();

  this->Textures[BackTemp]->Deactivate();
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::StartTranslucentOcclusionQuery()
{
  // ES 3.0 only supports checking if *any* samples passed. We'll just use
  // that query to stop peeling once all frags are processed, and ignore the
  // requested occlusion ratio.
#if GL_ES_VERSION_3_0 == 1
  glBeginQuery(GL_ANY_SAMPLES_PASSED, this->TranslucentOcclusionQueryId);
#else // GL ES 3.0
  glBeginQuery(GL_SAMPLES_PASSED, this->TranslucentOcclusionQueryId);
#endif // GL ES 3.0
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::EndTranslucentOcclusionQuery()
{
  // We time the end, but not the start, since this is where we stall to
  // sync the stream.
  TIME_FUNCTION(vtkDualDepthPeelingPass::EndTranslucentOcclusionQuery);

#if GL_ES_VERSION_3_0 == 1
  glEndQuery(GL_ANY_SAMPLES_PASSED);
  GLuint anySamplesPassed;
  glGetQueryObjectuiv(this->TranslucentOcclusionQueryId, GL_QUERY_RESULT,
                      &anySamplesPassed);
  this->TranslucentWrittenPixels =
      anySamplesPassed ? this->OcclusionThreshold + 1 : 0;
#else // GL ES 3.0
  glEndQuery(GL_SAMPLES_PASSED);
  glGetQueryObjectuiv(this->TranslucentOcclusionQueryId, GL_QUERY_RESULT,
                      &this->TranslucentWrittenPixels);
#endif // GL ES 3.0
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::StartVolumetricOcclusionQuery()
{
  // ES 3.0 only supports checking if *any* samples passed. We'll just use
  // that query to stop peeling once all frags are processed, and ignore the
  // requested occlusion ratio.
#if GL_ES_VERSION_3_0 == 1
  glBeginQuery(GL_ANY_SAMPLES_PASSED, this->VolumetricOcclusionQueryId);
#else // GL ES 3.0
  glBeginQuery(GL_SAMPLES_PASSED, this->VolumetricOcclusionQueryId);
#endif // GL ES 3.0
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::EndVolumetricOcclusionQuery()
{
  // We time the end, but not the start, since this is where we stall to
  // sync the stream.
  TIME_FUNCTION(vtkDualDepthPeelingPass::EndVolumetricOcclusionQuery);

#if GL_ES_VERSION_3_0 == 1
  glEndQuery(GL_ANY_SAMPLES_PASSED);
  GLuint anySamplesPassed;
  glGetQueryObjectuiv(this->VolumetricOcclusionQueryId, GL_QUERY_RESULT,
                      &anySamplesPassed);
  this->VolumetricWrittenPixels =
      anySamplesPassed ? this->OcclusionThreshold + 1 : 0;
#else // GL ES 3.0
  glEndQuery(GL_SAMPLES_PASSED);
  glGetQueryObjectuiv(this->VolumetricOcclusionQueryId, GL_QUERY_RESULT,
                      &this->VolumetricWrittenPixels);
#endif // GL ES 3.0

}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::SwapFrontBufferSourceDest()
{
  std::swap(this->FrontSource, this->FrontDestination);
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::SwapDepthBufferSourceDest()
{
  std::swap(this->DepthSource, this->DepthDestination);
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::Finalize()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::Finalize);

  // Mop up any unrendered fragments using simple alpha blending into the back
  // buffer.
#ifndef DEBUG_VOLUME_PREPASS_PIXELS
  if (this->TranslucentWrittenPixels > 0 ||
      this->VolumetricWrittenPixels > 0)
  {
    this->AlphaBlendRender();
  }
#endif // DEBUG_VOLUME_PREPASS_PIXELS

  this->NumberOfRenderedProps =
      this->TranslucentPass->GetNumberOfRenderedProps();

  if (this->IsRenderingVolumes())
  {
    this->NumberOfRenderedProps +=
        this->VolumetricPass->GetNumberOfRenderedProps();
  }

  this->Framebuffer->UnBind(GL_DRAW_FRAMEBUFFER);
  this->Framebuffer->RestorePreviousBindingsAndBuffers(GL_DRAW_FRAMEBUFFER);
  this->BlendFinalImage();

  // Restore blending parameters:
  glEnable(GL_BLEND);
  glBlendEquation(GL_FUNC_ADD);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  size_t numProps = this->RenderState->GetPropArrayCount();
  for (size_t i = 0; i < numProps; ++i)
  {
    vtkProp *prop = this->RenderState->GetPropArray()[i];
    vtkInformation *info = prop->GetPropertyKeys();
    if (info)
    {
      info->Remove(vtkOpenGLActor::GLDepthMaskOverride());
    }
  }

  this->Timer = NULL;
  this->RenderState = NULL;
  this->DeleteOcclusionQueryIds();
  this->SetCurrentStage(Inactive);

  if (this->CullFaceEnabled)
  {
    glEnable(GL_CULL_FACE);
  }
  else
  {
    glDisable(GL_CULL_FACE);
  }
#ifdef DEBUG_FRAME
  std::cout << "Depth peel done:\n"
            << "  - Number of peels: " << this->CurrentPeel << "\n"
            << "  - Number of geometry passes: "
            << this->TranslucentRenderCount << "\n"
            << "  - Number of volume passes: "
            << this->VolumetricRenderCount << "\n"
            << "  - Occlusion Ratio: trans="
            << static_cast<float>(this->TranslucentWrittenPixels) /
               static_cast<float>(this->ViewportWidth * this->ViewportHeight)
            << " volume="
            << static_cast<float>(this->VolumetricWrittenPixels) /
               static_cast<float>(this->ViewportWidth * this->ViewportHeight)
            << " (target: " << this->OcclusionRatio << ")\n";
#endif // DEBUG_FRAME
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::AlphaBlendRender()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::AlphaBlendRender);

  /* This pass is mopping up the remaining fragments when we exceed the max
   * number of peels or hit the occlusion limit. We'll simply render all of the
   * remaining fragments into the back destination buffer using the
   * premultiplied-alpha over-blending equations:
   *
   * aC = f.a * f.rgb + (1 - f.a) * b.a * b.rgb
   * a = f.a + (1 - f.a) * b.a
   */
  glEnable(GL_BLEND);
  glBlendEquation(GL_FUNC_ADD);
  glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

  this->SetCurrentStage(AlphaBlending);
  this->Framebuffer->ActivateDrawBuffer(Back);
  this->Textures[this->DepthSource]->Activate();

  if (this->TranslucentWrittenPixels > 0)
  {
    this->SetCurrentPeelType(TranslucentPeel);
    annotate("Alpha blend translucent render start");
    this->RenderTranslucentPass();
    annotate("Alpha blend translucent render end");
  }

  // Do not check VolumetricWrittenPixels to determine if alpha blending
  // volumes is needed -- there's no guarantee that a previous slice had
  // volume data if the current slice does.
  if (this->IsRenderingVolumes())
  {
    this->SetCurrentPeelType(VolumetricPeel);
    annotate("Alpha blend volumetric render start");
    this->RenderVolumetricPass();
    annotate("Alpha blend volumetric render end");
  }

  this->Textures[this->DepthSource]->Deactivate();
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::BlendFinalImage()
{
  TIME_FUNCTION(vtkDualDepthPeelingPass::BlendFinalImage);

  this->Textures[this->FrontSource]->Activate();
  this->Textures[Back]->Activate();

  /* Peeling is done, time to blend the front and back peel textures with the
   * opaque geometry in the existing framebuffer. First, we'll underblend the
   * back texture beneath the front texture in the shader:
   *
   * Blend 'b' under 'f' to form 't':
   * t.rgb = f.a * b.a * b.rgb + f.rgb
   * t.a   = (1 - b.a) * f.a
   *
   * ( t = translucent layer (back + front), f = front layer, b = back layer )
   *
   * Also in the shader, we adjust the translucent layer's alpha so that it
   * can be used for back-to-front blending, so
   *
   * alphaOverBlend = 1. - alphaUnderBlend
   *
   * To blend the translucent layer over the opaque layer, use regular
   * overblending via glBlendEquation/glBlendFunc:
   *
   * Blend 't' over 'o'
   * C = t.rgb + o.rgb * (1 - t.a)
   * a = t.a + o.a * (1 - t.a)
   *
   * These blending parameters and fragment shader perform this work.
   * Note that the opaque fragments are assumed to have premultiplied alpha
   * in this implementation. */
  glEnable(GL_BLEND);
  glBlendEquation(GL_FUNC_ADD);
  glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

  // Restore the original viewport and scissor test settings (see note in
  // Prepare).
  glViewport(this->ViewportX, this->ViewportY,
             this->ViewportWidth, this->ViewportHeight);
  if (this->SaveScissorTestState)
  {
    glEnable(GL_SCISSOR_TEST);
  }
  else
  {
    glDisable(GL_SCISSOR_TEST);
  }

  typedef vtkOpenGLRenderUtilities GLUtil;

  vtkOpenGLRenderWindow *renWin = static_cast<vtkOpenGLRenderWindow*>(
        this->RenderState->GetRenderer()->GetRenderWindow());
  if (!this->BlendProgram)
  {
    std::string fragShader = GLUtil::GetFullScreenQuadFragmentShaderTemplate();
    vtkShaderProgram::Substitute(
          fragShader, "//VTK::FSQ::Decl",
          "uniform sampler2D frontTexture;\n"
          "uniform sampler2D backTexture;\n"
          );
    vtkShaderProgram::Substitute(
          fragShader, "//VTK::FSQ::Impl",
          "  vec4 front = texture2D(frontTexture, texCoord);\n"
          "  vec4 back = texture2D(backTexture, texCoord);\n"
          "  front.a = 1. - front.a; // stored as (1 - alpha)\n"
          "  // Underblend. Back color is premultiplied:\n"
          "  gl_FragData[0].rgb = (front.rgb + back.rgb * front.a);\n"
          "  // The first '1. - ...' is to convert the 'underblend' alpha to\n"
          "  // an 'overblend' alpha, since we'll be letting GL do the\n"
          "  // transparent-over-opaque blending pass.\n"
          "  gl_FragData[0].a = (1. - front.a * (1. - back.a));\n"
          );
    this->BlendProgram = renWin->GetShaderCache()->ReadyShaderProgram(
          GLUtil::GetFullScreenQuadVertexShader().c_str(),
          fragShader.c_str(),
          GLUtil::GetFullScreenQuadGeometryShader().c_str());
  }
  else
  {
    renWin->GetShaderCache()->ReadyShaderProgram(this->BlendProgram);
  }

  if (!this->BlendProgram)
  {
    return;
  }

  if (!this->BlendVAO)
  {
    this->BlendVBO = vtkOpenGLBufferObject::New();
    this->BlendVAO = vtkOpenGLVertexArrayObject::New();
    GLUtil::PrepFullScreenVAO(this->BlendVBO, this->BlendVAO,
                              this->BlendProgram);
  }

  this->BlendProgram->SetUniformi(
        "frontTexture", this->Textures[this->FrontSource]->GetTextureUnit());
  this->BlendProgram->SetUniformi(
        "backTexture", this->Textures[Back]->GetTextureUnit());

  this->BlendVAO->Bind();

  annotate("blending final!");
  GLUtil::DrawFullScreenQuad();
  annotate("final blended!");

  this->BlendVAO->Release();

  this->Textures[this->FrontSource]->Deactivate();
  this->Textures[Back]->Deactivate();
}

//------------------------------------------------------------------------------
void vtkDualDepthPeelingPass::DeleteOcclusionQueryIds()
{
  glDeleteQueries(1, &this->TranslucentOcclusionQueryId);
  glDeleteQueries(1, &this->VolumetricOcclusionQueryId);
}
