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