{
     Apophysis Copyright (C) 2001-2004 Mark Townsend
     Apophysis Copyright (C) 2005-2006 Ronald Hordijk, Piotr Borys, Peter Sdobnov
     Apophysis Copyright (C) 2007-2008 Piotr Borys, Peter Sdobnov
     
     Apophysis "3D hack" Copyright (C) 2007-2008 Peter Sdobnov
     Apophysis "7X" Copyright (C) 2009-2010 Georg Kiehne
     Apophysis AV "Phoenix Edition" Copyright (C) 2021 Alice V. Koryagina

     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
     (at your option) any later version.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
}
unit RenderingImplementation;

{$ifndef CPUX64}
  {$define _ASM_}
{$endif}

interface

uses
{$ifdef _ASM_}
AsmRandom,
{$endif}
  Windows, Classes, Forms, Graphics, Global,
  RenderingInterface, Xform, Math, Translation,
  Binary, RenderingCommon, ControlPoint, Sysutils,
  BucketFillerThread;

type
  TBatchProc = procedure of object;
  TRenderWorkerST = class(TBaseRenderer)

  protected
    PropTable: array[0..PROP_TABLE_SIZE] of TXform;
    finalXform: TXform;
    UseFinalXform: boolean;

    procedure Prepare; override;
    procedure SetPixels; override;

  protected
    procedure IterateBatch;
    procedure IterateBatchAngle;
    procedure IterateBatchFX;
    procedure IterateBatchAngleFX;
end;

type
  TRenderWorkerMT = class(TBaseRenderer)

  protected
    batchcounter: Integer;
    WorkingThreads: array of TBucketFillerThread;
    CriticalSection: TRTLCriticalSection;

    function NewThread: TBucketFillerThread;
    procedure Prepare; override;
    procedure SetPixels; override;
    
  protected
    procedure AddPointsToBuckets(const points: TPointsArray);
    procedure AddPointsToBucketsAngle(const points: TPointsArray);
    
  public
    procedure Stop; override;
    procedure BreakRender; override;

    procedure Pause; override;
    procedure UnPause; override;
    procedure SetThreadPriority(p: TThreadPriority); override; // AV
end;

type
  TRenderWorkerST_MM = class(TRenderWorkerST)
  protected
    procedure CalcBufferSize; override;
  public
    procedure Render; override;

end;

type
  TRenderWorkerMT_MM = class(TRenderWorkerMT)
  protected
    procedure CalcBufferSize; override;
  public
    procedure Render; override;
end;

// ----------------------------------------------------------------------------

implementation

////////////////////////////////////////////////////////////////////////////////
// PREPARE
////////////////////////////////////////////////////////////////////////////////
procedure TRenderWorkerST.Prepare;
var
  i, n: Integer;
  propsum: double;
  LoopValue: double;
  j: integer;
  TotValue: double;
begin
  totValue := 0;
  n := fcp.NumXforms;
  assert(n > 0);

  finalXform := fcp.xform[n];
  finalXform.Prepare;
  useFinalXform := fcp.FinalXformEnabled and fcp.HasFinalXform;

  fcp.Prepare;
end;

procedure TRenderWorkerMT.Prepare;
begin
  fcp.Prepare;
end;

////////////////////////////////////////////////////////////////////////////////
// SETPIXELS
////////////////////////////////////////////////////////////////////////////////
procedure TRenderWorkerST.SetPixels;
var
  i: integer;
  nsamples: int64;
  IterateBatchProc: procedure of object;
