{* UltraStar Deluxe - Karaoke Game
*
* UltraStar Deluxe is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*}
unit UTexture;
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses
gl,
glu,
glext,
Classes,
SysUtils,
UCommon,
UPath,
SDL,
SDL_Image;
type
PTexture = ^TTexture;
TTexture = record
TexNum: GLuint;
X: real;
Y: real;
Z: real;
W: real;
H: real;
ScaleW: real; // for dynamic scalling while leaving width constant
ScaleH: real; // for dynamic scalling while leaving height constant
Rot: real; // 0 - 2*pi
Int: real; // intensity
ColR: real;
ColG: real;
ColB: real;
TexW: real; // percentage of width to use [0..1]
TexH: real; // percentage of height to use [0..1]
TexX1: real;
TexY1: real;
TexX2: real;
TexY2: real;
Alpha: real;
Name: IPath; // experimental for handling cache images. maybe it's useful for dynamic skins
end;
type
TTextureType = (
TEXTURE_TYPE_PLAIN, // Plain (alpha = 1)
TEXTURE_TYPE_TRANSPARENT, // Alpha is used
TEXTURE_TYPE_COLORIZED // Alpha is used; Hue of the HSV color-model will be replaced by a new value
);
const
TextureTypeStr: array[TTextureType] of string = (
'Plain',
'Transparent',
'Colorized'
);
function TextureTypeToStr(TexType: TTextureType): string;
function ParseTextureType(const TypeStr: string; Default: TTextureType): TTextureType;
procedure AdjustPixelFormat(var TexSurface: PSDL_Surface; Typ: TTextureType);
type
PTextureEntry = ^TTextureEntry;
TTextureEntry = record
Name: IPath;
Typ: TTextureType;
Color: cardinal;
// we use normal TTexture, it's easier to implement and if needed - we copy ready data
Texture: TTexture; // Full-size texture
TextureCache: TTexture; // Thumbnail texture
end;
TTextureDatabase = class
private
Texture: array of TTextureEntry;
public
procedure AddTexture(var Tex: TTexture; Typ: TTextureType; Color: cardinal; Cache: boolean);
function FindTexture(const Name: IPath; Typ: TTextureType; Color: cardinal): integer;
end;
TTextureUnit = class
//private
public
Limit: integer;
procedure AddTexture(var Tex: TTexture; Typ: TTextureType; Cache: boolean = false); overload;
procedure AddTexture(var Tex: TTexture; Typ: TTextureType; Color: cardinal; Cache: boolean = false); overload;
function GetTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean = false): TTexture; overload;
function GetTexture(const Name: IPath; Typ: TTextureType; Col: LongWord; FromCache: boolean = false): TTexture; overload;
function LoadTexture(const Identifier: IPath; Typ: TTextureType; Col: LongWord): TTexture; overload;
function LoadTexture(const Identifier: IPath): TTexture; overload;
function CreateTexture(Data: PChar; const Name: IPath; Width, Height: word; BitsPerPixel: byte): TTexture;
procedure UnloadTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean); overload;
procedure UnloadTexture(const Name: IPath; Typ: TTextureType; Col: cardinal; FromCache: boolean); overload;
//procedure FlushTextureDatabase();
constructor Create;
destructor Destroy; override;
end;
var
Texture: TTextureUnit;
TextureDatabase: TTextureDatabase;
SupportsNPOT: Boolean;
implementation
uses
DateUtils,
StrUtils,
Math,
ULog,
UCovers,
UThemes,
UImage;
procedure AdjustPixelFormat(var TexSurface: PSDL_Surface; Typ: TTextureType);
var
TempSurface: PSDL_Surface;
NeededPixFmt: PSDL_Pixelformat;
begin
if (Typ = TEXTURE_TYPE_PLAIN) then
NeededPixFmt := @PixelFmt_RGB
else if (Typ = TEXTURE_TYPE_TRANSPARENT) or
(Typ = TEXTURE_TYPE_COLORIZED) then
NeededPixFmt := @PixelFmt_RGBA
else
NeededPixFmt := @PixelFmt_RGB;
if not PixelformatEquals(TexSurface^.format, NeededPixFmt) then
begin
TempSurface := TexSurface;
TexSurface := SDL_ConvertSurface(TempSurface, NeededPixFmt, SDL_SWSURFACE);
SDL_FreeSurface(TempSurface);
end;
end;
{ TTextureDatabase }
procedure TTextureDatabase.AddTexture(var Tex: TTexture; Typ: TTextureType; Color: cardinal; Cache: boolean);
var
TextureIndex: integer;
begin
TextureIndex := FindTexture(Tex.Name, Typ, Color);
if (TextureIndex = -1) then
begin
TextureIndex := Length(Texture);
SetLength(Texture, TextureIndex+1);
Texture[TextureIndex].Name := Tex.Name;
Texture[TextureIndex].Typ := Typ;
Texture[TextureIndex].Color := Color;
end;
if (Cache) then
Texture[TextureIndex].TextureCache := Tex
else
Texture[TextureIndex].Texture := Tex;
end;
function TTextureDatabase.FindTexture(const Name: IPath; Typ: TTextureType; Color: cardinal): integer;
var
TextureIndex: integer;
CurrentTexture: PTextureEntry;
begin
Result := -1;
for TextureIndex := 0 to High(Texture) do
begin
CurrentTexture := @Texture[TextureIndex];
if (CurrentTexture.Name.Equals(Name)) and
(CurrentTexture.Typ = Typ) then
begin
// colorized textures must match in their color too
if (CurrentTexture.Typ <> TEXTURE_TYPE_COLORIZED) or
(CurrentTexture.Color = Color) then
begin
Result := TextureIndex;
Break;
end;
end;
end;
end;
{ TTextureUnit }
constructor TTextureUnit.Create;
begin
inherited Create;
TextureDatabase := TTextureDatabase.Create;
Log.LogInfo('OpenGL vendor ' + glGetString(GL_VENDOR), 'TTextureUnit.Create');
Log.LogInfo('OpenGL renderer ' + glGetString(GL_RENDERER), 'TTextureUnit.Create');
Log.LogInfo('OpenGL version ' + glGetString(GL_VERSION), 'TTextureUnit.Create');
SupportsNPOT := AnsiContainsStr(glGetString(GL_EXTENSIONS),'texture_non_power_of_two');
Log.LogInfo('OpenGL TextureNPOT-support: ' + BoolToStr(SupportsNPOT), 'TTextureUnit.Create');
end;
destructor TTextureUnit.Destroy;
begin
TextureDatabase.Free;
inherited Destroy;
end;
procedure TTextureUnit.AddTexture(var Tex: TTexture; Typ: TTextureType; Cache: boolean);
begin
TextureDatabase.AddTexture(Tex, Typ, 0, Cache);
end;
procedure TTextureUnit.AddTexture(var Tex: TTexture; Typ: TTextureType; Color: cardinal; Cache: boolean);
begin
TextureDatabase.AddTexture(Tex, Typ, Color, Cache);
end;
function TTextureUnit.LoadTexture(const Identifier: IPath): TTexture;
begin
Result := LoadTexture(Identifier, TEXTURE_TYPE_PLAIN, 0);
end;
function TTextureUnit.LoadTexture(const Identifier: IPath; Typ: TTextureType; Col: LongWord): TTexture;
var
TexSurface: PSDL_Surface;
newWidth, newHeight: integer;
oldWidth, oldHeight: integer;
ActTex: GLuint;
begin
// zero texture data
FillChar(Result, SizeOf(Result), 0);
// load texture data into memory
TexSurface := LoadImage(Identifier);
if not assigned(TexSurface) then
begin
Log.LogError('Could not load texture: "' + Identifier.ToNative +'" with type "'+ TextureTypeToStr(Typ) +'"',
'TTextureUnit.LoadTexture');
Exit;
end;
// convert pixel format as needed
AdjustPixelFormat(TexSurface, Typ);
// adjust texture size (scale down, if necessary)
newWidth := TexSurface.W;
newHeight := TexSurface.H;
if (newWidth > Limit) then
newWidth := Limit;
if (newHeight > Limit) then
newHeight := Limit;
if (TexSurface.W > newWidth) or (TexSurface.H > newHeight) then
ScaleImage(TexSurface, newWidth, newHeight);
// now we might colorize the whole thing
if (Typ = TEXTURE_TYPE_COLORIZED) then
ColorizeImage(TexSurface, Col);
// save actual dimensions of our texture
oldWidth := newWidth;
oldHeight := newHeight;
if (SupportsNPOT = false) then
begin
// make texture dimensions be powers of 2
newWidth := Round(Power(2, Ceil(Log2(newWidth))));
newHeight := Round(Power(2, Ceil(Log2(newHeight))));
if (newHeight <> oldHeight) or (newWidth <> oldWidth) then
FitImage(TexSurface, newWidth, newHeight);
end;
// at this point we have the image in memory...
// scaled so that dimensions are powers of 2
// and converted to either RGB or RGBA
// if we got a Texture of Type Plain, Transparent or Colorized,
// then we're done manipulating it
// and could now create our openGL texture from it
// prepare OpenGL texture
glGenTextures(1, @ActTex);
glBindTexture(GL_TEXTURE_2D, ActTex);
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);
// load data into gl texture
if (Typ = TEXTURE_TYPE_TRANSPARENT) or
(Typ = TEXTURE_TYPE_COLORIZED) then
begin
{$IFDEF FPC_BIG_ENDIAN}
glTexImage2D(GL_TEXTURE_2D, 0, 4, newWidth, newHeight, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, TexSurface.pixels);
{$ELSE}
glTexImage2D(GL_TEXTURE_2D, 0, 4, newWidth, newHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, TexSurface.pixels);
{$ENDIF}
end
else //if Typ = TEXTURE_TYPE_PLAIN then
begin
{$IFDEF FPC_BIG_ENDIAN}
glTexImage2D(GL_TEXTURE_2D, 0, 3, newWidth, newHeight, 0, GL_BGR, GL_UNSIGNED_BYTE, TexSurface.pixels);
{$ELSE}
glTexImage2D(GL_TEXTURE_2D, 0, 3, newWidth, newHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, TexSurface.pixels);
{$ENDIF}
end;
// setup texture struct
with Result do
begin
X := 0;
Y := 0;
Z := 0;
W := oldWidth;
H := oldHeight;
ScaleW := 1;
ScaleH := 1;
Rot := 0;
TexNum := ActTex;
TexW := oldWidth / newWidth;
TexH := oldHeight / newHeight;
Int := 1;
ColR := 1;
ColG := 1;
ColB := 1;
Alpha := 1;
// new test - default use whole texure, taking TexW and TexH as const and changing these
TexX1 := 0;
TexY1 := 0;
TexX2 := 1;
TexY2 := 1;
Name := Identifier;
end;
SDL_FreeSurface(TexSurface);
end;
function TTextureUnit.GetTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean): TTexture;
begin
Result := GetTexture(Name, Typ, 0, FromCache);
end;
function TTextureUnit.GetTexture(const Name: IPath; Typ: TTextureType; Col: LongWord; FromCache: boolean): TTexture;
var
TextureIndex: integer;
begin
if (Name.IsUnset) then
begin
// zero texture data
FillChar(Result, SizeOf(Result), 0);
Exit;
end;
if (FromCache) then
begin
TextureIndex := TextureDatabase.FindTexture(Name, Typ, Col);
if (TextureIndex > -1) then
Result := TextureDatabase.Texture[TextureIndex].TextureCache;
Exit;
end;
// find texture entry in database
TextureIndex := TextureDatabase.FindTexture(Name, Typ, Col);
if (TextureIndex = -1) then
begin
// create texture entry in database
TextureIndex := Length(TextureDatabase.Texture);
SetLength(TextureDatabase.Texture, TextureIndex+1);
TextureDatabase.Texture[TextureIndex].Name := Name;
TextureDatabase.Texture[TextureIndex].Typ := Typ;
TextureDatabase.Texture[TextureIndex].Color := Col;
// inform database that no textures have been loaded into memory
TextureDatabase.Texture[TextureIndex].Texture.TexNum := 0;
TextureDatabase.Texture[TextureIndex].TextureCache.TexNum := 0;
end;
// load full texture
if (TextureDatabase.Texture[TextureIndex].Texture.TexNum = 0) then
TextureDatabase.Texture[TextureIndex].Texture := LoadTexture(Name, Typ, Col);
// use texture
Result := TextureDatabase.Texture[TextureIndex].Texture;
end;
function TTextureUnit.CreateTexture(Data: PChar; const Name: IPath; Width, Height: word; BitsPerPixel: byte): TTexture;
var
//Error: integer;
ActTex: GLuint;
begin
glGenTextures(1, @ActTex); // ActText = new texture number
glBindTexture(GL_TEXTURE_2D, ActTex);
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);
{$IFDEF FPC_BIG_ENDIAN}
glTexImage2D(GL_TEXTURE_2D, 0, 3, Width, Height, 0, GL_BGR, GL_UNSIGNED_BYTE, Data);
{$ELSE}
glTexImage2D(GL_TEXTURE_2D, 0, 3, Width, Height, 0, GL_RGB, GL_UNSIGNED_BYTE, Data);
{$ENDIF}
{
if Mipmapping then
begin
Error := gluBuild2DMipmaps(GL_TEXTURE_2D, 3, W, H, GL_RGB, GL_UNSIGNED_BYTE, @Data[0]);
// FPC_BIG_ENDIAN Error := gluBuild2DMipmaps(GL_TEXTURE_2D, 3, W, H, GL_BGR, GL_UNSIGNED_BYTE, @Data[0]);
if Error > 0 then
Log.LogError('gluBuild2DMipmaps() failed', 'TTextureUnit.CreateTexture');
end;
}
Result.X := 0;
Result.Y := 0;
Result.Z := 0;
Result.W := 0;
Result.H := 0;
Result.ScaleW := 1;
Result.ScaleH := 1;
Result.Rot := 0;
Result.TexNum := ActTex;
Result.TexW := 1;
Result.TexH := 1;
Result.Int := 1;
Result.ColR := 1;
Result.ColG := 1;
Result.ColB := 1;
Result.Alpha := 1;
// new test - default use whole texure, taking TexW and TexH as const and changing these
Result.TexX1 := 0;
Result.TexY1 := 0;
Result.TexX2 := 1;
Result.TexY2 := 1;
Result.Name := Name;
end;
procedure TTextureUnit.UnloadTexture(const Name: IPath; Typ: TTextureType; FromCache: boolean);
begin
UnloadTexture(Name, Typ, 0, FromCache);
end;
procedure TTextureUnit.UnloadTexture(const Name: IPath; Typ: TTextureType; Col: cardinal; FromCache: boolean);
var
T: integer;
TexNum: GLuint;
begin
T := TextureDatabase.FindTexture(Name, Typ, Col);
if not FromCache then
begin
TexNum := TextureDatabase.Texture[T].Texture.TexNum;
if TexNum > 0 then
begin
glDeleteTextures(1, PGLuint(@TexNum));
TextureDatabase.Texture[T].Texture.TexNum := 0;
//Log.LogError('Unload texture no '+IntToStr(TexNum));
end;
end
else
begin
TexNum := TextureDatabase.Texture[T].TextureCache.TexNum;
if TexNum > 0 then
begin
glDeleteTextures(1, @TexNum);
TextureDatabase.Texture[T].TextureCache.TexNum := 0;
//Log.LogError('Unload texture cache no '+IntToStr(TexNum));
end;
end;
end;
(* This needs some work
procedure TTextureUnit.FlushTextureDatabase();
var
i: integer;
Tex: ^TTexture;
begin
for i := 0 to High(TextureDatabase.Texture) do
begin
// only delete non-cached entries
if (TextureDatabase.Texture[i].Texture.TexNum > 0) then
begin
Tex := @TextureDatabase.Texture[i].Texture;
glDeleteTextures(1, PGLuint(Tex^.TexNum));
Tex^.TexNum := 0;
end;
end;
end;
*)
function TextureTypeToStr(TexType: TTextureType): string;
begin
Result := TextureTypeStr[TexType];
end;
function ParseTextureType(const TypeStr: string; Default: TTextureType): TTextureType;
var
TextureType: TTextureType;
UpCaseStr: string;
begin
UpCaseStr := UpperCase(TypeStr);
for TextureType := Low(TextureTypeStr) to High(TextureTypeStr) do
begin
if (UpCaseStr = UpperCase(TextureTypeStr[TextureType])) then
begin
Result := TextureType;
Exit;
end;
end;
Log.LogInfo('Unknown texture type: "' + TypeStr + '". Using default texture type "'
+ TextureTypeToStr(Default) + '"', 'UTexture.ParseTextureType');
Result := Default;
end;
end.