mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
libShake was used by one driver, sdl_dingux, for its vibration motor, and linked on webOS for nothing. What it did there is a few evdev calls: find the node that plays a periodic sine, upload a sine per motor, play and stop it, set the gain. input/common/linux_ff.h does those with no allocation (libShake scanned /dev/input with scandir and malloc'd a node per entry) and none of its bugs (an open that failed was taken as success). The uploads and plays now go out from the shared output writer, as udev's and XInput's rumble do, so a motor's driver never holds the frame; the frontend's thread notes the strengths, both in one word, and returns. A core asking the same strength each frame costs no call at all. On MIPS (-O2, mips32) that path is 15 to 19 instructions with no stack frame, where it was about 32 with a frame and four spills. Every SDL Dingux target gets the motor now, not only the three that built libShake; one without a motor finds none and has no rumble, as before. The output writer makes no syscall for a wake while one is still on its way, which every driver on it gains from. xinput_rumble_test says a call did not wait by the write not being done yet, not by a time a loaded runner can overrun. samples/input/dingux_rumble runs the real driver against a stand-in /dev/input and SDL, with threads and without.
244 lines
6.5 KiB
C
244 lines
6.5 KiB
C
/* RetroArch - A frontend for libretro.
|
|
*
|
|
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
|
|
#include "output_writer.h"
|
|
|
|
#if defined(HAVE_THREADS) && !defined(_XBOX) && !defined(__WINRT__)
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#endif
|
|
|
|
#include <retro_atomic.h>
|
|
#include <rthreads/rthreads.h>
|
|
|
|
struct input_output_writer
|
|
{
|
|
input_output_writer_cb cb;
|
|
input_output_writer_timeout_cb timeout_cb;
|
|
void *userdata;
|
|
sthread_t *thread;
|
|
retro_atomic_int_t quit;
|
|
/* a wake is on its way and the writer has yet to look: another
|
|
* needs no syscall */
|
|
retro_atomic_int_t pending;
|
|
#ifdef _WIN32
|
|
HANDLE wake;
|
|
#else
|
|
int wake[2];
|
|
#endif
|
|
};
|
|
|
|
#ifdef _WIN32
|
|
typedef HANDLE (WINAPI *output_writer_timer_new_t)(LPSECURITY_ATTRIBUTES,
|
|
LPCWSTR, DWORD, DWORD);
|
|
|
|
/* A wait of a few milliseconds is a whole timer tick, 15.6 ms, unless
|
|
* something raised the process's timer resolution: the timeout goes
|
|
* through a high resolution waitable timer where Windows has one
|
|
* (10 1803 on). The thread's own; NULL elsewhere. */
|
|
static HANDLE output_writer_timer_new(void)
|
|
{
|
|
HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
|
|
output_writer_timer_new_t timer_new = kernel32
|
|
? (output_writer_timer_new_t)GetProcAddress(kernel32,
|
|
"CreateWaitableTimerExW") : NULL;
|
|
/* CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS */
|
|
return timer_new ? timer_new(NULL, NULL, 0x00000002,
|
|
STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x3) : NULL;
|
|
}
|
|
#endif
|
|
|
|
static void input_output_writer_thread(void *data)
|
|
{
|
|
input_output_writer_t *writer = (input_output_writer_t*)data;
|
|
#ifdef _WIN32
|
|
HANDLE timer = writer->timeout_cb ? output_writer_timer_new() : NULL;
|
|
#endif
|
|
|
|
for (;;)
|
|
{
|
|
bool quit = false;
|
|
int timeout = writer->timeout_cb
|
|
? writer->timeout_cb(writer->userdata) : -1;
|
|
#ifdef _WIN32
|
|
LARGE_INTEGER due;
|
|
due.QuadPart = -(LONGLONG)timeout * 10000;
|
|
if (timeout > 0 && timer && SetWaitableTimer(timer, &due, 0,
|
|
NULL, NULL, FALSE))
|
|
{
|
|
HANDLE both[2];
|
|
both[0] = writer->wake;
|
|
both[1] = timer;
|
|
if (WaitForMultipleObjects(2, both, FALSE, INFINITE)
|
|
== WAIT_FAILED)
|
|
quit = true;
|
|
}
|
|
else if (WaitForSingleObject(writer->wake,
|
|
timeout < 0 ? INFINITE : (DWORD)timeout) == WAIT_FAILED)
|
|
quit = true;
|
|
#else
|
|
bool readable = true;
|
|
if (writer->timeout_cb)
|
|
{
|
|
struct pollfd pfd;
|
|
int ready;
|
|
pfd.fd = writer->wake[0];
|
|
pfd.events = POLLIN;
|
|
ready = poll(&pfd, 1, timeout);
|
|
if (ready < 0 && errno == EINTR)
|
|
continue;
|
|
if (ready < 0 || (ready > 0 && !(pfd.revents & POLLIN)))
|
|
quit = true;
|
|
readable = ready > 0 && !quit;
|
|
}
|
|
if (readable)
|
|
{
|
|
char buf[64];
|
|
ssize_t n = read(writer->wake[0], buf, sizeof(buf));
|
|
if (n < 0 && errno == EINTR)
|
|
continue;
|
|
if (n <= 0)
|
|
quit = true;
|
|
}
|
|
#endif
|
|
/* before the driver looks, so a wake from here on is not lost */
|
|
retro_atomic_exchange_int(&writer->pending, 0);
|
|
if (retro_atomic_load_acquire_int(&writer->quit))
|
|
quit = true;
|
|
writer->cb(writer->userdata, quit);
|
|
if (quit)
|
|
break;
|
|
}
|
|
#ifdef _WIN32
|
|
if (timer)
|
|
CloseHandle(timer);
|
|
#endif
|
|
}
|
|
|
|
input_output_writer_t *input_output_writer_new_timed(
|
|
input_output_writer_cb cb, input_output_writer_timeout_cb timeout_cb,
|
|
void *userdata)
|
|
{
|
|
input_output_writer_t *writer = (input_output_writer_t*)
|
|
calloc(1, sizeof(*writer));
|
|
|
|
if (!writer)
|
|
return NULL;
|
|
writer->timeout_cb = timeout_cb;
|
|
writer->cb = cb;
|
|
writer->userdata = userdata;
|
|
retro_atomic_store_release_int(&writer->quit, 0);
|
|
retro_atomic_store_release_int(&writer->pending, 0);
|
|
|
|
#ifdef _WIN32
|
|
if (!(writer->wake = CreateEventA(NULL, FALSE, FALSE, NULL)))
|
|
{
|
|
free(writer);
|
|
return NULL;
|
|
}
|
|
#else
|
|
if (pipe(writer->wake) != 0)
|
|
{
|
|
free(writer);
|
|
return NULL;
|
|
}
|
|
/* a full pipe has wakes enough in it: the waker never waits */
|
|
fcntl(writer->wake[1], F_SETFL,
|
|
fcntl(writer->wake[1], F_GETFL) | O_NONBLOCK);
|
|
#endif
|
|
|
|
if (!(writer->thread = sthread_create(input_output_writer_thread, writer)))
|
|
{
|
|
#ifdef _WIN32
|
|
CloseHandle(writer->wake);
|
|
#else
|
|
close(writer->wake[0]);
|
|
close(writer->wake[1]);
|
|
#endif
|
|
free(writer);
|
|
return NULL;
|
|
}
|
|
return writer;
|
|
}
|
|
|
|
static void input_output_writer_signal(input_output_writer_t *writer)
|
|
{
|
|
#ifdef _WIN32
|
|
SetEvent(writer->wake);
|
|
#else
|
|
{
|
|
char c = 1;
|
|
ssize_t written;
|
|
do
|
|
{
|
|
written = write(writer->wake[1], &c, 1);
|
|
} while (written < 0 && errno == EINTR);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void input_output_writer_wake(input_output_writer_t *writer)
|
|
{
|
|
if (writer && !retro_atomic_exchange_int(&writer->pending, 1))
|
|
input_output_writer_signal(writer);
|
|
}
|
|
|
|
void input_output_writer_free(input_output_writer_t *writer)
|
|
{
|
|
if (!writer)
|
|
return;
|
|
retro_atomic_store_release_int(&writer->quit, 1);
|
|
input_output_writer_signal(writer);
|
|
sthread_join(writer->thread);
|
|
#ifdef _WIN32
|
|
CloseHandle(writer->wake);
|
|
#else
|
|
close(writer->wake[0]);
|
|
close(writer->wake[1]);
|
|
#endif
|
|
free(writer);
|
|
}
|
|
|
|
#else
|
|
|
|
input_output_writer_t *input_output_writer_new_timed(
|
|
input_output_writer_cb cb, input_output_writer_timeout_cb timeout_cb,
|
|
void *userdata)
|
|
{
|
|
(void)timeout_cb;
|
|
(void)cb;
|
|
(void)userdata;
|
|
return NULL;
|
|
}
|
|
|
|
void input_output_writer_wake(input_output_writer_t *writer) { (void)writer; }
|
|
void input_output_writer_free(input_output_writer_t *writer) { (void)writer; }
|
|
|
|
#endif
|
|
|
|
input_output_writer_t *input_output_writer_new(
|
|
input_output_writer_cb cb, void *userdata)
|
|
{
|
|
return input_output_writer_new_timed(cb, NULL, userdata);
|
|
}
|