{$MODE OBJFPC}
{$MODESWITCH AUTODEREF}
// TODO move unit to opengl profile and remove it
{$define DENG_USE_DRAW_OPENGL}
unit DDrawTexHolder;

interface

uses
  {$IFDEF DENG_USE_DRAW_OPENGL}{$IFDEF ANDROID}GLES,{$ELSE}GL, GLU,{$ENDIF}{$ENDIF}
  derror,
  deng,
  dstrnum,
  dvector2i,
  dvector2f,
  dstream,
  dscreenbuffer,
  dtexture2d,
  dresman,
  dtaskque,
{$IFDEF ANDROID}
  DDrawVertexBuffer,
{$ENDIF}
  DMetrics;

type
PDrawTexParameters = ^TDrawTexParameters;
PDrawTexHolder = ^TDrawTexHolder;

TCreateHolder = function (Man: PManager; Parameters: PDrawTexParameters): PDrawTexHolder;

// public class
TDrawTexParameters = object
private
  Eng: TEngine;
  Data: TData;
  MipMap: Boolean;
  Texture: TTexture2dRgba;
  ScreenBuffer: PScreenBuffer;
  procedure SetDefaults(NewEng: TEngine);
public
  Key: AnsiString;
  Func: TCreateHolder;
  NeedTexture: Boolean; // do we need to use texture data from RAM
  procedure Done;
  procedure SetData(aEng: TEngine; const aData: TData; aMipMap: Boolean = True);
  procedure SetScreenBuffer(aEng: TEngine; aScreenBuffer: PScreenBuffer; aMipMap: Boolean = True);
  procedure SetTexture(aEng: TEngine; aTexture: TTexture2dRgba; aMipMap: Boolean = True);
end;

// Private class
TDrawTexHolder = object(TResourceHolder)
public
  {$IFDEF DENG_USE_DRAW_OPENGL}Id: GLuint;{$ENDIF}
  Key: AnsiString; // do we need it only for log?
  Width: Integer;
  Height: Integer;
  Eng: TEngine;
  MipMap: Boolean;
  Texture: TTexture2dRgba;
  PriorityCount: array[0 .. PRIORITIES_COUNT - 1] of LongInt;
  Status: record
    InRAM: Boolean;
    Encoded: Boolean;
    GPUPrepared: Boolean;
    GPULoaded: Boolean;
  end;
  constructor Init;
  destructor Done; virtual;
  function Size: TVec2i; inline;
  function Ready: Boolean; inline;
  procedure Force;
  procedure Require;
  // Loads data to RAM
  procedure LoadToRAM; // ENQUEUED -> INRAM
  // Encodes data to actual texture
  procedure EncodeTexture;
  // Generates GPU handle for the resource, but does not fill it with data
  procedure PrepareGPU;
  // Loads texture to GPU
  procedure LoadToGPU; // INRAM -> READY
  // If resource has been dead this method restores it
  procedure RestoreGPU;
  function GetTaskLoadToGPU: ITask;
  function GetTaskEncode: ITask;
  function GetPriority: LongInt;
end;

implementation

constructor TDrawTexHolder.Init;
begin
  inherited Init;
  Status.InRAM := False;
  Status.Encoded := False;
  Status.GPUPrepared := False;
  Status.GPULoaded := False;
end;

destructor TDrawTexHolder.Done;
begin
  Texture.Done;
  Key := '';
  inherited Done;
end;

function TDrawTexHolder.Size: TVec2i;
begin
  Result := Vec2i(Width, Height);
end;

function TDrawTexHolder.Ready: Boolean;
begin
  Result := Status.GPULoaded;
end;

procedure TDrawTexHolder.Force;
begin
  LoadToGPU;
  LogTagged('R', 'LOADED TO GPU ' + Key);
end;

procedure TDrawTexHolder.Require;
begin
end;

procedure TDrawTexHolder.LoadToRAM;
begin
  if Status.InRAM then
    Exit;
  //Log(GetKey);
  Status.InRAM := True;
end;

procedure TDrawTexHolder.EncodeTexture;
begin
  if Status.Encoded then
    Exit;
  Status.Encoded := True;
end;

procedure TDrawTexHolder.PrepareGPU;
begin
  if Status.GPUPrepared then
    Exit;
  glGenTextures(1, @Id);
  glBindTexture(GL_TEXTURE_2D, Id);
  //glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // set 1-byte alignment

  // set texture to repeat mode
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

  if MipMap then begin
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    {$IFDEF ANDROID}
    //glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
    {$ENDIF}
  end else begin
    // TODO Only for Al Dente, in other cases use GL_LINEAR instead of GL_NEAREST
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  end;
  //glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0);
  glDisable(GL_LIGHTING);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_DITHER);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

  glBindTexture(GL_TEXTURE_2D, 0);
  Status.GPUPrepared := True;
end;

procedure TDrawTexHolder.LoadToGPU;
var
  Dur: TMetricDuration;
