Merge
authorWolfgang Steffens <WolfgangSteff@gmail.com>
Mon, 25 Jun 2012 15:46:49 +0200
changeset 7299 c633d00dc593
parent 7297 af64b509725c (diff)
parent 7290 390d76b29ed0 (current diff)
child 7301 bea42438a2ec
Merge
--- a/hedgewars/options.inc	Mon Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/options.inc	Mon Jun 25 15:46:49 2012 +0200
@@ -30,6 +30,8 @@
 {$DEFINE USE_LUA_SCRIPT}
 
 
+{$DEFINE GL2}
+
 {$IFDEF ANDROID}
     {$DEFINE MOBILE}
     {$DEFINE USE_SDLTHREADS}
@@ -53,6 +55,10 @@
 {$ENDIF}
 
 
+{$IFDEF GL2}
+    {$DEFINE S3D_DISABLED}
+{$ENDIF}
+
 {$IFDEF WIN32}
     {$DEFINE USE_CONTEXT_RESTORE}
 {$ENDIF}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uAtlas.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -0,0 +1,698 @@
+{$INCLUDE "options.inc"}
+{$IF GLunit = GL}{$DEFINE GLunit:=GL,GLext}{$ENDIF}
+
+unit uAtlas;
+
+interface
+
+uses SDLh, uTypes;
+
+procedure initModule;
+
+function Surface2Tex_(surf: PSDL_Surface; enableClamp: boolean): PTexture;
+procedure FreeTexture_(sprite: PTexture);
+procedure DebugAtlas;
+
+implementation
+
+uses GLunit, uBinPacker, uDebug, png, sysutils, uTextures;
+
+const
+    MaxAtlases = 4;    // Maximum number of atlases (textures) to allocate
+    MaxTexSize = 1024; // Maximum atlas size in pixels
+    MinTexSize = 128;  // Minimum atlas size in pixels
+    CompressionThreshold = 0.4; // Try to compact (half the size of) an atlas, when occupancy is less than this
+
+type
+    AtlasInfo = record
+        PackerInfo: Atlas;     // Rectangle packer context
+        TextureInfo: TAtlas;   // OpenGL texture information
+        Allocated: boolean;    // indicates if this atlas is in use
+    end;
+
+var
+    Info: array[0..MaxAtlases-1] of AtlasInfo;
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Debug routines
+
+procedure AssertCount(tex: PTexture; count: Integer);
+var
+    i, j: Integer;
+    found: Integer;
+begin
+    found:= 0;
+    for i:= 0 to pred(MaxAtlases) do
+    begin
+        if not Info[i].Allocated then
+            continue;
+        for j:=0 to pred(Info[i].PackerInfo.usedRectangles.count) do
+        begin
+            if Info[i].PackerInfo.usedRectangles.data[j].UserData = tex then
+                inc(found);
+        end;
+    end;
+    if found <> count then
+    begin
+        writeln('AssertCount(', IntToHex(Integer(tex), 8), ') failed, found ', found, ' times');
+
+        for i:= 0 to pred(MaxAtlases) do
+        begin
+            if not Info[i].Allocated then
+                continue;
+            for j:=0 to pred(Info[i].PackerInfo.usedRectangles.count) do
+            begin
+                if Info[i].PackerInfo.usedRectangles.data[j].UserData = tex then
+                    writeln(' found in atlas ', i, ' at slot ', j);
+            end;
+        end;
+        halt(-2);
+    end;
+end;
+
+var
+    DumpID: Integer;
+    DumpFile: File of byte;
+
+const
+    PNG_COLOR_TYPE_RGBA = 6;
+    PNG_COLOR_TYPE_RGB = 2;
+    PNG_INTERLACE_NONE = 0;
+    PNG_COMPRESSION_TYPE_DEFAULT = 0;
+    PNG_FILTER_TYPE_DEFAULT = 0;
+    
+
+
+procedure writefunc(png: png_structp; buffer: png_bytep; size: QWord); cdecl;
+var
+    p: Pbyte;
+    i: Integer;
+begin
+  //TStream(png_get_io_ptr(png)).Write(buffer^, size);
+    BlockWrite(DumpFile, buffer^, size);
+{    p:= PByte(buffer^);
+    for i:=0 to pred(size) do
+    begin
+        Write(DumpFile, p^);
+        inc(p);
+    end;}
+end;
+
+function IntToStrPad(i: Integer): string;
+var
+  s: string;
+begin
+   s:= IntToStr(i);
+   if (i < 10) then s:='0' + s;
+   if (i < 100) then s:='0' + s;
+
+   IntToStrPad:=s;
+end;
+
+// GL1 ATLAS DEBUG ONLY CODE!
+procedure DebugAtlas;
+var
+    vp: array[0..3] of GLint;
+    prog: GLint;
+    i: Integer;
+    x, y: Integer;
+const
+    SZ = 512;
+begin
+    x:= 0;
+    y:= 0;
+    for i:= 0 to pred(MaxAtlases) do
+    begin
+        if not Info[i].allocated then
+            continue;
+        glGetIntegerv(GL_VIEWPORT, @vp);
+        glGetIntegerv(GL_CURRENT_PROGRAM, @prog);
+
+        glUseProgram(0);
+        glPushMatrix;
+        glLoadIdentity;
+        glOrtho(0, vp[2], vp[3], 0, -1, 1);
+
+
+        glBindTexture(GL_TEXTURE_2D, Info[i].TextureInfo.id);
+        glBegin(GL_QUADS);
+        glTexCoord2f(0.0, 0.0);
+        glVertex2i(x * SZ, y * SZ);
+        glTexCoord2f(1.0, 0.0);
+        glVertex2i((x + 1) * SZ, y * SZ);
+        glTexCoord2f(1.0, 1.0);
+        glVertex2i((x + 1) * SZ, (y + 1) * SZ);
+        glTexCoord2f(0.0, 1.0);
+        glVertex2i(x * SZ, (y + 1) * SZ);
+        glEnd();
+
+        glPopMatrix;
+
+        inc(x);
+        if (x = 2) then
+        begin
+            x:=0;
+            inc(y);
+        end;
+     
+
+        glUseProgram(prog);
+    end;
+end;
+
+procedure DumpAtlas(var info: AtlasInfo);
+var
+    png: png_structp;
+    png_info: png_infop;
+    w, h, sz: Integer;
+    filename: string;
+    rows: array of png_bytep;
+    size: Integer;
+    i, j: Integer;
+    mem, p, pp: PByte;
+begin
+    filename:= '/home/wolfgangst/hedgewars/dump/atlas_' + IntToStrPad(DumpID) + '.png';
+    Assign(DumpFile, filename);
+    inc(DumpID);
+    Rewrite(DumpFile);
+
+    w:= info.TextureInfo.w;
+    h:= info.TextureInfo.h;
+    size:= w * h * 4;
+    SetLength(rows, h);
+    GetMem(mem, size);
+
+    glBindTexture(GL_TEXTURE_2D, info.TextureInfo.id);
+
+    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, mem);
+
+    p:= mem;
+    for i:= 0 to pred(h) do
+    begin
+        rows[i]:= p;
+        pp:= p;
+        inc(pp, 3);
+        {for j:= 0 to pred(w) do
+        begin
+            pp^:=255;
+            inc(pp, 4);
+        end;}
+        inc(p, w * 4);
+    end;
+
+    png := png_create_write_struct(PNG_LIBPNG_VER_STRING, nil, nil, nil);
+    png_info := png_create_info_struct(png);
+
+    png_set_write_fn(png, nil, @writefunc, nil);
+    png_set_IHDR(png, png_info, w, h, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+    png_write_info(png, png_info);
+    png_write_image(png, @rows[0]);
+    png_write_end(png, png_info);
+    png_destroy_write_struct(@png, @png_info);
+
+    FreeMem(mem);
+    
+    SetLength(rows, 0);
+    Close(DumpFile);
+
+    //if (DumpID >= 30) then
+    //    halt(0);
+end;
+
+////////////////////////////////////////////////////////////////////////////////
+// Upload routines
+
+function createTexture(width, height: Integer): TAtlas;
+var
+  nullTex: Pointer;
+begin
+    createTexture.w:= width;
+    createTexture.h:= height;
+    createTexture.priority:= 0;
+    glGenTextures(1, @createTexture.id);
+    glBindTexture(GL_TEXTURE_2D, createTexture.id);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+    //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
+    
+    GetMem(NullTex, width * height * 4);
+    FillChar(NullTex^, width * height * 4, 0);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NullTex);
+    FreeMem(NullTex);
+
+    glBindTexture(GL_TEXTURE_2D, 0);
+end;
+
+function Min(x, y: Single): Single;
+begin
+  if x < y then
+    Min:=x
+  else Min:=y;
+end;
+
+function Max(x, y: Single): Single;
+begin
+  if x > y then
+    Max:=x
+  else Max:=y;
+end;
+
+
+procedure HSVToRGB(const H, S, V: Single; out R, G, B: Single); 
+const 
+    SectionSize = 60/360; 
+var 
+    Section: Single; 
+    SectionIndex: Integer; 
+    f: single; 
+    p, q, t: Single; 
+begin
+    if H < 0 then 
+    begin 
+        R:= V; 
+        G:= R; 
+        B:= R; 
+    end 
+    else 
+    begin 
+        Section:= H/SectionSize; 
+        SectionIndex:= Trunc(Section); 
+        f:= Section - SectionIndex; 
+        p:= V * ( 1 - S ); 
+        q:= V * ( 1 - S * f ); 
+        t:= V * ( 1 - S * ( 1 - f ) ); 
+        case SectionIndex of 
+            0: 
+            begin 
+                R:= V; 
+                G:= t; 
+                B:= p; 
+            end; 
+            1: 
+            begin 
+                R:= q; 
+                G:= V; 
+                B:= p; 
+            end; 
+            2: 
+            begin 
+                R:= p; 
+                G:= V; 
+                B:= t; 
+            end; 
+            3: 
+            begin 
+                R:= p; 
+                G:= q; 
+                B:= V; 
+            end; 
+            4: 
+            begin 
+                R:= t; 
+                G:= p; 
+                B:= V; 
+            end; 
+            else 
+                R:= V; 
+                G:= p; 
+                B:= q; 
+        end; 
+    end; 
+end; 
+
+procedure DebugColorize(surf: PSDL_Surface);
+var
+    sz: Integer;
+    p: PByte;
+    i: Integer;
+    r, g, b, a, inva: Integer;
+    randr, randg, randb: Single;
+    randh: Single;
+begin
+    sz:= surf^.w * surf^.h;
+    p:= surf^.pixels;
+    //randr:=Random;
+    //randg:=Random;
+    //randb:=1 - min(randr, randg);
+    randh:=Random;
+    HSVToRGB(randh, 1.0, 1.0, randr, randg, randb);
+    for i:=0 to pred(sz) do
+    begin
+        a:= p[3];
+        inva:= 255 - a;
+
+        r:=Trunc(inva*randr + p[0]*a/255);
+        g:=Trunc(inva*randg + p[1]*a/255);
+        b:=Trunc(inva*randb + p[2]*a/255);
+        if r > 255 then r:= 255;
+        if g > 255 then g:= 255;
+        if b > 255 then b:= 255;
+
+        p[0]:=r;
+        p[1]:=g;
+        p[2]:=b;
+        p[3]:=255;
+        inc(p, 4);
+    end;
+end;
+
+procedure Upload(var info: AtlasInfo; sprite: Rectangle; surf: PSDL_Surface);
+var
+    sp: PTexture;
+    i, j, stride: Integer;
+    scanline: PByte;
+    r: TSDL_Rect;
+begin
+    writeln('Uploading sprite to ', sprite.x, ',', sprite.y, ',', sprite.width, ',', sprite.height);
+    sp:= PTexture(sprite.UserData);
+    sp^.x:= sprite.x;
+    sp^.y:= sprite.y;
+    sp^.isRotated:= sp^.w <> sprite.width;
+    sp^.atlas:= @info.TextureInfo;
+
+    if SDL_MustLock(surf) then
+        SDLTry(SDL_LockSurface(surf) >= 0, true);
+
+    //if GrayScale then
+    //    Surface2GrayScale(surf);
+    //DebugColorize(surf);
+
+    glBindTexture(GL_TEXTURE_2D, info.TextureInfo.id);
+    if (sp^.isRotated) then
+    begin
+        scanline:= surf^.pixels;
+        for i:= 0 to pred(sprite.width) do
+        begin
+            glTexSubImage2D(GL_TEXTURE_2D, 0, sprite.x + i, sprite.y, 1, sprite.height, GL_RGBA, GL_UNSIGNED_BYTE, scanline);
+            inc(scanline, sprite.height * 4);
+        end;
+    end
+    else
+        glTexSubImage2D(GL_TEXTURE_2D, 0, sprite.x, sprite.y, sprite.width, sprite.height, GL_RGBA, GL_UNSIGNED_BYTE, surf^.pixels);
+    glBindTexture(GL_TEXTURE_2D, 0);
+
+    if SDL_MustLock(surf) then
+        SDL_UnlockSurface(surf);
+
+    r.x:= 0;
+    r.y:= 0;
+    r.w:= sp^.w;
+    r.h:= sp^.h;
+    ComputeTexcoords(sp, @r, @sp^.tb);
+end;
+
+procedure Repack(var info: AtlasInfo; newAtlas: Atlas);
+var
+    base: PByte;
+    oldSize: Integer;
+    oldWidth: Integer;
+    offset: Integer;
+    i,j : Integer;
+    r: Rectangle;
+    sp: PTexture;
+    newIsRotated: boolean;
+    newSpriteRect: Rectangle;
+begin
+    writeln('Repacking atlas (', info.PackerInfo.width, 'x', info.PackerInfo.height, ')', ' -> (', newAtlas.width, 'x', newAtlas.height, ')');
+
+    // delete the old atlas
+    glDeleteTextures(1, @info.TextureInfo.id);
+
+    // create a new atlas with different size
+    info.TextureInfo:= createTexture(newAtlas.width, newAtlas.height);
+    glBindTexture(GL_TEXTURE_2D, info.TextureInfo.id);
+
+    atlasDelete(info.PackerInfo);
+    info.PackerInfo:= newAtlas;
+
+    // and process all sprites of the new atlas
+    for i:=0 to pred(newAtlas.usedRectangles.count) do
+    begin
+        r:= newAtlas.usedRectangles.data[i];
+        sp:= PTexture(r.UserData);
+        Upload(info, r, sp^.surface);
+    end;
+
+    glBindTexture(GL_TEXTURE_2D, 0);
+end;
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Utility functions
+
+function SizeForSprite(sprite: PTexture): Size;
+begin
+    SizeForSprite.width:= sprite^.w;
+    SizeForSprite.height:= sprite^.h;
+    SizeForSprite.UserData:= sprite;
+end;
+
+procedure EnlargeSize(var x: Integer; var y: Integer);
+begin
+    if (y < x) then
+        y:= y + y
+    else
+        x:= x + x;
+end;
+
+procedure CompactSize(var x: Integer; var y: Integer);
+begin
+    if (x > y) then
+        x:= x div 2
+    else
+        y:= y div 2;
+end;
+
+////////////////////////////////////////////////////////////////////////////////
+// Sprite allocation logic
+
+function TryRepack(var info: AtlasInfo; w, h: Integer; hasNewSprite: boolean; newSprite: Size): boolean;
+var
+    sizes: SizeList;
+    repackedAtlas: Atlas;
+    sprite: PTexture;
+    i: Integer;
+    rects: RectangleList; // we wont really need this as we do a full repack using the atlas later on
+begin
+    TryRepack:= false;
+
+    // STEP 1: collect sizes of all existing sprites
+    sizeListInit(sizes);
+    for i:= 0 to pred(info.PackerInfo.usedRectangles.count) do
+    begin
+        sprite:= PTexture(info.PackerInfo.usedRectangles.data[i].UserData);
+        sizeListAdd(sizes, SizeForSprite(sprite));
+    end;
+
+    // STEP 2: add the new sprite to the list
+    if hasNewSprite then
+        sizeListAdd(sizes, newSprite);
+
+    // STEP 3: try to create a non adaptive re-packing using the whole list
+    repackedAtlas:= atlasNew(w, h);
+    rectangleListInit(rects);
+    if atlasInsertSet(repackedAtlas, sizes, rects) then
+    begin
+        TryRepack:= true;
+        Repack(info, repackedAtlas);
+        // repack assigns repackedAtlas to the current info and deletes the old one
+        // thus we wont do atlasDelete(repackedAtlas); here 
+        rectangleListClear(rects);
+        sizeListClear(sizes);
+        //DumpAtlas(info);
+        exit;
+    end;
+
+    rectangleListClear(rects);
+    sizeListClear(sizes);
+    atlasDelete(repackedAtlas);
+end;
+
+function TryInsert(var info: AtlasInfo; newSprite: Size; surf: PSDL_Surface): boolean;
+var
+    rect: Rectangle;
+    sprite: PTexture;
+begin
+    TryInsert:= false;
+
+    if atlasInsertAdaptive(info.PackerInfo, newSprite, rect) then
+    begin
+        // we succeeded adaptivley allocating the sprite to the i'th atlas.
+        Upload(info, rect, surf);
+        //DumpAtlas(info);
+        TryInsert:= true;
+    end;
+end;
+
+function Surface2Tex_(surf: PSDL_Surface; enableClamp: boolean): PTexture;
+var
+    sz: Size;
+    sprite: PTexture;
+    currentWidth, currentHeight: Integer;
+    i: Integer;
+begin
+    if (surf^.w > MaxTexSize) or (surf^.h > MaxTexSize) then
+    begin
+        // we could at best downscale the sprite, abort for now
+        writeln('Sprite size larger than maximum texture size');
+        halt(-1);        
+    end;
+
+    // allocate the sprite
+    new(sprite);
+    Surface2Tex_:= sprite;
+
+    sprite^.w:= surf^.w;
+    sprite^.h:= surf^.h;
+    sprite^.x:= 0;
+    sprite^.y:= 0;
+    sprite^.isRotated:= false;
+    sprite^.surface:= surf;
+    sprite^.shared:= true;
+
+    sz:= SizeForSprite(sprite);
+
+    // STEP 1
+    // try to allocate the new sprite in one of the existing atlases
+    for i:= 0 to pred(MaxAtlases) do
+    begin
+        if not Info[i].Allocated then
+            continue;
+        if TryInsert(Info[i], sz, surf) then
+            exit;
+    end;
+
+
+    // STEP 2
+    // none of the atlases has space left for the allocation, try a garbage collection
+    for i:= 0 to pred(MaxAtlases) do
+    begin
+        if not Info[i].Allocated then
+            continue;
+
+        if TryRepack(Info[i], Info[i].PackerInfo.width, Info[i].PackerInfo.height, true, sz) then
+            exit;
+    end;
+
+    // STEP 3
+    // none of the atlases could be repacked in a way to fit the new sprite, try enlarging
+    for i:= 0 to pred(MaxAtlases) do
+    begin
+        if not Info[i].Allocated then
+            continue;
+
+        currentWidth:= Info[i].PackerInfo.width;
+        currentHeight:= Info[i].PackerInfo.height;
+
+        EnlargeSize(currentWidth, currentHeight);
+        while (currentWidth <= MaxTexSize) and (currentHeight <= MaxTexSize) do
+        begin
+            if TryRepack(Info[i], currentWidth, currentHeight, true, sz) then
+                exit;
+            EnlargeSize(currentWidth, currentHeight);
+        end;
+    end;
+
+    // STEP 4
+    // none of the existing atlases could be resized, try to allocate a new atlas
+    for i:= 0 to pred(MaxAtlases) do
+    begin
+        if Info[i].Allocated then
+            continue;
+
+        currentWidth:= MinTexSize;
+        currentHeight:= MinTexSize;
+        while (sz.width > currentWidth) do
+            currentWidth:= currentWidth + currentWidth;
+        while (sz.height > currentHeight) do
+            currentHeight:= currentHeight + currentHeight;
+
+        with Info[i] do
+        begin
+            PackerInfo:= atlasNew(currentWidth, currentHeight);
+            TextureInfo:= createTexture(currentWidth, currentHeight);
+            Allocated:= true;
+        end;
+
+        if TryInsert(Info[i], sz, surf) then
+            exit;
+
+        // this shouldnt have happened, the rectpacker should be able to fit the sprite
+        // into an unused rectangle that is the same size or larger than the requested sprite.
+        writeln('Internal error: atlas allocation failed');
+        halt(-1);
+    end;
+
+    // we reached the upperbound of resources we are willing to allocate
+    writeln('Exhausted maximum sprite allocation size');
+    halt(-1);
+end;
+
+////////////////////////////////////////////////////////////////////////////////
+// Sprite deallocation logic
+
+
+procedure FreeTexture_(sprite: PTexture);
+var
+    i, j, deleteAt: Integer;
+    usedArea: Integer;
+    totalArea: Integer;
+    r: Rectangle;
+    atlasW, atlasH: Integer;
+    unused: Size;
+begin
+    if sprite = nil then
+        exit;
+
+    deleteAt:= -1;
+    for i:= 0 to pred(MaxAtlases) do
+    begin
+        if sprite^.atlas <> @Info[i].TextureInfo then
+            continue;
+
+        usedArea:= 0;
+        for j:=0 to pred(Info[i].PackerInfo.usedRectangles.count) do
+        begin
+            r:= Info[i].PackerInfo.usedRectangles.data[j];
+            if r.UserData = sprite then
+                deleteAt:= j
+            else
+                inc(usedArea, r.width * r.height);
+        end;
+
+        rectangleListRemoveAt(Info[i].PackerInfo.usedRectangles, deleteAt);
+        dispose(sprite);
+
+        while true do
+        begin
+            atlasW:= Info[i].PackerInfo.width;
+            atlasH:= Info[i].PackerInfo.height;
+            totalArea:=  atlasW * atlasH;
+            if usedArea >= totalArea * CompressionThreshold then
+                exit;
+
+            if (atlasW = MinTexSize) and (atlasH = MinTexSize) then
+                exit; // we could try to move everything from this to another atlas here
+
+            CompactSize(atlasW, atlasH);
+            unused:= unused;
+            TryRepack(Info[i], atlasW, atlasH, false, unused);
+        end;
+
+        exit;
+    end;
+end;
+
+procedure initModule;
+var
+    i: Integer;
+begin
+    DumpID:=0;
+    for i:= 0 to pred(MaxAtlases) do
+        Info[i].Allocated:= false;
+end;
+
+end.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uBinPacker.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -0,0 +1,438 @@
+unit uBinPacker;
+
+interface
+
+// implements a maxrects packer with best short side fit heuristic
+
+type Rectangle = record
+    x, y, width, height: LongInt;
+    UserData: Pointer;
+end;
+
+type Size = record
+    width, height: LongInt;
+    UserData: Pointer;
+end;
+
+type PRectangle = ^Rectangle;
+type PSize = ^Size;
+
+type RectangleList = record
+    data: PRectangle;
+    count: LongInt;
+    size: LongInt;
+end;
+
+type SizeList = record
+    data: PSize;
+    count: LongInt;
+    size: LongInt;
+end;
+
+type Atlas = record
+    width, height: Longint;
+    freeRectangles: RectangleList;
+    usedRectangles: RectangleList;
+end;
+
+function atlasInsertAdaptive(var a: Atlas; sz: Size; var output: Rectangle): boolean;
+function atlasInsertSet(var a: Atlas; var input: SizeList; var outputs: RectangleList): boolean;
+function atlasNew(width, height: LongInt): Atlas;
+procedure atlasDelete(var a: Atlas);
+procedure atlasReset(var a: Atlas);
+
+procedure rectangleListInit(var list: RectangleList);
+procedure rectangleListRemoveAt(var list: RectangleList; index: LongInt);
+procedure rectangleListAdd(var list: RectangleList; r: Rectangle);
+procedure rectangleListClear(var list: RectangleList);
+procedure sizeListInit(var list: SizeList);
+procedure sizeListRemoveAt(var list: SizeList; index: LongInt);
+procedure sizeListAdd(var list: SizeList; s: Size); overload;
+procedure sizeListAdd(var list: SizeList; width, height: LongInt; UserData: Pointer); overload;
+procedure sizeListClear(var list: SizeList);
+
+implementation
+
+uses Math; // for min/max
+
+procedure rectangleListRemoveAt(var list: RectangleList; index: LongInt);
+var
+    i: Integer;
+begin
+    i:=index;
+    while (i + 1 < list.count) do
+    begin
+        list.data[i]:=list.data[i + 1];
+        inc(i);
+    end;
+    dec(list.count);
+end;
+
+procedure rectangleListAdd(var list: RectangleList; r: Rectangle);
+begin
+    if list.count >= list.size then
+    begin
+        inc(list.size, 512);
+        ReAllocMem(list.data, sizeof(Rectangle) * list.size);
+    end;
+    list.data[list.count]:=r;
+    inc(list.count);
+end;
+
+procedure rectangleListInit(var list: RectangleList);
+begin
+    list.data:= nil;
+    list.count:= 0;
+    list.size:= 0;
+end;
+
+procedure rectangleListClear(var list: RectangleList);
+begin
+    FreeMem(list.data);
+    list.count:= 0;
+    list.size:= 0;
+end;
+
+procedure sizeListRemoveAt(var list: SizeList; index: LongInt);
+begin
+    list.data[index]:= list.data[list.count - 1];
+    dec(list.count);
+end;
+
+procedure sizeListAdd(var list: SizeList; s: Size); overload;
+begin
+    if list.count >= list.size then
+    begin
+        inc(list.size, 512);
+        ReAllocMem(list.data, sizeof(Size) * list.size);
+    end;
+    list.data[list.count]:=s;
+    inc(list.count);
+end;
+
+procedure sizeListAdd(var list: SizeList; width, height: LongInt; UserData: Pointer); overload;
+var
+    sz: Size;
+begin
+    sz.width:= width;
+    sz.height:= height;
+    sz.UserData:= UserData;
+    sizeListAdd(list, sz);
+end;
+
+procedure sizeListInit(var list: SizeList);
+begin
+    list.data:= nil;
+    list.count:= 0;
+    list.size:= 0;
+end;
+
+procedure sizeListClear(var list: SizeList);
+begin
+    FreeMem(list.data);
+    list.count:= 0;
+    list.size:= 0;
+end;
+
+
+function isContainedIn(a, b: Rectangle): boolean;
+begin
+    isContainedIn:= (a.x >= b.x) and (a.y >= b.y)
+                and (a.x + a.width <= b.x + b.width)
+                and (a.y + a.height <= b.y + b.height);
+end;
+
+function findPositionForNewNodeBestShortSideFit(var list: RectangleList; width, height: LongInt; 
+     var bestShortSideFit, bestLongSideFit: LongInt): Rectangle;
+var
+    bestNode: Rectangle;
+    i: Integer;
+    ri: Rectangle;
+    leftoverHoriz, leftoverVert, shortSideFit, longSideFit: Longint;
+begin
+    bestNode.x:= 0;
+    bestNode.y:= 0;
+    bestNode.width:= 0;
+    bestNode.height:= 0;
+    bestShortSideFit:= $7FFFFFFF;
+
+    for i:=0 to pred(list.count) do
+    begin
+        ri:= list.data[i];
+
+        // Try to place the rectangle in upright (non-flipped) orientation.
+        if (ri.width >= width) and (ri.height >= height) then
+        begin
+            leftoverHoriz:= Abs(ri.width - width);
+            leftoverVert:= Abs(ri.height - height);
+            shortSideFit:= Min(leftoverHoriz, leftoverVert);
+            longSideFit:= Max(leftoverHoriz, leftoverVert);
+
+            if (shortSideFit < bestShortSideFit) or
+              ((shortSideFit = bestShortSideFit) and (longSideFit < bestLongSideFit)) then
+            begin
+                bestNode.x:= ri.x;
+                bestNode.y:= ri.y;
+                bestNode.width:= width;
+                bestNode.height:= height;
+                bestShortSideFit:= shortSideFit;
+                bestLongSideFit:= longSideFit;
+            end;
+        end;
+
+        if (ri.width >= height) and (ri.height >= width) then
+        begin
+            leftoverHoriz:= Abs(ri.width - height);
+            leftoverVert:= Abs(ri.height - width);
+            shortSideFit:= Min(leftoverHoriz, leftoverVert);
+            longSideFit:= Max(leftoverHoriz, leftoverVert);
+
+            if (shortSideFit < bestShortSideFit) or
+              ((shortSideFit = bestShortSideFit) and (longSideFit < bestLongSideFit)) then
+            begin
+                bestNode.x:= ri.x;
+                bestNode.y:= ri.y;
+                bestNode.width:= height;
+                bestNode.height:= width;
+                bestShortSideFit:= shortSideFit;
+                bestLongSideFit:= longSideFit;
+            end;
+        end;
+    end;
+
+    findPositionForNewNodeBestShortSideFit:= bestNode;
+end;
+
+function scoreRect(var list: RectangleList; width, height: LongInt; var score1, score2: LongInt): Rectangle;
+var
+    newNode: Rectangle;
+begin
+    newNode:= findPositionForNewNodeBestShortSideFit(list, width, height, score1, score2);
+
+    // Cannot fit the current rectangle.
+    if newNode.height = 0 then
+    begin
+        score1:= $7FFFFFFF;
+        score2:= $7FFFFFFF;
+    end;
+
+    scoreRect:= newNode;
+end;
+
+function splitFreeNode(var freeRectangles: RectangleList; freeNode, usedNode: Rectangle): boolean;
+var
+    newNode: Rectangle;
+begin
+    // Test with SAT if the rectangles even intersect.
+    if (usedNode.x >= freeNode.x + freeNode.width) or (usedNode.x + usedNode.width <= freeNode.x) or
+       (usedNode.y >= freeNode.y + freeNode.height) or (usedNode.y + usedNode.height <= freeNode.y) then
+    begin
+        splitFreeNode:=false;
+        exit;
+    end;
+
+    if (usedNode.x < freeNode.x + freeNode.width) and (usedNode.x + usedNode.width > freeNode.x) then
+    begin
+        // New node at the top side of the used node.
+        if (usedNode.y > freeNode.y) and (usedNode.y < freeNode.y + freeNode.height) then
+        begin
+            newNode:= freeNode;
+            newNode.height:= usedNode.y - newNode.y;
+            rectangleListAdd(freeRectangles, newNode);
+        end;
+
+        // New node at the bottom side of the used node.
+        if (usedNode.y + usedNode.height < freeNode.y + freeNode.height) then
+        begin
+            newNode:= freeNode;
+            newNode.y:= usedNode.y + usedNode.height;
+            newNode.height:= freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
+            rectangleListAdd(freeRectangles, newNode);
+        end;
+    end;
+
+    if (usedNode.y < freeNode.y + freeNode.height) and (usedNode.y + usedNode.height > freeNode.y) then
+    begin
+        // New node at the left side of the used node.
+        if (usedNode.x > freeNode.x) and (usedNode.y < freeNode.y + freeNode.width) then
+        begin
+            newNode:= freeNode;
+            newNode.width:= usedNode.x - newNode.x;
+            rectangleListAdd(freeRectangles, newNode);
+        end;
+
+        // New node at the right side of the used node.
+        if (usedNode.x + usedNode.width < freeNode.x + freeNode.width) then
+        begin
+            newNode:= freeNode;
+            newNode.x:= usedNode.x + usedNode.width;
+            newNode.width:= freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
+            rectangleListAdd(freeRectangles, newNode);
+        end;
+  end;
+
+  splitFreeNode:= true;
+end;
+
+procedure pruneFreeList(var freeRectangles: RectangleList);
+var
+  i, j: LongInt;
+begin
+    // Go through each pair and remove any rectangle that is redundant.
+    i:= 0;
+    while i < freeRectangles.count do
+    begin
+        j:= i + 1;
+        while j < freeRectangles.count do
+        begin  
+            if (isContainedIn(freeRectangles.data[i], freeRectangles.data[j])) then
+            begin
+                rectangleListRemoveAt(freeRectangles, i);
+                dec(i);
+                break;
+            end;
+
+            if (isContainedIn(freeRectangles.data[j], freeRectangles.data[i])) then
+                rectangleListRemoveAt(freeRectangles, j)
+            else
+                inc(j);
+        end;
+        inc(i);
+    end;
+end;
+
+function atlasInsertAdaptive(var a: Atlas; sz: Size; var output: Rectangle): boolean;
+var
+    newNode: Rectangle;
+    score1, score2: LongInt;
+    numRectanglesToProcess: LongInt;
+    i: LongInt;
+begin
+    newNode:= findPositionForNewNodeBestShortSideFit(a.freeRectangles, sz.width, sz.height, score1, score2);
+    if newNode.height = 0 then
+    begin
+        output:= newNode;
+        output.UserData:= nil;
+        atlasInsertAdaptive:= false;
+        exit;
+    end;
+
+    numRectanglesToProcess:= a.freeRectangles.count;
+
+    i:=0;
+    while i < numRectanglesToProcess do
+    begin
+        if splitFreeNode(a.freeRectangles, a.freeRectangles.data[i], newNode) then
+        begin
+            rectangleListRemoveAt(a.freeRectangles, i);
+            dec(numRectanglesToProcess);
+        end
+        else
+            inc(i);
+    end;
+    
+    pruneFreeList(a.freeRectangles);
+    newNode.UserData:= sz.UserData;
+    rectangleListAdd(a.usedRectangles, newNode);
+    output:= newNode;
+    atlasInsertAdaptive:= true;
+end;
+
+procedure placeRect(var a: Atlas; node: Rectangle);
+var
+    numRectanglesToProcess: LongInt;
+    i: LongInt;
+begin
+    numRectanglesToProcess:= a.freeRectangles.Count;
+
+    i:= 0;
+    while i < numRectanglesToProcess do
+    begin
+        if not splitFreeNode(a.freeRectangles, a.freeRectangles.data[i], node) then
+            inc(i)
+        else
+        begin
+            rectangleListRemoveAt(a.freeRectangles, i);
+            dec(numRectanglesToProcess);
+        end;
+    end;
+
+    pruneFreeList(a.freeRectangles);
+    rectangleListAdd(a.usedRectangles, node);
+end;
+
+
+function atlasInsertSet(var a: Atlas; var input: SizeList; var outputs: RectangleList): boolean;
+var
+    bestScore1, bestScore2, bestRectIndex: LongInt;
+    score1, score2: LongInt;
+    bestNode, newNode: Rectangle;
+    i: LongInt;
+    sz: Size;
+begin
+    atlasInsertSet:= false;
+
+    while input.count > 0 do
+    begin
+        bestScore1:= $7FFFFFFF;
+        bestScore2:= $7FFFFFFF;
+        bestRectIndex:= -1;
+    
+        for i:=0 to pred(input.count) do
+        begin
+            sz:= input.data[i];
+            newNode:= scoreRect(a.freeRectangles, sz.width, sz.height, score1, score2);
+
+            if (score1 >= bestScore1) and ((score1 <> bestScore1) or (score2 >= bestScore2)) then
+                continue;
+
+            bestScore1:= score1;
+            bestScore2:= score2;
+            bestNode:= newNode;
+            bestRectIndex:= i;
+        end;
+
+        if bestRectIndex = -1 then
+            exit;
+
+        bestNode.UserData:= input.data[bestRectIndex].UserData;
+        placeRect(a, bestNode);
+        rectangleListAdd(outputs, bestNode);
+        sizeListRemoveAt(input, bestRectIndex);
+    end;
+    atlasInsertSet:= true;
+end;
+
+function atlasNew(width, height: LongInt): Atlas;
+var
+    a: Atlas;
+    r: Rectangle;
+begin
+    rectangleListInit(a.freeRectangles);
+    rectangleListInit(a.usedRectangles);
+
+    a.width:= width;
+    a.height:= height;
+    r.x:= 0;
+    r.y:= 0;
+    r.width:= width;
+    r.height:= height;
+    rectangleListAdd(a.freeRectangles, r);
+
+    atlasNew:=a;
+end;
+
+procedure atlasDelete(var a: atlas);
+begin
+    rectangleListClear(a.freeRectangles);
+    rectangleListClear(a.usedRectangles);
+end;
+
+procedure atlasReset(var a: atlas);
+begin
+    atlasDelete(a);
+    a:=atlasNew(a.width, a.height);
+end;
+
+begin
+end.
--- a/hedgewars/uChat.pas	Mon Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/uChat.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -31,7 +31,7 @@
 procedure KeyPressChat(Key: Longword);
 
 implementation
