{$MODE FPC}
{$MODESWITCH out}
{$MODESWITCH RESULT}
{$I deng_defines.inc}
unit DDrawVertexBuffer;

interface

uses
  {$IFDEF DENG_USE_DRAW_OPENGL}{$IFDEF ANDROID}GLES,{$ELSE}GL,GLext,{$ENDIF}{$ENDIF}
  {$IFDEF DENG_USE_DRAW_VULKAN}vulkan,{$ENDIF}
  {$IFDEF MSWINDOWS}windows,{$ENDIF}
  DEng,
  dstrnum,
  dcolors,
  dvector2i,
  dvector2f,
  dvector3f,
  dvector4f;

type
PBuffer = ^TBuffer;
TBuffer = object
private
  FData: Pointer;
  FSize: LongInt;
  FStride: SizeUInt; // Offset between consecutive elements. Zero by default
  FGPUData: Pointer; // Related to VBO, must be equal to Data if VBO is not in use
public
  procedure Init(Size: LongInt); inline; overload;
  procedure Init(P: Pointer); inline; overload;
  procedure Done; inline;
  property Data: Pointer read FData;
end;

// TODO loading to GPU indices
PIndexBuffer = ^TIndexBuffer;
TIndexBuffer = object
public type
  TIndex = Word;
  PIndex = ^TIndex;
  PArrayOfIndex = ^TArrayOfIndex;
  TArrayOfIndex = array[0..1] of TIndex;
private
  FCount: LongInt;
  FIndices: TBuffer;
public
  procedure Init; overload;
  procedure Init(Count: LongInt); overload;
  procedure Init(const _Indices: array of TIndex); overload;
  procedure Init(P: Pointer; Count: UInt32); overload;
  procedure Done;
  procedure Reinit(const _Indices: array of TIndex); overload;
  procedure Reinit(P: Pointer; Count: UInt32); overload;
  procedure SetCount(Count: LongInt);
  function Indices: PArrayOfIndex; inline;
  property Count: LongInt read FCount write SetCount;
end;

PVertexLayer = ^TVertexLayer;
TVertexLayer = object
private
  FBuffer: TBuffer;
  FComponents: LongInt;
  FType: LongInt;
  FId: PtrUInt;
  FElementSize: LongInt;
end;

PVertexBuffer = ^TVertexBuffer;
TVertexBuffer = object
public type
  TArrayOfVec4ub  = array[0..1] of TColor;
  TArrayOfVec2i   = array[0..1] of TVec2i;
  TArrayOfVec2f   = array[0..1] of TVec2f;
  TArrayOfVec3f   = array[0..1] of TVec3f;
  TArrayOfVec4f   = array[0..1] of TVec4f;
  PArrayOfVec4ub  = ^TArrayOfVec4ub;
  PArrayOfVec2i   = ^TArrayOfVec2i;
  PArrayOfVec2f   = ^TArrayOfVec2f;
  PArrayOfVec3f   = ^TArrayOfVec3f;
  PArrayOfVec4f   = ^TArrayOfVec4f;
private type
  TLayersArray = array[0 .. 15] of TVertexLayer;
  PLayersArray = ^TLayersArray;
  TLayersMap = array of TVertexLayer;
private
  FCount: LongInt;
  FVertexType: LongInt;
  FVertices: TBuffer;
  FColorType: LongInt;
  FColors: TBuffer;
  FNormalType: LongInt;
  FNormals: TBuffer;
  FTexCoordType: LongInt;
  FTexCoords: TBuffer;
  FInterleavedUsed: Boolean;
  FInterleaved: TBuffer;
  FInterleavedSize: SizeUInt;

  FLayersArray: PLayersArray;
  FLayersMap: TLayersMap;

  FGPUStorageDirty: Boolean;

{$IF Defined(DENG_USE_DRAW_OPENGL)}
  FVertexBufferObject: GLuint;
  FVertexArrayObject: GLuint;
  FVertexBufferObjectSize: SizeUInt;
{$ENDIF}

  function FindAndCreateLayer(LayerId: PtrUInt;
                              out Layer: PVertexLayer): Boolean;
      //  Finds for the LayerId. Returns True and the layer Layer if it exists.
      //  Returns False and creates the layer if does not.

  function GetVertexSize(_Type: LongInt): LongInt;
  procedure SetVerticesType(NewType: LongInt);
  function GetColorSize(_Type: LongInt): LongInt;
  procedure SetColorsType(NewType: LongInt);
  function GetNormalSize(_Type: LongInt): LongInt;
  procedure SetNormalsType(NewType: LongInt);
  function GetTexCoordSize(_Type: LongInt): LongInt;
  procedure SetTexCoordsType(NewType: LongInt);
  function GetInterleavedSize(InterleavedUsed: Boolean): LongInt;

  procedure EnableLayer(Layer: PVertexLayer); inline;
  procedure DisableLayer(Layer: PVertexLayer); inline;
