PDA

View Full Version : Pointers or not?



FusionWolf
20-01-2005, 08:17 PM
Simple stupid question.

If I have declared 2 variables type of IDirect3DTexture9 and I use DirectX loader function to load image to first one of those variables.

After that If I assign that variable which holds the texture to another variable is that image data copied to new memory location also or is that another variable just pointing to same memory space (debugger says it's a same memory space, but.....)

For example.....



var
T1: IDirect3DTexture9;
T2: IDirect3DTexture9;

begin
T1 := Loadtexture(FileName);

// Now T1 is referencing to texture in memory.

T2 := T1;

// is T2 referencing to same texture or is that a copy of previous one?

T1._Release;

// Now texture should be freed from memory, but it can be drawn by
// using T2?!?
end.

Crisp_N_Dry
20-01-2005, 08:54 PM
as far as I know, T2 has become a texture in it's own right but it is looking at the same memory space as T1. Except when you free T1, the image is still left in memory because it is still being referenced. T1 will be unusable but the data remains for use by T2. Correct me if I'm wrong here guys.

FusionWolf
20-01-2005, 09:45 PM
as far as I know, T2 has become a texture in it's own right but it is looking at the same memory space as T1. Except when you free T1, the image is still left in memory because it is still being referenced. T1 will be unusable but the data remains for use by T2. Correct me if I'm wrong here guys.

Okey, it means that T2 isn't taking anymore memory than that IDirect3DTexture interface needs (which is 4 bytes) without texture data. Am I correct?

So, how next example differs from earlier and why it behaves differently?



type
PTexture: ^IDirect3DTexture9; // typed pointer.

var
T1: IDirect3DTexture9;
P1: PTexture;

begin
T1 := LoadTexture(FileName);

// T1 is now referencing texture data in memory.

P1 := @T1;

// P1 is now referencing to same data (as T2 does in earlier example).

T1._Release;

// T1 is freed and so is texture data also. From now on P1 cannot be
// used for drawing or if so then nothing is drawed.
end.

Sly
20-01-2005, 10:20 PM
Implicit reference counting.

When you create the texture object, it sets the reference count to one. When you assign T1 to T2, T2 now points to the same texture object and it increments the reference count of that object. When you call _Release on T1, it decrements the reference count, but since the reference count is still greater than zero, the object itself is not destroyed. Therefore the texture can still be used via T2.

Tip: Setting T1 to nil implicitly calls _Release just like setting T2 to equal T1 implicitly calls AddRef.

In your second example, you are just getting a pointer to the pointer to the texture object. You are not creating a second reference to the texture object.

First example

T1-->|
| IDirect3DTexture9
T2-->|
Second example

P1-->T1-->IDirect3DTexture9

FusionWolf
20-01-2005, 10:55 PM
Implicit reference counting.

When you create the texture object, it sets the reference count to one. When you assign T1 to T2, T2 now points to the same texture object and it increments the reference count of that object. When you call _Release on T1, it decrements the reference count, but since the reference count is still greater than zero, the object itself is not destroyed. Therefore the texture can still be used via T2.

Tip: Setting T1 to nil implicitly calls _Release just like setting T2 to equal T1 implicitly calls AddRef.

In your second example, you are just getting a pointer to the pointer to the texture object. You are not creating a second reference to the texture object.


Okey, thanks a lot. That makes it all clear, but one question. Which one you should recommend to if I'm making a simple game and there's TSprite class and TSpriteManager class.

TSpriteManager loads textures for the TSprites and manages which object owns which texture and so on.

TSprite has typed pointer to it's texture which is in dynamic array which is managed by TSpriteManager. Should that typed pointer in TSprite class be a simple variable instead of typed pointer.

Example....



type
TSpriteManager = Class
private
Textures: Array of IDirect3DTexture9;
public
LoadTexture(File);
DeleteTexture(ID);
Etc....
.
.
end;

type
PTexture = ^IDirect3DTexture9;

type
TSprite = Class
private
Texture: PTexture;
public
Create(TextureID);
Draw;
Etc...
.
.
end;

constructor TSprite.Create(TextureID);
begin

// some pseudo code.
Seek(TextureID) from TextureManager.
If texture is loaded then
set self.texture point to that texture in manager
else
manager loads texture and then returns pointer to texture.
end;

end;


So should it be typed like this or just a reference to texture? Is the memory usage same anyway?

Sly
20-01-2005, 11:20 PM
Same memory usage either way.

In your case, I would hesitate to use a pointer to a member of the array simply because you are using a dynamic array. If that array is made large through the use of SetLength(), the entire array may change location in memory, thus invalidating any pointers that currently point to items in that array. That is because they point to where the array items used to be, not where they are now.

So in the case that you describe, I would use a IDirect3DTexture9 member in TSprite, not a PTexture. This will still be valid even if the array in TSpriteManager gets reallocated and moves in memory.

FusionWolf
20-01-2005, 11:28 PM
Ouh, really? I didn't know that SetLength() would change its location. Okey, then I begun to use IDirect3DTexture9 instead of pointer. Those classes on my example was very simplified, but idea is same.

Currently I'm keeping reference count of pointers in TSpriteManager class, so when I begun to use variables instead of pointers I can get rid of keeping the book of references, because the texture stays in memory as long as any sprite variable points to it - correct? And when all sprites which references to the same texture is freed also that texture data is freed from memory - correct?

Hey, and thanks for your answers. These have been very helpfull.

Sly
20-01-2005, 11:36 PM
If each TSprite has a reference to a IDirect3DTexture9 object, then you can destroy the TSpriteManager object and each TSprite will still have its texture because they are reference counted.

As for the SetLength() statement, think of it this way. You have the array set to 4 elements.

allocated|arrayx4|allocated|empty...
Now you want to change the length of the array to 8 elements. Since we have the memory immediately after the array in use, the array cannot be made longer in-place, so the system must reallocate and copy the array.

allocated|empty|allocated|arrayx8|empty...
The array moved, but your code does not realise this because it was all done behind the scenes. The array member you have in your class is not the array itself, but really a pointer to the array. Dynamic arrays work exactly the same as long strings. The system manages the allocation, reallocation, destruction of the memory used by the array/string, but you do not see this because the system has changed your pointer to point to the correct location in memory.

FusionWolf
21-01-2005, 12:03 AM
Ah, you are soooooo right. I appreciate your answers. Thanks very much,

Sly
21-01-2005, 12:37 AM
Yes. Spr2 is pointing to the same object that Spr1 is, so moving Spr2 would move Spr1.

Ok. Keep your TSpriteManager. It will deal with creating the TSprite objects so it can give each TSprite instance a reference to the texture.


type
TSprite = class
private
FTexture: IDirect3DTexture9;
public
constructor Create(ATexture: IDirect3DTexture9);
destructor Destroy; override;
end;

TSpriteManager = class
private
FTextures: TStringList;
function LoadTexture(FileName: String): IDirect3DTexture9;
function FindTexture(FileName: String): IDirect3DTexture9;
public
constructor Create;
destructor Destroy; override;
function CreateSprite(FileName: String): TSprite;
end;

implementation

{ TSprite }

constructor TSprite.Create(ATexture: IDirect3DTexture9);
begin
{ Get a reference to the texture }
FTexture := ATexture;
end;

destructor TSprite.Destroy;
begin
{ Release the reference to the texture }
FTexture := nil;
inherited;
end;

{ TSpriteManager }

constructor TSpriteManager.Create;
begin
FTextures := TStringList.Create;
end;

destructor TSpriteManager.Destroy;
begin
while FTextures.Count > 0 do
begin
IDirect3DTexture9(FTextures.Objects[0])._Release;
FTextures.Remove(0);
end;
FTextures.Free;
inherited;
end;

function CreateSprite(FileName: String): TSprite;
var
Texture: IDirect3DTexture9;
begin
{ Have we already loaded this texture? }
Texture := FindTexture(FileName);
{ If not, the load it }
if Texture = nil then
Texture := LoadTexture(FileName);
{ Create the TSprite and give it a reference to the texture }
Result := TSprite.Create(Texture);
{ Release the reference to the texture }
Texture := nil;
end;

function TSpriteManager.LoadTexture(FileName: String): IDirect3DTexture9;
begin
Result := {however you load your texture};
{ Add the texture and filename to the list for later use }
FTextures.AddObject(FileName, Pointer(Result));
end;

function TSpriteManager.FindTexture(FileName: String): IDirect3DTexture9;
var
Texture: IDirect3DTexture9;
Index: Integer;
begin
Result := nil;
{ Is the texture in the list? }
Index := FTextures.IndexOf(FileName);
if Index > -1 then
Result := IDirect3DTexture9(FTextures.Objects[Index]);
end;


Note that this code is off-the-top-of-my-head, so it may contain some errors. :)

