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

Program:   Visualization Toolkit
Module:    vtkOpenGLHAVSVolumeMapper.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.

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

/* Copyright 2005, 2006 by University of Utah. */

#include "vtkOpenGLHAVSVolumeMapper.h"

#include "vtkCamera.h"
#include "vtkColorTransferFunction.h"
#include "vtkgl.h"
#include "vtkInformation.h"
#include "vtkObjectFactory.h"
#include "vtkOpenGL.h"
#include "vtkOpenGLExtensionManager.h"
#include "vtkPiecewiseFunction.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkTimerLog.h"
#include "vtkTransform.h"
#include "vtkUnstructuredGrid.h"
#include "vtkUnstructuredGridPartialPreIntegration.h"
#include "vtkVolumeProperty.h"
#include "vtkOpenGLRenderWindow.h"
#include "vtkOpenGLError.h"

#include "vtkHAVSVolumeMapper_kbufferVP.h"
#include "vtkHAVSVolumeMapper_k2BeginFP.h"
#include "vtkHAVSVolumeMapper_k2FP.h"
#include "vtkHAVSVolumeMapper_k2EndFP.h"
#include "vtkHAVSVolumeMapper_k6BeginFP.h"
#include "vtkHAVSVolumeMapper_k6FP.h"
#include "vtkHAVSVolumeMapper_k6EndFP.h"

vtkStandardNewMacro(vtkOpenGLHAVSVolumeMapper);

//----------------------------------------------------------------------------
// return the correct type of UnstructuredGridVolumeMapper
vtkOpenGLHAVSVolumeMapper::vtkOpenGLHAVSVolumeMapper()
{
  this->VBOVertexName              = 0;
  this->VBOTexCoordName            = 0;
  this->VBOVertexIndexName         = 0;
  this->PsiTableTexture            = 0;
  this->FramebufferObjectSize      = 0;
  this->OrderedTriangles           = 0;
  this->RenderWindow=0;
}

//----------------------------------------------------------------------------
// return the correct type of UnstructuredGridVolumeMapper
vtkOpenGLHAVSVolumeMapper::~vtkOpenGLHAVSVolumeMapper()
{
  if (!this->GPUDataStructures)
    {
    if(this->OrderedTriangles!=0)
      {
      delete [] this->OrderedTriangles;
      }
    }
}

//----------------------------------------------------------------------------
void vtkOpenGLHAVSVolumeMapper::CheckOpenGLError(const char * str)
{
  int err = glGetError(); (void)str;
  if ( err != GL_NO_ERROR && this->GetDebug() )
    {
    vtkDebugMacro( << "OpenGL Error: " << str );
    }
}

//----------------------------------------------------------------------------
void vtkOpenGLHAVSVolumeMapper::ReleaseGraphicsResources(vtkWindow *renWin)
{
  if (this->Initialized)
    {
    static_cast<vtkRenderWindow *>(renWin)->MakeCurrent();
    vtkOpenGLClearErrorMacro();

    this->DeleteShaders();

    glDisable( vtkgl::VERTEX_PROGRAM_ARB );
    glDisable( vtkgl::FRAGMENT_PROGRAM_ARB );

    glDeleteTextures(1, reinterpret_cast<GLuint *>(&this->TransferFunctionTexture));

    int numBuffers = (this->KBufferState == VTK_KBUFFER_SIZE_2)? 2 : 4;
    for (int i = 0; i < numBuffers; i++)
      {
      glDeleteTextures(1,reinterpret_cast<GLuint *>(&this->FramebufferTextures[i]));
      }

    vtkgl::DeleteFramebuffersEXT(1,reinterpret_cast<GLuint *>(&this->FramebufferObject));
    this->Initialized = false;
    if (this->GPUDataStructures)
      {
      vtkgl::DeleteBuffers(1, reinterpret_cast<GLuint *>(&this->VBOVertexName));
      vtkgl::DeleteBuffers(1, reinterpret_cast<GLuint *>(&this->VBOTexCoordName));
      vtkgl::DeleteBuffers(1,reinterpret_cast<GLuint *>(&this->VBOVertexIndexName));
      vtkgl::BindBuffer(vtkgl::ARRAY_BUFFER, 0);
      vtkgl::BindBuffer(vtkgl::ELEMENT_ARRAY_BUFFER, 0);
      }
    vtkOpenGLCheckErrorMacro("failed after ReleaseGraphicsResources");
    }
  this->Superclass::ReleaseGraphicsResources(renWin);
}

//----------------------------------------------------------------------------
void vtkOpenGLHAVSVolumeMapper::Initialize(vtkRenderer *ren,
                                           vtkVolume *vol)
{
  // Check for the required extensions only.
  if (!this->SupportedByHardware(ren))
    {
    this->InitializationError = vtkHAVSVolumeMapper::UNSUPPORTED_EXTENSIONS;
    return;
    }

  vtkOpenGLExtensionManager *extensions=
    static_cast<vtkOpenGLRenderWindow *>(ren->GetRenderWindow())
    ->GetExtensionManager();

  // Load required extensions

  // supports_GL_1_3=1 as checked by this->SupportedByHardware()
  // OpenGL 1.3 is required by GL_ARB_draw_buffers, GL_ARB_fragment_program
  //  and GL_ARB_vertex_program.
  // CLAMP_TO_EGDE is core feature of OpenGL 1.2 and
  // multitexture is core feature of OpenGL 1.3.
  extensions->LoadExtension("GL_VERSION_1_3"); // multitexture

  // supports_draw_buffers as checked by this->SupportedByHardware()
  int supports_GL_2_0=extensions->ExtensionSupported( "GL_VERSION_2_0" );

  if(supports_GL_2_0)
    {
    extensions->LoadExtension("GL_VERSION_2_0");
    }
  else
    {
    extensions->LoadCorePromotedExtension( "GL_ARB_draw_buffers" );
    }

  // supports_fragment_program && supports_vertex_program as checked
  // by this->SupportedByHardware()
  extensions->LoadExtension( "GL_ARB_fragment_program" );
  extensions->LoadExtension( "GL_ARB_vertex_program" );

  // supports_GL_EXT_framebuffer_object==1 as checked
  // by this->SupportedByHardware()
  extensions->LoadExtension("GL_EXT_framebuffer_object");

  // GL_ARB_texture_float or GL_ATI_texture_float introduce new tokens but
  // no new function: don't need to call LoadExtension.

  // Optional extension.
  int supports_GL_1_5=extensions->ExtensionSupported( "GL_VERSION_1_5" );
  int supports_vertex_buffer_object;

  if(supports_GL_1_5)
    {
    supports_vertex_buffer_object=1;
    }
  else
    {
    supports_vertex_buffer_object =
      extensions->ExtensionSupported( "GL_ARB_vertex_buffer_object" );
    }

  if(supports_vertex_buffer_object)
    {
    if(supports_GL_1_5)
      {
      extensions->LoadExtension("GL_VERSION_1_5");
      }
    else
      {
      extensions->LoadCorePromotedExtension( "GL_ARB_vertex_buffer_object" );
      }
    }

  if (!supports_vertex_buffer_object)
    {
    this->SetGPUDataStructures(false);
    }

  this->UpdateProgress(0.0);

  // Initialize triangles and VBOs or Vertex Arrays
  this->InitializePrimitives(vol);

  this->UpdateProgress(0.4);

  // Initialize scalars and VBOs
  this->InitializeScalars();

  this->UpdateProgress(0.5);

  // Initialize Level-of-Detail data structures
  this->InitializeLevelOfDetail();

  this->UpdateProgress(0.7);

  // Initialize Lookup tables
  this->InitializeLookupTables(vol);

  this->UpdateProgress(0.8);

  // Initialize vertex and scalar storage
  this->InitializeGPUDataStructures();

  this->UpdateProgress(0.9);

  // Initialize shaders
  this->InitializeShaders();

  // Initialize FBOs
  this->InitializeFramebufferObject();

  this->UpdateProgress(1.0);

  this->Initialized = 1;
}

//----------------------------------------------------------------------------
// Change GPU data structures state
void vtkOpenGLHAVSVolumeMapper::SetGPUDataStructures(bool gpu)
{
  if(this->GPUDataStructures!=gpu)
    {
    if(!this->GPUDataStructures)
      {
      if(this->OrderedTriangles!=0)
        {
        delete [] this->OrderedTriangles;
        this->OrderedTriangles=0;
        }
      }
    this->GPUDataStructures = gpu;
    if (this->Initialized)
      {
      this->InitializeGPUDataStructures();
      }
    }
}

//----------------------------------------------------------------------------
// Store data structures on GPU if possible
void vtkOpenGLHAVSVolumeMapper::InitializeGPUDataStructures()
{
  vtkOpenGLClearErrorMacro();

  if (this->GPUDataStructures)
    {
    if (VBOVertexName)
      {
      vtkgl::DeleteBuffers(1, reinterpret_cast<GLuint *>(&this->VBOVertexName));
      }
    if (VBOVertexIndexName)
      {
      vtkgl::DeleteBuffers(1,reinterpret_cast<GLuint *>(&this->VBOVertexIndexName));
      }
    if (VBOTexCoordName)
      {
      vtkgl::DeleteBuffers(1, reinterpret_cast<GLuint *>(&this->VBOTexCoordName));
      }

    // Build vertex array
    vtkgl::GenBuffers(1, reinterpret_cast<GLuint *>(&this->VBOVertexName));
    vtkgl::BindBuffer(vtkgl::ARRAY_BUFFER, this->VBOVertexName);
    vtkgl::BufferData(vtkgl::ARRAY_BUFFER,
                      this->NumberOfVertices*3*sizeof(float),
                      this->Vertices, vtkgl::STATIC_DRAW);
    // Build dynamic vertex index array
    vtkgl::GenBuffers(1, reinterpret_cast<GLuint *>(&this->VBOVertexIndexName));
    vtkgl::BindBuffer(vtkgl::ELEMENT_ARRAY_BUFFER,
                      this->VBOVertexIndexName);
    vtkgl::BufferData(vtkgl::ELEMENT_ARRAY_BUFFER,
                      this->NumberOfTriangles*3*sizeof(GLuint), 0,
                      vtkgl::STREAM_DRAW);

    vtkgl::BindBuffer(vtkgl::ARRAY_BUFFER, 0);
    vtkgl::BindBuffer(vtkgl::ELEMENT_ARRAY_BUFFER, 0);

    // Build tex coord array
    vtkgl::GenBuffers(1, reinterpret_cast<GLuint *>(&this->VBOTexCoordName));
    vtkgl::BindBuffer(vtkgl::ARRAY_BUFFER, this->VBOTexCoordName);
    vtkgl::BufferData(vtkgl::ARRAY_BUFFER,
                         this->NumberOfScalars*sizeof(float),
                         this->Scalars, vtkgl::STATIC_DRAW);
    vtkgl::BindBuffer(vtkgl::ARRAY_BUFFER, 0);
    }
  else
    {
    if (this->OrderedTriangles)
      {
      delete [] this->OrderedTriangles;
      }
    this->OrderedTriangles = new unsigned int[this->NumberOfTriangles*3];
    }

  vtkOpenGLCheckErrorMacro("failed after InitializeGPUDataStructures");
}