public
  procedure Init; overload;
  procedure Init(aCount: LongInt); overload;
  procedure Init(var A: array of TVec2i); overload;
  procedure Init(var A: array of TVec2f); overload;
  procedure Init(var A: array of TVec3f); overload;
  procedure Done;
  procedure Reinit(var A: array of TVec3f); overload;
  procedure SetCount(Count: LongInt);

  // VBO
  procedure SetUseGPUStorage(UseGPUStorage: Boolean);
  function GetUseGPUStorage: Boolean;

  procedure SyncGPUStorage;
        //  Forces reuploading data to GPU. Should be called after implicit
        //  data change in client arrays. May be called after explicit data
        //  change.
        //
        //  Does nothing if UseGPUStorage=False.

  procedure UploadToGPUStorage;
         //   SetUseGPUStorage(True);
         //   SyncGPUStorage;

  // All these methods do not copy data, just saves a pointer to it
  procedure SetVertices(const P: array of TVec2i);
  procedure SetVertices(const P: array of TVec2f);
  procedure SetVertices(const P: array of TVec3f);
  procedure SetVertices(var V: TVec2f; Count: LongInt);
  procedure SetColors(const P: array of TColor);
  procedure SetColors(const P: array of TVec4f);
  procedure SetColors(constref Colors: TColor);
  procedure SetNormals(const P: array of TVec3f);
  procedure SetTexCoords(const P: array of TVec2f);
  procedure SetTexCoords(const P: array of TVec3f);

  procedure SetInterleavedData(P: Pointer; ElementSize: SizeUInt);
  procedure SetInterleavedVertices2i(Offset: SizeUInt);
  procedure SetInterleavedVertices2f(Offset: SizeUInt);
  procedure SetInterleavedVertices2s(Offset: SizeUInt);
  procedure SetInterleavedVertices3f(Offset: SizeUInt);
  procedure SetInterleavedColors4ub(Offset: SizeUInt);
  procedure SetInterleavedColors4f(Offset: SizeUInt);
  procedure SetInterleavedNormal3f(Offset: SizeUInt);

  procedure DeleteVertices; inline;
  function Vertices2i: PArrayOfVec2i; inline;
  function Vertices2f: PArrayOfVec2f; inline;
  function Vertices3f: PArrayOfVec3f; inline;
  procedure DeleteColors; inline;
  function Colors4ub: PArrayOfVec4ub; inline;
  function Colors4f: PArrayOfVec4f; inline;
  procedure DeleteNormals; inline;
  function Normals: PArrayOfVec3f; inline;
  procedure DeleteTexCoords; inline;
  function TexCoords2f: PArrayOfVec2f; inline;
  function TexCoords3f: PArrayOfVec3f; inline;

  // All following methods do not copy data, just use links to it
  procedure SetLayer(LayerId: PtrUInt;
                     Components: PtrUInt;
                     _Type: PtrUInt;
                     ElementSize: LongInt;
                     Data: Pointer);
  procedure SetLayer(LayerId: PtrUInt;
                     const P: array of TVec2f); overload;
  procedure SetLayer(LayerId: PtrUInt;
                     const P: array of TVec3f); overload;
  procedure SetLayer(LayerId: PtrUInt;
                     const P: array of TVec4f); overload;

  function GetLayer(LayerId: PtrUInt;
                    Components: PtrUInt;
                    _Type: PtrUInt;
                    ElementSize: LongInt): Pointer; inline;
  function Layer2f(LayerId: PtrUInt): PArrayOfVec2f; inline;
  function Layer3f(LayerId: PtrUInt): PArrayOfVec3f; inline;
  function Layer4f(LayerId: PtrUInt): PArrayOfVec4f; inline;
      // Returns pointer to data of the LayerId. If the LayerId is not created
      // yet, allocates memory for it.

  procedure DeleteLayer(LayerId: PtrUInt);
      // Removes the layer LayerId. Does nothing if the layer does not exist.

  function GetAllArraysSize: SizeUInt;
      // Returns size in bytes of all arrays in the buffer.

  procedure DrawPoints;
  {$IFNDEF ANDROID}procedure DrawQuads;{$ENDIF}
  procedure DrawTriangles;
  procedure DrawLineLoop;
  procedure DrawLines;
  procedure DrawPolygon;
  procedure DrawPoints(const Indices: TIndexBuffer);
  {$IFNDEF ANDROID}procedure DrawQuads(const Indices: TIndexBuffer);{$ENDIF}
  procedure DrawTriangles(const Indices: TIndexBuffer);
  procedure DrawTriangles(const Indices: TIndexBuffer; First, Count: PtrInt);

  procedure Enable;
  procedure Disable;

  procedure BatchTriangles(First, Count: PtrInt); inline;

  property Count: LongInt read FCount write SetCount;
