Skip to content

Instantly share code, notes, and snippets.

@bit-hack
Last active July 25, 2017 20:53
Show Gist options
  • Save bit-hack/0ce311db7e940d29925cb212da27872b to your computer and use it in GitHub Desktop.
Save bit-hack/0ce311db7e940d29925cb212da27872b to your computer and use it in GitHub Desktop.
Direct Window Pixel Blitting
#include <cassert>
#include <string>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
typedef unsigned int uint;
struct GDIDetail {
struct LockInfo {
uint32_t* _pixels;
uint32_t _width, _pitch;
uint32_t _height;
};
GDIDetail()
: _window(NULL)
, _dwExStyle(WS_EX_APPWINDOW | WS_EX_OVERLAPPEDWINDOW)
, _dwStyle(WS_CAPTION | WS_OVERLAPPED)
{
ZeroMemory(&_screen, sizeof(_screen));
}
bool windowCreate(uint w, uint h);
bool windowResize(uint w, uint h);
bool screenInvalidate();
bool screenLock(LockInfo* out);
HWND windowHandle() const
{
return _window;
}
protected:
static LRESULT CALLBACK windowEventHandler(HWND, UINT, WPARAM, LPARAM);
// repaint the current window (WM_PAINT)
LRESULT _windowRedraw();
// create a back buffer of a specific size
bool _screenCreate(uint w, uint h);
// window handle
HWND _window;
// window styles used by resizing code
const DWORD _dwStyle, _dwExStyle;
// screen buffer info
struct {
BITMAPINFO _bmp;
uint32_t* _data;
} _screen;
};
bool GDIDetail::_screenCreate(uint w, uint h)
{
assert(w && h);
// create screen data
if (_screen._data) {
delete[] _screen._data;
_screen._data = NULL;
}
_screen._data = new uint32_t[w * h];
assert(_screen._data);
// create bitmap info
ZeroMemory(&_screen._bmp, sizeof(BITMAPINFO));
BITMAPINFOHEADER& b = _screen._bmp.bmiHeader;
b.biSize = sizeof(BITMAPINFOHEADER);
b.biBitCount = 32;
b.biWidth = w;
b.biHeight = h;
b.biPlanes = 1;
b.biCompression = BI_RGB;
// success
return true;
}
LRESULT GDIDetail::_windowRedraw()
{
if (!_screen._data) {
// no screen; hand back to default handler
return DefWindowProcA(_window, WM_PAINT, 0, 0);
}
// flip blitted image
static const int xFlip = false ? -1 : 1;
static const int yFlip = false ? -1 : 1;
// blit buffer to screen
PAINTSTRUCT ps;
HDC hdc = BeginPaint(_window, &ps);
const BITMAPINFOHEADER& bih = _screen._bmp.bmiHeader;
// do the bit blit
StretchDIBits(
hdc,
0,
0,
bih.biWidth * xFlip,
bih.biHeight * yFlip,
0,
0,
bih.biWidth,
bih.biHeight,
_screen._data,
&(_screen._bmp),
DIB_RGB_COLORS,
SRCCOPY);
// finished WM_PAINT
EndPaint(_window, &ps);
return 0;
}
LRESULT CALLBACK GDIDetail::windowEventHandler(HWND hWnd, UINT Msg,
WPARAM wParam, LPARAM lParam)
{
GDIDetail* self;
switch (Msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT: {
// dispatch redraw to window instance
if (self = (GDIDetail*)GetWindowLongPtrA(hWnd, GWLP_USERDATA)) {
return self->_windowRedraw();
}
// fallthrough
}
default:
printf("- %x\n", Msg);
return DefWindowProcA(hWnd, Msg, wParam, lParam);
}
}
bool GDIDetail::windowCreate(uint w, uint h)
{
assert(_window == NULL && w && h);
static const char* kClassName = "ScummVMClass";
static const char* kWndName = "ScummVM";
// create window class
HINSTANCE hinstance = GetModuleHandle(NULL);
WNDCLASSEXA wndClassEx = {
sizeof(WNDCLASSEXA),
CS_OWNDC,
windowEventHandler,
0,
0,
hinstance,
NULL,
NULL,
NULL,
NULL,
(LPCSTR)kClassName,
NULL
};
if (RegisterClassExA(&wndClassEx) == 0) {
return false;
}
// create window
_window = CreateWindowExA(_dwExStyle, kClassName, (LPCSTR)kWndName, _dwStyle, CW_USEDEFAULT,
CW_USEDEFAULT, w, h, NULL, NULL, hinstance, NULL);
if (_window == NULL) {
return false;
}
// resize window and create the offscreen buffer
if (!windowResize(w, h)) {
return false;
}
// pass class instance to window user data to that the static window proc
// can dispatch to the GDIDetail instance.
SetLastError(0);
if (SetWindowLongPtrA(_window, GWLP_USERDATA, (LONG)this) == 0 && GetLastError()) {
// unable to set window user data
return false;
}
// force a screen redraw
screenInvalidate();
// display window
ShowWindow(_window, SW_SHOW);
return true;
}
bool GDIDetail::windowResize(uint w, uint h)
{
assert(w && h);
// get current window rect
RECT rect;
GetWindowRect(_window, &rect);
// adjust to expected client area
rect.right = rect.left + w;
rect.bottom = rect.top + h;
// adjust so rect becomes client area
if (AdjustWindowRectEx(&rect, _dwStyle, FALSE, _dwExStyle) == FALSE) {
return false;
}
// XXX: this will move the window by [5,5] pixels
MoveWindow(_window, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
// create a new screen buffer for out window size
if (!_screenCreate(w, h)) {
return false;
}
// force a screen redraw
screenInvalidate();
return true;
}
bool GDIDetail::screenLock(LockInfo* info)
{
assert(info);
info->_pixels = _screen._data;
info->_width = _screen._bmp.bmiHeader.biWidth;
info->_height = _screen._bmp.bmiHeader.biHeight;
info->_pitch = _screen._bmp.bmiHeader.biWidth;
return info->_pixels != NULL;
}
bool GDIDetail::screenInvalidate()
{
// invalidate entire window
return InvalidateRect(_window, NULL, FALSE) != 0;
}
namespace {
bool appEventPump()
{
MSG msg;
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) {
switch (msg.message) {
case WM_PAINT:
break;
case WM_QUIT:
return false;
default:
printf("+ %x\n", msg.message);
}
// translate virtual key code
TranslateMessage(&msg);
// dispatch to window proc
DispatchMessage(&msg);
}
Sleep(1);
return true;
}
uint32_t xorshift32()
{
static uint32_t x = 12345;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return x;
}
}
int main()
{
GDIDetail gdi;
if (!gdi.windowCreate(320, 240)) {
return false;
}
while (appEventPump()) {
GDIDetail::LockInfo lock;
if (gdi.screenLock(&lock)) {
for (uint i = 0; i < lock._width * lock._height; ++i) {
lock._pixels[i] = xorshift32();
}
}
gdi.screenInvalidate();
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment