fkj's picture

Using multiple textures

The texture examples I've seen only shows how to use a single texture (to keep it simple obviously), but I'm trying to use two textures, and am having a problem. I use the following code to load a texture (I believe there is nothing special about this code):

        Bitmap bitmap = new Bitmap( path );
 
        BitmapData data = bitmap.LockBits(
          new System.Drawing.Rectangle( 0, 0, bitmap.Width, bitmap.Height ),
          System.Drawing.Imaging.ImageLockMode.ReadOnly,
          System.Drawing.Imaging.PixelFormat.Format32bppRgb
        );
 
        int textureId = GL.GenTexture();
 
        GL.TexImage2D(
          TextureTarget.Texture2D,
          0,
          PixelInternalFormat.Rgba,
          data.Width, data.Height,
          0,
          OpenTK.Graphics.OpenGL.PixelFormat.Bgra,
          PixelType.UnsignedByte,
          data.Scan0
        );
 
        bitmap.UnlockBits( data );

Loading two textures I get textureId's 1 and 2. This is done once in the start of the program (...OnLoad...).

I then use only the first texture on some quads (GL.BindTexture( 1 ) etc.).

It all works fine but for one thing, it always uses the last texture loaded! If I only load the first texture it looks fine, but it's like it always uses the last texture nomatter what id I try to use. Note that BindTexture() is never called using 2 anywhere in the code, and GetError() returns no error on neither TexImage2D or BindTexture. Both textures works fine if only one texture is loaded.

What am I missing? Do I have to do something extra to make two textures available?

(using: OpenTK 1.1 (by Subversion), my driver supports only OpenGL 2.1)


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

Try adding a call to GL.BindTexture between GenTexture and TexImage2D. The code above tries to upload texture data to texture #id 0, which leads to undefined behavior.

fkj's picture

It did not solve the problem:

        Bitmap bitmap = new Bitmap( path );
 
        BitmapData data = bitmap.LockBits(
          new System.Drawing.Rectangle( 0, 0, bitmap.Width, bitmap.Height ),
          System.Drawing.Imaging.ImageLockMode.ReadOnly,
          System.Drawing.Imaging.PixelFormat.Format32bppRgb
        );
 
        int textureId = GL.GenTexture();
 
        GL.BindTexture( TextureTarget.Texture2D, textureId );
 
        GL.TexImage2D(
          TextureTarget.Texture2D,
          0,
          PixelInternalFormat.Rgba,
          data.Width, data.Height,
          0,
          OpenTK.Graphics.OpenGL.PixelFormat.Bgra,
          PixelType.UnsignedByte,
          data.Scan0
        );
 
        GL.Finish(); // to be sure!
 
        bitmap.UnlockBits( data );
 
        Console.WriteLine( textureId + ": " + path );
1: data/images/textures/texture.png
2: data/images/textures/digits.png

It is always the last of the two textures that is used, even though I'm only using GL.BindTexture( TextureTarget.Texture2D, 1 ) when applying textures.

the Fiddler's picture

Try displaying both textures on screen, do they show the same thing? Are the contents of the pngs different?

There are just too many things that could be at fault here, it's impossible to say what the cause could be without seeing more of your code (at least rendering function).

Radar's picture

Do you have two objects that should have the different textures or do you want to multitexture your object?
In the first case you can (inside the renderloop):
BindTexture 1
render Geometry
BindTexture 2
render Geometry

in the 2nd case you need to:
Activate TextureUnit1
BindTexture 1
Activate TextureUnit2
BindTexture 2
render Object
(take a look at multitexturing-tutorials)

fkj's picture

@Radar: I have two objects (quads) that should use different textures.

@Fiddler: (the remaining)

The textures are very different, and using both textures, then in all cases image data from the last texture will be used (i.e. apparently binding will be to the last texture no matter what texture id is used).

