Search code examples
c++winapigraphicsbufferdrawing

Draw raw array of pixels onto a window (C++ WinAPI)


I have two buffers, both of the same size and type (uint32_t*). These buffers are supposed to represent the front and back buffers of a rendering/drawing system. They store 32-bit pixel data.

I declare and initialize these buffers using;

private:
    ...
    uint32_t* frontBuf;
    uint32_t* backBuf;
    size_t    gbufSize;
    ...
public:
    void Initialize() {
        ...  
        // prepare for rendering
        gbufSize = sizeof(uint32_t) * w * h;
        backBuf  = (uint32_t*)malloc(gbufSize);
        frontBuf = (uint32_t*)malloc(gbufSize);
        ...
    }

I also get the output, window and device handles for the console in the Initialize() method using:

        // get handles
        cwd = GetDC(GetConsoleWindow());
        chd = GetStdHandle(STD_OUTPUT_HANDLE);
        cwn = GetConsoleWindow();

I then have a few drawing methods, like a SetPixel method:

    size_t GFlatten(int x, int y) {
        return y * h + x;
    }

    void GSetPixel(int x, int y, uint32_t color) {
        backBuf[GFlatten(x, y)] = color;
    }

And finally, I have a GSwap method. This method is supposed to swap the pointers of the buffers, clear the new back buffer and copy the front buffer to the screen.

The first two work (I think), but I have no idea how to implement the 3rd one (copying to the screen). I would prefer to not use any external libraries.

Code for GSwap method:

    void GSwap() {
        // swap pointers
        uint32_t* frontTmp = frontBuf;
        frontBuf = backBuf;
        backBuf = frontTmp;

        // clear new back buffer
        memset(backBuf, 0, gbufSize);

        // draw on screen
        /* ??? */
    }

The full code: Pastebin


Solution

  • You can't draw to an arbitrary window by poking bytes into some buffer. Win32 is a higher level API than that.

    It is possible to draw into a bitmap by writing into a buffer, however. You create a bitmap such that Windows returns a pointer to its contents when you create it with CreateDIBSection(...). You can then paint that bitmap to a Window via BitBlt and appropriate device contexts, etc.

    Below is a minimal example. (I retained your usage of a back buffer and front buffer even though it isn't really necessary here. The window itself is essentially a front buffer so you would only really need "a swap chain" of one back buffer to not have flickering.)

    #include <windows.h>
    #include <stdint.h>
    #include <utility>
    #include <algorithm>
    
    constexpr int kTimerID = 101;
    
    LRESULT CALLBACK wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
    
    struct graphics_buffer {
        HBITMAP hbm;
        uint32_t* data;
    };
    
    graphics_buffer create_graphics_buffer(int wd, int hgt)
    {
        HDC hdcScreen = GetDC(NULL);
    
        BITMAPINFO bmi = {};
        bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        bmi.bmiHeader.biWidth = wd;
        bmi.bmiHeader.biHeight = -hgt; // top-down
        bmi.bmiHeader.biPlanes = 1;
        bmi.bmiHeader.biBitCount = 32;
        bmi.bmiHeader.biCompression = BI_RGB;
    
        graphics_buffer gb;
        gb.hbm = CreateDIBSection(hdcScreen, &bmi, DIB_RGB_COLORS, reinterpret_cast<void**>(&gb.data), NULL, NULL);
    
        ReleaseDC(NULL, hdcScreen);
        return gb;
    }
    
    class graphic_buffers {
        graphics_buffer front_;
        graphics_buffer back_;
        int wd_; 
        int hgt_;
    
    public:
    
        graphic_buffers(int wd, int hgt) :
            wd_(wd),
            hgt_(hgt),
            front_(create_graphics_buffer(wd, hgt)),
            back_(create_graphics_buffer(wd, hgt))
        {
            clear();
        }
    
        HBITMAP front_bmp() {
            return front_.hbm;
        }
    
        void swap() {
            std::swap(front_, back_);
        }
    
        size_t size() const {
            return static_cast<size_t>(wd_ * hgt_);
        }
    
        int width() const {
            return wd_;
        }
    
        int height() const {
            return hgt_;
        }
    
        void clear() {
            std::fill(back_.data, back_.data + size(), 0);
        }
    
        void set_pixel(int x, int y, uint32_t pix) {
            back_.data[y * wd_ + x] = pix;
        }
    
        ~graphic_buffers() {
            DeleteObject(front_.hbm);
            DeleteObject(back_.hbm);
        }
    };
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
    
        MSG msg = { 0 };
        WNDCLASS wc = { 0 };
        wc.lpfnWndProc = wndproc;
        wc.hInstance = hInstance;
        wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
        wc.lpszClassName = L"swap_buffers_window";
        if (!RegisterClass(&wc))
            return 1;
    
        if (!CreateWindow(wc.lpszClassName,
            L"buffered window",
            WS_OVERLAPPEDWINDOW | WS_VISIBLE,
            0, 0, 640, 480, 0, 0, hInstance, NULL))
            return 2;
    
        while (GetMessage(&msg, NULL, 0, 0) > 0) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
        return 0;
    }
    
    void draw_something(graphic_buffers& buffs) {
        int wd = buffs.width();
        int hgt = buffs.height();
    
        static int x = 0;
        static int y = 0;
        static int x_vel = 4;
        static int y_vel = 7;
    
        if (x >= 0 && x < wd && y >= 0 && y < hgt) {
            buffs.set_pixel(x, y, 0xffffffff);
        }
    
        x += x_vel;
        y += y_vel;
        if (x < 0 || x > wd) {
            x_vel *= -1;
        }
        if (y < 0 || y > hgt) {
            y_vel *= -1;
        }
    }
    
    LRESULT CALLBACK wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    
        switch (message)
        {
        case WM_CREATE: {
                RECT r;
                GetClientRect(hWnd, &r);
                SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(new graphic_buffers(r.right - r.left, r.bottom - r.top)));
                SetTimer(hWnd, kTimerID, 1, NULL);
            }
            break;
        case WM_TIMER: {
                auto buffs = reinterpret_cast<graphic_buffers*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
                draw_something(*buffs);
                buffs->swap();
                buffs->clear();
                InvalidateRect(hWnd, NULL, FALSE);
            }
            break;
        case WM_PAINT: {
                auto buffs = reinterpret_cast<graphic_buffers*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
                PAINTSTRUCT ps;
                HDC hdc = BeginPaint(hWnd, &ps);
                HDC hdc_bmp = CreateCompatibleDC(hdc);
                auto old_bmp = SelectObject(hdc_bmp, buffs->front_bmp());
    
                BitBlt(hdc, 0, 0, buffs->width(), buffs->height(), hdc_bmp, 0, 0, SRCCOPY);
    
                SelectObject(hdc, old_bmp);
                DeleteDC(hdc_bmp);
                EndPaint(hWnd, &ps);
            }
            break;
    
        case WM_DESTROY: {
                auto buffs = reinterpret_cast<graphic_buffers*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
                delete buffs;
            }
            break;
    
        case WM_CLOSE:
            PostQuitMessage(0);
            break;
    
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;
    }