To create a sprite, call SpriteManager.CreateSprite(FileName). This will search through the list of textures that have already been loaded. If the texture is found, it gives the sprite a reference to that texture, otherwise it creates a new texture.

Hope that helps. I use a similar (but more extensive) system in my own project. I have a base TResource class and a TResourceManager that is used as the basis for everything (textures, materials, fonts, meshes, etc).

FusionWolf
21-01-2005, 12:41 AM
Ok. Keep your TSpriteManager. It will deal with creating the TSprite objects so it can give each TSprite instance a reference to the texture.

To create a sprite, call SpriteManager.CreateSprite(FileName). This will search through the list of textures that have already been loaded. If the texture is found, it gives the sprite a reference to that texture, otherwise it creates a new texture.

Hope that helps. I use a similar (but more extensive) system in my own project. I have a base TResource class and a TResourceManager that is used as the basis for everything (textures, materials, fonts, meshes, etc).

And again thanks a lot. While you was writing this message I made some tests with the classes and noted my self that I should keep the manager.

You have been very helpful. Thanks a lot.

Clootie
21-01-2005, 01:13 PM
Hmm, how about freeing texture in TSprite.Destroy - not local reference to ID3D9Texture but one in TSpriteManager.FTextures too? :)

FusionWolf
21-01-2005, 04:08 PM
Hmm, how about freeing texture in TSprite.Destroy - not local reference to ID3D9Texture but one in TSpriteManager.FTextures too? :)

