early-access version 2824
This commit is contained in:
parent
da2f33c5e0
commit
29fab4f91a
43 changed files with 543 additions and 403 deletions
|
@ -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
|
||||
|
||||
|
|
10
externals/cubeb/.github/workflows/build.yml
vendored
10
externals/cubeb/.github/workflows/build.yml
vendored
|
@ -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
|
||||
|
||||
|
|
10
externals/cubeb/INSTALL.md
vendored
10
externals/cubeb/INSTALL.md
vendored
|
@ -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.
|
||||
|
|
2
externals/cubeb/README.md
vendored
2
externals/cubeb/README.md
vendored
|
@ -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.
|
||||
|
|
7
externals/cubeb/src/cubeb_audiounit.cpp
vendored
7
externals/cubeb/src/cubeb_audiounit.cpp
vendored
|
@ -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
|
||||
|
|
2
externals/cubeb/src/cubeb_log.cpp
vendored
2
externals/cubeb/src/cubeb_log.cpp
vendored
|
@ -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;
|
||||
|
|
2
externals/cubeb/src/cubeb_log.h
vendored
2
externals/cubeb/src/cubeb_log.h
vendored
|
@ -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
|
||||
}
|
||||
|
|
4
externals/cubeb/src/cubeb_pulse.c
vendored
4
externals/cubeb/src/cubeb_pulse.c
vendored
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
10
externals/cubeb/src/cubeb_ringbuffer.h
vendored
10
externals/cubeb/src/cubeb_ringbuffer.h
vendored
|
@ -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));
|
||||
}
|
||||
/**
|
||||
|
|
271
externals/cubeb/src/cubeb_wasapi.cpp
vendored
271
externals/cubeb/src/cubeb_wasapi.cpp
vendored
|
@ -240,8 +240,9 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
|
|||
void
|
||||
wasapi_destroy_device(cubeb_device_info * device_info);
|
||||
static int
|
||||
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * out);
|
||||
wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * out,
|
||||
DWORD state_mask);
|
||||
static int
|
||||
wasapi_device_collection_destroy(cubeb * ctx,
|
||||
cubeb_device_collection * collection);
|
||||
|
@ -409,12 +410,9 @@ struct cubeb_stream {
|
|||
float volume = 1.0;
|
||||
/* True if the stream is draining. */
|
||||
bool draining = false;
|
||||
/* True when we've destroyed the stream. This pointer is leaked on stream
|
||||
* destruction if we could not join the thread. */
|
||||
std::atomic<std::atomic<bool> *> emergency_bailout{nullptr};
|
||||
/* Synchronizes render thread start to ensure safe access to
|
||||
* emergency_bailout. */
|
||||
HANDLE thread_ready_event = 0;
|
||||
/* If the render thread fails to stop, this is set to true and ownership of
|
||||
* the stm is "leaked" to the render thread for later cleanup. */
|
||||
std::atomic<bool> emergency_bailout{false};
|
||||
/* This needs an active audio input stream to be known, and is updated in the
|
||||
* first audio input callback. */
|
||||
std::atomic<int64_t> input_latency_hns{LATENCY_NOT_AVAILABLE_YET};
|
||||
|
@ -753,6 +751,27 @@ private:
|
|||
|
||||
namespace {
|
||||
|
||||
long
|
||||
wasapi_data_callback(cubeb_stream * stm, void * user_ptr,
|
||||
void const * input_buffer, void * output_buffer,
|
||||
long nframes)
|
||||
{
|
||||
if (stm->emergency_bailout) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return stm->data_callback(stm, user_ptr, input_buffer, output_buffer,
|
||||
nframes);
|
||||
}
|
||||
|
||||
void
|
||||
wasapi_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state)
|
||||
{
|
||||
if (stm->emergency_bailout) {
|
||||
return;
|
||||
}
|
||||
return stm->state_callback(stm, user_ptr, state);
|
||||
}
|
||||
|
||||
char const *
|
||||
intern_device_id(cubeb * ctx, wchar_t const * id)
|
||||
{
|
||||
|
@ -873,8 +892,11 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
|
|||
long out_frames =
|
||||
cubeb_resampler_fill(stm->resampler.get(), input_buffer,
|
||||
&input_frames_count, dest, output_frames_needed);
|
||||
/* TODO: Report out_frames < 0 as an error via the API. */
|
||||
XASSERT(out_frames >= 0);
|
||||
if (out_frames < 0) {
|
||||
ALOGV("Callback refill error: %d", out_frames);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return out_frames;
|
||||
}
|
||||
|
||||
float volume = 1.0;
|
||||
{
|
||||
|
@ -991,7 +1013,7 @@ get_input_buffer(cubeb_stream * stm)
|
|||
(stm->input_stream_params.prefs &
|
||||
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
|
||||
!trigger_async_reconfigure(stm)) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -1105,7 +1127,7 @@ get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
|
|||
(stm->output_stream_params.prefs &
|
||||
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
|
||||
!trigger_async_reconfigure(stm)) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -1121,7 +1143,7 @@ get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
|
|||
if (stm->draining) {
|
||||
if (padding_out == 0) {
|
||||
LOG("Draining finished.");
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
return false;
|
||||
}
|
||||
LOG("Draining.");
|
||||
|
@ -1190,6 +1212,7 @@ refill_callback_duplex(cubeb_stream * stm)
|
|||
static_cast<long>(stm->total_output_frames) - stm->total_input_frames,
|
||||
static_cast<float>(stm->total_output_frames) / stm->total_input_frames);
|
||||
|
||||
long got;
|
||||
if (stm->has_dummy_output) {
|
||||
ALOGV(
|
||||
"Duplex callback (dummy output): input frames: %Iu, output frames: %Iu",
|
||||
|
@ -1197,13 +1220,15 @@ refill_callback_duplex(cubeb_stream * stm)
|
|||
|
||||
// We don't want to expose the dummy output to the callback so don't pass
|
||||
// the output buffer (it will be released later with silence in it)
|
||||
got =
|
||||
refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
|
||||
|
||||
} else {
|
||||
ALOGV("Duplex callback: input frames: %Iu, output frames: %Iu",
|
||||
input_frames, output_frames);
|
||||
|
||||
refill(stm, stm->linear_input_buffer->data(), input_frames, output_buffer,
|
||||
output_frames);
|
||||
got = refill(stm, stm->linear_input_buffer->data(), input_frames,
|
||||
output_buffer, output_frames);
|
||||
}
|
||||
|
||||
stm->linear_input_buffer->clear();
|
||||
|
@ -1219,6 +1244,9 @@ refill_callback_duplex(cubeb_stream * stm)
|
|||
LOG("failed to release buffer: %lx", hr);
|
||||
return false;
|
||||
}
|
||||
if (got < 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1245,8 +1273,9 @@ refill_callback_input(cubeb_stream * stm)
|
|||
|
||||
long read =
|
||||
refill(stm, stm->linear_input_buffer->data(), input_frames, nullptr, 0);
|
||||
|
||||
XASSERT(read >= 0);
|
||||
if (read < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
stm->linear_input_buffer->clear();
|
||||
|
||||
|
@ -1276,8 +1305,9 @@ refill_callback_output(cubeb_stream * stm)
|
|||
|
||||
ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
|
||||
got);
|
||||
|
||||
XASSERT(got >= 0);
|
||||
if (got < 0) {
|
||||
return false;
|
||||
}
|
||||
XASSERT(size_t(got) == output_frames || stm->draining);
|
||||
|
||||
hr = stm->render_client->ReleaseBuffer(got, 0);
|
||||
|
@ -1289,17 +1319,25 @@ refill_callback_output(cubeb_stream * stm)
|
|||
return size_t(got) == output_frames || stm->draining;
|
||||
}
|
||||
|
||||
void
|
||||
wasapi_stream_destroy(cubeb_stream * stm);
|
||||
|
||||
static void
|
||||
handle_emergency_bailout(cubeb_stream * stm)
|
||||
{
|
||||
if (stm->emergency_bailout) {
|
||||
CloseHandle(stm->thread);
|
||||
stm->thread = NULL;
|
||||
CloseHandle(stm->shutdown_event);
|
||||
stm->shutdown_event = 0;
|
||||
wasapi_stream_destroy(stm);
|
||||
_endthreadex(0);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
||||
{
|
||||
cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
|
||||
std::atomic<bool> * emergency_bailout = stm->emergency_bailout;
|
||||
|
||||
// Signal wasapi_stream_start that we've copied emergency_bailout.
|
||||
BOOL ok = SetEvent(stm->thread_ready_event);
|
||||
if (!ok) {
|
||||
LOG("thread_ready SetEvent failed: %lx", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool is_playing = true;
|
||||
HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event,
|
||||
|
@ -1332,20 +1370,10 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
|||
unsigned timeout_count = 0;
|
||||
const unsigned timeout_limit = 3;
|
||||
while (is_playing) {
|
||||
// We want to check the emergency bailout variable before a
|
||||
// and after the WaitForMultipleObject, because the handles
|
||||
// WaitForMultipleObjects is going to wait on might have been closed
|
||||
// already.
|
||||
if (*emergency_bailout) {
|
||||
delete emergency_bailout;
|
||||
return 0;
|
||||
}
|
||||
handle_emergency_bailout(stm);
|
||||
DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
|
||||
wait_array, FALSE, 1000);
|
||||
if (*emergency_bailout) {
|
||||
delete emergency_bailout;
|
||||
return 0;
|
||||
}
|
||||
handle_emergency_bailout(stm);
|
||||
if (waitResult != WAIT_TIMEOUT) {
|
||||
timeout_count = 0;
|
||||
}
|
||||
|
@ -1355,7 +1383,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
|||
/* We don't check if the drain is actually finished here, we just want to
|
||||
shutdown. */
|
||||
if (stm->draining) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -1417,7 +1445,8 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
|||
case WAIT_OBJECT_0 + 3: { /* input available */
|
||||
HRESULT rv = get_input_buffer(stm);
|
||||
if (FAILED(rv)) {
|
||||
return rv;
|
||||
is_playing = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!has_output(stm)) {
|
||||
|
@ -1436,18 +1465,29 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
|||
break;
|
||||
default:
|
||||
LOG("case %lu not handled in render loop.", waitResult);
|
||||
abort();
|
||||
XASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
// Stop audio clients since this thread will no longer service
|
||||
// the events.
|
||||
if (stm->output_client) {
|
||||
stm->output_client->Stop();
|
||||
}
|
||||
if (stm->input_client) {
|
||||
stm->input_client->Stop();
|
||||
}
|
||||
|
||||
if (mmcss_handle) {
|
||||
AvRevertMmThreadCharacteristics(mmcss_handle);
|
||||
}
|
||||
|
||||
handle_emergency_bailout(stm);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1696,54 +1736,58 @@ wasapi_init(cubeb ** context, char const * context_name)
|
|||
}
|
||||
|
||||
namespace {
|
||||
enum ShutdownPhase { OnStop, OnDestroy };
|
||||
|
||||
bool
|
||||
stop_and_join_render_thread(cubeb_stream * stm)
|
||||
stop_and_join_render_thread(cubeb_stream * stm, ShutdownPhase phase)
|
||||
{
|
||||
bool rv = true;
|
||||
LOG("Stop and join render thread.");
|
||||
// Only safe to transfer `stm` ownership to the render thread when
|
||||
// the stream is being destroyed by the caller.
|
||||
bool bailout = phase == OnDestroy;
|
||||
|
||||
LOG("%p: Stop and join render thread: %p (%d), phase=%d", stm, stm->thread,
|
||||
stm->emergency_bailout.load(), static_cast<int>(phase));
|
||||
if (!stm->thread) {
|
||||
LOG("No thread present.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we've already leaked the thread, just return,
|
||||
// there is not much we can do.
|
||||
if (!stm->emergency_bailout.load()) {
|
||||
return false;
|
||||
}
|
||||
XASSERT(!stm->emergency_bailout);
|
||||
|
||||
BOOL ok = SetEvent(stm->shutdown_event);
|
||||
if (!ok) {
|
||||
LOG("Destroy SetEvent failed: %lx", GetLastError());
|
||||
LOG("stop_and_join_render_thread: SetEvent failed: %lx", GetLastError());
|
||||
stm->emergency_bailout = bailout;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Wait five seconds for the rendering thread to return. It's supposed to
|
||||
* check its event loop very often, five seconds is rather conservative. */
|
||||
DWORD r = WaitForSingleObject(stm->thread, 5000);
|
||||
* check its event loop very often, five seconds is rather conservative.
|
||||
* Note: 5*1s loop to work around timer sleep issues on pre-Windows 8. */
|
||||
DWORD r;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
r = WaitForSingleObject(stm->thread, 1000);
|
||||
if (r == WAIT_OBJECT_0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r != WAIT_OBJECT_0) {
|
||||
/* Something weird happened, leak the thread and continue the shutdown
|
||||
* process. */
|
||||
*(stm->emergency_bailout) = true;
|
||||
// We give the ownership to the rendering thread.
|
||||
stm->emergency_bailout = nullptr;
|
||||
LOG("Destroy WaitForSingleObject on thread failed: %lx, %lx", r,
|
||||
GetLastError());
|
||||
rv = false;
|
||||
LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: "
|
||||
"%lx, %lx",
|
||||
r, GetLastError());
|
||||
stm->emergency_bailout = bailout;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only attempts to close and null out the thread and event if the
|
||||
// WaitForSingleObject above succeeded, so that calling this function again
|
||||
// attemps to clean up the thread and event each time.
|
||||
if (rv) {
|
||||
LOG("Closing thread.");
|
||||
// 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;
|
||||
}
|
||||
|
||||
return rv;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1881,9 +1925,6 @@ wasapi_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
|
|||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
void
|
||||
wasapi_stream_destroy(cubeb_stream * stm);
|
||||
|
||||
static void
|
||||
waveformatex_update_derived_properties(WAVEFORMATEX * format)
|
||||
{
|
||||
|
@ -2259,8 +2300,12 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
|
|||
if (wasapi_create_device(stm->context, device_info,
|
||||
stm->device_enumerator.get(), device.get(),
|
||||
&default_devices) == CUBEB_OK) {
|
||||
if (device_info.latency_hi == 0) {
|
||||
LOG("Input: could not query latency_hi to guess safe latency");
|
||||
wasapi_destroy_device(&device_info);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
// This multiplicator has been found empirically.
|
||||
XASSERT(device_info.latency_hi > 0);
|
||||
uint32_t latency_frames = device_info.latency_hi * 8;
|
||||
LOG("Input: latency increased to %u frames from a default of %u",
|
||||
latency_frames, device_info.latency_hi);
|
||||
|
@ -2362,10 +2407,10 @@ wasapi_find_bt_handsfree_output_device(cubeb_stream * stm)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
int rv = wasapi_enumerate_devices(
|
||||
int rv = wasapi_enumerate_devices_internal(
|
||||
stm->context,
|
||||
(cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT),
|
||||
&collection);
|
||||
&collection, DEVICE_STATE_ACTIVE);
|
||||
if (rv != CUBEB_OK) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -2561,7 +2606,7 @@ setup_wasapi_stream(cubeb_stream * stm)
|
|||
stm->resampler.reset(cubeb_resampler_create(
|
||||
stm, has_input(stm) ? &input_params : nullptr,
|
||||
has_output(stm) && !stm->has_dummy_output ? &output_params : nullptr,
|
||||
target_sample_rate, stm->data_callback, stm->user_ptr,
|
||||
target_sample_rate, wasapi_data_callback, stm->user_ptr,
|
||||
stm->voice ? CUBEB_RESAMPLER_QUALITY_VOIP
|
||||
: CUBEB_RESAMPLER_QUALITY_DESKTOP,
|
||||
CUBEB_RESAMPLER_RECLOCK_NONE));
|
||||
|
@ -2794,33 +2839,25 @@ wasapi_stream_destroy(cubeb_stream * stm)
|
|||
XASSERT(stm);
|
||||
LOG("Stream destroy (%p)", stm);
|
||||
|
||||
// Only free stm->emergency_bailout if we could join the thread.
|
||||
// If we could not join the thread, stm->emergency_bailout is true
|
||||
// and is still alive until the thread wakes up and exits cleanly.
|
||||
if (stop_and_join_render_thread(stm)) {
|
||||
delete stm->emergency_bailout.load();
|
||||
stm->emergency_bailout = nullptr;
|
||||
if (!stop_and_join_render_thread(stm, OnDestroy)) {
|
||||
// Emergency bailout: render thread becomes responsible for calling
|
||||
// wasapi_stream_destroy.
|
||||
return;
|
||||
}
|
||||
|
||||
if (stm->notification_client) {
|
||||
unregister_notification_client(stm);
|
||||
}
|
||||
|
||||
CloseHandle(stm->reconfigure_event);
|
||||
CloseHandle(stm->refill_event);
|
||||
CloseHandle(stm->input_available_event);
|
||||
|
||||
// The variables intialized in wasapi_stream_init,
|
||||
// must be destroyed in wasapi_stream_destroy.
|
||||
stm->linear_input_buffer.reset();
|
||||
|
||||
stm->device_enumerator = nullptr;
|
||||
|
||||
{
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
close_wasapi_stream(stm);
|
||||
}
|
||||
|
||||
CloseHandle(stm->reconfigure_event);
|
||||
CloseHandle(stm->refill_event);
|
||||
CloseHandle(stm->input_available_event);
|
||||
|
||||
delete stm;
|
||||
}
|
||||
|
||||
|
@ -2875,8 +2912,6 @@ wasapi_stream_start(cubeb_stream * stm)
|
|||
XASSERT(stm && !stm->thread && !stm->shutdown_event);
|
||||
XASSERT(stm->output_client || stm->input_client);
|
||||
|
||||
stm->emergency_bailout = new std::atomic<bool>(false);
|
||||
|
||||
if (stm->output_client) {
|
||||
int rv = stream_start_one_side(stm, OUTPUT);
|
||||
if (rv != CUBEB_OK) {
|
||||
|
@ -2897,30 +2932,18 @@ wasapi_stream_start(cubeb_stream * stm)
|
|||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL);
|
||||
if (!stm->thread_ready_event) {
|
||||
LOG("Can't create the thread_ready event, error: %lx", GetLastError());
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
cubeb_async_log_reset_threads();
|
||||
stm->thread =
|
||||
(HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
|
||||
STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
|
||||
if (stm->thread == NULL) {
|
||||
LOG("could not create WASAPI render thread.");
|
||||
CloseHandle(stm->shutdown_event);
|
||||
stm->shutdown_event = 0;
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
// Wait for wasapi_stream_render_loop to signal that emergency_bailout has
|
||||
// been read, avoiding a bailout situation where we could free `stm`
|
||||
// before wasapi_stream_render_loop had a chance to run.
|
||||
HRESULT hr = WaitForSingleObject(stm->thread_ready_event, INFINITE);
|
||||
XASSERT(hr == WAIT_OBJECT_0);
|
||||
CloseHandle(stm->thread_ready_event);
|
||||
stm->thread_ready_event = 0;
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
@ -2950,15 +2973,12 @@ wasapi_stream_stop(cubeb_stream * stm)
|
|||
}
|
||||
}
|
||||
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
}
|
||||
|
||||
if (stop_and_join_render_thread(stm)) {
|
||||
delete stm->emergency_bailout.load();
|
||||
stm->emergency_bailout = nullptr;
|
||||
} else {
|
||||
if (!stop_and_join_render_thread(stm, OnStop)) {
|
||||
// If we could not join the thread, put the stream in error.
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
|
@ -3324,8 +3344,9 @@ wasapi_destroy_device(cubeb_device_info * device)
|
|||
}
|
||||
|
||||
static int
|
||||
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * out)
|
||||
wasapi_enumerate_devices_internal(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * out,
|
||||
DWORD state_mask)
|
||||
{
|
||||
com_ptr<IMMDeviceEnumerator> enumerator;
|
||||
com_ptr<IMMDeviceCollection> collection;
|
||||
|
@ -3353,8 +3374,7 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
|
|||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL,
|
||||
collection.receive());
|
||||
hr = enumerator->EnumAudioEndpoints(flow, state_mask, collection.receive());
|
||||
if (FAILED(hr)) {
|
||||
LOG("Could not enumerate audio endpoints: %lx", hr);
|
||||
return CUBEB_ERROR;
|
||||
|
@ -3388,6 +3408,15 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
|
|||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * out)
|
||||
{
|
||||
return wasapi_enumerate_devices_internal(
|
||||
context, type, out,
|
||||
DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED);
|
||||
}
|
||||
|
||||
static int
|
||||
wasapi_device_collection_destroy(cubeb * /*ctx*/,
|
||||
cubeb_device_collection * collection)
|
||||
|
|
38
externals/cubeb/test/test_callback_ret.cpp
vendored
38
externals/cubeb/test/test_callback_ret.cpp
vendored
|
@ -34,6 +34,7 @@ enum test_direction {
|
|||
struct user_state_callback_ret {
|
||||
std::atomic<int> cb_count{ 0 };
|
||||
std::atomic<int> expected_cb_count{ 0 };
|
||||
std::atomic<int> error_state{ 0 };
|
||||
};
|
||||
|
||||
// Data callback that always returns 0
|
||||
|
@ -98,10 +99,30 @@ long data_cb_ret_nframes(cubeb_stream * stream, void * user, const void * inputb
|
|||
return nframes;
|
||||
}
|
||||
|
||||
void state_cb_ret(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
||||
// Data callback that always returns CUBEB_ERROR
|
||||
long
|
||||
data_cb_ret_error(cubeb_stream * stream, void * user, const void * inputbuffer,
|
||||
void * outputbuffer, long nframes)
|
||||
{
|
||||
user_state_callback_ret * u = (user_state_callback_ret *)user;
|
||||
// If this is the first time the callback has been called set our expected
|
||||
// callback count
|
||||
if (u->cb_count == 0) {
|
||||
u->expected_cb_count = 1;
|
||||
}
|
||||
u->cb_count++;
|
||||
if (nframes < 1) {
|
||||
// This shouldn't happen
|
||||
EXPECT_TRUE(false) << "nframes should not be 0 in data callback!";
|
||||
}
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
void state_cb_ret(cubeb_stream * stream, void * user, cubeb_state state)
|
||||
{
|
||||
if (stream == NULL)
|
||||
return;
|
||||
user_state_callback_ret * u = (user_state_callback_ret *)user;
|
||||
|
||||
switch (state) {
|
||||
case CUBEB_STATE_STARTED:
|
||||
|
@ -110,11 +131,13 @@ void state_cb_ret(cubeb_stream * stream, void * /*user*/, cubeb_state state)
|
|||
fprintf(stderr, "stream stopped\n"); break;
|
||||
case CUBEB_STATE_DRAINED:
|
||||
fprintf(stderr, "stream drained\n"); break;
|
||||
case CUBEB_STATE_ERROR:
|
||||
fprintf(stderr, "stream error\n");
|
||||
u->error_state.fetch_add(1);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "unknown stream state %d\n", state);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void run_test_callback(test_direction direction,
|
||||
|
@ -183,6 +206,10 @@ void run_test_callback(test_direction direction,
|
|||
|
||||
ASSERT_EQ(user_state.expected_cb_count, user_state.cb_count) <<
|
||||
"Callback called unexpected number of times for " << test_desc << "!";
|
||||
// TODO: On some test configurations, the data_callback is never called.
|
||||
if (data_cb == data_cb_ret_error && user_state.cb_count != 0) {
|
||||
ASSERT_EQ(user_state.error_state, 1) << "Callback expected error state";
|
||||
}
|
||||
}
|
||||
|
||||
TEST(cubeb, test_input_callback)
|
||||
|
@ -190,6 +217,8 @@ TEST(cubeb, test_input_callback)
|
|||
run_test_callback(INPUT_ONLY, data_cb_ret_zero, "input only, return 0");
|
||||
run_test_callback(INPUT_ONLY, data_cb_ret_nframes_minus_one, "input only, return nframes - 1");
|
||||
run_test_callback(INPUT_ONLY, data_cb_ret_nframes, "input only, return nframes");
|
||||
run_test_callback(INPUT_ONLY, data_cb_ret_error,
|
||||
"input only, return CUBEB_ERROR");
|
||||
}
|
||||
|
||||
TEST(cubeb, test_output_callback)
|
||||
|
@ -197,6 +226,8 @@ TEST(cubeb, test_output_callback)
|
|||
run_test_callback(OUTPUT_ONLY, data_cb_ret_zero, "output only, return 0");
|
||||
run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes_minus_one, "output only, return nframes - 1");
|
||||
run_test_callback(OUTPUT_ONLY, data_cb_ret_nframes, "output only, return nframes");
|
||||
run_test_callback(OUTPUT_ONLY, data_cb_ret_error,
|
||||
"output only, return CUBEB_ERROR");
|
||||
}
|
||||
|
||||
TEST(cubeb, test_duplex_callback)
|
||||
|
@ -204,4 +235,5 @@ TEST(cubeb, test_duplex_callback)
|
|||
run_test_callback(DUPLEX, data_cb_ret_zero, "duplex, return 0");
|
||||
run_test_callback(DUPLEX, data_cb_ret_nframes_minus_one, "duplex, return nframes - 1");
|
||||
run_test_callback(DUPLEX, data_cb_ret_nframes, "duplex, return nframes");
|
||||
run_test_callback(DUPLEX, data_cb_ret_error, "duplex, return CUBEB_ERROR");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -65,6 +65,20 @@ public:
|
|||
*/
|
||||
void PauseSinks(bool pausing) const;
|
||||
|
||||
/**
|
||||
* Get the size of the current stream queue.
|
||||
*
|
||||
* @return Current stream queue size.
|
||||
*/
|
||||
u32 GetStreamQueue() const;
|
||||
|
||||
/**
|
||||
* Get the size of the current stream queue.
|
||||
*
|
||||
* @param size - New stream size.
|
||||
*/
|
||||
void SetStreamQueue(u32 size);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Create the sinks on startup.
|
||||
|
@ -79,6 +93,8 @@ private:
|
|||
std::unique_ptr<Sink::Sink> input_sink;
|
||||
/// The ADSP in the sysmodule
|
||||
std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
|
||||
/// Current size of the stream queue
|
||||
std::atomic<u32> estimated_queue{0};
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include "audio_core/audio_in_manager.h"
|
||||
#include "audio_core/audio_manager.h"
|
||||
#include "audio_core/in/audio_in.h"
|
||||
#include "audio_core/sink/sink_details.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
|
@ -78,9 +80,12 @@ u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceN
|
|||
|
||||
LinkToManager();
|
||||
|
||||
auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
|
||||
if (input_devices.size() > 1) {
|
||||
names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac"));
|
||||
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioIn
|
||||
|
|
|
@ -37,14 +37,17 @@ 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() {
|
||||
if (initialized) {
|
||||
Stop();
|
||||
sink->CloseStream(stream);
|
||||
stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceSession::Start() {
|
||||
stream->SetPlayedSampleCount(played_sample_count);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
if (index == 0) {
|
||||
auto stream{command_list_processor.GetOutputSinkStream()};
|
||||
if (stream->GetQueueSize() >= 4) {
|
||||
render_event.WaitFor(std::chrono::milliseconds(5));
|
||||
system.AudioCore().SetStreamQueue(stream->GetQueueSize());
|
||||
}
|
||||
|
||||
const auto end_time{system.CoreTiming().GetClockTicks()};
|
||||
|
|
|
@ -14,8 +14,11 @@
|
|||
#include "common/thread.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
namespace Timing {
|
||||
struct EventType;
|
||||
}
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace AudioCore {
|
||||
namespace Sink {
|
||||
|
@ -194,8 +197,6 @@ private:
|
|||
Sink::Sink& sink;
|
||||
/// The streams which will receive the processed samples
|
||||
std::array<Sink::SinkStream*, MaxRendererSessions> streams;
|
||||
/// An event signalled from the backend when a buffer is consumed, used for timing.
|
||||
Common::Event render_event{};
|
||||
};
|
||||
|
||||
} // namespace AudioRenderer::ADSP
|
||||
|
|
|
@ -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,13 +86,15 @@ static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& p
|
|||
auto lookahead_sample{
|
||||
state.look_ahead_sample_buffers[channel]
|
||||
[state.look_ahead_sample_offsets[channel]]};
|
||||
|
||||
state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] =
|
||||
sample;
|
||||
state.look_ahead_sample_offsets[channel] =
|
||||
(state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min;
|
||||
|
||||
outputs[channel][sample_index] = static_cast<s32>(std::clamp(
|
||||
(lookahead_sample * state.compression_gain[channel] * params.output_gain)
|
||||
outputs[channel][sample_index] = static_cast<s32>(
|
||||
std::clamp((lookahead_sample * state.compression_gain[channel] *
|
||||
params.output_gain * Common::FixedPoint<49, 15>::one)
|
||||
.to_long(),
|
||||
min, max));
|
||||
|
||||
|
|
|
@ -15,12 +15,17 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
|
|||
MP_RGB(60, 19, 97));
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL};
|
||||
constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL};
|
||||
|
||||
SystemManager::SystemManager(Core::System& core_)
|
||||
: core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
|
||||
thread_event{Core::Timing::CreateEvent(
|
||||
"AudioRendererSystemManager",
|
||||
[this](std::uintptr_t userdata, std::chrono::nanoseconds ns_late) { ThreadFunc2(); })} {}
|
||||
"AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
|
||||
return ThreadFunc2(time);
|
||||
})} {
|
||||
core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); });
|
||||
}
|
||||
|
||||
SystemManager::~SystemManager() {
|
||||
Stop();
|
||||
|
@ -30,9 +35,9 @@ bool SystemManager::InitializeUnsafe() {
|
|||
if (!active) {
|
||||
if (adsp.Start()) {
|
||||
active = true;
|
||||
core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(2'304'000ULL * 2),
|
||||
thread_event);
|
||||
thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
|
||||
core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0),
|
||||
BaseRenderTime - RenderTimeOffset, thread_event);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,11 +100,13 @@ void SystemManager::ThreadFunc() {
|
|||
constexpr char name[]{"yuzu:AudioRenderSystemManager"};
|
||||
MicroProfileOnThreadCreate(name);
|
||||
Common::SetCurrentThreadName(name);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
while (active) {
|
||||
{
|
||||
std::scoped_lock l{mutex1};
|
||||
|
||||
MICROPROFILE_SCOPE(Audio_RenderSystemManager);
|
||||
|
||||
for (auto system : systems) {
|
||||
system->SendCommandToDsp();
|
||||
}
|
||||
|
@ -113,9 +120,43 @@ void SystemManager::ThreadFunc() {
|
|||
}
|
||||
}
|
||||
|
||||
void SystemManager::ThreadFunc2() {
|
||||
std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
|
||||
std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt};
|
||||
const auto queue_size{core.AudioCore().GetStreamQueue()};
|
||||
switch (state) {
|
||||
case StreamState::Filling:
|
||||
if (queue_size >= 5) {
|
||||
new_schedule_time = BaseRenderTime;
|
||||
state = StreamState::Steady;
|
||||
}
|
||||
break;
|
||||
case StreamState::Steady:
|
||||
if (queue_size <= 2) {
|
||||
new_schedule_time = BaseRenderTime - RenderTimeOffset;
|
||||
state = StreamState::Filling;
|
||||
} else if (queue_size > 5) {
|
||||
new_schedule_time = BaseRenderTime + RenderTimeOffset;
|
||||
state = StreamState::Draining;
|
||||
}
|
||||
break;
|
||||
case StreamState::Draining:
|
||||
if (queue_size <= 5) {
|
||||
new_schedule_time = BaseRenderTime;
|
||||
state = StreamState::Steady;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
update.store(true);
|
||||
update.notify_all();
|
||||
return new_schedule_time;
|
||||
}
|
||||
|
||||
void SystemManager::PauseCallback(bool paused) {
|
||||
if (paused && core.IsPoweredOn() && core.IsShuttingDown()) {
|
||||
update.store(true);
|
||||
update.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
#include "audio_core/renderer/system.h"
|
||||
|
@ -70,7 +71,20 @@ private:
|
|||
/**
|
||||
* Signalling core timing thread to run ThreadFunc.
|
||||
*/
|
||||
void ThreadFunc2();
|
||||
std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
|
||||
|
||||
/**
|
||||
* Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc.
|
||||
*
|
||||
* @param paused - Are we pausing or resuming?
|
||||
*/
|
||||
void PauseCallback(bool paused);
|
||||
|
||||
enum class StreamState {
|
||||
Filling,
|
||||
Steady,
|
||||
Draining,
|
||||
};
|
||||
|
||||
/// Core system
|
||||
Core::System& core;
|
||||
|
@ -92,6 +106,8 @@ private:
|
|||
std::shared_ptr<Core::Timing::EventType> thread_event;
|
||||
/// Atomic for main thread to wait on
|
||||
std::atomic<bool> update{};
|
||||
/// Current state of the streams
|
||||
StreamState state{StreamState::Filling};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
||||
|
|
|
@ -44,8 +44,8 @@ public:
|
|||
*/
|
||||
CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_,
|
||||
cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
|
||||
const StreamType type_, Core::System& system_, Common::Event* event)
|
||||
: ctx{ctx_}, type{type_}, system{system_}, render_event{event} {
|
||||
const StreamType type_, Core::System& system_)
|
||||
: ctx{ctx_}, type{type_}, system{system_} {
|
||||
#ifdef _WIN32
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
@ -306,7 +306,6 @@ private:
|
|||
manager.SetEvent(Event::Type::AudioInManager, true);
|
||||
break;
|
||||
case StreamType::Render:
|
||||
render_event->Set();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -469,8 +468,6 @@ private:
|
|||
::AudioCore::Sink::SinkBuffer released_buffer{};
|
||||
/// The last played (or received) frame of audio, used when the callback underruns
|
||||
std::array<s16, MaxChannels> last_frame{};
|
||||
/// Audio render-only event, signalled when a render buffer is consumed
|
||||
Common::Event* render_event;
|
||||
};
|
||||
|
||||
CubebSink::CubebSink(std::string_view target_device_name) {
|
||||
|
@ -525,11 +522,9 @@ CubebSink::~CubebSink() {
|
|||
}
|
||||
|
||||
SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels,
|
||||
const std::string& name, const StreamType type,
|
||||
Common::Event* event) {
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(
|
||||
std::make_unique<CubebSinkStream>(ctx, device_channels, system_channels, output_device,
|
||||
input_device, name, type, system, event));
|
||||
const std::string& name, const StreamType type) {
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
|
||||
ctx, device_channels, system_channels, output_device, input_device, name, type, system));
|
||||
|
||||
return stream.get();
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -46,8 +46,8 @@ public:
|
|||
*/
|
||||
SDLSinkStream(u32 device_channels_, const u32 system_channels_,
|
||||
const std::string& output_device, const std::string& input_device,
|
||||
const StreamType type_, Core::System& system_, Common::Event* event)
|
||||
: type{type_}, system{system_}, render_event{event} {
|
||||
const StreamType type_, Core::System& system_)
|
||||
: type{type_}, system{system_} {
|
||||
system_channels = system_channels_;
|
||||
device_channels = device_channels_;
|
||||
|
||||
|
@ -279,7 +279,6 @@ private:
|
|||
manager.SetEvent(Event::Type::AudioInManager, true);
|
||||
break;
|
||||
case StreamType::Render:
|
||||
render_event->Set();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -417,8 +416,6 @@ private:
|
|||
::AudioCore::Sink::SinkBuffer released_buffer{};
|
||||
/// The last played (or received) frame of audio, used when the callback underruns
|
||||
std::array<s16, MaxChannels> last_frame{};
|
||||
/// Audio render-only event, signalled when a render buffer is consumed
|
||||
Common::Event* render_event;
|
||||
};
|
||||
|
||||
SDLSink::SDLSink(std::string_view target_device_name) {
|
||||
|
@ -441,10 +438,9 @@ SDLSink::SDLSink(std::string_view target_device_name) {
|
|||
SDLSink::~SDLSink() = default;
|
||||
|
||||
SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels,
|
||||
const std::string&, const StreamType type,
|
||||
Common::Event* event) {
|
||||
const std::string&, const StreamType type) {
|
||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
|
||||
device_channels, system_channels, output_device, input_device, type, system, event));
|
||||
device_channels, system_channels, output_device, input_device, type, system));
|
||||
return stream.get();
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -139,7 +139,6 @@ struct System::Impl {
|
|||
|
||||
kernel.Suspend(false);
|
||||
core_timing.SyncPause(false);
|
||||
cpu_manager.Pause(false);
|
||||
is_paused = false;
|
||||
|
||||
audio_core->PauseSinks(false);
|
||||
|
@ -155,7 +154,6 @@ struct System::Impl {
|
|||
|
||||
core_timing.SyncPause(true);
|
||||
kernel.Suspend(true);
|
||||
cpu_manager.Pause(true);
|
||||
is_paused = true;
|
||||
|
||||
return status;
|
||||
|
@ -170,7 +168,6 @@ struct System::Impl {
|
|||
std::unique_lock<std::mutex> lk(suspend_guard);
|
||||
kernel.Suspend(true);
|
||||
core_timing.SyncPause(true);
|
||||
cpu_manager.Pause(true);
|
||||
return lk;
|
||||
}
|
||||
|
||||
|
@ -178,7 +175,6 @@ struct System::Impl {
|
|||
if (!is_paused) {
|
||||
core_timing.SyncPause(false);
|
||||
kernel.Suspend(false);
|
||||
cpu_manager.Pause(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -348,13 +344,14 @@ struct System::Impl {
|
|||
gpu_core->NotifyShutdown();
|
||||
}
|
||||
|
||||
kernel.ShutdownCores();
|
||||
cpu_manager.Shutdown();
|
||||
debugger.reset();
|
||||
kernel.CloseServices();
|
||||
services.reset();
|
||||
service_manager.reset();
|
||||
cheat_engine.reset();
|
||||
telemetry_session.reset();
|
||||
cpu_manager.Shutdown();
|
||||
time_manager.Shutdown();
|
||||
core_timing.Shutdown();
|
||||
app_loader.reset();
|
||||
|
|
|
@ -22,11 +22,11 @@ std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callbac
|
|||
}
|
||||
|
||||
struct CoreTiming::Event {
|
||||
u64 time;
|
||||
s64 time;
|
||||
u64 fifo_order;
|
||||
std::uintptr_t user_data;
|
||||
std::weak_ptr<EventType> type;
|
||||
u64 reschedule_time;
|
||||
s64 reschedule_time;
|
||||
|
||||
// Sort by time, unless the times are the same, in which case sort by
|
||||
// the order added to the queue
|
||||
|
@ -59,7 +59,8 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
|||
event_fifo_id = 0;
|
||||
shutting_down = false;
|
||||
ticks = 0;
|
||||
const auto empty_timed_callback = [](std::uintptr_t, std::chrono::nanoseconds) {};
|
||||
const auto empty_timed_callback = [](std::uintptr_t, u64, std::chrono::nanoseconds)
|
||||
-> std::optional<std::chrono::nanoseconds> { return std::nullopt; };
|
||||
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
|
||||
if (is_multicore) {
|
||||
worker_threads.emplace_back(ThreadEntry, std::ref(*this), 0);
|
||||
|
@ -77,6 +78,7 @@ void CoreTiming::Shutdown() {
|
|||
thread.join();
|
||||
}
|
||||
worker_threads.clear();
|
||||
pause_callbacks.clear();
|
||||
ClearPendingEvents();
|
||||
has_started = false;
|
||||
}
|
||||
|
@ -94,6 +96,14 @@ void CoreTiming::Pause(bool is_paused_) {
|
|||
}
|
||||
}
|
||||
paused_state.store(is_paused_, std::memory_order_relaxed);
|
||||
|
||||
if (!is_paused_) {
|
||||
pause_end_time = GetGlobalTimeNs().count();
|
||||
}
|
||||
|
||||
for (auto& cb : pause_callbacks) {
|
||||
cb(is_paused_);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::SyncPause(bool is_paused_) {
|
||||
|
@ -117,6 +127,14 @@ void CoreTiming::SyncPause(bool is_paused_) {
|
|||
wait_signal_cv.wait(main_lock, [this] { return pause_count == 0; });
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_paused_) {
|
||||
pause_end_time = GetGlobalTimeNs().count();
|
||||
}
|
||||
|
||||
for (auto& cb : pause_callbacks) {
|
||||
cb(is_paused_);
|
||||
}
|
||||
}
|
||||
|
||||
bool CoreTiming::IsRunning() const {
|
||||
|
@ -130,12 +148,12 @@ bool CoreTiming::HasPendingEvents() const {
|
|||
|
||||
void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
||||
const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data) {
|
||||
std::uintptr_t user_data, bool absolute_time) {
|
||||
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
const u64 timeout = static_cast<u64>((GetGlobalTimeNs() + ns_into_future).count());
|
||||
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
|
||||
|
||||
event_queue.emplace_back(Event{timeout, event_fifo_id++, user_data, event_type, 0});
|
||||
event_queue.emplace_back(Event{next_time.count(), event_fifo_id++, user_data, event_type, 0});
|
||||
pending_events.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
|
@ -145,30 +163,15 @@ void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
|||
}
|
||||
}
|
||||
|
||||
void CoreTiming::ScheduleEventAt(std::chrono::nanoseconds next_time,
|
||||
void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
||||
std::chrono::nanoseconds resched_time,
|
||||
const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data) {
|
||||
std::uintptr_t user_data, bool absolute_time) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
const u64 timeout = static_cast<u64>(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,
|
||||
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());
|
||||
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
|
||||
|
||||
event_queue.emplace_back(
|
||||
Event{timeout, event_fifo_id++, user_data, event_type, static_cast<u64>(time.count())});
|
||||
Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
|
||||
pending_events.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
|
@ -247,6 +250,11 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
|
|||
}
|
||||
}
|
||||
|
||||
void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) {
|
||||
std::unique_lock main_lock(event_mutex);
|
||||
pause_callbacks.emplace_back(std::move(callback));
|
||||
}
|
||||
|
||||
std::optional<s64> CoreTiming::Advance() {
|
||||
global_timer = GetGlobalTimeNs().count();
|
||||
|
||||
|
@ -257,22 +265,32 @@ std::optional<s64> CoreTiming::Advance() {
|
|||
event_queue.pop_back();
|
||||
|
||||
if (const auto event_type{evt.type.lock()}) {
|
||||
|
||||
event_mutex.unlock();
|
||||
|
||||
const s64 delay = static_cast<s64>(GetGlobalTimeNs().count() - evt.time);
|
||||
event_type->callback(evt.user_data, std::chrono::nanoseconds{delay});
|
||||
const auto new_schedule_time{event_type->callback(
|
||||
evt.user_data, evt.time,
|
||||
std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
|
||||
|
||||
event_mutex.lock();
|
||||
pending_events.fetch_sub(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
if (evt.reschedule_time != 0) {
|
||||
// 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{global_timer + evt.reschedule_time - (global_timer - evt.time),
|
||||
event_fifo_id++, evt.user_data, evt.type, evt.reschedule_time});
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -20,8 +20,9 @@
|
|||
namespace Core::Timing {
|
||||
|
||||
/// A callback that may be scheduled for a particular core timing event.
|
||||
using TimedCallback =
|
||||
std::function<void(std::uintptr_t user_data, std::chrono::nanoseconds ns_late)>;
|
||||
using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
|
||||
std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>;
|
||||
using PauseCallback = std::function<void(bool paused)>;
|
||||
|
||||
/// Contains the characteristics of a particular event.
|
||||
struct EventType {
|
||||
|
@ -93,18 +94,15 @@ public:
|
|||
|
||||
/// Schedules an event in core timing
|
||||
void ScheduleEvent(std::chrono::nanoseconds ns_into_future,
|
||||
const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0);
|
||||
|
||||
/// Schedules an event with an absolute time in core timing
|
||||
void ScheduleEventAt(std::chrono::nanoseconds next_time,
|
||||
const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data = 0);
|
||||
const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data = 0,
|
||||
bool absolute_time = false);
|
||||
|
||||
/// Schedules an event which will automatically re-schedule itself with the given time, until
|
||||
/// unscheduled
|
||||
void ScheduleLoopingEvent(std::chrono::nanoseconds time,
|
||||
void ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
||||
std::chrono::nanoseconds resched_time,
|
||||
const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data = 0);
|
||||
std::uintptr_t user_data = 0, bool absolute_time = false);
|
||||
|
||||
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data);
|
||||
|
||||
|
@ -136,6 +134,9 @@ public:
|
|||
/// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
|
||||
std::optional<s64> Advance();
|
||||
|
||||
/// Register a callback function to be called when coretiming pauses.
|
||||
void RegisterPauseCallback(PauseCallback&& callback);
|
||||
|
||||
private:
|
||||
struct Event;
|
||||
|
||||
|
@ -147,7 +148,7 @@ private:
|
|||
|
||||
std::unique_ptr<Common::WallClock> clock;
|
||||
|
||||
u64 global_timer = 0;
|
||||
s64 global_timer = 0;
|
||||
|
||||
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
|
||||
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
|
||||
|
@ -173,10 +174,13 @@ private:
|
|||
bool shutting_down{};
|
||||
bool is_multicore{};
|
||||
size_t pause_count{};
|
||||
s64 pause_end_time{};
|
||||
|
||||
/// Cycle timing
|
||||
u64 ticks{};
|
||||
s64 downcount{};
|
||||
|
||||
std::vector<PauseCallback> pause_callbacks{};
|
||||
};
|
||||
|
||||
/// Creates a core timing event with the given name and callback.
|
||||
|
|
|
@ -25,10 +25,8 @@ void CpuManager::ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager
|
|||
}
|
||||
|
||||
void CpuManager::Initialize() {
|
||||
running_mode = true;
|
||||
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
|
||||
gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
|
||||
pause_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
|
||||
|
||||
for (std::size_t core = 0; core < num_cores; core++) {
|
||||
core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core);
|
||||
|
@ -36,8 +34,11 @@ void CpuManager::Initialize() {
|
|||
}
|
||||
|
||||
void CpuManager::Shutdown() {
|
||||
running_mode = false;
|
||||
Pause(false);
|
||||
for (std::size_t core = 0; core < num_cores; core++) {
|
||||
if (core_data[core].host_thread.joinable()) {
|
||||
core_data[core].host_thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::GuestThreadFunction() {
|
||||
|
@ -64,6 +65,10 @@ void CpuManager::IdleThreadFunction() {
|
|||
}
|
||||
}
|
||||
|
||||
void CpuManager::ShutdownThreadFunction() {
|
||||
ShutdownThread();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// MultiCore ///
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -176,41 +181,13 @@ void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
|
|||
}
|
||||
}
|
||||
|
||||
void CpuManager::SuspendThread() {
|
||||
void CpuManager::ShutdownThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
kernel.CurrentScheduler()->OnThreadStart();
|
||||
|
||||
while (true) {
|
||||
auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0;
|
||||
auto& scheduler = *kernel.CurrentScheduler();
|
||||
Kernel::KThread* current_thread = scheduler.GetSchedulerCurrentThread();
|
||||
auto* current_thread = kernel.GetCurrentEmuThread();
|
||||
|
||||
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();
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
|
|
@ -50,16 +50,14 @@ public:
|
|||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
void Pause(bool paused);
|
||||
|
||||
std::function<void()> GetGuestThreadStartFunc() {
|
||||
return [this] { GuestThreadFunction(); };
|
||||
}
|
||||
std::function<void()> GetIdleThreadStartFunc() {
|
||||
return [this] { IdleThreadFunction(); };
|
||||
}
|
||||
std::function<void()> GetSuspendThreadStartFunc() {
|
||||
return [this] { SuspendThread(); };
|
||||
std::function<void()> GetShutdownThreadStartFunc() {
|
||||
return [this] { ShutdownThreadFunction(); };
|
||||
}
|
||||
|
||||
void PreemptSingleCore(bool from_running_enviroment = true);
|
||||
|
@ -72,6 +70,7 @@ private:
|
|||
void GuestThreadFunction();
|
||||
void GuestRewindFunction();
|
||||
void IdleThreadFunction();
|
||||
void ShutdownThreadFunction();
|
||||
|
||||
void MultiCoreRunGuestThread();
|
||||
void MultiCoreRunGuestLoop();
|
||||
|
@ -83,7 +82,7 @@ private:
|
|||
|
||||
static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core);
|
||||
|
||||
void SuspendThread();
|
||||
void ShutdownThread();
|
||||
void RunThread(std::size_t core);
|
||||
|
||||
struct CoreData {
|
||||
|
@ -91,12 +90,7 @@ private:
|
|||
std::jthread host_thread;
|
||||
};
|
||||
|
||||
std::atomic<bool> running_mode{};
|
||||
std::atomic<bool> pause_state{};
|
||||
std::unique_ptr<Common::Barrier> pause_barrier{};
|
||||
std::unique_ptr<Common::Barrier> gpu_barrier{};
|
||||
std::mutex pause_lock{};
|
||||
|
||||
std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{};
|
||||
|
||||
bool is_async_gpu{};
|
||||
|
|
|
@ -269,7 +269,7 @@ Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32
|
|||
Result KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread,
|
||||
KThreadFunction func, uintptr_t arg, s32 virt_core) {
|
||||
return InitializeThread(thread, func, arg, {}, {}, virt_core, nullptr, ThreadType::HighPriority,
|
||||
system.GetCpuManager().GetSuspendThreadStartFunc());
|
||||
system.GetCpuManager().GetShutdownThreadStartFunc());
|
||||
}
|
||||
|
||||
Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThreadFunction func,
|
||||
|
@ -739,6 +739,19 @@ void KThread::Continue() {
|
|||
KScheduler::OnThreadStateChanged(kernel, this, old_state);
|
||||
}
|
||||
|
||||
void KThread::WaitUntilSuspended() {
|
||||
// Make sure we have a suspend requested.
|
||||
ASSERT(IsSuspendRequested());
|
||||
|
||||
// Loop until the thread is not executing on any core.
|
||||
for (std::size_t i = 0; i < static_cast<std::size_t>(Core::Hardware::NUM_CPU_CORES); ++i) {
|
||||
KThread* core_thread{};
|
||||
do {
|
||||
core_thread = kernel.Scheduler(i).GetSchedulerCurrentThread();
|
||||
} while (core_thread == this);
|
||||
}
|
||||
}
|
||||
|
||||
Result KThread::SetActivity(Svc::ThreadActivity activity) {
|
||||
// Lock ourselves.
|
||||
KScopedLightLock lk(activity_pause_lock);
|
||||
|
|
|
@ -208,6 +208,8 @@ public:
|
|||
|
||||
void Continue();
|
||||
|
||||
void WaitUntilSuspended();
|
||||
|
||||
constexpr void SetSyncedIndex(s32 index) {
|
||||
synced_index = index;
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ struct KernelCore::Impl {
|
|||
InitializeMemoryLayout();
|
||||
Init::InitializeKPageBufferSlabHeap(system);
|
||||
InitializeSchedulers();
|
||||
InitializeSuspendThreads();
|
||||
InitializeShutdownThreads();
|
||||
InitializePreemption(kernel);
|
||||
|
||||
RegisterHostThread();
|
||||
|
@ -131,9 +131,9 @@ struct KernelCore::Impl {
|
|||
CleanupObject(system_resource_limit);
|
||||
|
||||
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||
if (suspend_threads[core_id]) {
|
||||
suspend_threads[core_id]->Close();
|
||||
suspend_threads[core_id] = nullptr;
|
||||
if (shutdown_threads[core_id]) {
|
||||
shutdown_threads[core_id]->Close();
|
||||
shutdown_threads[core_id] = nullptr;
|
||||
}
|
||||
|
||||
schedulers[core_id]->Finalize();
|
||||
|
@ -238,26 +238,27 @@ struct KernelCore::Impl {
|
|||
|
||||
void InitializePreemption(KernelCore& kernel) {
|
||||
preemption_event = Core::Timing::CreateEvent(
|
||||
"PreemptionCallback", [this, &kernel](std::uintptr_t, std::chrono::nanoseconds) {
|
||||
"PreemptionCallback",
|
||||
[this, &kernel](std::uintptr_t, s64 time,
|
||||
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
|
||||
{
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
global_scheduler_context->PreemptThreads();
|
||||
}
|
||||
const auto time_interval = std::chrono::nanoseconds{std::chrono::milliseconds(10)};
|
||||
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
const auto time_interval = std::chrono::nanoseconds{std::chrono::milliseconds(10)};
|
||||
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
|
||||
system.CoreTiming().ScheduleLoopingEvent(time_interval, time_interval, preemption_event);
|
||||
}
|
||||
|
||||
void InitializeSuspendThreads() {
|
||||
void InitializeShutdownThreads() {
|
||||
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||
suspend_threads[core_id] = KThread::Create(system.Kernel());
|
||||
ASSERT(KThread::InitializeHighPriorityThread(system, suspend_threads[core_id], {}, {},
|
||||
shutdown_threads[core_id] = KThread::Create(system.Kernel());
|
||||
ASSERT(KThread::InitializeHighPriorityThread(system, shutdown_threads[core_id], {}, {},
|
||||
core_id)
|
||||
.IsSuccess());
|
||||
suspend_threads[core_id]->SetName(fmt::format("SuspendThread:{}", core_id));
|
||||
shutdown_threads[core_id]->SetName(fmt::format("SuspendThread:{}", core_id));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -779,7 +780,7 @@ struct KernelCore::Impl {
|
|||
std::weak_ptr<ServiceThread> default_service_thread;
|
||||
Common::ThreadWorker service_threads_manager;
|
||||
|
||||
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> suspend_threads;
|
||||
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> shutdown_threads;
|
||||
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
|
||||
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
|
||||
|
||||
|
@ -1093,15 +1094,26 @@ 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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -11,14 +11,16 @@
|
|||
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) {
|
||||
time_manager_event_type = Core::Timing::CreateEvent(
|
||||
"Kernel::TimeManagerCallback",
|
||||
[this](std::uintptr_t thread_handle, s64 time,
|
||||
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
|
||||
KThread* thread = reinterpret_cast<KThread*>(thread_handle);
|
||||
{
|
||||
KScopedSchedulerLock sl(system.Kernel());
|
||||
thread->OnTimer();
|
||||
}
|
||||
return std::nullopt;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -74,26 +74,34 @@ IAppletResource::IAppletResource(Core::System& system_,
|
|||
// Register update callbacks
|
||||
pad_update_event = Core::Timing::CreateEvent(
|
||||
"HID::UpdatePadCallback",
|
||||
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||
[this](std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
const auto guard = LockService();
|
||||
UpdateControllers(user_data, ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
mouse_keyboard_update_event = Core::Timing::CreateEvent(
|
||||
"HID::UpdateMouseKeyboardCallback",
|
||||
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||
[this](std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
const auto guard = LockService();
|
||||
UpdateMouseKeyboard(user_data, ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
motion_update_event = Core::Timing::CreateEvent(
|
||||
"HID::UpdateMotionCallback",
|
||||
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||
[this](std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
const auto guard = LockService();
|
||||
UpdateMotion(user_data, ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event);
|
||||
system.CoreTiming().ScheduleEvent(mouse_keyboard_update_ns, mouse_keyboard_update_event);
|
||||
system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event);
|
||||
system.CoreTiming().ScheduleLoopingEvent(pad_update_ns, pad_update_ns, pad_update_event);
|
||||
system.CoreTiming().ScheduleLoopingEvent(mouse_keyboard_update_ns, mouse_keyboard_update_ns,
|
||||
mouse_keyboard_update_event);
|
||||
system.CoreTiming().ScheduleLoopingEvent(motion_update_ns, motion_update_ns,
|
||||
motion_update_event);
|
||||
|
||||
system.HIDCore().ReloadInputDevices();
|
||||
}
|
||||
|
@ -135,13 +143,6 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data,
|
|||
}
|
||||
controller->OnUpdate(core_timing);
|
||||
}
|
||||
|
||||
// If ns_late is higher than the update rate ignore the delay
|
||||
if (ns_late > pad_update_ns) {
|
||||
ns_late = {};
|
||||
}
|
||||
|
||||
core_timing.ScheduleEvent(pad_update_ns - ns_late, pad_update_event);
|
||||
}
|
||||
|
||||
void IAppletResource::UpdateMouseKeyboard(std::uintptr_t user_data,
|
||||
|
@ -150,26 +151,12 @@ void IAppletResource::UpdateMouseKeyboard(std::uintptr_t user_data,
|
|||
|
||||
controllers[static_cast<size_t>(HidController::Mouse)]->OnUpdate(core_timing);
|
||||
controllers[static_cast<size_t>(HidController::Keyboard)]->OnUpdate(core_timing);
|
||||
|
||||
// If ns_late is higher than the update rate ignore the delay
|
||||
if (ns_late > mouse_keyboard_update_ns) {
|
||||
ns_late = {};
|
||||
}
|
||||
|
||||
core_timing.ScheduleEvent(mouse_keyboard_update_ns - ns_late, mouse_keyboard_update_event);
|
||||
}
|
||||
|
||||
void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||
auto& core_timing = system.CoreTiming();
|
||||
|
||||
controllers[static_cast<size_t>(HidController::NPad)]->OnMotionUpdate(core_timing);
|
||||
|
||||
// If ns_late is higher than the update rate ignore the delay
|
||||
if (ns_late > motion_update_ns) {
|
||||
ns_late = {};
|
||||
}
|
||||
|
||||
core_timing.ScheduleEvent(motion_update_ns - ns_late, motion_update_event);
|
||||
}
|
||||
|
||||
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
|
||||
|
|
|
@ -50,12 +50,15 @@ HidBus::HidBus(Core::System& system_)
|
|||
// Register update callbacks
|
||||
hidbus_update_event = Core::Timing::CreateEvent(
|
||||
"Hidbus::UpdateCallback",
|
||||
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||
[this](std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
const auto guard = LockService();
|
||||
UpdateHidbus(user_data, ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
system_.CoreTiming().ScheduleEvent(hidbus_update_ns, hidbus_update_event);
|
||||
system_.CoreTiming().ScheduleLoopingEvent(hidbus_update_ns, hidbus_update_ns,
|
||||
hidbus_update_event);
|
||||
}
|
||||
|
||||
HidBus::~HidBus() {
|
||||
|
@ -63,8 +66,6 @@ HidBus::~HidBus() {
|
|||
}
|
||||
|
||||
void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||
auto& core_timing = system.CoreTiming();
|
||||
|
||||
if (is_hidbus_enabled) {
|
||||
for (std::size_t i = 0; i < devices.size(); ++i) {
|
||||
if (!devices[i].is_device_initializated) {
|
||||
|
@ -82,13 +83,6 @@ void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_
|
|||
sizeof(HidbusStatusManagerEntry));
|
||||
}
|
||||
}
|
||||
|
||||
// If ns_late is higher than the update rate ignore the delay
|
||||
if (ns_late > hidbus_update_ns) {
|
||||
ns_late = {};
|
||||
}
|
||||
|
||||
core_timing.ScheduleEvent(hidbus_update_ns - ns_late, hidbus_update_event);
|
||||
}
|
||||
|
||||
std::optional<std::size_t> HidBus::GetDeviceIndexFromHandle(BusHandle handle) const {
|
||||
|
|
|
@ -69,21 +69,20 @@ NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_dr
|
|||
|
||||
// Schedule the screen composition events
|
||||
composition_event = Core::Timing::CreateEvent(
|
||||
"ScreenComposition", [this](std::uintptr_t, std::chrono::nanoseconds ns_late) {
|
||||
"ScreenComposition",
|
||||
[this](std::uintptr_t, s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
const auto lock_guard = Lock();
|
||||
Compose();
|
||||
|
||||
const auto ticks = std::chrono::nanoseconds{GetNextTicks()};
|
||||
const auto ticks_delta = ticks - ns_late;
|
||||
const auto future_ns = std::max(std::chrono::nanoseconds::zero(), ticks_delta);
|
||||
|
||||
this->system.CoreTiming().ScheduleEvent(future_ns, composition_event);
|
||||
return std::max(std::chrono::nanoseconds::zero(),
|
||||
std::chrono::nanoseconds(GetNextTicks()) - ns_late);
|
||||
});
|
||||
|
||||
if (system.IsMulticore()) {
|
||||
vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); });
|
||||
} else {
|
||||
system.CoreTiming().ScheduleEvent(frame_ns, composition_event);
|
||||
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, composition_event);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -184,10 +184,12 @@ CheatEngine::~CheatEngine() {
|
|||
void CheatEngine::Initialize() {
|
||||
event = Core::Timing::CreateEvent(
|
||||
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
|
||||
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||
[this](std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
FrameCallback(user_data, ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_NS, event);
|
||||
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event);
|
||||
|
||||
metadata.process_id = system.CurrentProcess()->GetProcessID();
|
||||
metadata.title_id = system.GetCurrentProcessProgramID();
|
||||
|
@ -237,8 +239,6 @@ void CheatEngine::FrameCallback(std::uintptr_t, std::chrono::nanoseconds ns_late
|
|||
MICROPROFILE_SCOPE(Cheat_Engine);
|
||||
|
||||
vm.Execute(metadata);
|
||||
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_NS - ns_late, event);
|
||||
}
|
||||
|
||||
} // namespace Core::Memory
|
||||
|
|
|
@ -53,8 +53,10 @@ Freezer::Freezer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& m
|
|||
: core_timing{core_timing_}, memory{memory_} {
|
||||
event = Core::Timing::CreateEvent(
|
||||
"MemoryFreezer::FrameCallback",
|
||||
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||
[this](std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
FrameCallback(user_data, ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
core_timing.ScheduleEvent(memory_freezer_ns, event);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "core/core.h"
|
||||
|
@ -25,13 +26,15 @@ u64 expected_callback = 0;
|
|||
std::mutex control_mutex;
|
||||
|
||||
template <unsigned int IDX>
|
||||
void HostCallbackTemplate(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||
std::optional<std::chrono::nanoseconds> HostCallbackTemplate(std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) {
|
||||
std::unique_lock<std::mutex> lk(control_mutex);
|
||||
static_assert(IDX < CB_IDS.size(), "IDX out of range");
|
||||
callbacks_ran_flags.set(IDX);
|
||||
REQUIRE(CB_IDS[IDX] == user_data);
|
||||
delays[IDX] = ns_late.count();
|
||||
++expected_callback;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct ScopeInit final {
|
||||
|
|
Loading…
Reference in a new issue