hedgewars/uAtlas.pas
changeset 7297 af64b509725c
parent 7295 e70b81854fb9
child 7301 bea42438a2ec
equal deleted inserted replaced
7295:e70b81854fb9 7297:af64b509725c
     9 
     9 
    10 procedure initModule;
    10 procedure initModule;
    11 
    11 
    12 function Surface2Tex_(surf: PSDL_Surface; enableClamp: boolean): PTexture;
    12 function Surface2Tex_(surf: PSDL_Surface; enableClamp: boolean): PTexture;
    13 procedure FreeTexture_(sprite: PTexture);
    13 procedure FreeTexture_(sprite: PTexture);
       
    14 procedure DebugAtlas;
    14 
    15 
    15 implementation
    16 implementation
    16 
    17 
    17 uses GLunit, uBinPacker, uDebug, png, sysutils;
    18 uses GLunit, uBinPacker, uDebug, png, sysutils, uTextures;
    18 
    19 
    19 const
    20 const
    20     MaxAtlases = 1;    // Maximum number of atlases (textures) to allocate
    21     MaxAtlases = 4;    // Maximum number of atlases (textures) to allocate
    21     MaxTexSize = 4096; // Maximum atlas size in pixels
    22     MaxTexSize = 1024; // Maximum atlas size in pixels
    22     MinTexSize = 128;  // Minimum atlas size in pixels
    23     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     CompressionThreshold = 0.4; // Try to compact (half the size of) an atlas, when occupancy is less than this
    24 
    25 
    25 type
    26 type
    26     AtlasInfo = record
    27     AtlasInfo = record
    33     Info: array[0..MaxAtlases-1] of AtlasInfo;
    34     Info: array[0..MaxAtlases-1] of AtlasInfo;
    34 
    35 
    35 
    36 
    36 ////////////////////////////////////////////////////////////////////////////////
    37 ////////////////////////////////////////////////////////////////////////////////
    37 // Debug routines
    38 // Debug routines
       
    39 
       
    40 procedure AssertCount(tex: PTexture; count: Integer);
       
    41 var
       
    42     i, j: Integer;
       
    43     found: Integer;
       
    44 begin
       
    45     found:= 0;
       
    46     for i:= 0 to pred(MaxAtlases) do
       
    47     begin
       
    48         if not Info[i].Allocated then
       
    49             continue;
       
    50         for j:=0 to pred(Info[i].PackerInfo.usedRectangles.count) do
       
    51         begin
       
    52             if Info[i].PackerInfo.usedRectangles.data[j].UserData = tex then
       
    53                 inc(found);
       
    54         end;
       
    55     end;
       
    56     if found <> count then
       
    57     begin
       
    58         writeln('AssertCount(', IntToHex(Integer(tex), 8), ') failed, found ', found, ' times');
       
    59 
       
    60         for i:= 0 to pred(MaxAtlases) do
       
    61         begin
       
    62             if not Info[i].Allocated then
       
    63                 continue;
       
    64             for j:=0 to pred(Info[i].PackerInfo.usedRectangles.count) do
       
    65             begin
       
    66                 if Info[i].PackerInfo.usedRectangles.data[j].UserData = tex then
       
    67                     writeln(' found in atlas ', i, ' at slot ', j);
       
    68             end;
       
    69         end;
       
    70         halt(-2);
       
    71     end;
       
    72 end;
    38 
    73 
    39 var
    74 var
    40     DumpID: Integer;
    75     DumpID: Integer;
    41     DumpFile: File of byte;
    76     DumpFile: File of byte;
    42 
    77 
    71    s:= IntToStr(i);
   106    s:= IntToStr(i);
    72    if (i < 10) then s:='0' + s;
   107    if (i < 10) then s:='0' + s;
    73    if (i < 100) then s:='0' + s;
   108    if (i < 100) then s:='0' + s;
    74 
   109 
    75    IntToStrPad:=s;
   110    IntToStrPad:=s;
       
   111 end;
       
   112 
       
   113 // GL1 ATLAS DEBUG ONLY CODE!
       
   114 procedure DebugAtlas;
       
   115 var
       
   116     vp: array[0..3] of GLint;
       
   117     prog: GLint;
       
   118     i: Integer;
       
   119     x, y: Integer;
       
   120 const
       
   121     SZ = 512;
       
   122 begin
       
   123     x:= 0;
       
   124     y:= 0;
       
   125     for i:= 0 to pred(MaxAtlases) do
       
   126     begin
       
   127         if not Info[i].allocated then
       
   128             continue;
       
   129         glGetIntegerv(GL_VIEWPORT, @vp);
       
   130         glGetIntegerv(GL_CURRENT_PROGRAM, @prog);
       
   131 
       
   132         glUseProgram(0);
       
   133         glPushMatrix;
       
   134         glLoadIdentity;
       
   135         glOrtho(0, vp[2], vp[3], 0, -1, 1);
       
   136 
       
   137 
       
   138         glBindTexture(GL_TEXTURE_2D, Info[i].TextureInfo.id);
       
   139         glBegin(GL_QUADS);
       
   140         glTexCoord2f(0.0, 0.0);
       
   141         glVertex2i(x * SZ, y * SZ);
       
   142         glTexCoord2f(1.0, 0.0);
       
   143         glVertex2i((x + 1) * SZ, y * SZ);
       
   144         glTexCoord2f(1.0, 1.0);
       
   145         glVertex2i((x + 1) * SZ, (y + 1) * SZ);
       
   146         glTexCoord2f(0.0, 1.0);
       
   147         glVertex2i(x * SZ, (y + 1) * SZ);
       
   148         glEnd();
       
   149 
       
   150         glPopMatrix;
       
   151 
       
   152         inc(x);
       
   153         if (x = 2) then
       
   154         begin
       
   155             x:=0;
       
   156             inc(y);
       
   157         end;
       
   158      
       
   159 
       
   160         glUseProgram(prog);
       
   161     end;
    76 end;
   162 end;
    77 
   163 
    78 procedure DumpAtlas(var info: AtlasInfo);
   164 procedure DumpAtlas(var info: AtlasInfo);
    79 var
   165 var
    80     png: png_structp;
   166     png: png_structp;
   144     createTexture.w:= width;
   230     createTexture.w:= width;
   145     createTexture.h:= height;
   231     createTexture.h:= height;
   146     createTexture.priority:= 0;
   232     createTexture.priority:= 0;
   147     glGenTextures(1, @createTexture.id);
   233     glGenTextures(1, @createTexture.id);
   148     glBindTexture(GL_TEXTURE_2D, createTexture.id);
   234     glBindTexture(GL_TEXTURE_2D, createTexture.id);
       
   235 
       
   236     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
       
   237     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   149 
   238 
   150     //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
   239     //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
   151     
   240     
   152     GetMem(NullTex, width * height * 4);
   241     GetMem(NullTex, width * height * 4);
   153     FillChar(NullTex^, width * height * 4, 0);
   242     FillChar(NullTex^, width * height * 4, 0);
   273 procedure Upload(var info: AtlasInfo; sprite: Rectangle; surf: PSDL_Surface);
   362 procedure Upload(var info: AtlasInfo; sprite: Rectangle; surf: PSDL_Surface);
   274 var
   363 var
   275     sp: PTexture;
   364     sp: PTexture;
   276     i, j, stride: Integer;
   365     i, j, stride: Integer;
   277     scanline: PByte;
   366     scanline: PByte;
       
   367     r: TSDL_Rect;
   278 begin
   368 begin
   279     writeln('Uploading sprite to ', sprite.x, ',', sprite.y, ',', sprite.width, ',', sprite.height);
   369     writeln('Uploading sprite to ', sprite.x, ',', sprite.y, ',', sprite.width, ',', sprite.height);
   280     sp:= PTexture(sprite.UserData);
   370     sp:= PTexture(sprite.UserData);
   281     sp^.x:= sprite.x;
   371     sp^.x:= sprite.x;
   282     sp^.y:= sprite.y;
   372     sp^.y:= sprite.y;
   286     if SDL_MustLock(surf) then
   376     if SDL_MustLock(surf) then
   287         SDLTry(SDL_LockSurface(surf) >= 0, true);
   377         SDLTry(SDL_LockSurface(surf) >= 0, true);
   288 
   378 
   289     //if GrayScale then
   379     //if GrayScale then
   290     //    Surface2GrayScale(surf);
   380     //    Surface2GrayScale(surf);
   291     DebugColorize(surf);
   381     //DebugColorize(surf);
   292 
   382 
   293     glBindTexture(GL_TEXTURE_2D, info.TextureInfo.id);
   383     glBindTexture(GL_TEXTURE_2D, info.TextureInfo.id);
   294     if (sp^.isRotated) then
   384     if (sp^.isRotated) then
   295     begin
   385     begin
   296         scanline:= surf^.pixels;
   386         scanline:= surf^.pixels;
   304         glTexSubImage2D(GL_TEXTURE_2D, 0, sprite.x, sprite.y, sprite.width, sprite.height, GL_RGBA, GL_UNSIGNED_BYTE, surf^.pixels);
   394         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);
   395     glBindTexture(GL_TEXTURE_2D, 0);
   306 
   396 
   307     if SDL_MustLock(surf) then
   397     if SDL_MustLock(surf) then
   308         SDL_UnlockSurface(surf);
   398         SDL_UnlockSurface(surf);
   309 end;
   399 
   310 
   400     r.x:= 0;
   311 procedure Repack(var info: AtlasInfo; newAtlas: Atlas; newSprite: PTexture; surf: PSDL_Surface);
   401     r.y:= 0;
       
   402     r.w:= sp^.w;
       
   403     r.h:= sp^.h;
       
   404     ComputeTexcoords(sp, @r, @sp^.tb);
       
   405 end;
       
   406 
       
   407 procedure Repack(var info: AtlasInfo; newAtlas: Atlas);
   312 var
   408 var
   313     base: PByte;
   409     base: PByte;
   314     oldSize: Integer;
   410     oldSize: Integer;
   315     oldWidth: Integer;
   411     oldWidth: Integer;
   316     offset: Integer;
   412     offset: Integer;
   371 end;
   467 end;
   372 
   468 
   373 ////////////////////////////////////////////////////////////////////////////////
   469 ////////////////////////////////////////////////////////////////////////////////
   374 // Sprite allocation logic
   470 // Sprite allocation logic
   375 
   471 
   376 function TryRepack(var info: AtlasInfo; w, h: Integer; hasNewSprite: boolean; 
   472 function TryRepack(var info: AtlasInfo; w, h: Integer; hasNewSprite: boolean; newSprite: Size): boolean;
   377                    newSprite: Size; surf: PSDL_Surface): boolean;
       
   378 var
   473 var
   379     sizes: SizeList;
   474     sizes: SizeList;
   380     repackedAtlas: Atlas;
   475     repackedAtlas: Atlas;
   381     sprite: PTexture;
   476     sprite: PTexture;
   382     i: Integer;
   477     i: Integer;
   400     repackedAtlas:= atlasNew(w, h);
   495     repackedAtlas:= atlasNew(w, h);
   401     rectangleListInit(rects);
   496     rectangleListInit(rects);
   402     if atlasInsertSet(repackedAtlas, sizes, rects) then
   497     if atlasInsertSet(repackedAtlas, sizes, rects) then
   403     begin
   498     begin
   404         TryRepack:= true;
   499         TryRepack:= true;
   405         if hasNewSprite then
   500         Repack(info, repackedAtlas);
   406             sprite:= PTexture(newSprite.UserData)
       
   407         else
       
   408             sprite:= nil;
       
   409         Repack(info, repackedAtlas, sprite, surf);
       
   410         // repack assigns repackedAtlas to the current info and deletes the old one
   501         // repack assigns repackedAtlas to the current info and deletes the old one
   411         // thus we wont do atlasDelete(repackedAtlas); here 
   502         // thus we wont do atlasDelete(repackedAtlas); here 
   412         rectangleListClear(rects);
   503         rectangleListClear(rects);
   413         sizeListClear(sizes);
   504         sizeListClear(sizes);
   414         DumpAtlas(info);
   505         //DumpAtlas(info);
   415         exit;
   506         exit;
   416     end;
   507     end;
   417 
   508 
   418     rectangleListClear(rects);
   509     rectangleListClear(rects);
   419     sizeListClear(sizes);
   510     sizeListClear(sizes);
   429 
   520 
   430     if atlasInsertAdaptive(info.PackerInfo, newSprite, rect) then
   521     if atlasInsertAdaptive(info.PackerInfo, newSprite, rect) then
   431     begin
   522     begin
   432         // we succeeded adaptivley allocating the sprite to the i'th atlas.
   523         // we succeeded adaptivley allocating the sprite to the i'th atlas.
   433         Upload(info, rect, surf);
   524         Upload(info, rect, surf);
   434         DumpAtlas(info);
   525         //DumpAtlas(info);
   435         TryInsert:= true;
   526         TryInsert:= true;
   436     end;
   527     end;
   437 end;
   528 end;
   438 
   529 
   439 function Surface2Tex_(surf: PSDL_Surface; enableClamp: boolean): PTexture;
   530 function Surface2Tex_(surf: PSDL_Surface; enableClamp: boolean): PTexture;
   458     sprite^.h:= surf^.h;
   549     sprite^.h:= surf^.h;
   459     sprite^.x:= 0;
   550     sprite^.x:= 0;
   460     sprite^.y:= 0;
   551     sprite^.y:= 0;
   461     sprite^.isRotated:= false;
   552     sprite^.isRotated:= false;
   462     sprite^.surface:= surf;
   553     sprite^.surface:= surf;
       
   554     sprite^.shared:= true;
   463 
   555 
   464     sz:= SizeForSprite(sprite);
   556     sz:= SizeForSprite(sprite);
   465 
   557 
   466     // STEP 1
   558     // STEP 1
   467     // try to allocate the new sprite in one of the existing atlases
   559     // try to allocate the new sprite in one of the existing atlases
   468     for i:= 0 to pred(MaxAtlases) do
   560     for i:= 0 to pred(MaxAtlases) do
   469     begin
   561     begin
   470         if not Info[i].Allocated then
   562         if not Info[i].Allocated then
   471             continue;
   563             continue;
   472         if TryInsert(Info[i], sz, surf) then
   564         if TryInsert(Info[i], sz, surf) then
   473             exit; 
   565             exit;
   474     end;
   566     end;
   475 
   567 
   476 
   568 
   477     // STEP 2
   569     // STEP 2
   478     // none of the atlases has space left for the allocation, try a garbage collection
   570     // none of the atlases has space left for the allocation, try a garbage collection
   479     for i:= 0 to pred(MaxAtlases) do
   571     for i:= 0 to pred(MaxAtlases) do
   480     begin
   572     begin
   481         if not Info[i].Allocated then
   573         if not Info[i].Allocated then
   482             continue;
   574             continue;
   483 
   575 
   484         if TryRepack(Info[i], Info[i].PackerInfo.width, Info[i].PackerInfo.height, true, sz, surf) then
   576         if TryRepack(Info[i], Info[i].PackerInfo.width, Info[i].PackerInfo.height, true, sz) then
   485             exit;
   577             exit;
   486     end;
   578     end;
   487 
   579 
   488     // STEP 3
   580     // STEP 3
   489     // none of the atlases could be repacked in a way to fit the new sprite, try enlarging
   581     // none of the atlases could be repacked in a way to fit the new sprite, try enlarging
   496         currentHeight:= Info[i].PackerInfo.height;
   588         currentHeight:= Info[i].PackerInfo.height;
   497 
   589 
   498         EnlargeSize(currentWidth, currentHeight);
   590         EnlargeSize(currentWidth, currentHeight);
   499         while (currentWidth <= MaxTexSize) and (currentHeight <= MaxTexSize) do
   591         while (currentWidth <= MaxTexSize) and (currentHeight <= MaxTexSize) do
   500         begin
   592         begin
   501             if TryRepack(Info[i], currentWidth, currentHeight, true, sz, surf) then
   593             if TryRepack(Info[i], currentWidth, currentHeight, true, sz) then
   502                 exit;
   594                 exit;
   503             EnlargeSize(currentWidth, currentHeight);
   595             EnlargeSize(currentWidth, currentHeight);
   504         end;
   596         end;
   505     end;
   597     end;
   506 
   598 
   553     unused: Size;
   645     unused: Size;
   554 begin
   646 begin
   555     if sprite = nil then
   647     if sprite = nil then
   556         exit;
   648         exit;
   557 
   649 
       
   650     deleteAt:= -1;
   558     for i:= 0 to pred(MaxAtlases) do
   651     for i:= 0 to pred(MaxAtlases) do
   559     begin
   652     begin
   560         if sprite^.atlas <> @Info[i].TextureInfo then
   653         if sprite^.atlas <> @Info[i].TextureInfo then
   561             continue;
   654             continue;
   562 
   655 
   568                 deleteAt:= j
   661                 deleteAt:= j
   569             else
   662             else
   570                 inc(usedArea, r.width * r.height);
   663                 inc(usedArea, r.width * r.height);
   571         end;
   664         end;
   572 
   665 
   573         rectangleListRemoveAt(Info[i].PackerInfo.usedRectangles, j);
   666         rectangleListRemoveAt(Info[i].PackerInfo.usedRectangles, deleteAt);
   574         dispose(sprite);
   667         dispose(sprite);
   575 
   668 
   576         while true do
   669         while true do
   577         begin
   670         begin
   578             atlasW:= Info[i].PackerInfo.width;
   671             atlasW:= Info[i].PackerInfo.width;
   584             if (atlasW = MinTexSize) and (atlasH = MinTexSize) then
   677             if (atlasW = MinTexSize) and (atlasH = MinTexSize) then
   585                 exit; // we could try to move everything from this to another atlas here
   678                 exit; // we could try to move everything from this to another atlas here
   586 
   679 
   587             CompactSize(atlasW, atlasH);
   680             CompactSize(atlasW, atlasH);
   588             unused:= unused;
   681             unused:= unused;
   589             TryRepack(Info[i], atlasW, atlasH, false, unused, nil);
   682             TryRepack(Info[i], atlasW, atlasH, false, unused);
   590         end;
   683         end;
       
   684 
       
   685         exit;
   591     end;
   686     end;
   592 end;
   687 end;
   593 
   688 
   594 procedure initModule;
   689 procedure initModule;
   595 var
   690 var