early-access version 2824

main
pineappleEA 2022-07-10 14:59:48 +02:00
parent da2f33c5e0
commit 29fab4f91a
43 changed files with 543 additions and 403 deletions

View File

@ -1,7 +1,7 @@
yuzu emulator early access
=============
This is the source code for early-access 2823.
This is the source code for early-access 2824.
## Legal Notice

View File

@ -9,11 +9,11 @@ jobs:
BUILD_TYPE: ${{ matrix.type }}
strategy:
matrix:
os: [ubuntu-20.04, windows-2019, macos-10.15]
os: [ubuntu-20.04, windows-2019, macos-11]
type: [Release, Debug]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: true
@ -46,13 +46,13 @@ jobs:
matrix:
type: [Release, Debug]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: true
- name: Configure CMake
shell: bash
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-26
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-28
- name: Build
shell: bash
@ -61,7 +61,7 @@ jobs:
check_format:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: true

View File

@ -5,9 +5,9 @@ You must have CMake v3.1 or later installed.
1. `git clone --recursive https://github.com/mozilla/cubeb.git`
2. `mkdir cubeb-build`
3. `cd cubeb-build`
3. `cmake ../cubeb`
4. `cmake --build .`
5. `ctest`
4. `cmake ../cubeb`
5. `cmake --build .`
6. `ctest`
# Windows build notes
@ -41,6 +41,6 @@ To build with MinGW-w64, install the following items:
- Download and install MinGW-w64 with Win32 threads.
- Download and install CMake.
- Run MinGW-w64 Terminal from the Start Menu.
- Follow the build steps at the top of this file, but at step 3 run:
`cmake -G "MinGW Makefiles" ..`
- Follow the build steps at the top of this file, but at step 4 run:
`cmake -G "MinGW Makefiles" ../cubeb`
- Continue the build steps at the top of this file.

View File

@ -2,6 +2,6 @@
See INSTALL.md for build instructions.
See [Backend Support](https://github.com/kinetiknz/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend.
See [Backend Support](https://github.com/mozilla/cubeb/wiki/Backend-Support) in the wiki for the support level of each backend.
Licensed under an ISC-style license. See LICENSE for details.

View File

@ -541,6 +541,13 @@ audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
long outframes = cubeb_resampler_fill(stm->resampler.get(),
stm->input_linear_buffer->data(),
&total_input_frames, NULL, 0);
if (outframes < 0) {
stm->shutdown = true;
OSStatus r = AudioOutputUnitStop(stm->input_unit);
assert(r == 0);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return noErr;
}
stm->draining = outframes < total_input_frames;
// Reset input buffer

View File

@ -123,7 +123,7 @@ cubeb_async_log(char const * fmt, ...)
}
void
cubeb_async_log_reset_threads()
cubeb_async_log_reset_threads(void)
{
if (!g_cubeb_log_callback) {
return;

View File

@ -35,7 +35,7 @@ extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2);
void
cubeb_async_log(const char * fmt, ...);
void
cubeb_async_log_reset_threads();
cubeb_async_log_reset_threads(void);
#ifdef __cplusplus
}

View File

@ -280,6 +280,7 @@ trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes,
if (got < 0) {
WRAP(pa_stream_cancel_write)(s);
stm->shutdown = 1;
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return;
}
// If more iterations move offset of read buffer
@ -392,6 +393,9 @@ stream_read_callback(pa_stream * s, size_t nbytes, void * u)
if (got < 0 || (size_t)got != read_frames) {
WRAP(pa_stream_cancel_write)(s);
stm->shutdown = 1;
if (got < 0) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
}
break;
}
}

View File

