Page 1 of 1

Texture Arrays

Posted: Fri Sep 24, 2010 12:08 am
by zfx
Hi everyone!
I just started using Ogre and am currently in the process of porting my existing project from another rendering engine. I've been using 2D Texture arrays (as of DX10, or GL_TEXTURE_ARRAY extension for OpenGL) in order to minimize occupied texture units. I searched google, the forums and the wiki for some info on that topic, but was only able to find something dicussing this "new" feature in some DX10 topic in the year 2008! I strongly suspect that it is implemented in some form, but it would really help me if someone could point me in the right direction to look (i.e. some links to the API reference, discussions, samples, whatever). Any help is much appreciated!

Re: Texture Arrays

Posted: Fri Sep 24, 2010 6:18 am
by Jabberwocky
I've never used, nor heard of a texture array. Although I'm not exactly up on the cutting edge graphics techniques. If it's a DX10-only feature, I think you're going to need to find an alternative. I don't believe Ogre has formally advanced beyond DX9 yet, although there has been some preliminary work to do so.

There are some implementations of a "texture atlas" kicking around, which may accomplish the same thing.
Try searching for those terms in the forum search box.

Re: Texture Arrays

Posted: Tue Sep 28, 2010 2:06 pm
by zfx
Well it's a fairly used feature by now. It is basically what is says, an array of textures which can be referenced by texture index in the shader. It is strictly for shader usage. Problems with atlases are bleeding issues when textures are of odd sizes and the other severe problem (which i avoided by using texture arrays) is that they do not support tiling without specialized shaders, which of course introduce a severe performance penalty themselves. I saw that OGRE has DX10 & DX11 renderers. I read a bit in the forums and found that the DX10 renderer is abandoned by now and only DX11 is actively developed. If the DX11 renderer is compatible with DX10 that should not be a problem. But I don't know if DX10 is included in the DX11 system. And also what features of DX10 are currently exposed, what are the limitations. OGRE is a very nice engine from what I've seen so far, especially material scripts and exporter support. I really hope they get more serious about DX10/11, cuz it's about time now, when pretty much any gfx card you can buy today supports it and it is used actively :) I come from an OpenGL background, but I am ready to assist with what I can to add the features that i need to whatever rendering system they need to be added (both OpenGL and DirectX) :)

Re: Texture Arrays

Posted: Mon Nov 15, 2010 4:14 pm
by Assaf Raman
No texture arrays support for now.
I will put it in my todo list.

Re: Texture Arrays

Posted: Sat Nov 20, 2010 2:52 am
by Assaf Raman
Added texture array support.
For now the texture array sample only supports GL.
Latest code committed to the trunk.

Re: Texture Arrays

Posted: Sat Nov 20, 2010 4:22 am
by Jabberwocky
Wow, that looks like a Salvador Dali painting. :shock:

Nice work on the new feature Assaf! Your work is much appreciated as always.

Re: Texture Arrays

Posted: Sat Nov 20, 2010 11:53 pm
by LBDude
NOOOO... when are going to see this in the DX renderer? I may just have to look at your code and see if I can add it myself...but is texture arrays even available for DX9?

I would definitely use OpenGL if it were not the fact I'm using some shaders that is HLSL only :(. Hell, I would be coding in Linux if it weren't for that.

Re: Texture Arrays

Posted: Sun Nov 21, 2010 1:03 am
by Assaf Raman
No d3d9 or gl es support, only d3d11 and gl support.

Re: Texture Arrays

Posted: Fri Nov 26, 2010 9:09 pm
by plfiorini
LBDude wrote: I would definitely use OpenGL if it were not the fact I'm using some shaders that is HLSL only :(. Hell, I would be coding in Linux if it weren't for that.
Well, try to port your HLSL shaders to Cg. It should be pretty straightforward.

Re: Texture Arrays

Posted: Fri Nov 26, 2010 9:14 pm
by plfiorini
Assaf Raman wrote:Added texture array support.
For now the texture array sample only supports GL.
Latest code committed to the trunk.
Thank you very much! I'm coding on Linux now (and planning to use it on OSX as well) using Ogre 1.8 from trunk.
I think that texture arrays may be useful to get terrains working with deferred shading (I'm basically using Ogre's demo code in my project, just extended the GBuffer vertex and fragment program generator with Parallax Occlusion Mapping), in fact as far as I understand with terrains you'll have to handle it with the GBuffer scheme so the fragment program may run out of texture units. Am I right?

Re: Texture Arrays

Posted: Fri Nov 26, 2010 9:27 pm
by Assaf Raman
I am not sure, but it seems you know what you are talking about.

Re: Texture Arrays

