better rendering

This commit is contained in:
izzy2lost
2025-12-16 23:06:28 -05:00
parent 60602b735e
commit ea0bfaed3f
6 changed files with 446 additions and 51 deletions
+6
View File
@@ -112,6 +112,8 @@ add_library(super3 SHARED
android_shim.cpp
model3_stubs.cpp
android_input_system.cpp
gles_presenter.cpp
gles_stub_render3d.cpp
render2d_android.cpp
${SUPER3_SOURCES}
${M68K_GENERATED_SOURCES}
@@ -129,10 +131,14 @@ target_include_directories(super3 PRIVATE
find_library(log-lib log)
find_library(android-lib android)
find_library(z-lib z)
find_library(egl-lib EGL)
find_library(glesv3-lib GLESv3)
target_link_libraries(super3 PRIVATE
${log-lib}
${android-lib}
${z-lib}
${egl-lib}
${glesv3-lib}
SDL2::SDL2 # present when SUPER3_FETCH_SDL2 is ON
)
+230
View File
@@ -0,0 +1,230 @@
#include "gles_presenter.h"
#include <GLES3/gl3.h>
#include <algorithm>
#include <cstring>
namespace {
static GLuint Compile(GLenum type, const char* src)
{
GLuint sh = glCreateShader(type);
glShaderSource(sh, 1, &src, nullptr);
glCompileShader(sh);
GLint ok = 0;
glGetShaderiv(sh, GL_COMPILE_STATUS, &ok);
if (!ok)
{
char log[2048];
GLsizei n = 0;
glGetShaderInfoLog(sh, (GLsizei)sizeof(log), &n, log);
glDeleteShader(sh);
return 0;
}
return sh;
}
} // namespace
bool GlesPresenter::Init()
{
if (!CreateProgram())
return false;
if (!CreateGeometry())
return false;
glGenTextures(1, &m_tex);
glBindTexture(GL_TEXTURE_2D, m_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);
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}
void GlesPresenter::Shutdown()
{
if (m_tex) { glDeleteTextures(1, &m_tex); m_tex = 0; }
if (m_vbo) { glDeleteBuffers(1, &m_vbo); m_vbo = 0; }
if (m_vao) { glDeleteVertexArrays(1, &m_vao); m_vao = 0; }
if (m_program) { glDeleteProgram(m_program); m_program = 0; }
m_uTex = -1;
m_uploadRGBA.clear();
m_outputW = m_outputH = m_srcW = m_srcH = 0;
}
bool GlesPresenter::CreateProgram()
{
static const char* vs = R"glsl(
#version 300 es
layout(location=0) in vec2 aPos;
layout(location=1) in vec2 aUv;
out vec2 vUv;
void main() {
vUv = aUv;
gl_Position = vec4(aPos, 0.0, 1.0);
}
)glsl";
static const char* fs = R"glsl(
#version 300 es
precision mediump float;
in vec2 vUv;
uniform sampler2D uTex;
out vec4 oColor;
void main() {
oColor = texture(uTex, vUv);
}
)glsl";
GLuint v = Compile(GL_VERTEX_SHADER, vs);
GLuint f = Compile(GL_FRAGMENT_SHADER, fs);
if (!v || !f)
{
if (v) glDeleteShader(v);
if (f) glDeleteShader(f);
return false;
}
m_program = glCreateProgram();
glAttachShader(m_program, v);
glAttachShader(m_program, f);
glLinkProgram(m_program);
glDeleteShader(v);
glDeleteShader(f);
GLint ok = 0;
glGetProgramiv(m_program, GL_LINK_STATUS, &ok);
if (!ok)
{
glDeleteProgram(m_program);
m_program = 0;
return false;
}
m_uTex = glGetUniformLocation(m_program, "uTex");
return true;
}
bool GlesPresenter::CreateGeometry()
{
glGenVertexArrays(1, &m_vao);
glGenBuffers(1, &m_vbo);
glBindVertexArray(m_vao);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(m_verts), m_verts, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, (void*)(sizeof(float) * 2));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
return true;
}
void GlesPresenter::Resize(int outputW, int outputH)
{
m_outputW = std::max(1, outputW);
m_outputH = std::max(1, outputH);
UpdateQuadVerts();
}
void GlesPresenter::UpdateFrameARGB(const uint32_t* pixelsARGB, int width, int height)
{
if (!pixelsARGB || width <= 0 || height <= 0)
return;
if (m_srcW != width || m_srcH != height)
{
m_srcW = width;
m_srcH = height;
glBindTexture(GL_TEXTURE_2D, m_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_srcW, m_srcH, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glBindTexture(GL_TEXTURE_2D, 0);
UpdateQuadVerts();
}
const size_t count = static_cast<size_t>(width) * static_cast<size_t>(height);
m_uploadRGBA.resize(count * 4);
// Convert ARGB (0xAARRGGBB) -> RGBA8 bytes.
// Note: input is packed in CPU endian; treat as integer and extract.
for (size_t i = 0; i < count; ++i)
{
const uint32_t p = pixelsARGB[i];
const uint8_t a = static_cast<uint8_t>(p >> 24);
const uint8_t r = static_cast<uint8_t>(p >> 16);
const uint8_t g = static_cast<uint8_t>(p >> 8);
const uint8_t b = static_cast<uint8_t>(p >> 0);
m_uploadRGBA[(i * 4) + 0] = r;
m_uploadRGBA[(i * 4) + 1] = g;
m_uploadRGBA[(i * 4) + 2] = b;
m_uploadRGBA[(i * 4) + 3] = a;
}
glBindTexture(GL_TEXTURE_2D, m_tex);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_srcW, m_srcH, GL_RGBA, GL_UNSIGNED_BYTE, m_uploadRGBA.data());
glBindTexture(GL_TEXTURE_2D, 0);
}
void GlesPresenter::UpdateQuadVerts()
{
if (m_outputW <= 0 || m_outputH <= 0 || m_srcW <= 0 || m_srcH <= 0)
return;
const float outW = (float)m_outputW;
const float outH = (float)m_outputH;
const float srcW = (float)m_srcW;
const float srcH = (float)m_srcH;
const float scale = std::min(outW / srcW, outH / srcH);
const float drawW = srcW * scale;
const float drawH = srcH * scale;
const float ndcW = (drawW / outW) * 2.0f;
const float ndcH = (drawH / outH) * 2.0f;
const float l = -ndcW * 0.5f;
const float r = +ndcW * 0.5f;
const float b = -ndcH * 0.5f;
const float t = +ndcH * 0.5f;
// Triangle strip: (l,b)->(r,b)->(l,t)->(r,t)
// pos.xy, uv.xy
const float v[] = {
l, b, 0.0f, 1.0f,
r, b, 1.0f, 1.0f,
l, t, 0.0f, 0.0f,
r, t, 1.0f, 0.0f,
};
std::memcpy(m_verts, v, sizeof(v));
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(m_verts), m_verts);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void GlesPresenter::Render()
{
if (!m_program || !m_vao || !m_tex || m_outputW <= 0 || m_outputH <= 0)
return;
glViewport(0, 0, m_outputW, m_outputH);
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glUseProgram(m_program);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_tex);
glUniform1i(m_uTex, 0);
glBindVertexArray(m_vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glUseProgram(0);
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <cstdint>
#include <vector>
// Minimal OpenGL ES presenter:
// - Uploads an ARGB8888 (0xAARRGGBB) framebuffer as a texture
// - Renders it with aspect-correct letterboxing
class GlesPresenter
{
public:
bool Init();
void Shutdown();
void Resize(int outputW, int outputH);
// pixelsARGB: 0xAARRGGBB packed.
void UpdateFrameARGB(const uint32_t* pixelsARGB, int width, int height);
void Render();
private:
bool CreateProgram();
bool CreateGeometry();
void UpdateQuadVerts();
int m_outputW = 0;
int m_outputH = 0;
int m_srcW = 0;
int m_srcH = 0;
unsigned m_program = 0;
unsigned m_vao = 0;
unsigned m_vbo = 0;
unsigned m_tex = 0;
int m_uTex = -1;
// Interleaved: pos.xy, uv.xy (4 verts)
float m_verts[16]{};
// Converted upload buffer in RGBA8.
std::vector<uint8_t> m_uploadRGBA;
};
@@ -0,0 +1,96 @@
#include "gles_stub_render3d.h"
#include <cstring>
namespace {
static GLuint Compile(GLenum type, const char* src)
{
GLuint sh = glCreateShader(type);
glShaderSource(sh, 1, &src, nullptr);
glCompileShader(sh);
GLint ok = 0;
glGetShaderiv(sh, GL_COMPILE_STATUS, &ok);
if (!ok)
{
glDeleteShader(sh);
return 0;
}
return sh;
}
} // namespace
void GlesStubRender3D::BeginFrame()
{
if (m_inited)
return;
static const char* vs = R"glsl(
#version 300 es
layout(location=0) in vec2 aPos;
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }
)glsl";
static const char* fs = R"glsl(
#version 300 es
precision mediump float;
out vec4 oColor;
void main() { oColor = vec4(0.15, 0.2, 0.45, 1.0); }
)glsl";
GLuint v = Compile(GL_VERTEX_SHADER, vs);
GLuint f = Compile(GL_FRAGMENT_SHADER, fs);
if (!v || !f)
return;
m_program = glCreateProgram();
glAttachShader(m_program, v);
glAttachShader(m_program, f);
glLinkProgram(m_program);
glDeleteShader(v);
glDeleteShader(f);
GLint ok = 0;
glGetProgramiv(m_program, GL_LINK_STATUS, &ok);
if (!ok)
{
glDeleteProgram(m_program);
m_program = 0;
return;
}
glGenVertexArrays(1, &m_vao);
glGenBuffers(1, &m_vbo);
// Small triangle in the top-left corner (NDC).
const float verts[] = {
-0.98f, 0.98f,
-0.70f, 0.98f,
-0.98f, 0.70f,
};
glBindVertexArray(m_vao);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
m_inited = true;
}
void GlesStubRender3D::RenderFrame()
{
if (!m_inited)
BeginFrame();
if (!m_inited)
return;
glUseProgram(m_program);
glBindVertexArray(m_vao);
glDisable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glUseProgram(0);
}
@@ -0,0 +1,31 @@
#pragma once
#include <GLES3/gl3.h>
#include "Graphics/IRender3D.h"
// Tiny GLES-backed IRender3D implementation used as a stepping stone:
// - Verifies the 3D renderer hook is being called from the Real3D/GPU path.
// - Does NOT implement Model 3 Real3D yet.
class GlesStubRender3D final : public IRender3D
{
public:
bool Init(unsigned, unsigned, unsigned, unsigned, unsigned, unsigned) override { return true; }
void AttachMemory(const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*, const uint16_t*) override {}
void UploadTextures(unsigned, unsigned, unsigned, unsigned, unsigned) override {}
void SetStepping(int) override {}
void SetSunClamp(bool) override {}
void SetSignedShade(bool) override {}
float GetLosValue(int) override { return 0.f; }
void BeginFrame() override;
void RenderFrame() override;
void EndFrame() override {}
private:
bool m_inited = false;
GLuint m_program = 0;
GLuint m_vao = 0;
GLuint m_vbo = 0;
};
+40 -51
View File
@@ -10,6 +10,8 @@
#include <thread>
#include <cstring>
#include <GLES3/gl3.h>
#include "GameLoader.h"
#include "Model3/Model3.h"
#include "ROMSet.h"
@@ -24,6 +26,8 @@
#include "BlockFile.h"
#include "android_input_system.h"
#include "gles_presenter.h"
#include "gles_stub_render3d.h"
// Minimal OSD glue -----------------------------------------------------------
@@ -58,7 +62,7 @@ struct Super3Host {
AndroidInputSystem inputSystem;
CInputs inputs{&inputSystem};
StubOutputs outputs;
NullRender3D render3d;
GlesStubRender3D render3d;
CRender2D render2d{config};
std::unique_ptr<GameLoader> loader;
@@ -264,7 +268,6 @@ struct Super3Host {
// it mainly keeps the input system in a sane state.
inputs.Poll(&game, 0, 0, 496, 384);
model3->RunFrame();
model3->RenderFrame();
}
}
};
@@ -303,13 +306,20 @@ extern "C" int SDL_main(int argc, char* argv[]) {
return 1;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_Window* window = SDL_CreateWindow(
"Super3 (SDL bootstrap)",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
1280,
720,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL
);
if (!window) {
@@ -318,24 +328,21 @@ extern "C" int SDL_main(int argc, char* argv[]) {
return 1;
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!renderer) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateRenderer failed: %s", SDL_GetError());
SDL_GLContext gl = SDL_GL_CreateContext(window);
if (!gl) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_GL_CreateContext failed: %s", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
}
// Texture for tile-generator output (496x384 ARGB). This renders the built-in test screen.
SDL_Texture* tgTexture = SDL_CreateTexture(
renderer,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
496,
384);
if (!tgTexture) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateTexture failed: %s", SDL_GetError());
SDL_DestroyRenderer(renderer);
SDL_GL_MakeCurrent(window, gl);
SDL_GL_SetSwapInterval(1);
GlesPresenter presenter;
if (!presenter.Init()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "GlesPresenter.Init failed");
SDL_GL_DeleteContext(gl);
SDL_DestroyWindow(window);
SDL_Quit();
return 1;
@@ -466,45 +473,27 @@ extern "C" int SDL_main(int argc, char* argv[]) {
host.RunFrame();
}
// Present the tile generator framebuffer if available; otherwise clear to black.
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xFF);
SDL_RenderClear(renderer);
int winW = 0, winH = 0;
SDL_GL_GetDrawableSize(window, &winW, &winH);
presenter.Resize(winW, winH);
glClearColor(0.f, 0.f, 0.f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
if (state == 1 && host.render2d.HasFrame()) {
const uint32_t* pixels = host.render2d.GetFrameBufferRGBA();
const int pitch = (int)(host.render2d.GetFrameWidth() * sizeof(uint32_t));
if (SDL_UpdateTexture(tgTexture, nullptr, pixels, pitch) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_UpdateTexture failed: %s", SDL_GetError());
} else {
int winW = 0, winH = 0;
SDL_GetRendererOutputSize(renderer, &winW, &winH);
const int srcW = (int)host.render2d.GetFrameWidth();
const int srcH = (int)host.render2d.GetFrameHeight();
presenter.UpdateFrameARGB(pixels, (int)host.render2d.GetFrameWidth(), (int)host.render2d.GetFrameHeight());
presenter.Render();
// Preserve aspect ratio (letterbox/pillarbox).
float scale = std::min((float)winW / (float)srcW, (float)winH / (float)srcH);
SDL_Rect dst{};
dst.w = (int)(srcW * scale);
dst.h = (int)(srcH * scale);
dst.x = (winW - dst.w) / 2;
dst.y = (winH - dst.h) / 2;
SDL_RenderCopy(renderer, tgTexture, nullptr, &dst);
}
// Debug: draw a small triangle on top so we can verify GLES is working
// and we have a valid 3D hook. The real integration will happen inside
// the GPU/TileGen render path once New3D is ported.
host.render3d.BeginFrame();
host.render3d.RenderFrame();
host.render3d.EndFrame();
}
SDL_RenderPresent(renderer);
if (SDL_GetError()[0] != '\0') {
// SDL doesn't always surface present errors via return codes; log and clear.
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL error after present: %s", SDL_GetError());
// If the EGL surface has gone away (background/minimize), stop rendering
// until we get a foreground/surface recreation event.
if (strstr(SDL_GetError(), "EGL_BAD_SURFACE") != nullptr ||
strstr(SDL_GetError(), "unable to show color buffer") != nullptr) {
backgrounded = true;
SDL_Log("Detected bad surface; pausing rendering until foreground");
}
SDL_ClearError();
}
SDL_GL_SwapWindow(window);
uint32_t t = SDL_GetTicks();
if (t - lastStatusLog > 2000) {
@@ -513,12 +502,12 @@ extern "C" int SDL_main(int argc, char* argv[]) {
}
}
SDL_DestroyTexture(tgTexture);
presenter.Shutdown();
if (loadState.load(std::memory_order_acquire) == 1) {
host.SaveNVRAM();
}
CloseAudio();
SDL_DestroyRenderer(renderer);
SDL_GL_DeleteContext(gl);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;