unit dflash_scenebuffer;

//
//  Aggregates several flash meshes into one vertex buffer and render them only
//  with one rendering context. The aggregated flashs are called "scene buffer".
//

{$MODE FPC}
{$MODESWITCH ADVANCEDRECORDS}
{$MODESWITCH DEFAULTPARAMETERS}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}

interface

uses
  dstrnum,
  dvector2f,
  DEng,
  DDrawVertexBuffer,
  dshaperenderer;

type
PFlashSceneBuffer = ^TFlashSceneBuffer;
TFlashSceneBuffer = object
public type
  PChunk = ^TChunk;
  TChunk = record
    First, Count: PtrUInt; // may be changed for other rendering backends
    function IsEmpty: Boolean; inline;
    procedure Invalidate; inline;
  end;
  PVertexData = ^TVertexData;
  TVertexData = packed record
    X, Y: Int16;
    // Coord: TVec2f;
    // Normal: TVec2f;
    // Color: UInt32;
    // BgColor: UInt32;
  end;
private
  FCount: PtrUInt;
  FDirtyFlag: Boolean;
  FData: array of TVertexData;
  FBuffer: TVertexBuffer;
  FShapeRenderer: TShapeRenderer;

  procedure Sync;
      // Syncs FData and VBO

public
  procedure Init;
  procedure Done;

  procedure Clear;
      // Clears the scene buffers and unlinks all chunks. All linked to the
      // scene buffer objects should be relinked and updated again.

  procedure GetChunk(Count: PtrUInt; out Chunk: TChunk);
      // Allocates data in the buffer for a chunk

  procedure UpdateChunk(const Chunk: TChunk; Vertices: PVertexData);
      // Writes new data to a chunk

  procedure Bind;
  procedure Unbind;
      // Binds and unbinds rendering context for the scene buffer.

  procedure DrawChunk(const Chunk: TChunk);
end;

implementation

function IncGet(var Value: PtrUInt; Delta: PtrUInt; Temp: PtrUInt = 0): PtrUInt; inline;
begin
  Temp := Value + Delta;
  Value := Temp;
  Exit(Temp);
end;

function TFlashSceneBuffer.TChunk.IsEmpty: Boolean;
begin
  Exit(Count <= 0);
end;

procedure TFlashSceneBuffer.TChunk.Invalidate;
begin
  First := 0;
  Count := 0;
end;

procedure TFlashSceneBuffer.Sync;
var
  PreviousCount, I: LongInt;
  V: PVec2f;
begin
  FDirtyFlag := False;
  if Length(FData) < FCount then begin
    PreviousCount := Length(FData);
    SetLength(FData, FCount);
    for I := PreviousCount to FCount - 1 do begin
      // FData[I].Coord := Vec2f(0, 0);
      // FData[I].Normal := Vec2f(0, 0);
      // FData[I].Color := 0;
      // FData[I].BgColor := 0;
    end;
  end;
  {FBuffer.SetCount(Length(FData));
  for I := 0 to High(FData) do begin
    PVec2f(FBuffer.Vertices2f)[I] := FData[I].Coord;
    // PUInt32(FBuffer.Colors4ub)[I] := FData[I].Color;
  end;}
  FBuffer.SetInterleavedData(@FData[0], SizeOf(TVertexData));
  FBuffer.SetInterleavedVertices2s(Pointer(@FData[0].X) - Pointer(@FData[0]));
  // FBuffer.SetInterleavedColors4ub(Pointer(@FData[0].Color) - Pointer(@FData[0]));
  FBuffer.UploadToGPUStorage;
  Log('Size of TFlashSceneBuffer: ' + IntToStr(Length(FData) * SizeOf(TVertexData)));
end;

procedure TFlashSceneBuffer.Init;
begin
  FDirtyFlag := True;
  FBuffer.Init;
  FShapeRenderer.Init;
end;

procedure TFlashSceneBuffer.Done;
begin
  FShapeRenderer.Done;
  FBuffer.Done;
end;

procedure TFlashSceneBuffer.Clear;
begin
  FCount := 0;
  SetLength(FData, 0);
  FDirtyFlag := True;
end;

procedure TFlashSceneBuffer.GetChunk(Count: PtrUInt; out Chunk: TChunk);
begin
  Chunk.First := FCount;
  Chunk.Count := Count;
  Inc(FCount, Count);
  FDirtyFlag := True;
end;

procedure TFlashSceneBuffer.UpdateChunk(const Chunk: TChunk; Vertices: PVertexData);
begin
  if Length(FData) < FCount then
    SetLength(FData, FCount);
  Move(Vertices^, FData[Chunk.First], Chunk.Count * SizeOf(TVertexData));
end;

procedure TFlashSceneBuffer.Bind;
begin
  if FDirtyFlag then
    Sync;
  FBuffer.Enable;
end;

procedure TFlashSceneBuffer.Unbind;
begin
  FBuffer.Disable;
end;

procedure TFlashSceneBuffer.DrawChunk(const Chunk: TChunk);
begin
  FBuffer.BatchTriangles(Chunk.First, Chunk.Count);
end;

end.
