glDrawArrays对于小数组永远不比glBegin/glEnd快吗?

3

为了一个客户项目,我需要一个简单的精灵图像处理程序,能够在旧硬件上使用。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之间。由于我的疏忽导致误导,再次向大家道歉!


一个驱动程序可以在VBO流式架构之上实现即时模式,使用固定属性布局和优化的着色器管线。它也是几年来的标准,所以它能够快速运行并不奇怪。 - ratchet freak
我不太理解你刚才写的内容,但我们最近在新旧机器上运行了这个实验,在这种情况下glDrawArrays从未比glBegin/glEnd更快。一般而言,在旧的机器上性能大致相同,而在较新的机器上glBegin/glEnd通常要快得多。 - Marladu
1
glBeginglDrawArrays 快吗?试着渲染更多的东西,比如说渲染一百万个四边形,你就会清楚地看到 glDrawArrays 更快,而且快得多。 - vallentin
嗯?我猜帖子不太清楚,但我是在谈论有10000个移动纹理四边形,与少于1000个没有区别。今晚回家后,我会发布Delphi单元的代码,以制作小演示。我认为它大约有400行,我可以把这么多行放在第一篇帖子中,让每个人都可以下载和查看吗? - Marladu
好的,我已经放入代码来测试 cheapo demo 的第一篇文章了。如果在这种情况下可以使 glDrawArrays 比 glBegin/glEnd 更快,我愿意学习如何实现! - Marladu
2个回答

1
我实际上在13年前就开始使用OpenGL,我可以告诉你,即使当时你的应用程序结构正确,顶点数组通常也会更快。
那时我们没有顶点缓冲对象,但我们有交错的顶点数组(我的意思是glInterleavedArrays (...))和编译的顶点数组。NVIDIA后来创建了一个扩展(顶点数组范围),允许顶点数据存储在虚拟内存中(该虚拟内存的地址范围被设计为允许有效的DMA传输)。

当时还被称为ATI的AMD公司,也有自己的扩展顶点数组对象,可以提高顶点数组的性能(通过在服务器端存储顶点数据)。不要将AMD的扩展与现代OpenGL中所谓的VAO混淆。实际上,AMD的扩展为顶点缓冲对象奠定了基础,只是不幸地与完全无关的东西共享了名称。

现在,我刚刚讨论的所有事情都实际上记录了OpenGL中顶点数组的演变。特别是,它们表明趋势是(1)将顶点数据存储在用户定义的内存组织中,(2)存储在服务器(GPU)内存中以实现最大限度的重用,以及(3)尽可能少的API调用。立即模式(glBegin / glEnd)违反了所有这些原则,你只能将命令放入显示列表中,并希望驱动程序处理(2)(3)


更新:

请注意,由于我们正在谈论OpenGL 1.1时代的硬件,因此图形硬件并不总是处理顶点变换。很长一段时间,“GPU”的祖先只加速光栅化,而顶点变换是在CPU上处理的。在我们拥有能够实现整个图形管线的GPU之前,顶点和光栅化之间的分离意味着传递到管线中的顶点效率如何并不重要,因为一些工作是在CPU上完成的。一旦硬件T&L出现,服务器端顶点就至关重要。

这就是你问题的关键所在。你的软件没有设置成从硬件加速顶点变换中受益。你正在CPU上执行所有的变换,并在每次需要更改时向GPU发送新数据。现代应用程序使用顶点着色器和静态顶点数据来完成同样的事情,同时最小化每帧需要发送到GPU的数据量。在现代软件中,大多数变换可以通过更新4x4矩阵或两个矩阵以供顶点着色器使用来完成。

除此之外,除非你的软件与顶点绑定,否则提高顶点效率不会显著提高性能。对我来说,“精灵贴图”更像是片段绑定的情况,特别是如果精灵是透明混合的。