-uses SDLh, uInputHandler, uTypes, uVariables, uCommands, uUtils, uTextures, uRender, uIO;
+uses SDLh, uInputHandler, uTypes, uVariables, uCommands, uUtils, uTextures, uRender, uStore, uIO;
 
 const MaxStrIndex = 27;
 
@@ -96,9 +96,7 @@
 SDL_FreeSurface(strSurface);
 
 cl.Time:= RealTicks + 12500;
-cl.Tex:= Surface2Tex(resSurface, false);
-
-SDL_FreeSurface(resSurface)
+cl.Tex:= Surface2Atlas(resSurface, false);
 end;
 
 // For uStore texture recreation
--- a/hedgewars/uGearsRender.pas	Mon Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/uGearsRender.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -37,7 +37,7 @@
                 end;
 
 implementation
-uses uRender, uUtils, uVariables, uAmmos, Math, uVisualGears;
+uses uRender, uStore, uUtils, uVariables, uAmmos, Math, uVisualGears;
 
 procedure DrawRopeLinesRQ(Gear: PGear);
 begin
@@ -54,20 +54,18 @@
     glDisable(GL_TEXTURE_2D);
     //glEnable(GL_LINE_SMOOTH);
 
-    glPushMatrix;
-
-    glTranslatef(WorldDx, WorldDy, 0);
+    ResetRotation;
+    SetOffset(WorldDx, WorldDy);
+    UpdateModelview;
 
     glLineWidth(4.0);
 
     Tint($C0, $C0, $C0, $FF);
 