The code is distributed over a number of classes (effeciency is not an issue, I'm just trying to learn some OpenGL). The concepts are as follows:

The game window has a world, a world has a player and a collection of renderable objects. The player has the camera. A Cube is a renderable object that has a collection of six squares. Each square has a texture.
When the game window is asked to render, it asks the players camera to setup, and then asks each renderable object (the world has) to render itself. The cube will ask the squares to render themselves, and they will do this by asking the texture to bind and attach itself to the square (i.e. quad).

The code has been edited down, but should contain all relevant code:

  public class ApplicationWindow : GameWindow {
    protected World world;
 
    ...
 
    public ApplicationWindow( int width, int height, string title )
      : base( width, height, GraphicsMode.Default, title ) {
    }
 
    protected override void OnLoad( EventArgs e ) {
      ...
 
      GL.ClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); // black
      GL.Enable( EnableCap.DepthTest );
      GL.Enable( EnableCap.CullFace ); // enable culling
 
      CursorVisible = false;
 
      // textures are loaded (see previously)
 
      world = new World();
    }
 
    ...
 
    protected override void OnResize( EventArgs e ) {
      base.OnResize( e );
 
      world.Player.Camera.OnResize( e, ClientRectangle );
    }
 
    ...
 
    protected override void OnRenderFrame( FrameEventArgs e ) {
      GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit );
 
      world.OnRender( e );
 
      ...
 
      SwapBuffers();
    }
 
    ...
  }
 
 
 
  public interface IRenderable {
    void OnRender( FrameEventArgs e );
  }
 
 
 
  public class World : IRenderable, ... {
    private List<IRenderable> renderables;
    protected AbstractPlayer player;
 
    public World() {
      renderables = new List<IRenderable>();
    }
 
    ...
 
    public AbstractPlayer Player {
      get { return player; }
    }
 
    ...
 
    public void OnRender( FrameEventArgs e ) {
      player.Camera.SetView();
 
      foreach ( IRenderable renderable in renderables )
        renderable.OnRender( e );
    }
  }
 
 
 
  public class Camera {
    ...
    private Vector3 eye, up;
 
    public Camera( Vector3 location ) {
      up = new Vector3( 0, 1, 0 );
 
      eye = location;
      ...
    }
 
    public Vector3 Direction {
      get { return ...; }
    }
 
    ...
 
    public void OnResize( EventArgs e, Rectangle rectangle ) {
      GL.Viewport( rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height );
 
      Matrix4 projection =
        Matrix4.CreatePerspectiveFieldOfView(
          (float) Math.PI / 4,
          (float) rectangle.Width / rectangle.Height,
          1.0f,
          64.0f
        );
 
      GL.MatrixMode( MatrixMode.Projection );
      GL.LoadMatrix( ref projection );
    }
 
    public void SetView() {
      Matrix4 modelview = Matrix4.LookAt( eye, eye + Direction, up );
 
      GL.MatrixMode( MatrixMode.Modelview );
      GL.LoadMatrix( ref modelview );
    }
  }
 
 
 
  public class Cube : IRenderable {
    ...
    private Square[] sides;
 
    ...
 
    public void OnRender( FrameEventArgs e ) {
      GL.Enable( EnableCap.Texture2D );
 
      GL.Color3( 1.0f, 1.0f, 1.0f );
 
      foreach ( Square side in sides )
        side.OnRender( e );
 
      GL.Disable( EnableCap.Texture2D );
    }
  }
 
 
 
  public class Square : IRenderable {
    private Texture texture;
    private int textureOffset; // turns the texture
    private Vector3[] points;
 
    public Square(
      Texture texture, int textureOffset,
      Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4
    ) {
      this.texture = texture;
      this.textureOffset = textureOffset;
 
      points = new Vector3[ 4 ];
      points[ 0 ] = p1;
      points[ 1 ] = p2;
      points[ 2 ] = p3;
      points[ 3 ] = p4;
    }
 
    public void OnRender( FrameEventArgs e ) {
      GL.Begin( BeginMode.Quads );
      {
        texture.Bind();
        texture.SetOnQuad( points, textureOffset );
      }
      GL.End();
    }
  }
 
 
 
  public class Texture {
    private int textureMapId;
    private RectangleF mapArea;
 
    public Texture( int textureMapId, RectangleF mapArea ) {
      this.textureMapId = textureMapId;
      this.mapArea = mapArea;
    }
 
    public void Bind() {
      GL.BindTexture( TextureTarget.Texture2D, textureMapId );
    }
 
    // offset is used to turn the texture
    public void SetOnQuad( Vector3[] points, int offset ) {
      SetTextureLowerRight( points[ offset % 4 ] );
      offset++;
      SetTextureUpperRight( points[ offset % 4 ] );
      offset++;
      SetTextureUpperLeft( points[ offset % 4 ] );
      offset++;
      SetTextureLowerLeft( points[ offset % 4 ] );
    }
 
    private void SetTextureUpperLeft( Vector3 p ) {
      SetTextureCorner( mapArea.Left, mapArea.Top, p );
    }
 
    private void SetTextureLowerLeft( Vector3 p ) {
      SetTextureCorner( mapArea.Left, mapArea.Bottom, p );
    }
 
    private void SetTextureLowerRight( Vector3 p ) {
      SetTextureCorner( mapArea.Right, mapArea.Bottom, p );
    }
 
    private void SetTextureUpperRight( Vector3 p ) {
      SetTextureCorner( mapArea.Right, mapArea.Top, p );
    }
 
    private void SetTextureCorner( float imageX, float imageY, Vector3 p ) {
      GL.TexCoord2( imageX, imageY );
      GL.Vertex3( p );
    }
  }

