Refactored TTexture to allow encoding sprite(s) at an arbitrary location within
authorWolfgang Steffens <WolfgangSteff@gmail.com>
Mon, 14 May 2012 19:26:50 +0200
changeset 7080 dbf43c07a507
parent 7079 939f53515489
child 7082 17b0892ac794
Refactored TTexture to allow encoding sprite(s) at an arbitrary location within a (possibly shared) physical texture. The original variant only allowed encoding a sprite at the bottomleft of a larger physical texture to use NPOT sprites on POT only supporting systems.
hedgewars/uLandTexture.pas
hedgewars/uRender.pas
hedgewars/uTextures.pas
hedgewars/uTypes.pas
hedgewars/uWorld.pas
--- a/hedgewars/uLandTexture.pas	Mon May 14 22:47:56 2012 +0400
+++ b/hedgewars/uLandTexture.pas	Mon May 14 19:26:50 2012 +0200
@@ -91,7 +91,7 @@
             with LandTextures[x, y] do
                 begin
                 tex:= NewTexture(TEXSIZE, TEXSIZE, Pixels(x, y));
-                glBindTexture(GL_TEXTURE_2D, tex^.id);
+                glBindTexture(GL_TEXTURE_2D, tex^.atlas^.id);
                 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_PRIORITY, tpHigh);
                 end
 else
@@ -101,7 +101,7 @@
                 if shouldUpdate then
                     begin
                     shouldUpdate:= false;
-                    glBindTexture(GL_TEXTURE_2D, tex^.id);
+                    glBindTexture(GL_TEXTURE_2D, tex^.atlas^.id);
                     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEXSIZE, TEXSIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, Pixels(x,y));
                     end
 end;
--- a/hedgewars/uRender.pas	Mon May 14 22:47:56 2012 +0400
+++ b/hedgewars/uRender.pas	Mon May 14 19:26:50 2012 +0200
@@ -22,7 +22,7 @@
 
 interface
 
-uses SDLh, uTypes, GLunit, uConsts;
+uses SDLh, uTypes, GLunit, uConsts, uTextures;
 
 procedure DrawSprite            (Sprite: TSprite; X, Y, Frame: LongInt);
 procedure DrawSprite            (Sprite: TSprite; X, Y, FrameX, FrameY: LongInt);
@@ -71,8 +71,7 @@
 
 procedure DrawTextureFromRect(X, Y, W, H: LongInt; r: PSDL_Rect; SourceTexture: PTexture);
 var rr: TSDL_Rect;
-    _l, _r, _t, _b: real;
-    VertexBuffer, TextureBuffer: array [0..3] of TVertex2f;
+    VertexBuffer, TextureBuffer: TVertexRect;
 begin
 if (SourceTexture^.h = 0) or (SourceTexture^.w = 0) then
     exit;
@@ -88,12 +87,9 @@
 rr.w:= W;
 rr.h:= H;
 
-_l:= r^.x / SourceTexture^.w * SourceTexture^.rx;
-_r:= (r^.x + r^.w) / SourceTexture^.w * SourceTexture^.rx;
-_t:= r^.y / SourceTexture^.h * SourceTexture^.ry;
-_b:= (r^.y + r^.h) / SourceTexture^.h * SourceTexture^.ry;
+glBindTexture(GL_TEXTURE_2D, SourceTexture^.atlas^.id);
 
-glBindTexture(GL_TEXTURE_2D, SourceTexture^.id);
+ComputeTexcoords(SourceTexture, r, @TextureBuffer);
 
 VertexBuffer[0].X:= X;
 VertexBuffer[0].Y:= Y;
@@ -104,15 +100,6 @@
 VertexBuffer[3].X:= X;
 VertexBuffer[3].Y:= rr.h + Y;
 
-TextureBuffer[0].X:= _l;
-TextureBuffer[0].Y:= _t;
-TextureBuffer[1].X:= _r;
-TextureBuffer[1].Y:= _t;
-TextureBuffer[2].X:= _r;
-TextureBuffer[2].Y:= _b;
-TextureBuffer[3].X:= _l;
-TextureBuffer[3].Y:= _b;
-
 glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
 glTexCoordPointer(2, GL_FLOAT, 0, @TextureBuffer[0]);
 glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