-    glVertexPointer(2, GL_FLOAT, 0, @RopePoints.rounded[0]);
+    SetVertexPointer(@RopePoints.rounded[0]);
     glDrawArrays(GL_LINE_STRIP, 0, RopePoints.Count + 2);
     Tint($FF, $FF, $FF, $FF);
 
-    glPopMatrix;
-
     glEnable(GL_TEXTURE_2D);
     //glDisable(GL_LINE_SMOOTH)
     end
--- a/hedgewars/uLandTexture.pas	Mon Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/uLandTexture.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -99,7 +99,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
@@ -156,7 +156,7 @@
                     if not isEmpty then
                         begin
                         if tex = nil then tex:= NewTexture(TEXSIZE, TEXSIZE, Pixels(x, y));
-                        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
                     else if tex <> nil then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hedgewars/uMatrix.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -0,0 +1,123 @@
+(*
+ * Hedgewars, a free turn based strategy game
+ * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *)
+
+{$INCLUDE "options.inc"}
+
+unit uMatrix;
+
+interface
+
+uses uTypes, gl;
+
+procedure MatrixLoadIdentity(out Result: TMatrix4x4f);
+procedure MatrixMultiply(out Result: TMatrix4x4f; const lhs, rhs: TMatrix4x4f);
+
+implementation
+
+procedure MatrixLoadIdentity(out Result: TMatrix4x4f);
+begin
+    Result[0,0]:= 1.0; Result[1,0]:=0.0; Result[2,0]:=0.0; Result[3,0]:=0.0;
+    Result[0,1]:= 0.0; Result[1,1]:=1.0; Result[2,1]:=0.0; Result[3,1]:=0.0;
+    Result[0,2]:= 0.0; Result[1,2]:=0.0; Result[2,2]:=1.0; Result[3,2]:=0.0;
+    Result[0,3]:= 0.0; Result[1,3]:=0.0; Result[2,3]:=0.0; Result[3,3]:=1.0;
+end;
+
+procedure MatrixMultiply(out Result: TMatrix4x4f; const lhs, rhs: TMatrix4x4f);
+var
+    test: TMatrix4x4f;
+    i, j: Integer;
+    error: boolean;
+begin
+    Result[0,0]:=lhs[0,0]*rhs[0,0] + lhs[1,0]*rhs[0,1] + lhs[2,0]*rhs[0,2] + lhs[3,0]*rhs[0,3];
+    Result[0,1]:=lhs[0,1]*rhs[0,0] + lhs[1,1]*rhs[0,1] + lhs[2,1]*rhs[0,2] + lhs[3,1]*rhs[0,3];
+    Result[0,2]:=lhs[0,2]*rhs[0,0] + lhs[1,2]*rhs[0,1] + lhs[2,2]*rhs[0,2] + lhs[3,2]*rhs[0,3];
+    Result[0,3]:=lhs[0,3]*rhs[0,0] + lhs[1,3]*rhs[0,1] + lhs[2,3]*rhs[0,2] + lhs[3,3]*rhs[0,3];
+
+    Result[1,0]:=lhs[0,0]*rhs[1,0] + lhs[1,0]*rhs[1,1] + lhs[2,0]*rhs[1,2] + lhs[3,0]*rhs[1,3];
+    Result[1,1]:=lhs[0,1]*rhs[1,0] + lhs[1,1]*rhs[1,1] + lhs[2,1]*rhs[1,2] + lhs[3,1]*rhs[1,3];
+    Result[1,2]:=lhs[0,2]*rhs[1,0] + lhs[1,2]*rhs[1,1] + lhs[2,2]*rhs[1,2] + lhs[3,2]*rhs[1,3];
+    Result[1,3]:=lhs[0,3]*rhs[1,0] + lhs[1,3]*rhs[1,1] + lhs[2,3]*rhs[1,2] + lhs[3,3]*rhs[1,3];
+
+    Result[2,0]:=lhs[0,0]*rhs[2,0] + lhs[1,0]*rhs[2,1] + lhs[2,0]*rhs[2,2] + lhs[3,0]*rhs[2,3];
+    Result[2,1]:=lhs[0,1]*rhs[2,0] + lhs[1,1]*rhs[2,1] + lhs[2,1]*rhs[2,2] + lhs[3,1]*rhs[2,3];
+    Result[2,2]:=lhs[0,2]*rhs[2,0] + lhs[1,2]*rhs[2,1] + lhs[2,2]*rhs[2,2] + lhs[3,2]*rhs[2,3];
+    Result[2,3]:=lhs[0,3]*rhs[2,0] + lhs[1,3]*rhs[2,1] + lhs[2,3]*rhs[2,2] + lhs[3,3]*rhs[2,3];
+
+    Result[3,0]:=lhs[0,0]*rhs[3,0] + lhs[1,0]*rhs[3,1] + lhs[2,0]*rhs[3,2] + lhs[3,0]*rhs[3,3];
+    Result[3,1]:=lhs[0,1]*rhs[3,0] + lhs[1,1]*rhs[3,1] + lhs[2,1]*rhs[3,2] + lhs[3,1]*rhs[3,3];
+    Result[3,2]:=lhs[0,2]*rhs[3,0] + lhs[1,2]*rhs[3,1] + lhs[2,2]*rhs[3,2] + lhs[3,2]*rhs[3,3];
+    Result[3,3]:=lhs[0,3]*rhs[3,0] + lhs[1,3]*rhs[3,1] + lhs[2,3]*rhs[3,2] + lhs[3,3]*rhs[3,3];
+
+{
+    Result[0,0]:=lhs[0,0]*rhs[0,0] + lhs[1,0]*rhs[0,1] + lhs[2,0]*rhs[0,2] + lhs[3,0]*rhs[0,3];
+    Result[0,1]:=lhs[0,0]*rhs[1,0] + lhs[1,0]*rhs[1,1] + lhs[2,0]*rhs[1,2] + lhs[3,0]*rhs[1,3];
+    Result[0,2]:=lhs[0,0]*rhs[2,0] + lhs[1,0]*rhs[2,1] + lhs[2,0]*rhs[2,2] + lhs[3,0]*rhs[2,3];
+    Result[0,3]:=lhs[0,0]*rhs[3,0] + lhs[1,0]*rhs[3,1] + lhs[2,0]*rhs[3,2] + lhs[3,0]*rhs[3,3];
+  
+    Result[1,0]:=lhs[0,1]*rhs[0,0] + lhs[1,1]*rhs[0,1] + lhs[2,1]*rhs[0,2] + lhs[3,1]*rhs[0,3];
+    Result[1,1]:=lhs[0,1]*rhs[1,0] + lhs[1,1]*rhs[1,1] + lhs[2,1]*rhs[1,2] + lhs[3,1]*rhs[1,3];
+    Result[1,2]:=lhs[0,1]*rhs[2,0] + lhs[1,1]*rhs[2,1] + lhs[2,1]*rhs[2,2] + lhs[3,1]*rhs[2,3];
+    Result[1,3]:=lhs[0,1]*rhs[3,0] + lhs[1,1]*rhs[3,1] + lhs[2,1]*rhs[3,2] + lhs[3,1]*rhs[3,3];
+
+    Result[2,0]:=lhs[0,2]*rhs[0,0] + lhs[1,2]*rhs[0,1] + lhs[2,2]*rhs[0,2] + lhs[3,2]*rhs[0,3];
+    Result[2,1]:=lhs[0,2]*rhs[1,0] + lhs[1,2]*rhs[1,1] + lhs[2,2]*rhs[1,2] + lhs[3,2]*rhs[1,3];
+    Result[2,2]:=lhs[0,2]*rhs[2,0] + lhs[1,2]*rhs[2,1] + lhs[2,2]*rhs[2,2] + lhs[3,2]*rhs[2,3];
+    Result[2,3]:=lhs[0,2]*rhs[3,0] + lhs[1,2]*rhs[3,1] + lhs[2,2]*rhs[3,2] + lhs[3,2]*rhs[3,3];
+
+    Result[3,0]:=lhs[0,3]*rhs[0,0] + lhs[1,3]*rhs[0,1] + lhs[2,3]*rhs[0,2] + lhs[3,3]*rhs[0,3];
+    Result[3,1]:=lhs[0,3]*rhs[1,0] + lhs[1,3]*rhs[1,1] + lhs[2,3]*rhs[1,2] + lhs[3,3]*rhs[1,3];
+    Result[3,2]:=lhs[0,3]*rhs[2,0] + lhs[1,3]*rhs[2,1] + lhs[2,3]*rhs[2,2] + lhs[3,3]*rhs[2,3];
+    Result[3,3]:=lhs[0,3]*rhs[3,0] + lhs[1,3]*rhs[3,1] + lhs[2,3]*rhs[3,2] + lhs[3,3]*rhs[3,3];
+}
+    glPushMatrix;
+    glLoadMatrixf(@lhs[0, 0]);
+    glMultMatrixf(@rhs[0, 0]);
+    glGetFloatv(GL_MODELVIEW_MATRIX, @test[0, 0]);
+    glPopMatrix;
+
+    error:=false;
+    for i:=0 to 3 do
+      for j:=0 to 3 do
+        if Abs(test[i, j] - Result[i, j]) > 0.000001 then
+          error:=true;
+
+    if error then
+    begin
+        writeln('shall:');
+        for i:=0 to 3 do
+        begin
+          for j:=0 to 3 do
+            write(test[i, j]);
+          writeln;
+        end;
+
+        writeln('is:');
+        for i:=0 to 3 do
+        begin
+          for j:=0 to 3 do
+            write(Result[i, j]);
+          writeln;
+        end;
+        halt(0);
+    end;
+
+
+end;
+
+
+end.
--- a/hedgewars/uRender.pas	Mon Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/uRender.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -22,7 +22,7 @@
 
 interface
 
