early-access version 2824

This commit is contained in:
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 yuzu emulator early access
============= =============
This is the source code for early-access 2823. This is the source code for early-access 2824.
## Legal Notice ## Legal Notice

View file

@ -9,11 +9,11 @@ jobs:
BUILD_TYPE: ${{ matrix.type }} BUILD_TYPE: ${{ matrix.type }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, windows-2019, macos-10.15] os: [ubuntu-20.04, windows-2019, macos-11]
type: [Release, Debug] type: [Release, Debug]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
@ -46,13 +46,13 @@ jobs:
matrix: matrix:
type: [Release, Debug] type: [Release, Debug]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- name: Configure CMake - name: Configure CMake
shell: bash 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 - name: Build
shell: bash shell: bash
@ -61,7 +61,7 @@ jobs:
check_format: check_format:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
with: with:
submodules: true 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` 1. `git clone --recursive https://github.com/mozilla/cubeb.git`
2. `mkdir cubeb-build` 2. `mkdir cubeb-build`
3. `cd cubeb-build` 3. `cd cubeb-build`
3. `cmake ../cubeb` 4. `cmake ../cubeb`
4. `cmake --build .` 5. `cmake --build .`
5. `ctest` 6. `ctest`
# Windows build notes # 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 MinGW-w64 with Win32 threads.
- Download and install CMake. - Download and install CMake.
- Run MinGW-w64 Terminal from the Start Menu. - Run MinGW-w64 Terminal from the Start Menu.
- Follow the build steps at the top of this file, but at step 3 run: - Follow the build steps at the top of this file, but at step 4 run:
`cmake -G "MinGW Makefiles" ..` `cmake -G "MinGW Makefiles" ../cubeb`
- Continue the build steps at the top of this file. - Continue the build steps at the top of this file.

View file

@ -2,6 +2,6 @@
See INSTALL.md for build instructions. 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. 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(), long outframes = cubeb_resampler_fill(stm->resampler.get(),
stm->input_linear_buffer->data(), stm->input_linear_buffer->data(),
&total_input_frames, NULL, 0); &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; stm->draining = outframes < total_input_frames;
// Reset input buffer // Reset input buffer

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,6 +34,7 @@ enum test_direction {
struct user_state_callback_ret { struct user_state_callback_ret {
std::atomic<int> cb_count{ 0 }; std::atomic<int> cb_count{ 0 };
std::atomic<int> expected_cb_count{ 0 }; std::atomic<int> expected_cb_count{ 0 };
std::atomic<int> error_state{ 0 };
}; };
// Data callback that always returns 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; 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) if (stream == NULL)
return; return;
user_state_callback_ret * u = (user_state_callback_ret *)user;
switch (state) { switch (state) {
case CUBEB_STATE_STARTED: 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; fprintf(stderr, "stream stopped\n"); break;
case CUBEB_STATE_DRAINED: case CUBEB_STATE_DRAINED:
fprintf(stderr, "stream drained\n"); break; fprintf(stderr, "stream drained\n"); break;
case CUBEB_STATE_ERROR:
fprintf(stderr, "stream error\n");
u->error_state.fetch_add(1);
break;
default: default:
fprintf(stderr, "unknown stream state %d\n", state); fprintf(stderr, "unknown stream state %d\n", state);
} }
return;
} }
void run_test_callback(test_direction direction, 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) << ASSERT_EQ(user_state.expected_cb_count, user_state.cb_count) <<
"Callback called unexpected number of times for " << test_desc << "!"; "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) 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_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_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_nframes, "input only, return nframes");
run_test_callback(INPUT_ONLY, data_cb_ret_error,
"input only, return CUBEB_ERROR");
} }
TEST(cubeb, test_output_callback) 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_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_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_nframes, "output only, return nframes");
run_test_callback(OUTPUT_ONLY, data_cb_ret_error,
"output only, return CUBEB_ERROR");
} }
TEST(cubeb, test_duplex_callback) 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_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_minus_one, "duplex, return nframes - 1");
run_test_callback(DUPLEX, data_cb_ret_nframes, "duplex, return nframes"); 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 } // namespace AudioCore

