hedgewars/uAtlas.pas
changeset 7292 18430abfbcd2
child 7295 e70b81854fb9
equal deleted inserted replaced
7291:ad4b6c2b09e8 7292:18430abfbcd2
       
     1 {$INCLUDE "options.inc"}
       
     2 {$IF GLunit = GL}{$DEFINE GLunit:=GL,GLext}{$ENDIF}
       
     3 
       
     4 unit uAtlas;
       
     5 
       
     6 interface
       
     7 
       
     8 uses SDLh, uTypes;
       
     9 
       
    10 procedure initModule;
       
    11 
       
    12 function Surface2Tex_(surf: PSDL_Surface; enableClamp: boolean): PTexture;
       
    13 procedure FreeTexture_(sprite: PTexture);
       
    14 
       
    15 implementation
       
    16 
       
    17 uses GLunit, uBinPacker, uDebug, png, sysutils;
       
    18 
       
    19 const
       
    20     MaxAtlases = 1;    // Maximum number of atlases (textures) to allocate
       
    21     MaxTexSize = 4096; // Maximum atlas size in pixels
       
    22     MinTexSize = 128;  // Minimum atlas size in pixels
       
    23     CompressionThreshold = 0.4; // Try to compact (half the size of) an atlas, when occupancy is less than this
       
    24 
       
    25 type
       
    26     AtlasInfo = record
       
    27         PackerInfo: Atlas;     // Rectangle packer context
       
    28         TextureInfo: TAtlas;   // OpenGL texture information
       
    29         Allocated: boolean;    // indicates if this atlas is in use
       
    30     end;
       
    31 
       
    32 var
       
    33     Info: array[0..MaxAtlases-1] of AtlasInfo;
       
    34 
       
    35 
       
    36 ////////////////////////////////////////////////////////////////////////////////
       
    37 // Debug routines
       
    38 
       
    39 var
       
    40     DumpID: Integer;
       
    41     DumpFile: File of byte;
       
    42 
       
    43 const
       
    44     PNG_COLOR_TYPE_RGBA = 6;
       
    45     PNG_COLOR_TYPE_RGB = 2;
       
    46     PNG_INTERLACE_NONE = 0;
       
    47     PNG_COMPRESSION_TYPE_DEFAULT = 0;
       
    48     PNG_FILTER_TYPE_DEFAULT = 0;
       
    49     
       
    50 
       
    51 
       
    52 procedure writefunc(png: png_structp; buffer: png_bytep; size: QWord); cdecl;
       
    53 var
       
    54     p: Pbyte;
       
    55     i: Integer;
       
    56 begin
       
    57   //TStream(png_get_io_ptr(png)).Write(buffer^, size);
       
    58     BlockWrite(DumpFile, buffer^, size);
       
    59 {    p:= PByte(buffer^);
       
    60     for i:=0 to pred(size) do
       
    61     begin
       
    62         Write(DumpFile, p^);
       
    63         inc(p);
       
    64     end;}
       
    65 end;
       
    66 
       
    67 function IntToStrPad(i: Integer): string;
       
    68 var
       
    69   s: string;
       
    70 begin
       
    71    s:= IntToStr(i);
       
    72    if (i < 10) then s:='0' + s;
       
    73    if (i < 100) then s:='0' + s;
       
    74 
       
    75    IntToStrPad:=s;
       
    76 end;
       
    77 
       
    78 procedure DumpAtlas(var info: AtlasInfo);
       
    79 var
       
    80     png: png_structp;
       
    81     png_info: png_infop;
       
    82     w, h, sz: Integer;
       
    83     filename: string;
       
    84     rows: array of png_bytep;
       
    85     size: Integer;
       
    86     i, j: Integer;
       
    87     mem, p, pp: PByte;
       
    88 begin
       
    89     filename:= '/home/wolfgangst/hedgewars/dump/atlas_' + IntToStrPad(DumpID) + '.png';
       
    90     Assign(DumpFile, filename);
       
    91     inc(DumpID);
       
    92     Rewrite(DumpFile);
       
    93 
       
    94     w:= info.TextureInfo.w;
       
    95     h:= info.TextureInfo.h;
       
    96     size:= w * h * 4;
       
    97     SetLength(rows, h);
       
    98     GetMem(mem, size);
       
    99 
       
   100     glBindTexture(GL_TEXTURE_2D, info.TextureInfo.id);
       
   101 
       
   102     glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, mem);
       
   103 
       
   104     p:= mem;
       
   105     for i:= 0 to pred(h) do
       
   106     begin
       
   107         rows[i]:= p;
       
   108         pp:= p;
       
   109         inc(pp, 3);
       
   110         {for j:= 0 to pred(w) do
       
   111         begin
       
   112             pp^:=255;
       
   113             inc(pp, 4);
       
   114         end;}
       
   115         inc(p, w * 4);
       
   116     end;
       
   117 
       
   118     png := png_create_write_struct(PNG_LIBPNG_VER_STRING, nil, nil, nil);
       
   119     png_info := png_create_info_struct(png);
       
   120 
       
   121     png_set_write_fn(png, nil, @writefunc, nil);
       
   122     png_set_IHDR(png, png_info, w, h, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
       
   123     png_write_info(png, png_info);
       
   124     png_write_image(png, @rows[0]);
       
   125     png_write_end(png, png_info);
       
   126     png_destroy_write_struct(@png, @png_info);
       
   127 
       
   128     FreeMem(mem);
       
   129     
       
   130     SetLength(rows, 0);
       
   131     Close(DumpFile);
       
   132 
       
   133     //if (DumpID >= 30) then
       
   134     //    halt(0);
       
   135 end;
       
   136 
       
   137 ////////////////////////////////////////////////////////////////////////////////
       
   138 // Upload routines
       
   139 
       
   140 function createTexture(width, height: Integer): TAtlas;
       
   141 var
       
   142   nullTex: Pointer;
       
   143 begin
       
   144     createTexture.w:= width;
       
   145     createTexture.h:= height;
       
   146     createTexture.priority:= 0;
       
   147     glGenTextures(1, @createTexture.id);
       
   148     glBindTexture(GL_TEXTURE_2D, createTexture.id);
       
   149 
       
   150     //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
       
   151     
       
   152     GetMem(NullTex, width * height * 4);
       
   153     FillChar(NullTex^, width * height * 4, 0);
       
   154     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NullTex);
       
   155     FreeMem(NullTex);
       
   156 
       
   157     glBindTexture(GL_TEXTURE_2D, 0);
       
   158 end;
       
   159 
       
   160 function Min(x, y: Single): Single;
       
   161 begin
       
   162   if x < y then
       
   163     Min:=x
       
   164   else Min:=y;
       
   165 end;
       
   166 
       
   167 function Max(x, y: Single): Single;
       
   168 begin
       
   169   if x > y then
       
   170     Max:=x
       
   171   else Max:=y;
       
   172 end;
       
   173 
       
   174 
       
   175 procedure HSVToRGB(const H, S, V: Single; out R, G, B: Single); 
       
   176 const 
       
   177     SectionSize = 60/360; 
       
   178 var 
       
   179     Section: Single; 
       
   180     SectionIndex: Integer; 
       
   181     f: single; 
       
   182     p, q, t: Single; 
       
   183 begin
       
   184     if H < 0 then 
       
   185     begin 
       
   186         R:= V; 
       
   187         G:= R; 
       
   188         B:= R; 
       
   189     end 
       
   190     else 
       
   191     begin 
       
   192         Section:= H/SectionSize; 
       
   193         SectionIndex:= Trunc(Section); 
       
   194         f:= Section - SectionIndex; 
       
   195         p:= V * ( 1 - S ); 
       
   196         q:= V * ( 1 - S * f ); 
       
   197         t:= V * ( 1 - S * ( 1 - f ) ); 
       
   198         case SectionIndex of 
       
   199             0: 
       
   200             begin 
       
   201                 R:= V; 
       
   202                 G:= t; 
       
   203                 B:= p; 
       
   204             end; 
       
   205             1: 
       
   206             begin 
       
   207                 R:= q; 
       
   208                 G:= V; 
       
   209                 B:= p; 
       
   210             end; 
       
   211             2: 
       
   212             begin 
       
   213                 R:= p; 
       
   214                 G:= V; 
       
   215                 B:= t; 
       
   216             end; 
       
   217             3: 
       
   218             begin 
       
   219                 R:= p; 
       
   220                 G:= q; 
       
   221                 B:= V; 
       
   222             end; 
       
   223             4: 
       
   224             begin 
       
   225                 R:= t; 
       
   226                 G:= p; 
       
   227                 B:= V; 
       
   228             end; 
       
   229             else 
       
   230                 R:= V; 
       
   231                 G:= p; 
       
   232                 B:= q; 
       
   233         end; 
       
   234     end; 
       
   235 end; 
       
   236 
       
   237 procedure DebugColorize(surf: PSDL_Surface);
       
   238 var
       
   239     sz: Integer;
       
   240     p: PByte;
       
   241     i: Integer;
       
   242     r, g, b, a, inva: Integer;
       
   243     randr, randg, randb: Single;
       
   244     randh: Single;
       
   245 begin
       
   246     sz:= surf^.w * surf^.h;
       
   247     p:= surf^.pixels;
       
   248     //randr:=Random;
       
   249     //randg:=Random;
       
   250     //randb:=1 - min(randr, randg);
       
   251     randh:=Random;
       
   252     HSVToRGB(randh, 1.0, 1.0, randr, randg, randb);
       
   253     for i:=0 to pred(sz) do
       
   254     begin
       
   255         a:= p[3];
       
   256         inva:= 255 - a;
       
   257 
       
   258         r:=Trunc(inva*randr + p[0]*a/255);
       
   259         g:=Trunc(inva*randg + p[1]*a/255);
       
   260         b:=Trunc(inva*randb + p[2]*a/255);
       
   261         if r > 255 then r:= 255;
       
   262         if g > 255 then g:= 255;
       
   263         if b > 255 then b:= 255;
       
   264 
       
   265         p[0]:=r;
       
   266         p[1]:=g;
       
   267         p[2]:=b;
       
   268         p[3]:=255;
       
   269         inc(p, 4);
       
   270     end;
       
   271 end;
       
   272 
       
   273 procedure Upload(var info: AtlasInfo; sprite: Rectangle; surf: PSDL_Surface);
       
   274 var
       
   275     sp: PTexture;
       
   276     i, j, stride: Integer;
       
   277     scanline: PByte;
       
   278 begin
       
   279     writeln('Uploading sprite to ', sprite.x, ',', sprite.y, ',', sprite.width, ',', sprite.height);
       
   280     sp:= PTexture(sprite.UserData);
       
   281     sp^.x:= sprite.x;
       
   282     sp^.y:= sprite.y;
       
   283     sp^.isRotated:= sp^.w <> sprite.width;
       
   284     sp^.atlas:= @info.TextureInfo;
       
   285 
       
   286     if SDL_MustLock(surf) then
       
   287         SDLTry(SDL_LockSurface(surf) >= 0, true);
       
   288 
       
   289     //if GrayScale then
       
   290     //    Surface2GrayScale(surf);
       
   291     DebugColorize(surf);
       
   292 
       
   293     glBindTexture(GL_TEXTURE_2D, info.TextureInfo.id);
       
   294     if (sp^.isRotated) then
       
   295     begin
       
   296         scanline:= surf^.pixels;
       
   297         for i:= 0 to pred(sprite.width) do
       
   298         begin
       
   299             glTexSubImage2D(GL_TEXTURE_2D, 0, sprite.x + i, sprite.y, 1, sprite.height, GL_RGBA, GL_UNSIGNED_BYTE, scanline);
       
   300             inc(scanline, sprite.height * 4);
       
   301         end;
       
   302     end
       
   303     else
       
   304         glTexSubImage2D(GL_TEXTURE_2D, 0, sprite.x, sprite.y, sprite.width, sprite.height, GL_RGBA, GL_UNSIGNED_BYTE, surf^.pixels);
       
   305     glBindTexture(GL_TEXTURE_2D, 0);
       
   306 
       
   307     if SDL_MustLock(surf) then
       
   308         SDL_UnlockSurface(surf);
       
   309 end;
       
   310 
       
   311 {$DEFINE HAS_PBO}
       
   312 procedure Repack(var info: AtlasInfo; newAtlas: Atlas; newSprite: PTexture; surf: PSDL_Surface);
       
   313 var
       
   314 {$IFDEF HAS_PBO}
       
   315     pbo: GLuint;
       
   316 {$ENDIF}
       
   317     base: PByte;
       
   318     oldSize: Integer;
       
   319     oldWidth: Integer;
       
   320     offset: Integer;
       
   321     i,j : Integer;
       
   322     r: Rectangle;
       
   323     sp: PTexture;
       
   324     newIsRotated: boolean;
       
   325     newSpriteRect: Rectangle;
       
   326 begin
       
   327     writeln('Repacking atlas (', info.PackerInfo.width, 'x', info.PackerInfo.height, ')', ' -> (', newAtlas.width, 'x', newAtlas.height, ')');
       
   328 
       
   329 {$IFDEF RETAIN_SURFACES}
       
   330     // we can simply re-upload from RAM
       
   331 
       
   332     // delete the old atlas
       
   333     glDeleteTextures(1, @info.TextureInfo.id);
       
   334 
       
   335     // create a new atlas with different size
       
   336     info.TextureInfo:= createTexture(newAtlas.width, newAtlas.height);
       
   337     glBindTexture(GL_TEXTURE_2D, info.TextureInfo.id);
       
   338 
       
   339     atlasDelete(info.PackerInfo);
       
   340     info.PackerInfo:= newAtlas;
       
   341 
       
   342     // and process all sprites of the new atlas
       
   343     for i:=0 to pred(newAtlas.usedRectangles.count) do
       
   344     begin
       
   345         r:= newAtlas.usedRectangles.data[i];
       
   346         sp:= PTexture(r.UserData);
       
   347         Upload(info, r, sp^.surface);
       
   348     end;
       
   349 
       
   350 {$ELSE}
       
   351     // as we dont have access to the original sprites in ram anymore,
       
   352     // we need to copy from the existing atlas to an PBO, delete the original texture
       
   353     // and finally copy from the PBO back to the new texture object
       
   354 
       
   355     // allocate a PBO and copy from old atlas to it
       
   356     oldSize:= info.TextureInfo.w * info.TextureInfo.h * 4;
       
   357     oldWidth:= info.TextureInfo.w;
       
   358 
       
   359     glBindTexture(GL_TEXTURE_2D, info.TextureInfo.id);
       
   360 
       
   361 {$IFDEF HAS_PBO}
       
   362     base:= nil;
       
   363     glGenBuffers(1, @pbo);
       
   364     glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
       
   365     glBufferData(GL_PIXEL_PACK_BUFFER, oldSize, nil, GL_COPY);
       
   366     //glGetTexImage( GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
       
   367     
       
   368     glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
       
   369     glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
       
   370 {$ELSE}
       
   371     GetMem(base, oldSize);
       
   372     glGetTexImage( GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, base);
       
   373 {$ENDIF}
       
   374 
       
   375     // delete the old atlas
       
   376     glDeleteTextures(1, @info.TextureInfo.id);
       
   377 
       
   378     // create a new atlas with different size
       
   379     info.TextureInfo:= createTexture(newAtlas.width, newAtlas.height);
       
   380     glBindTexture(GL_TEXTURE_2D, info.TextureInfo.id);
       
   381     
       
   382     
       
   383     // and process all sprites of the new atlas
       
   384     for i:=0 to pred(newAtlas.usedRectangles.count) do
       
   385     begin
       
   386         r:= newAtlas.usedRectangles.data[i];
       
   387         sp:= PTexture(r.UserData);
       
   388         if sp = newSprite then // this is the to be added sprite
       
   389         begin
       
   390             // we need to do defer the upload till after this loop, 
       
   391             // as we currently upload from the PBO to texture
       
   392             newSpriteRect:= r;
       
   393             continue;
       
   394         end;
       
   395 
       
   396         newIsRotated:= sp^.w <> r.width;
       
   397         if newIsRotated <> sp^.isRotated then
       
   398         begin
       
   399             glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
       
   400             glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
       
   401             glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
       
   402             offset:= sp^.x + sp^.y * oldWidth;
       
   403             for j:= 0 to pred(r.width) do
       
   404             begin
       
   405                 glTexSubImage2D(GL_TEXTURE_2D, 0, r.x + j, r.y, 1, r.height, GL_RGBA, GL_UNSIGNED_BYTE, base + offset * 4);
       
   406                 inc(offset, oldWidth);
       
   407             end;
       
   408         end 
       
   409         else
       
   410         begin
       
   411             glPixelStorei(GL_UNPACK_ROW_LENGTH, oldWidth);
       
   412             glPixelStorei(GL_UNPACK_SKIP_PIXELS, sp^.x);
       
   413             glPixelStorei(GL_UNPACK_SKIP_ROWS, sp^.y);
       
   414             glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.width, r.height, GL_RGBA, GL_UNSIGNED_BYTE, base);
       
   415         end;
       
   416 
       
   417         sp^.x:= r.x;
       
   418         sp^.y:= r.y;
       
   419         sp^.isRotated:= newIsRotated;
       
   420         sp^.atlas:= @info.TextureInfo;
       
   421     end;
       
   422     glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
       
   423     glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
       
   424     glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
       
   425 
       
   426     atlasDelete(info.PackerInfo);
       
   427     info.PackerInfo:= newAtlas;
       
   428 
       
   429 {$IFDEF HAS_PBO}
       
   430     glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
       
   431     glDeleteBuffers(1, @pbo);
       
   432 {$ELSE}
       
   433     FreeMem(base, oldSize);
       
   434 {$ENDIF}
       
   435 
       
   436     // finally upload the new sprite (if any)
       
   437     if newSprite <> nil then
       
   438         Upload(info, newSpriteRect, surf);
       
   439 
       
   440     glBindTexture(GL_TEXTURE_2D, 0);
       
   441 {$ENDIF}
       
   442 end;
       
   443 
       
   444 
       
   445 ////////////////////////////////////////////////////////////////////////////////
       
   446 // Utility functions
       
   447 
       
   448 function SizeForSprite(sprite: PTexture): Size;
       
   449 begin
       
   450     SizeForSprite.width:= sprite^.w;
       
   451     SizeForSprite.height:= sprite^.h;
       
   452     SizeForSprite.UserData:= sprite;
       
   453 end;
       
   454 
       
   455 procedure EnlargeSize(var x: Integer; var y: Integer);
       
   456 begin
       
   457     if (y < x) then
       
   458         y:= y + y
       
   459     else
       
   460         x:= x + x;
       
   461 end;
       
   462 
       
   463 procedure CompactSize(var x: Integer; var y: Integer);
       
   464 begin
       
   465     if (x > y) then
       
   466         x:= x div 2
       
   467     else
       
   468         y:= y div 2;
       
   469 end;
       
   470 
       
   471 ////////////////////////////////////////////////////////////////////////////////
       
   472 // Sprite allocation logic
       
   473 
       
   474 function TryRepack(var info: AtlasInfo; w, h: Integer; hasNewSprite: boolean; 
       
   475                    newSprite: Size; surf: PSDL_Surface): boolean;
       
   476 var
       
   477     sizes: SizeList;
       
   478     repackedAtlas: Atlas;
       
   479     sprite: PTexture;
       
   480     i: Integer;
       
   481     rects: RectangleList; // we wont really need this as we do a full repack using the atlas later on
       
   482 begin
       
   483     TryRepack:= false;
       
   484 
       
   485     // STEP 1: collect sizes of all existing sprites
       
   486     sizeListInit(sizes);
       
   487     for i:= 0 to pred(info.PackerInfo.usedRectangles.count) do
       
   488     begin
       
   489         sprite:= PTexture(info.PackerInfo.usedRectangles.data[i].UserData);
       
   490         sizeListAdd(sizes, SizeForSprite(sprite));
       
   491     end;
       
   492 
       
   493     // STEP 2: add the new sprite to the list
       
   494     if hasNewSprite then
       
   495         sizeListAdd(sizes, newSprite);
       
   496 
       
   497     // STEP 3: try to create a non adaptive re-packing using the whole list
       
   498     repackedAtlas:= atlasNew(w, h);
       
   499     rectangleListInit(rects);
       
   500     if atlasInsertSet(repackedAtlas, sizes, rects) then
       
   501     begin
       
   502         TryRepack:= true;
       
   503         if hasNewSprite then
       
   504             sprite:= PTexture(newSprite.UserData)
       
   505         else
       
   506             sprite:= nil;
       
   507         Repack(info, repackedAtlas, sprite, surf);
       
   508         // repack assigns repackedAtlas to the current info and deletes the old one
       
   509         // thus we wont do atlasDelete(repackedAtlas); here 
       
   510         rectangleListClear(rects);
       
   511         sizeListClear(sizes);
       
   512         DumpAtlas(info);
       
   513         exit;
       
   514     end;
       
   515 
       
   516     rectangleListClear(rects);
       
   517     sizeListClear(sizes);
       
   518     atlasDelete(repackedAtlas);
       
   519 end;
       
   520 
       
   521 function TryInsert(var info: AtlasInfo; newSprite: Size; surf: PSDL_Surface): boolean;
       
   522 var
       
   523     rect: Rectangle;
       
   524     sprite: PTexture;
       
   525 begin
       
   526     TryInsert:= false;
       
   527 
       
   528     if atlasInsertAdaptive(info.PackerInfo, newSprite, rect) then
       
   529     begin
       
   530         // we succeeded adaptivley allocating the sprite to the i'th atlas.
       
   531         Upload(info, rect, surf);
       
   532         DumpAtlas(info);
       
   533         TryInsert:= true;
       
   534     end;
       
   535 end;
       
   536 
       
   537 function Surface2Tex_(surf: PSDL_Surface; enableClamp: boolean): PTexture;
       
   538 var
       
   539     sz: Size;
       
   540     sprite: PTexture;
       
   541     currentWidth, currentHeight: Integer;
       
   542     i: Integer;
       
   543 begin
       
   544     if (surf^.w > MaxTexSize) or (surf^.h > MaxTexSize) then
       
   545     begin
       
   546         // we could at best downscale the sprite, abort for now
       
   547         writeln('Sprite size larger than maximum texture size');
       
   548         halt(-1);        
       
   549     end;
       
   550 
       
   551     // allocate the sprite
       
   552     new(sprite);
       
   553     Surface2Tex_:= sprite;
       
   554 
       
   555     sprite^.w:= surf^.w;
       
   556     sprite^.h:= surf^.h;
       
   557     sprite^.x:= 0;
       
   558     sprite^.y:= 0;
       
   559     sprite^.isRotated:= false;
       
   560     sprite^.surface:= surf;
       
   561 
       
   562     sz:= SizeForSprite(sprite);
       
   563 
       
   564     // STEP 1
       
   565     // try to allocate the new sprite in one of the existing atlases
       
   566     for i:= 0 to pred(MaxAtlases) do
       
   567     begin
       
   568         if not Info[i].Allocated then
       
   569             continue;
       
   570         if TryInsert(Info[i], sz, surf) then
       
   571             exit; 
       
   572     end;
       
   573 
       
   574 
       
   575     // STEP 2
       
   576     // none of the atlases has space left for the allocation, try a garbage collection
       
   577     for i:= 0 to pred(MaxAtlases) do
       
   578     begin
       
   579         if not Info[i].Allocated then
       
   580             continue;
       
   581 
       
   582         if TryRepack(Info[i], Info[i].PackerInfo.width, Info[i].PackerInfo.height, true, sz, surf) then
       
   583             exit;
       
   584     end;
       
   585 
       
   586     // STEP 3
       
   587     // none of the atlases could be repacked in a way to fit the new sprite, try enlarging
       
   588     for i:= 0 to pred(MaxAtlases) do
       
   589     begin
       
   590         if not Info[i].Allocated then
       
   591             continue;
       
   592 
       
   593         currentWidth:= Info[i].PackerInfo.width;
       
   594         currentHeight:= Info[i].PackerInfo.height;
       
   595 
       
   596         EnlargeSize(currentWidth, currentHeight);
       
   597         while (currentWidth <= MaxTexSize) and (currentHeight <= MaxTexSize) do
       
   598         begin
       
   599             if TryRepack(Info[i], currentWidth, currentHeight, true, sz, surf) then
       
   600                 exit;
       
   601             EnlargeSize(currentWidth, currentHeight);
       
   602         end;
       
   603     end;
       
   604 
       
   605     // STEP 4
       
   606     // none of the existing atlases could be resized, try to allocate a new atlas
       
   607     for i:= 0 to pred(MaxAtlases) do
       
   608     begin
       
   609         if Info[i].Allocated then
       
   610             continue;
       
   611 
       
   612         currentWidth:= MinTexSize;
       
   613         currentHeight:= MinTexSize;
       
   614         while (sz.width > currentWidth) do
       
   615             currentWidth:= currentWidth + currentWidth;
       
   616         while (sz.height > currentHeight) do
       
   617             currentHeight:= currentHeight + currentHeight;
       
   618 
       
   619         with Info[i] do
       
   620         begin
       
   621             PackerInfo:= atlasNew(currentWidth, currentHeight);
       
   622             TextureInfo:= createTexture(currentWidth, currentHeight);
       
   623             Allocated:= true;
       
   624         end;
       
   625 
       
   626         if TryInsert(Info[i], sz, surf) then
       
   627             exit;
       
   628 
       
   629         // this shouldnt have happened, the rectpacker should be able to fit the sprite
       
   630         // into an unused rectangle that is the same size or larger than the requested sprite.
       
   631         writeln('Internal error: atlas allocation failed');
       
   632         halt(-1);
       
   633     end;
       
   634 
       
   635     // we reached the upperbound of resources we are willing to allocate
       
   636     writeln('Exhausted maximum sprite allocation size');
       
   637     halt(-1);
       
   638 end;
       
   639 
       
   640 ////////////////////////////////////////////////////////////////////////////////
       
   641 // Sprite deallocation logic
       
   642 
       
   643 
       
   644 procedure FreeTexture_(sprite: PTexture);
       
   645 var
       
   646     i, j, deleteAt: Integer;
       
   647     usedArea: Integer;
       
   648     totalArea: Integer;
       
   649     r: Rectangle;
       
   650     atlasW, atlasH: Integer;
       
   651     unused: Size;
       
   652 begin
       
   653     if sprite = nil then
       
   654         exit;
       
   655 
       
   656     for i:= 0 to pred(MaxAtlases) do
       
   657     begin
       
   658         if sprite^.atlas <> @Info[i].TextureInfo then
       
   659             continue;
       
   660 
       
   661         usedArea:= 0;
       
   662         for j:=0 to pred(Info[i].PackerInfo.usedRectangles.count) do
       
   663         begin
       
   664             r:= Info[i].PackerInfo.usedRectangles.data[j];
       
   665             if r.UserData = sprite then
       
   666                 deleteAt:= j
       
   667             else
       
   668                 inc(usedArea, r.width * r.height);
       
   669         end;
       
   670 
       
   671         rectangleListRemoveAt(Info[i].PackerInfo.usedRectangles, j);
       
   672         dispose(sprite);
       
   673 
       
   674         while true do
       
   675         begin
       
   676             atlasW:= Info[i].PackerInfo.width;
       
   677             atlasH:= Info[i].PackerInfo.height;
       
   678             totalArea:=  atlasW * atlasH;
       
   679             if usedArea >= totalArea * CompressionThreshold then
       
   680                 exit;
       
   681 
       
   682             if (atlasW = MinTexSize) and (atlasH = MinTexSize) then
       
   683                 exit; // we could try to move everything from this to another atlas here
       
   684 
       
   685             CompactSize(atlasW, atlasH);
       
   686             unused:= unused;
       
   687             TryRepack(Info[i], atlasW, atlasH, false, unused, nil);
       
   688         end;
       
   689     end;
       
   690 end;
       
   691 
       
   692 procedure initModule;
       
   693 var
       
   694     i: Integer;
       
   695 begin
       
   696     DumpID:=0;
       
   697     for i:= 0 to pred(MaxAtlases) do
       
   698         Info[i].Allocated:= false;
       
   699 end;
       
   700 
       
   701 end.