From a3dfb04eb8b651d4209fce9601b7a52964c81df2 Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Fri, 19 Dec 2025 15:20:17 -0500 Subject: [PATCH] Added experimental real3d option --- README.md | 15 +- Src/Graphics/New3D/New3D.cpp | 64 ++++--- Src/Graphics/New3D/New3D.h | 14 +- Src/Graphics/New3D/R3DFrameBuffers.cpp | 175 ++++++++++++++--- Src/Graphics/New3D/R3DScrollFog.cpp | 177 +++++++++++++++++- Src/Graphics/New3D/R3DScrollFog.h | 8 +- .../app/src/main/assets/Config/Supermodel.ini | 2 + android/app/src/main/cpp/CMakeLists.txt | 3 + android/app/src/main/cpp/native-lib.cpp | 2 + .../java/com/izzy2lost/super3/MainActivity.kt | 29 ++- .../app/src/main/res/layout/nav_header.xml | 9 + 11 files changed, 435 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 27fc019..7f875f0 100644 --- a/README.md +++ b/README.md @@ -13,21 +13,24 @@ This app does **not** include any games, ROMs, BIOS files, or copyrighted game c - In-game pause/resume button. ## Video settings (Resolution & Widescreen) -From the main game list, open the side menu (toolbar “menu” icon) and look under **Video**. +From the main game list, open the side menu (toolbar "menu" icon) and look under **Video**. -- **Resolution:** Native (496×384) and integer scale options (2× through 8×). Higher values look sharper but can reduce performance. -- **Match device resolution:** Sets the internal resolution to your device’s exact pixel resolution (landscape). This also enables **Widescreen** and **Wide background**. +- **Resolution:** Native (496x384) and integer scale options (2x through 8x). Higher values look sharper but can reduce performance. +- **Match device resolution:** Sets the internal resolution to your device's exact pixel resolution (landscape). This also enables **Widescreen** and **Wide background**. - **Widescreen:** Expands the horizontal field of view (Supermodel `-wide-screen`). At very wide aspect ratios some games may cull objects at the edges. - **Wide background:** Stretches the 2D background layers when using widescreen (Supermodel `-wide-bg`). +- **Enhanced Real3D (experimental):** Enables more desktop-like Real3D rendering (multi-pass transparency compositing + scroll fog). This can improve visuals in some games/areas, but may reduce performance on lower-end devices. + +These settings are written to `.../Android/data//files/super3/Config/Supermodel.ini` (`XResolution`, `YResolution`, `WideScreen`, `WideBackground`, `New3DAccurate`). ## Files & Storage -- Save states and thumbnails are stored under the app’s user data directory in `Saves/` (for example: `.../Android/data//files/super3/Saves/`). -- The app may offer an optional “sync”/export workflow using Android’s Storage Access Framework. If enabled, the app only accesses the folder(s) you explicitly choose. +- Save states and thumbnails are stored under the app's user data directory in `Saves/` (for example: `.../Android/data//files/super3/Saves/`). +- The app may offer an optional "sync"/export workflow using Android's Storage Access Framework. If enabled, the app only accesses the folder(s) you explicitly choose. ## Legal / Google Play Policy Notes - **No games included:** SUPER3 does not ship with ROMs or game data, and it is not intended to facilitate piracy. - **You must own the games:** Only use ROMs you are legally permitted to use in your region. -- **Trademarks:** “SEGA” and all associated game names, trademarks, and copyrighted works are the property of their respective owners. +- **Trademarks:** "SEGA" and all associated game names, trademarks, and copyrighted works are the property of their respective owners. - **Not affiliated:** This project is not affiliated with or endorsed by SEGA or any arcade hardware manufacturer. ## Privacy diff --git a/Src/Graphics/New3D/New3D.cpp b/Src/Graphics/New3D/New3D.cpp index f16589e..8542768 100644 --- a/Src/Graphics/New3D/New3D.cpp +++ b/Src/Graphics/New3D/New3D.cpp @@ -17,10 +17,8 @@ namespace New3D { CNew3D::CNew3D(const Util::Config::Node &config, const std::string& gameName) - : m_r3dShader(config), -#ifndef __ANDROID__ - m_r3dScrollFog(config), -#endif + : m_config(config), + m_r3dShader(config), m_gameName(gameName) { m_cullingRAMLo = nullptr; @@ -30,6 +28,7 @@ CNew3D::CNew3D(const Util::Config::Node &config, const std::string& gameName) m_textureRAM = nullptr; m_sunClamp = true; m_shadeIsSigned = true; + m_new3dAccurate = config["New3DAccurate"].ValueAsDefault(false); m_numPolyVerts = 3; m_primType = GL_TRIANGLES; @@ -89,7 +88,23 @@ bool CNew3D::Init(unsigned xOffset, unsigned yOffset, unsigned xRes, unsigned yR m_r3dShader.LoadShader(); #ifndef __ANDROID__ - m_r3dFrameBuffers.CreateFBO(totalXResParam, totalYResParam); + if (!m_r3dFrameBuffers) + m_r3dFrameBuffers = std::make_unique(); + if (!m_r3dScrollFog) + m_r3dScrollFog = std::make_unique(m_config); + m_r3dFrameBuffers->CreateFBO((int)totalXResParam, (int)totalYResParam); +#else + if (m_new3dAccurate) { + if (!m_r3dFrameBuffers) + m_r3dFrameBuffers = std::make_unique(); + if (!m_r3dScrollFog) + m_r3dScrollFog = std::make_unique(m_config); + if (!m_r3dFrameBuffers->CreateFBO((int)totalXResParam, (int)totalYResParam)) { + m_r3dFrameBuffers.reset(); + m_r3dScrollFog.reset(); + m_new3dAccurate = false; + } + } #endif glUseProgram(0); @@ -122,7 +137,11 @@ void CNew3D::UploadTextures(unsigned level, unsigned x, unsigned y, unsigned wid void CNew3D::DrawScrollFog() { -#ifndef __ANDROID__ +#ifdef __ANDROID__ + if (!m_new3dAccurate) { + return; + } +#endif // this is my best guess at the logic based upon what games are doing // // ocean hunter - every viewport has scroll fog values set. Must start with lowest priority layers as the higher ones sometimes are garbage @@ -164,7 +183,9 @@ CheckScroll: rgba[2] == n.viewport.fogParams[2]) { glViewport(n.viewport.x, n.viewport.y, n.viewport.width, n.viewport.height); - m_r3dScrollFog.DrawScrollFog(rgba, n.viewport.scrollAtt, n.viewport.fogParams[6], n.viewport.spotFogColor, n.viewport.spotEllipse); + if (m_r3dScrollFog) { + m_r3dScrollFog->DrawScrollFog(rgba, n.viewport.scrollAtt, n.viewport.fogParams[6], n.viewport.spotFogColor, n.viewport.spotEllipse); + } return; } } @@ -172,7 +193,6 @@ CheckScroll: } } } -#endif } bool CNew3D::RenderScene(int priority, bool renderOverlay, Layer layer) @@ -314,7 +334,8 @@ void CNew3D::DisableRenderStates() void CNew3D::RenderFrame(void) { #ifdef __ANDROID__ - // Android/GLES: basic mesh path (no multi-pass transparency compositing yet). + if (!m_new3dAccurate) { + // Android/GLES: basic mesh path (no multi-pass transparency compositing yet). for (int i = 0; i < 4; i++) { m_nfPairs[i].zNear = -std::numeric_limits::max(); m_nfPairs[i].zFar = std::numeric_limits::max(); @@ -383,8 +404,9 @@ void CNew3D::RenderFrame(void) glDepthMask(GL_TRUE); DisableRenderStates(); } - return; -#else + return; + } +#endif for (int i = 0; i < 4; i++) { m_nfPairs[i].zNear = -std::numeric_limits::max(); m_nfPairs[i].zFar = std::numeric_limits::max(); @@ -431,7 +453,8 @@ void CNew3D::RenderFrame(void) } } - m_r3dFrameBuffers.SetFBO(Layer::trans12); + if (!m_r3dFrameBuffers) return; + m_r3dFrameBuffers->SetFBO(Layer::trans12); glClear(GL_COLOR_BUFFER_BIT); // wipe both trans layers for (int pri = 0; pri <= 3; pri++) { @@ -442,7 +465,7 @@ void CNew3D::RenderFrame(void) bool renderOverlay = (i == 1); - m_r3dFrameBuffers.SetFBO(Layer::colour); + m_r3dFrameBuffers->SetFBO(Layer::colour); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); SetRenderStates(); @@ -456,20 +479,20 @@ void CNew3D::RenderFrame(void) DisableRenderStates(); - m_r3dFrameBuffers.DrawOverTransLayers(); // mask trans layer with opaque pixels - m_r3dFrameBuffers.CompositeBaseLayer(); // copy opaque pixels to back buffer + m_r3dFrameBuffers->DrawOverTransLayers(); // mask trans layer with opaque pixels + m_r3dFrameBuffers->CompositeBaseLayer(); // copy opaque pixels to back buffer SetRenderStates(); glDepthFunc(GL_LESS); // alpha polys seem to use gl_less (ocean hunter) m_r3dShader.DiscardAlpha (false); // render only translucent pixels - m_r3dFrameBuffers.StoreDepth (); // save depth buffer for 1st trans pass - m_r3dFrameBuffers.SetFBO (Layer::trans1); + m_r3dFrameBuffers->StoreDepth (); // save depth buffer for 1st trans pass + m_r3dFrameBuffers->SetFBO (Layer::trans1); RenderScene (pri, renderOverlay, Layer::trans1); - m_r3dFrameBuffers.RestoreDepth (); // restore depth buffer, trans layers don't seem to depth test against each other - m_r3dFrameBuffers.SetFBO (Layer::trans2); + m_r3dFrameBuffers->RestoreDepth (); // restore depth buffer, trans layers don't seem to depth test against each other + m_r3dFrameBuffers->SetFBO (Layer::trans2); RenderScene (pri, renderOverlay, Layer::trans2); DisableRenderStates(); @@ -478,8 +501,7 @@ void CNew3D::RenderFrame(void) } } - m_r3dFrameBuffers.CompositeAlphaLayer(); -#endif + m_r3dFrameBuffers->CompositeAlphaLayer(); } void CNew3D::BeginFrame(void) diff --git a/Src/Graphics/New3D/New3D.h b/Src/Graphics/New3D/New3D.h index 29d9e5a..412a7ec 100644 --- a/Src/Graphics/New3D/New3D.h +++ b/Src/Graphics/New3D/New3D.h @@ -39,19 +39,17 @@ #include "Graphics/IRender3D.h" #include "Model.h" #include "Mat4.h" +#include "Util/NewConfig.h" #include "R3DShader.h" #include "VBO.h" #include "R3DData.h" #include "Plane.h" #include "Vec.h" -#ifndef __ANDROID__ #include "R3DScrollFog.h" -#endif #include "PolyHeader.h" -#ifndef __ANDROID__ #include "R3DFrameBuffers.h" -#endif #include +#include namespace New3D { @@ -236,6 +234,7 @@ private: */ // Misc + const Util::Config::Node &m_config; std::string m_gameName; int m_numPolyVerts; GLenum m_primType; @@ -243,6 +242,7 @@ private: // GPU configuration bool m_sunClamp; bool m_shadeIsSigned; + bool m_new3dAccurate; // Stepping int m_step; @@ -290,10 +290,8 @@ private: VBO m_vbo; // large VBO to hold our poly data, start of VBO is ROM data, ram polys follow R3DShader m_r3dShader; -#ifndef __ANDROID__ - R3DScrollFog m_r3dScrollFog; - R3DFrameBuffers m_r3dFrameBuffers; -#endif + std::unique_ptr m_r3dScrollFog; + std::unique_ptr m_r3dFrameBuffers; Plane m_planes[5]; diff --git a/Src/Graphics/New3D/R3DFrameBuffers.cpp b/Src/Graphics/New3D/R3DFrameBuffers.cpp index 8a9fb8c..55c90e6 100644 --- a/Src/Graphics/New3D/R3DFrameBuffers.cpp +++ b/Src/Graphics/New3D/R3DFrameBuffers.cpp @@ -189,7 +189,12 @@ void R3DFrameBuffers::SetFBO(Layer layer) case Layer::none: { glBindFramebuffer(GL_FRAMEBUFFER, 0); +#ifdef __ANDROID__ + GLenum buffers[] = { GL_BACK }; + glDrawBuffers(countof(buffers), buffers); +#else glDrawBuffer(GL_BACK); +#endif break; } } @@ -199,6 +204,43 @@ void R3DFrameBuffers::SetFBO(Layer layer) void R3DFrameBuffers::AllocShaderBase() { +#ifdef __ANDROID__ + const char *vertexShader = R"glsl( + + #version 300 es + precision highp float; + + in vec3 inVertex; + in vec2 inTexCoord; + + out vec2 fsTexCoord; + + void main(void) + { + fsTexCoord = inTexCoord; + gl_Position = vec4(inVertex,1.0); + } + + )glsl"; + + const char *fragmentShader = R"glsl( + + #version 300 es + precision mediump float; + + uniform sampler2D tex1; + in vec2 fsTexCoord; + out vec4 oColor; + + void main() + { + vec4 colBase = texture(tex1, fsTexCoord); + if(colBase.a < 1.0) discard; + oColor = colBase; + } + + )glsl"; +#else const char *vertexShader = R"glsl( #version 120 @@ -234,15 +276,62 @@ void R3DFrameBuffers::AllocShaderBase() } )glsl"; +#endif m_shaderBase.LoadShaders(vertexShader, fragmentShader); - m_shaderBase.uniformLoc[0] = m_shaderTrans.GetUniformLocation("tex1"); - m_shaderBase.attribLoc[0] = m_shaderTrans.GetAttributeLocation("inVertex"); - m_shaderBase.attribLoc[1] = m_shaderTrans.GetAttributeLocation("inTexCoord"); + m_shaderBase.uniformLoc[0] = m_shaderBase.GetUniformLocation("tex1"); + m_shaderBase.attribLoc[0] = m_shaderBase.GetAttributeLocation("inVertex"); + m_shaderBase.attribLoc[1] = m_shaderBase.GetAttributeLocation("inTexCoord"); } void R3DFrameBuffers::AllocShaderTrans() { +#ifdef __ANDROID__ + const char *vertexShader = R"glsl( + + #version 300 es + precision highp float; + + in vec3 inVertex; + in vec2 inTexCoord; + + out vec2 fsTexCoord; + + void main(void) + { + fsTexCoord = inTexCoord; + gl_Position = vec4(inVertex,1.0); + } + + )glsl"; + + const char *fragmentShader = R"glsl( + + #version 300 es + precision mediump float; + + uniform sampler2D tex1; + uniform sampler2D tex2; + + in vec2 fsTexCoord; + out vec4 oColor; + + void main() + { + vec4 colTrans1 = texture(tex1, fsTexCoord); + vec4 colTrans2 = texture(tex2, fsTexCoord); + + if(colTrans1.a + colTrans2.a > 0.0) { + vec3 col1 = colTrans1.rgb * colTrans1.a; + vec3 col2 = colTrans2.rgb * colTrans2.a; + colTrans1 = vec4((col1 + col2) / (colTrans1.a + colTrans2.a), colTrans1.a + colTrans2.a); + } + + oColor = colTrans1; + } + + )glsl"; +#else const char *vertexShader = R"glsl( #version 120 @@ -288,6 +377,7 @@ void R3DFrameBuffers::AllocShaderTrans() } )glsl"; +#endif m_shaderTrans.LoadShaders(vertexShader, fragmentShader); @@ -300,6 +390,48 @@ void R3DFrameBuffers::AllocShaderTrans() void R3DFrameBuffers::AllocShaderWipe() { +#ifdef __ANDROID__ + const char *vertexShader = R"glsl( + + #version 300 es + precision highp float; + + in vec3 inVertex; + in vec2 inTexCoord; + + out vec2 fsTexCoord; + + void main(void) + { + fsTexCoord = inTexCoord; + gl_Position = vec4(inVertex,1.0); + } + + )glsl"; + + const char *fragmentShader = R"glsl( + + #version 300 es + precision mediump float; + + uniform sampler2D texColor; + in vec2 fsTexCoord; + + layout(location=0) out vec4 oLayer1; + layout(location=1) out vec4 oLayer2; + + void main() + { + vec4 colBase = texture(texColor, fsTexCoord); + if(colBase.a == 0.0) { + discard; + } + oLayer1 = vec4(0.0); + oLayer2 = vec4(0.0); + } + + )glsl"; +#else const char *vertexShader = R"glsl( #version 120 @@ -339,13 +471,14 @@ void R3DFrameBuffers::AllocShaderWipe() } )glsl"; +#endif m_shaderWipe.LoadShaders(vertexShader, fragmentShader); - m_shaderWipe.uniformLoc[0] = m_shaderTrans.GetUniformLocation("texColor"); + m_shaderWipe.uniformLoc[0] = m_shaderWipe.GetUniformLocation("texColor"); - m_shaderWipe.attribLoc[0] = m_shaderTrans.GetAttributeLocation("inVertex"); - m_shaderWipe.attribLoc[1] = m_shaderTrans.GetAttributeLocation("inTexCoord"); + m_shaderWipe.attribLoc[0] = m_shaderWipe.GetAttributeLocation("inVertex"); + m_shaderWipe.attribLoc[1] = m_shaderWipe.GetAttributeLocation("inTexCoord"); } void R3DFrameBuffers::Draw() @@ -437,16 +570,16 @@ void R3DFrameBuffers::DrawOverTransLayers() m_shaderWipe.EnableShader(); glUniform1i(m_shaderWipe.uniformLoc[0], 0); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); + glEnableVertexAttribArray(m_shaderWipe.attribLoc[0]); + glEnableVertexAttribArray(m_shaderWipe.attribLoc[1]); glVertexAttribPointer(m_shaderWipe.attribLoc[0], 3, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, verts)); glVertexAttribPointer(m_shaderWipe.attribLoc[1], 2, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, texCoords)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glDisableVertexAttribArray(0); - glDisableVertexAttribArray(1); + glDisableVertexAttribArray(m_shaderWipe.attribLoc[0]); + glDisableVertexAttribArray(m_shaderWipe.attribLoc[1]); m_shaderWipe.DisableShader(); @@ -456,18 +589,18 @@ void R3DFrameBuffers::DrawOverTransLayers() void R3DFrameBuffers::DrawBaseLayer() { m_shaderBase.EnableShader(); - glUniform1i(m_shaderTrans.uniformLoc[0], 0); + glUniform1i(m_shaderBase.uniformLoc[0], 0); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); + glEnableVertexAttribArray(m_shaderBase.attribLoc[0]); + glEnableVertexAttribArray(m_shaderBase.attribLoc[1]); - glVertexAttribPointer(m_shaderTrans.attribLoc[0], 3, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, verts)); - glVertexAttribPointer(m_shaderTrans.attribLoc[1], 2, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, texCoords)); + glVertexAttribPointer(m_shaderBase.attribLoc[0], 3, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, verts)); + glVertexAttribPointer(m_shaderBase.attribLoc[1], 2, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, texCoords)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glDisableVertexAttribArray(0); - glDisableVertexAttribArray(1); + glDisableVertexAttribArray(m_shaderBase.attribLoc[0]); + glDisableVertexAttribArray(m_shaderBase.attribLoc[1]); m_shaderBase.DisableShader(); } @@ -478,16 +611,16 @@ void R3DFrameBuffers::DrawAlphaLayer() glUniform1i(m_shaderTrans.uniformLoc[0], 1); // tex unit 1 glUniform1i(m_shaderTrans.uniformLoc[1], 2); // tex unit 2 - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); + glEnableVertexAttribArray(m_shaderTrans.attribLoc[0]); + glEnableVertexAttribArray(m_shaderTrans.attribLoc[1]); glVertexAttribPointer(m_shaderTrans.attribLoc[0], 3, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, verts)); glVertexAttribPointer(m_shaderTrans.attribLoc[1], 2, GL_FLOAT, GL_FALSE, sizeof(FBVertex), (void*)offsetof(FBVertex, texCoords)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glDisableVertexAttribArray(0); - glDisableVertexAttribArray(1); + glDisableVertexAttribArray(m_shaderTrans.attribLoc[0]); + glDisableVertexAttribArray(m_shaderTrans.attribLoc[1]); m_shaderTrans.DisableShader(); } diff --git a/Src/Graphics/New3D/R3DScrollFog.cpp b/Src/Graphics/New3D/R3DScrollFog.cpp index 0169250..2d5db70 100644 --- a/Src/Graphics/New3D/R3DScrollFog.cpp +++ b/Src/Graphics/New3D/R3DScrollFog.cpp @@ -1,9 +1,149 @@ #include "R3DScrollFog.h" -#include "Graphics/Shader.h" #include "Mat4.h" +#include +#include namespace New3D { +#ifndef __ANDROID__ +#include "Graphics/Shader.h" +#endif + +#ifdef __ANDROID__ +namespace { +static void LogShaderError(GLuint shader, const char* label) +{ + GLint ok = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + if (ok == GL_TRUE) return; + GLint len = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); + if (len <= 0) return; + std::string msg; + msg.resize(static_cast(len)); + GLsizei written = 0; + glGetShaderInfoLog(shader, len, &written, msg.data()); + fprintf(stderr, "R3DScrollFog %s compile error: %s\n", label ? label : "shader", msg.c_str()); +} + +static void LogProgramError(GLuint program) +{ + GLint ok = 0; + glGetProgramiv(program, GL_LINK_STATUS, &ok); + if (ok == GL_TRUE) return; + GLint len = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len); + if (len <= 0) return; + std::string msg; + msg.resize(static_cast(len)); + GLsizei written = 0; + glGetProgramInfoLog(program, len, &written, msg.data()); + fprintf(stderr, "R3DScrollFog link error: %s\n", msg.c_str()); +} + +static bool CompileShaderProgram(GLuint* programOut, GLuint* vsOut, GLuint* fsOut, const char* vsSrc, const char* fsSrc) +{ + if (!programOut || !vsOut || !fsOut) return false; + *programOut = 0; + *vsOut = 0; + *fsOut = 0; + + GLuint vs = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vs, 1, &vsSrc, nullptr); + glCompileShader(vs); + LogShaderError(vs, "vertex"); + + GLuint fs = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fs, 1, &fsSrc, nullptr); + glCompileShader(fs); + LogShaderError(fs, "fragment"); + + GLuint program = glCreateProgram(); + glAttachShader(program, vs); + glAttachShader(program, fs); + glLinkProgram(program); + LogProgramError(program); + + GLint ok = 0; + glGetProgramiv(program, GL_LINK_STATUS, &ok); + if (ok != GL_TRUE) { + glDeleteProgram(program); + glDeleteShader(vs); + glDeleteShader(fs); + return false; + } + + *programOut = program; + *vsOut = vs; + *fsOut = fs; + return true; +} +} // namespace +#endif + +#ifdef __ANDROID__ +static const char *vertexShaderFog = R"glsl( + +#version 300 es +precision highp float; + +uniform mat4 mvp; +in vec3 inVertex; + +void main(void) +{ + gl_Position = mvp * vec4(inVertex,1.0); +} + +)glsl"; + +static const char *fragmentShaderFog = R"glsl( + +#version 300 es +precision mediump float; + +uniform float fogAttenuation; +uniform float fogAmbient; +uniform vec4 fogColour; +uniform vec3 spotFogColor; +uniform vec4 spotEllipse; + +out vec4 oColor; + +// Spotlight on fog +float ellipse; +vec2 position, size; +vec3 lSpotFogColor; + +// Scroll fog +vec3 lFogColor; +vec4 scrollFog; + +void main() +{ + // Scroll fog base color + lFogColor = fogColour.rgb * fogAmbient; + + // Spotlight on fog (area) + position = spotEllipse.xy; + size = spotEllipse.zw; + ellipse = length((gl_FragCoord.xy - position) / size); + ellipse = ellipse * ellipse; // decay rate = square of distance from center + ellipse = 1.0 - ellipse; // invert + ellipse = max(0.0, ellipse); // clamp + + // Spotlight on fog (color) + lSpotFogColor = mix(spotFogColor * ellipse * fogColour.rgb, vec3(0.0), fogAttenuation); + + // Scroll fog density + scrollFog = vec4(lFogColor + lSpotFogColor, fogColour.a); + + // Final Color + oColor = scrollFog; +} + +)glsl"; +#else static const char *vertexShaderFog = R"glsl( #version 120 @@ -62,6 +202,7 @@ void main() } )glsl"; +#endif R3DScrollFog::R3DScrollFog(const Util::Config::Node &config) @@ -112,10 +253,10 @@ void R3DScrollFog::DrawScrollFog(float rgba[4], float attenuation, float ambient glUniform4f (m_locSpotEllipse, spotEllipse[0], spotEllipse[1], spotEllipse[2], spotEllipse[3]); glUniformMatrix4fv (m_locMVP, 1, GL_FALSE, mvp); - glEnableVertexAttribArray (0); + glEnableVertexAttribArray (m_locInVertex); glVertexAttribPointer (m_locInVertex, 3, GL_FLOAT, GL_FALSE, sizeof(SFVertex), 0); glDrawArrays (GL_TRIANGLES, 0, 6); - glDisableVertexAttribArray (0); + glDisableVertexAttribArray (m_locInVertex); glUseProgram (0); m_vbo.Bind (false); @@ -126,7 +267,26 @@ void R3DScrollFog::DrawScrollFog(float rgba[4], float attenuation, float ambient void R3DScrollFog::AllocResources() { - bool success = LoadShaderProgram(&m_shaderProgram, &m_vertexShader, &m_fragmentShader, m_config["VertexShaderFog"].ValueAs(), m_config["FragmentShaderFog"].ValueAs(), vertexShaderFog, fragmentShaderFog); +#ifdef __ANDROID__ + bool success = CompileShaderProgram(&m_shaderProgram, &m_vertexShader, &m_fragmentShader, vertexShaderFog, fragmentShaderFog); +#else + bool success = + LoadShaderProgram( + &m_shaderProgram, + &m_vertexShader, + &m_fragmentShader, + m_config["VertexShaderFog"].ValueAsDefault(""), + m_config["FragmentShaderFog"].ValueAsDefault(""), + vertexShaderFog, + fragmentShaderFog + ); +#endif + if (!success) { + m_shaderProgram = 0; + m_vertexShader = 0; + m_fragmentShader = 0; + return; + } m_locMVP = glGetUniformLocation(m_shaderProgram, "mvp"); m_locFogColour = glGetUniformLocation(m_shaderProgram, "fogColour"); @@ -143,7 +303,16 @@ void R3DScrollFog::AllocResources() void R3DScrollFog::DeallocResources() { if (m_shaderProgram) { +#ifdef __ANDROID__ + glUseProgram(0); + glDetachShader(m_shaderProgram, m_vertexShader); + glDetachShader(m_shaderProgram, m_fragmentShader); + glDeleteShader(m_vertexShader); + glDeleteShader(m_fragmentShader); + glDeleteProgram(m_shaderProgram); +#else DestroyShaderProgram(m_shaderProgram, m_vertexShader, m_fragmentShader); +#endif } m_shaderProgram = 0; diff --git a/Src/Graphics/New3D/R3DScrollFog.h b/Src/Graphics/New3D/R3DScrollFog.h index 7fadef1..333ed0f 100644 --- a/Src/Graphics/New3D/R3DScrollFog.h +++ b/Src/Graphics/New3D/R3DScrollFog.h @@ -1,6 +1,12 @@ #ifndef _R3DSCROLLFOG_H_ #define _R3DSCROLLFOG_H_ +#ifdef __ANDROID__ +#include +#else +#include +#endif + #include "Util/NewConfig.h" #include "VBO.h" @@ -61,4 +67,4 @@ private: } -#endif \ No newline at end of file +#endif diff --git a/android/app/src/main/assets/Config/Supermodel.ini b/android/app/src/main/assets/Config/Supermodel.ini index 4042416..4e346bf 100644 --- a/android/app/src/main/assets/Config/Supermodel.ini +++ b/android/app/src/main/assets/Config/Supermodel.ini @@ -31,6 +31,8 @@ YResolution = 384 VSync = 1 WideScreen = 0 WideBackground = 0 +; Enhanced Real3D compositing (FBO transparency + scroll fog). More accurate but may be slower. +New3DAccurate = 0 ; Input system (Android uses SDL input backend) InputSystem = sdl diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt index 53a8732..c6780e0 100644 --- a/android/app/src/main/cpp/CMakeLists.txt +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -116,11 +116,14 @@ add_library(super3 SHARED gles_stub_render3d.cpp render2d_android.cpp "${REPO_ROOT}/Src/Graphics/New3D/Mat4.cpp" + "${REPO_ROOT}/Src/Graphics/New3D/GLSLShader.cpp" "${REPO_ROOT}/Src/Graphics/New3D/Model.cpp" "${REPO_ROOT}/Src/Graphics/New3D/New3D.cpp" "${REPO_ROOT}/Src/Graphics/New3D/PolyHeader.cpp" "${REPO_ROOT}/Src/Graphics/New3D/R3DFloat.cpp" + "${REPO_ROOT}/Src/Graphics/New3D/R3DFrameBuffers.cpp" "${REPO_ROOT}/Src/Graphics/New3D/R3DShader.cpp" + "${REPO_ROOT}/Src/Graphics/New3D/R3DScrollFog.cpp" "${REPO_ROOT}/Src/Graphics/New3D/Texture.cpp" "${REPO_ROOT}/Src/Graphics/New3D/TextureSheet.cpp" "${REPO_ROOT}/Src/Graphics/New3D/VBO.cpp" diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp index 3d800ee..c9aa4b7 100644 --- a/android/app/src/main/cpp/native-lib.cpp +++ b/android/app/src/main/cpp/native-lib.cpp @@ -174,6 +174,7 @@ struct Super3Host { config.Set("MusicVolume", "200"); config.Set("LegacySoundDSP", false); config.Set("New3DEngine", false); + config.Set("New3DAccurate", false); config.Set("QuadRendering", false); config.Set("FlipStereo", false); // The core expects this node to exist (throws std::range_error otherwise). @@ -573,6 +574,7 @@ struct Super3Host { return !!new3d; SDL_Log("Initializing New3D (GLES) ..."); + SDL_Log("New3DAccurate=%d", config["New3DAccurate"].ValueAsDefault(false) ? 1 : 0); new3d = std::make_unique(config, game.name); if (new3d->Init(xOff, yOff, xRes, yRes, totalXRes, totalYRes) != 0) { diff --git a/android/app/src/main/java/com/izzy2lost/super3/MainActivity.kt b/android/app/src/main/java/com/izzy2lost/super3/MainActivity.kt index 31f35e3..648b573 100644 --- a/android/app/src/main/java/com/izzy2lost/super3/MainActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/super3/MainActivity.kt @@ -45,6 +45,7 @@ class MainActivity : AppCompatActivity() { private lateinit var btnResolutionMatchDevice: MaterialButton private lateinit var btnWidescreen: MaterialButton private lateinit var btnWideBackground: MaterialButton + private lateinit var btnEnhancedReal3d: MaterialButton private lateinit var gamesAdapter: GamesAdapter @@ -62,6 +63,7 @@ class MainActivity : AppCompatActivity() { val yResolution: Int, val wideScreen: Boolean, val wideBackground: Boolean, + val enhancedReal3d: Boolean, val matchDevice: Boolean, ) @@ -125,6 +127,7 @@ class MainActivity : AppCompatActivity() { btnResolutionMatchDevice = headerView.findViewById(R.id.btn_resolution_match_device) btnWidescreen = headerView.findViewById(R.id.btn_widescreen) btnWideBackground = headerView.findViewById(R.id.btn_wide_background) + btnEnhancedReal3d = headerView.findViewById(R.id.btn_enhanced_real3d) val btnShowTouchControls: MaterialButton = headerView.findViewById(R.id.btn_show_touch_controls) val btnShowShifterOverlay: MaterialButton = headerView.findViewById(R.id.btn_show_shifter_overlay) @@ -230,7 +233,8 @@ class MainActivity : AppCompatActivity() { val wideScreen = prefs.getBoolean("video_wideScreen", false) val wideBackground = prefs.getBoolean("video_wideBackground", false) val matchDevice = prefs.getBoolean("video_matchDevice", false) - return VideoSettings(x, y, wideScreen, wideBackground, matchDevice) + val enhancedReal3d = prefs.getBoolean("video_enhancedReal3d", false) + return VideoSettings(x, y, wideScreen, wideBackground, enhancedReal3d, matchDevice) } val ini = supermodelIniFile() @@ -238,7 +242,8 @@ class MainActivity : AppCompatActivity() { val y = readIniInt(ini, "YResolution") ?: 384 val wideScreen = readIniBool(ini, "WideScreen") ?: false val wideBackground = readIniBool(ini, "WideBackground") ?: false - return VideoSettings(x, y, wideScreen, wideBackground, matchDevice = false) + val enhancedReal3d = readIniBool(ini, "New3DAccurate") ?: false + return VideoSettings(x, y, wideScreen, wideBackground, enhancedReal3d, matchDevice = false) } private fun saveVideoSettings(settings: VideoSettings) { @@ -247,6 +252,7 @@ class MainActivity : AppCompatActivity() { .putInt("video_yResolution", settings.yResolution) .putBoolean("video_wideScreen", settings.wideScreen) .putBoolean("video_wideBackground", settings.wideBackground) + .putBoolean("video_enhancedReal3d", settings.enhancedReal3d) .putBoolean("video_matchDevice", settings.matchDevice) .apply() } @@ -271,6 +277,7 @@ class MainActivity : AppCompatActivity() { btnResolutionMatchDevice.isChecked = settings.matchDevice btnWidescreen.isChecked = settings.wideScreen btnWideBackground.isChecked = settings.wideBackground + btnEnhancedReal3d.isChecked = settings.enhancedReal3d } fun persistAndApply(settings: VideoSettings) { @@ -334,6 +341,23 @@ class MainActivity : AppCompatActivity() { persistAndApply(updated) applyUi(updated) } + + btnEnhancedReal3d.setOnClickListener { + val cur = loadVideoSettings() + val enabled = btnEnhancedReal3d.isChecked + val updated = cur.copy(enhancedReal3d = enabled, matchDevice = false) + persistAndApply(updated) + applyUi(updated) + Toast.makeText( + this, + if (enabled) { + "Enhanced Real3D enabled (restart game to apply)" + } else { + "Enhanced Real3D disabled (restart game to apply)" + }, + Toast.LENGTH_SHORT, + ).show() + } } private fun applyVideoSettingsToIni(internalRoot: File, settings: VideoSettings) { @@ -345,6 +369,7 @@ class MainActivity : AppCompatActivity() { "YResolution" to settings.yResolution.toString(), "WideScreen" to if (settings.wideScreen) "1" else "0", "WideBackground" to if (settings.wideBackground) "1" else "0", + "New3DAccurate" to if (settings.enhancedReal3d) "1" else "0", ), ) } diff --git a/android/app/src/main/res/layout/nav_header.xml b/android/app/src/main/res/layout/nav_header.xml index d6e0f2c..7d1b10f 100644 --- a/android/app/src/main/res/layout/nav_header.xml +++ b/android/app/src/main/res/layout/nav_header.xml @@ -101,6 +101,15 @@ android:text="Wide background" style="?attr/materialButtonElevatedStyle" /> + +