DEADSOFTWARE

gl: implement texture filtering
[d2df-sdl.git] / src / game / renders / opengl / r_textures.pas
index ce71ebc727d7e96b0001e0a44a512ba1b9269846..ae9d02afa6596d878453ffb7d1dc87e6f8e43485 100644 (file)
@@ -18,17 +18,16 @@ unit r_textures;
 interface
 
   uses
-    {$IFDEF USE_GLES1}
-      GLES11,
-    {$ELSE}
-      GL, GLEXT,
-    {$ENDIF}
-    g_base,  // TRectHW
+    {$I ../../../nogl/noGLuses.inc}
+    g_base, g_animations,  // TRectHW, TAnimInfo
     utils,
-    r_atlas
+    r_atlas, r_fonts
   ;
 
   type
+    TGLHints = (txNoRepeat);
+    TGLHintsSet = set of TGLHints;
+
     TGLAtlas = class;
 
     TGLAtlasNode = class (TAtlasNode)
@@ -48,6 +47,7 @@ interface
     TGLAtlas = class (TAtlas)
       private
         mID: GLuint;
+        mFilter: Boolean;
 
       public
         constructor Create (ww, hh: Integer; id: GLuint);
@@ -57,6 +57,7 @@ interface
         function Alloc (ww, hh: Integer): TGLAtlasNode; overload;
 
         property id: GLuint read mID write mID default 0;
+        property filter: Boolean read mFilter write mFilter;
     end;
 
     TGLTexture = class
@@ -65,6 +66,8 @@ interface
         mHeight: Integer;
         mCols: Integer;
         mTile: array of TGLAtlasNode;
+        mHints: TGLHintsSet;
+        mFilter: Boolean;
 
       public
         destructor Destroy; override;
@@ -77,12 +80,13 @@ interface
         property height: Integer read mHeight;
         property cols: Integer read mCols;
         property lines: Integer read GetLines;
+        property hints: TGLHintsSet read mHints;
+        property filter: Boolean read mFilter write mFilter;
     end;
 
     TGLMultiTexture = class
       private
         mTexture: array of TGLTexture;
-        mBackanim: Boolean;
 
       public
         destructor Destroy; override;
@@ -95,32 +99,73 @@ interface
         property width: Integer read GetWidth;
         property height: Integer read GetHeight;
         property count: Integer read GetCount;
-        property backAnim: Boolean read mBackanim; (* this property must be located at TAnimState? *)
     end;
 
     TGLTextureArray = array of TGLTexture;
 
     TRectArray = array of TRectWH;
 
+    TGLFont = class sealed (TFont)
+      private
+        info: TFontInfo;
+        ch: TGLTextureArray;
+
+      public
+        destructor Destroy; override;
+        function GetChar (c: AnsiChar): TGLTexture;
+        function GetWidth (c: AnsiChar): Integer;
+        function GetMaxWidth (): Integer;
+        function GetMaxHeight (): Integer;
+        function GetSpace (): Integer;
+    end;
+
+    TAnimTextInfo = record
+      name: AnsiString;
+      w, h: Integer;
+      anim: TAnimInfo;
+    end;
+
+    TConvProc = function (x: Integer): Integer;
+
   procedure r_Textures_Initialize;
   procedure r_Textures_Finalize;
 
-  function r_Textures_LoadFromFile (const filename: AnsiString; log: Boolean = True): TGLTexture;
-  function r_Textures_LoadMultiFromFile (const filename: AnsiString; log: Boolean = True): TGLMultiTexture;
-  function r_Textures_LoadMultiFromFileAndInfo (const filename: AnsiString; w, h, count: Integer; backanim: Boolean; log: Boolean = True): TGLMultiTexture;
-  function r_Textures_LoadStreamFromFile (const filename: AnsiString; w, h, count: Integer; st: TGLTextureArray; rs: TRectArray; log: Boolean = True): Boolean;
+  function r_Textures_LoadFromFile (const filename: AnsiString; hints: TGLHintsSet; log: Boolean = True): TGLTexture;
+  function r_Textures_LoadMultiFromFile (const filename: AnsiString; hints: TGLHintsSet; log: Boolean = True): TGLMultiTexture;
+  function r_Textures_LoadMultiFromFileAndInfo (const filename: AnsiString; w, h, count: Integer; hints: TGLHintsSet; log: Boolean = True): TGLMultiTexture;
+  function r_Textures_LoadMultiTextFromFile (const filename: AnsiString; var txt: TAnimTextInfo; hints: TGLHintsSet; log: Boolean = True): TGLMultiTexture;
+
+  function r_Textures_LoadStreamFromFile (const filename: AnsiString; w, h, count, cw: Integer; st: TGLTextureArray; rs: TRectArray; hints: TGLHintsSet; log: Boolean = True): Boolean;
+
+  function r_Textures_LoadFontFromFile (const filename: AnsiString; constref f: TFontInfo; font2enc: TConvProc; log: Boolean = true): TGLFont;
+
+  procedure r_Textures_GL_Bind (id: GLuint);
 
 implementation
 
   uses
     SysUtils, Classes,