-uses SDLh, uTypes, GLunit, uConsts;
+uses SDLh, uTypes, GLunit, uConsts, uTextures, math;
 
 procedure DrawSprite            (Sprite: TSprite; X, Y, Frame: LongInt);
 procedure DrawSprite            (Sprite: TSprite; X, Y, FrameX, FrameY: LongInt);
@@ -48,14 +48,78 @@
 procedure DrawHedgehog          (X, Y: LongInt; Dir: LongInt; Pos, Step: LongWord; Angle: real);
 procedure DrawScreenWidget      (widget: POnScreenWidget);
 
-procedure Tint                  (r, g, b, a: Byte); inline;
-procedure Tint                  (c: Longword); inline;
+// This is just temporary and becomes non public once everything changed to GL2
+procedure UpdateModelview;
+procedure ResetModelview;
+procedure SetOffset(X, Y: Longint);
+procedure ResetRotation;
 
 
 implementation
-uses uVariables;
+uses uVariables, uStore;
+
+const DegToRad =  0.01745329252; // 2PI / 360
+
+procedure UpdateModelview;
+begin
+{$IFDEF GL2}
+    UpdateModelviewProjection;
+{$ELSE}
+    glLoadMatrixf(@mModelview[0,0]);
+{$ENDIF}
+end;
+
+procedure ResetModelview;
+begin
+    mModelview[0,0]:= 1.0; mModelview[1,0]:=0.0; mModelview[3,0]:= 0;
+    mModelview[0,1]:= 0.0; mModelview[1,1]:=1.0; mModelview[3,1]:= 0;
+    UpdateModelview;
+end;
+
+procedure SetOffset(X, Y: Longint);
+begin
+    mModelview[3,0]:= X;
+    mModelview[3,1]:= Y;
+end;
+
+procedure AddOffset(X, Y: GLfloat); // probably want to refactor this to use integers
+begin
+    mModelview[3,0]:=mModelview[3,0] + mModelview[0,0]*X + mModelview[1,0]*Y;
+    mModelview[3,1]:=mModelview[3,1] + mModelview[0,1]*X + mModelview[1,1]*Y;
+end;
 
