Is there a texture count limit? Hundreds of small textures VS few big textures?

Started by lainmaster, January 22, 2010, 19:42:58

Previous topic - Next topic

lainmaster

Hey, I just now realized there's a limit to the texture size ¬¬

Most images in my game are rather small, all below 512^2, except for the game logo, which I'll have to split somehow, but that won't be much of an issue (I hope)

But for the tileset, which can be really big (over 1012 pixels of height), should I load each tile (32x32) as a separate texture or limit tilesets to 512^2 and modify the map and map editor to use several tilesets?
Using a texture for each tile seems like it will slow down both loading and drawing a lot, come to think of it. I guess I'll have to go with some sort of big-image thing.

Anyway, is there a limit to the number of textures there can be, or is it just the VRAM limit?

Evil-Devil

Mainly you will be limit to the VRAM.

But does a tileset really have to exceed the 1024^2 llimit? Modern graphiccards support 2048^2 and even 4048^2 but those are rare i guess.

As for speeding up try to put as many as possible tiles on your 1024^2 texture and order them in their usage. Maybe you could create even a tmp texture that fits all tiles in one texture if there aren't that many tiles on the screen.

lainmaster

Right now I ended up creating a "bigimage" system which takes care of loading textures without exceeding the size limit in a hidden manner, and now my draw methods check if the texture is split or not and deal with it. There's a low performance hit when the tileset texture is split in two parts, but most computers seem able to load at least 1024^2 textures. Works fine for now ^^

Anyhow, is it better to have one tileset texture rather than one texture per tile? (I'm talking about non-split Tileset, just one texture)

Evil-Devil

Yes it is better as you don't have to bind the current texture over and over again. Image 100 single texture tiles each a seperate file. To display them you have to bind each of them to have them displayed. With just several bigger textures holding your tiles you will only need to bind thus few and access the individual tiles via glTexSubImage =)

lainmaster

glTexSubImage? I didn't know anything like that existed, I was doing

private void _drawEmbeddedFull(float x, float y, float w, float h, float sx, float sy, float sw, float sh) {
		float fsw = sw / (float) _iWidth;
		float fsh = sh / (float) _iHeight;
		float fsx = sx / (float) _iWidth;
		float fsy = sy / (float) _iHeight;

		GL11.glTexCoord2f(fsx, fsy);
		GL11.glVertex3f(x, y, _iZ);

		GL11.glTexCoord2f(fsw + fsx, fsy); 
		GL11.glVertex3f(x + w, y, _iZ);

		GL11.glTexCoord2f(fsw + fsx, fsh + fsy); 
		GL11.glVertex3f(x + w, y + h, _iZ);

		GL11.glTexCoord2f(fsx, fsh + fsy);
		GL11.glVertex3f(x, y + h, _iZ);
	}


What's glTexSubImage for?

Evil-Devil

Oh, my fault. glTexSubImage is used to replace partial areas of an existing texture. Therefor your texture coordinate approach should be fast enough for the editor. Maybe for the game itself a texture of the lvl created with the help of glTexSubImage might be usefull.

lainmaster

Well everything seems fine, then. Thanks for your help, Evil-Devil :)

charstar

Quote from: lainmaster on January 22, 2010, 19:42:58
Hey, I just now realized there's a limit to the texture size ¬¬

Most images in my game are rather small, all below 512^2, except for the game logo, which I'll have to split somehow, but that won't be much of an issue (I hope)

Since you mentioned "game logo", you might also want to check into NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV, e.g.

glEnable( NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV );
glBindTexture( NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV, textureID );

etc as NVTextureRectangle has fewer constraints (i.e. NPOT dimensions) on it which makes it nicer for drawing arbitrarily sized textures on arbitrarily sized quads.

lainmaster

Quote from: charstar on March 05, 2010, 07:54:35
Quote from: lainmaster on January 22, 2010, 19:42:58
Hey, I just now realized there's a limit to the texture size ¬¬

Most images in my game are rather small, all below 512^2, except for the game logo, which I'll have to split somehow, but that won't be much of an issue (I hope)

Since you mentioned "game logo", you might also want to check into NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV, e.g.

glEnable( NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV );
glBindTexture( NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV, textureID );

etc as NVTextureRectangle has fewer constraints (i.e. NPOT dimensions) on it which makes it nicer for drawing arbitrarily sized textures on arbitrarily sized quads.

And how do I use that NVTextureRectangle thing? Is there a tutorial or something you might link me to?

EDIT

I just found http://www.gamedev.net/reference/articles/article2429.asp

I'm about to try it, and from what I have read, since my game is pure 2D, it seems NVTextureRectangle is much more reasonable, but before changing code that already works, I'd like to know a phew things: it NVTextureRectangle supported as much as normal opengl texture drawing is? Is it faster?

EDIT2:

Well I have tested changing the background rendering of the log in part of my game for the NVTextureRectangle thing. It's a 800x600 PNG 32 bpp image, and NVTextureRectangle was actually slower than usual rendering. I followed the code on http://www.gamedev.net/reference/articles/article2429.asp .

