PDA

View Full Version : Cannot Draw Textures



Exaxis
08-08-2011, 08:48 AM
Hello again, everyone. Here's what i have going:

In my project, I've written a small library that will open a bitmap stored somewhere on my machine and read in the bitmap's dimensions, as well as the pixel information and store it in memory. Here is the Bitmap_24 class, and the method in that class that reads in bitmap data:

Bitmap_24 class
<div class="ubbcode-block"><div class="ubbcode-header">Click to reveal.. <input type="button" class="form-button" value="Show me!" onclick="toggle_spoiler(this, 'Yikes, my eyes!', 'Show me!')" />]<div style="display: none;">
class Bitmap_24
{
// Member Fields
private:
int m_iWidth; // Image Width in Pixels
int m_iHeight; // Image height in Pixels
DWORD *m_pPixelArray; // Actual Pixels of the image

public:
// Constructor(s) / Destructor
Bitmap_24(char *Filename);

// General Methods
void Cleanup();
bool ReadBitmap(char *Filename);

// Accessor methods
int GetWidth();
int GetHeight();
DWORD* GetPixels();

};
[/QUOTE]</div>

ReadBitmap method
<div class="ubbcode-block"><div class="ubbcode-header">Click to reveal.. <input type="button" class="form-button" value="Show me!" onclick="toggle_spoiler(this, 'Yikes, my eyes!', 'Show me!')" />]<div style="display: none;">
bool Bitmap_24::ReadBitmap(char *Filename)
{
FILE* pFileStream;
BITMAPFILEHEADER bmFileHeader;
BITMAPINFO bmInfo;
int iBytesPerLine;
int iTotalBytes;

// Open the file, obtaining our stream
if(!(pFileStream = fopen(Filename, "r")))
{
//TODO: Error handle
MessageBox(NULL, "Bitmap file was not opened", "An error occurred", MB_ICONERROR | MB_OK);
return false;
}

// read the BITMAPFILEHEADER
fread((char *)&amp;bmFileHeader, sizeof(BITMAPFILEHEADER), 1, pFileStream);

// Read the BITMAPINFO
// NOTE: Because this bitmap should have a 24-bit pixel resolution, there
// should be no color table, so the entire BITMAPINFO can be read together in one call.
fread((char *)&amp;bmInfo, sizeof(BITMAPINFO), 1, pFileStream);

//TODO: Bitmap resolution / compression validation
if((bmInfo.bmiHeader.biBitCount != 24) || (bmInfo.bmiHeader.biCompression != 0))
{
MessageBox(NULL, "Bitmap was not 24bpp or was compressed", "An error occurred", MB_ICONERROR | MB_OK);
return false;
}

// Populate Image Data Member Variables
m_iWidth = (int)bmInfo.bmiHeader.biWidth;
m_iHeight = (int)bmInfo.bmiHeader.biHeight;

// Calculate the number of bytes in a line
/*
* This is simple to calculate. We have the number of pixels in a row (Image width.)
* We know that there are 24 bits per pixel.
* Because each pixel is 24 bits, each pixel is 3 bytes. We can find the byte width (w/out padding):
* notPaddedByteWidth = (pixelWidth * 24) / 8
* Depending on the image width, there may be extra "padding" bytes in the array. In a bitmap,
* each "scan line" or "row" must have a number of bytes that is a multiple of 4. If the width in
* bytes is not naturally a multiple of four, empty padding bytes are added - 1, 2, or 3 depending.
* We can figure this out with a simple mod function:
* padBytesPerLine = notPaddedByteWidth % 4
* padBytesPerLine tells us how many extra bytes are included in each line. Knowing this, each line has
* notPaddedByteWidth + padBytesPerLine bytes per line. The total number of bytes taken up by the pixel
* data, then, is this number of bytes per line multiplied by the image height.
*/

iBytesPerLine = ((m_iWidth * 24) / 8) + (((m_iWidth * 8) / 3) % 4);
iTotalBytes = iBytesPerLine * m_iHeight;

// Now that we know how many bytes there are, we can initialize our member array.
// We can figure out the size of the array in DWORDS by dividing the number of bytes
// by 4. (One DWORD = 4 bytes).
m_pPixelArray = new DWORD[iTotalBytes / 4];

// Move the file pointer to the beginning of the array.
fseek(pFileStream, bmFileHeader.bfOffBits, SEEK_SET);

// Read in the array
fread((char *)&amp;m_pPixelArray, sizeof(DWORD), iTotalBytes / 4, pFileStream);

// At this point, the entire bitmap should be loaded into memory!
return true;
}
[/QUOTE]</div>

