mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-08-05 08:52:36 +00:00
Merge branch 'main' into qt-style
This commit is contained in:
commit
bc7bde8bcd
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
|
19
src/core/libraries/audio/audioout_backend.h
Normal file
19
src/core/libraries/audio/audioout_backend.h
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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"/>
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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 ||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -74,7 +74,6 @@ private:
|
||||
|
||||
void UpdateDynamicState(const GraphicsPipeline& pipeline);
|
||||
void UpdateViewportScissorState();
|
||||
void UpdateDepthStencilState();
|
||||
|
||||
bool FilterDraw();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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{};
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user