Posted: Sat Dec 11, 2010 11:01 am
by zfx
This is great!! WOW! Only OpenGL, but it is a very good start :) DirectX equivalent would require DX10 or greater. Here you can see a sampe of it:
http://developer.download.nvidia.com/SD ... mples.html

Just scroll down to the end of the page, it is the last example! Thanks again for the great feature, I will test it next week :)

Re: Texture Arrays

Posted: Tue Nov 01, 2011 1:30 am
by Wolfski
Hello

I'm struggling to get the sample to work with compressed textures. There seems to be an placeholder for handling this at line 204 of OgreGLTexture.cpp.

case TEX_TYPE_2D_ARRAY: // todo - check this...
case TEX_TYPE_3D:

With the original code I get a “zero sized texture on surface on texture” error. If I insert

glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, mip, format,
width, height, depth, 0,
size, template);
break;

then the buffer would seem to be created, but I get a “cannot return subvolume of compressed PixelBuffer” error when the sample tries to lock the buffer prior to adding the first texture - at line 71 of TextureArray.h:

const PixelBox& currImage = pixelBufferBuf->lock(Box(0,0,i,terrainTex.getHeight(), terrainTex.getHeight(), i+1), HardwareBuffer::HBL_DISCARD);

Looking at PixelBox::getSubVolume (OgrePixelFormat.cpp) it would seem it doesn't support compressed formats.

Any ideas what to do next?

Also, are there any plans to fully integrate texture arrays into the material system, so the list of textures might be specified in the material script?

Re: Texture Arrays

Posted: Tue Nov 01, 2011 10:06 am
by Assaf Raman
Regarding your issue - find a working GL compressed textures sample by someone and give us a link - then we can compare it to what is going on in OGRE.
Regarding the integrating into the scripts - I don't think this is in any of us todo list.

Re: Texture Arrays

