PDA

View Full Version : Opengl Texture Management



technomage
02-09-2005, 06:35 PM
Hi everyone

I've been looking a possible texture management solutions for my current project and thought I'd get some input from you all.

The problem
---------------


opengl only allows a limited number of resident texture objects (got by glGenTextures). But most games have a large number of textures that need to be used.

The solution
--------------

write a management class that will use glCopyTexImage2D to update the textures.



What I think would be usefull to many people is how to go about writting the management class.

Personally the one I'm writting will allow the developer to pregenerate a number of blank texture objects of various sizes. Then as the game/app requires a texture it checks to see if it is already active and if not gets a texture object id from the management system and copys the pixel data to video memory.

There are also a number of things to do with unloading images if they are no used very often.

Anyone want to comment on how they solved this problem? I'm hoping to get varous poonts of view for people to look at. once my own code is written and working I'll post it on this thread for people to use.

Dean

JSoftware
02-09-2005, 11:08 PM
i personally think it would be much too slow. i believe opengl would be much faster to swap the textures itself..

When you gen extra textures and video mem is filled it will generate in system mem and swap it to video texbuffer when it's needed. glcopyteximage2d takes alot of time

but anyways.. i hate texture management :P write a class where i can set things like compresssion and which filtering mode to be used and make it able to load all the much used gfx formats from filename and tstreams, and i'll love you for the rest of my days :D

Regards Jeppe

Paulius
03-09-2005, 06:09 PM
You?¢_~re driver is likely to just swap textures into vram when they?¢_Tre needed. What you should do is implement a user defined texture detail setting and make sure that when it?¢_Ts set to minimum all downsized and compressed textures fit in you?¢_Tre minimum target vram.

technomage
04-09-2005, 01:10 PM
My main reason for asking this question is that I hit a snag in my current projet where glGenTextures returns a 0 in for the texture id, which doesn't seem to be valid. It appears to do this after it has already loaded a number of textures (about 5-6). I couldn't see the problem so I begain looking into the glCopy option.

My current texture class can load from file or tstream, it used SDL_Image and SDL_RWops to accomplish this. If you're interested I'll post the code for you to look at :D

technomage
04-09-2005, 08:17 PM
well here is my class and manager , let me know what you think.


unit SDLGLTexture;

interface

uses SDL, SDL_Image, SDLUtils, gl,glu, Classes;

type

TSDLGLTextureSize = (ts64x64,ts128x64,ts128x128,ts256x128,ts256x256,ts 512x512,ts1024x1024,ts1024x512, ts1024x128);

TSDLGLTexture = class;

TSDLGLTexture = class
private
FHeight: UInt16;
FWidth: UInt16;
FSize: TSDLGLTextureSize;
FSurface: PSDL_Surface;
FTextureID: GLuint;
protected
function CreateBlankSurface: PSDL_Surface;
function CreateSurfaceFromFile(Filename: string): PSDL_Surface;
function CreateSurfaceFromStream(AStream: TStream): PSDL_Surface; virtual;
procedure CreateTexture;
procedure RefreshTexture;
public
constructor Create(ASize: TSDLGLTextureSize);
constructor CreateFromFile(ASize: TSDLGLTextureSize; AFilename: String );
constructor CreateFromStream(ASize: TSDLGLTextureSize; AStream: TStream);
destructor Destroy; override;
procedure Activate;
procedure DeActivate;
property Width: UInt16 read FWidth;
property Height: UInt16 read FHeight;
property Size: TSDLGLTextureSize read FSize;
property TextureID: GLuint read FTextureID;
property Surface: PSDL_Surface read FSurface;
end;

TTextureManager = class
private
FTextureList: TStringList;
protected
function GetTexture(Name: string): TSDLGLTexture;
property TextureList: TStringList read FTextureList;
public
constructor Create;
destructor Destroy; override;
function AddTexture(AName: string; ASize: TSDLGLTextureSize = ts256x256): TSDLGLTexture;
function AddTextureFromStream(AName: string; AStream: TStream; ASize: TSDLGLTextureSize = ts256x256): TSDLGLTexture;
function AddBlankTexture(AName: string; ASize: TSDLGLTextureSize = ts256x256): TSDLGLTexture;
procedure DeleteTexture(AName: string);
property Textures[Name: string]: TSDLGLTexture read GetTexture;
end;

const TextureManager: TTextureManager = nil;

implementation

uses SysUtils, sdlstreams;

{ TSDLGLTexture }
type