+    r_common,
     e_log, e_res, WADReader, Config,
+    g_console, // cvar declaration
     Imaging, ImagingTypes, ImagingUtility
   ;
 
   var
+    r_GL_MaxTexSize: WORD;
+    r_GL_RepeatOpt: Boolean;
     maxTileSize: Integer;
-    atl: array of TGLAtlas;
+    atl, ratl: array of TGLAtlas;
+    currentTexture2D: GLuint;
+
+  procedure r_Textures_GL_Bind (id: GLuint);
+  begin
+    if id <> currentTexture2D then
+    begin
+      glBindTexture(GL_TEXTURE_2D, id);
+      currentTexture2D := id;
+    end
+  end;
 
   (* --------- TGLAtlasNode --------- *)
 
@@ -152,9 +197,9 @@ implementation
     ASSERT(n.l + x + w - 1 <= n.r);
     ASSERT(n.t + y + h - 1 <= n.b);
     ASSERT(n.id > 0);
-    glBindTexture(GL_TEXTURE_2D, n.id);
+    r_Textures_GL_Bind(n.id);
     glTexSubImage2D(GL_TEXTURE_2D, 0, n.l + x, n.t + y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data);
-    glBindTexture(GL_TEXTURE_2D, 0);
+    r_Textures_GL_Bind(0);
   end;
 
   (* --------- TGLAtlas --------- *)
@@ -165,6 +210,7 @@ implementation
     ASSERT(hh > 0);
     inherited Create(ww, hh);
     self.mID := id;
+    self.mFilter := false;
   end;
 
   destructor TGLAtlas.Destroy;
@@ -188,11 +234,13 @@ implementation
     glGenTextures(1, @id);
     if id <> 0 then
     begin
-      glBindTexture(GL_TEXTURE_2D, id);
+      r_Textures_GL_Bind(id);
+      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
-      glBindTexture(GL_TEXTURE_2D, 0);
+      r_Textures_GL_Bind(0);
     end;
     result := id
   end;
@@ -211,6 +259,20 @@ implementation
     end;
   end;
 
+  function r_Textures_AllocRepeatAtlas (w, h: Integer): TGLAtlas;
+    var i: Integer; id: GLuint;
+  begin
+    result := nil;
+    id := r_Textures_AllocHWTexture(w, h);
+    if id <> 0 then
+    begin
+      i := Length(ratl);
+      SetLength(ratl, i + 1);
+      ratl[i] := TGLAtlas.Create(w, h, id);
+      result := ratl[i];
+    end;
+  end;
+
   function r_Textures_AllocNode (w, h: Integer): TGLAtlasNode;
     var i: Integer; n: TGLAtlasNode; a: TGLAtlas;
   begin
@@ -233,22 +295,56 @@ implementation
     result := n
   end;
 
+  function r_Textures_AllocRepeatNode (w, h: Integer): TGLAtlasNode;
+    var i: Integer; n: TGLAtlasNode; a: TGLAtlas;
+  begin
+    n := nil; a := nil;
+    if ratl <> nil then
+    begin
+      i := High(ratl);
+      while (i >= 0) and (ratl[i] <> nil) do DEC(i);
+      if i >= 0 then a := ratl[i];
+    end;
+    if a = nil then a := r_Textures_AllocRepeatAtlas(w, h);
+    if a <> nil then
+    begin
+      n := a.Alloc(w, h);
+      if n = nil then
+      begin
+        i := High(ratl); while (i >= 0) and (ratl[i] <> a) do DEC(i);
+        if i >= 0 then ratl[i] := nil;
+        r_Common_FreeAndNil(a);
+      end;
+    end;
+    result := n
+  end;
+
   (* --------- TGLTexture --------- *)
 
   destructor TGLTexture.Destroy;