-var LastTint: LongWord = 0;
+procedure SetScale(Scale: GLfloat);
+begin
+    mModelview[0,0]:= Scale;
+    mModelview[1,1]:= Scale;
+end;
+
+procedure AddScale(Scale: GLfloat);
+begin
+    mModelview[0,0]:= mModelview[0,0]*Scale; mModelview[1,0]:= mModelview[1,0]*Scale;
+    mModelview[0,1]:= mModelview[0,1]*Scale; mModelview[1,1]:= mModelview[1,1]*Scale;
+end;
+
+procedure AddScale(X, Y: GLfloat);
+begin
+    mModelview[0,0]:= mModelview[0,0]*X; mModelview[1,0]:= mModelview[1,0]*Y;
+    mModelview[0,1]:= mModelview[0,1]*X; mModelview[1,1]:= mModelview[1,1]*Y;
+end;
+
+
+procedure SetRotation(Angle, ZAxis: GLfloat);
+var s, c: Extended;
+begin
+    SinCos(Angle*DegToRad, s, c);
+    mModelview[0,0]:= c;       mModelview[1,0]:=-s*ZAxis;
+    mModelview[0,1]:= s*ZAxis; mModelview[1,1]:= c;
+end;
+
+procedure ResetRotation;
+begin
+    mModelview[0,0]:= 1.0; mModelview[1,0]:=0.0;
+    mModelview[0,1]:= 0.0; mModelview[1,1]:=1.0;
+end;
 
 procedure DrawSpriteFromRect(Sprite: TSprite; r: TSDL_Rect; X, Y, Height, Position: LongInt);
 begin
@@ -71,8 +135,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 +151,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,17 +164,8 @@
 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]);
+SetVertexPointer(@VertexBuffer[0]);
+SetTexCoordPointer(@TextureBuffer[0]);
 glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
 end;
 
@@ -125,18 +176,17 @@
 
 procedure DrawTexture(X, Y: LongInt; Texture: PTexture; Scale: GLfloat);
 begin
-
-glPushMatrix;
-glTranslatef(X, Y, 0);
-glScalef(Scale, Scale, 1);
+SetOffset(X, Y);
+ResetRotation;
+SetScale(Scale);
+UpdateModelview;
 
-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);
+SetVertexPointer(@Texture^.vb);
+SetTexCoordPointer(@Texture^.tb);
 glDrawArrays(GL_TRIANGLE_FAN, 0, Length(Texture^.vb));
-
-glPopMatrix
+ResetModelview;
 end;
 
 procedure DrawTextureF(Texture: PTexture; Scale: GLfloat; X, Y, Frame, Dir, w, h: LongInt);
@@ -145,8 +195,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)
@@ -155,14 +205,13 @@
 if (abs(Y) > H) and ((abs(Y + OffsetY - (0.5 * cScreenHeight)) - W / 2) * cScaleFactor > cScreenHeight) then
     exit;
 
-glPushMatrix;
-glTranslatef(X, Y, 0);
+SetOffset(X, Y);
 if Dir = 0 then Dir:= 1;
 
-glRotatef(Angle, 0, 0, Dir);
-
-glTranslatef(Dir*OffsetX, OffsetY, 0);
-glScalef(Scale, Scale, 1);
+SetRotation(Angle, Dir);
+AddOffset(Dir*OffsetX, OffsetY);
+AddScale(Scale);
+UpdateModelview;
 
 // Any reason for this call? And why only in t direction, not s?
 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
@@ -172,12 +221,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,20 +238,11 @@
 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]);
+SetVertexPointer(@VertexBuffer[0]);
+SetTexCoordPointer(@TextureBuffer[0]);
 glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
 
-glPopMatrix
+ResetModelview;
 end;
 
 procedure DrawSpriteRotated(Sprite: TSprite; X, Y, Dir: LongInt; Angle: real);
@@ -214,19 +255,18 @@
 
 procedure DrawSpriteRotatedF(Sprite: TSprite; X, Y, Frame, Dir: LongInt; Angle: real);
 begin
-glPushMatrix;
-glTranslatef(X, Y, 0);
-
+SetOffset(X, Y);
 if Dir < 0 then
-    glRotatef(Angle, 0, 0, -1)
+    SetRotation(Angle, -1.0)
 else
-    glRotatef(Angle, 0, 0,  1);
+    SetRotation(Angle, 1.0);
 if Dir < 0 then
-    glScalef(-1.0, 1.0, 1.0);
+    AddScale(-1.0, 1.0);
+UpdateModelview;
 
 DrawSprite(Sprite, -SpritesData[Sprite].Width div 2, -SpritesData[Sprite].Height div 2, Frame);
 
-glPopMatrix
+ResetModelview;
 end;
 
 procedure DrawTextureRotated(Texture: PTexture; hw, hh, X, Y, Dir: LongInt; Angle: real);
@@ -238,19 +278,18 @@
 if (abs(Y) > 2 * hh) and ((abs(Y - 0.5 * cScreenHeight) - hh) > cScreenHeight / cScaleFactor) then
     exit;
 
-glPushMatrix;
-glTranslatef(X, Y, 0);
+SetOffset(X, Y);
 
 if Dir < 0 then
     begin
     hw:= - hw;
-    glRotatef(Angle, 0, 0, -1);
+    SetRotation(Angle, -1.0);
     end
 else
-    glRotatef(Angle, 0, 0,  1);
+    SetRotation(Angle, 1.0);
+UpdateModelview;
 
-
-glBindTexture(GL_TEXTURE_2D, Texture^.id);
+glBindTexture(GL_TEXTURE_2D, Texture^.atlas^.id);
 
 VertexBuffer[0].X:= -hw;
 VertexBuffer[0].Y:= -hh;
@@ -261,11 +300,11 @@
 VertexBuffer[3].X:= -hw;
 VertexBuffer[3].Y:= hh;
 
-glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
-glTexCoordPointer(2, GL_FLOAT, 0, @Texture^.tb);
+SetVertexPointer(@VertexBuffer[0]);
+SetTexCoordPointer(@Texture^.tb);
 glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
 
-glPopMatrix
+ResetModelview;
 end;
 
 procedure DrawSprite(Sprite: TSprite; X, Y, Frame: LongInt);
@@ -329,8 +368,9 @@
     glDisable(GL_TEXTURE_2D);
     glEnable(GL_LINE_SMOOTH);
 
-    glPushMatrix;
-    glTranslatef(WorldDx, WorldDy, 0);
+    ResetRotation;
+    SetOffset(WorldDx, WorldDy);
+    UpdateModelview;
     glLineWidth(Width);
 
     Tint(r, g, b, a);
@@ -339,11 +379,11 @@
     VertexBuffer[1].X:= X1;
     VertexBuffer[1].Y:= Y1;
 
-    glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
+    SetVertexPointer(@VertexBuffer[0]);
     glDrawArrays(GL_LINES, 0, Length(VertexBuffer));
     Tint($FF, $FF, $FF, $FF);
     
-    glPopMatrix;
+    ResetModelview;
     
     glEnable(GL_TEXTURE_2D);
     glDisable(GL_LINE_SMOOTH);
@@ -352,6 +392,10 @@
 procedure DrawFillRect(r: TSDL_Rect);
 var VertexBuffer: array [0..3] of TVertex2f;
 begin
+SetOffset(0, 0);
+ResetRotation;
+UpdateModelview;
+
 // do not draw anything outside the visible screen space (first check fixes some sprite drawing, e.g. hedgehogs)
 if (abs(r.x) > r.w) and ((abs(r.x + r.w / 2) - r.w / 2) * cScaleFactor > cScreenWidth) then
     exit;
@@ -371,11 +415,13 @@
 VertexBuffer[3].X:= r.x;
 VertexBuffer[3].Y:= r.y + r.h;
 
-glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
+SetVertexPointer(@VertexBuffer[0]);
 glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
 
 Tint($FF, $FF, $FF, $FF);
-glEnable(GL_TEXTURE_2D)
+glEnable(GL_TEXTURE_2D);
+
+ResetModelview;
 end;
 
 procedure DrawCircle(X, Y, Radius, Width: LongInt; r, g, b, a: Byte); 
@@ -396,23 +442,29 @@
     end;
     glDisable(GL_TEXTURE_2D);
     glEnable(GL_LINE_SMOOTH);
-    glPushMatrix;
+    SetOffset(0, 0);
+    ResetRotation;
+    UpdateModelview;
     glLineWidth(Width);
-    glVertexPointer(2, GL_FLOAT, 0, @CircleVertex[0]);
+    SetVertexPointer(@CircleVertex[0]);
     glDrawArrays(GL_LINE_LOOP, 0, 60);
-    glPopMatrix;
     glEnable(GL_TEXTURE_2D);
     glDisable(GL_LINE_SMOOTH);
+    ResetModelview;
 end;
 
 
 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,41 +473,25 @@
     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;
+    r.x:=Step * 32;
+    r.y:=Pos * 32;
+    r.w:=32;
+    r.h:=32;
+    ComputeTexcoords(HHTexture, @r, @TextureBuffer);
 
-    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;
-
+    SetOffset(X, Y);
+    SetRotation(Angle, 1.0);
+    UpdateModelview;
 
-    glPushMatrix();
-    glTranslatef(X, Y, 0);
-    glRotatef(Angle, 0, 0, 1);
-
-    glBindTexture(GL_TEXTURE_2D, HHTexture^.id);
+    glBindTexture(GL_TEXTURE_2D, HHTexture^.atlas^.id);
+    if Dir = -1 then
+        SetVertexPointer(@VertexBuffers[1][0])
+    else
+        SetVertexPointer(@VertexBuffers[0][0]);
+    SetTexCoordPointer(@TextureBuffer[0]);
+    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
-    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));
-
-    glPopMatrix
+    ResetModelview;
 end;
 
 procedure DrawScreenWidget(widget: POnScreenWidget);
@@ -505,31 +541,4 @@
 {$ENDIF}
 end;
 
-procedure Tint(r, g, b, a: Byte); inline;
-var nc, tw: Longword;
-begin
-    nc:= (a shl 24) or (b shl 16) or (g shl 8) or r;
-
-    if nc = lastTint then
-        exit;
-
-    if GrayScale then
-        begin
-        tw:= round(r * RGB_LUMINANCE_RED + g * RGB_LUMINANCE_GREEN + b * RGB_LUMINANCE_BLUE);
-        if tw > 255 then
-            tw:= 255;
-        r:= tw;
-        g:= tw;
-        b:= tw
-        end;
-
-    glColor4ub(r, g, b, a);
-    lastTint:= nc;
-end;
-
-procedure Tint(c: Longword); inline;
-begin
-    Tint(((c shr 24) and $FF), ((c shr 16) and $FF), (c shr 8) and $FF, (c and $FF))
-end;
-
 end.
--- a/hedgewars/uRenderUtils.pas	Mon Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/uRenderUtils.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -272,9 +272,7 @@
 
     TryDo(SDL_SetColorKey(finalSurface, SDL_SRCCOLORKEY, 0) = 0, errmsgTransparentSet, true);
 
-    RenderStringTexLim:= Surface2Tex(finalSurface, false);
-
-    SDL_FreeSurface(finalSurface);
+    RenderStringTexLim:= Surface2Atlas(finalSurface, false);
 end;
 
 
@@ -459,10 +457,9 @@
         inc(pos);
         end;
 
-    RenderSpeechBubbleTex:= Surface2Tex(finalSurface, true);
+    RenderSpeechBubbleTex:= Surface2Atlas(finalSurface, true);
 
     SDL_FreeSurface(rotatedEdge);
-    SDL_FreeSurface(finalSurface);
 end;
 
 end.
--- a/hedgewars/uScript.pas	Mon Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/uScript.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -738,8 +738,7 @@
             DrawRoundRect(@rr, clan^.Color, clan^.Color, texsurf, false);
 
             FreeTexture(team^.HealthTex);
-            team^.HealthTex:= Surface2Tex(texsurf, false);
-            SDL_FreeSurface(texsurf);
+            team^.HealthTex:= Surface2Atlas(texsurf, false);
             MakeCrossHairs
             end
         end;
--- a/hedgewars/uStore.pas	Mon Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/uStore.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -43,10 +43,23 @@
 
 procedure WarpMouse(x, y: Word); inline;
 procedure SwapBuffers; inline;
+procedure UpdateProjection;
+
+{$IFDEF GL2}
+procedure UpdateModelviewProjection;
+{$ENDIF}
+
+procedure Tint(r, g, b, a: Byte); inline;
+procedure Tint(c: Longword); inline;
+procedure SetTexCoordPointer(p: Pointer);
+procedure SetVertexPointer(p: Pointer);
+procedure SetColorPointer(p: Pointer);
+procedure BeginWater;
+procedure EndWater;
 
 implementation
 uses uMisc, uConsole, uMobile, uVariables, uUtils, uTextures, uRender, uRenderUtils, uCommands,
