Its a bit involved to explain but bear with me.
The setup is as follows:
- single texture bound to 2 different texture units (say 0 and 1)
- 2 sampler objects, one for each tex unit and bound to them.
- the sampler objects have different sampling states, say 0 has mag filter LINEAR and 1 has mag filter NEAREST.
- a fragment shader uses both samplers to fetch the (same) texture.
Then the fetched data from the 2 samplers use the sampling states from only one of the sampler objects - both are either LINEAR or NEAREST. I’m not sure how the choice is made, maybe the sampler object which was set latest is used.
here is simple win32-based code to reproduce the bug
to display the result, the fragment shader outputs sample0 to the red channel and sample1 to the green channel - one of them should be filtered and the other not, but they are both the same.
#include <windows.h>
#include <GL/gl.h>
#include "glext.h"
#define OGLENTRY __stdcall
void (OGLENTRY *_glEnableVertexAttribArray) (GLuint index);
void (OGLENTRY *_glVertexAttribPointer) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
void (OGLENTRY *_glBufferData) (GLenum target, GLsizeiptr size, const void *data, GLenum usage);
void (OGLENTRY *_glBindBufferBase) (GLenum target, GLuint index, GLuint buffer);
void (OGLENTRY *_glGenBuffers) (GLsizei n, GLuint *buffers);
void (OGLENTRY *_glSamplerParameteri) (GLuint sampler, GLenum pname, GLint param);
void (OGLENTRY *_glBindSampler) (GLuint unit, GLuint sampler);
GLuint (OGLENTRY *_glCreateShader) (GLenum type);
void (OGLENTRY *_glShaderSource) (GLuint shader, GLsizei count, const GLchar* *string, const GLint *length);
void (OGLENTRY *_glCompileShader) (GLuint shader);
void (OGLENTRY *_glGetShaderiv) (GLuint shader, GLenum pname, GLint *params);
GLuint (OGLENTRY *_glCreateProgram) (void);
void (OGLENTRY *_glAttachShader) (GLuint program, GLuint shader);
void (OGLENTRY *_glLinkProgram) (GLuint program);
void (OGLENTRY *_glGetProgramiv) (GLuint program, GLenum pname, GLint *params);
void (OGLENTRY *_glUseProgram) (GLuint program);
GLint (OGLENTRY *_glGetUniformLocation) (GLuint program, const GLchar *name);
void (OGLENTRY *_glUniform1i) (GLint location, GLint v0);
void (OGLENTRY *_glActiveTexture) (GLenum texture);
void (OGLENTRY *_glGenSamplers) (GLsizei count, GLuint *samplers);
void (OGLENTRY *_glBindBuffer) (GLenum target, GLuint buffer);
void (OGLENTRY *_glGetShaderInfoLog) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
void (OGLENTRY *_glGetProgramInfoLog) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
void init()
{
static const char vs_text[] =
"#version 330
"
"layout(location=0) in vec2 a2v_pos;
"
"layout(location=1) in vec2 a2v_tc;
"
"out vec2 v2f_tc;
"
"void main() {
"
" v2f_tc = a2v_tc;
"
" gl_Position = vec4(a2v_pos, 0, 1);
"
"}";
static const char fs_text[] =
"#version 330
"
"uniform sampler2D s0, s1;
"
"in vec2 v2f_tc;
"
"layout(location=0) out vec4 c0;
"
"void main() {
"
" vec4 f0 = texture(s0, v2f_tc);
"
" vec4 f1 = texture(s1, v2f_tc);
"
" c0 = vec4(f0.x, f1.x, 0, 0);
"
"}";
static const short image[] = {
0, -1, 0, -1,
-1, 0, -1, 0,
0, -1, 0, -1,
-1, 0, -1, 0,
};
static const struct { float x, y, s, t; } verts[4] = {
-1, -1, 0, 0,
+1, -1, 1, 0,
-1, +1, 0, 1,
+1, +1, 1, 1,
};
GLenum status;
GLint len, i;
GLuint vs, fs, prog, tex, sam[2], vb;
const char *txt;
#define GET(f) *(void **)&_ ## f = wglGetProcAddress( # f )
GET(glEnableVertexAttribArray);
GET(glVertexAttribPointer);
GET(glBufferData);
GET(glBindBufferBase);
GET(glGenBuffers);
GET(glSamplerParameteri);
GET(glBindSampler);
GET(glCreateShader);
GET(glShaderSource);
GET(glCompileShader);
GET(glGetShaderiv);
GET(glCreateProgram);
GET(glAttachShader);
GET(glLinkProgram);
GET(glGetProgramiv);
GET(glUseProgram);
GET(glGetUniformLocation);
GET(glUniform1i);
GET(glActiveTexture);
GET(glGenSamplers);
GET(glBindBuffer);
GET(glGetShaderInfoLog);
GET(glGetProgramInfoLog);
vs = _glCreateShader(GL_VERTEX_SHADER);
txt = vs_text;
len = sizeof(vs_text);
_glShaderSource(vs, 1, &txt, &len);
_glCompileShader(vs);
_glGetShaderiv(vs, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) _asm int 3
fs = _glCreateShader(GL_FRAGMENT_SHADER);
txt = fs_text;
len = sizeof(fs_text);
_glShaderSource(fs, 1, &txt, &len);
_glCompileShader(fs);
_glGetShaderiv(fs, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
char log[65536];
GLint log_size;
_glGetShaderInfoLog(fs, sizeof(log), &log_size, log);
_asm int 3
}
prog = _glCreateProgram();
_glAttachShader(prog, vs);
_glAttachShader(prog, fs);
_glLinkProgram(prog);
_glGetProgramiv(prog, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
char log[65536];
GLint log_size;
_glGetProgramInfoLog(prog, sizeof(log), &log_size, log);
_asm int 3
}
_glUseProgram(prog);
i = _glGetUniformLocation(prog, "s0");
if (i < 0) _asm int 3
_glUniform1i(i, 0);
i = _glGetUniformLocation(prog, "s1");
if (i < 0) _asm int 3
_glUniform1i(i, 1);
// create texture and bind to sampler 0
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5, 4, 4, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, image);
// bind the texture to sampler 1 too
_glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, tex);
_glGenSamplers(2, sam);
_glBindSampler(0, sam[0]);
_glBindSampler(1, sam[1]);
_glSamplerParameteri(sam[0], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
_glSamplerParameteri(sam[1], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
_glGenBuffers(1, &vb);
_glBindBuffer(GL_ARRAY_BUFFER, vb);
_glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
_glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(verts[0]), NULL);
_glEnableVertexAttribArray(0);
_glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(verts[0]), (char *)NULL + 2*sizeof(float));
_glEnableVertexAttribArray(1);
if (glGetError()) _asm int 3
}
int run;
LRESULT CALLBACK wnd_proc(HWND wnd, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg) {
case WM_PAINT: ValidateRect(wnd, NULL); return 0;
case WM_CLOSE: run = 0; return 0;
default: return DefWindowProcA(wnd, msg, wp, lp);
}
}
int CALLBACK WinMain(HINSTANCE inst, HINSTANCE prev_inst, char *cl, int cs)
{
WNDCLASSA wc;
HWND wnd;
HDC dc;
PIXELFORMATDESCRIPTOR pfd;
int pf;
HGLRC rc;
memset(&wc, 0, sizeof(wc));
wc.hInstance = inst;
wc.lpfnWndProc = wnd_proc;
wc.lpszClassName = "_test_class_name";
RegisterClassA(&wc);
wnd = CreateWindowExA(0, wc.lpszClassName, NULL, WS_SYSMENU, 100, 100, 256, 196, NULL, NULL, NULL, NULL);
ShowWindow(wnd, SW_SHOW);
dc = GetDC(wnd);
memset(&pfd, 0, sizeof(pfd));
pfd.nSize = sizeof(pdf);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DEPTH_DONTCARE;
pf = ChoosePixelFormat(dc, &pfd);
SetPixelFormat(dc, pf, NULL);
rc = wglCreateContext(dc);
wglMakeCurrent(dc, rc);
init();
run = 1;
while (run) {
MSG msg;
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
DispatchMessageA(&msg);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glFinish();
Sleep(100);
}
return 0;
}
by the way this is not a regression for 11.12, it is present in the older drivers too
I imagine this is some kind of optimization from the pre-sampler-object time which collapses samplers when they have the same texture bound and which was not updated to take into account the new sampler objects.