// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-FileCopyrightText: Copyright 2003 Sandia Corporation
// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-Sandia-USGov
#include "vtkParallelRenderManager.h"

#include "vtkActor.h"
#include "vtkActorCollection.h"
#include "vtkCallbackCommand.h"
#include "vtkCamera.h"
#include "vtkDoubleArray.h"
#include "vtkLight.h"
#include "vtkLightCollection.h"
#include "vtkMath.h"
#include "vtkMultiProcessController.h"
#include "vtkMultiProcessStream.h" // needed for vtkMultiProcessStream.
#include "vtkPolyDataMapper.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkRendererCollection.h"
#include "vtkTimerLog.h"
#include "vtkUnsignedCharArray.h"

VTK_ABI_NAMESPACE_BEGIN
static void AbortRenderCheck(
  vtkObject* caller, unsigned long vtkNotUsed(event), void* clientData, void*);

static void GenericStartRender(
  vtkObject* caller, unsigned long vtkNotUsed(event), void* clientData, void*);
static void GenericEndRender(
  vtkObject* caller, unsigned long vtkNotUsed(event), void* clientData, void*);
/*
static void ResetCamera(vtkObject *caller,
                        unsigned long vtkNotUsed(event),
                        void *clientData, void *);
static void ResetCameraClippingRange(vtkObject *caller,
                                     unsigned long vtkNotUsed(event),
                                     void *clientData, void *);
*/
static void RenderRMI(void* arg, void*, int, int);
static void ComputeVisiblePropBoundsRMI(void* arg, void*, int, int);
bool vtkParallelRenderManager::DefaultRenderEventPropagation = true;

//------------------------------------------------------------------------------
vtkParallelRenderManager::vtkParallelRenderManager()
{
  this->RenderWindow = nullptr;
  this->ObservingRenderWindow = 0;
  this->ObservingAbort = 0;

  this->Controller = nullptr;
  this->SetController(vtkMultiProcessController::GetGlobalController());
  this->RootProcessId = 0;

  this->Renderers = vtkRendererCollection::New();
  this->SyncRenderWindowRenderers = 1;

  this->Lock = 0;

  this->ImageReductionFactor = 1;
  this->MaxImageReductionFactor = 16;
  this->AutoImageReductionFactor = 0;
  this->AverageTimePerPixel = 0.0;

  this->RenderTime = 0.0;
  this->ImageProcessingTime = 0.0;

  this->ParallelRendering = 1;
  this->WriteBackImages = 1;
  this->MagnifyImages = 1;
  this->MagnifyImageMethod = vtkParallelRenderManager::NEAREST;
  this->RenderEventPropagation = vtkParallelRenderManager::DefaultRenderEventPropagation ? 1 : 0;
  this->UseCompositing = 1;

  this->FullImage = vtkUnsignedCharArray::New();
  this->ReducedImage = vtkUnsignedCharArray::New();
  this->FullImageUpToDate = 0;
  this->ReducedImageUpToDate = 0;
  this->RenderWindowImageUpToDate = 0;
  this->FullImageSize[0] = 0;
  this->FullImageSize[1] = 0;

  this->ReducedImageSize[0] = 0;
  this->ReducedImageSize[1] = 0;

  this->ForceRenderWindowSize = 0;
  this->ForcedRenderWindowSize[0] = 0;
  this->ForcedRenderWindowSize[1] = 0;

  this->Viewports = vtkDoubleArray::New();
  this->Viewports->SetNumberOfComponents(4);

  this->UseRGBA = 1;

  this->AddedRMIs = 0;
  this->RenderRMIId = 0;
  this->BoundsRMIId = 0;
  this->Timer = vtkTimerLog::New();

  this->UseBackBuffer = 1;
  this->SynchronizeTileProperties = 1;
}

//------------------------------------------------------------------------------
vtkParallelRenderManager::~vtkParallelRenderManager()
{
  this->SetRenderWindow(nullptr);
  if (this->Controller && this->AddedRMIs)
  {
    this->Controller->RemoveRMI(this->RenderRMIId);
    this->Controller->RemoveRMI(this->BoundsRMIId);
    this->AddedRMIs = 0;
  }
  this->SetController(nullptr);
  if (this->FullImage)
    this->FullImage->Delete();
  if (this->ReducedImage)
    this->ReducedImage->Delete();
  if (this->Viewports)
    this->Viewports->Delete();
  if (this->Timer)
    this->Timer->Delete();
  if (this->Renderers)
    this->Renderers->Delete();
}

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

  os << indent << "ParallelRendering: " << (this->ParallelRendering ? "on" : "off") << endl;
  os << indent << "RenderEventPropagation: " << (this->RenderEventPropagation ? "on" : "off")
     << endl;
  os << indent << "UseCompositing: " << (this->UseCompositing ? "on" : "off") << endl;
  os << indent << "SyncRenderWindowRenderers: " << (this->SyncRenderWindowRenderers ? "on" : "off")
     << endl;

  os << indent << "ObservingRenderWindow: " << (this->ObservingRenderWindow ? "yes" : "no") << endl;
  os << indent << "Locked: " << (this->Lock ? "yes" : "no") << endl;

  os << indent << "ImageReductionFactor: " << this->ImageReductionFactor << endl;
  os << indent << "MaxImageReductionFactor: " << this->MaxImageReductionFactor << endl;
  os << indent << "AutoImageReductionFactor: " << (this->AutoImageReductionFactor ? "on" : "off")
     << endl;

  if (this->MagnifyImageMethod == vtkParallelRenderManager::LINEAR)
  {
    os << indent << "MagnifyImageMethod: LINEAR\n";
  }
  else if (this->MagnifyImageMethod == vtkParallelRenderManager::NEAREST)
  {
    os << indent << "MagnifyImageMethod: NEAREST\n";
  }

  os << indent << "WriteBackImages: " << (this->WriteBackImages ? "on" : "off") << endl;
  os << indent << "MagnifyImages: " << (this->MagnifyImages ? "on" : "off") << endl;

  os << indent << "FullImageSize: (" << this->FullImageSize[0] << ", " << this->FullImageSize[1]
     << ")" << endl;
  os << indent << "ReducedImageSize: (" << this->ReducedImageSize[0] << ", "
     << this->ReducedImageSize[1] << ")" << endl;

  os << indent << "RenderWindow: " << this->RenderWindow << endl;
  os << indent << "Controller: " << this->Controller << endl;
  os << indent << "Renderers: " << this->Renderers << endl;
  os << indent << "RootProcessId: " << this->RootProcessId << endl;

  os << indent << "Last render time: " << this->RenderTime << endl;

  os << indent << "Last image processing time: " << this->ImageProcessingTime << endl;
  os << indent << "UseRGBA: " << this->UseRGBA << endl;
  os << indent << "SynchronizeTileProperties: " << this->SynchronizeTileProperties << endl;

  os << indent << "FullImage: ";
  if (this->FullImage)
  {
    this->FullImage->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << "(none)" << endl;
  }

  os << indent << "ForcedRenderWindowSize: " << this->ForcedRenderWindowSize[0] << " "
     << this->ForcedRenderWindowSize[1] << endl;

  os << indent << "ForceRenderWindowSize: " << this->ForceRenderWindowSize << endl;

  os << indent << "UseBackBuffer: " << (this->UseBackBuffer ? "on" : "off") << endl;
}

//------------------------------------------------------------------------------
vtkRenderWindow* vtkParallelRenderManager::MakeRenderWindow()
{
  vtkDebugMacro("MakeRenderWindow");

  return vtkRenderWindow::New();
}

