对于客户端项目,我需要一个在旧硬件上工作的简单精灵闪电战。OpenGL 1.1似乎是一个简单的答案,因为我有可以重用的旧代码。
无论如何,一切都很好,但令我大吃一惊的是,事实证明,对于移动的子画面(因此在正交投影中渲染纹理四边形),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单元的代码。使用表单创建一个新项目,在其上添加timer1(启用,间隔=1)和timer2(禁用,间隔=1)对象,将单元代码替换为this,插入表单事件:doubleclick/keydown/resize/销毁。请注意,这是在旧版本的delphi中编译的,因此在单元的开头添加了一些opengl标头。此外,按左/右更改精灵数量,并在glDrawArray和glStart in/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:=ranhtml" target="_blank">dom(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上。抱歉遗漏误导!
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;
}
我实际上是在13年前使用OpenGL的,即使在那时我也可以告诉你,如果您的应用程序结构正确,顶点数组通常会更快。
那时我们还没有顶点缓冲区对象,但是我们有交错顶点数组(我的字面意思是glInterleavedArray(...)
)和编译顶点数组。英伟达后来创建了一个扩展(顶点数组范围),允许顶点数据存储在虚拟内存中(该虚拟内存的地址范围旨在允许高效的DMA传输)。
AMD,当时仍然被称为ATI,也有自己的扩展,它提高了顶点数组性能(通过在服务器端存储顶点数据),称为顶点数组对象。不要将AMD的扩展与我们在现代OpenGL中所说的VAO混淆。实际上,AMD的扩展为顶点缓冲区对象奠定了基础,它只是不幸地与完全不相关的东西共享它的名字。
现在,我刚刚讨论的所有内容实际上都描绘了OpenGL中顶点html" target="_blank">数组的演变。特别是,它们表明趋势是(1)将顶点数据存储在用户定义的内存组织中(2)存储在服务器(GPU)内存中以获得最大的重用,以及(3)尽可能少的API调用。即时模式(glStart in
/glEnd
)违反了所有这些原则,您可以使用即时模式将命令放入显示列表并希望驱动程序处理第2点和第3点。
还要注意,因为我们谈论的硬件可以追溯到OpenGL 1.1时代,图形硬件并不总是处理顶点变换。在很长一段时间里,“GPU”的祖先只加速光栅化,顶点变换是在CPU上处理的。在我们拥有可以实现整个图形管道的GPU之前,顶点和光栅化之间的分离意味着顶点传递到管道中的效率并不重要,因为一些工作是在CPU上完成的。一次硬件测试
这就是问题的症结所在。您的软件的设置方式不会从顶点变换的硬件加速中受益。您正在CPU上进行所有转换,并在需要更改时随时向GPU发送新数据。现代应用程序使用顶点着色器和静态顶点数据来做同样的事情,同时最大限度地减少每帧需要发送到GPU的数据量。在现代软件中,大多数转换都可以通过更新一两个4x4矩阵来完成,以便在顶点着色器中使用。
最重要的是,除非你的软件是顶点受限的,否则提高顶点效率不会显著提高性能。“精灵blitting”对我来说听起来更像是一个碎片绑定的场景,特别是如果精灵是alpha混合的。
我希望TPP是一个数组,每个阈值都有TPP值。打印应该是这样的:TPP是:n1,n2。。。
基本上,我要问的是给定一个正方形2D阵列和一个有效的补丁大小(2D子阵列的大小),我将如何做到这一点。最终,我不需要以任何方式存储子阵列,我只需要找到每个子阵列的中值并将它们存储在一个一维阵列中。中值和存储到新阵列对我来说很简单,我只是不知道如何处理原始2D阵列并正确拆分它。我已经尝试了几次,但一直出现越界错误。我有一个4x4: 我需要像这样拆分它 < code>[1,2] [3,4] [2,3]
本文向大家介绍JavaScript中数组slice和splice的对比小结,包括了JavaScript中数组slice和splice的对比小结的使用技巧和注意事项,需要的朋友参考一下 前言 今天重温了一下Javascript,看到了数组的方法,其中有两个比较相似的方法——splice和splice,看着很像,就是多了一个p,但是用法却相当不一样。 在使用中,可以通过选择一个具有强语义表达性的 AP
我一直认为numpy数组比list更紧凑,占用的内存更少,但是,对于三维float64 np数组, 输出是,, 列表占用的内存要小得多。使用?如果是,我能做些什么来提高np数组内存使用率吗? ###################### 使用pympler@J_H(pympler似乎不能处理列表中的数组,比如list(一个3-D数组) 谢谢大家!!
问题内容: 我试图弄清楚为什么没有触发我。这是相关控制器的片段: 在我看来,显然是正确更新的,因为我的长度范围是这样的: 我想念什么? 问题答案: 尝试或。 默认情况下,$ watch 不检查对象是否相等,而仅作为参考。因此,将总是简单地返回相同的数组引用,并且不会改变。 更新: Angular v1.1.4添加了一个$ watchCollection() 方法来处理这种情况: Shallow监视
我一直在研究一个深度学习库,自己写作。在矩阵运算中,获得最佳性能对我来说是一个关键。我一直在研究编程语言及其对数字运算的性能。过了一段时间,我发现C#SIMD具有与C++SIMD非常相似的性能。所以,我决定用C#编写这个库。 首先,我测试了C#SIMD(我测试了很多东西,但是这里不写了)。我注意到,当使用较小的数组时,它的工作效果要好得多。当使用较大的数组时,效率不高。我觉得很可笑。通常情况下,当