Posted: Wed Nov 02, 2011 11:15 am
by Wolfski
I've modified NVIDIA's SDK sample ( which looks like it was the basis for Ogre's sample ) and that seems to work OK.

Changes from the original are marked with: // change #

Code: Select all

//
// simple texture array example
//
// Demonstrates the use of EXT_texure_array.
//
// Author: Simon Green
// Email: sdkfeedback@nvidia.com
//
// Copyright (c) NVIDIA Corporation. All rights reserved.
////////////////////////////////////////////////////////////////////////////////

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <map>

#include <GL/glew.h>
#include <GL/glut.h>

#include <nvImage.h>
#include <nvGlutManipulators.h>

#define NV_REPORT_COMPILE_ERRORS
#include <nvShaderUtils.h>

using std::map;

////////////////////////////////////////////////////////////////////////////////
//
// Globals
//
////////////////////////////////////////////////////////////////////////////////

// change #1, path adjusted to run from the IDE
//#define IMAGE_PATH "../media/textures/"
#define IMAGE_PATH "../../media/textures/"

nv::GlutExamine manipulator;
GLuint fprog, lerp_fprog;

enum UIOption {
    OPTION_DISPLAY_WIREFRAME,
    OPTION_LERP_LAYERS,
    OPTION_ANIMATE,
    OPTION_USE_PROGRAM,
    OPTION_COUNT
};
bool options[OPTION_COUNT];
map<char,UIOption> optionKeyMap;

//
// fragment program to look up in texture array
//
static const char *fprog_code = 
"!!NVfp4.0                                        \n"
"TEMP texcoord;                                   \n"
"MOV texcoord, fragment.texcoord[0];              \n"
"FLR texcoord.z, texcoord;                        \n"
"TEX result.color, texcoord, texture[0], ARRAY2D; \n"
"END";

//
// interpolate between two nearest layers in array
//
static const char *lerp_fprog_code = 
"!!NVfp4.0                                        \n"
"TEMP texcoord, c0, c1, frac;                     \n"
"MOV texcoord, fragment.texcoord[0];              \n"
"FLR texcoord.z, texcoord;                        \n"
"TEX c0, texcoord, texture[0], ARRAY2D;           \n"
"ADD texcoord.z, texcoord, { 0, 0, 1, 0 };        \n"
"TEX c1, texcoord, texture[0], ARRAY2D;           \n"
"FRC frac.x, fragment.texcoord[0].z;              \n"
"LRP result.color, frac.x, c1, c0;                \n"
"END";

////////////////////////////////////////////////////////////////////////////////
//
// Functions
//
////////////////////////////////////////////////////////////////////////////////


//
//
//////////////////////////////////////////////////////////////////////
void init_opengl() {
    glEnable(GL_DEPTH_TEST);
    glClearColor(0.2, 0.2, 0.2, 1.0);

    glewInit();

    if (!glewIsSupported(
        "GL_VERSION_2_0 "
		"GL_ARB_vertex_program "
        "GL_ARB_fragment_program "
        "GL_EXT_texture_array "
        "GL_NV_gpu_program4 "   // also initializes NV_fragment_program4 etc.
        ))
    {
        printf("Unable to load extension(s), this sample requires:\n  OpenGL version 2.0\n"
               "  GL_ARB_vertex_program\n  GL_ARB_fragment_program\n  GL_EXT_texture_array\n"
               "  GL_NV_gpu_program4\n Exiting...\n");
        exit(-1);
    }

    // load images
    #define NIMAGES 4

	// change #2, use dds (dxt5) images
    char *imageName[] = {
        //IMAGE_PATH "rock.png",
        //IMAGE_PATH "snow.png",
        //IMAGE_PATH "grass.png",
        //IMAGE_PATH "graydirt.png"
        IMAGE_PATH "rock.dds",
        IMAGE_PATH "snow.dds",
        IMAGE_PATH "grass.dds",
        IMAGE_PATH "graydirt.dds"
    };

    nv::Image images[NIMAGES];
    for(int i=0; i<NIMAGES; i++) {
        images[i].loadImageFromFile( imageName[i]);
    }

    // load images as 2d texture array
    GLuint texid;
    glGenTextures(1, &texid);
    glBindTexture( GL_TEXTURE_2D_ARRAY_EXT, texid);

    glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri( GL_TEXTURE_2D_ARRAY_EXT, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);

	// change #3, allocate some storage
	char *tmp = new char[512*512*4];
    
	// 2D Texture arrays a loaded just like 3D textures

	// change #4, use glCompressedTexImage3D instead of glTexImage3D
    //glTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0, GL_RGBA8, images[0].getWidth(), images[0].getHeight(), NIMAGES, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY_EXT, 0,  GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, images[0].getWidth(), images[0].getHeight(), NIMAGES, 0, 512*512*4, tmp);

    for (int i = 0; i < NIMAGES; i++) {
		// change #5, use glCompressedTexSubImage instead of glTexSubImage
        //glTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, i, images[i].getWidth(), images[i].getHeight(), 1, images[i].getFormat(), images[i].getType(), images[i].getLevel(0));
		glCompressedTexSubImage3D( GL_TEXTURE_2D_ARRAY_EXT, 0, 0, 0, i, images[i].getWidth(), images[i].getHeight(), 1, images[i].getFormat(), images[i].getImageSize(), images[i].getLevel(0));
    }

    fprog = nv::CompileASMShader(GL_FRAGMENT_PROGRAM_ARB, fprog_code);
    lerp_fprog = nv::CompileASMShader(GL_FRAGMENT_PROGRAM_ARB, lerp_fprog_code);
}

//
//
//////////////////////////////////////////////////////////////////////
void draw_quad() {
    // r texture coordinate is used to select layer
    glBegin(GL_QUADS);
    glTexCoord3f(0.0, 0.0, 0.0);
    glVertex2f(-1.0, -1.0);
    glTexCoord3f(1.0, 0.0, 4.0);
    glVertex2f(1.0, -1.0);
    glTexCoord3f(1.0, 1.0, 4.0);
    glVertex2f(1.0, 1.0);
    glTexCoord3f(0.0, 1.0, 0.0);
    glVertex2f(-1.0, 1.0);
    glEnd();
}

//
//
//////////////////////////////////////////////////////////////////////
void display() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    manipulator.applyTransform();

    glColor3f(1.0, 1.0, 1.0);

    if ( options[OPTION_LERP_LAYERS]) {
        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, lerp_fprog);
    } else {
        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, fprog);
    }

    if (options[OPTION_USE_PROGRAM]) {
        glEnable(GL_FRAGMENT_PROGRAM_ARB);
    }

    glPolygonMode( GL_FRONT_AND_BACK, options[OPTION_DISPLAY_WIREFRAME] ? GL_LINE : GL_FILL);

    draw_quad();

    glDisable(GL_FRAGMENT_PROGRAM_ARB);

    glutSwapBuffers();
}

//
//
//////////////////////////////////////////////////////////////////////
void idle() {
    if ( options[OPTION_ANIMATE])
        manipulator.idle();
    
    glutPostRedisplay();
}

//
//
//////////////////////////////////////////////////////////////////////
void key(unsigned char k, int x, int y) {
    k = tolower(k);

    if (optionKeyMap.find(k) != optionKeyMap.end())
        options[optionKeyMap[k]] = !options[optionKeyMap[k]];
	
    switch(k) {
        case 27:
        case 'q':
            exit(0);
            break;
    }
    
	glutPostRedisplay();
}

