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)
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()

View File

@ -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<std::filesystem::path> 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<bool>(general, "isPS4Pro", false);
isFullscreen = toml::find_or<bool>(general, "Fullscreen", 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);
enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", true);
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);
isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", 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")) {
@ -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

View File

@ -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<std::filesystem::path>& settings_install_dirs_config);
void setCompatibilityEnabled(bool use);
void setCheckCompatibilityOnStartup(bool use);
void setCursorState(s16 cursorState);
void setCursorHideTimeout(int newcursorHideTimeout);

View File

@ -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()) {

View File

@ -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);
}

View File

@ -2,6 +2,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <magic_enum/magic_enum.hpp>
#include "common/assert.h"
@ -13,7 +15,22 @@
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) {
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() {

View File

@ -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();

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-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include <SDL3/SDL_audio.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_timer.h>
#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<SDL_AudioStream*>(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<SDL_AudioStream*>(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<int, 8> ch_volumes) {
// Not yet implemented
}
} // namespace Libraries::AudioOut

View File

@ -3,40 +3,16 @@
#pragma once
#include <shared_mutex>
#include <SDL3/SDL_audio.h>
#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<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{};
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<int, 8> ch_volumes) override;
};
} // namespace Libraries::AudioOut

View File

@ -5,6 +5,7 @@
#include <mutex>
#include <imgui.h>
#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<std::mutex> 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;

View File

@ -4,6 +4,7 @@
#include <QFileInfo>
#include <QMessageBox>
#include <QProgressDialog>
#include <QTimer>
#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<QByteArray>::finished, [&]() {
connect(&future_watcher, &QFutureWatcher<void>::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<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);
dialog.setRange(0, remaining_pages);
connect(&future_watcher, &QFutureWatcher<void>::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<int>(OSType::Last); 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)) {
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() {

View File

@ -57,6 +57,7 @@ class CompatibilityInfoClass : public QObject {
public:
// Please think of a better alternative
inline static const std::unordered_map<QString, CompatibilityStatus> 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:

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));
}
});
// 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,

View File

@ -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);
}
}
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 PlayBackgroundMusic();
QIcon RecolorIcon(const QIcon& icon, bool isWhite);
void StartEmulator(std::filesystem::path);
bool isIconBlack = false;
bool isTableList = true;
bool isGameRunning = false;

View File

@ -196,6 +196,7 @@ SettingsDialog::SettingsDialog(std::span<const QString> 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<bool>(data, "GPU", "dumpShaders", false));
ui->nullGpuCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "nullGpu", 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->currentwidgetComboBox->setCurrentText(
QString::fromStdString(toml::find_or<std::string>(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);
}
}
}

View File

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

View File

@ -1205,6 +1205,11 @@
<location filename="../settings_dialog.cpp" line="307"/>
<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>
</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>
<location filename="../settings_dialog.cpp" line="450"/>

View File

@ -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();
};

View File

@ -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;

View File

@ -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:

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) {
if (fmt == vk::Format::eR32Sfloat) {
return vk::Format::eD32Sfloat;

View File

@ -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 ||

View File

@ -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

View File

@ -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<u32>(state.width, image.info.size.width);
state.height = std::min<u32>(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;

View File

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

View File

@ -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<u32>::max() ? render_state.width : 1;
const auto height =
render_state.height != std::numeric_limits<u32>::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);

View File

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

View File

@ -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();