src/Context.cc | src/Context.cc | ||||
src/GlWrappers.cc | src/GlWrappers.cc | ||||
src/Renderer.cc | src/Renderer.cc | ||||
src/ResourceManager.cc | |||||
src/shaders.cc | src/shaders.cc | ||||
src/TileAtlas.cc | src/TileAtlas.cc | ||||
src/util.cc | src/util.cc |
}; | }; | ||||
GlShader(Type type, const char *source); | GlShader(Type type, const char *source); | ||||
~GlShader(); | |||||
virtual ~GlShader(); | |||||
GLuint id() const { return id_; } | GLuint id() const { return id_; } | ||||
int frameCount; | int frameCount; | ||||
}; | }; | ||||
struct RenderTile { | |||||
uint16_t id; | |||||
}; | |||||
struct RenderCamera { | struct RenderCamera { | ||||
SwanCommon::Vec2 pos; | SwanCommon::Vec2 pos; | ||||
SwanCommon::Vec2i size; | SwanCommon::Vec2i size; | ||||
void draw(const RenderCamera &cam); | 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( | RenderChunk createChunk( | ||||
TileID tiles[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]); | TileID tiles[SwanCommon::CHUNK_WIDTH * SwanCommon::CHUNK_HEIGHT]); | ||||
void modifyChunk(RenderChunk chunk, SwanCommon::Vec2i pos, TileID id); | void modifyChunk(RenderChunk chunk, SwanCommon::Vec2i pos, TileID id); | ||||
void destroyChunk(RenderChunk chunk); | 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, int fh); | ||||
RenderSprite createSprite(void *data, int width, int height); | |||||
void destroySprite(RenderSprite sprite); | void destroySprite(RenderSprite sprite); | ||||
private: | private: |
#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), | |||||
}); | |||||
} | |||||
} | |||||
} |
TileAtlas(); | TileAtlas(); | ||||
~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); | const unsigned char *getImage(size_t *w, size_t *h); | ||||
private: | private: |
SpriteProg spriteProg{spriteVx, spriteFr}; | SpriteProg spriteProg{spriteVx, spriteFr}; | ||||
ChunkProg chunkProg{chunkVx, chunkFr}; | 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; | Renderer::~Renderer() = default; | ||||
glUniformMatrix3fv(chunkProg.camera, 1, GL_TRUE, camMat.data()); | glUniformMatrix3fv(chunkProg.camera, 1, GL_TRUE, camMat.data()); | ||||
glCheck(); | 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(); | glCheck(); | ||||
glActiveTexture(GL_TEXTURE0); | glActiveTexture(GL_TEXTURE0); | ||||
glBindTexture(GL_TEXTURE_2D, state_->atlasTex.id()); | |||||
glBindTexture(GL_TEXTURE_2D, state_->atlasTex); | |||||
glCheck(); | glCheck(); | ||||
glActiveTexture(GL_TEXTURE1); | glActiveTexture(GL_TEXTURE1); | ||||
} | } | ||||
} | } | ||||
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( | RenderChunk Renderer::createChunk( |
#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); | |||||
} | |||||
} | |||||
} |
TileAtlas::~TileAtlas() = default; | 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; | const unsigned char *bytes = (const unsigned char *)data; | ||||
size_t x = tileId % state_->tilesPerLine; | size_t x = tileId % state_->tilesPerLine; | ||||
size_t y = tileId / state_->tilesPerLine; | size_t y = tileId / state_->tilesPerLine; | ||||
state_->height * SwanCommon::TILE_SIZE * 4; | state_->height * SwanCommon::TILE_SIZE * 4; | ||||
state_->data.resize(requiredSize); | 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; | const unsigned char *src = bytes + ty * SwanCommon::TILE_SIZE * 4; | ||||
unsigned char *dest = state_->data.data() + | unsigned char *dest = state_->data.data() + | ||||
(y * SwanCommon::TILE_SIZE + ty) * state_->tilesPerLine * SwanCommon::TILE_SIZE * 4 + | (y * SwanCommon::TILE_SIZE + ty) * state_->tilesPerLine * SwanCommon::TILE_SIZE * 4 + |
onResize(w, h); | onResize(w, h); | ||||
} | } | ||||
Window::~Window() = default; | |||||
Window::~Window() { | |||||
SDL_DestroyWindow(state_->window); | |||||
} | |||||
void Window::makeCurrent() { | void Window::makeCurrent() { | ||||
SDL_GL_MakeCurrent(state_->window, state_->glctx); | SDL_GL_MakeCurrent(state_->window, state_->glctx); |
#include <cygnet/Context.h> | #include <cygnet/Context.h> | ||||
#include <cygnet/Window.h> | #include <cygnet/Window.h> | ||||
#include <cygnet/Renderer.h> | #include <cygnet/Renderer.h> | ||||
#include <cygnet/ResourceManager.h> | |||||
#include <swan-common/constants.h> | #include <swan-common/constants.h> | ||||
#include <time.h> | #include <time.h> | ||||
return tv.tv_sec + tv.tv_nsec / 1000000000.0; | 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; | static size_t id = 0; | ||||
SDL_Surface *surf = IMG_Load(path); | SDL_Surface *surf = IMG_Load(path); | ||||
rnd.registerTileTexture(id++, surf->pixels, surf->pitch * surf->h); | |||||
builder.addTile(id++, surf->pixels); | |||||
SDL_FreeSurface(surf); | 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); | 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); | SDL_FreeSurface(surf); | ||||
return sprite; | 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); | 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); | SDL_FreeSurface(surf); | ||||
return sprite; | return sprite; | ||||
} | } | ||||
IMG_Init(IMG_INIT_PNG); | IMG_Init(IMG_INIT_PNG); | ||||
Cygnet::Window win("Cygnet Test", 680, 680); | Cygnet::Window win("Cygnet Test", 680, 680); | ||||
Cygnet::Renderer rnd; | Cygnet::Renderer rnd; | ||||
Cygnet::ResourceBuilder rbuilder(rnd); | |||||
for (auto path: { | for (auto path: { | ||||
"core.mod/assets/tile/dirt.png", | "core.mod/assets/tile/dirt.png", | ||||
"core.mod/assets/tile/stone.png", | "core.mod/assets/tile/stone.png", | ||||
"core.mod/assets/tile/torch.png", | "core.mod/assets/tile/torch.png", | ||||
"core.mod/assets/tile/tree-trunk.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; | Cygnet::RenderChunk chunk; | ||||
{ | { | ||||
tiles[0] = 1; | tiles[0] = 1; | ||||
tiles[1] = 2; | tiles[1] = 2; | ||||
tiles[2] = 3; | tiles[2] = 3; | ||||
tiles[10] = 10; | |||||
chunk = rnd.createChunk(tiles); | chunk = rnd.createChunk(tiles); | ||||
} | } | ||||
bool keys[512] = { 0 }; | bool keys[512] = { 0 }; | ||||
double acc = 0; | |||||
double tileAnimAcc = 0; | |||||
double fpsAcc = 0; | |||||
double prevTime = getTime() - 1/60.0; | double prevTime = getTime() - 1/60.0; | ||||
int frames = 0; | int frames = 0; | ||||
float x = 0, y = 0; | float x = 0, y = 0; | ||||
double currTime = getTime(); | double currTime = getTime(); | ||||
double dt = currTime - prevTime; | double dt = currTime - prevTime; | ||||
prevTime = currTime; | prevTime = currTime; | ||||
acc += dt; | |||||
lol += dt; | |||||
fpsAcc += dt; | |||||
frames += 1; | frames += 1; | ||||
if (acc >= 2) { | |||||
if (fpsAcc >= 2) { | |||||
std::cerr << "FPS: " << (frames / 2.0) << '\n'; | std::cerr << "FPS: " << (frames / 2.0) << '\n'; | ||||
acc -= 2; | |||||
fpsAcc -= 2; | |||||
frames = 0; | frames = 0; | ||||
} | } | ||||
tileAnimAcc += dt; | |||||
if (tileAnimAcc >= 0.5) { | |||||
resources.tick(); | |||||
tileAnimAcc -= 0.5; | |||||
} | |||||
SDL_Event evt; | SDL_Event evt; | ||||
while (SDL_PollEvent(&evt)) { | while (SDL_PollEvent(&evt)) { | ||||
switch (evt.type) { | switch (evt.type) { | ||||
y += 1 * dt; | 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.drawChunk(chunk, { 0, 0 }); | ||||
rnd.drawSprite(playerSprite, { x, y }, (int)lol % 2); | rnd.drawSprite(playerSprite, { x, y }, (int)lol % 2); |