aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/Classes
diff options
context:
space:
mode:
authorwhiteshark0 <whiteshark0@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-04-06 18:55:31 +0000
committerwhiteshark0 <whiteshark0@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-04-06 18:55:31 +0000
commitc5721f524c7a3a322b13458598ef4eeacab71406 (patch)
tree7897bc5abae0605a999b031f07648361d8b40c48 /Game/Code/Classes
parent4282e544fb31c3dcc25baae7b5e21062aff6edb1 (diff)
downloadusdx-c5721f524c7a3a322b13458598ef4eeacab71406.tar.gz
usdx-c5721f524c7a3a322b13458598ef4eeacab71406.tar.xz
usdx-c5721f524c7a3a322b13458598ef4eeacab71406.zip
New Covers.Cache Texture loading finished
There is some bug rescaling the textures, but i'm not sure where. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1010 b956fd51-792f-4845-bead-9b4dfca2ff2c
Diffstat (limited to '')
-rw-r--r--Game/Code/Classes/UCovers.pas188
-rw-r--r--Game/Code/Classes/UGraphic.pas4
-rw-r--r--Game/Code/Classes/UTexture.pas141
3 files changed, 267 insertions, 66 deletions
diff --git a/Game/Code/Classes/UCovers.pas b/Game/Code/Classes/UCovers.pas
index 3bd9e183..7a192e66 100644
--- a/Game/Code/Classes/UCovers.pas
+++ b/Game/Code/Classes/UCovers.pas
@@ -76,7 +76,7 @@ type
TCC_FileIndexHeader = record
Indicator: Array [1..4] of Char; //Contains INDE
- HighestID: Cardinal; //Highest ID of a Cover
+ HighestID: Integer; //Highest ID of a Cover
end;
TCC_FileIndex = record
@@ -108,10 +108,10 @@ type
IndexNeedRewrite: Boolean; //Index in CacheFile is overwritten by other Data
CacheReadOnly: Boolean; //Cache File is read only
- Function WriteHeader:Boolean;
+ Function WriteHeader(const ReWriteCache: Boolean = false):Boolean;
Function ReadHeader: Boolean;
Function ReadIndex: Boolean;
- Function WriteIndex: Boolean;
+ Function WriteIndex(const ReWriteCache: Boolean = false): Boolean;
Function AddTexData(Data: PCC_TextureData): Cardinal;
public
W: word;
@@ -124,11 +124,13 @@ type
constructor Create(const Filename: String);
procedure Load(const Filename: String);
- Function AddCover(FileName: string): Integer; //Returns ID, Checks Cover for Change, Updates Cover if required
- function CoverExists(FileName: string): Integer; //Returns ID by FilePath
+ Function AddCover(FileName: string): Integer; //Returns ID, Checks Cover for Change, Updates Cover if required
+ function CoverExists(FileName: string): Integer; //Returns ID by FilePath
procedure PrepareData(FileName: string);
Procedure LoadTextures;
Function ReWriteCache: Boolean; //Deletes old cover.cache file and writes new one
+
+ Function GetTexbyID(ID: Cardinal): Integer;
end;
var
@@ -146,7 +148,7 @@ constructor TCovers.Create(const Filename: String);
begin
HighestID := -1;
SetLength(Index, HighestID + 2);
- Load(Filename);
+ //Load(Filename);
end;
//----------------------------------------------
@@ -189,7 +191,7 @@ end;
//--------
// Writes Header(Resets File). Returns True if Writing succeed
//--------
-Function TCovers.WriteHeader:Boolean;
+Function TCovers.WriteHeader(const ReWriteCache: Boolean):Boolean;
var
F: File of TCC_FileHeader;
begin
@@ -198,7 +200,7 @@ begin
//Read Header
AssignFile(F, Filename);
try
- If (not FileExists(Filename)) then
+ If (not FileExists(Filename)) OR (ReWriteCache) then
ReWrite(F)
else
Reset(F);
@@ -222,16 +224,16 @@ var
I: Integer;
Procedure mReadLn(var S: String);
- var J: Integer;
+ var
+ Len: Integer;
begin
S := '';
- J := 0;
- Repeat
- Inc(J);
- SetLength(S, J);
- Read(F, Byte(S[J]));
- Until S[J] = #10
+ BlockRead(F, Len, 4); //Read Len of Filename String
+
+ //Read Filename String
+ SetLength(S, Len);
+ BlockRead(F, S[1], Len);
end;
begin
try
@@ -245,21 +247,22 @@ begin
If (IndexHeader.Indicator = cCC_IndexIndicator) then
begin
- Result := False;
-
+ Log.LogError('TCovers: loading Cover Index Header. HighestID: ' + InttoStr(IndexHeader.HighestID));
HighestID := IndexHeader.HighestID;
SetLength(Index, HighestID + 2);
Count := 0;
Result := True;
- If (HighestID > 0) then
+ If (HighestID >= 0) then
begin
//Read File Infos until (Eof or Footer)
I := 0;
- While (Not Eof(F)) AND ((I <= 0) OR (Index[I].FileIndex.DataStart <> High(Cardinal))) do
- begin
- If (I > HighestID) then
+ //While (Not Eof(F)) AND ((I <= 0) OR (Index[I-1].FileIndex.DataStart <> High(Cardinal))) do
+ Repeat
+ Log.LogError('TCovers: loading Cover Index. Position #' + InttoStr(I));
+ If (I > HighestID + 1) then
begin //Header IndexCOunt was wrong, running out of array
+ Log.LogError('TCovers: Wrong HighestID in Index Header. Running out of Array at Postion #' + InttoStr(I));
Inc(HighestID);
IndexNeedReWrite := True;
SetLength(Index, HighestID + 2);
@@ -268,15 +271,24 @@ begin
BlockRead(F, Index[I].FileIndex, SizeOf(TCC_FileIndex));
Index[I].TexID := -1;
- If (Index[I].FileIndex.DataStart <> High(Cardinal)) AND (Not Eof(F)) then
+ If (Index[I].FileIndex.DataStart = High(Cardinal)) then
+ begin //Found Footer
+ Log.LogError('TCovers: Found footer at Position #' + InttoStr(I));
+ Break;
+ end;
+
+ If (Not Eof(F)) then
begin
//Read Filename
mReadLn(Index[I].Filename);
- Inc(Count);
+
+ Log.LogError('TCovers: Cover loaded: ' + Index[I].Filename);
+ If (Index[I].FileIndex.DataStart <> 0) AND (Index[I].FileIndex.DataStart <> 1) then
+ Inc(Count);
end;
Inc(I);
- end;
+ Until Eof(F);
If (Index[HighestID + 1].FileIndex.DataStart = High(Cardinal)) then
begin //No Footer found
@@ -297,21 +309,24 @@ end;
//--------
// Writes Index. Returns True if Writing succeed
//--------
-Function TCovers.WriteIndex: Boolean;
+Function TCovers.WriteIndex(const ReWriteCache: Boolean): Boolean;
var
F: File of Byte;
IndexHeader: TCC_FileIndexHeader;
I: Integer;
Procedure mWriteLn(var S: String);
- var N: Byte;
+ var Len: Integer;
begin
- BlockWrite(F, S, Length(S));
- N := Byte(#10);
- Write(F, N);
+ //Write Length of String
+ Len := Length(S);
+ BlockWrite(F, Len, 4);
+
+ //Write String
+ BlockWrite(F, S[1], Len);
end;
begin
- Result := WriteHeader;
+ Result := WriteHeader(ReWriteCache);
If (Result) then
begin
@@ -338,9 +353,12 @@ begin
For I := 0 to HighestID+1 do
begin
BlockWrite(F, Index[I].FileIndex, SizeOf(TCC_FileIndex));
- mWriteLn(Index[I].Filename);
+ If (I <= HighestID) then
+ mWriteLn(Index[I].Filename);
end;
+ IndexNeedRewrite := False;
+
finally
CloseFile(F);
end;
@@ -363,7 +381,7 @@ begin
Reset(F);
Seek(F, Header.DataStart + Header.DataLength);
- BlockWrite(F, Data, SizeOf(TCC_TextureData));
+ BlockWrite(F, Data^, SizeOf(TCC_TextureData));
Result := Header.DataStart + Header.DataLength;
Inc(Header.DataLength, SizeOf(TCC_TextureData));
@@ -382,6 +400,8 @@ procedure TCovers.Load(const Filename: String);
var
Succeed: Boolean;
begin
+ Log.LogError('TCovers: Load cache from file: ''' + Filename + '''');
+
Self.Filename := Filename;
Succeed := False;
If (FileExists(Filename)) then
@@ -398,7 +418,10 @@ begin
If not Succeed and not CacheReadOnly then
If not (ReWriteCache) then
+ begin
CacheReadOnly := True;
+ Log.LogError('TCovers: Cache readonly!');
+ end;
end;
Function TCovers.AddCover(FileName: string): Integer;
@@ -407,11 +430,13 @@ begin
Result := CoverExists(Filename);
If (Result = -1) then
begin //Add Cover(Does not exist)
+ Log.LogError('TCovers: Adding cover: ''' + Filename + '''');
If (Count <= HighestID) then
begin //There is an empty slot, Search It
+ Log.LogError('TCovers: Searching for Empty Slot');
For I := 0 to HighestID do
If (Index[I].FileIndex.DataStart = 0) then
- begin //Found the Slot
+ begin //Found that Slot
Result := I;
Break;
end;
@@ -419,6 +444,7 @@ begin
If (Result = -1) then
begin //Attach it to the End
+ Log.LogError('TCovers: Attach Cover to the end');
Inc(HighestID);
SetLength(Index, HighestID + 2);
Result := HighestID;
@@ -427,6 +453,7 @@ begin
Index[Result].Filename := Filename;
Index[Result].TexID := -1;
Index[Result].FileIndex.DataStart := 1;
+ Log.LogError('TCovers: Cover Added, ID: ' + InttoStr(Result));
end
else
begin //Check if File has Changed
@@ -476,6 +503,8 @@ Function TCovers.ReWriteCache: Boolean;
begin
If not CacheReadOnly then
begin
+ Log.LogError('TCovers: Rewriting Cache');
+
Header.FileTyp := cCC_HeaderText;
Header.Version := cCC_HeaderVersion;
Header.Hash := MakeHash;
@@ -485,7 +514,10 @@ begin
Header.DataLength := 0;
Header.IndexStart := Header.DataStart + Header.DataLength + 4;
- Result := WriteIndex;
+ HighestID := -1;
+ SetLength(Index, HighestID + 2);
+
+ Result := WriteIndex(True);
end
else
Result := False;
@@ -514,20 +546,100 @@ end;
Procedure TCovers.LoadTextures;
var
I: Integer;
-
+ TexData: PCC_TextureData;
+ CachedData: TCC_TextureData;
+ F: File of Byte;
+
Function LoadCover: Integer;
begin
+ Result := -1;
+
+ If (Index[I].FileIndex.DataStart = 1) then
+ begin //This Texture is new and has to be loaded
+ TexData := Texture.GetCoverThumbnail(Index[I].Filename);
+ If (TexData <> nil) then
+ begin
+ If not (CacheReadonly) then
+ begin //Save this Tex to Cache
+ Index[I].FileIndex.DataStart := AddTexData(TexData);
+ If (Index[I].FileIndex.DataStart = 0) then
+ begin
+ CacheReadOnly := True; //Failed to write Data
+ Log.LogError('Failed to Write TextureData to Cache');
+ end;
+ end;
+ //Create Texture
+ glGenTextures(1, @Result);
+
+ glBindTexture(GL_TEXTURE_2D, Result);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ //glTexImage2D(GL_TEXTURE_2D, 0, 3, cCC_CoverW, cCC_CoverH, 0, GL_RGB, GL_UNSIGNED_BYTE, Data);
+ glTexImage2D(GL_TEXTURE_2D, 0, 3, cCC_CoverW, cCC_CoverH, 0, GL_RGB, GL_UNSIGNED_BYTE, TexData);
+ end
+ else
+ Log.LogError('Couldn''t get Thumbnail Data');
+ end
+ Else If (Index[I].FileIndex.DataStart > 1) then
+ begin //This texture is already in Cache, Load it from there
+ try
+ Log.LogError('TCovers: Loading Cover #' + InttoStr(I) + ' from Cache at Position: ' + InttoStr(Index[I].FileIndex.DataStart));
+ Assign(F, Filename);
+ try
+ Reset(F);
+ Seek(F, Index[I].FileIndex.DataStart);
+ BlockRead(F, CachedData, SizeOf(TCC_TextureData));
+ finally
+ CloseFile(F);
+ end;
+
+
+ //Create Texture
+ glGenTextures(1, @Result);
+
+ if (Result > 0) then
+ begin
+
+ glBindTexture(GL_TEXTURE_2D, Result);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ //glTexImage2D(GL_TEXTURE_2D, 0, 3, cCC_CoverW, cCC_CoverH, 0, GL_RGB, GL_UNSIGNED_BYTE, Data);
+ glTexImage2D(GL_TEXTURE_2D, 0, 3, cCC_CoverW, cCC_CoverH, 0, GL_RGB, GL_UNSIGNED_BYTE, @CachedData[0]);
+ end
+ else
+ Log.LogError('TCovers: Error Generating Texture');
+ except
+ Log.LogError('TCovers: Error during loading');
+ end;
+ end;
end;
begin
- //Texture.SetCoverSize(cCC_CoverW, cCC_CoverH);
+ Texture.SetCoverSize(cCC_CoverW, cCC_CoverH);
+ Log.LogError('TCovers: LoadingTextures');
For I := 0 to HighestID do
begin //Load all the Covers
- {Index[I].TexID
+ If (Index[I].FileIndex.DataStart > 0) then
+ Index[I].TexID := LoadCover; //No empty SLot -> Load the Texture
- Index[I].FileIndex.}
+ Log.LogError('TCovers: Texture for ID#' + InttoStr(I) + ': ' + InttoStr(Index[I].TexID));
end;
+
+ If IndexNeedRewrite then
+ WriteIndex;
+end;
+
+Function TCovers.GetTexbyID(ID: Cardinal): Integer;
+begin
+ If (ID <= HighestID) then
+ Result := Index[ID].TexID
+ else
+ Result := -1;
end;
end.
diff --git a/Game/Code/Classes/UGraphic.pas b/Game/Code/Classes/UGraphic.pas
index afb986e1..90772ca8 100644
--- a/Game/Code/Classes/UGraphic.pas
+++ b/Game/Code/Classes/UGraphic.pas
@@ -253,6 +253,7 @@ uses UMain,
UIni,
UDisplay,
UCommandLine,
+ UCovers,
{$IFNDEF FPC}
Graphics,
{$ENDIF}
@@ -538,7 +539,8 @@ begin
// the mainthread have to know somehow what opengl function have to be called with which parameters like
// texturetype, textureobjekt, textur-buffer-adress, ...
-
+ //Load Covers from Cache and new added/ Updated Covers
+ Covers.LoadTextures;
// wait for loading thread to finish
// funktioniert so auch noch nicht - currently dos not work this way
diff --git a/Game/Code/Classes/UTexture.pas b/Game/Code/Classes/UTexture.pas
index 35757a8e..712c54bf 100644
--- a/Game/Code/Classes/UTexture.pas
+++ b/Game/Code/Classes/UTexture.pas
@@ -81,6 +81,11 @@ type
TTextureUnit = class
private
+ TnWidth, TnHeight: Cardinal; //Width and Height of the Cover Thumbnails
+
+ TnBuffer: array of byte;
+ TnSurface: PSDL_Surface;
+
function LoadImage(const Identifier: string): PSDL_Surface;
function pixfmt_eq(fmt1,fmt2: PSDL_Pixelformat): boolean;
procedure AdjustPixelFormat(var TexSurface: PSDL_Surface; Typ: TTextureType);
@@ -88,7 +93,6 @@ type
procedure ScaleTexture(var TexSurface: PSDL_Surface; W,H: Cardinal);
procedure FitTexture(var TexSurface: PSDL_Surface; W,H: Cardinal);
procedure ColorizeTexture(TexSurface: PSDL_Surface; Col: Cardinal);
-
public
Limit: integer;
CreateCacheMipmap: boolean;
@@ -106,6 +110,9 @@ type
procedure UnloadTexture(const Name: string; Typ: TTextureType; Col: Cardinal; FromCache: boolean); overload;
//procedure FlushTextureDatabase();
+ Function GetCoverThumbnail(const Name: string): Pointer;
+ Procedure SetCoverSize(W, H: Integer);
+
Constructor Create;
Destructor Destroy; override;
@@ -119,15 +126,10 @@ var
Mipmapping: Boolean;
- CacheMipmap: array[0..256*256*3-1] of byte; // 3KB
- CacheMipmapSurface: PSDL_Surface;
-
-
implementation
uses ULog,
DateUtils,
- UCovers,
UThemes,
{$ifdef LINUX}
fileutil,
@@ -521,7 +523,7 @@ begin
Log.LogStatus( 'ERROR Could not load texture' , Identifier +' '+ TextureTypeToStr(Typ) );
Exit;
end;
-
+
// convert pixel format as needed
{$ifdef blindydebug}
Log.LogStatus('',' AdjustPixelFormat');
@@ -536,7 +538,7 @@ begin
if (newWidth > Limit) then
newWidth := Limit;
-
+
if (newHeight > Limit) then
newHeight := Limit;
@@ -560,36 +562,36 @@ begin
// don't actually understand, if this is needed...
// this should definately be changed... together with all this
// cover cache stuff
- if (CreateCacheMipmap) and (Typ = TEXTURE_TYPE_PLAIN) then
+ {if (CreateCacheMipmap) and (Typ = TEXTURE_TYPE_PLAIN) then
begin
- {$ifdef blindydebug}
+ {$ifdef blindydebug}{
Log.LogStatus('',' JB-1 : Minimap');
{$endif}
- if (Covers.W <= 256) and (Covers.H <= 256) then
+ {if (TnWidth <= 256) and (TnHeight <= 256) then
begin
- {$ifdef blindydebug}
+ {$ifdef blindydebug}{
Log.LogStatus('',' GetScaledTexture('''+inttostr(Covers.W)+''','''+inttostr(Covers.H)+''') (for CacheMipmap)');
- {$endif}
- MipmapSurface:=GetScaledTexture(TexSurface, Covers.W, Covers.H);
+ {$endif}{
+ MipmapSurface:=GetScaledTexture(TexSurface, TnWidth, TnHeight);
if assigned(MipmapSurface) then
begin
- {$ifdef blindydebug}
+ {$ifdef blindydebug}{
Log.LogStatus('',' ok');
Log.LogStatus('',' BlitSurface Stuff');
- {$endif}
+ {$endif}{
// creating and freeing the surface could be done once, if Cover.W and Cover.H don't change
- CacheMipmapSurface:=SDL_CreateRGBSurfaceFrom(@CacheMipmap[0], Covers.W, Covers.H, 24, Covers.W*3, $000000ff, $0000ff00, $00ff0000, 0);
- SDL_BlitSurface(MipMapSurface, nil, CacheMipmapSurface, nil);
- SDL_FreeSurface(CacheMipmapSurface);
- {$ifdef blindydebug}
+ TnSurface:=SDL_CreateRGBSurfaceFrom(@TnBuffer[0], TnWidth, TnHeight, 24, TnWidth*3, $000000ff, $0000ff00, $00ff0000, 0);
+ SDL_BlitSurface(TnSurface, nil, TnSurface, nil);
+ SDL_FreeSurface(TnSurface);
+ {$ifdef blindydebug}{
Log.LogStatus('',' ok');
Log.LogStatus('',' SDL_FreeSurface (CacheMipmap)');
- {$endif}
- SDL_FreeSurface(MipmapSurface);
- {$ifdef blindydebug}
+ {$endif}{
+ SDL_FreeSurface(TnSurface);
+ {$ifdef blindydebug}{
Log.LogStatus('',' ok');
- {$endif}
+ {$endif}{
end
else
begin
@@ -597,7 +599,7 @@ begin
end;
end;
// should i create a cache texture, if Covers.W/H are larger?
- end;
+ end; }
{$ifdef blindydebug}
Log.LogStatus('',' JB-2');
@@ -744,7 +746,7 @@ begin
end;
// use preloaded texture
- if (not FromCache) or (FromCache and (Covers.CoverExists(Name) < 0)) then
+ if (not FromCache) or (FromCache{ and (Covers.CoverExists(Name) < 0)}) then
begin
// use full texture
if TextureDatabase.Texture[T].Texture.TexNum = -1 then
@@ -763,7 +765,7 @@ begin
Result := TextureDatabase.Texture[T].Texture;
end;
- if FromCache and (Covers.CoverExists(Name) >= 0) then
+ {if FromCache and (Covers.CoverExists(Name) >= 0) then
begin
// use cache texture
C := Covers.CoverExists(Name);
@@ -777,6 +779,91 @@ begin
// use texture
Result := TextureDatabase.Texture[T].TextureCache;
+ end;}
+end;
+
+//--------
+// Returns Pointer to an Array of Byte containing the Texture Data in the
+// requested Size
+//--------
+Function TTextureUnit.GetCoverThumbnail(const Name: string): Pointer;
+var
+ TexSurface: PSDL_Surface;
+ newHeight, newWidth: Cardinal;
+const
+ Typ = TEXTURE_TYPE_PLAIN;
+begin
+ Result := nil;
+ If (FileExists(Name)) then
+ begin
+ {$ifdef blindydebug}
+ Log.LogStatus('',' ----------------------------------------------------');
+ Log.LogStatus('',' GetCoverThumbnail('''+Name+''')');
+ {$endif}
+ TexSurface := LoadImage(Name);
+ {$ifdef blindydebug}
+ Log.LogStatus('',' ok');
+ {$endif}
+ if assigned(TexSurface) then
+ begin
+ // convert pixel format as needed
+ {$ifdef blindydebug}
+ Log.LogStatus('',' AdjustPixelFormat');
+ {$endif}
+ AdjustPixelFormat(TexSurface, Typ);
+
+ {$ifdef blindydebug}
+ Log.LogStatus('',' ok');
+ {$endif}
+
+ // Scale Texture to Covers Dimensions
+ {$ifdef blindydebug}
+ Log.LogStatus('',' ScaleTexture('''+inttostr(tnWidth)+''','''+inttostr(TnHeight)+''') (for CacheMipmap)');
+ {$endif}
+ ScaleTexture(TexSurface, TnWidth, TnHeight);
+
+ if assigned(TexSurface) AND assigned(TnSurface) then
+ begin
+ {$ifdef blindydebug}
+ Log.LogStatus('',' ok');
+ Log.LogStatus('',' BlitSurface Stuff');
+ {$endif}
+
+ SDL_BlitSurface(TexSurface, nil, TnSurface, nil);
+
+ Result := @TnBuffer[0];
+
+ {$ifdef blindydebug}
+ Log.LogStatus('',' ok');
+ {$endif}
+ end
+ else
+ Log.LogStatus(' Error creating Cover Thumbnail',' LoadTexture('''+Name+''')');
+ end
+ else
+ Log.LogStatus( 'ERROR Could not load texture for Cover Thumbnail: ' , name+' '+ TextureTypeToStr(Typ) );
+
+ SDL_FreeSurface(TexSurface);
+ end;
+end;
+
+//--------
+// Sets Textures Thumbnail Size Vars and Sets LEngth of DataBuffer and Create CoverSurface
+//--------
+Procedure TTextureUnit.SetCoverSize(W, H: Integer);
+begin
+ If (H > 0) AND (W > 0) then
+ begin
+ TnWidth := W;
+ TnHeight := H;
+
+ SetLength(TnBuffer, TnWidth * TnHeight * 3);
+
+ //Free if necesary and Create new Surface at Data
+ If (Assigned(TnSurface)) then
+ SDL_FreeSurface(TnSurface);
+
+ TnSurface := SDL_CreateRGBSurfaceFrom(@TnBuffer[0], TnWidth, TnHeight, 24, TnWidth*3, $000000ff, $0000ff00, $00ff0000, 0);
end;
end;