-     uDebug{$IFDEF USE_CONTEXT_RESTORE}, uWorld{$ENDIF};
+     uDebug{$IFDEF USE_CONTEXT_RESTORE}, uWorld{$ENDIF}, uMatrix;
 
 //type TGPUVendor = (gvUnknown, gvNVIDIA, gvATI, gvIntel, gvApple);
 
@@ -57,6 +70,27 @@
 {$ELSE}
     SDLPrimSurface: PSDL_Surface;
 {$ENDIF}
+{$IFDEF GL2}
+    shaderMain: GLuint;
+    shaderWater: GLuint;
+
+    // attributes
+const
+    aVertex: GLint   = 0;
+    aTexCoord: GLint = 1;
+    aColor: GLint    = 2;
+
+var
+    uCurrentMVPLocation: GLint;
+
+    uMainMVPLocation: GLint;
+    uMainTintLocation: GLint;
+
+    uWaterMVPLocation: GLint;
+
+{$ENDIF}
+    LastTint: LongWord = 0;
+
 
 function WriteInRect(Surface: PSDL_Surface; X, Y: LongInt; Color: LongWord; Font: THWFont; s: ansistring): TSDL_Rect;
 var w, h: LongInt;
@@ -121,8 +155,7 @@
         SDL_UnlockSurface(texsurf);
 
     FreeTexture(CrosshairTex);
-    CrosshairTex:= Surface2Tex(texsurf, false);
-    SDL_FreeSurface(texsurf)
+    CrosshairTex:= Surface2Atlas(texsurf, false);
     end;
 
 SDL_FreeSurface(tmpsurf)
@@ -155,8 +188,7 @@
         rr:= r;
         inc(rr.x, 2); dec(rr.w, 4); inc(rr.y, 2); dec(rr.h, 4);
         DrawRoundRect(@rr, Clan^.Color, Clan^.Color, texsurf, false);
-        HealthTex:= Surface2Tex(texsurf, false);
-        SDL_FreeSurface(texsurf);
+        HealthTex:= Surface2Atlas(texsurf, false);
 
         r.x:= 0;
         r.y:= 0;
@@ -196,8 +228,7 @@
         PLongwordArray(texsurf^.pixels)^[32 * 16 +  2]:= cNearBlackColor;
         PLongwordArray(texsurf^.pixels)^[32 * 16 + 23]:= cNearBlackColor;
 
-        FlagTex:= Surface2Tex(texsurf, false);
-        SDL_FreeSurface(texsurf);
+        FlagTex:= Surface2Atlas(texsurf, false);
         texsurf:= nil;
 
         AIKillsTex := RenderStringTex(inttostr(stats.AIKills), Clan^.Color, fnt16);
@@ -229,8 +260,7 @@
         r.w:= 28;
         r.h:= 28;
         DrawRoundRect(@r, cWhiteColor, cNearBlackColor, iconsurf, true);
-        ropeIconTex:= Surface2Tex(iconsurf, false);
-        SDL_FreeSurface(iconsurf);
+        ropeIconTex:= Surface2Atlas(iconsurf, false);
         iconsurf:= nil;
         end;
 end;
@@ -265,8 +295,7 @@
                 texsurf:= LoadImage(UserPathz[ptGraves] + '/Statue', ifTransparent);
             if texsurf = nil then
                 texsurf:= LoadImage(Pathz[ptGraves] + '/Statue', ifCritical or ifTransparent);
-            GraveTex:= Surface2Tex(texsurf, false);
-            SDL_FreeSurface(texsurf)
+            GraveTex:= Surface2Atlas(texsurf, false);
             end
 end;
 
@@ -363,12 +392,12 @@
                     end;
                 if (ii in [sprSky, sprSkyL, sprSkyR, sprHorizont, sprHorizontL, sprHorizontR]) then
                     begin
-                    Texture:= Surface2Tex(tmpsurf, true);
+                    Texture:= Surface2Atlas(tmpsurf, true);
                     Texture^.Scale:= 2
                     end
                 else
                     begin
-                    Texture:= Surface2Tex(tmpsurf, false);
+                    Texture:= Surface2Atlas(tmpsurf, false);
                     // HACK: We should include some sprite attribute to define the texture wrap directions
                     if ((ii = sprWater) or (ii = sprSDWater)) and ((cReducedQuality and (rq2DWater or rqClampLess)) = 0) then
                         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
@@ -382,8 +411,8 @@
 {$ELSE}
                     if saveSurf then
                         Surface:= tmpsurf
-                    else
-                        SDL_FreeSurface(tmpsurf)
+                    //else
+                    //    SDL_FreeSurface(tmpsurf) released by FreeTexture
 {$ENDIF}
                     end
                 end
@@ -398,8 +427,7 @@
 if tmpsurf = nil then
     tmpsurf:= LoadImage(Pathz[ptGraphics] + '/' + cHHFileName, ifAlpha or ifCritical or ifTransparent);
     
-HHTexture:= Surface2Tex(tmpsurf, false);
-SDL_FreeSurface(tmpsurf);
+HHTexture:= Surface2Atlas(tmpsurf, false);
 
 InitHealth;
 
@@ -419,8 +447,7 @@
         TryDo(tmpsurf <> nil,'Name-texture creation for ammo type #' + intToStr(ord(ai)) + ' failed!',true);
         tmpsurf:= doSurfaceConversion(tmpsurf);
         FreeTexture(NameTex);
-        NameTex:= Surface2Tex(tmpsurf, false);
-        SDL_FreeSurface(tmpsurf)
+        NameTex:= Surface2Atlas(tmpsurf, false);
         end;
 
 // number of weapons in ammo menu
@@ -429,8 +456,7 @@
     tmpsurf:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar(IntToStr(i) + 'x'), cWhiteColorChannels);
     tmpsurf:= doSurfaceConversion(tmpsurf);
     FreeTexture(CountTexz[i]);
-    CountTexz[i]:= Surface2Tex(tmpsurf, false);
-    SDL_FreeSurface(tmpsurf)
+    CountTexz[i]:= Surface2Atlas(tmpsurf, false);
     end;
 
 if not reload then
@@ -449,7 +475,7 @@
     SpritesData[ii].Texture:= nil;
     if (SpritesData[ii].Surface <> nil) and (not reload) then
         begin
-        SDL_FreeSurface(SpritesData[ii].Surface);
+        //SDL_FreeSurface(SpritesData[ii].Surface); released by FreeTexture
         SpritesData[ii].Surface:= nil
         end
     end;
@@ -582,10 +608,7 @@
         FreeTexture(HHGear^.Hedgehog^.HatTex);
 
         // assign new hat to hedgehog
-        HHGear^.Hedgehog^.HatTex:= Surface2Tex(texsurf, true);
-
-        // cleanup: free temporary surface mem
-        SDL_FreeSurface(texsurf)
+        HHGear^.Hedgehog^.HatTex:= Surface2Atlas(texsurf, true);
         end;
 end;
 
@@ -625,6 +648,112 @@
     SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); // try to prefer hardware rendering
 end;
 
+{$IFDEF GL2}
+function CompileShader(shaderFile: string; shaderType: GLenum): GLuint;
+var
+    shader: GLuint;
+    f: Textfile;
+    source, line: AnsiString;
+    sourceA: Pchar;
+    lengthA: GLint;
+    compileResult: GLint;
+    logLength: GLint;
+    log: PChar;
+begin
+    Assign(f, Pathz[ptShaders] + '/' + shaderFile);
+    filemode:= 0; // readonly
+    Reset(f);
+    if IOResult <> 0 then
+    begin
+        AddFileLog('Unable to load ' + shaderFile);
+        halt(-1);
+    end;
+
+    source:='';
+    while not eof(f) do
+    begin
+        ReadLn(f, line);
+        source:= source + line + #10;
+    end;
+    
+    CloseFile(f);
+
+    WriteLnToConsole('Compiling shader: ' + Pathz[ptShaders] + '/' + shaderFile);
+
+    sourceA:=PChar(source);
+    lengthA:=Length(source);
+
+    shader:=glCreateShader(shaderType);
+    glShaderSource(shader, 1, @sourceA, @lengthA);
+    glCompileShader(shader);
+    glGetShaderiv(shader, GL_COMPILE_STATUS, @compileResult);
+    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, @logLength);
+
+    if logLength > 1 then
+    begin
+        GetMem(log, logLength);
+        glGetShaderInfoLog(shader, logLength, nil, log);
+        WriteLnToConsole('========== Compiler log  ==========');
+        WriteLnToConsole(log);
+        WriteLnToConsole('===================================');
+        FreeMem(log, logLength);
+    end;
+
+    if compileResult <> GL_TRUE then
+    begin
+        WriteLnToConsole('Shader compilation failed, halting');
+        halt(-1);
+    end;
+
+    CompileShader:= shader;
+end;
+
+function CompileProgram(shaderName: string): GLuint;
+var
+    program_: GLuint;
+    vs, fs: GLuint;
+    linkResult: GLint;
+    logLength: GLint;
+    log: PChar;
+begin
+    program_:= glCreateProgram();
+    vs:= CompileShader(shaderName + '.vs', GL_VERTEX_SHADER);
+    fs:= CompileShader(shaderName + '.fs', GL_FRAGMENT_SHADER);
+    glAttachShader(program_, vs);
+    glAttachShader(program_, fs);
+
+    glBindAttribLocation(program_, aVertex, 'vertex');
+    glBindAttribLocation(program_, aTexCoord, 'texcoord');
+    glBindAttribLocation(program_, aColor, 'color');
+
+    glLinkProgram(program_);
+    glDeleteShader(vs);
+    glDeleteShader(fs);
+
+    glGetProgramiv(program_, GL_LINK_STATUS, @linkResult);
+    glGetProgramiv(program_, GL_INFO_LOG_LENGTH, @logLength);
+
+    if logLength > 1 then
+    begin
+        GetMem(log, logLength);
+        glGetProgramInfoLog(program_, logLength, nil, log);
+        WriteLnToConsole('========== Compiler log  ==========');
+        WriteLnToConsole(log);
+        WriteLnToConsole('===================================');
+        FreeMem(log, logLength);
+    end;
+
+    if linkResult <> GL_TRUE then
+    begin
+        WriteLnToConsole('Linking program failed, halting');
+        halt(-1);
+    end;
+
+    CompileProgram:= program_;
+end;
+
+{$ENDIF}
+
 procedure SetupOpenGL;
 //var vendor: shortstring = '';
 var buf: array[byte] of char;
@@ -682,6 +811,26 @@
     AddFileLog('  \----- Extensions: ' + shortstring(pchar(glGetString(GL_EXTENSIONS))));
     //TODO: don't have the Extensions line trimmed but slipt it into multiple lines
 
+{$IFDEF GL2}
+    Load_GL_VERSION_2_0;
+
+    shaderWater:= CompileProgram('water');
+    glUseProgram(shaderWater);
+    glUniform1i(glGetUniformLocation(shaderWater, 'tex0'), 0);
+    uWaterMVPLocation:= glGetUniformLocation(shaderWater, 'mvp');
+
+    shaderMain:= CompileProgram('default');
+    glUseProgram(shaderMain);
+    glUniform1i(glGetUniformLocation(shaderMain, 'tex0'), 0);
+    uMainMVPLocation:= glGetUniformLocation(shaderMain, 'mvp');
+    uMainTintLocation:= glGetUniformLocation(shaderMain, 'tint');
+
+    uCurrentMVPLocation:= uMainMVPLocation;
+
+    Tint(255, 255, 255, 255);
+    UpdateModelviewProjection;
+{$ENDIF}
+
 {$IFNDEF S3D_DISABLED}
     if (cStereoMode = smHorizontal) or (cStereoMode = smVertical) or (cStereoMode = smAFR) then
     begin
@@ -729,9 +878,7 @@
 
     glMatrixMode(GL_MODELVIEW);
     // prepare default translation/scaling
-    glLoadIdentity();
-    glScalef(2.0 / cScreenWidth, -2.0 / cScreenHeight, 1.0);
-    glTranslatef(0, -cScreenHeight / 2, 0);
+    SetScale(2.0);
 
     // enable alpha blending
     glEnable(GL_BLEND);
@@ -742,27 +889,144 @@
     glDisable(GL_DITHER);
     // enable common states by default as they save a lot
     glEnable(GL_TEXTURE_2D);
+
+{$IFDEF GL2}
+    glEnableVertexAttribArray(aVertex);
+    glEnableVertexAttribArray(aTexCoord);
+{$ELSE}
     glEnableClientState(GL_VERTEX_ARRAY);
     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+{$ENDIF}
