206 lines
5.9 KiB
C++
206 lines
5.9 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <xenia/apu/audio_system.h>
|
|
#include <xenia/apu/audio_driver.h>
|
|
|
|
#include <xenia/emulator.h>
|
|
#include <xenia/cpu/processor.h>
|
|
#include <xenia/cpu/xenon_thread_state.h>
|
|
|
|
|
|
using namespace xe;
|
|
using namespace xe::apu;
|
|
using namespace xe::cpu;
|
|
|
|
|
|
AudioSystem::AudioSystem(Emulator* emulator) :
|
|
emulator_(emulator), memory_(emulator->memory()),
|
|
thread_(0), running_(false) {
|
|
lock_ = xe_mutex_alloc();
|
|
memset(clients_, 0, sizeof(clients_));
|
|
for (size_t i = 0; i < maximum_client_count_; ++i) {
|
|
client_wait_handles_[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
unused_clients_.push(i);
|
|
}
|
|
}
|
|
|
|
AudioSystem::~AudioSystem() {
|
|
for (size_t i = 0; i < maximum_client_count_; ++i) {
|
|
CloseHandle(client_wait_handles_[i]);
|
|
}
|
|
xe_mutex_free(lock_);
|
|
}
|
|
|
|
X_STATUS AudioSystem::Setup() {
|
|
processor_ = emulator_->processor();
|
|
|
|
// Let the processor know we want register access callbacks.
|
|
RegisterAccessCallbacks callbacks;
|
|
callbacks.context = this;
|
|
callbacks.handles = (RegisterHandlesCallback)HandlesRegisterThunk;
|
|
callbacks.read = (RegisterReadCallback)ReadRegisterThunk;
|
|
callbacks.write = (RegisterWriteCallback)WriteRegisterThunk;
|
|
emulator_->processor()->AddRegisterAccessCallbacks(callbacks);
|
|
|
|
// Setup worker thread state. This lets us make calls into guest code.
|
|
thread_state_ = new XenonThreadState(
|
|
emulator_->processor()->runtime(), 0, 16 * 1024, 0);
|
|
thread_state_->set_name("Audio Worker");
|
|
thread_block_ = (uint32_t)memory_->HeapAlloc(
|
|
0, 2048, alloy::MEMORY_FLAG_ZERO);
|
|
thread_state_->context()->r[13] = thread_block_;
|
|
|
|
// Create worker thread.
|
|
// This will initialize the audio system.
|
|
// Init needs to happen there so that any thread-local stuff
|
|
// is created on the right thread.
|
|
running_ = true;
|
|
thread_ = xe_thread_create(
|
|
"AudioSystem",
|
|
(xe_thread_callback)ThreadStartThunk, this);
|
|
xe_thread_start(thread_);
|
|
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
void AudioSystem::ThreadStart() {
|
|
// Initialize driver and ringbuffer.
|
|
Initialize();
|
|
|
|
auto processor = emulator_->processor();
|
|
|
|
// Main run loop.
|
|
while (running_) {
|
|
auto result = WaitForMultipleObjectsEx(maximum_client_count_, client_wait_handles_, FALSE, INFINITE, FALSE);
|
|
if (result == WAIT_FAILED) {
|
|
DWORD err = GetLastError();
|
|
XEASSERTALWAYS();
|
|
break;
|
|
}
|
|
|
|
size_t pumped = 0;
|
|
if (result >= WAIT_OBJECT_0 && result <= WAIT_OBJECT_0 + (maximum_client_count_ - 1)) {
|
|
size_t index = result - WAIT_OBJECT_0;
|
|
do {
|
|
xe_mutex_lock(lock_);
|
|
uint32_t client_callback = clients_[index].callback;
|
|
uint32_t client_callback_arg = clients_[index].wrapped_callback_arg;
|
|
xe_mutex_unlock(lock_);
|
|
if (client_callback) {
|
|
processor->Execute(thread_state_, client_callback, client_callback_arg, 0);
|
|
}
|
|
pumped++;
|
|
index++;
|
|
} while (index < maximum_client_count_ && WaitForSingleObject(client_wait_handles_[index], 0) == WAIT_OBJECT_0);
|
|
}
|
|
|
|
if (!running_) {
|
|
break;
|
|
}
|
|
|
|
if (!pumped) {
|
|
SCOPE_profile_cpu_i("apu", "Sleep");
|
|
Sleep(500);
|
|
}
|
|
}
|
|
running_ = false;
|
|
|
|
// TODO(benvanik): call module API to kill?
|
|
}
|
|
|
|
void AudioSystem::Initialize() {
|
|
}
|
|
|
|
void AudioSystem::Shutdown() {
|
|
running_ = false;
|
|
xe_thread_join(thread_);
|
|
xe_thread_release(thread_);
|
|
|
|
delete thread_state_;
|
|
memory()->HeapFree(thread_block_, 0);
|
|
}
|
|
|
|
X_STATUS AudioSystem::RegisterClient(
|
|
uint32_t callback, uint32_t callback_arg, size_t* out_index) {
|
|
SCOPE_profile_cpu_f("apu");
|
|
|
|
XEASSERTTRUE(unused_clients_.size());
|
|
xe_mutex_lock(lock_);
|
|
|
|
auto index = unused_clients_.front();
|
|
|
|
auto wait_handle = client_wait_handles_[index];
|
|
ResetEvent(wait_handle);
|
|
AudioDriver* driver;
|
|
auto result = CreateDriver(index, wait_handle, &driver);
|
|
if (XFAILED(result)) {
|
|
return result;
|
|
}
|
|
XEASSERTNOTNULL(driver != NULL);
|
|
|
|
unused_clients_.pop();
|
|
|
|
uint32_t ptr = (uint32_t)memory()->HeapAlloc(0, 0x4, 0);
|
|
auto mem = memory()->membase();
|
|
XESETUINT32BE(mem + ptr, callback_arg);
|
|
|
|
clients_[index] = { driver, callback, callback_arg, ptr };
|
|
|
|
if (out_index) {
|
|
*out_index = index;
|
|
}
|
|
|
|
xe_mutex_unlock(lock_);
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
void AudioSystem::SubmitFrame(size_t index, uint32_t samples_ptr) {
|
|
SCOPE_profile_cpu_f("apu");
|
|
|
|
xe_mutex_lock(lock_);
|
|
XEASSERTTRUE(index < maximum_client_count_);
|
|
XEASSERTTRUE(clients_[index].driver != NULL);
|
|
(clients_[index].driver)->SubmitFrame(samples_ptr);
|
|
ResetEvent(client_wait_handles_[index]);
|
|
xe_mutex_unlock(lock_);
|
|
}
|
|
|
|
void AudioSystem::UnregisterClient(size_t index) {
|
|
SCOPE_profile_cpu_f("apu");
|
|
|
|
xe_mutex_lock(lock_);
|
|
XEASSERTTRUE(index < maximum_client_count_);
|
|
DestroyDriver(clients_[index].driver);
|
|
clients_[index] = { 0 };
|
|
unused_clients_.push(index);
|
|
xe_mutex_unlock(lock_);
|
|
}
|
|
|
|
bool AudioSystem::HandlesRegister(uint64_t addr) {
|
|
return (addr & 0xFFFF0000) == 0x7FEA0000;
|
|
}
|
|
|
|
// free60 may be useful here, however it looks like it's using a different
|
|
// piece of hardware:
|
|
// https://github.com/Free60Project/libxenon/blob/master/libxenon/drivers/xenon_sound/sound.c
|
|
|
|
uint64_t AudioSystem::ReadRegister(uint64_t addr) {
|
|
uint32_t r = addr & 0xFFFF;
|
|
XELOGAPU("ReadRegister(%.4X)", r);
|
|
// 1800h is read on startup and stored -- context? buffers?
|
|
// 1818h is read during a lock?
|
|
return 0;
|
|
}
|
|
|
|
void AudioSystem::WriteRegister(uint64_t addr, uint64_t value) {
|
|
uint32_t r = addr & 0xFFFF;
|
|
XELOGAPU("WriteRegister(%.4X, %.8X)", r, value);
|
|
// 1804h is written to with 0x02000000 and 0x03000000 around a lock operation
|
|
} |