begin
  if FNumSlices > 1 then
    TimeTrace(Format(TextByKey('common-trace-rendering-multipleslices'), [FSlice + 1, FNumSlices]))
  else
    TimeTrace(TextByKey('common-trace-rendering-oneslice'));

  Randomize;

  if FCP.FAngle = 0 then begin
    if UseFinalXform then
      IterateBatchProc := IterateBatchFX
    else
      IterateBatchProc := IterateBatch;
  end
  else begin
    if UseFinalXform then
      IterateBatchProc := IterateBatchAngleFX
    else
      IterateBatchProc := IterateBatchAngle;
  end;

  NSamples := Round(sample_density * NrSlices * bucketSize / (oversample * oversample));
  FNumBatches := Round(nsamples / (fcp.nbatches * SUB_BATCH_SIZE));
  if FNumBatches = 0 then FNumBatches := 1;

  FMinBatches := Round(FNumBatches * FMinDensity / fcp.sample_density);
  if FMinBatches = 0 then FMinBatches := 1;

  for i := 0 to FNumBatches-1 do
  begin
    if FStop <> 0 then begin
      { AV: moved below to update running quality estimate for mid-render snapshots
      fcp.actual_density := fcp.actual_density + fcp.sample_density * i / FNumBatches;
      }
      FNumBatches := i;
      exit;
    end;

    if ((i and $1F) = 0) then Progress(i / FNumBatches);

    IterateBatchProc;
    // AV: update running quality estimate (for mid-render snapshots)
    fcp.actual_density := fcp.actual_density + fcp.sample_density / FNumBatches; // AV

    Inc(FBatch);
  end;

  fcp.actual_density := {fcp.actual_density +} fcp.sample_density;  // AV

  Progress(1);
end;

procedure TRenderWorkerMT.SetPixels;
var
  i: integer;
  nSamples: Int64;
  bc : integer;
begin
  if FNumSlices > 1 then
    TimeTrace(Format(TextByKey('common-trace-rendering-multipleslices'), [FSlice + 1, FNumSlices]))
  else
    TimeTrace(TextByKey('common-trace-rendering-oneslice'));

  nSamples := Round(sample_density * NrSlices * BucketSize / (oversample * oversample));
  FNumBatches := Round(nSamples / (fcp.nbatches * SUB_BATCH_SIZE));
  if FNumBatches = 0 then FNumBatches := 1;
  FMinBatches := Round(FNumBatches * FMinDensity / fcp.sample_density);
  if FMinBatches = 0 then FMinBatches := 1;  // AV

  batchcounter := 1;
  Randomize;

  InitializeCriticalSection(CriticalSection);

  SetLength(WorkingThreads, NumThreads);
  for i := 0 to NumThreads - 1 do
    WorkingThreads[i] := NewThread;

  for i := 0 to NumThreads - 1 do
    WorkingThreads[i].Resume;

  bc := 1;
  while (FStop = 0) and (bc <= FNumBatches) do begin
    sleep(250);
    try
      EnterCriticalSection(CriticalSection);

      Progress(batchcounter / FNumBatches);
      if (bc < batchcounter) then
      begin
        // AV: update running quality estimate for mid-render snapshots
        fcp.actual_density := fcp.actual_density +
          fcp.sample_density * (BatchCounter - bc) / FNumBatches; // actual quality of incomplete render
        bc := batchcounter;
      end;
    finally
      LeaveCriticalSection(CriticalSection);
    end;
  end;

  for i := 0 to High(WorkingThreads) do begin
    WorkingThreads[i].Terminate;
    WorkingThreads[i].WaitFor;
    WorkingThreads[i].Free;
  end;
  SetLength(WorkingThreads, 0);

   // AV: running quality estimate is updated above; tidy up remaining counts
  fcp.actual_density := fcp.actual_density +
                        fcp.sample_density * (BatchCounter - bc + 1) / FNumBatches; // AV: actual quality of incomplete render
                        // fcp.sample_density * BatchCounter / FNumBatches; // actual quality of incomplete render
  FNumBatches := BatchCounter;

  DeleteCriticalSection(CriticalSection);
  Progress(1);
end;

////////////////////////////////////////////////////////////////////////////////
// MM OVERRIDES
////////////////////////////////////////////////////////////////////////////////
procedure TRenderWorkerST_MM.CalcBufferSize;
begin
  CalcBufferSizeMM;
end;

procedure TRenderWorkerST_MM.Render;
begin
  RenderMM;
end;

procedure TRenderWorkerMT_MM.CalcBufferSize;
begin
  CalcBufferSizeMM;
