Playing with ARB_direct_state_access

I am trying to write my code with extension ARB_direct_state_access (core in 4.5) and a fall-back in pre-ARB_direct_state_access times.

So, I have the texture loading functions, which work.

To render, I must run the old code: (when I run this code, texture appears).


glActiveTexture(GL_TEXTURE0 + unit);
gl.glBindTexture(target, texture);

Or the new code: (when I run this code, texture doesn’t appear).

glBindTextureUnit(GL_TEXTURE0 + unit, texture);

What is wrong, with this? Is n’t glBindTextureUnit() enough for rendering?

I include the full code:

Texture.java

public class Texture implements Release {
    public Texture() {
        int a[] = new int[1];
        gl.glGenTextures(1, a, 0);
        texture = a[0];
    }
    protected Texture(int tex) { texture = tex; }
    @Override public void release() { gl.glDeleteTextures(1, new int[] { texture }, 0); }
    
    public final int texture;
    
    /** Bind texture to texture target.
     * @param target texture target like GL_TEXTURE_2D. */
    public void bindToTarget(int target) { gl.glBindTexture(target, texture); }
    /** Bind texture to texture unit.
     * @param unit index of the texture unit. */
    public void bindToUnit(int unit) { gl.glBindTextureUnit(GL_TEXTURE0 + unit, texture); }
    /** Set active texture unit.
     * @param unit index of the texture unit. */
    static public void setActiveUnit(int unit) { gl.glActiveTexture(GL_TEXTURE0 + unit); }
    static public float getFloat(int pname) {
        float[] ret = new float[1];
        gl.glGetFloatv(pname, ret, 0);
        return ret[0];
    }
    static public int getInteger(int pname) {
        int[] ret = new int[1];
        gl.glGetIntegerv(pname, ret, 0);
        return ret[0];
    }

    static public class Config {
        /** Max supported anisotropy from device. At least 1.f */
        private static float max_anisotropy = 0;
        /** Get max supported anisotropy from device.
         * @return max anisotropy. 1.f means no anisotropy available. */
        public static float getMaxAnisotropy() {
            if (max_anisotropy == 0) {
                max_anisotropy = getFloat(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT);
                if (max_anisotropy < 1.0f) max_anisotropy = 1.0f;
            }
            return max_anisotropy;
        }
        /** Texture anisotropy for request.
         * Must be lower than getMaxAnisotropy(). No anisotropy is 1.f */
        public float anisotropy = getMaxAnisotropy();
        //public int magnify = GL_LINEAR;    // default
        //public int minify = GL_LINEAR_MIPMAP_LINEAR;    // not default
        //public int edgePolicy = GL_REPEAT;    //default
        /** Skip higher mipmap levels.
         * e.g if 3, skip levels 4*4 and higher. Default 0. */
        public int skip_last_levels = 0;
        
        /** Edge policy of texture coordinate s. */
        public int s_edge = GL_CLAMP_TO_EDGE;
        /** Edge policy of texture coordinate t. */
        public int t_edge = GL_CLAMP_TO_EDGE;
        /** Edge policy of texture coordinate r. */
        public int r_edge = GL_CLAMP_TO_EDGE;
        /** Border color for texture, if it has border. */
        public int border;
        
        public void textureParameters(int texture) {
            //default: gl.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            gl.glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
            gl.glTextureParameterf(texture, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
            gl.glTextureParameteri(texture, GL_TEXTURE_WRAP_S, s_edge);
            gl.glTextureParameteri(texture, GL_TEXTURE_WRAP_T, t_edge);
            gl.glTextureParameteri(texture, GL_TEXTURE_WRAP_R, r_edge);
            gl.glTextureParameterfv(texture, GL_TEXTURE_BORDER_COLOR, getColorFromInt(border), 0);
        }

        public void texParameters(int target) {
            //default: gl.glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            gl.glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
            gl.glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
            gl.glTexParameteri(target, GL_TEXTURE_WRAP_S, s_edge);
            gl.glTexParameteri(target, GL_TEXTURE_WRAP_T, t_edge);
            gl.glTexParameteri(target, GL_TEXTURE_WRAP_R, r_edge);
            gl.glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, getColorFromInt(border), 0);
        }
        
