{$MODE OBJFPC}
{$MODESWITCH AUTODEREF}
// TODO move unit to opengl profile and remove it
{$define DENG_USE_DRAW_OPENGL}
{
    Стадии загрузки текстуры:
      DrawTex.Init(nil, 'tex.tga'); // клиент инициирует загрузку текстуры
        CreateHolder // если текстуры tex.tga нет -- создаём холдер
        LinkToHolder // связываем линк с холдером
        Status := ENQUEUED // требуется загрузка текстуры
      // здесь управление может быть возвращено в клиентский код

        LoadToRAM // стадия загрузки текстуры в оперативную память
                  // может быть выполнена в фоновом потоке
          Status := INRAM // текстура в оперативке, осталось отправить в видео

        LoadToGPU // стадия загрузки текстуры в видеоапи
                  // должно выполняться из того же потока,
                  // что и основной рендер
          Status := READY // текстура готова к использованию по назначению

      // пользуемся
      DrawTex.Bind
      DrawTex.Draw

      DrawTex.Done; // клиент освобождается от использования текстуры
        UnlinkFromHolder // разлинкуемся с холдером
                         // уменьшаем число ссылок на холдер
                         // возможно, что холдер после этого станет
                         // ненужным и предназначенным к удалению
}
unit DDrawTex;

interface

uses
  {$IFDEF DENG_USE_DRAW_OPENGL}{$IFDEF ANDROID}GLES,{$ELSE}GL, GLU,{$ENDIF}{$ENDIF}
  dstrnum,
  dvector2i,
  dvector2f,
  dvector3f,
  dvector4f,
  dstream,
  dstreamfile, // for Init(FileName)
  dscreenbuffer,
  dtexture2d,
  dresman,
  dtaskque,
  deng,
  DDrawTexHolder,
  DDrawVertexBuffer,
  DMetrics;

type
PDrawTexManager = ^TDrawTexManager;
PDrawTex = ^TDrawTex;

TDrawTexManager = object(TManager)
private
  FForceAll: Boolean;
public
  destructor Done; virtual;
  procedure Update; // background, uploading, gc etc
  procedure ForceUploadAll; // upload all textures to video
  procedure RestoreAll; // restore all textures
  procedure WaitRequireLoaded; // wait for all required textures
  property ForceAll: Boolean read FForceAll write FForceAll;
end;

TDrawTex = object(specialize TResourceBase<TDrawTexParameters, TDrawTexHolder>)
public const
  PRIORITY_BACKGROUND = TDrawTexHolder.PRIORITY_BACKGROUND;
  PRIORITY_NORMAL = TDrawTexHolder.PRIORITY_NORMAL;
  PRIORITY_REQUIRE = TDrawTexHolder.PRIORITY_REQUIRE;
public type
  TTexCoords4 = array[0 .. 3] of TVec2f;
