介绍
rgfw是一个轻量级的单头窗口库,它的源代码可以在这里找到。
本教程基于其源代码。
软件渲染的基本思想很简单。归根结底就是绘制到缓冲区并将其传输到屏幕。
然而,使用低级 api 时软件渲染会更加复杂,因为您必须
正确初始化渲染上下文,告诉 api 如何期望数据。然后要绘制就必须使用api的函数
blit 到屏幕,这可能很复杂。
本教程解释了rgfw如何处理软件渲染,以便您可以了解如何自己实现。
注意:macos 代码将在编写时考虑到 cocoa c 包装器(请参阅 rgfw.h 或 silicon.h)
概述
所需步骤的快速概述
- 初始化缓冲区和渲染上下文
- 绘制到缓冲区
- 将缓冲区传输到屏幕
- 免费剩余数据
第 1 步(初始化缓冲区和渲染上下文)
注意:您可能希望缓冲区的大小大于窗口,这样您就可以缩放缓冲区的大小而无需重新分配它。
在 x11 上,您首先创建一个视觉(或像素格式)来告诉窗口如何处理绘制数据。
然后为缓冲区创建一个位图来渲染,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是一个单头软件渲染图形库。
首先包括丝绸,
#define silk_pixelbuffer_width w
#define silk_pixelbuffer_height h
#define silk_implementation
#include "silk.h"
现在可以使用silk渲染了。
步骤 3(将缓冲区传输到屏幕)
在x11上,首先将位图数据设置到缓冲区。
位图数据将使用bgr渲染,所以你必须
如果要使用 rgb,请转换数据。那你就得用xputimage
使用gc将ximage绘制到窗口。
相关文档:xputimage
bitmap->data = (char*) buffer;
#ifndef rgfw_x11_dont_convert_bgr
u32 x, y;
for (y = 0; y 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 上,您必须首先选择位图并确保保存最后选择的对象,以便稍后可以重新选择它。
现在您可以将位图传输到屏幕并重新选择旧位图。
相关文档: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。
在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>
#include <x11>
#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 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);
}
</stdlib.h></stdio.h></x11></x11>
视窗
// 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;
}
</assert.h></stdint.h></stdio.h></windows.h>