@@ -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 |
@@ -21,7 +21,7 @@ public: | |||
}; | |||
GlShader(Type type, const char *source); | |||
~GlShader(); | |||
virtual ~GlShader(); | |||
GLuint id() const { return id_; } | |||
@@ -23,6 +23,10 @@ struct RenderSprite { | |||
int frameCount; | |||
}; | |||
struct RenderTile { | |||
uint16_t id; | |||
}; | |||
struct RenderCamera { | |||
SwanCommon::Vec2 pos; | |||
SwanCommon::Vec2i size; | |||
@@ -43,16 +47,16 @@ public: | |||
void draw(const RenderCamera &cam); | |||
void registerTileTexture(TileID tileId, const void *data, size_t len); | |||
void uploadTileTexture(); | |||
void uploadTileAtlas(const void *data, int width, int height); | |||
void modifyTile(TileID id, const void *data); | |||
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); | |||
RenderSprite createSprite(void *data, int width, int height, int fh); | |||
RenderSprite createSprite(void *data, int width, int height); | |||
void destroySprite(RenderSprite sprite); | |||
private: |
@@ -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), | |||
}); | |||
} | |||
} | |||
} |
@@ -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: |
@@ -134,11 +134,14 @@ struct RendererState { | |||
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; | |||
@@ -159,13 +162,11 @@ void Renderer::draw(const RenderCamera &cam) { | |||
glUniformMatrix3fv(chunkProg.camera, 1, GL_TRUE, camMat.data()); | |||
glCheck(); | |||
glUniform2f(chunkProg.tileAtlasSize, | |||
(float)(int)(state_->atlasTex.width() / SwanCommon::TILE_SIZE), | |||
(float)(int)(state_->atlasTex.height() / SwanCommon::TILE_SIZE)); | |||
glUniform2f(chunkProg.tileAtlasSize, state_->atlasTexSize.x, state_->atlasTexSize.y); | |||
glCheck(); | |||
glActiveTexture(GL_TEXTURE0); | |||
glBindTexture(GL_TEXTURE_2D, state_->atlasTex.id()); | |||
glBindTexture(GL_TEXTURE_2D, state_->atlasTex); | |||
glCheck(); | |||
glActiveTexture(GL_TEXTURE1); | |||
@@ -200,15 +201,31 @@ void Renderer::draw(const RenderCamera &cam) { | |||
} | |||
} | |||
void Renderer::registerTileTexture(TileID tileId, const void *data, size_t len) { | |||
state_->atlas.addTile(tileId, data, len); | |||
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_CLAMP_TO_EDGE); | |||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |||
glCheck(); | |||
state_->atlasTexSize = { | |||
(float)(int)(width / SwanCommon::TILE_SIZE), | |||
(float)(int)(height / SwanCommon::TILE_SIZE) }; | |||
} | |||
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); | |||
std::cerr << "Uploaded image of size " << w << 'x' << h << '\n'; | |||
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( |
@@ -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); | |||
} | |||
} | |||
} |
@@ -27,8 +27,7 @@ TileAtlas::TileAtlas(): state_(std::make_unique<AtlasState>()) { | |||
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; | |||
@@ -46,7 +45,7 @@ void TileAtlas::addTile(size_t tileId, const void *data, size_t len) { | |||
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 * SwanCommon::TILE_SIZE + ty) * state_->tilesPerLine * SwanCommon::TILE_SIZE * 4 + |
@@ -32,7 +32,9 @@ Window::Window(const char *name, int w, int h): | |||
onResize(w, h); | |||
} | |||
Window::~Window() = default; | |||
Window::~Window() { | |||
SDL_DestroyWindow(state_->window); | |||
} | |||
void Window::makeCurrent() { | |||
SDL_GL_MakeCurrent(state_->window, state_->glctx); |
@@ -1,6 +1,7 @@ | |||
#include <cygnet/Context.h> | |||
#include <cygnet/Window.h> | |||
#include <cygnet/Renderer.h> | |||
#include <cygnet/ResourceManager.h> | |||
#include <swan-common/constants.h> | |||
#include <time.h> | |||
@@ -15,23 +16,23 @@ double getTime() { | |||
return tv.tv_sec + tv.tv_nsec / 1000000000.0; | |||
} | |||
void addTile(Cygnet::Renderer &rnd, const char *path) { | |||
void addTile(Cygnet::ResourceBuilder &builder, const char *path) { | |||
static size_t id = 0; | |||
SDL_Surface *surf = IMG_Load(path); | |||
rnd.registerTileTexture(id++, surf->pixels, surf->pitch * surf->h); | |||
builder.addTile(id++, surf->pixels); | |||
SDL_FreeSurface(surf); | |||
} | |||
Cygnet::RenderSprite loadSprite(Cygnet::Renderer &rnd, const char *path, int fh) { | |||
Cygnet::RenderSprite loadSprite(Cygnet::ResourceBuilder &builder, const char *path, int fh) { | |||
SDL_Surface *surf = IMG_Load(path); | |||
auto sprite = rnd.createSprite(surf->pixels, surf->w, surf->h, fh); | |||
auto sprite = builder.addSprite(path, surf->pixels, surf->w, surf->h, fh); | |||
SDL_FreeSurface(surf); | |||
return sprite; | |||
} | |||
Cygnet::RenderSprite loadSprite(Cygnet::Renderer &rnd, const char *path) { | |||
Cygnet::RenderSprite loadSprite(Cygnet::ResourceBuilder &builder, const char *path) { | |||
SDL_Surface *surf = IMG_Load(path); | |||
auto sprite = rnd.createSprite(surf->pixels, surf->w, surf->h); | |||
auto sprite = builder.addSprite(path, surf->pixels, surf->w, surf->h); | |||
SDL_FreeSurface(surf); | |||
return sprite; | |||
} | |||
@@ -41,6 +42,7 @@ int main() { | |||
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", | |||
@@ -49,10 +51,26 @@ int main() { | |||
"core.mod/assets/tile/stone.png", | |||
"core.mod/assets/tile/torch.png", | |||
"core.mod/assets/tile/tree-trunk.png", | |||
}) addTile(rnd, path); | |||
rnd.uploadTileTexture(); | |||
}) addTile(rbuilder, path); | |||
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; | |||
} | |||
} | |||
} | |||
rbuilder.addTile(10, lolTexture, 3); | |||
Cygnet::RenderSprite playerSprite = loadSprite( | |||
rbuilder, "core.mod/assets/entity/player-still.png", 64); | |||
Cygnet::RenderSprite playerSprite = loadSprite(rnd, "core.mod/assets/entity/player-still.png", 64); | |||
Cygnet::ResourceManager resources(std::move(rbuilder)); | |||
Cygnet::RenderChunk chunk; | |||
{ | |||
@@ -61,6 +79,7 @@ int main() { | |||
tiles[0] = 1; | |||
tiles[1] = 2; | |||
tiles[2] = 3; | |||
tiles[10] = 10; | |||
chunk = rnd.createChunk(tiles); | |||
} | |||
@@ -74,7 +93,8 @@ int main() { | |||
bool keys[512] = { 0 }; | |||
double acc = 0; | |||
double tileAnimAcc = 0; | |||
double fpsAcc = 0; | |||
double prevTime = getTime() - 1/60.0; | |||
int frames = 0; | |||
float x = 0, y = 0; | |||
@@ -83,15 +103,22 @@ int main() { | |||
double currTime = getTime(); | |||
double dt = currTime - prevTime; | |||
prevTime = currTime; | |||
acc += dt; | |||
lol += dt; | |||
fpsAcc += dt; | |||
frames += 1; | |||
if (acc >= 2) { | |||
if (fpsAcc >= 2) { | |||
std::cerr << "FPS: " << (frames / 2.0) << '\n'; | |||
acc -= 2; | |||
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) { | |||
@@ -134,11 +161,6 @@ int main() { | |||
y += 1 * dt; | |||
} | |||
lol += 1 * dt; | |||
rnd.modifyChunk(chunk, { 0, 0 }, (int)lol % 6); | |||
rnd.modifyChunk(chunk, { 4, 4 }, ((int)(lol / 2) + 3) % 6); | |||
rnd.modifyChunk(chunk, { 3, 2 }, ((int)(lol * 1.5) + 7) % 6); | |||
rnd.drawChunk(chunk, { 0, 0 }); | |||
rnd.drawSprite(playerSprite, { x, y }, (int)lol % 2); |