A Console.WriteLine( ... ) in Texture.Bind() shows that 'textureId' is always 1 (as it should be), but image data is from the texture having id 2 (the last one loaded using the code previously shown).

I would expect it to be some small thing, that does some kind of reset, that I do not notice due to my limited experience with OpenGL.

the Fiddler's picture

Ok, another sanity check: what happens if you switch the loading order for the two textures (i.e. load the second one first and vice versa)?

Your rendering code appears to be in order. My first guess was that the second texture was overwriting the data of the first texture (i.e. both TexImage2d calls would upload to the same texture id). My second guess is that the texture ids passed to the Texture constructors are incorrect (i.e. do you actually assign the results of GL.GenTexture() to the Texture constructors?)

fkj's picture

I've tried all this before and none of it exposed the error!

But is my rendering code really in order?

I tried to comment all instance of GL.BindTexture( ... ), and the program performed consistently (in various tests) with not binding anything at all! (i.e. exactly the same behaviour with or without any bindings)

I then checked if I was using a debug version of OpenTK (compiled in debug mode, and not release mode), and I found I mistakenly was using a version compiled in release mode (using a version compiled in debug mode adds extra error checking. @Fiddler: I know you know this, but others reading this might benefit from knowing it). Then something came up!

GL.End() threw an exception (something like "illegal operation") in:

  public class Square : IRenderable {
    ...
 
    public void OnRender( FrameEventArgs e ) {
      GL.Begin( BeginMode.Quads );
      {
        texture.Bind();
        texture.SetOnQuad( points, textureOffset );
      }
      GL.End();
    }
  }

I then tried to comment various things, and the result was that GL.BindTexture() is not allowed between GL.Begin(...) and GL.End() (is this correct?).

I then changed it to:

  public class Square : IRenderable {
    ...
 
    public void OnRender( FrameEventArgs e ) {
      texture.Bind();
 
      GL.Begin( BeginMode.Quads );
      {
        texture.SetOnQuad( points, textureOffset );
      }
      GL.End();
    }
  }

Now GL.End() does not throw any exception, and secondly my framerate hits bottom - something (costly) is really happening - it's really binding a texture (or trying to).

The behaviour is still not correct, but the error has changed to something that (hopefully) makes more sence.

If I use the last texture loaded it still works fine (apart from the framerate going down). If I use the first texture loaded it does no longer use image data from the last texture loaded (but the framerate still goes down), but uses the current color (white) to fill the quads. In the render code, the texture id is correct (1), and GL.GetError() after GL.BindTexture(...) claims there is no error, but the texture is obviously not bound! When I switch between using the first and last texture loaded, I only swap the loading order, everything else is the same (except the texture id, obviously).

the Fiddler's picture

Ok, this is a good step forward.

A white texture typically indicates some invalid or undefined texture state.

Are your textures dimensions powers-of-two? Some video cards (e.g. older Intel chips) don't support non-POT textures, which could explain the discrepancy. (What video card are you using?)

Have you enabled mipmapping? (GL.TexParameter(..., TextureParameter.LinearMipmapLinear or LinearMipmapNearest)). In this case, you must provide a complete mipmap chain for the texture to be considered valid. Try disabling mipmapping explicitly (set TextureParameter.Linear for both textures).

Regarding performance, calling GL.Begin/GL.End like this can really hurt. Try reorganizing your rendering code like this:

  1. Bind texture 1
  2. GL.Begin
  3. Render quad
  4. GL.End
  5. Bind texture 2
  6. ...
fkj's picture

The textures are 256x256 and 32x32, and mipmaps are not enabled, but asking about the texture parameters got me onto the right track!

I had the misconception that setting the texture parameters affected all loaded textures. Because of this I loaded all (both) textures before I set the texture parameters (setting Min/Mag to Near, by the way). When I placed the same code after loading each texture, there were no more errors! Previously only the last texture had the texture parameters set.

To sum up, there were three errors. Two in loading the textures:

1. Not binding the texture after GenTexture() (believing this to be implicit)

2. Not setting texture parameters after loading each texture (believing these to be "global" to all loaded textures)

and one in rendering:

3. Binding textures between Begin() and End()

Performance is another issue, this code is just for learning the basics.

Thank you for taking time to help!

the Fiddler's picture

You are welcome, this was a tough one to crack! (Multiple interrelated issues, that's exactly like the issue I'm hunting down right now at work.)