云风的个人空间 : 脏矩形 demo[DirtyRect]

首页 :: 索引 :: 修订历史 :: 你好, 18.219.26.117
你的足迹: » 脏矩形 demo
FAQ

Q: 这是什么?
A: 这是一个脏矩形技术演示的 demo source code 。

Q: 如何编译?
A: 在 windows 平台选一款 C 编译器,将包内所有的 .c 文件编译链接在一起即可。

Q: 这个 demo 实现了什么?
A: 实现了一个支持分区域以脏矩形方式更新的画布,并以绘制矩形为实例作为演示。

Q: demo 表现出来的东西是什么?
A: 可以说,这个 demo 什么都没有表现,仅仅是一个正方形从窗口飘过。

Q: 这些 source code 有实用价值吗?
A: 价值要看你怎么看待它,如果你不了解脏矩形技术,那么读懂它就是有价值的。如果你想在此基础上发展这些代码,它们或许也有价值。如果你想直接用来做项目,很抱歉,那它太简陋了。

Q: 代码中为什么没有注释?
A: 因为云风已经尽可能的将代码写的简洁清晰,过于详尽的代码注释不是我的风格。

Q: 为什么是 C 写的?
A: 因为这并非一个很复杂的技术,也不需要过于复杂的结构,C 更纯净一些,不需要用 C++ 。而不用汇编,可以让代码更好的被理解,如果真正需要效率的时候,我们可以做汇编优化。

Q: 有哪些地方可以被改进?
A: 代码结构设计的并不理想,函数和变量命名都太过随意。但我已经在我能够掌握的时间内(周末的整整一个晚上)把它写到让自己比较满意了。有些地方设计的并不一定合理,比如,每次刷新 canvas 的时候强制用黑色清屏(见 canvas.c 中的 drawfunc_clear 函数)。test.c 是用一个默认的机器向导生成的 win32 程序框架,并且随便添加了几行代码,显得很不严谨,鉴于这个 demo 只是演示脏举行技术,而非 windows 程序框架,时间有限的情况下一个简陋的 test 代码应当是可以接受的。

Q: 为什么发布这样的 source code?
A: 风魂已经几年没有公开的版本发布,已经流传的版本从现在的眼光看已经非常糟糕。代码组织混乱、C++ 的不正确使用、结构设计不合理等等。云风个人认为,其中还有点价值的只是脏矩形技术的思想,但这些已经混杂在体积臃肿的代码堆里。在《游戏之旅——我的编程感悟》一书第八章,我本来想用出版文字的方式把这部分技术记录下来。但直到今天才有些许空暇编写 demo 演示。

Q: 脏矩形技术在 3d 游戏为主流的今天是否已经毫无用武之地?
A: 即使在 3d 游戏中,我们依然可以用这个技术做 UI 模块,如果设计合理,可以带来极大的便利甚至更高的效率。而制作 2d 休闲游戏更加有用。

Q: source code 中 gdi 部分是可以被替换的吗?
A: 设计上就考虑了将显示部分分离,整个 demo 的代码篇幅已经被压缩到很短,读懂代码后,应该很容易把 gdi 部分替换成 directx 或是其它。比如将 canvas 提交到一个带 alpha 通道的贴图中,以制作 3d 游戏的 UI 用。同样的道理,这个 demo 中使用 16bit 色彩深度,你也可以方便的修改为 32bit 。

Q: 脏矩形技术可以进一步优化吗?
A: 可以。对于经常滚动的情况可以做滚动优化(即让 canvas 跟随场景滚动)。对于大面积的遮挡物,比如不透明的对话框时,可以做覆盖优化,让被覆盖的对象不进入渲染管道。在这个 demo 里都没有实现。或许以后再有时间,我会丰富 demo 的内容。

Q: 发现了 Bug ?
A: 如何你想使用这些代码,请关注最新版本。最新版本在云风的个人空间中能够找到。[External Link]http://blog.codingnow.com/cloud/DirtyRect 链接如果失效,请使用搜索引擎。bug fix 的信息通常不会显著标出,但是在相关的页面的版本信息页上会表现出来,同时也有 RSS 可以订阅。如果发现了最新版本仍然存在的 Bug ,非常欢迎提交给作者云风。

Q: 如何联系作者?
A: 作者的主页是 [External Link]http://www.codingnow.com 在那里可以找到云风最新的联系方式,而且主页上有留言本,blog 和个人 wiki 也都可以留言。如果主页有一天不能访问,请用 google 搜索“云风”