+end;
+
+
+procedure Tint(r, g, b, a: Byte); inline;
+var
+    nc, tw: Longword;
+const
+    scale = 1.0/255.0;
+begin
+    nc:= (a shl 24) or (b shl 16) or (g shl 8) or r;
+
+    if nc = lastTint then
+        exit;
+
+    if GrayScale then
+        begin
+        tw:= round(r * RGB_LUMINANCE_RED + g * RGB_LUMINANCE_GREEN + b * RGB_LUMINANCE_BLUE);
+        if tw > 255 then
+            tw:= 255;
+        r:= tw;
+        g:= tw;
+        b:= tw
+        end;
+
+    {$IFDEF GL2}
+    glUniform4f(uMainTintLocation, r*scale, g*scale, b*scale, a*scale);
+    glColor4ub(r, g, b, a);
+    {$ELSE}
+    glColor4ub(r, g, b, a);
+    {$ENDIF}
+    lastTint:= nc;
+end;
+
+procedure Tint(c: Longword); inline;
+begin
+    Tint(((c shr 24) and $FF), ((c shr 16) and $FF), (c shr 8) and $FF, (c and $FF))
+end;
+
+procedure SetTexCoordPointer(p: Pointer);
+begin
+    {$IFDEF GL2}
+    glVertexAttribPointer(aTexCoord, 2, GL_FLOAT, GL_FALSE, 0, p);
+    {$ELSE}
+    glTexCoordPointer(2, GL_FLOAT, 0, p);
+    {$ENDIF}
+end;
+
+procedure SetVertexPointer(p: Pointer);
+begin
+    {$IFDEF GL2}
+    glVertexAttribPointer(aVertex, 2, GL_FLOAT, GL_FALSE, 0, p);
+    {$ELSE}
+    glVertexPointer(2, GL_FLOAT, 0, p);
+    {$ENDIF}
+end;
+
+procedure SetColorPointer(p: Pointer);
+begin
+    {$IFDEF GL2}
+    glVertexAttribPointer(aColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, p);
+    {$ELSE}
+    glColorPointer(4, GL_UNSIGNED_BYTE, 0, p);
+    {$ENDIF}
+end;
+
+{$IFDEF GL2}
+procedure UpdateModelviewProjection;
+var
+    mvp: TMatrix4x4f;
+begin
+    MatrixMultiply(mvp, mProjection, mModelview);
+    glUniformMatrix4fv(uCurrentMVPLocation, 1, GL_FALSE, @mvp[0, 0]);
+end;
+{$ENDIF GL2}
+
+procedure UpdateProjection;
+var
+    s: GLfloat;
+begin
+    s:=cScaleFactor;
+    mProjection[0,0]:= s/cScreenWidth; mProjection[0,1]:=  0.0;             mProjection[0,2]:=0.0; mProjection[0,3]:=  0.0;
+    mProjection[1,0]:= 0.0;            mProjection[1,1]:= -s/cScreenHeight; mProjection[1,2]:=0.0; mProjection[1,3]:=  0.0;
+    mProjection[2,0]:= 0.0;            mProjection[2,1]:=  0.0;             mProjection[2,2]:=1.0; mProjection[2,3]:=  0.0;
+    mProjection[3,0]:= cStereoDepth;   mProjection[3,1]:=  s/2;             mProjection[3,2]:=0.0; mProjection[3,3]:=  1.0;
+
+    {$IFDEF GL2}
+    UpdateModelviewProjection;
+    {$ELSE}
+    glMatrixMode(GL_PROJECTION);
+    glLoadMatrixf(@mProjection[0, 0]);
+    glMatrixMode(GL_MODELVIEW);
+    {$ENDIF}    
 end;
 
 procedure SetScale(f: GLfloat);
 begin
-// leave immediately if scale factor did not change
-    if f = cScaleFactor then
-        exit;
+    // This lazy update conflicts with R7103 at the moment, missing the initial SetScale(2.0)
+    //if cScaleFactor <> f then
+    //begin
+        cScaleFactor:=f;
+        UpdateProjection;
+    //end;
+end;
 
-    if f = cDefaultZoomLevel then
-        glPopMatrix         // "return" to default scaling
-    else                    // other scaling
-        begin
-        glPushMatrix;       // save default scaling
-        glLoadIdentity;
-        glScalef(f / cScreenWidth, -f / cScreenHeight, 1.0);
-        glTranslatef(0, -cScreenHeight / 2, 0);
-        end;
+procedure BeginWater;
+begin
+    {$IFDEF GL2}
+    glUseProgram(shaderWater);
+    uCurrentMVPLocation:=uWaterMVPLocation;
+    UpdateModelviewProjection;
+    glDisableVertexAttribArray(aTexCoord);
+    glEnableVertexAttribArray(aColor);
+    {$ELSE}
+    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+    glEnableClientState(GL_COLOR_ARRAY);
+    {$ENDIF}
+end;
 
-    cScaleFactor:= f;
+procedure EndWater;
+begin
+    {$IFDEF GL2}
+    glUseProgram(shaderMain);
+    uCurrentMVPLocation:=uMainMVPLocation;
+    UpdateModelviewProjection;
+    glDisableVertexAttribArray(aColor);
+    glEnableVertexAttribArray(aTexCoord);
+    {$ELSE}
+    glDisableClientState(GL_COLOR_ARRAY);
+    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+    {$ENDIF}
 end;
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -777,11 +1041,10 @@
         if texsurf = nil then
             texsurf:= LoadImage(Pathz[ptGraphics] + '/Progress', ifCritical or ifTransparent);
 
-        ProgrTex:= Surface2Tex(texsurf, false);
+        ProgrTex:= Surface2Atlas(texsurf, false);
 
         squaresize:= texsurf^.w shr 1;
         numsquares:= texsurf^.h div squaresize;
-        SDL_FreeSurface(texsurf);
 
         uMobile.GameLoading();
         end;
@@ -926,8 +1189,7 @@
 SDL_FillRect(tmpsurf, @r, $ffffffff);
 SDL_UpperBlit(iconsurf, iconrect, tmpsurf, @r);
 
-RenderHelpWindow:=  Surface2Tex(tmpsurf, true);
-SDL_FreeSurface(tmpsurf)
+RenderHelpWindow:=  Surface2Atlas(tmpsurf, true);
 end;
 
 procedure RenderWeaponTooltip(atype: TAmmoType);
@@ -1033,11 +1295,7 @@
         // chFullScr is called when there is a rotation event and needs the SetScale and SetupOpenGL to set up the new resolution
         // this 6 gl functions are the relevant ones and are hacked together here for optimisation
         glMatrixMode(GL_MODELVIEW);
-        glPopMatrix;
-        glLoadIdentity();
-        glScalef(2.0 / cScreenWidth, -2.0 / cScreenHeight, 1.0);
-        glTranslatef(0, -cScreenHeight / 2, 0);
-        glViewport(0, 0, cScreenWidth, cScreenHeight);
+        SetScale(2.0);
         exit;
 {$ELSE}
         SetScale(cDefaultZoomLevel);
@@ -1151,6 +1409,10 @@
 
 procedure freeModule;
 begin
+{$IFDEF GL2}
+    glDeleteProgram(shaderMain);
+    glDeleteProgram(shaderWater);
+{$ENDIF}
     StoreRelease(false);
     TTF_Quit();
 {$IFDEF SDL13}
--- a/hedgewars/uTextures.pas	Mon Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/uTextures.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -24,14 +24,15 @@
 
 function  NewTexture(width, height: Longword; buf: Pointer): PTexture;
 procedure Surface2GrayScale(surf: PSDL_Surface);
-function  Surface2Tex(surf: PSDL_Surface; enableClamp: boolean): PTexture;
+function  Surface2Atlas(surf: PSDL_Surface; enableClamp: boolean): PTexture;
 procedure FreeTexture(tex: PTexture);
+procedure ComputeTexcoords(texture: PTexture; r: PSDL_Rect; tb: PVertexRect);
 
 procedure initModule;
 procedure freeModule;
 
 implementation
-uses GLunit, uUtils, uVariables, uConsts, uDebug, uConsole;
+uses GLunit, uUtils, uVariables, uConsts, uDebug, uConsole, uAtlas;
 
 var TextureList: PTexture;
 
@@ -47,10 +48,58 @@
     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, tmp: 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:= (texture^.x + r^.x +     texelOffset)/aw;
+x1:= (texture^.x + r^.x + w - texelOffset)/aw;
+y0:= (texture^.y + r^.y +     texelOffset)/ah;
+y1:= (texture^.y + r^.y + h - texelOffset)/ah;
+
+if (texture^.isRotated) then
+begin
+  tb^[0].X:= x0;
+  tb^[0].Y:= y0;
+  tb^[3].X:= x1;
+  tb^[3].Y:= y0;
+  tb^[2].X:= x1;
+  tb^[2].Y:= y1;
+  tb^[1].X:= x0;
+  tb^[1].Y:= y1
+end else
+begin
+  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;
+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 +108,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 +130,24 @@
     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;
+NewTexture^.shared:=false;
+NewTexture^.surface:=nil;
 
 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);
@@ -121,35 +175,50 @@
 end;
 
 
-function Surface2Tex(surf: PSDL_Surface; enableClamp: boolean): PTexture;
+function Surface2Atlas(surf: PSDL_Surface; enableClamp: boolean): PTexture;
 var tw, th, x, y: Longword;
     tmpp: pointer;
     fromP4, toP4: PLongWordArray;
 begin
-new(Surface2Tex);
-Surface2Tex^.PrevTexture:= nil;
-Surface2Tex^.NextTexture:= nil;
+    if (surf^.w <= 256) and (surf^.h <= 256) then
+    begin
+        Surface2Atlas:= Surface2Tex_(surf, enableClamp); // run the atlas side by side for debugging
+        ResetVertexArrays(Surface2Atlas);
+        exit;
+    end;
+new(Surface2Atlas);
+Surface2Atlas^.PrevTexture:= nil;
+Surface2Atlas^.NextTexture:= nil;
 if TextureList <> nil then
     begin
-    TextureList^.PrevTexture:= Surface2Tex;
-    Surface2Tex^.NextTexture:= TextureList
+    TextureList^.PrevTexture:= Surface2Atlas;
+    Surface2Atlas^.NextTexture:= TextureList
     end;
-TextureList:= Surface2Tex;
+TextureList:= Surface2Atlas;
+
+// Atlas allocation happens here later on. For now we just allocate one exclusive atlas per sprite
+new(Surface2Atlas^.atlas);
 
-Surface2Tex^.w:= surf^.w;
-Surface2Tex^.h:= surf^.h;
+Surface2Atlas^.w:= surf^.w;
+Surface2Atlas^.h:= surf^.h;
+Surface2Atlas^.x:=0;
+Surface2Atlas^.y:=0;
+Surface2Atlas^.isRotated:=false;
+Surface2Atlas^.surface:= surf;
+Surface2Atlas^.shared:= false;
+
 
 if (surf^.format^.BytesPerPixel <> 4) then
     begin
     TryDo(false, 'Surface2Tex failed, expecting 32 bit surface', true);
-    Surface2Tex^.id:= 0;
+    Surface2Atlas^.atlas^.id:= 0;
     exit
     end;
 
 
-glGenTextures(1, @Surface2Tex^.id);
+glGenTextures(1, @Surface2Atlas^.atlas^.id);
 
-glBindTexture(GL_TEXTURE_2D, Surface2Tex^.id);
+glBindTexture(GL_TEXTURE_2D, Surface2Atlas^.atlas^.id);
 
 if SDL_MustLock(surf) then
     SDLTry(SDL_LockSurface(surf) >= 0, true);
@@ -164,8 +233,8 @@
     tw:= toPowerOf2(Surf^.w);
     th:= toPowerOf2(Surf^.h);
 
-    Surface2Tex^.rx:= Surf^.w / tw;
-    Surface2Tex^.ry:= Surf^.h / th;
+    Surface2Atlas^.atlas^.w:=tw;
+    Surface2Atlas^.atlas^.h:=th;
 
     tmpp:= GetMem(tw * th * surf^.format^.BytesPerPixel);
 
@@ -195,12 +264,12 @@
     end
 else
     begin
-    Surface2Tex^.rx:= 1.0;
-    Surface2Tex^.ry:= 1.0;
+    Surface2Atlas^.atlas^.w:=Surf^.w;
+    Surface2Atlas^.atlas^.h:=Surf^.h;
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf^.w, surf^.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf^.pixels);
     end;
 
-ResetVertexArrays(Surface2Tex);
+ResetVertexArrays(Surface2Atlas);
 
 if SDL_MustLock(surf) then
     SDL_UnlockSurface(surf);
@@ -214,19 +283,33 @@
 begin
 if tex <> nil then
     begin
