将原始像素数组绘制到 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;
}
我有两个缓冲区,大小和类型都相同 (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;
}