//----------------------------------------------------------------------------
// Vertex and Fragment shaders
void vtkOpenGLHAVSVolumeMapper::InitializeShaders()
{
  vtkOpenGLClearErrorMacro();

  // Create vertex shader
  glEnable( vtkgl::VERTEX_PROGRAM_ARB );
  vtkgl::GenProgramsARB(1, reinterpret_cast<GLuint *>(&this->VertexProgram));
  vtkgl::BindProgramARB(vtkgl::VERTEX_PROGRAM_ARB, this->VertexProgram);
  vtkgl::ProgramStringARB(vtkgl::VERTEX_PROGRAM_ARB,
                          vtkgl::PROGRAM_FORMAT_ASCII_ARB,
                          static_cast<GLsizei>(strlen(vtkHAVSVolumeMapper_kbufferVP)),
                          vtkHAVSVolumeMapper_kbufferVP);

  // Create fragment shaders
  glEnable( vtkgl::FRAGMENT_PROGRAM_ARB );
  if (this->KBufferSize == VTK_KBUFFER_SIZE_2)
    {
    vtkgl::GenProgramsARB(1,
                          reinterpret_cast<GLuint *>(&this->FragmentProgramBegin));
    vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB,
                          this->FragmentProgramBegin);
    vtkgl::ProgramStringARB(vtkgl::FRAGMENT_PROGRAM_ARB,
                            vtkgl::PROGRAM_FORMAT_ASCII_ARB,
                            static_cast<GLsizei>(strlen(vtkHAVSVolumeMapper_k2BeginFP)),
                            vtkHAVSVolumeMapper_k2BeginFP);
    vtkgl::GenProgramsARB(1, reinterpret_cast<GLuint *>(&this->FragmentProgram));
    vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB, this->FragmentProgram);
    vtkgl::ProgramStringARB(vtkgl::FRAGMENT_PROGRAM_ARB,
                            vtkgl::PROGRAM_FORMAT_ASCII_ARB,
                            static_cast<GLsizei>(strlen(vtkHAVSVolumeMapper_k2FP)),
                            vtkHAVSVolumeMapper_k2FP);
    vtkgl::GenProgramsARB(1, reinterpret_cast<GLuint *>(&this->FragmentProgramEnd));
    vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB,
                          this->FragmentProgramEnd);
    vtkgl::ProgramStringARB(vtkgl::FRAGMENT_PROGRAM_ARB,
                            vtkgl::PROGRAM_FORMAT_ASCII_ARB,
                            static_cast<GLsizei>(strlen(vtkHAVSVolumeMapper_k2EndFP)),
                            vtkHAVSVolumeMapper_k2EndFP);
    }
  else
    {
    vtkgl::GenProgramsARB(1,
                          reinterpret_cast<GLuint *>(&this->FragmentProgramBegin));
    vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB,
                          this->FragmentProgramBegin);
    vtkgl::ProgramStringARB(vtkgl::FRAGMENT_PROGRAM_ARB,
                            vtkgl::PROGRAM_FORMAT_ASCII_ARB,
                            static_cast<GLsizei>(strlen(vtkHAVSVolumeMapper_k6BeginFP)),
                            vtkHAVSVolumeMapper_k6BeginFP);
    vtkgl::GenProgramsARB(1, reinterpret_cast<GLuint *>(&this->FragmentProgram));
    vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB, this->FragmentProgram);
    vtkgl::ProgramStringARB(vtkgl::FRAGMENT_PROGRAM_ARB,
                            vtkgl::PROGRAM_FORMAT_ASCII_ARB,
                            static_cast<GLsizei>(strlen(vtkHAVSVolumeMapper_k6FP)),
                            vtkHAVSVolumeMapper_k6FP);
    vtkgl::GenProgramsARB(1, reinterpret_cast<GLuint *>(&this->FragmentProgramEnd));
    vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB,
                          this->FragmentProgramEnd);
    vtkgl::ProgramStringARB(vtkgl::FRAGMENT_PROGRAM_ARB,
                            vtkgl::PROGRAM_FORMAT_ASCII_ARB,
                            static_cast<GLsizei>(strlen(vtkHAVSVolumeMapper_k6EndFP)),
                            vtkHAVSVolumeMapper_k6EndFP);
    }

  // Disable shaders
  vtkgl::BindProgramARB(vtkgl::VERTEX_PROGRAM_ARB, 0);
  vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB, 0);
  glDisable( vtkgl::VERTEX_PROGRAM_ARB );
  glDisable( vtkgl::FRAGMENT_PROGRAM_ARB );

  vtkOpenGLCheckErrorMacro("failed after InitializeShaders");
}

//----------------------------------------------------------------------------
void vtkOpenGLHAVSVolumeMapper::DeleteShaders()
{
  vtkgl::DeleteProgramsARB(1, reinterpret_cast<GLuint *>(&this->VertexProgram));
  vtkgl::DeleteProgramsARB(1,
                           reinterpret_cast<GLuint *>(&this->FragmentProgramBegin));
  vtkgl::DeleteProgramsARB(1, reinterpret_cast<GLuint *>(&this->FragmentProgram));
  vtkgl::DeleteProgramsARB(1,
                           reinterpret_cast<GLuint *>(&this->FragmentProgramEnd));

  vtkOpenGLCheckErrorMacro("failed after DeleteShaders");
}

//----------------------------------------------------------------------------
// Build the lookup tables used for partial pre-integration
void vtkOpenGLHAVSVolumeMapper::InitializeLookupTables(vtkVolume *vol)
{
  vtkOpenGLClearErrorMacro();
  this->Superclass::InitializeLookupTables(vol);

  // Create a 1D texture for transfer function look up
  glGenTextures(1, reinterpret_cast<GLuint *>(&this->TransferFunctionTexture));
  glBindTexture(GL_TEXTURE_1D, this->TransferFunctionTexture);
  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, vtkgl::CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA8, this->TransferFunctionSize,0,
               GL_RGBA,GL_FLOAT, this->TransferFunction);

  if (!this->PsiTableTexture)
    {
    vtkUnstructuredGridPartialPreIntegration *ppi =
      vtkUnstructuredGridPartialPreIntegration::New();
    ppi->BuildPsiTable();
    int tableSize = 0;
    float *psiTable = ppi->GetPsiTable(tableSize);

    // Create a 2D texture for the PSI lookup table
    glGenTextures(1, reinterpret_cast<GLuint *>(&this->PsiTableTexture));
    glBindTexture(GL_TEXTURE_2D, this->PsiTableTexture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, vtkgl::CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, vtkgl::CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE8, tableSize, tableSize,
                 0, GL_LUMINANCE, GL_FLOAT, psiTable);
    ppi->Delete();
    }
  vtkOpenGLCheckErrorMacro("failed after InitializeLookupTables");
}

