sampler object bound to unit 0, affects other units

I’m playing around with sampler objects and observing an strange behavior with multiple texture units.

When a sampler object is bound to a texture unit, its state supersedes that of the texture object bound to that texture unit. If the sampler name zero is bound to a texture unit, the currently bound texture’s sampler state becomes active.

So a sampler should only affect the units it is bound to, right? If I bind a sampler to a specific unit, every texture also bound to that unit uses the sampler parameters instead of the internal parameters (glTexParameter) of that texture. Ok.
But, for whatever the fuck reason, if i bind at least one sampler to one unit, it affects all other units as well.

So if for example a sampler is bound to unit 0, and unit 1 is also used but not bound to the sampler, i would expect, that the bound texture for unit 1 is unaffected from the sampler and the internal texparameters for that texture are used instead of the ones of the sampler.
But it isn’t unaffected.

In short: Is it necessary to use (maybe different) sampler objects for all texture units, if at least one unit is using a sampler object? Is a “mixed mode” not possible, i mean for example, unit 0 is using a sampler, unit 1 not?

The behavior you say is happening represents a driver bug. Do you have some code that exhibits the problem? What hardware and drivers are you on?

Also, generally speaking, if you’re using sampler objects, you’re probably using them everywhere. That’s probably why nobody’s caught that bug yet. But you don’t have to use them everywhere, so again, such behavior would be a bug. Oh and:

If I bind a sampler to a specific unit, every texture also bound to that unit uses the sampler parameters instead of the internal parameters (glTexParameter) of that texture.

That’s not quite true. Sampler objects only encapsulate the filtering, edge sampling, and other state necessary for fetching it within the shader. Texture object state that represents something fundamental to the texture itself remains purely in the texture object.

Hi Alfonso,

yes i have code, of course. But to post it here i need to eliminate unnecessary parts. The Code is in C# with OpenTK binding for OpenGL.

Maybe i just did something wrong, but i simply can’t find any failure from my side.
Give me a bit time, maybe until tomorrow evening for a full example showing the (supposed) bug.

I know i don’t have to use them everywhere, but sampler objects are a new thing for me, so i tried a lot of use cases.

[QUOTE=Alfonse Reinheart;1264105]
That’s not quite true. Sampler objects only encapsulate the filtering, edge sampling, and other state necessary for fetching it within the shader. Texture object state that represents something fundamental to the texture itself remains purely in the texture object.[/Quote]
Oh, yes, i know so far. I’m not a native english speaker and it’s pretty hard to express myself in english, so sorry if i’m not always really accurate. But i really appreciate your posts and links. Thank you

Well what’s best now: Just parts of the code to show the relevant parts (so that you could recode it in your language) or a full working solution copy for visual studio for c#?

What hardware and drivers are you on?

AMD Radeon HD 6800 Series
Driver Version 14.501.1003.0

Ok whatever, here we go.

Variant 1.
Two sampler Objects.
Output as expected: http://prntscr.com/5zjdkf

Both textures has internal nearest filtering.
Sampler 1 (plane down) overrides, and change, filtering with linear filter.
Sampler 2 (plane up) overrides, but don’t change, filtering with nearest filtering.
So texture down is linear filtered, texture up nearest.
Pretty well.


namespace Betrayal
{
    class BugPage : Page
    {
        private Frame camera = new Frame(FrameType.Camera);
        private Frame plane1Frame = new Frame(FrameType.Model);
        private Frame plane2Frame = new Frame(FrameType.Model);
        
        private Shader_Tex shaderTex;

        private TextureHandler textureHandler = new TextureHandler();
        private GeoObject plane1;
        private GeoObject plane2;
        
        private int tex1, tex2;
        private int sampler1, sampler2;
        
        public BugPage(PageConstructorArgs cargs)
            : base(cargs)
        { }

