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: 如何你想使用这些代码,请关注最新版本。最新版本在云风的个人空间中能够找到。
http://blog.codingnow.com/cloud/DirtyRect 链接如果失效,请使用搜索引擎。bug fix 的信息通常不会显著标出,但是在相关的页面的版本信息页上会表现出来,同时也有 RSS 可以订阅。
Q: 如何联系作者?
A: 作者的主页是
http://www.codingnow.com 在那里可以找到云风最新的联系方式,而且主页上有留言本,blog 和个人 wiki 也都可以留言。如果主页有一天不能访问,请用 google 搜索“云风”
Q: 这些 source code 有版权吗?
A: 作者云风保留版权,但是你可以自由的使用它,免费用在你想用的任何项目中。如果你在使用之前通知一下我,我将感到高兴,但你不这样做,也不会被指责。不过,云风保留修改和散发这些代码的权利,任何人不允许自己在公开的场合,如你的个人主页,blog,或是公开的 论坛,maillist 等地方散发这些代码。这样做,是为了代码中的 bug 可以得到及时修改,请谅解。如果你对代码做出了大规模的修改增删,那么欢迎你当成你自己的作品发布。
云风,于二○○六年五月廿一日
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;
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;
obj->dirty=0;
left=(x-=obj->kx);
top=(y-=obj->ky);
right=left+obj->width;
bottom=top+obj->height;
// 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;
}