【发布时间】:2013-12-20 16:17:10
【问题描述】:
对于一个客户项目,我需要一个可以在旧硬件上运行的简单精灵 blitter。 OpenGL 1.1 似乎是一个简单的答案,因为我有可以重用的旧代码。
无论如何,一切都很好,但令我惊讶的是,对于移动精灵(因此在正交投影中渲染纹理四边形),glBegin/glTexCoord2/glVertex2/glEnd 模式总是与 glDrawArrays 一样快。这在新旧硬件上都进行了测试,我有点困惑,因为我期望得到不同的结果!
需要注意的是,这两种模式的速度肯定足以满足要求(所以这真的是好奇的书呆子谈话而不是严肃的工作谈话!),但客户端的演示允许泵送精灵计数高达 10000 或更多,然后我们注意到启用 gldrawarrays 选项的速度通常相同,并且在某些机器上是 glbegin/glen 的一半。
下面是呈现的代码。请注意,对于此演示,顶点和纹理数组是彼此相邻的全局变量。
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 单元的代码。使用表单创建一个新项目,在其上添加 timer1(启用,interval=1)和 timer2(禁用,interval=1)对象,用此替换单元代码,插入表单事件:双击/keydown/resize/destroy。请注意,这是在旧版本的 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.
edit2:向大家道歉,我忘了提到在这种情况下,每个精灵的单个纹理对于最终结果是必不可少的,即使我通过删除它来简化代码以将渲染循环集中在 glBegin/glEnd vs glDrawArrays。因疏忽造成误导,敬请见谅!
【问题讨论】:
-
驱动程序可以使用固定属性布局和优化的着色器管道在 VBO 流式架构之上实现即时模式。这也是几年来的标准,所以它很快就不足为奇了
-
我真的不明白你刚刚写了什么,但我们在最近的机器上运行了这个实验,而且机器最多 13 岁,在这个例子中,glDrawArrays 从来没有比 glBegin/glEnd 快。一般来说,旧机器上的性能大致相同,而在新机器上 glBegin/glEnd 通常要快得多。
-
glBegin总是比glDrawArrays快?尝试渲染一些不仅仅是几个四边形的东西,比如说,渲染一百万个四边形,那么你应该清楚地能够看到glDrawArrays更快,更快。 -
嗯?我猜帖子不清楚,但我说的是有 10000 个移动纹理四边形,少于 1000 个没有区别。今晚我回家时,我将发布用于制作小演示的 delphi 单元的代码。我认为它大约有 400 行长,我可以在第一篇文章中放那么多行,以便每个人都可以下载并查看它吗?
-
好的,我在第一篇文章中放入了测试cheapo 演示的代码。如果 glDrawArrays 可以更快,那么 glBegin/glEnd 在这种情况下我愿意学习!