[nostalgia/core] Fix OpenGL sprites, add priority

This commit is contained in:
Gary Talent 2023-12-19 22:37:18 -06:00
parent 2b8dbb88b2
commit 59016ee894
6 changed files with 171 additions and 127 deletions

View File

@ -16,23 +16,26 @@ namespace nostalgia::core {
extern ox::Array<char, 128> 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;
}

View File

@ -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<uint16_t>(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<uint16_t>(
static_cast<uint16_t>(y & ox::onMask<uint8_t>(0b111'1111))
| (static_cast<uint16_t>(1) << 10) // enable alpha
| (static_cast<uint16_t>(spriteShape) << 14));
oa.attr1 = static_cast<uint16_t>(
(static_cast<uint16_t>(x) & ox::onMask<uint8_t>(8))
| (static_cast<uint16_t>(flipX) << 12)
| (static_cast<uint16_t>(spriteSize) << 14));
oa.attr2 = static_cast<uint16_t>(tileIdx & ox::onMask<uint16_t>(8));
oa.attr0 &= uint16_t{0b1111'1100'1111'1111};
oa.idx = static_cast<uint16_t>(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<uint16_t>(
(static_cast<uint16_t>(s.y & ox::onMask<uint8_t>(0b111'1111)))
| (static_cast<uint16_t>(1) << 10) // enable alpha
| (static_cast<uint16_t>(s.spriteShape) << 14)),
.attr1 = static_cast<uint16_t>(
(static_cast<uint16_t>(s.x) & ox::onMask<uint8_t>(8))
| (static_cast<uint16_t>(s.flipX) << 12)
| (static_cast<uint16_t>(s.spriteSize) << 14)),
.attr2 = static_cast<uint16_t>(
(static_cast<uint16_t>(s.tileIdx & ox::onMask<uint16_t>(8)))
| (static_cast<uint16_t>(s.priority & 0b11) << 10)),
.idx = static_cast<uint16_t>(idx),
};
teagba::addSpriteUpdate(oa);
}
}

View File

@ -137,8 +137,4 @@ ox::Array<char, 128> 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);
}
}

View File

@ -27,6 +27,10 @@ class Context {
ox::Array<renderer::Background, 4> 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;
};

View File

@ -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<float>(textureRow);
const float L = flipX ? 1 : 0;
const float R = flipX ? 0 : 1;
const ox::Array<float, SpriteVertexVboLength> 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<float>(priority) * 0.1f;
auto const textureRowf = static_cast<float>(textureRow);
float const L = flipX ? 1 : 0;
float const R = flipX ? 0 : 1;
ox::Array<float, SpriteVertexVboLength> 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<GLuint, SpriteVertexEboLength> elms {
ox::Array<GLuint, SpriteVertexEboLength> 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<std::size_t>(SpriteVertexVboLength)];
auto ebo = &bs.elements[i * static_cast<std::size_t>(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<GLuint>(glGetAttribLocation(shader, "vEnabled"));
glEnableVertexAttribArray(enabledAttr);
glVertexAttribPointer(enabledAttr, 1, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float), nullptr);
// in vec3 vPosition;
auto const posAttr = static_cast<GLuint>(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<void*>(uintptr_t{1 * sizeof(float)}));
// in vec2 vTexCoord;
auto const texCoordAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vTexCoord"));
glEnableVertexAttribArray(texCoordAttr);
glVertexAttribPointer(texCoordAttr, 2, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float),
std::bit_cast<void*>(uintptr_t{3 * sizeof(float)}));
std::bit_cast<void*>(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<ox::Vec<uint_t>, 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<int>(s.x) % 255;
auto const uY = static_cast<int>(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<float>(uX + xIt * 8) / 8;
auto const fY = static_cast<float>(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<uint_t>(vboIdx),
enabled,
fX,
fY,
s.tileIdx + i,
s.flipX,
s.priority,
vbo,
ebo);
++i;
};
if (!s.flipX) {
for (auto yIt = 0; yIt < static_cast<int>(dim.y); ++yIt) {
for (auto xIt = 0u; xIt < dim.x; ++xIt) {
set(static_cast<int>(xIt), static_cast<int>(yIt), s.enabled);
}
}
} else {
for (auto yIt = 0u; yIt < dim.y; ++yIt) {
for (auto xIt = dim.x - 1; xIt < ~0u; --xIt) {
set(static_cast<int>(xIt), static_cast<int>(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<ox::Vec<uint_t>, 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<int>(x) % 255;
const auto uY = static_cast<int>(y + 8) % 255 - 8;
auto i = 0u;
const auto set = [&](int xIt, int yIt) {
const auto fX = static_cast<float>(uX + xIt * 8) / 8;
const auto fY = static_cast<float>(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<int>(dim.y); ++yIt) {
for (auto xIt = 0u; xIt < dim.x; ++xIt) {
set(static_cast<int>(xIt), static_cast<int>(yIt));
}
}
} else {
for (auto yIt = 0u; yIt < dim.y; ++yIt) {
for (auto xIt = dim.x - 1; xIt < ~0u; --xIt) {
set(static_cast<int>(xIt), static_cast<int>(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);

View File

@ -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;
}
}