Q: 这些 source code 有版权吗?
A: 作者云风保留版权,但是你可以自由的使用它,免费用在你想用的任何项目中。如果你在使用之前通知一下我,我将感到高兴,但你不这样做,也不会被指责。不过,云风保留修改和散发这些代码的权利,任何人不允许自己在公开的场合,如你的个人主页,blog,或是公开的 论坛,maillist 等地方散发这些代码。这样做,是为了代码中的 bug 可以得到及时修改,请谅解。如果你对代码做出了大规模的修改增删,那么欢迎你当成你自己的作品发布。


云风,于二○○六年五月廿一日
[External Link]http://www.codingnow.com


canvas.h
#ifndef _DIRTYRECT_CANVAS_H
#define _DIRTYRECT_CANVAS_H
 
#define CANVAS_BLOCK_WIDTH 64
#define CANVAS_BLOCK_HEIGHT 32
#define DIRTY (~0)
 
typedef unsigned short pixel;
typedef void (*draw_func)(pixel *ptr,int pitch,void *object,int x,int y);
struct canvas;
 
#define COMMON_HEADER	\
	unsigned dirty;	\
	int width;	\
	int height;	\
	int kx;	\
	int ky;
 
struct canvas* canvas_create(int w,int h,pixel *ptr,int pitch,draw_func flip,void *flip_object);
void canvas_release(struct canvas *c);
void canvas_draw(struct canvas *c,draw_func func,void *object,int x,int y);
void canvas_update(struct canvas *c);
void canvas_redraw(struct canvas *c);
 
#endif


canvas.c
#include <malloc.h>
#include <assert.h>
#include <string.h>
#include "canvas.h"
 
#define CANVAS_PIPELINE_EXPAND 32
 
struct pipe_node {
	draw_func func;
	void *object;
	int x;
	int y;
};
 
struct canvas_block {
	pixel *ptr;
	struct pipe_node *pipeline;
	int total_object;
	int total_last;
	int capability_pipeline;
};
 
struct canvas {
	int width;
	int height;
	int pitch;
	int block_w;
	int block_h;
	struct canvas_block block[1];
};
 
static void drawfunc_clear(pixel *ptr,int pitch,void *object,int x,int y)
{
	int i;
	for (i=0;i<CANVAS_BLOCK_HEIGHT;i++) {
		memset(ptr,0,CANVAS_BLOCK_WIDTH*sizeof(pixel));
		ptr=(pixel*)((char*)ptr+pitch);
	}
}
 
static void canvas_block_resize(struct canvas_block *blk)
{
	const int size=blk->capability_pipeline+CANVAS_PIPELINE_EXPAND;
	const int newsize=size*sizeof(struct pipe_node);
	blk->capability_pipeline=size;
	blk->pipeline=(struct pipe_node*)realloc(blk->pipeline,newsize);
}
 
static void canvas_init(struct canvas *c,pixel *ptr,draw_func flip,void *flip_object)
{
	int pitch=c->pitch * CANVAS_BLOCK_HEIGHT;
	int i,j;
	const int w=c->width;
	const int h=c->height;
	pixel *line=ptr;
	struct canvas_block *block=c->block;
	for (i=0;i<h;i+=CANVAS_BLOCK_HEIGHT) {
		for (j=0;j<w;j+=CANVAS_BLOCK_WIDTH) {
			block->ptr=line+j;
			block->pipeline=0;
			block->capability_pipeline=0;
			canvas_block_resize(block);
			block->total_object=1;
			block->total_last=0;
			block->pipeline[0].func=flip;
			block->pipeline[0].object=flip_object;
			block->pipeline[0].x=j;
			block->pipeline[0].y=i;
			++block;
		}
		line=(pixel*)((char*)line+pitch);
	}
}
 
struct canvas* canvas_create(int w,int h,pixel *ptr,int pitch,draw_func flip,void *flip_object)
{
	const int nw=(w+CANVAS_BLOCK_WIDTH-1)/CANVAS_BLOCK_WIDTH;
	const int nh=(h+CANVAS_BLOCK_HEIGHT-1)/CANVAS_BLOCK_HEIGHT;
	struct canvas *c=(struct canvas *)malloc(
		sizeof(struct canvas)-sizeof(struct canvas_block)+
		sizeof(struct canvas_block)*(nw*nh)
	);
 
	c->width=w;
	c->height=h;
	c->block_w=nw;
	c->block_h=nh;
	c->pitch=pitch;
	canvas_init(c,ptr,flip,flip_object);
	return c;
}
 
void canvas_release(struct canvas *c)
{
	int i,j;
	const int w=c->block_w;
	const int h=c->block_h;
	struct canvas_block *block=c->block;
	for (i=0;i<h;i++) {
		for (j=0;j<w;j++) {
			free(block->pipeline);
			++block;
		}
	}
	free(c);
}
 
struct object_2d { COMMON_HEADER } ;
 
