Browse Source

Merge branch 'feature/replace-renderer'

New Cygnet is approaching feature completeness. Merging Cygnet into
master before the imminent hard work of replacing the SDL renderer with
Cygnet.
fix/style
Martin Dørum 3 years ago
parent
commit
e3eecfb266

+ 6
- 2
CMakeLists.txt View File

@@ -26,12 +26,12 @@ set(libraries

if(CMAKE_BUILD_TYPE STREQUAL Sanitize OR CMAKE_BUILD_TYPE STREQUAL "")
message(STATUS "Build mode: Sanitize")
add_compile_options(-g -Og -fsanitize=address -fsanitize=undefined)
add_compile_options(-g -fsanitize=address -fsanitize=undefined)
add_link_options(-fsanitize=address -fsanitize=undefined)

elseif(CMAKE_BUILD_TYPE STREQUAL Debug)
message(STATUS "Build mode: Debug")
add_compile_options(-g -Og)
add_compile_options(-g)

elseif(CMAKE_BUILD_TYPE STREQUAL Optimize)
message(STATUS "Build mode: Optimize")
@@ -96,3 +96,7 @@ install(TARGETS swan DESTINATION swan)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/assets DESTINATION swan)

add_custom_target(check DEPENDS check_libswan)

add_custom_target(cloc
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND cloc core.mod src libcygnet libswan)

+ 89
- 0
include/swan-common/Matrix3.h View File

@@ -0,0 +1,89 @@
#pragma once

#include <iostream>
#include <cmath>
#include <array>

#include "Vector2.h"

namespace SwanCommon {

template<typename T>
struct Matrix3 {
using Vec = Vector2<T>;

static constexpr std::array<T, 9> identity = {1, 0, 0, 0, 1, 0, 0, 0, 1};
std::array<T, 9> vals;

constexpr Matrix3(): vals(identity) {}
constexpr Matrix3(const Matrix3 &mat): vals(mat.vals) {}

constexpr Matrix3 &operator=(const Matrix3 &mat) { vals = mat.vals; }

constexpr T *data() {
return vals.data();
}

constexpr const T *data() const {
return vals.data();
}

constexpr T &at(int x, int y) {
return vals[y * 3 + x];
}

constexpr const T &at(int x, int y) const {
return vals[y * 3 + x];
}

constexpr Matrix3<T> &set(std::initializer_list<T> vals) {
this->vals = vals;
return *this;
}

constexpr Matrix3<T> &reset() {
vals = identity;
return *this;
}

constexpr Matrix3<T> &translate(Vec vec) {
at(2, 0) += vec.x;
at(2, 1) += vec.y;
return *this;
}

constexpr Matrix3<T> &scale(Vec vec) {
at(0, 0) *= vec.x;
at(1, 1) *= vec.y;
return *this;
}

constexpr Matrix3<T> &rotate(T rads) {
T s = std::sin(rads);
T c = std::cos(rads);
at(0, 0) += c;
at(1, 0) -= s;
at(0, 1) += s;
at(1, 1) += c;
return *this;
}

static const Matrix3<T> IDENTITY;

template<typename U>
friend std::ostream &operator<<(std::ostream &os, const Matrix3<U> &mat);
};

template<typename T>
const Matrix3<T> Matrix3<T>::IDENTITY = Matrix3<T>();

template<typename T>
std::ostream &operator<<(std::ostream &os, const Matrix3<T> &mat) {
os << '('
<< '(' << mat.at(0, 0) << ", " << mat.at(1, 0) << ", " << mat.at(2, 0) << "), "
<< '(' << mat.at(0, 1) << ", " << mat.at(1, 1) << ", " << mat.at(2, 1) << "), "
<< '(' << mat.at(0, 2) << ", " << mat.at(1, 2) << ", " << mat.at(2, 2) << "))";
return os;
}

}

+ 6
- 6
include/swan-common/Vector2.h View File

@@ -23,27 +23,27 @@ struct Vector2 {
return *this;
}

