mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-01-09 11:17:30 -03:00
Merge branch 'main' into metal
This commit is contained in:
commit
68aa40518d
26 changed files with 336 additions and 149 deletions
|
@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.21.1)
|
|||
|
||||
option(ENABLE_VCPKG "Enable the vcpkg package manager" ON)
|
||||
option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF)
|
||||
option(ALLOW_PORTABLE "Allow Cemu to be run in portable mode" ON)
|
||||
|
||||
# used by CI script to set version:
|
||||
set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "")
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "audio/IAudioAPI.h"
|
||||
#include "audio/IAudioInputAPI.h"
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "config/LaunchSettings.h"
|
||||
#include "Cafe/TitleList/GameInfo.h"
|
||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||
#include "util/helpers/SystemException.h"
|
||||
|
@ -852,7 +853,7 @@ namespace CafeSystem
|
|||
module->TitleStart();
|
||||
cemu_initForGame();
|
||||
// enter scheduler
|
||||
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler)
|
||||
if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler && !LaunchSettings::ForceInterpreter())
|
||||
coreinit::OSSchedulerBegin(3);
|
||||
else
|
||||
coreinit::OSSchedulerBegin(1);
|
||||
|
|
|
@ -575,7 +575,7 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
|
|||
debuggerState.debugSession.stepInto = false;
|
||||
debuggerState.debugSession.stepOver = false;
|
||||
debuggerState.debugSession.run = false;
|
||||
while (true)
|
||||
while (debuggerState.debugSession.isTrapped)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
// check for step commands
|
||||
|
|
|
@ -460,9 +460,8 @@ void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShad
|
|||
suffix = "gs";
|
||||
else if (shader->shaderType == LatteConst::ShaderType::Pixel)
|
||||
suffix = "ps";
|
||||
fs::path dumpPath = "dump/shaders";
|
||||
dumpPath /= fmt::format("{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix);
|
||||
FileStream* fs = FileStream::createFile2(dumpPath);
|
||||
|
||||
FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix));
|
||||
if (fs)
|
||||
{
|
||||
if (shader->strBuf_shaderSource)
|
||||
|
@ -488,9 +487,8 @@ void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uin
|
|||
suffix = "copy";
|
||||
else if (type == SHADER_DUMP_TYPE_COMPUTE)
|
||||
suffix = "compute";
|
||||
fs::path dumpPath = "dump/shaders";
|
||||
dumpPath /= fmt::format("{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix);
|
||||
FileStream* fs = FileStream::createFile2(dumpPath);
|
||||
|
||||
FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix));
|
||||
if (fs)
|
||||
{
|
||||
fs->writeData(programCode, programLen);
|
||||
|
|
|
@ -29,6 +29,9 @@
|
|||
#include "util/helpers/Serializer.h"
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
#include <audio/IAudioAPI.h>
|
||||
#include <util/bootSound/BootSoundReader.h>
|
||||
#include <thread>
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
#include <psapi.h>
|
||||
|
@ -159,6 +162,118 @@ bool LoadTGAFile(const std::vector<uint8>& buffer, TGAFILE *tgaFile)
|
|||
return true;
|
||||
}
|
||||
|
||||
class BootSoundPlayer
|
||||
{
|
||||
public:
|
||||
BootSoundPlayer() = default;
|
||||
~BootSoundPlayer()
|
||||
{
|
||||
m_stopRequested = true;
|
||||
}
|
||||
|
||||
void StartSound()
|
||||
{
|
||||
if (!m_bootSndPlayThread.joinable())
|
||||
{
|
||||
m_fadeOutRequested = false;
|
||||
m_stopRequested = false;
|
||||
m_bootSndPlayThread = std::thread{[this]() {
|
||||
StreamBootSound();
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
void FadeOutSound()
|
||||
{
|
||||
m_fadeOutRequested = true;
|
||||
}
|
||||
|
||||
void ApplyFadeOutEffect(std::span<sint16> samples, uint64& fadeOutSample, uint64 fadeOutDuration)
|
||||
{
|
||||
for (size_t i = 0; i < samples.size(); i += 2)
|
||||
{
|
||||
const float decibel = (float)fadeOutSample / fadeOutDuration * -60.0f;
|
||||
const float volumeFactor = pow(10, decibel / 20);
|
||||
samples[i] *= volumeFactor;
|
||||
samples[i + 1] *= volumeFactor;
|
||||
fadeOutSample++;
|
||||
}
|
||||
}
|
||||
|
||||
void StreamBootSound()
|
||||
{
|
||||
SetThreadName("bootsnd");
|
||||
constexpr sint32 sampleRate = 48'000;
|
||||
constexpr sint32 bitsPerSample = 16;
|
||||
constexpr sint32 samplesPerBlock = sampleRate / 10; // block is 1/10th of a second
|
||||
constexpr sint32 nChannels = 2;
|
||||
static_assert(bitsPerSample % 8 == 0, "bits per sample is not a multiple of 8");
|
||||
|
||||
AudioAPIPtr bootSndAudioDev;
|
||||
|
||||
try
|
||||
{
|
||||
bootSndAudioDev = IAudioAPI::CreateDeviceFromConfig(true, sampleRate, nChannels, samplesPerBlock, bitsPerSample);
|
||||
if(!bootSndAudioDev)
|
||||
return;
|
||||
}
|
||||
catch (const std::runtime_error& ex)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Failed to initialise audio device for bootup sound");
|
||||
return;
|
||||
}
|
||||
bootSndAudioDev->SetAudioDelayOverride(4);
|
||||
bootSndAudioDev->Play();
|
||||
|
||||
std::string sndPath = fmt::format("{}/meta/{}", CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()), "bootSound.btsnd");
|
||||
sint32 fscStatus = FSC_STATUS_UNDEFINED;
|
||||
|
||||
if(!fsc_doesFileExist(sndPath.c_str()))
|
||||
return;
|
||||
|
||||
FSCVirtualFile* bootSndFileHandle = fsc_open(sndPath.c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
|
||||
if(!bootSndFileHandle)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "failed to open bootSound.btsnd");
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr sint32 audioBlockSize = samplesPerBlock * (bitsPerSample/8) * nChannels;
|
||||
BootSoundReader bootSndFileReader(bootSndFileHandle, audioBlockSize);
|
||||
|
||||
uint64 fadeOutSample = 0; // track how far into the fadeout
|
||||
constexpr uint64 fadeOutDuration = sampleRate * 2; // fadeout should last 2 seconds
|
||||
while(fadeOutSample < fadeOutDuration && !m_stopRequested)
|
||||
{
|
||||
while (bootSndAudioDev->NeedAdditionalBlocks())
|
||||
{
|
||||
sint16* data = bootSndFileReader.getSamples();
|
||||
if(data == nullptr)
|
||||
{
|
||||
// break outer loop
|
||||
m_stopRequested = true;
|
||||
break;
|
||||
}
|
||||
if(m_fadeOutRequested)
|
||||
ApplyFadeOutEffect({data, samplesPerBlock * nChannels}, fadeOutSample, fadeOutDuration);
|
||||
|
||||
bootSndAudioDev->FeedBlock(data);
|
||||
}
|
||||
// sleep for the duration of a single block
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(samplesPerBlock / (sampleRate/ 1'000)));
|
||||
}
|
||||
|
||||
if(bootSndFileHandle)
|
||||
fsc_close(bootSndFileHandle);
|
||||
}
|
||||
|
||||
private:
|
||||
std::thread m_bootSndPlayThread;
|
||||
std::atomic_bool m_fadeOutRequested = false;
|
||||
std::atomic_bool m_stopRequested = false;
|
||||
};
|
||||
static BootSoundPlayer g_bootSndPlayer;
|
||||
|
||||
void LatteShaderCache_finish()
|
||||
{
|
||||
if (g_renderer->GetType() == RendererAPI::Vulkan)
|
||||
|
@ -316,6 +431,9 @@ void LatteShaderCache_Load()
|
|||
loadBackgroundTexture(true, g_shaderCacheLoaderState.textureTVId);
|
||||
loadBackgroundTexture(false, g_shaderCacheLoaderState.textureDRCId);
|
||||
|
||||
if(GetConfig().play_boot_sound)
|
||||
g_bootSndPlayer.StartSound();
|
||||
|
||||
sint32 numLoadedShaders = 0;
|
||||
uint32 loadIndex = 0;
|
||||
|
||||
|
@ -382,6 +500,8 @@ void LatteShaderCache_Load()
|
|||
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureTVId);
|
||||
if (g_shaderCacheLoaderState.textureDRCId)
|
||||
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureDRCId);
|
||||
|
||||
g_bootSndPlayer.FadeOutSound();
|
||||
}
|
||||
|
||||
void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines)
|
||||
|
@ -846,4 +966,4 @@ void LatteShaderCache_handleDeprecatedCacheFiles(fs::path pathGeneric, fs::path
|
|||
fs::remove(pathGenericPre1_25_0, ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -396,90 +396,35 @@ namespace snd_core
|
|||
|
||||
void AXOut_init()
|
||||
{
|
||||
auto& config = GetConfig();
|
||||
const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api;
|
||||
|
||||
numQueuedFramesSndGeneric = 0;
|
||||
|
||||
std::unique_lock lock(g_audioMutex);
|
||||
if (!g_tvAudio)
|
||||
{
|
||||
sint32 channels;
|
||||
switch (config.tv_channels)
|
||||
try
|
||||
{
|
||||
case 0:
|
||||
channels = 1; // will mix mono sound on both output channels
|
||||
break;
|
||||
case 2:
|
||||
channels = 6;
|
||||
break;
|
||||
default: // stereo
|
||||
channels = 2;
|
||||
break;
|
||||
g_tvAudio = IAudioAPI::CreateDeviceFromConfig(true, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
||||
}
|
||||
|
||||
IAudioAPI::DeviceDescriptionPtr device_description;
|
||||
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
|
||||
catch (std::runtime_error& ex)
|
||||
{
|
||||
auto devices = IAudioAPI::GetDevices(audio_api);
|
||||
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.tv_device; });
|
||||
if (it != devices.end())
|
||||
device_description = *it;
|
||||
}
|
||||
|
||||
if (device_description)
|
||||
{
|
||||
try
|
||||
{
|
||||
g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
||||
g_tvAudio->SetVolume(config.tv_volume);
|
||||
}
|
||||
catch (std::runtime_error& ex)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what());
|
||||
exit(0);
|
||||
}
|
||||
cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what());
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_padAudio)
|
||||
{
|
||||
sint32 channels;
|
||||
switch (config.pad_channels)
|
||||
try
|
||||
{
|
||||
case 0:
|
||||
channels = 1; // will mix mono sound on both output channels
|
||||
break;
|
||||
case 2:
|
||||
channels = 6;
|
||||
break;
|
||||
default: // stereo
|
||||
channels = 2;
|
||||
break;
|
||||
g_padAudio = IAudioAPI::CreateDeviceFromConfig(false, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
||||
if(g_padAudio)
|
||||
g_padVolume = g_padAudio->GetVolume();
|
||||
}
|
||||
|
||||
IAudioAPI::DeviceDescriptionPtr device_description;
|
||||
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
|
||||
catch (std::runtime_error& ex)
|
||||
{
|
||||
auto devices = IAudioAPI::GetDevices(audio_api);
|
||||
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.pad_device; });
|
||||
if (it != devices.end())
|
||||
device_description = *it;
|
||||
}
|
||||
|
||||
if (device_description)
|
||||
{
|
||||
try
|
||||
{
|
||||
g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
||||
g_padAudio->SetVolume(config.pad_volume);
|
||||
g_padVolume = config.pad_volume;
|
||||
}
|
||||
catch (std::runtime_error& ex)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what());
|
||||
exit(0);
|
||||
}
|
||||
cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what());
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ CubebAPI::~CubebAPI()
|
|||
bool CubebAPI::NeedAdditionalBlocks() const
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
return m_buffer.size() < s_audioDelay * m_bytesPerBlock;
|
||||
return m_buffer.size() < GetAudioDelay() * m_bytesPerBlock;
|
||||
}
|
||||
|
||||
bool CubebAPI::FeedBlock(sint16* data)
|
||||
|
|
|
@ -210,7 +210,7 @@ void DirectSoundAPI::SetVolume(sint32 volume)
|
|||
bool DirectSoundAPI::NeedAdditionalBlocks() const
|
||||
{
|
||||
std::shared_lock lock(m_mutex);
|
||||
return m_buffer.size() < s_audioDelay;
|
||||
return m_buffer.size() < GetAudioDelay();
|
||||
}
|
||||
|
||||
std::vector<DirectSoundAPI::DeviceDescriptionPtr> DirectSoundAPI::GetDevices()
|
||||
|
|
|
@ -97,7 +97,40 @@ bool IAudioAPI::IsAudioAPIAvailable(AudioAPI api)
|
|||
return false;
|
||||
}
|
||||
|
||||
AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample)
|
||||
{
|
||||
auto& config = GetConfig();
|
||||
sint32 channels = CemuConfig::AudioChannelsToNChannels(TV ? config.tv_channels : config.pad_channels);
|
||||
return CreateDeviceFromConfig(TV, rate, channels, samples_per_block, bits_per_sample);
|
||||
}
|
||||
|
||||
AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample)
|
||||
{
|
||||
AudioAPIPtr audioAPIDev;
|
||||
|
||||
auto& config = GetConfig();
|
||||
|
||||
const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api;
|
||||
auto& selectedDevice = TV ? config.tv_device : config.pad_device;
|
||||
|
||||
if(selectedDevice.empty())
|
||||
return {};
|
||||
|
||||
IAudioAPI::DeviceDescriptionPtr device_description;
|
||||
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
|
||||
{
|
||||
auto devices = IAudioAPI::GetDevices(audio_api);
|
||||
const auto it = std::find_if(devices.begin(), devices.end(), [&selectedDevice](const auto& d) {return d->GetIdentifier() == selectedDevice; });
|
||||
if (it != devices.end())
|
||||
device_description = *it;
|
||||
}
|
||||
if (!device_description)
|
||||
throw std::runtime_error("failed to find selected device while trying to create audio device");
|
||||
|
||||
audioAPIDev = CreateDevice(audio_api, device_description, rate, channels, samples_per_block, bits_per_sample);
|
||||
audioAPIDev->SetVolume(TV ? config.tv_volume : config.pad_volume);
|
||||
return audioAPIDev;
|
||||
}
|
||||
|
||||
AudioAPIPtr IAudioAPI::CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample)
|
||||
{
|
||||
|
@ -167,3 +200,12 @@ std::vector<IAudioAPI::DeviceDescriptionPtr> IAudioAPI::GetDevices(AudioAPI api)
|
|||
}
|
||||
}
|
||||
|
||||
void IAudioAPI::SetAudioDelayOverride(uint32 delay)
|
||||
{
|
||||
m_audioDelayOverride = delay;
|
||||
}
|
||||
|
||||
uint32 IAudioAPI::GetAudioDelay() const
|
||||
{
|
||||
return m_audioDelayOverride > 0 ? m_audioDelayOverride : s_audioDelay;
|
||||
}
|
||||
|
|
|
@ -55,11 +55,15 @@ public:
|
|||
virtual bool FeedBlock(sint16* data) = 0;
|
||||
virtual bool Play() = 0;
|
||||
virtual bool Stop() = 0;
|
||||
void SetAudioDelayOverride(uint32 delay);
|
||||
uint32 GetAudioDelay() const;
|
||||
|
||||
static void PrintLogging();
|
||||
static void InitializeStatic();
|
||||
static bool IsAudioAPIAvailable(AudioAPI api);
|
||||
|
||||
|
||||
static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample);
|
||||
static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
|
||||
static std::unique_ptr<IAudioAPI> CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
|
||||
static std::vector<DeviceDescriptionPtr> GetDevices(AudioAPI api);
|
||||
|
||||
|
@ -75,9 +79,10 @@ protected:
|
|||
bool m_playing = false;
|
||||
|
||||
static std::array<bool, AudioAPIEnd> s_availableApis;
|
||||
static uint32 s_audioDelay;
|
||||
uint32 m_audioDelayOverride = 0;
|
||||
|
||||
private:
|
||||
static uint32 s_audioDelay;
|
||||
void InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample);
|
||||
|
||||
};
|
||||
|
|
|
@ -33,8 +33,8 @@ XAudio27API::XAudio27API(uint32 device_id, uint32 samplerate, uint32 channels, u
|
|||
m_wfx.Format.nChannels = channels;
|
||||
m_wfx.Format.nSamplesPerSec = samplerate;
|
||||
m_wfx.Format.wBitsPerSample = bits_per_sample;
|
||||
m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels × wBitsPerSample) / 8
|
||||
m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign.
|
||||
m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels × wBitsPerSample) / 8
|
||||
m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign.
|
||||
m_wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
|
||||
|
||||
m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
|
@ -199,9 +199,7 @@ bool XAudio27API::FeedBlock(sint16* data)
|
|||
// check if we queued too many blocks
|
||||
if(m_blocks_queued >= kBlockCount)
|
||||
{
|
||||
XAUDIO2_VOICE_STATE state{};
|
||||
m_source_voice->GetState(&state);
|
||||
m_blocks_queued = state.BuffersQueued;
|
||||
m_blocks_queued = GetQueuedBuffers();
|
||||
|
||||
if (m_blocks_queued >= kBlockCount)
|
||||
{
|
||||
|
@ -222,7 +220,14 @@ bool XAudio27API::FeedBlock(sint16* data)
|
|||
return true;
|
||||
}
|
||||
|
||||
uint32 XAudio27API::GetQueuedBuffers() const
|
||||
{
|
||||
XAUDIO2_VOICE_STATE state{};
|
||||
m_source_voice->GetState(&state);
|
||||
return state.BuffersQueued;
|
||||
}
|
||||
|
||||
bool XAudio27API::NeedAdditionalBlocks() const
|
||||
{
|
||||
return m_blocks_queued < s_audioDelay;
|
||||
return GetQueuedBuffers() < GetAudioDelay();
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ public:
|
|||
static std::vector<DeviceDescriptionPtr> GetDevices();
|
||||
|
||||
private:
|
||||
uint32 GetQueuedBuffers() const;
|
||||
|
||||
struct XAudioDeleter
|
||||
{
|
||||
void operator()(IXAudio2* ptr) const;
|
||||
|
|
|
@ -270,9 +270,7 @@ bool XAudio2API::FeedBlock(sint16* data)
|
|||
// check if we queued too many blocks
|
||||
if (m_blocks_queued >= kBlockCount)
|
||||
{
|
||||
XAUDIO2_VOICE_STATE state{};
|
||||
m_source_voice->GetState(&state);
|
||||
m_blocks_queued = state.BuffersQueued;
|
||||
m_blocks_queued = GetQueuedBuffers();
|
||||
|
||||
if (m_blocks_queued >= kBlockCount)
|
||||
{
|
||||
|
@ -293,7 +291,14 @@ bool XAudio2API::FeedBlock(sint16* data)
|
|||
return true;
|
||||
}
|
||||
|
||||
uint32 XAudio2API::GetQueuedBuffers() const
|
||||
{
|
||||
XAUDIO2_VOICE_STATE state{};
|
||||
m_source_voice->GetState(&state);
|
||||
return state.BuffersQueued;
|
||||
}
|
||||
|
||||
bool XAudio2API::NeedAdditionalBlocks() const
|
||||
{
|
||||
return m_blocks_queued < s_audioDelay;
|
||||
return GetQueuedBuffers() < GetAudioDelay();
|
||||
}
|
||||
|
|
|
@ -46,6 +46,8 @@ public:
|
|||
static const std::vector<DeviceDescriptionPtr>& GetDevices() { return s_devices; }
|
||||
|
||||
private:
|
||||
uint32 GetQueuedBuffers() const;
|
||||
|
||||
static const std::vector<DeviceDescriptionPtr>& RefreshDevices();
|
||||
|
||||
struct XAudioDeleter
|
||||
|
|
|
@ -46,6 +46,7 @@ void CemuConfig::Load(XMLConfigParser& parser)
|
|||
fullscreen = parser.get("fullscreen", fullscreen);
|
||||
proxy_server = parser.get("proxy_server", "");
|
||||
disable_screensaver = parser.get("disable_screensaver", disable_screensaver);
|
||||
play_boot_sound = parser.get("play_boot_sound", play_boot_sound);
|
||||
console_language = parser.get("console_language", console_language.GetInitValue());
|
||||
|
||||
window_position.x = parser.get("window_position").get("x", -1);
|
||||
|
@ -372,6 +373,7 @@ void CemuConfig::Save(XMLConfigParser& parser)
|
|||
config.set<bool>("fullscreen", fullscreen);
|
||||
config.set("proxy_server", proxy_server.GetValue().c_str());
|
||||
config.set<bool>("disable_screensaver", disable_screensaver);
|
||||
config.set<bool>("play_boot_sound", play_boot_sound);
|
||||
|
||||
// config.set("cpu_mode", cpu_mode.GetValue());
|
||||
//config.set("console_region", console_region.GetValue());
|
||||
|
|
|
@ -404,6 +404,7 @@ struct CemuConfig
|
|||
#endif
|
||||
ConfigValue<bool> disable_screensaver{DISABLE_SCREENSAVER_DEFAULT};
|
||||
#undef DISABLE_SCREENSAVER_DEFAULT
|
||||
ConfigValue<bool> play_boot_sound{false};
|
||||
|
||||
std::vector<std::string> game_paths;
|
||||
std::mutex game_cache_entries_mutex;
|
||||
|
@ -550,7 +551,20 @@ struct CemuConfig
|
|||
ConfigValue<bool> emulate_dimensions_toypad{false};
|
||||
}emulated_usb_devices{};
|
||||
|
||||
private:
|
||||
static int AudioChannelsToNChannels(AudioChannels kStereo)
|
||||
{
|
||||
switch (kStereo)
|
||||
{
|
||||
case 0:
|
||||
return 1; // will mix mono sound on both output channels
|
||||
case 2:
|
||||
return 6;
|
||||
default: // stereo
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
GameEntry* GetGameEntryByTitleId(uint64 titleId);
|
||||
GameEntry* CreateGameEntry(uint64 titleId);
|
||||
};
|
||||
|
|
|
@ -185,3 +185,7 @@ endif()
|
|||
if(WIN32)
|
||||
target_link_libraries(CemuGui PRIVATE bthprops)
|
||||
endif()
|
||||
|
||||
if(ALLOW_PORTABLE)
|
||||
target_compile_definitions(CemuGui PRIVATE CEMU_ALLOW_PORTABLE)
|
||||
endif ()
|
|
@ -88,12 +88,14 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Windo
|
|||
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
|
||||
fs::path portablePath = exePath.parent_path() / "portable";
|
||||
data_path = exePath.parent_path(); // the data path is always the same as the exe path
|
||||
if (fs::exists(portablePath, ec))
|
||||
#ifdef CEMU_ALLOW_PORTABLE
|
||||
if (fs::is_directory(portablePath, ec))
|
||||
{
|
||||
isPortable = true;
|
||||
user_data_path = config_path = cache_path = portablePath;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
fs::path roamingPath = GetAppDataRoamingPath() / "Cemu";
|
||||
user_data_path = config_path = cache_path = roamingPath;
|
||||
|
@ -124,12 +126,13 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Linux
|
|||
fs::path portablePath = exePath.parent_path() / "portable";
|
||||
// GetExecutablePath returns the AppImage's temporary mount location
|
||||
wxString appImagePath;
|
||||
if (wxGetEnv(("APPIMAGE"), &appImagePath))
|
||||
if (wxGetEnv("APPIMAGE", &appImagePath))
|
||||
{
|
||||
exePath = wxHelper::MakeFSPath(appImagePath);
|
||||
portablePath = exePath.parent_path() / "portable";
|
||||
}
|
||||
if (fs::exists(portablePath, ec))
|
||||
#ifdef CEMU_ALLOW_PORTABLE
|
||||
if (fs::is_directory(portablePath, ec))
|
||||
{
|
||||
isPortable = true;
|
||||
user_data_path = config_path = cache_path = portablePath;
|
||||
|
@ -137,6 +140,7 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Linux
|
|||
data_path = exePath.parent_path();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
SetAppName("Cemu");
|
||||
wxString appName = GetAppName();
|
||||
|
@ -167,16 +171,18 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for MacOS
|
|||
fs::path user_data_path, config_path, cache_path, data_path;
|
||||
auto standardPaths = wxStandardPaths::Get();
|
||||
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
|
||||
// If run from an app bundle, use its parent directory
|
||||
fs::path appPath = exePath.parent_path().parent_path().parent_path();
|
||||
fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable";
|
||||
if (fs::exists(portablePath, ec))
|
||||
// If run from an app bundle, use its parent directory
|
||||
fs::path appPath = exePath.parent_path().parent_path().parent_path();
|
||||
fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable";
|
||||
#ifdef CEMU_ALLOW_PORTABLE
|
||||
if (fs::is_directory(portablePath, ec))
|
||||
{
|
||||
isPortable = true;
|
||||
user_data_path = config_path = cache_path = portablePath;
|
||||
data_path = exePath.parent_path();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
SetAppName("Cemu");
|
||||
wxString appName = GetAppName();
|
||||
|
|
|
@ -224,8 +224,10 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook)
|
|||
#if BOOST_OS_MACOS
|
||||
m_disable_screensaver->Enable(false);
|
||||
#endif
|
||||
|
||||
// InsertEmptyRow();
|
||||
m_play_boot_sound = new wxCheckBox(box, wxID_ANY, _("Enable intro sound"));
|
||||
m_play_boot_sound->SetToolTip(_("Play bootSound file while compiling shaders/pipelines."));
|
||||
second_row->Add(m_play_boot_sound, 0, botflag, 5);
|
||||
CountRowElement();
|
||||
|
||||
m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates"));
|
||||
m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup"));
|
||||
|
@ -970,6 +972,7 @@ void GeneralSettings2::StoreConfig()
|
|||
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
||||
config.feral_gamemode = m_feral_gamemode->IsChecked();
|
||||
#endif
|
||||
config.play_boot_sound = m_play_boot_sound->IsChecked();
|
||||
config.disable_screensaver = m_disable_screensaver->IsChecked();
|
||||
// Toggle while a game is running
|
||||
if (CafeSystem::IsTitleRunning())
|
||||
|
@ -1662,6 +1665,7 @@ void GeneralSettings2::ApplyConfig()
|
|||
m_save_screenshot->SetValue(config.save_screenshot);
|
||||
|
||||
m_disable_screensaver->SetValue(config.disable_screensaver);
|
||||
m_play_boot_sound->SetValue(config.play_boot_sound);
|
||||
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
||||
m_feral_gamemode->SetValue(config.feral_gamemode);
|
||||
#endif
|
||||
|
@ -1865,20 +1869,7 @@ void GeneralSettings2::UpdateAudioDevice()
|
|||
if (m_game_launched && g_tvAudio)
|
||||
channels = g_tvAudio->GetChannels();
|
||||
else
|
||||
{
|
||||
switch (config.tv_channels)
|
||||
{
|
||||
case 0:
|
||||
channels = 1;
|
||||
break;
|
||||
case 2:
|
||||
channels = 6;
|
||||
break;
|
||||
default: // stereo
|
||||
channels = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
channels = CemuConfig::AudioChannelsToNChannels(config.tv_channels);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -1913,20 +1904,7 @@ void GeneralSettings2::UpdateAudioDevice()
|
|||
if (m_game_launched && g_padAudio)
|
||||
channels = g_padAudio->GetChannels();
|
||||
else
|
||||
{
|
||||
switch (config.pad_channels)
|
||||
{
|
||||
case 0:
|
||||
channels = 1;
|
||||
break;
|
||||
case 2:
|
||||
channels = 6;
|
||||
break;
|
||||
default: // stereo
|
||||
channels = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
channels = CemuConfig::AudioChannelsToNChannels(config.pad_channels);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -1962,20 +1940,7 @@ void GeneralSettings2::UpdateAudioDevice()
|
|||
if (m_game_launched && g_inputAudio)
|
||||
channels = g_inputAudio->GetChannels();
|
||||
else
|
||||
{
|
||||
switch (config.input_channels)
|
||||
{
|
||||
case 0:
|
||||
channels = 1;
|
||||
break;
|
||||
case 2:
|
||||
channels = 6;
|
||||
break;
|
||||
default: // stereo
|
||||
channels = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
channels = CemuConfig::AudioChannelsToNChannels(config.input_channels);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
@ -43,6 +43,7 @@ private:
|
|||
wxCheckBox* m_discord_presence, *m_fullscreen_menubar;
|
||||
wxCheckBox* m_auto_update, *m_receive_untested_releases, *m_save_screenshot;
|
||||
wxCheckBox* m_disable_screensaver;
|
||||
wxCheckBox* m_play_boot_sound;
|
||||
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
||||
wxCheckBox* m_feral_gamemode;
|
||||
#endif
|
||||
|
|
|
@ -1126,9 +1126,7 @@ void MainWindow::OnDebugDumpUsedShaders(wxCommandEvent& event)
|
|||
{
|
||||
try
|
||||
{
|
||||
// create directory
|
||||
const fs::path path(ActiveSettings::GetUserDataPath());
|
||||
fs::create_directories(path / "dump" / "shaders");
|
||||
fs::create_directories(ActiveSettings::GetUserDataPath("dump/shaders"));
|
||||
}
|
||||
catch (const std::exception & ex)
|
||||
{
|
||||
|
|
|
@ -78,9 +78,7 @@ bool DSUControllerProvider::connect()
|
|||
using namespace boost::asio;
|
||||
|
||||
ip::udp::resolver resolver(m_io_service);
|
||||
const ip::udp::resolver::query query(ip::udp::v4(), get_settings().ip, fmt::format("{}", get_settings().port),
|
||||
ip::udp::resolver::query::canonical_name);
|
||||
m_receiver_endpoint = *resolver.resolve(query);
|
||||
m_receiver_endpoint = *resolver.resolve(get_settings().ip, fmt::format("{}", get_settings().port)).cbegin();
|
||||
|
||||
if (m_socket.is_open())
|
||||
m_socket.close();
|
||||
|
|
|
@ -102,7 +102,7 @@ private:
|
|||
std::condition_variable m_writer_cond;
|
||||
|
||||
uint32 m_uid;
|
||||
boost::asio::io_service m_io_service;
|
||||
boost::asio::io_context m_io_service;
|
||||
boost::asio::ip::udp::endpoint m_receiver_endpoint;
|
||||
boost::asio::ip::udp::socket m_socket;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
add_library(CemuUtil
|
||||
boost/bluetooth.h
|
||||
bootSound/BootSoundReader.cpp
|
||||
bootSound/BootSoundReader.h
|
||||
ChunkedHeap/ChunkedHeap.h
|
||||
containers/flat_hash_map.hpp
|
||||
containers/IntervalBucketContainer.h
|
||||
|
|
51
src/util/bootSound/BootSoundReader.cpp
Normal file
51
src/util/bootSound/BootSoundReader.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#include "BootSoundReader.h"
|
||||
#include "Cafe/CafeSystem.h"
|
||||
|
||||
BootSoundReader::BootSoundReader(FSCVirtualFile* bootsndFile, sint32 blockSize) : bootsndFile(bootsndFile), blockSize(blockSize)
|
||||
{
|
||||
// crash if this constructor is invoked with a blockSize that has a different number of samples per channel
|
||||
cemu_assert(blockSize % (sizeof(sint16be) * 2) == 0);
|
||||
|
||||
fsc_setFileSeek(bootsndFile, 0);
|
||||
fsc_readFile(bootsndFile, &muteBits, 4);
|
||||
fsc_readFile(bootsndFile, &loopPoint, 4);
|
||||
|
||||
buffer.resize(blockSize / sizeof(sint16));
|
||||
bufferBE.resize(blockSize / sizeof(sint16be));
|
||||
|
||||
// workaround: SM3DW has incorrect loop point
|
||||
const auto titleId = CafeSystem::GetForegroundTitleId();
|
||||
if(titleId == 0x0005000010145D00 || titleId == 0x0005000010145C00 || titleId == 0x0005000010106100)
|
||||
loopPoint = 113074;
|
||||
}
|
||||
|
||||
sint16* BootSoundReader::getSamples()
|
||||
{
|
||||
size_t totalRead = 0;
|
||||
const size_t loopPointOffset = 8 + loopPoint * 4;
|
||||
while (totalRead < blockSize)
|
||||
{
|
||||
auto read = fsc_readFile(bootsndFile, bufferBE.data(), blockSize - totalRead);
|
||||
if (read == 0)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "failed to read PCM samples from bootSound.btsnd");
|
||||
return nullptr;
|
||||
}
|
||||
if (read % (sizeof(sint16be) * 2) != 0)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "failed to play bootSound.btsnd: reading PCM data stopped at an odd number of samples (is the file corrupt?)");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::copy_n(bufferBE.begin(), read / sizeof(sint16be), buffer.begin() + (totalRead / sizeof(sint16)));
|
||||
totalRead += read;
|
||||
if (totalRead < blockSize)
|
||||
fsc_setFileSeek(bootsndFile, loopPointOffset);
|
||||
}
|
||||
|
||||
// handle case where the end of a block of samples lines up with the end of the file
|
||||
if(fsc_getFileSeek(bootsndFile) == fsc_getFileSize(bootsndFile))
|
||||
fsc_setFileSeek(bootsndFile, loopPointOffset);
|
||||
|
||||
return buffer.data();
|
||||
}
|
20
src/util/bootSound/BootSoundReader.h
Normal file
20
src/util/bootSound/BootSoundReader.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
#include "Cafe/Filesystem/fsc.h"
|
||||
|
||||
class BootSoundReader
|
||||
{
|
||||
public:
|
||||
BootSoundReader() = delete;
|
||||
BootSoundReader(FSCVirtualFile* bootsndFile, sint32 blockSize);
|
||||
|
||||
sint16* getSamples();
|
||||
|
||||
private:
|
||||
FSCVirtualFile* bootsndFile{};
|
||||
sint32 blockSize{};
|
||||
|
||||
uint32be muteBits{};
|
||||
uint32be loopPoint{};
|
||||
std::vector<sint16> buffer{};
|
||||
std::vector<sint16be> bufferBE{};
|
||||
};
|
Loading…
Reference in a new issue