end;

implementation

//{{{ TBuffer
procedure TBuffer.Init(Size: LongInt);
begin
  GetMem(FData, Size);
  FSize := Size;
  FStride := 0;
  FGPUData := FData;
end;

procedure TBuffer.Init(P: Pointer);
begin
  FData := P;
  FSize := 0;
  FStride := 0;
  FGPUData := P;
end;

procedure TBuffer.Done;
begin
  if (FData <> nil) and (FSize <> 0) then begin
    FreeMem(FData, FSize);
  end;
end;
//}}}
//{{{ TIndexBuffer
procedure TIndexBuffer.Init;
begin
  FCount := 0;
  FIndices.Init(nil);
end;

procedure TIndexBuffer.Init(Count: LongInt);
begin
  FCount := 0;
  SetCount(Count);
end;

procedure TIndexBuffer.Init(const _Indices: array of TIndex);
begin
  Init;
  FIndices.Init(@_Indices[0]);
  FCount := Length(_Indices);
end;

procedure TIndexBuffer.Init(P: Pointer; Count: UInt32);
begin
  Init;
  FIndices.Init(P);
  FCount := Count;
end;

procedure TIndexBuffer.Done;
begin
  FIndices.Done;
end;

procedure TIndexBuffer.Reinit(const _Indices: array of TIndex);
begin
  Done;
  Init(_Indices);
end;

procedure TIndexBuffer.Reinit(P: Pointer; Count: UInt32);
begin
  Log('Done');
  Done;
  Log('Init');
  Init(P, Count);
end;

procedure TIndexBuffer.SetCount(Count: LongInt);
begin
  if Count > FCount then begin
    if FCount <> 0 then
      FIndices.Done;
    FIndices.Init(Count * SizeOf(TIndex));
  end;
  FCount := Count;
end;

function TIndexBuffer.Indices: PArrayOfIndex;
begin
  Result := FIndices.Data;
end;
//}}}
//{{{ TVertexBuffer
procedure TVertexBuffer.Init;
begin
  FCount := 0;
  FVertexType := 0;
  FColorType := 0;
  FNormalType := 0;
  FTexCoordType := 0;
  FInterleavedUsed := False;
  FLayersArray := nil;
  SetLength(FLayersMap, 0);
  FGPUStorageDirty := True;
{$IF Defined(DENG_USE_DRAW_OPENGL)}
  FVertexBufferObject := 0;
  FVertexArrayObject := 0;
  FVertexBufferObjectSize := 0;
{$ENDIF}
end;

procedure TVertexBuffer.Init(aCount: LongInt);
begin
  Init;
  SetCount(aCount);
end;

procedure TVertexBuffer.Init(var A: array of TVec2i);
begin
  Init;
  SetCount(Length(A));
  SetVertices(A);
end;

procedure TVertexBuffer.Init(var A: array of TVec2f);
begin
  Init;
  SetCount(Length(A));
  SetVertices(A);
end;

procedure TVertexBuffer.Init(var A: array of TVec3f);
begin
  Init;
  SetCount(Length(A));
  SetVertices(A);
end;

procedure TVertexBuffer.Done;
begin
  SetUseGPUStorage(False);
  if FVertexType <> 0 then
    FVertices.Done;
  if FColorType <> 0 then
    FColors.Done;
  if FNormalType <> 0 then
    FNormals.Done;
  if FTexCoordType <> 0 then
    FTexCoords.Done;
  if FInterleavedUsed then
    FInterleaved.Done;
end;

procedure TVertexBuffer.Reinit(var A: array of TVec3f);
begin
  Done;
  Init(A);
end;

procedure TVertexBuffer.SetCount(Count: LongInt);
var
  I: LongInt;
