Merge branch 'main' into qt-style

This commit is contained in:
tomboylover93 2024-12-22 12:53:08 -03:00 committed by GitHub
commit bc7bde8bcd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 528 additions and 288 deletions

View File

@ -890,7 +890,8 @@ if (ENABLE_DISCORD_RPC)
target_compile_definitions(shadps4 PRIVATE ENABLE_DISCORD_RPC) target_compile_definitions(shadps4 PRIVATE ENABLE_DISCORD_RPC)
endif() 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) target_compile_definitions(shadps4 PRIVATE ENABLE_USERFAULTFD)
endif() endif()

View File

@ -34,6 +34,7 @@ namespace Config {
static bool isNeo = false; static bool isNeo = false;
static bool isFullscreen = false; static bool isFullscreen = false;
static bool playBGM = false; static bool playBGM = false;
static bool isTrophyPopupDisabled = false;
static int BGMvolume = 50; static int BGMvolume = 50;
static bool enableDiscordRPC = false; static bool enableDiscordRPC = false;
static u32 screenWidth = 1280; static u32 screenWidth = 1280;
@ -65,6 +66,8 @@ static bool vkCrashDiagnostic = false;
static s16 cursorState = HideCursorState::Idle; static s16 cursorState = HideCursorState::Idle;
static int cursorHideTimeout = 5; // 5 seconds (default) static int cursorHideTimeout = 5; // 5 seconds (default)
static bool separateupdatefolder = false; static bool separateupdatefolder = false;
static bool compatibilityData = false;
static bool checkCompatibilityOnStartup = false;
// Gui // Gui
std::vector<std::filesystem::path> settings_install_dirs = {}; std::vector<std::filesystem::path> settings_install_dirs = {};
@ -97,6 +100,10 @@ bool isFullscreenMode() {
return isFullscreen; return isFullscreen;
} }
bool getisTrophyPopupDisabled() {
return isTrophyPopupDisabled;
}
bool getPlayBGM() { bool getPlayBGM() {
return playBGM; return playBGM;
} }
@ -229,6 +236,14 @@ bool getSeparateUpdateEnabled() {
return separateupdatefolder; return separateupdatefolder;
} }
bool getCompatibilityEnabled() {
return compatibilityData;
}
bool getCheckCompatibilityOnStartup() {
return checkCompatibilityOnStartup;
}
void setGpuId(s32 selectedGpuId) { void setGpuId(s32 selectedGpuId) {
gpuId = selectedGpuId; gpuId = selectedGpuId;
} }
@ -289,6 +304,10 @@ void setFullscreenMode(bool enable) {
isFullscreen = enable; isFullscreen = enable;
} }
void setisTrophyPopupDisabled(bool disable) {
isTrophyPopupDisabled = disable;
}
void setPlayBGM(bool enable) { void setPlayBGM(bool enable) {
playBGM = enable; playBGM = enable;
} }
@ -353,6 +372,14 @@ void setSeparateUpdateEnabled(bool use) {
separateupdatefolder = 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) { void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
main_window_geometry_x = x; main_window_geometry_x = x;
main_window_geometry_y = y; main_window_geometry_y = y;
@ -540,6 +567,7 @@ void load(const std::filesystem::path& path) {
isNeo = toml::find_or<bool>(general, "isPS4Pro", false); isNeo = toml::find_or<bool>(general, "isPS4Pro", false);
isFullscreen = toml::find_or<bool>(general, "Fullscreen", false); isFullscreen = toml::find_or<bool>(general, "Fullscreen", false);
playBGM = toml::find_or<bool>(general, "playBGM", false); playBGM = toml::find_or<bool>(general, "playBGM", false);
isTrophyPopupDisabled = toml::find_or<bool>(general, "isTrophyPopupDisabled", false);
BGMvolume = toml::find_or<int>(general, "BGMvolume", 50); BGMvolume = toml::find_or<int>(general, "BGMvolume", 50);
enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", true); enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", true);
logFilter = toml::find_or<std::string>(general, "logFilter", ""); logFilter = toml::find_or<std::string>(general, "logFilter", "");
@ -553,6 +581,9 @@ void load(const std::filesystem::path& path) {
isShowSplash = toml::find_or<bool>(general, "showSplash", true); isShowSplash = toml::find_or<bool>(general, "showSplash", true);
isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", false); isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", false);
separateupdatefolder = toml::find_or<bool>(general, "separateUpdateEnabled", false); separateupdatefolder = toml::find_or<bool>(general, "separateUpdateEnabled", false);
compatibilityData = toml::find_or<bool>(general, "compatibilityEnabled", false);
checkCompatibilityOnStartup =
toml::find_or<bool>(general, "checkCompatibilityOnStartup", false);
} }
if (data.contains("Input")) { if (data.contains("Input")) {
@ -656,6 +687,7 @@ void save(const std::filesystem::path& path) {
data["General"]["isPS4Pro"] = isNeo; data["General"]["isPS4Pro"] = isNeo;
data["General"]["Fullscreen"] = isFullscreen; data["General"]["Fullscreen"] = isFullscreen;
data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled;
data["General"]["playBGM"] = playBGM; data["General"]["playBGM"] = playBGM;
data["General"]["BGMvolume"] = BGMvolume; data["General"]["BGMvolume"] = BGMvolume;
data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["General"]["enableDiscordRPC"] = enableDiscordRPC;
@ -666,6 +698,8 @@ void save(const std::filesystem::path& path) {
data["General"]["showSplash"] = isShowSplash; data["General"]["showSplash"] = isShowSplash;
data["General"]["autoUpdate"] = isAutoUpdate; data["General"]["autoUpdate"] = isAutoUpdate;
data["General"]["separateUpdateEnabled"] = separateupdatefolder; data["General"]["separateUpdateEnabled"] = separateupdatefolder;
data["General"]["compatibilityEnabled"] = compatibilityData;
data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup;
data["Input"]["cursorState"] = cursorState; data["Input"]["cursorState"] = cursorState;
data["Input"]["cursorHideTimeout"] = cursorHideTimeout; data["Input"]["cursorHideTimeout"] = cursorHideTimeout;
data["Input"]["backButtonBehavior"] = backButtonBehavior; data["Input"]["backButtonBehavior"] = backButtonBehavior;
@ -751,6 +785,7 @@ void saveMainWindow(const std::filesystem::path& path) {
void setDefaultValues() { void setDefaultValues() {
isNeo = false; isNeo = false;
isFullscreen = false; isFullscreen = false;
isTrophyPopupDisabled = false;
playBGM = false; playBGM = false;
BGMvolume = 50; BGMvolume = 50;
enableDiscordRPC = true; enableDiscordRPC = true;
@ -787,6 +822,8 @@ void setDefaultValues() {
m_language = 1; m_language = 1;
gpuId = -1; gpuId = -1;
separateupdatefolder = false; separateupdatefolder = false;
compatibilityData = false;
checkCompatibilityOnStartup = false;
} }
} // namespace Config } // namespace Config

View File

@ -19,8 +19,11 @@ bool isNeoMode();
bool isFullscreenMode(); bool isFullscreenMode();
bool getPlayBGM(); bool getPlayBGM();
int getBGMvolume(); int getBGMvolume();
bool getisTrophyPopupDisabled();
bool getEnableDiscordRPC(); bool getEnableDiscordRPC();
bool getSeparateUpdateEnabled(); bool getSeparateUpdateEnabled();
bool getCompatibilityEnabled();
bool getCheckCompatibilityOnStartup();
std::string getLogFilter(); std::string getLogFilter();
std::string getLogType(); std::string getLogType();
@ -61,6 +64,7 @@ void setGpuId(s32 selectedGpuId);
void setScreenWidth(u32 width); void setScreenWidth(u32 width);
void setScreenHeight(u32 height); void setScreenHeight(u32 height);
void setFullscreenMode(bool enable); void setFullscreenMode(bool enable);
void setisTrophyPopupDisabled(bool disable);
void setPlayBGM(bool enable); void setPlayBGM(bool enable);
void setBGMvolume(int volume); void setBGMvolume(int volume);
void setEnableDiscordRPC(bool enable); void setEnableDiscordRPC(bool enable);
@ -70,6 +74,8 @@ void setUserName(const std::string& type);
void setUpdateChannel(const std::string& type); void setUpdateChannel(const std::string& type);
void setSeparateUpdateEnabled(bool use); void setSeparateUpdateEnabled(bool use);
void setGameInstallDirs(const std::vector<std::filesystem::path>& settings_install_dirs_config); void setGameInstallDirs(const std::vector<std::filesystem::path>& settings_install_dirs_config);
void setCompatibilityEnabled(bool use);
void setCheckCompatibilityOnStartup(bool use);
void setCursorState(s16 cursorState); void setCursorState(s16 cursorState);
void setCursorHideTimeout(int newcursorHideTimeout); void setCursorHideTimeout(int newcursorHideTimeout);

View File

@ -9,7 +9,7 @@
#include "splash.h" #include "splash.h"
bool Splash::Open(const std::filesystem::path& filepath) { 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); Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
if (!file.IsOpen()) { if (!file.IsOpen()) {

View File

@ -171,6 +171,9 @@ void HandleTable::DeleteHandle(int d) {
File* HandleTable::GetFile(int d) { File* HandleTable::GetFile(int d) {
std::scoped_lock lock{m_mutex}; std::scoped_lock lock{m_mutex};
if (d < 0 || d >= m_files.size()) {
return nullptr;
}
return m_files.at(d); return m_files.at(d);
} }

View File

@ -2,6 +2,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <memory> #include <memory>
#include <mutex>
#include <shared_mutex>
#include <magic_enum/magic_enum.hpp> #include <magic_enum/magic_enum.hpp>
#include "common/assert.h" #include "common/assert.h"
@ -13,7 +15,22 @@
namespace Libraries::AudioOut { namespace Libraries::AudioOut {
static std::unique_ptr<SDLAudioOut> audio; struct PortOut {
void* impl;
u32 samples_num;
u32 freq;
OrbisAudioOutParamFormat format;
OrbisAudioOutPort type;
int channels_num;
bool is_float;
std::array<int, 8> volume;
u8 sample_size;
bool is_open;
};
std::shared_mutex ports_mutex;
std::array<PortOut, SCE_AUDIO_OUT_NUM_PORTS> ports_out{};
static std::unique_ptr<AudioOutBackend> audio;
static std::string_view GetAudioOutPort(OrbisAudioOutPort port) { static std::string_view GetAudioOutPort(OrbisAudioOutPort port) {
switch (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() { int PS4_SYSV_ABI sceAudioOutDeviceIdOpen() {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); LOG_ERROR(Lib_AudioOut, "(STUBBED) called");
return ORBIS_OK; return ORBIS_OK;
@ -110,8 +179,21 @@ int PS4_SYSV_ABI sceAudioOutChangeAppModuleState() {
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceAudioOutClose() { int PS4_SYSV_ABI sceAudioOutClose(s32 handle) {
LOG_ERROR(Lib_AudioOut, "(STUBBED) called"); 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; return ORBIS_OK;
} }
@ -180,16 +262,21 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; 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->rerouteCounter = 0;
state->volume = 127; state->volume = 127;
switch (type) { switch (port.type) {
case OrbisAudioOutPort::Main: case OrbisAudioOutPort::Main:
case OrbisAudioOutPort::Bgm: case OrbisAudioOutPort::Bgm:
case OrbisAudioOutPort::Voice: case OrbisAudioOutPort::Voice:
state->output = 1; state->output = 1;
state->channel = (channels_num > 2 ? 2 : channels_num); state->channel = port.channels_num > 2 ? 2 : port.channels_num;
break; break;
case OrbisAudioOutPort::Personal: case OrbisAudioOutPort::Personal:
case OrbisAudioOutPort::Padspk: case OrbisAudioOutPort::Padspk:
@ -276,7 +363,7 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
u32 sample_rate, u32 sample_rate,
OrbisAudioOutParamExtendedInformation param_type) { OrbisAudioOutParamExtendedInformation param_type) {
LOG_INFO(Lib_AudioOut, LOG_INFO(Lib_AudioOut,
"AudioOutOpen id = {} port_type = {} index = {} lenght= {} sample_rate = {} " "id = {} port_type = {} index = {} length = {} sample_rate = {} "
"param_type = {} attr = {}", "param_type = {} attr = {}",
user_id, GetAudioOutPort(port_type), index, length, sample_rate, user_id, GetAudioOutPort(port_type), index, length, sample_rate,
GetAudioOutParamFormat(param_type.data_format), 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"); LOG_ERROR(Lib_AudioOut, "Invalid format attribute");
return ORBIS_AUDIO_OUT_ERROR_INVALID_FORMAT; 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() { int PS4_SYSV_ABI sceAudioOutOpenEx() {
@ -326,7 +432,15 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) {
// Nothing to output // Nothing to output
return ORBIS_OK; 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) { 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) { if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; 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() { int PS4_SYSV_ABI sceAudioOutSetVolumeDown() {

View File

@ -64,7 +64,7 @@ int PS4_SYSV_ABI sceAudioOutA3dExit();
int PS4_SYSV_ABI sceAudioOutA3dInit(); int PS4_SYSV_ABI sceAudioOutA3dInit();
int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid(); int PS4_SYSV_ABI sceAudioOutAttachToApplicationByPid();
int PS4_SYSV_ABI sceAudioOutChangeAppModuleState(); 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 sceAudioOutDetachFromApplicationByPid();
int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode(); int PS4_SYSV_ABI sceAudioOutExConfigureOutputMode();
int PS4_SYSV_ABI sceAudioOutExGetSystemInfo(); int PS4_SYSV_ABI sceAudioOutExGetSystemInfo();

View File

@ -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<int, 8> ch_volumes) = 0;
};
} // namespace Libraries::AudioOut

View File

@ -1,141 +1,44 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include <SDL3/SDL_audio.h> #include <SDL3/SDL_audio.h>
#include <SDL3/SDL_init.h> #include <SDL3/SDL_init.h>
#include <SDL3/SDL_timer.h> #include <SDL3/SDL_timer.h>
#include "common/assert.h" #include "common/assert.h"
#include "core/libraries/audio/audioout_error.h"
#include "core/libraries/audio/sdl_audio.h" #include "core/libraries/audio/sdl_audio.h"
namespace Libraries::AudioOut { namespace Libraries::AudioOut {
constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold
s32 SDLAudioOut::Open(OrbisAudioOutPort type, u32 samples_num, u32 freq, void* SDLAudioOut::Open(bool is_float, int num_channels, u32 sample_rate) {
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);
SDL_AudioSpec fmt; SDL_AudioSpec fmt;
SDL_zero(fmt); SDL_zero(fmt);
fmt.format = sampleFormat; fmt.format = is_float ? SDL_AUDIO_F32 : SDL_AUDIO_S16;
fmt.channels = port->channels_num; fmt.channels = num_channels;
fmt.freq = freq; fmt.freq = sample_rate;
port->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, NULL, NULL);
SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(port->stream)); auto* stream =
return std::distance(ports_out.begin(), port) + 1; SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr);
SDL_ResumeAudioStreamDevice(stream);
return stream;
} }
s32 SDLAudioOut::Output(s32 handle, const void* ptr) { void SDLAudioOut::Close(void* impl) {
auto& port = ports_out.at(handle - 1); SDL_DestroyAudioStream(static_cast<SDL_AudioStream*>(impl));
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; void SDLAudioOut::Output(void* impl, const void* ptr, size_t size) {
bool result = SDL_PutAudioStreamData(port.stream, ptr, data_size); auto* stream = static_cast<SDL_AudioStream*>(impl);
while (SDL_GetAudioStreamAvailable(port.stream) > AUDIO_STREAM_BUFFER_THRESHOLD) { SDL_PutAudioStreamData(stream, ptr, size);
while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) {
SDL_Delay(0); SDL_Delay(0);
} }
return result ? ORBIS_OK : -1;
} }
s32 SDLAudioOut::SetVolume(s32 handle, s32 bitflag, s32* volume) { void SDLAudioOut::SetVolume(void* impl, std::array<int, 8> ch_volumes) {
using Libraries::AudioOut::OrbisAudioOutParamFormat; // Not yet implemented
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;
} }
} // namespace Libraries::AudioOut } // namespace Libraries::AudioOut

View File

@ -3,40 +3,16 @@
#pragma once #pragma once
#include <shared_mutex> #include "core/libraries/audio/audioout_backend.h"
#include <SDL3/SDL_audio.h>
#include "core/libraries/audio/audioout.h"
namespace Libraries::AudioOut { namespace Libraries::AudioOut {
class SDLAudioOut { class SDLAudioOut final : public AudioOutBackend {
public: public:
explicit SDLAudioOut() = default; void* Open(bool is_float, int num_channels, u32 sample_rate) override;
~SDLAudioOut() = default; void Close(void* impl) override;
void Output(void* impl, const void* ptr, size_t size) override;
s32 Open(OrbisAudioOutPort type, u32 samples_num, u32 freq, OrbisAudioOutParamFormat format); void SetVolume(void* impl, std::array<int, 8> ch_volumes) override;
s32 Output(s32 handle, const void* ptr);
s32 SetVolume(s32 handle, s32 bitflag, s32* volume);
constexpr std::pair<OrbisAudioOutPort, int> 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<int, 8> volume;
u8 sample_size;
bool is_open;
};
std::shared_mutex m_mutex;
std::array<PortOut, Libraries::AudioOut::SCE_AUDIO_OUT_NUM_PORTS> ports_out{};
}; };
} // namespace Libraries::AudioOut } // namespace Libraries::AudioOut

View File

@ -5,6 +5,7 @@
#include <mutex> #include <mutex>
#include <imgui.h> #include <imgui.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/config.h"
#include "common/singleton.h" #include "common/singleton.h"
#include "imgui/imgui_std.h" #include "imgui/imgui_std.h"
#include "trophy_ui.h" #include "trophy_ui.h"
@ -82,7 +83,10 @@ void TrophyUI::Draw() {
void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::string& trophyName) { void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::string& trophyName) {
std::lock_guard<std::mutex> lock(queueMtx); std::lock_guard<std::mutex> lock(queueMtx);
if (current_trophy_ui.has_value()) {
if (Config::getisTrophyPopupDisabled()) {
return;
} else if (current_trophy_ui.has_value()) {
TrophyInfo new_trophy; TrophyInfo new_trophy;
new_trophy.trophy_icon_path = trophyIconPath; new_trophy.trophy_icon_path = trophyIconPath;
new_trophy.trophy_name = trophyName; new_trophy.trophy_name = trophyName;

View File

@ -4,6 +4,7 @@
#include <QFileInfo> #include <QFileInfo>
#include <QMessageBox> #include <QMessageBox>
#include <QProgressDialog> #include <QProgressDialog>
#include <QTimer>
#include "common/path_util.h" #include "common/path_util.h"
#include "compatibility_info.h" #include "compatibility_info.h"
@ -22,7 +23,8 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) {
return; return;
QNetworkReply* reply = FetchPage(1); QNetworkReply* reply = FetchPage(1);
WaitForReply(reply); if (!WaitForReply(reply))
return;
QProgressDialog dialog(tr("Fetching compatibility data, please wait"), tr("Cancel"), 0, 0, QProgressDialog dialog(tr("Fetching compatibility data, please wait"), tr("Cancel"), 0, 0,
parent); parent);
@ -57,12 +59,17 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) {
} }
future_watcher.setFuture(QtConcurrent::map(replies, WaitForReply)); future_watcher.setFuture(QtConcurrent::map(replies, WaitForReply));
connect(&future_watcher, &QFutureWatcher<QByteArray>::finished, [&]() { connect(&future_watcher, &QFutureWatcher<void>::finished, [&]() {
for (int i = 0; i < remaining_pages; i++) { for (int i = 0; i < remaining_pages; i++) {
if (replies[i]->bytesAvailable()) {
if (replies[i]->error() == QNetworkReply::NoError) { if (replies[i]->error() == QNetworkReply::NoError) {
ExtractCompatibilityInfo(replies[i]->readAll()); ExtractCompatibilityInfo(replies[i]->readAll());
} }
replies[i]->deleteLater(); replies[i]->deleteLater();
} else {
// This means the request timed out
return;
}
} }
QFile compatibility_file(m_compatibility_filename); QFile compatibility_file(m_compatibility_filename);
@ -83,6 +90,16 @@ void CompatibilityInfoClass::UpdateCompatibilityDatabase(QWidget* parent) {
dialog.reset(); dialog.reset();
}); });
connect(&future_watcher, &QFutureWatcher<void>::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<void>::cancel); connect(&dialog, &QProgressDialog::canceled, &future_watcher, &QFutureWatcher<void>::cancel);
dialog.setRange(0, remaining_pages); dialog.setRange(0, remaining_pages);
connect(&future_watcher, &QFutureWatcher<void>::progressValueChanged, &dialog, connect(&future_watcher, &QFutureWatcher<void>::progressValueChanged, &dialog,
@ -105,20 +122,34 @@ QNetworkReply* CompatibilityInfoClass::FetchPage(int page_num) {
return reply; 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; QEventLoop loop;
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
timer.start(5000);
loop.exec(); 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) { CompatibilityEntry CompatibilityInfoClass::GetCompatibilityInfo(const std::string& serial) {
QString title_id = QString::fromStdString(serial); QString title_id = QString::fromStdString(serial);
if (m_compatibility_database.contains(title_id)) { 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<int>(OSType::Last); os_int++) { for (int os_int = 0; os_int != static_cast<int>(OSType::Last); os_int++) {
QString os_string = OSTypeToString.at(static_cast<OSType>(os_int)); QString os_string = OSTypeToString.at(static_cast<OSType>(os_int));
QJsonObject compatibility_obj = m_compatibility_database[title_id].toObject();
if (compatibility_obj.contains(os_string)) { if (compatibility_obj.contains(os_string)) {
QJsonObject compatibility_entry_obj = compatibility_obj[os_string].toObject(); QJsonObject compatibility_entry_obj = compatibility_obj[os_string].toObject();
CompatibilityEntry compatibility_entry{ 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() { bool CompatibilityInfoClass::LoadCompatibilityFile() {

View File

@ -57,6 +57,7 @@ class CompatibilityInfoClass : public QObject {
public: public:
// Please think of a better alternative // Please think of a better alternative
inline static const std::unordered_map<QString, CompatibilityStatus> LabelToCompatStatus = { inline static const std::unordered_map<QString, CompatibilityStatus> LabelToCompatStatus = {
{QStringLiteral("status-unknown"), CompatibilityStatus::Unknown},
{QStringLiteral("status-nothing"), CompatibilityStatus::Nothing}, {QStringLiteral("status-nothing"), CompatibilityStatus::Nothing},
{QStringLiteral("status-boots"), CompatibilityStatus::Boots}, {QStringLiteral("status-boots"), CompatibilityStatus::Boots},
{QStringLiteral("status-menus"), CompatibilityStatus::Menus}, {QStringLiteral("status-menus"), CompatibilityStatus::Menus},
@ -87,7 +88,7 @@ public:
bool LoadCompatibilityFile(); bool LoadCompatibilityFile();
CompatibilityEntry GetCompatibilityInfo(const std::string& serial); CompatibilityEntry GetCompatibilityInfo(const std::string& serial);
void ExtractCompatibilityInfo(QByteArray response); void ExtractCompatibilityInfo(QByteArray response);
static void WaitForReply(QNetworkReply* reply); static bool WaitForReply(QNetworkReply* reply);
QNetworkReply* FetchPage(int page_num); QNetworkReply* FetchPage(int page_num);
private: private:

View File

@ -80,6 +80,11 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
QDesktopServices::openUrl(QUrl(m_game_info->m_games[row].compatibility.url)); 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, void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int previousRow,

View File

@ -198,7 +198,9 @@ void MainWindow::CreateDockWindows() {
void MainWindow::LoadGameLists() { void MainWindow::LoadGameLists() {
// Update compatibility database // Update compatibility database
if (Config::getCheckCompatibilityOnStartup()) {
m_compat_info->UpdateCompatibilityDatabase(this); m_compat_info->UpdateCompatibilityDatabase(this);
}
// Get game info from game folders. // Get game info from game folders.
m_game_info->GetGameInfo(this); m_game_info->GetGameInfo(this);
if (isTableList) { if (isTableList) {
@ -600,7 +602,6 @@ void MainWindow::CreateConnects() {
} }
void MainWindow::StartGame() { void MainWindow::StartGame() {
isGameRunning = true;
BackgroundMusicPlayer::getInstance().stopMusic(); BackgroundMusicPlayer::getInstance().stopMusic();
QString gamePath = ""; QString gamePath = "";
int table_mode = Config::getTableMode(); int table_mode = Config::getTableMode();
@ -623,13 +624,12 @@ void MainWindow::StartGame() {
} }
if (gamePath != "") { if (gamePath != "") {
AddRecentFiles(gamePath); AddRecentFiles(gamePath);
Core::Emulator emulator;
const auto path = Common::FS::PathFromQString(gamePath); const auto path = Common::FS::PathFromQString(gamePath);
if (!std::filesystem::exists(path)) { if (!std::filesystem::exists(path)) {
QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found")));
return; return;
} }
emulator.Run(path); StartEmulator(path);
} }
} }
@ -726,13 +726,12 @@ void MainWindow::BootGame() {
QString(tr("Only one file can be selected!"))); QString(tr("Only one file can be selected!")));
} else { } else {
std::filesystem::path path = Common::FS::PathFromQString(fileNames[0]); std::filesystem::path path = Common::FS::PathFromQString(fileNames[0]);
Core::Emulator emulator;
if (!std::filesystem::exists(path)) { if (!std::filesystem::exists(path)) {
QMessageBox::critical(nullptr, tr("Run Game"), QMessageBox::critical(nullptr, tr("Run Game"),
QString(tr("Eboot.bin file not found"))); QString(tr("Eboot.bin file not found")));
return; return;
} }
emulator.Run(path); StartEmulator(path);
} }
} }
} }
@ -1113,12 +1112,11 @@ void MainWindow::CreateRecentGameActions() {
connect(m_recent_files_group, &QActionGroup::triggered, this, [this](QAction* action) { connect(m_recent_files_group, &QActionGroup::triggered, this, [this](QAction* action) {
auto gamePath = Common::FS::PathFromQString(action->text()); auto gamePath = Common::FS::PathFromQString(action->text());
AddRecentFiles(action->text()); // Update the list. AddRecentFiles(action->text()); // Update the list.
Core::Emulator emulator;
if (!std::filesystem::exists(gamePath)) { if (!std::filesystem::exists(gamePath)) {
QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found")));
return; return;
} }
emulator.Run(gamePath); StartEmulator(gamePath);
}); });
} }
@ -1167,3 +1165,22 @@ bool MainWindow::eventFilter(QObject* obj, QEvent* event) {
} }
return QMainWindow::eventFilter(obj, event); return QMainWindow::eventFilter(obj, event);
} }
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
}

View File

@ -72,6 +72,7 @@ private:
void LoadTranslation(); void LoadTranslation();
void PlayBackgroundMusic(); void PlayBackgroundMusic();
QIcon RecolorIcon(const QIcon& icon, bool isWhite); QIcon RecolorIcon(const QIcon& icon, bool isWhite);
void StartEmulator(std::filesystem::path);
bool isIconBlack = false; bool isIconBlack = false;
bool isTableList = true; bool isTableList = true;
bool isGameRunning = false; bool isGameRunning = false;

View File

@ -196,6 +196,7 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
#endif #endif
ui->GUIgroupBox->installEventFilter(this); ui->GUIgroupBox->installEventFilter(this);
ui->widgetGroupBox->installEventFilter(this); ui->widgetGroupBox->installEventFilter(this);
ui->disableTrophycheckBox->installEventFilter(this);
// Input // Input
ui->hideCursorGroupBox->installEventFilter(this); ui->hideCursorGroupBox->installEventFilter(this);
@ -265,6 +266,8 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->dumpShadersCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "dumpShaders", false)); ui->dumpShadersCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "dumpShaders", false));
ui->nullGpuCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "nullGpu", false)); ui->nullGpuCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "nullGpu", false));
ui->playBGMCheckBox->setChecked(toml::find_or<bool>(data, "General", "playBGM", false)); ui->playBGMCheckBox->setChecked(toml::find_or<bool>(data, "General", "playBGM", false));
ui->disableTrophycheckBox->setChecked(
toml::find_or<bool>(data, "General", "isTrophyPopupDisabled", false));
ui->BGMVolumeSlider->setValue(toml::find_or<int>(data, "General", "BGMvolume", 50)); ui->BGMVolumeSlider->setValue(toml::find_or<int>(data, "General", "BGMvolume", 50));
ui->currentwidgetComboBox->setCurrentText( ui->currentwidgetComboBox->setCurrentText(
QString::fromStdString(toml::find_or<std::string>(data, "GUI", "widgetStyle", "fusion"))); QString::fromStdString(toml::find_or<std::string>(data, "GUI", "widgetStyle", "fusion")));
@ -403,6 +406,8 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) {
text = tr("GUIgroupBox"); text = tr("GUIgroupBox");
} else if (elementName == "widgetGroupBox") { } else if (elementName == "widgetGroupBox") {
text = tr("widgetGroupBox"); text = tr("widgetGroupBox");
} else if (elementName == "disableTrophycheckBox") {
text = tr("disableTrophycheckBox");
} }
// Input // Input
@ -491,6 +496,7 @@ void SettingsDialog::UpdateSettings() {
Config::setBackButtonBehavior(TouchPadIndex[ui->backButtonBehaviorComboBox->currentIndex()]); Config::setBackButtonBehavior(TouchPadIndex[ui->backButtonBehaviorComboBox->currentIndex()]);
Config::setNeoMode(ui->ps4proCheckBox->isChecked()); Config::setNeoMode(ui->ps4proCheckBox->isChecked());
Config::setFullscreenMode(ui->fullscreenCheckBox->isChecked()); Config::setFullscreenMode(ui->fullscreenCheckBox->isChecked());
Config::setisTrophyPopupDisabled(ui->disableTrophycheckBox->isChecked());
Config::setPlayBGM(ui->playBGMCheckBox->isChecked()); Config::setPlayBGM(ui->playBGMCheckBox->isChecked());
Config::setNeoMode(ui->ps4proCheckBox->isChecked()); Config::setNeoMode(ui->ps4proCheckBox->isChecked());
Config::setLogType(ui->logTypeComboBox->currentText().toStdString()); Config::setLogType(ui->logTypeComboBox->currentText().toStdString());

View File

@ -59,9 +59,9 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>-97</y>
<width>822</width> <width>815</width>
<height>554</height> <height>618</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -469,6 +469,13 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>11</number> <number>11</number>
</property> </property>
<item>
<widget class="QCheckBox" name="disableTrophycheckBox">
<property name="text">
<string>Disable Trophy Pop-ups</string>
</property>
</widget>
</item>
<item> <item>
<layout class="QVBoxLayout" name="GUIMusicLayout"> <layout class="QVBoxLayout" name="GUIMusicLayout">
<property name="topMargin"> <property name="topMargin">
@ -561,16 +568,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QWidget" name="GUIwidgetSpacer" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>61</height>
</size>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -803,7 +800,7 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>5</number> <number>5</number>
</property> </property>
<item alignment="Qt::AlignmentFlag::AlignHCenter"> <item>
<widget class="QSpinBox" name="idleTimeoutSpinBox"> <widget class="QSpinBox" name="idleTimeoutSpinBox">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>

View File

@ -1206,6 +1206,11 @@
<source>widgetGroupBox</source> <source>widgetGroupBox</source>
<translation>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.</translation> <translation>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.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="267"/>
<source>disableTrophycheckBox</source>
<translation>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).</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="450"/> <location filename="../settings_dialog.cpp" line="450"/>
<source>hideCursorGroupBox</source> <source>hideCursorGroupBox</source>

View File

@ -428,6 +428,14 @@ struct Liverpool {
BitField<0, 22, u32> tile_max; BitField<0, 22, u32> tile_max;
} depth_slice; } 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 { u32 Pitch() const {
return (depth_size.pitch_tile_max + 1) << 3; return (depth_size.pitch_tile_max + 1) << 3;
} }
@ -1275,6 +1283,26 @@ struct Liverpool {
return nullptr; 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(); void SetDefaults();
}; };

View File

@ -235,25 +235,44 @@ bool BufferCache::BindVertexBuffers(
} }
u32 BufferCache::BindIndexBuffer(bool& is_indexed, u32 index_offset) { 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; const auto& regs = liverpool->regs;
if (regs.primitive_type == AmdGpu::PrimitiveType::QuadList && !is_indexed) { if (!is_indexed) {
is_indexed = true; 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. // Emit indices.
const u32 index_size = 3 * regs.num_indices; const u32 index_size = 3 * regs.num_indices;
const auto [data, offset] = stream_buffer.Map(index_size); const auto [data, offset] = stream_buffer.Map(index_size);
switch (regs.primitive_type) {
case AmdGpu::PrimitiveType::QuadList:
Vulkan::LiverpoolToVK::EmitQuadToTriangleListIndices(data, regs.num_indices); 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(); stream_buffer.Commit();
// Bind index buffer. // Bind index buffer.
is_indexed = true;
const auto cmdbuf = scheduler.CommandBuffer(); const auto cmdbuf = scheduler.CommandBuffer();
cmdbuf.bindIndexBuffer(stream_buffer.Handle(), offset, vk::IndexType::eUint16); cmdbuf.bindIndexBuffer(stream_buffer.Handle(), offset, vk::IndexType::eUint16);
return index_size / sizeof(u16); return index_size / sizeof(u16);
} }
if (!is_indexed) {
return regs.num_indices;
}
// Figure out index type and size. // Figure out index type and size.
const bool is_index16 = 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); cmdbuf.bindIndexBuffer(stream_buffer.Handle(), offset, index_type);
return new_index_size / index_size; return new_index_size / index_size;
} }
if (regs.primitive_type == AmdGpu::PrimitiveType::Polygon) {
UNREACHABLE();
}
// Bind index buffer. // Bind index buffer.
const u32 index_buffer_size = regs.num_indices * index_size; const u32 index_buffer_size = regs.num_indices * index_size;

View File

@ -117,6 +117,7 @@ vk::PrimitiveTopology PrimitiveType(AmdGpu::PrimitiveType type) {
case AmdGpu::PrimitiveType::PatchPrimitive: case AmdGpu::PrimitiveType::PatchPrimitive:
return vk::PrimitiveTopology::ePatchList; return vk::PrimitiveTopology::ePatchList;
case AmdGpu::PrimitiveType::QuadList: case AmdGpu::PrimitiveType::QuadList:
case AmdGpu::PrimitiveType::Polygon:
// Needs to generate index buffer on the fly. // Needs to generate index buffer on the fly.
return vk::PrimitiveTopology::eTriangleList; return vk::PrimitiveTopology::eTriangleList;
case AmdGpu::PrimitiveType::RectList: case AmdGpu::PrimitiveType::RectList:

View File

@ -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<u16*>(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) { static inline vk::Format PromoteFormatToDepth(vk::Format fmt) {
if (fmt == vk::Format::eR32Sfloat) { if (fmt == vk::Format::eR32Sfloat) {
return vk::Format::eD32Sfloat; return vk::Format::eD32Sfloat;

View File

@ -85,10 +85,6 @@ public:
return key.mrt_mask; return key.mrt_mask;
} }
bool IsDepthEnabled() const {
return key.depth_stencil.depth_enable.Value();
}
[[nodiscard]] bool IsPrimitiveListTopology() const { [[nodiscard]] bool IsPrimitiveListTopology() const {
return key.prim_type == AmdGpu::PrimitiveType::PointList || return key.prim_type == AmdGpu::PrimitiveType::PointList ||
key.prim_type == AmdGpu::PrimitiveType::LineList || key.prim_type == AmdGpu::PrimitiveType::LineList ||

View File

@ -258,32 +258,28 @@ bool PipelineCache::RefreshGraphicsKey() {
auto& key = graphics_key; auto& key = graphics_key;
key.depth_stencil = regs.depth_control; 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() && key.depth_stencil.depth_write_enable.Assign(regs.depth_control.depth_write_enable.Value() &&
!regs.depth_render_control.depth_clear_enable); !regs.depth_render_control.depth_clear_enable);
key.depth_bias_enable = regs.polygon_control.NeedsBias(); key.depth_bias_enable = regs.polygon_control.NeedsBias();
const auto& db = regs.depth_buffer; const auto depth_format = instance.GetSupportedFormat(
const auto ds_format = instance.GetSupportedFormat( LiverpoolToVK::DepthFormat(regs.depth_buffer.z_info.format,
LiverpoolToVK::DepthFormat(db.z_info.format, db.stencil_info.format), regs.depth_buffer.stencil_info.format),
vk::FormatFeatureFlagBits2::eDepthStencilAttachment); vk::FormatFeatureFlagBits2::eDepthStencilAttachment);
if (db.z_info.format != AmdGpu::Liverpool::DepthBuffer::ZFormat::Invalid) { if (regs.depth_buffer.DepthValid()) {
key.depth_format = ds_format; key.depth_format = depth_format;
} else { } else {
key.depth_format = vk::Format::eUndefined; key.depth_format = vk::Format::eUndefined;
key.depth_stencil.depth_enable.Assign(false);
} }
if (regs.depth_control.depth_enable) { if (regs.depth_buffer.StencilValid()) {
key.depth_stencil.depth_enable.Assign(key.depth_format != vk::Format::eUndefined); key.stencil_format = depth_format;
}
key.stencil = regs.stencil_control;
if (db.stencil_info.format != AmdGpu::Liverpool::DepthBuffer::StencilFormat::Invalid) {
key.stencil_format = key.depth_format;
} else { } else {
key.stencil_format = vk::Format::eUndefined; 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.prim_type = regs.primitive_type;
key.enable_primitive_restart = regs.enable_primitive_restart & 1; key.enable_primitive_restart = regs.enable_primitive_restart & 1;
key.primitive_restart_index = regs.primitive_restart_index; key.primitive_restart_index = regs.primitive_restart_index;
@ -291,7 +287,7 @@ bool PipelineCache::RefreshGraphicsKey() {
key.cull_mode = regs.polygon_control.CullingMode(); key.cull_mode = regs.polygon_control.CullingMode();
key.clip_space = regs.clipper_control.clip_space; key.clip_space = regs.clipper_control.clip_space;
key.front_face = regs.polygon_control.front_face; 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 = const bool skip_cb_binding =
regs.color_control.mode == AmdGpu::Liverpool::ColorControl::OperationMode::Disable; 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 // Second pass to fill remain CB pipeline key data
for (auto cb = 0u, remapped_cb = 0u; cb < Liverpool::NumColorBuffers; ++cb) { for (auto cb = 0u, remapped_cb = 0u; cb < Liverpool::NumColorBuffers; ++cb) {
auto const& col_buf = regs.color_buffers[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.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)); key.cb_shader_mask.SetMask(remapped_cb, regs.color_shader_mask.GetMask(cb));
++remapped_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; return true;
} // namespace Vulkan } // namespace Vulkan

View File

@ -87,9 +87,11 @@ RenderState Rasterizer::PrepareRenderState(u32 mrt_mask) {
LOG_WARNING(Render_Vulkan, "Color buffers require gamma correction"); 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) { for (auto col_buf_id = 0u; col_buf_id < Liverpool::NumColorBuffers; ++col_buf_id) {
const auto& col_buf = regs.color_buffers[col_buf_id]; const auto& col_buf = regs.color_buffers[col_buf_id];
if (!col_buf) { if (skip_cb_binding || !col_buf) {
continue; continue;
} }
@ -134,12 +136,8 @@ RenderState Rasterizer::PrepareRenderState(u32 mrt_mask) {
}; };
} }
using ZFormat = AmdGpu::Liverpool::DepthBuffer::ZFormat; if ((regs.depth_control.depth_enable && regs.depth_buffer.DepthValid()) ||
using StencilFormat = AmdGpu::Liverpool::DepthBuffer::StencilFormat; (regs.depth_control.stencil_enable && regs.depth_buffer.StencilValid())) {
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))) {
const auto htile_address = regs.depth_htile_data_base.GetAddress(); const auto htile_address = regs.depth_htile_data_base.GetAddress();
const auto& hint = liverpool->last_db_extent; const auto& hint = liverpool->last_db_extent;
auto& [image_id, desc] = auto& [image_id, desc] =
@ -152,25 +150,36 @@ RenderState Rasterizer::PrepareRenderState(u32 mrt_mask) {
image.binding.is_target = 1u; image.binding.is_target = 1u;
const auto slice = image_view.info.range.base.layer; const auto slice = image_view.info.range.base.layer;
const bool is_clear = regs.depth_render_control.depth_clear_enable || const bool is_depth_clear = regs.depth_render_control.depth_clear_enable ||
texture_cache.IsMetaCleared(htile_address, slice); 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); ASSERT(desc.view_info.range.extent.layers == 1);
state.width = std::min<u32>(state.width, image.info.size.width); state.width = std::min<u32>(state.width, image.info.size.width);
state.height = std::min<u32>(state.height, image.info.size.height); state.height = std::min<u32>(state.height, image.info.size.height);
state.has_depth = regs.depth_buffer.DepthValid();
state.has_stencil = regs.depth_buffer.StencilValid();
if (state.has_depth) {
state.depth_attachment = { state.depth_attachment = {
.imageView = *image_view.image_view, .imageView = *image_view.image_view,
.imageLayout = vk::ImageLayout::eUndefined, .imageLayout = vk::ImageLayout::eUndefined,
.loadOp = is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, .loadOp =
is_depth_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad,
.storeOp = vk::AttachmentStoreOp::eStore, .storeOp = vk::AttachmentStoreOp::eStore,
.clearValue = vk::ClearValue{.depthStencil = {.depth = regs.depth_clear, .clearValue = vk::ClearValue{.depthStencil = {.depth = regs.depth_clear}},
.stencil = regs.stencil_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); 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; return state;
@ -246,11 +255,12 @@ void Rasterizer::DrawIndirect(bool is_indexed, VAddr arg_address, u32 offset, u3
} }
const auto& regs = liverpool->regs; const auto& regs = liverpool->regs;
if (regs.primitive_type == AmdGpu::PrimitiveType::QuadList) { if (regs.primitive_type == AmdGpu::PrimitiveType::QuadList ||
// For QuadList we use generated index buffer to convert quads to triangles. Since it 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 // 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. // 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; return;
} }
@ -777,6 +787,7 @@ void Rasterizer::BeginRendering(const GraphicsPipeline& pipeline, RenderState& s
desc.view_info.range); desc.view_info.range);
} }
state.depth_attachment.imageLayout = image.last_state.layout; state.depth_attachment.imageLayout = image.last_state.layout;
state.stencil_attachment.imageLayout = image.last_state.layout;
image.usage.depth_target = true; image.usage.depth_target = true;
image.usage.stencil = has_stencil; image.usage.stencil = has_stencil;
} }
@ -806,6 +817,38 @@ void Rasterizer::Resolve() {
mrt1_range.base.layer = liverpool->regs.color_buffers[1].view.slice_start; 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; mrt1_range.extent.layers = liverpool->regs.color_buffers[1].NumSlices() - mrt1_range.base.layer;
mrt0_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead,
mrt0_range);
mrt1_image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite,
mrt1_range);
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 = { vk::ImageResolve region = {
.srcSubresource = .srcSubresource =
{ {
@ -825,15 +868,9 @@ void Rasterizer::Resolve() {
.dstOffset = {0, 0, 0}, .dstOffset = {0, 0, 0},
.extent = {mrt1_image.info.size.width, mrt1_image.info.size.height, 1}, .extent = {mrt1_image.info.size.width, mrt1_image.info.size.height, 1},
}; };
cmdbuf.resolveImage(mrt0_image.image, vk::ImageLayout::eTransferSrcOptimal,
mrt0_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, mrt1_image.image, vk::ImageLayout::eTransferDstOptimal, region);
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);
} }
void Rasterizer::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { 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); enable_offset ? regs.window_offset.window_y_offset : 0);
for (u32 idx = 0; idx < Liverpool::NumViewports; idx++) { 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; auto vp_scsr = scsr;
if (regs.mode_control.vport_scissor_enable) { if (regs.mode_control.vport_scissor_enable) {
vp_scsr.top_left_x = vp_scsr.top_left_x =
@ -1011,13 +1052,6 @@ void Rasterizer::UpdateViewportScissorState() {
cmdbuf.setScissor(0, scissors); 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) { void Rasterizer::ScopeMarkerBegin(const std::string_view& str) {
if (Config::nullGpu() || !Config::vkMarkersEnabled()) { if (Config::nullGpu() || !Config::vkMarkersEnabled()) {
return; return;

View File

@ -74,7 +74,6 @@ private:
void UpdateDynamicState(const GraphicsPipeline& pipeline); void UpdateDynamicState(const GraphicsPipeline& pipeline);
void UpdateViewportScissorState(); void UpdateViewportScissorState();
void UpdateDepthStencilState();
bool FilterDraw(); bool FilterDraw();

View File

@ -30,7 +30,7 @@ void Scheduler::BeginRendering(const RenderState& new_state) {
is_rendering = true; is_rendering = true;
render_state = new_state; render_state = new_state;
const auto witdh = const auto width =
render_state.width != std::numeric_limits<u32>::max() ? render_state.width : 1; render_state.width != std::numeric_limits<u32>::max() ? render_state.width : 1;
const auto height = const auto height =
render_state.height != std::numeric_limits<u32>::max() ? render_state.height : 1; render_state.height != std::numeric_limits<u32>::max() ? render_state.height : 1;
@ -39,7 +39,7 @@ void Scheduler::BeginRendering(const RenderState& new_state) {
.renderArea = .renderArea =
{ {
.offset = {0, 0}, .offset = {0, 0},
.extent = {witdh, height}, .extent = {width, height},
}, },
.layerCount = 1, .layerCount = 1,
.colorAttachmentCount = render_state.num_color_attachments, .colorAttachmentCount = render_state.num_color_attachments,
@ -47,7 +47,7 @@ void Scheduler::BeginRendering(const RenderState& new_state) {
? render_state.color_attachments.data() ? render_state.color_attachments.data()
: nullptr, : nullptr,
.pDepthAttachment = render_state.has_depth ? &render_state.depth_attachment : 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); current_cmdbuf.beginRendering(rendering_info);

View File

@ -21,6 +21,7 @@ class Instance;
struct RenderState { struct RenderState {
std::array<vk::RenderingAttachmentInfo, 8> color_attachments{}; std::array<vk::RenderingAttachmentInfo, 8> color_attachments{};
vk::RenderingAttachmentInfo depth_attachment{}; vk::RenderingAttachmentInfo depth_attachment{};
vk::RenderingAttachmentInfo stencil_attachment{};
u32 num_color_attachments{}; u32 num_color_attachments{};
bool has_depth{}; bool has_depth{};
bool has_stencil{}; bool has_stencil{};

View File

@ -266,7 +266,7 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::ColorBuffer& buffer,
props.is_tiled = buffer.IsTiled(); props.is_tiled = buffer.IsTiled();
tiling_mode = buffer.GetTilingMode(); tiling_mode = buffer.GetTilingMode();
pixel_format = LiverpoolToVK::SurfaceFormat(buffer.info.format, buffer.NumFormat()); 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); num_bits = NumBits(buffer.info.format);
type = vk::ImageType::e2D; type = vk::ImageType::e2D;
size.width = hint.Valid() ? hint.width : buffer.Pitch(); 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; props.is_tiled = false;
pixel_format = LiverpoolToVK::DepthFormat(buffer.z_info.format, buffer.stencil_info.format); pixel_format = LiverpoolToVK::DepthFormat(buffer.z_info.format, buffer.stencil_info.format);
type = vk::ImageType::e2D; 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(); num_bits = buffer.NumBits();
size.width = hint.Valid() ? hint.width : buffer.Pitch(); size.width = hint.Valid() ? hint.width : buffer.Pitch();
size.height = hint.Valid() ? hint.height : buffer.Height(); size.height = hint.Valid() ? hint.height : buffer.Height();