        public override void Init()
        {
            shaderTex = new Shader_Tex(store.ShaderDir);

            plane1 = new Plane(15, 15, 100);
            plane1.Construct();

            plane2 = new Plane(15, 15, 100);
            plane2.Construct();

            camera.Move(0, 0, 3);
            plane1Frame.Move(0, -2, 0);
            plane2Frame.Move(0, 2, 0);
            //loading the same file twice just to be sure ...
            tex1 = textureHandler.LoadTexture(Path.Combine(store.TexDir, "schach.jpg"), TextureMinFilter.Nearest, TextureMagFilter.Nearest, TextureWrapMode.Repeat);
            tex2 = textureHandler.LoadTexture(Path.Combine(store.TexDir, "schach.jpg"), TextureMinFilter.Nearest, TextureMagFilter.Nearest, TextureWrapMode.Repeat);
        }

        
        public override void InitGL()
        {
            sampler1 = GL.GenSampler();
            GL.SamplerParameter(sampler1, SamplerParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.SamplerParameter(sampler1, SamplerParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
  
            sampler2 = GL.GenSampler();
            GL.SamplerParameter(sampler2, SamplerParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
            GL.SamplerParameter(sampler2, SamplerParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);

            GL.ClearColor(0.1f, 0.2f, 0.5f, 0.0f);
            GL.Enable(EnableCap.DepthTest);
        }
    
        public override void Render()
        {
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, state.Width / (float)state.Height, 0.01f, 1000.0f);
            Matrix4 view = camera.GetMatrix();

            GL.ActiveTexture(TextureUnit.Texture1);
            GL.BindTexture(TextureTarget.Texture2D, tex1);
            GL.BindSampler(1, sampler1);

            GL.ActiveTexture(TextureUnit.Texture2);
            GL.BindTexture(TextureTarget.Texture2D, tex2);
            GL.BindSampler(2, sampler2);
         
           
            shaderTex.UseProgram();
            Matrix4 modelPlane1 = plane1Frame.GetMatrix();
            MatrixPack mpPlane1 = new MatrixPack(modelPlane1, view, projection);
            shaderTex.SetUniforms(mpPlane1.ModelViewProj, TextureUnit.Texture1);
            plane1.Draw();

            Matrix4 modelPlane2 = plane2Frame.GetMatrix();
            MatrixPack mpPlane2 = new MatrixPack(modelPlane2, view, projection);
            shaderTex.SetUniforms(mpPlane2.ModelViewProj, TextureUnit.Texture2);
            plane2.Draw();
        }
    }
}


Variant 2.
One sampler Object.
Output weird: http://prntscr.com/5zje3g

Both textures has still internal nearest filtering.
Only one sampler is active now (for plane down).

Expected result:
Plane down is linear filtered due to sampler.
Plane up is nearest filtered due to texture internal parameters.

But as you see, both are linear filtered.


namespace Betrayal
{
    class BugPage : Page
    {
        private Frame camera = new Frame(FrameType.Camera);
        private Frame plane1Frame = new Frame(FrameType.Model);
        private Frame plane2Frame = new Frame(FrameType.Model);
        
        private Shader_Tex shaderTex;

        private TextureHandler textureHandler = new TextureHandler();
        private GeoObject plane1;
        private GeoObject plane2;
        
        private int tex1, tex2;
        private int sampler1;//, sampler2;
        
        public BugPage(PageConstructorArgs cargs)
            : base(cargs)
        { }

