diff -r bea42438a2ec -r 8b3575750cd2 hedgewars/uTextures.pas --- a/hedgewars/uTextures.pas Mon Jun 25 16:22:03 2012 +0200 +++ b/hedgewars/uTextures.pas Tue Jul 10 11:08:35 2012 +0200 @@ -24,6 +24,7 @@ function NewTexture(width, height: Longword; buf: Pointer): PTexture; procedure Surface2GrayScale(surf: PSDL_Surface); +function SurfaceSheet2Atlas(surf: PSDL_Surface; spriteWidth: Integer; spriteHeight: Integer): PTexture; function Surface2Atlas(surf: PSDL_Surface; enableClamp: boolean): PTexture; procedure FreeTexture(tex: PTexture); procedure ComputeTexcoords(texture: PTexture; r: PSDL_Rect; tb: PVertexRect); @@ -32,10 +33,164 @@ procedure freeModule; implementation -uses GLunit, uUtils, uVariables, uConsts, uDebug, uConsole, uAtlas; +uses GLunit, uUtils, uVariables, uConsts, uDebug, uConsole, uAtlas, SysUtils; + +var + logFile: TextFile; + +function CropSurface(source: PSDL_Surface; rect: PSDL_Rect): PSDL_Surface; +var + fmt: PSDL_PixelFormat; + srcP, dstP: PByte; + copySize: Integer; + i: Integer; +const + pixelSize = 4; +begin + //writeln(stdout, 'Cropping from ' + IntToStr(source^.w) + 'x' + IntToStr(source^.h) + ' -> ' + IntToStr(rect^.w) + 'x' + IntToStr(rect^.h)); + + fmt:= source^.format; + + CropSurface:= SDL_CreateRGBSurface(source^.flags, rect^.w, rect^.h, + fmt^.BitsPerPixel, fmt^.Rmask, fmt^.Gmask, fmt^.Bmask, fmt^.Amask); + + if SDL_MustLock(source) then + SDLTry(SDL_LockSurface(source) >= 0, true); + if SDL_MustLock(CropSurface) then + SDLTry(SDL_LockSurface(CropSurface) >= 0, true); + + srcP:= source^.pixels; + dstP:= CropSurface^.pixels; + + inc(srcP, pixelSize * rect^.x); + inc(srcP, source^.pitch * rect^.y); + copySize:= rect^.w * pixelSize; + for i:= 0 to Pred(rect^.h) do + begin + Move(srcP^, dstP^, copySize); + inc(srcP, source^.pitch); + inc(dstP, CropSurface^.pitch); + end; + + if SDL_MustLock(source) then + SDL_UnlockSurface(source); + if SDL_MustLock(CropSurface) then + SDL_UnlockSurface(CropSurface); +end; + +function TransparentLine(p: PByte; stride: Integer; length: Integer): boolean; +var + i: Integer; +begin + TransparentLine:= false; + for i:=0 to pred(length) do + begin + if p^ <> 0 then + exit; + inc(p, stride); + end; + TransparentLine:= true; +end; + +function AutoCrop(source: PSDL_Surface; var cropinfo: TCropInformation): PSDL_Surface; +var + l,r,t,b, i: Integer; + pixels, p: PByte; + scanlineSize: Integer; + rect: TSDL_Rect; +const + pixelSize = 4; +begin + l:= source^.w; + r:= 0; + t:= source^.h; + b:= 0; + + if SDL_MustLock(source) then + SDLTry(SDL_LockSurface(source) >= 0, true); + + pixels:= source^.pixels; + scanlineSize:= source^.pitch; -var TextureList: PTexture; + inc(pixels, 3); // advance to alpha value + + // check top + p:= pixels; + for i:= 0 to Pred(source^.h) do + begin + if not TransparentLine(p, pixelSize, source^.w) then + begin + t:= i; + break; + end; + inc(p, scanlineSize); + end; + + + // check bottom + p:= pixels; + inc(p, scanlineSize * source^.h); + for i:= 0 to Pred(source^.h - t) do + begin + dec(p, scanlineSize); + if not TransparentLine(p, pixelSize, source^.w) then + begin + b:= i; + break; + end; + end; + + // check left + p:= pixels; + for i:= 0 to Pred(source^.w) do + begin + if not TransparentLine(p, scanlineSize, source^.h) then + begin + l:= i; + break; + end; + inc(p, pixelSize); + end; + // check right + p:= pixels; + inc(p, scanlineSize); + for i:= 0 to Pred(source^.w - l) do + begin + dec(p, pixelSize); + if not TransparentLine(p, scanlineSize, source^.h) then + begin + r:= i; + break; + end; + end; + + if SDL_MustLock(source) then + SDL_UnlockSurface(source); + + rect.x:= l; + rect.y:= t; + + rect.w:= source^.w - r - l; + rect.h:= source^.h - b - t; + + cropInfo.l:= l; + cropInfo.r:= r; + cropInfo.t:= t; + cropInfo.b:= b; + cropInfo.x:= Trunc(source^.w / 2 - l + r); + cropInfo.y:= Trunc(source^.h / 2 - t + b); + + if (l = source^.w) or (t = source^.h) then + begin + result:= nil; + exit; + end; + + if (l <> 0) or (r <> 0) or (t <> 0) or (b <> 0) then + result:= CropSurface(source, @rect) + else result:= source; +end; procedure SetTextureParameters(enableClamp: Boolean); begin @@ -49,87 +204,80 @@ end; procedure ComputeTexcoords(texture: PTexture; r: PSDL_Rect; tb: PVertexRect); -var x0, y0, x1, y1, tmp: Real; +var + x0, y0, x1, y1, tmp: Real; w, h, aw, ah: LongInt; -const texelOffset = 0.0; + p: PChar; +const + texelOffset = 0.0; begin -aw:=texture^.atlas^.w; -ah:=texture^.atlas^.h; + aw:=texture^.atlas^.w; + ah:=texture^.atlas^.h; -if texture^.isRotated then + if texture^.isRotated then begin - w:=r^.h; - h:=r^.w; - end -else + w:=r^.h; + h:=r^.w; + end else begin - w:=r^.w; - h:=r^.h; + 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; + 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; + 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 - vb[0].X:= 0; - vb[0].Y:= 0; - vb[1].X:= w; - vb[1].Y:= 0; - vb[2].X:= w; - vb[2].Y:= h; - vb[3].X:= 0; - vb[3].Y:= h; -end; + with texture^ do + begin + vb[0].X:= texture^.cropInfo.l; + vb[0].Y:= texture^.cropInfo.t; + vb[1].X:= texture^.cropInfo.l + w; + vb[1].Y:= texture^.cropInfo.t; + vb[2].X:= texture^.cropInfo.l + w; + vb[2].Y:= texture^.cropInfo.t + h; + vb[3].X:= texture^.cropInfo.l; + vb[3].Y:= texture^.cropInfo.t + h; + end; -r.x:= 0; -r.y:= 0; -r.w:= texture^.w; -r.h:= texture^.h; -ComputeTexcoords(texture, @r, @texture^.tb); + 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; begin new(NewTexture); -NewTexture^.PrevTexture:= nil; -NewTexture^.NextTexture:= nil; NewTexture^.Scale:= 1; -if TextureList <> nil then - begin - TextureList^.PrevTexture:= NewTexture; - NewTexture^.NextTexture:= TextureList - end; -TextureList:= NewTexture; - // Atlas allocation happens here later on. For now we just allocate one exclusive atlas per sprite new(NewTexture^.atlas); @@ -142,6 +290,14 @@ NewTexture^.isRotated:=false; NewTexture^.shared:=false; NewTexture^.surface:=nil; +NewTexture^.nextFrame:=nil; +NewTexture^.cropInfo.l:= 0; +NewTexture^.cropInfo.r:= 0; +NewTexture^.cropInfo.t:= 0; +NewTexture^.cropInfo.b:= 0; +NewTexture^.cropInfo.x:= width div 2; +NewTexture^.cropInfo.y:= height div 2; + ResetVertexArrays(NewTexture); @@ -175,26 +331,86 @@ end; +function SurfaceSheet2Atlas(surf: PSDL_Surface; spriteWidth: Integer; spriteHeight: Integer): PTexture; +var + subSurface: PSDL_Surface; + framesX, framesY: Integer; + last, current: PTexture; + r: TSDL_Rect; + x, y: Integer; +begin + SurfaceSheet2Atlas:= nil; + r.x:= 0; + r.y:= 0; + r.w:= spriteWidth; + r.h:= spriteHeight; + last:= nil; + + framesX:= surf^.w div spriteWidth; + framesY:= surf^.h div spriteHeight; + + for x:=0 to Pred(framesX) do + begin + r.y:= 0; + for y:=0 to Pred(framesY) do + begin + subSurface:= CropSurface(surf, @r); + current:= Surface2Atlas(subSurface, false); + + if last = nil then + begin + SurfaceSheet2Atlas:= current; + last:= current; + end else + begin + last^.nextFrame:= current; + last:= current; + end; + inc(r.y, spriteHeight); + end; + inc(r.x, spriteWidth); + end; + + SDL_FreeSurface(surf); +end; + function Surface2Atlas(surf: PSDL_Surface; enableClamp: boolean): PTexture; var tw, th, x, y: Longword; tmpp: pointer; + cropped: PSDL_Surface; fromP4, toP4: PLongWordArray; + cropInfo: TCropInformation; begin - if (surf^.w <= 256) and (surf^.h <= 256) then + cropped:= AutoCrop(surf, cropInfo); + if cropped <> surf then + begin + SDL_FreeSurface(surf); + surf:= cropped; + end; + + if surf = nil then + begin + new(Surface2Atlas); + Surface2Atlas^.w:= 0; + Surface2Atlas^.h:= 0; + Surface2Atlas^.x:=0 ; + Surface2Atlas^.y:=0 ; + Surface2Atlas^.isRotated:= false; + Surface2Atlas^.surface:= nil; + Surface2Atlas^.shared:= false; + Surface2Atlas^.nextFrame:= nil; + Surface2Atlas^.cropInfo:= cropInfo; + exit; + end; + + if (surf^.w <= 512) and (surf^.h <= 512) then begin Surface2Atlas:= Surface2Tex_(surf, enableClamp); // run the atlas side by side for debugging + Surface2Atlas^.cropInfo:= cropInfo; ResetVertexArrays(Surface2Atlas); exit; end; new(Surface2Atlas); -Surface2Atlas^.PrevTexture:= nil; -Surface2Atlas^.NextTexture:= nil; -if TextureList <> nil then - begin - TextureList^.PrevTexture:= Surface2Atlas; - Surface2Atlas^.NextTexture:= TextureList - end; -TextureList:= Surface2Atlas; // Atlas allocation happens here later on. For now we just allocate one exclusive atlas per sprite new(Surface2Atlas^.atlas); @@ -206,13 +422,15 @@ Surface2Atlas^.isRotated:=false; Surface2Atlas^.surface:= surf; Surface2Atlas^.shared:= false; +Surface2Atlas^.nextFrame:= nil; +Surface2Atlas^.cropInfo:= cropInfo; if (surf^.format^.BytesPerPixel <> 4) then begin TryDo(false, 'Surface2Tex failed, expecting 32 bit surface', true); Surface2Atlas^.atlas^.id:= 0; - exit + exit; end; @@ -281,26 +499,27 @@ // if nil is passed nothing is done procedure FreeTexture(tex: PTexture); begin -if tex <> nil then + if tex <> nil then begin + FreeTexture(tex^.nextFrame); // free all frames linked to this animation + + if tex^.surface = nil then + begin + Dispose(tex); + exit; + end; + if tex^.shared then begin + SDL_FreeSurface(tex^.surface); 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 + glDeleteTextures(1, @tex^.atlas^.id); 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^.atlas^.id); - if (tex^.surface <> nil) then SDL_FreeSurface(tex^.surface); Dispose(tex); @@ -309,19 +528,14 @@ procedure initModule; begin +assign(logFile, 'out.log'); +rewrite(logFile); uAtlas.initModule; -TextureList:= nil; end; procedure freeModule; begin -if TextureList <> nil then - 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('Sprite not freed: width='+inttostr(LongInt(TextureList^.w))+' height='+inttostr(LongInt(TextureList^.h))+' priority='+inttostr(round(TextureList^.atlas^.priority*1000))); - FreeTexture(TextureList); - end +close(logFile); end; end.