add_library(cygnet SHARED | add_library(cygnet SHARED | ||||
src/GlProgram.cc | |||||
src/BuiltinShaders.cc | |||||
src/glutil.cc | |||||
src/GlWrappers.cc | |||||
src/Window.cc) | src/Window.cc) | ||||
target_link_libraries(cygnet PUBLIC SDL2 GLESv2) | target_link_libraries(cygnet PUBLIC SDL2 GLESv2) | ||||
target_include_directories(cygnet | target_include_directories(cygnet | ||||
add_executable(cygnet-hello-triangle | add_executable(cygnet-hello-triangle | ||||
samples/hello-triangle/hello-triangle.cc) | samples/hello-triangle/hello-triangle.cc) | ||||
target_link_libraries(cygnet-hello-triangle PUBLIC cygnet) | target_link_libraries(cygnet-hello-triangle PUBLIC cygnet) | ||||
add_executable(cygnet-hello-texture | |||||
samples/hello-texture/hello-texture.cc) | |||||
target_link_libraries(cygnet-hello-texture PUBLIC cygnet) |
#include <optional> | |||||
#include "GlWrappers.h" | |||||
namespace Cygnet { | |||||
struct BuiltinShaders { | |||||
BuiltinShaders(); | |||||
GlShader textureVertex; | |||||
GlShader textureFragment; | |||||
GlShader whiteFragment; | |||||
}; | |||||
} |
FRAGMENT, | FRAGMENT, | ||||
}; | }; | ||||
GlShader(const char *source, Type type); | |||||
GlShader(Type type, const char *source); | |||||
~GlShader(); | ~GlShader(); | ||||
GLuint id() const { return id_; } | GLuint id() const { return id_; } | ||||
private: | private: | ||||
GLuint id_; | GLuint id_; | ||||
bool valid_ = false; | |||||
}; | }; | ||||
class GlProgram: NonCopyable { | class GlProgram: NonCopyable { | ||||
void use() { glUseProgram(id_); } | void use() { glUseProgram(id_); } | ||||
GLuint id() { return id_; } | GLuint id() { return id_; } | ||||
GLuint getLocation(const char *name) { return glGetAttribLocation(id_, name); } | |||||
GLint attribLocation(const char *name) { return glGetAttribLocation(id_, name); } | |||||
GLint uniformLocation(const char *name) { return glGetAttribLocation(id_, name); } | |||||
private: | private: | ||||
GLuint id_; | GLuint id_; | ||||
bool valid_ = false; | |||||
}; | }; | ||||
} | } |
#pragma once | |||||
namespace Cygnet { | |||||
const char *glErrorString(int err); | |||||
void glCheck(); | |||||
void sdlAssert(bool val); | |||||
} |
#include <cygnet/Window.h> | |||||
#include <cygnet/GlWrappers.h> | |||||
#include <cygnet/BuiltinShaders.h> | |||||
#include <cygnet/glutil.h> | |||||
#include <iostream> | |||||
int main() { | |||||
SDL_Init(SDL_INIT_VIDEO); | |||||
Cygnet::Deferred<SDL_Quit> sdl; | |||||
Cygnet::Window win("Hello Triangle", 640, 480); | |||||
Cygnet::BuiltinShaders shaders; | |||||
Cygnet::GlProgram program(shaders.textureVertex, shaders.textureFragment); | |||||
program.use(); | |||||
GLfloat vertexes[] = { | |||||
-0.5f, -0.5f, 0.0f, // pos 0: top left | |||||
0.0f, 0.0f, // tex 0: top left | |||||
-0.5f, 0.5f, 0.0f, // pos 1: bottom left | |||||
0.0f, 1.0f, // tex 1: bottom left | |||||
0.5f, 0.5f, 0.0f, // pos 2: bottom right | |||||
1.0f, 1.0f, // tex 2: bottom right | |||||
0.5f, -0.5f, 0.0f, // pos 3: top right | |||||
1.0f, 0.0f, // tex 3: top right | |||||
}; | |||||
GLushort indexes[] = { | |||||
0, 1, 2, // top left -> bottom left -> bottom right | |||||
2, 3, 0, // bottom right -> top right -> top left | |||||
}; | |||||
uint8_t image[] = { | |||||
0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa, 0xff, 0x00, 0xba, | |||||
0xaa, 0xaa, 0xaa, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, | |||||
0xa0, 0x55, 0x77, 0x00, 0xf0, 0x0f, 0xff, 0x00, 0xff, | |||||
}; | |||||
GLuint texId; | |||||
glGenTextures(1, &texId); | |||||
glBindTexture(GL_TEXTURE_2D, texId); | |||||
Cygnet::glCheck(); | |||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 3, 3, 0, GL_RGB, GL_UNSIGNED_BYTE, image); | |||||
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_TO_EDGE); | |||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |||||
Cygnet::glCheck(); | |||||
GLint positionLoc = program.attribLocation("position"); | |||||
GLint texCoordLoc = program.attribLocation("texCoord"); | |||||
GLint texLoc = program.uniformLocation("tex"); | |||||
// Draw loop | |||||
while (true) { | |||||
SDL_Event evt; | |||||
while (SDL_PollEvent(&evt)) { | |||||
switch (evt.type) { | |||||
case SDL_QUIT: | |||||
goto exit; | |||||
break; | |||||
} | |||||
} | |||||
win.clear(); | |||||
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), vertexes); | |||||
glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), &vertexes[3]); | |||||
Cygnet::glCheck(); | |||||
glEnableVertexAttribArray(positionLoc); | |||||
glEnableVertexAttribArray(texCoordLoc); | |||||
Cygnet::glCheck(); | |||||
glActiveTexture(GL_TEXTURE0); | |||||
glBindTexture(GL_TEXTURE_2D, texId); | |||||
glUniform1i(texLoc, 0); | |||||
Cygnet::glCheck(); | |||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indexes); | |||||
Cygnet::glCheck(); | |||||
win.flip(); | |||||
} | |||||
exit: | |||||
return EXIT_SUCCESS; | |||||
} |
#include <cygnet/Window.h> | #include <cygnet/Window.h> | ||||
#include <cygnet/GlProgram.h> | |||||
#include <cygnet/GlWrappers.h> | |||||
#include <iostream> | #include <iostream> | ||||
const char *vertexSource = R"( | const char *vertexSource = R"( | ||||
Cygnet::Deferred<SDL_Quit> sdl; | Cygnet::Deferred<SDL_Quit> sdl; | ||||
Cygnet::Window win("Hello Triangle", 640, 480); | Cygnet::Window win("Hello Triangle", 640, 480); | ||||
Cygnet::GlShader vertex(vertexSource, Cygnet::GlShader::Type::VERTEX); | |||||
Cygnet::GlShader fragment(fragmentSource, Cygnet::GlShader::Type::FRAGMENT); | |||||
Cygnet::GlShader vertex(Cygnet::GlShader::Type::VERTEX, vertexSource); | |||||
Cygnet::GlShader fragment(Cygnet::GlShader::Type::FRAGMENT, fragmentSource); | |||||
Cygnet::GlProgram program(vertex, fragment); | Cygnet::GlProgram program(vertex, fragment); | ||||
GLuint positionAttrib = program.getLocation("position"); | |||||
GLuint positionAttrib = program.attribLocation("position"); | |||||
program.use(); | program.use(); | ||||
GLfloat vertixes[] = { | GLfloat vertixes[] = { |
#include "BuiltinShaders.h" | |||||
namespace Cygnet { | |||||
BuiltinShaders::BuiltinShaders(): | |||||
textureVertex(GlShader::Type::VERTEX, R"( | |||||
attribute vec4 position; | |||||
attribute vec2 texCoord; | |||||
varying vec2 v_texCoord; | |||||
void main() { | |||||
gl_Position = position; | |||||
v_texCoord = texCoord; | |||||
} | |||||
)"), | |||||
textureFragment(GlShader::Type::FRAGMENT, R"( | |||||
precision mediump float; | |||||
varying vec2 v_texCoord; | |||||
uniform sampler2D tex; | |||||
void main() { | |||||
gl_FragColor = texture2D(tex, v_texCoord); | |||||
} | |||||
)"), | |||||
whiteFragment(GlShader::Type::FRAGMENT, R"( | |||||
precision mediump float; | |||||
void main() { | |||||
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); | |||||
} | |||||
)") {} | |||||
} |
#include "GlProgram.h" | |||||
#include "GlWrappers.h" | |||||
#include <iostream> | #include <iostream> | ||||
#include <stdexcept> | #include <stdexcept> | ||||
#include "glutil.h" | |||||
namespace Cygnet { | namespace Cygnet { | ||||
GlShader::GlShader(const char *source, Type type) { | |||||
GlShader::GlShader(Type type, const char *source) { | |||||
switch (type) { | switch (type) { | ||||
case Type::VERTEX: | case Type::VERTEX: | ||||
id_ = glCreateShader(GL_VERTEX_SHADER); | id_ = glCreateShader(GL_VERTEX_SHADER); | ||||
glCheck(); | |||||
std::cerr << "compiling vertex shader...\n"; | |||||
break; | break; | ||||
case Type::FRAGMENT: | case Type::FRAGMENT: | ||||
id_ = glCreateShader(GL_FRAGMENT_SHADER); | id_ = glCreateShader(GL_FRAGMENT_SHADER); | ||||
glCheck(); | |||||
std::cerr << "compiling fragment shader...\n"; | |||||
break; | break; | ||||
} | } | ||||
glShaderSource(id_, 1, &source, NULL); | glShaderSource(id_, 1, &source, NULL); | ||||
glCheck(); | |||||
glCompileShader(id_); | glCompileShader(id_); | ||||
glCheck(); | |||||
char log[4096]; | char log[4096]; | ||||
GLsizei length; | |||||
GLsizei length = 0; | |||||
glGetShaderInfoLog(id_, sizeof(log), &length, log); | glGetShaderInfoLog(id_, sizeof(log), &length, log); | ||||
glCheck(); | |||||
if (length != 0) { | if (length != 0) { | ||||
std::cerr << "Shader compile info:\n" << log << '\n'; | |||||
std::cerr << "Shader compile info (" << length << "):\n" << log << '\n'; | |||||
} | } | ||||
GLint status; | |||||
GLint status = 0; | |||||
glGetShaderiv(id_, GL_COMPILE_STATUS, &status); | glGetShaderiv(id_, GL_COMPILE_STATUS, &status); | ||||
glCheck(); | |||||
if (status == GL_FALSE) { | if (status == GL_FALSE) { | ||||
id_ = -1; | |||||
throw std::runtime_error("GL shader compilation failed."); | throw std::runtime_error("GL shader compilation failed."); | ||||
} | } | ||||
valid_ = true; | |||||
} | } | ||||
GlShader::~GlShader() { | GlShader::~GlShader() { | ||||
if (valid_) { | |||||
glDeleteShader(id_); | |||||
} | |||||
glDeleteShader(id_); | |||||
glCheck(); | |||||
} | } | ||||
void GlProgram::link() { | void GlProgram::link() { | ||||
std::cout << "link\n"; | std::cout << "link\n"; | ||||
glLinkProgram(id_); | glLinkProgram(id_); | ||||
glCheck(); | |||||
char log[4096]; | char log[4096]; | ||||
GLsizei length; | |||||
GLsizei length = 0; | |||||
glGetProgramInfoLog(id_, sizeof(log), &length, log); | glGetProgramInfoLog(id_, sizeof(log), &length, log); | ||||
glCheck(); | |||||
if (length != 0) { | if (length != 0) { | ||||
std::cerr << "Program link info:\n" << log << '\n'; | std::cerr << "Program link info:\n" << log << '\n'; | ||||
} | } | ||||
GLint status; | |||||
GLint status = 0; | |||||
glGetProgramiv(id_, GL_LINK_STATUS, &status); | glGetProgramiv(id_, GL_LINK_STATUS, &status); | ||||
glCheck(); | |||||
if (status == GL_FALSE) { | if (status == GL_FALSE) { | ||||
id_ = -1; | |||||
throw std::runtime_error("GL program link failed."); | throw std::runtime_error("GL program link failed."); | ||||
} | } | ||||
valid_ = true; | |||||
} | } | ||||
GlProgram::~GlProgram() { | GlProgram::~GlProgram() { | ||||
if (valid_) { | |||||
glDeleteProgram(id_); | |||||
} | |||||
glDeleteProgram(id_); | |||||
} | } | ||||
} | } |
#include "Window.h" | #include "Window.h" | ||||
#include <SDL_opengles2.h> | #include <SDL_opengles2.h> | ||||
#include <assert.h> | |||||
#include "glutil.h" | |||||
namespace Cygnet { | namespace Cygnet { | ||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); | ||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | ||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); | SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); | ||||
glCheck(); | |||||
assert(glctx_ = SDL_GL_CreateContext(win_.get())); | |||||
sdlAssert(glctx_ = SDL_GL_CreateContext(win_.get())); | |||||
makeCurrent(); | makeCurrent(); | ||||
assert(SDL_GL_SetSwapInterval(1) == 0); | |||||
glCheck(); | |||||
} | } | ||||
Window::~Window() { | Window::~Window() { |
#include "glutil.h" | |||||
#include <SDL.h> | |||||
#include <SDL_opengles2.h> | |||||
#include <stdexcept> | |||||
namespace Cygnet { | |||||
const char *glErrorString(int err) { | |||||
#define errcase(x) case x: return #x | |||||
switch (err) { | |||||
errcase(GL_NO_ERROR); | |||||
errcase(GL_INVALID_ENUM); | |||||
errcase(GL_INVALID_VALUE); | |||||
errcase(GL_INVALID_OPERATION); | |||||
errcase(GL_INVALID_FRAMEBUFFER_OPERATION); | |||||
errcase(GL_OUT_OF_MEMORY); | |||||
default: return "(unknown)"; | |||||
} | |||||
#undef errcase | |||||
} | |||||
void glCheck() { | |||||
GLenum err = glGetError(); | |||||
if (err != GL_NO_ERROR) { | |||||
throw std::runtime_error(glErrorString(err)); | |||||
} | |||||
} | |||||
void sdlAssert(bool val) { | |||||
if (!val) { | |||||
throw std::runtime_error(SDL_GetError()); | |||||
} | |||||
} | |||||
} |