        public override void Init()
        {
            shaderTex = new Shader_Tex(store.ShaderDir);

            plane1 = new Plane(15, 15, 100);
            plane1.Construct();

            plane2 = new Plane(15, 15, 100);
            plane2.Construct();

            camera.Move(0, 0, 3);
            plane1Frame.Move(0, -2, 0);
            plane2Frame.Move(0, 2, 0);
            
            tex1 = textureHandler.LoadTexture(Path.Combine(store.TexDir, "schach.jpg"), TextureMinFilter.Nearest, TextureMagFilter.Nearest, TextureWrapMode.Repeat);
            tex2 = textureHandler.LoadTexture(Path.Combine(store.TexDir, "schach.jpg"), TextureMinFilter.Nearest, TextureMagFilter.Nearest, TextureWrapMode.Repeat);
        }

        
        public override void InitGL()
        {
            sampler1 = GL.GenSampler();
            
            GL.SamplerParameter(sampler1, SamplerParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.SamplerParameter(sampler1, SamplerParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);

            //sampler2 = GL.GenSampler();
            //GL.SamplerParameter(sampler2, SamplerParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
            //GL.SamplerParameter(sampler2, SamplerParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);

            GL.ClearColor(0.1f, 0.2f, 0.5f, 0.0f);
            GL.Enable(EnableCap.DepthTest);
        }
    
        public override void Render()
        {
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, state.Width / (float)state.Height, 0.01f, 1000.0f);
            Matrix4 view = camera.GetMatrix();

            GL.ActiveTexture(TextureUnit.Texture1);
            GL.BindTexture(TextureTarget.Texture2D, tex1);
            GL.BindSampler(1, sampler1);

            GL.ActiveTexture(TextureUnit.Texture2);
            GL.BindTexture(TextureTarget.Texture2D, tex2);
           // GL.BindSampler(2, sampler2);
         
           
            shaderTex.UseProgram();
            Matrix4 modelPlane1 = plane1Frame.GetMatrix();
            MatrixPack mpPlane1 = new MatrixPack(modelPlane1, view, projection);
            shaderTex.SetUniforms(mpPlane1.ModelViewProj, TextureUnit.Texture1);
            plane1.Draw();

            Matrix4 modelPlane2 = plane2Frame.GetMatrix();
            MatrixPack mpPlane2 = new MatrixPack(modelPlane2, view, projection);
            shaderTex.SetUniforms(mpPlane2.ModelViewProj, TextureUnit.Texture2);
            plane2.Draw();
        }
    }
}

Well, can someone confirm?

shaderTex.SetUniforms(mpPlane1.ModelViewProj, TextureUnit.Texture1);

I find this confusing. If this is doing some kind of glUniform1i to set the texture unit for the shader, passing TextureUnit.Texture1 is not the correct answer. That’s an enumerator; you don’t pass enumerators to GLSL samplers. You pass the unit index (in this case, 1).

It’s possible that this SetUniforms function is doing the conversion internally. But we’re not privy to that code.

It’s just a little helper function


public void SetUniforms(Matrix4 mvpMatrix, TextureUnit colorMap)
{
	GL.UniformMatrix4(loc[0], false, ref mvpMatrix);
	GL.Uniform1(loc[1], TextureUnitToValue(colorMap));
}


//somewhere in the base class
protected int TextureUnitToValue(TextureUnit unit)
{
	if (unit == TextureUnit.Texture0)
		return 0;
	else if (unit == TextureUnit.Texture1)
		return 1;
	else if (unit == TextureUnit.Texture2)
		return 2;
	else if (unit == TextureUnit.Texture3)
		return 3;

	throw new NotImplementedException();
}

I just tested the second variant on a NVidia card: http://prntscr.com/60h0pt

It works as expected and thus i’m sure now, there is a bug in the AMD driver.
It’s pretty weird this is not a well known and already solved issue.

It’s pretty weird this is not a well known and already solved issue.

Not at all. AMD claimed OpenGL 3.3 support despite Sampler Objects being almost completely broken [i]for 6 months[/i].

Also, what happens if you actually use the first texture unit? OpenGL, like the vast majority of the programming world, uses zero-based indices. So “texture1” is the second texture unit. And the above mentioned bug seemed to suggest that AMD’s drivers have some special code built around texture unit 0.

Yes i know that texture1 is the second unit. I just tested around, different units. Of course also tested Unit 0, same problem.