From 59016ee8947caca9e1affd829b9d58c9b74ec89b Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Tue, 19 Dec 2023 22:37:18 -0600 Subject: [PATCH] [nostalgia/core] Fix OpenGL sprites, add priority --- .../core/include/nostalgia/core/gfx.hpp | 10 +- src/nostalgia/modules/core/src/gba/gfx.cpp | 41 ++-- src/nostalgia/modules/core/src/gfx.cpp | 4 - .../modules/core/src/opengl/context.hpp | 4 + src/nostalgia/modules/core/src/opengl/gfx.cpp | 224 +++++++++++------- src/nostalgia/modules/core/src/opengl/gfx.hpp | 15 +- 6 files changed, 171 insertions(+), 127 deletions(-) diff --git a/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp b/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp index ebb433aa..5f246dd1 100644 --- a/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp +++ b/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp @@ -16,23 +16,26 @@ namespace nostalgia::core { extern ox::Array charMap; struct Sprite { - unsigned idx = 0; + bool enabled = false; int x = 0; int y = 0; unsigned tileIdx = 0; unsigned spriteShape = 0; unsigned spriteSize = 0; unsigned flipX = 0; + unsigned priority = 0; }; oxModelBegin(Sprite) oxModelField(idx) oxModelField(x) oxModelField(y) + oxModelField(enabled) oxModelField(tileIdx) oxModelField(spriteShape) oxModelField(spriteSize) oxModelField(flipX) + oxModelField(priority) oxModelEnd() [[nodiscard]] @@ -70,10 +73,9 @@ void clearTileLayer(Context &ctx, unsigned bgIdx) noexcept; void hideSprite(Context &ctx, unsigned) noexcept; -void setSprite(Context &ctx, unsigned idx, int x, int y, unsigned tileIdx, - unsigned spriteShape = 0, unsigned spriteSize = 0, unsigned flipX = 0) noexcept; +void showSprite(Context &ctx, unsigned) noexcept; -void setSprite(Context &ctx, Sprite const&s) noexcept; +void setSprite(Context &c, uint_t idx, Sprite const&s) noexcept; } diff --git a/src/nostalgia/modules/core/src/gba/gfx.cpp b/src/nostalgia/modules/core/src/gba/gfx.cpp index 5119c92c..2968dc28 100644 --- a/src/nostalgia/modules/core/src/gba/gfx.cpp +++ b/src/nostalgia/modules/core/src/gba/gfx.cpp @@ -216,32 +216,37 @@ void clearTileLayer(Context&, unsigned bgIdx) noexcept { void hideSprite(Context&, unsigned idx) noexcept { //oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow"); teagba::GbaSpriteAttrUpdate oa; - oa.attr0 = 2 << 8; + oa.attr0 = uint16_t{0b11 << 8}; oa.idx = static_cast(idx); teagba::addSpriteUpdate(oa); } -void setSprite(Context&, - unsigned idx, - int x, - int y, - unsigned tileIdx, - unsigned spriteShape, - unsigned spriteSize, - unsigned flipX) noexcept { +[[maybe_unused]] +void showSprite(Context&, unsigned idx) noexcept { //oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow"); teagba::GbaSpriteAttrUpdate oa; - oa.attr0 = static_cast( - static_cast(y & ox::onMask(0b111'1111)) - | (static_cast(1) << 10) // enable alpha - | (static_cast(spriteShape) << 14)); - oa.attr1 = static_cast( - (static_cast(x) & ox::onMask(8)) - | (static_cast(flipX) << 12) - | (static_cast(spriteSize) << 14)); - oa.attr2 = static_cast(tileIdx & ox::onMask(8)); + oa.attr0 &= uint16_t{0b1111'1100'1111'1111}; oa.idx = static_cast(idx); teagba::addSpriteUpdate(oa); } +void setSprite(Context&, uint_t idx, Sprite const&s) noexcept { + //oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow"); + teagba::GbaSpriteAttrUpdate const oa{ + .attr0 = static_cast( + (static_cast(s.y & ox::onMask(0b111'1111))) + | (static_cast(1) << 10) // enable alpha + | (static_cast(s.spriteShape) << 14)), + .attr1 = static_cast( + (static_cast(s.x) & ox::onMask(8)) + | (static_cast(s.flipX) << 12) + | (static_cast(s.spriteSize) << 14)), + .attr2 = static_cast( + (static_cast(s.tileIdx & ox::onMask(8))) + | (static_cast(s.priority & 0b11) << 10)), + .idx = static_cast(idx), + }; + teagba::addSpriteUpdate(oa); +} + } diff --git a/src/nostalgia/modules/core/src/gfx.cpp b/src/nostalgia/modules/core/src/gfx.cpp index 956da1f9..d4204fb9 100644 --- a/src/nostalgia/modules/core/src/gfx.cpp +++ b/src/nostalgia/modules/core/src/gfx.cpp @@ -137,8 +137,4 @@ ox::Array charMap = { 50, // ~ }; -void setSprite(Context &c, Sprite const&s) noexcept { - setSprite(c, s.idx, s.x, s.y, s.tileIdx, s.spriteShape, s.spriteSize, s.flipX); -} - } diff --git a/src/nostalgia/modules/core/src/opengl/context.hpp b/src/nostalgia/modules/core/src/opengl/context.hpp index d50ad810..4a7163e1 100644 --- a/src/nostalgia/modules/core/src/opengl/context.hpp +++ b/src/nostalgia/modules/core/src/opengl/context.hpp @@ -27,6 +27,10 @@ class Context { ox::Array backgrounds; renderer::Drawer drawer; explicit Context(turbine::Context &tctx) noexcept; + Context(Context const&) = delete; + Context(Context&&) = delete; + Context &operator=(Context const&) = delete; + Context &operator=(Context&&) = delete; ~Context() noexcept; }; diff --git a/src/nostalgia/modules/core/src/opengl/gfx.cpp b/src/nostalgia/modules/core/src/opengl/gfx.cpp index 72cd809e..a47e2ddc 100644 --- a/src/nostalgia/modules/core/src/opengl/gfx.cpp +++ b/src/nostalgia/modules/core/src/opengl/gfx.cpp @@ -29,7 +29,7 @@ void Drawer::draw(turbine::Context &tctx) noexcept { core::gl::draw(m_ctx, turbine::getScreenSize(tctx)); } -constexpr ox::StringView bgvshadTmpl = R"glsl( +constexpr ox::CStringView bgvshadTmpl = R"glsl( {} in vec2 vTexCoord; in vec2 vPosition; @@ -41,13 +41,13 @@ constexpr ox::StringView bgvshadTmpl = R"glsl( float xScaleInvert = 1.0 - vXScale; gl_Position = vec4( vPosition.x * vXScale - xScaleInvert, vPosition.y, - 0.0, 1.0); + 0.5, 1.0); fTexCoord = vec2( vTexCoord.x, vTexCoord.y * vTileHeight + vTileIdx * vTileHeight); })glsl"; -constexpr ox::StringView bgfshadTmpl = R"glsl( +constexpr ox::CStringView bgfshadTmpl = R"glsl( {} out vec4 outColor; in vec2 fTexCoord; @@ -55,17 +55,17 @@ constexpr ox::StringView bgfshadTmpl = R"glsl( uniform vec2 fSrcImgSz; uniform vec4 fPalette[256]; void main() { - vec2 pixelSz = vec2(1, 1) / (fSrcImgSz); + vec2 pixelSz = vec2(1, 1) / fSrcImgSz; vec2 pixelCoord = floor(fTexCoord / pixelSz) * pixelSz; outColor = fPalette[int(texture(image, fTexCoord).rgb.r * 256)]; //outColor = vec4(0.0, 0.7, 1.0, 1.0); })glsl"; -constexpr ox::StringView spritevshadTmpl = R"glsl( +constexpr ox::CStringView spritevshadTmpl = R"glsl( {} in float vEnabled; + in vec3 vPosition; in vec2 vTexCoord; - in vec2 vPosition; out vec2 fTexCoord; uniform float vXScale; uniform float vTileHeight; @@ -73,11 +73,23 @@ constexpr ox::StringView spritevshadTmpl = R"glsl( float xScaleInvert = 1.0 - vXScale; gl_Position = vec4( vPosition.x * vXScale - xScaleInvert, vPosition.y, - 0.0, 1.0); + vPosition.z, 1.0); fTexCoord = vTexCoord * vec2(1, vTileHeight) * vec2(vEnabled, vEnabled); })glsl"; -constexpr ox::StringView spritefshadTmpl = bgfshadTmpl; +constexpr ox::CStringView spritefshadTmpl = R"glsl( + {} + out vec4 outColor; + in vec2 fTexCoord; + uniform sampler2D image; + uniform vec2 fSrcImgSz; + uniform vec4 fPalette[256]; + void main() { + vec2 pixelSz = vec2(1, 1) / fSrcImgSz; + vec2 pixelCoord = floor(fTexCoord / pixelSz) * pixelSz; + outColor = fPalette[int(texture(image, fTexCoord).rgb.r * 256)]; + //outColor = vec4(0.0, 0.7, 1.0, 1.0); + })glsl";; [[nodiscard]] static constexpr auto bgVertexRow(uint_t x, uint_t y) noexcept { @@ -91,6 +103,7 @@ static void setSpriteBufferObject( float y, uint_t textureRow, uint_t flipX, + uint_t priority, float *vbo, GLuint *ebo) noexcept { // don't worry, this memcpy gets optimized to something much more ideal @@ -100,17 +113,19 @@ static void setSpriteBufferObject( y *= -ymod; x -= 1.f; y += 1.f - ymod; - const auto textureRowf = static_cast(textureRow); - const float L = flipX ? 1 : 0; - const float R = flipX ? 0 : 1; - const ox::Array vertices { - enabled, x, y, L, textureRowf + 1, // bottom left - enabled, x + xmod, y, R, textureRowf + 1, // bottom right - enabled, x + xmod, y + ymod, R, textureRowf + 0, // top right - enabled, x, y + ymod, L, textureRowf + 0, // top left + auto const prif = static_cast(priority) * 0.1f; + auto const textureRowf = static_cast(textureRow); + float const L = flipX ? 1 : 0; + float const R = flipX ? 0 : 1; + ox::Array const vertices { + // vEnabled| vPosition | vTexCoord + enabled, x, y, prif, L, textureRowf + 1, // bottom left + enabled, x + xmod, y, prif, R, textureRowf + 1, // bottom right + enabled, x + xmod, y + ymod, prif, R, textureRowf + 0, // top right + enabled, x, y + ymod, prif, L, textureRowf + 0, // top left }; memcpy(vbo, vertices.data(), sizeof(vertices)); - const ox::Array elms { + ox::Array const elms { vi + 0, vi + 1, vi + 2, vi + 2, vi + 3, vi + 0, }; @@ -149,7 +164,7 @@ static void initSpriteBufferObjects(glutils::BufferSet &bs) noexcept { for (auto i = 0u; i < SpriteCount; ++i) { auto vbo = &bs.vertices[i * static_cast(SpriteVertexVboLength)]; auto ebo = &bs.elements[i * static_cast(SpriteVertexEboLength)]; - setSpriteBufferObject(i * SpriteVertexVboRows, 0, 0, 0, 0, false, vbo, ebo); + setSpriteBufferObject(i * SpriteVertexVboRows * BlocksPerSprite, 0, 0, 0, 0, false, 0, vbo, ebo); } } @@ -170,7 +185,9 @@ static void initBackgroundBufferObjects(glutils::BufferSet &bg) noexcept { } } -static void initSpritesBufferset(GLuint shader, glutils::BufferSet &bs) noexcept { +static void initSpritesBufferset(Context &ctx) noexcept { + auto const shader = ctx.spriteShader.id; + auto &bs = ctx.spriteBlocks; // vao bs.vao = glutils::generateVertexArrayObject(); glBindVertexArray(bs.vao); @@ -181,17 +198,20 @@ static void initSpritesBufferset(GLuint shader, glutils::BufferSet &bs) noexcept glutils::sendVbo(bs); glutils::sendEbo(bs); // vbo layout + // in float vEnabled; auto const enabledAttr = static_cast(glGetAttribLocation(shader, "vEnabled")); glEnableVertexAttribArray(enabledAttr); glVertexAttribPointer(enabledAttr, 1, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float), nullptr); + // in vec3 vPosition; auto const posAttr = static_cast(glGetAttribLocation(shader, "vPosition")); glEnableVertexAttribArray(posAttr); - glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float), + glVertexAttribPointer(posAttr, 3, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float), std::bit_cast(uintptr_t{1 * sizeof(float)})); + // in vec2 vTexCoord; auto const texCoordAttr = static_cast(glGetAttribLocation(shader, "vTexCoord")); glEnableVertexAttribArray(texCoordAttr); glVertexAttribPointer(texCoordAttr, 2, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float), - std::bit_cast(uintptr_t{3 * sizeof(float)})); + std::bit_cast(uintptr_t{4 * sizeof(float)})); } static void initBackgroundBufferset( @@ -347,11 +367,86 @@ static void loadSpriteTexture( ctx.spriteBlocks.tex = createTexture(w, h, pixels); } +static void setSprite( + Context &ctx, + uint_t const idx, + Sprite const&s) noexcept { + // Tonc Table 8.4 + static constexpr ox::Array, 12> dimensions{ + // col 0 + {1, 1}, // 0, 0 + {2, 2}, // 0, 1 + {4, 4}, // 0, 2 + {8, 8}, // 0, 3 + // col 1 + {2, 1}, // 1, 0 + {4, 1}, // 1, 1 + {4, 2}, // 1, 2 + {8, 4}, // 1, 3 + // col 2 + {1, 1}, // 2, 0 + {1, 4}, // 2, 1 + {2, 4}, // 2, 2 + {4, 8}, // 2, 3 + }; + oxAssert(idx < ctx.spriteStates.size(), "overflow"); + auto const dim = dimensions[(s.spriteShape << 2) | s.spriteSize]; + auto const uX = static_cast(s.x) % 255; + auto const uY = static_cast(s.y + 8) % 255 - 8; + oxAssert(1 < ctx.spriteBlocks.vertices.size(), "vbo overflow"); + oxAssert(1 < ctx.spriteBlocks.elements.size(), "ebo overflow"); + constexpr auto spriteVboSz = renderer::BlocksPerSprite * renderer::SpriteVertexVboLength; + constexpr auto spriteEboSz = renderer::BlocksPerSprite * renderer::SpriteVertexEboLength; + auto const vboBase = spriteVboSz * idx; + auto const eboBase = spriteEboSz * idx; + auto i = 0u; + const auto set = [&](int xIt, int yIt, bool enabled) { + auto const fX = static_cast(uX + xIt * 8) / 8; + auto const fY = static_cast(uY + yIt * 8) / 8; + auto const vboIdx = vboBase + renderer::SpriteVertexVboLength * i; + auto const eboIdx = eboBase + renderer::SpriteVertexEboLength * i; + oxAssert(vboIdx < ctx.spriteBlocks.vertices.size(), "vbo overflow"); + oxAssert(eboIdx < ctx.spriteBlocks.elements.size(), "ebo overflow"); + auto const vbo = &ctx.spriteBlocks.vertices[vboIdx]; + auto const ebo = &ctx.spriteBlocks.elements[eboIdx]; + renderer::setSpriteBufferObject( + static_cast(vboIdx), + enabled, + fX, + fY, + s.tileIdx + i, + s.flipX, + s.priority, + vbo, + ebo); + ++i; + }; + if (!s.flipX) { + for (auto yIt = 0; yIt < static_cast(dim.y); ++yIt) { + for (auto xIt = 0u; xIt < dim.x; ++xIt) { + set(static_cast(xIt), static_cast(yIt), s.enabled); + } + } + } else { + for (auto yIt = 0u; yIt < dim.y; ++yIt) { + for (auto xIt = dim.x - 1; xIt < ~0u; --xIt) { + set(static_cast(xIt), static_cast(yIt), s.enabled); + } + } + } + // clear remaining blocks in the sprite + for (; i < BlocksPerSprite; ++i) { + set(0, 0, false); + } + ctx.spriteBlocks.updated = true; +} + } ox::Error initGfx( Context &ctx, InitParams const&initParams) noexcept { + glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); const auto bgVshad = ox::sfmt(renderer::bgvshadTmpl, gl::GlslVersion); @@ -364,7 +459,7 @@ ox::Error initGfx( for (auto &bg : ctx.cbbs) { initBackgroundBufferset(ctx.bgShader, bg); } - initSpritesBufferset(ctx.spriteShader, ctx.spriteBlocks); + renderer::initSpritesBufferset(ctx); if (initParams.glInstallDrawer) { turbine::gl::addDrawer(ctx.turbineCtx, &ctx.drawer); } @@ -497,77 +592,21 @@ void clearTileLayer(Context &ctx, uint_t bgIdx) noexcept { } void hideSprite(Context &ctx, uint_t idx) noexcept { - auto vbo = &ctx.spriteBlocks.vertices[idx * renderer::SpriteVertexVboLength]; - auto ebo = &ctx.spriteBlocks.elements[idx * renderer::SpriteVertexEboLength]; - renderer::setSpriteBufferObject( - idx * renderer::SpriteVertexVboRows, 0, 0, 0, 0, false, vbo, ebo); - ctx.spriteBlocks.updated = true; + auto &s = ctx.spriteStates[idx]; + s.enabled = false; + renderer::setSprite(ctx, idx, s); } -void setSprite( - Context &ctx, - uint_t idx, - int x, - int y, - uint_t tileIdx, - uint_t spriteShape, - uint_t spriteSize, - uint_t flipX) noexcept { - //oxTracef("nostalgia::core::gfx::gl", "setSprite(ctx, {}, {}, {}, {}, {}, {}, {})", - // idx, x, y, tileIdx, spriteShape, spriteSize, flipX); - // Tonc Table 8.4 - static constexpr ox::Array, 12> dimensions{ - // col 0 - {1, 1}, // 0, 0 - {2, 2}, // 0, 1 - {4, 4}, // 0, 2 - {8, 8}, // 0, 3 - // col 1 - {2, 1}, // 1, 0 - {4, 1}, // 1, 1 - {4, 2}, // 1, 2 - {8, 4}, // 1, 3 - // col 2 - {1, 1}, // 2, 0 - {1, 4}, // 2, 1 - {2, 4}, // 2, 2 - {4, 8}, // 2, 3 - }; - const auto dim = dimensions[(spriteShape << 2) | spriteSize]; - const auto uX = static_cast(x) % 255; - const auto uY = static_cast(y + 8) % 255 - 8; - auto i = 0u; - const auto set = [&](int xIt, int yIt) { - const auto fX = static_cast(uX + xIt * 8) / 8; - const auto fY = static_cast(uY + yIt * 8) / 8; - const auto cidx = idx + i; - auto vbo = &ctx.spriteBlocks.vertices[cidx * renderer::SpriteVertexVboLength]; - auto ebo = &ctx.spriteBlocks.elements[cidx * renderer::SpriteVertexEboLength]; - renderer::setSpriteBufferObject( - cidx * renderer::SpriteVertexVboRows, - 1, - fX, - fY, - tileIdx + i, - flipX, - vbo, - ebo); - ++i; - }; - if (!flipX) { - for (auto yIt = 0; yIt < static_cast(dim.y); ++yIt) { - for (auto xIt = 0u; xIt < dim.x; ++xIt) { - set(static_cast(xIt), static_cast(yIt)); - } - } - } else { - for (auto yIt = 0u; yIt < dim.y; ++yIt) { - for (auto xIt = dim.x - 1; xIt < ~0u; --xIt) { - set(static_cast(xIt), static_cast(yIt)); - } - } - } - ctx.spriteBlocks.updated = true; +void showSprite(Context &ctx, uint_t idx) noexcept { + auto &s = ctx.spriteStates[idx]; + s.enabled = true; + renderer::setSprite(ctx, idx, s); +} + +void setSprite(Context &ctx, uint_t idx, Sprite const&sprite) noexcept { + auto &s = ctx.spriteStates[idx]; + s = sprite; + renderer::setSprite(ctx, idx, s); } void setTile( @@ -605,7 +644,8 @@ ox::Size drawSize(int scale) noexcept { void draw(core::Context &ctx, ox::Size const&renderSz) noexcept { glViewport(0, 0, renderSz.width, renderSz.height); - glutils::clearScreen(); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderer::drawBackgrounds(ctx, renderSz); if (ctx.spriteBlocks.tex) { renderer::drawSprites(ctx, renderSz); diff --git a/src/nostalgia/modules/core/src/opengl/gfx.hpp b/src/nostalgia/modules/core/src/opengl/gfx.hpp index f54a886d..37c363d2 100644 --- a/src/nostalgia/modules/core/src/opengl/gfx.hpp +++ b/src/nostalgia/modules/core/src/opengl/gfx.hpp @@ -22,10 +22,11 @@ constexpr uint64_t BgVertexVboRows = 4; constexpr uint64_t BgVertexVboRowLength = 5; constexpr uint64_t BgVertexVboLength = BgVertexVboRows * BgVertexVboRowLength; constexpr uint64_t BgVertexEboLength = 6; -constexpr uint64_t SpriteVertexVboRows = 256; -constexpr uint64_t SpriteVertexVboRowLength = 5; +constexpr uint64_t SpriteVertexVboRows = 4; +constexpr uint64_t SpriteVertexVboRowLength = 6; constexpr uint64_t SpriteVertexVboLength = SpriteVertexVboRows * SpriteVertexVboRowLength; constexpr uint64_t SpriteVertexEboLength = 6; +constexpr uint64_t BlocksPerSprite = 64; struct CBB: public glutils::BufferSet { bool updated = false; @@ -38,8 +39,8 @@ struct CBB: public glutils::BufferSet { struct SpriteBlockset: public glutils::BufferSet { bool updated = false; constexpr SpriteBlockset() noexcept { - vertices.resize(SpriteCount * SpriteVertexVboLength); - elements.resize(SpriteCount * SpriteVertexEboLength); + vertices.resize(SpriteCount * SpriteVertexVboLength * BlocksPerSprite); + elements.resize(SpriteCount * SpriteVertexEboLength * BlocksPerSprite); } }; @@ -48,10 +49,6 @@ struct Background { unsigned cbbIdx = 0; }; -struct Sprite { - bool enabled = false; -}; - class Drawer: public turbine::gl::Drawer { private: Context &m_ctx; @@ -65,4 +62,4 @@ class Drawer: public turbine::gl::Drawer { namespace nostalgia::core { ox::Error initGfx(Context &ctx, InitParams const&) noexcept; void shutdownGfx(Context &ctx) noexcept; -} \ No newline at end of file +}