//----------------------------------------------------------------------------
// Initialize FBO and attach color and depth textures.
void vtkOpenGLHAVSVolumeMapper::InitializeFramebufferObject()
{
  vtkOpenGLClearErrorMacro();

  GLint maxRB;
  glGetIntegerv(vtkgl::MAX_RENDERBUFFER_SIZE_EXT, &maxRB);
  int texSize = (maxRB > 1024)? 1024 : maxRB;
  int numBuffers = (this->KBufferState == VTK_KBUFFER_SIZE_2)? 2 : 4;

  if (!this->Initialized)
    {
    // Create FBO
    vtkgl::GenFramebuffersEXT(1,
                              reinterpret_cast<GLuint *>(&this->FramebufferObject));
    vtkOpenGLCheckErrorMacro("creating FBO");
    }
  else
    {
    glDeleteTextures(numBuffers,
                     reinterpret_cast<GLuint *>(this->FramebufferTextures));
    vtkgl::DeleteRenderbuffersEXT(1,
                                  reinterpret_cast<GLuint *>(&this->DepthTexture));
    }

  numBuffers = (this->KBufferSize == VTK_KBUFFER_SIZE_2)? 2 : 4;

  // Create FBO textures
  glGenTextures(numBuffers, reinterpret_cast<GLuint *>(this->FramebufferTextures));
  for (int i = 0; i < numBuffers; i++)
    {
    glBindTexture(GL_TEXTURE_2D, this->FramebufferTextures[i]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, vtkgl::RGBA32F_ARB, texSize, texSize, 0,
                 GL_RGBA, GL_FLOAT, 0);
    }

  vtkOpenGLCheckErrorMacro("creating fbo textures");

  // Bind framebuffer object
  GLint savedFrameBuffer;
  glGetIntegerv(vtkgl::FRAMEBUFFER_BINDING_EXT,&savedFrameBuffer);
  vtkgl::BindFramebufferEXT(vtkgl::FRAMEBUFFER_EXT, this->FramebufferObject);
  vtkOpenGLCheckErrorMacro("binding FBO");

  // Generate depth buffer texture for framebuffer
  vtkgl::GenRenderbuffersEXT(1, reinterpret_cast<GLuint *>(&this->DepthTexture));

  // Attach texture to framebuffer color buffer
  vtkgl::FramebufferTexture2DEXT( vtkgl::FRAMEBUFFER_EXT,
                                  vtkgl::COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
                                  this->FramebufferTextures[0], 0 );
  vtkgl::FramebufferTexture2DEXT( vtkgl::FRAMEBUFFER_EXT,
                                  vtkgl::COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D,
                                  this->FramebufferTextures[1], 0 );
  if (numBuffers == 4)
    {
    vtkgl::FramebufferTexture2DEXT( vtkgl::FRAMEBUFFER_EXT,
                                    vtkgl::COLOR_ATTACHMENT2_EXT,
                                    GL_TEXTURE_2D,
                                    this->FramebufferTextures[2], 0 );
    vtkgl::FramebufferTexture2DEXT( vtkgl::FRAMEBUFFER_EXT,
                                    vtkgl::COLOR_ATTACHMENT3_EXT,
                                    GL_TEXTURE_2D,
                                    this->FramebufferTextures[3], 0 );
    }

  // Attach depth texture to framebuffer
  vtkgl::BindRenderbufferEXT(vtkgl::RENDERBUFFER_EXT, this->DepthTexture);
  vtkgl::RenderbufferStorageEXT(vtkgl::RENDERBUFFER_EXT,
                                vtkgl::DEPTH_COMPONENT24,
                                texSize, texSize);
  vtkgl::FramebufferRenderbufferEXT(vtkgl::FRAMEBUFFER_EXT,
                                    vtkgl::DEPTH_ATTACHMENT_EXT,
                                    vtkgl::RENDERBUFFER_EXT,
                                    this->DepthTexture);

  vtkOpenGLCheckErrorMacro("attach textures to FBO");

  // Validate FBO after attaching textures
  if (vtkgl::CheckFramebufferStatusEXT(vtkgl::FRAMEBUFFER_EXT) !=
      vtkgl::FRAMEBUFFER_COMPLETE_EXT && this->GetDebug())
    {
    vtkDebugMacro( << "FBO incomplete" );
    }

  // Disable FBO rendering
  vtkgl::BindFramebufferEXT(vtkgl::FRAMEBUFFER_EXT,savedFrameBuffer);

  this->FramebufferObjectSize = texSize;
  this->KBufferState = this->KBufferSize;

  vtkOpenGLCheckErrorMacro("failed after InitializeFramebufferObject");
}

//----------------------------------------------------------------------------
void vtkOpenGLHAVSVolumeMapper::Render(vtkRenderer *ren,
                                       vtkVolume *vol)
{
  ren->GetRenderWindow()->MakeCurrent();

  this->RenderWindow=ren->GetRenderWindow();

  if (!this->Initialized)
    {
    this->InitializationError =
      vtkHAVSVolumeMapper::NO_INIT_ERROR;
    this->Initialize(ren, vol);
    if (this->CheckInitializationError())
      {
      return;
      }
    }

  if (ren->GetRenderWindow()->CheckAbortStatus())
    {
    return;
    }

  // Check to see if we need to update the lookup table
  if ((this->ColorTransferFunctionMTime <
       vol->GetProperty()->GetRGBTransferFunction()->GetMTime())
      ||(this->AlphaTransferFunctionMTime <
         vol->GetProperty()->GetScalarOpacity()->GetMTime())
      ||(this->UnitDistance != vol->GetProperty()->GetScalarOpacityUnitDistance()))
    {
    this->InitializeLookupTables(vol);
    this->ColorTransferFunctionMTime.Modified();
    this->AlphaTransferFunctionMTime.Modified();
    }

  if (ren->GetRenderWindow()->CheckAbortStatus())
    {
    return;
    }

  // Check to see if we need to update the scalars
  if ((this->ScalarsMTime < this->MTime)
      || (this->ScalarsMTime < this->GetInput()->GetMTime())
      || (this->LastVolume != vol))
    {
    this->InitializationError =
      vtkHAVSVolumeMapper::NO_INIT_ERROR;
    this->InitializeScalars();
    this->InitializeGPUDataStructures();
    if (this->CheckInitializationError())
      return;
    this->ScalarsMTime.Modified();
    }

  if (ren->GetRenderWindow()->CheckAbortStatus())
    {
    return;
    }

  // Check to see if we need to update the geometry
  if ((this->UnstructuredGridMTime < this->GetInput()->GetMTime())
      || (this->UnstructuredGridMTime < this->MTime))
    {
    this->InitializationError =
      vtkHAVSVolumeMapper::NO_INIT_ERROR;
    this->InitializePrimitives(vol);
    this->InitializeLevelOfDetail();
    this->InitializeGPUDataStructures();
    if (this->CheckInitializationError())
      return;
    this->UnstructuredGridMTime.Modified();
    }

  if (ren->GetRenderWindow()->CheckAbortStatus())
    {
    return;
    }

  this->Timer->StartTimer();

  this->RenderHAVS(ren);
  this->LastVolume = vol;

  this->Timer->StopTimer();
  this->TimeToDraw = static_cast<float>(this->Timer->GetElapsedTime());

  // Update level-of-detail
  this->UpdateLevelOfDetail(TimeToDraw);
}

