diff --git a/CMakeLists.txt b/CMakeLists.txt index 172733840..d0c27c503 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -890,7 +890,8 @@ if (ENABLE_DISCORD_RPC) target_compile_definitions(shadps4 PRIVATE ENABLE_DISCORD_RPC) endif() -if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +# Optional due to https://github.com/shadps4-emu/shadPS4/issues/1704 +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND ENABLE_USERFAULTFD) target_compile_definitions(shadps4 PRIVATE ENABLE_USERFAULTFD) endif() diff --git a/src/common/config.cpp b/src/common/config.cpp index 991065933..b16c9999b 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -34,6 +34,7 @@ namespace Config { static bool isNeo = false; static bool isFullscreen = false; static bool playBGM = false; +static bool isTrophyPopupDisabled = false; static int BGMvolume = 50; static bool enableDiscordRPC = false; static u32 screenWidth = 1280; @@ -65,6 +66,8 @@ static bool vkCrashDiagnostic = false; static s16 cursorState = HideCursorState::Idle; static int cursorHideTimeout = 5; // 5 seconds (default) static bool separateupdatefolder = false; +static bool compatibilityData = false; +static bool checkCompatibilityOnStartup = false; // Gui std::vector settings_install_dirs = {}; @@ -97,6 +100,10 @@ bool isFullscreenMode() { return isFullscreen; } +bool getisTrophyPopupDisabled() { + return isTrophyPopupDisabled; +} + bool getPlayBGM() { return playBGM; } @@ -229,6 +236,14 @@ bool getSeparateUpdateEnabled() { return separateupdatefolder; } +bool getCompatibilityEnabled() { + return compatibilityData; +} + +bool getCheckCompatibilityOnStartup() { + return checkCompatibilityOnStartup; +} + void setGpuId(s32 selectedGpuId) { gpuId = selectedGpuId; } @@ -289,6 +304,10 @@ void setFullscreenMode(bool enable) { isFullscreen = enable; } +void setisTrophyPopupDisabled(bool disable) { + isTrophyPopupDisabled = disable; +} + void setPlayBGM(bool enable) { playBGM = enable; } @@ -353,6 +372,14 @@ void setSeparateUpdateEnabled(bool use) { separateupdatefolder = use; } +void setCompatibilityEnabled(bool use) { + compatibilityData = use; +} + +void setCheckCompatibilityOnStartup(bool use) { + checkCompatibilityOnStartup = use; +} + void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) { main_window_geometry_x = x; main_window_geometry_y = y; @@ -540,6 +567,7 @@ void load(const std::filesystem::path& path) { isNeo = toml::find_or(general, "isPS4Pro", false); isFullscreen = toml::find_or(general, "Fullscreen", false); playBGM = toml::find_or(general, "playBGM", false); + isTrophyPopupDisabled = toml::find_or(general, "isTrophyPopupDisabled", false); BGMvolume = toml::find_or(general, "BGMvolume", 50); enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", true); logFilter = toml::find_or(general, "logFilter", ""); @@ -553,6 +581,9 @@ void load(const std::filesystem::path& path) { isShowSplash = toml::find_or(general, "showSplash", true); isAutoUpdate = toml::find_or(general, "autoUpdate", false); separateupdatefolder = toml::find_or(general, "separateUpdateEnabled", false); + compatibilityData = toml::find_or(general, "compatibilityEnabled", false); + checkCompatibilityOnStartup = + toml::find_or(general, "checkCompatibilityOnStartup", false); } if (data.contains("Input")) { @@ -656,6 +687,7 @@ void save(const std::filesystem::path& path) { data["General"]["isPS4Pro"] = isNeo; data["General"]["Fullscreen"] = isFullscreen; + data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled; data["General"]["playBGM"] = playBGM; data["General"]["BGMvolume"] = BGMvolume; data["General"]["enableDiscordRPC"] = enableDiscordRPC; @@ -666,6 +698,8 @@ void save(const std::filesystem::path& path) { data["General"]["showSplash"] = isShowSplash; data["General"]["autoUpdate"] = isAutoUpdate; data["General"]["separateUpdateEnabled"] = separateupdatefolder; + data["General"]["compatibilityEnabled"] = compatibilityData; + data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; data["Input"]["cursorState"] = cursorState; data["Input"]["cursorHideTimeout"] = cursorHideTimeout; data["Input"]["backButtonBehavior"] = backButtonBehavior; @@ -751,6 +785,7 @@ void saveMainWindow(const std::filesystem::path& path) { void setDefaultValues() { isNeo = false; isFullscreen = false; + isTrophyPopupDisabled = false; playBGM = false; BGMvolume = 50; enableDiscordRPC = true; @@ -787,6 +822,8 @@ void setDefaultValues() { m_language = 1; gpuId = -1; separateupdatefolder = false; + compatibilityData = false; + checkCompatibilityOnStartup = false; } } // namespace Config diff --git a/src/common/config.h b/src/common/config.h index 0b2cae6ee..ffaf276a4 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -19,8 +19,11 @@ bool isNeoMode(); bool isFullscreenMode(); bool getPlayBGM(); int getBGMvolume(); +bool getisTrophyPopupDisabled(); bool getEnableDiscordRPC(); bool getSeparateUpdateEnabled(); +bool getCompatibilityEnabled(); +bool getCheckCompatibilityOnStartup(); std::string getLogFilter(); std::string getLogType(); @@ -61,6 +64,7 @@ void setGpuId(s32 selectedGpuId); void setScreenWidth(u32 width); void setScreenHeight(u32 height); void setFullscreenMode(bool enable); +void setisTrophyPopupDisabled(bool disable); void setPlayBGM(bool enable); void setBGMvolume(int volume); void setEnableDiscordRPC(bool enable); @@ -70,6 +74,8 @@ void setUserName(const std::string& type); void setUpdateChannel(const std::string& type); void setSeparateUpdateEnabled(bool use); void setGameInstallDirs(const std::vector& settings_install_dirs_config); +void setCompatibilityEnabled(bool use); +void setCheckCompatibilityOnStartup(bool use); void setCursorState(s16 cursorState); void setCursorHideTimeout(int newcursorHideTimeout); diff --git a/src/core/file_format/splash.cpp b/src/core/file_format/splash.cpp index b68702157..4eb701cf7 100644 --- a/src/core/file_format/splash.cpp +++ b/src/core/file_format/splash.cpp @@ -9,7 +9,7 @@ #include "splash.h" bool Splash::Open(const std::filesystem::path& filepath) { - ASSERT_MSG(filepath.stem().string() != "png", "Unexpected file format passed"); + ASSERT_MSG(filepath.extension().string() == ".png", "Unexpected file format passed"); Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); if (!file.IsOpen()) { diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 45ba67b93..bf340e9e3 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -171,6 +171,9 @@ void HandleTable::DeleteHandle(int d) { File* HandleTable::GetFile(int d) { std::scoped_lock lock{m_mutex}; + if (d < 0 || d >= m_files.size()) { + return nullptr; + } return m_files.at(d); } diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index 78b04cc90..db43ee928 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include +#include #include #include "common/assert.h" @@ -13,7 +15,22 @@ namespace Libraries::AudioOut { -static std::unique_ptr audio; +struct PortOut { + void* impl; + u32 samples_num; + u32 freq; + OrbisAudioOutParamFormat format; + OrbisAudioOutPort type; + int channels_num; + bool is_float; + std::array volume; + u8 sample_size; + bool is_open; +}; +std::shared_mutex ports_mutex; +std::array ports_out{}; + +static std::unique_ptr audio; static std::string_view GetAudioOutPort(OrbisAudioOutPort port) { switch (port) { @@ -70,6 +87,58 @@ static std::string_view GetAudioOutParamAttr(OrbisAudioOutParamAttr attr) { } } +static bool IsFormatFloat(const OrbisAudioOutParamFormat format) { + switch (format) { + case OrbisAudioOutParamFormat::S16Mono: + case OrbisAudioOutParamFormat::S16Stereo: + case OrbisAudioOutParamFormat::S16_8CH: + case OrbisAudioOutParamFormat::S16_8CH_Std: + return false; + case OrbisAudioOutParamFormat::FloatMono: + case OrbisAudioOutParamFormat::FloatStereo: + case OrbisAudioOutParamFormat::Float_8CH: + case OrbisAudioOutParamFormat::Float_8CH_Std: + return true; + default: + UNREACHABLE_MSG("Unknown format"); + } +} + +static int GetFormatNumChannels(const OrbisAudioOutParamFormat format) { + switch (format) { + case OrbisAudioOutParamFormat::S16Mono: + case OrbisAudioOutParamFormat::FloatMono: + return 1; + case OrbisAudioOutParamFormat::S16Stereo: + case OrbisAudioOutParamFormat::FloatStereo: + return 2; + case OrbisAudioOutParamFormat::S16_8CH: + case OrbisAudioOutParamFormat::Float_8CH: + case OrbisAudioOutParamFormat::S16_8CH_Std: + case OrbisAudioOutParamFormat::Float_8CH_Std: + return 8; + default: + UNREACHABLE_MSG("Unknown format"); + } +} + +static u8 GetFormatSampleSize(const OrbisAudioOutParamFormat format) { + switch (format) { + case OrbisAudioOutParamFormat::S16Mono: + case OrbisAudioOutParamFormat::S16Stereo: + case OrbisAudioOutParamFormat::S16_8CH: + case OrbisAudioOutParamFormat::S16_8CH_Std: + return 2; + case OrbisAudioOutParamFormat::FloatMono: + case OrbisAudioOutParamFormat::FloatStereo: + case OrbisAudioOutParamFormat::Float_8CH: + case OrbisAudioOutParamFormat::Float_8CH_Std: + return 4; + default: + UNREACHABLE_MSG("Unknown format"); + } +} + int PS4_SYSV_ABI sceAudioOutDeviceIdOpen() { LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); return ORBIS_OK; @@ -110,8 +179,21 @@ int PS4_SYSV_ABI sceAudioOutChangeAppModuleState() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAudioOutClose() { - LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); +int PS4_SYSV_ABI sceAudioOutClose(s32 handle) { + LOG_INFO(Lib_AudioOut, "handle = {}", handle); + if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + } + + std::scoped_lock lock(ports_mutex); + auto& port = ports_out.at(handle - 1); + if (!port.is_open) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + } + + audio->Close(port.impl); + port.impl = nullptr; + port.is_open = false; return ORBIS_OK; } @@ -180,16 +262,21 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } - const auto [type, channels_num] = audio->GetStatus(handle); + std::scoped_lock lock(ports_mutex); + const auto& port = ports_out.at(handle - 1); + if (!port.is_open) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + } + state->rerouteCounter = 0; state->volume = 127; - switch (type) { + switch (port.type) { case OrbisAudioOutPort::Main: case OrbisAudioOutPort::Bgm: case OrbisAudioOutPort::Voice: state->output = 1; - state->channel = (channels_num > 2 ? 2 : channels_num); + state->channel = port.channels_num > 2 ? 2 : port.channels_num; break; case OrbisAudioOutPort::Personal: case OrbisAudioOutPort::Padspk: @@ -276,7 +363,7 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, u32 sample_rate, OrbisAudioOutParamExtendedInformation param_type) { LOG_INFO(Lib_AudioOut, - "AudioOutOpen id = {} port_type = {} index = {} lenght= {} sample_rate = {} " + "id = {} port_type = {} index = {} length = {} sample_rate = {} " "param_type = {} attr = {}", user_id, GetAudioOutPort(port_type), index, length, sample_rate, GetAudioOutParamFormat(param_type.data_format), @@ -310,7 +397,26 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, LOG_ERROR(Lib_AudioOut, "Invalid format attribute"); return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT; } - return audio->Open(port_type, length, sample_rate, format); + + std::scoped_lock lock{ports_mutex}; + const auto port = std::ranges::find(ports_out, false, &PortOut::is_open); + if (port == ports_out.end()) { + LOG_ERROR(Lib_AudioOut, "Audio ports are full"); + return ORBIS_AUDIO_OUT_ERROR_PORT_FULL; + } + + port->is_open = true; + port->type = port_type; + port->samples_num = length; + port->freq = sample_rate; + port->format = format; + port->is_float = IsFormatFloat(format); + port->channels_num = GetFormatNumChannels(format); + port->sample_size = GetFormatSampleSize(format); + port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB); + + port->impl = audio->Open(port->is_float, port->channels_num, port->freq); + return std::distance(ports_out.begin(), port) + 1; } int PS4_SYSV_ABI sceAudioOutOpenEx() { @@ -326,7 +432,15 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) { // Nothing to output return ORBIS_OK; } - return audio->Output(handle, ptr); + + auto& port = ports_out.at(handle - 1); + if (!port.is_open) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + } + + const size_t data_size = port.samples_num * port.sample_size * port.channels_num; + audio->Output(port.impl, ptr, data_size); + return ORBIS_OK; } int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) { @@ -431,7 +545,42 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) { if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } - return audio->SetVolume(handle, flag, vol); + + std::scoped_lock lock(ports_mutex); + auto& port = ports_out.at(handle - 1); + if (!port.is_open) { + return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; + } + + for (int i = 0; i < port.channels_num; i++, flag >>= 1u) { + auto bit = flag & 0x1u; + if (bit == 1) { + int src_index = i; + if (port.format == OrbisAudioOutParamFormat::Float_8CH_Std || + port.format == OrbisAudioOutParamFormat::S16_8CH_Std) { + switch (i) { + case 4: + src_index = 6; + break; + case 5: + src_index = 7; + break; + case 6: + src_index = 4; + break; + case 7: + src_index = 5; + break; + default: + break; + } + } + port.volume[i] = vol[src_index]; + } + } + + audio->SetVolume(port.impl, port.volume); + return ORBIS_OK; } int PS4_SYSV_ABI sceAudioOutSetVolumeDown() { diff --git a/src/core/libraries/audio/audioout.h b/src/core/libraries/audio/audioout.h index e8e718b87..c66a0e9f5 100644 --- a/src/core/libraries/audio/audioout.h +++ b/src/core/libraries/audio/audioout.h @@ -64,7 +64,7 @@ int PS4_SYSV_ABI sceAudioOutA3dExit(); int PS4_SYSV_ABI sceAudioOutA3dInit(); int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid(); int PS4_SYSV_ABI sceAudioOutChangeAppModuleState(); -int PS4_SYSV_ABI sceAudioOutClose(); +int PS4_SYSV_ABI sceAudioOutClose(s32 handle); int PS4_SYSV_ABI sceAudioOutDetachFromApplicationByPid(); int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode(); int PS4_SYSV_ABI sceAudioOutExGetSystemInfo(); diff --git a/src/core/libraries/audio/audioout_backend.h b/src/core/libraries/audio/audioout_backend.h new file mode 100644 index 000000000..238ef0201 --- /dev/null +++ b/src/core/libraries/audio/audioout_backend.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Libraries::AudioOut { + +class AudioOutBackend { +public: + AudioOutBackend() = default; + virtual ~AudioOutBackend() = default; + + virtual void* Open(bool is_float, int num_channels, u32 sample_rate) = 0; + virtual void Close(void* impl) = 0; + virtual void Output(void* impl, const void* ptr, size_t size) = 0; + virtual void SetVolume(void* impl, std::array ch_volumes) = 0; +}; + +} // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/sdl_audio.cpp b/src/core/libraries/audio/sdl_audio.cpp index 8cc823abe..ce385ad9c 100644 --- a/src/core/libraries/audio/sdl_audio.cpp +++ b/src/core/libraries/audio/sdl_audio.cpp @@ -1,141 +1,44 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include #include #include #include "common/assert.h" -#include "core/libraries/audio/audioout_error.h" #include "core/libraries/audio/sdl_audio.h" namespace Libraries::AudioOut { constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold -s32 SDLAudioOut::Open(OrbisAudioOutPort type, u32 samples_num, u32 freq, - OrbisAudioOutParamFormat format) { - std::scoped_lock lock{m_mutex}; - const auto port = std::ranges::find(ports_out, false, &PortOut::is_open); - if (port == ports_out.end()) { - LOG_ERROR(Lib_AudioOut, "Audio ports are full"); - return ORBIS_AUDIO_OUT_ERROR_PORT_FULL; - } - - port->is_open = true; - port->type = type; - port->samples_num = samples_num; - port->freq = freq; - port->format = format; - SDL_AudioFormat sampleFormat; - switch (format) { - case OrbisAudioOutParamFormat::S16Mono: - sampleFormat = SDL_AUDIO_S16; - port->channels_num = 1; - port->sample_size = 2; - break; - case OrbisAudioOutParamFormat::FloatMono: - sampleFormat = SDL_AUDIO_F32; - port->channels_num = 1; - port->sample_size = 4; - break; - case OrbisAudioOutParamFormat::S16Stereo: - sampleFormat = SDL_AUDIO_S16; - port->channels_num = 2; - port->sample_size = 2; - break; - case OrbisAudioOutParamFormat::FloatStereo: - sampleFormat = SDL_AUDIO_F32; - port->channels_num = 2; - port->sample_size = 4; - break; - case OrbisAudioOutParamFormat::S16_8CH: - sampleFormat = SDL_AUDIO_S16; - port->channels_num = 8; - port->sample_size = 2; - break; - case OrbisAudioOutParamFormat::Float_8CH: - sampleFormat = SDL_AUDIO_F32; - port->channels_num = 8; - port->sample_size = 4; - break; - case OrbisAudioOutParamFormat::S16_8CH_Std: - sampleFormat = SDL_AUDIO_S16; - port->channels_num = 8; - port->sample_size = 2; - break; - case OrbisAudioOutParamFormat::Float_8CH_Std: - sampleFormat = SDL_AUDIO_F32; - port->channels_num = 8; - port->sample_size = 4; - break; - default: - UNREACHABLE_MSG("Unknown format"); - } - - port->volume.fill(Libraries::AudioOut::SCE_AUDIO_OUT_VOLUME_0DB); - +void* SDLAudioOut::Open(bool is_float, int num_channels, u32 sample_rate) { SDL_AudioSpec fmt; SDL_zero(fmt); - fmt.format = sampleFormat; - fmt.channels = port->channels_num; - fmt.freq = freq; - port->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, NULL, NULL); - SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(port->stream)); - return std::distance(ports_out.begin(), port) + 1; + fmt.format = is_float ? SDL_AUDIO_F32 : SDL_AUDIO_S16; + fmt.channels = num_channels; + fmt.freq = sample_rate; + + auto* stream = + SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr); + SDL_ResumeAudioStreamDevice(stream); + return stream; } -s32 SDLAudioOut::Output(s32 handle, const void* ptr) { - auto& port = ports_out.at(handle - 1); - if (!port.is_open) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; - } +void SDLAudioOut::Close(void* impl) { + SDL_DestroyAudioStream(static_cast(impl)); +} - const size_t data_size = port.samples_num * port.sample_size * port.channels_num; - bool result = SDL_PutAudioStreamData(port.stream, ptr, data_size); - while (SDL_GetAudioStreamAvailable(port.stream) > AUDIO_STREAM_BUFFER_THRESHOLD) { +void SDLAudioOut::Output(void* impl, const void* ptr, size_t size) { + auto* stream = static_cast(impl); + SDL_PutAudioStreamData(stream, ptr, size); + while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) { SDL_Delay(0); } - return result ? ORBIS_OK : -1; } -s32 SDLAudioOut::SetVolume(s32 handle, s32 bitflag, s32* volume) { - using Libraries::AudioOut::OrbisAudioOutParamFormat; - auto& port = ports_out.at(handle - 1); - if (!port.is_open) { - return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; - } - - for (int i = 0; i < port.channels_num; i++, bitflag >>= 1u) { - auto bit = bitflag & 0x1u; - - if (bit == 1) { - int src_index = i; - if (port.format == OrbisAudioOutParamFormat::Float_8CH_Std || - port.format == OrbisAudioOutParamFormat::S16_8CH_Std) { - switch (i) { - case 4: - src_index = 6; - break; - case 5: - src_index = 7; - break; - case 6: - src_index = 4; - break; - case 7: - src_index = 5; - break; - default: - break; - } - } - port.volume[i] = volume[src_index]; - } - } - - return ORBIS_OK; +void SDLAudioOut::SetVolume(void* impl, std::array ch_volumes) { + // Not yet implemented } } // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/sdl_audio.h b/src/core/libraries/audio/sdl_audio.h index 2c34f8e29..d55f2f6e3 100644 --- a/src/core/libraries/audio/sdl_audio.h +++ b/src/core/libraries/audio/sdl_audio.h @@ -3,40 +3,16 @@ #pragma once -#include -#include -#include "core/libraries/audio/audioout.h" +#include "core/libraries/audio/audioout_backend.h" namespace Libraries::AudioOut { -class SDLAudioOut { +class SDLAudioOut final : public AudioOutBackend { public: - explicit SDLAudioOut() = default; - ~SDLAudioOut() = default; - - s32 Open(OrbisAudioOutPort type, u32 samples_num, u32 freq, OrbisAudioOutParamFormat format); - s32 Output(s32 handle, const void* ptr); - s32 SetVolume(s32 handle, s32 bitflag, s32* volume); - - constexpr std::pair GetStatus(s32 handle) const { - const auto& port = ports_out.at(handle - 1); - return std::make_pair(port.type, port.channels_num); - } - -private: - struct PortOut { - SDL_AudioStream* stream; - u32 samples_num; - u32 freq; - OrbisAudioOutParamFormat format; - OrbisAudioOutPort type; - int channels_num; - std::array volume; - u8 sample_size; - bool is_open; - }; - std::shared_mutex m_mutex; - std::array ports_out{}; + void* Open(bool is_float, int num_channels, u32 sample_rate) override; + void Close(void* impl) override; + void Output(void* impl, const void* ptr, size_t size) override; + void SetVolume(void* impl, std::array ch_volumes) override; }; } // namespace Libraries::AudioOut diff --git a/src/core/libraries/np_trophy/trophy_ui.cpp b/src/core/libraries/np_trophy/trophy_ui.cpp index 55ef7b8de..4bb8c8240 100644 --- a/src/core/libraries/np_trophy/trophy_ui.cpp +++ b/src/core/libraries/np_trophy/trophy_ui.cpp @@ -5,6 +5,7 @@ #include #include #include "common/assert.h" +#include "common/config.h" #include "common/singleton.h" #include "imgui/imgui_std.h" #include "trophy_ui.h" @@ -82,7 +83,10 @@ void TrophyUI::Draw() { void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::string& trophyName) { std::lock_guard lock(queueMtx); - if (current_trophy_ui.has_value()) { + + if (Config::getisTrophyPopupDisabled()) { + return; + } else if (current_trophy_ui.has_value()) { TrophyInfo new_trophy; new_trophy.trophy_icon_path = trophyIconPath; new_trophy.trophy_name = trophyName; diff --git a/src/qt_gui/compatibility_info.cpp b/src/qt_gui/compatibility_info.cpp index c8d6bf36d..aecac60cd 100644 --- a/src/qt_gui/compatibility_info.cpp +++ b/src/qt_gui/compatibility_info.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "common/path_util.h" #include "compatibility_info.h" @@ -22,7 +23,8 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) { return; QNetworkReply* reply = FetchPage(1); - WaitForReply(reply); + if (!WaitForReply(reply)) + return; QProgressDialog dialog(tr("Fetching compatibility data, please wait"), tr("Cancel"), 0, 0, parent); @@ -57,12 +59,17 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) { } future_watcher.setFuture(QtConcurrent::map(replies, WaitForReply)); - connect(&future_watcher, &QFutureWatcher::finished, [&]() { + connect(&future_watcher, &QFutureWatcher::finished, [&]() { for (int i = 0; i < remaining_pages; i++) { - if (replies[i]->error() == QNetworkReply::NoError) { - ExtractCompatibilityInfo(replies[i]->readAll()); + if (replies[i]->bytesAvailable()) { + if (replies[i]->error() == QNetworkReply::NoError) { + ExtractCompatibilityInfo(replies[i]->readAll()); + } + replies[i]->deleteLater(); + } else { + // This means the request timed out + return; } - replies[i]->deleteLater(); } QFile compatibility_file(m_compatibility_filename); @@ -83,6 +90,16 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) { dialog.reset(); }); + connect(&future_watcher, &QFutureWatcher::canceled, [&]() { + // Cleanup if user cancels pulling data + for (int i = 0; i < remaining_pages; i++) { + if (!replies[i]->bytesAvailable()) { + replies[i]->deleteLater(); + } else if (!replies[i]->isFinished()) { + replies[i]->abort(); + } + } + }); connect(&dialog, &QProgressDialog::canceled, &future_watcher, &QFutureWatcher::cancel); dialog.setRange(0, remaining_pages); connect(&future_watcher, &QFutureWatcher::progressValueChanged, &dialog, @@ -105,20 +122,34 @@ QNetworkReply* CompatibilityInfoClass::FetchPage(int page_num) { return reply; } -void CompatibilityInfoClass::WaitForReply(QNetworkReply* reply) { +bool CompatibilityInfoClass::WaitForReply(QNetworkReply* reply) { + // Returns true if reply succeeded, false if reply timed out + QTimer timer; + timer.setSingleShot(true); + QEventLoop loop; connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + timer.start(5000); loop.exec(); - return; + + if (timer.isActive()) { + timer.stop(); + return true; + } else { + disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + reply->abort(); + return false; + } }; CompatibilityEntry CompatibilityInfoClass::GetCompatibilityInfo(const std::string& serial) { QString title_id = QString::fromStdString(serial); if (m_compatibility_database.contains(title_id)) { { + QJsonObject compatibility_obj = m_compatibility_database[title_id].toObject(); for (int os_int = 0; os_int != static_cast(OSType::Last); os_int++) { QString os_string = OSTypeToString.at(static_cast(os_int)); - QJsonObject compatibility_obj = m_compatibility_database[title_id].toObject(); if (compatibility_obj.contains(os_string)) { QJsonObject compatibility_entry_obj = compatibility_obj[os_string].toObject(); CompatibilityEntry compatibility_entry{ @@ -133,7 +164,9 @@ CompatibilityEntry CompatibilityInfoClass::GetCompatibilityInfo(const std::strin } } } - return CompatibilityEntry{CompatibilityStatus::Unknown}; + + return CompatibilityEntry{CompatibilityStatus::Unknown, "", QDateTime::currentDateTime(), "", + 0}; } bool CompatibilityInfoClass::LoadCompatibilityFile() { diff --git a/src/qt_gui/compatibility_info.h b/src/qt_gui/compatibility_info.h index 2b970670a..dcbaef847 100644 --- a/src/qt_gui/compatibility_info.h +++ b/src/qt_gui/compatibility_info.h @@ -57,6 +57,7 @@ class CompatibilityInfoClass : public QObject { public: // Please think of a better alternative inline static const std::unordered_map LabelToCompatStatus = { + {QStringLiteral("status-unknown"), CompatibilityStatus::Unknown}, {QStringLiteral("status-nothing"), CompatibilityStatus::Nothing}, {QStringLiteral("status-boots"), CompatibilityStatus::Boots}, {QStringLiteral("status-menus"), CompatibilityStatus::Menus}, @@ -87,7 +88,7 @@ public: bool LoadCompatibilityFile(); CompatibilityEntry GetCompatibilityInfo(const std::string& serial); void ExtractCompatibilityInfo(QByteArray response); - static void WaitForReply(QNetworkReply* reply); + static bool WaitForReply(QNetworkReply* reply); QNetworkReply* FetchPage(int page_num); private: diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index d43c35ef4..53159d8e7 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -80,6 +80,11 @@ GameListFrame::GameListFrame(std::shared_ptr game_info_get, QDesktopServices::openUrl(QUrl(m_game_info->m_games[row].compatibility.url)); } }); + + // Do not show status column if it is not enabled + if (!Config::getCompatibilityEnabled()) { + this->setColumnHidden(2, true); + } } void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow, diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 94832c6d5..a8a3b9c30 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -198,7 +198,9 @@ void MainWindow::CreateDockWindows() { void MainWindow::LoadGameLists() { // Update compatibility database - m_compat_info->UpdateCompatibilityDatabase(this); + if (Config::getCheckCompatibilityOnStartup()) { + m_compat_info->UpdateCompatibilityDatabase(this); + } // Get game info from game folders. m_game_info->GetGameInfo(this); if (isTableList) { @@ -600,7 +602,6 @@ void MainWindow::CreateConnects() { } void MainWindow::StartGame() { - isGameRunning = true; BackgroundMusicPlayer::getInstance().stopMusic(); QString gamePath = ""; int table_mode = Config::getTableMode(); @@ -623,13 +624,12 @@ void MainWindow::StartGame() { } if (gamePath != "") { AddRecentFiles(gamePath); - Core::Emulator emulator; const auto path = Common::FS::PathFromQString(gamePath); if (!std::filesystem::exists(path)) { QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); return; } - emulator.Run(path); + StartEmulator(path); } } @@ -726,13 +726,12 @@ void MainWindow::BootGame() { QString(tr("Only one file can be selected!"))); } else { std::filesystem::path path = Common::FS::PathFromQString(fileNames[0]); - Core::Emulator emulator; if (!std::filesystem::exists(path)) { QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); return; } - emulator.Run(path); + StartEmulator(path); } } } @@ -1113,12 +1112,11 @@ void MainWindow::CreateRecentGameActions() { connect(m_recent_files_group, &QActionGroup::triggered, this, [this](QAction* action) { auto gamePath = Common::FS::PathFromQString(action->text()); AddRecentFiles(action->text()); // Update the list. - Core::Emulator emulator; if (!std::filesystem::exists(gamePath)) { QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); return; } - emulator.Run(gamePath); + StartEmulator(gamePath); }); } @@ -1166,4 +1164,23 @@ bool MainWindow::eventFilter(QObject* obj, QEvent* event) { } } return QMainWindow::eventFilter(obj, event); -} \ No newline at end of file +} + +void MainWindow::StartEmulator(std::filesystem::path path) { + if (isGameRunning) { + QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Game is already running!"))); + return; + } + isGameRunning = true; +#ifdef __APPLE__ + // SDL on macOS requires main thread. + Core::Emulator emulator; + emulator.Run(path); +#else + std::thread emulator_thread([=] { + Core::Emulator emulator; + emulator.Run(path); + }); + emulator_thread.detach(); +#endif +} diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index d624c45be..e5fb7e702 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -72,6 +72,7 @@ private: void LoadTranslation(); void PlayBackgroundMusic(); QIcon RecolorIcon(const QIcon& icon, bool isWhite); + void StartEmulator(std::filesystem::path); bool isIconBlack = false; bool isTableList = true; bool isGameRunning = false; diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 0dbc56dff..c232be141 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -196,6 +196,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge #endif ui->GUIgroupBox->installEventFilter(this); ui->widgetGroupBox->installEventFilter(this); + ui->disableTrophycheckBox->installEventFilter(this); // Input ui->hideCursorGroupBox->installEventFilter(this); @@ -265,6 +266,8 @@ void SettingsDialog::LoadValuesFromConfig() { ui->dumpShadersCheckBox->setChecked(toml::find_or(data, "GPU", "dumpShaders", false)); ui->nullGpuCheckBox->setChecked(toml::find_or(data, "GPU", "nullGpu", false)); ui->playBGMCheckBox->setChecked(toml::find_or(data, "General", "playBGM", false)); + ui->disableTrophycheckBox->setChecked( + toml::find_or(data, "General", "isTrophyPopupDisabled", false)); ui->BGMVolumeSlider->setValue(toml::find_or(data, "General", "BGMvolume", 50)); ui->currentwidgetComboBox->setCurrentText( QString::fromStdString(toml::find_or(data, "GUI", "widgetStyle", "fusion"))); @@ -403,6 +406,8 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("GUIgroupBox"); } else if (elementName == "widgetGroupBox") { text = tr("widgetGroupBox"); + } else if (elementName == "disableTrophycheckBox") { + text = tr("disableTrophycheckBox"); } // Input @@ -491,6 +496,7 @@ void SettingsDialog::UpdateSettings() { Config::setBackButtonBehavior(TouchPadIndex[ui->backButtonBehaviorComboBox->currentIndex()]); Config::setNeoMode(ui->ps4proCheckBox->isChecked()); Config::setFullscreenMode(ui->fullscreenCheckBox->isChecked()); + Config::setisTrophyPopupDisabled(ui->disableTrophycheckBox->isChecked()); Config::setPlayBGM(ui->playBGMCheckBox->isChecked()); Config::setNeoMode(ui->ps4proCheckBox->isChecked()); Config::setLogType(ui->logTypeComboBox->currentText().toStdString()); @@ -568,4 +574,4 @@ void SettingsDialog::ResetInstallFolders() { } Config::setGameInstallDirs(settings_install_dirs_config); } -} \ No newline at end of file +} diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 9b1c1f5ba..79dfb93d9 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -59,9 +59,9 @@ 0 - 0 - 822 - 554 + -97 + 815 + 618 @@ -469,6 +469,13 @@ 11 + + + + Disable Trophy Pop-ups + + + @@ -561,16 +568,6 @@ - - - - - 0 - 61 - - - - @@ -803,7 +800,7 @@ 5 - + true diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index ef1de019e..db26cbe0c 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -1205,6 +1205,11 @@ widgetGroupBox Widget Styles:\nChoose the widget style to be applied at startup. Defaults to Fusion. "System" uses your system's widget style (Breeze, Oxygen, Kvantum etc). Requires an app restart to apply. + + + + disableTrophycheckBox + Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 4c74d37d0..83271a82d 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -428,6 +428,14 @@ struct Liverpool { BitField<0, 22, u32> tile_max; } depth_slice; + bool DepthValid() const { + return Address() != 0 && z_info.format != ZFormat::Invalid; + } + + bool StencilValid() const { + return Address() != 0 && stencil_info.format != StencilFormat::Invalid; + } + u32 Pitch() const { return (depth_size.pitch_tile_max + 1) << 3; } @@ -1275,6 +1283,26 @@ struct Liverpool { return nullptr; } + u32 NumSamples() const { + // It seems that the number of samples > 1 set in the AA config doesn't mean we're + // always rendering with MSAA, so we need to derive MS ratio from the CB and DB + // settings. + u32 num_samples = 1u; + if (color_control.mode != ColorControl::OperationMode::Disable) { + for (auto cb = 0u; cb < NumColorBuffers; ++cb) { + const auto& col_buf = color_buffers[cb]; + if (!col_buf) { + continue; + } + num_samples = std::max(num_samples, col_buf.NumSamples()); + } + } + if (depth_buffer.DepthValid() || depth_buffer.StencilValid()) { + num_samples = std::max(num_samples, depth_buffer.NumSamples()); + } + return num_samples; + } + void SetDefaults(); }; diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index e9fc06493..f265fb68d 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -235,25 +235,44 @@ bool BufferCache::BindVertexBuffers( } u32 BufferCache::BindIndexBuffer(bool& is_indexed, u32 index_offset) { - // Emulate QuadList primitive type with CPU made index buffer. + // Emulate QuadList and Polygon primitive types with CPU made index buffer. const auto& regs = liverpool->regs; - if (regs.primitive_type == AmdGpu::PrimitiveType::QuadList && !is_indexed) { - is_indexed = true; + if (!is_indexed) { + bool needs_index_buffer = false; + if (regs.primitive_type == AmdGpu::PrimitiveType::QuadList || + regs.primitive_type == AmdGpu::PrimitiveType::Polygon) { + needs_index_buffer = true; + } + + if (!needs_index_buffer) { + return regs.num_indices; + } // Emit indices. const u32 index_size = 3 * regs.num_indices; const auto [data, offset] = stream_buffer.Map(index_size); - Vulkan::LiverpoolToVK::EmitQuadToTriangleListIndices(data, regs.num_indices); + + switch (regs.primitive_type) { + case AmdGpu::PrimitiveType::QuadList: + Vulkan::LiverpoolToVK::EmitQuadToTriangleListIndices(data, regs.num_indices); + break; + case AmdGpu::PrimitiveType::Polygon: + Vulkan::LiverpoolToVK::EmitPolygonToTriangleListIndices(data, regs.num_indices); + break; + default: + UNREACHABLE(); + break; + } + stream_buffer.Commit(); // Bind index buffer. + is_indexed = true; + const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.bindIndexBuffer(stream_buffer.Handle(), offset, vk::IndexType::eUint16); return index_size / sizeof(u16); } - if (!is_indexed) { - return regs.num_indices; - } // Figure out index type and size. const bool is_index16 = @@ -288,6 +307,9 @@ u32 BufferCache::BindIndexBuffer(bool& is_indexed, u32 index_offset) { cmdbuf.bindIndexBuffer(stream_buffer.Handle(), offset, index_type); return new_index_size / index_size; } + if (regs.primitive_type == AmdGpu::PrimitiveType::Polygon) { + UNREACHABLE(); + } // Bind index buffer. const u32 index_buffer_size = regs.num_indices * index_size; diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index ec0bb3bb7..6df89dbae 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -117,6 +117,7 @@ vk::PrimitiveTopology PrimitiveType(AmdGpu::PrimitiveType type) { case AmdGpu::PrimitiveType::PatchPrimitive: return vk::PrimitiveTopology::ePatchList; case AmdGpu::PrimitiveType::QuadList: + case AmdGpu::PrimitiveType::Polygon: // Needs to generate index buffer on the fly. return vk::PrimitiveTopology::eTriangleList; case AmdGpu::PrimitiveType::RectList: diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.h b/src/video_core/renderer_vulkan/liverpool_to_vk.h index ebd09f0ee..72bddc6b6 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.h +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.h @@ -98,6 +98,15 @@ void ConvertQuadToTriangleListIndices(u8* out_ptr, const u8* in_ptr, u32 num_ver } } +inline void EmitPolygonToTriangleListIndices(u8* out_ptr, u32 num_vertices) { + u16* out_data = reinterpret_cast(out_ptr); + for (u16 i = 1; i < num_vertices - 1; i++) { + *out_data++ = 0; + *out_data++ = i; + *out_data++ = i + 1; + } +} + static inline vk::Format PromoteFormatToDepth(vk::Format fmt) { if (fmt == vk::Format::eR32Sfloat) { return vk::Format::eD32Sfloat; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 444c8517e..f25341bbb 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -85,10 +85,6 @@ public: return key.mrt_mask; } - bool IsDepthEnabled() const { - return key.depth_stencil.depth_enable.Value(); - } - [[nodiscard]] bool IsPrimitiveListTopology() const { return key.prim_type == AmdGpu::PrimitiveType::PointList || key.prim_type == AmdGpu::PrimitiveType::LineList || diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 4b88bd374..43e02dd9d 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -258,32 +258,28 @@ bool PipelineCache::RefreshGraphicsKey() { auto& key = graphics_key; key.depth_stencil = regs.depth_control; + key.stencil = regs.stencil_control; key.depth_stencil.depth_write_enable.Assign(regs.depth_control.depth_write_enable.Value() && !regs.depth_render_control.depth_clear_enable); key.depth_bias_enable = regs.polygon_control.NeedsBias(); - const auto& db = regs.depth_buffer; - const auto ds_format = instance.GetSupportedFormat( - LiverpoolToVK::DepthFormat(db.z_info.format, db.stencil_info.format), + const auto depth_format = instance.GetSupportedFormat( + LiverpoolToVK::DepthFormat(regs.depth_buffer.z_info.format, + regs.depth_buffer.stencil_info.format), vk::FormatFeatureFlagBits2::eDepthStencilAttachment); - if (db.z_info.format != AmdGpu::Liverpool::DepthBuffer::ZFormat::Invalid) { - key.depth_format = ds_format; + if (regs.depth_buffer.DepthValid()) { + key.depth_format = depth_format; } else { key.depth_format = vk::Format::eUndefined; + key.depth_stencil.depth_enable.Assign(false); } - if (regs.depth_control.depth_enable) { - key.depth_stencil.depth_enable.Assign(key.depth_format != vk::Format::eUndefined); - } - key.stencil = regs.stencil_control; - - if (db.stencil_info.format != AmdGpu::Liverpool::DepthBuffer::StencilFormat::Invalid) { - key.stencil_format = key.depth_format; + if (regs.depth_buffer.StencilValid()) { + key.stencil_format = depth_format; } else { key.stencil_format = vk::Format::eUndefined; + key.depth_stencil.stencil_enable.Assign(false); } - if (key.depth_stencil.stencil_enable) { - key.depth_stencil.stencil_enable.Assign(key.stencil_format != vk::Format::eUndefined); - } + key.prim_type = regs.primitive_type; key.enable_primitive_restart = regs.enable_primitive_restart & 1; key.primitive_restart_index = regs.primitive_restart_index; @@ -291,7 +287,7 @@ bool PipelineCache::RefreshGraphicsKey() { key.cull_mode = regs.polygon_control.CullingMode(); key.clip_space = regs.clipper_control.clip_space; key.front_face = regs.polygon_control.front_face; - key.num_samples = regs.aa_config.NumSamples(); + key.num_samples = regs.NumSamples(); const bool skip_cb_binding = regs.color_control.mode == AmdGpu::Liverpool::ColorControl::OperationMode::Disable; @@ -437,8 +433,6 @@ bool PipelineCache::RefreshGraphicsKey() { } } - u32 num_samples = 1u; - // Second pass to fill remain CB pipeline key data for (auto cb = 0u, remapped_cb = 0u; cb < Liverpool::NumColorBuffers; ++cb) { auto const& col_buf = regs.color_buffers[cb]; @@ -463,15 +457,8 @@ bool PipelineCache::RefreshGraphicsKey() { key.write_masks[remapped_cb] = vk::ColorComponentFlags{regs.color_target_mask.GetMask(cb)}; key.cb_shader_mask.SetMask(remapped_cb, regs.color_shader_mask.GetMask(cb)); ++remapped_cb; - - num_samples = std::max(num_samples, 1u << col_buf.attrib.num_samples_log2); } - // It seems that the number of samples > 1 set in the AA config doesn't mean we're always - // rendering with MSAA, so we need to derive MS ratio from the CB settings. - num_samples = std::max(num_samples, regs.depth_buffer.NumSamples()); - key.num_samples = num_samples; - return true; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index bd8906f86..a0899f7c8 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -87,9 +87,11 @@ RenderState Rasterizer::PrepareRenderState(u32 mrt_mask) { LOG_WARNING(Render_Vulkan, "Color buffers require gamma correction"); } + const bool skip_cb_binding = + regs.color_control.mode == AmdGpu::Liverpool::ColorControl::OperationMode::Disable; for (auto col_buf_id = 0u; col_buf_id < Liverpool::NumColorBuffers; ++col_buf_id) { const auto& col_buf = regs.color_buffers[col_buf_id]; - if (!col_buf) { + if (skip_cb_binding || !col_buf) { continue; } @@ -134,12 +136,8 @@ RenderState Rasterizer::PrepareRenderState(u32 mrt_mask) { }; } - using ZFormat = AmdGpu::Liverpool::DepthBuffer::ZFormat; - using StencilFormat = AmdGpu::Liverpool::DepthBuffer::StencilFormat; - if (regs.depth_buffer.Address() != 0 && - ((regs.depth_control.depth_enable && regs.depth_buffer.z_info.format != ZFormat::Invalid) || - (regs.depth_control.stencil_enable && - regs.depth_buffer.stencil_info.format != StencilFormat::Invalid))) { + if ((regs.depth_control.depth_enable && regs.depth_buffer.DepthValid()) || + (regs.depth_control.stencil_enable && regs.depth_buffer.StencilValid())) { const auto htile_address = regs.depth_htile_data_base.GetAddress(); const auto& hint = liverpool->last_db_extent; auto& [image_id, desc] = @@ -152,25 +150,36 @@ RenderState Rasterizer::PrepareRenderState(u32 mrt_mask) { image.binding.is_target = 1u; const auto slice = image_view.info.range.base.layer; - const bool is_clear = regs.depth_render_control.depth_clear_enable || - texture_cache.IsMetaCleared(htile_address, slice); + const bool is_depth_clear = regs.depth_render_control.depth_clear_enable || + texture_cache.IsMetaCleared(htile_address, slice); + const bool is_stencil_clear = regs.depth_render_control.stencil_clear_enable; ASSERT(desc.view_info.range.extent.layers == 1); state.width = std::min(state.width, image.info.size.width); state.height = std::min(state.height, image.info.size.height); - state.depth_attachment = { - .imageView = *image_view.image_view, - .imageLayout = vk::ImageLayout::eUndefined, - .loadOp = is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, - .storeOp = vk::AttachmentStoreOp::eStore, - .clearValue = vk::ClearValue{.depthStencil = {.depth = regs.depth_clear, - .stencil = regs.stencil_clear}}, - }; + state.has_depth = regs.depth_buffer.DepthValid(); + state.has_stencil = regs.depth_buffer.StencilValid(); + if (state.has_depth) { + state.depth_attachment = { + .imageView = *image_view.image_view, + .imageLayout = vk::ImageLayout::eUndefined, + .loadOp = + is_depth_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = vk::ClearValue{.depthStencil = {.depth = regs.depth_clear}}, + }; + } + if (state.has_stencil) { + state.stencil_attachment = { + .imageView = *image_view.image_view, + .imageLayout = vk::ImageLayout::eUndefined, + .loadOp = + is_stencil_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = vk::ClearValue{.depthStencil = {.stencil = regs.stencil_clear}}, + }; + } texture_cache.TouchMeta(htile_address, slice, false); - state.has_depth = - regs.depth_buffer.z_info.format != AmdGpu::Liverpool::DepthBuffer::ZFormat::Invalid; - state.has_stencil = regs.depth_buffer.stencil_info.format != - AmdGpu::Liverpool::DepthBuffer::StencilFormat::Invalid; } return state; @@ -246,11 +255,12 @@ void Rasterizer::DrawIndirect(bool is_indexed, VAddr arg_address, u32 offset, u3 } const auto& regs = liverpool->regs; - if (regs.primitive_type == AmdGpu::PrimitiveType::QuadList) { - // For QuadList we use generated index buffer to convert quads to triangles. Since it + if (regs.primitive_type == AmdGpu::PrimitiveType::QuadList || + regs.primitive_type == AmdGpu::PrimitiveType::Polygon) { + // We use a generated index buffer to convert quad lists and polygons to triangles. Since it // changes type of the draw, arguments are not valid for this case. We need to run a // conversion pass to repack the indirect arguments buffer first. - LOG_WARNING(Render_Vulkan, "QuadList primitive type is not supported for indirect draw"); + LOG_WARNING(Render_Vulkan, "Primitive type is not supported for indirect draw"); return; } @@ -777,6 +787,7 @@ void Rasterizer::BeginRendering(const GraphicsPipeline& pipeline, RenderState& s desc.view_info.range); } state.depth_attachment.imageLayout = image.last_state.layout; + state.stencil_attachment.imageLayout = image.last_state.layout; image.usage.depth_target = true; image.usage.stencil = has_stencil; } @@ -806,34 +817,60 @@ void Rasterizer::Resolve() { mrt1_range.base.layer = liverpool->regs.color_buffers[1].view.slice_start; mrt1_range.extent.layers = liverpool->regs.color_buffers[1].NumSlices() - mrt1_range.base.layer; - vk::ImageResolve region = { - .srcSubresource = - { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = 0, - .baseArrayLayer = mrt0_range.base.layer, - .layerCount = mrt0_range.extent.layers, - }, - .srcOffset = {0, 0, 0}, - .dstSubresource = - { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .mipLevel = 0, - .baseArrayLayer = mrt1_range.base.layer, - .layerCount = mrt1_range.extent.layers, - }, - .dstOffset = {0, 0, 0}, - .extent = {mrt1_image.info.size.width, mrt1_image.info.size.height, 1}, - }; - mrt0_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, mrt0_range); mrt1_image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, mrt1_range); - cmdbuf.resolveImage(mrt0_image.image, vk::ImageLayout::eTransferSrcOptimal, mrt1_image.image, - vk::ImageLayout::eTransferDstOptimal, region); + if (mrt0_image.info.num_samples == 1) { + // Vulkan does not allow resolve from a single sample image, so change it to a copy. + // Note that resolving a single-sampled image doesn't really make sense, but a game might do + // it. + vk::ImageCopy region = { + .srcSubresource = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = mrt0_range.base.layer, + .layerCount = mrt0_range.extent.layers, + }, + .srcOffset = {0, 0, 0}, + .dstSubresource = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = mrt1_range.base.layer, + .layerCount = mrt1_range.extent.layers, + }, + .dstOffset = {0, 0, 0}, + .extent = {mrt1_image.info.size.width, mrt1_image.info.size.height, 1}, + }; + cmdbuf.copyImage(mrt0_image.image, vk::ImageLayout::eTransferSrcOptimal, mrt1_image.image, + vk::ImageLayout::eTransferDstOptimal, region); + } else { + vk::ImageResolve region = { + .srcSubresource = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = mrt0_range.base.layer, + .layerCount = mrt0_range.extent.layers, + }, + .srcOffset = {0, 0, 0}, + .dstSubresource = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = mrt1_range.base.layer, + .layerCount = mrt1_range.extent.layers, + }, + .dstOffset = {0, 0, 0}, + .extent = {mrt1_image.info.size.width, mrt1_image.info.size.height, 1}, + }; + cmdbuf.resolveImage(mrt0_image.image, vk::ImageLayout::eTransferSrcOptimal, + mrt1_image.image, vk::ImageLayout::eTransferDstOptimal, region); + } } void Rasterizer::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { @@ -989,6 +1026,10 @@ void Rasterizer::UpdateViewportScissorState() { enable_offset ? regs.window_offset.window_y_offset : 0); for (u32 idx = 0; idx < Liverpool::NumViewports; idx++) { + if (regs.viewports[idx].xscale == 0) { + // Scissor and viewport counts should be equal. + continue; + } auto vp_scsr = scsr; if (regs.mode_control.vport_scissor_enable) { vp_scsr.top_left_x = @@ -1011,13 +1052,6 @@ void Rasterizer::UpdateViewportScissorState() { cmdbuf.setScissor(0, scissors); } -void Rasterizer::UpdateDepthStencilState() { - auto& depth = liverpool->regs.depth_control; - - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.setDepthBoundsTestEnable(depth.depth_bounds_enable); -} - void Rasterizer::ScopeMarkerBegin(const std::string_view& str) { if (Config::nullGpu() || !Config::vkMarkersEnabled()) { return; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index ec1b5e134..80b22c7d8 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -74,7 +74,6 @@ private: void UpdateDynamicState(const GraphicsPipeline& pipeline); void UpdateViewportScissorState(); - void UpdateDepthStencilState(); bool FilterDraw(); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 2c4e7a3c6..f6b0edda4 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -30,7 +30,7 @@ void Scheduler::BeginRendering(const RenderState& new_state) { is_rendering = true; render_state = new_state; - const auto witdh = + const auto width = render_state.width != std::numeric_limits::max() ? render_state.width : 1; const auto height = render_state.height != std::numeric_limits::max() ? render_state.height : 1; @@ -39,7 +39,7 @@ void Scheduler::BeginRendering(const RenderState& new_state) { .renderArea = { .offset = {0, 0}, - .extent = {witdh, height}, + .extent = {width, height}, }, .layerCount = 1, .colorAttachmentCount = render_state.num_color_attachments, @@ -47,7 +47,7 @@ void Scheduler::BeginRendering(const RenderState& new_state) { ? render_state.color_attachments.data() : nullptr, .pDepthAttachment = render_state.has_depth ? &render_state.depth_attachment : nullptr, - .pStencilAttachment = render_state.has_stencil ? &render_state.depth_attachment : nullptr, + .pStencilAttachment = render_state.has_stencil ? &render_state.stencil_attachment : nullptr, }; current_cmdbuf.beginRendering(rendering_info); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index cdd33745a..fd5e68373 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -21,6 +21,7 @@ class Instance; struct RenderState { std::array color_attachments{}; vk::RenderingAttachmentInfo depth_attachment{}; + vk::RenderingAttachmentInfo stencil_attachment{}; u32 num_color_attachments{}; bool has_depth{}; bool has_stencil{}; diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 1445d41cd..606ede558 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -266,7 +266,7 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::ColorBuffer& buffer, props.is_tiled = buffer.IsTiled(); tiling_mode = buffer.GetTilingMode(); pixel_format = LiverpoolToVK::SurfaceFormat(buffer.info.format, buffer.NumFormat()); - num_samples = 1 << buffer.attrib.num_fragments_log2; + num_samples = buffer.NumSamples(); num_bits = NumBits(buffer.info.format); type = vk::ImageType::e2D; size.width = hint.Valid() ? hint.width : buffer.Pitch(); @@ -289,7 +289,7 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slice props.is_tiled = false; pixel_format = LiverpoolToVK::DepthFormat(buffer.z_info.format, buffer.stencil_info.format); type = vk::ImageType::e2D; - num_samples = 1 << buffer.z_info.num_samples; // spec doesn't say it is a log2 + num_samples = buffer.NumSamples(); num_bits = buffer.NumBits(); size.width = hint.Valid() ? hint.width : buffer.Pitch(); size.height = hint.Valid() ? hint.height : buffer.Height();