public
  constructor Init; overload;
  constructor Init(Man: PDrawTexManager; Parameters: PDrawTexParameters); overload;
  constructor Init(Man: PDrawTexManager; Eng: TEngine; Data: TData; MipMap: Boolean = True); overload;
  constructor Init(Man: PDrawTexManager; Eng: TEngine; ScreenBuffer: PScreenBuffer; MipMap: Boolean = True); overload;
  constructor Init(Man: PDrawTexManager; Eng: TEngine; Texture: TTexture2dRgba; MipMap: Boolean = True); overload;
  constructor Init(const FileName: AnsiString); overload;
  constructor InitClone(Tex: PDrawTex);
  constructor Init(constref Tex: TDrawTex);
  destructor Done; virtual;
  function Loaded: Boolean;
  // Forces loading of the texture and waits it is actually loaded
  // So you can use the texture right after this method
  procedure Force; inline;
  // Marks the texture with highest priority
  // Manager.WaitRequireLoaded waits for loading of this texture
  procedure Require; inline;
  procedure LinkToHolder(_Holder: TDrawTex.PHolder); virtual;
  procedure UnlinkFromHolder; virtual;
  // TODO check that this functions is actual inline and access to Id is direct
  procedure Bind(Eng: TEngine); inline;
  procedure Unbind(Eng: TEngine); inline;
  procedure Draw(Eng: TEngine; const P: TVec2f; FlipX: Boolean = False; FlipY: Boolean = False); inline;
  procedure Draw(Eng: TEngine; const P: TVec2f; const Color: TVec4f; FlipX: Boolean = False; FlipY: Boolean = False); inline;
  procedure Draw(Eng: TEngine; const P, S: TVec2f; FlipX: Boolean = False; FlipY: Boolean = False);
  procedure Draw(Eng: TEngine; const P, S: TVec2f; const Color: TVec4f; FlipX: Boolean = False; FlipY: Boolean = False);
  procedure Draw(Eng: TEngine; const P, S: TVec2f; const T, R: TVec2f);
  procedure Draw(Eng: TEngine; const P, S: TVec2f; const T, R: TVec2i; FlipX: Boolean = False; FlipY: Boolean = False);
  procedure Draw(Eng: TEngine; const P, S: TVec2f; const T: TTexCoords4; FlipX: Boolean = False; FlipY: Boolean = False);
  procedure DrawLT(Eng: TEngine; const P, S: TVec2f; const T, R: TVec2i);
  procedure Draw(Eng: TEngine; const Pos, Right, Up: TVec3f; const Pivot: TVec2f);
  procedure DrawLT(Eng: TEngine; const P, S: TVec2f; const T, R: TVec2f);
  function Size: TVec2i; inline;
  function Width: LongInt; inline;
  function Height: LongInt; inline;
private
  Holder: PHolder;
  FPriority: LongInt;
end;

TDrawTexSubsystem = object(TEngSubsystem)
private
  FTaskQue: TTaskQue;
  FTasks: array of ITask;
public
  procedure OnInit; virtual;
  procedure OnDone; virtual;
  procedure Update; virtual;
  procedure AddTask(Task: ITask);
end;

var
  DrawTexMan: TDrawTexManager;
  DrawTexSubsystem: TDrawTexSubsystem;

implementation

function GetActualManager(Man: PDrawTexManager): PDrawTexManager;
begin
  Result := Man;
  if Result = nil then
    Result := @DrawTexMan;
end;

destructor TDrawTexManager.Done;
var
  It: dresman.TManager.TIterator;
begin
  It.Init(@FHashMap);
  while It.Next do begin
    Dispose(PDrawTexHolder(It.Value^), Done);
  end;
  It.Done;
  inherited Done;
end;

procedure TDrawTexManager.Update;
begin
end;

procedure TDrawTexManager.ForceUploadAll;
var
  It: dresman.TManager.TIterator;
begin
  It.Init(@FHashMap);
  while It.Next do
    PDrawTexHolder(It.Value^).LoadToGPU;
  It.Done;
end;

procedure TDrawTexManager.RestoreAll;
var
  It: dresman.TManager.TIterator;
begin
  It.Init(@FHashMap);
  while It.Next do
    PDrawTexHolder(It.Value^).RestoreGPU;
  It.Done;
end;

procedure TDrawTexManager.WaitRequireLoaded;
var
  It: dresman.TManager.TIterator;
  Holder: PDrawTexHolder;
begin
  It.Init(@FHashMap);
  while It.Next do begin
    Holder := PDrawTexHolder(It.Value^);
    if not Holder^.Ready then begin
      if Holder^.PriorityCount[TDrawTex.PRIORITY_REQUIRE] > 0 then begin
        Holder^.Force;
      end;
    end;
  end;
  It.Done;
end;

constructor TDrawTex.Init;
begin
  Holder := nil;
end;

constructor TDrawTex.Init(Man: PDrawTexManager; Parameters: PDrawTexParameters);
begin
  Man := GetActualManager(Man);
  InitBase(Man, Parameters, TCreateHolder(Parameters.Func));
  FPriority := PRIORITY_NORMAL;
  Inc(Holder^.PriorityCount[PRIORITY_NORMAL]);
  if Man^.ForceAll then
    Force;
end;

constructor TDrawTex.Init(Man: PDrawTexManager; Eng: TEngine; Data: TData; MipMap: Boolean);
var
  Parameters: TDrawTexParameters;