begin
  if Count > FCount then begin
    // TODO support resize inside TBuffer
    if FVertexType <> 0 then begin
      FVertices.Done;
      FVertices.Init(Count * GetVertexSize(FVertexType));
    end;
    if FColorType <> 0 then begin
      FColors.Done;
      FColors.Init(Count * GetColorSize(FColorType));
    end;
    if FNormalType <> 0 then begin
      FNormals.Done;
      FNormals.Init(Count * GetNormalSize(FNormalType));
    end;
    if FTexCoordType <> 0 then begin
      FTexCoords.Done;
      FTexCoords.Init(Count * GetTexCoordSize(FTexCoordType));
    end;
    if FLayersArray <> nil then begin
      for I := 0 to High(TLayersArray) do begin
        if FLayersArray^[I].FType <> -1 then begin
          FLayersArray^[I].FBuffer.Done;
          FLayersArray^[I].FBuffer.Init(Count * FLayersArray^[I].FElementSize);
        end;
      end;
    end;
    for I := 0 to High(FLayersMap) do begin
      FLayersMap[I].FBuffer.Done;
      FLayersMap[I].FBuffer.Init(Count * FLayersMap[I].FElementSize);
    end;
  end;
  FCount := Count;
  FGPUStorageDirty := True;
end;
//}}}
//{{{ TVertexBuffer static buffers
procedure TVertexBuffer.SetVertices(const P: array of TVec2i);
begin
  SetVerticesType(0);
  FVertices.Init(@P[0]);
  FVertexType := 1;
end;

procedure TVertexBuffer.SetVertices(const P: array of TVec2f);
begin
  SetVerticesType(0);
  FVertices.Init(@P[0]);
  FVertexType := 2;
end;

procedure TVertexBuffer.SetVertices(const P: array of TVec3f);
begin
  SetVerticesType(0);
  FVertices.Init(@P[0]);
  FVertexType := 3;
end;

procedure TVertexBuffer.SetVertices(var V: TVec2f; Count: LongInt);
begin
  SetVerticesType(0);
  SetCount(Count);
  FVertices.Init(@V);
  FVertexType := 2;
end;

procedure TVertexBuffer.SetColors(const P: array of TColor);
begin
  SetColors(P[0]);
end;

procedure TVertexBuffer.SetColors(const P: array of TVec4f);
begin
  SetColorsType(0);
  FColors.Init(@P[0]);
  FColorType := 2;
end;

procedure TVertexBuffer.SetColors(constref Colors: TColor);
begin
  SetColorsType(0);
  FColors.Init(@Colors);
  FColorType := 1;
end;

procedure TVertexBuffer.SetNormals(const P: array of TVec3f);
begin
  SetNormalsType(0);
  FNormals.Init(@P[0]);
  FNormalType := 1;
end;

procedure TVertexBuffer.SetTexCoords(const P: array of TVec2f);
begin
  SetTexCoordsType(0);
  FTexCoords.Init(@P[0]);
  FTexCoordType := 1;
end;

procedure TVertexBuffer.SetTexCoords(const P: array of TVec3f);
begin
  SetTexCoordsType(0);
  FTexCoords.Init(@P[0]);
  FTexCoordType := 2;
end;

procedure TVertexBuffer.SetInterleavedData(P: Pointer; ElementSize: SizeUInt);
begin
  FInterleavedUsed := True;
  FInterleaved.Init(P);
  FInterleavedSize := ElementSize;
end;

procedure TVertexBuffer.SetInterleavedVertices2i(Offset: SizeUInt);
begin
end;

procedure TVertexBuffer.SetInterleavedVertices2f(Offset: SizeUInt);
begin
  SetVerticesType(0);
  FVertices.Init(Pointer(Offset));
  FVertices.FStride := FInterleavedSize;
  FVertices.FGPUData := Pointer(FInterleaved.Data) + Offset;
  FVertexType := 2;
end;

procedure TVertexBuffer.SetInterleavedVertices2s(Offset: SizeUInt);
begin
  SetVerticesType(0);
  FVertices.Init(Pointer(Offset));
  FVertices.FStride := FInterleavedSize;
  FVertices.FGPUData := Pointer(FInterleaved.Data) + Offset;
  FVertexType := 4;
end;

procedure TVertexBuffer.SetInterleavedVertices3f(Offset: SizeUInt);
begin
end;

procedure TVertexBuffer.SetInterleavedColors4ub(Offset: SizeUInt);
begin
  SetColorsType(0);
  FColors.Init(Pointer(Offset));
  FColors.FStride := FInterleavedSize;
  FColors.FGPUData := Pointer(FInterleaved.Data) + Offset;
  FColorType := 1;
end;

procedure TVertexBuffer.SetInterleavedColors4f(Offset: SizeUInt);
begin
end;

procedure TVertexBuffer.SetInterleavedNormal3f(Offset: SizeUInt);
begin
end;
//}}}

{$IF Defined(DENG_USE_DRAW_OPENGL)}
{$INCLUDE DDrawVertexBuffer_gl.inc}
{$ENDIF}

{$IF Defined(DENG_USE_DRAW_VULKAN)}
{$INCLUDE DDrawVertexBuffer_vulkan.inc}
{$ENDIF}

end.
