为了一个客户项目,我需要一个简单的精灵图像处理程序,能够在旧硬件上使用。OpenGL 1.1似乎是一个很容易的选择,因为我可以重用旧代码。
无论如何,所有功能都能正常工作,但令我惊讶的是,在绘制移动精灵(在ortho投影中渲染纹理四边形)方面,glBegin/glTexCoord2/glVertex2/glEnd模式始终与glDrawArrays模式一样快。这已经在新旧硬件上进行了测试,我感到有些困惑,因为我原本期望得到不同的结果!
需要注意的是,两种模式对于要求来说都足够快(因此这只是一个好奇者的谈话而不是严肃的工作谈话!),但是对于客户的演示程序,它允许将精灵数增加到10000甚至更多,然后我们发现启用gldrawarrays选项通常速度相同,并且在某些机器上比glbegin/glend慢一半。
下面是渲染代码的一部分。请注意,对于这个演示,顶点和纹理数组是相邻的全局变量。
for index:=0 to (sprite_list.count-1) do begin
s:=sprite_list[index];
s.update;
glBindTexture(GL_TEXTURE_2D,s.sprite_id);
glColor4b(127,127,127,s.ialpha);
if immediate then begin
glBegin(GL_QUADS);
glTexCoord2f(0,0); glVertex2i(coords[0].x,coords[0].y);
glTexCoord2f(0,1); glVertex2i(coords[1].x,coords[1].y);
glTexCoord2f(1,1); glVertex2i(coords[2].x,coords[2].y);
glTexCoord2f(1,0); glVertex2i(coords[3].x,coords[3].y);
glEnd();
end else
glDrawArrays(GL_QUADS, 0, 4);
编辑:以下是 Delphi 单元的代码。创建一个带有 Form 的新项目,将 Timer1(启用,间隔=1)和 Timer2(禁用,间隔=1)对象添加到其中,在此处替换单元代码,并插入表单事件:双击/按键/调整大小/销毁。请注意,这是在旧版本的 Delphi 中编译的,因此一些 OpenGL 标头被添加到单元的开头。此外,按左/右箭头更改精灵数量,按空格键在 glDrawArrays 和 glBegin/glEnd 之间切换。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;
type
TForm1 = class(TForm)
Timer1: TTimer;
Timer2: TTimer;
procedure Timer1Timer(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure FormDblClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
opengl;
const
GL_BGRA_EXT = $80E1;
GL_VERTEX_ARRAY = $8074;
GL_TEXTURE_COORD_ARRAY = $8078;
type
PGLvoid = Pointer;
procedure glDeleteTextures(n: GLsizei; textures: pGLuint);stdcall;external opengl32;
procedure glGenTextures(n: GLsizei; textures: pGLuint);stdcall;external opengl32;
procedure glBindTexture(target: GLenum; texture: GLuint);stdcall;external opengl32;
procedure glEnableClientState(state: GLenum);stdcall;external opengl32;
procedure glDisableClientState(state: GLenum);stdcall;external opengl32;
procedure glTexCoordPointer(size: GLint; _type: GLenum; stride: GLsizei; const _pointer: PGLvoid);stdcall;external opengl32;
procedure glVertexPointer(size: GLint; _type: GLenum; stride: GLsizei; const _pointer: PGLvoid);stdcall;external opengl32;
procedure glDrawArrays(mode: GLenum; first: GLint; count: GLsizei);stdcall;external opengl32;
type
tgeo_point=record
x,y:longint;
end;
var
gl_Texture_Coordinates:array [0..7] of single=(0,0,0,1,1,1,1,0);
coords:array [0..3] of tgeo_point;
immediate:boolean=false;
type
tsprite=class
private
ix,iy:longint;
ix_dir,iy_dir:longint;
ialpha:longint;
public
constructor create;
destructor Destroy;override;
procedure update(w,h:longint);
end;
var
gl_dc:hdc;
gl_pixel_format:longint;
gl_context:longint;
gl_sprite_id:cardinal;
sprite:array [0..1023] of dword;
sprite_width:longint=32;
sprite_height:longint=32;
sprite_list:tlist;
times:array [0..10] of longint=(0,0,0,0,0,0,0,0,0,0,0);
procedure gl_init;
var
p,p2:tpixelformatdescriptor;
begin
gl_dc:=getdc(form1.handle);
zeromemory(@p,sizeof(p));
p.nSize:=sizeof(p);
p.nVersion:=1;
p.dwFlags:=PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;
p.iPixelType:=PFD_TYPE_RGBA;
p.cColorBits:=32;
p.iLayerType:=PFD_MAIN_PLANE;
gl_pixel_format:=choosepixelformat(gl_dc,@p);
if gl_pixel_format=0 then
showmessage('error');
if not setpixelformat(gl_dc,gl_pixel_format,@p) then
showmessage('error');
describepixelformat(gl_dc,gl_pixel_format,sizeof(p2),p2);
if ((p.dwFlags and p2.dwFlags)<>p.dwFlags) or
(p.iPixelType<>p2.iPixelType) or
(p.cColorBits<>p2.cColorBits) or
(p.iLayerType<>p2.iLayerType) then
showmessage('errrrror');
gl_context:=wglcreatecontext(gl_dc);
if gl_context=0 then
showmessage('error');
if not wglmakecurrent(gl_dc,gl_context) then
showmessage('error');
glEnable(GL_BLEND);
glEnable(GL_TEXTURE_2D);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glViewport(0,0,form1.clientwidth,form1.clientheight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0,form1.clientwidth,0,form1.clientheight,-1,1);
glMatrixMode(GL_MODELVIEW);
glColor4f(1,1,1,1);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_INT, 0, @coords);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2,gl_float,0,@gl_Texture_Coordinates);
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
SwapBuffers(gl_dc);
end;
procedure gl_un_init;
begin
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
wgldeletecontext(gl_context);
releasedc(form1.handle,gl_dc);
end;
procedure gl_resize;
begin
glViewport(0,0,form1.clientwidth,form1.clientheight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0,form1.clientwidth,0,form1.clientheight,-1,1);
glMatrixMode(GL_MODELVIEW);
end;
function make_color(a,r,g,b:longint):cardinal;
begin
result:=(a and 255) shl 24 or
(r and 255) shl 16 or
(g and 255) shl 8 or
(b and 255);
end;
procedure sprite_init;
var
x,y:longint;
begin
for x:=0 to (sprite_width-1) do
for y:=0 to (sprite_height-1) do
sprite[y*(sprite_width)+x]:=
make_color((x div 2+1)*(y div 2+1)-1,$ff,$ff,$ff);
glgentextures(1,@gl_sprite_id);
glBindTexture(GL_TEXTURE_2D,gl_sprite_id);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_nearest);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_nearest);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_clamp);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_clamp);
glTexImage2D(GL_TEXTURE_2D,0,4,sprite_width,sprite_height,0,GL_BGRA_EXT,
GL_UNSIGNED_BYTE,@sprite);
end;
procedure sprite_un_init;
begin
gldeletetextures(1,@gl_sprite_id);
end;
constructor tsprite.create;
begin
inherited create;
ix:=random(form1.clientwidth);
iy:=random(form1.clientheight);
if random(2)=1 then
ix_dir:=1
else
ix_dir:=-1;
if random(2)=1 then
iy_dir:=1
else
iy_dir:=-1;
ialpha:=random(128);
end;
destructor tsprite.Destroy;
begin
inherited destroy;
end;
procedure tsprite.update(w,h:longint);
begin
if ix_dir=-1 then begin
dec(ix);
if ix<0 then begin
ix:=0;
ix_dir:=1;
end;
end else begin
inc(ix);
if ix>=w then begin
ix:=w;
ix_dir:=-1;
end;
end;
if iy_dir=-1 then begin
dec(iy);
if iy<0 then begin
iy:=0;
iy_dir:=1;
end;
end else begin
inc(iy);
if iy>=h then begin
iy:=h;
iy_dir:=-1;
end;
end;
coords[0].x:=ix;
coords[0].y:=iy;
coords[1].x:=ix;
coords[1].y:=iy+sprite_height;
coords[2].x:=ix+sprite_width;
coords[2].y:=iy+sprite_height;
coords[3].x:=ix+sprite_height;
coords[3].y:=iy;
end;
procedure TForm1.FormDestroy(Sender: TObject);
var
index:longint;
begin
for index:=0 to (sprite_list.count-1) do
tsprite(sprite_list[index]).free;
sprite_list.free;
sprite_un_init;
gl_un_init;
end;
// --nVidia video card memory
//const
// GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX=$9048;
// GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX=$9049;
procedure TForm1.FormDblClick(Sender: TObject);
var
a,b:longint;
begin
// glGetIntegerv(GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX,@a);
// glGetIntegerv(GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX,@b);
showmessage(
glgetstring(GL_VENDOR)+#13#10+
glgetstring(GL_RENDERER)+#13#10+
glgetstring(GL_VERSION)
+#13#10+'Memory: '+inttostr(b)+'/'+inttostr(a)
// +#13#10+glgetstring(GL_EXTENSIONS)
);
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
index:longint;
begin
case key of
vk_space:immediate:=not immediate;
vk_escape:form1.close;
vk_left:if sprite_list.count>0 then
for index:=(sprite_list.count-1) downto (sprite_list.count-100) do begin
tsprite(sprite_list[index]).free;
sprite_list.delete(index);
end;
vk_right:for index:=1 to 100 do sprite_list.add(tsprite.create);
end;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
gl_resize;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
timer1.enabled:=false;
timer2.enabled:=true;
gl_init;
sprite_init;
sprite_list:=tlist.create;
end;
procedure TForm1.Timer2Timer(Sender: TObject);
var
index,w,h,elapsed:longint;
s:tsprite;
ss:string;
begin
glClear(GL_COLOR_BUFFER_BIT);
w:=form1.clientwidth;
h:=form1.clientheight;
glBindTexture(GL_TEXTURE_2D,gl_sprite_id);
for index:=0 to (sprite_list.count-1) do begin
s:=sprite_list[index];
s.update(w,h);
glColor4b(127,127,127,s.ialpha);
if immediate then begin
glBegin(GL_QUADS);
glTexCoord2f(0,0); glVertex2i(coords[0].x,coords[0].y);
glTexCoord2f(0,1); glVertex2i(coords[1].x,coords[1].y);
glTexCoord2f(1,1); glVertex2i(coords[2].x,coords[2].y);
glTexCoord2f(1,0); glVertex2i(coords[3].x,coords[3].y);
glEnd();
end else
glDrawArrays(GL_QUADS, 0, 4);
end;
glBindTexture(GL_TEXTURE_2D,0);
SwapBuffers(gl_dc);
for index:=10 downto 1 do
times[index]:=times[index-1];
times[0]:=gettickcount;
elapsed:=times[0]-times[10];
if elapsed=0 then elapsed:=1;
if immediate then
ss:='glBegin/glEnd '
else
ss:='glDrawArrays ';
form1.caption:=ss+'Sprites: '+inttostr(sprite_list.count)+' / FPS: '+inttostr(10*1000 div elapsed);
end;
end.
编辑2:向所有人致以诚挚的歉意,我忘记提到在这种情况下,每个精灵只能有一个纹理才能得到最终结果,即使我简化了代码,将其移除以便将渲染循环集中在glBegin/glEnd与glDrawArrays之间。由于我的疏忽导致误导,再次向大家道歉!
glBegin
比glDrawArrays
快吗?试着渲染更多的东西,比如说渲染一百万个四边形,你就会清楚地看到glDrawArrays
更快,而且快得多。 - vallentin