+        if tex^.shared then
+        begin
+            FreeTexture_(tex); // run atlas side by side for debugging
+            SDL_FreeSurface(tex^.surface);
+            exit;
+        end;
+
+    // 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);
+
+    if (tex^.surface <> nil) then
+        SDL_FreeSurface(tex^.surface);
     Dispose(tex);
     end
 end;
 
 procedure initModule;
 begin
+uAtlas.initModule;
 TextureList:= nil;
 end;
 
@@ -236,7 +319,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 Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/uTypes.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -44,7 +44,8 @@
     // Different files are stored in different folders, this enumeration is used to tell which folder to use
     TPathType = (ptNone, ptData, ptGraphics, ptThemes, ptCurrTheme, ptTeams, ptMaps,
             ptMapCurrent, ptDemos, ptSounds, ptGraves, ptFonts, ptForts,
-            ptLocale, ptAmmoMenu, ptHedgehog, ptVoices, ptHats, ptFlags, ptMissionMaps, ptSuddenDeath, ptButtons);
+            ptLocale, ptAmmoMenu, ptHedgehog, ptVoices, ptHats, ptFlags, ptMissionMaps, ptSuddenDeath, ptButtons,
+            ptShaders);
 
     // Available sprites for displaying stuff
     TSprite = (sprWater, sprCloud, sprBomb, sprBigDigit, sprFrame,
@@ -199,13 +200,34 @@
             X, Y: GLint;
             end;
 
+    TMatrix4x4f = array[0..3, 0..3] of GLfloat;
+
+    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
+
+            shared: boolean; // true if in an atlas, false if atlas points to a dedicated texture for this sprite
+
+            surface: PSDL_Surface; // retained in memory surface
+
+            // Cached values for texel coordinates and vertex coordinates            
+            vb, tb: TVertexRect;
+
             PrevTexture, NextTexture: PTexture;
             end;
 
--- a/hedgewars/uVariables.pas	Mon Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/uVariables.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -21,7 +21,7 @@
 unit uVariables;
 interface
 
-uses SDLh, uTypes, uFloat, GLunit, uConsts, Math, uMobile;
+uses SDLh, uTypes, uFloat, GLunit, uConsts, Math, uMobile, uMatrix;
 
 var
 /////// init flags ///////
@@ -225,8 +225,9 @@
         'Graphics/Hats',                 // ptHats
         'Graphics/Flags',                // ptFlags
         'Missions/Maps',                 // ptMissionMaps
-        'Graphics/SuddenDeath',           // ptSuddenDeath
-        'Graphics/Buttons'                // ptButton
+        'Graphics/SuddenDeath',          // ptSuddenDeath
+        'Graphics/Buttons',              // ptButton
+        'Shaders'                        // ptShaders
     );
 
     Fontz: array[THWFont] of THHFont = (
@@ -2427,6 +2428,7 @@
     SyncTexture,
     ConfirmTexture: PTexture;
     cScaleFactor: GLfloat;
+    cStereoDepth: GLfloat;
     SupportNPOTT: Boolean;
     Step: LongInt;
     squaresize : LongInt;
@@ -2465,6 +2467,9 @@
 
     lastTurnChecksum : Longword;
 
+    mModelview: TMatrix4x4f;
+    mProjection: TMatrix4x4f;
+
 var trammo:  array[TAmmoStrId] of ansistring;   // name of the weapon
     trammoc: array[TAmmoStrId] of ansistring;   // caption of the weapon
     trammod: array[TAmmoStrId] of ansistring;   // description of the weapon
@@ -2614,6 +2619,9 @@
     GrayScale:= false;
 
     LuaGoals:= '';
+
+    MatrixLoadIdentity(mModelview);
+    MatrixLoadIdentity(mProjection);
 end;
 
 procedure freeModule;
--- a/hedgewars/uWorld.pas	Mon Jun 25 09:40:56 2012 -0400
+++ b/hedgewars/uWorld.pas	Mon Jun 25 15:46:49 2012 +0200
@@ -60,7 +60,8 @@
     uCaptions,
     uCursor,
     uCommands,
-    uMobile
+    uMobile,
+    uAtlas
     ;
 
 var cWaveWidth, cWaveHeight: LongInt;
@@ -77,7 +78,6 @@
     amSel: TAmmoType = amNothing;
     missionTex: PTexture;
     missionTimer: LongInt;
-    stereoDepth: GLfloat;
     isFirstFrame: boolean;
     AMAnimType: LongInt;
 
@@ -495,8 +495,7 @@
 DrawLine2Surf(amSurface, AMRect.w+BORDERSIZE+i, BORDERSIZE, AMRect.w + BORDERSIZE+i, AMRect.h + BORDERSIZE, 160,160,160);//right
 end;
 
-GetAmmoMenuTexture:= Surface2Tex(amSurface, false);
-if amSurface <> nil then SDL_FreeSurface(amSurface);
+GetAmmoMenuTexture:= Surface2Atlas(amSurface, false);
 end;
 
 procedure ShowAmmoMenu;
@@ -795,28 +794,29 @@
         VertexBuffer[3].X:= -lw;
         VertexBuffer[3].Y:= lh;
 
-        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
-        glEnableClientState(GL_COLOR_ARRAY);
+        BeginWater;        
         if SuddenDeathDmg then
-            glColorPointer(4, GL_UNSIGNED_BYTE, 0, @SDWaterColorArray[0])
+            SetColorPointer(@SDWaterColorArray[0])
         else
-            glColorPointer(4, GL_UNSIGNED_BYTE, 0, @WaterColorArray[0]);
+            SetColorPointer(@WaterColorArray[0]);
 
-        glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
+        SetVertexPointer(@VertexBuffer[0]);
 
         glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
 
-        glDisableClientState(GL_COLOR_ARRAY);
-        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+        EndWater;
+        {$IFNDEF GL2}
         glColor4ub($FF, $FF, $FF, $FF); // must not be Tint() as color array seems to stay active and color reset is required
+        {$ENDIF}
         glEnable(GL_TEXTURE_2D);
     end;
 end;
 
 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
@@ -826,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,
@@ -841,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;
@@ -852,19 +851,16 @@
 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]);
-glTexCoordPointer(2, GL_FLOAT, 0, @TextureBuffer[0]);
+SetVertexPointer(@VertexBuffer[0]);
+SetTexCoordPointer(@TextureBuffer[0]);
 glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
 
 Tint($FF, $FF, $FF, $FF);
@@ -961,7 +957,7 @@
         begin
         glClear(GL_COLOR_BUFFER_BIT);
         DrawWorldStereo(Lag, rmDefault)
-        end
+        end;
 {$IFNDEF S3D_DISABLED}
     else if (cStereoMode = smAFR) then
         begin
@@ -1069,41 +1065,24 @@
         else
             glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE);
         DrawWorldStereo(Lag, rmRightEye);
-        end
+        end;
 {$ENDIF}
+    DebugAtlas;
 end;
 
 procedure ChangeDepth(rm: TRenderMode; d: GLfloat);
 begin
-{$IFDEF S3D_DISABLED}
-    rm:= rm; d:= d; // avoid hint
-    exit;
-{$ELSE}
     d:= d / 5;
-    if rm = rmDefault then
-        exit
-    else if rm = rmLeftEye then
+    if rm = rmLeftEye then
         d:= -d;
-    stereoDepth:= stereoDepth + d;
-    glMatrixMode(GL_PROJECTION);
-    glTranslatef(d, 0, 0);
-    glMatrixMode(GL_MODELVIEW);
-{$ENDIF}
+    cStereoDepth:= cStereoDepth + d;
+    UpdateProjection;
 end;
  
 procedure ResetDepth(rm: TRenderMode);
 begin
-{$IFDEF S3D_DISABLED}
-    rm:= rm; // avoid hint
-    exit;
-{$ELSE}
-    if rm = rmDefault then
-        exit;
-    glMatrixMode(GL_PROJECTION);
-    glTranslatef(-stereoDepth, 0, 0);
-    glMatrixMode(GL_MODELVIEW);
-    stereoDepth:= 0;
-{$ENDIF}
+    cStereoDepth:= 0;
+    UpdateProjection;
 end;
  
 procedure DrawWorldStereo(Lag: LongInt; RM: TRenderMode);
@@ -1483,8 +1462,7 @@
         tmpSurface:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar(s), cWhiteColorChannels);
         tmpSurface:= doSurfaceConversion(tmpSurface);
         FreeTexture(timeTexture);
-        timeTexture:= Surface2Tex(tmpSurface, false);
-        SDL_FreeSurface(tmpSurface)
+        timeTexture:= Surface2Atlas(tmpSurface, false);
         end;
 
     if timeTexture <> nil then
@@ -1501,8 +1479,7 @@
             tmpSurface:= TTF_RenderUTF8_Blended(Fontz[fnt16].Handle, Str2PChar(s), cWhiteColorChannels);
             tmpSurface:= doSurfaceConversion(tmpSurface);
             FreeTexture(fpsTexture);
-            fpsTexture:= Surface2Tex(tmpSurface, false);
-            SDL_FreeSurface(tmpSurface)
+            fpsTexture:= Surface2Atlas(tmpSurface, false);
             end;
         if fpsTexture <> nil then
             DrawTexture((cScreenWidth shr 1) - 60 - offsetY, offsetX, fpsTexture);
@@ -1556,7 +1533,7 @@
 
         glDisable(GL_TEXTURE_2D);
 
-        glVertexPointer(2, GL_FLOAT, 0, @VertexBuffer[0]);
+        SetVertexPointer(@VertexBuffer[0]);
         glDrawArrays(GL_TRIANGLE_FAN, 0, Length(VertexBuffer));
 
         glEnable(GL_TEXTURE_2D);
@@ -1823,14 +1800,13 @@
     missionTimer:= 0;
     missionTex:= nil;
     cOffsetY:= 0;
-    stereoDepth:= 0;
+    cStereoDepth:= 0;
     AMState:= AMHidden;
     isFirstFrame:= true;
 end;
 
 procedure freeModule;
 begin
-    stereoDepth:= stereoDepth; // avoid hint
     FreeTexture(fpsTexture);
     fpsTexture:= nil;
     FreeTexture(timeTexture);
--- a/share/hedgewars/Data/CMakeLists.txt	Mon Jun 25 09:40:56 2012 -0400
+++ b/share/hedgewars/Data/CMakeLists.txt	Mon Jun 25 15:46:49 2012 +0200
@@ -1,3 +1,3 @@
-foreach(dir "Fonts" "Forts" "Graphics" "Locale" "Maps" "Music" "Sounds" "Themes" "Missions" "Names" "misc" "Scripts")
+foreach(dir "Fonts" "Forts" "Graphics" "Locale" "Maps" "Music" "Sounds" "Themes" "Missions" "Names" "misc" "Scripts" "Shaders")
   add_subdirectory(${dir})
 endforeach(dir)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/hedgewars/Data/Shaders/CMakeLists.txt	Mon Jun 25 15:46:49 2012 +0200
@@ -0,0 +1,5 @@
+file(GLOB BaseShaders *.vs *.fs)
+
+install(FILES
+	${BaseShaders}
+	DESTINATION ${SHAREPATH}Data/Shaders)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/hedgewars/Data/Shaders/default.fs	Mon Jun 25 15:46:49 2012 +0200
@@ -0,0 +1,13 @@
+#version 130
+
+uniform sampler2D tex0;
+uniform vec4 tint;
+
+in vec2 tex;
+
+out vec4 color;
+
+void main()
+{
+    color = texture(tex0, tex) * tint;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/hedgewars/Data/Shaders/default.vs	Mon Jun 25 15:46:49 2012 +0200
@@ -0,0 +1,16 @@
+#version 130
+
+in vec2 vertex;
+in vec2 texcoord;
+in vec4 colors;
+
+out vec2 tex;
+
+uniform mat4 mvp;
+
+void main()
+{
+    vec4 p = mvp * vec4(vertex, 0.0f, 1.0f);
+    gl_Position = p;
+    tex = texcoord;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/hedgewars/Data/Shaders/water.fs	Mon Jun 25 15:46:49 2012 +0200
@@ -0,0 +1,9 @@
+#version 130
+
+in vec4 vcolor;
+out vec4 color;
+
+void main()
+{
+    color = vcolor;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/hedgewars/Data/Shaders/water.vs	Mon Jun 25 15:46:49 2012 +0200
@@ -0,0 +1,14 @@
+#version 130
+
+in vec2 vertex;
+in vec4 color;
+out vec4 vcolor;
+
+uniform mat4 mvp;
+
+void main()
+{
+    vec4 p = mvp * vec4(vertex, 0.0f, 1.0f);
+    gl_Position = p;
+    vcolor = color;
+}