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

  Program:   ParaView
  Module:    vtkIceTSynchronizedRenderers.cxx

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html 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 "vtkIceTSynchronizedRenderers.h"

#include "vtkCamera.h"
#include "vtkCameraPass.h"
#include "vtkCullerCollection.h"
#include "vtkImageProcessingPass.h"
#include "vtkMultiProcessController.h"
#include "vtkObjectFactory.h"
#include "vtkPVDefaultPass.h"
#include "vtkRenderer.h"
#include "vtkOpenGLRenderer.h"
#include "vtkRenderState.h"
#include "vtkRenderWindow.h"
#include "vtkSmartPointer.h"
#include "vtkTileDisplayHelper.h"
#include "vtkTilesHelper.h"
#include "vtkTimerLog.h"
#include "vtkOpenGLError.h"

#include <assert.h>
#include <vtkgl.h>
#include <IceT.h>
#include <IceTGL.h>

#include <map>

// This pass is used to simply render an image onto the frame buffer. Used when
// an ImageProcessingPass is set to paste the IceT composited image into the
// frame buffer for th ImageProcessingPass.
class vtkMyImagePasterPass : public vtkRenderPass
{
public:
  static vtkMyImagePasterPass* New();
  vtkTypeMacro(vtkMyImagePasterPass, vtkRenderPass);

  vtkIceTCompositePass* IceTCompositePass;
  vtkRenderPass* DelegatePass;
  bool UseDepthBuffer;

  virtual void Render(const vtkRenderState* render_state)
    {
    vtkOpenGLClearErrorMacro();
    if (this->DelegatePass)
      {
      this->DelegatePass->Render(render_state);
      vtkOpenGLCheckErrorMacro("failed after delegate pass render");
      }
    if (this->IceTCompositePass)
      {
      this->IceTCompositePass->GetLastRenderedTile(this->Image);
      }
    if (this->Image.IsValid())
      {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      this->Image.PushToFrameBuffer();
      if (this->UseDepthBuffer)
        {
        this->IceTCompositePass->PushIceTDepthBufferToScreen(render_state);
        }
      }
    glFinish();
    vtkOpenGLCheckErrorMacro("failed after push depth buffer");
    }

  void SetImage(const vtkSynchronizedRenderers::vtkRawImage& image)
    {
    this->Image = image;
    }

  void SetUseDepthBuffer(bool useDB)
    {
    this->UseDepthBuffer = useDB;
    }

  virtual void ReleaseGraphicsResources(vtkWindow *window)
    {
    if (this->DelegatePass)
      {
      this->DelegatePass->ReleaseGraphicsResources(window);
      }
    if (this->IceTCompositePass)
      {
      this->IceTCompositePass->ReleaseGraphicsResources(window);
      }
    this->Superclass::ReleaseGraphicsResources(window);
    }


protected:
  vtkMyImagePasterPass()
    {
    this->DelegatePass = NULL;
    this->IceTCompositePass = NULL;
    this->UseDepthBuffer = false;
    }
  ~vtkMyImagePasterPass()
    {
    }
  vtkSynchronizedRenderers::vtkRawImage Image;
};
vtkStandardNewMacro(vtkMyImagePasterPass);

namespace
{
  class vtkMyCameraPass : public vtkCameraPass
  {
  vtkIceTCompositePass* IceTCompositePass;
public:
  vtkTypeMacro(vtkMyCameraPass, vtkCameraPass);
  static vtkMyCameraPass* New();

  // Description:
  // Set the icet composite pass.
  vtkSetObjectMacro(IceTCompositePass, vtkIceTCompositePass);

  virtual void GetTiledSizeAndOrigin(
    const vtkRenderState* render_state,
    int* width, int* height, int *originX,
    int* originY)
    {
    assert (this->IceTCompositePass != NULL);
    int tile_dims[2];
    this->IceTCompositePass->GetTileDimensions(tile_dims);
    if (tile_dims[0] > 1 || tile_dims[1]  > 1)
      {
      // we have a complicated relationship with tile-scale when we are in
      // tile-display mode :).
      // vtkPVSynchronizedRenderWindows sets up the tile-scale and origin on the
      // window so that 2D annotations work just fine. However that messes up
      // when we are using IceT since IceT will do the camera translations. So
      // for IceT's sake, we reset the tile_scale/tile_viewport when doing the
      // camera transformations. Of course, this is only an issue when rendering
      // for tile-displays.
      int tile_scale[2];
      double tile_viewport[4];
      render_state->GetRenderer()->GetRenderWindow()->GetTileScale(tile_scale);
      render_state->GetRenderer()->GetRenderWindow()->GetTileViewport(tile_viewport);
      render_state->GetRenderer()->GetRenderWindow()->SetTileScale(1, 1);
      render_state->GetRenderer()->GetRenderWindow()->SetTileViewport(0,0,1,1);
      this->Superclass::GetTiledSizeAndOrigin(render_state, width, height, originX, originY);
      render_state->GetRenderer()->GetRenderWindow()->SetTileScale(tile_scale);
      render_state->GetRenderer()->GetRenderWindow()->SetTileViewport(tile_viewport);

      *originX *= this->IceTCompositePass->GetTileDimensions()[0];
      *originY *= this->IceTCompositePass->GetTileDimensions()[1];
      *width *= this->IceTCompositePass->GetTileDimensions()[0];
      *height *= this->IceTCompositePass->GetTileDimensions()[1];
      }
    else
      {
      this->Superclass::GetTiledSizeAndOrigin(render_state, width, height, originX, originY);
      }
    }