begin
  Parameters.SetData(Eng, Data, MipMap);
  {$IFDEF ANDROID}
  // TODO check for Data is started with data/ and not from Asset, drop warning
  {$ENDIF}
  Init(Man, @Parameters);
  Parameters.Key := '';
  Data.Done;
end;

constructor TDrawTex.Init(Man: PDrawTexManager; Eng: TEngine; ScreenBuffer: PScreenBuffer; MipMap: Boolean = True);
var
  Parameters: TDrawTexParameters;
begin
  Parameters.SetScreenBuffer(Eng, ScreenBuffer, MipMap);
  Init(Man, @Parameters);
end;

constructor TDrawTex.Init(Man: PDrawTexManager; Eng: TEngine; Texture: TTexture2dRgba; MipMap: Boolean = True);
var
  Parameters: TDrawTexParameters;
begin
  Parameters.SetTexture(Eng, Texture, MipMap);
  Init(Man, @Parameters);
end;

constructor TDrawTex.Init(const FileName: AnsiString);
begin
  Init(nil, @Eng, FileName);
end;

constructor TDrawTex.InitClone(Tex: PDrawTex);
begin
  // TODO
end;

constructor TDrawTex.Init(constref Tex: TDrawTex);
begin
  Holder := Tex.Holder;
  Holder.IncRef;
end;

procedure ChangePriority(var DrawTex: TDrawTex; Priority: LongInt);
begin
  Dec(DrawTex.Holder^.PriorityCount[DrawTex.FPriority]);
  DrawTex.FPriority := TDrawTex.PRIORITY_REQUIRE;
  Inc(DrawTex.Holder^.PriorityCount[DrawTex.FPriority]);
end;

destructor TDrawTex.Done;
begin
  inherited Done;
end;

function TDrawTex.Loaded: Boolean;
begin
  Exit(Holder^.Ready);
end;

procedure TDrawTex.Force;
begin
  if FPriority <> PRIORITY_REQUIRE then begin
    ChangePriority(Self, PRIORITY_REQUIRE);
  end;
  Holder^.Force;
end;

procedure TDrawTex.Require;
begin
  if FPriority <> PRIORITY_REQUIRE then begin
    ChangePriority(Self, PRIORITY_REQUIRE);
    Holder^.Require;
  end;
end;

procedure TDrawTex.LinkToHolder(_Holder: TDrawTex.PHolder);
begin
  Holder := _Holder;
end;

procedure TDrawTex.UnlinkFromHolder;
begin
  if Holder <> nil then begin
    Dec(Holder^.PriorityCount[PRIORITY_NORMAL]);
    if Holder^.GetPriority = -1 then begin
      // Dispose(Holder, Done);
    end;
  end;
  Holder := nil;
end;

procedure TDrawTex.Bind(Eng: TEngine);
begin
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, Holder.Id);
end;

procedure TDrawTex.Unbind(Eng: TEngine);
begin
  glDisable(GL_TEXTURE_2D);
end;

procedure TDrawTex.Draw(Eng: TEngine; const P: TVec2f; FlipX: Boolean = False; FlipY: Boolean = False);
begin
  Draw(Eng, P, Vec2f(Holder^.Size), FlipX, FlipY);
end;

procedure TDrawTex.Draw(Eng: TEngine; const P: TVec2f; const Color: TVec4f; FlipX: Boolean = False; FlipY: Boolean = False);
begin
  Draw(Eng, P, Vec2f(Holder^.Size), Color, FlipX, FlipY);
end;

procedure TDrawTex.Draw(Eng: TEngine; const P, S: TVec2f; FlipX: Boolean = False; FlipY: Boolean = False);
{$IFDEF ANDROID}
var
  Vertices2f: array[0..3] of TVec2f;
  TexCoords2f: array[0..3] of TVec2f;
  Indices: array[0..5] of TIndexBuffer.TIndex;
  VB: TVertexBuffer;
  IB: TIndexBuffer;