@@ -130,7 +117,7 @@
 glTranslatef(X, Y, 0);
 glScalef(Scale, Scale, 1);
 
-glBindTexture(GL_TEXTURE_2D, Texture^.id);
+glBindTexture(GL_TEXTURE_2D, Texture^.atlas^.id);
 
 glVertexPointer(2, GL_FLOAT, 0, @Texture^.vb);
 glTexCoordPointer(2, GL_FLOAT, 0, @Texture^.tb);
@@ -145,8 +132,8 @@
 end;
 
 procedure DrawTextureRotatedF(Texture: PTexture; Scale, OffsetX, OffsetY: GLfloat; X, Y, Frame, Dir, w, h: LongInt; Angle: real);
-var ft, fb, fl, fr: GLfloat;
-    hw, nx, ny: LongInt;
+var hw, nx, ny: LongInt;
+    r: TSDL_Rect;
     VertexBuffer, TextureBuffer: array [0..3] of TVertex2f;
 begin
 // do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
@@ -172,12 +159,13 @@
 nx:= round(Texture^.w / w); // number of horizontal frames
 ny:= round(Texture^.h / h); // number of vertical frames
 
-ft:= (Frame mod ny) * Texture^.ry / ny;
-fb:= ((Frame mod ny) + 1) * Texture^.ry / ny;
-fl:= (Frame div ny) * Texture^.rx / nx;
-fr:= ((Frame div ny) + 1) * Texture^.rx / nx;
+r.y:=(Frame mod ny) * h;
+r.x:=(Frame div ny) * w;
+r.w:=w;
+r.h:=h;
+ComputeTexcoords(Texture, @r, @TextureBuffer);
 
-glBindTexture(GL_TEXTURE_2D, Texture^.id);
+glBindTexture(GL_TEXTURE_2D, Texture^.atlas^.id);
 
 VertexBuffer[0].X:= -hw;
 VertexBuffer[0].Y:= w / -2;
@@ -188,15 +176,6 @@
 VertexBuffer[3].X:= -hw;
 VertexBuffer[3].Y:= w / 2;
 
-TextureBuffer[0].X:= fl;
-TextureBuffer[0].Y:= ft;
-TextureBuffer[1].X:= fr;
-TextureBuffer[1].Y:= ft;
-TextureBuffer[2].X:= fr;
-TextureBuffer[2].Y:= fb;
-TextureBuffer[3].X:= fl;
-TextureBuffer[3].Y:= fb;
-
 glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
 glTexCoordPointer(2, GL_FLOAT, 0, @TextureBuffer[0]);
 glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
@@ -250,7 +229,7 @@
     glRotatef(Angle, 0, 0,  1);
 
 
-glBindTexture(GL_TEXTURE_2D, Texture^.id);
+glBindTexture(GL_TEXTURE_2D, Texture^.atlas^.id);
 
 VertexBuffer[0].X:= -hw;
 VertexBuffer[0].Y:= -hh;
@@ -407,12 +386,16 @@
 
 
 procedure DrawHedgehog(X, Y: LongInt; Dir: LongInt; Pos, Step: LongWord; Angle: real);
-const VertexBuffer: array [0..3] of TVertex2f = (
-        (X: -16; Y: -16),
-        (X:  16; Y: -16),
-        (X:  16; Y:  16),
-        (X: -16; Y:  16));
-var l, r, t, b: real;
+const VertexBuffers: array[0..1] of TVertexRect = (
+        ((x: -16; y: -16),
+         (x:  16; y: -16),
+         (x:  16; y:  16),
+         (x: -16; y:  16)),
+        ((x:  16; y: -16),
+         (x: -16; y: -16),
+         (x: -16; y:  16),
+         (x:  16; y:  16)));
+var r: TSDL_Rect;
     TextureBuffer: array [0..3] of TVertex2f;
 begin
     // do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
@@ -421,39 +404,23 @@
     if (abs(Y) > 32) and ((abs(Y - 0.5 * cScreenHeight) - 16) * cScaleFactor > cScreenHeight) then
         exit;
 
