diff --git a/README.md b/README.md index 366e32c20..9c5657d91 100755 --- a/README.md +++ b/README.md @@ -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 diff --git a/externals/cubeb/.github/workflows/build.yml b/externals/cubeb/.github/workflows/build.yml index a5f533b8b..e10744f49 100755 --- a/externals/cubeb/.github/workflows/build.yml +++ b/externals/cubeb/.github/workflows/build.yml @@ -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 diff --git a/externals/cubeb/INSTALL.md b/externals/cubeb/INSTALL.md index f4229c051..4e941b9f8 100755 --- a/externals/cubeb/INSTALL.md +++ b/externals/cubeb/INSTALL.md @@ -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. diff --git a/externals/cubeb/README.md b/externals/cubeb/README.md index 92df4f22c..e4e165882 100755 --- a/externals/cubeb/README.md +++ b/externals/cubeb/README.md @@ -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. diff --git a/externals/cubeb/src/cubeb_audiounit.cpp b/externals/cubeb/src/cubeb_audiounit.cpp index 8f2741ace..37036a3f2 100755 --- a/externals/cubeb/src/cubeb_audiounit.cpp +++ b/externals/cubeb/src/cubeb_audiounit.cpp @@ -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 diff --git a/externals/cubeb/src/cubeb_log.cpp b/externals/cubeb/src/cubeb_log.cpp index ff72e0e87..bd34af166 100755 --- a/externals/cubeb/src/cubeb_log.cpp +++ b/externals/cubeb/src/cubeb_log.cpp @@ -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; diff --git a/externals/cubeb/src/cubeb_log.h b/externals/cubeb/src/cubeb_log.h index aee31805e..4380da439 100755 --- a/externals/cubeb/src/cubeb_log.h +++ b/externals/cubeb/src/cubeb_log.h @@ -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 } diff --git a/externals/cubeb/src/cubeb_pulse.c b/externals/cubeb/src/cubeb_pulse.c index 3ed6bdc52..13f679164 100755 --- a/externals/cubeb/src/cubeb_pulse.c +++ b/externals/cubeb/src/cubeb_pulse.c @@ -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; } } diff --git a/externals/cubeb/src/cubeb_ringbuffer.h b/externals/cubeb/src/cubeb_ringbuffer.h index 28381849f..f02035156 100755 --- a/externals/cubeb/src/cubeb_ringbuffer.h +++ b/externals/cubeb/src/cubeb_ringbuffer.h @@ -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)); } /** diff --git a/externals/cubeb/src/cubeb_wasapi.cpp b/externals/cubeb/src/cubeb_wasapi.cpp index 90e195066..039ad76c0 100755 --- a/externals/cubeb/src/cubeb_wasapi.cpp +++ b/externals/cubeb/src/cubeb_wasapi.cpp @@ -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 *> 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 emergency_bailout{false}; /* This needs an active audio input stream to be known, and is updated in the * first audio input callback. */ std::atomic 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(stm->total_output_frames) - stm->total_input_frames, static_cast(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(stream); - std::atomic * 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(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(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 enumerator; com_ptr 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) diff --git a/externals/cubeb/test/test_callback_ret.cpp b/externals/cubeb/test/test_callback_ret.cpp index 517d847df..7ce33d061 100755 --- a/externals/cubeb/test/test_callback_ret.cpp +++ b/externals/cubeb/test/test_callback_ret.cpp @@ -34,6 +34,7 @@ enum test_direction { struct user_state_callback_ret { std::atomic cb_count{ 0 }; std::atomic expected_cb_count{ 0 }; + std::atomic 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"); } diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index cf7e763e6..78e615a10 100755 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -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 diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index fd1e43356..0f7d61ee4 100755 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -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 input_sink; /// The ADSP in the sysmodule std::unique_ptr adsp; + /// Current size of the stream queue + std::atomic estimated_queue{0}; }; } // namespace AudioCore diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp index a9e9acd90..4aadb7fd6 100755 --- a/src/audio_core/audio_in_manager.cpp +++ b/src/audio_core/audio_in_manager.cpp @@ -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 1) { + names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac")); + return 1; + } + return 0; } } // namespace AudioCore::AudioIn diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp index 9b211116d..095fc96ce 100755 --- a/src/audio_core/device/device_session.cpp +++ b/src/audio_core/device/device_session.cpp @@ -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() { diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h index 24a02f41f..4a031b765 100755 --- a/src/audio_core/device/device_session.h +++ b/src/audio_core/device/device_session.h @@ -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 diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp index 85112fcd7..53f32f8cd 100755 --- a/src/audio_core/renderer/adsp/audio_renderer.cpp +++ b/src/audio_core/renderer/adsp/audio_renderer.cpp @@ -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()}; diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h index 3b2abd437..b6ced9d2b 100755 --- a/src/audio_core/renderer/adsp/audio_renderer.h +++ b/src/audio_core/renderer/adsp/audio_renderer.h @@ -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 streams; - /// An event signalled from the backend when a buffer is consumed, used for timing. - Common::Event render_event{}; }; } // namespace AudioRenderer::ADSP diff --git a/src/audio_core/renderer/command/effect/light_limiter.cpp b/src/audio_core/renderer/command/effect/light_limiter.cpp index 676dbec59..604e65299 100755 --- a/src/audio_core/renderer/command/effect/light_limiter.cpp +++ b/src/audio_core/renderer/command/effect/light_limiter.cpp @@ -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(std::clamp( - (lookahead_sample * state.compression_gain[channel] * params.output_gain) - .to_long(), - min, max)); + outputs[channel][sample_index] = static_cast( + 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] = diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp index 098e58a81..b326819ed 100755 --- a/src/audio_core/renderer/system_manager.cpp +++ b/src/audio_core/renderer/system_manager.cpp @@ -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 SystemManager::ThreadFunc2(s64 time) { + std::optional 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 diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h index 2f7964a1d..1291e9e0e 100755 --- a/src/audio_core/renderer/system_manager.h +++ b/src/audio_core/renderer/system_manager.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "audio_core/renderer/system.h" @@ -70,7 +71,20 @@ private: /** * Signalling core timing thread to run ThreadFunc. */ - void ThreadFunc2(); + std::optional 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 thread_event; /// Atomic for main thread to wait on std::atomic update{}; + /// Current state of the streams + StreamState state{StreamState::Filling}; }; } // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp index 56021a31a..7486d726e 100755 --- a/src/audio_core/sink/cubeb_sink.cpp +++ b/src/audio_core/sink/cubeb_sink.cpp @@ -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 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(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( + ctx, device_channels, system_channels, output_device, input_device, name, type, system)); return stream.get(); } diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h index aebdd2fc2..f0f43dfa1 100755 --- a/src/audio_core/sink/cubeb_sink.h +++ b/src/audio_core/sink/cubeb_sink.h @@ -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. diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h index 340db311e..47a342171 100755 --- a/src/audio_core/sink/null_sink.h +++ b/src/audio_core/sink/null_sink.h @@ -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; } diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp index 3ad9fab56..c28e6bdb9 100755 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl2_sink.cpp @@ -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 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( - device_channels, system_channels, output_device, input_device, type, system, event)); + device_channels, system_channels, output_device, input_device, type, system)); return stream.get(); } diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h index 953807cbf..186bc2fa3 100755 --- a/src/audio_core/sink/sdl2_sink.h +++ b/src/audio_core/sink/sdl2_sink.h @@ -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. diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h index d6b31e270..91fe455e4 100755 --- a/src/audio_core/sink/sink.h +++ b/src/audio_core/sink/sink.h @@ -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. diff --git a/src/core/core.cpp b/src/core/core.cpp index 9709131cc..7aff3f3e9 100755 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -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 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(); diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index d394573bb..5425637f5 100755 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -22,11 +22,11 @@ std::shared_ptr CreateEvent(std::string name, TimedCallback&& callbac } struct CoreTiming::Event { - u64 time; + s64 time; u64 fifo_order; std::uintptr_t user_data; std::weak_ptr 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&& 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 { 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& 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((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& event_type, - std::uintptr_t user_data) { - std::unique_lock main_lock(event_mutex); - const u64 timeout = static_cast(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& 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((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(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& event_type) { } } +void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) { + std::unique_lock main_lock(event_mutex); + pause_callbacks.emplace_back(std::move(callback)); +} + std::optional CoreTiming::Advance() { global_timer = GetGlobalTimeNs().count(); @@ -257,21 +265,31 @@ std::optional CoreTiming::Advance() { event_queue.pop_back(); if (const auto event_type{evt.type.lock()}) { - event_mutex.unlock(); - const s64 delay = static_cast(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(); diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 4500dccdf..09b6ed81a 100755 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -20,8 +20,9 @@ namespace Core::Timing { /// A callback that may be scheduled for a particular core timing event. -using TimedCallback = - std::function; +using TimedCallback = std::function( + std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>; +using PauseCallback = std::function; /// 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& 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& event_type, - std::uintptr_t user_data = 0); + const std::shared_ptr& 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& event_type, - std::uintptr_t user_data = 0); + std::uintptr_t user_data = 0, bool absolute_time = false); void UnscheduleEvent(const std::shared_ptr& 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 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 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 pause_callbacks{}; }; /// Creates a core timing event with the given name and callback. diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index d3d5cc009..37d3d83b9 100755 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -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(num_cores + 1); - pause_barrier = std::make_unique(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 diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h index 033a1f3d6..76dc58ee1 100755 --- a/src/core/cpu_manager.h +++ b/src/core/cpu_manager.h @@ -50,16 +50,14 @@ public: void Initialize(); void Shutdown(); - void Pause(bool paused); - std::function GetGuestThreadStartFunc() { return [this] { GuestThreadFunction(); }; } std::function GetIdleThreadStartFunc() { return [this] { IdleThreadFunction(); }; } - std::function GetSuspendThreadStartFunc() { - return [this] { SuspendThread(); }; + std::function 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 running_mode{}; - std::atomic pause_state{}; - std::unique_ptr pause_barrier{}; std::unique_ptr gpu_barrier{}; - std::mutex pause_lock{}; - std::array core_data{}; bool is_async_gpu{}; diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 9e294e81f..50cb5fc90 100755 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -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(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); diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index d2bc38518..28cd7ecb0 100755 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -208,6 +208,8 @@ public: void Continue(); + void WaitUntilSuspended(); + constexpr void SetSyncedIndex(s32 index) { synced_index = index; } diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 42f321e2b..f23c629dc 100755 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -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 { { 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 default_service_thread; Common::ThreadWorker service_threads_manager; - std::array suspend_threads; + std::array shutdown_threads; std::array interrupts{}; std::array, 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; } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 7ecb708f4..6c7cf6af2 100755 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -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; diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp index 2724c3782..5ee72c432 100755 --- a/src/core/hle/kernel/time_manager.cpp +++ b/src/core/hle/kernel/time_manager.cpp @@ -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(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 { + KThread* thread = reinterpret_cast(thread_handle); + { + KScopedSchedulerLock sl(system.Kernel()); + thread->OnTimer(); + } + return std::nullopt; + }); } void TimeManager::ScheduleTimeEvent(KThread* thread, s64 nanoseconds) { diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 78efffc50..89bb12442 100755 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -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 { 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 { 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 { 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(HidController::Mouse)]->OnUpdate(core_timing); controllers[static_cast(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(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 { diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp index fa6153b4c..e5e50845f 100755 --- a/src/core/hle/service/hid/hidbus.cpp +++ b/src/core/hle/service/hid/hidbus.cpp @@ -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 { 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 HidBus::GetDeviceIndexFromHandle(BusHandle handle) const { diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 747efb063..2aef80d29 100755 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -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 { 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); } } diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index 5f71f0ff5..ffdbacc18 100755 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp @@ -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 { 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 diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp index 5cc99fbe4..98ebbbf32 100755 --- a/src/core/tools/freezer.cpp +++ b/src/core/tools/freezer.cpp @@ -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 { FrameCallback(user_data, ns_late); + return std::nullopt; }); core_timing.ScheduleEvent(memory_freezer_ns, event); } diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp index e687416a8..894975e6f 100755 --- a/src/tests/core/core_timing.cpp +++ b/src/tests/core/core_timing.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "core/core.h" @@ -25,13 +26,15 @@ u64 expected_callback = 0; std::mutex control_mutex; template -void HostCallbackTemplate(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { +std::optional HostCallbackTemplate(std::uintptr_t user_data, s64 time, + std::chrono::nanoseconds ns_late) { std::unique_lock 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 {