Yes. There should be some sort of call to method in TSpriteManager which seeks if there's no any references to texture itself and frees it.

If that's what you meant.

edit: but of course that should be possible to avoid also, because if you destroy last sprite in the game which references to some texture and after couple frames sprite with same texture reappears in the game. So there should be boolean variable when destroying sprite which determines that is that "Seek 'n' Destroy" method ran or not.

FusionWolf
21-01-2005, 04:41 PM
Sly.

You use StringList to maintain textures, but when I try to do something similar I get error "incompatible types IDirect3Dtexture9 and TObject".

In your example where you wrote IDirect3DTexture9(FTextures.Objects[0])._release;

Clootie
21-01-2005, 04:48 PM
Yep, it's design issue. 8)
Other issue is that video drivers do not like to create and destroy textures every frame or so. So, it's way better to allocate some pool of sprites of different sizes at start of your app, and later reuse these containers for real sprites then they are requested by your game engine.

Clootie
21-01-2005, 05:09 PM
Working code:
[background=#FFFFFF][normal=#000000][number=#0000FF][string=#0000FF][comment=#248F24][reserved=#000000] var
l: TStringList;
texture: IDirect3DTexture9;
TextureFileName: String;
begin
l:= TStringList.Create;

// Add texture to list
l.AddObject(TextureFileName, Pointer(texture));
Pointer(texture):= nil;

// Remove texture from list
Pointer(texture):= Pointer(l.Objects[0]);
texture:= nil;

// You can also do this to free interface, but above is more recommended
// IDirect3DTexture9(Pointer(l.Objects[0]))._Release;
end.

FusionWolf
21-01-2005, 07:43 PM
Working code:
[background=#FFFFFF][normal=#000000][number=#0000FF][string=#0000FF][comment=#248F24][reserved=#000000] var
l: TStringList;
texture: IDirect3DTexture9;
TextureFileName: String;
begin
l:= TStringList.Create;

// Add texture to list
l.AddObject(TextureFileName, Pointer(texture));
Pointer(texture):= nil;

// Remove texture from list
Pointer(texture):= Pointer(l.Objects[0]);
texture:= nil;

// You can also do this to free interface, but above is more recommended
// IDirect3DTexture9(Pointer(l.Objects[0]))._Release;
end.

Thank you. I got it to work now. I got it to working earlier too by that way what you mention in the last row of your post, but then I got exception all the time when exiting from application.

That exception occured because all sprites was freed before SpriteManager and in sprite destructor I assigned nil to that texture referencer.

edit: Why it doesn't work? When I exit from application, I'll free all sprites first. In those sprite destructors i assign nil to that local texture referencer. After all sprites is released I free sprite manager itself, where I use method you descripted above. That causes an exception if there's no any sprite referencing to texture anymore. If I leave even one sprite to reference it and then call that sprite manager destructor all works fine. Why's that?

Shouldn't that texture exists in the stringlist even there's no sprite to referencing to it? In my opinion it should, but somehow it's released when all sprites stops referencing to it?!? Or do I lost my texture data to somewhere in memory?

Some pseudo code to clearify what I mean...




TSprite = Class
private
FTexture: IDirect3DTexture;
public
Create(Texture);
Destroy;
end;

TSpriteManager = Class
private
FTextures : TStringList;
function CreateSprite(FileName): TSprite;
public
Create;
Destroy;
end;

{ TSprite }

TSprite.Create(Texture);
begin
FTexture := Texture; // assign reference to texture.
end;

TSprite.Destroy;
begin
FTexture := nil; // Free references to texture.
end;


{ TSpriteManager }

TSpriteManager.Create;
begin
FTextures := TStringList.Create;
end;

TSpriteManager.Destroy;
var
Texture: IDirect3DTexture9;
begin
while FTextures.Count > 0 do begin
Pointer(Texture) := Pointer(FTextures.Objects[0]); // Get pointer to textue;
Texture := nil; // Remove texture;
FTextures.Delete(0); // Delete texture from stringlist.
end;
inherited;
end;

TSpriteManager.CreateSprite(FileName): TSprite;
begin
Seek(filename)
if texture exists then
Result := Sprite.Create(Pointer to existing texture)
else begin
LoadTexture(FileName);
FTextures.Add(Loaded Texture);
Result := Sprite.Create(Loaded Texture);
end;
end;


And when exiting SpriteManager destructor causes an exception if there's no sprite referencing to texture.

FusionWolf
22-01-2005, 12:00 AM
I finally managed to help my self out. I changed that TStringList to Tlist type. I also made a record which holds the texture, filename and reference count in it. When new texture is loaded a new pointer to that record (which stores the texture itself) is made and that record pointer is stored in a Tlist.

I also build one procedure to SpriteManager class which is used to release sprite. That procedure has argument for releasing the texture from memory among the sprite itself (if there's no more sprites pointing to texture).

Finally when spritemanager is freed and if there's any pending textures in TList, it releases them in destructor and frees Tlist itself.

I stress tested this with hundreds of sprites. Some of them refenced to same texture and some of them had texture for that specific sprite only. When textures was released, created, released, created and so on memory changed (I was interrupting with debugger and watching memory usage from process list) and everything worked just fine.

Now I can finish the sprite engine. Still I want to thank you guys (or gals). Without your help I would never manage to do anything like this.