-    t:= Pos * 32 / HHTexture^.h;
-    b:= (Pos + 1) * 32 / HHTexture^.h;
-
-    if Dir = -1 then
-        begin
-        l:= (Step + 1) * 32 / HHTexture^.w;
-        r:= Step * 32 / HHTexture^.w
-        end
-    else
-        begin
-        l:= Step * 32 / HHTexture^.w;
-        r:= (Step + 1) * 32 / HHTexture^.w
-    end;
-
+    r.x:=Step * 32;
+    r.y:=Pos * 32;
+    r.w:=32;
+    r.h:=32;
+    ComputeTexcoords(HHTexture, @r, @TextureBuffer);
 
     glPushMatrix();
     glTranslatef(X, Y, 0);
     glRotatef(Angle, 0, 0, 1);
 
-    glBindTexture(GL_TEXTURE_2D, HHTexture^.id);
-
-    TextureBuffer[0].X:= l;
-    TextureBuffer[0].Y:= t;
-    TextureBuffer[1].X:= r;
-    TextureBuffer[1].Y:= t;
-    TextureBuffer[2].X:= r;
-    TextureBuffer[2].Y:= b;
-    TextureBuffer[3].X:= l;
-    TextureBuffer[3].Y:= b;
-
-    glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
+    glBindTexture(GL_TEXTURE_2D, HHTexture^.atlas^.id);
+    if Dir = -1 then
+        glVertexPointer(2, GL_FLOAT, 0, @VertexBuffers[1][0])
+    else
+        glVertexPointer(2, GL_FLOAT, 0, @VertexBuffers[0][0]);
     glTexCoordPointer(2, GL_FLOAT, 0, @TextureBuffer[0]);
-    glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
+    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
     glPopMatrix
 end;
--- a/hedgewars/uTextures.pas	Mon May 14 22:47:56 2012 +0400
+++ b/hedgewars/uTextures.pas	Mon May 14 19:26:50 2012 +0200
@@ -26,6 +26,7 @@
 procedure Surface2GrayScale(surf: PSDL_Surface);
 function  Surface2Tex(surf: PSDL_Surface; enableClamp: boolean): PTexture;
 procedure FreeTexture(tex: PTexture);
+procedure ComputeTexcoords(texture: PTexture; r: PSDL_Rect; tb: PVertexRect);
 
 procedure initModule;
 procedure freeModule;
@@ -47,10 +48,44 @@
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
 end;
 
+procedure ComputeTexcoords(texture: PTexture; r: PSDL_Rect; tb: PVertexRect);
+var x0, y0, x1, y1: Real;
+    w, h, aw, ah: LongInt;
+const texelOffset = 0.0;
+begin
+aw:=texture^.atlas^.w;
+ah:=texture^.atlas^.h;
+if texture^.isRotated then
+    begin
+    w:=r^.h;
+    h:=r^.w;
+    end 
+else
+    begin
+    w:=r^.w;
+    h:=r^.h;        
+    end;
+
+x0:= (r^.x +     texelOffset)/aw;
+x1:= (r^.x + w - texelOffset)/aw;
+y0:= (r^.y +     texelOffset)/ah;
+y1:= (r^.y + h - texelOffset)/ah;
+
+tb^[0].X:= x0;
+tb^[0].Y:= y0;
+tb^[1].X:= x1;
+tb^[1].Y:= y0;
+tb^[2].X:= x1;
+tb^[2].Y:= y1;
+tb^[3].X:= x0;
+tb^[3].Y:= y1
+end;
+
 procedure ResetVertexArrays(texture: PTexture);
+var r: TSDL_Rect;
 begin
 with texture^ do
-    begin
+begin
     vb[0].X:= 0;
     vb[0].Y:= 0;
     vb[1].X:= w;
@@ -59,16 +94,13 @@
     vb[2].Y:= h;
     vb[3].X:= 0;
     vb[3].Y:= h;
+end;
 
-    tb[0].X:= 0;
-    tb[0].Y:= 0;
-    tb[1].X:= rx;
-    tb[1].Y:= 0;
-    tb[2].X:= rx;
-    tb[2].Y:= ry;
-    tb[3].X:= 0;
-    tb[3].Y:= ry
-    end;
+r.x:= 0;
+r.y:= 0;
+r.w:= texture^.w;
+r.h:= texture^.h;
+ComputeTexcoords(texture, @r, @texture^.tb);
 end;
 
 function NewTexture(width, height: Longword; buf: Pointer): PTexture;