end;

procedure TRenderWorkerMT_MM.Render;
begin
  RenderMM;
end;

////////////////////////////////////////////////////////////////////////////////
// BATCH ITERATION
////////////////////////////////////////////////////////////////////////////////
procedure TRenderWorkerST.IterateBatch;
var
  i: integer;
  px, py: double;
  Bucket: PBucket;
 // ZBufPos: PDouble;
  MapColor: PColorMapColor;

  ix, iy: integer;
  BmpColor: TColor;

  p, q: TCPPoint;
  xf: TXForm;
begin
{$ifndef _ASM_}
  p.x := 2 * random - 1;
  p.y := 2 * random - 1;
  // AV: fixed - Apo renderer crashes if z-value is not initialized
  p.z := 0;   // AV
  p.c := random;
{$else}
asm
    fld1
    call    AsmRandExt
    fadd    st, st
    fsub    st, st(1)
    fstp    qword ptr [p.x]
    call    AsmRandExt
    fadd    st, st
    fsubrp  st(1), st
    fstp    qword ptr [p.y]
    call    AsmRandExt
    fstp    qword ptr [p.c]
    fldz     // AV
    fstp    qword ptr [p.z]   // AV
end;
{$endif}

  try
    xf := fcp.xform[0];
    for i := 0 to FUSE do begin
      xf := xf.PropTable[Random(PROP_TABLE_SIZE)];
      xf.NextPoint(p);
    end;

    for i := 0 to SUB_BATCH_SIZE-1 do begin
      xf := xf.PropTable[Random(PROP_TABLE_SIZE)];
      xf.NextPoint(p);

     // if random >= xf.transOpacity then continue;
     if (xf.transOpacity = 0) then continue    // AV
     else if ((xf.transOpacity < 1) and (random > xf.transOpacity)) then  continue;

      q := p;
      fcp.ProjectionFunc(@q); // 3d hack

      px := q.x - camX0;
      if (px < 0) or (px > camW) then continue;
      py := q.y - camY0;
      if (py < 0) or (py > camH) then continue;

      Bucket := @buckets[Round(bhs * py)][Round(bws * px)];
      MapColor := @ColorMap[Round(p.c * 255)];

      {$ifdef ENABLEZBUF}
      ZBufPos := @zbuffer[Round(bhs * py)][Round(bws * px)];
      if (q.z < ZBufPos^) then
      begin
        ZBufPos^ := q.z;
        Bucket.Red := Bucket.Red + MapColor.Red;
        Bucket.Green := Bucket.Green + MapColor.Green;
        Bucket.Blue := Bucket.Blue + MapColor.Blue;
        Bucket.Count := Bucket.Count + 1;
      end;  
      {$else}
      with Bucket^ do begin
        Red   := Red   + MapColor.Red;
        Green := Green + MapColor.Green;
        Blue  := Blue  + MapColor.Blue;
        Count := Count + 1;
      end;
      {$endif}
    end;

  except
    on EMathError do begin
      exit;
    end;
  end;
end;

procedure TRenderWorkerST.IterateBatchAngle;
var
  i: integer;
  px, py: double;
  Bucket: PBucket;
  MapColor: PColorMapColor;
//  ZBufPos: PDouble;
  ix, iy: integer;
  BmpColor: TColor;

  p, q: TCPPoint;
  xf: TXForm;
begin
{$ifndef _ASM_}
  p.x := 2 * random - 1;
  p.y := 2 * random - 1;
  p.z := 0;   // AV: fixed - Apo renderer crashes if z-value is not initialized
  p.c := random;
{$else}
asm
    fld1
    call    AsmRandExt
    fadd    st, st
    fsub    st, st(1)
    fstp    qword ptr [p.x]
    call    AsmRandExt
    fadd    st, st
    fsubrp  st(1), st
    fstp    qword ptr [p.y]
    call    AsmRandExt
    fstp    qword ptr [p.c]
    fldz	 // AV
    fstp    qword ptr [p.z]   // AV
end;
{$endif}

  try
    xf := fcp.xform[0];
    for i := 0 to FUSE do begin
      xf := xf.PropTable[Random(PROP_TABLE_SIZE)];
      xf.NextPoint(p);
    end;

    for i := 0 to SUB_BATCH_SIZE-1 do begin
      xf := xf.PropTable[Random(PROP_TABLE_SIZE)];
      xf.NextPoint(p);

     // if random >= xf.transOpacity then continue;
     if (xf.transOpacity = 0) then continue    // AV
     else if ((xf.transOpacity < 1) and (random > xf.transOpacity)) then  continue;

      q := p;
      fcp.ProjectionFunc(@q);

      px := q.x * cosa + q.y * sina + rcX;
      if (px < 0) or (px > camW) then continue;
      py := q.y * cosa - q.x * sina + rcY;
      if (py < 0) or (py > camH) then continue;

      Bucket := @buckets[Round(bhs * py)][Round(bws * px)];
      MapColor := @ColorMap[Round(p.c * 255)];

     {$ifdef ENABLEZBUF}
      ZBufPos := @zbuffer[Round(bhs * py)][Round(bws * px)];
      if (q.z < ZBufPos^) then
      begin
        ZBufPos^ := q.z;
        Bucket.Red := Bucket.Red + MapColor.Red;
        Bucket.Green := Bucket.Green + MapColor.Green;
        Bucket.Blue := Bucket.Blue + MapColor.Blue;
        Bucket.Count := Bucket.Count + 1;
      end;  
      {$else}
      with Bucket^ do begin
        Red   := Red   + MapColor.Red;
        Green := Green + MapColor.Green;
        Blue  := Blue  + MapColor.Blue;
        Count := Count + 1;
      end;
      {$endif}
    end;

  except
    on EMathError do begin
      exit;
    end;
  end;
end;

procedure TRenderWorkerST.IterateBatchFX;
var
  i: integer;
  px, py: double;
  Bucket: PBucket;
  MapColor: PColorMapColor;
//  ZbufPos: PDouble;
  ix, iy: integer;
  BmpColor: TColor;

  p, q: TCPPoint;
  xf: TXForm;
begin
{$ifndef _ASM_}
  p.x := 2 * random - 1;
  p.y := 2 * random - 1;
  p.z := 0;   // AV: fixed - Apo renderer crashes if z-value is not initialized
  p.c := random;
{$else}
asm
    fld1
    call    AsmRandExt
    fadd    st, st
    fsub    st, st(1)
    fstp    qword ptr [p.x]
    call    AsmRandExt
    fadd    st, st
    fsubrp  st(1), st
    fstp    qword ptr [p.y]
    call    AsmRandExt
    fstp    qword ptr [p.c]
    fldz     // AV
    fstp    qword ptr [p.z]  // AV
end;
{$endif}

  try
    xf := fcp.xform[0];
    for i := 0 to FUSE do begin
      xf := xf.PropTable[Random(PROP_TABLE_SIZE)];
      xf.NextPoint(p);
    end;

    for i := 0 to SUB_BATCH_SIZE-1 do begin
      xf := xf.PropTable[Random(PROP_TABLE_SIZE)];
      xf.NextPoint(p);

     if (xf.transOpacity = 0) then continue    // AV
     else if ((xf.transOpacity < 1) and (random > xf.transOpacity)) then  continue;

      finalXform.NextPointTo(p, q);
      fcp.ProjectionFunc(@q);

      px := q.x - camX0;
      if (px < 0) or (px > camW) then continue;
      py := q.y - camY0;
      if (py < 0) or (py > camH) then continue;

      Bucket := @buckets[Round(bhs * py)][Round(bws * px)];
      MapColor := @ColorMap[Round(q.c * 255)];

      {$ifdef ENABLEZBUF}
      ZBufPos := @zbuffer[Round(bhs * py)][Round(bws * px)];
      if (q.z < ZBufPos^) then
      begin
        ZBufPos^ := q.z;
        Bucket.Red := Bucket.Red + MapColor.Red;
        Bucket.Green := Bucket.Green + MapColor.Green;
        Bucket.Blue := Bucket.Blue + MapColor.Blue;
        Bucket.Count := Bucket.Count + 1;
      end;  
      {$else}
      with Bucket^ do begin
        Red   := Red   + MapColor.Red;
        Green := Green + MapColor.Green;
        Blue  := Blue  + MapColor.Blue;
        Count := Count + 1;
      end;
     {$endif}
    end;

  except
    on EMathError do begin
      exit;
    end;
  end;
