@@ -1,9 +1,10 @@ | |||
add_library(cygnet SHARED | |||
src/BuiltinShaders.cc | |||
src/builtins.cc | |||
src/glutil.cc | |||
src/GlWrappers.cc | |||
src/Image.cc | |||
src/Window.cc) | |||
target_link_libraries(cygnet PUBLIC SDL2 GLESv2) | |||
target_link_libraries(cygnet PUBLIC SDL2 SDL2_image GLESv2) | |||
target_include_directories(cygnet | |||
PUBLIC include | |||
PRIVATE include/cygnet) | |||
@@ -15,3 +16,14 @@ 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) | |||
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) | |||
@@ -1,15 +0,0 @@ | |||
#include <optional> | |||
#include "GlWrappers.h" | |||
namespace Cygnet { | |||
struct BuiltinShaders { | |||
BuiltinShaders(); | |||
GlShader textureVertex; | |||
GlShader textureFragment; | |||
GlShader whiteFragment; | |||
}; | |||
} |
@@ -37,11 +37,38 @@ public: | |||
void use() { glUseProgram(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: | |||
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); | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
#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; | |||
}; | |||
} |
@@ -2,11 +2,14 @@ | |||
#include <SDL.h> | |||
#include <stdint.h> | |||
#include <glm/mat4x4.hpp> | |||
#include "util.h" | |||
namespace Cygnet { | |||
struct GlTexture; | |||
class Window { | |||
public: | |||
Window(const char *name, int width, int height); |
@@ -0,0 +1,11 @@ | |||
#include <optional> | |||
#include "GlWrappers.h" | |||
namespace Cygnet { | |||
const GlShader &builtinTextureVertex(); | |||
const GlShader &builtinTextureFragment(); | |||
const GlShader &builtinWhiteFragment(); | |||
} |
@@ -0,0 +1,164 @@ | |||
#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: ; | |||
} |
@@ -1,6 +1,6 @@ | |||
#include <cygnet/Window.h> | |||
#include <cygnet/GlWrappers.h> | |||
#include <cygnet/BuiltinShaders.h> | |||
#include <cygnet/builtins.h> | |||
#include <cygnet/glutil.h> | |||
#include <iostream> | |||
@@ -9,8 +9,7 @@ int main() { | |||
Cygnet::Deferred<SDL_Quit> sdl; | |||
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(); | |||
GLfloat vertexes[] = { | |||
@@ -35,21 +34,14 @@ int main() { | |||
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 | |||
while (true) { | |||
@@ -72,16 +64,7 @@ int main() { | |||
glEnableVertexAttribArray(texCoordLoc); | |||
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); | |||
Cygnet::glCheck(); | |||
@@ -24,7 +24,7 @@ int main() { | |||
Cygnet::GlShader vertex(Cygnet::GlShader::Type::VERTEX, vertexSource); | |||
Cygnet::GlShader fragment(Cygnet::GlShader::Type::FRAGMENT, fragmentSource); | |||
Cygnet::GlProgram program(vertex, fragment); | |||
GLuint positionAttrib = program.attribLocation("position"); | |||
GLuint positionAttrib = program.attribLoc("position"); | |||
program.use(); | |||
GLfloat vertixes[] = { |
@@ -72,4 +72,25 @@ GlProgram::~GlProgram() { | |||
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(); | |||
} | |||
} |
@@ -0,0 +1,43 @@ | |||
#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_; | |||
} | |||
} |
@@ -22,6 +22,10 @@ Window::Window(const char *name, int width, int height) { | |||
sdlAssert(glctx_ = SDL_GL_CreateContext(win_.get())); | |||
makeCurrent(); | |||
glCheck(); | |||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |||
glEnable(GL_BLEND); | |||
glCheck(); | |||
} | |||
Window::~Window() { | |||
@@ -33,13 +37,12 @@ void Window::makeCurrent() { | |||
} | |||
void Window::clear() { | |||
glClearColor(0, 0, 0, 1); | |||
//glClearColor(0, 0, 0, 1); | |||
glClear(GL_COLOR_BUFFER_BIT); | |||
} | |||
void Window::flip() { | |||
SDL_GL_SwapWindow(win_.get()); | |||
} | |||
} |
@@ -1,9 +1,9 @@ | |||
#include "BuiltinShaders.h" | |||
#include "builtins.h" | |||
namespace Cygnet { | |||
BuiltinShaders::BuiltinShaders(): | |||
textureVertex(GlShader::Type::VERTEX, R"( | |||
const GlShader &builtinTextureVertex() { | |||
static GlShader shader(GlShader::Type::VERTEX, R"( | |||
attribute vec4 position; | |||
attribute vec2 texCoord; | |||
varying vec2 v_texCoord; | |||
@@ -11,21 +11,30 @@ BuiltinShaders::BuiltinShaders(): | |||
gl_Position = position; | |||
v_texCoord = texCoord; | |||
} | |||
)"), | |||
)"); | |||
return shader; | |||
} | |||
textureFragment(GlShader::Type::FRAGMENT, R"( | |||
const GlShader &builtinTextureFragment() { | |||
static GlShader shader(GlShader::Type::FRAGMENT, R"( | |||
precision mediump float; | |||
varying vec2 v_texCoord; | |||
uniform sampler2D tex; | |||
void main() { | |||
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; | |||
void main() { | |||
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); | |||
} | |||
)") {} | |||
)"); | |||
return shader; | |||
} | |||
} |