@ -110,8 +110,8 @@ public:
assert_correct_thread(producer_id);
#endif
int rd_idx = read_index_.load(std::memory_order_relaxed);
int wr_idx = write_index_.load(std::memory_order_relaxed);
int rd_idx = read_index_.load(std::memory_order_acquire);
if (full_internal(rd_idx, wr_idx)) {
return 0;
@ -154,8 +154,8 @@ public:
assert_correct_thread(consumer_id);
#endif
int wr_idx = write_index_.load(std::memory_order_acquire);
int rd_idx = read_index_.load(std::memory_order_relaxed);
int wr_idx = write_index_.load(std::memory_order_acquire);
if (empty_internal(rd_idx, wr_idx)) {
return 0;
@ -172,7 +172,7 @@ public:
}
read_index_.store(increment_index(rd_idx, to_read),
std::memory_order_relaxed);
std::memory_order_release);
return to_read;
}
@ -190,7 +190,7 @@ public:
#endif
return available_read_internal(
read_index_.load(std::memory_order_relaxed),
write_index_.load(std::memory_order_relaxed));
write_index_.load(std::memory_order_acquire));
}
/**
* Get the number of available elements for consuming.
@ -205,7 +205,7 @@ public:
assert_correct_thread(producer_id);
#endif
return available_write_internal(
read_index_.load(std::memory_order_relaxed),
read_index_.load(std::memory_order_acquire),
write_index_.load(std::memory_order_relaxed));
}
/**

View File

@ -240,8 +240,9 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
void
wasapi_destroy_device(cubeb_device_info * device_info);
static int
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection * out);
wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type,
cubeb_device_collection * out,
DWORD state_mask);
static int
wasapi_device_collection_destroy(cubeb * ctx,
cubeb_device_collection * collection);
@ -409,12 +410,9 @@ struct cubeb_stream {
float volume = 1.0;
/* True if the stream is draining. */
bool draining = false;
/* True when we've destroyed the stream. This pointer is leaked on stream
* destruction if we could not join the thread. */
std::atomic<std::atomic<bool> *> emergency_bailout{nullptr};
/* Synchronizes render thread start to ensure safe access to
* emergency_bailout. */
HANDLE thread_ready_event = 0;
/* If the render thread fails to stop, this is set to true and ownership of
* the stm is "leaked" to the render thread for later cleanup. */
std::atomic<bool> emergency_bailout{false};
/* This needs an active audio input stream to be known, and is updated in the
* first audio input callback. */
std::atomic<int64_t> input_latency_hns{LATENCY_NOT_AVAILABLE_YET};
@ -753,6 +751,27 @@ private:
namespace {
long
wasapi_data_callback(cubeb_stream * stm, void * user_ptr,
void const * input_buffer, void * output_buffer,
long nframes)
{
if (stm->emergency_bailout) {
return CUBEB_ERROR;
}
return stm->data_callback(stm, user_ptr, input_buffer, output_buffer,
nframes);
}
void
wasapi_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state)
{
if (stm->emergency_bailout) {
return;
}
return stm->state_callback(stm, user_ptr, state);
}
char const *
intern_device_id(cubeb * ctx, wchar_t const * id)
{
@ -873,8 +892,11 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
long out_frames =
cubeb_resampler_fill(stm->resampler.get(), input_buffer,
&input_frames_count, dest, output_frames_needed);
/* TODO: Report out_frames < 0 as an error via the API. */
XASSERT(out_frames >= 0);
if (out_frames < 0) {
ALOGV("Callback refill error: %d", out_frames);
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return out_frames;
}
float volume = 1.0;
{
@ -991,7 +1013,7 @@ get_input_buffer(cubeb_stream * stm)
(stm->input_stream_params.prefs &
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
!trigger_async_reconfigure(stm)) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return false;
}
return true;
@ -1105,7 +1127,7 @@ get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
(stm->output_stream_params.prefs &
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
!trigger_async_reconfigure(stm)) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return false;
}
return true;
@ -1121,7 +1143,7 @@ get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
if (stm->draining) {
if (padding_out == 0) {
LOG("Draining finished.");
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
return false;
}
LOG("Draining.");
@ -1190,6 +1212,7 @@ refill_callback_duplex(cubeb_stream * stm)
static_cast<long>(stm->total_output_frames) - stm->total_input_frames,
static_cast<float>(stm->total_output_frames) / stm->total_input_frames);
long got;
if (stm->has_dummy_output) {
ALOGV(
"Duplex callback (dummy output): input frames: %Iu, output frames: %Iu",
@ -1197,13 +1220,15 @@ refill_callback_duplex(cubeb_stream * stm)
// We don't want to expose the dummy output to the callback so don't pass
// the output buffer (it will be released later with silence in it)
refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
got =
refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
} else {
ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
input_frames, output_frames);
refill(stm, stm->linear_input_buffer->data(), input_frames, output_buffer,
output_frames);
got = refill(stm, stm->linear_input_buffer->data(), input_frames,
output_buffer, output_frames);
}
stm->linear_input_buffer->clear();
@ -1219,6 +1244,9 @@ refill_callback_duplex(cubeb_stream * stm)
LOG("failed to release buffer: %lx", hr);
return false;
}
if (got < 0) {
return false;
}
return true;
}
@ -1245,8 +1273,9 @@ refill_callback_input(cubeb_stream * stm)
long read =
refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
XASSERT(read >= 0);
if (read < 0) {
return false;
}
stm->linear_input_buffer->clear();
@ -1276,8 +1305,9 @@ refill_callback_output(cubeb_stream * stm)
ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
got);
XASSERT(got >= 0);
if (got < 0) {
return false;
}
XASSERT(size_t(got) == output_frames || stm->draining);
hr = stm->render_client->ReleaseBuffer(got, 0);
@ -1289,17 +1319,25 @@ refill_callback_output(cubeb_stream * stm)
return size_t(got) == output_frames || stm->draining;
}
void
wasapi_stream_destroy(cubeb_stream * stm);
static void
handle_emergency_bailout(cubeb_stream * stm)
{
if (stm->emergency_bailout) {
CloseHandle(stm->thread);
stm->thread = NULL;
CloseHandle(stm->shutdown_event);
stm->shutdown_event = 0;
wasapi_stream_destroy(stm);
_endthreadex(0);
}
}
static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
{
cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
std::atomic<bool> * emergency_bailout = stm->emergency_bailout;
// Signal wasapi_stream_start that we've copied emergency_bailout.
BOOL ok = SetEvent(stm->thread_ready_event);
if (!ok) {
LOG("thread_ready SetEvent failed: %lx", GetLastError());
return 0;
}
bool is_playing = true;
HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event,
@ -1332,20 +1370,10 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
unsigned timeout_count = 0;
const unsigned timeout_limit = 3;
while (is_playing) {
// We want to check the emergency bailout variable before a
// and after the WaitForMultipleObject, because the handles
// WaitForMultipleObjects is going to wait on might have been closed
// already.
if (*emergency_bailout) {
delete emergency_bailout;
return 0;
}
handle_emergency_bailout(stm);
DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
wait_array, FALSE, 1000);
if (*emergency_bailout) {
delete emergency_bailout;
return 0;
}
handle_emergency_bailout(stm);
if (waitResult != WAIT_TIMEOUT) {
timeout_count = 0;
}
@ -1355,7 +1383,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
/* We don't check if the drain is actually finished here, we just want to
shutdown. */
if (stm->draining) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
}
continue;
}
@ -1417,7 +1445,8 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
case WAIT_OBJECT_0 + 3: { /* input available */
HRESULT rv = get_input_buffer(stm);
if (FAILED(rv)) {
return rv;
is_playing = false;
continue;
}
if (!has_output(stm)) {
@ -1436,18 +1465,29 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
break;
default:
LOG("case %lu not handled in render loop.", waitResult);
abort();
XASSERT(false);
}
}
if (FAILED(hr)) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
// Stop audio clients since this thread will no longer service
// the events.
if (stm->output_client) {
stm->output_client->Stop();
}
if (stm->input_client) {
stm->input_client->Stop();
}
if (mmcss_handle) {
AvRevertMmThreadCharacteristics(mmcss_handle);
}
handle_emergency_bailout(stm);
if (FAILED(hr)) {
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
}
return 0;
}
@ -1696,54 +1736,58 @@ wasapi_init(cubeb ** context, char const * context_name)
}
namespace {
enum ShutdownPhase { OnStop, OnDestroy };
bool
stop_and_join_render_thread(cubeb_stream * stm)
stop_and_join_render_thread(cubeb_stream * stm, ShutdownPhase phase)
{
bool rv = true;
LOG("Stop and join render thread.");
// Only safe to transfer `stm` ownership to the render thread when
// the stream is being destroyed by the caller.
bool bailout = phase == OnDestroy;
LOG("%p: Stop and join render thread: %p (%d), phase=%d", stm, stm->thread,
stm->emergency_bailout.load(), static_cast<int>(phase));
if (!stm->thread) {
LOG("No thread present.");
return true;
}
// If we've already leaked the thread, just return,
// there is not much we can do.
if (!stm->emergency_bailout.load()) {
return false;
}
XASSERT(!stm->emergency_bailout);
BOOL ok = SetEvent(stm->shutdown_event);
if (!ok) {
LOG("Destroy SetEvent failed: %lx", GetLastError());
LOG("stop_and_join_render_thread: SetEvent failed: %lx", GetLastError());
stm->emergency_bailout = bailout;
return false;
}
/* Wait five seconds for the rendering thread to return. It's supposed to
* check its event loop very often, five seconds is rather conservative. */
DWORD r = WaitForSingleObject(stm->thread, 5000);
* check its event loop very often, five seconds is rather conservative.
* Note: 5*1s loop to work around timer sleep issues on pre-Windows 8. */
DWORD r;
for (int i = 0; i < 5; ++i) {
r = WaitForSingleObject(stm->thread, 1000);
if (r == WAIT_OBJECT_0) {
break;
}
}
if (r != WAIT_OBJECT_0) {
/* Something weird happened, leak the thread and continue the shutdown
* process. */
*(stm->emergency_bailout) = true;
// We give the ownership to the rendering thread.
stm->emergency_bailout = nullptr;
LOG("Destroy WaitForSingleObject on thread failed: %lx, %lx", r,
GetLastError());
rv = false;
LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: "
"%lx, %lx",
r, GetLastError());
stm->emergency_bailout = bailout;
return false;
}
// Only attempts to close and null out the thread and event if the
// WaitForSingleObject above succeeded, so that calling this function again
// attemps to clean up the thread and event each time.
if (rv) {
LOG("Closing thread.");
CloseHandle(stm->thread);
stm->thread = NULL;
// Only attempt to close and null out the thread and event if the
// WaitForSingleObject above succeeded.
LOG("stop_and_join_render_thread: Closing thread.");
CloseHandle(stm->thread);
stm->thread = NULL;
CloseHandle(stm->shutdown_event);
stm->shutdown_event = 0;
}
CloseHandle(stm->shutdown_event);
stm->shutdown_event = 0;
return rv;
return true;
}
void
@ -1881,9 +1925,6 @@ wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
return CUBEB_OK;
}
void
wasapi_stream_destroy(cubeb_stream * stm);
static void
waveformatex_update_derived_properties(WAVEFORMATEX * format)
{
@ -2259,8 +2300,12 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
if (wasapi_create_device(stm->context, device_info,
stm->device_enumerator.get(), device.get(),
&default_devices) == CUBEB_OK) {
if (device_info.latency_hi == 0) {
LOG("Input: could not query latency_hi to guess safe latency");
wasapi_destroy_device(&device_info);
return CUBEB_ERROR;
}
// This multiplicator has been found empirically.
XASSERT(device_info.latency_hi > 0);
uint32_t latency_frames = device_info.latency_hi * 8;
LOG("Input: latency increased to %u frames from a default of %u",
latency_frames, device_info.latency_hi);
@ -2362,10 +2407,10 @@ wasapi_find_bt_handsfree_output_device(cubeb_stream * stm)
return nullptr;
}
int rv = wasapi_enumerate_devices(
int rv = wasapi_enumerate_devices_internal(
stm->context,
(cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT),
&collection);
&collection, DEVICE_STATE_ACTIVE);
if (rv != CUBEB_OK) {
return nullptr;
}
@ -2561,7 +2606,7 @@ setup_wasapi_stream(cubeb_stream * stm)
stm->resampler.reset(cubeb_resampler_create(
stm, has_input(stm) ? &input_params : nullptr,
has_output(stm) && !stm->has_dummy_output ? &output_params : nullptr,
target_sample_rate, stm->data_callback, stm->user_ptr,
target_sample_rate, wasapi_data_callback, stm->user_ptr,
stm->voice ? CUBEB_RESAMPLER_QUALITY_VOIP
: CUBEB_RESAMPLER_QUALITY_DESKTOP,
CUBEB_RESAMPLER_RECLOCK_NONE));
@ -2794,33 +2839,25 @@ wasapi_stream_destroy(cubeb_stream * stm)
XASSERT(stm);
LOG("Stream destroy (%p)", stm);
// Only free stm->emergency_bailout if we could join the thread.
// If we could not join the thread, stm->emergency_bailout is true
// and is still alive until the thread wakes up and exits cleanly.
if (stop_and_join_render_thread(stm)) {
delete stm->emergency_bailout.load();
stm->emergency_bailout = nullptr;
if (!stop_and_join_render_thread(stm, OnDestroy)) {
// Emergency bailout: render thread becomes responsible for calling
// wasapi_stream_destroy.
return;
}
if (stm->notification_client) {
unregister_notification_client(stm);
}
CloseHandle(stm->reconfigure_event);
CloseHandle(stm->refill_event);
CloseHandle(stm->input_available_event);
// The variables intialized in wasapi_stream_init,
// must be destroyed in wasapi_stream_destroy.
stm->linear_input_buffer.reset();
stm->device_enumerator = nullptr;
{
auto_lock lock(stm->stream_reset_lock);
close_wasapi_stream(stm);
}
CloseHandle(stm->reconfigure_event);
CloseHandle(stm->refill_event);
CloseHandle(stm->input_available_event);
delete stm;
}
@ -2875,8 +2912,6 @@ wasapi_stream_start(cubeb_stream * stm)
XASSERT(stm && !stm->thread && !stm->shutdown_event);
XASSERT(stm->output_client || stm->input_client);
stm->emergency_bailout = new std::atomic<bool>(false);
if (stm->output_client) {
int rv = stream_start_one_side(stm, OUTPUT);
if (rv != CUBEB_OK) {
@ -2897,30 +2932,18 @@ wasapi_stream_start(cubeb_stream * stm)
return CUBEB_ERROR;
}
stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->thread_ready_event) {
LOG("Can't create the thread_ready event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
cubeb_async_log_reset_threads();
stm->thread =
(HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
if (stm->thread == NULL) {
LOG("could not create WASAPI render thread.");
CloseHandle(stm->shutdown_event);
stm->shutdown_event = 0;
return CUBEB_ERROR;
}
// Wait for wasapi_stream_render_loop to signal that emergency_bailout has
// been read, avoiding a bailout situation where we could free `stm`
// before wasapi_stream_render_loop had a chance to run.
HRESULT hr = WaitForSingleObject(stm->thread_ready_event, INFINITE);
XASSERT(hr == WAIT_OBJECT_0);
CloseHandle(stm->thread_ready_event);
stm->thread_ready_event = 0;
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
return CUBEB_OK;
}
@ -2950,15 +2973,12 @@ wasapi_stream_stop(cubeb_stream * stm)
}
}
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
}
if (stop_and_join_render_thread(stm)) {
delete stm->emergency_bailout.load();
stm->emergency_bailout = nullptr;
} else {
if (!stop_and_join_render_thread(stm, OnStop)) {
// If we could not join the thread, put the stream in error.
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return CUBEB_ERROR;
}
@ -3324,8 +3344,9 @@ wasapi_destroy_device(cubeb_device_info * device)
}
static int
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection * out)
wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type,
cubeb_device_collection * out,
DWORD state_mask)
{
com_ptr<IMMDeviceEnumerator> enumerator;
com_ptr<IMMDeviceCollection> collection;
@ -3353,8 +3374,7 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
return CUBEB_ERROR;
}
hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL,
collection.receive());
hr = enumerator->EnumAudioEndpoints(flow, state_mask, collection.receive());
if (FAILED(hr)) {
LOG("Could not enumerate audio endpoints: %lx", hr);
return CUBEB_ERROR;
@ -3388,6 +3408,15 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
return CUBEB_OK;
}
static int
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection * out)
{
return wasapi_enumerate_devices_internal(
context, type, out,
DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED);
}
static int
wasapi_device_collection_destroy(cubeb * /*ctx*/,
cubeb_device_collection * collection)