end;

procedure TRenderWorkerST.IterateBatchAngleFX;
var
  i: integer;
  px, py: double;
  Bucket: PBucket;
  MapColor: PColorMapColor;
//  ZBufPos: PDouble;
  ix, iy: integer;
  BmpColor: TColor;

  p, q: TCPPoint;
  xf: TXForm;
begin
{$ifndef _ASM_}
  p.x := 2 * random - 1;
  p.y := 2 * random - 1;
  p.z := 0;   // AV: fixed - Apo renderer crashes if z-value is not initialized
  p.c := random;
{$else}
asm
    fld1
    call    AsmRandExt
    fadd    st, st
    fsub    st, st(1)
    fstp    qword ptr [p.x]
    call    AsmRandExt
    fadd    st, st
    fsubrp  st(1), st
    fstp    qword ptr [p.y]
    call    AsmRandExt
    fstp    qword ptr [p.c]
    fldz    // AV
    fstp    qword ptr [p.z]  // AV
end;
{$endif}

  try
    xf := fcp.xform[0];
    for i := 0 to FUSE do begin
      xf := xf.PropTable[Random(PROP_TABLE_SIZE)];
      xf.NextPoint(p);
    end;

    for i := 0 to SUB_BATCH_SIZE-1 do begin
      xf := xf.PropTable[Random(PROP_TABLE_SIZE)];
      xf.NextPoint(p);

     if (xf.transOpacity = 0) then continue    // AV
     else if ((xf.transOpacity < 1) and (random > xf.transOpacity)) then  continue;

      finalXform.NextPointTo(p, q);
      fcp.ProjectionFunc(@q);

      px := q.x * cosa + q.y * sina + rcX;
      if (px < 0) or (px > camW) then continue;
      py := q.y * cosa - q.x * sina + rcY;
      if (py < 0) or (py > camH) then continue;

      Bucket := @buckets[Round(bhs * py)][Round(bws * px)];
      MapColor := @ColorMap[Round(q.c * 255)];

      {$ifdef ENABLEZBUF}
      ZBufPos := @zbuffer[Round(bhs * py)][Round(bws * px)];
      if (q.z < ZBufPos^) then
      begin
        ZBufPos^ := q.z;
        Bucket.Red := Bucket.Red + MapColor.Red;
        Bucket.Green := Bucket.Green + MapColor.Green;
        Bucket.Blue := Bucket.Blue + MapColor.Blue;
        Bucket.Count := Bucket.Count + 1;
      end;   
      {$else}
      with Bucket^ do begin
        Red   := Red   + MapColor.Red;
        Green := Green + MapColor.Green;
        Blue  := Blue  + MapColor.Blue;
        Count := Count + 1;
      end;
      {$endif}
    end;

  except
    on EMathError do begin
      exit;
    end;
  end;
end;

procedure TRenderWorkerMT.AddPointsToBuckets(const points: TPointsArray);
var
  i: integer;
  px, py: double;
  Bucket: PBucket;
 // ZBufPos: PDouble;
  MapColor: PColorMapColor;