//----------------------------------------------------------------------------
// The OpenGL rendering
void vtkOpenGLHAVSVolumeMapper::RenderHAVS(vtkRenderer *ren)
{
  vtkOpenGLClearErrorMacro();

  glPushAttrib(GL_ENABLE_BIT         |
               GL_CURRENT_BIT        |
               GL_COLOR_BUFFER_BIT   |
               GL_STENCIL_BUFFER_BIT |
               GL_DEPTH_BUFFER_BIT   |
               GL_POLYGON_BIT        |
               GL_TEXTURE_BIT        |
               GL_LIGHTING_BIT       |
               GL_TRANSFORM_BIT      |
               GL_VIEWPORT_BIT);

  // Setup OpenGL state
  glShadeModel(GL_SMOOTH);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_CULL_FACE);
  glDisable(GL_LIGHTING);
  glDisable(GL_NORMALIZE);
  glDisable(GL_BLEND);
  glDisable(GL_SCISSOR_TEST);
  glDisable(GL_STENCIL_TEST);

  int screenWidth, screenHeight;
  ren->GetTiledSize(&screenWidth, &screenHeight);

  // Keep shaders up to date
  if (this->KBufferSize != this->KBufferState)
    {
    this->DeleteShaders();
    this->InitializeShaders();
    // Keep FBO up to date
    this->InitializeFramebufferObject();
    }

  int vpWidth = screenWidth;
  int vpHeight = screenHeight;
  if (screenWidth > this->FramebufferObjectSize)
    {
    vpWidth = this->FramebufferObjectSize;
    }
  if (screenHeight > this->FramebufferObjectSize)
    {
    vpHeight = this->FramebufferObjectSize;
    }

  // Bind geometry arrays
  if (this->GPUDataStructures)
    {
    glEnableClientState(GL_VERTEX_ARRAY);
    vtkgl::BindBuffer(vtkgl::ARRAY_BUFFER, this->VBOVertexName);
    glVertexPointer(3, GL_FLOAT, 0, static_cast<char *>(NULL));
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    vtkgl::BindBuffer(vtkgl::ARRAY_BUFFER, this->VBOTexCoordName);
    glTexCoordPointer(1, GL_FLOAT, 0, static_cast<char *>(NULL));

    vtkgl::BindBuffer(vtkgl::ELEMENT_ARRAY_BUFFER, this->VBOVertexIndexName);
    }
  else
    {
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, this->Vertices);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glTexCoordPointer(1, GL_FLOAT, 0, this->Scalars);
    }

  if (ren->GetRenderWindow()->CheckAbortStatus())
    {
    vtkOpenGLCheckErrorMacro("failed during Render");
    return;
    }

  // Object-space sorting
  double *e = ren->GetActiveCamera()->GetPosition();
  float eye[3];
  eye[0] = e[0];
  eye[1] = e[1];
  eye[2] = e[2];
  if (this->GPUDataStructures)
    {
    this->OrderedTriangles =
      static_cast<unsigned int *>(vtkgl::MapBuffer(vtkgl::ELEMENT_ARRAY_BUFFER,
                                                   vtkgl::WRITE_ONLY));
    }

  this->PartialVisibilitySort(eye);

  if (this->GPUDataStructures)
    {
    vtkgl::UnmapBuffer(vtkgl::ELEMENT_ARRAY_BUFFER);
    }

  this->UpdateProgress(0.4);
  if (ren->GetRenderWindow()->CheckAbortStatus())
    {
    vtkOpenGLCheckErrorMacro("failed during Render");
    return;
    }

  // Get depth range from OpenGL state for correct z
  GLfloat depthRange[2];
  glGetFloatv(GL_DEPTH_RANGE, depthRange);

  // Get the current z-buffer
  float *zbuffer =
    ren->GetRenderWindow()->GetZbufferData(0,0,screenWidth-1,screenHeight-1);

  // Enable FBO Rendering
  GLint savedFrameBuffer;
  glGetIntegerv(vtkgl::FRAMEBUFFER_BINDING_EXT,&savedFrameBuffer);
  vtkgl::BindFramebufferEXT(vtkgl::FRAMEBUFFER_EXT, this->FramebufferObject);

  // Setup z-buffer
  this->SetupFBOZBuffer(vpWidth, vpHeight, depthRange[0], depthRange[1],
                        zbuffer);
  delete [] zbuffer;

  // Setup multiple render targets
  this->SetupFBOMRT();

  // Draw Initialization pass
  this->DrawFBOInit(vpWidth, vpHeight, depthRange[0], depthRange[1]);

  // Draw Geometry pass
  this->DrawFBOGeometry();
  this->UpdateProgress(0.9);

  // Draw Flushing pass
  this->DrawFBOFlush(vpWidth, vpHeight, depthRange[0], depthRange[1]);

  // Disable FBO rendering
  vtkgl::BindFramebufferEXT(vtkgl::FRAMEBUFFER_EXT, savedFrameBuffer);

  glPopAttrib();

  // Blend Result into framebuffer
  this->DrawBlend(vpWidth, vpHeight, depthRange[0], depthRange[1]);

  this->UpdateProgress(1.0);
  vtkOpenGLCheckErrorMacro("failed after Render");
}

