RGFW 幕后:软件渲染





0/5 (0投票)
本教程解释了如何为 X11、WinAPI 和 Cocoa 设置和处理软件渲染。
引言
RGFW 是一个轻量级的单头文件窗口库,其源代码可以在 这里 找到。本教程基于其源代码。软件渲染的基本思想很简单。它归结为绘制到一个缓冲区,然后将其“blit”到屏幕上。然而,在处理低级 API 时,软件渲染会更复杂,因为你必须正确初始化一个渲染上下文,告诉 API 如何处理数据。然后,为了绘制,你必须使用 API 的函数将内容“blit”到屏幕上,这可能会很复杂。
本教程解释了 RGFW 如何处理软件渲染,以便你能理解如何自己实现它。
注意:MacOS 代码将以 Cocoa C Wrapper 为目标(请参阅 RGFW.h 或 Silicon.h)
概述
所需步骤的快速概述
- 初始化缓冲区和渲染上下文
- 绘制到缓冲区
- 将缓冲区“blit”到屏幕
- 释放残留数据
步骤 1(初始化缓冲区和渲染上下文)
注意:你可能希望缓冲区的大小大于窗口,这样你就可以在不重新分配内存的情况下缩放缓冲区的大小。
在 X11 上,你首先创建一个 Visual(或像素格式),它告诉窗口如何处理绘制数据。然后为缓冲区创建一个位图进行渲染,RGFW 使用 XImage 结构来表示位图。接下来,你使用显示和窗口数据创建一个图形上下文 (GC)。GC 用于告诉 X11 如何将绘制数据提供给窗口。
这也是你可以分配缓冲区的地方。除 Windows 外,必须为每个平台分配缓冲区。
为此,你需要使用 XMatchVisualInfo、XCreateImage 和 XCreateGC。
XVisualInfo vi;
vi.visual = DefaultVisual(display, DefaultScreen(display));
XMatchVisualInfo(display, DefaultScreen(display), 32, TrueColor, &vi);
XImage* bitmap = XCreateImage(
display, XDefaultVisual(display, vi.screen),
vi.depth,
ZPixmap, 0, NULL, RGFW_bufferSize.w, RGFW_bufferSize.h,
32, 0
);
/* ..... */
/* Now this visual can be used to create a window and colormap */
XSetWindowAttributes swa;
Colormap cmap;
swa.colormap = cmap = XCreateColormap((Display*) display, DefaultRootWindow(display), vi.visual, AllocNone);
swa.background_pixmap = None;
swa.border_pixel = 0;
swa.event_mask = event_mask;
swa.background_pixel = 0;
Window window = XCreateWindow((Display*) display, DefaultRootWindow((Display*) display), x, y, w, h,
0, vi.depth, InputOutput, vi.visual,
CWColormap | CWBorderPixel | CWBackPixel | CWEventMask, &swa);
/* .... */
GC gc = XCreateGC(display, window, 0, NULL);
u8* buffer = (u8*)malloc(RGFW_bufferSize.w * RGFW_bufferSize.h * 4);
在 Windows 上,你将从创建一个位图头开始,它用于创建一个具有指定格式的位图。格式结构用于告诉 Windows API 如何将缓冲区渲染到屏幕。
接下来,你创建一个在内存中分配的设备上下文句柄 (HDC),它用于稍后选择位图。
注意:Windows 不需要分配缓冲区,因为 Winapi 会为我们处理这部分内存。你也可以手动分配内存。
相关文档:BITMAPV5HEADER
、CreateDIBSection
和 CreateCompatibleDC
BITMAPV5HEADER bi;
ZeroMemory(&bi, sizeof(bi));
bi.bV5Size = sizeof(bi);
bi.bV5Width = RGFW_bufferSize.w;
bi.bV5Height = -((LONG) RGFW_bufferSize.h);
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_BITFIELDS;
// where it can expect to find the RGBA data
// (note: this might need to be changed according to the endianness)
bi.bV5BlueMask = 0x00ff0000;
bi.bV5GreenMask = 0x0000ff00;
bi.bV5RedMask = 0x000000ff;
bi.bV5AlphaMask = 0xff000000;
u8* buffer;
HBITMAP bitmap = CreateDIBSection(hdc,
(BITMAPINFO*) &bi,
DIB_RGB_COLORS,
(void**) &buffer,
NULL,
(DWORD) 0);
HDC hdcMem = CreateCompatibleDC(hdc);
在 MacOS 上,设置并不复杂,大部分工作在渲染过程中完成。
你只需要分配缓冲区数据。
u8* buffer = malloc(RGFW_bufferSize.w * RGFW_bufferSize.h * 4);
步骤 2(绘制到缓冲区)
在本教程中,我将使用 Silk.h 来绘制到缓冲区。Silk.h 是一个单头文件软件渲染图形库。
首先,包含 silk,
#define SILK_PIXELBUFFER_WIDTH w
#define SILK_PIXELBUFFER_HEIGHT h
#define SILK_IMPLEMENTATION
#include "silk.h"
现在你可以使用 silk 进行渲染了。
silkClearPixelBufferColor((pixel*)buffer, 0x11AA0033);
silkDrawCircle(
(pixel*)buffer,
(vec2i) { SILK_PIXELBUFFER_WIDTH, SILK_PIXELBUFFER_HEIGHT },
SILK_PIXELBUFFER_WIDTH,
(vec2i) { SILK_PIXELBUFFER_CENTER_X, SILK_PIXELBUFFER_CENTER_Y - 60},
60,
0xff0000ff
);
步骤 3(将缓冲区“blit”到屏幕)
在 X11 上,你首先将位图数据设置为缓冲区。位图数据将以 BGR 格式渲染,所以你必须
转换数据如果你想使用 RGB。然后你必须使用 `XPutImage` 使用 GC 将 XImage 绘制到窗口。
bitmap->data = (char*) buffer;
#ifndef RGFW_X11_DONT_CONVERT_BGR
u32 x, y;
for (y = 0; y < (u32)window_height; y++) {
for (x = 0; x < (u32)window_width; x++) {
u32 index = (y * 4 * area.w) + x * 4;
u8 red = bitmap->data[index];
bitmap->data[index] = buffer[index + 2];
bitmap->data[index + 2] = red;
}
}
#endif
XPutImage(display, (Window)window, gc, bitmap, 0, 0, 0, 0, RGFW_bufferSize.w, RGFW_bufferSize.h);
在 Windows 上,你必须先选择位图,并确保保存上一个选中的对象,以便稍后重新选中它。现在你可以将位图“blit”到屏幕上,并重新选中旧的位图。
相关文档:SelectObject 和 BitBlt
HGDIOBJ oldbmp = SelectObject(hdcMem, bitmap);
BitBlt(hdc, 0, 0, window_width, window_height, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, oldbmp);
在 MacOS 上,根据你的窗口设置视图的 CALayer,这用于将图像渲染到屏幕。接下来,使用缓冲区创建图像(位图)。最后,你可以将图像添加到图层的图形上下文中,然后绘制并刷新图层到屏幕。
相关文档:CGColorSpaceCreateDeviceRGB、CGBitmapContextCreate、CGBitmapContextCreateImage、CGColorSpaceRelease、CGContextRelease、CALayer、NSGraphicsContext、CGContextDrawImage、flushGraphics 和 CGImageRelease
CGImageRef createImageFromBytes(unsigned char *buffer, int width, int height) {
// Define color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Create bitmap context
CGContextRef context = CGBitmapContextCreate(
buffer,
width, height,
8,
RGFW_bufferSize.w * 4,
colorSpace,
kCGImageAlphaPremultipliedLast);
// Create image from bitmap context
CGImageRef image = CGBitmapContextCreateImage(context);
// Release the color space and context
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
return image;
}
...
void* view = NSWindow_contentView(window);
void* layer = objc_msgSend_id(view, sel_registerName("layer"));
((void(*)(id, SEL, NSRect))objc_msgSend)(layer,
sel_registerName("setFrame:"),
(NSRect){{0, 0}, {window_width, window_height}});
CGImageRef image = createImageFromBytes(buffer, window_width, window_height);
// Get the current graphics context
id graphicsContext = objc_msgSend_class(objc_getClass("NSGraphicsContext"), sel_registerName("currentContext"));
// Get the CGContext from the current NSGraphicsContext
id cgContext = objc_msgSend_id(graphicsContext, sel_registerName("graphicsPort"));
// Draw the image in the context
NSRect bounds = (NSRect){{0,0}, {window_width, window_height}};
CGContextDrawImage((void*)cgContext, *(CGRect*)&bounds, image);
// Flush the graphics context to ensure the drawing is displayed
objc_msgSend_id(graphicsContext, sel_registerName("flushGraphics"));
objc_msgSend_void_id(layer, sel_registerName("setContents:"), (id)image);
objc_msgSend_id(layer, sel_registerName("setNeedsDisplay"));
CGImageRelease(image);
步骤 4(释放残留数据)
渲染完成后,你应该使用相应的 API 函数释放位图和图像数据。
在 X11 和 MacOS 上,你也应该释放缓冲区。
在 X11 上,你必须使用 XDestoryImage 和 XFreeGC。
XDestroyImage(bitmap);
XFreeGC(display, gc);
free(buffer);
在 Windows 上,你必须使用 DeleteDC 和 DeleteObject。
DeleteDC(hdcMem);
DeleteObject(bitmap);
在 MacOS 上,你必须使用 release。
release(bitmap);
release(image);
free(buffer);
完整示例
X11
// This can be compiled with // gcc x11.c -lX11 -lm #include <X11/Xlib.h> #include <X11/Xutil.h> #include <stdio.h> #include <stdlib.h> #define SILK_PIXELBUFFER_WIDTH 500 #define SILK_PIXELBUFFER_HEIGHT 500 #define SILK_IMPLEMENTATION #include "silk.h" int main() { Display* display = XOpenDisplay(NULL); XVisualInfo vi; vi.visual = DefaultVisual(display, DefaultScreen(display)); XMatchVisualInfo(display, DefaultScreen(display), 32, TrueColor, &vi); XImage* bitmap = XCreateImage( display, XDefaultVisual(display, vi.screen), vi.depth, ZPixmap, 0, NULL, 500, 500, 32, 0 ); /* ..... */ /* Now this visual can be used to create a window and colormap */ XSetWindowAttributes swa; Colormap cmap; swa.colormap = cmap = XCreateColormap((Display*) display, DefaultRootWindow(display), vi.visual, AllocNone); swa.background_pixmap = None; swa.border_pixel = 0; swa.event_mask = CWColormap | CWBorderPixel | CWBackPixel | CWEventMask; swa.background_pixel = 0; Window window = XCreateWindow((Display*) display, DefaultRootWindow((Display*) display), 500, 500, 500, 500, 0, vi.depth, InputOutput, vi.visual, CWColormap | CWBorderPixel | CWBackPixel | CWEventMask, &swa); /* .... */ GC gc = XCreateGC(display, window, 0, NULL); u8* buffer = (u8*)malloc(500 * 500 * 4); XSelectInput(display, window, ExposureMask | KeyPressMask); XMapWindow(display, window); XEvent event; for (;;) { XNextEvent(display, &event); silkClearPixelBufferColor((pixel*)buffer, 0x11AA0033); silkDrawCircle( (pixel*)buffer, (vec2i) { SILK_PIXELBUFFER_WIDTH, SILK_PIXELBUFFER_HEIGHT }, SILK_PIXELBUFFER_WIDTH, (vec2i) { SILK_PIXELBUFFER_CENTER_X, SILK_PIXELBUFFER_CENTER_Y - 60}, 60, 0xff0000ff ); bitmap->data = (char*) buffer; #ifndef RGFW_X11_DONT_CONVERT_BGR u32 x, y; for (y = 0; y < (u32)500; y++) { for (x = 0; x < (u32)500; x++) { u32 index = (y * 4 * 500) + x * 4; u8 red = bitmap->data[index]; bitmap->data[index] = buffer[index + 2]; bitmap->data[index + 2] = red; } } #endif XPutImage(display, (Window) window, gc, bitmap, 0, 0, 0, 0, 500, 500); } XDestroyImage(bitmap); XFreeGC(display, gc); free(buffer); }
Windows
// This can be compiled with
// gcc win32.c -lgdi32 -lm
#include <windows.h>
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#define SILK_PIXELBUFFER_WIDTH 500
#define SILK_PIXELBUFFER_HEIGHT 500
#define SILK_IMPLEMENTATION
#include "silk.h"
int main() {
WNDCLASS wc = {0};
wc.lpfnWndProc = DefWindowProc; // Default window procedure
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = "SampleWindowClass";
RegisterClass(&wc);
HWND hwnd = CreateWindowA(wc.lpszClassName, "Sample Window", 0,
500, 500, 500, 500,
NULL, NULL, wc.hInstance, NULL);
BITMAPV5HEADER bi = { 0 };
ZeroMemory(&bi, sizeof(bi));
bi.bV5Size = sizeof(bi);
bi.bV5Width = 500;
bi.bV5Height = -((LONG) 500);
bi.bV5Planes = 1;
bi.bV5BitCount = 32;
bi.bV5Compression = BI_BITFIELDS;
// where it can expect to find the RGB data
// (note: this might need to be changed according to the endianness)
bi.bV5BlueMask = 0x00ff0000;
bi.bV5GreenMask = 0x0000ff00;
bi.bV5RedMask = 0x000000ff;
bi.bV5AlphaMask = 0xff000000;
u8* buffer;
HDC hdc = GetDC(hwnd);
HBITMAP bitmap = CreateDIBSection(hdc,
(BITMAPINFO*) &bi,
DIB_RGB_COLORS,
(void**) &buffer,
NULL,
(DWORD) 0);
HDC hdcMem = CreateCompatibleDC(hdc);
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
MSG msg;
BOOL running = TRUE;
while (running) {
if (PeekMessageA(&msg, hwnd, 0u, 0u, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
running = IsWindow(hwnd);
silkClearPixelBufferColor((pixel*)buffer, 0x11AA0033);
silkDrawCircle(
(pixel*)buffer,
(vec2i) { SILK_PIXELBUFFER_WIDTH, SILK_PIXELBUFFER_HEIGHT },
SILK_PIXELBUFFER_WIDTH,
(vec2i) { SILK_PIXELBUFFER_CENTER_X, SILK_PIXELBUFFER_CENTER_Y - 60},
60,
0xff0000ff
);
HGDIOBJ oldbmp = SelectObject(hdcMem, bitmap);
BitBlt(hdc, 0, 0, 500, 500, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, oldbmp);
}
DeleteDC(hdcMem);
DeleteObject(bitmap);
return 0;
}