/* * Copyright 2016 - 2021 gary@drinkingtea.net * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #define IMGUI_IMPL_OPENGL_ES3 #include #include #include #include #include #include namespace nostalgia::core { namespace renderer { constexpr auto TileRows = 128; constexpr auto TileColumns = 128; constexpr auto TileCount = TileRows * TileColumns; constexpr auto BgVertexVboRows = 4; constexpr auto BgVertexVboRowLength = 4; constexpr auto BgVertexVboLength = BgVertexVboRows * BgVertexVboRowLength; constexpr auto BgVertexEboLength = 6; struct Background { glutils::GLVertexArray vao; glutils::GLBuffer vbo; glutils::GLBuffer ebo; glutils::GLTexture tex; bool enabled = false; bool updated = false; std::array bgVertices = {}; std::array bgElements = {}; }; struct GlImplData { glutils::GLProgram bgShader; int64_t prevFpsCheckTime = 0; uint64_t draws = 0; std::array backgrounds; }; constexpr const GLchar *bgvshad = R"( {} in vec2 vTexCoord; in vec2 position; out vec2 fTexCoord; uniform float vTileHeight; void main() { gl_Position = vec4(position, 0.0, 1.0); fTexCoord = vTexCoord * vec2(1, vTileHeight); })"; constexpr const GLchar *bgfshad = R"( {} out vec4 outColor; in vec2 fTexCoord; uniform sampler2D image; void main() { //outColor = vec4(0.0, 0.7, 1.0, 1.0); outColor = texture(image, fTexCoord); })"; [[nodiscard]] static constexpr auto bgVertexRow(unsigned x, unsigned y) noexcept { return y * TileRows + x; } static void setTileBufferObject(Context *ctx, unsigned vi, float x, float y, int textureRow, float *vbo, GLuint *ebo) { // don't worry, this memcpy gets optimized to something much more ideal const auto [sw, sh] = getScreenSize(ctx); constexpr float ymod = 2.0f / 20.0f; const float xmod = ymod * static_cast(sh) / static_cast(sw); x *= xmod; y *= -ymod; x -= 1.0f; y += 1.0f - ymod; const float vertices[BgVertexVboLength] = { x, y, 0, static_cast(textureRow + 1), // bottom left x + xmod, y, 1, static_cast(textureRow + 1), // bottom right x + xmod, y + ymod, 1, static_cast(textureRow + 0), // top right x, y + ymod, 0, static_cast(textureRow + 0), // top left }; memcpy(vbo, vertices, sizeof(vertices)); const GLuint elms[BgVertexEboLength] = { vi + 0, vi + 1, vi + 2, vi + 2, vi + 3, vi + 0, }; memcpy(ebo, elms, sizeof(elms)); } static void sendVbo(const Background &bg) noexcept { // vbo glBindBuffer(GL_ARRAY_BUFFER, bg.vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(bg.bgVertices), &bg.bgVertices, GL_DYNAMIC_DRAW); } static void sendEbo(const Background &bg) noexcept { // ebo glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bg.ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(bg.bgElements), &bg.bgElements, GL_STATIC_DRAW); } static void initBackgroundBufferObjects(Context *ctx, Background *bg) noexcept { for (auto x = 0u; x < TileColumns; ++x) { for (auto y = 0u; y < TileRows; ++y) { const auto i = bgVertexRow(x, y); auto vbo = &bg->bgVertices[i * BgVertexVboLength]; auto ebo = &bg->bgElements[i * BgVertexEboLength]; setTileBufferObject(ctx, i * BgVertexVboRows, static_cast(x), static_cast(y), 0, vbo, ebo); } } } static glutils::GLVertexArray genVertexArrayObject() noexcept { GLuint vao = 0; glGenVertexArrays(1, &vao); return glutils::GLVertexArray(vao); } static glutils::GLBuffer genBuffer() noexcept { GLuint buff = 0; glGenBuffers(1, &buff); return glutils::GLBuffer(buff); } static void initBackgroundBufferset(Context *ctx, GLuint shader, Background *bg) noexcept { // vao bg->vao = genVertexArrayObject(); glBindVertexArray(bg->vao); // vbo & ebo bg->vbo = genBuffer(); bg->ebo = genBuffer(); initBackgroundBufferObjects(ctx, bg); sendVbo(*bg); sendEbo(*bg); // vbo layout auto posAttr = static_cast(glGetAttribLocation(shader, "position")); glEnableVertexAttribArray(posAttr); glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float), nullptr); auto texCoordAttr = static_cast(glGetAttribLocation(shader, "vTexCoord")); glEnableVertexAttribArray(texCoordAttr); glVertexAttribPointer(texCoordAttr, 2, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float), ox::bit_cast(2 * sizeof(float))); } static glutils::GLTexture loadTexture(GLsizei w, GLsizei h, void *pixels) noexcept { GLuint texId = 0; glGenTextures(1, &texId); glutils::GLTexture tex(texId); tex.width = w; tex.height = h; glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex.id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 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); return ox::move(tex); } static void tickFps(GlImplData *id) noexcept { ++id->draws; if (id->draws >= 500) { using namespace std::chrono; const auto now = duration_cast(system_clock::now().time_since_epoch()).count(); const auto duration = static_cast(now - id->prevFpsCheckTime) / 1000.0; const auto fps = static_cast(static_cast(id->draws) / duration); if constexpr(config::UserlandFpsPrint) { oxInfof("FPS: {}", fps); } oxTracef("nostalgia::core::gfx::gl::fps", "FPS: {}", fps); id->prevFpsCheckTime = now; id->draws = 0; } } static void drawBackground(Background *bg) noexcept { if (bg->enabled) { glBindVertexArray(bg->vao); if (bg->updated) { bg->updated = false; renderer::sendVbo(*bg); } glBindTexture(GL_TEXTURE_2D, bg->tex); glDrawElements(GL_TRIANGLES, static_cast(bg->bgElements.size()), GL_UNSIGNED_INT, nullptr); } } static void drawBackgrounds(GlImplData *id) noexcept { // load background shader and its uniforms glUseProgram(id->bgShader); const auto uniformTileHeight = static_cast(glGetUniformLocation(id->bgShader, "vTileHeight")); for (auto &bg : id->backgrounds) { glUniform1f(uniformTileHeight, 1.0f / static_cast(bg.tex.height / 8)); drawBackground(&bg); } } ox::Error init(Context *ctx) noexcept { constexpr auto GlslVersion = "#version 150"; const auto id = new GlImplData; ctx->setRendererData(id); const auto vshad = ox::sfmt(bgvshad, GlslVersion); const auto fshad = ox::sfmt(bgfshad, GlslVersion); oxReturnError(glutils::buildShaderProgram(vshad.c_str(), fshad.c_str()).moveTo(&id->bgShader)); for (auto &bg : id->backgrounds) { initBackgroundBufferset(ctx, id->bgShader, &bg); } ImGui_ImplOpenGL3_Init(GlslVersion); return OxError(0); } ox::Error shutdown(Context *ctx) noexcept { const auto id = ctx->rendererData(); ctx->setRendererData(nullptr); delete id; return OxError(0); } ox::Error loadBgTexture(Context *ctx, int section, void *pixels, int w, int h) noexcept { oxTracef("nostalgia::core::gfx::gl", "loadBgTexture: { section: {}, w: {}, h: {} }", section, w, h); const auto &id = ctx->rendererData(); auto &tex = id->backgrounds[static_cast(section)].tex; tex = loadTexture(w, h, pixels); return OxError(0); } } uint8_t bgStatus(Context *ctx) noexcept { const auto &id = ctx->rendererData(); uint8_t out = 0; for (unsigned i = 0; i < id->backgrounds.size(); ++i) { out |= id->backgrounds[i].enabled << i; } return out; } void setBgStatus(Context *ctx, uint32_t status) noexcept { const auto &id = ctx->rendererData(); for (unsigned i = 0; i < id->backgrounds.size(); ++i) { id->backgrounds[i].enabled = (status >> i) & 1; } } bool bgStatus(Context *ctx, unsigned bg) noexcept { const auto &id = ctx->rendererData(); return id->backgrounds[bg].enabled; } void setBgStatus(Context *ctx, unsigned bg, bool status) noexcept { const auto &id = ctx->rendererData(); id->backgrounds[bg].enabled = status; } void draw(Context *ctx) noexcept { const auto id = ctx->rendererData(); renderer::tickFps(id); ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); // clear screen glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); // render //bool showDemo = true; //ImGui::ShowDemoWindow(&showDemo); renderer::drawBackgrounds(id); ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); } void clearTileLayer(Context *ctx, int layer) noexcept { const auto id = ctx->rendererData(); auto &bg = id->backgrounds[static_cast(layer)]; initBackgroundBufferObjects(ctx, &bg); bg.updated = true; } void hideSprite(Context*, unsigned) noexcept { } void setSprite(Context*, unsigned, unsigned, unsigned, unsigned, unsigned, unsigned, unsigned) noexcept { } void setTile(Context *ctx, int layer, int column, int row, uint8_t tile) noexcept { const auto id = ctx->rendererData(); const auto z = static_cast(layer); const auto y = static_cast(row); const auto x = static_cast(column); const auto i = renderer::bgVertexRow(x, y); auto &bg = id->backgrounds[z]; auto vbo = &bg.bgVertices[i * renderer::BgVertexVboLength]; auto ebo = &bg.bgElements[i * renderer::BgVertexEboLength]; renderer::setTileBufferObject(ctx, i * renderer::BgVertexVboRows, static_cast(x), static_cast(y), tile, vbo, ebo); bg.updated = true; } }