        /** Convert an integer RGBA, to a float array of 4 components.
         * @param color A 4 byte integer as RGBA color, with lower byte the red.
         * @return A float array with 4 color components, where 0-th element is red. */
        protected static float[] getColorFromInt(int color) {
            float[] c = new float[4];
            for (int z = 0; z < 4; ++z, color >>= 8)
                c[0] = color & 255;
            return c;
        }
    }

    /** How many levels a texture can have.
     * @param i Texture image.
     * @param cfg Texture loading preferences. Can be null.
     * @return Number of mipmap levels for that texture. */
    public static int getMipmaps(Image i, Config cfg) {
        return max(1, i.getMipmaps() - cfg.skip_last_levels);
    }

    public static Texture loadTexture2D(InputStream is, Config cfg) throws IOException {
        if (is == null) throw new IOException("InputStream parameter is null, on loadTexture2D");
        if (cfg == null) cfg = new Config();
        Image i = new Image(is);
        if (i.width != i.height) throw new IOException("2d textures must be square");
        Texture texture = new Texture();
        texture.bindToTarget(GL_TEXTURE_2D);    // needed to baptize texture
        final int levels = getMipmaps(i, cfg);
        gl.glTextureStorage2D(texture.texture, levels, i.format, i.width, i.height);
        if (i.isCompressed) 
            for (int z = 0, sz, offs = 0; z < levels; ++z, offs += sz) {
                sz = i.getMipmapSize(z);
                gl.glCompressedTextureSubImage2D(texture.texture, z/*level*/,
                        0/*offset_x*/, 0/*offset_y*/, i.width, i.height, i.format,
                        sz/*image_size*/, ByteBuffer.wrap(i.raw, offs, sz));
            }
        else {
            gl.glTextureSubImage2D(texture.texture, 0/*level*/, 0, 0/*offset_x-y*/,
                    i.width, i.height, i.extformat, i.type, ByteBuffer.wrap(i.raw));
            gl.glGenerateTextureMipmap(texture.texture);
        }
        cfg.textureParameters(texture.texture);
                
        return texture;
    }

    public static Texture loadTex2D(InputStream is, Config cfg) throws IOException {
        if (is == null) throw new IOException("InputStream parameter is null, on loadTex2D");
        if (cfg == null) cfg = new Config();
        Image i = new Image(is);
        if (i.width != i.height) throw new IOException("2d textures must be square");
        Texture texture = new Texture();
        texture.bindToTarget(GL_TEXTURE_2D);
        final int levels = getMipmaps(i, cfg);
        gl.glTexStorage2D(GL_TEXTURE_2D, levels, i.format, i.width, i.height);
        if (i.isCompressed) 
            for (int z = 0, sz, offs = 0; z < levels; ++z, offs += sz) {
                sz = i.getMipmapSize(z);
                gl.glCompressedTexSubImage2D(GL_TEXTURE_2D, z/*level*/,
                        0/*offset_x*/, 0/*offset_y*/, i.width, i.height, i.format,
                        sz/*image_size*/, ByteBuffer.wrap(i.raw, offs, sz));
            }
        else {
            gl.glTexSubImage2D(GL_TEXTURE_2D, 0/*level*/, 0, 0/*offset_x-y*/,
                    i.width, i.height, i.extformat, i.type, ByteBuffer.wrap(i.raw));
            gl.glGenerateMipmap(GL_TEXTURE_2D);
        }
        cfg.texParameters(GL_TEXTURE_2D);
        return texture;
    }
    
    public static Texture loadTextureArray2D(InputStream is, Config cfg) throws IOException {
        if (is == null) throw new IOException("InputStream parameter is null, on loadTexArray2D");
        if (cfg == null) cfg = new Config();
        Image i = new Image(is);
        if (i.width != i.height) throw new IOException("2d array textures must be square");
        Texture texture = new Texture();
        texture.bindToTarget(GL_TEXTURE_2D_ARRAY);    // needed to baptize texture
        final int levels = getMipmaps(i, cfg);
        gl.glTextureStorage3D(texture.texture, levels, i.format, i.width, i.height, i.depth);
        if (i.isCompressed)
            for (int z = 0, sz, offs = 0; z < levels; ++z, offs += sz) {
                sz = i.getMipmapSize(z);
                gl.glCompressedTextureSubImage3D(texture.texture, z/*level*/,
                        0, 0, 0/*offset_x-y-z*/, i.width, i.height, i.depth,
                        i.format, sz/*image_size*/, ByteBuffer.wrap(i.raw, offs, sz));
            }
        else {
            gl.glTextureSubImage3D(texture.texture, 0/*level*/, 0, 0, 0/*offset_x-y-z*/,
                    i.width, i.height, i.depth, i.extformat, i.type, ByteBuffer.wrap(i.raw));
            gl.glGenerateTextureMipmap(texture.texture);
        }
        cfg.textureParameters(texture.texture);
        return texture;
    }
    