TTextureDimensions = record
Width, Height: Integer;
end;

const SizeLookup: array[TSDLGLTextureSize] of TTextureDimensions =
( (Width : 64; Height : 64),
(Width : 128; Height : 64),
(Width : 128; Height : 128),
(Width : 256; Height : 128),
(Width : 256; Height : 256),
(Width : 512; Height : 512),
(Width : 1024; Height: 1024),
(Width : 1024; Height: 512),
(Width : 1024; Height: 128)
);

constructor TSDLGLTexture.Create(ASize: TSDLGLTextureSize);
begin
inherited Create;
FSurface := nil;
FSize := ASize;
FTextureID := 0;
FWidth := SizeLookup[ASize].Width;
FHeight := SizeLookup[ASize].Height;
FSurface := CreateBlankSurface;
Assert(FSurface <> nil,'SDLGLTexture.Create : Failed to Create Surface');
end;

constructor TSDLGLTexture.CreateFromFile(ASize: TSDLGLTextureSize;
AFilename: String);
begin
inherited Create;
FSurface := nil;
FSize := ASize;
FTextureID := 0;
FWidth := SizeLookup[ASize].Width;
FHeight := SizeLookup[ASize].Height;
FSurface := CreateSurfaceFromFile(AFilename);
Assert(FSurface <> nil,'SDLGLTexture.CreateFromFile : Failed to Create Surface');
end;

function TSDLGLTexture.CreateBlankSurface: PSDL_Surface;
begin
Result := SDL_CreateRGBSurface(
SDL_HWSURFACE or SDL_SRCALPHA or SDL_RLEACCEL,
FWidth, FHeight,
32,
//{$IFDEF IA32} (* OpenGL RGBA masks *)
// $000000FF,
// $0000FF00,
// $00FF0000,
// $FF000000
//{$ELSE}
$0000FF00,
$00FF0000,
$FF000000,
$000000FF
//{$ENDIF}
);
end;

destructor TSDLGLTexture.Destroy;
begin
if Surface <> nil then
SDL_FreeSurface(Surface);
inherited Destroy;
end;

function TSDLGLTexture.CreateSurfaceFromFile(Filename: string): PSDL_Surface;
var fs:TFileStream;
begin
fs := TFileStream.Create(Filename, fmOpenRead);
try
Result := CreateSurfaceFromStream(fs);
finally
fs.Free;
end;
end;

procedure TSDLGLTexture.Activate;
begin
//
if TextureID = 0 then
begin
// Generate Texture.
CreateTexture;
end
else
begin
glBindTexture(GL_TEXTURE_2D, TextureID);
end;
end;

procedure TSDLGLTexture.DeActivate;
begin
//
glBindTexture(GL_TEXTURE_2D, 0);
end;

procedure TSDLGLTexture.CreateTexture;
var Format: Cardinal;
begin
glEnable(GL_TEXTURE_2D);
glGenTextures(1, @FTextureID);
glBindTexture(GL_TEXTURE_2D, FTextureID);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); {Texture blends with object background}
// glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); {Texture does NOT blend with object background}

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); { only first two can be used }
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); { all of the above can be used }
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, Width, Height, GL_RGBA, GL_UNSIGNED_BYTE, Surface.pixels);
Format := GL_RGB;
if Surface.format.BitsPerPixel = 32 then Format := GL_RGBA;

glTexImage2D(GL_TEXTURE_2D, 0, Format, Width, Height, 0, Format, GL_UNSIGNED_BYTE, Surface.pixels);
end;

constructor TSDLGLTexture.CreateFromStream(ASize: TSDLGLTextureSize;
AStream: TStream);
var Target: TStream;
TempImage: PSDL_Surface;
begin
inherited Create;
FSurface := nil;
FSize := ASize;
FTextureID := 0;
FWidth := SizeLookup[ASize].Width;
FHeight := SizeLookup[ASize].Height;
Target.Position := 0;
FSurface := CreateSurfaceFromStream(AStream);
Assert(FSurface <> nil,'SDLGLTexture.CreateFromStream : Failed to Create Surface');
end;

function TSDLGLTexture.CreateSurfaceFromStream(AStream: TStream): PSDL_Surface;
var Rwops: PSDL_RWops;
begin
Rwops := SDLStreamSetup(AStream);
try
Result := IMG_Load_RW(Rwops, 0);
if Result <> nil then SDL_FlipRectV(Result, nil);
finally
SDLStreamCloseRWops(Rwops);
end;
end;

