#include "Renderer.h" #include #include #include #include #include // std::endian was originally in type_traits, was moved to bit #include #include #include "shaders.h" #include "GlWrappers.h" #include "TileAtlas.h" #include "util.h" namespace Cygnet { struct SpriteProg: public GlProgram { template SpriteProg(const T &... shaders): GlProgram(shaders...) { init(); } ~SpriteProg() { deinit(); } GLint camera = uniformLoc("camera"); GLint transform = uniformLoc("transform"); GLint vertex = attribLoc("vertex"); GLint texCoord = attribLoc("texCoord"); GLint tex = uniformLoc("tex"); GLuint vbo; static constexpr GLfloat vertexes[] = { -0.5f, 0.5f, // pos 0: top left 0.0f, 0.0f, // tex 0: top left -0.5f, -0.5f, // pos 1: bottom left 0.0f, 1.0f, // tex 1: bottom left 0.5f, -0.5f, // pos 2: bottom right 1.0f, 1.0f, // tex 2: bottom right 0.5f, -0.5f, // pos 2: bottom right 1.0f, 1.0f, // tex 2: bottom right 0.5f, 0.5f, // pos 3: top right 1.0f, 0.0f, // tex 3: top right -0.5f, 0.5f, // pos 0: top left 0.0f, 0.0f, // tex 0: top left }; void enable() { glUseProgram(id()); glBindBuffer(GL_ARRAY_BUFFER, vbo); glVertexAttribPointer(vertex, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void *)0); glVertexAttribPointer(texCoord, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void *)(2 * sizeof(GLfloat))); glEnableVertexAttribArray(vertex); glEnableVertexAttribArray(texCoord); glCheck(); } void disable() { glDisableVertexAttribArray(vertex); glDisableVertexAttribArray(texCoord); 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 ChunkProg: public GlProgram { template ChunkProg(const T &... shaders): GlProgram(shaders...) { init(); } ~ChunkProg() { deinit(); } 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 RendererState { GlVxShader spriteVx{Shaders::spriteVx}; GlFrShader spriteFr{Shaders::spriteFr}; GlVxShader chunkVx{Shaders::chunkVx}; GlFrShader chunkFr{Shaders::chunkFr}; SpriteProg spriteProg{spriteVx, spriteFr}; ChunkProg chunkProg{chunkVx, chunkFr}; TileAtlas atlas; GlTexture atlasTex; }; Renderer::Renderer(): state_(std::make_unique()) {} Renderer::~Renderer() = default; void Renderer::draw(const RenderCamera &cam) { Mat3gf camMat; camMat.translate({ -cam.pos.x, cam.pos.y }); // TODO: Change something to make this -cam.pos camMat.scale({ cam.zoom, cam.zoom }); auto &chunkProg = state_->chunkProg; chunkProg.enable(); glUniform2f(chunkProg.tileAtlasSize, (float)(int)(state_->atlasTex.width() / SwanCommon::TILE_SIZE), (float)(int)(state_->atlasTex.height() / SwanCommon::TILE_SIZE)); glUniformMatrix3fv(chunkProg.camera, 1, GL_TRUE, camMat.data()); glCheck(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, state_->atlasTex.id()); // Necessary? 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(); /* state_->spriteProg.enable(); state_->camera.translate(-0.00001, 0); glUniformMatrix3fv(state_->spriteProg.transform, 1, GL_TRUE, Mat3gf::IDENTITY.data()); glUniformMatrix3fv(state_->spriteProg.camera, 1, GL_TRUE, state_->camera.data()); glCheck(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,state_->atlasTex.id()); // Necessary? glUniform1i(state_->spriteProg.tex, 0); glCheck(); glDrawArrays(GL_TRIANGLES, 0, 6); glCheck(); state_->spriteProg.disable(); */ } void Renderer::registerTileTexture(TileID tileId, const void *data, size_t len) { state_->atlas.addTile(tileId, data, len); } 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'; } 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(); 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_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 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); } return chunk; } void Renderer::destroyChunk(RenderChunk chunk) { glDeleteTextures(1, &chunk.tex); } }