touch: Improve accuracy of SceTouch functions.

This commit is contained in:
Macdu 2022-06-11 20:06:54 +02:00
parent 8fb76acd45
commit 647e254265
9 changed files with 151 additions and 79 deletions

View File

@ -8,5 +8,5 @@ add_library(
)
target_include_directories(display PUBLIC include)
target_link_libraries(display PUBLIC mem threads kernel util)
target_link_libraries(display PUBLIC host)
target_link_libraries(display PRIVATE)

View File

@ -18,10 +18,12 @@
#pragma once
#include <cstdint>
#include <kernel/callback.h>
#include <util/types.h>
struct DisplayState;
struct KernelState;
struct HostState;
void start_sync_thread(DisplayState &display, KernelState &kernel);
void wait_vblank(DisplayState &display, KernelState &kernel, const ThreadStatePtr &wait_thread, const int count, const bool is_cb);
void start_sync_thread(HostState &host);
void wait_vblank(DisplayState &display, KernelState &kernel, const ThreadStatePtr &wait_thread, const uint64_t target_vcount, const bool is_cb);

View File

@ -36,7 +36,7 @@ typedef std::shared_ptr<ThreadState> ThreadStatePtr;
struct DisplayStateVBlankWaitInfo {
ThreadStatePtr target_thread;
std::int32_t vsync_left;
uint64_t target_vcount;
bool is_cb;
};

View File

@ -15,10 +15,10 @@
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <display/state.h>
#include <kernel/state.h>
#include <host/state.h>
#include <chrono>
#include <touch/functions.h>
#include <util/find.h>
// Code heavily influenced by PPSSSPP's SceDisplay.cpp
@ -26,7 +26,9 @@
static constexpr int TARGET_FPS = 60;
static constexpr int64_t TARGET_MICRO_PER_FRAME = 1000000LL / TARGET_FPS;
static void vblank_sync_thread(DisplayState &display, KernelState &kernel) {
static void vblank_sync_thread(HostState &host) {
DisplayState &display = host.display;
while (!display.abort.load()) {
{
const std::lock_guard<std::mutex> guard(display.mutex);
@ -41,13 +43,16 @@ static void vblank_sync_thread(DisplayState &display, KernelState &kernel) {
}
}
// maybe we should also use a mutex for this part, but it shouldn't be an issue
touch_vsync_update(host);
// Notify Vblank callback in each VBLANK start
for (auto &cb : display.vblank_callbacks)
cb.second->event_notify(cb.second->get_notifier_id());
for (std::size_t i = 0; i < display.vblank_wait_infos.size();) {
auto &vblank_wait_info = display.vblank_wait_infos[i];
if (--vblank_wait_info.vsync_left == 0) {
if (vblank_wait_info.target_vcount <= display.vblank_count) {
ThreadStatePtr target_wait = vblank_wait_info.target_thread;
target_wait->update_status(ThreadStatus::run);
@ -57,7 +62,7 @@ static void vblank_sync_thread(DisplayState &display, KernelState &kernel) {
CallbackPtr &cb = callback.second;
if (cb->get_owner_thread_id() == target_wait->id) {
std::string name = cb->get_name();
cb->execute(kernel, [name]() {
cb->execute(host.kernel, [name]() {
});
}
}
@ -75,11 +80,11 @@ static void vblank_sync_thread(DisplayState &display, KernelState &kernel) {
}
}
void start_sync_thread(DisplayState &display, KernelState &kernel) {
display.vblank_thread = std::make_unique<std::thread>(vblank_sync_thread, std::ref(display), std::ref(kernel));
void start_sync_thread(HostState &host) {
host.display.vblank_thread = std::make_unique<std::thread>(vblank_sync_thread, std::ref(host));
}
void wait_vblank(DisplayState &display, KernelState &kernel, const ThreadStatePtr &wait_thread, int count, const bool is_cb) {
void wait_vblank(DisplayState &display, KernelState &kernel, const ThreadStatePtr &wait_thread, const uint64_t target_vcount, const bool is_cb) {
if (!wait_thread) {
return;
}
@ -89,8 +94,11 @@ void wait_vblank(DisplayState &display, KernelState &kernel, const ThreadStatePt
{
const std::lock_guard<std::mutex> guard(display.mutex);
if (target_vcount <= display.vblank_count)
return;
wait_thread->update_status(ThreadStatus::wait);
display.vblank_wait_infos.push_back({ wait_thread, count, is_cb });
display.vblank_wait_infos.push_back({ wait_thread, target_vcount, is_cb });
}
wait_thread->status_cond.wait(thread_lock, [=]() { return wait_thread->status == ThreadStatus::run; });

View File

@ -774,7 +774,7 @@ ExitCode run_app(HostState &host, Ptr<const void> &entry_point) {
return RunThreadFailed;
}
start_sync_thread(host.display, host.kernel);
start_sync_thread(host);
if (host.cfg.boot_apps_full_screen && !host.display.fullscreen.load())
switch_full_screen(host);

View File

@ -29,22 +29,20 @@
static int display_wait(HostState &host, SceUID thread_id, int vcount, const bool is_since_setbuf, const bool is_cb) {
const auto &thread = host.kernel.get_thread(thread_id);
// this part should not need a mutex
const uint64_t vblank_count = host.display.vblank_count.load();
uint64_t target_vcount;
if (is_since_setbuf) {
vcount = host.display.last_setframe_vblank_count + vcount - vblank_count;
target_vcount = host.display.last_setframe_vblank_count + vcount;
} else {
// the wait is considered starting from the last time the thread resumed
// from a vblank wait (sceDisplayWait...) and not from the time this function was called
// but we still need to wait at least for one vblank
const uint64_t next_vsync = vblank_count + 1;
const uint64_t next_vsync = host.display.vblank_count + 1;
const uint64_t min_vsync = thread->last_vblank_waited + vcount;
thread->last_vblank_waited = std::max(next_vsync, min_vsync);
vcount = static_cast<int>(thread->last_vblank_waited - vblank_count);
target_vcount = thread->last_vblank_waited;
}
if (vcount > 0)
wait_vblank(host.display, host.kernel, thread, vcount, is_cb);
wait_vblank(host.display, host.kernel, thread, target_vcount, is_cb);
if (host.display.abort.load())
return SCE_DISPLAY_ERROR_NO_PIXEL_DATA;

View File

@ -130,7 +130,7 @@ EXPORT(int, sceTouchPeek, SceUInt32 port, SceTouchData *pData, SceUInt32 nBufs)
return RET_ERROR(SCE_TOUCH_ERROR_INVALID_ARG);
}
return peek_touch(host, port, pData, nBufs);
return touch_get(thread_id, host, port, pData, nBufs, true);
}
EXPORT(int, sceTouchPeek2, SceUInt32 port, SceTouchData *pData, SceUInt32 nBufs) {
@ -141,7 +141,7 @@ EXPORT(int, sceTouchPeek2, SceUInt32 port, SceTouchData *pData, SceUInt32 nBufs)
return RET_ERROR(SCE_TOUCH_ERROR_INVALID_ARG);
}
return peek_touch(host, port, pData, nBufs);
return touch_get(thread_id, host, port, pData, nBufs, true);
}
EXPORT(int, sceTouchPeekRegion) {
@ -159,8 +159,7 @@ EXPORT(int, sceTouchRead, SceUInt32 port, SceTouchData *pData, SceUInt32 nBufs)
if (pData == nullptr) {
return RET_ERROR(SCE_TOUCH_ERROR_INVALID_ARG);
}
return peek_touch(host, port, pData, nBufs);
return touch_get(thread_id, host, port, pData, nBufs, false);
}
EXPORT(int, sceTouchRead2, SceUInt32 port, SceTouchData *pData, SceUInt32 nBufs) {
@ -171,7 +170,7 @@ EXPORT(int, sceTouchRead2, SceUInt32 port, SceTouchData *pData, SceUInt32 nBufs)
return RET_ERROR(SCE_TOUCH_ERROR_INVALID_ARG);
}
return peek_touch(host, port, pData, nBufs);
return touch_get(thread_id, host, port, pData, nBufs, false);
}
EXPORT(int, sceTouchReadRegion) {

View File

@ -3,6 +3,7 @@
#include <host/state.h>
#include <touch/touch.h>
void touch_vsync_update(const HostState &host);
int handle_touch_event(SDL_TouchFingerEvent &finger);
int toggle_touchscreen();
int peek_touch(const HostState &host, const SceUInt32 &port, SceTouchData *pData, SceUInt32 count);
int touch_get(const SceUID thread_id, HostState &host, const SceUInt32 &port, SceTouchData *pData, SceUInt32 count, bool is_peek);

View File

@ -15,6 +15,8 @@
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <display/functions.h>
#include <host/state.h>
#include <touch/functions.h>
#include <touch/touch.h>
@ -22,11 +24,19 @@
#include <cstring>
constexpr int MAX_TOUCH_BUFFER_SAVED = 64;
static SceTouchData touch_buffers[MAX_TOUCH_BUFFER_SAVED][2];
int touch_buffer_idx = 0;
static uint64_t timestamp;
static SDL_TouchFingerEvent finger_buffer[8];
static uint8_t finger_count = 0;
static long int finger_id_ref;
static auto touchscreen_port = SCE_TOUCH_PORT_FRONT;
static bool is_touched[2] = { false, false };
static int curr_touch_id[2] = { 0, 0 };
static uint64_t last_vcount[2] = { 0, 0 };
static SceTouchData recover_touch_events() {
SceTouchData touch_data;
@ -49,6 +59,71 @@ static SceTouchData recover_touch_events() {
return touch_data;
}
void touch_vsync_update(const HostState &host) {
SceIVector2 touch_pos_window = { 0, 0 };
const uint32_t buttons = SDL_GetMouseState(&touch_pos_window.x, &touch_pos_window.y);
std::chrono::time_point<std::chrono::steady_clock> ts = std::chrono::steady_clock::now();
uint64_t timestamp = std::chrono::duration_cast<std::chrono::microseconds>(ts.time_since_epoch()).count();
for (int port = 0; port < 2; port++) {
// do it for both the front and the back touchscreen
SceTouchData *data = &touch_buffers[(touch_buffer_idx + 1) % MAX_TOUCH_BUFFER_SAVED][port];
memset(data, 0, sizeof(SceTouchData));
data->timeStamp = timestamp;
const uint32_t mask = (port == SCE_TOUCH_PORT_BACK) ? SDL_BUTTON_RMASK : SDL_BUTTON_LMASK;
if ((buttons & mask) && host.renderer_focused) {
if (!is_touched[port]) {
curr_touch_id[port]++;
// the touch id must be between 0 and 127
curr_touch_id[port] %= 128;
is_touched[port] = true;
}
SceIVector2 SDL_window_size = { 0, 0 };
SDL_Window *const window = SDL_GetMouseFocus();
SDL_GetWindowSize(window, &SDL_window_size.x, &SDL_window_size.y);
SceFVector2 scale = { 1, 1 };
if ((SDL_window_size.x > 0) && (SDL_window_size.y > 0)) {
scale.x = static_cast<float>(host.drawable_size.x) / SDL_window_size.x;
scale.y = static_cast<float>(host.drawable_size.y) / SDL_window_size.y;
}
const SceFVector2 touch_pos_drawable = {
touch_pos_window.x * scale.x,
touch_pos_window.y * scale.y
};
const SceFVector2 touch_pos_viewport = {
(touch_pos_drawable.x - host.viewport_pos.x) / host.viewport_size.x,
(touch_pos_drawable.y - host.viewport_pos.y) / host.viewport_size.y
};
if ((touch_pos_viewport.x >= 0) && (touch_pos_viewport.y >= 0) && (touch_pos_viewport.x < 1) && (touch_pos_viewport.y < 1)) {
data->report[data->reportNum].x = static_cast<uint16_t>(touch_pos_viewport.x * 1920);
if (port == SCE_TOUCH_PORT_FRONT) {
data->report[data->reportNum].y = static_cast<uint16_t>(touch_pos_viewport.y * 1088);
} else {
data->report[data->reportNum].y = static_cast<uint16_t>(108 + touch_pos_viewport.y * 781);
}
data->report[data->reportNum].id = curr_touch_id[port];
++data->reportNum;
}
if (!host.touch.touch_mode[port]) {
data->reportNum = 0;
}
} else {
is_touched[port] = false;
}
}
touch_buffer_idx++;
touch_buffer_idx %= MAX_TOUCH_BUFFER_SAVED;
}
int handle_touch_event(SDL_TouchFingerEvent &finger) {
switch (finger.type) {
case SDL_FINGERDOWN: {
@ -105,63 +180,52 @@ int toggle_touchscreen() {
return 0;
}
int peek_touch(const HostState &host, const SceUInt32 &port, SceTouchData *pData, SceUInt32 count) {
memset(pData, 0, sizeof(SceTouchData));
int touch_get(const SceUID thread_id, HostState &host, const SceUInt32 &port, SceTouchData *pData, SceUInt32 count, bool is_peek) {
memset(pData, 0, sizeof(SceTouchData) * count);
const int port_idx = static_cast<int>(port);
std::chrono::time_point<std::chrono::steady_clock> ts = std::chrono::steady_clock::now();
pData->timeStamp = std::chrono::duration_cast<std::chrono::microseconds>(ts.time_since_epoch()).count();
if (host.common_dialog.status == SCE_COMMON_DIALOG_STATUS_RUNNING) {
// return one empty touch data
return 1;
}
SceIVector2 touch_pos_window = { 0, 0 };
const uint32_t buttons = SDL_GetMouseState(&touch_pos_window.x, &touch_pos_window.y);
const uint32_t mask = (port == SCE_TOUCH_PORT_BACK) ? SDL_BUTTON_RMASK : SDL_BUTTON_LMASK;
if ((buttons & mask) && host.renderer_focused) {
SceIVector2 SDL_window_size = { 0, 0 };
SDL_Window *const window = SDL_GetMouseFocus();
SDL_GetWindowSize(window, &SDL_window_size.x, &SDL_window_size.y);
SceFVector2 scale = { 1, 1 };
if ((SDL_window_size.x > 0) && (SDL_window_size.y > 0)) {
scale.x = static_cast<float>(host.drawable_size.x) / SDL_window_size.x;
scale.y = static_cast<float>(host.drawable_size.y) / SDL_window_size.y;
}
const SceFVector2 touch_pos_drawable = {
touch_pos_window.x * scale.x,
touch_pos_window.y * scale.y
};
const SceFVector2 touch_pos_viewport = {
(touch_pos_drawable.x - host.viewport_pos.x) / host.viewport_size.x,
(touch_pos_drawable.y - host.viewport_pos.y) / host.viewport_size.y
};
if ((touch_pos_viewport.x >= 0) && (touch_pos_viewport.y >= 0) && (touch_pos_viewport.x < 1) && (touch_pos_viewport.y < 1)) {
pData->report[pData->reportNum].x = static_cast<uint16_t>(touch_pos_viewport.x * 1920);
if (port == SCE_TOUCH_PORT_FRONT) {
pData->report[pData->reportNum].y = static_cast<uint16_t>(touch_pos_viewport.y * 1088);
int nb_returned_data = 1;
if (is_peek) {
if (host.touch.touch_mode[port])
nb_returned_data = count;
else
nb_returned_data = 0;
} else {
pData->report[pData->reportNum].y = static_cast<uint16_t>(108 + touch_pos_viewport.y * 781);
uint64_t vblank_count = host.display.vblank_count;
if (vblank_count <= last_vcount[port_idx]) {
// sceTouchRead is blocking, wait for the next vsync for the buffer to be updated
auto thread = host.kernel.get_thread(thread_id);
wait_vblank(host.display, host.kernel, thread, last_vcount[port_idx] + 1, false);
vblank_count = host.display.vblank_count;
}
pData->report[pData->reportNum].id = 1;
++pData->reportNum;
nb_returned_data = std::min<int>(count, host.display.vblank_count - last_vcount[port_idx]);
last_vcount[port_idx] = host.display.vblank_count;
}
if (!host.touch.touch_mode[port]) {
pData->reportNum = 0;
}
} else if (registered_touch() == true && port == touchscreen_port) {
if (registered_touch() && port == touchscreen_port) {
// less accurate implementation, but which is able to take a real touchscreen as the input
pData[0] = recover_touch_events();
for (uint32_t i = 0; i < nb_returned_data; i++) {
memcpy(&pData[i], &pData[0], sizeof(SceTouchData));
}
} else {
int corr_buffer_idx;
if (is_peek) {
corr_buffer_idx = touch_buffer_idx;
} else {
// give the oldest buffer first
corr_buffer_idx = (touch_buffer_idx - nb_returned_data + 1 + MAX_TOUCH_BUFFER_SAVED) % MAX_TOUCH_BUFFER_SAVED;
}
for (uint32_t i = 0; i < nb_returned_data; i++) {
memcpy(&pData[i], &touch_buffers[corr_buffer_idx][port_idx], sizeof(SceTouchData));
// if peek, repeat the last buffer
if (!is_peek) {
corr_buffer_idx++;
corr_buffer_idx %= MAX_TOUCH_BUFFER_SAVED;
}
}
}
// the other buffers are used when a call to ReadTouch is late
for (uint32_t i = 1; i < count; i++) {
memset(&pData[i], 0, sizeof(SceTouchData));
}
return 1;
return nb_returned_data;
}