views:

387

answers:

3

UPDATE:

I've posted the Renderer code below, since this code here doesn't seem to be the problem.

I'm having a problem with some code of mine where when I try to upload multiple textures to openGL, one at a time, it fails somewhat spectacularly, with the renderer only ending up using a single texture. I've done a bit of debugging to trace the error to this function, but I'm having problems figuring out what part of the function is at fault. Are there are particularly obvious screwups I'm making that I'm simply not seeing, or is there a more subtle flaw in my code?

Here're the structs I use for storing texture information and generally just keeping track of all my pointers

typedef struct {
  float Width;
  float Height;
} texInfo;

typedef struct {
  dshlib::utfstr ResourceName;
  texInfo * TextureInfo;
  GLuint TextureNum;
  SDL_Surface * Image;
} texCacheItem;

And here's the current WIP graphics loader. Basically, it loads a named .png file out of a .zip archive using a prewritten library (incidentally, it's being tested with this program). Then it's loaded with libpng and then loaded up as a texture, with caching thrown in to speed loading up and avoid loading a single texture more than once. I omitted the #include statements since they were just cruft.

texCacheItem * loadGraphics(dshlib::utfstr FileName) {

  for(int i = 0; i < NumTexCached; i++) { //First see if this texture has already been loaded
    if(TextureCache[i]->ResourceName == FileName)
      return TextureCache[i];
  }

  dshlib::utfstr FullFileName = "Data/Graphics/"; //If not, create the full file path in the archive
  FullFileName += FileName;
  dshlib::FilePtr file = resourceCtr.OpenFile(FullFileName); //And open the file

  if (!file->IsOk()) { //If the file failed to load...
    EngineState = ENGINESTATE_ERR;
    return NULL;
  }

  SDL_Surface * T = loadPNG(file);
  texCacheItem * Texture = new texCacheItem;
  Texture->TextureInfo = new texInfo;

  glGenTextures(1, &Texture->TextureNum); //Allocate one more texture and save the name to the texCacheItem
  glBindTexture(GL_TEXTURE_2D, Texture->TextureNum); //Then create it
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, T->w, T->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, T->pixels);

  Texture->TextureInfo->Width = (float)T->w; //Write the useful data
  Texture->TextureInfo->Height = (float)T->h;
  Texture->ResourceName = FileName; //And the caching info needed
  Texture->Image = T; //And save the image for if it's needed later and for deleting

  if (!TexCacheSize) { //If this is the first load this is 0, so allocate the first 8 Cache slots.
    TexCacheSize = 8;
    TextureCache = new texCacheItem*[8];
  }

  if(NumTexCached == TexCacheSize) { //If we're out of cache space
    if (TexCacheSize == 32768) { //If too many cache items, error out
      EngineState = ENGINESTATE_ERR;
      return NULL;
    }
    TexCacheSize <<= 1; //Double cache size
    texCacheItem ** NewSet = new texCacheItem*[TexCacheSize];
    memcpy(NewSet, TextureCache, NumTexCached * sizeof(texCacheItem*)); //And copy over the old cache
    delete TextureCache; //Delete the old cache
    TextureCache = NewSet; //And assign the pointer to the new one
  }
  TextureCache[NumTexCached++] = Texture; //Store the texCacheItem to the Cache

  file->Close(); //Close the file
  file = NULL;   //And NULL the smart pointer. [NTS: Confirm with Disch this is what won't cause a memory leak]

  return Texture; //And return the loaded texture in texCacheItem form.
}

SDL_Surface *loadPNG(dshlib::FilePtr File)
{
    Uint8 *PNGFile = new Uint8[(long)File->GetSize()];
    File->GetAr<Uint8>(PNGFile, (long)File->GetSize());
    return IMG_LoadPNG_RW(SDL_RWFromMem(PNGFile, (long)File->GetSize()));
}

Here's the renderer code file. It's quite messy at the moment, apologies for that. level->activeMap basically tells the renderer which "layer" of the tilemap (0 being the front, 3 the back) to draw the sprites above.

#include "../MegaJul.h"
void render(void) {

  //Render the current tilemap to the screen

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0f, 0.0f, -4.0f);