//
//
//////////////////////////////////////////////////////////////////////
void resize(int w, int h) {
    glViewport(0, 0, w, h);
    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    
    gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 0.1, 100.0);
    
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    manipulator.reshape(w, h);
}

//
//
//////////////////////////////////////////////////////////////////////
void mouse(int button, int state, int x, int y) {
    manipulator.mouse(button, state, x, y);
}

//
//
//////////////////////////////////////////////////////////////////////
void motion(int x, int y) {
    manipulator.motion(x, y);
}

//
//
//////////////////////////////////////////////////////////////////////
int main(int argc, char **argv) {
	glutInit(&argc, argv);
	glutInitWindowSize(512, 512);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB);
	glutCreateWindow("simple_texture_array");

	init_opengl();

    manipulator.setDollyActivate( GLUT_LEFT_BUTTON, GLUT_ACTIVE_CTRL);
    manipulator.setPanActivate( GLUT_LEFT_BUTTON, GLUT_ACTIVE_SHIFT);
    manipulator.setDollyPosition( -2.0f);

	glutDisplayFunc(display);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
    glutIdleFunc(idle);
    glutKeyboardFunc(key);
    glutReshapeFunc(resize);

    //configure the options
    optionKeyMap['w'] = OPTION_DISPLAY_WIREFRAME;
    options[OPTION_DISPLAY_WIREFRAME] = false;

    optionKeyMap['f'] = OPTION_USE_PROGRAM;
    options[OPTION_USE_PROGRAM] = true;
    
    optionKeyMap[' '] = OPTION_ANIMATE;
    options[OPTION_ANIMATE] = true;

    optionKeyMap['l'] = OPTION_LERP_LAYERS;
    options[OPTION_LERP_LAYERS] = false;

    //print the help info
    printf( "Simple_texture_array - sample showing the usage of texture arrays\n");
    printf( "  Commands:\n");
    printf( "    q / [ESC] - Quit the application\n");
    printf( "    [SPACE]   - Toggle continuous animation\n");
    printf( "    f         - Toggle using a fragment program\n");
    printf( "    w         - Toggle displaying wireframe\n");
    printf( "    l         - Toggle interpolation between texture layers\n\n");

	glutMainLoop();

	return 0;
}
I've also modified PixelBox::getSubVolume to return what appear to be correct values for a DXT5 texture, and Ogre's sample now at least runs.

Code: Select all

	PixelBox PixelBox::getSubVolume(const Box &def) const
	{
		// change #1, allow DXT5
		//if(PixelUtil::isCompressed(format))
		if(PixelUtil::isCompressed(format) && (format != PF_DXT5))
		{
			if(def.left == left && def.top == top && def.front == front &&
			   def.right == right && def.bottom == bottom && def.back == back)
			{
				// Entire buffer is being queried
				return *this;
			}
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Cannot return subvolume of compressed PixelBuffer", "PixelBox::getSubVolume");
		}
		if(!contains(def))
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Bounds out of range", "PixelBox::getSubVolume");

		// change #2, set elemSize = 1 for DXT5
		//const size_t elemSize = PixelUtil::getNumElemBytes(format);
		size_t elemSize = PixelUtil::getNumElemBytes(format);
		if (format == PF_DXT5)
			elemSize = 1;

		// Calculate new data origin
		// Notice how we do not propagate left/top/front from the incoming box, since
		// the returned pointer is already offset
		PixelBox rval(def.getWidth(), def.getHeight(), def.getDepth(), format, 
			((uint8*)data) + ((def.left-left)*elemSize)
			+ ((def.top-top)*rowPitch*elemSize)
			+ ((def.front-front)*slicePitch*elemSize)
		);

		rval.rowPitch = rowPitch;
		rval.slicePitch = slicePitch;
		rval.format = format;

		return rval;
	}
But all I can see from the shader is the texture from the first layer.

The obvious difference between the two is glCompressedTexSubImage3D vs PixelUtil::bulkPixelConversion.

Re: Texture Arrays

Posted: Wed Nov 02, 2011 11:39 am
by Assaf Raman
Nice work,I will have a look when I can.

Re: Texture Arrays

Posted: Thu Nov 10, 2011 1:42 pm
by Wolfski
I've found the cause of why I can only see the first layer of a compressed texture array.

There is code in OgreGLHardwarePixelBuffer.cpp (line 354 onwards) for "some systems (e.g. old Apple) don't like compressed subimage calls so prefer non-sub versions". If I disable this code, the other layers become visible.