@@ -84,16 +116,22 @@
     end;
 TextureList:= NewTexture;
 
-NewTexture^.w:= width;
-NewTexture^.h:= height;
-NewTexture^.rx:= 1.0;
-NewTexture^.ry:= 1.0;
+
+// Atlas allocation happens here later on. For now we just allocate one exclusive atlas per sprite
+new(NewTexture^.atlas);
+NewTexture^.atlas^.w:=width;
+NewTexture^.atlas^.h:=height;
+NewTexture^.x:=0;
+NewTexture^.y:=0;
+NewTexture^.w:=width;
+NewTexture^.h:=height;
+NewTexture^.isRotated:=false;
 
 ResetVertexArrays(NewTexture);
 
-glGenTextures(1, @NewTexture^.id);
+glGenTextures(1, @NewTexture^.atlas^.id);
 
-glBindTexture(GL_TEXTURE_2D, NewTexture^.id);
+glBindTexture(GL_TEXTURE_2D, NewTexture^.atlas^.id);
 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf);
 
 SetTextureParameters(true);
@@ -136,20 +174,27 @@
     end;
 TextureList:= Surface2Tex;
 
+// Atlas allocation happens here later on. For now we just allocate one exclusive atlas per sprite
+new(Surface2Tex^.atlas);
+
 Surface2Tex^.w:= surf^.w;
 Surface2Tex^.h:= surf^.h;
+Surface2Tex^.x:=0;
+Surface2Tex^.y:=0;
+Surface2Tex^.isRotated:=false;
+
 
 if (surf^.format^.BytesPerPixel <> 4) then
     begin
     TryDo(false, 'Surface2Tex failed, expecting 32 bit surface', true);
-    Surface2Tex^.id:= 0;
+    Surface2Tex^.atlas^.id:= 0;
     exit
     end;
 
 
-glGenTextures(1, @Surface2Tex^.id);
+glGenTextures(1, @Surface2Tex^.atlas^.id);
 
-glBindTexture(GL_TEXTURE_2D, Surface2Tex^.id);
+glBindTexture(GL_TEXTURE_2D, Surface2Tex^.atlas^.id);
 
 if SDL_MustLock(surf) then
     SDLTry(SDL_LockSurface(surf) >= 0, true);
@@ -164,8 +209,8 @@
     tw:= toPowerOf2(Surf^.w);
     th:= toPowerOf2(Surf^.h);
 
-    Surface2Tex^.rx:= Surf^.w / tw;
-    Surface2Tex^.ry:= Surf^.h / th;
+    Surface2Tex^.atlas^.w:=tw;
+    Surface2Tex^.atlas^.h:=th;
 
     GetMem(tmpp, tw * th * surf^.format^.BytesPerPixel);
 
@@ -195,8 +240,8 @@
     end
 else
     begin
-    Surface2Tex^.rx:= 1.0;
-    Surface2Tex^.ry:= 1.0;
+    Surface2Tex^.atlas^.w:=Surf^.w;
+    Surface2Tex^.atlas^.h:=Surf^.h;
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf^.w, surf^.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf^.pixels);
     end;
 
@@ -214,13 +259,16 @@
 begin
 if tex <> nil then
     begin
+    // Atlas cleanup happens here later on. For now we just free as each sprite has one atlas
+    Dispose(tex^.atlas);
+
     if tex^.NextTexture <> nil then
         tex^.NextTexture^.PrevTexture:= tex^.PrevTexture;
     if tex^.PrevTexture <> nil then
         tex^.PrevTexture^.NextTexture:= tex^.NextTexture
     else
         TextureList:= tex^.NextTexture;
-    glDeleteTextures(1, @tex^.id);
+    glDeleteTextures(1, @tex^.atlas^.id);
     Dispose(tex);
     end
 end;
@@ -236,7 +284,7 @@
     WriteToConsole('FIXME FIXME FIXME. App shutdown without full cleanup of texture list; read game0.log and please report this problem');
     while TextureList <> nil do 
         begin
