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

  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 "vtkOpenGLError.h"
#include "vtkOpenGLRenderUtilities.h"
#include "vtkOpenGLRenderer.h"
#include "vtkPVDefaultPass.h"
#include "vtkRenderState.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkSmartPointer.h"
#include "vtkTileDisplayHelper.h"
#include "vtkTilesHelper.h"

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

using RenUtils = vtkOpenGLRenderUtilities;

// 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 the 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) VTK_OVERRIDE
  {
    vtkOpenGLClearErrorMacro();
    if (this->DelegatePass)
    {
      RenUtils::MarkDebugEvent("vtkMyImagePasterPass: Delegate Begin");
      this->DelegatePass->Render(render_state);
      vtkOpenGLCheckErrorMacro("failed after delegate pass render");
      RenUtils::MarkDebugEvent("vtkMyImagePasterPass: Delegate End");
    }
    if (this->IceTCompositePass)
    {
      RenUtils::MarkDebugEvent("vtkMyImagePasterPass: Image Extract Begin");
      this->IceTCompositePass->GetLastRenderedTile(this->Image);
      RenUtils::MarkDebugEvent("vtkMyImagePasterPass: Image Extract End");
    }
    if (this->Image.IsValid())
    {
      RenUtils::MarkDebugEvent("vtkMyImagePasterPass: RGBA Push Begin");
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      this->Image.PushToFrameBuffer(render_state->GetRenderer());
      RenUtils::MarkDebugEvent("vtkMyImagePasterPass: RGBA Push End");
      if (this->UseDepthBuffer)
      {
        RenUtils::MarkDebugEvent("vtkMyImagePasterPass: Depth Push Begin");
        this->IceTCompositePass->PushIceTDepthBufferToScreen(render_state);
        RenUtils::MarkDebugEvent("vtkMyImagePasterPass: Depth Push End");
      }
    }
    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) VTK_OVERRIDE
  {
    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) VTK_OVERRIDE
  {
    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) VTK_OVERRIDE
  {
    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) VTK_OVERRIDE
  {
    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);
}

//----------------------------------------------------------------------------
void vtkIceTSynchronizedRenderers::SlaveStartRender()
{
  this->Superclass::SlaveStartRender();

  int x, y;
  this->IceTCompositePass->GetTileDimensions(x, y);
  if (!(x == 1 && y == 1))
  {
    // Don't mess with tile mode behavior.
    return;
  }
  // Otherwise ensure that every node starts with a black background
  // to blend onto. This is somewhat redundant, we do the same elsewhere
  // with a glClear call, but OSPRay can't see that one.
  // see also vtkPVClientServerSynchronizedRenderers
  this->Renderer->SetBackground(0, 0, 0);
  this->Renderer->SetGradientBackground(false);
  this->Renderer->SetTexturedBackground(false);
}