  virtual void ReleaseGraphicsResources(vtkWindow *window)
    {
    if (this->IceTCompositePass)
      {
      this->IceTCompositePass->ReleaseGraphicsResources(window);
      }
    this->Superclass::ReleaseGraphicsResources(window);
    }
protected:
  vtkMyCameraPass() {this->IceTCompositePass = NULL; }
  ~vtkMyCameraPass() { this->SetIceTCompositePass(0); }
  };
  vtkStandardNewMacro(vtkMyCameraPass);

  // vtkPVIceTCompositePass extends vtkIceTCompositePass to add some ParaView
  // specific rendering tweaks eg.
  // * render to full viewport
  // * don't let IceT paste back rendered images to the active frame buffer.
  class vtkPVIceTCompositePass : public vtkIceTCompositePass
  {
public:
  vtkTypeMacro(vtkPVIceTCompositePass, vtkIceTCompositePass);
  static vtkPVIceTCompositePass* New();

  // Description:
  // Updates some IceT context parameters to suit ParaView's need esp. in
  // multi-view configuration.
  virtual void SetupContext(const vtkRenderState* render_state)
    {
    vtkOpenGLClearErrorMacro();
    this->Superclass::SetupContext(render_state);

    // Don't make icet render the composited image to the screen. We'll paste it
    // explicitly if needed. This is required since IceT/Viewport interactions
    // lead to weird results in multi-view configurations. Much easier to simply
    // paste back the image to the correct region after icet has rendered.
    icetDisable(ICET_GL_DISPLAY);
    icetDisable(ICET_GL_DISPLAY_INFLATE);

    if (render_state->GetFrameBuffer() == NULL)
      {
      vtkRenderWindow* window = render_state->GetRenderer()->GetRenderWindow();
      int *size = window->GetActualSize();
      glViewport(0, 0, size[0], size[1]);
      }
    glDisable(GL_SCISSOR_TEST);
    double background[3];
    render_state->GetRenderer()->GetBackground(background);
    // gradient background is not going to work correctly when using volume
    // rendering or translucent geometry, but otherwise if should work fine. We
    // set the background color so that solid background works correctly for
    // these two cases.
    glClearColor((GLclampf)background[0], (GLclampf)background[1],
      (GLclampf)background[2], 0.0f);
    icetEnable(ICET_CORRECT_COLORED_BACKGROUND);
    vtkOpenGLCheckErrorMacro("failed after setup context");
    }
protected:

  vtkPVIceTCompositePass()
    {
    vtkPVDefaultPass* defaultPass = vtkPVDefaultPass::New();
    this->SetRenderPass(defaultPass);
    defaultPass->Delete();
    }

  ~vtkPVIceTCompositePass()
    {
    }
  };
  vtkStandardNewMacro(vtkPVIceTCompositePass);
};

vtkStandardNewMacro(vtkIceTSynchronizedRenderers);
//vtkCxxSetObjectMacro(vtkIceTSynchronizedRenderers, ImageProcessingPass, vtkImageProcessingPass);
//----------------------------------------------------------------------------
vtkIceTSynchronizedRenderers::vtkIceTSynchronizedRenderers()
{
  // First thing we do is create the ice-t render pass. This is essential since
  // most methods calls on this class simply forward it to the ice-t render
  // pass.
  this->IceTCompositePass = vtkPVIceTCompositePass::New();

  vtkMyCameraPass* cameraPass = vtkMyCameraPass::New();
  cameraPass->SetDelegatePass(this->IceTCompositePass);
  cameraPass->SetIceTCompositePass(this->IceTCompositePass);
  this->CameraRenderPass = cameraPass;
  this->SetParallelController(vtkMultiProcessController::GetGlobalController());

  this->ImagePastingPass = vtkMyImagePasterPass::New();

  this->ImageProcessingPass = NULL;
  this->RenderPass = NULL;

  this->Identifier = 0;
}

//----------------------------------------------------------------------------
vtkIceTSynchronizedRenderers::~vtkIceTSynchronizedRenderers()
{
  vtkTileDisplayHelper::GetInstance()->EraseTile(this->Identifier);

  this->ImagePastingPass->Delete();
  this->IceTCompositePass->Delete();
  this->IceTCompositePass = 0;
  this->CameraRenderPass->Delete();
  this->CameraRenderPass = 0;
  this->SetImageProcessingPass(0);
  this->SetRenderPass(0);
}