void canvas_draw(struct canvas *c,draw_func func,void *object,int x,int y)
{
	struct object_2d *obj=(struct object_2d *)object;
	int left,right,top,bottom;
	unsigned dirty=obj->dirty;
	if (obj->width<=0 || obj->height<=0)
		return;
	obj->dirty=0;
	left=(x-=obj->kx);
	top=(y-=obj->ky);
	right=left+obj->width-1;
	bottom=top+obj->height-1;
 
	// clip to canvas
	if (left<0) {
		left=0;
	}
	else if (left>=c->width) {
		return;
	}
	if (top<0) {
		top=0;
	}
	else if (top>=c->height) {
		return;
	}
	if (right>c->width-1) {
		right=c->width-1;
	}
	else if (right<0) {
		return;
	}
	if (bottom>c->height-1) {
		bottom=c->height-1;
	}
	else if (bottom<0) {
		return;
	}
 
	// splite to blocks
	do {
		int i,j;
		int w=right/CANVAS_BLOCK_WIDTH;
		int h=bottom/CANVAS_BLOCK_HEIGHT;
		int from_x=left/CANVAS_BLOCK_WIDTH;
		int from_y=top/CANVAS_BLOCK_HEIGHT;
		struct canvas_block *block=&c->block[from_y*c->block_w];
		for (i=from_y;i<=h;i++) {
			for (j=from_x;j<=w;j++) {
				struct canvas_block *blk=&block[j];
				struct pipe_node *node;
				if (blk->total_object==blk->capability_pipeline) {
					canvas_block_resize(blk);
				}
				node=&blk->pipeline[blk->total_object++];
				node->func=func;
				node->object=object;
				node->x=x-j*CANVAS_BLOCK_WIDTH;
				node->y=y-i*CANVAS_BLOCK_HEIGHT;
				blk->total_last|=dirty;
			}
			block+=c->block_w;
		}
	} while(0);
}
 
void canvas_update(struct canvas *c)
{
	int i,j;
	struct canvas_block *blk=c->block;
	int pitch=c->pitch;
	for (i=c->block_w*c->block_h;i>0;--i,++blk) {
		if (blk->total_object!=blk->total_last) {
			pixel *ptr=blk->ptr;
			drawfunc_clear(ptr,pitch,0,0,0);
			for (j=blk->total_object-1;j>=0;j--) {
				struct pipe_node *node=&blk->pipeline[j];
				node->func(ptr,pitch,node->object,node->x,node->y);
			}
			blk->total_last=blk->total_object;
		}
		blk->total_object=1;
	}
}
 
void canvas_redraw(struct canvas *c)
{
	int i;
	struct canvas_block *blk=c->block;
	int pitch=c->pitch;
	for (i=c->block_w*c->block_h;i>0;--i,++blk) {
		blk->total_last=0;
	}
}


box.h
#ifndef _DIRTYRECT_BOX_H
#define _DIRTYRECT_BOX_H
 
#include "canvas.h"
 
struct box {
	COMMON_HEADER
	pixel color;
};
 
void box_draw(struct canvas *c,struct box *b,int x,int y);
 
#endif


box.c
#include "box.h"
 
static void draw_box(pixel *ptr,int pitch,void *object,int x,int y)
{
	struct box *obj=(struct box*)object;
	int w=obj->width;
	int h=obj->height;
	int i,j;
	pixel *buffer=ptr;
	pixel color=obj->color;
	if (y<0) {
		h+=y;
		y=0;
	}
	else {
		buffer=(pixel*)((char*)buffer+y*pitch);
	}
	if (x<0) {
		w+=x;
		x=0;
	}
	else {
		buffer+=x;
	}
	if (w+x>CANVAS_BLOCK_WIDTH) {
		w=CANVAS_BLOCK_WIDTH-x;
	}
	if (h+y>CANVAS_BLOCK_HEIGHT) {
		h=CANVAS_BLOCK_HEIGHT-y;
	}
	for (i=0;i<h;i++) {
		for (j=0;j<w;j++) {
			buffer[j]=color;
		}
		buffer=(pixel*)((char*)buffer+pitch);
	}
}
 
void box_draw(struct canvas *c,struct box *b,int x,int y)
{
	canvas_draw(c,draw_box,b,x,y);
}


gdi.h
#ifndef _DIRTYRECT_FLIPGDI_H
#define _DIRTYRECT_FLIPGDI_H
 
#include "canvas.h"
 
struct gdi {
	draw_func flip;
	void *object;
	pixel *buffer;
	int width;
	int height;
	int pitch;
};
 
void gdi_create(struct gdi *g,void *wnd,int w,int h);
void gdi_release(struct gdi *g);
 
#endif


gdi.c
#include "gdi.h"
#include <windows.h>
#include <malloc.h>
#include <string.h>
 
struct gdi_object {
	HWND wnd;
	HDC dc;
	BITMAPINFO bi;
	RGBQUAD pal[2];
};
 
