将原始像素数组绘制到 window (C++ WinAPI)

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

我有两个缓冲区,大小和类型都相同 (uint32_t*)。 这些缓冲区应该代表 rendering/drawing 系统的前缓冲区和后缓冲区。它们存储 32 位像素数据。

我使用声明和初始化这些缓冲区;

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);
        ...
    }

我还得到输出,window 和 Initialize() 方法中控制台的设备句柄,使用:

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

然后我有一些绘图方法,比如SetPixel方法:

    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;
    }

最后,我有一个 GSwap 方法。此方法应该交换缓冲区的指针,清除新的后台缓冲区并将前台缓冲区复制到屏幕。

前两个工作(我认为),但我不知道如何实现第三个(复制到屏幕)。我宁愿不使用任何外部库。

GSwap 方法的代码:

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

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

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

完整代码:Pastebin

您不能通过将字节插入某个缓冲区来绘制任意 window。 Win32 比那个更高级别 API。

但是,可以通过写入缓冲区将 绘制到位图 中。您创建一个位图,这样 Windows returns 一个指向其内容的指针,当您使用 CreateDIBSection(...) 创建它时。然后,您可以通过 BitBlt 和适当的设备上下文等将该位图绘制到 Window

下面是一个最小的例子。 (我保留了你对后台缓冲区和前台缓冲区的使用,尽管这里并不是真的有必要。window 本身本质上是一个前台缓冲区,所以你只需要一个后台缓冲区的“交换链”没有闪烁。)

#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>(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;
}