-    var i: Integer;
+    var i: Integer; a: TGLAtlas;
   begin
     if self.mTile <> nil then
     begin
-      for i := 0 to High(self.mTile) do
+      if TGLHints.txNoRepeat in self.hints then (* non repeatable texture -> delete tiles only *)
       begin
-        if self.mTile[i] <> nil then
+        for i := 0 to High(self.mTile) do
         begin
-          self.mTile[i].Dealloc;
-          self.mTile[i] := nil;
-        end;
+          if self.mTile[i] <> nil then
+          begin
+            self.mTile[i].Dealloc;
+            self.mTile[i] := nil
+          end
+        end
+      end
+      else (* repeatable texture -> delete whole atlas *)
+      begin
+        a := self.mTile[0].base;
+        i := High(ratl); while (i >= 0) and (ratl[i] <> a) do DEC(i);
+        if i >= 0 then ratl[i] := nil;
+        r_Common_FreeAndNil(a);
       end;
-      self.mTile := nil;
+      SetLength(self.mTile, 0);
     end;
     inherited;
   end;
@@ -272,29 +368,45 @@ implementation
     ASSERT(result <> nil)
   end;
 
-  function r_Textures_Alloc (w, h: Integer): TGLTexture;
+  function r_Textures_Alloc (w, h: Integer; hints: TGLHintsSet): TGLTexture;
     var x, y, mw, mh, cols, lines: Integer; t: TGLTexture;
   begin
     ASSERT(w > 0);
     ASSERT(h > 0);
-    cols := (w + maxTileSize - 1) div maxTileSize;
-    lines := (h + maxTileSize - 1) div maxTileSize;
-    t := TGLTexture.Create;
-    t.mWidth := w;
-    t.mHeight := h;
-    t.mCols := cols;
-    // t.mLines := lines;
-    SetLength(t.mTile, cols * lines);
-    for y := 0 to lines - 1 do
-    begin
-      mh := Min(maxTileSize, h - y * maxTileSize);
-      ASSERT(mh > 0);
-      for x := 0 to cols - 1 do
+    if TGLHints.txNoRepeat in hints then
+    begin
+      cols := (w + maxTileSize - 1) div maxTileSize;
+      lines := (h + maxTileSize - 1) div maxTileSize;
+      t := TGLTexture.Create;
+      t.mWidth := w;
+      t.mHeight := h;
+      t.mCols := cols;
+      // t.mLines := lines;
+      t.mHints := hints;
+      t.mFilter := false;
+      SetLength(t.mTile, cols * lines);
+      for y := 0 to lines - 1 do
       begin
-        mw := Min(maxTileSize, w - x * maxTileSize);
-        ASSERT(mw > 0);
-        t.mTile[y * cols + x] := r_Textures_AllocNode(mw, mh);
-      end
+        mh := Min(maxTileSize, h - y * maxTileSize);
+        ASSERT(mh > 0);
+        for x := 0 to cols - 1 do
+        begin
+          mw := Min(maxTileSize, w - x * maxTileSize);
+          ASSERT(mw > 0);
+          t.mTile[y * cols + x] := r_Textures_AllocNode(mw, mh);
+        end
+      end;
+    end
+    else
+    begin
+      t := TGLTexture.Create;
+      t.mWidth := w;
+      t.mHeight := h;
+      t.mCols := 1;
+      // t.mLines := 1
+      t.mHints := hints;
+      SetLength(t.mTile, 1);
+      t.mTile[0] := r_Textures_AllocRepeatNode(w, h);
     end;
     result := t;
   end;
@@ -305,8 +417,8 @@ implementation
     var i: Integer;
   begin
     for i := 0 to self.count - 1 do
-      self.mTexture[i].Free;
-    self.mTexture := nil;
+      r_Common_FreeAndNil(self.mTexture[i]);
+    SetLength(self.mTexture, 0);
     inherited;
   end;
 
@@ -335,19 +447,49 @@ implementation
 
   (* --------- Init / Fin --------- *)
 
