add_library(cygnet SHARED | add_library(cygnet SHARED | ||||
src/BuiltinShaders.cc | |||||
src/builtins.cc | |||||
src/glutil.cc | src/glutil.cc | ||||
src/GlWrappers.cc | src/GlWrappers.cc | ||||
src/Image.cc | |||||
src/Window.cc) | src/Window.cc) | ||||
target_link_libraries(cygnet PUBLIC SDL2 GLESv2) | |||||
target_link_libraries(cygnet PUBLIC SDL2 SDL2_image GLESv2) | |||||
target_include_directories(cygnet | target_include_directories(cygnet | ||||
PUBLIC include | PUBLIC include | ||||
PRIVATE include/cygnet) | PRIVATE include/cygnet) | ||||
add_executable(cygnet-hello-texture | add_executable(cygnet-hello-texture | ||||
samples/hello-texture/hello-texture.cc) | samples/hello-texture/hello-texture.cc) | ||||
target_link_libraries(cygnet-hello-texture PUBLIC cygnet) | target_link_libraries(cygnet-hello-texture PUBLIC cygnet) | ||||
add_executable(cygnet-game | |||||
samples/game/game.cc) | |||||
target_link_libraries(cygnet-game PUBLIC cygnet) | |||||
set(assets | |||||
samples/game/assets/player.png) | |||||
foreach(a ${assets}) | |||||
configure_file("${a}" "${a}" COPYONLY) | |||||
endforeach(a) | |||||
#include <optional> | |||||
#include "GlWrappers.h" | |||||
namespace Cygnet { | |||||
struct BuiltinShaders { | |||||
BuiltinShaders(); | |||||
GlShader textureVertex; | |||||
GlShader textureFragment; | |||||
GlShader whiteFragment; | |||||
}; | |||||
} |
void use() { glUseProgram(id_); } | void use() { glUseProgram(id_); } | ||||
GLuint id() { return id_; } | GLuint id() { return id_; } | ||||
GLint attribLocation(const char *name) { return glGetAttribLocation(id_, name); } | |||||
GLint uniformLocation(const char *name) { return glGetAttribLocation(id_, name); } | |||||
GLint attribLoc(const char *name); | |||||
GLint attribLoc(const char *name, GLuint index); | |||||
GLint uniformLoc(const char *name); | |||||
private: | private: | ||||
GLuint id_; | GLuint id_; | ||||
}; | }; | ||||
class GlTexture: NonCopyable { | |||||
public: | |||||
GlTexture(); | |||||
void bind(); | |||||
void upload(GLsizei width, GLsizei height, void *data, | |||||
GLenum format, GLenum type = GL_UNSIGNED_BYTE); | |||||
GLuint id() { return id_; } | |||||
private: | |||||
GLuint id_; | |||||
}; | |||||
inline GLint GlProgram::attribLoc(const char *name) { | |||||
return glGetAttribLocation(id_, name); | |||||
} | |||||
inline GLint GlProgram::attribLoc(const char *name, GLuint index) { | |||||
glBindAttribLocation(id_, index, name); | |||||
return index; | |||||
} | |||||
inline GLint GlProgram::uniformLoc(const char *name) { | |||||
return glGetUniformLocation(id_, name); | |||||
} | |||||
} | } |
#pragma once | |||||
#include <memory> | |||||
#include <stdlib.h> | |||||
#include "GlWrappers.h" | |||||
namespace Cygnet { | |||||
class Image { | |||||
public: | |||||
Image(std::string path); | |||||
GlTexture &texture(); | |||||
private: | |||||
std::string path_; | |||||
int w_, h_, pitch_; | |||||
CPtr<void, free> bytes_; | |||||
GlTexture tex_; | |||||
bool tex_dirty_ = true; | |||||
}; | |||||
} |
#include <SDL.h> | #include <SDL.h> | ||||
#include <stdint.h> | #include <stdint.h> | ||||
#include <glm/mat4x4.hpp> | |||||
#include "util.h" | #include "util.h" | ||||
namespace Cygnet { | namespace Cygnet { | ||||
struct GlTexture; | |||||
class Window { | class Window { | ||||
public: | public: | ||||
Window(const char *name, int width, int height); | Window(const char *name, int width, int height); |
#include <optional> | |||||
#include "GlWrappers.h" | |||||
namespace Cygnet { | |||||
const GlShader &builtinTextureVertex(); | |||||
const GlShader &builtinTextureFragment(); | |||||
const GlShader &builtinWhiteFragment(); | |||||
} |
#include <cygnet/Window.h> | |||||
#include <cygnet/GlWrappers.h> | |||||
#include <cygnet/builtins.h> | |||||
#include <cygnet/glutil.h> | |||||
#include <cygnet/Image.h> | |||||
#include <stdio.h> | |||||
#include <memory> | |||||
#include <vector> | |||||
enum class Key { | |||||
UP, DOWN, LEFT, RIGHT, NONE, | |||||
}; | |||||
struct State { | |||||
bool keys[(int)Key::NONE]{}; | |||||
Cygnet::Window &win; | |||||
Cygnet::GlProgram &program; | |||||
}; | |||||
class Entity { | |||||
public: | |||||
virtual void update(State &state, float dt) = 0; | |||||
virtual void draw(State &state) = 0; | |||||
}; | |||||
class PlayerEntity: public Entity { | |||||
public: | |||||
PlayerEntity(float x, float y): x_(x), y_(y) {} | |||||
void update(State &state, float dt) override { | |||||
float fx{}, fy{}; | |||||
if (state.keys[(int)Key::LEFT]) | |||||
fx -= 1; | |||||
if (state.keys[(int)Key::RIGHT]) | |||||
fx += 1; | |||||
vx_ += fx * dt; | |||||
vy_ += fy * dt; | |||||
x_ += vx_ * dt; | |||||
y_ += vy_ * dt; | |||||
} | |||||
void draw(State &state) override { | |||||
printf("\ram at (%f,%f)", x_, y_); | |||||
fflush(stdout); | |||||
const GLfloat vertexes[] = { | |||||
x_ - 0.5f, y_ + 0.5f, 0.0f, // pos 0: top left | |||||
0.0f, 0.0f, // tex 0: top left | |||||
x_ - 0.5f, y_ - 0.5f, 0.0f, // pos 1: bottom left | |||||
0.0f, 1.0f, // tex 1: bottom left | |||||
x_ + 0.5f, y_ - 0.5f, 0.0f, // pos 2: bottom right | |||||
1.0f, 1.0f, // tex 2: bottom right | |||||
x_ + 0.5f, y_ + 0.5f, 0.0f, // pos 3: top right | |||||
1.0f, 0.0f, // tex 3: top right | |||||
}; | |||||
static const GLushort indexes[] = { | |||||
0, 1, 2, // top left -> bottom left -> bottom right | |||||
2, 3, 0, // bottom right -> top right -> top left | |||||
}; | |||||
GLint positionLoc = state.program.attribLoc("position", 0); | |||||
GLint texCoordLoc = state.program.attribLoc("texCoord", 1); | |||||
GLint texLoc = state.program.uniformLoc("tex"); | |||||
glUniform1i(texLoc, 0); | |||||
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(); | |||||
image_.texture().bind(); | |||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indexes); | |||||
Cygnet::glCheck(); | |||||
} | |||||
private: | |||||
float x_, y_; | |||||
float vx_{}, vy_{}; | |||||
Cygnet::Image image_{"libcygnet/samples/game/assets/player.png"}; | |||||
}; | |||||
Key keyFromSym(SDL_Keysym sym) { | |||||
switch (sym.scancode) { | |||||
case SDL_SCANCODE_W: | |||||
return Key::UP; | |||||
case SDL_SCANCODE_A: | |||||
return Key::LEFT; | |||||
case SDL_SCANCODE_S: | |||||
return Key::DOWN; | |||||
case SDL_SCANCODE_D: | |||||
return Key::RIGHT; | |||||
default: | |||||
return Key::NONE; | |||||
} | |||||
} | |||||
int main() { | |||||
SDL_Init(SDL_INIT_VIDEO); | |||||
Cygnet::Deferred<SDL_Quit> sdl; | |||||
Cygnet::Window win("Game", 640, 480); | |||||
Cygnet::GlProgram program(Cygnet::builtinTextureVertex(), Cygnet::builtinTextureFragment()); | |||||
program.use(); | |||||
State state{ | |||||
.keys{}, | |||||
.win = win, | |||||
.program = program, | |||||
}; | |||||
std::vector<std::unique_ptr<Entity>> entities; | |||||
entities.emplace_back(std::make_unique<PlayerEntity>(0, 0)); | |||||
SDL_Event evt; | |||||
while (true) { | |||||
while (SDL_PollEvent(&evt)) { | |||||
switch (evt.type) { | |||||
case SDL_QUIT: | |||||
goto exit; | |||||
break; | |||||
case SDL_KEYDOWN: | |||||
{ | |||||
Key key = keyFromSym(evt.key.keysym); | |||||
if (key != Key::NONE) { | |||||
state.keys[(int)key] = true; | |||||
} | |||||
} | |||||
break; | |||||
case SDL_KEYUP: | |||||
{ | |||||
Key key = keyFromSym(evt.key.keysym); | |||||
if (key != Key::NONE) { | |||||
state.keys[(int)key] = false; | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
win.clear(); | |||||
for (auto &ent: entities) { | |||||
ent->update(state, 1/60.0); | |||||
} | |||||
for (auto &ent: entities) { | |||||
ent->draw(state); | |||||
} | |||||
win.flip(); | |||||
} | |||||
exit: ; | |||||
} |
#include <cygnet/Window.h> | #include <cygnet/Window.h> | ||||
#include <cygnet/GlWrappers.h> | #include <cygnet/GlWrappers.h> | ||||
#include <cygnet/BuiltinShaders.h> | |||||
#include <cygnet/builtins.h> | |||||
#include <cygnet/glutil.h> | #include <cygnet/glutil.h> | ||||
#include <iostream> | #include <iostream> | ||||
Cygnet::Deferred<SDL_Quit> sdl; | Cygnet::Deferred<SDL_Quit> sdl; | ||||
Cygnet::Window win("Hello Texture", 640, 480); | Cygnet::Window win("Hello Texture", 640, 480); | ||||
Cygnet::BuiltinShaders shaders; | |||||
Cygnet::GlProgram program(shaders.textureVertex, shaders.textureFragment); | |||||
Cygnet::GlProgram program(Cygnet::builtinTextureVertex(), Cygnet::builtinTextureFragment()); | |||||
program.use(); | program.use(); | ||||
GLfloat vertexes[] = { | GLfloat vertexes[] = { | ||||
0xa0, 0x55, 0x77, 0x00, 0xf0, 0x0f, 0xff, 0x00, 0xff, 0, 0, 0, | 0xa0, 0x55, 0x77, 0x00, 0xf0, 0x0f, 0xff, 0x00, 0xff, 0, 0, 0, | ||||
}; | }; | ||||
GLuint texId; | |||||
glGenTextures(1, &texId); | |||||
glBindTexture(GL_TEXTURE_2D, texId); | |||||
Cygnet::glCheck(); | |||||
Cygnet::GlTexture tex; | |||||
tex.upload(3, 3, image, GL_RGB); | |||||
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.attribLoc("position", 0); | |||||
GLint texCoordLoc = program.attribLoc("texCoord", 1); | |||||
GLint texLoc = program.uniformLoc("tex"); | |||||
GLint positionLoc = program.attribLocation("position"); | |||||
GLint texCoordLoc = program.attribLocation("texCoord"); | |||||
GLint texLoc = program.uniformLocation("tex"); | |||||
glUniform1i(texLoc, 0); | |||||
// Draw loop | // Draw loop | ||||
while (true) { | while (true) { | ||||
glEnableVertexAttribArray(texCoordLoc); | glEnableVertexAttribArray(texCoordLoc); | ||||
Cygnet::glCheck(); | Cygnet::glCheck(); | ||||
glActiveTexture(GL_TEXTURE0); | |||||
glBindTexture(GL_TEXTURE_2D, texId); | |||||
glUniform1i(texLoc, 0); | |||||
Cygnet::glCheck(); | |||||
glActiveTexture(GL_TEXTURE0); | |||||
glBindTexture(GL_TEXTURE_2D, texId); | |||||
glUniform1i(texLoc, 0); | |||||
Cygnet::glCheck(); | |||||
tex.bind(); | |||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indexes); | glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indexes); | ||||
Cygnet::glCheck(); | Cygnet::glCheck(); | ||||
Cygnet::GlShader vertex(Cygnet::GlShader::Type::VERTEX, vertexSource); | Cygnet::GlShader vertex(Cygnet::GlShader::Type::VERTEX, vertexSource); | ||||
Cygnet::GlShader fragment(Cygnet::GlShader::Type::FRAGMENT, fragmentSource); | Cygnet::GlShader fragment(Cygnet::GlShader::Type::FRAGMENT, fragmentSource); | ||||
Cygnet::GlProgram program(vertex, fragment); | Cygnet::GlProgram program(vertex, fragment); | ||||
GLuint positionAttrib = program.attribLocation("position"); | |||||
GLuint positionAttrib = program.attribLoc("position"); | |||||
program.use(); | program.use(); | ||||
GLfloat vertixes[] = { | GLfloat vertixes[] = { |
glDeleteProgram(id_); | glDeleteProgram(id_); | ||||
} | } | ||||
GlTexture::GlTexture() { | |||||
glGenTextures(1, &id_); | |||||
glCheck(); | |||||
} | |||||
void GlTexture::bind() { | |||||
glActiveTexture(GL_TEXTURE0); | |||||
glBindTexture(GL_TEXTURE_2D, id_); | |||||
} | |||||
void GlTexture::upload(GLsizei width, GLsizei height, void *data, | |||||
GLenum format, GLenum type) { | |||||
bind(); | |||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, format, type, data); | |||||
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); | |||||
glCheck(); | |||||
} | |||||
} | } |
#include "Image.h" | |||||
#include <SDL2/SDL.h> | |||||
#include <SDL2/SDL_image.h> | |||||
#include <stdexcept> | |||||
#include "util.h" | |||||
namespace Cygnet { | |||||
// TODO: Maybe this should be conditional based on endianness? | |||||
static SDL_PixelFormatEnum format = SDL_PIXELFORMAT_ABGR8888; | |||||
Image::Image(std::string path) { | |||||
CPtr<SDL_Surface, SDL_FreeSurface> surface(IMG_Load(path.c_str())); | |||||
if (!surface.get()) { | |||||
throw std::runtime_error("Failed to load " + path + ": " + IMG_GetError()); | |||||
} | |||||
if (surface->format->format != format) { | |||||
printf("Converting pixel format for %s", path.c_str()); | |||||
surface.reset(SDL_ConvertSurfaceFormat(surface.get(), format, 0)); | |||||
if (!surface.get()) { | |||||
throw std::runtime_error("Failed convert " + path + ": " + SDL_GetError()); | |||||
} | |||||
} | |||||
w_ = surface->w; | |||||
h_ = surface->h; | |||||
pitch_ = surface->pitch; | |||||
bytes_.reset(surface->pixels); | |||||
surface->pixels = nullptr; | |||||
} | |||||
GlTexture &Image::texture() { | |||||
if (tex_dirty_) { | |||||
tex_.upload(w_, h_, bytes_.get(), GL_RGBA); | |||||
} | |||||
return tex_; | |||||
} | |||||
} |
sdlAssert(glctx_ = SDL_GL_CreateContext(win_.get())); | sdlAssert(glctx_ = SDL_GL_CreateContext(win_.get())); | ||||
makeCurrent(); | makeCurrent(); | ||||
glCheck(); | glCheck(); | ||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |||||
glEnable(GL_BLEND); | |||||
glCheck(); | |||||
} | } | ||||
Window::~Window() { | Window::~Window() { | ||||
} | } | ||||
void Window::clear() { | void Window::clear() { | ||||
glClearColor(0, 0, 0, 1); | |||||
//glClearColor(0, 0, 0, 1); | |||||
glClear(GL_COLOR_BUFFER_BIT); | glClear(GL_COLOR_BUFFER_BIT); | ||||
} | } | ||||
void Window::flip() { | void Window::flip() { | ||||
SDL_GL_SwapWindow(win_.get()); | SDL_GL_SwapWindow(win_.get()); | ||||
} | } | ||||
} | } |
#include "BuiltinShaders.h" | |||||
#include "builtins.h" | |||||
namespace Cygnet { | namespace Cygnet { | ||||
BuiltinShaders::BuiltinShaders(): | |||||
textureVertex(GlShader::Type::VERTEX, R"( | |||||
const GlShader &builtinTextureVertex() { | |||||
static GlShader shader(GlShader::Type::VERTEX, R"( | |||||
attribute vec4 position; | attribute vec4 position; | ||||
attribute vec2 texCoord; | attribute vec2 texCoord; | ||||
varying vec2 v_texCoord; | varying vec2 v_texCoord; | ||||
gl_Position = position; | gl_Position = position; | ||||
v_texCoord = texCoord; | v_texCoord = texCoord; | ||||
} | } | ||||
)"), | |||||
)"); | |||||
return shader; | |||||
} | |||||
textureFragment(GlShader::Type::FRAGMENT, R"( | |||||
const GlShader &builtinTextureFragment() { | |||||
static GlShader shader(GlShader::Type::FRAGMENT, R"( | |||||
precision mediump float; | precision mediump float; | ||||
varying vec2 v_texCoord; | varying vec2 v_texCoord; | ||||
uniform sampler2D tex; | uniform sampler2D tex; | ||||
void main() { | void main() { | ||||
gl_FragColor = texture2D(tex, v_texCoord); | gl_FragColor = texture2D(tex, v_texCoord); | ||||
} | } | ||||
)"), | |||||
)"); | |||||
return shader; | |||||
} | |||||
whiteFragment(GlShader::Type::FRAGMENT, R"( | |||||
const GlShader &builtinWhiteFragment() { | |||||
static GlShader shader(GlShader::Type::FRAGMENT, R"( | |||||
precision mediump float; | precision mediump float; | ||||
void main() { | void main() { | ||||
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); | gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); | ||||
} | } | ||||
)") {} | |||||
)"); | |||||
return shader; | |||||
} | |||||
} | } |