OpenGL에서의 디버깅은 굉장히 까다롭다.
// Draw the triangle !
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
glDrawElements(GL_TRIANGLES, 6, GL_INT, nullptr);
//DrawArrays에서 DrawElements로 변했다.
//대부분의 상황에서 index buffer가 쓰이기 때문에 DrawElements가 더 자주 쓰인다.
//또 앞서 여러차례 보았듯이 unsigned int를 사용했다는 걸 알 수 있는데,
//그냥 int를 사용하면 화면이 검게 나오며 아무것도 draw되지 않는다!
//(참고: unsinged char등을 사용해 크기를 줄일수야 있지만 최대 정점수가 줄기 때문에 그냥 int를 사용하는 게 권장된다)
지난 시간에 작성한 코드의 일부인데, Unsigned int 대신 int를 사용하게 된다면 화면이 까만 상태로 아무 것도 렌더링되지 않는다.
OpenGL은 에러가 어디서 났는지 알려주지 않으므로 이를 확인하는 일종의 디버거를 개발자가 직접 작성해야 한다.
이러한 상황에서 OpenGL에서는 주로 glGetError 또는 glDebugMessageCallback 두 함수를 사용하는데,
전자는 다소 원시적이고 불편한 대신 구버전까지 호환되고, 후자는 더 편한 대신 최신 버전에서만 지원이 가능하다는 특징이 있다.
오늘은 그중 glGetError에 대해 자세히 알아보겠다.
glGetError
// Include GLEW
#include <GL/glew.h>
// Include GLFW
#include <GLFW/glfw3.h>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <assert.h> // assert는 false가 인풋으로 주어질 때 프로그램을 종료한다.
// Visual Studio에서는 __debugerror() 사용 가능하다.
// 매크로 함수1:
#define ASSERT(x) if (!(x)) assert(false)
// 매크로 함수2:
#define GLCall(x) GLClearError();\ // 일단 에러가 없음을 보증
/*
주의: 여기서 x는 변수가 아니라, 매크로 함수에 옮겨넣을 문자열을 나타낸다고 생각하면 된다.
즉 GlCall(add(1,3))이라 적었다면 컴파일 타임에 다음과 같이 변경된다.
GLClearError();
add(1,3);
ASSERT(GLCheckError())
*/
x;\
ASSERT(GLCheckError()) // 방금 실행한 x 부분에서 에러가 발생했는지 확인한다.
static void GLClearError()
{
/*
glGetError()는 호출 이전까지의 모든 에러들을 전부 수집해 반환하기 때문에
어떤 특정 위치에서 에러가 발생하는지를 확인하려면 그 이전까지의 에러들을 Clear해줄 필요가 있다.
여기서 Clear해준다는 표현이 조금 애매한데, 정확히는 이전까지 아무 에러도 없었다는 것을 검증할 필요가 있다.
그래야 에러가 발생했을 시 구체적인 발생지점을 짚어낼 수 있다.
*/
while (glGetError() != GL_NO_ERROR);
}
static bool GLCheckError()
{
/*
에러 발견시 false를 반환한다.
그 외엔 true를 반환한다.
glGetError()는 에러 존재 시 에러코드(Enum)을 반환한다.
참고: 아무 에러도 없을 때 반환하는 enum인 GL_NO_ERROR은 0으로 취급된다.
참고: 어떤 에러코드가 각각 어떤 에러와 대응하는지를 알아보려면 opengl헤더인 <glew.h>에 가서
해당 에러코드를 Hex형태로 (0x23 형태) ctrl+f 해주면 된다.
때문에 이를 조금 보기 쉽게 바꿔주려면 enum을 로그 메세지로 변환하는 코드를 추가하면 좋다.
*/
while (GLenum error = glGetError())
{
std::cout << "[OpenGL Error] ";
switch(error) {
case GL_INVALID_ENUM :
std::cout << "GL_INVALID_ENUM : An unacceptable value is specified for an enumerated argument.";
break;
case GL_INVALID_VALUE :
std::cout << "GL_INVALID_OPERATION : A numeric argument is out of range.";
break;
case GL_INVALID_OPERATION :
std::cout << "GL_INVALID_OPERATION : The specified operation is not allowed in the current state.";
break;
case GL_INVALID_FRAMEBUFFER_OPERATION :
std::cout << "GL_INVALID_FRAMEBUFFER_OPERATION : The framebuffer object is not complete.";
break;
case GL_OUT_OF_MEMORY :
std::cout << "GL_OUT_OF_MEMORY : There is not enough memory left to execute the command.";
break;
case GL_STACK_UNDERFLOW :
std::cout << "GL_STACK_UNDERFLOW : An attempt has been made to perform an operation that would cause an internal stack to underflow.";
break;
case GL_STACK_OVERFLOW :
std::cout << "GL_STACK_OVERFLOW : An attempt has been made to perform an operation that would cause an internal stack to overflow.";
break;
default :
std::cout << "Unrecognized error" << error;
}
std::cout << std::endl;
return false;
}
return true;
}
struct ShaderProgramSource
{
std::string VertexSource;
std::string FragmentSource;
};
static struct ShaderProgramSource ParseShader(const std::string& filepath)
{
enum class ShaderType
{
NONE = -1, VERTEX = 0, FRAGMENT = 1
};
std::ifstream stream(filepath);
std::string line;
std::stringstream ss[2];
ShaderType type = ShaderType::NONE;
while (getline(stream, line))
{
if (line.find("#shader") != std::string::npos)
{
if (line.find("vertex") != std::string::npos)
type = ShaderType::VERTEX;
else if (line.find("fragment") != std::string::npos)
type = ShaderType::FRAGMENT;
}
else
{
ss[(int)type] << line << '\n';
}
}
return { ss[0].str(), ss[1].str() };
}
static unsigned int CompileShader(unsigned int type, const std::string& source)
{
GLCall( unsigned int id = glCreateShader(type) );
const char* src = source.c_str();
GLCall( glShaderSource(id, 1, &src, nullptr) );
GLCall( glCompileShader(id) );
// Error handling
int result;
GLCall( glGetShaderiv(id, GL_COMPILE_STATUS, &result) );
std::cout << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader compile status: " << result << std::endl;
if ( result == GL_FALSE )
{
int length;
GLCall( glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length) );
char* message = (char*) alloca(length * sizeof(char));
GLCall( glGetShaderInfoLog(id, length, &length, message) );
std::cout
<< "Failed to compile "
<< (type == GL_VERTEX_SHADER ? "vertex" : "fragment")
<< "shader"
<< std::endl;
std::cout << message << std::endl;
GLCall( glDeleteShader(id) );
return 0;
}
return id;
}
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
// create a shader program
unsigned int program = glCreateProgram();
unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);
GLCall( glAttachShader(program, vs) );
GLCall( glAttachShader(program, fs) );
GLCall( glLinkProgram(program) );
GLint program_linked;
GLCall( glGetProgramiv(program, GL_LINK_STATUS, &program_linked) );
std::cout << "Program link status: " << program_linked << std::endl;
if (program_linked != GL_TRUE)
{
GLsizei log_length = 0;
GLchar message[1024];
GLCall( glGetProgramInfoLog(program, 1024, &log_length, message) );
std::cout << "Failed to link program" << std::endl;
std::cout << message << std::endl;
}
GLCall( glValidateProgram(program) );
GLCall( glDeleteShader(vs) );
GLCall( glDeleteShader(fs) );
return program;
}
int main( void )
{
// Initialise GLFW
if( !glfwInit() )
{
fprintf( stderr, "Failed to initialize GLFW\n" );
getchar();
return -1;
}
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Open a window and create its OpenGL context
GLFWwindow* window = glfwCreateWindow( 1024, 768, "Tutorial 02 - Red triangle", NULL, NULL);
if( window == NULL ){
fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );
getchar();
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// Initialize GLEW
glewExperimental = true; // Needed for core profile
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEW\n");
getchar();
glfwTerminate();
return -1;
}
std::cout << "Using GL Version: " << glGetString(GL_VERSION) << std::endl;
// Ensure we can capture the escape key being pressed below
glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
// Dark blue background
// glClearColor(0.0f, 0.0f, 0.4f, 0.0f);
// Have to set VAO, don't know why yet
GLuint VertexArrayID;
GLCall( glGenVertexArrays(1, &VertexArrayID) );
GLCall( glBindVertexArray(VertexArrayID) );
float positions[] = {
-0.5f, -0.5f, // 0
0.5f, -0.5f, // 1
0.5f, 0.5f, // 2
-0.5f, 0.5f // 3
};
unsigned int indices[] = {
0, 1, 2,
2, 3, 0
};
/*
많은 opengl함수들이 GLCall wrapper function으로 둘러쌓인 것을 볼 수 있다.
이는 결코 이상적인 방법은 아니지만, 원시적인 glGetError을 사용해 디버깅하려면
어쩔 수 없는 선택이다.
보다 더 나은 개선책은 나중에 glDebugMessageCallback를 다루며 알아보겠다.
현재 이 방식에는 다양한 문제점들이 있는데 대표적인 걸 꼽자면 다음과 같다.
1) GlCall(x)의 x에 if(A) func(); 같은 1 line if statement 전달이 안됨.
이는 if(A)에서 아규먼트가 짤리기 때문으로 추정됨.
2) int a = glExampleFunc();으로 a를 초기화하는 코드가 있을 때,
GlCall(int a = glExampleFunc());가 불가능함.
이는 매크로 함수의 문제임.
사실 매크로로 이렇게 모든 함수들을 둘러쌓는 방식 자체가 아주 좋지 못한 해결책이지만,
여기서는 glGetError을 자세히 알아보는 용도로 임시로 사용함.
참고:
https://stackoverflow.com/questions/163365/how-do-i-make-a-c-macro-behave-like-a-function
*/
// Create buffer and copy data
unsigned int buffer;
GLCall( glGenBuffers(1, &buffer) );
GLCall( glBindBuffer(GL_ARRAY_BUFFER, buffer) );
GLCall( glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(float), positions, GL_STATIC_DRAW) );
// define vertex layout
GLCall( glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0) );
GLCall( glEnableVertexAttribArray(0) );
// Create index buffer
unsigned int ibo;
GLCall( glGenBuffers(1, &ibo) );
GLCall( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo) );
GLCall( glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW) );
ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");
std::cout << "VERTEX" << std::endl << source.VertexSource << std::endl;
std::cout << "FRAGMENT" << std::endl << source.FragmentSource << std::endl;
unsigned int shader = CreateShader(source.VertexSource, source.FragmentSource);
GLCall( glUseProgram(shader) );
do{
// Clear the screen
GLCall( glClear( GL_COLOR_BUFFER_BIT );
// Draw the triangle !
GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr)) );
// Swap buffers
glfwSwapBuffers(window);
glfwPollEvents();
} // Check if the ESC key was pressed or the window was closed
while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&
glfwWindowShouldClose(window) == 0 );
// Cleanup VBO
GLCall( glDeleteBuffers(1, &buffer) );
GLCall( glDeleteVertexArrays(1, &VertexArrayID) );
GLCall( glDeleteProgram(shader) );
// Close OpenGL window and terminate GLFW
glfwTerminate();
return 0;
}
'computer graphics > OpenGL' 카테고리의 다른 글
[OpenGL] 7. Vertex Array Object (VAO) (0) | 2022.03.01 |
---|---|
[OpenGL] 6. Uniform (0) | 2022.02.27 |
[OpenGL] 4. Index Buffer (0) | 2022.02.19 |
[OpenGL] 3. Shader (0) | 2022.02.15 |
[OpenGL] 2. Vertex Attribute (0) | 2022.02.12 |