View File

@ -34,6 +34,7 @@ enum test_direction {
struct user_state_callback_ret {
std::atomic<int> cb_count{ 0 };
std::atomic<int> expected_cb_count{ 0 };
std::atomic<int> error_state{ 0 };
};
// Data callback that always returns 0
@ -98,10 +99,30 @@ long data_cb_ret_nframes(cubeb_stream * stream, void * user, const void * inputb
return nframes;
}
void state_cb_ret(cubeb_stream * stream, void * /*user*/, cubeb_state state)
// Data callback that always returns CUBEB_ERROR
long
data_cb_ret_error(cubeb_stream * stream, void * user, const void * inputbuffer,
void * outputbuffer, long nframes)
{
user_state_callback_ret * u = (user_state_callback_ret *)user;
// If this is the first time the callback has been called set our expected
// callback count
if (u->cb_count == 0) {
u->expected_cb_count = 1;
}
u->cb_count++;
if (nframes < 1) {
// This shouldn't happen
EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
}
return CUBEB_ERROR;
}
void state_cb_ret(cubeb_stream * stream, void * user, cubeb_state state)
{
if (stream == NULL)
return;
user_state_callback_ret * u = (user_state_callback_ret *)user;
switch (state) {
case CUBEB_STATE_STARTED:
@ -110,11 +131,13 @@ void state_cb_ret(cubeb_stream * stream, void * /*user*/, cubeb_state state)
fprintf(stderr, "stream stopped\n"); break;
case CUBEB_STATE_DRAINED:
fprintf(stderr, "stream drained\n"); break;
case CUBEB_STATE_ERROR:
fprintf(stderr, "stream error\n");
u->error_state.fetch_add(1);
break;
default:
fprintf(stderr, "unknown stream state %d\n", state);
}
return;
}
void run_test_callback(test_direction direction,
@ -183,6 +206,10 @@ void run_test_callback(test_direction direction,
ASSERT_EQ(user_state.expected_cb_count, user_state.cb_count) <<
"Callback called unexpected number of times for " << test_desc << "!";
// TODO: On some test configurations, the data_callback is never called.
if (data_cb == data_cb_ret_error && user_state.cb_count != 0) {
ASSERT_EQ(user_state.error_state, 1) << "Callback expected error state";
}
}
TEST(cubeb, test_input_callback)
@ -190,6 +217,8 @@ TEST(cubeb, test_input_callback)
run_test_callback(INPUT_ONLY, data_cb_ret_zero, "input only, return 0");
run_test_callback(INPUT_ONLY, data_cb_ret_nframes_minus_one, "input only, return nframes - 1");
run_test_callback(INPUT_ONLY, data_cb_ret_nframes, "input only, return nframes");
run_test_callback(INPUT_ONLY, data_cb_ret_error,
"input only, return CUBEB_ERROR");
}
TEST(cubeb, test_output_callback)
@ -197,6 +226,8 @@ TEST(cubeb, test_output_callback)
run_test_callback(OUTPUT_ONLY, data_cb_ret_zero, "output only, return 0");
run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes_minus_one, "output only, return nframes - 1");
run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes, "output only, return nframes");
run_test_callback(OUTPUT_ONLY, data_cb_ret_error,
"output only, return CUBEB_ERROR");
}
TEST(cubeb, test_duplex_callback)
@ -204,4 +235,5 @@ TEST(cubeb, test_duplex_callback)
run_test_callback(DUPLEX, data_cb_ret_zero, "duplex, return 0");
run_test_callback(DUPLEX, data_cb_ret_nframes_minus_one, "duplex, return nframes - 1");
run_test_callback(DUPLEX, data_cb_ret_nframes, "duplex, return nframes");
run_test_callback(DUPLEX, data_cb_ret_error, "duplex, return CUBEB_ERROR");
}