//----------------------------------------------------------------------------
// Draw the current z-buffer into the FBO z-buffer for correct compositing
// with existing geometry or widgets.
void vtkOpenGLHAVSVolumeMapper::SetupFBOZBuffer(int screenWidth,
                                                int screenHeight,
                                                float depthNear,
                                                float depthFar,
                                                float *zbuffer)
{
  vtkOpenGLClearErrorMacro();

  // Setup view for z-buffer copy
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0.0, static_cast<GLdouble>(screenWidth), 0.0,
          static_cast<GLdouble>(screenHeight),
          static_cast<GLdouble>(depthNear), static_cast<GLdouble>(depthFar));
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  // Enable FBO z-buffer
  glEnable(GL_DEPTH_TEST);
  glClearDepth(depthFar);
  glClear(GL_DEPTH_BUFFER_BIT);
  glDepthFunc(GL_LESS);

  glRasterPos2i(0,0);
  glPixelStorei(GL_PACK_ALIGNMENT, 1);
  glDrawPixels(screenWidth, screenHeight, GL_DEPTH_COMPONENT, GL_FLOAT,
               zbuffer);
  glFlush();

  // Make z-buffer Read only
  glDepthMask(GL_FALSE);

  // Reset view state after z-buffer copy
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();

  vtkOpenGLCheckErrorMacro("failed after SetupFBOZBuffer");
}

//----------------------------------------------------------------------------
// Setup reading and writing into multiple textures from an FBO
void vtkOpenGLHAVSVolumeMapper::SetupFBOMRT()
{
  vtkOpenGLClearErrorMacro();

  int numBuffers = (this->KBufferSize == VTK_KBUFFER_SIZE_2)? 2 : 4;
  GLenum buffers[4] = {vtkgl::COLOR_ATTACHMENT0_EXT,
                       vtkgl::COLOR_ATTACHMENT1_EXT,
                       vtkgl::COLOR_ATTACHMENT2_EXT,
                       vtkgl::COLOR_ATTACHMENT3_EXT};
  vtkgl::DrawBuffers(numBuffers, buffers);

  // Bind textures for reading
  glEnable(GL_TEXTURE_2D);
  vtkgl::ActiveTexture(vtkgl::TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, this->FramebufferTextures[0]);

  vtkgl::ActiveTexture(vtkgl::TEXTURE1);
  glBindTexture(GL_TEXTURE_2D, this->FramebufferTextures[1]);

  if (numBuffers == 2)
    {
    // Bind lookup tables
    glEnable(GL_TEXTURE_2D);
    vtkgl::ActiveTexture(vtkgl::TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, this->PsiTableTexture);

    glEnable(GL_TEXTURE_1D);
    vtkgl::ActiveTexture(vtkgl::TEXTURE3);
    glBindTexture(GL_TEXTURE_1D,this->TransferFunctionTexture);
    }
  else
    {
    // Bind lookup tables
    vtkgl::ActiveTexture(vtkgl::TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, this->FramebufferTextures[2]);

    vtkgl::ActiveTexture(vtkgl::TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, this->FramebufferTextures[3]);

    glEnable(GL_TEXTURE_2D);
    vtkgl::ActiveTexture(vtkgl::TEXTURE4);
    glBindTexture(GL_TEXTURE_2D, this->PsiTableTexture);

    glEnable(GL_TEXTURE_1D);
    vtkgl::ActiveTexture(vtkgl::TEXTURE5);
    glBindTexture(GL_TEXTURE_1D,this->TransferFunctionTexture);
    }

  vtkOpenGLCheckErrorMacro("SetupFBOMRT");
}

//----------------------------------------------------------------------------
// Draw a screen-aligned plane with the init fragment shader enabled.  The
// init fragment shader clears the framebuffer to 0 and the k-buffers to -1.
void vtkOpenGLHAVSVolumeMapper::DrawFBOInit(int screenWidth, int screenHeight,
                                            float depthNear, float depthFar)
{
  vtkOpenGLClearErrorMacro();

  // Bind initializing fragment shader
  glEnable(vtkgl::FRAGMENT_PROGRAM_ARB);
  vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB,
                        this->FragmentProgramBegin);

  // Setup ortho view
  glViewport(0,0,screenWidth,screenHeight);
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0.0, static_cast<GLdouble>(screenWidth), 0.0,
          static_cast<GLdouble>(screenHeight),
          static_cast<GLdouble>(depthNear), static_cast<GLdouble>(depthFar));
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  // Draw a quad to initialize the k-buffer
  glBegin(GL_QUADS);
  glVertex3f(0.0, 0.0, 0.0);
  glVertex3f(static_cast<float>(screenWidth), 0.0, 0.0);
  glVertex3f(static_cast<float>(screenWidth),
             static_cast<float>(screenHeight), 0.0);
  glVertex3f(0.0, static_cast<float>(screenHeight), 0.0);
  glEnd();

  vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB, 0);

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();

  vtkOpenGLCheckErrorMacro("failed after DrawFBOInit");
}