My code for loading the image is this:
IntBuffer oIntBuffer = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder()).asIntBuffer();
		BufferedImage oBufferedImage = Texture.getBufferedImage("E:/Raven Shrine Server Files/background-a1.png");
		ByteBuffer oPixelsBuffer = ByteBuffer.allocateDirect(oBufferedImage.getWidth() * oBufferedImage.getHeight() * 4).order(ByteOrder.nativeOrder());
		DataBufferByte oDataBufferByte = ((DataBufferByte) oBufferedImage.getRaster().getDataBuffer());
		byte[] iData = oDataBufferByte.getData();

		GL11.glEnable( NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV );

		GL11.glGenTextures(oIntBuffer);
		_iTexture = oIntBuffer.get(0);

		GL11.glBindTexture(NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV, _iTexture);

		GL11.glTexParameteri(NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
		GL11.glTexParameteri(NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);

		oPixelsBuffer.put(iData);

//		for (int i = 0; i < iData.length; i += 3) {
//			oPixelsBuffer.put(iData[i + 2]);
//			oPixelsBuffer.put(iData[i + 1]);
//			oPixelsBuffer.put(iData[i + 0]);
//		}

		oPixelsBuffer.flip();

		GL11.glTexImage2D(NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV, 0, GL11.GL_RGBA, oBufferedImage.getWidth(), oBufferedImage.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, oPixelsBuffer);

		GL11.glDisable( NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV );
		System.out.println("_iTexture" + _iTexture);


And the code for rendering:

//		_oLoginBackground.draw(0, 0, _oCamera.getScreenWidth(), _oCamera.getScreenHeight(), 0, 0, _oCamera.getScreenWidth(), _oCamera.getScreenHeight());
//		_oLoginBackground.draw(0, 0);

		GL11.glEnable( NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV );
		GL11.glBindTexture( NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV, _iTexture );
		double w = 800, h = 600;
		GL11.glBegin( GL11.GL_QUADS );
			GL11.glTexCoord2d( 0, 0 );
			GL11.glVertex2i( 0, 0 );

			GL11.glTexCoord2d( w, 0 );
//			GL11.glVertex2i( _oCamera.getScreenWidth(), 0 );
			GL11.glVertex2d(w, 0 );

			GL11.glTexCoord2d( w, h );
//			GL11.glVertex2i( _oCamera.getScreenWidth(), _oCamera.getScreenHeight() );
			GL11.glVertex2d(w, h);

			GL11.glTexCoord2d( 0, h );
//			GL11.glVertex2i( 0, _oCamera.getScreenHeight() );
			GL11.glVertex2d( 0, h);
		GL11.glEnd();
//
		GL11.glDisable( NVTextureRectangle.GL_TEXTURE_RECTANGLE_NV );


Anything I may be doing wrong?

Matthias

Hi,

don't use glVertex*d and glTexCoord*d - use the float versions of these calls.
Also you should use ARBTextureRectangle and not the NV* version.

You should check if your HW supports texture stacks - this way you can load several same sized images into one GL texture and select which layer to render via the 3rd texture coordinate. This is similar to 3D textures but without mipmapping on the layer (3rd coordinate). But this check has to be a runtime check as this extension is not available on older HW.

Ciao Matthias

lainmaster

Quote from: Matthias on March 06, 2010, 03:53:45
Hi,

don't use glVertex*d and glTexCoord*d - use the float versions of these calls.
Also you should use ARBTextureRectangle and not the NV* version.

Tested both things separately, neither seemed to change the FPS at all. Both at the same time didn't change FPS either.

Quote from: Matthias on March 06, 2010, 03:53:45
You should check if your HW supports texture stacks - this way you can load several same sized images into one GL texture and select which layer to render via the 3rd texture coordinate. This is similar to 3D textures but without mipmapping on the layer (3rd coordinate). But this check has to be a runtime check as this extension is not available on older HW.

What for?

charstar

Quote from: lainmaster on March 06, 2010, 04:39:39
Quote from: Matthias on March 06, 2010, 03:53:45
Hi,

don't use glVertex*d and glTexCoord*d - use the float versions of these calls.
Also you should use ARBTextureRectangle and not the NV* version.

Tested both things separately, neither seemed to change the FPS at all. Both at the same time didn't change FPS either.

Matthias is right about ARBTextureRectangle... NV is an old habit.  They actually do the same thing but one is from before it was put through as a standard (NVidia / EXTension -> Architecture Review Board).

Quote from: lainmaster on March 06, 2010, 04:39:39
Quote from: Matthias on March 06, 2010, 03:53:45
You should check if your HW supports texture stacks - this way you can load several same sized images into one GL texture and select which layer to render via the 3rd texture coordinate. This is similar to 3D textures but without mipmapping on the layer (3rd coordinate). But this check has to be a runtime check as this extension is not available on older HW.

What for?


I believe Matthias is referring to your original tile method.  For instance, if you have 6 images you want to stich together, that is 6 texture binds using the tile method.  If you load the images as the 3rd coord in a 3D texture, you save yourself the extra texture binds (which is expensive, many various engines do a secondary sort of objects to draw as many objects using the same texture as possible).  You then only need to specify which "tile" to use as the 3rd texture coordinate for each quad.

Regarding using TextureRectangle, how much slower are we talking here?  If it's reeaaaally slow, is there any chance you are accidently reloading the texture in your loop?

Matthias

slow TextureRectangle rendering could also be a sign of bad support by your GPU. Some HW has issues with NPOT textures. Which GPU do you use and which OS?

Also I don't recommend to use a 3D texture (GL_TEXTURE_3D) as this will most likely be slower on many older cards. Texture stacks are available on nVidia starting with the 8800 series.

Another way to speedup rendering is by sorting your sprites/tiles accordingly, either try to draw stuff sorted based on their texture - this only works when they don't overlap, or try to group the sprites/tiles based on their usage. This of course will only help if you remove redundant state changes - like glBindTexture, glBegin/End etc.

Also make sure that you render your tiles 1:1 (without scaling down) - as texture rectangles don't have mipmaps. When you minimize a texture without mipmaps you get a) alias effects b) slower rendering compared to rendering with mipmaps.

It also helps to disable unused features like alpha blending, depth tests/updates, stencil etc. Also never disable individual color channels (glColorMask).

lainmaster

QuoteI believe Matthias is referring to your original tile method.  For instance, if you have 6 images you want to stich together, that is 6 texture binds using the tile method.  If you load the images as the 3rd coord in a 3D texture, you save yourself the extra texture binds (which is expensive, many various engines do a secondary sort of objects to draw as many objects using the same texture as possible).  You then only need to specify which "tile" to use as the 3rd texture coordinate for each quad.

The most consuming part of each frame is the rendering of the map, which is done by layers. The map has 3 layers, read in three for loops (layer > x > y). The tileset texture is bound once for each layer, so it surely isn't a bottleneck. Between each layer, sprites are drawn, but I have never tested my game with more than 3 sprites at the same time, usually only 1 (the player's sprite).
Each sprite has one texture with all the "poses", and only a part of the texture is drawn depending on the animation frame. Either case, it is always one bind per sprite here.

