|
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. |