//------------------------------------------------------------------------------
vtkRenderer* vtkParallelRenderManager::MakeRenderer()
{
  vtkDebugMacro("MakeRenderer");

  return vtkRenderer::New();
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::AddRenderWindowEventHandlers()
{
  if (this->RenderWindow && !this->ObservingRenderWindow)
  {
    vtkCallbackCommand* cbc = vtkCallbackCommand::New();
    cbc->SetCallback(::GenericStartRender);
    cbc->SetClientData((void*)this);
    // renWin will delete the cbc when the observer is removed.
    this->StartRenderTag = this->RenderWindow->AddObserver(vtkCommand::StartEvent, cbc);
    cbc->Delete();

    cbc = vtkCallbackCommand::New();
    cbc->SetCallback(::GenericEndRender);
    cbc->SetClientData((void*)this);
    // renWin will delete the cbc when the observer is removed.
    this->EndRenderTag = this->RenderWindow->AddObserver(vtkCommand::EndEvent, cbc);
    cbc->Delete();
    this->ObservingRenderWindow = 1;
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::RemoveRenderWindowEventHandlers()
{
  if (this->RenderWindow && this->ObservingRenderWindow)
  {
    this->RenderWindow->RemoveObserver(this->StartRenderTag);
    this->RenderWindow->RemoveObserver(this->EndRenderTag);
    this->StartRenderTag = 0;
    this->EndRenderTag = 0;
    this->ObservingRenderWindow = 0;
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::SetRenderWindow(vtkRenderWindow* renWin)
{
  vtkDebugMacro("SetRenderWindow");
  if (this->RenderWindow == renWin)
  {
    return;
  }

  if (this->RenderWindow)
  {
    // Remove all of the observers.
    if (this->ObservingAbort)
    {
      this->RenderWindow->RemoveObserver(this->AbortRenderCheckTag);
      this->AbortRenderCheckTag = 0;
      this->ObservingAbort = 0;
    }
    this->RemoveRenderWindowEventHandlers();
  }

  vtkSetObjectBodyMacro(RenderWindow, vtkRenderWindow, renWin);

  if (this->RenderWindow)
  {
    vtkCallbackCommand* cbc;

    // In case a subclass wants to raise aborts.
    cbc = vtkCallbackCommand::New();
    cbc->SetCallback(::AbortRenderCheck);
    cbc->SetClientData((void*)this);
    // renWin will delete the cbc when the observer is removed.
    this->AbortRenderCheckTag = renWin->AddObserver(vtkCommand::AbortCheckEvent, cbc);
    cbc->Delete();
    this->ObservingAbort = 1;

    this->AddRenderWindowEventHandlers();
  }
}

//------------------------------------------------------------------------------
vtkCxxSetObjectMacro(vtkParallelRenderManager, Controller, vtkMultiProcessController);

//------------------------------------------------------------------------------
void vtkParallelRenderManager::InitializePieces()
{
  vtkDebugMacro("InitializePieces");

  vtkRendererCollection* rens;
  vtkRenderer* ren;
  vtkActorCollection* actors;
  vtkActor* actor;
  vtkMapper* mapper;
  vtkPolyDataMapper* pdMapper;
  int piece, numPieces;

  if ((this->RenderWindow == nullptr) || (this->Controller == nullptr))
  {
    vtkWarningMacro("Called InitializePieces before setting RenderWindow or Controller");
    return;
  }
  piece = this->Controller->GetLocalProcessId();
  numPieces = this->Controller->GetNumberOfProcesses();

  rens = this->GetRenderers();
  vtkCollectionSimpleIterator rsit;
  rens->InitTraversal(rsit);
  while ((ren = rens->GetNextRenderer(rsit)))
  {
    actors = ren->GetActors();
    vtkCollectionSimpleIterator ait;
    actors->InitTraversal(ait);
    while ((actor = actors->GetNextActor(ait)))
    {
      mapper = actor->GetMapper();
      pdMapper = vtkPolyDataMapper::SafeDownCast(mapper);
      if (pdMapper)
      {
        pdMapper->SetPiece(piece);
        pdMapper->SetNumberOfPieces(numPieces);
      }
    }
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::InitializeOffScreen()
{
  vtkDebugMacro("InitializeOffScreen");

  if ((this->RenderWindow == nullptr) || (this->Controller == nullptr))
  {
    vtkWarningMacro("Called InitializeOffScreen before setting RenderWindow or Controller");
    return;
  }

  if ((this->Controller->GetLocalProcessId() != this->RootProcessId) || !this->WriteBackImages)
  {
    this->RenderWindow->OffScreenRenderingOn();
  }
  else
  {
    this->RenderWindow->OffScreenRenderingOff();
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::StartInteractor()
{
  vtkDebugMacro("StartInteractor");

  if ((this->Controller == nullptr) || (this->RenderWindow == nullptr))
  {
    vtkErrorMacro("Must set Controller and RenderWindow before starting interactor.");
    return;
  }

  if (this->Controller->GetLocalProcessId() == this->RootProcessId)
  {
    vtkRenderWindowInteractor* inter = this->RenderWindow->GetInteractor();
    if (!inter)
    {
      vtkErrorMacro("Render window does not have an interactor.");
    }
    else
    {
      inter->Initialize();
      inter->Start();
    }
    // By the time we reach here, the interaction is finished.
    this->StopServices();
  }
  else
  {
    this->StartServices();
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::StartServices()
{
  vtkDebugMacro("StartServices");

  if (!this->Controller)
  {
    vtkErrorMacro("Must set Controller before starting service");
    return;
  }
  if (this->Controller->GetLocalProcessId() == this->RootProcessId)
  {
    vtkWarningMacro("Starting service on root process (probably not what you wanted to do)");
  }

  this->InitializeRMIs();
  this->Controller->ProcessRMIs();
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::StopServices()
{
  vtkDebugMacro("StopServices");

  if (!this->Controller)
  {
    vtkErrorMacro("Must set Controller before stopping service");
    return;
  }
  if (this->Controller->GetLocalProcessId() != this->RootProcessId)
  {
    vtkErrorMacro("Can only stop services on root node");
    return;
  }

  this->Controller->TriggerRMIOnAllChildren(vtkMultiProcessController::BREAK_RMI_TAG);
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::GenericStartRenderCallback()
{
  if (!this->Controller)
  {
    return;
  }

  if (this->Controller->GetLocalProcessId() == this->RootProcessId)
  {
    this->StartRender();
  }
  else // LocalProcessId != RootProcessId
  {
    this->SatelliteStartRender();
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::GenericEndRenderCallback()
{
  if (!this->Controller)
  {
    return;
  }

  if (this->Controller->GetLocalProcessId() == this->RootProcessId)
  {
    this->EndRender();
  }
  else // LocalProcessId != RootProcessId
  {
    this->SatelliteEndRender();
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::StartRender()
{
  vtkParallelRenderManager::RenderWindowInfo winInfo;
  vtkParallelRenderManager::RendererInfo renInfo;
  vtkParallelRenderManager::LightInfo lightInfo;

  vtkDebugMacro("StartRender");

  if ((this->Controller == nullptr) || (this->Lock))
  {
    return;
  }
  this->Lock = 1;

  this->FullImageUpToDate = 0;
  this->ReducedImageUpToDate = 0;
  this->RenderWindowImageUpToDate = 0;

  if (this->FullImage->GetPointer(0) == this->ReducedImage->GetPointer(0))
  {
    // "Un-share" pointer for full/reduced images in case we need separate
    // arrays this run.
    this->ReducedImage->Initialize();
  }

  if (!this->ParallelRendering)
  {
    this->Lock = 0;
    return;
  }

  this->InvokeEvent(vtkCommand::StartEvent, nullptr);

  this->ImageProcessingTime = 0;

  // Used to time the total render (without compositing).
  this->Timer->StartTimer();

  if (this->AutoImageReductionFactor)
  {
    this->SetImageReductionFactorForUpdateRate(this->RenderWindow->GetDesiredUpdateRate());
  }

  // Make adjustments for window size.
  int* tilesize;
  if (this->ForceRenderWindowSize)
  {
    tilesize = this->ForcedRenderWindowSize;
  }
  else
  {
    tilesize = this->RenderWindow->GetActualSize();
  }
  int size[2];
  size[0] = tilesize[0];
  size[1] = tilesize[1];
  if ((size[0] == 0) || (size[1] == 0))
  {
    // It helps to have a real window size.
    vtkDebugMacro("Resetting window size to 300x300");
    size[0] = size[1] = 300;
    this->RenderWindow->SetSize(size[0], size[1]);
  }
  this->FullImageSize[0] = size[0];
  this->FullImageSize[1] = size[1];

  // Round up.
  this->ReducedImageSize[0] =
    (int)((size[0] + this->ImageReductionFactor - 1) / this->ImageReductionFactor);
  this->ReducedImageSize[1] =
    (int)((size[1] + this->ImageReductionFactor - 1) / this->ImageReductionFactor);

  // Collect and distribute information about current state of RenderWindow
  vtkRendererCollection* rens = this->GetRenderers();
  winInfo.FullSize[0] = this->FullImageSize[0];
  winInfo.FullSize[1] = this->FullImageSize[1];
  winInfo.ReducedSize[0] = this->ReducedImageSize[0];
  winInfo.ReducedSize[1] = this->ReducedImageSize[1];
  winInfo.NumberOfRenderers = rens->GetNumberOfItems();
  winInfo.ImageReductionFactor = this->ImageReductionFactor;
  winInfo.UseCompositing = this->UseCompositing;
  winInfo.DesiredUpdateRate = this->RenderWindow->GetDesiredUpdateRate();
  this->RenderWindow->GetTileScale(winInfo.TileScale);
  this->RenderWindow->GetTileViewport(winInfo.TileViewport);

  if (this->RenderEventPropagation)
  {
    this->Controller->TriggerRMIOnAllChildren(vtkParallelRenderManager::RENDER_RMI_TAG);
  }

  // Gather information about the window to send.
  vtkMultiProcessStream stream;
  winInfo.Save(stream);
  this->CollectWindowInformation(stream);

  if (this->ImageReductionFactor > 1)
  {
    this->Viewports->SetNumberOfTuples(rens->GetNumberOfItems());
  }

  vtkCollectionSimpleIterator cookie;
  vtkRenderer* ren;
  int i;

  for (rens->InitTraversal(cookie), i = 0; (ren = rens->GetNextRenderer(cookie)) != nullptr; i++)
  {
    ren->GetViewport(renInfo.Viewport);

    // Adjust Renderer viewports to get reduced size image.
    if (this->ImageReductionFactor > 1)
    {
      this->Viewports->SetTuple(i, renInfo.Viewport);
      if (this->ImageReduceRenderer(ren))
      {
        renInfo.Viewport[0] /= this->ImageReductionFactor;
        renInfo.Viewport[1] /= this->ImageReductionFactor;
        renInfo.Viewport[2] /= this->ImageReductionFactor;
        renInfo.Viewport[3] /= this->ImageReductionFactor;
        ren->SetViewport(renInfo.Viewport);
      }
    }

    vtkTypeBool hasActiveCamera = ren->IsActiveCameraCreated();
    vtkCamera* cam = ren->GetActiveCamera();
    if (!hasActiveCamera)
    {
      this->ResetCamera(ren);
    }
    cam->GetPosition(renInfo.CameraPosition);
    cam->GetFocalPoint(renInfo.CameraFocalPoint);
    cam->GetViewUp(renInfo.CameraViewUp);
    cam->GetClippingRange(renInfo.CameraClippingRange);
    renInfo.CameraViewAngle = cam->GetViewAngle();
    cam->GetWindowCenter(renInfo.WindowCenter);

    ren->GetBackground(renInfo.Background);
    ren->GetBackground2(renInfo.Background2);
    renInfo.GradientBackground = ren->GetGradientBackground();
    if (cam->GetParallelProjection())
    {
      renInfo.ParallelScale = cam->GetParallelScale();
    }
    else
    {
      renInfo.ParallelScale = 0.0;
    }
    renInfo.Draw = ren->GetDraw();
    vtkLightCollection* lc = ren->GetLights();
    renInfo.NumberOfLights = lc->GetNumberOfItems();
    renInfo.Save(stream);

    vtkLight* light;
    vtkCollectionSimpleIterator lsit;
    for (lc->InitTraversal(lsit); (light = lc->GetNextLight(lsit));)
    {
      lightInfo.Type = (double)(light->GetLightType());
      light->GetPosition(lightInfo.Position);
      light->GetFocalPoint(lightInfo.FocalPoint);
      lightInfo.Save(stream);
    }
    this->CollectRendererInformation(ren, stream);
  }

  if (!this->Controller->Broadcast(stream, this->Controller->GetLocalProcessId()))
  {
    return;
  }

  // Backwards compatibility stuff.
  this->SendWindowInformation();
  rens->InitTraversal(cookie);
  while ((ren = rens->GetNextRenderer(cookie)) != nullptr)
  {
    this->SendRendererInformation(ren);
  }

  this->PreRenderProcessing();
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::EndRender()
{
  if (!this->ParallelRendering)
  {
    return;
  }

  this->Timer->StopTimer();
  this->RenderTime = this->Timer->GetElapsedTime() - this->ImageProcessingTime;

  // Just because we are not doing compositing does not mean a subclass
  // does not need to do post render processing.
  //   if (!this->UseCompositing)
  //     {
  //     this->Lock = 0;
  //     return;
  //     }

  if (this->CheckForAbortComposite())
  {
    this->Lock = 0;
    return;
  }

  this->PostRenderProcessing();

  // Restore renderer viewports, if necessary.
  if (this->ImageReductionFactor > 1)
  {
    vtkRendererCollection* rens = this->GetRenderers();
    vtkCollectionSimpleIterator cookie;
    vtkRenderer* ren;
    int i;
    for (rens->InitTraversal(cookie), i = 0; (ren = rens->GetNextRenderer(cookie)) != nullptr; i++)
    {
      ren->SetViewport(this->Viewports->GetPointer(4 * i));
    }
  }

  this->WriteFullImage();

  this->InvokeEvent(vtkCommand::EndEvent, nullptr);

  this->Lock = 0;
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::SatelliteEndRender()
{
  if (this->CheckForAbortComposite())
  {
    return;
  }
  // It's a mistake to check ParallelRendering on the Satellites.
  // The Root node decides if the render calls are to be propagated to the
  // satellites...the satellites always reply to the Root nodes requests.
  //  if (!this->ParallelRendering)
  //    {
  //    return;
  //    }
  // Just because we are not doing compositing does not mean a subclass
  // does not need to do post render processing.
  //   if (!this->UseCompositing)
  //     {
  //     return;
  //     }

  this->PostRenderProcessing();

  this->WriteFullImage();

  this->InvokeEvent(vtkCommand::EndEvent, nullptr);
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::RenderRMI()
{
  this->RenderWindow->Render();
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::ResetCamera(vtkRenderer* ren)
{
  vtkDebugMacro("ResetCamera");

  double bounds[6];

  if (this->Lock)
  {
    // Can't query other processes in the middle of a render.
    // Just grab local value instead.
    this->LocalComputeVisiblePropBounds(ren, bounds);
    ren->ResetCamera(bounds);
    return;
  }

  this->Lock = 1;

  this->ComputeVisiblePropBounds(ren, bounds);
  // Keep from setting camera from some outrageous value.
  if (!vtkMath::AreBoundsInitialized(bounds))
  {
    // See if the not pickable values are better.
    ren->ComputeVisiblePropBounds(bounds);
    if (!vtkMath::AreBoundsInitialized(bounds))
    {
      this->Lock = 0;
      return;
    }
  }
  ren->ResetCamera(bounds);

  this->Lock = 0;
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::ResetCameraClippingRange(vtkRenderer* ren)
{
  vtkDebugMacro("ResetCameraClippingRange");

  double bounds[6];

  if (this->Lock)
  {
    // Can't query other processes in the middle of a render.
    // Just grab local value instead.
    this->LocalComputeVisiblePropBounds(ren, bounds);
    ren->ResetCameraClippingRange(bounds);
    return;
  }

  this->Lock = 1;

  this->ComputeVisiblePropBounds(ren, bounds);
  ren->ResetCameraClippingRange(bounds);

  this->Lock = 0;
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::ComputeVisiblePropBoundsRMI(int renderId)
{
  vtkDebugMacro("ComputeVisiblePropBoundsRMI");
  int i;

  vtkRendererCollection* rens = this->GetRenderers();
  vtkRenderer* ren = nullptr;
  vtkCollectionSimpleIterator rsit;
  rens->InitTraversal(rsit);
  for (i = 0; i <= renderId; i++)
  {
    ren = rens->GetNextRenderer(rsit);
  }

  if (ren == nullptr)
  {
    vtkWarningMacro("Client requested invalid renderer in "
                    "ComputeVisiblePropBoundsRMI\n"
                    "Defaulting to first renderer");
    ren = rens->GetFirstRenderer();
  }

  double bounds[6];
  this->LocalComputeVisiblePropBounds(ren, bounds);

  this->Controller->Send(bounds, 6, this->RootProcessId, vtkParallelRenderManager::BOUNDS_TAG);
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::LocalComputeVisiblePropBounds(vtkRenderer* ren, double bounds[6])
{
  ren->ComputeVisiblePropBounds(bounds);
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::ComputeVisiblePropBounds(vtkRenderer* ren, double bounds[6])
{
  vtkDebugMacro(<< "ComputeVisiblePropBounds");

  if (!this->ParallelRendering)
  {
    ren->ComputeVisiblePropBounds(bounds);
    return;
  }

  if (this->Controller)
  {
    if (this->Controller->GetLocalProcessId() != this->RootProcessId)
    {
      vtkErrorMacro("ComputeVisiblePropBounds/ResetCamera can only be called on root process");
      return;
    }

    vtkRendererCollection* rens = this->GetRenderers();
    vtkCollectionSimpleIterator rsit;
    rens->InitTraversal(rsit);
    int renderId = 0;
    while (true)
    {
      vtkRenderer* myren = rens->GetNextRenderer(rsit);
      if (myren == nullptr)
      {
        vtkWarningMacro("ComputeVisiblePropBounds called with unregistered renderer "
          << ren << "\nDefaulting to first renderer.");
        renderId = 0;
        break;
      }
      if (myren == ren)
      {
        // Found correct renderer.
        break;
      }
      renderId++;
    }

    // Invoke RMI's on servers to perform their own ComputeVisiblePropBounds.
    int numProcs = this->Controller->GetNumberOfProcesses();
    int id;
    this->Controller->TriggerRMIOnAllChildren(
      &renderId, sizeof(int), vtkParallelRenderManager::COMPUTE_VISIBLE_PROP_BOUNDS_RMI_TAG);

    // Now that all the RMI's have been invoked, we can safely query our
    // local bounds even if an Update requires a parallel operation.

    this->LocalComputeVisiblePropBounds(ren, bounds);

    // Collect all the bounds.
    for (id = 0; id < numProcs; id++)
    {
      double tmp[6];

      if (id == this->RootProcessId)
      {
        continue;
      }

      this->Controller->Receive(tmp, 6, id, vtkParallelRenderManager::BOUNDS_TAG);

      if (tmp[0] < bounds[0])
      {
        bounds[0] = tmp[0];
      }
      if (tmp[1] > bounds[1])
      {
        bounds[1] = tmp[1];
      }
      if (tmp[2] < bounds[2])
      {
        bounds[2] = tmp[2];
      }
      if (tmp[3] > bounds[3])
      {
        bounds[3] = tmp[3];
      }
      if (tmp[4] < bounds[4])
      {
        bounds[4] = tmp[4];
      }
      if (tmp[5] > bounds[5])
      {
        bounds[5] = tmp[5];
      }
    }
  }
  else
  {
    vtkWarningMacro("ComputeVisiblePropBounds/ResetCamera called before Controller set");

    ren->ComputeVisiblePropBounds(bounds);
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::InitializeRMIs()
{
  vtkDebugMacro("InitializeRMIs");

  if (this->Controller == nullptr)
  {
    vtkErrorMacro("InitializeRMIs requires a controller.");
    return;
  }

  if (!this->AddedRMIs)
  {
    this->AddedRMIs = 1;
    this->RenderRMIId =
      this->Controller->AddRMI(::RenderRMI, this, vtkParallelRenderManager::RENDER_RMI_TAG);
    this->BoundsRMIId = this->Controller->AddRMI(::ComputeVisiblePropBoundsRMI, this,
      vtkParallelRenderManager::COMPUTE_VISIBLE_PROP_BOUNDS_RMI_TAG);
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::ResetAllCameras()
{
  vtkDebugMacro("ResetAllCameras");

  if (!this->RenderWindow)
  {
    vtkErrorMacro("Called ResetAllCameras before RenderWindow set");
    return;
  }

  vtkRendererCollection* rens;
  vtkRenderer* ren;

  rens = this->GetRenderers();
  vtkCollectionSimpleIterator rsit;
  for (rens->InitTraversal(rsit); (ren = rens->GetNextRenderer(rsit));)
  {
    this->ResetCamera(ren);
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::SetImageReductionFactor(double factor)
{
  // Clamp factor.
  factor = (factor < 1) ? 1 : factor;
  factor = (factor > this->MaxImageReductionFactor) ? this->MaxImageReductionFactor : factor;

  if (this->MagnifyImageMethod == LINEAR)
  {
    // Make factor be a power of 2.
    int pow_of_2 = 1;
    while (pow_of_2 <= factor)
    {
      pow_of_2 <<= 1;
    }
    factor = pow_of_2 >> 1;
  }

  if (factor == this->ImageReductionFactor)
  {
    return;
  }

  this->ImageReductionFactor = factor;
  this->Modified();
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::SetMagnifyImageMethod(int method)
{
  if (this->MagnifyImageMethod == method)
  {
    return;
  }

  this->MagnifyImageMethod = method;
  // May need to modify image reduction factor.
  this->SetImageReductionFactor(this->ImageReductionFactor);
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::SetImageReductionFactorForUpdateRate(double desiredUpdateRate)
{
  vtkDebugMacro("Setting reduction factor for update rate of " << desiredUpdateRate);

  if (desiredUpdateRate == 0.0)
  {
    this->SetImageReductionFactor(1);
    return;
  }

  int* size;
  if (this->ForceRenderWindowSize)
  {
    size = this->ForcedRenderWindowSize;
  }
  else
  {
    size = this->RenderWindow->GetActualSize();
  }
  int numPixels = size[0] * size[1];
  int numReducedPixels =
    (int)(numPixels / (this->ImageReductionFactor * this->ImageReductionFactor));

  double renderTime = this->GetRenderTime();
  double pixelTime = this->GetImageProcessingTime();

  double timePerPixel;
  if (numReducedPixels > 0)
  {
    timePerPixel = pixelTime / numReducedPixels;
  }
  else
  {
    // Must be before first render.
    this->SetImageReductionFactor(1);
    return;
  }

  this->AverageTimePerPixel = (3 * this->AverageTimePerPixel + timePerPixel) / 4;
  if (this->AverageTimePerPixel <= 0)
  {
    this->AverageTimePerPixel = 0;
    this->SetImageReductionFactor(1);
    return;
  }

  double allottedPixelTime = 1.0 / desiredUpdateRate - renderTime;
  // Give ourselves at least 15% of render time.
  if (allottedPixelTime < 0.15 * renderTime)
  {
    allottedPixelTime = 0.15 * renderTime;
  }

  vtkDebugMacro("TimePerPixel: " << timePerPixel
                                 << ", AverageTimePerPixel: " << this->AverageTimePerPixel
                                 << ", AllottedPixelTime: " << allottedPixelTime);

  double pixelsToUse = allottedPixelTime / this->AverageTimePerPixel;

  if ((pixelsToUse < 1) || (numPixels / pixelsToUse > this->MaxImageReductionFactor))
  {
    this->SetImageReductionFactor(this->MaxImageReductionFactor);
  }
  else if (pixelsToUse >= numPixels)
  {
    this->SetImageReductionFactor(1);
  }
  else
  {
    this->SetImageReductionFactor((int)(numPixels / pixelsToUse));
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::SetRenderWindowSize()
{
  if (!this->RenderWindow->GetOffScreenRendering())
  {
    // Make sure we can support the requested image size.
    const int* screensize = this->RenderWindow->GetScreenSize();
    if (this->FullImageSize[0] > screensize[0])
    {
      // Reduce both dimensions to preserve aspect ratio.
      this->FullImageSize[1] = (this->FullImageSize[1] * screensize[0]) / this->FullImageSize[0];
      this->FullImageSize[0] = screensize[0];
    }
    if (this->FullImageSize[1] > screensize[1])
    {
      // Reduce both dimensions to preserve aspect ratio.
      this->FullImageSize[0] = (this->FullImageSize[0] * screensize[1]) / this->FullImageSize[1];
      this->FullImageSize[1] = screensize[1];
    }

    // Make sure the reduced image is no bigger than the full image.
    if (this->ReducedImageSize[0] > this->FullImageSize[0])
    {
      this->ReducedImageSize[0] = this->FullImageSize[0];
    }
    if (this->ReducedImageSize[1] > this->FullImageSize[1])
    {
      this->ReducedImageSize[1] = this->FullImageSize[1];
    }
  }

  // Correct image reduction factor.
  this->ImageReductionFactor = (double)this->FullImageSize[0] / this->ReducedImageSize[0];

  this->RenderWindow->SetSize(this->FullImageSize[0], this->FullImageSize[1]);
}

//------------------------------------------------------------------------------
vtkRendererCollection* vtkParallelRenderManager::GetRenderers()
{
  if (this->SyncRenderWindowRenderers)
  {
    return this->RenderWindow->GetRenderers();
  }
  else
  {
    return this->Renderers;
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::AddRenderer(vtkRenderer* ren)
{
  this->Renderers->AddItem(ren);
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::RemoveRenderer(vtkRenderer* ren)
{
  this->Renderers->RemoveItem(ren);
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::RemoveAllRenderers()
{
  this->Renderers->RemoveAllItems();
}

//------------------------------------------------------------------------------
int vtkParallelRenderManager::LastRenderInFrontBuffer()
{
  return this->RenderWindow->GetSwapBuffers();
}

//------------------------------------------------------------------------------
int vtkParallelRenderManager::ChooseBuffer()
{
  // always render buffer
  return 0;
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::MagnifyImageNearest(vtkUnsignedCharArray* fullImage,
  const int fullImageSize[2], vtkUnsignedCharArray* reducedImage, const int reducedImageSize[2],
  const int fullImageViewport[4], const int reducedImageViewport[4])
{
  int numComp = reducedImage->GetNumberOfComponents();

  fullImage->SetNumberOfComponents(4);
  fullImage->SetNumberOfTuples(fullImageSize[0] * fullImageSize[1]);

  int destLeft, destBottom, destWidth, destHeight;
  if (fullImageViewport)
  {
    destLeft = fullImageViewport[0];
    destBottom = fullImageViewport[1];
    destWidth = fullImageViewport[2] - fullImageViewport[0];
    destHeight = fullImageViewport[3] - fullImageViewport[1];
  }
  else
  {
    destLeft = destBottom = 0;
    destWidth = fullImageSize[0];
    destHeight = fullImageSize[1];
  }

  int srcLeft, srcBottom, srcWidth, srcHeight;
  if (reducedImageViewport)
  {
    srcLeft = reducedImageViewport[0];
    srcBottom = reducedImageViewport[1];
    srcWidth = reducedImageViewport[2] - reducedImageViewport[0];
    srcHeight = reducedImageViewport[3] - reducedImageViewport[1];
  }
  else
  {
    srcLeft = srcBottom = 0;
    srcWidth = reducedImageSize[0];
    srcHeight = reducedImageSize[1];
  }

  if (numComp == 4)
  {
    // If there are 4 components per pixel, we can speed up the inflation
    // by copying integers instead of characters.

    // Making a bunch of tmp variables for speed within the loops
    // Look I know the compiler should optimize this stuff
    // but I don't trust compilers... besides testing shows
    // this code is faster than the old code
    float xstep = (float)srcWidth / destWidth;
    float ystep = (float)srcHeight / destHeight;
    float xaccum = 0, yaccum = 0;
    int destlinesize = fullImageSize[0];
    int srclinesize = reducedImageSize[0];
    int xmemsize = 4 * destWidth;
    unsigned int* lastsrcline = nullptr;
    unsigned int* destline =
      (unsigned int*)fullImage->GetPointer(4 * (destBottom * destlinesize + destLeft));
    unsigned int* srcline =
      (unsigned int*)reducedImage->GetPointer(4 * (srcBottom * srclinesize + srcLeft));
    unsigned int* srczero = srcline;

    // Inflate image.
    for (int y = 0; y < destHeight; ++y, yaccum += ystep)
    {
      // If this line same as last one.
      if (srcline == lastsrcline)
      {
        memcpy(destline, destline - destlinesize, xmemsize);
      }
      else
      {
        for (int x = 0; x < destWidth; ++x, xaccum += xstep)
        {
          destline[x] = srcline[(int)(xaccum)];
        }
        xaccum = 0;
        lastsrcline = srcline;
      }
      destline += destlinesize;
      srcline = srczero + srclinesize * int(yaccum); // Performance fixme
    }
  }
  else
  {
    // Inflate image.
    double xstep = (double)srcWidth / destWidth;
    double ystep = (double)srcHeight / destHeight;
    unsigned char* lastsrcline = nullptr;
    for (int y = 0; y < destHeight; y++)
    {
      unsigned char* destline =
        fullImage->GetPointer(4 * (fullImageSize[0] * (y + destBottom) + destLeft));
      unsigned char* srcline = reducedImage->GetPointer(
        numComp * (reducedImageSize[0] * ((int)(ystep * y) + srcBottom) + srcLeft));
      if (srcline == lastsrcline)
      {
        // This line same as last one.
        memcpy(destline, (const unsigned char*)(destline - 4 * fullImageSize[0]), 4 * destWidth);
      }
      else
      {
        for (int x = 0; x < destWidth; x++)
        {
          int srcloc = numComp * (int)(x * xstep);
          int destloc = 4 * x;
          int i;
          for (i = 0; i < numComp; i++)
          {
            destline[destloc + i] = srcline[srcloc + i];
          }
          for (; i < 4; i++)
          {
            destline[destloc + i] = 0xFF;
          }
        }
        lastsrcline = srcline;
      }
    }
  }
}

//------------------------------------------------------------------------------
// A neat trick to quickly divide all 4 of the bytes in an integer by 2.
#define VTK_VEC_DIV_2(intvector) (((intvector) >> 1) & 0x7F7F7F7F)

void vtkParallelRenderManager::MagnifyImageLinear(vtkUnsignedCharArray* fullImage,
  const int fullImageSize[2], vtkUnsignedCharArray* reducedImage, const int reducedImageSize[2],
  const int fullImageViewport[4], const int reducedImageViewport[4])
{
  int xmag, ymag;
  int x, y;
  int srcComp = reducedImage->GetNumberOfComponents();

  // Allocate full image so all pixels are on 4-byte integer boundaries.
  fullImage->SetNumberOfComponents(4);
  fullImage->SetNumberOfTuples(fullImageSize[0] * fullImageSize[1]);

  int destLeft, destBottom, destWidth, destHeight;
  if (fullImageViewport)
  {
    destLeft = fullImageViewport[0];
    destBottom = fullImageViewport[1];
    destWidth = fullImageViewport[2] - fullImageViewport[0];
    destHeight = fullImageViewport[3] - fullImageViewport[1];
  }
  else
  {
    destLeft = destBottom = 0;
    destWidth = fullImageSize[0];
    destHeight = fullImageSize[1];
  }

  int srcLeft, srcBottom, srcWidth, srcHeight;
  if (reducedImageViewport)
  {
    srcLeft = reducedImageViewport[0];
    srcBottom = reducedImageViewport[1];
    srcWidth = reducedImageViewport[2] - reducedImageViewport[0];
    srcHeight = reducedImageViewport[3] - reducedImageViewport[1];
  }
  else
  {
    srcLeft = srcBottom = 0;
    srcWidth = reducedImageSize[0];
    srcHeight = reducedImageSize[1];
  }

  // Guess x and y magnification.  Round up to ensure we do not try to
  // read data from the image data that does not exist.
  xmag = (destWidth + srcWidth - 1) / srcWidth;
  ymag = (destHeight + srcHeight - 1) / srcHeight;

  // For speed, we only magnify by powers of 2.  Round up to the nearest
  // power of 2 to ensure that the reduced image is large enough.
  int powOf2;
  for (powOf2 = 1; powOf2 < xmag; powOf2 <<= 1)
  {
  }
  xmag = powOf2;
  for (powOf2 = 1; powOf2 < ymag; powOf2 <<= 1)
  {
  }
  ymag = powOf2;

  unsigned char* srcline = reducedImage->GetPointer(srcComp * srcBottom * reducedImageSize[0]);
  unsigned char* destline = fullImage->GetPointer(4 * destBottom * fullImageSize[0]);
  for (y = 0; y < destHeight; y += ymag)
  {
    unsigned char* srcval = srcline + srcComp * srcLeft;
    unsigned char* destval = destline + 4 * destLeft;
    for (x = 0; x < destWidth; x += xmag)
    {
      destval[0] = srcval[0];
      destval[1] = srcval[1];
      destval[2] = srcval[2];
      destval[3] = 0xFF; // Hope we don't need the alpha value.
      srcval += srcComp;
      destval += 4 * xmag;
    }
    srcline += srcComp * reducedImageSize[0];
    destline += 4 * fullImageSize[0] * ymag;
  }

  // Now that we have everything on 4-byte boundaries, we will treat
  // everything as integers for much faster computation.
  unsigned int* image =
    (unsigned int*)fullImage->GetPointer(0) + destBottom * fullImageSize[0] + destLeft;

  // Fill in scanlines.
  for (; xmag > 1; xmag >>= 1)
  {
    int halfXMag = xmag / 2;
    for (y = 0; y < destHeight; y += ymag)
    {
      unsigned int* scanline = image + y * fullImageSize[0];
      int maxX = destWidth - halfXMag; // Don't access bad memory.
      for (x = halfXMag; x < maxX; x += xmag)
      {
        scanline[x] = VTK_VEC_DIV_2(scanline[x - halfXMag]) + VTK_VEC_DIV_2(scanline[x + halfXMag]);
      }
      if (x < destWidth)
      {
        scanline[x] = scanline[x - halfXMag];
      }
    }
  }

  // Add blank scanlines.
  for (; ymag > 1; ymag >>= 1)
  {
    int halfYMag = ymag / 2;
    int maxY = destHeight - halfYMag; // Don't access bad memory.
    for (y = halfYMag; y < maxY; y += ymag)
    {
      unsigned int* destline2 = image + y * fullImageSize[0];
      unsigned int* srcline1 = image + (y - halfYMag) * fullImageSize[0];
      unsigned int* srcline2 = image + (y + halfYMag) * fullImageSize[0];
      for (x = 0; x < destWidth; x++)
      {
        destline2[x] = VTK_VEC_DIV_2(srcline1[x]) + VTK_VEC_DIV_2(srcline2[x]);
      }
    }
    if (y < destHeight)
    {
      unsigned int* destline2 = image + y * fullImageSize[0];
      unsigned int* srcline1 = image + (y - halfYMag) * fullImageSize[0];
      for (x = 0; x < destWidth; x++)
      {
        destline2[x] = srcline1[x];
      }
    }
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::MagnifyImage(vtkUnsignedCharArray* fullImage,
  const int fullImageSize[2], vtkUnsignedCharArray* reducedImage, const int reducedImageSize[2],
  const int fullImageViewport[4], const int reducedImageViewport[4])
{
  switch (this->MagnifyImageMethod)
  {
    case vtkParallelRenderManager::NEAREST:
      this->MagnifyImageNearest(fullImage, fullImageSize, reducedImage, reducedImageSize,
        fullImageViewport, reducedImageViewport);
      break;
    case LINEAR:
      this->MagnifyImageLinear(fullImage, fullImageSize, reducedImage, reducedImageSize,
        fullImageViewport, reducedImageViewport);
      break;
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::MagnifyReducedImage()
{
  if ((this->FullImageUpToDate))
  {
    return;
  }

  this->ReadReducedImage();

  if (this->FullImage->GetPointer(0) != this->ReducedImage->GetPointer(0))
  {
    this->Timer->StartTimer();
    this->MagnifyImage(
      this->FullImage, this->FullImageSize, this->ReducedImage, this->ReducedImageSize);
    this->Timer->StopTimer();
    // We log the image inflation under render time because it is inversely
    // proportional to the image size.  This makes the auto image reduction
    // calculation work better.
    this->RenderTime += this->Timer->GetElapsedTime();
  }

  this->FullImageUpToDate = 1;
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::WriteFullImage()
{
  if (this->RenderWindowImageUpToDate || !this->WriteBackImages)
  {
    return;
  }

  if (this->MagnifyImages &&
    ((this->FullImageSize[0] != this->ReducedImageSize[0]) ||
      (this->FullImageSize[1] != this->ReducedImageSize[1])))
  {
    this->MagnifyReducedImage();
    this->SetRenderWindowPixelData(this->FullImage, this->FullImageSize);
  }
  else
  {
    // Only write back image if it has already been read and potentially
    // changed.
    if (this->ReducedImageUpToDate)
    {
      this->SetRenderWindowPixelData(this->ReducedImage, this->ReducedImageSize);
    }
  }

  this->RenderWindowImageUpToDate = 1;
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::SetRenderWindowPixelData(
  vtkUnsignedCharArray* pixels, const int pixelDimensions[2])
{
  if (pixels->GetNumberOfComponents() == 4)
  {
    this->RenderWindow->SetRGBACharPixelData(
      0, 0, pixelDimensions[0] - 1, pixelDimensions[1] - 1, pixels, this->ChooseBuffer());
  }
  else
  {
    this->RenderWindow->SetPixelData(
      0, 0, pixelDimensions[0] - 1, pixelDimensions[1] - 1, pixels, this->ChooseBuffer());
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::ReadReducedImage()
{
  if (this->ReducedImageUpToDate)
  {
    return;
  }

  this->Timer->StartTimer();

  if (this->ImageReductionFactor > 1)
  {
    if (this->UseRGBA)
    {
      this->RenderWindow->GetRGBACharPixelData(0, 0, this->ReducedImageSize[0] - 1,
        this->ReducedImageSize[1] - 1, this->ChooseBuffer(), this->ReducedImage);
    }
    else
    {
      this->RenderWindow->GetPixelData(0, 0, this->ReducedImageSize[0] - 1,
        this->ReducedImageSize[1] - 1, this->ChooseBuffer(), this->ReducedImage);
    }
  }
  else
  {
    if (this->UseRGBA)
    {
      this->RenderWindow->GetRGBACharPixelData(0, 0, this->FullImageSize[0] - 1,
        this->FullImageSize[1] - 1, this->ChooseBuffer(), this->FullImage);
    }
    else
    {
      this->RenderWindow->GetPixelData(0, 0, this->FullImageSize[0] - 1, this->FullImageSize[1] - 1,
        this->ChooseBuffer(), this->FullImage);
    }
    this->FullImageUpToDate = 1;
    this->ReducedImage->SetNumberOfComponents(this->FullImage->GetNumberOfComponents());
    this->ReducedImage->SetArray(this->FullImage->GetPointer(0), this->FullImage->GetSize(), 1);
    this->ReducedImage->SetNumberOfTuples(this->FullImage->GetNumberOfTuples());
  }

  this->Timer->StopTimer();
  this->ImageProcessingTime += this->Timer->GetElapsedTime();

  this->ReducedImageUpToDate = 1;
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::GetPixelData(vtkUnsignedCharArray* data)
{
  if (!this->RenderWindow)
  {
    vtkErrorMacro("Tried to read pixel data from non-existent RenderWindow");
    return;
  }

  // Read image from RenderWindow and magnify if necessary.
  this->MagnifyReducedImage();

  data->SetNumberOfComponents(this->FullImage->GetNumberOfComponents());
  data->SetArray(this->FullImage->GetPointer(0), this->FullImage->GetSize(), 1);
  data->SetNumberOfTuples(this->FullImage->GetNumberOfTuples());
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::GetPixelData(
  int x1, int y1, int x2, int y2, vtkUnsignedCharArray* data)
{
  if (!this->RenderWindow)
  {
    vtkErrorMacro("Tried to read pixel data from non-existent RenderWindow");
    return;
  }

  this->MagnifyReducedImage();

  if (x1 > x2)
  {
    int tmp = x1;
    x1 = x2;
    x2 = tmp;
  }
  if (y1 > y2)
  {
    int tmp = y1;
    y1 = y2;
    y2 = tmp;
  }

  if ((x1 < 0) || (x2 >= this->FullImageSize[0]) || (y1 < 0) || (y2 >= this->FullImageSize[1]))
  {
    vtkErrorMacro("Requested pixel data out of RenderWindow bounds");
    return;
  }

  vtkIdType width = x2 - x1 + 1;
  vtkIdType height = y2 - y1 + 1;

  int numComp = this->FullImage->GetNumberOfComponents();

  data->SetNumberOfComponents(numComp);
  data->SetNumberOfTuples(width * height);

  const unsigned char* src = this->FullImage->GetPointer(0);
  unsigned char* dest = data->WritePointer(0, width * height * numComp);

  for (int row = 0; row < height; row++)
  {
    memcpy(dest + row * width * numComp,
      src + (row + y1) * this->FullImageSize[0] * numComp + x1 * numComp, width * numComp);
  }
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::GetReducedPixelData(vtkUnsignedCharArray* data)
{
  if (!this->RenderWindow)
  {
    vtkErrorMacro("Tried to read pixel data from non-existent RenderWindow");
    return;
  }

  // Read image from RenderWindow and magnify if necessary.
  this->ReadReducedImage();

  data->SetNumberOfComponents(this->ReducedImage->GetNumberOfComponents());
  data->SetArray(this->ReducedImage->GetPointer(0), this->ReducedImage->GetSize(), 1);
  data->SetNumberOfTuples(this->ReducedImage->GetNumberOfTuples());
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::GetReducedPixelData(
  int x1, int y1, int x2, int y2, vtkUnsignedCharArray* data)
{
  if (!this->RenderWindow)
  {
    vtkErrorMacro("Tried to read pixel data from non-existent RenderWindow");
    return;
  }

  this->ReadReducedImage();

  if (x1 > x2)
  {
    int tmp = x1;
    x1 = x2;
    x2 = tmp;
  }
  if (y1 > y2)
  {
    int tmp = y1;
    y1 = y2;
    y2 = tmp;
  }

  if ((x1 < 0) || (x2 >= this->ReducedImageSize[0]) || (y1 < 0) ||
    (y2 >= this->ReducedImageSize[1]))
  {
    vtkErrorMacro("Requested pixel data out of RenderWindow bounds");
    return;
  }

  vtkIdType width = x2 - x1 + 1;
  vtkIdType height = y2 - y1 + 1;

  int numComp = this->ReducedImage->GetNumberOfComponents();

  data->SetNumberOfComponents(numComp);
  data->SetNumberOfTuples(width * height);

  const unsigned char* src = this->ReducedImage->GetPointer(0);
  unsigned char* dest = data->WritePointer(0, width * height * numComp);

  for (int row = 0; row < height; row++)
  {
    memcpy(dest + row * width * numComp,
      src + (row + y1) * this->ReducedImageSize[0] * numComp + x1 * numComp, width * numComp);
  }
}

// Static function prototypes --------------------------------------------

static void AbortRenderCheck(
  vtkObject* vtkNotUsed(caller), unsigned long vtkNotUsed(event), void* clientData, void*)
{
  vtkParallelRenderManager* self = (vtkParallelRenderManager*)clientData;
  self->CheckForAbortRender();
}

static void GenericStartRender(
  vtkObject* vtkNotUsed(caller), unsigned long vtkNotUsed(event), void* clientData, void*)
{
  vtkParallelRenderManager* self = (vtkParallelRenderManager*)clientData;
  self->GenericStartRenderCallback();
}

static void GenericEndRender(
  vtkObject* vtkNotUsed(caller), unsigned long vtkNotUsed(event), void* clientData, void*)
{
  vtkParallelRenderManager* self = (vtkParallelRenderManager*)clientData;
  self->GenericEndRenderCallback();
}

/*
static void ResetCamera(vtkObject *caller,
                        unsigned long vtkNotUsed(event),
                        void *clientData, void *)
{
  vtkParallelRenderManager *self = (vtkParallelRenderManager *)clientData;
  vtkRenderer *ren = (vtkRenderer *)caller;
  self->ResetCamera(ren);
}

static void ResetCameraClippingRange(vtkObject *caller,
                                     unsigned long vtkNotUsed(event),
                                     void *clientData, void *)
{
  vtkParallelRenderManager *self = (vtkParallelRenderManager *)clientData;
  vtkRenderer *ren = (vtkRenderer *)caller;
  self->ResetCameraClippingRange(ren);
}
*/

static void RenderRMI(void* arg, void*, int, int)
{
  vtkParallelRenderManager* self = (vtkParallelRenderManager*)arg;
  self->RenderRMI();
}

static void ComputeVisiblePropBoundsRMI(void* arg, void* remoteArg, int remoteArgLength, int)
{
  assert(remoteArgLength == sizeof(int));
  (void)remoteArgLength;
  int* iarg = reinterpret_cast<int*>(remoteArg);

  vtkParallelRenderManager* self = (vtkParallelRenderManager*)arg;
  self->ComputeVisiblePropBoundsRMI(*iarg);
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::SatelliteStartRender()
{
  vtkParallelRenderManager::RenderWindowInfo winInfo;
  vtkParallelRenderManager::RendererInfo renInfo;
  vtkParallelRenderManager::LightInfo lightInfo;
  int i, j;

  vtkDebugMacro("SatelliteStartRender");

  this->FullImageUpToDate = 0;
  this->ReducedImageUpToDate = 0;
  this->RenderWindowImageUpToDate = 0;

  if (this->FullImage->GetPointer(0) == this->ReducedImage->GetPointer(0))
  {
    // "Un-share" pointer for full/reduced images in case we need separate
    // arrays this run.
    this->ReducedImage->Initialize();
  }

  // if (!this->ParallelRendering)
  //  {
  //  return;
  //  }

  this->InvokeEvent(vtkCommand::StartEvent, nullptr);
  vtkMultiProcessStream stream;
  if (!this->Controller->Broadcast(stream, this->RootProcessId))
  {
    return;
  }

  if (!winInfo.Restore(stream))
  {
    vtkErrorMacro("Failed to read window information");
    return;
  }

  this->RenderWindow->SetDesiredUpdateRate(winInfo.DesiredUpdateRate);
  if (this->SynchronizeTileProperties)
  {
    this->RenderWindow->SetTileViewport(winInfo.TileViewport);
    this->RenderWindow->SetTileScale(winInfo.TileScale);
  }
  this->SetUseCompositing(winInfo.UseCompositing);
  if (this->MaxImageReductionFactor < winInfo.ImageReductionFactor)
  {
    this->SetMaxImageReductionFactor(winInfo.ImageReductionFactor);
  }
  this->SetImageReductionFactor(winInfo.ImageReductionFactor);
  this->FullImageSize[0] = winInfo.FullSize[0];
  this->FullImageSize[1] = winInfo.FullSize[1];
  this->ReducedImageSize[0] = winInfo.ReducedSize[0];
  this->ReducedImageSize[1] = winInfo.ReducedSize[1];

  // Backwards compatibility.
  this->ReceiveWindowInformation();

  if (!this->ProcessWindowInformation(stream))
  {
    vtkErrorMacro("Failed to process window information correctly.");
    return;
  }

  this->SetRenderWindowSize();

  vtkCollectionSimpleIterator rsit;
  vtkRendererCollection* rens = this->GetRenderers();

  this->Viewports->SetNumberOfTuples(rens->GetNumberOfItems());

  rens->InitTraversal(rsit);
  for (i = 0; i < winInfo.NumberOfRenderers; i++)
  {
    vtkLightCollection* lc = nullptr;
    vtkCollectionSimpleIterator lsit;
    vtkRenderer* ren = rens->GetNextRenderer(rsit);
    if (ren == nullptr)
    {
      vtkErrorMacro("Not enough renderers");
    }
    else
    {
      // Backwards compatibility
      this->ReceiveRendererInformation(ren);

      if (!renInfo.Restore(stream))
      {
        vtkErrorMacro("Failed to read renderer information for " << i);
        continue;
      }

      this->Viewports->SetTuple(i, ren->GetViewport());
      ren->SetViewport(renInfo.Viewport);
      ren->SetBackground(renInfo.Background[0], renInfo.Background[1], renInfo.Background[2]);
      ren->SetBackground2(renInfo.Background2[0], renInfo.Background2[1], renInfo.Background2[2]);
      ren->SetGradientBackground(renInfo.GradientBackground);
      vtkCamera* cam = ren->GetActiveCamera();
      cam->SetPosition(renInfo.CameraPosition);
      cam->SetFocalPoint(renInfo.CameraFocalPoint);
      cam->SetViewUp(renInfo.CameraViewUp);
      cam->SetClippingRange(renInfo.CameraClippingRange);
      cam->SetViewAngle(renInfo.CameraViewAngle);
      cam->SetWindowCenter(renInfo.WindowCenter[0], renInfo.WindowCenter[1]);
      if (renInfo.ParallelScale != 0.0)
      {
        cam->ParallelProjectionOn();
        cam->SetParallelScale(renInfo.ParallelScale);
      }
      else
      {
        cam->ParallelProjectionOff();
      }
      ren->SetDraw(renInfo.Draw);
      lc = ren->GetLights();
      lc->InitTraversal(lsit);
    }

    for (j = 0; j < renInfo.NumberOfLights; j++)
    {
      if (ren != nullptr && lc != nullptr)
      {
        vtkLight* light = lc->GetNextLight(lsit);
        if (light == nullptr)
        {
          // Not enough lights?  Just create them.
          vtkDebugMacro("Adding light");
          light = vtkLight::New();
          ren->AddLight(light);
          light->Delete();
        }

        if (!lightInfo.Restore(stream))
        {
          vtkErrorMacro("Failed to read light information");
          continue;
        }
        light->SetLightType((int)(lightInfo.Type));
        light->SetPosition(lightInfo.Position);
        light->SetFocalPoint(lightInfo.FocalPoint);
      }
    }

    if (ren != nullptr)
    {
      vtkLight* light;
      while ((light = lc->GetNextLight(lsit)))
      {
        // To many lights?  Just remove the extras.
        ren->RemoveLight(light);
      }
    }

    if (!this->ProcessRendererInformation(ren, stream))
    {
      vtkErrorMacro("Failed to process renderer information correctly.");
    }
  }

  if (rens->GetNextRenderer(rsit))
  {
    vtkErrorMacro("Too many renderers.");
  }

  this->PreRenderProcessing();
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::TileWindows(int xsize, int ysize, int ncolumn)
{
  if (!this->RenderWindow || !this->Controller)
  {
    return;
  }

  int procId = this->Controller->GetLocalProcessId();

  int row = procId / ncolumn;
  int column = procId % ncolumn;

  this->RenderWindow->SetPosition(xsize * column, ysize * row);
}

//------------------------------------------------------------------------------
// ********* INFO OBJECT METHODS ***************************
//------------------------------------------------------------------------------
void vtkParallelRenderManager::RenderWindowInfo::Save(vtkMultiProcessStream& stream)
{
  stream << vtkParallelRenderManager::WIN_INFO_TAG << this->FullSize[0] << this->FullSize[1]
         << this->ReducedSize[0] << this->ReducedSize[1] << this->NumberOfRenderers
         << this->UseCompositing << this->TileScale[0] << this->TileScale[1]
         << this->ImageReductionFactor << this->DesiredUpdateRate << this->TileViewport[0]
         << this->TileViewport[1] << this->TileViewport[2] << this->TileViewport[3];
}

//------------------------------------------------------------------------------
bool vtkParallelRenderManager::RenderWindowInfo::Restore(vtkMultiProcessStream& stream)
{
  int tag;
  stream >> tag;
  if (tag != vtkParallelRenderManager::WIN_INFO_TAG)
  {
    return false;
  }
  stream >> this->FullSize[0] >> this->FullSize[1] >> this->ReducedSize[0] >>
    this->ReducedSize[1] >> this->NumberOfRenderers >> this->UseCompositing >> this->TileScale[0] >>
    this->TileScale[1] >> this->ImageReductionFactor >> this->DesiredUpdateRate >>
    this->TileViewport[0] >> this->TileViewport[1] >> this->TileViewport[2] >>
    this->TileViewport[3];
  return true;
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::RendererInfo::Save(vtkMultiProcessStream& stream)
{
  int value = this->GradientBackground;
  stream << vtkParallelRenderManager::REN_INFO_TAG << this->Draw << this->NumberOfLights
         << this->Viewport[0] << this->Viewport[1] << this->Viewport[2] << this->Viewport[3]
         << this->CameraPosition[0] << this->CameraPosition[1] << this->CameraPosition[2]
         << this->CameraFocalPoint[0] << this->CameraFocalPoint[1] << this->CameraFocalPoint[2]
         << this->CameraViewUp[0] << this->CameraViewUp[1] << this->CameraViewUp[2]
         << this->WindowCenter[0] << this->WindowCenter[1] << this->CameraClippingRange[0]
         << this->CameraClippingRange[1] << this->CameraViewAngle << this->Background[0]
         << this->Background[1] << this->Background[2] << this->Background2[0]
         << this->Background2[1] << this->Background2[2] << value << this->ParallelScale;
}

//------------------------------------------------------------------------------
bool vtkParallelRenderManager::RendererInfo::Restore(vtkMultiProcessStream& stream)
{
  int tag;
  stream >> tag;
  if (tag != vtkParallelRenderManager::REN_INFO_TAG)
  {
    return false;
  }

  int value;

  stream >> this->Draw >> this->NumberOfLights >> this->Viewport[0] >> this->Viewport[1] >>
    this->Viewport[2] >> this->Viewport[3] >> this->CameraPosition[0] >> this->CameraPosition[1] >>
    this->CameraPosition[2] >> this->CameraFocalPoint[0] >> this->CameraFocalPoint[1] >>
    this->CameraFocalPoint[2] >> this->CameraViewUp[0] >> this->CameraViewUp[1] >>
    this->CameraViewUp[2] >> this->WindowCenter[0] >> this->WindowCenter[1] >>
    this->CameraClippingRange[0] >> this->CameraClippingRange[1] >> this->CameraViewAngle >>
    this->Background[0] >> this->Background[1] >> this->Background[2] >> this->Background2[0] >>
    this->Background2[1] >> this->Background2[2] >> value >> this->ParallelScale;

  this->GradientBackground = value == 1;
  return true;
}

//------------------------------------------------------------------------------
bool vtkParallelRenderManager::LightInfo::Restore(vtkMultiProcessStream& stream)
{
  int tag;
  stream >> tag;
  if (tag != vtkParallelRenderManager::LIGHT_INFO_TAG)
  {
    return false;
  }
  stream >> this->Position[0] >> this->Position[1] >> this->Position[2] >> this->FocalPoint[0] >>
    this->FocalPoint[1] >> this->FocalPoint[2] >> this->Type;
  return true;
}

//------------------------------------------------------------------------------
void vtkParallelRenderManager::LightInfo::Save(vtkMultiProcessStream& stream)
{
  stream << vtkParallelRenderManager::LIGHT_INFO_TAG << this->Position[0] << this->Position[1]
         << this->Position[2] << this->FocalPoint[0] << this->FocalPoint[1] << this->FocalPoint[2]
         << this->Type;
}
VTK_ABI_NAMESPACE_END