constexpr T length() {
constexpr T length() const {
return (T)std::sqrt((double)squareLength());
}

constexpr T squareLength() {
constexpr T squareLength() const {
return this->x * this->x + this->y * this->y;
}

constexpr Vector2<T> sign() {
constexpr Vector2<T> sign() const {
return Vector2<T>(x > 0 ? 1 : -1, y > 0 ? 1 : -1);
}

constexpr Vector2<T> scale(T sx, T sy) {
constexpr Vector2<T> scale(T sx, T sy) const {
return Vector2<T>(x * sx, y * sy);
}

constexpr Vector2<T> scale(T s) {
constexpr Vector2<T> scale(T s) const {
return scale(s, s);
}

constexpr Vector2<T> norm() {
constexpr Vector2<T> norm() const {
return *this / length();
}


+ 9
- 0
include/swan-common/concurrency.h View File

@@ -0,0 +1,9 @@
#pragma once

namespace SwanCommon {

template<typename T>
class Lock {
};

}

+ 1
- 1
libcygnet/CMakeLists.txt View File

@@ -2,6 +2,7 @@ add_library(libcygnet SHARED
src/Context.cc
src/GlWrappers.cc
src/Renderer.cc
src/ResourceManager.cc
src/shaders.cc
src/TileAtlas.cc
src/util.cc
@@ -9,7 +10,6 @@ add_library(libcygnet SHARED
target_include_directories(libcygnet
PUBLIC "include"
PRIVATE "include/cygnet")
set_target_properties(libcygnet PROPERTIES OUTPUT_NAME cygnet)
target_link_libraries(libcygnet swan-common GLESv2 ${libraries})

install(TARGETS libcygnet DESTINATION swan/libcygnet)

+ 2
- 21
libcygnet/include/cygnet/GlWrappers.h View File

@@ -21,7 +21,7 @@ public:
};

GlShader(Type type, const char *source);
~GlShader();
virtual ~GlShader();

GLuint id() const { return id_; }

@@ -44,13 +44,11 @@ public:
template <typename... T>
GlProgram(const T &... shaders): GlProgram() { (addShader(shaders), ...); link(); }
GlProgram();
~GlProgram();
virtual ~GlProgram();

void use();
GLuint id() const { return id_; }

protected:

GLint attribLoc(const char *name);
GLint uniformLoc(const char *name);

@@ -61,21 +59,4 @@ private:
GLuint id_;
};

class GlTexture {
public:
GlTexture();

void bind();
void upload(GLsizei width, GLsizei height, void *data,
GLenum format, GLenum type);
GLuint id() { return id_; }
int width() { return w_; }
int height() { return h_; }

private:
GLuint id_;
int w_;
int h_;
};

}

+ 76
- 3
libcygnet/include/cygnet/Renderer.h View File

@@ -1,23 +1,96 @@
#pragma once

#include <memory>
#include <vector>
#include <stdint.h>

#include <swan-common/constants.h>
#include <swan-common/Vector2.h>

#include "util.h"

namespace Cygnet {

struct RendererState;

struct RenderChunk {
GLuint tex;
};

struct RenderSprite {
GLuint tex;
SwanCommon::Vec2 scale;
int frameCount;
};

struct RenderTile {
uint16_t id;
};

struct RenderCamera {
SwanCommon::Vec2 pos;
SwanCommon::Vec2i size;
float zoom;
};

class Renderer {
public:
using TileID = uint16_t;

Renderer();
~Renderer();

void draw();
void drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos);
void drawSprite(RenderSprite sprite, Mat3gf mat, int y = 0);
void drawSprite(RenderSprite sprite, SwanCommon::Vec2 pos, int y = 0);
void drawSpriteFlipped(RenderSprite chunk, SwanCommon::Vec2 pos, int y = 0);

void draw(const RenderCamera &cam);

void uploadTileAtlas(const void *data, int width, int height);
void modifyTile(TileID id, const void *data);

void registerTileTexture(size_t tileId, const void *data, size_t len);
void uploadTileTexture();
RenderChunk createChunk(
TileID tiles[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]);
void modifyChunk(RenderChunk chunk, SwanCommon::Vec2i pos, TileID id);
void destroyChunk(RenderChunk chunk);

RenderSprite createSprite(void *data, int width, int height, int fh);
RenderSprite createSprite(void *data, int width, int height);
void destroySprite(RenderSprite sprite);

private:
struct DrawChunk {
SwanCommon::Vec2 pos;
RenderChunk chunk;
};

struct DrawSprite {
Mat3gf transform;
int frame;
RenderSprite sprite;
};

std::unique_ptr<RendererState> state_;

std::vector<DrawChunk> draw_chunks_;
std::vector<DrawSprite> draw_sprites_;
};

inline void Renderer::drawChunk(RenderChunk chunk, SwanCommon::Vec2 pos) {
draw_chunks_.emplace_back(pos, chunk);
}

inline void Renderer::drawSprite(RenderSprite sprite, Mat3gf mat, int frame) {
draw_sprites_.emplace_back(mat, frame, sprite);
}

inline void Renderer::drawSprite(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) {
draw_sprites_.emplace_back(Mat3gf{}.translate(pos), frame, sprite);
}

inline void Renderer::drawSpriteFlipped(RenderSprite sprite, SwanCommon::Vec2 pos, int frame) {
draw_sprites_.emplace_back(Mat3gf{}.translate(pos).scale({ -1, 1 }), frame, sprite);
}

}

+ 91
- 0
libcygnet/include/cygnet/ResourceManager.h View File

@@ -0,0 +1,91 @@
#pragma once

#include <unordered_map>
#include <optional>
#include <memory>
#include <stdint.h>
#include <string.h>
#include <swan-common/constants.h>

#include "Renderer.h"
#include "TileAtlas.h"

namespace Cygnet {

struct ResourceTileAnimation {
uint16_t id;
int frames;
int index;
std::unique_ptr<unsigned char[]> data;
};

class ResourceManager;

class ResourceBuilder {
public:
ResourceBuilder(Renderer &rnd): rnd_(rnd) {}

RenderSprite addSprite(std::string name, void *data, int width, int height, int fh);
RenderSprite addSprite(std::string name, void *data, int width, int height);
void addTile(Renderer::TileID id, void *data, int frames = 1);
void addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames = 1);

private:
Renderer &rnd_;
std::unordered_map<std::string, RenderSprite> sprites_;
std::vector<ResourceTileAnimation> tile_anims_;
TileAtlas atlas_;

friend ResourceManager;
};

class ResourceManager {
public:
ResourceManager(ResourceBuilder &&builder);
~ResourceManager();

RenderSprite getSprite(std::string name) { return sprites_.at(std::move(name)); }

void tick();

private:
Renderer &rnd_;
std::unordered_map<std::string, RenderSprite> sprites_;
std::unordered_map<std::string, RenderTile> tiles_;
std::vector<ResourceTileAnimation> tile_anims_;
};

inline RenderSprite ResourceBuilder::addSprite(
std::string name, void *data, int width, int height, int fh) {
return sprites_[std::move(name)] = rnd_.createSprite(data, width, height, fh);
}

inline RenderSprite ResourceBuilder::addSprite(
std::string name, void *data, int width, int height) {
return sprites_[std::move(name)] = rnd_.createSprite(data, width, height);
}

inline void ResourceBuilder::addTile(uint16_t id, void *data, int frames) {
if (frames == 0) {
atlas_.addTile(id, data);
} else {
auto ptr = std::make_unique<unsigned char[]>(
SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE * 4 * frames);
memcpy(ptr.get(), data, SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE * 4 * frames);
addTile(id, std::move(ptr), frames);
}
}

inline void ResourceBuilder::addTile(Renderer::TileID id, std::unique_ptr<unsigned char[]> data, int frames) {
atlas_.addTile(id, data.get());
if (frames > 1) {
tile_anims_.push_back({
.id = id,
.frames = frames,
.index = 0,
.data = std::move(data),
});
}
}

}

+ 1
- 1
libcygnet/include/cygnet/TileAtlas.h View File

@@ -11,7 +11,7 @@ public:
TileAtlas();
~TileAtlas();

void addTile(size_t tileId, const void *data, size_t len);
void addTile(size_t tileId, const void *data);
const unsigned char *getImage(size_t *w, size_t *h);

private:

+ 2
- 0
libcygnet/include/cygnet/Window.h View File

@@ -1,5 +1,6 @@
#pragma once

#include <swan-common/Vector2.h>
#include <memory>

namespace Cygnet {
@@ -15,6 +16,7 @@ public:
void clear();
void flip();
void onResize(int w, int h);
SwanCommon::Vec2i size() { return { w_, h_ }; }

private:
std::unique_ptr<WindowState> state_;

+ 1
- 0
libcygnet/include/cygnet/gl.h View File

@@ -0,0 +1 @@
#include <SDL_opengles2.h>

+ 4
- 4
libcygnet/include/cygnet/shaders.h View File

@@ -2,10 +2,10 @@

namespace Cygnet::Shaders {

extern const char *basicVx;
extern const char *texturedVx;
extern const char *spriteVx;
extern const char *spriteFr;

extern const char *solidColorFr;
extern const char *texturedFr;
extern const char *chunkVx;
extern const char *chunkFr;

}

+ 5
- 1
libcygnet/include/cygnet/util.h View File

@@ -2,7 +2,8 @@

#include <stdexcept>
#include <stdint.h>
#include <SDL.h>

#include "swan-common/Matrix3.h"

namespace Cygnet {

@@ -11,6 +12,9 @@ using GLint = int32_t;
using GLuint = uint32_t;
using GLsizei = int32_t;
using GLenum = uint32_t;
using GLfloat = float;

using Mat3gf = SwanCommon::Matrix3<GLfloat>;

struct SDLError: public std::exception {
SDLError(std::string msg): message(std::move(msg)) {}

+ 5
- 1
libcygnet/src/Context.cc View File

@@ -2,6 +2,8 @@

#include <SDL.h>

#include "util.h"

namespace Cygnet {

Context::Context() {
@@ -13,7 +15,9 @@ Context::Context() {
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 16);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 8);

SDL_GL_SetSwapInterval(1);
}

Context::~Context() {

+ 2
- 29
libcygnet/src/GlWrappers.cc View File

@@ -1,8 +1,8 @@
#include "GlWrappers.h"

#include <SDL_opengles2.h>
#include <iostream>

#include "gl.h"
#include "util.h"

namespace Cygnet {
@@ -39,6 +39,7 @@ GlShader::GlShader(Type type, const char *source) {
glGetShaderiv(id_, GL_COMPILE_STATUS, &status);
glCheck();
if (status == GL_FALSE) {
std::cerr << "Cygnet: Here's the broken shader:\n" << source << '\n';
throw GlCompileError("GL shader compilation failed.");
}
}
@@ -58,11 +59,6 @@ GlProgram::~GlProgram() {
glCheck();
}

void GlProgram::use() {
glUseProgram(id_);
glCheck();
}

void GlProgram::addShader(const GlShader &shader) {
glAttachShader(id_, shader.id());
glCheck();
@@ -97,27 +93,4 @@ void GlProgram::link() {
}
}

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) {
w_ = width;
h_ = height;
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();
}

}

+ 311
- 27
libcygnet/src/Renderer.cc View File

@@ -1,7 +1,15 @@
#include "Renderer.h"

#include <SDL_opengles2.h>
#include <iostream>
#include <stdio.h>
#include <swan-common/constants.h>
#include <string.h>

// std::endian was originally in type_traits, was moved to bit
#include <type_traits>
#include <bit>

#include "gl.h"
#include "shaders.h"
#include "GlWrappers.h"
#include "TileAtlas.h"
@@ -9,50 +17,326 @@

namespace Cygnet {

struct TexturedProg: public GlProgram {
using GlProgram::GlProgram;
struct ChunkProg: public GlProgram {
template<typename... T>
ChunkProg(const T &... shaders): GlProgram(shaders...) { init(); }
~ChunkProg() { deinit(); }

GLint position = attribLoc("position");
GLint texCoord = attribLoc("texCoord");
GLint tex = uniformLoc("tex");
GLint camera = uniformLoc("camera");
GLint pos = uniformLoc("pos");
GLint vertex = attribLoc("vertex");
GLint tileAtlas = uniformLoc("tileAtlas");
GLint tileAtlasSize = uniformLoc("tileAtlasSize");
GLint tiles = uniformLoc("tiles");

GLuint vbo;

static constexpr float ch = (float)SwanCommon::CHUNK_HEIGHT;
static constexpr float cw = (float)SwanCommon::CHUNK_WIDTH;
static constexpr GLfloat vertexes[] = {
0.0f, 0.0f, // pos 0: top left
0.0f, ch , // pos 1: bottom left
cw, ch, // pos 2: bottom right
cw, ch, // pos 2: bottom right
cw, 0.0f, // pos 3: top right
0.0f, 0.0f, // pos 0: top left
};

void enable() {
glUseProgram(id());
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(vertex, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
glEnableVertexAttribArray(vertex);
glCheck();

glUniform1i(tileAtlas, 0);
glUniform1i(tiles, 1);
}

void disable() {
glDisableVertexAttribArray(vertex);
glCheck();
}

void init() {
glGenBuffers(1, &vbo);
glCheck();

glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
glCheck();
}

void deinit() {
glDeleteBuffers(1, &vbo);
glCheck();
}
};

struct SolidColorProg: public GlProgram {
using GlProgram::GlProgram;
struct SpriteProg: public GlProgram {
template<typename... T>
SpriteProg(const T &... shaders): GlProgram(shaders...) { init(); }
~SpriteProg() { deinit(); }

GLint camera = uniformLoc("camera");
GLint transform = uniformLoc("transform");
GLint frameInfo = uniformLoc("frameInfo");
GLint vertex = attribLoc("vertex");
GLint tex = uniformLoc("tex");

GLuint vbo;

static constexpr GLfloat vertexes[] = {
0.0f, 0.0f, // pos 0: top left
0.0f, 1.0f, // pos 1: bottom left
1.0f, 1.0f, // pos 2: bottom right
1.0f, 1.0f, // pos 2: bottom right
1.0f, 0.0f, // pos 3: top right
0.0f, 0.0f, // pos 0: top left
};

GLint position = attribLoc("position");
GLint color = uniformLoc("color");
void enable() {
glUseProgram(id());
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(vertex, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
glEnableVertexAttribArray(vertex);
glCheck();

glUniform1i(tex, 0);
}

void disable() {
glDisableVertexAttribArray(vertex);
glCheck();
}

void init() {
glGenBuffers(1, &vbo);
glCheck();

glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
glCheck();
}

void deinit() {
glDeleteBuffers(1, &vbo);
glCheck();
}
};

struct RendererState {
GlVxShader basicVx{Shaders::basicVx};
GlVxShader texturedVx{Shaders::texturedVx};
GlFrShader solidColorFr{Shaders::solidColorFr};
GlFrShader texturedFr{Shaders::texturedFr};
GlVxShader spriteVx{Shaders::spriteVx};
GlFrShader spriteFr{Shaders::spriteFr};
GlVxShader chunkVx{Shaders::chunkVx};
GlFrShader chunkFr{Shaders::chunkFr};

TexturedProg texturedProg{texturedVx, texturedFr};
SolidColorProg solidColorProg{basicVx, solidColorFr};
SpriteProg spriteProg{spriteVx, spriteFr};
ChunkProg chunkProg{chunkVx, chunkFr};

TileAtlas atlas;
GlTexture atlasTex;
GLuint atlasTex;
SwanCommon::Vec2 atlasTexSize;
};

Renderer::Renderer(): state_(std::make_unique<RendererState>()) {}
Renderer::Renderer(): state_(std::make_unique<RendererState>()) {
glGenTextures(1, &state_->atlasTex);
glCheck();
}

Renderer::~Renderer() = default;

void Renderer::draw() {
state_->texturedProg.use();
void Renderer::draw(const RenderCamera &cam) {
Mat3gf camMat;

// Make the matrix translate to { -camX, -camY }, fix up the aspect ratio,
// flip the Y axis so that positive Y direction is down, and scale according to zoom.
float ratio = (float)cam.size.y / (float)cam.size.x;
camMat.translate(cam.pos.scale(-ratio, 1) * cam.zoom);
camMat.scale({ cam.zoom * ratio, -cam.zoom });

auto &chunkProg = state_->chunkProg;
auto &spriteProg = state_->spriteProg;

{
chunkProg.enable();
glUniformMatrix3fv(chunkProg.camera, 1, GL_TRUE, camMat.data());
glCheck();

glUniform2f(chunkProg.tileAtlasSize, state_->atlasTexSize.x, state_->atlasTexSize.y);
glCheck();

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, state_->atlasTex);
glCheck();

glActiveTexture(GL_TEXTURE1);
for (auto [pos, chunk]: draw_chunks_) {
glUniform2f(chunkProg.pos, pos.x, pos.y);
glBindTexture(GL_TEXTURE_2D, chunk.tex);
glDrawArrays(GL_TRIANGLES, 0, 6);
glCheck();
}

draw_chunks_.clear();
chunkProg.disable();
}

{
spriteProg.enable();
glUniformMatrix3fv(spriteProg.camera, 1, GL_TRUE, camMat.data());
glCheck();

glActiveTexture(GL_TEXTURE0);
for (auto [mat, frame, sprite]: draw_sprites_) {
mat.scale(sprite.scale);
glUniformMatrix3fv(spriteProg.transform, 1, GL_TRUE, mat.data());
glUniform3f(spriteProg.frameInfo, sprite.scale.y, sprite.frameCount, frame);
glBindTexture(GL_TEXTURE_2D, sprite.tex);
glDrawArrays(GL_TRIANGLES, 0, 6);
glCheck();
}

draw_sprites_.clear();
spriteProg.disable();
}
}

void Renderer::uploadTileAtlas(const void *data, int width, int height) {
glBindTexture(GL_TEXTURE_2D, state_->atlasTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 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_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glCheck();

state_->atlasTexSize = {
(float)(int)(width / SwanCommon::TILE_SIZE),
(float)(int)(height / SwanCommon::TILE_SIZE) };
}

void Renderer::modifyTile(TileID id, const void *data) {
int w = (int)state_->atlasTexSize.x;
int x = id % w;
int y = id / w;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, state_->atlasTex);
glTexSubImage2D(
GL_TEXTURE_2D, 0, x * SwanCommon::TILE_SIZE, y * SwanCommon::TILE_SIZE,
SwanCommon::TILE_SIZE, SwanCommon::TILE_SIZE,
GL_RGBA, GL_UNSIGNED_BYTE, data);
glCheck();
}

RenderChunk Renderer::createChunk(
TileID tiles[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]) {
// TODO: Maybe don't do this here? Maybe instead store the buffer and
// upload the texture in the draw method?
// The current approach needs createChunk to be called on the graphics thread.

RenderChunk chunk;
glGenTextures(1, &chunk.tex);
glCheck();

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, chunk.tex);
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_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glCheck();

static_assert(
std::endian::native == std::endian::big ||
std::endian::native == std::endian::little,
"Expected either big or little endian");

if constexpr (std::endian::native == std::endian::little) {
glTexImage2D(
GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA,
SwanCommon::CHUNK_WIDTH, SwanCommon::CHUNK_HEIGHT,
0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, tiles);
} else if constexpr (std::endian::native == std::endian::big) {
uint8_t buf[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT * 2];
for (size_t y = 0; y < SwanCommon::CHUNK_HEIGHT; ++y) {
for (size_t x = 0; x < SwanCommon::CHUNK_WIDTH; ++x) {
size_t dst = y * SwanCommon::CHUNK_WIDTH * 2 + x * 2;
size_t src = y * SwanCommon::CHUNK_WIDTH + x;
buf[dst + 0] = tiles[src] & 0xff;
buf[dst + 1] = (tiles[src] & 0xff00) >> 8;
}
}

glTexImage2D(
GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA,
SwanCommon::CHUNK_WIDTH, SwanCommon::CHUNK_HEIGHT,
0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, buf);
}

glCheck();
return chunk;
}

void Renderer::modifyChunk(RenderChunk chunk, SwanCommon::Vec2i pos, TileID id) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, chunk.tex);
glCheck();

static_assert(
std::endian::native == std::endian::big ||
std::endian::native == std::endian::little,
"Expected either big or little endian");

if constexpr (std::endian::native == std::endian::little) {
glTexSubImage2D(
GL_TEXTURE_2D, 0, pos.x, pos.y, 1, 1,
GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, &id);
} else if constexpr (std::endian::native == std::endian::big) {
uint8_t buf[] = { (uint8_t)(id & 0xff), (uint8_t)((id & 0xff00) >> 8) };
glTexSubImage2D(
GL_TEXTURE_2D, 0, pos.x, pos.y, 1, 1,
GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, buf);
}

glCheck();
}

void Renderer::destroyChunk(RenderChunk chunk) {
glDeleteTextures(1, &chunk.tex);
glCheck();
}

RenderSprite Renderer::createSprite(void *data, int width, int height, int fh) {
RenderSprite sprite;
sprite.scale = {
(float)width / SwanCommon::TILE_SIZE,
(float)fh / SwanCommon::TILE_SIZE };
sprite.frameCount = height / fh;
glGenTextures(1, &sprite.tex);
glCheck();

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, sprite.tex);
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();

glTexImage2D(
GL_TEXTURE_2D, 0, GL_RGBA, width, height,
0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glCheck();

return sprite;
}

void Renderer::registerTileTexture(size_t tileId, const void *data, size_t len) {
state_->atlas.addTile(tileId, data, len);
RenderSprite Renderer::createSprite(void *data, int width, int height) {
return createSprite(data, width, height, height);
}

void Renderer::uploadTileTexture() {
size_t w, h;
const unsigned char *data = state_->atlas.getImage(&w, &h);
state_->atlasTex.upload(w, h, (void *)data, GL_RGBA, GL_UNSIGNED_BYTE);
void Renderer::destroySprite(RenderSprite sprite) {
glDeleteTextures(1, &sprite.tex);
}

}

+ 29
- 0
libcygnet/src/ResourceManager.cc View File

@@ -0,0 +1,29 @@
#include "ResourceManager.h"

namespace Cygnet {

ResourceManager::ResourceManager(ResourceBuilder &&builder):
rnd_(builder.rnd_), sprites_(std::move(builder.sprites_)),
tile_anims_(std::move(builder.tile_anims_)) {
size_t width, height;
const unsigned char *data = builder.atlas_.getImage(&width, &height);
rnd_.uploadTileAtlas(data, width, height);
}

ResourceManager::~ResourceManager() {
for (auto &[name, sprite]: sprites_) {
rnd_.destroySprite(sprite);
}
}

void ResourceManager::tick() {
// TODO: Maybe do a GPU->GPU copy instead of an upload from the CPU?
for (auto &anim: tile_anims_) {
anim.index = (anim.index + 1) % anim.frames;
unsigned char *data = anim.data.get() +
SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE * 4 * anim.index;
rnd_.modifyTile(anim.id, data);
}
}

}

+ 12
- 14
libcygnet/src/TileAtlas.cc View File

@@ -2,11 +2,13 @@

#include <iostream>
#include <vector>
#include <SDL_opengles2.h>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <swan-common/constants.h>

#include "gl.h"

namespace Cygnet {

struct AtlasState {
@@ -20,20 +22,16 @@ struct AtlasState {
TileAtlas::TileAtlas(): state_(std::make_unique<AtlasState>()) {
GLint size;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &size);
state_->tilesPerLine = size / SwanCommon::TILE_SIZE;
state_->tilesPerLine = std::min(size / SwanCommon::TILE_SIZE, 256);
}

TileAtlas::~TileAtlas() = default;

void TileAtlas::addTile(size_t tileId, const void *data, size_t len) {
size_t rows = len / (SwanCommon::TILE_SIZE * 4);
void TileAtlas::addTile(size_t tileId, const void *data) {
const unsigned char *bytes = (const unsigned char *)data;
size_t x = tileId % state_->tilesPerLine;
size_t y = tileId / state_->tilesPerLine;
if (y >= state_->tilesPerLine) {
std::cerr << "Cygnet: Warning: Tile ID " << tileId << " too big for texture atlas\n";
return;
}
std::cerr << "Tile " << tileId << " to " << x << ", " << y << '\n';

if (state_->width <= x) {
state_->width = x + 1;
@@ -43,21 +41,21 @@ void TileAtlas::addTile(size_t tileId, const void *data, size_t len) {
state_->height = y + 1;
}

size_t tileImgSize = SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE * 4;
size_t requiredSize = (x + 1) * (y + 1) * tileImgSize;
size_t requiredSize = state_->tilesPerLine * SwanCommon::TILE_SIZE *
state_->height * SwanCommon::TILE_SIZE * 4;
state_->data.resize(requiredSize);

for (size_t ty = 0; ty < rows; ++ty) {
for (size_t ty = 0; ty < SwanCommon::TILE_SIZE; ++ty) {
const unsigned char *src = bytes + ty * SwanCommon::TILE_SIZE * 4;
unsigned char *dest = state_->data.data() +
((y + ty) * state_->width * SwanCommon::TILE_SIZE * 4) +
(y * SwanCommon::TILE_SIZE + ty) * state_->tilesPerLine * SwanCommon::TILE_SIZE * 4 +
(x * SwanCommon::TILE_SIZE * 4);
const unsigned char *src = bytes + ty * SwanCommon::TILE_SIZE * 4;
memcpy(dest, src, SwanCommon::TILE_SIZE * 4);
}
}

const unsigned char *TileAtlas::getImage(size_t *w, size_t *h) {
*w = state_->width * SwanCommon::TILE_SIZE;
*w = state_->tilesPerLine * SwanCommon::TILE_SIZE;
*h = state_->height * SwanCommon::TILE_SIZE;
return state_->data.data();
}

+ 5
- 2
libcygnet/src/Window.cc View File

@@ -1,8 +1,8 @@
#include "Window.h"

#include <SDL.h>
#include <SDL_opengles2.h>

#include "gl.h"
#include "util.h"

namespace Cygnet {
@@ -28,10 +28,13 @@ Window::Window(const char *name, int w, int h):
glEnable(GL_BLEND);
glCheck();

SDL_GetWindowSize(state_->window, &w, &h);
onResize(w, h);
}

Window::~Window() = default;
Window::~Window() {
SDL_DestroyWindow(state_->window);
}

void Window::makeCurrent() {
SDL_GL_MakeCurrent(state_->window, state_->glctx);

+ 46
- 19
libcygnet/src/shaders.cc View File

@@ -2,41 +2,68 @@

namespace Cygnet::Shaders {

const char *basicVx = R"glsl(
const char *spriteVx = R"glsl(
uniform mat3 camera;
uniform mat3 transform;
attribute vec2 position;
uniform vec3 frameInfo; // frame height, frame count, frame index
attribute vec2 vertex;
varying vec2 v_texCoord;

void main() {
vec3 pos = transform * vec3(position, 0);
gl_Position = vec4(pos.x, pos.y, 0, 1);
v_texCoord = vec2(
vertex.x,
(frameInfo.x * frameInfo.z + (frameInfo.x * vertex.y)) /
(frameInfo.x * frameInfo.y));

vec3 pos = camera * transform * vec3(vertex, 1);
gl_Position = vec4(pos.xy, 0, 1);
}
)glsl";

const char *texturedVx = R"glsl(
uniform mat3 transform;
attribute vec2 position;
attribute vec2 texCoord;
const char *spriteFr = R"glsl(
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D tex;

void main() {
vec3 pos = transform * vec3(position, 0);
gl_Position = vec4(pos.x, pos.y, 0, 1);
v_texCoord = texCoord;
gl_FragColor = texture2D(tex, v_texCoord);
}
)glsl";

const char *solidColorFr = R"glsl(
precision mediump float;
uniform vec4 color;
const char *chunkVx = R"glsl(
uniform mat3 camera;
uniform vec2 pos;
attribute vec2 vertex;
varying vec2 v_tileCoord;

void main() {
gl_FragColor = color;
vec3 pos = camera * vec3(pos + vertex, 1);
gl_Position = vec4(pos.xy, 0, 1);
v_tileCoord = vec2(vertex.x, vertex.y);
}
)glsl";

const char *texturedFr = R"glsl(
const char *chunkFr = R"glsl(
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D tex;
#define TILE_SIZE 32.0
#define CHUNK_WIDTH 64
#define CHUNK_HEIGHT 64

varying vec2 v_tileCoord;
uniform sampler2D tileAtlas;
uniform vec2 tileAtlasSize;
uniform sampler2D tiles;

void main() {
gl_FragColor = texture2D(tex, v_texCoord);
vec2 tilePos = floor(vec2(v_tileCoord.x, v_tileCoord.y));
vec4 tileColor = texture2D(tiles, tilePos / vec2(CHUNK_WIDTH, CHUNK_HEIGHT));
float tileID = floor((tileColor.r * 256.0 + tileColor.a) * 256.0);

vec2 atlasPos = vec2(
tileID + v_tileCoord.x - tilePos.x,
floor(tileID / tileAtlasSize.x) + v_tileCoord.y - tilePos.y);

gl_FragColor = texture2D(tileAtlas, atlasPos / tileAtlasSize);
}
)glsl";


+ 3
- 1
libcygnet/src/util.cc View File

@@ -1,6 +1,8 @@
#include "util.h"

#include <SDL_opengles2.h>
#include <SDL.h>

#include "gl.h"

namespace Cygnet {


+ 147
- 8
src/cygnet-test.cc View File

@@ -1,37 +1,176 @@
#include <cygnet/Context.h>
#include <cygnet/Window.h>
#include <cygnet/Renderer.h>
#include <cygnet/ResourceManager.h>
#include <swan-common/constants.h>

#include <time.h>
#include <stdint.h>
#include <iostream>
#include <SDL_image.h>
#include <SDL.h>

double getTime() {
struct timespec tv;
clock_gettime(CLOCK_MONOTONIC, &tv);
return tv.tv_sec + tv.tv_nsec / 1000000000.0;
}

void addTile(Cygnet::ResourceBuilder &builder, const char *path) {
static size_t id = 0;
SDL_Surface *surf = IMG_Load(path);
builder.addTile(id++, surf->pixels);
SDL_FreeSurface(surf);
}

Cygnet::RenderSprite loadSprite(Cygnet::ResourceBuilder &builder, const char *path, int fh) {
SDL_Surface *surf = IMG_Load(path);
auto sprite = builder.addSprite(path, surf->pixels, surf->w, surf->h, fh);
SDL_FreeSurface(surf);
return sprite;
}

Cygnet::RenderSprite loadSprite(Cygnet::ResourceBuilder &builder, const char *path) {
SDL_Surface *surf = IMG_Load(path);
auto sprite = builder.addSprite(path, surf->pixels, surf->w, surf->h);
SDL_FreeSurface(surf);
return sprite;
}

int main() {
Cygnet::Context ctx;
Cygnet::Window win("Cygnet Test", 640, 480);
IMG_Init(IMG_INIT_PNG);
Cygnet::Window win("Cygnet Test", 680, 680);
Cygnet::Renderer rnd;
Cygnet::ResourceBuilder rbuilder(rnd);

for (auto path: {
"core.mod/assets/tile/dirt.png",
"core.mod/assets/tile/grass.png",
"core.mod/assets/tile/leaves.png",
"core.mod/assets/tile/stone.png",
"core.mod/assets/tile/torch.png",
"core.mod/assets/tile/tree-trunk.png",
}) addTile(rbuilder, path);

uint32_t img[SwanCommon::TILE_SIZE * SwanCommon::TILE_SIZE];
for (size_t i = 0; i < sizeof(img) / sizeof(*img); ++i) {
img[i] = 0xff00aaff;
unsigned char lolTexture[32*32*4*3];
for (size_t i = 0; i < 3; ++i) {
int col = 100 * i + 50;;
for (size_t y = 0; y < 32; ++y) {
for (size_t x = 0; x < 32; ++x) {
lolTexture[i * 32 * 32 * 4 + y * 32 * 4 + x * 4 + 0] = col;
lolTexture[i * 32 * 32 * 4 + y * 32 * 4 + x * 4 + 1] = col;
lolTexture[i * 32 * 32 * 4 + y * 32 * 4 + x * 4 + 2] = col;
lolTexture[i * 32 * 32 * 4 + y * 32 * 4 + x * 4 + 3] = 255;
}
}
}
rnd.registerTileTexture(0, img, sizeof(img));
rnd.uploadTileTexture();
rbuilder.addTile(10, lolTexture, 3);

Cygnet::RenderSprite playerSprite = loadSprite(
rbuilder, "core.mod/assets/entity/player-still.png", 64);

Cygnet::ResourceManager resources(std::move(rbuilder));

Cygnet::RenderChunk chunk;
{
uint16_t tiles[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT];
memset(tiles, 0, sizeof(tiles));
tiles[0] = 1;
tiles[1] = 2;
tiles[2] = 3;
tiles[10] = 10;
chunk = rnd.createChunk(tiles);
}

Cygnet::RenderCamera cam = {
.pos = { 0, 0 },
.size = win.size(),
.zoom = 1,
};

float lol = 0;

bool keys[512] = { 0 };

double tileAnimAcc = 0;
double fpsAcc = 0;
double prevTime = getTime() - 1/60.0;
int frames = 0;
float x = 0, y = 0;

while (true) {
double currTime = getTime();
double dt = currTime - prevTime;
prevTime = currTime;
lol += dt;

fpsAcc += dt;
frames += 1;
if (fpsAcc >= 2) {
std::cerr << "FPS: " << (frames / 2.0) << '\n';
fpsAcc -= 2;
frames = 0;
}

tileAnimAcc += dt;
if (tileAnimAcc >= 0.5) {
resources.tick();
tileAnimAcc -= 0.5;
}

SDL_Event evt;
while (SDL_PollEvent(&evt)) {
switch (evt.type) {
case SDL_QUIT:
goto exit;

case SDL_WINDOWEVENT:
switch (evt.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
win.onResize(evt.window.data1, evt.window.data2);
cam.size = win.size();
break;
}
break;

case SDL_MOUSEWHEEL:
cam.zoom += evt.wheel.y * 0.1 * cam.zoom;
break;

case SDL_KEYDOWN:
keys[evt.key.keysym.scancode] = true;
break;

case SDL_KEYUP:
keys[evt.key.keysym.scancode] = false;
break;
}
}

if (keys[SDL_SCANCODE_A]) {
x -= 1 * dt;
}
if (keys[SDL_SCANCODE_D]) {
x += 1 * dt;
}
if (keys[SDL_SCANCODE_W]) {
y -= 1 * dt;
}
if (keys[SDL_SCANCODE_S]) {
y += 1 * dt;
}

rnd.drawChunk(chunk, { 0, 0 });

rnd.drawSprite(playerSprite, { x, y }, (int)lol % 2);
cam.pos = { x, y };

win.clear();
rnd.draw();
rnd.draw(cam);
win.flip();
}

exit:
;
IMG_Quit();
}

Loading…
Cancel
Save