很酷的东西。希望稍后我发布小演示的完整源代码时,你可以帮忙弄清楚我做错了什么或者哪里有问题,因为结果看起来很奇怪,但是涉及的代码很少,我很难想象可能有什么不同。 - Marladu
我很欣赏这篇文章中的所有努力,特别是因为我从中学到了一些东西,但实际上这并没有解决这种情况。这是关于一种情况,即在使用glBegin(quads)/(glTex/glVertex)x4/glEnd时,始终至少与glDrawArrays一样快。这种知识只对处于特定情况下(必须使用硬件新技术来复制唯一的alpha精灵,同时还要使用10多年前的旧设备)的人有用。值得注意的是,在win98或windows8机器上,对于500个精灵,性能没有差异,但对于成千上万个精灵,glBegin/glEnd的速度最多快两倍。 - Marladu
@Marladu:这是相关的,因为当您使用带有客户端内存的glDrawArrays(...)时,GL必须确保由glVertexPointer(...)指向的数据在命令完成之前不会更改。这意味着您正在引入CPU和GPU之间的同步点。在glBegin(...)glEnd(...)之间的操作每次调用它们都会创建新数据,因此GL无需等待任何东西完成或复制数据即可接受另一个命令。这归结于指向GL没有完全控制的内存,这是通过VBO解决的。 - Andon M. Coleman
好的,抱歉我忘了在这种情况下每个精灵都必须有一个独特的纹理。我已经将这些信息添加到其他答案和第一篇帖子中了。如果这让你误解了,我很抱歉! - Marladu
我刚刚看了你的评论,再次感谢你抽出时间来尝试教我。但是我并没有完全理解这些内容,我只是工作了很多然后在测量后使用最好的方法。我很感激你试图向我展示原因,但这已经超出了我理解的边界。希望其他人能够从我的实践和你的理论中学到一些东西。 - Marladu
@Marladu:在编写良好的GL软件中,CPU和GPU是异步操作的。通过让GL完全控制glDrawArrays(...)使用的内存(例如,您可以读取或修改它的唯一方法是与GL交谈),它知道必须在内存可以被修改之前完成哪些命令。否则,glDrawArrays(...)将假定内存可以随时更改,并且要么会复制您的数据,要么会阻塞直到命令完全完成。此论坛帖子可能有助于更好地解释发生了什么。 - Andon M. Coleman

0
glDrawArrays(GL_QUADS, 0, 4);

你需要增加批处理大小,才能真正看到顶点数组的性能优势。

缺点是通常需要重新设计代码,并愿意容忍一些“冗余”的存储和计算。

例如:(C++,但除了向量数学的GLM运算符重载之外,没有太复杂的东西)

#include <GL/glut.h>

#include <vector>
#include <iostream>
using namespace std;

#include <glm/glm.hpp>
#include <glm/gtc/random.hpp>
using namespace glm;

class Sprites
{
public:
    struct State
    {
        State() {}
        State( const vec2& pos, const vec2& vel ) : pos(pos), vel(vel) {}
        vec2 pos;
        vec2 vel;
    };

    struct Vertex
    {
        Vertex() {}
        Vertex( const vec4& color ) : color(color) {}
        vec2 pos;
        vec4 color;
    };

    size_t Size()
    {
        return states.size();
    }

    void PushBack( const State& state, const vec4& color )
    {
        states.push_back( state );
        verts.push_back( Vertex( color ) );
        verts.push_back( Vertex( color ) );
        verts.push_back( Vertex( color ) );
        verts.push_back( Vertex( color ) );
    }

    void Add( unsigned int number )
    {
        const float w = (float)glutGet( GLUT_WINDOW_WIDTH );
        const float h = (float)glutGet( GLUT_WINDOW_HEIGHT );
        for( unsigned int i = 0; i < number; ++i )
        {
            State state( glm::linearRand( vec2(-w,-h), vec2(w,h) ), glm::diskRand( 100.0f ) );
            vec4 color( glm::linearRand( vec4(1,1,1,1) * 0.1f, vec4(1,1,1,1) ) );
            PushBack( state, color );
        }
    }

    void Remove( unsigned int number )
    {
        if( states.size() >= number ) 
            states.resize( states.size() - number );
        if( verts.size() >= number * 4 )
            verts.resize( verts.size() - number * 4 );
    }

    void Step( float dt )
    {
        // run physics
        const float w = (float)glutGet( GLUT_WINDOW_WIDTH );
        const float h = (float)glutGet( GLUT_WINDOW_HEIGHT );
        const vec2 minExts = vec2(-w, -h);
        const vec2 maxExts = vec2(w, h);
        for( int i = 0; i < (int)states.size(); ++i )
        {
            State& state = states[i];

            if( state.pos.x < minExts.x || state.pos.x > maxExts.x )
                state.vel.x = -state.vel.x;
            if( state.pos.y < minExts.y || state.pos.y > maxExts.y )
                state.vel.y = -state.vel.y;

            state.pos += state.vel * dt;
        }

        // update geometry
        const vec2 spriteDims( 32, 32 );
        const vec2 offsets[4] =
        {
            vec2( -1, -1 ) * 0.5f * spriteDims,
            vec2(  1, -1 ) * 0.5f * spriteDims,
            vec2(  1,  1 ) * 0.5f * spriteDims,
            vec2( -1,  1 ) * 0.5f * spriteDims,
        };
        for( int i = 0; i < (int)states.size(); ++i )
        {
            verts[i*4 + 0].pos = states[i].pos + offsets[0];
            verts[i*4 + 1].pos = states[i].pos + offsets[1];
            verts[i*4 + 2].pos = states[i].pos + offsets[2];
            verts[i*4 + 3].pos = states[i].pos + offsets[3];
        }
    }