begin
  Bind(Eng);
  TexCoords2f[0] := Vec2f(Ord(    FlipX), Ord(    FlipY));
  TexCoords2f[1] := Vec2f(Ord(not FlipX), Ord(    FlipY));
  TexCoords2f[2] := Vec2f(Ord(not FlipX), Ord(not FlipY));
  TexCoords2f[3] := Vec2f(Ord(    FlipX), Ord(not FlipY));
  Vertices2f[0] := Vec2f(P.X - S.X / 2, P.Y - S.Y / 2);
  Vertices2f[1] := Vec2f(P.X + S.X / 2, P.Y - S.Y / 2);
  Vertices2f[2] := Vec2f(P.X + S.X / 2, P.Y + S.Y / 2);
  Vertices2f[3] := Vec2f(P.X - S.X / 2, P.Y + S.Y / 2);
  VB.Init(Vertices2f);
  VB.SetTexCoords(TexCoords2f);
  Indices[0] := 0; Indices[1] := 1; Indices[2] := 3;
  Indices[3] := 1; Indices[4] := 2; Indices[5] := 3;
  IB.Init(Indices);
  VB.DrawTriangles(IB);
  IB.Done;
  VB.Done;
  Unbind(Eng);
end;
{$ELSE}
begin
  Bind(Eng);
  glBegin(GL_QUADS);
    glTexCoord2f(Ord(    FlipX), Ord(    FlipY));
    glVertex2f(P.X - S.X / 2, P.Y - S.Y / 2);
    glTexCoord2f(Ord(not FlipX), Ord(    FlipY));
    glVertex2f(P.X + S.X / 2, P.Y - S.Y / 2);
    glTexCoord2f(Ord(not FlipX), Ord(not FlipY));
    glVertex2f(P.X + S.X / 2, P.Y + S.Y / 2);
    glTexCoord2f(Ord(    FlipX), Ord(not FlipY));
    glVertex2f(P.X - S.X / 2, P.Y + S.Y / 2);
  glEnd;
  Unbind(Eng)
end;
{$ENDIF}

procedure TDrawTex.Draw(Eng: TEngine; const P, S: TVec2f; const Color: TVec4f; FlipX: Boolean = False; FlipY: Boolean = False);
{$IFDEF ANDROID}
var
  Vertices2f: array[0..3] of TVec2f;
  TexCoords2f: array[0..3] of TVec2f;
  Indices: array[0..5] of TIndexBuffer.TIndex;
  VB: TVertexBuffer;
  IB: TIndexBuffer;
begin
  Bind(Eng);
  TexCoords2f[0] := Vec2f(Ord(    FlipX), Ord(    FlipY));
  TexCoords2f[1] := Vec2f(Ord(not FlipX), Ord(    FlipY));
  TexCoords2f[2] := Vec2f(Ord(not FlipX), Ord(not FlipY));
  TexCoords2f[3] := Vec2f(Ord(    FlipX), Ord(not FlipY));
  Vertices2f[0] := Vec2f(P.X - S.X / 2, P.Y - S.Y / 2);
  Vertices2f[1] := Vec2f(P.X + S.X / 2, P.Y - S.Y / 2);
  Vertices2f[2] := Vec2f(P.X + S.X / 2, P.Y + S.Y / 2);
  Vertices2f[3] := Vec2f(P.X - S.X / 2, P.Y + S.Y / 2);
  VB.Init(Vertices2f);
  TODO VB.SetColors(Colors);
  VB.SetTexCoords(TexCoords2f);
  Indices[0] := 0; Indices[1] := 1; Indices[2] := 3;
  Indices[3] := 1; Indices[4] := 2; Indices[5] := 3;
  IB.Init(Indices);
  VB.DrawTriangles(IB);
  IB.Done;
  VB.Done;
  Unbind(Eng);