-        AddFileLog('Texture not freed: width='+inttostr(LongInt(TextureList^.w))+' height='+inttostr(LongInt(TextureList^.h))+' priority='+inttostr(round(TextureList^.priority*1000)));
+        AddFileLog('Sprite not freed: width='+inttostr(LongInt(TextureList^.w))+' height='+inttostr(LongInt(TextureList^.h))+' priority='+inttostr(round(TextureList^.atlas^.priority*1000)));
         FreeTexture(TextureList);
         end
 end;
--- a/hedgewars/uTypes.pas	Mon May 14 22:47:56 2012 +0400
+++ b/hedgewars/uTypes.pas	Mon May 14 19:26:50 2012 +0200
@@ -199,13 +199,28 @@
             X, Y: GLint;
             end;
 
+    PAtlas = ^TAtlas;
     PTexture = ^TTexture;
+
+    TAtlas = record
+                id: GLuint;
+                w, h: LongInt;
+                priority: GLfloat;
+            end;
+
+    PVertexRect = ^TVertexRect;
+    TVertexRect = array[0..3] of TVertex2f;
+    
     TTexture = record
-            id: GLuint;
+            atlas: PAtlas;
             w, h, scale: LongInt;
-            rx, ry: GLfloat;
-            priority: GLfloat;
-            vb, tb: array [0..3] of TVertex2f;
+
+            x, y: LongInt; // Offset in the texture atlas
+            isRotated: boolean; // if true sprite is flipped in the atlas taking w pixels along the y and h pixels along the x axis
+
+            // Cached values for texel coordinates and vertex coordinates            
+            vb, tb: TVertexRect;
+
             PrevTexture, NextTexture: PTexture;
             end;
 
--- a/hedgewars/uWorld.pas	Mon May 14 22:47:56 2012 +0400
+++ b/hedgewars/uWorld.pas	Mon May 14 19:26:50 2012 +0200
@@ -814,8 +814,9 @@
 
 procedure DrawWaves(Dir, dX, dY: LongInt; tnt: Byte);
 var VertexBuffer, TextureBuffer: array [0..3] of TVertex2f;
-    lw, waves, shift: GLfloat;
+    lw, waves: GLfloat;
     sprite: TSprite;
+    r: TSDL_Rect;
 begin
 if SuddenDeathDmg then
     sprite:= sprSDWater
@@ -825,7 +826,6 @@
 cWaveWidth:= SpritesData[sprite].Width;
 
 lw:= cScreenWidth / cScaleFactor;
-waves:= lw * 2 / cWaveWidth;
 
 if SuddenDeathDmg then
     Tint(LongInt(tnt) * SDWaterColorArray[2].r div 255 + 255 - tnt,
@@ -840,7 +840,7 @@
          255
     );
 
-glBindTexture(GL_TEXTURE_2D, SpritesData[sprite].Texture^.id);
+glBindTexture(GL_TEXTURE_2D, SpritesData[sprite].Texture^.atlas^.id);
 
 VertexBuffer[0].X:= -lw;
 VertexBuffer[0].Y:= cWaterLine + WorldDy + dY;
@@ -851,15 +851,12 @@
 VertexBuffer[3].X:= -lw;
 VertexBuffer[3].Y:= VertexBuffer[2].Y;
 
-shift:= - lw / cWaveWidth;
-TextureBuffer[0].X:= shift + (( - WorldDx + LongInt(RealTicks shr 6) * Dir + dX) mod cWaveWidth) / (cWaveWidth - 1);
-TextureBuffer[0].Y:= 0;
-TextureBuffer[1].X:= TextureBuffer[0].X + waves;
-TextureBuffer[1].Y:= TextureBuffer[0].Y;
-TextureBuffer[2].X:= TextureBuffer[1].X;
-TextureBuffer[2].Y:= SpritesData[sprite].Texture^.ry;
-TextureBuffer[3].X:= TextureBuffer[0].X;
-TextureBuffer[3].Y:= TextureBuffer[2].Y;
+// this uses texture repeat mode, when using an atlas rect we need to split to several quads here!
+r.x := -Trunc(lw) + (( - WorldDx + LongInt(RealTicks shr 6) * Dir + dX) mod cWaveWidth);
+r.y:= 0;
+r.w:= Trunc(lw + lw);
+r.h:= SpritesData[sprite].Texture^.h;
+ComputeTexcoords(SpritesData[sprite].Texture, @r, @TextureBuffer);
 
 
 glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);