Code: Select all

			case GL_TEXTURE_2D_ARRAY:
				// some systems (e.g. old Apple) don't like compressed subimage calls
				// so prefer non-sub versions
				
				// change #1, this non sub call makes only the front layer of the texture accessable
				// remove it and compressed texture arrays work

				//if (dest.left == 0 && dest.top == 0 && dest.front == 0)
				if (false)
				{
					glCompressedTexImage3DARB(mTarget, mLevel,
						format,
						dest.getWidth(),
						dest.getHeight(),
						dest.getDepth(),
						0,
						data.getConsecutiveSize(),
						data.data);
				}
				else
				{
					glCompressedTexSubImage3DARB(mTarget, mLevel, 
						dest.left, dest.top, dest.front,
						dest.getWidth(), dest.getHeight(), dest.getDepth(),
						format, data.getConsecutiveSize(),
						data.data);
				}
				break;
I don't know the history behind this non sub image compressed image call for the front layer, but there must be a better test for systems that don't like compressed subimage calls. Could I request a fix for this please.

Also, could someone explain the PixelUtil::getMemorySize calculations for DXT1 and 5 formats:

Code: Select all

				case PF_DXT1:
					return ((width+3)/4)*((height+3)/4)*8 * depth;
				case PF_DXT2:
				case PF_DXT3:
				case PF_DXT4:
				case PF_DXT5:
					return ((width+3)/4)*((height+3)/4)*16 * depth;
My understanding is, DXT1 and 5 compress a 4 x 4 block of pixels into 8 and 16 bytes respectively. Which would simply be:

DXT1 width * height * depth / 2
DXT5 width * height * depth

Am I missing something here?

Re: Texture Arrays

Posted: Thu Nov 10, 2011 1:47 pm
by Assaf Raman
Regarding OgreGLHardwarePixelBuffer.cpp - what is your suggested fix?

Regarding dx format - no clue. Anyone else?

Re: Texture Arrays

Posted: Thu Nov 10, 2011 4:20 pm
by Wolfski
I'm probably a bit out of my depth here, but glCompressedTexImage would only seem to make sense for a bulk copy of all the layers.

So how about:

Code: Select all

			case GL_TEXTURE_2D_ARRAY:
				// some systems (e.g. old Apple) don't like compressed subimage calls
				// so prefer non-sub versions
				
				// change #1, only use glCompressedTexImage3D if copying all the layers at once
				//if (dest.left == 0 && dest.top == 0 && dest.front == 0)
				GLint textureDepth;
				glGetTexLevelParameteriv(mTarget, mLevel, GL_TEXTURE_DEPTH, &textureDepth);
				if (dest.left == 0 && dest.top == 0 && dest.getDepth() == textureDepth)
				{
					glCompressedTexImage3DARB(mTarget, mLevel,
						format,
						dest.getWidth(),
						dest.getHeight(),
						dest.getDepth(),
						0,
						data.getConsecutiveSize(),
						data.data);
				}
				else
				{
					glCompressedTexSubImage3DARB(mTarget, mLevel, 
						dest.left, dest.top, dest.front,
						dest.getWidth(), dest.getHeight(), dest.getDepth(),
						format, data.getConsecutiveSize(),
						data.data);
				}
				break;

Re: Texture Arrays

Posted: Fri Nov 11, 2011 4:04 am
by Kojack
My understanding is, DXT1 and 5 compress a 4 x 4 block of pixels into 8 and 16 bytes respectively. Which would simply be:

DXT1 width * height * depth / 2
DXT5 width * height * depth

Am I missing something here?
It's because dxt textures only work on resolutions which are multiples of 4.
If you have a 5x5 texture, it takes the memory of an 8x8, size is always padded to the next multiple of 4. That's what ogre's code is doing.

Re: Texture Arrays

Posted: Fri Nov 11, 2011 9:52 am
by Wolfski
Doh. Of course, they're integers. I was missing something.

Thanks

Re: Texture Arrays

Posted: Tue Mar 06, 2012 10:32 am
by m2codeGEN
Tanks to masterfalcon.
rev 3421 (048a35d56fda) GL: 2D array texture support for GLSL. The texture array sample now works on GL
v1-8
2012-02-19

Re: Texture Arrays

Posted: Wed Mar 19, 2014 12:13 am
by pontus
I had some trouble with using texture arrays with the GL RenderSystem. Got a vector bounds error. I checked the source and found that GCT_SAMPLER2DARRAY is not listed as an int type in updateUniforms(), causing the uniform to default to float. Here is the fix:
https://www.dropbox.com/s/g8wexm23afwr2al/6349.patch