+  function IsPOT (v: LongWord): Boolean;
+  begin
+    result := (v <> 0) and ((v and (v - 1)) = 0)
+  end;
+
+  function NextPOT (v: LongWord): LongWord;
+  begin
+    DEC(v);
+    v := v or (v >> 1);
+    v := v or (v >> 2);
+    v := v or (v >> 4);
+    v := v or (v >> 8);
+    v := v or (v >> 16);
+    INC(v);
+    result := v;
+  end;
+
   function r_Textures_GetMaxHardwareSize (): Integer;
     var size: GLint = 0;
   begin
-    glGetIntegerv(GL_MAX_TEXTURE_SIZE, @size);
-    if size < 64 then size := 64;
-    //if size > 512 then size := 512;
-    //size := 64; // !!!
+    if r_GL_MaxTexSize <= 0 then
+    begin
+      // auto, max possible reccomended by driver
+      glGetIntegerv(GL_MAX_TEXTURE_SIZE, @size);
+      size := size div 2; (* hack: on some devices max size may produce invalid texture *)
+      if size < 64 then size := 64; (* at least 64x64 are guarantied by specification *)
+    end
+    else
+    begin
+      // selected by user
+      if IsPOT(r_GL_MaxTexSize) then
+        size := r_GL_MaxTexSize
+      else
+        size := NextPOT(r_GL_MaxTexSize);
+    end;
     result := size;
   end;
 
   procedure r_Textures_Initialize;
   begin
+    currentTexture2D := 0;
     maxTileSize := r_Textures_GetMaxHardwareSize();
+    e_LogWritefln('Texture Tile Size: %s', [maxTileSize]);
   end;
 
   procedure r_Textures_Finalize;
@@ -357,12 +499,29 @@ implementation
     begin
       for i := 0 to High(atl) do
       begin
-        glDeleteTextures(1, @atl[i].id);
-        atl[i].id := 0;
-        atl[i].Free;
+        if atl[i] <> nil then
+        begin
+          glDeleteTextures(1, @atl[i].id);
+          atl[i].id := 0;
+          r_Common_FreeAndNil(atl[i]);
+        end;
       end;
-      atl := nil;
     end;
+    SetLength(atl, 0);
+
+    if ratl <> nil then
+    begin
+      for i := 0 to High(ratl) do
+      begin
+        if ratl[i] <> nil then
+        begin
+          glDeleteTextures(1, @ratl[i].id);
+          ratl[i].id := 0;
+          r_Common_FreeAndNil(ratl[i]);
+        end;
+      end;
+    end;
+    SetLength(ratl, 0);
   end;
 
   function r_Textures_FixImageData (var img: TImageData): Boolean;
@@ -373,14 +532,34 @@ implementation
         result := true;
   end;
 
-  function r_Textures_LoadFromImage (var img: TImageData): TGLTexture;
+  function r_Textures_ValidRepeatTexture (w, h: Integer; hints: TGLHintsSet): Boolean;
+  begin
+    result := r_GL_RepeatOpt and
+              not (TGLHints.txNoRepeat in hints) and
+              (w <= maxTileSize) and
+              (h <= maxTileSize) and
+              IsPOT(w) and
+              IsPOT(h)
+  end;
+
+  function r_Textures_LoadFromImage (var img: TImageData; hints: TGLHintsSet): TGLTexture; // !!!
     var t: TGLTexture; n: TGLAtlasNode; c: TDynImageDataArray; cw, ch, i, j: LongInt;
   begin
-    // e_logwritefln('r_Textures_CreateFromImage: w=%s h=%s', [img.width, img.height]);
     result := nil;
-    if SplitImage(img, c, maxTileSize, maxTileSize, cw, ch, False) then
+    if r_Textures_ValidRepeatTexture(img.width, img.height, hints) then
+    begin
+      t := r_Textures_Alloc(img.width, img.height, hints - [TGLHints.txNoRepeat]);
+      if t <> nil then
+      begin
+        n := t.GetTile(0, 0);
+        ASSERT(n <> nil);
+        r_Textures_UpdateNode(n, img.bits, 0, 0, n.width, n.height);
+        result := t
+      end
+    end
+    else if SplitImage(img, c, maxTileSize, maxTileSize, cw, ch, False) then
     begin
-      t := r_Textures_Alloc(img.width, img.height);
+      t := r_Textures_Alloc(img.width, img.height, hints + [TGLHints.txNoRepeat]);
       if t <> nil then
       begin
         ASSERT(cw = t.cols);