    void Draw( bool useVertexArrays )
    {
        if( verts.empty() ) return;

        if( useVertexArrays )
        {
            glEnableClientState( GL_VERTEX_ARRAY );
            glVertexPointer( 2, GL_FLOAT, sizeof(Vertex), &verts[0].pos );

            glEnableClientState( GL_COLOR_ARRAY );
            glColorPointer( 4, GL_FLOAT, sizeof(Vertex), &verts[0].color );

            glDrawArrays( GL_QUADS, 0, verts.size() );

            glDisableClientState( GL_VERTEX_ARRAY );
            glDisableClientState( GL_COLOR_ARRAY );
        }
        else
        {
            glBegin( GL_QUADS );
            for( size_t i = 0; i < states.size(); ++i )
            {
                glColor4fv(  &verts[i*4 + 0 ].color.r );
                glVertex2fv( &verts[i*4 + 0 ].pos.x );
                glColor4fv(  &verts[i*4 + 1 ].color.r );
                glVertex2fv( &verts[i*4 + 1 ].pos.x );
                glColor4fv(  &verts[i*4 + 2 ].color.r );
                glVertex2fv( &verts[i*4 + 2 ].pos.x );
                glColor4fv(  &verts[i*4 + 3 ].color.r );
                glVertex2fv( &verts[i*4 + 3 ].pos.x );
            }
            glEnd();
        }
    }

private:
    vector< State > states;
    vector< Vertex > verts;
};

Sprites sprites;
bool useVAs = false;
void keyboard( unsigned char key, int x, int y )
{
    switch( key )
    {
    case 'a':   sprites.Add( 10000 );       break;
    case 'z':   sprites.Remove( 10000 );    break;
    case 'v':   useVAs = !useVAs;           break;
    case 27:    exit( 1 );                  break;
    default:    break;
    }
}

void display()
{
    static int prvTime = glutGet( GLUT_ELAPSED_TIME );
    const int curTime = glutGet( GLUT_ELAPSED_TIME );
    const float dt = ( curTime - prvTime ) / 1000.0f;
    prvTime = curTime;

    cout << "Sprites: " << sprites.Size() << "; "; 
    cout << "dt: " << dt * 1000.0f << "ms ";
    cout << endl;

    sprites.Step( dt );

    glClear( GL_COLOR_BUFFER_BIT );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    double w = glutGet( GLUT_WINDOW_WIDTH );
    double h = glutGet( GLUT_WINDOW_HEIGHT );
    glOrtho( -w, w, -h, h, -1, 1 );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    sprites.Draw( useVAs );

    glutSwapBuffers();
}

int main( int argc, char** argv )
{
    glutInit( &argc, argv );
    glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE );
    glutInitWindowSize( 640, 480 );
    glutCreateWindow( "GLUT" );
    glutDisplayFunc( display );
    glutIdleFunc( display );
    glutKeyboardFunc( keyboard );

    sprites.Add( 10000 );

    glutMainLoop();
    return 0;
}

1
哇,老兄,我真的很感激你的努力,非常感谢!不过,这里的大问题是,在这种情况下,我无法批处理精灵,因为每个精灵都是独特的,可以独立更改(包括它们的尺寸),所以没有精灵表,因此我必须为每个精灵调用glBindTexture,因此,通过glDrawArrays一次不能批量发送多个精灵。我仍然期望glDrawArrays比glBegin(quads)/…/glEnd更快,因为那意味着1个OpenGL调用与10个OpenGL调用,但在这种非常特殊的情况下并不是这样。 - Marladu
1
上周我制作的简单演示(在第一篇帖子中粘贴)是为了向客户展示即使是旧的烂笔记本电脑视频卡也可以将大量精灵绘制到屏幕上。我尽可能简化了代码,以便在此处发布它,以便我们可以讨论glDrawArrays与glBegin/End之间的区别,如果我忘记提到其中一个要求,请原谅!仍然有趣的是找到一个可能违反传统的例外情况,即glDrawArrays并不总是优于glBegin/glEnd。 - Marladu
@Marladu:啊,我刚看到gl_sprite_id的单一值,以为你只有一个纹理。每个精灵都有一个纹理会改变很多事情 :) - genpfault
1
非常抱歉,我实际上将其删除以便尽可能专注于glBegin/end与glDrawarrays的测试,并在渲染循环中尽可能少地使用它。由于我太蠢了,所以忘记在有关代码的帖子中提及它,因此我的道歉是让您使用缺少非常重要信息的工作。非常抱歉!!! - Marladu
@Marladu:没问题,我喜欢为SO编写小的演示程序 :) - genpfault

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接