diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 35de68ce..73397276 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -579,11 +579,13 @@ void RendererGL::display() { OpenGL::draw(OpenGL::TriangleStrip, 4); } +#ifndef __LIBRETRO__ if constexpr (!Helpers::isHydraCore()) { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); screenFramebuffer.bind(OpenGL::ReadFramebuffer); glBlitFramebuffer(0, 0, 400, 480, 0, 0, outputWindowWidth, outputWindowHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); } +#endif } void RendererGL::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { diff --git a/src/libretro_core.cpp b/src/libretro_core.cpp index fe3cb6c4..2e5a3a86 100644 --- a/src/libretro_core.cpp +++ b/src/libretro_core.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include @@ -17,8 +18,32 @@ static retro_input_state_t inputStateCallback; static retro_hw_render_callback hwRender; static std::filesystem::path savePath; -static bool screenTouched; +static std::string touchScreenMode; +static bool renderTouchScreen; +static auto cursorTimeout = 0; +static auto cursorMovedAt = std::chrono::steady_clock::now(); +static bool cursorVisible = false; + +static bool screenTouched; +static int lastMouseX; +static int lastMouseY; + +static int touchX = 0; +static int touchY = 0; + +class CursorRenderer { + OpenGL::Program shader; + OpenGL::VertexArray vao; + OpenGL::VertexBuffer vbo; + + public: + void init(); + void deinit(); + void draw(float x, float y, float size = 5); +}; + +std::unique_ptr cursorRenderer; std::unique_ptr emulator; RendererGL* renderer; @@ -46,9 +71,11 @@ static void videoResetContext() { #endif emulator->initGraphicsContext(nullptr); + cursorRenderer->init(); } static void videoDestroyContext() { + cursorRenderer->deinit(); emulator->deinitGraphicsContext(); } @@ -123,8 +150,11 @@ static void inputInit() { {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "X"}, {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Y"}, + {0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Screen Touch"}, {0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Circle Pad X"}, {0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Circle Pad Y"}, + {0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Pointer X"}, + {0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Pointer Y"}, {0}, }; @@ -183,6 +213,9 @@ static void configInit() { {"panda3ds_write_protect_virtual_sd", "Write protect virtual SD card; disabled|enabled"}, {"panda3ds_battery_level", "Battery percentage; 5|10|20|30|50|70|90|100"}, {"panda3ds_use_charger", "Charger plugged; enabled|disabled"}, + {"panda3ds_touchscreen_mode", "Touchscreen touch mode; Auto|Pointer|Joystick|None"}, + {"panda3ds_render_touchscreen", "Render touchscreen pointer; disabled|enabled"}, + {"panda3ds_hide_cursor_timeout", "Hide touchScreen pointer timeout; 3 Seconds|5 Seconds|10 Seconds|15 Seconds|20 Seconds|Never Hide"}, {nullptr, nullptr}, }; @@ -215,8 +248,13 @@ static void configUpdate() { config.lightShadergenThreshold = fetchVariableRange("panda3ds_ubershader_lighting_override_threshold", 1, 8); config.discordRpcEnabled = false; + touchScreenMode = fetchVariable("panda3ds_touchscreen_mode", "Auto"); + renderTouchScreen = fetchVariableBool("panda3ds_render_touchscreen", false); + cursorTimeout = fetchVariableInt("panda3ds_hide_cursor_timeout", 3); + // Handle any settings that might need the emulator core to be notified when they're changed, and save the config. emulator->setAudioEnabled(config.audioEnabled); + config.save(); } @@ -229,6 +267,95 @@ static void configCheckVariables() { } } +static void updateCursorVisibility() { + if (renderTouchScreen && cursorTimeout) { + if (cursorVisible) { + auto current = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(current - cursorMovedAt).count(); + + if (elapsed >= cursorTimeout) { + cursorVisible = false; + } + } + } else { + cursorVisible = true; + } +} + +void CursorRenderer::init() { +#ifdef USING_GLES + static const std::string version = R"( + #version 300 es + precision mediump float; + )"; +#else + static const std::string version = R"( + #version 410 core + )"; +#endif + + std::string vertex = version + R"( + in vec2 position; + void main() { gl_Position = vec4(position, 0.0, 1.0); } + )"; + + std::string fragment = version + R"( + out vec4 color; + void main() { color = vec4(1.0, 1.0, 1.0, 1.0); } + )"; + + OpenGL::Shader vertCursor(vertex.c_str(), OpenGL::Vertex); + OpenGL::Shader fragCursor(fragment.c_str(), OpenGL::Fragment); + shader.create({vertCursor, fragCursor}); + + vbo.createFixedSize(12 * 12 * sizeof(GLfloat)); + vbo.bind(); + + vao.create(); + vao.bind(); + + vao.setAttributeFloat(0, 2, 4 * sizeof(GLfloat), nullptr); + vao.enableAttribute(0); + + vertCursor.free(); + fragCursor.free(); +} + +void CursorRenderer::deinit() { + shader.free(); + vao.free(); + vbo.free(); +} + +void CursorRenderer::draw(float x, float y, float size) { + shader.use(); + vao.bind(); + vbo.bind(); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR); + + float centerX = (x / 320) * 2.0f - 1.0f; + float centerY = 1.0f - (y / 240) * 2.0f; + + GLfloat sizeX = size / 320; + GLfloat sizeY = size / 240; + + GLfloat cursor[] = { + centerX - sizeX, centerY - sizeY, -0.5f, -0.5f, + centerX + sizeX, centerY - sizeY, 0.5f, -0.5f, + centerX + sizeX, centerY + sizeY, 0.5f, 0.5f, + centerX - sizeX, centerY + sizeY, -0.5f, 0.5f + }; + + vbo.bufferVertsSub(cursor, 12 * sizeof(GLfloat)); + + OpenGL::setViewport(40, 0, 320, 240); + OpenGL::draw(OpenGL::TriangleFan, 0, 4); + + glDisable(GL_BLEND); +} + void retro_get_system_info(retro_system_info* info) { info->need_fullpath = true; info->valid_extensions = "3ds|3dsx|elf|axf|cci|cxi|app"; @@ -285,10 +412,12 @@ void retro_init() { } emulator = std::make_unique(); + cursorRenderer = std::make_unique(); } void retro_deinit() { emulator = nullptr; + cursorRenderer = nullptr; } bool retro_load_game(const retro_game_info* game) { @@ -321,6 +450,7 @@ void retro_reset() { void retro_run() { configCheckVariables(); + updateCursorVisibility(); renderer->setFBO(hwRender.get_current_framebuffer()); renderer->resetStateManager(); @@ -351,26 +481,62 @@ void retro_run() { bool touchScreen = false; - const int posX = inputStateCallback(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_X); - const int posY = inputStateCallback(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_Y); - - const int newX = static_cast((posX + 0x7fff) / (float)(0x7fff * 2) * emulator->width); - const int newY = static_cast((posY + 0x7fff) / (float)(0x7fff * 2) * emulator->height); + int pointerX = touchX; + int pointerY = touchY; const int offsetX = 40; const int offsetY = emulator->height / 2; - const bool inScreenX = newX >= offsetX && newX <= emulator->width - offsetX; - const bool inScreenY = newY >= offsetY && newY <= emulator->height; + if (touchScreenMode == "Pointer" || touchScreenMode == "Auto") { + const int posX = inputStateCallback(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_X); + const int posY = inputStateCallback(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_Y); - if (inScreenX && inScreenY) { - touchScreen |= inputStateCallback(0, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT); - touchScreen |= inputStateCallback(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_PRESSED); + const int newX = static_cast((posX + 0x7fff) / (float)(0x7fff * 2) * emulator->width); + const int newY = static_cast((posY + 0x7fff) / (float)(0x7fff * 2) * emulator->height); + + const bool inScreenX = newX >= offsetX && newX <= emulator->width - offsetX; + const bool inScreenY = newY >= offsetY && newY <= emulator->height; + + if (inScreenX && inScreenY) { + touchScreen |= inputStateCallback(0, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT); + touchScreen |= inputStateCallback(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_PRESSED); + } + + if ((posX != 0 || posY != 0) && (lastMouseX != newX || lastMouseY != newY)) { + lastMouseX = newX; + lastMouseY = newY; + + pointerX = newX - offsetX; + pointerY = newY - offsetY; + } } + if (touchScreenMode == "Joystick" || touchScreenMode == "Auto") { + const float speedX = (emulator->width / 60.0); + const float speedY = (emulator->height / 2 / 60.0); + + const float moveX = getAxisState(RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X); + const float moveY = getAxisState(RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y); + + touchScreen |= getButtonState(RETRO_DEVICE_ID_JOYPAD_R3); + + if (moveX != 0 || moveY != 0) { + pointerX += static_cast((moveX / 32767) * speedX); + pointerY += static_cast((moveY / 32767) * speedY); + } + } + + if (cursorTimeout && (pointerX != touchX || pointerY != touchY)) { + cursorVisible = true; + cursorMovedAt = std::chrono::steady_clock::now(); + } + + touchX = std::clamp(pointerX, 0, (int)(emulator->width - (offsetX * 2))); + touchY = std::clamp(pointerY, 0, (int)(emulator->height - offsetY)); + if (touchScreen) { - u16 x = static_cast(newX - offsetX); - u16 y = static_cast(newY - offsetY); + u16 x = static_cast(touchX); + u16 y = static_cast(touchY); hid.setTouchScreenPress(x, y); screenTouched = true; @@ -382,6 +548,13 @@ void retro_run() { hid.updateInputs(emulator->getTicks()); emulator->runFrame(); + if (renderTouchScreen && cursorVisible) { + cursorRenderer->draw(touchX, touchY); + } + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBlitFramebuffer(0, 0, 400, 480, 0, 0, 400, 480, GL_COLOR_BUFFER_BIT, GL_LINEAR); + videoCallback(RETRO_HW_FRAME_BUFFER_VALID, emulator->width, emulator->height, 0); }