begin
  for i := SUB_BATCH_SIZE - 1 downto 0 do begin

    px := points[i].x - camX0;
    if (px < 0) or (px > camW) then continue;
    py := points[i].y - camY0;
    if (py < 0) or (py > camH) then continue;

    Bucket := @buckets[Round(bhs * py)][Round(bws * px)];
    MapColor := @ColorMap[Round(points[i].c * 255)];

    {$ifdef ENABLEZBUF}
    ZBufPos := @zbuffer[Round(bhs * py)][Round(bws * px)];
    if (points[i].z < ZBufPos^) then
    begin
      ZBufPos^ := points[i].z;
      Bucket.Red := Bucket.Red + MapColor.Red;
      Bucket.Green := Bucket.Green + MapColor.Green;
      Bucket.Blue := Bucket.Blue + MapColor.Blue;
      Bucket.Count := Bucket.Count + 1;
    end; 
    {$else}
      with Bucket^ do begin
        Red   := Red   + MapColor.Red;
        Green := Green + MapColor.Green;
        Blue  := Blue  + MapColor.Blue;
        Count := Count + 1;
      end;
    {$endif}
  end;
end;

procedure TRenderWorkerMT.AddPointsToBucketsAngle(const points: TPointsArray);
var
  i: integer;
  px, py: double;
  Bucket: PBucket;
  MapColor: PColorMapColor;
  //ZBufPos: PDouble;
begin
  for i := SUB_BATCH_SIZE - 1 downto 0 do begin

    px := points[i].x * cosa + points[i].y * sina + rcX;
    if (px < 0) or (px > camW) then continue;
    py := points[i].y * cosa - points[i].x * sina + rcY;
    if (py < 0) or (py > camH) then continue;

    Bucket := @buckets[Round(bhs * py)][Round(bws * px)];
    MapColor := @ColorMap[Round(points[i].c * 255)];

    {$ifdef ENABLEZBUF}
    ZBufPos := @zbuffer[Round(bhs * py)][Round(bws * px)];
    if (points[i].z < ZBufPos^) then
    begin
      ZBufPos^ := points[i].z;
      Bucket.Red := Bucket.Red + MapColor.Red;
      Bucket.Green := Bucket.Green + MapColor.Green;
      Bucket.Blue := Bucket.Blue + MapColor.Blue;
      Bucket.Count := Bucket.Count + 1;
    end;  
    {$else}
      with Bucket^ do begin
        Red   := Red   + MapColor.Red;
        Green := Green + MapColor.Green;
        Blue  := Blue  + MapColor.Blue;
        Count := Count + 1;
      end;
    {$endif}
  end;
end;

////////////////////////////////////////////////////////////////////////////////
// THREADING
////////////////////////////////////////////////////////////////////////////////
procedure TRenderWorkerMT.Stop;
var
  i: integer;
begin
  for i := 0 to High(WorkingThreads) do
    WorkingThreads[i].Terminate;

  inherited;
end;

procedure TRenderWorkerMT.BreakRender;
var
  i: integer;
begin
  inherited;

  for i := 0 to High(WorkingThreads) do
    WorkingThreads[i].Terminate;
end;

procedure TRenderWorkerMT.Pause;
var
  i: integer;
begin
  inherited;

  for i := 0 to High(WorkingThreads) do
    WorkingThreads[i].Suspend;
end;

procedure TRenderWorkerMT.UnPause;
var
  i: integer;
begin
  inherited;

  for i := 0 to High(WorkingThreads) do
    WorkingThreads[i].Resume;
end;

procedure TRenderWorkerMT.SetThreadPriority(p: TThreadPriority);  // AV
var
  i: integer;
begin
  inherited;

  for i := 0 to High(WorkingThreads) do
    WorkingThreads[i].Priority := p;
end;

function TRenderWorkerMT.NewThread: TBucketFillerThread;
begin
  Result := TBucketFillerThread.Create(fcp);
  assert(Result <> nil);

  if FThreadPriority <> tpNormal then   // AV
    Result.Priority := {tpLower;} FThreadPriority;

  if FCP.FAngle = 0 then
    Result.AddPointsProc := self.AddPointsToBuckets
  else
    Result.AddPointsProc := self.AddPointsToBucketsAngle;

  Result.CriticalSection := CriticalSection;
  Result.Nrbatches := FNumBatches;
  Result.batchcounter := @batchcounter;
end;

end.

