GLFW/OpenGL 在贪吃蛇游戏中的性能问题
Performance problems with GLFW/OpenGL in a snake game
我开始使用 GLFW 和 QT 在 OpenGL 中构建游戏 IDE,在我完成绘图系统后,我测试了它,它非常慢。
这是代码:Here(包括 x64 windows 库)
我在代码中看到一些东西我可以多线程,一个例子是绘图函数,因为 opengl 坐标从屏幕中间的原点开始,我必须为每个象限制作一个绘图循环(- 1,1| 1,-1| 1,1| -1,-1) 这些循环我可以多线程但我认为它不会创造任何奇迹。
GLFW + 绘图代码
#include <GL/glew.h>
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <iostream>
#include <fstream>
#include <glm.hpp>
#include "campo.h"
#include "ponto.h"
#define GLFW_INCLUDE_GLU
#include <GLFW/glfw3.h>
using namespace std;
using namespace glm;
static void error_callback(int error, const char* description)
{
fputs(description, stderr);
}
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}
GLFWwindow* iniciaGL(){ //---- Funtion to start GLFW
GLFWwindow* window;
glfwSetErrorCallback(error_callback);
if (!glfwInit())
exit(EXIT_FAILURE);
window = glfwCreateWindow(700, 700, "Snake", NULL, NULL);
if (!window)
{
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
glfwSetKeyCallback(window, key_callback);
return window;
}
int main()
{
GLFWwindow* window = iniciaGL();
Campo camp; // ---- Campo is the snake field
Campo::campo **cmp;
cmp = camp.GetCampo(); //-- Get the pointer to the matrix (Field) that contains the information of each square in the field
int ts=0,tst=0;
while (!glfwWindowShouldClose(window)) // --- Game loop
{
float ratio;
int width, height;
glfwGetFramebufferSize(window, &width, &height);
ratio = width / (float) height;
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//glOrtho(-ratio, ratio, -1.f, 1.f, 1.f, -1.f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//---------- TESTING
camp.apple(); //----- Puts an "apple" in a random position in the field
//----- this makes random red squares appear in the screen
/*
ts++;
if(ts > 39){ //---- this will should fill the screen by inserting on red squeare at time
ts=0;
if(tst > 39){
tst=0;
}else{
tst++;
}
}
camp.mudaBloco(ts,tst,camp.APPLE);
*/
//----------------
//---- since opengl grid has the origin in the middle of the screen, i have to draw in each quadrant (++,+-,-+,--)
//---- thats why there is 4 fors below
for(int a=0;a<(camp.Getlar())/2;a++){//line -+
for(int b=0;b<(camp.Getalt())/2;b++){//column
glBegin(GL_POLYGON);
switch (cmp[a][b])
{
case camp.NADA:
glColor3f(0,0,0);
break;
case camp.SNAKE:
glColor3f(0,0.85f,0.20f);
break;
case camp.APPLE:
glColor3f(1.0f,0,0.1f);
break;
case camp.MURO:
glColor3f(0.3f,0.3f,0.3f);
break;
}
glVertex2f(-1.0f+a/(camp.Getlar()/2.0f), 1.0f-b/(camp.Getalt()/2.0f) );
glVertex2f(-1.0f+a/(camp.Getlar()/2.0f), 1.0f-(1+b)/(camp.Getalt()/2.0f) );
glVertex2f(-1.0f+((1+a)/(camp.Getlar()/2.0f)), 1.0f-(1+b)/(camp.Getalt()/2.0f));
glVertex2f(-1.0f+((1+a)/(camp.Getlar()/2.0f)), 1.0f-b/(camp.Getalt()/2.0f ));
glEnd();
}
}
for(int a=0;a<(camp.Getlar())/2;a++){//line ++
for(int b=0;b<(camp.Getalt())/2;b++){//column
glBegin(GL_POLYGON);
switch (cmp[a+camp.Getlar()/2][b])
{
case camp.NADA:
glColor3f(0,0,0);
break;
case camp.SNAKE:
glColor3f(0,0.85f,0.20f);
break;
case camp.APPLE:
glColor3f(1.0f,0,0.1f);
break;
case camp.MURO:
glColor3f(0.3f,0.3f,0.3f);
break;
}
glVertex2f(a/(camp.Getlar()/2.0f), 1.0f-b/(camp.Getalt()/2.0f) );
glVertex2f(a/(camp.Getlar()/2.0f), 1.0f-(1+b)/(camp.Getalt()/2.0f) );
glVertex2f(((1+a)/(camp.Getlar()/2.0f)), 1.0f-(1+b)/(camp.Getalt()/2.0f));
glVertex2f(((1+a)/(camp.Getlar()/2.0f)), 1.0f-b/(camp.Getalt()/2.0f) );
glEnd();
}
}
for(int a=0;a<(camp.Getlar())/2;a++){//line +-
for(int b=0;b<(camp.Getalt())/2;b++){//column
glBegin(GL_POLYGON);
switch (cmp[a+camp.Getlar()/2][b+camp.Getlar()/2])
{
case camp.NADA:
glColor3f(0,0,0);
break;
case camp.SNAKE:
glColor3f(0,0.85f,0.20f);
break;
case camp.APPLE:
glColor3f(1.0f,0,0.1f);
break;
case camp.MURO:
glColor3f(0.3f,0.3f,0.3f);
break;
}
glVertex2f(a/(camp.Getlar()/2.0f), -b/(camp.Getalt()/2.0f) );
glVertex2f(a/(camp.Getlar()/2.0f), -(1+b)/(camp.Getalt()/2.0f) );
glVertex2f(((1+a)/(camp.Getlar()/2.0f)), -(1+b)/(camp.Getalt()/2.0f));
glVertex2f(((1+a)/(camp.Getlar()/2.0f)), -b/(camp.Getalt()/2.0f ));
glEnd();
}
}
for(int a=0;a<(camp.Getlar())/2;a++){//line --
for(int b=0;b<(camp.Getalt())/2;b++){//column
glBegin(GL_POLYGON);
switch (cmp[a][b+camp.Getlar()/2])
{
case camp.NADA:
glColor3f(0,0,0);
break;
case camp.SNAKE:
glColor3f(0,0.85f,0.20f);
break;
case camp.APPLE:
glColor3f(1.0f,0,0.1f);
break;
case camp.MURO:
glColor3f(0.3f,0.3f,0.3f);
break;
}
glVertex2f(-1.0f+a/(camp.Getlar()/2.0f), -b/(camp.Getalt()/2.0f) );
glVertex2f(-1.0f+a/(camp.Getlar()/2.0f), -(1+b)/(camp.Getalt()/2.0f) );
glVertex2f(-1.0f+((1+a)/(camp.Getlar()/2.0f)), -(1+b)/(camp.Getalt()/2.0f));
glVertex2f(-1.0f+((1+a)/(camp.Getlar()/2.0f)), -b/(camp.Getalt()/2.0f ));
glEnd();
}
}
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
return 0;
}
Campo.CPP
#include "campo.h"
using namespace std;
Campo::Campo()
{
cmp = new campo*[Campo::lar];
for (int x = 0; x < Campo::lar; ++x) {
cmp[x] = new campo[Campo::alt];
for (int y = 0; y < Campo::alt; ++y) {
cmp[x][y] = NADA;
}
}
cout << "Campo iniciado" << endl;
}
void Campo::mudaBloco(int x, int y,campo val){
cmp[x][y]=val;
}
Campo::~Campo()
{
}
Campo::campo** Campo::GetCampo(){
return cmp;
}
void Campo::vitoria(){
cout << "Parabéns" << endl;
system("pause");
exit(0);
}
int Campo::Getlar(){
return lar;
}
int Campo::Getalt(){
return alt;
}
void Campo::Setalt(int alt)
{
this->alt = alt;
}
void Campo::Setlar(int lar){
this->lar = lar;
}
void Campo::apple(){
ponto hold;
bool vit=true;
for (int a = 0; a < Campo::lar; ++a) {
for (int b = 0; b < Campo::alt; ++b) {
if(cmp[a][b]==NADA){
hold.x=a;
hold.y=b;
campo_livre.push_back(hold);
vit = false;
}
}
}
if(vit)
vitoria();
srand(apples+time(NULL));
ponto novo = campo_livre.at(rand()%campo_livre.size());
mudaBloco(novo.x,novo.y,APPLE);
apples++;
campo_livre.clear();
}
Campo.h
#ifndef CAMPO_H
#define CAMPO_H
#pragma once
#include <vector>
#include <iostream>
#include <cstdlib>
#include <ctime>
class Campo
{
public:
enum campo{NADA,SNAKE,APPLE,MURO}; // field may only contain these types {nothing, snake, Apple, wall}
Campo(); //--- constructor
~Campo();//--- destructor
campo **GetCampo(); //--- Field matrix getter
void mudaBloco(int x, int y, campo val); //---- function that changes the block in a coordinates
void apple(); //--- puts an apple at a "random" position
void Setlar(int lar); //---- set max size x of the grid
void Setalt(int alt); //---- set max size y of the grid
int Getlar();
int Getalt();
struct ponto{ //---- this structure represents a dot in the field with the respective coordinates
int x=0;
int y=0;
};
private:
int apples=0; //----- current number of apples
int alt=40,lar=40; //---- default max x,y size
campo **cmp; //--- Field matrix
void vitoria(); //--- win function
std::vector<ponto> campo_livre; //-- this vector maps the free spaces in the field, so instead of putting a random apple in the field, we put randomly only in the free spaces
};
#endif // CAMPO_H
对于 QT 用户
TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt
SOURCES += main.cpp \
campo.cpp
QMAKE_CXXFLAGS += -std=c++14 -Ofast
INCLUDEPATH += C:/glfw/include
INCLUDEPATH += C:/glew/include
INCLUDEPATH += C:/glm/glm
LIBS +=-L"C:/glfw/build_r/src"
LIBS += -lglfw3 -lopengl32 -lglu32 -lgdi32
LIBS += -L"C:/glew/lib/Release/x64"
LIBS += -lglew32
include(deployment.pri)
qtcAddDeployment()
HEADERS += \
campo.h
如何更改我的代码以使其 运行 速度更快?
多线程 OpenGL 几乎不值得用于主要渲染目的,因为大多数性能密集型的东西都发生在您的视频卡上:无论您使用多少线程,您实际上只有 1 个视频卡可以完成工作。如果你想加载一些外部资源,例如纹理,你可以使用多线程。仍然有 1 个视频卡,但工作负载在线程之间平均平衡,并且您不必看到渲染循环挂起 3 秒,因为您正在加载纹理。
您的程序运行缓慢的原因之一是因为您正在使用即时模式。立即模式是一种古老而过时的绘图方式。您可以通过在 glBegin
和 glEnd
之间插入一堆 glVertex
来使用它。这样做的目的是,当您绘制某些内容时,您的 CPU 会将所有模型数据发送到 GPU,即使数据没有改变(大多数游戏的数据基本上不会改变)。相反,您想要做的是使用 VBO。使用 VBO 基本上就是告诉 OpenGL:"Hey I got this enormous amount of data, I'm gonna call it Bob, so remember it"。然后从那一刻起你可以告诉 opengl 到 "Draw bob" 并且它会这样做而不会每秒来回发送 60 次所有数据。从立即模式切换基本上是真正的 OpenGL 开始的地方,因此它有相当大的学习曲线。但这一切都得到了很好的记录和解释。
现在可能还有其他原因导致您的程序运行缓慢,但这很可能是主要原因。
要查看即时模式和保留模式之间的良好解释和转换,请参阅此 link:What does "immediate mode" mean in OpenGL?
我开始使用 GLFW 和 QT 在 OpenGL 中构建游戏 IDE,在我完成绘图系统后,我测试了它,它非常慢。
这是代码:Here(包括 x64 windows 库)
我在代码中看到一些东西我可以多线程,一个例子是绘图函数,因为 opengl 坐标从屏幕中间的原点开始,我必须为每个象限制作一个绘图循环(- 1,1| 1,-1| 1,1| -1,-1) 这些循环我可以多线程但我认为它不会创造任何奇迹。
GLFW + 绘图代码
#include <GL/glew.h>
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <iostream>
#include <fstream>
#include <glm.hpp>
#include "campo.h"
#include "ponto.h"
#define GLFW_INCLUDE_GLU
#include <GLFW/glfw3.h>
using namespace std;
using namespace glm;
static void error_callback(int error, const char* description)
{
fputs(description, stderr);
}
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}
GLFWwindow* iniciaGL(){ //---- Funtion to start GLFW
GLFWwindow* window;
glfwSetErrorCallback(error_callback);
if (!glfwInit())
exit(EXIT_FAILURE);
window = glfwCreateWindow(700, 700, "Snake", NULL, NULL);
if (!window)
{
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
glfwSetKeyCallback(window, key_callback);
return window;
}
int main()
{
GLFWwindow* window = iniciaGL();
Campo camp; // ---- Campo is the snake field
Campo::campo **cmp;
cmp = camp.GetCampo(); //-- Get the pointer to the matrix (Field) that contains the information of each square in the field
int ts=0,tst=0;
while (!glfwWindowShouldClose(window)) // --- Game loop
{
float ratio;
int width, height;
glfwGetFramebufferSize(window, &width, &height);
ratio = width / (float) height;
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//glOrtho(-ratio, ratio, -1.f, 1.f, 1.f, -1.f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
//---------- TESTING
camp.apple(); //----- Puts an "apple" in a random position in the field
//----- this makes random red squares appear in the screen
/*
ts++;
if(ts > 39){ //---- this will should fill the screen by inserting on red squeare at time
ts=0;
if(tst > 39){
tst=0;
}else{
tst++;
}
}
camp.mudaBloco(ts,tst,camp.APPLE);
*/
//----------------
//---- since opengl grid has the origin in the middle of the screen, i have to draw in each quadrant (++,+-,-+,--)
//---- thats why there is 4 fors below
for(int a=0;a<(camp.Getlar())/2;a++){//line -+
for(int b=0;b<(camp.Getalt())/2;b++){//column
glBegin(GL_POLYGON);
switch (cmp[a][b])
{
case camp.NADA:
glColor3f(0,0,0);
break;
case camp.SNAKE:
glColor3f(0,0.85f,0.20f);
break;
case camp.APPLE:
glColor3f(1.0f,0,0.1f);
break;
case camp.MURO:
glColor3f(0.3f,0.3f,0.3f);
break;
}
glVertex2f(-1.0f+a/(camp.Getlar()/2.0f), 1.0f-b/(camp.Getalt()/2.0f) );
glVertex2f(-1.0f+a/(camp.Getlar()/2.0f), 1.0f-(1+b)/(camp.Getalt()/2.0f) );
glVertex2f(-1.0f+((1+a)/(camp.Getlar()/2.0f)), 1.0f-(1+b)/(camp.Getalt()/2.0f));
glVertex2f(-1.0f+((1+a)/(camp.Getlar()/2.0f)), 1.0f-b/(camp.Getalt()/2.0f ));
glEnd();
}
}
for(int a=0;a<(camp.Getlar())/2;a++){//line ++
for(int b=0;b<(camp.Getalt())/2;b++){//column
glBegin(GL_POLYGON);
switch (cmp[a+camp.Getlar()/2][b])
{
case camp.NADA:
glColor3f(0,0,0);
break;
case camp.SNAKE:
glColor3f(0,0.85f,0.20f);
break;
case camp.APPLE:
glColor3f(1.0f,0,0.1f);
break;
case camp.MURO:
glColor3f(0.3f,0.3f,0.3f);
break;
}
glVertex2f(a/(camp.Getlar()/2.0f), 1.0f-b/(camp.Getalt()/2.0f) );
glVertex2f(a/(camp.Getlar()/2.0f), 1.0f-(1+b)/(camp.Getalt()/2.0f) );
glVertex2f(((1+a)/(camp.Getlar()/2.0f)), 1.0f-(1+b)/(camp.Getalt()/2.0f));
glVertex2f(((1+a)/(camp.Getlar()/2.0f)), 1.0f-b/(camp.Getalt()/2.0f) );
glEnd();
}
}
for(int a=0;a<(camp.Getlar())/2;a++){//line +-
for(int b=0;b<(camp.Getalt())/2;b++){//column
glBegin(GL_POLYGON);
switch (cmp[a+camp.Getlar()/2][b+camp.Getlar()/2])
{
case camp.NADA:
glColor3f(0,0,0);
break;
case camp.SNAKE:
glColor3f(0,0.85f,0.20f);
break;
case camp.APPLE:
glColor3f(1.0f,0,0.1f);
break;
case camp.MURO:
glColor3f(0.3f,0.3f,0.3f);
break;
}
glVertex2f(a/(camp.Getlar()/2.0f), -b/(camp.Getalt()/2.0f) );
glVertex2f(a/(camp.Getlar()/2.0f), -(1+b)/(camp.Getalt()/2.0f) );
glVertex2f(((1+a)/(camp.Getlar()/2.0f)), -(1+b)/(camp.Getalt()/2.0f));
glVertex2f(((1+a)/(camp.Getlar()/2.0f)), -b/(camp.Getalt()/2.0f ));
glEnd();
}
}
for(int a=0;a<(camp.Getlar())/2;a++){//line --
for(int b=0;b<(camp.Getalt())/2;b++){//column
glBegin(GL_POLYGON);
switch (cmp[a][b+camp.Getlar()/2])
{
case camp.NADA:
glColor3f(0,0,0);
break;
case camp.SNAKE:
glColor3f(0,0.85f,0.20f);
break;
case camp.APPLE:
glColor3f(1.0f,0,0.1f);
break;
case camp.MURO:
glColor3f(0.3f,0.3f,0.3f);
break;
}
glVertex2f(-1.0f+a/(camp.Getlar()/2.0f), -b/(camp.Getalt()/2.0f) );
glVertex2f(-1.0f+a/(camp.Getlar()/2.0f), -(1+b)/(camp.Getalt()/2.0f) );
glVertex2f(-1.0f+((1+a)/(camp.Getlar()/2.0f)), -(1+b)/(camp.Getalt()/2.0f));
glVertex2f(-1.0f+((1+a)/(camp.Getlar()/2.0f)), -b/(camp.Getalt()/2.0f ));
glEnd();
}
}
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
return 0;
}
Campo.CPP
#include "campo.h"
using namespace std;
Campo::Campo()
{
cmp = new campo*[Campo::lar];
for (int x = 0; x < Campo::lar; ++x) {
cmp[x] = new campo[Campo::alt];
for (int y = 0; y < Campo::alt; ++y) {
cmp[x][y] = NADA;
}
}
cout << "Campo iniciado" << endl;
}
void Campo::mudaBloco(int x, int y,campo val){
cmp[x][y]=val;
}
Campo::~Campo()
{
}
Campo::campo** Campo::GetCampo(){
return cmp;
}
void Campo::vitoria(){
cout << "Parabéns" << endl;
system("pause");
exit(0);
}
int Campo::Getlar(){
return lar;
}
int Campo::Getalt(){
return alt;
}
void Campo::Setalt(int alt)
{
this->alt = alt;
}
void Campo::Setlar(int lar){
this->lar = lar;
}
void Campo::apple(){
ponto hold;
bool vit=true;
for (int a = 0; a < Campo::lar; ++a) {
for (int b = 0; b < Campo::alt; ++b) {
if(cmp[a][b]==NADA){
hold.x=a;
hold.y=b;
campo_livre.push_back(hold);
vit = false;
}
}
}
if(vit)
vitoria();
srand(apples+time(NULL));
ponto novo = campo_livre.at(rand()%campo_livre.size());
mudaBloco(novo.x,novo.y,APPLE);
apples++;
campo_livre.clear();
}
Campo.h
#ifndef CAMPO_H
#define CAMPO_H
#pragma once
#include <vector>
#include <iostream>
#include <cstdlib>
#include <ctime>
class Campo
{
public:
enum campo{NADA,SNAKE,APPLE,MURO}; // field may only contain these types {nothing, snake, Apple, wall}
Campo(); //--- constructor
~Campo();//--- destructor
campo **GetCampo(); //--- Field matrix getter
void mudaBloco(int x, int y, campo val); //---- function that changes the block in a coordinates
void apple(); //--- puts an apple at a "random" position
void Setlar(int lar); //---- set max size x of the grid
void Setalt(int alt); //---- set max size y of the grid
int Getlar();
int Getalt();
struct ponto{ //---- this structure represents a dot in the field with the respective coordinates
int x=0;
int y=0;
};
private:
int apples=0; //----- current number of apples
int alt=40,lar=40; //---- default max x,y size
campo **cmp; //--- Field matrix
void vitoria(); //--- win function
std::vector<ponto> campo_livre; //-- this vector maps the free spaces in the field, so instead of putting a random apple in the field, we put randomly only in the free spaces
};
#endif // CAMPO_H
对于 QT 用户
TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt
SOURCES += main.cpp \
campo.cpp
QMAKE_CXXFLAGS += -std=c++14 -Ofast
INCLUDEPATH += C:/glfw/include
INCLUDEPATH += C:/glew/include
INCLUDEPATH += C:/glm/glm
LIBS +=-L"C:/glfw/build_r/src"
LIBS += -lglfw3 -lopengl32 -lglu32 -lgdi32
LIBS += -L"C:/glew/lib/Release/x64"
LIBS += -lglew32
include(deployment.pri)
qtcAddDeployment()
HEADERS += \
campo.h
如何更改我的代码以使其 运行 速度更快?
多线程 OpenGL 几乎不值得用于主要渲染目的,因为大多数性能密集型的东西都发生在您的视频卡上:无论您使用多少线程,您实际上只有 1 个视频卡可以完成工作。如果你想加载一些外部资源,例如纹理,你可以使用多线程。仍然有 1 个视频卡,但工作负载在线程之间平均平衡,并且您不必看到渲染循环挂起 3 秒,因为您正在加载纹理。
您的程序运行缓慢的原因之一是因为您正在使用即时模式。立即模式是一种古老而过时的绘图方式。您可以通过在 glBegin
和 glEnd
之间插入一堆 glVertex
来使用它。这样做的目的是,当您绘制某些内容时,您的 CPU 会将所有模型数据发送到 GPU,即使数据没有改变(大多数游戏的数据基本上不会改变)。相反,您想要做的是使用 VBO。使用 VBO 基本上就是告诉 OpenGL:"Hey I got this enormous amount of data, I'm gonna call it Bob, so remember it"。然后从那一刻起你可以告诉 opengl 到 "Draw bob" 并且它会这样做而不会每秒来回发送 60 次所有数据。从立即模式切换基本上是真正的 OpenGL 开始的地方,因此它有相当大的学习曲线。但这一切都得到了很好的记录和解释。
现在可能还有其他原因导致您的程序运行缓慢,但这很可能是主要原因。
要查看即时模式和保留模式之间的良好解释和转换,请参阅此 link:What does "immediate mode" mean in OpenGL?