65.9K
CodeProject 正在变化。 阅读更多。
Home

RGFW 幕后:软件渲染

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2024年8月19日

CPOL

3分钟阅读

viewsIcon

2373

downloadIcon

43

本教程解释了如何为 X11、WinAPI 和 Cocoa 设置和处理软件渲染。

引言

RGFW 是一个轻量级的单头文件窗口库,其源代码可以在 这里 找到。本教程基于其源代码。

软件渲染的基本思想很简单。它归结为绘制到一个缓冲区,然后将其“blit”到屏幕上。然而,在处理低级 API 时,软件渲染会更复杂,因为你必须正确初始化一个渲染上下文,告诉 API 如何处理数据。然后,为了绘制,你必须使用 API 的函数将内容“blit”到屏幕上,这可能会很复杂。

本教程解释了 RGFW 如何处理软件渲染,以便你能理解如何自己实现它。

注意:MacOS 代码将以 Cocoa C Wrapper 为目标(请参阅 RGFW.h 或 Silicon.h)

概述

所需步骤的快速概述

  1. 初始化缓冲区和渲染上下文
  2. 绘制到缓冲区
  3. 将缓冲区“blit”到屏幕
  4. 释放残留数据

步骤 1(初始化缓冲区和渲染上下文)

注意:你可能希望缓冲区的大小大于窗口,这样你就可以在不重新分配内存的情况下缩放缓冲区的大小。

在 X11 上,你首先创建一个 Visual(或像素格式),它告诉窗口如何处理绘制数据。然后为缓冲区创建一个位图进行渲染,RGFW 使用 XImage 结构来表示位图。接下来,你使用显示和窗口数据创建一个图形上下文 (GC)。GC 用于告诉 X11 如何将绘制数据提供给窗口。

这也是你可以分配缓冲区的地方。除 Windows 外,必须为每个平台分配缓冲区。

为此,你需要使用 XMatchVisualInfoXCreateImageXCreateGC

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 会为我们处理这部分内存。你也可以手动分配内存。

相关文档:BITMAPV5HEADERCreateDIBSectionCreateCompatibleDC

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”到屏幕上,并重新选中旧的位图。

相关文档:SelectObjectBitBlt

HGDIOBJ oldbmp = SelectObject(hdcMem, bitmap);
BitBlt(hdc, 0, 0, window_width, window_height, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, oldbmp);

在 MacOS 上,根据你的窗口设置视图的 CALayer,这用于将图像渲染到屏幕。接下来,使用缓冲区创建图像(位图)。最后,你可以将图像添加到图层的图形上下文中,然后绘制并刷新图层到屏幕。

相关文档:CGColorSpaceCreateDeviceRGBCGBitmapContextCreateCGBitmapContextCreateImageCGColorSpaceReleaseCGContextReleaseCALayerNSGraphicsContextCGContextDrawImageflushGraphicsCGImageRelease

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 上,你必须使用 XDestoryImageXFreeGC

XDestroyImage(bitmap);
XFreeGC(display, gc);
free(buffer);

在 Windows 上,你必须使用 DeleteDCDeleteObject

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;
}
© . All rights reserved.