R200: since when is glDrawPixels supposed to filter?

I recently went back to my chroma-keying stuff and discovered that now my Radeon8500 applies a filter to glDrawPixels. Half a year ago it certainly didn’t do that and I fail to see where the spec allows it.
Puzzles me why, and it also gives me some new headaches. Now I just could start using EXT_texture_rectangle and use GL_NEAREST filtering. But ho hum.

Here’s a little glut app that will show you what I mean. And it will also show just why this bothers me:
I use the alpha test to conditionally discard pixels. This starts to break when the filter kicks in.

Also note that the luminance of the ‘texture’ fluctuates as a result of the filter (might not be visible depending on your gamma settings).

Use WSAD to move the thingy around, holding ALT will decrease movement speed below integer pixel positions, which makes it interesting.

//Make sure to link with glut

//#include <windows.h>
#include <gl/gl.h>
#include <gl/glut.h>
#include <gl/glati.h>
#include <stdio.h>
#include <malloc.h>

typedef unsigned char ubyte;
typedef unsigned short ushort;
typedef unsigned int uint;

char description[256];
bool init_done=false;

PFNGLWINDOWPOS2IARBPROC glWindowPos2iARB;
PFNGLWINDOWPOS2FARBPROC glWindowPos2fARB;

ubyte* pixels;

//raster position for drawing the pixel rectangle
float raster_x=282.0f;
float raster_y=208.0f;

const char* const
toggle_status_string(bool stat)
{
	if (stat)	return("on");
	else		return("off");
}

void
release_memory()
{
	if (pixels)
	{
		free(pixels);
		pixels=NULL;
	}
}

void
init_stuff()
{
	glWindowPos2iARB=(PFNGLWINDOWPOS2IARBPROC)wglGetProcAddress("glWindowPos2iARB");
	glWindowPos2fARB=(PFNGLWINDOWPOS2FARBPROC)wglGetProcAddress("glWindowPos2fARB");

	glClearColor(0.0f,0.0f,0.0f,0.0f);
//allocate small RGBA pixel rectangle
	pixels=(ubyte*)malloc(64*64*4);

	int x,y;
//fill completely with opaque checkerboard green/blue
	for (y=0;y<64;++y)
	{
		for (x=0;x<64;++x)
		{
			ubyte* p=pixels+4*(x+64*y);
			if (((x>>1)^(y>>1))&1)
			{
				//green tile
				p[0]=0;
				p[1]=255;
				p[2]=0;
			}
			else
			{
				//blue tile
				p[0]=0;
				p[1]=0;
				p[2]=255;
			}
			p[3]=255;		//full alpha on all tiles
		}
	}
//draw an opaque white outline along the outer edge
	for (y=0;y<64;++y)
	{
		for (x=0;x<64;++x)
		{
			if ( ((y==0)| |(y==63))
				| |((x==0)| |(x==63)) )
			{
				ubyte* p=pixels+4*(x+64*y);
				p[0]=255;	p[1]=255;	p[2]=255;	p[3]=255;
			}
		}
	}

//'cut a hole' with completely transparent pink (alpha=0)
	for (y=16;y<48;++y)
	{
		for (x=16;x<48;++x)
		{
			ubyte* p=pixels+4*(x+64*y);
			p[0]=255;
			p[1]=0;
			p[2]=255;
			p[3]=0;			//zero alpha
		}
	}

	atexit(release_memory);
}

void
display_callback()
{
	if (!init_done)
	{
		init_stuff();
		init_done=true;
	}

	glClear(GL_COLOR_BUFFER_BIT);

//enable alpha test, pass condition is a>0
	glAlphaFunc(GL_GREATER,0);
	glEnable(GL_ALPHA_TEST);

//set raster position
	glPixelZoom(1,1);
	glWindowPos2fARB(raster_x,raster_y);
//		glWindowPos2iARB(raster_x,raster_y);

//NOTE: no pink pixels should be visible
//		no filtering is expected

	glDrawPixels(64,64,GL_RGBA,GL_UNSIGNED_BYTE,pixels);

	glutSwapBuffers();
}

void
idle_callback()
{
	glutPostRedisplay();
}

