1
\$\begingroup\$

I made the following class to simpify texture loading in LWJGL 3, and modeled it after the slick-util API:

public class Texture {

    private ByteBuffer data;
    private int id;
    private int width;
    private int height;

    private ByteBuffer resizeBuffer(ByteBuffer buffer, int size) {
        ByteBuffer newBuffer = BufferUtils.createByteBuffer(size);
        buffer.flip();
        newBuffer.put(buffer);
        return newBuffer;
    }

    private ByteBuffer imageToByteBuffer(String path, int size) throws IOException {
        ByteBuffer buffer;

        URL url = Thread.currentThread().getContextClassLoader().getResource(path);
        File file = new File(url.getFile());

        if (file.isFile()) {
            FileInputStream stream = new FileInputStream(file);
            FileChannel channel = stream.getChannel();
            buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());

            channel.close();
            stream.close();
        } else {
            buffer = BufferUtils.createByteBuffer(size);
            InputStream source = url.openStream();

            if (source == null) {
                throw new FileNotFoundException(path);
            }

            try {
                ReadableByteChannel channel = Channels.newChannel(source);

                try {
                    while (true) {
                        int bytes = channel.read(buffer);

                        if (bytes == -1) {
                            break;
                        }

                        if (buffer.remaining() == 0) {
                            buffer = resizeBuffer(buffer, buffer.capacity() * 2);
                        }
                    }

                    buffer.flip();
                } finally {
                    channel.close();
                }
            } finally {
                source.close();
            }
        }

        return buffer;
    }

    public Texture(String path) throws IOException {
        IntBuffer width = BufferUtils.createIntBuffer(1);
        IntBuffer height = BufferUtils.createIntBuffer(1);
        IntBuffer components = BufferUtils.createIntBuffer(1);
        data = STBImage.stbi_load_from_memory(imageToByteBuffer(path, 1024), width, height, components, 4);
        id = GL11.glGenTextures();

        GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_LINEAR);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL14.GL_GENERATE_MIPMAP, GL11.GL_TRUE);

        this.width = width.get(0);
        this.height = height.get(0);

        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, this.width, this.height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, data);
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
        STBImage.stbi_image_free(data);
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public int getID() {
        return id;
    }

    public ByteBuffer getByteBuffer() {
        return data;
    }

    public byte[] getByteArray() {
        return data.array();
    }

    public void bind() {
        GL11.glEnable(GL11.GL_TEXTURE_2D);
        GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
    }

    public void release() {
        GL11.glDisable(GL11.GL_TEXTURE_2D);
    }
}

I use it as follows:

bg.bind();
GL11.glBegin(GL11.GL_QUADS);

GL11.glTexCoord2f(0F, 0F);
GL11.glVertex2f(-1F, 1F);

GL11.glTexCoord2f(1F, 0F);
GL11.glVertex2f(1F, 1F);

GL11.glTexCoord2f(1F, 1F);
GL11.glVertex2f(1F, -1F);

GL11.glTexCoord2f(0F, 1F);
GL11.glVertex2f(-1F, -1F);

GL11.glEnd();
bg.release();

where bg is a declared Texture that throws no errors.

\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

This is a pretty straightforward and readable implementation, and I see how it can be used. Here are some suggestions:

Use Modern OpenGL

It looks like you're using OpenGL 1.x. That's really old OpenGL. You should be using OpenGL 3 or later, really. It's supported on all major platforms. It's not too difficult to learn if you already know OpenGL 1 and it offers a lot of benefits.

Keep Track of Your Texture Target

While a texture ID is certainly an important piece of information to keep around, it's not the only piece you need. Almost all texture-related functions in OpenGL require a texture target instead of or in addition to a texture ID. If you don't keep it around you'll either end up needing it and not having it, or being forced to always use one particular texture target, which is not ideal.

Naming

While your variable names are decent, they could be even better. For example, in both resizeBuffer() and imageToByteBuffer() you have an argument named size. What units is size in? Bytes? Pixels? Vertices? You should be more explicit with a name like numBytes or size_in_bytes, or whatever's appropriate.

Also the function name imageToByteBuffer() is confusing. From the name, it seems like it's going to read a file at a path and return a byte buffer containing the pixels from the image at that path. But the second half of the function looks like it does something else. I'm not familiar enough with the Java libraries to understand when file.isFile() would return false, and what else a file would be if not a file. (I assume since it's a URL it might be a network resource?) I would recommend making 2 functions - one for reading a local file and one for reading a remote file (or whatever the else path does), with appropriate names and call them from imageToByteBuffer. Something like this:

if (file.isFile())
{
    return imageFromLocalFile(path);
}
else
{
    return imageFromRemoteFile(path, size); // or whatever it should be called
}

Also, how does a caller know what to pass in for size? Isn't that something that is discovered by reading the file?

\$\endgroup\$
3
  • \$\begingroup\$ What would the use of modern GL look like in my context? How would you keep track of the texture target? Naming caveats aside, is there something more critical to the code? \$\endgroup\$
    – T145
    Commented Mar 10, 2017 at 1:34
  • \$\begingroup\$ I was mainly thinking about your use of immediate mode in the example. But for your texture class, you could use glGenerateMipmap which is the more modern way of generating mipmaps. \$\endgroup\$ Commented Mar 10, 2017 at 3:20
  • \$\begingroup\$ As for keeping track of the texture target, it would just be a private member variable like the id is. In terms of other critiques of the code - the buffer handling in imageToByteBuffer seems unrelated to keeping track of an OpenGL texture. I'd move that to a separate class. \$\endgroup\$ Commented Mar 10, 2017 at 3:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.