@@ -400,7 +579,7 @@ implementation
     end;
   end;
 
-  function r_Textures_LoadFromMemory (data: Pointer; size: LongInt): TGLTexture;
+  function r_Textures_LoadFromMemory (data: Pointer; size: LongInt; hints: TGLHintsSet): TGLTexture;
     var img: TImageData;
   begin
     result := nil;
@@ -410,14 +589,14 @@ implementation
       try
         if LoadImageFromMemory(data, size, img) then
           if r_Textures_FixImageData(img) then
-            result := r_Textures_LoadFromImage(img)
+            result := r_Textures_LoadFromImage(img, hints)
       except
       end;
       FreeImage(img);
     end;
   end;
 
-  function r_Textures_LoadFromFile (const filename: AnsiString; log: Boolean = True): TGLTexture;
+  function r_Textures_LoadFromFile (const filename: AnsiString; hints: TGLHintsSet; log: Boolean = True): TGLTexture;
     var wad: TWADFile; wadName, resName: AnsiString; data: Pointer; size: Integer;
   begin
     result := nil;
@@ -428,14 +607,14 @@ implementation
       resName := g_ExtractFilePathName(filename);
       if wad.GetResource(resName, data, size, log) then
       begin
-        result := r_Textures_LoadFromMemory(data, size);
+        result := r_Textures_LoadFromMemory(data, size, hints);
         FreeMem(data);
       end;
       wad.Free
     end
   end;
 
-  function r_Textures_LoadMultiFromImageAndInfo (var img: TImageData; w, h, c: Integer; b: Boolean): TGLMultiTexture;
+  function r_Textures_LoadMultiFromImageAndInfo (var img: TImageData; w, h, c: Integer; hints: TGLHintsSet): TGLMultiTexture;
     var t: TImageData; a: array of TGLTexture; i: Integer; m: TGLMultiTexture;
   begin
     ASSERT(w >= 0);
@@ -448,18 +627,17 @@ implementation
       InitImage(t);
       if NewImage(w, h, img.Format, t) then
         if CopyRect(img, w * i, 0, w, h, t, 0, 0) then
-          a[i] := r_Textures_LoadFromImage(t);
+          a[i] := r_Textures_LoadFromImage(t, hints);
       ASSERT(a[i] <> nil);
       FreeImage(t);
     end;
     m := TGLMultiTexture.Create();
     m.mTexture := a;
-    m.mBackanim := b;
     ASSERT(m.mTexture <> nil);
     result := m;
   end;
 
-  function r_Textures_LoadMultiFromDataAndInfo (data: Pointer; size: LongInt; w, h, c: Integer; b: Boolean): TGLMultiTexture;
+  function r_Textures_LoadMultiFromDataAndInfo (data: Pointer; size: LongInt; w, h, c: Integer; hints: TGLHintsSet): TGLMultiTexture;
     var img: TImageData;
   begin
     ASSERT(w > 0);
@@ -472,62 +650,90 @@ implementation
       try
         if LoadImageFromMemory(data, size, img) then
           if r_Textures_FixImageData(img) then
-            result := r_Textures_LoadMultiFromImageAndInfo(img, w, h, c, b)
+            result := r_Textures_LoadMultiFromImageAndInfo(img, w, h, c, hints)
       except
       end;
       FreeImage(img);
     end;
   end;
 