View File

@ -57,4 +57,12 @@ void AudioCore::PauseSinks(const bool pausing) const {
}
}
u32 AudioCore::GetStreamQueue() const {
return estimated_queue.load();
}
void AudioCore::SetStreamQueue(u32 size) {
estimated_queue.store(size);
}
} // namespace AudioCore

View File

@ -65,6 +65,20 @@ public:
*/
void PauseSinks(bool pausing) const;
/**
* Get the size of the current stream queue.
*
* @return Current stream queue size.
*/
u32 GetStreamQueue() const;
/**
* Get the size of the current stream queue.
*
* @param size - New stream size.
*/
void SetStreamQueue(u32 size);
private:
/**
* Create the sinks on startup.
@ -79,6 +93,8 @@ private:
std::unique_ptr<Sink::Sink> input_sink;
/// The ADSP in the sysmodule
std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
/// Current size of the stream queue
std::atomic<u32> estimated_queue{0};
};
} // namespace AudioCore

View File

@ -5,6 +5,8 @@
#include "audio_core/audio_in_manager.h"
#include "audio_core/audio_manager.h"
#include "audio_core/in/audio_in.h"
#include "audio_core/sink/sink_details.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/hle/service/audio/errors.h"
@ -78,9 +80,12 @@ u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceN
LinkToManager();
names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac"));
return 1;
auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
if (input_devices.size() > 1) {
names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac"));
return 1;
}
return 0;
}
} // namespace AudioCore::AudioIn

View File

@ -37,13 +37,16 @@ Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_for
sink = &system.AudioCore().GetOutputSink();
}
stream = sink->AcquireSinkStream(system, channel_count, name, type);
initialized = true;
return ResultSuccess;
}
void DeviceSession::Finalize() {
Stop();
sink->CloseStream(stream);
stream = nullptr;
if (initialized) {
Stop();
sink->CloseStream(stream);
stream = nullptr;
}
}
void DeviceSession::Start() {

View File

@ -100,7 +100,7 @@ private:
/// System
Core::System& system;
/// Output sink this device will use
Sink::Sink* sink;
Sink::Sink* sink{};
/// The backend stream for this device session to send samples to
Sink::SinkStream* stream{};
/// Name of this device session
@ -119,6 +119,8 @@ private:
u64 applet_resource_user_id{};
/// Total number of samples played by this device session
u64 played_sample_count{};
/// Is this session initialised?
bool initialized{};
};
} // namespace AudioCore

View File

@ -128,8 +128,8 @@ void AudioRenderer::CreateSinkStreams() {
u32 channels{sink.GetDeviceChannels()};
for (u32 i = 0; i < MaxRendererSessions; i++) {
std::string name{fmt::format("ADSP_RenderStream-{}", i)};
streams[i] = sink.AcquireSinkStream(system, channels, name,
::AudioCore::Sink::StreamType::Render, &render_event);
streams[i] =
sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
streams[i]->SetSystemChannels(streams[i]->GetDeviceChannels());
}
}
@ -199,12 +199,9 @@ void AudioRenderer::ThreadFunc() {
command_list_processor.Process(index) - start_time;
}
// If the stream queue is building up too much, wait for a signal
// from the backend that a buffer was consumed.
// In practice this will wait longer than 1 buffer due to timing.
auto stream{command_list_processor.GetOutputSinkStream()};
if (stream->GetQueueSize() >= 4) {
render_event.WaitFor(std::chrono::milliseconds(5));
if (index == 0) {
auto stream{command_list_processor.GetOutputSinkStream()};
system.AudioCore().SetStreamQueue(stream->GetQueueSize());
}
const auto end_time{system.CoreTiming().GetClockTicks()};

View File

@ -14,8 +14,11 @@
#include "common/thread.h"
namespace Core {
class System;
namespace Timing {
struct EventType;
}
class System;
} // namespace Core
namespace AudioCore {
namespace Sink {
@ -194,8 +197,6 @@ private:
Sink::Sink& sink;
/// The streams which will receive the processed samples
std::array<Sink::SinkStream*, MaxRendererSessions> streams;
/// An event signalled from the backend when a buffer is consumed, used for timing.
Common::Event render_event{};
};
} // namespace AudioRenderer::ADSP

View File

@ -63,7 +63,8 @@ static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& p
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
for (u32 channel = 0; channel < params.channel_count; channel++) {
auto sample{Common::FixedPoint<49, 15>(inputs[channel][sample_index]) *
auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) /
Common::FixedPoint<49, 15>::one) *
params.input_gain};
auto abs_sample{sample};
if (sample < 0.0f) {
@ -85,15 +86,17 @@ static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& p
auto lookahead_sample{
state.look_ahead_sample_buffers[channel]
[state.look_ahead_sample_offsets[channel]]};
state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] =
sample;
state.look_ahead_sample_offsets[channel] =
(state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min;
outputs[channel][sample_index] = static_cast<s32>(std::clamp(
(lookahead_sample * state.compression_gain[channel] * params.output_gain)
.to_long(),
min, max));
outputs[channel][sample_index] = static_cast<s32>(
std::clamp((lookahead_sample * state.compression_gain[channel] *
params.output_gain * Common::FixedPoint<49, 15>::one)
.to_long(),
min, max));
if (statistics) {
statistics->channel_max_sample[channel] =

View File

@ -15,12 +15,17 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
MP_RGB(60, 19, 97));
namespace AudioCore::AudioRenderer {
constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL};
constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL};
SystemManager::SystemManager(Core::System& core_)
: core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
thread_event{Core::Timing::CreateEvent(
"AudioRendererSystemManager",
[this](std::uintptr_t userdata, std::chrono::nanoseconds ns_late) { ThreadFunc2(); })} {}
"AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
return ThreadFunc2(time);
})} {
core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); });
}
SystemManager::~SystemManager() {
Stop();
@ -30,9 +35,9 @@ bool SystemManager::InitializeUnsafe() {
if (!active) {
if (adsp.Start()) {
active = true;
core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(2'304'000ULL * 2),
thread_event);
thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0),
BaseRenderTime - RenderTimeOffset, thread_event);
}
}
@ -95,11 +100,13 @@ void SystemManager::ThreadFunc() {
constexpr char name[]{"yuzu:AudioRenderSystemManager"};
MicroProfileOnThreadCreate(name);
Common::SetCurrentThreadName(name);
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
while (active) {
{
std::scoped_lock l{mutex1};
MICROPROFILE_SCOPE(Audio_RenderSystemManager);
for (auto system : systems) {
system->SendCommandToDsp();
}
@ -113,9 +120,43 @@ void SystemManager::ThreadFunc() {
}
}
void SystemManager::ThreadFunc2() {
std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt};
const auto queue_size{core.AudioCore().GetStreamQueue()};
switch (state) {
case StreamState::Filling:
if (queue_size >= 5) {
new_schedule_time = BaseRenderTime;
state = StreamState::Steady;
}
break;
case StreamState::Steady:
if (queue_size <= 2) {
new_schedule_time = BaseRenderTime - RenderTimeOffset;
state = StreamState::Filling;
} else if (queue_size > 5) {
new_schedule_time = BaseRenderTime + RenderTimeOffset;
state = StreamState::Draining;
}
break;
case StreamState::Draining:
if (queue_size <= 5) {
new_schedule_time = BaseRenderTime;
state = StreamState::Steady;
}
break;
}
update.store(true);
update.notify_all();
return new_schedule_time;
}
void SystemManager::PauseCallback(bool paused) {
if (paused && core.IsPoweredOn() && core.IsShuttingDown()) {
update.store(true);
update.notify_all();
}
}
} // namespace AudioCore::AudioRenderer

View File

@ -6,6 +6,7 @@
#include <list>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include "audio_core/renderer/system.h"
@ -70,7 +71,20 @@ private:
/**
* Signalling core timing thread to run ThreadFunc.
*/
void ThreadFunc2();
std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
/**
* Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc.
*
* @param paused - Are we pausing or resuming?
*/
void PauseCallback(bool paused);
enum class StreamState {
Filling,
Steady,
Draining,
};
/// Core system
Core::System& core;
@ -92,6 +106,8 @@ private:
std::shared_ptr<Core::Timing::EventType> thread_event;
/// Atomic for main thread to wait on
std::atomic<bool> update{};
/// Current state of the streams
StreamState state{StreamState::Filling};
};
} // namespace AudioCore::AudioRenderer