QuoteRegarding using TextureRectangle, how much slower are we talking here?  If it's reeaaaally slow, is there any chance you are accidently reloading the texture in your loop?

FPS normally in the log-in scene of my game: ~670
FPS using the ARB thingy to draw the background: ~660

The difference is pretty much insignificant in this case, maybe for the drawing of the map it could be significant.
But I was expecting to see a bigger difference when drawing, a significant increase of the FPS. At least 20 more FPS for this one image.
Note that the background in this case is drawn without resizing. Normally the background is resized to fit the screen. This should probably be the only image being resized in the entire game.

Things drawn in the log-in scene: an 800x600 32bpp background, a "log in box" with an image background (346x92 32bpp), two text input boxes which are in fact just two rectangles, and the logo (608x236 32bpp).

Quoteslow TextureRectangle rendering could also be a sign of bad support by your GPU. Some HW has issues with NPOT textures. Which GPU do you use and which OS?

GL_VENDOR: NVIDIA Corporation
GL_RENDERER: GeForce 9400 GT/PCI/SSE2
GL_VERSION: 3.2.0
GL_MAX_TEXTURE_SIZE: 8192
OS: Windows 7 64 bits
Available Processors:4
Available VRAM: 512MB
Available RAM: 2GB
Processor: AMD Phenom 9950 Quad-Core Processor 2.60 GHz

QuoteTexture stacks are available on nVidia starting with the 8800 series.
My game is running at over 600 FPS in my computer so it will run at 60FPS in older computers.
It will be an MMO RPG run in an Applet, and the target public is teenagers, who generally can't buy a new computer and may have a rather old one. Also, if a player is not at his own computer and wants to play in whatever computer, he should be able to. So 8800 series would be too high a requirement for my game.

QuoteAnother way to speedup rendering is by sorting your sprites/tiles accordingly, either try to draw stuff sorted based on their texture - this only works when they don't overlap, or try to group the sprites/tiles based on their usage. This of course will only help if you remove redundant state changes - like glBindTexture, glBegin/End etc.
In the tested game scene, this is irrelevant, as there are only about 3 images, using a different texture each. This scene is pretty much irrelevant to the game itself, too. The rest of the game consist of rendering of tiles and sprites, which I optimized as much as I could think of.

QuoteAlso make sure that you render your tiles 1:1 (without scaling down) - as texture rectangles don't have mipmaps. When you minimize a texture without mipmaps you get a) alias effects b) slower rendering compared to rendering with mipmaps.
Nothing is scaled. I'm 100% sure of this. Although, in other games I'm planning, I'd like zoom in and out, like in Yoshi's Story or Metal Slug 6. (2D as well)

QuoteIt also helps to disable unused features like alpha blending, depth tests/updates, stencil etc. Also never disable individual color channels (glColorMask).
All check.

---

Tried to answer everything on this one, phew.

Now, considering my game seems to rune fine in GeForce 6000 series and I want to keep it that way, and that from the test, the ARB thingy didn't seem to make any difference, should I recode a lot what is already working to
use texture stacks?
use the ARB thingy?

Is it worth it?