Skip to main content
gamedev.net
🔒 Locked

Alpha blending

Started by clashie 5 replies 1.3k views
Original Post
clashie
clashie
I've been looking at some MMX routines, but I still don't fully understand them. How do they operate on the entire pixel at once? I'm using a DIBSection to do all my drawing, so all the drawing is by setting the bits... but apparently I don't understand something.
	bi.bmiHeader.biSize     = sizeof(BITMAPINFO);
	bi.bmiHeader.biWidth    = SCENE_WIDTH;
	bi.bmiHeader.biHeight   = -SCENE_HEIGHT; //top down DIB
	bi.bmiHeader.biPlanes   = 1;
	bi.bmiHeader.biBitCount = 32;

	hdc	   = GetDC(hwnd);
	hdcMem = CreateCompatibleDC(hdc);
	hDib   = CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, (LPVOID*)&lpBits, 0, 0);
	SelectObject(hdcMem, (HGDIOBJ)hDib);
Setup is typical enough... Here's what I was doing... lpBits was a BYTE* and drawing like this is flawless. (which is basically from the MMX article)

	BYTE* dst = lpBits + (y * SCENE_WIDTH + x) << 2;

	dst[0] = a * (b - dst[0]) / 255 + dst[0];
	dst[1] = a * (g - dst[1]) / 255 + dst[1];
	dst[2] = a * (r - dst[2]) / 255 + dst[2];
I figured I could just do something like this though... color is a colorref / dword.

	DWORD* dst = lpBits + (y * SCENE_WIDTH + x);

	*dst = a * (color - *dst) / 255 + *dst;
I've ran the math, and it seems to give the right colors / results.
#include <iostream>
#include <iomanip>
using namespace std;


unsigned int FakeBlend(char a, unsigned int color, unsigned int dest){
	return (a * (color - dest) / 255 + dest);
}

int main()
{
	unsigned int alpha;

	for(int i = 0; i < 255; i++){
		alpha = FakeBlend(i, 0x00FFFF00, 0x00FF0000);
		cout << hex << alpha << endl;
	}
	return 0;
}
Except past 128 something funky seems to go on. 00 FF 7F 00... @ 128 01 FF 81 01... @ 129 I'm pretty sure that this 01 is what is my undoing. 0x00FFFF00 - 0x01FF8101 = -0x00FF8201 or CLEARLY WRONG. Any subtraction against 0x00 blows up the entire routine. For example: yellow 0x00FFFF00 blending against a white backround 0x00FFFFFF will end in disaster. Anyone have any idea for a solution to this, or a better way to go about this?
Nik02
Nik02
You need to separate the color channels from the unsigned ints in order to perform the operation correctly. You cannot expect that the compiler understands your ints as vectors of color components (which is your goal).

The original code works because it works on byte per byte basis (which happens to be exactly the size of one color channel), not pixel per pixel.

In an X8R8G8B8 pixel (32-bit color), each color channel has 8 bits and there is an extra channel for memory padding. It is easy to separate the channels from an int by using simple bit shifting and AND masking. Likewise, shifts and ORs are all you need to reconstruct the int from the colors.

As a small side note, "alpha" usually means the interpolant parameter, not the interpolation result. While this is not a fatal error, it can be confusing later on.
Niko Suni
clashie
clashie
Hmm, well I guess MMX is the way to go for this.

Guess it's time to look it over.

[Edited by - clashie on April 4, 2009 4:12:07 AM]
Nik02
Nik02
Do note that there's nothing MMX-specific in the original code per se - it would run just fine without compiling it specifically to MMX instructions. It is a good candidate for the compiler to implement as MMX-optimized assembly, though, and a smart compiler like that included with VC++ would very likely do so when optimizations are enabled.
Niko Suni
clashie
clashie
I sat down for a few hours and did an assload of thinking / reading.


This seems to spit out reasonable results, though I haven't actually tested it. I imagine that it should be fairly fast... it's about as minimal as you can get I think.
#include <iostream>#include <iomanip>using namespace std;unsigned int FakeBlend(unsigned int alpha, unsigned int src, unsigned int dst){	//return (a * (color - dest) / 255 + dest);	__asm{		//clear register		pxor mm3, mm3		//get everything ready		movd mm0, dword ptr [dst]		movd mm1, dword ptr [src]		movd mm2, dword ptr [alpha]		punpcklbw mm0, mm3 //unpack dst to words		punpcklbw mm1, mm3 //unpack src		punpcklbw mm2, mm2 //unpack alpha		punpcklbw mm2, mm2		punpcklbw mm2, mm3		//(src - dest)		psubusb	mm1, mm0		//alpha * (src - dest)		pmullw	mm1, mm2		psrlw	mm1, 8		paddusw	mm1, mm0		packuswb mm1, mm3		movd dst, mm1	}	return dst;}int main(){	unsigned int alpha;	for(int i = 0; i < 255; i++){		alpha = FakeBlend(i, 0x0022FF22, 0x0011DD11); //just example numbers so i could read through in the debugger easier.		cout << hex << alpha << endl;	}	return 0;}
clashie
clashie
Hi again, I've been doing some reading.

http://www.tommesani.com/SSEPrimer.html
http://avisynth.org/mediawiki/Filter_SDK/Simple_MMX_optimization

I noticed that the SSE instruction pshufw is perfect for the alpha setup.

pshufw mm2, mm2, 0


can just be done instead of the 3 unpacks for alpha.
XTAL256
XTAL256
Why are you doing all this "assload of thinking" when you can just use an image format that supports alpha channels with a graphics API that supports alpha blending (i.e. OpenGL)?
It's good that you are learning a lot from this, but, like a lot of obscure questions asked here, the solution already exists in some form. Don't reinvent the wheel unless it's absolutely necessary. On a similar note, i actually do need to do manual alpha blending in my game because of the way i load my map tile images and create the map. I haven't actually implemented the blending yet and i was looking for easier ways to do it but sometimes it is just easiest to DIY.
[Window Detective] - Windows UI spy utility for programmers

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.