if (level) {

glBegin(GL_QUADS);
float dT = 32.0f / level->dTex;
float sX, fX, fXa, sY, tX, tY, sYa, sYb, sXa, tXa, tYa;
unsigned long m = level->mapDimensions[0] * level->mapDimensions[1];
float ai; long long t; Sint16 * p;
glBindTexture(GL_TEXTURE_2D, level->tilemap->TextureNum);

for (int i = 3; i >= 0; i--) {

  if (level->layers[i]->mapPosition[0] > 0)
    level->layers[i]->mapPosition[0] = 0;
  if (level->layers[i]->mapPosition[0] < 0 - (signed long)((level->mapDimensions[0] - 21) * 32))
    level->layers[i]->mapPosition[0] = 0 - (signed long)((level->mapDimensions[0] - 21) * 32);

  if (level->layers[i]->mapPosition[1] < 0)
    level->layers[i]->mapPosition[1] = 0;
  if (level->layers[i]->mapPosition[1] > (signed long)((level->mapDimensions[1] - 16) * 32))
    level->layers[i]->mapPosition[1] = (signed long)((level->mapDimensions[1] - 16) * 32);

  if (i == level->activeMap) {
    for (int j = 0; j < NumSprites; j++) {
      glBindTexture(GL_TEXTURE_2D, Sprites[j]->Graphics->TextureNum);
      Sprites[j]->render(level->layers[i]->mapPosition[0], level->layers[i]->mapPosition[1]);
    }
    for (int j = 0; j < NumBullets; j++) {
      glBindTexture(GL_TEXTURE_2D, Bullets[j]->Texture->TextureNum);
      Bullets[j]->render(level->layers[i]->mapPosition[0], level->layers[i]->mapPosition[1]);
    }
  }

  glBindTexture(GL_TEXTURE_2D, level->tilemap->TextureNum);

  t = 0 - ((level->layers[i]->mapPosition[0] - (level->layers[i]->mapPosition[0] % 32)) /32) + (((level->layers[i]->mapPosition[1] - (level->layers[i]->mapPosition[1] % 32)) /32) * level->mapDimensions[0]);
  ai = (float)(3 - i); //Invert Z-Index
  sX = (float)((level->layers[i]->mapPosition[0] % 32));
  sY = (float)((level->layers[i]->mapPosition[1] % 32));
  if (sX > 0) 
      sX -= 32;
  if (sY < 0)
      sY += 32;
  fX = sX /= 32.0f;
  sY /= 32.0f;
  fXa = sXa = sX + 1.0f;
  sYa = sY + 14.0f;
  sYb = sY + 15.0f;

  for (int y = 0; y < 16; y++) {
    for (int x = 0; x < 21; x++) {
      p = level->tiles[level->layers[i]->map[t]]->position;
      tX = p[0] / level->dTex;
      tY = p[1] / level->dTex;
      tXa = tX + dT;
      tYa = tY + dT;
      glTexCoord2f(tX, tYa);     glVertex3f(fX, sYa, ai);   // Bottom Left Of The Texture and Quad
      glTexCoord2f(tXa,tYa);     glVertex3f(fXa, sYa, ai);  // Bottom Right Of The Texture and Quad
      glTexCoord2f(tXa,tY);      glVertex3f(fXa, sYb, ai);  // Top Right Of The Texture and Quad
          glTexCoord2f(tX, tY);      glVertex3f(fX, sYb, ai);     // Top Left Of The Texture and Quad
          fX += 1.0f;
          fXa += 1.0f;
          t++;
          if (t >= m) break;
        }
        sYb -= 1.0f; sYa -= 1.0f;
        fXa = sXa; fX = sX;
        t += level->mapDimensions[0] - 21; //21 is the number of tiles drawn on a line (20 visible + 1 extra for scrolling)
      }

    }
    glEnd();
  }

SDL_GL_SwapBuffers();
}

Here's the code segments that set the tilemap data for sprites and the level:

Level:

void loadLevel(dshlib::utfstr FileName) {
-snip-
  texCacheItem * Tex = loadGraphics(FileName);

  if (!Tex) { //Load the tile graphics for the level
    unloadLevel();
    EngineState = ENGINESTATE_ERR;
    return;
  } else {
    level->dTex = Tex->TextureInfo->Width;
    level->tilemap = Tex;
  }
-snip-
}

Sprite:

void SpriteBase::created() {
  this->Graphics = loadGraphics(DefaultGFX());
-snip-
}

UPDATE 2:

Sid Farkus noted one big mistake I made with the renderer, so here's an updated renderer.cpp:

#include "../MegaJul.h"
void render(void) {

  //Render the current tilemap to the screen

  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
  glTranslatef(0.0f, 0.0f, -4.0f);

  if (level) {

    float dT = 32.0f / level->dTex;
    float sX, fX, fXa, sY, tX, tY, sYa, sYb, sXa, tXa, tYa;
    unsigned long m = level->mapDimensions[0] * level->mapDimensions[1];
    float ai; long long t; Sint16 * p;

    for (int i = 3; i >= 0; i--) {

      if (level->layers[i]->mapPosition[0] > 0)
        level->layers[i]->mapPosition[0] = 0;
      if (level->layers[i]->mapPosition[0] < 0 - (signed long)((level->mapDimensions[0] - 21) * 32))
        level->layers[i]->mapPosition[0] = 0 - (signed long)((level->mapDimensions[0] - 21) * 32);

      if (level->layers[i]->mapPosition[1] < 0)    
        level->layers[i]->mapPosition[1] = 0;
      if (level->layers[i]->mapPosition[1] > (signed long)((level->mapDimensions[1] - 16) * 32))
        level->layers[i]->mapPosition[1] = (signed long)((level->mapDimensions[1] - 16) * 32);

      if (i == level->activeMap) {
        for (int j = 0; j < NumSprites; j++) {
          glBindTexture(GL_TEXTURE_2D, Sprites[j]->Graphics->TextureNum);
          glBegin(GL_QUADS);
          Sprites[j]->render(level->layers[i]->mapPosition[0], level->layers[i]->mapPosition[1]);
          glEnd();
        }
        for (int j = 0; j < NumBullets; j++) {
          glBindTexture(GL_TEXTURE_2D, Bullets[j]->Texture->TextureNum);
          glBegin(GL_QUADS);
          Bullets[j]->render(level->layers[i]->mapPosition[0], level->layers[i]->mapPosition[1]);
          glEnd();
        }
      }

      glBindTexture(GL_TEXTURE_2D, level->tilemap->TextureNum);
      glBegin(GL_QUADS);

  -snipped out renderer since it was bloat

    glEnd();
  }

  SDL_GL_SwapBuffers();
}
+2  A: 

In your renderer, are you calling glBindTexture appropriately? It sounds like your renderer is just using whatever the last texture you uploaded was since that was the last time you called glBindTexture. glBindTexture is what tells OpenGL texture to use for your polygons.

Jim Buck
Nope, I'm making sure I always call glBindTexture() in the renderer. Would it help if I posted the relevant parts of it for reference?
Sukasa
Yeah, definitely, since there is nothing obvious wrong in the loading code. (This assumes you are actually loading different textures, of course. :) )
Jim Buck
Print out the TextureNum before you call bind to be sure you are, in fact, at least setting a different opengl texture. Also, try using GLintercept to see if you can spot anything using that. (I use this all the time to track down my opengl problems.)
Jim Buck
Running a debugger check on those shows that the sprite has TextureNum = 2, and level has TextureNum = 1. Thanks for the glIntercept tip, though!
Sukasa
..And that solved my problem, thanks!
Sukasa
I should clarify; the GLIntercept program showed me what I did wrong. I had the last glEnd() one curly-brace too far down, and wasn't noticing that.
Sukasa
A: 

I'm assuming you can't do any sort of debugging or logging for this? If you could, I'd expect this would be trivial to diagnose.

The main thing that looks dangerous to me is that you're not checking the return value from loadPNG. I'd put something there as the very first thing I did.

I'd consider commenting out the initial check for an already-cached texture, too. If things start working at that point, you know it's a problem with the resource names or filenames (or the comparison of them).

As an aside, I'm surprised that you're using classes and smart pointers but rolling your own std::vector with bare pointers and arrays. ;)

Kylotan
I have done debugging - and I've had no luck which is why I'm asking here.As far as the intermix goes, well, I'm sure I'll get around to making it make sense soon enough.
Sukasa
+1  A: 

With your rendering code I can see you're calling BindTexture in a glBegin/End block. From the opengl docs:

GL_INVALID_OPERATION is generated if glBindTexture is executed between the execution of glBegin and the corresponding execution of glEnd.

Move your BindTexture calls outside the glBegin()/glEnd() block and you should be golden. You'll probably have to have multiple blocks to accommodate your rendering style.

edit:

With the updated code, make sure of a couple things; your sprite positions are visible on the screen with the current projection/model view matrix and your sprite texture ids are valid textures. There's nothing technically wrong that jumps out at me now but your values may be off.

Ron Warholic
Hm, now I'm getting the same issue, but instead the *other* texture is used... Copying in the newer version, knowing me I completely goofed it up...
Sukasa
Make sure inside your render() calls you don't do anything wierd either. Remember there are a limited subset of functions you're permitted to call between glBegin and glEnd.
Ron Warholic
The only things the sprite's render does is calculate some offsets and then call glTexCoord2f and glVertex3f.
Sukasa