How do I get the width and height of the texture for TCastleAbstractPrimitive?

How do I get the width and height of the texture for TCastleAbstractPrimitive?
While I’m doing it like this.

Tex := TImageTextureNode.Create;
Tex.SetUrl(Self.Texture);
writeln(Tex.TextureImage.Width);

But the texture loading takes place in TCastleAbstractPrimitive.UpdateMaterialNode

It turns out that I load the texture twice. Therefore, my way does not seem right to me.
Is there another way?

There are some “tricks” you could use to access the already loaded texture node:

  • One is to look into primitive children, there’s an internal TCastleScene, and you could search there for TImageTextureNode.

  • Another is to use X3DCache.TextureImage_IncReference to use cache directly,

… but my recommendation would be (for now) to not do these tricks.

Instead follow the approach you show above, create a TImageTextureNode just to read size, this is OK…

…just make sure to keep your TImageTextureNode existing for a longer time (maybe just keep it existing as much as the primitive) to make sure that both your TImageTextureNode and the TCastleAbstractPrimitive use the same texture instance from the cache. I.e. the texture will not be loaded twice if it was already present in the cache :slight_smile: So whoever (you or primitive) will load the texture for the 2nd time, in 2nd TImageTextureNode instance, will actually just use the cache, and it will be instant.

I did a quick test, attaching:

In TViewMain.Start I create a new

TextureNodeOnlyToGetSize := TImageTextureNode.Create

and set URL, only to know the texture size. I let this TextureNodeOnlyToGetSize to exist (until TViewMain.Stop) so that it keeps the texture in the cache. When the box (TCastleBox) needs to load the texture, it will just use the existing texture from the cache. This can be confirmed by activating log

LogTextureCache := true

and looking at log:

++: Texture image castle-data:/totoro-powerof2.jpg: 1
Size: 2048 x 1024
++: Texture image castle-data:/totoro-powerof2.jpg: 2

So the reference count of castle-data:/totoro-powerof2.jpg has increased to 2. The 2nd access to it was lighting fast, it just used existing instance of TEncodedImage (this one instance of TEncodedImage is internally used by 2 instances of TImageTextureNode in effect) from cache.

my-new-project-boxcache-0.1-src.zip (2.4 MB)

1 Like

Thanks very much. CGI stretches the width and height of the texture to the power of two. At what point does this happen? How do I get this width and height?

Indeed. The scaling happens right before texture is actually loaded into the GPU, so you cannot (kind of by design) actually see the scaled sizes now anywhere. But you can just invoke ResizeToTextureSize to use the same algorithm as CGE. It has an overloaded version that just takes and returns an integer size, i.e. it doesn’t actually resize any image, just answers what the resulting image “would have”: Castle Game Engine: CastleGLImages ,

function ResizeToTextureSize(const Size: Cardinal; const Sizing: TTextureSizing): Cardinal; overload;

So if you have a texture Width and Height, you can do

FinalWidth := ResizeToTextureSize(Width, tsScalablePowerOf2);
FinalHeight := ResizeToTextureSize(Height, tsScalablePowerOf2);

You can go one step further to support TextureProperties.guiTexture , Texturing component - extensions | Castle Game Engine (I admit I don’t like this name, I should change it to like Mipmaps with inverted meaning – because it has use-case outside of GUI of course too, sometimes you don’t need mipmaps and then you don’t need power of 2). This would look like:

var
  GUITexture: Boolean; // mipmaps not necessary, no need to scale to power-of-2
  TextureNode: TImageTextureNode;
  Sizing: TTextureSizing;
begin
  TextureNode := ...;

  if TextureNode.TextureProperties <> nil then
    GUITexture := TextureProperties.GUITexture
  else
    GUITexture := false;

  if GUITexture then
    Sizing := tsAny 
  else
    Sizing := tsScalablePowerOf2;

  FinalWidth := ResizeToTextureSize(TextureNode.TextureImage.Width, Sizing);
  FinalHeight := ResizeToTextureSize(TextureNode.TextureImage.Height, Sizing);
end;

I wrote this (untested) code looking at our current logic in src/scene/castleinternalrenderer_texture.inc .

1 Like