void
keyboard_callback(unsigned char key,int x,int y)
{
	ubyte upcase=key&0xDF;

	float delta;
	int modifiers=glutGetModifiers();

	if (modifiers&GLUT_ACTIVE_SHIFT)	delta=16.0f;
	else
	{
		if (modifiers&GLUT_ACTIVE_ALT)		delta=0.125f;
		else	delta=1.0f;
	}

//wsad controls position of 'black hole'
	if (upcase=='A') raster_x-=delta;
	if (upcase=='D') raster_x+=delta;
	if (upcase=='W') raster_y+=delta;
	if (upcase=='S') raster_y-=delta;
//clamp hole position
	if (raster_x>576) raster_x=576;
	if (raster_x<0) raster_x=0;
	if (raster_y>416) raster_y=416;
	if (raster_y<0) raster_y=0;

	sprintf(description,"Raster pos=%.3f/%.3f - hold SHIFT or ALT for different speeds",raster_x,raster_y);

	if (upcase=='X') sprintf(description,"height is %d",glutGet(GLUT_WINDOW_HEIGHT));

	glutSetWindowTitle(description);

	glutPostRedisplay();
}

int
main(int argc,char** argv)
{
	sprintf(description,"Use WSAD to move around, SHIFT/ALT modify speed");

	glutInit(&argc,argv);
	glutInitWindowSize(640,480);
	glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_STENCIL);

	glutCreateWindow(description);

	glutDisplayFunc(display_callback);
	glutKeyboardFunc(keyboard_callback);
	glutIdleFunc(idle_callback);

	glutMainLoop();

	return(0);
}

edit: Whoops, wrong code snippet …
edit2: Whoops, had to rip my own mistakes out …

[This message has been edited by zeckensack (edited 02-08-2003).]

[This message has been edited by zeckensack (edited 02-08-2003).]

[This message has been edited by zeckensack (edited 02-08-2003).]

A little screenie: http://home.t-online.de/~zsack/dp_filter.png

Hi,

Do you have fullscreen antialiasing enabled? I can imagine it creating a kind of a filtered effect.

-Ilkka

Originally posted by JustHanging:
[b]Hi,

Do you have fullscreen antialiasing enabled? I can imagine it creating a kind of a filtered effect.

-Ilkka[/b]
Nope, that’s not it.
I actually double checked that, because some users seem to have the problem that the AA settings don’t get applied before the next reboot.

If I recall correctly glDrawPixels() converts the pixels into fragemts and then treats them as normal fragments as they would have been generated by the raster process, hence it applies all the current fragment operations (texture-filter, fog, …) to them.

Please correct me if I am wrong.

Originally posted by HS:
[b]If I recall correctly glDrawPixels() converts the pixels into fragemts and then treats them as normal fragments as they would have been generated by the raster process, hence it applies all the current fragment operations (texture-filter, fog, …) to them.

Please correct me if I am wrong.[/b]

Two part answer: right and wrong

Pixels drawn behave like fragments, that’s true. You can apply fancy combiners and fragment shaders, you can blend them, alpha test them etc.

But texture filters only apply to textures. You can texture map the fragments generated with glDrawPixels, but for all means and purposes they should behave like flat shaded little rectangles. These won’t get filtered themselves, even if you apply a filtered texture to them.

I hope that made sense cough.

Note that this is not a stretched blit, I have explicitly set glPixelZoom(1,1). I’ve just noticed (by accident more or less), that my Radeon applies a bilinear filter to stretched pixels (ie: glPixelZoom(4,4)).
Now that does make some sense, but I think it’s a spec violation too. I must admit though that I’m not certain atm.

But I’m quite sure that filtering is not allowed for PixelZoom(1,1).

Originally posted by HS:
[b]If I recall correctly glDrawPixels() converts the pixels into fragemts and then treats them as normal fragments as they would have been generated by the raster process, hence it applies all the current fragment operations (texture-filter, fog, …) to them.

Please correct me if I am wrong.[/b]

glDrawPixels has nothing to do with texturing, but you can texture it. In this case, he isn’t.

If the card is cheating by actually rendering a textured quad … I dont know

This definitely shouldn’t be happening unless something like forced FSAA is on. I would ask that you mail the description above and glut sample to devrel@ati.com so that the bug is properly logged and tracked. (and you get notification of when it has been fixed)

-Evan

Originally posted by ehart:
[b]This definitely shouldn’t be happening unless something like forced FSAA is on. I would ask that you mail the description above and glut sample to devrel@ati.com so that the bug is properly logged and tracked. (and you get notification of when it has been fixed)

-Evan[/b]

I just did that. Thanks.

I thought I replied to this on Saturday, but I guess my post didn’t make it up here.

This is definately a driver bug seen on R200 only and has already been fixed. The fix should be released in the near future.

[This message has been edited by DanO (edited 02-10-2003).]