diff --git a/.github/gles.patch b/.github/gles.patch index 548b243db..b16733b5e 100644 --- a/.github/gles.patch +++ b/.github/gles.patch @@ -1,25 +1,3 @@ -diff --git a/src/host_shaders/opengl_display.frag b/src/host_shaders/opengl_display.frag -index 612671c8..1937f711 100644 ---- a/src/host_shaders/opengl_display.frag -+++ b/src/host_shaders/opengl_display.frag -@@ -1,4 +1,5 @@ --#version 410 core -+#version 300 es -+precision mediump float; - in vec2 UV; - out vec4 FragColor; - -diff --git a/src/host_shaders/opengl_display.vert b/src/host_shaders/opengl_display.vert -index 990e2f80..2e7842ac 100644 ---- a/src/host_shaders/opengl_display.vert -+++ b/src/host_shaders/opengl_display.vert -@@ -1,4 +1,5 @@ --#version 410 core -+#version 300 es -+precision mediump float; - out vec2 UV; - - void main() { diff --git a/src/host_shaders/opengl_fragment_shader.frag b/src/host_shaders/opengl_fragment_shader.frag index 9f07df0b..96a35afa 100644 --- a/src/host_shaders/opengl_fragment_shader.frag diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bd8b9ed5..0f4fdb130 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -465,8 +465,9 @@ if(ENABLE_OPENGL) set(RENDERER_GL_SOURCE_FILES src/core/renderer_gl/renderer_gl.cpp src/core/renderer_gl/textures.cpp src/core/renderer_gl/etc1.cpp - src/core/renderer_gl/gl_state.cpp src/host_shaders/opengl_display.frag - src/host_shaders/opengl_display.vert src/host_shaders/opengl_vertex_shader.vert + src/core/renderer_gl/gl_state.cpp src/host_shaders/opengl_display.vert + src/host_shaders/opengl_display.frag src/host_shaders/opengl_es_display.vert + src/host_shaders/opengl_es_display.frag src/host_shaders/opengl_vertex_shader.vert src/host_shaders/opengl_fragment_shader.frag ) @@ -479,8 +480,10 @@ if(ENABLE_OPENGL) resources_renderer_gl NAMESPACE RendererGL WHENCE "src/host_shaders/" - "src/host_shaders/opengl_display.frag" "src/host_shaders/opengl_display.vert" + "src/host_shaders/opengl_display.frag" + "src/host_shaders/opengl_es_display.vert" + "src/host_shaders/opengl_es_display.frag" "src/host_shaders/opengl_vertex_shader.vert" "src/host_shaders/opengl_fragment_shader.frag" ) diff --git a/include/emulator.hpp b/include/emulator.hpp index cf231328f..a222a021f 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -89,7 +89,6 @@ class Emulator { ~Emulator(); void step(); - void render(); void reset(ReloadOption reload); void runFrame(); // Poll the scheduler for events diff --git a/include/renderer.hpp b/include/renderer.hpp index bc5dfac68..b458ecced 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -81,6 +81,10 @@ class Renderer { virtual std::string getUbershader() { return ""; } virtual void setUbershader(const std::string& shader) {} + // Only relevant for OpenGL renderer and other OpenGL-based backends (eg software) + // Called to notify the core to use OpenGL ES and not desktop GL + virtual void setupGLES() {} + // This function is called on every draw call before parsing vertex data. // It is responsible for things like looking up which vertex/fragment shaders to use, recompiling them if they don't exist, choosing between // ubershaders and shadergen, and so on. diff --git a/include/renderer_gl/renderer_gl.hpp b/include/renderer_gl/renderer_gl.hpp index fab239f25..a862cd267 100644 --- a/include/renderer_gl/renderer_gl.hpp +++ b/include/renderer_gl/renderer_gl.hpp @@ -40,7 +40,7 @@ class RendererGL final : public Renderer { OpenGL::VertexArray hwShaderVAO; OpenGL::VertexBuffer vbo; - // Data + // Data struct { // TEV configuration uniform locations GLint textureEnvSourceLoc = -1; @@ -157,6 +157,7 @@ class RendererGL final : public Renderer { void initGraphicsContextInternal(); void accelerateVertexUpload(ShaderUnit& shaderUnit, PICA::DrawAcceleration* accel); + void compileDisplayShader(); public: RendererGL(GPU& gpu, const std::array& internalRegs, const std::array& externalRegs) @@ -169,14 +170,15 @@ class RendererGL final : public Renderer { void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) override; // Clear a GPU buffer in VRAM void displayTransfer(u32 inputAddr, u32 outputAddr, u32 inputSize, u32 outputSize, u32 flags) override; // Perform display transfer void textureCopy(u32 inputAddr, u32 outputAddr, u32 totalBytes, u32 inputSize, u32 outputSize, u32 flags) override; - void drawVertices(PICA::PrimType primType, std::span vertices) override; // Draw the given vertices + void drawVertices(PICA::PrimType primType, std::span vertices) override; // Draw the given vertices void deinitGraphicsContext() override; virtual bool supportsShaderReload() override { return true; } virtual std::string getUbershader() override; virtual void setUbershader(const std::string& shader) override; virtual bool prepareForDraw(ShaderUnit& shaderUnit, PICA::DrawAcceleration* accel) override; - + virtual void setupGLES() override; + std::optional getColourBuffer(u32 addr, PICA::ColorFmt format, u32 width, u32 height, bool createIfnotFound = true); // Note: The caller is responsible for deleting the currently bound FBO before calling this diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 35de68ce4..ab44eb081 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -56,10 +56,6 @@ void RendererGL::reset() { void RendererGL::initGraphicsContextInternal() { gl.reset(); -#if defined(USING_GLES) || defined(__ANDROID__) - driverInfo.usingGLES = true; -#endif - auto gl_resources = cmrc::RendererGL::get_filesystem(); auto vertexShaderSource = gl_resources.open("opengl_vertex_shader.vert"); auto fragmentShaderSource = gl_resources.open("opengl_fragment_shader.frag"); @@ -69,16 +65,7 @@ void RendererGL::initGraphicsContextInternal() { triangleProgram.create({vert, frag}); initUbershader(triangleProgram); - auto displayVertexShaderSource = gl_resources.open("opengl_display.vert"); - auto displayFragmentShaderSource = gl_resources.open("opengl_display.frag"); - - OpenGL::Shader vertDisplay({displayVertexShaderSource.begin(), displayVertexShaderSource.size()}, OpenGL::Vertex); - OpenGL::Shader fragDisplay({displayFragmentShaderSource.begin(), displayFragmentShaderSource.size()}, OpenGL::Fragment); - displayProgram.create({vertDisplay, fragDisplay}); - - gl.useProgram(displayProgram); - glUniform1i(OpenGL::uniformLocation(displayProgram, "u_texture"), 0); // Init sampler object - + compileDisplayShader(); // Create stream buffers for vertex, index and uniform buffers static constexpr usize hwIndexBufferSize = 2_MB; static constexpr usize hwVertexBufferSize = 16_MB; @@ -1156,6 +1143,19 @@ void RendererGL::initUbershader(OpenGL::Program& program) { glUniform1i(OpenGL::uniformLocation(program, "u_tex_luts"), 3); } +void RendererGL::compileDisplayShader() { + auto gl_resources = cmrc::RendererGL::get_filesystem(); + auto displayVertexShaderSource = driverInfo.usingGLES ? gl_resources.open("opengl_es_display.vert") : gl_resources.open("opengl_display.vert"); + auto displayFragmentShaderSource = driverInfo.usingGLES ? gl_resources.open("opengl_es_display.frag") : gl_resources.open("opengl_display.frag"); + + OpenGL::Shader vertDisplay({displayVertexShaderSource.begin(), displayVertexShaderSource.size()}, OpenGL::Vertex); + OpenGL::Shader fragDisplay({displayFragmentShaderSource.begin(), displayFragmentShaderSource.size()}, OpenGL::Fragment); + displayProgram.create({vertDisplay, fragDisplay}); + + gl.useProgram(displayProgram); + glUniform1i(OpenGL::uniformLocation(displayProgram, "u_texture"), 0); // Init sampler object +} + void RendererGL::accelerateVertexUpload(ShaderUnit& shaderUnit, PICA::DrawAcceleration* accel) { u32 buffer = 0; // Vertex buffer index for non-fixed attributes u32 attrCount = 0; @@ -1250,4 +1250,18 @@ void RendererGL::accelerateVertexUpload(ShaderUnit& shaderUnit, PICA::DrawAccele ); } } +} + +void RendererGL::setupGLES() { + driverInfo.usingGLES = true; + + // OpenGL ES hardware is typically way too slow to use the ubershader (eg RPi, mobile phones, handhelds) or has other issues with it. + // So, display a warning and turn them off on OpenGL ES. + if (emulatorConfig->useUbershaders) { + emulatorConfig->useUbershaders = false; + Helpers::warn("Ubershaders enabled on OpenGL ES. This usually results in a worse experience, turning it off..."); + } + + // Stub out logic operations so that calling them doesn't crash the emulator + glLogicOp = [](GLenum) {}; } \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index 81a18f307..11970d91c 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -117,7 +117,6 @@ std::filesystem::path Emulator::getConfigPath() { #endif void Emulator::step() {} -void Emulator::render() {} // Only resume if a ROM is properly loaded void Emulator::resume() { diff --git a/src/host_shaders/opengl_es_display.frag b/src/host_shaders/opengl_es_display.frag new file mode 100644 index 000000000..600ebfcd0 --- /dev/null +++ b/src/host_shaders/opengl_es_display.frag @@ -0,0 +1,10 @@ +#version 310 es +precision mediump float; + +in vec2 UV; +out vec4 FragColor; + +uniform sampler2D u_texture; +void main() { + FragColor = texture(u_texture, UV); +} \ No newline at end of file diff --git a/src/host_shaders/opengl_es_display.vert b/src/host_shaders/opengl_es_display.vert new file mode 100644 index 000000000..04fadfc67 --- /dev/null +++ b/src/host_shaders/opengl_es_display.vert @@ -0,0 +1,25 @@ +#version 310 es +precision mediump float; + +out vec2 UV; + +void main() { + const vec4 positions[4] = vec4[]( + vec4(-1.0, 1.0, 1.0, 1.0), // Top-left + vec4(1.0, 1.0, 1.0, 1.0), // Top-right + vec4(-1.0, -1.0, 1.0, 1.0), // Bottom-left + vec4(1.0, -1.0, 1.0, 1.0) // Bottom-right + ); + + // The 3DS displays both screens' framebuffer rotated 90 deg counter clockwise + // So we adjust our texcoords accordingly + const vec2 texcoords[4] = vec2[]( + vec2(1.0, 1.0), // Top-right + vec2(1.0, 0.0), // Bottom-right + vec2(0.0, 1.0), // Top-left + vec2(0.0, 0.0) // Bottom-left + ); + + gl_Position = positions[gl_VertexID]; + UV = texcoords[gl_VertexID]; +} \ No newline at end of file diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 078b8a6c9..0bcd21a8a 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -118,6 +118,7 @@ void HydraCore::resetContext() { if (!gladLoadGLES2Loader(reinterpret_cast(getProcAddress))) { Helpers::panic("OpenGL ES init failed"); } + emulator->getRenderer()->setupGLES(); #else if (!gladLoadGLLoader(reinterpret_cast(getProcAddress))) { Helpers::panic("OpenGL init failed"); diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index 60bbc6806..6a156360a 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -78,6 +78,7 @@ AlberFunction(void, Initialize)(JNIEnv* env, jobject obj) { } __android_log_print(ANDROID_LOG_INFO, "AlberDriver", "OpenGL ES %d.%d", GLVersion.major, GLVersion.minor); + emulator->getRenderer()->setupGLES(); emulator->initGraphicsContext(nullptr); } @@ -153,7 +154,6 @@ int AndroidUtils::openDocument(const char* path, const char* perms) { jstring uri = env->NewStringUTF(path); jstring jmode = env->NewStringUTF(perms); - jint result = env->CallStaticIntMethod(alberClass, alberClassOpenDocument, uri, jmode); env->DeleteLocalRef(uri); diff --git a/src/libretro_core.cpp b/src/libretro_core.cpp index fe3cb6c48..727da8d2d 100644 --- a/src/libretro_core.cpp +++ b/src/libretro_core.cpp @@ -17,7 +17,8 @@ static retro_input_state_t inputStateCallback; static retro_hw_render_callback hwRender; static std::filesystem::path savePath; -static bool screenTouched; +static bool screenTouched = false; +static bool usingGLES = false; std::unique_ptr emulator; RendererGL* renderer; @@ -35,15 +36,19 @@ static void* getGLProcAddress(const char* name) { } static void videoResetContext() { -#ifdef USING_GLES - if (!gladLoadGLES2Loader(reinterpret_cast(getGLProcAddress))) { - Helpers::panic("OpenGL ES init failed"); + if (usingGLES) { + if (!gladLoadGLES2Loader(reinterpret_cast(getGLProcAddress))) { + Helpers::panic("OpenGL ES init failed"); + } + + emulator->getRenderer()->setupGLES(); } -#else - if (!gladLoadGLLoader(reinterpret_cast(getGLProcAddress))) { - Helpers::panic("OpenGL init failed"); + + else { + if (!gladLoadGLLoader(reinterpret_cast(getGLProcAddress))) { + Helpers::panic("OpenGL init failed"); + } } -#endif emulator->initGraphicsContext(nullptr); } @@ -73,6 +78,7 @@ static bool setHWRender(retro_hw_context_type type) { hwRender.version_minor = 1; if (envCallback(RETRO_ENVIRONMENT_SET_HW_RENDER, &hwRender)) { + usingGLES = true; return true; } break; diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 881dc02d2..dffe5ca0f 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -140,6 +140,10 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) glContext->MakeCurrent(); glContext->SetSwapInterval(emu->getConfig().vsyncEnabled ? 1 : 0); + if (glContext->IsGLES()) { + emu->getRenderer()->setupGLES(); + } + emu->initGraphicsContext(glContext); } else if (usingVk) { Helpers::panic("Vulkan on Qt is currently WIP, try the SDL frontend instead!"); diff --git a/src/panda_qt/screen.cpp b/src/panda_qt/screen.cpp index 25ff576c4..919b66943 100644 --- a/src/panda_qt/screen.cpp +++ b/src/panda_qt/screen.cpp @@ -60,11 +60,12 @@ void ScreenWidget::resizeSurface(u32 width, u32 height) { } bool ScreenWidget::createGLContext() { - // List of GL context versions we will try. Anything 4.1+ is good - static constexpr std::array versionsToTry = { + // List of GL context versions we will try. Anything 4.1+ is good for desktop OpenGL, and 3.1+ for OpenGL ES + static constexpr std::array versionsToTry = { GL::Context::Version{GL::Context::Profile::Core, 4, 6}, GL::Context::Version{GL::Context::Profile::Core, 4, 5}, GL::Context::Version{GL::Context::Profile::Core, 4, 4}, GL::Context::Version{GL::Context::Profile::Core, 4, 3}, GL::Context::Version{GL::Context::Profile::Core, 4, 2}, GL::Context::Version{GL::Context::Profile::Core, 4, 1}, + GL::Context::Version{GL::Context::Profile::ES, 3, 2}, GL::Context::Version{GL::Context::Profile::ES, 3, 1}, }; std::optional windowInfo = getWindowInfo(); @@ -72,6 +73,10 @@ bool ScreenWidget::createGLContext() { this->windowInfo = *windowInfo; glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); + if (glContext == nullptr) { + return false; + } + glContext->DoneCurrent(); } diff --git a/src/panda_sdl/frontend_sdl.cpp b/src/panda_sdl/frontend_sdl.cpp index 1c07c25e4..2d60d2fa4 100644 --- a/src/panda_sdl/frontend_sdl.cpp +++ b/src/panda_sdl/frontend_sdl.cpp @@ -71,11 +71,27 @@ FrontendSDL::FrontendSDL() : keyboardMappings(InputMappings::defaultKeyboardMapp glContext = SDL_GL_CreateContext(window); if (glContext == nullptr) { - Helpers::panic("OpenGL context creation failed: %s", SDL_GetError()); - } + Helpers::warn("OpenGL context creation failed: %s\nTrying again with OpenGL ES.", SDL_GetError()); + + // Some low end devices (eg RPi, emulation handhelds) don't support desktop GL, but only OpenGL ES, so fall back to that if GL context + // creation failed + 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, 1); + glContext = SDL_GL_CreateContext(window); + if (glContext == nullptr) { + Helpers::panic("OpenGL context creation failed: %s", SDL_GetError()); + } - if (!gladLoadGLLoader(reinterpret_cast(SDL_GL_GetProcAddress))) { - Helpers::panic("OpenGL init failed"); + if (!gladLoadGLES2Loader(reinterpret_cast(SDL_GL_GetProcAddress))) { + Helpers::panic("OpenGL init failed"); + } + + emu.getRenderer()->setupGLES(); + } else { + if (!gladLoadGLLoader(reinterpret_cast(SDL_GL_GetProcAddress))) { + Helpers::panic("OpenGL init failed"); + } } SDL_GL_SetSwapInterval(config.vsyncEnabled ? 1 : 0);