【问题标题】:glDrawArrays never faster then glBegin/glEnd for small arrays?对于小数组,glDrawArrays 永远不会比 glBegin/glEnd 快吗?
【发布时间】: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 在这种情况下我愿意学习!

标签: delphi opengl


【解决方案1】:

我实际上在 13 年前使用 OpenGL,我什至可以告诉你,如果你的应用程序结构正确,顶点数组通常会更快。

当时我们没有顶点缓冲区对象,但我们有交错顶点数组(我的字面意思是glInterleavedArrays (...))和Compiled Vertex Arrays。 NVIDIA 后来创建了一个扩展 (Vertex Array Ranges),允许将顶点数据存储在虚拟内存中(该虚拟内存的地址范围旨在实现高效的 DMA 传输)。

AMD,当时仍被称为 ATI,也有自己的扩展名为 Vertex Array Object,可以提高顶点阵列性能(通过在服务器端存储顶点数据)。不要将 AMD 的扩展与现代 OpenGL 中的 VAO 混淆。实际上,AMD 的扩展为 Vertex Buffer Objects 奠定了基础,但不幸的是,它与完全不相关的东西共享了它的名字。

现在,我刚才讨论的所有内容实际上都描绘了 OpenGL 中顶点数组的演变。特别是,他们表明趋势是 (1) 将顶点数据存储在用户定义的内存组织中 (2) 存储在服务器 (GPU) 内存中,以最大限度地提高尽可能少地调用 API 和 (3)。立即模式 (glBegin / glEnd) 违反了所有这些原则,您在立即模式下所能做的就是将命令放入显示列表并希望驱动程序注意点 23


更新:

另外请注意,由于我们讨论的硬件可以追溯到 OpenGL 1.1 时代,因此图形硬件并不总是处理顶点变换。长期以来,“GPU”的祖先只在 CPU 上处理加速光栅化和顶点变换。在我们拥有可以实现整个图形管道的 GPU 之前,顶点和光栅化之间的分离意味着将顶点传递到管道中的效率实际上并不重要,因为有些工作是在 CPU 上完成的。一旦出现硬件 T&L,服务器端的顶点就变得至关重要。

这就是你问题的症结所在。您的软件没有以可以从顶点变换的硬件加速中受益的方式进行设置。您正在 CPU 上进行所有转换,并在需要更改的任何时候向 GPU 发送新数据。现代应用程序使用顶点着色器和静态顶点数据来做同样的事情,同时最大限度地减少每帧需要发送到 GPU 的数据量。 在现代软件中,大多数变换都可以通过更新一个或两个 4x4 矩阵来完成,以便在顶点着色器中使用。

除此之外,除非您的软件是顶点绑定的,否则提高顶点效率不会显着提高性能。 “Sprite blitting”对我来说听起来更像是一个片段绑定场景,特别是如果 sprite 是 alpha 混合的。

【讨论】:

  • 很酷的东西。当我稍后发布这个小演示的完整源代码时,希望你能帮助弄清楚我是否/我做错了什么,因为结果看起来很奇怪,但是涉及的代码太少了,我很难弄清楚可能会有什么不同。
  • 感谢这篇文章中的所有努力,特别是因为我从中学到了一些东西,但这实际上并没有解决这种情况。这是关于使用 glBegin(quads)/(glTex/glVertex)x4/glEnd 总是至少与 glDrawArrays 一样快的情况。这些知识只对处于这种特定情况的人有用(必须使用新硬件但也可以使用 10 年以上的硬件来 blit 独特的 alpha sprites)。值得注意的是,在 win98 或 windows8 机器上,500 个 sprite 的性能没有差异,但对于数千个 sprite,glBegin/glEnd 的速度是原来的两倍。
  • @Marladu:然而,这是相关的,因为当您将glDrawArrays (...) 与客户端内存一起使用时,GL 必须确保glVertexPointer (...) 指向的数据在命令完成之前不会更改。这意味着您要在 CPU 和 GPU 之间引入一个同步点。 glBegin (...)glEnd (...) 之间的操作每次调用它们时都会创建新数据,因此 GL 在接受另一个命令之前不必等待任何操作完成或复制数据。这一切都归结为指向 GL 无法完全控制的内存,这已通过 VBO 解决。
  • 好吧抱歉我忘了提到在这种情况下我必须为每个精灵设置一个独特的纹理。我将该信息添加到其他答案和第一篇文章中。如果误导了您,我们深表歉意!
  • 我刚看了你的评论,再次感谢你花时间尝试教我,我只是不太了解这一切,我只是工作了一堆,然后在服用后使用最有效的方法测量。我很感激你试图向我展示原因,但这在我理解能力的边缘。希望其他人能够从我的实用知识和您的理论知识中学到一些东西。
【解决方案2】:
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;
}

【讨论】:

  • 哇老兄,我真的很感谢你的努力,非常感谢!无论如何,这里的大问题是,在这种情况下,我不能批处理精灵,因为每个精灵都是唯一的并且可以独立更改(包括它们的尺寸),所以没有精灵表,所以我必须为每个精灵调用 glBindTexture,因此,可以'不要通过 glDrawArrays 一次批量发送超过 1 个精灵。我仍然希望 glDrawArrays 以这种方式比 glBegin(quads)/.../glEnd 更快,因为这意味着 1 个 OpenGL 调用与 10 个 OpenGL 调用,但在这种非常具体的情况下并非如此。
  • 我上周整理的愚蠢演示(粘贴在第一篇文章中)向客户展示了即使是旧的蹩脚笔记本视频卡也可以在屏幕上显示大量精灵。我尽可能简化了代码以将其发布在这里以放置 glDrawArrays 与 glBegin/End 以便我们可以对此感到厌烦,抱歉,如果我忘记提及其中一个要求!发现可能违反 glDrawArrays 始终优于 glBegin/glEnd 的约定的异常情况仍然很有趣。
  • @Marladu:啊,我刚刚看到gl_sprite_id 的单个值,并假设您只有一个纹理。每个精灵的纹理改变了很多东西:)
  • 是的,超级抱歉,我实际上删除了它以尽可能多地专注于glBegin / end vs glDrawarrays的测试,并且尽可能少地在渲染循环中。由于我是一个愚蠢的人,所以我忘了在帖子中提到它,所以我很抱歉让你处理丢失的非常重要的信息。对不起!!!!
  • @Marladu:不,没问题。我喜欢为 SO 编写一些小演示程序 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-15
  • 1970-01-01
  • 2015-12-02
  • 1970-01-01
相关资源
最近更新 更多