//----------------------------------------------------------------------------
// Draw the geometry using the k-buffer to sort and composite into the
// framebuffer.
void vtkOpenGLHAVSVolumeMapper::DrawFBOGeometry()
{
  vtkOpenGLClearErrorMacro();

  // Bind shaders
  glEnable(vtkgl::VERTEX_PROGRAM_ARB);
  vtkgl::BindProgramARB(vtkgl::VERTEX_PROGRAM_ARB, this->VertexProgram);
  vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB, this->FragmentProgram);

  float scale = this->MaxEdgeLength;
  if (this->LevelOfDetail || !this->PartiallyRemoveNonConvexities)
    {
    scale = this->LevelOfDetailMaxEdgeLength;
    }
  float params[4] = {1.0f/this->FramebufferObjectSize,
                     1.0f/this->FramebufferObjectSize,
                     scale,
                     0.0f};
  vtkgl::ProgramLocalParameter4fvARB(vtkgl::FRAGMENT_PROGRAM_ARB, 0, params);

  // Draw geometry
  if (this->GPUDataStructures)
    {
    glDrawElements(GL_TRIANGLES, this->LevelOfDetailTriangleCount*3,
                   GL_UNSIGNED_INT, static_cast<char *>(NULL));
    }
  else
    {
    glDrawElements(GL_TRIANGLES, this->LevelOfDetailTriangleCount*3,
                   GL_UNSIGNED_INT, this->OrderedTriangles);
    }

  vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB, 0);
  vtkgl::BindProgramARB(vtkgl::VERTEX_PROGRAM_ARB, 0);
  glDisable( vtkgl::VERTEX_PROGRAM_ARB);

  vtkOpenGLCheckErrorMacro("failed after DrawFBOGeometry");
}

//----------------------------------------------------------------------------
// Draw a k-1 screen-aligned planes to flush the valid entries from the
// k-buffer.
void vtkOpenGLHAVSVolumeMapper::DrawFBOFlush(int screenWidth,
                                             int screenHeight,
                                             float depthNear, float depthFar)
{
  vtkOpenGLClearErrorMacro();

  float scale = this->MaxEdgeLength;
  if (this->LevelOfDetail || !this->PartiallyRemoveNonConvexities)
    {
    scale = this->LevelOfDetailMaxEdgeLength;
    }
  float params[4] = {1.0f/this->FramebufferObjectSize,
                     1.0f/this->FramebufferObjectSize,
                     scale,
                     0.0f};

  // Bind fragment shader
  vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB, this->FragmentProgramEnd);
  vtkgl::ProgramLocalParameter4fvARB(vtkgl::FRAGMENT_PROGRAM_ARB, 0, params);


  // Setup ortho view
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0.0, static_cast<GLdouble>(screenWidth), 0.0,
          static_cast<GLdouble>(screenHeight),
          static_cast<GLdouble>(depthNear), static_cast<GLdouble>(depthFar));

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  // Draw k-1 quads to flush k-buffer
  int flushCount = (this->KBufferSize == VTK_KBUFFER_SIZE_2)? 1 : 5;
  for (int i = 0; i < flushCount; i++)
    {
    glBegin(GL_QUADS);
    glVertex3f(0.0, 0.0, 0.0);
    glVertex3f(0.0, static_cast<float>(screenHeight), 0.0);
    glVertex3f(static_cast<float>(screenWidth),
               static_cast<float>(screenHeight), 0.0);
    glVertex3f(static_cast<float>(screenWidth), 0.0, 0.0);
    glEnd();
    }

  // Disable shader
  vtkgl::BindProgramARB(vtkgl::FRAGMENT_PROGRAM_ARB, 0);
  glDisable( vtkgl::FRAGMENT_PROGRAM_ARB );

  // Reset view
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();

  // Disable Textures
  if (this->KBufferSize == VTK_KBUFFER_SIZE_2)
    {
    vtkgl::ActiveTexture(vtkgl::TEXTURE2);
    glDisable(GL_TEXTURE_2D);
    vtkgl::ActiveTexture(vtkgl::TEXTURE3);
    glDisable(GL_TEXTURE_1D);
    }
  else
    {
    vtkgl::ActiveTexture(vtkgl::TEXTURE4);
    glDisable(GL_TEXTURE_2D);
    vtkgl::ActiveTexture(vtkgl::TEXTURE5);
    glDisable(GL_TEXTURE_1D);

    }

  vtkgl::ActiveTexture(vtkgl::TEXTURE1);
  glDisable(GL_TEXTURE_2D);
  vtkgl::ActiveTexture(vtkgl::TEXTURE0);
  glDisable(GL_TEXTURE_2D);

  glDisable(GL_DEPTH_TEST);

  glFinish();

  // Disable vertex arrays
  if (this->GPUDataStructures)
    {
    vtkgl::BindBuffer(vtkgl::ARRAY_BUFFER, 0);
    vtkgl::BindBuffer(vtkgl::ELEMENT_ARRAY_BUFFER, 0);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }
  else
    {
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }

  vtkOpenGLCheckErrorMacro("failed after DrawFBOFlush");
}

//----------------------------------------------------------------------------
// Blend the result from the off screen rendering into the framebuffer by
// drawing a textured screen-aligned plane.  This avoids expensive data
// transfers between GPU and CPU.
void vtkOpenGLHAVSVolumeMapper::DrawBlend(int screenWidth, int screenHeight,
                                          float depthNear, float depthFar)
{
  vtkOpenGLClearErrorMacro();

  // Setup draw buffer
  glDrawBuffer(GL_BACK);

  // Setup 2D view
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0.0, screenWidth, 0.0, screenHeight, depthNear, depthFar);
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();

  // Bind resulting texture
  vtkgl::ActiveTexture(vtkgl::TEXTURE0);

  glBindTexture(GL_TEXTURE_2D, this->FramebufferTextures[0]);
  glEnable(GL_TEXTURE_2D);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

  float max_u =static_cast<float>(screenWidth)/static_cast<float>(this->FramebufferObjectSize);
  float max_v = static_cast<float>(screenHeight)/static_cast<float>(this->FramebufferObjectSize);
  if (max_u > 1.0) { max_u = 1.0; }
  if (max_v > 1.0) { max_v = 1.0; }

  // Setup blending.  Use the same non-standard blending function as PT to get
  // similar images.
  glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

  // Draw textured screen-aligned plane
  glColor4f(0.0, 0.0, 0.0, 0.0);
  glBegin(GL_QUADS);
  glTexCoord2f(0.0, 0.0);
  glVertex2f(0.0, 0.0);
  glTexCoord2f(max_u, 0.0);
  glVertex2f(screenWidth, 0.0);
  glTexCoord2f(max_u, max_v);
  glVertex2f(screenWidth, screenHeight);
  glTexCoord2f(0.0, max_v);
  glVertex2f(0.0, screenHeight);
  glEnd();

  // Reset view
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();

  // Restore OpenGL state
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glBindTexture(GL_TEXTURE_2D, 0);
  glDisable(GL_TEXTURE_2D);

  vtkOpenGLCheckErrorMacro("failed after DrawBlend");
}