The Bitmap_24 constructor requires a Filename string, and simply passes that to the ReadBitmap method.

I also have a method in my game engine that is designed to take in an array of "images" (the bitmap objects from my library), as well as an empty destination texture array. The method should create the appropriate number of Texture Objects and store their references in the texture array (pTexArray), and then one by one create the textures from the bitmap image data supplied by the image array (pImageArray). Here is the method:

BitmapToTexture
<div class="ubbcode-block"><div class="ubbcode-header">Click to reveal.. <input type="button" class="form-button" value="Show me!" onclick="toggle_spoiler(this, 'Yikes, my eyes!', 'Show me!')" />]<div style="display: none;">
bool hzGameEngine::hzBitmapToTexture(Bitmap_24 *pImageArray, GLuint *pTexArray)
{
int iArrayLength; // Length of the arrays.

// Check the length of the arrays
if((iArrayLength = (sizeof(pImageArray) / sizeof(*pImageArray))) > (sizeof(pTexArray) / sizeof(*pTexArray)))
{
// There are too many images and not enough elements in the texture array
// TODO: Error handling
MessageBox(NULL, "The image array is larger than the texture array", "An error occurred", MB_ICONERROR | MB_OK);
return false;
}

// Generate the OpenGL Texture Objects
glGenTextures(iArrayLength, pTexArray);

//Set Pixel Unpacking
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // Handles the fact that bitmap rows must
// be a multiple of 4 bytes

for(int i = 0; i < iArrayLength; i++)
{
//Bind a texture
glBindTexture(GL_TEXTURE_2D, pTexArray[i]);

//Unload image data into the current texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, pImageArray[i].GetWidth(), pImageArray[i].GetHeight(),
0, GL_BGR_EXT, GL_UNSIGNED_BYTE, pImageArray[i].GetPixels());

//Specify Filtering
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTE R,GL_LINEAR); // Linear Filtering
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTE R,GL_LINEAR); // Linear Filtering

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

//TODO: Free memory

return true;
}
[/QUOTE]</div>

And here is the relevant part of my game setup method, which actually calls this method. Note that _pTexArray is a global array of one GLuint:

Game Setup
<div class="ubbcode-block"><div class="ubbcode-header">Click to reveal.. <input type="button" class="form-button" value="Show me!" onclick="toggle_spoiler(this, 'Yikes, my eyes!', 'Show me!')" />]<div style="display: none;">
// Texture Setup
Bitmap_24 pImageArray[1] = { Bitmap_24("C:\\Users\\Mahlon\\Desktop\\grasstexture.bmp")};

if(!(_pGame->hzBitmapToTexture(pImageArray, _pTexArray)))
{
MessageBox(NULL, "BitmapToTexture failed" , "An error occurred", MB_ICONERROR | MB_OK);
}

if(_pTexArray[0] == 0)
{
MessageBox(NULL, "Nothing is in texarray", "An error occurred", MB_ICONERROR | MB_OK);
}

if(pImageArray[0].GetHeight() == 0)
{
MessageBox(NULL, "Image height is 0", "An error occurred", MB_ICONERROR | MB_OK);
}
[/QUOTE]</div>

Once that is finished I should, if everything went well, have an array of openGL texture names that i cna use to draw textures with. At the moment I'm simply trying to draw one texture to a single quad. Here is the relevant part of my draw method:

Draw
<div class="ubbcode-block"><div class="ubbcode-header">Click to reveal.. <input type="button" class="form-button" value="Show me!" onclick="toggle_spoiler(this, 'Yikes, my eyes!', 'Show me!')" />]<div style="display: none;">
//OpenGL drawing
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glBindTexture(GL_TEXTURE_2D, _pTexArray[0]);

glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); // Bottom-right texture coordinate
glVertex3f(_squareX, _squareY, _squareZ); //Bottom-left vertex
glTexCoord2f(1.0f, 0.0f);
glVertex3f(_squareX + _squareSize, _squareY, _squareZ); //Bottom-right vertex
glTexCoord2f(1.0f, 1.0f);
glVertex3f(_squareX + _squareSize, _squareY + _squareSize, _squareZ); //Top-Right Vertex
glTexCoord2f(0.0f, 1.0f);
glVertex3f(_squareX, _squareY + _squareSize, _squareZ); //Top-left vertex.
glEnd();

SwapBuffers(hDC);
[/QUOTE]</div>