procedure TSDLGLTexture.RefreshTexture;
var Format: Cardinal;
begin
Activate;
Format := GL_RGB;
if Surface.format.BitsPerPixel = 32 then Format := GL_RGBA;
glTexSubImage2D(GL_TEXTURE_2D, 0,0,0, Width, Height, Format, GL_UNSIGNED_BYTE, Surface.pixels)
end;

{ TTextureManager }

function TTextureManager.AddBlankTexture(AName: string;
ASize: TSDLGLTextureSize): TSDLGLTexture;
begin
Result := TSDLGLTexture.Create(ASize);
if Result <> nil then
begin
TextureList.AddObject(ExtractFileName(AName), Result);
end;
end;

function TTextureManager.AddTexture(AName: string; ASize: TSDLGLTextureSize = ts256x256): TSDLGLTexture;
begin
Result := nil;
if AName = EmptyStr then Exit;
Result := Textures[ExtractFileName(AName)];
if Result <> nil then Exit;
if not FileExists(AName) then Exit;
Result := TSDLGLTexture.CreateFromFile(ASize,AName);
if Result <> nil then
begin
TextureList.AddObject(UpperCase(ExtractFileName(AN ame)), Result);
end;
end;

function TTextureManager.AddTextureFromStream(AName: string; AStream: TStream;
ASize: TSDLGLTextureSize): TSDLGLTexture;
begin
Result := nil;
if AName = EmptyStr then Exit;
Result := Textures[UpperCase(AName)];
if Result <> nil then Exit;
Result := TSDLGLTexture.CreateFromStream(ASize,AStream);
if Result <> nil then
begin
TextureList.AddObject(UpperCase(AName), Result);
end;
end;

constructor TTextureManager.Create;
begin
inherited Create;
FTextureList := TStringList.Create;
end;

procedure TTextureManager.DeleteTexture(AName: string);
var idx: Integer;
begin
idx := TextureList.IndexOf(AName);
if idx <> -1 then
begin
TextureList.Objects[idx].Free;
TextureList.delete(idx);
end;
end;

destructor TTextureManager.Destroy;
var i: Integer;
begin
for i := 0 to TextureList.Count-1 do
begin
TextureList.Objects[i].Free;
end;
TextureList.Free;
inherited Destroy;
end;

function TTextureManager.GetTexture(Name: string): TSDLGLTexture;
var idx: Integer;
begin
Result := nil;
idx := TextureList.IndexOf(Name);
if idx <> -1 then
begin
Result := TSDLGLTexture(TextureList.Objects[idx]);
end;
end;

initialization

TextureManager := TTextureManager.Create;

finalization

TextureManager.Free;

end.

technomage
25-10-2005, 11:03 PM
For anyone who is interested I found out what the problem with glGenTextures was, I was callign it from a separate thread within the application, it doesn't appear to like that, so I moved the call to the main thread and it all worked fine :D

Sly
26-10-2005, 03:24 AM
Ahh. Multi-threading joy.

A tip for those people lucky enough to be using an AMD 64-bit X2 dual-core processor and use the high-frequency performance counter for game timing. Place a call to SetThreadAffinity() near the start of your game code to make sure the main thread always runs on the same CPU. The values returned by QueryPerformanceCounter() can differ depending on which CPU the thread is running on when it calls QueryPerformanceCounter(). This can make the game timing jump all over the place, even backwards. If the thread affinity is set to 1, then the thread always runs on the one CPU and the results returned by QueryPerformanceCounter() are what you would normally expect.

Clootie
26-10-2005, 07:50 PM
Hmm,... I always thought that QPC is processor independant (and should correctly handle SpeedStep like CPU frequency jums).

But now looked at MSDN docs and they have remarks for it:

On a multiprocessor computer, it should not matter which processor is called. However, you can get different results on different processors due to bugs in the basic input/output system (BIOS) or the hardware abstraction layer (HAL). To specify processor affinity for a thread, use the SetThreadAffinityMask function.

PS. Congratulations Sly for gettting "Word Lord" status! :)

Sly
26-10-2005, 09:37 PM
Even Serious Sam 2 has serious :) problems because of this discrepancy between QPC values on multi-CPU machines. I have to use a small utility called RunFirst that runs the demo using a process affinity of 1 to get it playing smooth.

My wordy lordy yes I am a Word Lord. :)

savage
29-10-2005, 02:39 PM
I'm not sure if this is relevant... - http://www.openscenegraph.org