-  function r_Textures_LoadMultiFromWad (wad: TWADFile): TGLMultiTexture;
-    var data: Pointer; size: LongInt; TexRes: AnsiString; w, h, c: Integer; b: Boolean; cfg: TConfig; img: TImageData;
+  function r_Textures_LoadTextFromMemory (data: Pointer; size: LongInt; var text: TAnimTextInfo): Boolean;
+    var cfg: TConfig;
+  begin
+    result := false;
+    if data <> nil then
+    begin
+      cfg := TConfig.CreateMem(data, size);
+      if cfg <> nil then
+      begin
+        text.name := cfg.ReadStr('', 'resource', '');
+        text.w := cfg.ReadInt('', 'framewidth', 0);
+        text.h := cfg.ReadInt('', 'frameheight', 0);
+        text.anim.loop := true;
+        text.anim.delay := cfg.ReadInt('', 'waitcount', 0);
+        text.anim.frames := cfg.ReadInt('', 'framecount', 0);
+        text.anim.back := cfg.ReadBool('', 'backanim', false);
+        if text.w <= 0 then e_LogWritefln('Warning: bad animation width %s for %s', [text.w, text.name]);
+        if text.h <= 0 then e_LogWritefln('Warning: bad animation height %s for %s', [text.h, text.name]);
+        if text.anim.delay <= 0 then e_LogWritefln('Warning: bad animation delay %s for %s', [text.anim.delay, text.name]);
+        if text.anim.frames <= 0 then e_LogWritefln('Warning: bad animation frame count %s for %s', [text.anim.frames, text.name]);
+        text.w := MAX(0, text.w);
+        text.h := MAX(0, text.h);
+        text.anim.delay := MAX(1, text.anim.delay);
+        text.anim.frames := MAX(1, text.anim.frames);
+        cfg.Free;
+        result := (text.name <> '') and (text.w > 0) and (text.h > 0) and (text.anim.delay > 0) and (text.anim.frames > 0);
+      end;
+    end;
+  end;
+
+  function r_Textures_LoadMultiFromWad (wad: TWADFile; var txt: TAnimTextInfo; hints: TGLHintsSet): TGLMultiTexture;
+    var data: Pointer; size: LongInt; img: TImageData;
   begin
     ASSERT(wad <> nil);
     result := nil;
     if wad.GetResource('TEXT/ANIM', data, size) then
     begin
-      cfg := TConfig.CreateMem(data, size);
-      FreeMem(data);
-      if cfg <> nil then
+      if r_Textures_LoadTextFromMemory(data, size, txt) then
       begin
-        TexRes := cfg.ReadStr('', 'resource', '');
-        w := cfg.ReadInt('', 'framewidth', 0);
-        h := cfg.ReadInt('', 'frameheight', 0);
-        c := cfg.ReadInt('', 'framecount', 0);
-        b := cfg.ReadBool('', 'backanim', false);
-        if (TexRes <> '') and (w > 0) and (h > 0) and (c > 0) then
+        FreeMem(data);
+        if wad.GetResource('TEXTURES/' + txt.name, data, size) then
         begin
-          if wad.GetResource('TEXTURES/' + TexRes, data, size) then
-          begin
-            InitImage(img);
-            try
-              if LoadImageFromMemory(data, size, img) then
-                if r_Textures_FixImageData(img) then
-                  result := r_Textures_LoadMultiFromImageAndInfo(img, w, h, c, b)
-            finally
-              FreeMem(data);
-            end;
-            FreeImage(img);
-          end
+          InitImage(img);
+          try
+            if LoadImageFromMemory(data, size, img) then
+              if r_Textures_FixImageData(img) then
+                result := r_Textures_LoadMultiFromImageAndInfo(img, txt.w, txt.h, txt.anim.frames, hints);
+          finally
+            FreeMem(data);
+          end;
+          FreeImage(img);
         end;
-        cfg.Free;
       end
+      else
+        FreeMem(data);
     end;
   end;
 
-  function r_Textures_LoadMultiFromMemory (data: Pointer; size: LongInt): TGLMultiTexture;
+  function r_Textures_LoadMultiFromMemory (data: Pointer; size: LongInt; var txt: TAnimTextInfo; hints: TGLHintsSet): TGLMultiTexture;
     var wad: TWADFile; t: TGLTexture; m: TGLMultiTexture;
   begin
     result := nil;
     if (data <> nil) and (size > 0) then
     begin
-      t := r_Textures_LoadFromMemory(data, size);
+      t := r_Textures_LoadFromMemory(data, size, hints);
       if t <> nil then
       begin
         m := TGLMultiTexture.Create();
         SetLength(m.mTexture, 1);
         m.mTexture[0] := t;
-        m.mBackanim := false;
+        txt.name := '';
+        txt.w := m.width;
+        txt.h := m.height;
+        txt.anim.loop := true;
+        txt.anim.delay := 1;
+        txt.anim.frames := 1;
+        txt.anim.back := false;
         result := m;
       end
       else if IsWadData(data, size) then
@@ -535,15 +741,15 @@ implementation
         wad := TWADFile.Create();
         if wad.ReadMemory(data, size) then
         begin
-          result := r_Textures_LoadMultiFromWad(wad);
+          result := r_Textures_LoadMultiFromWad(wad, txt, hints);
           wad.Free;
         end
       end
     end
   end;
 
