From 1c4c6d3dca26f50f253a4f71aaf490e5eb613bdc Mon Sep 17 00:00:00 2001 From: Doyle Thai Date: Tue, 4 Apr 2017 14:16:22 +1000 Subject: [PATCH] Add rendering to screen using win32 blits --- dchip-8.vcxproj | 1 + src/build.bat | 2 +- src/dchip-8.cpp | 123 ++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 111 insertions(+), 15 deletions(-) diff --git a/dchip-8.vcxproj b/dchip-8.vcxproj index 63e7779..464bf8a 100644 --- a/dchip-8.vcxproj +++ b/dchip-8.vcxproj @@ -83,6 +83,7 @@ Windows + msimg32.lib;%(AdditionalDependencies) diff --git a/src/build.bat b/src/build.bat index 41ceab6..d22a7c2 100644 --- a/src/build.bat +++ b/src/build.bat @@ -41,7 +41,7 @@ REM Include directories set IncludeFlags= REM Link libraries -set LinkLibraries=user32.lib gdi32.lib +set LinkLibraries=user32.lib gdi32.lib msimg32.lib REM incrmenetal:no, turn incremental builds off REM opt:ref, try to remove functions from libs that are referenced at all diff --git a/src/dchip-8.cpp b/src/dchip-8.cpp index 95406b5..d85ac8d 100644 --- a/src/dchip-8.cpp +++ b/src/dchip-8.cpp @@ -9,6 +9,15 @@ #include #include +#include + +FILE_SCOPE HBITMAP bitmap; +FILE_SCOPE BITMAPINFO bitmapInfo; + +const i32 WIDTH = 64; +const i32 HEIGHT = 32; +const i32 RESOLUTION = WIDTH * HEIGHT; +FILE_SCOPE void *bitmapMemory; FILE_SCOPE bool globalRunning = false; @@ -52,13 +61,14 @@ inline FILE_SCOPE LARGE_INTEGER getWallClock() return result; } +#define ErrorBox(text, title) MessageBox(nullptr, text, title, MB_OK); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASSEX wc = { sizeof(WNDCLASSEX), - CS_HREDRAW | CS_VREDRAW, + CS_HREDRAW | CS_VREDRAW | CS_OWNDC, mainWindowProcCallback, 0, // int cbClsExtra 0, // int cbWndExtra @@ -72,34 +82,34 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, }; if (!RegisterClassEx(&wc)) { - // TODO(doyle): Logging, couldn't register class - ASSERT(INVALID_CODE_PATH); + ErrorBox(L"RegisterClassEx() failed.", nullptr); return -1; } + globalRunning = true; + // NOTE: Regarding Window Sizes // If you specify a window size, e.g. 800x600, Windows regards the 800x600 // region to be inclusive of the toolbars and side borders. So in actuality, // when you blit to the screen blackness, the area that is being blitted to // is slightly smaller than 800x600. Windows provides a function to help // calculate the size you'd need by accounting for the window style. - RECT r = {}; - r.right = 450; - r.bottom = 200; + RECT rect = {}; + rect.right = 128; + rect.bottom = 64; - DWORD windowStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE; - AdjustWindowRect(&r, windowStyle, FALSE); + DWORD windowStyle = + WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; + AdjustWindowRect(&rect, windowStyle, FALSE); - globalRunning = true; HWND mainWindow = CreateWindowEx( WS_EX_COMPOSITED, wc.lpszClassName, L"dchip-8", windowStyle, - CW_USEDEFAULT, CW_USEDEFAULT, r.right - r.left, r.bottom - r.top, - nullptr, nullptr, hInstance, nullptr); + CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, + rect.bottom - rect.top, nullptr, nullptr, hInstance, nullptr); if (!mainWindow) { - // TODO(doyle): Logging, couldn't create root window - ASSERT(INVALID_CODE_PATH); + ErrorBox(L"CreateWindowEx() failed.", nullptr); return -1; } @@ -109,6 +119,66 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, f32 targetSecondsPerFrame = 1 / targetFramesPerSecond; f32 frameTimeInS = 0.0f; + { + BITMAPINFOHEADER header = {}; + header.biSize = sizeof(BITMAPINFOHEADER); + header.biWidth = WIDTH; + header.biHeight = HEIGHT; + header.biPlanes = 1; + header.biBitCount = 32; + header.biCompression = BI_RGB; // uncompressed bitmap + header.biSizeImage = 0; + header.biXPelsPerMeter = 0; + header.biYPelsPerMeter = 0; + header.biClrUsed = 0; + header.biClrImportant = 0; + + bitmapInfo.bmiHeader = header; + + const i32 numPixels = header.biWidth * header.biHeight; + const i32 bytesPerPixel = header.biBitCount / 8; +#if 0 + bitmapMemory = calloc(1, numPixels * bytesPerPixel); + + if (!bitmapMemory) + { + ErrorBox(L"malloc() failed.", nullptr); + return -1; + } + +#else + HDC deviceContext = GetDC(mainWindow); + bitmap = CreateDIBSection(deviceContext, &bitmapInfo, DIB_RGB_COLORS, + &bitmapMemory, NULL, NULL); + ReleaseDC(mainWindow, deviceContext); + + if (!bitmapMemory) + { + ErrorBox(L"CreateDIBSection() failed.", nullptr); + return -1; + } +#endif + + u32 *bitmapBuffer = (u32 *)bitmapMemory; + for (i32 i = 0; i < numPixels; i++) + { + // NOTE: Win32 AlphaBlend requires the RGB components to be + // premultiplied with alpha. + f32 normA = 1.0f; + f32 normR = (normA * 0.0f); + f32 normG = (normA * 0.0f); + f32 normB = (normA * 1.0f); + + u8 r = (u8)(normR * 255.0f); + u8 g = (u8)(normG * 255.0f); + u8 b = (u8)(normB * 255.0f); + u8 a = (u8)(normA * 255.0f); + + u32 color = (a << 24) | (r << 16) | (g << 8) | (b << 0); + bitmapBuffer[i] = color; + } + } + MSG msg; while (globalRunning) { @@ -119,10 +189,35 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, DispatchMessage(&msg); } + RECT clientRect = {}; + GetClientRect(mainWindow, &clientRect); + + LONG clientWidth = clientRect.right - clientRect.left; + LONG clientHeight = clientRect.bottom - clientRect.top; + + BLENDFUNCTION blend = {}; + blend.BlendOp = AC_SRC_OVER; + blend.SourceConstantAlpha = 255; + blend.AlphaFormat = AC_SRC_ALPHA; + + HDC deviceContext = GetDC(mainWindow); + HDC alphaBlendDC = CreateCompatibleDC(deviceContext); + SelectObject(alphaBlendDC, bitmap); + + AlphaBlend(deviceContext, 0, 0, WIDTH, HEIGHT, alphaBlendDC, 0, 0, + WIDTH, HEIGHT, blend); + StretchBlt(deviceContext, 0, 0, clientWidth, clientHeight, + deviceContext, 0, 0, WIDTH, HEIGHT, SRCCOPY); + + DeleteDC(alphaBlendDC); + ReleaseDC(mainWindow, deviceContext); + + //////////////////////////////////////////////////////////////////////// + // Frame Limiting + //////////////////////////////////////////////////////////////////////// LARGE_INTEGER endWorkTime = getWallClock(); f32 workTimeInS = getTimeFromQueryPerfCounter(startFrameTime, endWorkTime); - if (workTimeInS < targetSecondsPerFrame) { DWORD remainingTimeInMs =