View File

@ -44,8 +44,8 @@ public:
*/
CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_,
cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
const StreamType type_, Core::System& system_, Common::Event* event)
: ctx{ctx_}, type{type_}, system{system_}, render_event{event} {
const StreamType type_, Core::System& system_)
: ctx{ctx_}, type{type_}, system{system_} {
#ifdef _WIN32
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
#endif
@ -306,7 +306,6 @@ private:
manager.SetEvent(Event::Type::AudioInManager, true);
break;
case StreamType::Render:
render_event->Set();
break;
}
}
@ -469,8 +468,6 @@ private:
::AudioCore::Sink::SinkBuffer released_buffer{};
/// The last played (or received) frame of audio, used when the callback underruns
std::array<s16, MaxChannels> last_frame{};
/// Audio render-only event, signalled when a render buffer is consumed
Common::Event* render_event;
};
CubebSink::CubebSink(std::string_view target_device_name) {
@ -525,11 +522,9 @@ CubebSink::~CubebSink() {
}
SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels,
const std::string& name, const StreamType type,
Common::Event* event) {
SinkStreamPtr& stream = sink_streams.emplace_back(
std::make_unique<CubebSinkStream>(ctx, device_channels, system_channels, output_device,
input_device, name, type, system, event));
const std::string& name, const StreamType type) {
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
ctx, device_channels, system_channels, output_device, input_device, name, type, system));
return stream.get();
}

View File

@ -39,8 +39,7 @@ public:
* @return A pointer to the created SinkStream
*/
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
const std::string& name, StreamType type,
Common::Event* event = nullptr) override;
const std::string& name, StreamType type) override;
/**
* Close a given stream.

View File

@ -18,8 +18,7 @@ public:
SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system,
[[maybe_unused]] u32 system_channels,
[[maybe_unused]] const std::string& name,
[[maybe_unused]] StreamType type,
[[maybe_unused]] Common::Event* event = nullptr) override {
[[maybe_unused]] StreamType type) override {
return &null_sink_stream;
}

View File

@ -46,8 +46,8 @@ public:
*/
SDLSinkStream(u32 device_channels_, const u32 system_channels_,
const std::string& output_device, const std::string& input_device,
const StreamType type_, Core::System& system_, Common::Event* event)
: type{type_}, system{system_}, render_event{event} {
const StreamType type_, Core::System& system_)
: type{type_}, system{system_} {
system_channels = system_channels_;
device_channels = device_channels_;
@ -279,7 +279,6 @@ private:
manager.SetEvent(Event::Type::AudioInManager, true);
break;
case StreamType::Render:
render_event->Set();
break;
}
}
@ -417,8 +416,6 @@ private:
::AudioCore::Sink::SinkBuffer released_buffer{};
/// The last played (or received) frame of audio, used when the callback underruns
std::array<s16, MaxChannels> last_frame{};
/// Audio render-only event, signalled when a render buffer is consumed
Common::Event* render_event;
};
SDLSink::SDLSink(std::string_view target_device_name) {
@ -441,10 +438,9 @@ SDLSink::SDLSink(std::string_view target_device_name) {
SDLSink::~SDLSink() = default;
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels,
const std::string&, const StreamType type,
Common::Event* event) {
const std::string&, const StreamType type) {
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
device_channels, system_channels, output_device, input_device, type, system, event));
device_channels, system_channels, output_device, input_device, type, system));
return stream.get();
}

View File