-  function r_Textures_LoadMultiFromFile (const filename: AnsiString; log: Boolean = True): TGLMultiTexture;
-    var wad: TWADFile; wadName, resName: AnsiString; data: Pointer; size: Integer; t: TGLTexture;
+  function r_Textures_LoadMultiTextFromFile (const filename: AnsiString; var txt: TAnimTextInfo; hints: TGLHintsSet; log: Boolean = True): TGLMultiTexture;
+    var wad: TWADFile; wadName, resName: AnsiString; data: Pointer; size: Integer;
   begin
     result := nil;
     wadName := g_ExtractWadName(filename);
@@ -553,14 +759,20 @@ implementation
       resName := g_ExtractFilePathName(filename);
       if wad.GetResource(resName, data, size, log) then
       begin
-        result := r_Textures_LoadMultiFromMemory(data, size);
+        result := r_Textures_LoadMultiFromMemory(data, size, txt, hints);
         FreeMem(data);
       end;
       wad.Free
     end
   end;
 
-  function r_Textures_LoadMultiFromFileAndInfo (const filename: AnsiString; w, h, count: Integer; backanim: Boolean; log: Boolean = True): TGLMultiTexture;
+  function r_Textures_LoadMultiFromFile (const filename: AnsiString; hints: TGLHintsSet; log: Boolean = True): TGLMultiTexture;
+    var txt: TAnimTextInfo;
+  begin
+    result := r_Textures_LoadMultiTextFromFile(filename, txt, hints, log);
+  end;
+
+  function r_Textures_LoadMultiFromFileAndInfo (const filename: AnsiString; w, h, count: Integer; hints: TGLHintsSet; log: Boolean = True): TGLMultiTexture;
     var wad: TWADFile; wadName, resName: AnsiString; data: Pointer; size: Integer;
   begin
     ASSERT(w > 0);
@@ -574,7 +786,7 @@ implementation
       resName := g_ExtractFilePathName(filename);
       if wad.GetResource(resName, data, size, log) then
       begin
-        result := r_Textures_LoadMultiFromDataAndInfo(data, size, w, h, count, backanim);
+        result := r_Textures_LoadMultiFromDataAndInfo(data, size, w, h, count, hints);
         FreeMem(data);
       end;
       wad.Free
@@ -638,26 +850,29 @@ implementation
     end;
   end;
 
-  function r_Textures_LoadStreamFromImage (var img: TImageData; w, h, c: Integer; st: TGLTextureArray; rs: TRectArray): Boolean;
-    var i: Integer; t: TImageData;
+  function r_Textures_LoadStreamFromImage (var img: TImageData; w, h, c, cw: Integer; st: TGLTextureArray; rs: TRectArray; hints: TGLHintsSet): Boolean;
+    var i, x, y: Integer; t: TImageData;
   begin
     ASSERT(w >= 0);
     ASSERT(h >= 0);
     ASSERT(c >= 1);
+    ASSERT(cw >= 1);
     ASSERT((st <> nil) and (Length(st) >= c));
     ASSERT((rs = nil) or (Length(rs) >= c));
     result := true;
     for i := 0 to c - 1 do
     begin
+      x := i mod cw;
+      y := i div cw;
       InitImage(t);
       st[i] := nil;
       if NewImage(w, h, img.Format, t) then
       begin
-        if CopyRect(img, w * i, 0, w, h, t, 0, 0) then
+        if CopyRect(img, x * w, y * h, w, h, t, 0, 0) then
         begin
           if rs <> nil then
             rs[i] := r_Textures_GetRect(t);
-          st[i] := r_Textures_LoadFromImage(t);
+          st[i] := r_Textures_LoadFromImage(t, hints);
         end;
       end;
       ASSERT(st[i] <> nil);
@@ -665,12 +880,13 @@ implementation
     end;
   end;
 
-  function r_Textures_LoadStreamFromMemory (data: Pointer; size: LongInt; w, h, c: Integer; st: TGLTextureArray; rs: TRectArray): Boolean;
+  function r_Textures_LoadStreamFromMemory (data: Pointer; size: LongInt; w, h, c, cw: Integer; st: TGLTextureArray; rs: TRectArray; hints: TGLHintsSet): Boolean;
     var img: TImageData;
   begin
     ASSERT(w >= 0);
     ASSERT(h >= 0);
     ASSERT(c >= 1);