end;
{$ELSE}
begin
  Bind(Eng);
  glBegin(GL_QUADS);
    glColor4fv(@Color);
    glTexCoord2f(Ord(    FlipX), Ord(    FlipY));
    glVertex2f(P.X - S.X / 2, P.Y - S.Y / 2);
    glTexCoord2f(Ord(not FlipX), Ord(    FlipY));
    glVertex2f(P.X + S.X / 2, P.Y - S.Y / 2);
    glTexCoord2f(Ord(not FlipX), Ord(not FlipY));
    glVertex2f(P.X + S.X / 2, P.Y + S.Y / 2);
    glTexCoord2f(Ord(    FlipX), Ord(not FlipY));
    glVertex2f(P.X - S.X / 2, P.Y + S.Y / 2);
  glEnd;
  Unbind(Eng)
end;
{$ENDIF}

procedure TDrawTex.Draw(Eng: TEngine; const P, S: TVec2f; const T, R: TVec2f);
{$IFDEF ANDROID}
var
  Vertices2f: array[0..3] of TVec2f;
  TexCoords2f: array[0..3] of TVec2f;
  Indices: array[0..5] of TIndexBuffer.TIndex;
  VB: TVertexBuffer;
  IB: TIndexBuffer;
begin
  Bind(Eng);
  TexCoords2f[0] := Vec2f(T.X, T.Y);
  TexCoords2f[1] := Vec2f(T.X + R.X, T.Y);
  TexCoords2f[2] := Vec2f(T.X + R.X, T.Y + R.Y);
  TexCoords2f[3] := Vec2f(T.X, T.Y + R.Y);
  Vertices2f[0] := Vec2f(P.X - S.X / 2, P.Y - S.Y / 2);
  Vertices2f[1] := Vec2f(P.X + S.X / 2, P.Y - S.Y / 2);
  Vertices2f[2] := Vec2f(P.X + S.X / 2, P.Y + S.Y / 2);
  Vertices2f[3] := Vec2f(P.X - S.X / 2, P.Y + S.Y / 2);
  VB.Init(Vertices2f);
  VB.SetTexCoords(TexCoords2f);
  Indices[0] := 0; Indices[1] := 1; Indices[2] := 3;
  Indices[3] := 1; Indices[4] := 2; Indices[5] := 3;
  IB.Init(Indices);
  VB.DrawTriangles(IB);
  IB.Done;
  VB.Done;
  Unbind(Eng);
end;
{$ELSE}
begin
  Bind(Eng);
  glBegin(GL_QUADS);
    glTexCoord2f(T.X, T.Y);
    glVertex2f(P.X - S.X / 2, P.Y - S.Y / 2);
    glTexCoord2f(T.X + R.X, T.Y);
    glVertex2f(P.X + S.X / 2, P.Y - S.Y / 2);
    glTexCoord2f(T.X + R.X, T.Y + R.Y);
    glVertex2f(P.X + S.X / 2, P.Y + S.Y / 2);
    glTexCoord2f(T.X, T.Y + R.Y);
    glVertex2f(P.X - S.X / 2, P.Y + S.Y / 2);
  glEnd;
  Unbind(Eng);
end;
{$ENDIF}

procedure TDrawTex.Draw(Eng: TEngine; const P, S: TVec2f; const T, R: TVec2i; FlipX: Boolean = False; FlipY: Boolean = False);
var
  X1, Y1, X2, Y2: Single;
{$IFDEF ANDROID}
var
  Vertices2f: array[0..3] of TVec2f;
  TexCoords2f: array[0..3] of TVec2f;
  Indices: array[0..5] of TIndexBuffer.TIndex;
  VB: TVertexBuffer;
  IB: TIndexBuffer;