//----------------------------------------------------------------------------
void vtkIceTSynchronizedRenderers::SetImageProcessingPass(
  vtkImageProcessingPass* pass)
{
  vtkSetObjectBodyMacro(ImageProcessingPass, vtkImageProcessingPass, pass);
  if (pass && this->Renderer)
    {
    int tile_dims[2];
    this->IceTCompositePass->GetTileDimensions(tile_dims);
    if (tile_dims[0] >= 1 && tile_dims[1] >= 1)
      {
      this->CameraRenderPass->SetAspectRatioOverride(tile_dims[0]*1.0/tile_dims[1]);
      }
    this->ImagePastingPass->DelegatePass = this->CameraRenderPass;
    this->ImagePastingPass->IceTCompositePass = this->IceTCompositePass;
    pass->SetDelegatePass(this->ImagePastingPass);
    this->Renderer->SetPass(pass);
    }
  else if (this->Renderer && this->CameraRenderPass)
    {
    this->CameraRenderPass->SetAspectRatioOverride(1.0);
    this->Renderer->SetPass(this->CameraRenderPass);
    }
}

//----------------------------------------------------------------------------
void vtkIceTSynchronizedRenderers::SetUseDepthBuffer(bool useDB)
{
  this->ImagePastingPass->SetUseDepthBuffer(useDB);
}

//----------------------------------------------------------------------------
void vtkIceTSynchronizedRenderers::SetRenderPass(vtkRenderPass *pass)
{
  vtkSetObjectBodyMacro(RenderPass, vtkRenderPass, pass);
  if (this->IceTCompositePass)
    {
    if (pass)
      {
      this->IceTCompositePass->SetRenderPass(pass);
      }
    else
      {
      vtkPVDefaultPass* defaultPass = vtkPVDefaultPass::New();
      this->IceTCompositePass->SetRenderPass(defaultPass);
      defaultPass->Delete();
      }
    }
}

//----------------------------------------------------------------------------
void vtkIceTSynchronizedRenderers::HandleEndRender()
{
  if (this->WriteBackImages)
    {
    this->WriteBackImages = false;
    this->Superclass::HandleEndRender();
    this->WriteBackImages = true;
    }
  else
    {
    this->Superclass::HandleEndRender();
    }

  if (this->WriteBackImages)
    {
    vtkSynchronizedRenderers::vtkRawImage lastRenderedImage =
      this->CaptureRenderedImage();
    if (lastRenderedImage.IsValid())
      {
      double viewport[4];
      this->IceTCompositePass->GetPhysicalViewport(viewport);
      vtkTileDisplayHelper::GetInstance()->SetTile(this->Identifier,
        viewport, this->Renderer,
        lastRenderedImage);
      }
    else
      {
      vtkTileDisplayHelper::GetInstance()->EraseTile(this->Identifier);
      }


    // Write-back either the freshly rendered tile or what was most recently
    // rendered.
    vtkTileDisplayHelper::GetInstance()->FlushTiles(this->Identifier,
      this->Renderer->GetActiveCamera()->GetLeftEye());
    }
}

//----------------------------------------------------------------------------
void vtkIceTSynchronizedRenderers::SetRenderer(vtkRenderer* ren)
{
  if (this->Renderer && this->Renderer->GetPass() == this->CameraRenderPass)
    {
    this->Renderer->SetPass(NULL);
    }
  this->Superclass::SetRenderer(ren);
  if (ren)
    {
    this->Renderer->SetPass(this->CameraRenderPass);
    // icet cannot work correctly in tile-display mode is software culling is
    // applied in vtkRenderer inself. vtkPVIceTCompositePass will cull out-of-frustum
    // props using icet-model-view matrix later.
    ren->GetCullers()->RemoveAllItems();
    }
}

//----------------------------------------------------------------------------
void vtkIceTSynchronizedRenderers::SetRenderEmptyImages(bool useREI)
{
  this->IceTCompositePass->SetRenderEmptyImages(useREI);
}

//----------------------------------------------------------------------------
void vtkIceTSynchronizedRenderers::SetImageReductionFactor(int val)
{
  // Don't call superclass. Since ice-t has better mechanisms for dealing with
  // image reduction factor rather than simply reducing the viewport. This
  // ensures that it works nicely in tile-display mode as well.
  // this->Superclass::SetImageReductionFactor(val);
  this->IceTCompositePass->SetImageReductionFactor(val);
}

//----------------------------------------------------------------------------
vtkSynchronizedRenderers::vtkRawImage&
vtkIceTSynchronizedRenderers::CaptureRenderedImage()
{
  // We capture the image from IceTCompositePass. This avoids the capture of
  // buffer from screen when not necessary.
  vtkRawImage& rawImage =
    (this->GetImageReductionFactor() == 1)?
    this->FullImage : this->ReducedImage;

  if (!rawImage.IsValid())
    {
    this->IceTCompositePass->GetLastRenderedTile(rawImage);
    if (rawImage.IsValid() && this->ImageProcessingPass)
      {
      // When using an image processing pass, we simply capture the result from
      // the active buffer. However, we do that, only when IceT produced some
      // valid result on this process.
      rawImage.Capture(this->Renderer);
      }
    }
  return rawImage;
}

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