View file

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

View file

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

View file

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

View file

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

View file

@ -128,8 +128,8 @@ void AudioRenderer::CreateSinkStreams() {
u32 channels{sink.GetDeviceChannels()}; u32 channels{sink.GetDeviceChannels()};
for (u32 i = 0; i < MaxRendererSessions; i++) { for (u32 i = 0; i < MaxRendererSessions; i++) {
std::string name{fmt::format("ADSP_RenderStream-{}", i)}; std::string name{fmt::format("ADSP_RenderStream-{}", i)};
streams[i] = sink.AcquireSinkStream(system, channels, name, streams[i] =
::AudioCore::Sink::StreamType::Render, &render_event); sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
streams[i]->SetSystemChannels(streams[i]->GetDeviceChannels()); streams[i]->SetSystemChannels(streams[i]->GetDeviceChannels());
} }
} }
@ -199,12 +199,9 @@ void AudioRenderer::ThreadFunc() {
command_list_processor.Process(index) - start_time; command_list_processor.Process(index) - start_time;
} }
// If the stream queue is building up too much, wait for a signal if (index == 0) {
// 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()}; auto stream{command_list_processor.GetOutputSinkStream()};
if (stream->GetQueueSize() >= 4) { system.AudioCore().SetStreamQueue(stream->GetQueueSize());
render_event.WaitFor(std::chrono::milliseconds(5));
} }
const auto end_time{system.CoreTiming().GetClockTicks()}; const auto end_time{system.CoreTiming().GetClockTicks()};

View file