begin
  if FlipX then begin
    Y1 := (T.Y + R.Y) / Height;
    Y2 := T.Y / Height;
  end else begin
    Y1 := T.Y / Height;
    Y2 := (T.Y + R.Y) / Height;
  end;
  if FlipY then begin
    X1 := (T.X + R.X) / Width;
    X2 := T.X / Width;
  end else begin
    X1 := T.X / Width;
    X2 := (T.X + R.X) / Width;
  end;
  Bind(Eng);
  TexCoords2f[0] := Vec2f(X1, Y1);
  TexCoords2f[1] := Vec2f(X2, Y1);
  TexCoords2f[2] := Vec2f(X2, Y2);
  TexCoords2f[3] := Vec2f(X1, Y2);
  Vertices2f[0] := Vec2f(P.X - S.X / 2, P.Y - S.Y / 2);
  Vertices2f[1] := Vec2f(P.X + S.X / 2, P.Y - S.Y / 2);
  Vertices2f[2] := Vec2f(P.X + S.X / 2, P.Y + S.Y / 2);
  Vertices2f[3] := Vec2f(P.X - S.X / 2, P.Y + S.Y / 2);
  VB.Init(Vertices2f);
  VB.SetTexCoords(TexCoords2f);
  Indices[0] := 0; Indices[1] := 1; Indices[2] := 3;
  Indices[3] := 1; Indices[4] := 2; Indices[5] := 3;
  IB.Init(Indices);
  VB.DrawTriangles(IB);
  IB.Done;
  VB.Done;
  Unbind(Eng);
end;
{$ELSE}
begin
  if FlipX then begin
    Y1 := (T.Y + R.Y) / Height;
    Y2 := T.Y / Height;
  end else begin
    Y1 := T.Y / Height;
    Y2 := (T.Y + R.Y) / Height;
  end;
  if FlipY then begin
    X1 := (T.X + R.X) / Width;
    X2 := T.X / Width;
  end else begin
    X1 := T.X / Width;
    X2 := (T.X + R.X) / Width;
  end;
  Bind(Eng);
  glBegin(GL_QUADS);
    glTexCoord2f(X1, Y1);
    glVertex2f(P.X - S.X / 2, P.Y - S.Y / 2);
    glTexCoord2f(X2, Y1);
    glVertex2f(P.X + S.X / 2, P.Y - S.Y / 2);
    glTexCoord2f(X2, Y2);
    glVertex2f(P.X + S.X / 2, P.Y + S.Y / 2);
    glTexCoord2f(X1, Y2);
    glVertex2f(P.X - S.X / 2, P.Y + S.Y / 2);
  glEnd;
  Unbind(Eng);
end;
{$ENDIF}

procedure TDrawTex.Draw(Eng: TEngine; const P, S: TVec2f; const T: TTexCoords4; FlipX: Boolean = False; FlipY: Boolean = False);
var
  Indices: array[0..5] of TIndexBuffer.TIndex;
  Vertices2f: array[0..3] of TVec2f;
  VB: TVertexBuffer;
  IB: TIndexBuffer;
begin
  Bind(Eng);
  Vertices2f[0] := Vec2f(P.X + (Ord(    FlipX) - 0.5) * S.X,
                         P.Y + (Ord(    FlipY) - 0.5) * S.Y);
  Vertices2f[1] := Vec2f(P.X + (Ord(not FlipX) - 0.5) * S.X,
                         P.Y + (Ord(    FlipY) - 0.5) * S.Y);
  Vertices2f[2] := Vec2f(P.X + (Ord(not FlipX) - 0.5) * S.X,
                         P.Y + (Ord(not FlipY) - 0.5) * S.Y);
  Vertices2f[3] := Vec2f(P.X + (Ord(    FlipX) - 0.5) * S.X,
                         P.Y + (Ord(not FlipY) - 0.5) * S.Y);
  VB.Init(Vertices2f);
  VB.SetTexCoords(T);
  Indices[0] := 0; Indices[1] := 1; Indices[2] := 3;
  Indices[3] := 1; Indices[4] := 2; Indices[5] := 3;
  IB.Init(Indices);
  VB.DrawTriangles(IB);
  IB.Done;
  VB.Done;
  Unbind(Eng);
end;

procedure TDrawTex.DrawLT(Eng: TEngine; const P, S: TVec2f; const T, R: TVec2i);
var
  X1, Y1, X2, Y2: Single;
{$IFDEF ANDROID}
var
  Vertices2f: array[0..3] of TVec2f;
  TexCoords2f: array[0..3] of TVec2f;
  Indices: array[0..5] of TIndexBuffer.TIndex;
  VB: TVertexBuffer;
  IB: TIndexBuffer;