//----------------------------------------------------------------------------
void vtkOpenGLHAVSVolumeMapper::PrintSelf(ostream& os, vtkIndent indent)
{
  if(this->RenderWindow!=0)
    {
    vtkOpenGLExtensionManager *extensions=
      static_cast<vtkOpenGLRenderWindow *>(this->RenderWindow.GetPointer())
      ->GetExtensionManager();
    if ( this->Initialized )
      {
      os << indent << "Supports GL_VERSION_1_3 (edge_clamp (1.2) and"
         << " multitexture (1.3) minimal version required by"
         << " GL_ARB_draw_buffers): "
         << extensions->ExtensionSupported( "GL_VERSION_1_3" );

      os << indent << "Supports GL_VERSION_2_0 (GL_ARB_draw_buffers is a core"
         << "feature): "
         << extensions->ExtensionSupported( "GL_VERSION_2_0" );

      os << indent << "Supports GL_ARB_draw_buffers: "
         << extensions->ExtensionSupported( "GL_ARB_draw_buffers" );

      os << indent << "Supports GL_EXT_framebuffer_object: "
         << extensions->ExtensionSupported( "GL_EXT_framebuffer_object" )
         << endl;
      os << indent << "Supports GL_ARB_vertex_program: "
         << extensions->ExtensionSupported( "GL_ARB_vertex_program" ) << endl;
      os << indent << "Supports GL_ARB_fragment_program: "
         << extensions->ExtensionSupported( "GL_ARB_fragment_program" ) << endl;
      os << indent << "Supports GL_ARB_texture_float"
         << extensions->ExtensionSupported( "GL_ARB_texture_float" ) << endl;
      os << indent << "Supports GL_ATI_texture_float: "
         << extensions->ExtensionSupported( "GL_ATI_texture_float" ) << endl;

      os << indent << "Supports GL_VERSION_1_5 (for optional core feature VBO): "
         << extensions->ExtensionSupported( "GL_VERSION_1_5" ) <<endl;

      os << indent << "Supports (optional) GL_ARB_vertex_buffer_object: "
         << extensions->ExtensionSupported( "GL_ARB_vertex_buffer_object" )
         <<endl;
      }
    }

  os << indent << "Framebuffer Object Size: "
     << this->FramebufferObjectSize << endl;

  this->Superclass::PrintSelf(os,indent);
}

//----------------------------------------------------------------------------
// Check the OpenGL extension manager for GPU features necessary for the
// HAVS algorithm.
bool vtkOpenGLHAVSVolumeMapper::SupportedByHardware(vtkRenderer *r)
{
  vtkOpenGLExtensionManager *extensions=
    static_cast<vtkOpenGLRenderWindow *>(r->GetRenderWindow())
    ->GetExtensionManager();

  // os mesa notes:
  // 9.1.4 -- test fails with open gl errors (mesa bug)
  // 9.2.0 w/llvmpipe -- test passes
  if ( extensions->DriverIsMesa()
    && !(extensions->DriverGLRendererIsOSMesa()
    && extensions->DriverGLRendererHasToken("llvmpipe"))
    && !extensions->GetIgnoreDriverBugs("Mesa FBO bugs") )
    {
    return false;
    }

  // Temporarily filter out the Macs, as this mapper makes the ATI driver crash
  // (RogueResearch2 on VTK, ATI Radeon X1600 OpenGL Engine 2.0 ATI-1.4.56) and
  // makes the Nvidia driver render some corrupted image (kamino on ParaView3
  // dashboard NVIDIA GeForce 7300 GT 2.0 NVIDIA-1.4.56).
  // This mapper does not actually use texture3D but it is known that Macs
  // only support texture3d through OpenGL 1.2 API, not as an extension, so
  // this is a good way to filter them out.
  int iAmAMac=!extensions->ExtensionSupported("GL_EXT_texture3D");

  // OpenGL 1.3 is required by GL_ARB_draw_buffers, GL_ARB_fragment_program
  // and GL_ARB_vertex_program
  // CLAMP_TO_EGDE is core feature of OpenGL 1.2 and
  // multitexture is core feature of OpenGL 1.3.
  int supports_GL_1_3=extensions->ExtensionSupported( "GL_VERSION_1_3" );


  int supports_GL_2_0=extensions->ExtensionSupported( "GL_VERSION_2_0" );
  int supports_draw_buffers;
  if(supports_GL_2_0)
    {
    supports_draw_buffers=1;
    }
  else
    {
    supports_draw_buffers=
      extensions->ExtensionSupported( "GL_ARB_draw_buffers" );
    }

  int supports_GL_ARB_fragment_program =
    extensions->ExtensionSupported( "GL_ARB_fragment_program" );
  int supports_GL_ARB_vertex_program =
    extensions->ExtensionSupported( "GL_ARB_vertex_program" );

  int supports_GL_EXT_framebuffer_object =
    extensions->ExtensionSupported( "GL_EXT_framebuffer_object");

   // GL_ARB_texture_float or GL_ATI_texture_float introduce new tokens but
   // no new function: don't need to call LoadExtension.

  int supports_GL_ARB_texture_float =
    extensions->ExtensionSupported( "GL_ARB_texture_float" );
  int supports_GL_ATI_texture_float =
    extensions->ExtensionSupported( "GL_ATI_texture_float" );

  return !iAmAMac && supports_GL_1_3 && supports_draw_buffers &&
    supports_GL_ARB_fragment_program && supports_GL_ARB_vertex_program &&
    supports_GL_EXT_framebuffer_object &&
    ( supports_GL_ARB_texture_float || supports_GL_ATI_texture_float);
}

//----------------------------------------------------------------------------
int vtkOpenGLHAVSVolumeMapper::FillInputPortInformation(int port,
                                                        vtkInformation* info)
{
  if(!this->Superclass::FillInputPortInformation(port, info))
    {
    return 0;
    }
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkUnstructuredGrid");
  return 1;
}