static void gdi_flip(pixel *ptr,int pitch,void *object,int x,int y)
{
	struct gdi_object *obj=(struct gdi_object *)object;
	SetDIBitsToDevice(obj->dc,
		x,y,CANVAS_BLOCK_WIDTH,CANVAS_BLOCK_HEIGHT,
		x,0,
		0,CANVAS_BLOCK_HEIGHT,
		ptr-x,&(obj->bi),DIB_RGB_COLORS
	);
}
 
void gdi_create(struct gdi *g,void *wnd,int w,int h)
{
	struct gdi_object *obj;
	BITMAPINFOHEADER *bh;
	g->pitch=(w*sizeof(pixel)+3)&(~3);
	g->flip=gdi_flip;
	g->buffer=(pixel*)malloc(h*g->pitch);
	g->object=obj=(struct gdi_object *)malloc(sizeof(struct gdi_object));
	g->width=w;
	g->height=h;
	obj->wnd=(HWND)wnd;
	obj->dc=GetDC(obj->wnd);
	bh=&(obj->bi.bmiHeader);
	memset(bh,0,sizeof(*bh));
	bh->biSize=sizeof(*bh);
	bh->biWidth=g->pitch/sizeof(pixel);
	bh->biHeight=-CANVAS_BLOCK_HEIGHT;
	bh->biPlanes=1;
	bh->biBitCount=sizeof(pixel)*8;
	bh->biCompression=BI_BITFIELDS;
	*(int*)(obj->bi.bmiColors+0)=0xf800;
	*(int*)(obj->bi.bmiColors+1)=0x7e0;
	*(int*)(obj->bi.bmiColors+2)=0x1f;
}
 
void gdi_release(struct gdi *g)
{
	struct gdi_object *obj=(struct gdi_object *)g->object;
	ReleaseDC(obj->wnd,obj->dc);
	free(g->buffer);
	free(g->object);	
}


test.c
#include <windows.h>
#include "gdi.h"
#include "canvas.h"
#include "box.h"
 
#define WIDTH 640
#define HEIGHT 480
 
struct gdi g_gdi;
struct canvas *g_canvas;
 
HINSTANCE hInst;	// current instance
 
ATOM				MyRegisterClass();
BOOL				InitInstance();
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
 
int main()
{
	int i;
	MSG msg;
	struct box b;
	hInst = GetModuleHandle(0); // Store instance handle in our global variable
	MyRegisterClass();
 
	if (!InitInstance ()) {
		return FALSE;
	}
	b.kx=0;
	b.ky=0;
	b.width=100;
	b.height=100;
	b.color=0xffff;
	b.dirty=DIRTY;
 
	// Main message loop:
	i=0;
	for (;;) {
		if(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)) {
			if(!GetMessage(&msg,NULL,0,0)) {
				break;
			}
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		b.dirty=DIRTY;
		box_draw(g_canvas,&b,i,i);
		canvas_update(g_canvas);
		++i;
		Sleep(5);
	}
 
 
	gdi_release(&g_gdi);
	canvas_release(g_canvas);
 
	return msg.wParam;
}
 
ATOM MyRegisterClass()
{
	WNDCLASS	wc;
 
	wc.style=          CS_HREDRAW|CS_VREDRAW;
	wc.lpfnWndProc=    (WNDPROC)WndProc;
	wc.cbClsExtra=     0;
	wc.cbWndExtra=     0;
	wc.hInstance=      hInst;
	wc.hIcon=          NULL;
	wc.hCursor=        LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground=  NULL;
	wc.lpszMenuName=   NULL;
	wc.lpszClassName=  "TEST";
 
	return RegisterClass(&wc);
}
 
BOOL InitInstance()
{
	HWND hWnd;
	DWORD style=WS_OVERLAPPEDWINDOW&~WS_SIZEBOX&~WS_MAXIMIZEBOX;
	RECT rect={0,0,WIDTH,HEIGHT};
	AdjustWindowRect(&rect,style,FALSE);
 
	hWnd=CreateWindow("TEST","test",
		style, CW_USEDEFAULT,0,
		rect.right-rect.left,rect.bottom-rect.top,
		0,0,hInst,0);
 
 
   if (!hWnd)
   {
      return FALSE;
   }
 
   gdi_create(&g_gdi,hWnd,WIDTH,HEIGHT);
   g_canvas=canvas_create(g_gdi.width,g_gdi.height,
         g_gdi.buffer,g_gdi.pitch,g_gdi.flip,g_gdi.object);
 
   ShowWindow(hWnd, SW_SHOWDEFAULT);
   UpdateWindow(hWnd);
 
   return TRUE;
}
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) 
	{
		case WM_PAINT:
			canvas_redraw(g_canvas);
			break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}