begin
  X1 := T.X / Width;
  Y1 := T.Y / Height;
  X2 := (T.X + R.X) / Width;
  Y2 := (T.Y + R.Y) / Height;
  Bind(Eng);
  TexCoords2f[0] := Vec2f(X1, Y1);
  TexCoords2f[1] := Vec2f(X2, Y1);
  TexCoords2f[2] := Vec2f(X2, Y2);
  TexCoords2f[3] := Vec2f(X1, Y2);
  Vertices2f[0] := Vec2f(P.X      , P.Y      );
  Vertices2f[1] := Vec2f(P.X + S.X, P.Y      );
  Vertices2f[2] := Vec2f(P.X + S.X, P.Y + S.Y);
  Vertices2f[3] := Vec2f(P.X      , P.Y + S.Y);
  VB.Init(Vertices2f);
  VB.SetTexCoords(TexCoords2f);
  Indices[0] := 0; Indices[1] := 1; Indices[2] := 3;
  Indices[3] := 1; Indices[4] := 2; Indices[5] := 3;
  IB.Init(Indices);
  VB.DrawTriangles(IB);
  IB.Done;
  VB.Done;
  Unbind(Eng);
end;
{$ELSE}
begin
  X1 := T.X / Width;
  Y1 := T.Y / Height;
  X2 := (T.X + R.X) / Width;
  Y2 := (T.Y + R.Y) / Height;
  Bind(Eng);
  glBegin(GL_QUADS);
    glTexCoord2f(X1, Y1);
    glVertex2f(P.X      , P.Y      );
    glTexCoord2f(X2, Y1);
    glVertex2f(P.X + S.X, P.Y      );
    glTexCoord2f(X2, Y2);
    glVertex2f(P.X + S.X, P.Y + S.Y);
    glTexCoord2f(X1, Y2);
    glVertex2f(P.X      , P.Y + S.Y);
  glEnd;
  Unbind(Eng);
end;
{$ENDIF}

procedure TDrawTex.Draw(Eng: TEngine; const Pos, Right, Up: TVec3f; const Pivot: TVec2f);
var
  Vertices3f: array[0..3] of TVec3f;
  TexCoords2f: array[0..3] of TVec2f;
  Indices: array[0..5] of TIndexBuffer.TIndex;
  VB: TVertexBuffer;
  IB: TIndexBuffer;
  X, Y: Single;
begin
  Bind(Eng);
  TexCoords2f[0] := Vec2f(0.001, 0.001);
  TexCoords2f[1] := Vec2f(0.999, 0.001);
  TexCoords2f[2] := Vec2f(0.999, 0.999);
  TexCoords2f[3] := Vec2f(0.001, 0.999);
  X := Pivot.X / Width;
  Y := Pivot.Y / Height;
  Vertices3f[0] := Add3f(Sub3f(Pos, Mul3f(Right, X)), Mul3f(Up, Y));
  Vertices3f[1] := Add3f(Add3f(Pos, Mul3f(Right, 1 - X)), Mul3f(Up, Y));
  Vertices3f[2] := Sub3f(Add3f(Pos, Mul3f(Right, 1 - X)), Mul3f(Up, 1 - Y));
  Vertices3f[3] := Sub3f(Sub3f(Pos, Mul3f(Right, X)), Mul3f(Up, 1 - Y));
  VB.Init(Vertices3f);
  VB.SetTexCoords(TexCoords2f);
  Indices[0] := 0; Indices[1] := 1; Indices[2] := 3;
  Indices[3] := 1; Indices[4] := 2; Indices[5] := 3;
  IB.Init(Indices);
  VB.DrawTriangles(IB);
  IB.Done;
  VB.Done;
  Unbind(Eng);
end;

procedure TDrawTex.DrawLT(Eng: TEngine; const P, S: TVec2f; const T, R: TVec2f);
{$IFDEF ANDROID}
var
  Vertices2f: array[0..3] of TVec2f;
  TexCoords2f: array[0..3] of TVec2f;
  Indices: array[0..5] of TIndexBuffer.TIndex;
  VB: TVertexBuffer;
  IB: TIndexBuffer;