Unfortunately, something is not working. I get no texture drawn at all. And out of all of the error messages that I've built into my code, only the "There is nothing in TexArray" one launches. Admittedly, this could be a false positive. I have it setup to display if the entry in _pTexArray is zero, but since I don't know how OpenGL goes about naming textures, it may supposed to be zero.

I know this is a lot to look at, but any pointers or suggestions would be appreciated a lot. I've been working on this for a while, and I'm at a loss as to why it isn't working.

mobeen
08-08-2011, 08:38 PM
Could u try calling
glEnable(GL_TEXTURE_2D); before
glGenTextures(iArrayLength, pTexArray); and see if this helps.

Exaxis
08-08-2011, 08:46 PM
I tried that, but nothing different happened. Thanks for the suggestion though! :)

mobeen
08-08-2011, 10:18 PM
Your single pixel is 24 bits(3bytes) but u set the pack alignment to 32bits (4 bytes)? change it to 3.

Exaxis
08-08-2011, 10:30 PM
Your single pixel is 24 bits(3bytes) but u set the pack alignment to 32bits (4 bytes)? change it to 3.

I assume you're talking about the line:

glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

I looked up that function, and the description for this is that it sets the alignment requirements for each pixel row in memory. Bitmaps require that each row have a length that is a multiple of 4 bytes, so I used the value 4 as it specifies word-alignment. If I'm not mistaken, that would be 32 bits, which is 4 bytes, which is what I want. I got this information here. (http://www.khronos.org/opengles/sdk/1.1/docs/man/glPixelStorei.xml)

So did I misunderstand it, or am I using the function correctly?

xiphos
08-09-2011, 01:59 AM
If you are getting this error message “There is nothing in TexArray” it means that _pTexArray[0] == 0, and this will be happening if glGenTextures fails.
Can you check the value of iArrayLength, which has been passed to glGenTextures(iArrayLength, pTexArray)

iArrayLength should be number of textures you want to generate. As I see from calucation

if((iArrayLength = (sizeof(pImageArray) / sizeof(*pImageArray))) > (sizeof(pTexArray) / sizeof(*pTexArray)))

iArrayLength, it may be zero. It should'nt be zero.

As per my knowledge, glGenTextures will always gives non-zero values. so _pTexArray should hold array of non-zero values.

Exaxis
08-09-2011, 08:50 AM
If you are getting this error message “There is nothing in TexArray” it means that _pTexArray[0] == 0, and this will be happening if glGenTextures fails.
Can you check the value of iArrayLength, which has been passed to glGenTextures(iArrayLength, pTexArray)

iArrayLength should be number of textures you want to generate. As I see from calucation

if((iArrayLength = (sizeof(pImageArray) / sizeof(*pImageArray))) > (sizeof(pTexArray) / sizeof(*pTexArray)))

iArrayLength, it may be zero. It should'nt be zero.

As per my knowledge, glGenTextures will always gives non-zero values. so _pTexArray should hold array of non-zero values.



Ok, thank you for the information!

I did some debugging, and iArrayLength was indeed zero. That doesn't make any sense to me, because with what I'm doing right now I'm using two arrays of length one each.

However, if I manually set iArrayLength to 1 before the call to glGenTextures, nothing changes. I still get the error message "Nothing is in texArray", and no textures are drawn. This makes me wonder if I'm not doing something correctly with my arrays - since somehow the calculation for iArrayLength is going wrong, and glGenTextures isn't working.

Exaxis
08-09-2011, 06:20 PM
Ok. I managed to change something.

I made the array of Bitmap_24s global. So now I make an array like so:

Bitmap_24 _pImageArray[1];

And then I read the bitmap into _pImageArray[0] with ReadBitmap.

I also now pass _pImageArray into BitmapToTexture. When I do this, iArrayLength in BitmapToTexture still somehow winds up 0, and the error message "texArray is empty" pops up.

HOWEVER, if I manually set iArrayLength to 1 (as it should be), then i do not get the texArray is empty message. If I check with the debugger, _pTexArray[0] has the value "1" now. In addition, the object I'm trying to texture is a very slight shade of grey, whereas before it was just white. And by a slight shade of grey, I mean it's only barely off white.

This leads me to believe that i am now texturing my object, but the texture itself is not showing up successful. To me it seems most likely that I am reading in the pixel array from the bitmap incorrectly - I know that I am reading in the bitmap's height and width successfully.

So my questions at the moment are:

1) Why on Earth isn't iArraylength being set correctly
2) What am i doing wrong when I try to read in the Bitmap's pixel array in ReadBitmap?

EDIT: I used the debugger to dig into the texture array, _pImageArray, and I found something odd. The m_iWidth and m_iHeight fields were 256 and 256, as they should be. However, m_PixelArray is listed as a pointer to an unsigned long, instead of an array of DWORDs. The unsigned long that m_PixelArray points to is 3452816845, and that value doesn't change if I re-run the program. It appears that the pixel array m_pPixelArray isn't being set up like it should, but why would that be?

xiphos
08-09-2011, 11:44 PM
1) Why on Earth isn't iArraylength being set correctly

sizeof(pImageArray) in function hzBitmapToTexture will return the size of pointer(4 bytes) not the array size(). (pass by reference).
To fix this you can keep a varible to keep track of size or you can make pImageArray where you malloc to global and use that.




2) What am i doing wrong when I try to read in the Bitmap's pixel array in ReadBitmap?
You have opened bitmap file in text mode,

if(!(pFileStream = fopen(Filename, "r")))can you update this to open in binary mode,
if(!(pFileStream = fopen(Filename, "rb")))see if this fixes the problem. (bmp should be read as binary file)

Not sure but, I think you need to read BITMAPINFOHEADER instead of BITMAPINFO.

fread((char *)&amp;bmInfo, sizeof(BITMAPINFO), 1, pFileStream);

xiphos
08-10-2011, 02:15 AM
Ok, after debugging your code,
I was able to run successfully with minor change. (without modifying much from your 1st post)

Try update this line of code,

fread((char *)&amp;m_pPixelArray, sizeof(DWORD), iTotalBytes / 4, pFileStream); with
fread(m_pPixelArray, sizeof(DWORD), iTotalBytes / 4, pFileStream); check if this fixes the problem.

Exaxis
08-10-2011, 09:18 AM
1) Why on Earth isn't iArraylength being set correctly

sizeof(pImageArray) in function hzBitmapToTexture will return the size of pointer(4 bytes) not the array size(). (pass by reference).
To fix this you can keep a varible to keep track of size or you can make pImageArray where you malloc to global and use that.




2) What am i doing wrong when I try to read in the Bitmap's pixel array in ReadBitmap?
You have opened bitmap file in text mode,

if(!(pFileStream = fopen(Filename, "r")))can you update this to open in binary mode,
if(!(pFileStream = fopen(Filename, "rb")))see if this fixes the problem. (bmp should be read as binary file)

Not sure but, I think you need to read BITMAPINFOHEADER instead of BITMAPINFO.

fread((char *)&amp;bmInfo, sizeof(BITMAPINFO), 1, pFileStream);


Thanks for pointing all that out! :) That gives me everything I need to fix my iArraylength issue, which has been bugging me!

It also turns out that opening the bitmap in text mode works, BUT it really should be opened in binary, so I'm doing that now. Thanks for pointing that out - I didn't even know the difference!

Exaxis
08-10-2011, 09:27 AM
Ok, after debugging your code,
I was able to run successfully with minor change. (without modifying much from your 1st post)

Try update this line of code,

fread((char *)&amp;m_pPixelArray, sizeof(DWORD), iTotalBytes / 4, pFileStream); with
fread(m_pPixelArray, sizeof(DWORD), iTotalBytes / 4, pFileStream); check if this fixes the problem.






Bingo! That fixes it perfectly! :D

Thanks a TON for that. I don't know that I ever would have figured that out, or tried that.

Out of curiosity, why exactly does this work? In every other place that I use this function, the way to use it is to supply the first parameter with a char-pointer-typecast address of a storage space. I know I shouldn't have passed the address now, but why was it necessary to leave out any conversion?

Thanks again everyone who helped out! :)

xiphos
08-11-2011, 11:29 PM
Out of curiosity, why exactly does this work? In every other place that I use this function, the way to use it is to supply the first parameter with a char-pointer-typecast address of a storage space. I know I shouldn't have passed the address now, but why was it necessary to leave out any conversion?
char-pointer-typecast will not do any harm, it may or maynot throw warning.

Also you are right that you need to pass address where you need to copy filestream.But one thing you missed,
m_pPixelArray = new DWORD[iTotalBytes / 4];
means allocate (iTotalBytes/4) DWord of memory and return the address
so m_pPixelArray hold address of allocated memory.
If you do &amp;m_pPixelArray, it will return the address of m_pPixelArray i.e where it has been stored.

Hope this clears the doubt.