@ -37,8 +37,7 @@ public:
* @return A pointer to the created SinkStream
*/
SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
const std::string& name, StreamType type,
Common::Event* event = nullptr) override;
const std::string& name, StreamType type) override;
/**
* Close a given stream.

View File

@ -63,8 +63,7 @@ public:
* @return A pointer to the created SinkStream
*/
virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
const std::string& name, StreamType type,
Common::Event* event = nullptr) = 0;
const std::string& name, StreamType type) = 0;
/**
* Get the number of channels the hardware device supports.

View File

@ -139,7 +139,6 @@ struct System::Impl {
kernel.Suspend(false);
core_timing.SyncPause(false);
cpu_manager.Pause(false);
is_paused = false;
audio_core->PauseSinks(false);
@ -155,7 +154,6 @@ struct System::Impl {
core_timing.SyncPause(true);
kernel.Suspend(true);
cpu_manager.Pause(true);
is_paused = true;
return status;
@ -170,7 +168,6 @@ struct System::Impl {
std::unique_lock<std::mutex> lk(suspend_guard);
kernel.Suspend(true);
core_timing.SyncPause(true);
cpu_manager.Pause(true);
return lk;
}
@ -178,7 +175,6 @@ struct System::Impl {
if (!is_paused) {
core_timing.SyncPause(false);
kernel.Suspend(false);
cpu_manager.Pause(false);
}
}
@ -348,13 +344,14 @@ struct System::Impl {
gpu_core->NotifyShutdown();
}
kernel.ShutdownCores();
cpu_manager.Shutdown();
debugger.reset();
kernel.CloseServices();
services.reset();
service_manager.reset();
cheat_engine.reset();
telemetry_session.reset();
cpu_manager.Shutdown();
time_manager.Shutdown();
core_timing.Shutdown();
app_loader.reset();

View File

@ -22,11 +22,11 @@ std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callbac
}
struct CoreTiming::Event {
u64 time;
s64 time;
u64 fifo_order;
std::uintptr_t user_data;
std::weak_ptr<EventType> type;
u64 reschedule_time;
s64 reschedule_time;
// Sort by time, unless the times are the same, in which case sort by
// the order added to the queue
@ -59,7 +59,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
event_fifo_id = 0;
shutting_down = false;
ticks = 0;
const auto empty_timed_callback = [](std::uintptr_t, std::chrono::nanoseconds) {};
const auto empty_timed_callback = [](std::uintptr_t, u64, std::chrono::nanoseconds)
-> std::optional<std::chrono::nanoseconds> { return std::nullopt; };
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
if (is_multicore) {
worker_threads.emplace_back(ThreadEntry, std::ref(*this), 0);
@ -77,6 +78,7 @@ void CoreTiming::Shutdown() {
thread.join();
}
worker_threads.clear();
pause_callbacks.clear();
ClearPendingEvents();
has_started = false;
}
@ -94,6 +96,14 @@ void CoreTiming::Pause(bool is_paused_) {
}
}
paused_state.store(is_paused_, std::memory_order_relaxed);
if (!is_paused_) {
pause_end_time = GetGlobalTimeNs().count();
}
for (auto& cb : pause_callbacks) {
cb(is_paused_);
}
}
void CoreTiming::SyncPause(bool is_paused_) {
@ -117,6 +127,14 @@ void CoreTiming::SyncPause(bool is_paused_) {
wait_signal_cv.wait(main_lock, [this] { return pause_count == 0; });
}
}
if (!is_paused_) {
pause_end_time = GetGlobalTimeNs().count();
}
for (auto& cb : pause_callbacks) {
cb(is_paused_);
}
}
bool CoreTiming::IsRunning() const {
@ -130,12 +148,12 @@ bool CoreTiming::HasPendingEvents() const {
void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data) {
std::uintptr_t user_data, bool absolute_time) {
std::unique_lock main_lock(event_mutex);
const u64 timeout = static_cast<u64>((GetGlobalTimeNs() + ns_into_future).count());
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
event_queue.emplace_back(Event{timeout, event_fifo_id++, user_data, event_type, 0});
event_queue.emplace_back(Event{next_time.count(), event_fifo_id++, user_data, event_type, 0});
pending_events.fetch_add(1, std::memory_order_relaxed);
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
@ -145,30 +163,15 @@ void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
}
}
void CoreTiming::ScheduleEventAt(std::chrono::nanoseconds next_time,
const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data) {
std::unique_lock main_lock(event_mutex);
const u64 timeout = static_cast<u64>(next_time.count());
event_queue.emplace_back(Event{timeout, event_fifo_id++, user_data, event_type, 0});
pending_events.fetch_add(1, std::memory_order_relaxed);
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
if (is_multicore) {
event_cv.notify_one();
}
}
void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds time,
void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
std::chrono::nanoseconds resched_time,
const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data) {
std::uintptr_t user_data, bool absolute_time) {
std::unique_lock main_lock(event_mutex);
const u64 timeout = static_cast<u64>((GetGlobalTimeNs() + time).count());
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
event_queue.emplace_back(
Event{timeout, event_fifo_id++, user_data, event_type, static_cast<u64>(time.count())});
Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
pending_events.fetch_add(1, std::memory_order_relaxed);
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
@ -247,6 +250,11 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
}
}
void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) {
std::unique_lock main_lock(event_mutex);
pause_callbacks.emplace_back(std::move(callback));
}
std::optional<s64> CoreTiming::Advance() {
global_timer = GetGlobalTimeNs().count();
@ -257,21 +265,31 @@ std::optional<s64> CoreTiming::Advance() {
event_queue.pop_back();
if (const auto event_type{evt.type.lock()}) {
event_mutex.unlock();
const s64 delay = static_cast<s64>(GetGlobalTimeNs().count() - evt.time);
event_type->callback(evt.user_data, std::chrono::nanoseconds{delay});
const auto new_schedule_time{event_type->callback(
evt.user_data, evt.time,
std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
event_mutex.lock();
pending_events.fetch_sub(1, std::memory_order_relaxed);
}
if (evt.reschedule_time != 0) {
event_queue.emplace_back(
Event{global_timer + evt.reschedule_time - (global_timer - evt.time),
event_fifo_id++, evt.user_data, evt.type, evt.reschedule_time});
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
if (evt.reschedule_time != 0) {
// If this event was scheduled into a pause, its time now is going to be way behind.
// Re-set this event to continue from the end of the pause.
auto next_time{evt.time + evt.reschedule_time};
if (evt.time < pause_end_time) {
next_time = pause_end_time + evt.reschedule_time;
}
const auto next_schedule_time{new_schedule_time.has_value()
? new_schedule_time.value().count()
: evt.reschedule_time};
event_queue.emplace_back(
Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
pending_events.fetch_add(1, std::memory_order_relaxed);
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
}
}
global_timer = GetGlobalTimeNs().count();

View File

@ -20,8 +20,9 @@
namespace Core::Timing {
/// A callback that may be scheduled for a particular core timing event.
using TimedCallback =
std::function<void(std::uintptr_t user_data, std::chrono::nanoseconds ns_late)>;
using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>;
using PauseCallback = std::function<void(bool paused)>;
/// Contains the characteristics of a particular event.
struct EventType {
@ -93,18 +94,15 @@ public:
/// Schedules an event in core timing
void ScheduleEvent(std::chrono::nanoseconds ns_into_future,
const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0);
/// Schedules an event with an absolute time in core timing
void ScheduleEventAt(std::chrono::nanoseconds next_time,
const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data = 0);
const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0,
bool absolute_time = false);
/// Schedules an event which will automatically re-schedule itself with the given time, until
/// unscheduled
void ScheduleLoopingEvent(std::chrono::nanoseconds time,
void ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
std::chrono::nanoseconds resched_time,
const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data = 0);
std::uintptr_t user_data = 0, bool absolute_time = false);
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data);
@ -136,6 +134,9 @@ public:
/// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
std::optional<s64> Advance();
/// Register a callback function to be called when coretiming pauses.
void RegisterPauseCallback(PauseCallback&& callback);
private:
struct Event;
@ -147,7 +148,7 @@ private:
std::unique_ptr<Common::WallClock> clock;
u64 global_timer = 0;
s64 global_timer = 0;
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
@ -173,10 +174,13 @@ private:
bool shutting_down{};
bool is_multicore{};
size_t pause_count{};
s64 pause_end_time{};
/// Cycle timing
u64 ticks{};
s64 downcount{};
std::vector<PauseCallback> pause_callbacks{};
};
/// Creates a core timing event with the given name and callback.

View File

@ -25,10 +25,8 @@ void CpuManager::ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager
}
void CpuManager::Initialize() {
running_mode = true;
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
pause_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
for (std::size_t core = 0; core < num_cores; core++) {
core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core);
@ -36,8 +34,11 @@ void CpuManager::Initialize() {
}
void CpuManager::Shutdown() {
running_mode = false;
Pause(false);
for (std::size_t core = 0; core < num_cores; core++) {
if (core_data[core].host_thread.joinable()) {
core_data[core].host_thread.join();
}
}
}
void CpuManager::GuestThreadFunction() {
@ -64,6 +65,10 @@ void CpuManager::IdleThreadFunction() {
}
}
void CpuManager::ShutdownThreadFunction() {
ShutdownThread();
}
///////////////////////////////////////////////////////////////////////////////
/// MultiCore ///
///////////////////////////////////////////////////////////////////////////////
@ -176,41 +181,13 @@ void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
}
}
void CpuManager::SuspendThread() {
void CpuManager::ShutdownThread() {
auto& kernel = system.Kernel();
kernel.CurrentScheduler()->OnThreadStart();
auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0;
auto* current_thread = kernel.GetCurrentEmuThread();
while (true) {
auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0;
auto& scheduler = *kernel.CurrentScheduler();
Kernel::KThread* current_thread = scheduler.GetSchedulerCurrentThread();
Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context);
// This shouldn't be here. This is here because the scheduler needs the current
// thread to have dispatch disabled before explicitly rescheduling. Ideally in the
// future this will be called by RequestScheduleOnInterrupt and explicitly disabling
// dispatch outside the scheduler will not be necessary.
current_thread->DisableDispatch();
scheduler.RescheduleCurrentCore();
}
}
void CpuManager::Pause(bool paused) {
std::scoped_lock lk{pause_lock};
if (pause_state == paused) {
return;
}
// Set the new state
pause_state.store(paused);
// Wake up any waiting threads
pause_state.notify_all();
// Wait for all threads to successfully change state before returning
pause_barrier->Sync();
Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context);
UNREACHABLE();
}
void CpuManager::RunThread(std::size_t core) {
@ -241,27 +218,9 @@ void CpuManager::RunThread(std::size_t core) {
system.GPU().ObtainContext();
}
{
// Set the current thread on entry
auto* current_thread = system.Kernel().CurrentScheduler()->GetIdleThread();
Kernel::SetCurrentThread(system.Kernel(), current_thread);
}
while (running_mode) {
if (pause_state.load(std::memory_order_relaxed)) {
// Wait for caller to acknowledge pausing
pause_barrier->Sync();
// Wait until unpaused
pause_state.wait(true, std::memory_order_relaxed);
// Wait for caller to acknowledge unpausing
pause_barrier->Sync();
}
auto current_thread = system.Kernel().CurrentScheduler()->GetSchedulerCurrentThread();
Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext());
}
auto* current_thread = system.Kernel().CurrentScheduler()->GetIdleThread();
Kernel::SetCurrentThread(system.Kernel(), current_thread);
Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext());
}
} // namespace Core

View File

@ -50,16 +50,14 @@ public:
void Initialize();
void Shutdown();
void Pause(bool paused);
std::function<void()> GetGuestThreadStartFunc() {
return [this] { GuestThreadFunction(); };
}
std::function<void()> GetIdleThreadStartFunc() {
return [this] { IdleThreadFunction(); };
}
std::function<void()> GetSuspendThreadStartFunc() {
return [this] { SuspendThread(); };
std::function<void()> GetShutdownThreadStartFunc() {
return [this] { ShutdownThreadFunction(); };
}
void PreemptSingleCore(bool from_running_enviroment = true);
@ -72,6 +70,7 @@ private:
void GuestThreadFunction();
void GuestRewindFunction();
void IdleThreadFunction();
void ShutdownThreadFunction();
void MultiCoreRunGuestThread();
void MultiCoreRunGuestLoop();
@ -83,7 +82,7 @@ private:
static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core);
void SuspendThread();
void ShutdownThread();
void RunThread(std::size_t core);
struct CoreData {
@ -91,12 +90,7 @@ private:
std::jthread host_thread;
};
std::atomic<bool> running_mode{};
std::atomic<bool> pause_state{};
std::unique_ptr<Common::Barrier> pause_barrier{};
std::unique_ptr<Common::Barrier> gpu_barrier{};
std::mutex pause_lock{};
std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{};
bool is_async_gpu{};

View File

@ -269,7 +269,7 @@ Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32
Result KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread,
KThreadFunction func, uintptr_t arg, s32 virt_core) {
return InitializeThread(thread, func, arg, {}, {}, virt_core, nullptr, ThreadType::HighPriority,
system.GetCpuManager().GetSuspendThreadStartFunc());
system.GetCpuManager().GetShutdownThreadStartFunc());
}
Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThreadFunction func,
@ -739,6 +739,19 @@ void KThread::Continue() {
KScheduler::OnThreadStateChanged(kernel, this, old_state);
}
void KThread::WaitUntilSuspended() {
// Make sure we have a suspend requested.
ASSERT(IsSuspendRequested());
// Loop until the thread is not executing on any core.
for (std::size_t i = 0; i < static_cast<std::size_t>(Core::Hardware::NUM_CPU_CORES); ++i) {
KThread* core_thread{};
do {
core_thread = kernel.Scheduler(i).GetSchedulerCurrentThread();
} while (core_thread == this);
}
}
Result KThread::SetActivity(Svc::ThreadActivity activity) {
// Lock ourselves.
KScopedLightLock lk(activity_pause_lock);

View File

@ -208,6 +208,8 @@ public:
void Continue();
void WaitUntilSuspended();
constexpr void SetSyncedIndex(s32 index) {
synced_index = index;
}

View File

@ -76,7 +76,7 @@ struct KernelCore::Impl {
InitializeMemoryLayout();
Init::InitializeKPageBufferSlabHeap(system);
InitializeSchedulers();
InitializeSuspendThreads();
InitializeShutdownThreads();
InitializePreemption(kernel);
RegisterHostThread();
@ -131,9 +131,9 @@ struct KernelCore::Impl {
CleanupObject(system_resource_limit);
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
if (suspend_threads[core_id]) {
suspend_threads[core_id]->Close();
suspend_threads[core_id] = nullptr;
if (shutdown_threads[core_id]) {
shutdown_threads[core_id]->Close();
shutdown_threads[core_id] = nullptr;
}
schedulers[core_id]->Finalize();
@ -238,26 +238,27 @@ struct KernelCore::Impl {
void InitializePreemption(KernelCore& kernel) {
preemption_event = Core::Timing::CreateEvent(
"PreemptionCallback", [this, &kernel](std::uintptr_t, std::chrono::nanoseconds) {
"PreemptionCallback",
[this, &kernel](std::uintptr_t, s64 time,
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
{
KScopedSchedulerLock lock(kernel);
global_scheduler_context->PreemptThreads();
}
const auto time_interval = std::chrono::nanoseconds{std::chrono::milliseconds(10)};
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
return std::nullopt;
});
const auto time_interval = std::chrono::nanoseconds{std::chrono::milliseconds(10)};
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
system.CoreTiming().ScheduleLoopingEvent(time_interval, time_interval, preemption_event);
}
void InitializeSuspendThreads() {
void InitializeShutdownThreads() {
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
suspend_threads[core_id] = KThread::Create(system.Kernel());
ASSERT(KThread::InitializeHighPriorityThread(system, suspend_threads[core_id], {}, {},
shutdown_threads[core_id] = KThread::Create(system.Kernel());
ASSERT(KThread::InitializeHighPriorityThread(system, shutdown_threads[core_id], {}, {},
core_id)
.IsSuccess());
suspend_threads[core_id]->SetName(fmt::format("SuspendThread:{}", core_id));
shutdown_threads[core_id]->SetName(fmt::format("SuspendThread:{}", core_id));
}
}
@ -779,7 +780,7 @@ struct KernelCore::Impl {
std::weak_ptr<ServiceThread> default_service_thread;
Common::ThreadWorker service_threads_manager;
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> suspend_threads;
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> shutdown_threads;
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
@ -1093,16 +1094,27 @@ const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const {
void KernelCore::Suspend(bool suspended) {
const bool should_suspend{exception_exited || suspended};
const auto state{should_suspend ? ThreadState::Runnable : ThreadState::Waiting};
{
KScopedSchedulerLock lk{*this};
for (auto* thread : impl->suspend_threads) {
thread->SetState(state);
thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Suspended);
const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable;
for (auto* process : GetProcessList()) {
process->SetActivity(activity);
if (should_suspend) {
// Wait for execution to stop
for (auto* thread : process->GetThreadList()) {
thread->WaitUntilSuspended();
}
}
}
}
void KernelCore::ShutdownCores() {
for (auto* thread : impl->shutdown_threads) {
void(thread->Run());
}
InterruptAllPhysicalCores();
}
bool KernelCore::IsMulticore() const {
return impl->is_multicore;
}

View File

@ -283,6 +283,9 @@ public:
/// Exceptional exit all processes.
void ExceptionalExit();
/// Notify emulated CPU cores to shut down.
void ShutdownCores();
bool IsMulticore() const;
bool IsShuttingDown() const;

View File

@ -11,15 +11,17 @@
namespace Kernel {
TimeManager::TimeManager(Core::System& system_) : system{system_} {
time_manager_event_type =
Core::Timing::CreateEvent("Kernel::TimeManagerCallback",
[this](std::uintptr_t thread_handle, std::chrono::nanoseconds) {
KThread* thread = reinterpret_cast<KThread*>(thread_handle);
{
KScopedSchedulerLock sl(system.Kernel());
thread->OnTimer();
}
});
time_manager_event_type = Core::Timing::CreateEvent(
"Kernel::TimeManagerCallback",
[this](std::uintptr_t thread_handle, s64 time,
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
KThread* thread = reinterpret_cast<KThread*>(thread_handle);
{
KScopedSchedulerLock sl(system.Kernel());
thread->OnTimer();
}
return std::nullopt;
});
}
void TimeManager::ScheduleTimeEvent(KThread* thread, s64 nanoseconds) {

View File

@ -74,26 +74,34 @@ IAppletResource::IAppletResource(Core::System& system_,
// Register update callbacks
pad_update_event = Core::Timing::CreateEvent(
"HID::UpdatePadCallback",
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
[this](std::uintptr_t user_data, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
UpdateControllers(user_data, ns_late);
return std::nullopt;
});
mouse_keyboard_update_event = Core::Timing::CreateEvent(
"HID::UpdateMouseKeyboardCallback",
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
[this](std::uintptr_t user_data, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
UpdateMouseKeyboard(user_data, ns_late);
return std::nullopt;
});
motion_update_event = Core::Timing::CreateEvent(
"HID::UpdateMotionCallback",
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
[this](std::uintptr_t user_data, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
UpdateMotion(user_data, ns_late);
return std::nullopt;
});
system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event);
system.CoreTiming().ScheduleEvent(mouse_keyboard_update_ns, mouse_keyboard_update_event);
system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event);
system.CoreTiming().ScheduleLoopingEvent(pad_update_ns, pad_update_ns, pad_update_event);
system.CoreTiming().ScheduleLoopingEvent(mouse_keyboard_update_ns, mouse_keyboard_update_ns,
mouse_keyboard_update_event);
system.CoreTiming().ScheduleLoopingEvent(motion_update_ns, motion_update_ns,
motion_update_event);
system.HIDCore().ReloadInputDevices();
}
@ -135,13 +143,6 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data,
}
controller->OnUpdate(core_timing);
}
// If ns_late is higher than the update rate ignore the delay
if (ns_late > pad_update_ns) {
ns_late = {};
}
core_timing.ScheduleEvent(pad_update_ns - ns_late, pad_update_event);
}
void IAppletResource::UpdateMouseKeyboard(std::uintptr_t user_data,
@ -150,26 +151,12 @@ void IAppletResource::UpdateMouseKeyboard(std::uintptr_t user_data,
controllers[static_cast<size_t>(HidController::Mouse)]->OnUpdate(core_timing);
controllers[static_cast<size_t>(HidController::Keyboard)]->OnUpdate(core_timing);
// If ns_late is higher than the update rate ignore the delay
if (ns_late > mouse_keyboard_update_ns) {
ns_late = {};
}
core_timing.ScheduleEvent(mouse_keyboard_update_ns - ns_late, mouse_keyboard_update_event);
}
void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
controllers[static_cast<size_t>(HidController::NPad)]->OnMotionUpdate(core_timing);
// If ns_late is higher than the update rate ignore the delay
if (ns_late > motion_update_ns) {
ns_late = {};
}
core_timing.ScheduleEvent(motion_update_ns - ns_late, motion_update_event);
}
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {

View File

@ -50,12 +50,15 @@ HidBus::HidBus(Core::System& system_)
// Register update callbacks
hidbus_update_event = Core::Timing::CreateEvent(
"Hidbus::UpdateCallback",
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
[this](std::uintptr_t user_data, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto guard = LockService();
UpdateHidbus(user_data, ns_late);
return std::nullopt;
});
system_.CoreTiming().ScheduleEvent(hidbus_update_ns, hidbus_update_event);
system_.CoreTiming().ScheduleLoopingEvent(hidbus_update_ns, hidbus_update_ns,
hidbus_update_event);
}
HidBus::~HidBus() {
@ -63,8 +66,6 @@ HidBus::~HidBus() {
}
void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
if (is_hidbus_enabled) {
for (std::size_t i = 0; i < devices.size(); ++i) {
if (!devices[i].is_device_initializated) {
@ -82,13 +83,6 @@ void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_
sizeof(HidbusStatusManagerEntry));
}
}
// If ns_late is higher than the update rate ignore the delay
if (ns_late > hidbus_update_ns) {
ns_late = {};
}
core_timing.ScheduleEvent(hidbus_update_ns - ns_late, hidbus_update_event);
}
std::optional<std::size_t> HidBus::GetDeviceIndexFromHandle(BusHandle handle) const {

View File

@ -69,21 +69,20 @@ NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_dr
// Schedule the screen composition events
composition_event = Core::Timing::CreateEvent(
"ScreenComposition", [this](std::uintptr_t, std::chrono::nanoseconds ns_late) {
"ScreenComposition",
[this](std::uintptr_t, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
const auto lock_guard = Lock();
Compose();
const auto ticks = std::chrono::nanoseconds{GetNextTicks()};
const auto ticks_delta = ticks - ns_late;
const auto future_ns = std::max(std::chrono::nanoseconds::zero(), ticks_delta);
this->system.CoreTiming().ScheduleEvent(future_ns, composition_event);
return std::max(std::chrono::nanoseconds::zero(),
std::chrono::nanoseconds(GetNextTicks()) - ns_late);
});
if (system.IsMulticore()) {
vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); });
} else {
system.CoreTiming().ScheduleEvent(frame_ns, composition_event);
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, composition_event);
}
}

View File

@ -184,10 +184,12 @@ CheatEngine::~CheatEngine() {
void CheatEngine::Initialize() {
event = Core::Timing::CreateEvent(
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
[this](std::uintptr_t user_data, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
FrameCallback(user_data, ns_late);
return std::nullopt;
});
core_timing.ScheduleEvent(CHEAT_ENGINE_NS, event);
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event);
metadata.process_id = system.CurrentProcess()->GetProcessID();
metadata.title_id = system.GetCurrentProcessProgramID();
@ -237,8 +239,6 @@ void CheatEngine::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late
MICROPROFILE_SCOPE(Cheat_Engine);
vm.Execute(metadata);
core_timing.ScheduleEvent(CHEAT_ENGINE_NS - ns_late, event);
}
} // namespace Core::Memory

View File

@ -53,8 +53,10 @@ Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& m
: core_timing{core_timing_}, memory{memory_} {
event = Core::Timing::CreateEvent(
"MemoryFreezer::FrameCallback",
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
[this](std::uintptr_t user_data, s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
FrameCallback(user_data, ns_late);
return std::nullopt;
});
core_timing.ScheduleEvent(memory_freezer_ns, event);
}

View File

@ -9,6 +9,7 @@
#include <cstdlib>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include "core/core.h"
@ -25,13 +26,15 @@ u64 expected_callback = 0;
std::mutex control_mutex;
template <unsigned int IDX>
void HostCallbackTemplate(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
std::optional<std::chrono::nanoseconds> HostCallbackTemplate(std::uintptr_t user_data, s64 time,
std::chrono::nanoseconds ns_late) {
std::unique_lock<std::mutex> lk(control_mutex);
static_assert(IDX < CB_IDS.size(), "IDX out of range");
callbacks_ran_flags.set(IDX);
REQUIRE(CB_IDS[IDX] == user_data);
delays[IDX] = ns_late.count();
++expected_callback;
return std::nullopt;
}
struct ScopeInit final {