begin
  Bind(Eng);
  TexCoords2f[0] := Vec2f(T.X, T.Y);
  TexCoords2f[1] := Vec2f(T.X + R.X, T.Y);
  TexCoords2f[2] := Vec2f(T.X + R.X, T.Y + R.Y);
  TexCoords2f[3] := Vec2f(T.X, T.Y + R.Y);
  Vertices2f[0] := Vec2f(P.X,       P.Y);
  Vertices2f[1] := Vec2f(P.X + S.X, P.Y);
  Vertices2f[2] := Vec2f(P.X + S.X, P.Y + S.Y);
  Vertices2f[3] := Vec2f(P.X,       P.Y + S.Y);
  VB.Init(Vertices2f);
  VB.SetTexCoords(TexCoords2f);
  Indices[0] := 0; Indices[1] := 1; Indices[2] := 3;
  Indices[3] := 1; Indices[4] := 2; Indices[5] := 3;
  IB.Init(Indices);
  VB.DrawTriangles(IB);
  IB.Done;
  VB.Done;
  Unbind(Eng);
end;
{$ELSE}
begin
  Bind(Eng);
  glBegin(GL_QUADS);
    glTexCoord2f(T.X, T.Y);
    glVertex2f(P.X, P.Y);
    glTexCoord2f(T.X + R.X, T.Y);
    glVertex2f(P.X + S.X, P.Y);
    glTexCoord2f(T.X + R.X, T.Y + R.Y);
    glVertex2f(P.X + S.X, P.Y + S.Y);
    glTexCoord2f(T.X, T.Y + R.Y);
    glVertex2f(P.X, P.Y + S.Y);
  glEnd;
  Unbind(Eng);
end;
{$ENDIF}

function TDrawTex.Size: TVec2i;
begin
  Result := Holder^.Size;
end;

function TDrawTex.Width: LongInt;
begin
  Result := Holder^.Size.X;
end;

function TDrawTex.Height: LongInt;
begin
  Result := Holder^.Size.Y;
end;

procedure TDrawTexSubsystem.OnInit;
begin
  FTaskQue.Init;
end;

procedure TDrawTexSubsystem.OnDone;
begin
  FTaskQue.Done;
end;

procedure TDrawTexSubsystem.Update;
var
  It: dresman.TManager.TIterator;
  Holder: PDrawTexHolder;
  Time: LongInt;
  SomethingDone: Boolean;
begin
  if FTaskQue.State <> TTaskQue.RUNNING then begin
    if FTaskQue.State = TTaskQue.DEAD then begin
      LogTagged('R', 'TDrawTexSubsystem: failed tasks que at ' + IntToStr(FTaskQue.Progress) + '/' + IntToStr(FTaskQue.Count));
    end;
    Time := Eng.Time;
    It.Init(@DrawTexMan.FHashMap);
    SomethingDone := False;
    while It.Next do begin
      // Limit time that we can spend on loading
      if SomethingDone and (Eng.Time - Time > 1) then
        break;
      Holder := PDrawTexHolder(It.Value^);
      if not Holder^.Ready then begin
        if not Holder^.Status.Encoded then begin
          FTaskQue.AddTask(Holder^.GetTaskEncode);
        end else begin
          Holder^.LoadToGPU;
          LogTagged('R', 'LOADED TO GPU ' + It.Key.ToString);
        end;
      end;
      SomethingDone := True;
    end;
    It.Done;
    FTaskQue.AddTasks(FTasks);
    SetLength(FTasks, 0);
    if not FTaskQue.Run then begin
      LogTagged('R', 'TDrawTexSubsystem: failed to run task que in async mode, fallback to sync');
      FTaskQue.Run(True);
    end;
  end;
end;

procedure TDrawTexSubsystem.AddTask(Task: ITask);
begin
  SetLength(FTasks, Length(FTasks) + 1);
  FTasks[High(FTasks)] := Task;
end;

initialization
  DrawTexMan.Init;
  DrawTexSubsystem.Init('TDrawTexSubsystem', @Eng);
  Eng.RegistSubsystem(@DrawTexSubsystem);
finalization
  DrawTexSubsystem.Done;
  DrawTexMan.Done;
end.