    public static Texture loadTexArray2D(InputStream is, Config cfg) throws IOException {
        if (is == null) throw new IOException("InputStream parameter is null, on loadTextureArray2D");
        if (cfg == null) cfg = new Config();
        Image i = new Image(is);
        if (i.width != i.height) throw new IOException("2d array textures must be square");
        Texture texture = new Texture();
        texture.bindToTarget(GL_TEXTURE_2D_ARRAY);
        final int levels = getMipmaps(i, cfg);
        gl.glTexStorage3D(GL_TEXTURE_2D_ARRAY, levels, i.format, i.width, i.height, i.depth);
        if (i.isCompressed)
            for (int z = 0, sz, offs = 0; z < levels; ++z, offs += sz) {
                sz = i.getMipmapSize(z);
                gl.glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, z/*level*/,
                        0, 0, 0/*offset_x-y-z*/, i.width, i.height, i.depth,
                        i.format, sz/*image_size*/, ByteBuffer.wrap(i.raw, offs, sz));
            }
        else {
            gl.glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0/*level*/, 0, 0, 0/*offset_x-y-z*/,
                    i.width, i.height, i.depth, i.extformat, i.type, ByteBuffer.wrap(i.raw));
            gl.glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
        }
        cfg.texParameters(GL_TEXTURE_2D_ARRAY);
        return texture;
    }
}

TextureOld.java

public class TextureOld extends Texture {
    /** Target of texture. Like GL_TEXTURE_2D. */
    protected final int target;
    protected TextureOld(int tex, int target) { super(tex); this.target = target; }
    /** Bind texture to texture unit.
     * @param unit index of the texture unit. */
    @Override
    public void bindToUnit(int unit) {
        gl.glActiveTexture(GL_TEXTURE0 + unit);
        gl.glBindTexture(target, texture);
    }
    
    public static Texture loadTexture2D(InputStream is, Config cfg) throws IOException {
        return isDirectStateAccessSupported()
                ? Texture.loadTexture2D(is, cfg)
                : new TextureOld(Texture.loadTex2D(is, cfg).texture, GL_TEXTURE_2D);
    }
    
    public static Texture loadTextureArray2D(InputStream is, Config cfg) throws IOException {
        return isDirectStateAccessSupported()
                ? Texture.loadTextureArray2D(is, cfg)
                : new TextureOld(Texture.loadTexArray2D(is, cfg).texture, GL_TEXTURE_2D_ARRAY);
    }
    
    private static int ARB_direct_state_access = -1;
    public static boolean isDirectStateAccessSupported() {
        if (ARB_direct_state_access == -1) {
            // Check if OpenGL core version is at least 4.5
            int r = getInteger(GL_MAJOR_VERSION);
            if (r > 4 || r == 4 && getInteger(GL_MINOR_VERSION) >= 5)
                ARB_direct_state_access = 2;
            // Check if extension ARB_direct_state_access is supported
            else ARB_direct_state_access = gl.glGetString(GL_EXTENSIONS).contains("ARB_direct_state_access") ? 1 : 0;
        }
        return ARB_direct_state_access > 0;
    }
}

https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBindTextureUnit.xhtml

Description

glBindTextureUnit binds an existing texture object to the texture unit numbered unit.

It could probably be more explicit, but the first parameter to glBindTextureUnit is not “GL_TEXTURE0 + unit”:

glBindTextureUnit(unit, texture);

Whow! That solves the problem!

Indeed, OpenGL API prototypes have different parameter types:

void glActiveTexture(GLenum texture);        // GL_TEXTURE0 + unit
void glBindTextureUnit(GLuint unit, GLuint texture); // unit, textureid

I also note the two separate meanings assigned to the “texture” parameter, depending on which function you use. :sorrow: