mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-13 05:58:17 -04:00
Touch pointer rendering is now always enabled, but unless a controller is being used to move the touchscreen cursor, it will be hidden due to the timeout which is also enabled by default.
443 lines
17 KiB
C++
443 lines
17 KiB
C++
// Copyright Citra Emulator Project / Azahar Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <memory>
|
|
|
|
#include "citra_libretro/core_settings.h"
|
|
#include "citra_libretro/environment.h"
|
|
#include "citra_libretro/input/mouse_tracker.h"
|
|
#include "common/settings.h"
|
|
#include "core/frontend/framebuffer_layout.h"
|
|
|
|
#ifdef ENABLE_OPENGL
|
|
#include <glad/glad.h>
|
|
#include "video_core/shader/generator/glsl_shader_gen.h"
|
|
#endif
|
|
|
|
#ifdef ENABLE_VULKAN
|
|
#include "core/core.h"
|
|
#include "video_core/gpu.h"
|
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
|
#endif
|
|
|
|
namespace LibRetro {
|
|
|
|
namespace Input {
|
|
|
|
/// Shared cursor coordinate calculation
|
|
struct CursorCoordinates {
|
|
float centerX, centerY;
|
|
float renderWidth, renderHeight;
|
|
float boundingLeft, boundingTop, boundingRight, boundingBottom;
|
|
float verticalLeft, verticalRight, verticalTop, verticalBottom;
|
|
float horizontalLeft, horizontalRight, horizontalTop, horizontalBottom;
|
|
|
|
CursorCoordinates(int bufferWidth, int bufferHeight, float projectedX, float projectedY,
|
|
float renderRatio, const Layout::FramebufferLayout& layout) {
|
|
// Convert to normalized device coordinates
|
|
centerX = (projectedX / bufferWidth) * 2 - 1;
|
|
centerY = (projectedY / bufferHeight) * 2 - 1;
|
|
|
|
renderWidth = renderRatio / bufferWidth;
|
|
renderHeight = renderRatio / bufferHeight;
|
|
|
|
boundingLeft = (layout.bottom_screen.left / (float)bufferWidth) * 2 - 1;
|
|
boundingTop = (layout.bottom_screen.top / (float)bufferHeight) * 2 - 1;
|
|
boundingRight = (layout.bottom_screen.right / (float)bufferWidth) * 2 - 1;
|
|
boundingBottom = (layout.bottom_screen.bottom / (float)bufferHeight) * 2 - 1;
|
|
|
|
// Calculate cursor dimensions
|
|
verticalLeft = std::fmax(centerX - renderWidth / 5, boundingLeft);
|
|
verticalRight = std::fmin(centerX + renderWidth / 5, boundingRight);
|
|
verticalTop = -std::fmax(centerY - renderHeight, boundingTop);
|
|
verticalBottom = -std::fmin(centerY + renderHeight, boundingBottom);
|
|
|
|
horizontalLeft = std::fmax(centerX - renderWidth, boundingLeft);
|
|
horizontalRight = std::fmin(centerX + renderWidth, boundingRight);
|
|
horizontalTop = -std::fmax(centerY - renderHeight / 5, boundingTop);
|
|
horizontalBottom = -std::fmin(centerY + renderHeight / 5, boundingBottom);
|
|
}
|
|
};
|
|
|
|
/// Helper function to check if coordinates are within the touchscreen area
|
|
/// (uses the same logic as EmuWindow::IsWithinTouchscreen)
|
|
static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, unsigned framebuffer_x,
|
|
unsigned framebuffer_y) {
|
|
// Note: LibRetro doesn't support SeparateWindows, so we can skip that check
|
|
|
|
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
|
|
|
|
if (render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
|
render_3d_mode == Settings::StereoRenderOption::SideBySideFull) {
|
|
return (framebuffer_y >= layout.bottom_screen.top &&
|
|
framebuffer_y < layout.bottom_screen.bottom &&
|
|
((framebuffer_x >= layout.bottom_screen.left / 2 &&
|
|
framebuffer_x < layout.bottom_screen.right / 2) ||
|
|
(framebuffer_x >= (layout.bottom_screen.left / 2) + (layout.width / 2) &&
|
|
framebuffer_x < (layout.bottom_screen.right / 2) + (layout.width / 2))));
|
|
} else if (render_3d_mode == Settings::StereoRenderOption::CardboardVR) {
|
|
return (framebuffer_y >= layout.bottom_screen.top &&
|
|
framebuffer_y < layout.bottom_screen.bottom &&
|
|
((framebuffer_x >= layout.bottom_screen.left &&
|
|
framebuffer_x < layout.bottom_screen.right) ||
|
|
(framebuffer_x >= layout.cardboard.bottom_screen_right_eye + (layout.width / 2) &&
|
|
framebuffer_x < layout.cardboard.bottom_screen_right_eye +
|
|
layout.bottom_screen.GetWidth() + (layout.width / 2))));
|
|
} else {
|
|
return (framebuffer_y >= layout.bottom_screen.top &&
|
|
framebuffer_y < layout.bottom_screen.bottom &&
|
|
framebuffer_x >= layout.bottom_screen.left &&
|
|
framebuffer_x < layout.bottom_screen.right);
|
|
}
|
|
}
|
|
|
|
MouseTracker::MouseTracker() {
|
|
// Create renderer-specific cursor renderer based on current graphics API
|
|
cursor_renderer = nullptr;
|
|
switch (Settings::values.graphics_api.GetValue()) {
|
|
case Settings::GraphicsAPI::OpenGL:
|
|
#ifdef ENABLE_OPENGL
|
|
cursor_renderer = std::make_unique<OpenGLCursorRenderer>();
|
|
#endif
|
|
break;
|
|
case Settings::GraphicsAPI::Vulkan:
|
|
#ifdef ENABLE_VULKAN
|
|
cursor_renderer = std::make_unique<VulkanCursorRenderer>();
|
|
#endif
|
|
break;
|
|
case Settings::GraphicsAPI::Software:
|
|
cursor_renderer = std::make_unique<SoftwareCursorRenderer>();
|
|
break;
|
|
}
|
|
|
|
last_moved = std::chrono::steady_clock::time_point::min();
|
|
}
|
|
|
|
MouseTracker::~MouseTracker() = default;
|
|
|
|
void MouseTracker::OnMouseMove(int deltaX, int deltaY) {
|
|
x += deltaX;
|
|
y += deltaY;
|
|
}
|
|
|
|
void MouseTracker::Restrict(int minX, int minY, int maxX, int maxY) {
|
|
x = std::clamp(x, minX, maxX);
|
|
y = std::clamp(y, minY, maxY);
|
|
}
|
|
|
|
void MouseTracker::Update(int bufferWidth, int bufferHeight,
|
|
const Layout::FramebufferLayout& layout) {
|
|
bool state = false;
|
|
bool wasMoved = false;
|
|
|
|
if (LibRetro::settings.enable_mouse_touchscreen) {
|
|
// Check mouse input
|
|
state |= LibRetro::CheckInput(0, RETRO_DEVICE_MOUSE, 0, RETRO_DEVICE_ID_MOUSE_LEFT);
|
|
|
|
// Read in and convert pointer values to absolute values on the canvas
|
|
auto pointerX = LibRetro::CheckInput(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_X);
|
|
auto pointerY = LibRetro::CheckInput(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_Y);
|
|
auto newX = static_cast<int>((pointerX + 0x7fff) / (float)(0x7fff * 2) * bufferWidth);
|
|
auto newY = static_cast<int>((pointerY + 0x7fff) / (float)(0x7fff * 2) * bufferHeight);
|
|
|
|
// Use mouse pointer movement
|
|
if ((pointerX != 0 || pointerY != 0) && (newX != lastMouseX || newY != lastMouseY)) {
|
|
lastMouseX = newX;
|
|
lastMouseY = newY;
|
|
|
|
// Use layout system to validate and map coordinates
|
|
if (IsWithinTouchscreen(layout, newX, newY)) {
|
|
x = std::clamp(newX, static_cast<int>(layout.bottom_screen.left),
|
|
static_cast<int>(layout.bottom_screen.right)) -
|
|
layout.bottom_screen.left;
|
|
y = std::clamp(newY, static_cast<int>(layout.bottom_screen.top),
|
|
static_cast<int>(layout.bottom_screen.bottom)) -
|
|
layout.bottom_screen.top;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (LibRetro::settings.enable_touch_touchscreen) {
|
|
// Check touchscreen input
|
|
state |= LibRetro::CheckInput(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_PRESSED);
|
|
|
|
// Read in and convert pointer values to absolute values on the canvas
|
|
auto pointerX = LibRetro::CheckInput(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_X);
|
|
auto pointerY = LibRetro::CheckInput(0, RETRO_DEVICE_POINTER, 0, RETRO_DEVICE_ID_POINTER_Y);
|
|
auto newX = static_cast<int>((pointerX + 0x7fff) / (float)(0x7fff * 2) * bufferWidth);
|
|
auto newY = static_cast<int>((pointerY + 0x7fff) / (float)(0x7fff * 2) * bufferHeight);
|
|
|
|
// Use mouse pointer movement
|
|
if ((pointerX != 0 || pointerY != 0) && (newX != lastMouseX || newY != lastMouseY)) {
|
|
lastMouseX = newX;
|
|
lastMouseY = newY;
|
|
|
|
// Use layout system to validate and map coordinates
|
|
if (IsWithinTouchscreen(layout, newX, newY)) {
|
|
x = std::clamp(newX, static_cast<int>(layout.bottom_screen.left),
|
|
static_cast<int>(layout.bottom_screen.right)) -
|
|
layout.bottom_screen.left;
|
|
y = std::clamp(newY, static_cast<int>(layout.bottom_screen.top),
|
|
static_cast<int>(layout.bottom_screen.bottom)) -
|
|
layout.bottom_screen.top;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (LibRetro::settings.analog_function != LibRetro::CStickFunction::CStick) {
|
|
// Check right analog input
|
|
state |= LibRetro::CheckInput(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3);
|
|
|
|
// TODO: Provide config option for ratios here
|
|
auto widthSpeed = (layout.bottom_screen.GetWidth() / 20.0);
|
|
auto heightSpeed = (layout.bottom_screen.GetHeight() / 20.0);
|
|
|
|
// Use controller movement
|
|
float controllerX =
|
|
((float)LibRetro::CheckInput(0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT,
|
|
RETRO_DEVICE_ID_ANALOG_X) /
|
|
INT16_MAX);
|
|
float controllerY =
|
|
((float)LibRetro::CheckInput(0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT,
|
|
RETRO_DEVICE_ID_ANALOG_Y) /
|
|
INT16_MAX);
|
|
|
|
// Deadzone the controller inputs
|
|
float magnitudeX = std::abs(controllerX);
|
|
float magnitudeY = std::abs(controllerY);
|
|
|
|
if (magnitudeX < LibRetro::settings.analog_deadzone) {
|
|
controllerX = 0;
|
|
}
|
|
if (magnitudeY < LibRetro::settings.analog_deadzone) {
|
|
controllerY = 0;
|
|
}
|
|
|
|
if (controllerX != 0 || controllerY != 0) {
|
|
wasMoved = true;
|
|
}
|
|
|
|
OnMouseMove(static_cast<int>(controllerX * widthSpeed),
|
|
static_cast<int>(controllerY * heightSpeed));
|
|
}
|
|
|
|
Restrict(0, 0, layout.bottom_screen.GetWidth(), layout.bottom_screen.GetHeight());
|
|
|
|
// Store as bottom-screen-local pixel coordinates
|
|
projectedX = static_cast<float>(x);
|
|
projectedY = static_cast<float>(y);
|
|
|
|
isPressed = state;
|
|
|
|
if (wasMoved) {
|
|
last_moved = std::chrono::steady_clock::now();
|
|
}
|
|
|
|
this->framebuffer_layout = layout;
|
|
}
|
|
|
|
void MouseTracker::Render(int bufferWidth, int bufferHeight, void* framebuffer_data) {
|
|
if (GetCursorInfo().visible == false) {
|
|
return;
|
|
}
|
|
|
|
// Delegate to renderer-specific implementation.
|
|
// Convert from bottom-screen-local to layout-absolute for the legacy renderers.
|
|
if (cursor_renderer) {
|
|
const float abs_x = framebuffer_layout.bottom_screen.left + projectedX;
|
|
const float abs_y = framebuffer_layout.bottom_screen.top + projectedY;
|
|
const float ratio =
|
|
static_cast<float>(framebuffer_layout.bottom_screen.GetHeight()) / 30.0f;
|
|
cursor_renderer->Render(bufferWidth, bufferHeight, abs_x, abs_y, ratio, framebuffer_layout,
|
|
framebuffer_data);
|
|
}
|
|
}
|
|
|
|
Frontend::EmuWindow::CursorInfo MouseTracker::GetCursorInfo() {
|
|
bool visible = true;
|
|
auto current = std::chrono::steady_clock::now();
|
|
uint64_t since_last_moved =
|
|
std::chrono::duration_cast<std::chrono::seconds>(current - last_moved).count();
|
|
constexpr auto timeout_secs = 4; // TODO: Make this configurable maybe? -OS
|
|
if (LibRetro::settings.enable_touch_pointer_timeout && since_last_moved >= timeout_secs) {
|
|
visible = false;
|
|
}
|
|
return {visible, projectedX, projectedY};
|
|
}
|
|
|
|
#ifdef ENABLE_OPENGL
|
|
// OpenGL-specific cursor renderer implementation
|
|
OpenGLCursorRenderer::OpenGLCursorRenderer() {
|
|
// Could potentially also use Citra's built-in shaders, if they can be
|
|
// wrangled to cooperate.
|
|
|
|
std::string vertex;
|
|
if (Settings::values.use_gles) {
|
|
vertex += fragment_shader_precision_OES;
|
|
}
|
|
|
|
vertex += R"(
|
|
in vec2 position;
|
|
|
|
void main()
|
|
{
|
|
gl_Position = vec4(position, 0.0, 1.0);
|
|
}
|
|
)";
|
|
|
|
std::string fragment;
|
|
if (Settings::values.use_gles) {
|
|
fragment += fragment_shader_precision_OES;
|
|
}
|
|
fragment += R"(
|
|
out vec4 color;
|
|
|
|
void main()
|
|
{
|
|
color = vec4(1.0, 1.0, 1.0, 1.0);
|
|
}
|
|
)";
|
|
|
|
vao.Create();
|
|
vbo.Create();
|
|
|
|
glBindVertexArray(vao.handle);
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo.handle);
|
|
|
|
shader.Create(vertex.c_str(), fragment.c_str());
|
|
|
|
auto positionVariable = (GLuint)glGetAttribLocation(shader.handle, "position");
|
|
glEnableVertexAttribArray(positionVariable);
|
|
|
|
glVertexAttribPointer(positionVariable, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
|
}
|
|
|
|
OpenGLCursorRenderer::~OpenGLCursorRenderer() {
|
|
shader.Release();
|
|
vao.Release();
|
|
vbo.Release();
|
|
}
|
|
|
|
void OpenGLCursorRenderer::Render(int bufferWidth, int bufferHeight, float projectedX,
|
|
float projectedY, float renderRatio,
|
|
const Layout::FramebufferLayout& layout, void* framebuffer_data) {
|
|
// Use shared coordinate calculation
|
|
CursorCoordinates coords(bufferWidth, bufferHeight, projectedX, projectedY, renderRatio,
|
|
layout);
|
|
|
|
glUseProgram(shader.handle);
|
|
|
|
glBindVertexArray(vao.handle);
|
|
|
|
// clang-format off
|
|
GLfloat cursor[] = {
|
|
// | in the cursor
|
|
coords.verticalLeft, coords.verticalTop,
|
|
coords.verticalRight, coords.verticalTop,
|
|
coords.verticalRight, coords.verticalBottom,
|
|
|
|
coords.verticalLeft, coords.verticalTop,
|
|
coords.verticalRight, coords.verticalBottom,
|
|
coords.verticalLeft, coords.verticalBottom,
|
|
|
|
// - in the cursor
|
|
coords.horizontalLeft, coords.horizontalTop,
|
|
coords.horizontalRight, coords.horizontalTop,
|
|
coords.horizontalRight, coords.horizontalBottom,
|
|
|
|
coords.horizontalLeft, coords.horizontalTop,
|
|
coords.horizontalRight, coords.horizontalBottom,
|
|
coords.horizontalLeft, coords.horizontalBottom
|
|
};
|
|
// clang-format on
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo.handle);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(cursor), cursor, GL_STATIC_DRAW);
|
|
|
|
glDrawArrays(GL_TRIANGLES, 0, 12);
|
|
|
|
glBindVertexArray(0);
|
|
glUseProgram(0);
|
|
glDisable(GL_BLEND);
|
|
}
|
|
#endif
|
|
|
|
#ifdef ENABLE_VULKAN
|
|
// Vulkan cursor is drawn by RendererVulkan::DrawCursor() inside the render pass.
|
|
// This class exists only to satisfy the CursorRenderer interface.
|
|
VulkanCursorRenderer::VulkanCursorRenderer() = default;
|
|
VulkanCursorRenderer::~VulkanCursorRenderer() = default;
|
|
void VulkanCursorRenderer::Render(int, int, float, float, float, const Layout::FramebufferLayout&,
|
|
void*) {}
|
|
#endif
|
|
|
|
// Software-specific cursor renderer implementation
|
|
SoftwareCursorRenderer::SoftwareCursorRenderer() {
|
|
// Software renderer initialization
|
|
}
|
|
|
|
SoftwareCursorRenderer::~SoftwareCursorRenderer() = default;
|
|
|
|
void SoftwareCursorRenderer::Render(int bufferWidth, int bufferHeight, float projectedX,
|
|
float projectedY, float renderRatio,
|
|
const Layout::FramebufferLayout& layout,
|
|
void* framebuffer_data) {
|
|
if (!framebuffer_data) {
|
|
return; // No framebuffer data available
|
|
}
|
|
|
|
// Convert coordinates to screen space
|
|
int centerX = static_cast<int>(projectedX);
|
|
int centerY = static_cast<int>(projectedY);
|
|
int radius = static_cast<int>(renderRatio);
|
|
|
|
// Calculate cursor dimensions within bounds
|
|
int verticalLeft = std::max(centerX - radius / 5, static_cast<int>(layout.bottom_screen.left));
|
|
int verticalRight =
|
|
std::min(centerX + radius / 5, static_cast<int>(layout.bottom_screen.right));
|
|
int verticalTop = std::max(centerY - radius, static_cast<int>(layout.bottom_screen.top));
|
|
int verticalBottom = std::min(centerY + radius, static_cast<int>(layout.bottom_screen.bottom));
|
|
|
|
int horizontalLeft = std::max(centerX - radius, static_cast<int>(layout.bottom_screen.left));
|
|
int horizontalRight = std::min(centerX + radius, static_cast<int>(layout.bottom_screen.right));
|
|
int horizontalTop = std::max(centerY - radius / 5, static_cast<int>(layout.bottom_screen.top));
|
|
int horizontalBottom =
|
|
std::min(centerY + radius / 5, static_cast<int>(layout.bottom_screen.bottom));
|
|
|
|
// Draw cursor directly to framebuffer (assuming RGBA8888 format)
|
|
uint32_t* pixels = static_cast<uint32_t*>(framebuffer_data);
|
|
const uint32_t cursorColor = 0xFFFFFFFF; // White cursor
|
|
|
|
// Draw vertical line of cursor
|
|
for (int y = verticalTop; y < verticalBottom; ++y) {
|
|
for (int x = verticalLeft; x < verticalRight; ++x) {
|
|
if (x >= 0 && x < bufferWidth && y >= 0 && y < bufferHeight) {
|
|
int pixelIndex = y * bufferWidth + x;
|
|
// XOR blend for visibility on any background
|
|
pixels[pixelIndex] ^= cursorColor;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw horizontal line of cursor
|
|
for (int y = horizontalTop; y < horizontalBottom; ++y) {
|
|
for (int x = horizontalLeft; x < horizontalRight; ++x) {
|
|
if (x >= 0 && x < bufferWidth && y >= 0 && y < bufferHeight) {
|
|
int pixelIndex = y * bufferWidth + x;
|
|
// XOR blend for visibility on any background
|
|
pixels[pixelIndex] ^= cursorColor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace Input
|
|
|
|
} // namespace LibRetro
|