begin
  if Status.GPULoaded then
    Exit;
  if not Status.InRAM then
    LoadToRAM;
  if not Status.Encoded then
    EncodeTexture;
  if not Status.GPUPrepared then
    PrepareGPU;

  Dur.Init('texture_upload_dt');
//{$IF False} // TESTING ANDROID DIED
  glBindTexture(GL_TEXTURE_2D, Id);
  {$IFNDEF ANDROID}
  if MipMap then begin
    gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA,
                      Texture.Width, Texture.Height, GL_RGBA,
                      GL_UNSIGNED_BYTE, Texture.Data);
  end else
  {$ENDIF}
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
                 Texture.Width, Texture.Height, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, Texture.Data);
  glBindTexture(GL_TEXTURE_2D, 0);
//{$ENDIF}
  //Log('MARKER TEXTURE PTR = ' + IntToStr(PtrUInt(Parameters.Texture.Data)));
  Dur.Done;
  Status.GPULoaded := True;
end;

procedure TDrawTexHolder.RestoreGPU;
begin
  LoadToGPU;
  LogTagged('R', 'Restored to GPU: ' + Key);
end;

type
TTaskLoadToGPU = object(ITask)
  procedure Execute; virtual;
end;

procedure TTaskLoadToGPU.Execute;
begin
  PDrawTexHolder(Obj).LoadToGPU;
end;

function TDrawTexHolder.GetTaskLoadToGPU: ITask;
begin
  TTaskLoadToGPU(Result).Init(@Self);
end;

type
TTaskEncode = object(ITask)
  procedure Execute; virtual;
end;

procedure TTaskEncode.Execute;
begin
  with PDrawTexHolder(Obj)^ do begin
    LoadToRAM;
    EncodeTexture;
  end;
end;

function TDrawTexHolder.GetTaskEncode: ITask;
begin
  TTaskEncode(Result).Init(@Self);
end;

function TDrawTexHolder.GetPriority: LongInt;
var
  I: LongInt;
begin
  for I := 0 to PRIORITIES_COUNT - 1 do begin
    if PriorityCount[I] > 0 then
      Exit(I);
  end;
  Exit(-1);
end;

function CreateHolderFromTexture(Man: PManager; Parameters: PDrawTexParameters): PDrawTexHolder;
begin
  LogTagged('R', 'LOADING ' + Parameters.Key);
  New(Result, Init);

  Result.Key := Parameters.Key;
  Result.Width := Parameters.Texture.Width;
  Result.Height := Parameters.Texture.Height;
  Result.Texture := Parameters.Texture;
  Parameters.Texture.Init;
  Result.MipMap := Parameters.MipMap;
  //FreeMem(Result.Texture.Data); // TESTING ANDROID DIED
end;

function CreateHolderFromScreenBuffer(Man: PManager; Parameters: PDrawTexParameters): PDrawTexHolder;
begin
  Parameters.Texture.InitRGBA(Parameters.ScreenBuffer);
  Result := CreateHolderFromTexture(Man, Parameters);
  Parameters.Texture.Done;
end;

function CreateHolderFromData(Man: PManager; Parameters: PDrawTexParameters): PDrawTexHolder;
var
  Error: TError;
begin
  if not LoadTexture2d(Parameters.Data.Stream, Parameters.Texture, @Error) then begin
    LogTagged('R', 'FAILED ' + Parameters.Data.Stream^.Name + ': ' + Error.Msg);
    New(Result, Init);
    Result.Id := 0;
    Result.Width := 0;
    Result.Height := 0;
  end else begin
    Result := CreateHolderFromTexture(Man, Parameters);
    Parameters.Texture.Done;
  end;
end;

procedure TDrawTexParameters.SetDefaults(NewEng: TEngine);
begin
  Eng := NewEng;
  MipMap := True;
  ScreenBuffer := nil;
  Key := '';
  NeedTexture := False;
end;

procedure TDrawTexParameters.Done;
begin
  Data.Done;
  Key := '';
end;

procedure TDrawTexParameters.SetData(aEng: TEngine; const aData: TData; aMipMap: Boolean = True);
begin
  SetDefaults(Eng);
  Data := aData;
  MipMap := aMipMap;
  Key := 'TDrawTex Eng=... Data=' + Data.Stream.Name + ' MipMap=...';
  Func := @CreateHolderFromData;
end;

procedure TDrawTexParameters.SetScreenBuffer(aEng: TEngine; aScreenBuffer: PScreenBuffer; aMipMap: Boolean = True);
begin
  SetDefaults(aEng);
  Self.ScreenBuffer := aScreenBuffer;
  Self.MipMap := aMipMap;
  Self.Key := 'TDrawTex Eng=... ScreenBuffer=... MipMap=...';
  Func := @CreateHolderFromScreenBuffer;
end;

procedure TDrawTexParameters.SetTexture(aEng: TEngine; aTexture: TTexture2dRgba; aMipMap: Boolean = True);
begin
  SetDefaults(aEng);
  Texture := aTexture;
  MipMap := aMipMap;
  Key := 'TDrawTex Eng=... Texture=... MipMap=...';
  Func := @CreateHolderFromTexture;
end;

end.