@ -14,8 +14,11 @@
#include "common/thread.h" #include "common/thread.h"
namespace Core { namespace Core {
class System; namespace Timing {
struct EventType;
} }
class System;
} // namespace Core
namespace AudioCore { namespace AudioCore {
namespace Sink { namespace Sink {
@ -194,8 +197,6 @@ private:
Sink::Sink& sink; Sink::Sink& sink;
/// The streams which will receive the processed samples /// The streams which will receive the processed samples
std::array<Sink::SinkStream*, MaxRendererSessions> streams; 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 } // 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 sample_index = 0; sample_index < sample_count; sample_index++) {
for (u32 channel = 0; channel < params.channel_count; channel++) { 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}; params.input_gain};
auto abs_sample{sample}; auto abs_sample{sample};
if (sample < 0.0f) { if (sample < 0.0f) {
@ -85,13 +86,15 @@ static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& p
auto lookahead_sample{ auto lookahead_sample{
state.look_ahead_sample_buffers[channel] state.look_ahead_sample_buffers[channel]
[state.look_ahead_sample_offsets[channel]]}; [state.look_ahead_sample_offsets[channel]]};
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; sample;
state.look_ahead_sample_offsets[channel] = state.look_ahead_sample_offsets[channel] =
(state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min; (state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min;
outputs[channel][sample_index] = static_cast<s32>(std::clamp( outputs[channel][sample_index] = static_cast<s32>(
(lookahead_sample * state.compression_gain[channel] * params.output_gain) std::clamp((lookahead_sample * state.compression_gain[channel] *
params.output_gain * Common::FixedPoint<49, 15>::one)
.to_long(), .to_long(),
min, max)); min, max));

View file

@ -15,12 +15,17 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
MP_RGB(60, 19, 97)); MP_RGB(60, 19, 97));
namespace AudioCore::AudioRenderer { namespace AudioCore::AudioRenderer {
constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL};
constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL};
SystemManager::SystemManager(Core::System& core_) SystemManager::SystemManager(Core::System& core_)
: core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
thread_event{Core::Timing::CreateEvent( thread_event{Core::Timing::CreateEvent(
"AudioRendererSystemManager", "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
[this](std::uintptr_t userdata, std::chrono::nanoseconds ns_late) { ThreadFunc2(); })} {} return ThreadFunc2(time);
})} {
core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); });
}
SystemManager::~SystemManager() { SystemManager::~SystemManager() {
Stop(); Stop();
@ -30,9 +35,9 @@ bool SystemManager::InitializeUnsafe() {
if (!active) { if (!active) {
if (adsp.Start()) { if (adsp.Start()) {
active = true; active = true;
core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(2'304'000ULL * 2),
thread_event);
thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); 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"}; constexpr char name[]{"yuzu:AudioRenderSystemManager"};
MicroProfileOnThreadCreate(name); MicroProfileOnThreadCreate(name);
Common::SetCurrentThreadName(name); Common::SetCurrentThreadName(name);
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
while (active) { while (active) {
{ {
std::scoped_lock l{mutex1}; std::scoped_lock l{mutex1};
MICROPROFILE_SCOPE(Audio_RenderSystemManager); MICROPROFILE_SCOPE(Audio_RenderSystemManager);
for (auto system : systems) { for (auto system : systems) {
system->SendCommandToDsp(); 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.store(true);
update.notify_all(); update.notify_all();
} }
}
} // namespace AudioCore::AudioRenderer } // namespace AudioCore::AudioRenderer

View file

@ -6,6 +6,7 @@
#include <list> #include <list>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <optional>
#include <thread> #include <thread>
#include "audio_core/renderer/system.h" #include "audio_core/renderer/system.h"
@ -70,7 +71,20 @@ private:
/** /**
* Signalling core timing thread to run ThreadFunc. * 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::System& core; Core::System& core;
@ -92,6 +106,8 @@ private:
std::shared_ptr<Core::Timing::EventType> thread_event; std::shared_ptr<Core::Timing::EventType> thread_event;
/// Atomic for main thread to wait on /// Atomic for main thread to wait on
std::atomic<bool> update{}; std::atomic<bool> update{};
/// Current state of the streams
StreamState state{StreamState::Filling};
}; };
} // namespace AudioCore::AudioRenderer } // namespace AudioCore::AudioRenderer

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -22,11 +22,11 @@ std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callbac
} }
struct CoreTiming::Event { struct CoreTiming::Event {
u64 time; s64 time;
u64 fifo_order; u64 fifo_order;
std::uintptr_t user_data; std::uintptr_t user_data;
std::weak_ptr<EventType> type; 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 // Sort by time, unless the times are the same, in which case sort by
// the order added to the queue // the order added to the queue
@ -59,7 +59,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
event_fifo_id = 0; event_fifo_id = 0;
shutting_down = false; shutting_down = false;
ticks = 0; 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); ev_lost = CreateEvent("_lost_event", empty_timed_callback);
if (is_multicore) { if (is_multicore) {
worker_threads.emplace_back(ThreadEntry, std::ref(*this), 0); worker_threads.emplace_back(ThreadEntry, std::ref(*this), 0);
@ -77,6 +78,7 @@ void CoreTiming::Shutdown() {
thread.join(); thread.join();
} }
worker_threads.clear(); worker_threads.clear();
pause_callbacks.clear();
ClearPendingEvents(); ClearPendingEvents();
has_started = false; has_started = false;
} }
@ -94,6 +96,14 @@ void CoreTiming::Pause(bool is_paused_) {
} }
} }
paused_state.store(is_paused_, std::memory_order_relaxed); 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_) { 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; }); 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 { bool CoreTiming::IsRunning() const {
@ -130,12 +148,12 @@ bool CoreTiming::HasPendingEvents() const {
void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future, void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
const std::shared_ptr<EventType>& event_type, 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); 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); pending_events.fetch_add(1, std::memory_order_relaxed);
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); 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, void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
std::chrono::nanoseconds resched_time,
const std::shared_ptr<EventType>& event_type, 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); std::unique_lock main_lock(event_mutex);
const u64 timeout = static_cast<u64>(next_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, 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,
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>((GetGlobalTimeNs() + time).count());
event_queue.emplace_back( 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); pending_events.fetch_add(1, std::memory_order_relaxed);
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); 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() { std::optional<s64> CoreTiming::Advance() {
global_timer = GetGlobalTimeNs().count(); global_timer = GetGlobalTimeNs().count();
@ -257,22 +265,32 @@ std::optional<s64> CoreTiming::Advance() {
event_queue.pop_back(); event_queue.pop_back();
if (const auto event_type{evt.type.lock()}) { if (const auto event_type{evt.type.lock()}) {
event_mutex.unlock(); event_mutex.unlock();
const s64 delay = static_cast<s64>(GetGlobalTimeNs().count() - evt.time); const auto new_schedule_time{event_type->callback(
event_type->callback(evt.user_data, std::chrono::nanoseconds{delay}); evt.user_data, evt.time,
std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
event_mutex.lock(); event_mutex.lock();
pending_events.fetch_sub(1, std::memory_order_relaxed); pending_events.fetch_sub(1, std::memory_order_relaxed);
}
if (evt.reschedule_time != 0) { 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_queue.emplace_back(
Event{global_timer + evt.reschedule_time - (global_timer - evt.time), Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
event_fifo_id++, evt.user_data, evt.type, evt.reschedule_time}); pending_events.fetch_add(1, std::memory_order_relaxed);
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
} }
}
global_timer = GetGlobalTimeNs().count(); global_timer = GetGlobalTimeNs().count();
} }

View file

@ -20,8 +20,9 @@
namespace Core::Timing { namespace Core::Timing {
/// A callback that may be scheduled for a particular core timing event. /// A callback that may be scheduled for a particular core timing event.
using TimedCallback = using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
std::function<void(std::uintptr_t user_data, std::chrono::nanoseconds ns_late)>; 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. /// Contains the characteristics of a particular event.
struct EventType { struct EventType {
@ -93,18 +94,15 @@ public:
/// Schedules an event in core timing /// Schedules an event in core timing
void ScheduleEvent(std::chrono::nanoseconds ns_into_future, void ScheduleEvent(std::chrono::nanoseconds ns_into_future,
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 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);
/// Schedules an event which will automatically re-schedule itself with the given time, until /// Schedules an event which will automatically re-schedule itself with the given time, until
/// unscheduled /// 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, 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); 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. /// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
std::optional<s64> Advance(); std::optional<s64> Advance();
/// Register a callback function to be called when coretiming pauses.
void RegisterPauseCallback(PauseCallback&& callback);
private: private:
struct Event; struct Event;
@ -147,7 +148,7 @@ private:
std::unique_ptr<Common::WallClock> clock; 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. // 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 // 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 shutting_down{};
bool is_multicore{}; bool is_multicore{};
size_t pause_count{}; size_t pause_count{};
s64 pause_end_time{};
/// Cycle timing /// Cycle timing
u64 ticks{}; u64 ticks{};
s64 downcount{}; s64 downcount{};
std::vector<PauseCallback> pause_callbacks{};
}; };
/// Creates a core timing event with the given name and callback. /// 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() { void CpuManager::Initialize() {
running_mode = true;
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1; num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
gpu_barrier = std::make_unique<Common::Barrier>(num_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++) { for (std::size_t core = 0; core < num_cores; core++) {
core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core); core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core);
@ -36,8 +34,11 @@ void CpuManager::Initialize() {
} }
void CpuManager::Shutdown() { void CpuManager::Shutdown() {
running_mode = false; for (std::size_t core = 0; core < num_cores; core++) {
Pause(false); if (core_data[core].host_thread.joinable()) {
core_data[core].host_thread.join();
}
}
} }
void CpuManager::GuestThreadFunction() { void CpuManager::GuestThreadFunction() {
@ -64,6 +65,10 @@ void CpuManager::IdleThreadFunction() {
} }
} }
void CpuManager::ShutdownThreadFunction() {
ShutdownThread();
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
/// MultiCore /// /// MultiCore ///
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -176,41 +181,13 @@ void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
} }
} }
void CpuManager::SuspendThread() { void CpuManager::ShutdownThread() {
auto& kernel = system.Kernel(); auto& kernel = system.Kernel();
kernel.CurrentScheduler()->OnThreadStart();
while (true) {
auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0; auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0;
auto& scheduler = *kernel.CurrentScheduler(); auto* current_thread = kernel.GetCurrentEmuThread();
Kernel::KThread* current_thread = scheduler.GetSchedulerCurrentThread();
Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context); Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context);
UNREACHABLE();
// 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();
} }
void CpuManager::RunThread(std::size_t core) { void CpuManager::RunThread(std::size_t core) {
@ -241,27 +218,9 @@ void CpuManager::RunThread(std::size_t core) {
system.GPU().ObtainContext(); system.GPU().ObtainContext();
} }
{
// Set the current thread on entry
auto* current_thread = system.Kernel().CurrentScheduler()->GetIdleThread(); auto* current_thread = system.Kernel().CurrentScheduler()->GetIdleThread();
Kernel::SetCurrentThread(system.Kernel(), current_thread); 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()); Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext());
} }
}
} // namespace Core } // namespace Core

View file

@ -50,16 +50,14 @@ public:
void Initialize(); void Initialize();
void Shutdown(); void Shutdown();
void Pause(bool paused);
std::function<void()> GetGuestThreadStartFunc() { std::function<void()> GetGuestThreadStartFunc() {
return [this] { GuestThreadFunction(); }; return [this] { GuestThreadFunction(); };
} }
std::function<void()> GetIdleThreadStartFunc() { std::function<void()> GetIdleThreadStartFunc() {
return [this] { IdleThreadFunction(); }; return [this] { IdleThreadFunction(); };
} }
std::function<void()> GetSuspendThreadStartFunc() { std::function<void()> GetShutdownThreadStartFunc() {
return [this] { SuspendThread(); }; return [this] { ShutdownThreadFunction(); };
} }
void PreemptSingleCore(bool from_running_enviroment = true); void PreemptSingleCore(bool from_running_enviroment = true);
@ -72,6 +70,7 @@ private:
void GuestThreadFunction(); void GuestThreadFunction();
void GuestRewindFunction(); void GuestRewindFunction();
void IdleThreadFunction(); void IdleThreadFunction();
void ShutdownThreadFunction();
void MultiCoreRunGuestThread(); void MultiCoreRunGuestThread();
void MultiCoreRunGuestLoop(); void MultiCoreRunGuestLoop();
@ -83,7 +82,7 @@ private:
static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core); 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); void RunThread(std::size_t core);
struct CoreData { struct CoreData {
@ -91,12 +90,7 @@ private:
std::jthread host_thread; 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::unique_ptr<Common::Barrier> gpu_barrier{};
std::mutex pause_lock{};
std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{}; std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{};
bool is_async_gpu{}; 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, Result KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread,
KThreadFunction func, uintptr_t arg, s32 virt_core) { KThreadFunction func, uintptr_t arg, s32 virt_core) {
return InitializeThread(thread, func, arg, {}, {}, virt_core, nullptr, ThreadType::HighPriority, 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, Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThreadFunction func,
@ -739,6 +739,19 @@ void KThread::Continue() {
KScheduler::OnThreadStateChanged(kernel, this, old_state); 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) { Result KThread::SetActivity(Svc::ThreadActivity activity) {
// Lock ourselves. // Lock ourselves.
KScopedLightLock lk(activity_pause_lock); KScopedLightLock lk(activity_pause_lock);

View file

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

View file

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

View file

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

View file

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

View file

@ -74,26 +74,34 @@ IAppletResource::IAppletResource(Core::System& system_,
// Register update callbacks // Register update callbacks
pad_update_event = Core::Timing::CreateEvent( pad_update_event = Core::Timing::CreateEvent(
"HID::UpdatePadCallback", "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(); const auto guard = LockService();
UpdateControllers(user_data, ns_late); UpdateControllers(user_data, ns_late);
return std::nullopt;
}); });
mouse_keyboard_update_event = Core::Timing::CreateEvent( mouse_keyboard_update_event = Core::Timing::CreateEvent(
"HID::UpdateMouseKeyboardCallback", "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(); const auto guard = LockService();
UpdateMouseKeyboard(user_data, ns_late); UpdateMouseKeyboard(user_data, ns_late);
return std::nullopt;
}); });
motion_update_event = Core::Timing::CreateEvent( motion_update_event = Core::Timing::CreateEvent(
"HID::UpdateMotionCallback", "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(); const auto guard = LockService();
UpdateMotion(user_data, ns_late); UpdateMotion(user_data, ns_late);
return std::nullopt;
}); });
system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event); system.CoreTiming().ScheduleLoopingEvent(pad_update_ns, pad_update_ns, pad_update_event);
system.CoreTiming().ScheduleEvent(mouse_keyboard_update_ns, mouse_keyboard_update_event); system.CoreTiming().ScheduleLoopingEvent(mouse_keyboard_update_ns, mouse_keyboard_update_ns,
system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event); mouse_keyboard_update_event);
system.CoreTiming().ScheduleLoopingEvent(motion_update_ns, motion_update_ns,
motion_update_event);
system.HIDCore().ReloadInputDevices(); system.HIDCore().ReloadInputDevices();
} }
@ -135,13 +143,6 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data,
} }
controller->OnUpdate(core_timing); 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, 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::Mouse)]->OnUpdate(core_timing);
controllers[static_cast<size_t>(HidController::Keyboard)]->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) { void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming(); auto& core_timing = system.CoreTiming();
controllers[static_cast<size_t>(HidController::NPad)]->OnMotionUpdate(core_timing); 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> { class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {

View file

@ -50,12 +50,15 @@ HidBus::HidBus(Core::System& system_)
// Register update callbacks // Register update callbacks
hidbus_update_event = Core::Timing::CreateEvent( hidbus_update_event = Core::Timing::CreateEvent(
"Hidbus::UpdateCallback", "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(); const auto guard = LockService();
UpdateHidbus(user_data, ns_late); 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() { HidBus::~HidBus() {
@ -63,8 +66,6 @@ HidBus::~HidBus() {
} }
void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
auto& core_timing = system.CoreTiming();
if (is_hidbus_enabled) { if (is_hidbus_enabled) {
for (std::size_t i = 0; i < devices.size(); ++i) { for (std::size_t i = 0; i < devices.size(); ++i) {
if (!devices[i].is_device_initializated) { if (!devices[i].is_device_initializated) {
@ -82,13 +83,6 @@ void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_
sizeof(HidbusStatusManagerEntry)); 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 { 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 // Schedule the screen composition events
composition_event = Core::Timing::CreateEvent( 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(); const auto lock_guard = Lock();
Compose(); Compose();
const auto ticks = std::chrono::nanoseconds{GetNextTicks()}; return std::max(std::chrono::nanoseconds::zero(),
const auto ticks_delta = ticks - ns_late; std::chrono::nanoseconds(GetNextTicks()) - ns_late);
const auto future_ns = std::max(std::chrono::nanoseconds::zero(), ticks_delta);
this->system.CoreTiming().ScheduleEvent(future_ns, composition_event);
}); });
if (system.IsMulticore()) { if (system.IsMulticore()) {
vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); });
} else { } 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() { void CheatEngine::Initialize() {
event = Core::Timing::CreateEvent( event = Core::Timing::CreateEvent(
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id), "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); 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.process_id = system.CurrentProcess()->GetProcessID();
metadata.title_id = system.GetCurrentProcessProgramID(); metadata.title_id = system.GetCurrentProcessProgramID();
@ -237,8 +239,6 @@ void CheatEngine::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late
MICROPROFILE_SCOPE(Cheat_Engine); MICROPROFILE_SCOPE(Cheat_Engine);
vm.Execute(metadata); vm.Execute(metadata);
core_timing.ScheduleEvent(CHEAT_ENGINE_NS - ns_late, event);
} }
} // namespace Core::Memory } // 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_} { : core_timing{core_timing_}, memory{memory_} {
event = Core::Timing::CreateEvent( event = Core::Timing::CreateEvent(
"MemoryFreezer::FrameCallback", "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); FrameCallback(user_data, ns_late);
return std::nullopt;
}); });
core_timing.ScheduleEvent(memory_freezer_ns, event); core_timing.ScheduleEvent(memory_freezer_ns, event);
} }

View file

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