+    ASSERT(cw >= 1);
     ASSERT((st <> nil) and (Length(st) >= c));
     ASSERT((rs = nil) or (Length(rs) >= c));
     result := false;
@@ -679,20 +895,25 @@ implementation
       InitImage(img);
       try
         if LoadImageFromMemory(data, size, img) then
+        begin
           if r_Textures_FixImageData(img) then
-            result := r_Textures_LoadStreamFromImage(img, w, h, c, st, rs)
+          begin
+            result := r_Textures_LoadStreamFromImage(img, w, h, c, cw, st, rs, hints)
+          end;
+        end;
       except
       end;
       FreeImage(img);
     end;
   end;
 
-  function r_Textures_LoadStreamFromFile (const filename: AnsiString; w, h, count: Integer; st: TGLTextureArray; rs: TRectArray; log: Boolean = True): Boolean;
+  function r_Textures_LoadStreamFromFile (const filename: AnsiString; w, h, count, cw: Integer; st: TGLTextureArray; rs: TRectArray; hints: TGLHintsSet; log: Boolean = True): Boolean;
     var wad: TWADFile; wadName, resName: AnsiString; data: Pointer; size: Integer;
   begin
     ASSERT(w > 0);
     ASSERT(h > 0);
     ASSERT(count >= 1);
+    ASSERT(cw >= 1);
     ASSERT((st <> nil) and (Length(st) >= count));
     ASSERT((rs = nil) or (Length(rs) >= count));
     result := false;
@@ -703,11 +924,84 @@ implementation
       resName := g_ExtractFilePathName(filename);
       if wad.GetResource(resName, data, size, log) then
       begin
-        result := r_Textures_LoadStreamFromMemory(data, size, w, h, count, st, rs);
+        result := r_Textures_LoadStreamFromMemory(data, size, w, h, count, cw, st, rs, hints);
         FreeMem(data);
       end;
       wad.Free
-    end
+    end;
+  end;
+
+  (* --------- TGLFont --------- *)
+
+  function r_Textures_LoadFontFromFile (const filename: AnsiString; constref f: TFontInfo; font2enc: TConvProc; log: Boolean = true): TGLFont;
+    var i, ch: Integer; st, stch: TGLTextureArray; font: TGLFont;
+  begin
+    result := nil;
+    SetLength(st, 256);
+    if r_Textures_LoadStreamFromFile(filename, f.w, f.h, 256, 16, st, nil, [TGLHints.txNoRepeat], log) then
+    begin
+      font := TGLFont.Create();
+      font.info := f;
+      font.ch := st;
+      if Assigned(font2enc) then
+      begin
+        SetLength(stch, 256);
+        for i := 0 to 255 do
+        begin
+          ch := font2enc(i);
+          ASSERT((ch >= 0) and (ch <= 255));
+          stch[ch] := st[i];
+        end;
+        font.ch := stch;
+        SetLength(st, 0);
+      end;
+      result := font;
+    end;
+  end;
+
+  destructor TGLFont.Destroy;
+    var i: Integer;
+  begin
+    if self.ch <> nil then
+      for i := 0 to High(self.ch) do
+        self.ch[i].Free;
+    self.ch := nil;
+  end;
+
+  function TGLFont.GetChar (c: AnsiChar): TGLTexture;
+  begin
+    result := self.ch[ORD(c)];
+  end;
+
+  function TGLFont.GetWidth (c: AnsiChar): Integer;
+  begin
+    result := self.info.ch[c].w;
+    if result = 0 then
+      result := self.info.w;
+    if self.info.kern < 0 then
+      result := result + self.info.kern;
+  end;
+
+  function TGLFont.GetMaxWidth (): Integer;
+  begin
+    result := self.info.w;
+    if self.info.kern < 0 then
+      result := result + self.info.kern;
+  end;
+
+  function TGLFont.GetMaxHeight (): Integer;
+  begin
+    result := self.info.h;
+  end;
+
+  function TGLFont.GetSpace (): Integer;
+  begin
+    result := self.info.kern;
   end;
 
+initialization
+  conRegVar('r_gl_maxtexsize', @r_GL_MaxTexSize, '', '');
+  conRegVar('r_gl_repeat', @r_GL_RepeatOpt, '', '');
+  r_GL_MaxTexSize := 0; // default is automatic value
+  r_GL_RepeatOpt := true;
 end.