Compare commits
31 Commits
android-91
...
android-96
Author | SHA1 | Date | |
---|---|---|---|
0d705936cf | |||
da4f19c231 | |||
1c1959eaeb | |||
c0d152affa | |||
85d99f873f | |||
54fa1115a6 | |||
9ef9ca0927 | |||
bd42bba71c | |||
a27f94830a | |||
bd6f9f1d91 | |||
bf15aa093c | |||
0e9b839b6f | |||
15a5bdd979 | |||
fc4cde7513 | |||
ff3859d482 | |||
10de8f2c60 | |||
51b89fddd0 | |||
f585dec48d | |||
ad1a9f3d3a | |||
71044f6def | |||
a17cde7b2c | |||
a84c928827 | |||
9568d3bc60 | |||
0fe935a5de | |||
c84c35ac74 | |||
b32940d3ea | |||
38394f36d7 | |||
2f0db2708c | |||
1a246bf135 | |||
667ec28697 | |||
5464423667 |
@ -1,3 +1,12 @@
|
|||||||
|
| Pull Request | Commit | Title | Author | Merged? |
|
||||||
|
|----|----|----|----|----|
|
||||||
|
| [11718](https://github.com/yuzu-emu/yuzu//pull/11718) | [`21bc2c14b`](https://github.com/yuzu-emu/yuzu//pull/11718/files) | common: add arm64 native clock | [liamwhite](https://github.com/liamwhite/) | Yes |
|
||||||
|
|
||||||
|
|
||||||
|
End of merge log. You can find the original README.md below the break.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
||||||
SPDX-License-Identifier: GPL-2.0-or-later
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
@ -218,7 +218,6 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_window->OnSurfaceChanged(m_native_window);
|
m_window->OnSurfaceChanged(m_native_window);
|
||||||
m_system.Renderer().NotifySurfaceChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureFilesystemProvider(const std::string& filepath) {
|
void ConfigureFilesystemProvider(const std::string& filepath) {
|
||||||
|
@ -189,6 +189,14 @@ if(ARCHITECTURE_x86_64)
|
|||||||
target_link_libraries(common PRIVATE xbyak::xbyak)
|
target_link_libraries(common PRIVATE xbyak::xbyak)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
|
||||||
|
target_sources(common
|
||||||
|
PRIVATE
|
||||||
|
arm64/native_clock.cpp
|
||||||
|
arm64/native_clock.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
target_compile_definitions(common PRIVATE
|
target_compile_definitions(common PRIVATE
|
||||||
# The standard library doesn't provide any replacement for codecvt yet
|
# The standard library doesn't provide any replacement for codecvt yet
|
||||||
|
72
src/common/arm64/native_clock.cpp
Normal file
72
src/common/arm64/native_clock.cpp
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/arm64/native_clock.h"
|
||||||
|
|
||||||
|
namespace Common::Arm64 {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) {
|
||||||
|
return (static_cast<NativeClock::FactorType>(num) << 64) / den;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) {
|
||||||
|
return static_cast<u64>((m * factor) >> 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
NativeClock::NativeClock() {
|
||||||
|
const u64 host_cntfrq = GetHostCNTFRQ();
|
||||||
|
ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
|
||||||
|
us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
|
||||||
|
ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
|
||||||
|
guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
|
||||||
|
gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
|
||||||
|
return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_cntfrq_factor)};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::microseconds NativeClock::GetTimeUS() const {
|
||||||
|
return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_cntfrq_factor)};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::chrono::milliseconds NativeClock::GetTimeMS() const {
|
||||||
|
return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_cntfrq_factor)};
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 NativeClock::GetCNTPCT() const {
|
||||||
|
return MultiplyHigh(GetHostTicksElapsed(), guest_cntfrq_factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 NativeClock::GetGPUTick() const {
|
||||||
|
return MultiplyHigh(GetHostTicksElapsed(), gputick_cntfrq_factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 NativeClock::GetHostTicksNow() const {
|
||||||
|
u64 cntvct_el0 = 0;
|
||||||
|
asm volatile("dsb ish\n\t"
|
||||||
|
"mrs %[cntvct_el0], cntvct_el0\n\t"
|
||||||
|
"dsb ish\n\t"
|
||||||
|
: [cntvct_el0] "=r"(cntvct_el0));
|
||||||
|
return cntvct_el0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 NativeClock::GetHostTicksElapsed() const {
|
||||||
|
return GetHostTicksNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NativeClock::IsNative() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 NativeClock::GetHostCNTFRQ() {
|
||||||
|
u64 cntfrq_el0 = 0;
|
||||||
|
asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
|
||||||
|
return cntfrq_el0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common::Arm64
|
47
src/common/arm64/native_clock.h
Normal file
47
src/common/arm64/native_clock.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/wall_clock.h"
|
||||||
|
|
||||||
|
namespace Common::Arm64 {
|
||||||
|
|
||||||
|
class NativeClock final : public WallClock {
|
||||||
|
public:
|
||||||
|
explicit NativeClock();
|
||||||
|
|
||||||
|
std::chrono::nanoseconds GetTimeNS() const override;
|
||||||
|
|
||||||
|
std::chrono::microseconds GetTimeUS() const override;
|
||||||
|
|
||||||
|
std::chrono::milliseconds GetTimeMS() const override;
|
||||||
|
|
||||||
|
u64 GetCNTPCT() const override;
|
||||||
|
|
||||||
|
u64 GetGPUTick() const override;
|
||||||
|
|
||||||
|
u64 GetHostTicksNow() const override;
|
||||||
|
|
||||||
|
u64 GetHostTicksElapsed() const override;
|
||||||
|
|
||||||
|
bool IsNative() const override;
|
||||||
|
|
||||||
|
static u64 GetHostCNTFRQ();
|
||||||
|
|
||||||
|
public:
|
||||||
|
using FactorType = unsigned __int128;
|
||||||
|
|
||||||
|
FactorType GetGuestCNTFRQFactor() const {
|
||||||
|
return guest_cntfrq_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FactorType ns_cntfrq_factor;
|
||||||
|
FactorType us_cntfrq_factor;
|
||||||
|
FactorType ms_cntfrq_factor;
|
||||||
|
FactorType guest_cntfrq_factor;
|
||||||
|
FactorType gputick_cntfrq_factor;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common::Arm64
|
@ -18,10 +18,12 @@
|
|||||||
#define LOAD_DIR "load"
|
#define LOAD_DIR "load"
|
||||||
#define LOG_DIR "log"
|
#define LOG_DIR "log"
|
||||||
#define NAND_DIR "nand"
|
#define NAND_DIR "nand"
|
||||||
|
#define PLAY_TIME_DIR "play_time"
|
||||||
#define SCREENSHOTS_DIR "screenshots"
|
#define SCREENSHOTS_DIR "screenshots"
|
||||||
#define SDMC_DIR "sdmc"
|
#define SDMC_DIR "sdmc"
|
||||||
#define SHADER_DIR "shader"
|
#define SHADER_DIR "shader"
|
||||||
#define TAS_DIR "tas"
|
#define TAS_DIR "tas"
|
||||||
|
#define ICONS_DIR "icons"
|
||||||
|
|
||||||
// yuzu-specific files
|
// yuzu-specific files
|
||||||
|
|
||||||
|
@ -124,10 +124,12 @@ public:
|
|||||||
GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
|
GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
|
||||||
GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
|
GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
|
||||||
GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
|
GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
|
||||||
|
GenerateYuzuPath(YuzuPath::PlayTimeDir, yuzu_path / PLAY_TIME_DIR);
|
||||||
GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
|
GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
|
||||||
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
|
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
|
||||||
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
|
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
|
||||||
GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
|
GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
|
||||||
|
GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -20,10 +20,12 @@ enum class YuzuPath {
|
|||||||
LoadDir, // Where cheat/mod files are stored.
|
LoadDir, // Where cheat/mod files are stored.
|
||||||
LogDir, // Where log files are stored.
|
LogDir, // Where log files are stored.
|
||||||
NANDDir, // Where the emulated NAND is stored.
|
NANDDir, // Where the emulated NAND is stored.
|
||||||
|
PlayTimeDir, // Where play time data is stored.
|
||||||
ScreenshotsDir, // Where yuzu screenshots are stored.
|
ScreenshotsDir, // Where yuzu screenshots are stored.
|
||||||
SDMCDir, // Where the emulated SDMC is stored.
|
SDMCDir, // Where the emulated SDMC is stored.
|
||||||
ShaderDir, // Where shaders are stored.
|
ShaderDir, // Where shaders are stored.
|
||||||
TASDir, // Where TAS scripts are stored.
|
TASDir, // Where TAS scripts are stored.
|
||||||
|
IconsDir, // Where Icons for Windows shortcuts are stored.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,6 +135,11 @@ std::u16string UTF8ToUTF16(std::string_view input) {
|
|||||||
return convert.from_bytes(input.data(), input.data() + input.size());
|
return convert.from_bytes(input.data(), input.data() + input.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::u32string UTF8ToUTF32(std::string_view input) {
|
||||||
|
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
|
||||||
|
return convert.from_bytes(input.data(), input.data() + input.size());
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
|
static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
|
||||||
const auto size =
|
const auto size =
|
||||||
|
@ -38,6 +38,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
|
|||||||
|
|
||||||
[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
|
[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
|
||||||
[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
|
[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
|
||||||
|
[[nodiscard]] std::u32string UTF8ToUTF32(std::string_view input);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
|
[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
|
||||||
|
@ -10,6 +10,10 @@
|
|||||||
#include "common/x64/rdtsc.h"
|
#include "common/x64/rdtsc.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(ARCHITECTURE_arm64) && defined(__linux__)
|
||||||
|
#include "common/arm64/native_clock.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
class StandardWallClock final : public WallClock {
|
class StandardWallClock final : public WallClock {
|
||||||
@ -53,7 +57,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<WallClock> CreateOptimalClock() {
|
std::unique_ptr<WallClock> CreateOptimalClock() {
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#if defined(ARCHITECTURE_x86_64)
|
||||||
const auto& caps = GetCPUCaps();
|
const auto& caps = GetCPUCaps();
|
||||||
|
|
||||||
if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
|
if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
|
||||||
@ -64,6 +68,8 @@ std::unique_ptr<WallClock> CreateOptimalClock() {
|
|||||||
// - Is not more precise than 1 GHz (1ns resolution)
|
// - Is not more precise than 1 GHz (1ns resolution)
|
||||||
return std::make_unique<StandardWallClock>();
|
return std::make_unique<StandardWallClock>();
|
||||||
}
|
}
|
||||||
|
#elif defined(ARCHITECTURE_arm64) && defined(__linux__)
|
||||||
|
return std::make_unique<Arm64::NativeClock>();
|
||||||
#else
|
#else
|
||||||
return std::make_unique<StandardWallClock>();
|
return std::make_unique<StandardWallClock>();
|
||||||
#endif
|
#endif
|
||||||
|
@ -1078,6 +1078,10 @@ void System::ApplySettings() {
|
|||||||
impl->RefreshTime();
|
impl->RefreshTime();
|
||||||
|
|
||||||
if (IsPoweredOn()) {
|
if (IsPoweredOn()) {
|
||||||
|
if (Settings::values.custom_rtc_enabled) {
|
||||||
|
const s64 posix_time{Settings::values.custom_rtc.GetValue()};
|
||||||
|
GetTimeManager().UpdateLocalSystemClockTime(posix_time);
|
||||||
|
}
|
||||||
Renderer().RefreshBaseSettings();
|
Renderer().RefreshBaseSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <codecvt>
|
||||||
|
#include <locale>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@ -12,6 +14,7 @@
|
|||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/debugger/gdbstub.h"
|
#include "core/debugger/gdbstub.h"
|
||||||
@ -68,10 +71,16 @@ static std::string EscapeGDB(std::string_view data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static std::string EscapeXML(std::string_view data) {
|
static std::string EscapeXML(std::string_view data) {
|
||||||
|
std::u32string converted = U"[Encoding error]";
|
||||||
|
try {
|
||||||
|
converted = Common::UTF8ToUTF32(data);
|
||||||
|
} catch (std::range_error&) {
|
||||||
|
}
|
||||||
|
|
||||||
std::string escaped;
|
std::string escaped;
|
||||||
escaped.reserve(data.size());
|
escaped.reserve(data.size());
|
||||||
|
|
||||||
for (char c : data) {
|
for (char32_t c : converted) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '&':
|
case '&':
|
||||||
escaped += "&";
|
escaped += "&";
|
||||||
@ -86,7 +95,11 @@ static std::string EscapeXML(std::string_view data) {
|
|||||||
escaped += ">";
|
escaped += ">";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
escaped += c;
|
if (c > 0x7f) {
|
||||||
|
escaped += fmt::format("&#{};", static_cast<u32>(c));
|
||||||
|
} else {
|
||||||
|
escaped += static_cast<char>(c);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
#include "core/file_sys/program_metadata.h"
|
#include "core/file_sys/program_metadata.h"
|
||||||
#include "core/file_sys/vfs.h"
|
#include "core/file_sys/vfs.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
@ -95,6 +96,13 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
|
|||||||
return Loader::ResultStatus::Success;
|
return Loader::ResultStatus::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) {
|
||||||
|
const u64 original_program_id = aci_header.title_id;
|
||||||
|
SCOPE_EXIT({ aci_header.title_id = original_program_id; });
|
||||||
|
|
||||||
|
return this->Load(file);
|
||||||
|
}
|
||||||
|
|
||||||
/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
|
/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
|
||||||
// Allow use of cores 0~3 and thread priorities 1~63.
|
// Allow use of cores 0~3 and thread priorities 1~63.
|
||||||
constexpr u32 default_thread_info_capability = 0x30007F7;
|
constexpr u32 default_thread_info_capability = 0x30007F7;
|
||||||
|
@ -56,6 +56,7 @@ public:
|
|||||||
static ProgramMetadata GetDefault();
|
static ProgramMetadata GetDefault();
|
||||||
|
|
||||||
Loader::ResultStatus Load(VirtualFile file);
|
Loader::ResultStatus Load(VirtualFile file);
|
||||||
|
Loader::ResultStatus Reload(VirtualFile file);
|
||||||
|
|
||||||
/// Load from parameters instead of NPDM file, used for KIP
|
/// Load from parameters instead of NPDM file, used for KIP
|
||||||
void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio,
|
void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio,
|
||||||
|
@ -118,7 +118,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
|
|||||||
return {ResultStatus::ErrorMissingNPDM, {}};
|
return {ResultStatus::ErrorMissingNPDM, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResultStatus result2 = metadata.Load(npdm);
|
const ResultStatus result2 = metadata.Reload(npdm);
|
||||||
if (result2 != ResultStatus::Success) {
|
if (result2 != ResultStatus::Success) {
|
||||||
return {result2, {}};
|
return {result2, {}};
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ set(SHADER_FILES
|
|||||||
block_linear_unswizzle_2d.comp
|
block_linear_unswizzle_2d.comp
|
||||||
block_linear_unswizzle_3d.comp
|
block_linear_unswizzle_3d.comp
|
||||||
convert_abgr8_to_d24s8.frag
|
convert_abgr8_to_d24s8.frag
|
||||||
|
convert_d32f_to_abgr8.frag
|
||||||
convert_d24s8_to_abgr8.frag
|
convert_d24s8_to_abgr8.frag
|
||||||
convert_depth_to_float.frag
|
convert_depth_to_float.frag
|
||||||
convert_float_to_depth.frag
|
convert_float_to_depth.frag
|
||||||
|
14
src/video_core/host_shaders/convert_d32f_to_abgr8.frag
Normal file
14
src/video_core/host_shaders/convert_d32f_to_abgr8.frag
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(binding = 0) uniform sampler2D depth_tex;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
ivec2 coord = ivec2(gl_FragCoord.xy);
|
||||||
|
float depth = textureLod(depth_tex, coord, 0).r;
|
||||||
|
color = vec4(depth, depth, depth, 1.0);
|
||||||
|
}
|
@ -89,9 +89,6 @@ public:
|
|||||||
void RequestScreenshot(void* data, std::function<void(bool)> callback,
|
void RequestScreenshot(void* data, std::function<void(bool)> callback,
|
||||||
const Layout::FramebufferLayout& layout);
|
const Layout::FramebufferLayout& layout);
|
||||||
|
|
||||||
/// This is called to notify the rendering backend of a surface change
|
|
||||||
virtual void NotifySurfaceChanged() {}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
||||||
std::unique_ptr<Core::Frontend::GraphicsContext> context;
|
std::unique_ptr<Core::Frontend::GraphicsContext> context;
|
||||||
|
@ -116,6 +116,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
|
|||||||
{GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
|
{GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
|
||||||
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
|
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
|
||||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
|
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
|
||||||
|
{GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT_24_8}, // X8_D24_UNORM
|
||||||
{GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT
|
{GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT
|
||||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
|
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
|
||||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
|
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "video_core/host_shaders/blit_color_float_frag_spv.h"
|
#include "video_core/host_shaders/blit_color_float_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
|
#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
|
#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
|
||||||
|
#include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
|
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
|
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
|
||||||
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
|
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
|
||||||
@ -433,6 +434,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
|
|||||||
convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
|
convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
|
||||||
convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
|
convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
|
||||||
convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
|
convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
|
||||||
|
convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
|
||||||
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
|
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
|
||||||
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
|
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
|
||||||
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
|
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
|
||||||
@ -557,6 +559,13 @@ void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer,
|
|||||||
Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
|
Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BlitImageHelper::ConvertD32FToABGR8(const Framebuffer* dst_framebuffer,
|
||||||
|
ImageView& src_image_view) {
|
||||||
|
ConvertPipelineColorTargetEx(convert_d32f_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
|
||||||
|
convert_d32f_to_abgr8_frag);
|
||||||
|
ConvertDepthStencil(*convert_d32f_to_abgr8_pipeline, dst_framebuffer, src_image_view);
|
||||||
|
}
|
||||||
|
|
||||||
void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer,
|
void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer,
|
||||||
ImageView& src_image_view) {
|
ImageView& src_image_view) {
|
||||||
ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
|
ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
|
||||||
@ -609,6 +618,8 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
|
|||||||
const VkPipelineLayout layout = *clear_color_pipeline_layout;
|
const VkPipelineLayout layout = *clear_color_pipeline_layout;
|
||||||
scheduler.RequestRenderpass(dst_framebuffer);
|
scheduler.RequestRenderpass(dst_framebuffer);
|
||||||
scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) {
|
||||||
|
constexpr std::array blend_constants{0.0f, 0.0f, 0.0f, 0.0f};
|
||||||
|
cmdbuf.SetBlendConstants(blend_constants.data());
|
||||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||||
BindBlitState(cmdbuf, dst_region);
|
BindBlitState(cmdbuf, dst_region);
|
||||||
cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
|
cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
|
||||||
@ -865,7 +876,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
|
|||||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.depthTestEnable = VK_FALSE,
|
.depthTestEnable = key.depth_clear,
|
||||||
.depthWriteEnable = key.depth_clear,
|
.depthWriteEnable = key.depth_clear,
|
||||||
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
|
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
|
||||||
.depthBoundsTestEnable = VK_FALSE,
|
.depthBoundsTestEnable = VK_FALSE,
|
||||||
|
@ -67,6 +67,8 @@ public:
|
|||||||
|
|
||||||
void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||||
|
|
||||||
|
void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
|
||||||
|
|
||||||
void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
|
void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
|
||||||
|
|
||||||
void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
|
void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
|
||||||
@ -128,6 +130,7 @@ private:
|
|||||||
vk::ShaderModule convert_depth_to_float_frag;
|
vk::ShaderModule convert_depth_to_float_frag;
|
||||||
vk::ShaderModule convert_float_to_depth_frag;
|
vk::ShaderModule convert_float_to_depth_frag;
|
||||||
vk::ShaderModule convert_abgr8_to_d24s8_frag;
|
vk::ShaderModule convert_abgr8_to_d24s8_frag;
|
||||||
|
vk::ShaderModule convert_d32f_to_abgr8_frag;
|
||||||
vk::ShaderModule convert_d24s8_to_abgr8_frag;
|
vk::ShaderModule convert_d24s8_to_abgr8_frag;
|
||||||
vk::ShaderModule convert_s8d24_to_abgr8_frag;
|
vk::ShaderModule convert_s8d24_to_abgr8_frag;
|
||||||
vk::Sampler linear_sampler;
|
vk::Sampler linear_sampler;
|
||||||
@ -146,6 +149,7 @@ private:
|
|||||||
vk::Pipeline convert_d16_to_r16_pipeline;
|
vk::Pipeline convert_d16_to_r16_pipeline;
|
||||||
vk::Pipeline convert_r16_to_d16_pipeline;
|
vk::Pipeline convert_r16_to_d16_pipeline;
|
||||||
vk::Pipeline convert_abgr8_to_d24s8_pipeline;
|
vk::Pipeline convert_abgr8_to_d24s8_pipeline;
|
||||||
|
vk::Pipeline convert_d32f_to_abgr8_pipeline;
|
||||||
vk::Pipeline convert_d24s8_to_abgr8_pipeline;
|
vk::Pipeline convert_d24s8_to_abgr8_pipeline;
|
||||||
vk::Pipeline convert_s8d24_to_abgr8_pipeline;
|
vk::Pipeline convert_s8d24_to_abgr8_pipeline;
|
||||||
};
|
};
|
||||||
|
@ -214,8 +214,9 @@ struct FormatTuple {
|
|||||||
{VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT
|
{VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT
|
||||||
|
|
||||||
// Depth formats
|
// Depth formats
|
||||||
{VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
|
{VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
|
||||||
{VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
|
{VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
|
||||||
|
{VK_FORMAT_X8_D24_UNORM_PACK32, Attachable}, // X8_D24_UNORM
|
||||||
|
|
||||||
// Stencil formats
|
// Stencil formats
|
||||||
{VK_FORMAT_S8_UINT, Attachable}, // S8_UINT
|
{VK_FORMAT_S8_UINT, Attachable}, // S8_UINT
|
||||||
|
@ -56,10 +56,6 @@ public:
|
|||||||
return device.GetDriverName();
|
return device.GetDriverName();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySurfaceChanged() override {
|
|
||||||
present_manager.NotifySurfaceChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Report() const;
|
void Report() const;
|
||||||
|
|
||||||
|
@ -96,6 +96,7 @@ std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
|
|||||||
VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
|
VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
|
||||||
switch (framebuffer.pixel_format) {
|
switch (framebuffer.pixel_format) {
|
||||||
case Service::android::PixelFormat::Rgba8888:
|
case Service::android::PixelFormat::Rgba8888:
|
||||||
|
case Service::android::PixelFormat::Rgbx8888:
|
||||||
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||||
case Service::android::PixelFormat::Rgb565:
|
case Service::android::PixelFormat::Rgb565:
|
||||||
return VK_FORMAT_R5G6B5_UNORM_PACK16;
|
return VK_FORMAT_R5G6B5_UNORM_PACK16;
|
||||||
|
@ -103,8 +103,7 @@ PresentManager::PresentManager(const vk::Instance& instance_,
|
|||||||
surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(),
|
surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(),
|
||||||
swapchain.GetImageViewFormat())},
|
swapchain.GetImageViewFormat())},
|
||||||
use_present_thread{Settings::values.async_presentation.GetValue()},
|
use_present_thread{Settings::values.async_presentation.GetValue()},
|
||||||
image_count{swapchain.GetImageCount()}, last_render_surface{
|
image_count{swapchain.GetImageCount()} {
|
||||||
render_window_.GetWindowInfo().render_surface} {
|
|
||||||
|
|
||||||
auto& dld = device.GetLogical();
|
auto& dld = device.GetLogical();
|
||||||
cmdpool = dld.CreateCommandPool({
|
cmdpool = dld.CreateCommandPool({
|
||||||
@ -289,44 +288,36 @@ void PresentManager::PresentThread(std::stop_token token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PresentManager::NotifySurfaceChanged() {
|
void PresentManager::RecreateSwapchain(Frame* frame) {
|
||||||
#ifdef ANDROID
|
swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
|
||||||
std::scoped_lock lock{recreate_surface_mutex};
|
image_count = swapchain.GetImageCount();
|
||||||
recreate_surface_cv.notify_one();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PresentManager::CopyToSwapchain(Frame* frame) {
|
void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||||
MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
|
bool requires_recreation = false;
|
||||||
|
|
||||||
const auto recreate_swapchain = [&] {
|
while (true) {
|
||||||
swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
|
try {
|
||||||
image_count = swapchain.GetImageCount();
|
// Recreate surface and swapchain if needed.
|
||||||
};
|
if (requires_recreation) {
|
||||||
|
surface = CreateSurface(instance, render_window.GetWindowInfo());
|
||||||
|
RecreateSwapchain(frame);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef ANDROID
|
// Draw to swapchain.
|
||||||
std::unique_lock lock{recreate_surface_mutex};
|
return CopyToSwapchainImpl(frame);
|
||||||
|
} catch (const vk::Exception& except) {
|
||||||
|
if (except.GetResult() != VK_ERROR_SURFACE_LOST_KHR) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
const auto needs_recreation = [&] {
|
requires_recreation = true;
|
||||||
if (last_render_surface != render_window.GetWindowInfo().render_surface) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (swapchain.NeedsRecreation(frame->is_srgb)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
recreate_surface_cv.wait_for(lock, std::chrono::milliseconds(400),
|
|
||||||
[&]() { return !needs_recreation(); });
|
|
||||||
|
|
||||||
// If the frontend recreated the surface, recreate the renderer surface and swapchain.
|
|
||||||
if (last_render_surface != render_window.GetWindowInfo().render_surface) {
|
|
||||||
last_render_surface = render_window.GetWindowInfo().render_surface;
|
|
||||||
surface = CreateSurface(instance, render_window.GetWindowInfo());
|
|
||||||
recreate_swapchain();
|
|
||||||
}
|
}
|
||||||
#endif
|
}
|
||||||
|
|
||||||
|
void PresentManager::CopyToSwapchainImpl(Frame* frame) {
|
||||||
|
MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
|
||||||
|
|
||||||
// If the size or colorspace of the incoming frames has changed, recreate the swapchain
|
// If the size or colorspace of the incoming frames has changed, recreate the swapchain
|
||||||
// to account for that.
|
// to account for that.
|
||||||
@ -334,11 +325,11 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
|
|||||||
const bool size_changed =
|
const bool size_changed =
|
||||||
swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
|
swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
|
||||||
if (srgb_changed || size_changed) {
|
if (srgb_changed || size_changed) {
|
||||||
recreate_swapchain();
|
RecreateSwapchain(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (swapchain.AcquireNextImage()) {
|
while (swapchain.AcquireNextImage()) {
|
||||||
recreate_swapchain();
|
RecreateSwapchain(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
const vk::CommandBuffer cmdbuf{frame->cmdbuf};
|
const vk::CommandBuffer cmdbuf{frame->cmdbuf};
|
||||||
|
@ -54,14 +54,15 @@ public:
|
|||||||
/// Waits for the present thread to finish presenting all queued frames.
|
/// Waits for the present thread to finish presenting all queued frames.
|
||||||
void WaitPresent();
|
void WaitPresent();
|
||||||
|
|
||||||
/// This is called to notify the rendering backend of a surface change
|
|
||||||
void NotifySurfaceChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void PresentThread(std::stop_token token);
|
void PresentThread(std::stop_token token);
|
||||||
|
|
||||||
void CopyToSwapchain(Frame* frame);
|
void CopyToSwapchain(Frame* frame);
|
||||||
|
|
||||||
|
void CopyToSwapchainImpl(Frame* frame);
|
||||||
|
|
||||||
|
void RecreateSwapchain(Frame* frame);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const vk::Instance& instance;
|
const vk::Instance& instance;
|
||||||
Core::Frontend::EmuWindow& render_window;
|
Core::Frontend::EmuWindow& render_window;
|
||||||
@ -76,16 +77,13 @@ private:
|
|||||||
std::queue<Frame*> free_queue;
|
std::queue<Frame*> free_queue;
|
||||||
std::condition_variable_any frame_cv;
|
std::condition_variable_any frame_cv;
|
||||||
std::condition_variable free_cv;
|
std::condition_variable free_cv;
|
||||||
std::condition_variable recreate_surface_cv;
|
|
||||||
std::mutex swapchain_mutex;
|
std::mutex swapchain_mutex;
|
||||||
std::mutex recreate_surface_mutex;
|
|
||||||
std::mutex queue_mutex;
|
std::mutex queue_mutex;
|
||||||
std::mutex free_mutex;
|
std::mutex free_mutex;
|
||||||
std::jthread present_thread;
|
std::jthread present_thread;
|
||||||
bool blit_supported;
|
bool blit_supported;
|
||||||
bool use_present_thread;
|
bool use_present_thread;
|
||||||
std::size_t image_count{};
|
std::size_t image_count{};
|
||||||
void* last_render_surface{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
@ -422,7 +422,8 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) {
|
if (use_stencil && framebuffer->HasAspectStencilBit() && regs.stencil_front_mask != 0xFF &&
|
||||||
|
regs.stencil_front_mask != 0) {
|
||||||
Region2D dst_region = {
|
Region2D dst_region = {
|
||||||
Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y},
|
Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y},
|
||||||
Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width),
|
Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width),
|
||||||
|
@ -24,25 +24,38 @@ using namespace Common::Literals;
|
|||||||
|
|
||||||
// Maximum potential alignment of a Vulkan buffer
|
// Maximum potential alignment of a Vulkan buffer
|
||||||
constexpr VkDeviceSize MAX_ALIGNMENT = 256;
|
constexpr VkDeviceSize MAX_ALIGNMENT = 256;
|
||||||
// Maximum size to put elements in the stream buffer
|
|
||||||
constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8_MiB;
|
|
||||||
// Stream buffer size in bytes
|
// Stream buffer size in bytes
|
||||||
constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB;
|
constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB;
|
||||||
constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS;
|
|
||||||
|
|
||||||
size_t Region(size_t iterator) noexcept {
|
size_t GetStreamBufferSize(const Device& device) {
|
||||||
return iterator / REGION_SIZE;
|
VkDeviceSize size{0};
|
||||||
|
if (device.HasDebuggingToolAttached()) {
|
||||||
|
ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) {
|
||||||
|
size = std::max(size, heap.size);
|
||||||
|
});
|
||||||
|
// If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be
|
||||||
|
// loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue
|
||||||
|
// as the heap will be much larger.
|
||||||
|
if (size <= 256_MiB) {
|
||||||
|
size = size * 40 / 100;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
size = MAX_STREAM_BUFFER_SIZE;
|
||||||
|
}
|
||||||
|
return std::min(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
|
StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
|
||||||
Scheduler& scheduler_)
|
Scheduler& scheduler_)
|
||||||
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} {
|
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
|
||||||
|
stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size /
|
||||||
|
StagingBufferPool::NUM_SYNCS} {
|
||||||
VkBufferCreateInfo stream_ci = {
|
VkBufferCreateInfo stream_ci = {
|
||||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.size = STREAM_BUFFER_SIZE,
|
.size = stream_buffer_size,
|
||||||
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
|
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
|
||||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
||||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||||
@ -63,7 +76,7 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
|
|||||||
StagingBufferPool::~StagingBufferPool() = default;
|
StagingBufferPool::~StagingBufferPool() = default;
|
||||||
|
|
||||||
StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) {
|
StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) {
|
||||||
if (!deferred && usage == MemoryUsage::Upload && size <= MAX_STREAM_BUFFER_REQUEST_SIZE) {
|
if (!deferred && usage == MemoryUsage::Upload && size <= region_size) {
|
||||||
return GetStreamBuffer(size);
|
return GetStreamBuffer(size);
|
||||||
}
|
}
|
||||||
return GetStagingBuffer(size, usage, deferred);
|
return GetStagingBuffer(size, usage, deferred);
|
||||||
@ -101,7 +114,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
|
|||||||
used_iterator = iterator;
|
used_iterator = iterator;
|
||||||
free_iterator = std::max(free_iterator, iterator + size);
|
free_iterator = std::max(free_iterator, iterator + size);
|
||||||
|
|
||||||
if (iterator + size >= STREAM_BUFFER_SIZE) {
|
if (iterator + size >= stream_buffer_size) {
|
||||||
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
|
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
|
||||||
current_tick);
|
current_tick);
|
||||||
used_iterator = 0;
|
used_iterator = 0;
|
||||||
|
@ -90,6 +90,9 @@ private:
|
|||||||
void ReleaseCache(MemoryUsage usage);
|
void ReleaseCache(MemoryUsage usage);
|
||||||
|
|
||||||
void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
|
void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
|
||||||
|
size_t Region(size_t iter) const noexcept {
|
||||||
|
return iter / region_size;
|
||||||
|
}
|
||||||
|
|
||||||
const Device& device;
|
const Device& device;
|
||||||
MemoryAllocator& memory_allocator;
|
MemoryAllocator& memory_allocator;
|
||||||
@ -97,6 +100,8 @@ private:
|
|||||||
|
|
||||||
vk::Buffer stream_buffer;
|
vk::Buffer stream_buffer;
|
||||||
std::span<u8> stream_pointer;
|
std::span<u8> stream_pointer;
|
||||||
|
VkDeviceSize stream_buffer_size;
|
||||||
|
VkDeviceSize region_size;
|
||||||
|
|
||||||
size_t iterator = 0;
|
size_t iterator = 0;
|
||||||
size_t used_iterator = 0;
|
size_t used_iterator = 0;
|
||||||
|
@ -238,6 +238,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
|
|||||||
return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
|
return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||||
case PixelFormat::D16_UNORM:
|
case PixelFormat::D16_UNORM:
|
||||||
case PixelFormat::D32_FLOAT:
|
case PixelFormat::D32_FLOAT:
|
||||||
|
case PixelFormat::X8_D24_UNORM:
|
||||||
return VK_IMAGE_ASPECT_DEPTH_BIT;
|
return VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||||
case PixelFormat::S8_UINT:
|
case PixelFormat::S8_UINT:
|
||||||
return VK_IMAGE_ASPECT_STENCIL_BIT;
|
return VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||||
@ -1200,6 +1201,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
|
|||||||
if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) {
|
if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) {
|
||||||
return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view);
|
return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view);
|
||||||
}
|
}
|
||||||
|
if (src_view.format == PixelFormat::D32_FLOAT) {
|
||||||
|
return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case PixelFormat::R32_FLOAT:
|
case PixelFormat::R32_FLOAT:
|
||||||
if (src_view.format == PixelFormat::D32_FLOAT) {
|
if (src_view.format == PixelFormat::D32_FLOAT) {
|
||||||
|
@ -85,6 +85,8 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
|
|||||||
return PixelFormat::S8_UINT;
|
return PixelFormat::S8_UINT;
|
||||||
case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT:
|
case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT:
|
||||||
return PixelFormat::D32_FLOAT_S8_UINT;
|
return PixelFormat::D32_FLOAT_S8_UINT;
|
||||||
|
case Tegra::DepthFormat::X8Z24_UNORM:
|
||||||
|
return PixelFormat::X8_D24_UNORM;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED_MSG("Unimplemented format={}", format);
|
UNIMPLEMENTED_MSG("Unimplemented format={}", format);
|
||||||
return PixelFormat::S8_UINT_D24_UNORM;
|
return PixelFormat::S8_UINT_D24_UNORM;
|
||||||
@ -202,6 +204,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
|
|||||||
PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) {
|
PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case Service::android::PixelFormat::Rgba8888:
|
case Service::android::PixelFormat::Rgba8888:
|
||||||
|
case Service::android::PixelFormat::Rgbx8888:
|
||||||
return PixelFormat::A8B8G8R8_UNORM;
|
return PixelFormat::A8B8G8R8_UNORM;
|
||||||
case Service::android::PixelFormat::Rgb565:
|
case Service::android::PixelFormat::Rgb565:
|
||||||
return PixelFormat::R5G6B5_UNORM;
|
return PixelFormat::R5G6B5_UNORM;
|
||||||
|
@ -115,6 +115,7 @@ enum class PixelFormat {
|
|||||||
// Depth formats
|
// Depth formats
|
||||||
D32_FLOAT = MaxColorFormat,
|
D32_FLOAT = MaxColorFormat,
|
||||||
D16_UNORM,
|
D16_UNORM,
|
||||||
|
X8_D24_UNORM,
|
||||||
|
|
||||||
MaxDepthFormat,
|
MaxDepthFormat,
|
||||||
|
|
||||||
@ -251,6 +252,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
|
|||||||
1, // E5B9G9R9_FLOAT
|
1, // E5B9G9R9_FLOAT
|
||||||
1, // D32_FLOAT
|
1, // D32_FLOAT
|
||||||
1, // D16_UNORM
|
1, // D16_UNORM
|
||||||
|
1, // X8_D24_UNORM
|
||||||
1, // S8_UINT
|
1, // S8_UINT
|
||||||
1, // D24_UNORM_S8_UINT
|
1, // D24_UNORM_S8_UINT
|
||||||
1, // S8_UINT_D24_UNORM
|
1, // S8_UINT_D24_UNORM
|
||||||
@ -360,6 +362,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
|
|||||||
1, // E5B9G9R9_FLOAT
|
1, // E5B9G9R9_FLOAT
|
||||||
1, // D32_FLOAT
|
1, // D32_FLOAT
|
||||||
1, // D16_UNORM
|
1, // D16_UNORM
|
||||||
|
1, // X8_D24_UNORM
|
||||||
1, // S8_UINT
|
1, // S8_UINT
|
||||||
1, // D24_UNORM_S8_UINT
|
1, // D24_UNORM_S8_UINT
|
||||||
1, // S8_UINT_D24_UNORM
|
1, // S8_UINT_D24_UNORM
|
||||||
@ -469,6 +472,7 @@ constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
|
|||||||
32, // E5B9G9R9_FLOAT
|
32, // E5B9G9R9_FLOAT
|
||||||
32, // D32_FLOAT
|
32, // D32_FLOAT
|
||||||
16, // D16_UNORM
|
16, // D16_UNORM
|
||||||
|
32, // X8_D24_UNORM
|
||||||
8, // S8_UINT
|
8, // S8_UINT
|
||||||
32, // D24_UNORM_S8_UINT
|
32, // D24_UNORM_S8_UINT
|
||||||
32, // S8_UINT_D24_UNORM
|
32, // S8_UINT_D24_UNORM
|
||||||
|
@ -138,10 +138,16 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
|
|||||||
return PixelFormat::E5B9G9R9_FLOAT;
|
return PixelFormat::E5B9G9R9_FLOAT;
|
||||||
case Hash(TextureFormat::Z32, FLOAT):
|
case Hash(TextureFormat::Z32, FLOAT):
|
||||||
return PixelFormat::D32_FLOAT;
|
return PixelFormat::D32_FLOAT;
|
||||||
|
case Hash(TextureFormat::Z32, FLOAT, UINT, UINT, UINT, LINEAR):
|
||||||
|
return PixelFormat::D32_FLOAT;
|
||||||
case Hash(TextureFormat::Z16, UNORM):
|
case Hash(TextureFormat::Z16, UNORM):
|
||||||
return PixelFormat::D16_UNORM;
|
return PixelFormat::D16_UNORM;
|
||||||
case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR):
|
case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR):
|
||||||
return PixelFormat::D16_UNORM;
|
return PixelFormat::D16_UNORM;
|
||||||
|
case Hash(TextureFormat::X8Z24, UNORM):
|
||||||
|
return PixelFormat::X8_D24_UNORM;
|
||||||
|
case Hash(TextureFormat::X8Z24, UNORM, UINT, UINT, UINT, LINEAR):
|
||||||
|
return PixelFormat::X8_D24_UNORM;
|
||||||
case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR):
|
case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR):
|
||||||
return PixelFormat::S8_UINT_D24_UNORM;
|
return PixelFormat::S8_UINT_D24_UNORM;
|
||||||
case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR):
|
case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR):
|
||||||
|
@ -211,6 +211,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
|
|||||||
return "D32_FLOAT";
|
return "D32_FLOAT";
|
||||||
case PixelFormat::D16_UNORM:
|
case PixelFormat::D16_UNORM:
|
||||||
return "D16_UNORM";
|
return "D16_UNORM";
|
||||||
|
case PixelFormat::X8_D24_UNORM:
|
||||||
|
return "X8_D24_UNORM";
|
||||||
case PixelFormat::S8_UINT:
|
case PixelFormat::S8_UINT:
|
||||||
return "S8_UINT";
|
return "S8_UINT";
|
||||||
case PixelFormat::D24_UNORM_S8_UINT:
|
case PixelFormat::D24_UNORM_S8_UINT:
|
||||||
|
@ -85,6 +85,7 @@ bool ImageViewBase::SupportsAnisotropy() const noexcept {
|
|||||||
// Depth formats
|
// Depth formats
|
||||||
case PixelFormat::D32_FLOAT:
|
case PixelFormat::D32_FLOAT:
|
||||||
case PixelFormat::D16_UNORM:
|
case PixelFormat::D16_UNORM:
|
||||||
|
case PixelFormat::X8_D24_UNORM:
|
||||||
// Stencil formats
|
// Stencil formats
|
||||||
case PixelFormat::S8_UINT:
|
case PixelFormat::S8_UINT:
|
||||||
// DepthStencil formats
|
// DepthStencil formats
|
||||||
|
@ -84,9 +84,12 @@ constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{
|
|||||||
} // namespace Alternatives
|
} // namespace Alternatives
|
||||||
|
|
||||||
enum class NvidiaArchitecture {
|
enum class NvidiaArchitecture {
|
||||||
AmpereOrNewer,
|
KeplerOrOlder,
|
||||||
|
Maxwell,
|
||||||
|
Pascal,
|
||||||
|
Volta,
|
||||||
Turing,
|
Turing,
|
||||||
VoltaOrOlder,
|
AmpereOrNewer,
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -200,6 +203,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
|
|||||||
VK_FORMAT_BC7_UNORM_BLOCK,
|
VK_FORMAT_BC7_UNORM_BLOCK,
|
||||||
VK_FORMAT_D16_UNORM,
|
VK_FORMAT_D16_UNORM,
|
||||||
VK_FORMAT_D16_UNORM_S8_UINT,
|
VK_FORMAT_D16_UNORM_S8_UINT,
|
||||||
|
VK_FORMAT_X8_D24_UNORM_PACK32,
|
||||||
VK_FORMAT_D24_UNORM_S8_UINT,
|
VK_FORMAT_D24_UNORM_S8_UINT,
|
||||||
VK_FORMAT_D32_SFLOAT,
|
VK_FORMAT_D32_SFLOAT,
|
||||||
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||||||
@ -321,13 +325,38 @@ NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
|
|||||||
physical.GetProperties2(physical_properties);
|
physical.GetProperties2(physical_properties);
|
||||||
if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) {
|
if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) {
|
||||||
// Only Ampere and newer support this feature
|
// Only Ampere and newer support this feature
|
||||||
|
// TODO: Find a way to differentiate Ampere and Ada
|
||||||
return NvidiaArchitecture::AmpereOrNewer;
|
return NvidiaArchitecture::AmpereOrNewer;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (exts.contains(VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME)) {
|
|
||||||
return NvidiaArchitecture::Turing;
|
return NvidiaArchitecture::Turing;
|
||||||
}
|
}
|
||||||
return NvidiaArchitecture::VoltaOrOlder;
|
|
||||||
|
if (exts.contains(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME)) {
|
||||||
|
VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT advanced_blending_props{};
|
||||||
|
advanced_blending_props.sType =
|
||||||
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT;
|
||||||
|
VkPhysicalDeviceProperties2 physical_properties{};
|
||||||
|
physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
||||||
|
physical_properties.pNext = &advanced_blending_props;
|
||||||
|
physical.GetProperties2(physical_properties);
|
||||||
|
if (advanced_blending_props.advancedBlendMaxColorAttachments == 1) {
|
||||||
|
return NvidiaArchitecture::Maxwell;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exts.contains(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME)) {
|
||||||
|
VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_raster_props{};
|
||||||
|
conservative_raster_props.sType =
|
||||||
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT;
|
||||||
|
physical_properties.pNext = &conservative_raster_props;
|
||||||
|
physical.GetProperties2(physical_properties);
|
||||||
|
if (conservative_raster_props.degenerateLinesRasterized) {
|
||||||
|
return NvidiaArchitecture::Volta;
|
||||||
|
}
|
||||||
|
return NvidiaArchitecture::Pascal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NvidiaArchitecture::KeplerOrOlder;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const char*> ExtensionListForVulkan(
|
std::vector<const char*> ExtensionListForVulkan(
|
||||||
@ -504,19 +533,14 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
if (is_nvidia) {
|
if (is_nvidia) {
|
||||||
const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
|
const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
|
||||||
const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
|
const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
|
||||||
switch (arch) {
|
if (arch >= NvidiaArchitecture::AmpereOrNewer) {
|
||||||
case NvidiaArchitecture::AmpereOrNewer:
|
|
||||||
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
|
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
|
||||||
features.shader_float16_int8.shaderFloat16 = false;
|
features.shader_float16_int8.shaderFloat16 = false;
|
||||||
break;
|
} else if (arch <= NvidiaArchitecture::Volta) {
|
||||||
case NvidiaArchitecture::Turing:
|
|
||||||
break;
|
|
||||||
case NvidiaArchitecture::VoltaOrOlder:
|
|
||||||
if (nv_major_version < 527) {
|
if (nv_major_version < 527) {
|
||||||
LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
|
LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
|
||||||
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (nv_major_version >= 510) {
|
if (nv_major_version >= 510) {
|
||||||
LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits");
|
LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits");
|
||||||
@ -661,7 +685,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||||||
"ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
|
"ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
|
||||||
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||||
}
|
}
|
||||||
|
} else if (extensions.push_descriptor && is_nvidia) {
|
||||||
|
const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
|
||||||
|
if (arch <= NvidiaArchitecture::Pascal) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Pascal and older architectures have broken VK_KHR_push_descriptor");
|
||||||
|
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_mvk) {
|
if (is_mvk) {
|
||||||
LOG_WARNING(Render_Vulkan,
|
LOG_WARNING(Render_Vulkan,
|
||||||
"MVK driver breaks when using more than 16 vertex attributes/bindings");
|
"MVK driver breaks when using more than 16 vertex attributes/bindings");
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "common/literals.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/polyfill_ranges.h"
|
#include "common/polyfill_ranges.h"
|
||||||
#include "video_core/vulkan_common/vma.h"
|
#include "video_core/vulkan_common/vma.h"
|
||||||
@ -69,8 +70,7 @@ struct Range {
|
|||||||
case MemoryUsage::Download:
|
case MemoryUsage::Download:
|
||||||
return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
|
return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
|
||||||
case MemoryUsage::DeviceLocal:
|
case MemoryUsage::DeviceLocal:
|
||||||
return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
|
return {};
|
||||||
VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT;
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -212,7 +212,20 @@ MemoryAllocator::MemoryAllocator(const Device& device_)
|
|||||||
: device{device_}, allocator{device.GetAllocator()},
|
: device{device_}, allocator{device.GetAllocator()},
|
||||||
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
|
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
|
||||||
buffer_image_granularity{
|
buffer_image_granularity{
|
||||||
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {}
|
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
|
||||||
|
// GPUs not supporting rebar may only have a region with less than 256MB host visible/device
|
||||||
|
// local memory. In that case, opening 2 RenderDoc captures side-by-side is not possible due to
|
||||||
|
// the heap running out of memory. With RenderDoc attached and only a small host/device region,
|
||||||
|
// only allow the stream buffer in this memory heap.
|
||||||
|
if (device.HasDebuggingToolAttached()) {
|
||||||
|
using namespace Common::Literals;
|
||||||
|
ForEachDeviceLocalHostVisibleHeap(device, [this](size_t index, VkMemoryHeap& heap) {
|
||||||
|
if (heap.size <= 256_MiB) {
|
||||||
|
valid_memory_types &= ~(1u << index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MemoryAllocator::~MemoryAllocator() = default;
|
MemoryAllocator::~MemoryAllocator() = default;
|
||||||
|
|
||||||
@ -244,7 +257,7 @@ vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsa
|
|||||||
.usage = MemoryUsageVma(usage),
|
.usage = MemoryUsageVma(usage),
|
||||||
.requiredFlags = 0,
|
.requiredFlags = 0,
|
||||||
.preferredFlags = MemoryUsagePreferedVmaFlags(usage),
|
.preferredFlags = MemoryUsagePreferedVmaFlags(usage),
|
||||||
.memoryTypeBits = 0,
|
.memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
|
||||||
.pool = VK_NULL_HANDLE,
|
.pool = VK_NULL_HANDLE,
|
||||||
.pUserData = nullptr,
|
.pUserData = nullptr,
|
||||||
.priority = 0.f,
|
.priority = 0.f,
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "video_core/vulkan_common/vulkan_device.h"
|
||||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||||
|
|
||||||
VK_DEFINE_HANDLE(VmaAllocator)
|
VK_DEFINE_HANDLE(VmaAllocator)
|
||||||
@ -26,6 +27,18 @@ enum class MemoryUsage {
|
|||||||
Stream, ///< Requests device local host visible buffer, falling back host memory.
|
Stream, ///< Requests device local host visible buffer, falling back host memory.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
void ForEachDeviceLocalHostVisibleHeap(const Device& device, F&& f) {
|
||||||
|
auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
|
||||||
|
for (size_t i = 0; i < memory_props.memoryTypeCount; i++) {
|
||||||
|
auto& memory_type = memory_props.memoryTypes[i];
|
||||||
|
if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
|
||||||
|
(memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
|
||||||
|
f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Ownership handle of a memory commitment.
|
/// Ownership handle of a memory commitment.
|
||||||
/// Points to a subregion of a memory allocation.
|
/// Points to a subregion of a memory allocation.
|
||||||
class MemoryCommit {
|
class MemoryCommit {
|
||||||
@ -124,6 +137,7 @@ private:
|
|||||||
std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
|
std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
|
||||||
VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
|
VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
|
||||||
// and optimal images
|
// and optimal images
|
||||||
|
u32 valid_memory_types{~0u};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
@ -117,6 +117,9 @@ public:
|
|||||||
virtual ~Exception() = default;
|
virtual ~Exception() = default;
|
||||||
|
|
||||||
const char* what() const noexcept override;
|
const char* what() const noexcept override;
|
||||||
|
VkResult GetResult() const noexcept {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VkResult result;
|
VkResult result;
|
||||||
|
@ -195,6 +195,8 @@ add_executable(yuzu
|
|||||||
multiplayer/state.cpp
|
multiplayer/state.cpp
|
||||||
multiplayer/state.h
|
multiplayer/state.h
|
||||||
multiplayer/validation.h
|
multiplayer/validation.h
|
||||||
|
play_time_manager.cpp
|
||||||
|
play_time_manager.h
|
||||||
precompiled_headers.h
|
precompiled_headers.h
|
||||||
qt_common.cpp
|
qt_common.cpp
|
||||||
qt_common.h
|
qt_common.h
|
||||||
|
@ -42,6 +42,9 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) {
|
|||||||
for (auto* setting : Settings::values.linkage.by_category[category]) {
|
for (auto* setting : Settings::values.linkage.by_category[category]) {
|
||||||
settings.push_back(setting);
|
settings.push_back(setting);
|
||||||
}
|
}
|
||||||
|
for (auto* setting : UISettings::values.linkage.by_category[category]) {
|
||||||
|
settings.push_back(setting);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
push(Settings::Category::Audio);
|
push(Settings::Category::Audio);
|
||||||
|
@ -123,6 +123,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
|
|||||||
connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
|
connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
|
||||||
connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
|
connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
|
||||||
connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
|
connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
|
||||||
|
connect(ui->show_play_time, &QCheckBox::stateChanged, this,
|
||||||
|
&ConfigureUi::RequestGameListUpdate);
|
||||||
connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||||
&ConfigureUi::RequestGameListUpdate);
|
&ConfigureUi::RequestGameListUpdate);
|
||||||
connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||||
@ -167,6 +169,7 @@ void ConfigureUi::ApplyConfiguration() {
|
|||||||
UISettings::values.show_compat = ui->show_compat->isChecked();
|
UISettings::values.show_compat = ui->show_compat->isChecked();
|
||||||
UISettings::values.show_size = ui->show_size->isChecked();
|
UISettings::values.show_size = ui->show_size->isChecked();
|
||||||
UISettings::values.show_types = ui->show_types->isChecked();
|
UISettings::values.show_types = ui->show_types->isChecked();
|
||||||
|
UISettings::values.show_play_time = ui->show_play_time->isChecked();
|
||||||
UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
|
UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
|
||||||
UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
|
UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
|
||||||
UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
|
UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
|
||||||
@ -179,6 +182,7 @@ void ConfigureUi::ApplyConfiguration() {
|
|||||||
const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
|
const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
|
||||||
UISettings::values.screenshot_height.SetValue(height);
|
UISettings::values.screenshot_height.SetValue(height);
|
||||||
|
|
||||||
|
RequestGameListUpdate();
|
||||||
system.ApplySettings();
|
system.ApplySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +198,7 @@ void ConfigureUi::SetConfiguration() {
|
|||||||
ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
|
ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
|
||||||
ui->show_size->setChecked(UISettings::values.show_size.GetValue());
|
ui->show_size->setChecked(UISettings::values.show_size.GetValue());
|
||||||
ui->show_types->setChecked(UISettings::values.show_types.GetValue());
|
ui->show_types->setChecked(UISettings::values.show_types.GetValue());
|
||||||
|
ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue());
|
||||||
ui->game_icon_size_combobox->setCurrentIndex(
|
ui->game_icon_size_combobox->setCurrentIndex(
|
||||||
ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
|
ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
|
||||||
ui->folder_icon_size_combobox->setCurrentIndex(
|
ui->folder_icon_size_combobox->setCurrentIndex(
|
||||||
|
@ -104,6 +104,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="show_play_time">
|
||||||
|
<property name="text">
|
||||||
|
<string>Show Play Time Column</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
|
<layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
|
||||||
<item>
|
<item>
|
||||||
|
@ -29,9 +29,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
|
|||||||
INSERT(Settings, sink_id, "Output Engine:", "");
|
INSERT(Settings, sink_id, "Output Engine:", "");
|
||||||
INSERT(Settings, audio_output_device_id, "Output Device:", "");
|
INSERT(Settings, audio_output_device_id, "Output Device:", "");
|
||||||
INSERT(Settings, audio_input_device_id, "Input Device:", "");
|
INSERT(Settings, audio_input_device_id, "Input Device:", "");
|
||||||
INSERT(Settings, audio_muted, "Mute audio when in background", "");
|
INSERT(Settings, audio_muted, "Mute audio", "");
|
||||||
INSERT(Settings, volume, "Volume:", "");
|
INSERT(Settings, volume, "Volume:", "");
|
||||||
INSERT(Settings, dump_audio_commands, "", "");
|
INSERT(Settings, dump_audio_commands, "", "");
|
||||||
|
INSERT(UISettings, mute_when_in_background, "Mute audio when in background", "");
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
INSERT(Settings, use_multi_core, "Multicore CPU Emulation", "");
|
INSERT(Settings, use_multi_core, "Multicore CPU Emulation", "");
|
||||||
|
@ -312,8 +312,10 @@ void GameList::OnFilterCloseClicked() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
|
GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
|
||||||
Core::System& system_, GMainWindow* parent)
|
PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
|
||||||
: QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} {
|
GMainWindow* parent)
|
||||||
|
: QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_},
|
||||||
|
play_time_manager{play_time_manager_}, system{system_} {
|
||||||
watcher = new QFileSystemWatcher(this);
|
watcher = new QFileSystemWatcher(this);
|
||||||
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
|
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
|
||||||
|
|
||||||
@ -340,6 +342,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
|
|||||||
|
|
||||||
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
|
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
|
||||||
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
|
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
|
||||||
|
tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
|
||||||
item_model->setSortRole(GameListItemPath::SortRole);
|
item_model->setSortRole(GameListItemPath::SortRole);
|
||||||
|
|
||||||
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
|
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
|
||||||
@ -548,6 +551,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
|
|||||||
QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
|
QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
|
||||||
QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
|
QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
|
||||||
QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
|
QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
|
||||||
|
QAction* remove_play_time_data = remove_menu->addAction(tr("Remove Play Time Data"));
|
||||||
QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage"));
|
QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage"));
|
||||||
QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache"));
|
QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache"));
|
||||||
QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache"));
|
QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache"));
|
||||||
@ -560,9 +564,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
|
|||||||
QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
|
QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
|
||||||
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
|
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
|
||||||
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
|
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
|
||||||
#ifndef WIN32
|
|
||||||
QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
|
QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
|
||||||
QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
|
QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
|
||||||
|
#ifndef WIN32
|
||||||
QAction* create_applications_menu_shortcut =
|
QAction* create_applications_menu_shortcut =
|
||||||
shortcut_menu->addAction(tr("Add to Applications Menu"));
|
shortcut_menu->addAction(tr("Add to Applications Menu"));
|
||||||
#endif
|
#endif
|
||||||
@ -622,6 +626,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
|
|||||||
connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
|
connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
|
||||||
emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
|
emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
|
||||||
});
|
});
|
||||||
|
connect(remove_play_time_data, &QAction::triggered,
|
||||||
|
[this, program_id]() { emit RemovePlayTimeRequested(program_id); });
|
||||||
connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {
|
connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {
|
||||||
emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path);
|
emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path);
|
||||||
});
|
});
|
||||||
@ -638,10 +644,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
|
|||||||
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
|
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
|
||||||
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
|
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
|
||||||
});
|
});
|
||||||
#ifndef WIN32
|
|
||||||
connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
|
connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
|
||||||
emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
|
emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
|
||||||
});
|
});
|
||||||
|
#ifndef WIN32
|
||||||
connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
|
connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
|
||||||
emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
|
emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
|
||||||
});
|
});
|
||||||
@ -790,6 +796,7 @@ void GameList::RetranslateUI() {
|
|||||||
item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
|
item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
|
||||||
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
|
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
|
||||||
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
|
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
|
||||||
|
item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameListSearchField::changeEvent(QEvent* event) {
|
void GameListSearchField::changeEvent(QEvent* event) {
|
||||||
@ -817,6 +824,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
|||||||
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
|
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
|
||||||
tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
|
tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
|
||||||
tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
|
tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
|
||||||
|
tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
|
||||||
|
|
||||||
// Delete any rows that might already exist if we're repopulating
|
// Delete any rows that might already exist if we're repopulating
|
||||||
item_model->removeRows(0, item_model->rowCount());
|
item_model->removeRows(0, item_model->rowCount());
|
||||||
@ -825,7 +833,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
|
|||||||
emit ShouldCancelWorker();
|
emit ShouldCancelWorker();
|
||||||
|
|
||||||
GameListWorker* worker =
|
GameListWorker* worker =
|
||||||
new GameListWorker(vfs, provider, game_dirs, compatibility_list, system);
|
new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
|
||||||
|
|
||||||
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
|
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
|
||||||
connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
|
connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "uisettings.h"
|
#include "uisettings.h"
|
||||||
#include "yuzu/compatibility_list.h"
|
#include "yuzu/compatibility_list.h"
|
||||||
|
#include "yuzu/play_time_manager.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
@ -75,11 +76,13 @@ public:
|
|||||||
COLUMN_ADD_ONS,
|
COLUMN_ADD_ONS,
|
||||||
COLUMN_FILE_TYPE,
|
COLUMN_FILE_TYPE,
|
||||||
COLUMN_SIZE,
|
COLUMN_SIZE,
|
||||||
|
COLUMN_PLAY_TIME,
|
||||||
COLUMN_COUNT, // Number of columns
|
COLUMN_COUNT, // Number of columns
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
||||||
FileSys::ManualContentProvider* provider_, Core::System& system_,
|
FileSys::ManualContentProvider* provider_,
|
||||||
|
PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
|
||||||
GMainWindow* parent = nullptr);
|
GMainWindow* parent = nullptr);
|
||||||
~GameList() override;
|
~GameList() override;
|
||||||
|
|
||||||
@ -113,6 +116,7 @@ signals:
|
|||||||
void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
|
void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
|
||||||
void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
|
void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
|
||||||
const std::string& game_path);
|
const std::string& game_path);
|
||||||
|
void RemovePlayTimeRequested(u64 program_id);
|
||||||
void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
|
void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
|
||||||
void VerifyIntegrityRequested(const std::string& game_path);
|
void VerifyIntegrityRequested(const std::string& game_path);
|
||||||
void CopyTIDRequested(u64 program_id);
|
void CopyTIDRequested(u64 program_id);
|
||||||
@ -168,6 +172,7 @@ private:
|
|||||||
|
|
||||||
friend class GameListSearchField;
|
friend class GameListSearchField;
|
||||||
|
|
||||||
|
const PlayTime::PlayTimeManager& play_time_manager;
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
#include "yuzu/play_time_manager.h"
|
||||||
#include "yuzu/uisettings.h"
|
#include "yuzu/uisettings.h"
|
||||||
#include "yuzu/util/util.h"
|
#include "yuzu/util/util.h"
|
||||||
|
|
||||||
@ -221,6 +222,31 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GameListItem for Play Time values.
|
||||||
|
* This object stores the play time of a game in seconds, and its readable
|
||||||
|
* representation in minutes/hours
|
||||||
|
*/
|
||||||
|
class GameListItemPlayTime : public GameListItem {
|
||||||
|
public:
|
||||||
|
static constexpr int PlayTimeRole = SortRole;
|
||||||
|
|
||||||
|
GameListItemPlayTime() = default;
|
||||||
|
explicit GameListItemPlayTime(const qulonglong time_seconds) {
|
||||||
|
setData(time_seconds, PlayTimeRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setData(const QVariant& value, int role) override {
|
||||||
|
qulonglong time_seconds = value.toULongLong();
|
||||||
|
GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole);
|
||||||
|
GameListItem::setData(value, PlayTimeRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const QStandardItem& other) const override {
|
||||||
|
return data(PlayTimeRole).toULongLong() < other.data(PlayTimeRole).toULongLong();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class GameListDir : public GameListItem {
|
class GameListDir : public GameListItem {
|
||||||
public:
|
public:
|
||||||
static constexpr int GameDirRole = Qt::UserRole + 2;
|
static constexpr int GameDirRole = Qt::UserRole + 2;
|
||||||
|
@ -194,6 +194,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
|
|||||||
const std::size_t size, const std::vector<u8>& icon,
|
const std::size_t size, const std::vector<u8>& icon,
|
||||||
Loader::AppLoader& loader, u64 program_id,
|
Loader::AppLoader& loader, u64 program_id,
|
||||||
const CompatibilityList& compatibility_list,
|
const CompatibilityList& compatibility_list,
|
||||||
|
const PlayTime::PlayTimeManager& play_time_manager,
|
||||||
const FileSys::PatchManager& patch) {
|
const FileSys::PatchManager& patch) {
|
||||||
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||||
|
|
||||||
@ -212,6 +213,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
|
|||||||
new GameListItemCompat(compatibility),
|
new GameListItemCompat(compatibility),
|
||||||
new GameListItem(file_type_string),
|
new GameListItem(file_type_string),
|
||||||
new GameListItemSize(size),
|
new GameListItemSize(size),
|
||||||
|
new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto patch_versions = GetGameListCachedObject(
|
const auto patch_versions = GetGameListCachedObject(
|
||||||
@ -227,9 +229,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
|
|||||||
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
|
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
|
||||||
FileSys::ManualContentProvider* provider_,
|
FileSys::ManualContentProvider* provider_,
|
||||||
QVector<UISettings::GameDir>& game_dirs_,
|
QVector<UISettings::GameDir>& game_dirs_,
|
||||||
const CompatibilityList& compatibility_list_, Core::System& system_)
|
const CompatibilityList& compatibility_list_,
|
||||||
|
const PlayTime::PlayTimeManager& play_time_manager_,
|
||||||
|
Core::System& system_)
|
||||||
: vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
|
: vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
|
||||||
compatibility_list{compatibility_list_}, system{system_} {}
|
compatibility_list{compatibility_list_},
|
||||||
|
play_time_manager{play_time_manager_}, system{system_} {}
|
||||||
|
|
||||||
GameListWorker::~GameListWorker() = default;
|
GameListWorker::~GameListWorker() = default;
|
||||||
|
|
||||||
@ -280,7 +285,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
|
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
|
||||||
program_id, compatibility_list, patch),
|
program_id, compatibility_list, play_time_manager, patch),
|
||||||
parent_dir);
|
parent_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -357,7 +362,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
|||||||
|
|
||||||
emit EntryReady(MakeGameListEntry(physical_name, name,
|
emit EntryReady(MakeGameListEntry(physical_name, name,
|
||||||
Common::FS::GetSize(physical_name), icon,
|
Common::FS::GetSize(physical_name), icon,
|
||||||
*loader, id, compatibility_list, patch),
|
*loader, id, compatibility_list,
|
||||||
|
play_time_manager, patch),
|
||||||
parent_dir);
|
parent_dir);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -370,10 +376,11 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
|||||||
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
|
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
|
||||||
system.GetContentProvider()};
|
system.GetContentProvider()};
|
||||||
|
|
||||||
emit EntryReady(
|
emit EntryReady(MakeGameListEntry(physical_name, name,
|
||||||
MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name),
|
Common::FS::GetSize(physical_name), icon,
|
||||||
icon, *loader, program_id, compatibility_list, patch),
|
*loader, program_id, compatibility_list,
|
||||||
parent_dir);
|
play_time_manager, patch),
|
||||||
|
parent_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (is_dir) {
|
} else if (is_dir) {
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "yuzu/compatibility_list.h"
|
#include "yuzu/compatibility_list.h"
|
||||||
|
#include "yuzu/play_time_manager.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
@ -36,7 +37,9 @@ public:
|
|||||||
explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
|
||||||
FileSys::ManualContentProvider* provider_,
|
FileSys::ManualContentProvider* provider_,
|
||||||
QVector<UISettings::GameDir>& game_dirs_,
|
QVector<UISettings::GameDir>& game_dirs_,
|
||||||
const CompatibilityList& compatibility_list_, Core::System& system_);
|
const CompatibilityList& compatibility_list_,
|
||||||
|
const PlayTime::PlayTimeManager& play_time_manager_,
|
||||||
|
Core::System& system_);
|
||||||
~GameListWorker() override;
|
~GameListWorker() override;
|
||||||
|
|
||||||
/// Starts the processing of directory tree information.
|
/// Starts the processing of directory tree information.
|
||||||
@ -76,6 +79,7 @@ private:
|
|||||||
FileSys::ManualContentProvider* provider;
|
FileSys::ManualContentProvider* provider;
|
||||||
QVector<UISettings::GameDir>& game_dirs;
|
QVector<UISettings::GameDir>& game_dirs;
|
||||||
const CompatibilityList& compatibility_list;
|
const CompatibilityList& compatibility_list;
|
||||||
|
const PlayTime::PlayTimeManager& play_time_manager;
|
||||||
|
|
||||||
QStringList watch_list;
|
QStringList watch_list;
|
||||||
std::atomic_bool stop_processing;
|
std::atomic_bool stop_processing;
|
||||||
|
@ -98,6 +98,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
|||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
#include <shlobj.h>
|
||||||
#include "common/windows/timer_resolution.h"
|
#include "common/windows/timer_resolution.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef ARCHITECTURE_x86_64
|
#ifdef ARCHITECTURE_x86_64
|
||||||
@ -150,6 +151,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
|||||||
#include "yuzu/install_dialog.h"
|
#include "yuzu/install_dialog.h"
|
||||||
#include "yuzu/loading_screen.h"
|
#include "yuzu/loading_screen.h"
|
||||||
#include "yuzu/main.h"
|
#include "yuzu/main.h"
|
||||||
|
#include "yuzu/play_time_manager.h"
|
||||||
#include "yuzu/startup_checks.h"
|
#include "yuzu/startup_checks.h"
|
||||||
#include "yuzu/uisettings.h"
|
#include "yuzu/uisettings.h"
|
||||||
#include "yuzu/util/clickable_label.h"
|
#include "yuzu/util/clickable_label.h"
|
||||||
@ -338,6 +340,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
|
|||||||
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
|
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
|
||||||
discord_rpc->Update();
|
discord_rpc->Update();
|
||||||
|
|
||||||
|
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
|
||||||
|
|
||||||
system->GetRoomNetwork().Init();
|
system->GetRoomNetwork().Init();
|
||||||
|
|
||||||
RegisterMetaTypes();
|
RegisterMetaTypes();
|
||||||
@ -986,7 +990,7 @@ void GMainWindow::InitializeWidgets() {
|
|||||||
render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system);
|
render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system);
|
||||||
render_window->hide();
|
render_window->hide();
|
||||||
|
|
||||||
game_list = new GameList(vfs, provider.get(), *system, this);
|
game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this);
|
||||||
ui->horizontalLayout->addWidget(game_list);
|
ui->horizontalLayout->addWidget(game_list);
|
||||||
|
|
||||||
game_list_placeholder = new GameListPlaceholder(this);
|
game_list_placeholder = new GameListPlaceholder(this);
|
||||||
@ -1447,6 +1451,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
|
|||||||
Settings::values.audio_muted = false;
|
Settings::values.audio_muted = false;
|
||||||
auto_muted = false;
|
auto_muted = false;
|
||||||
}
|
}
|
||||||
|
UpdateVolumeUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1460,6 +1465,8 @@ void GMainWindow::ConnectWidgetEvents() {
|
|||||||
connect(game_list, &GameList::RemoveInstalledEntryRequested, this,
|
connect(game_list, &GameList::RemoveInstalledEntryRequested, this,
|
||||||
&GMainWindow::OnGameListRemoveInstalledEntry);
|
&GMainWindow::OnGameListRemoveInstalledEntry);
|
||||||
connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
|
connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
|
||||||
|
connect(game_list, &GameList::RemovePlayTimeRequested, this,
|
||||||
|
&GMainWindow::OnGameListRemovePlayTimeData);
|
||||||
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
|
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
|
||||||
connect(game_list, &GameList::VerifyIntegrityRequested, this,
|
connect(game_list, &GameList::VerifyIntegrityRequested, this,
|
||||||
&GMainWindow::OnGameListVerifyIntegrity);
|
&GMainWindow::OnGameListVerifyIntegrity);
|
||||||
@ -2534,6 +2541,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
|
||||||
|
if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No,
|
||||||
|
QMessageBox::No) != QMessageBox::Yes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
play_time_manager->ResetProgramPlayTime(program_id);
|
||||||
|
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) {
|
void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) {
|
||||||
const auto target_file_name = [target] {
|
const auto target_file_name = [target] {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
@ -2825,7 +2843,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
|||||||
const QStringList args = QApplication::arguments();
|
const QStringList args = QApplication::arguments();
|
||||||
std::filesystem::path yuzu_command = args[0].toStdString();
|
std::filesystem::path yuzu_command = args[0].toStdString();
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__FreeBSD__)
|
|
||||||
// If relative path, make it an absolute path
|
// If relative path, make it an absolute path
|
||||||
if (yuzu_command.c_str()[0] == '.') {
|
if (yuzu_command.c_str()[0] == '.') {
|
||||||
yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
|
yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
|
||||||
@ -2848,12 +2865,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
|||||||
UISettings::values.shortcut_already_warned = true;
|
UISettings::values.shortcut_already_warned = true;
|
||||||
}
|
}
|
||||||
#endif // __linux__
|
#endif // __linux__
|
||||||
#endif // __linux__ || __FreeBSD__
|
|
||||||
|
|
||||||
std::filesystem::path target_directory{};
|
std::filesystem::path target_directory{};
|
||||||
// Determine target directory for shortcut
|
// Determine target directory for shortcut
|
||||||
#if defined(__linux__) || defined(__FreeBSD__)
|
#if defined(WIN32)
|
||||||
|
const char* home = std::getenv("USERPROFILE");
|
||||||
|
#else
|
||||||
const char* home = std::getenv("HOME");
|
const char* home = std::getenv("HOME");
|
||||||
|
#endif
|
||||||
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
|
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
|
||||||
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
|
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
|
||||||
|
|
||||||
@ -2863,7 +2882,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
|||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
this, tr("Create Shortcut"),
|
this, tr("Create Shortcut"),
|
||||||
tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
|
tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
|
||||||
.arg(QString::fromStdString(target_directory)),
|
.arg(QString::fromStdString(target_directory.generic_string())),
|
||||||
QMessageBox::StandardButton::Ok);
|
QMessageBox::StandardButton::Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2871,15 +2890,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
|||||||
target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
|
target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
|
||||||
"applications";
|
"applications";
|
||||||
if (!Common::FS::CreateDirs(target_directory)) {
|
if (!Common::FS::CreateDirs(target_directory)) {
|
||||||
QMessageBox::critical(this, tr("Create Shortcut"),
|
QMessageBox::critical(
|
||||||
tr("Cannot create shortcut in applications menu. Path \"%1\" "
|
this, tr("Create Shortcut"),
|
||||||
"does not exist and cannot be created.")
|
tr("Cannot create shortcut in applications menu. Path \"%1\" "
|
||||||
.arg(QString::fromStdString(target_directory)),
|
"does not exist and cannot be created.")
|
||||||
QMessageBox::StandardButton::Ok);
|
.arg(QString::fromStdString(target_directory.generic_string())),
|
||||||
|
QMessageBox::StandardButton::Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
|
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
|
||||||
// Determine full paths for icon and shortcut
|
// Determine full paths for icon and shortcut
|
||||||
@ -2901,9 +2920,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
|||||||
const std::filesystem::path shortcut_path =
|
const std::filesystem::path shortcut_path =
|
||||||
target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
|
target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
|
||||||
: fmt::format("yuzu-{:016X}.desktop", program_id));
|
: fmt::format("yuzu-{:016X}.desktop", program_id));
|
||||||
|
#elif defined(WIN32)
|
||||||
|
std::filesystem::path icons_path =
|
||||||
|
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
|
||||||
|
std::filesystem::path icon_path =
|
||||||
|
icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
|
||||||
|
: fmt::format("yuzu-{:016X}.ico", program_id)));
|
||||||
#else
|
#else
|
||||||
const std::filesystem::path icon_path{};
|
std::string icon_extension;
|
||||||
const std::filesystem::path shortcut_path{};
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Get title from game file
|
// Get title from game file
|
||||||
@ -2928,29 +2952,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
|||||||
LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
|
LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage icon_jpeg =
|
QImage icon_data =
|
||||||
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
|
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
|
||||||
#if defined(__linux__) || defined(__FreeBSD__)
|
#if defined(__linux__) || defined(__FreeBSD__)
|
||||||
// Convert and write the icon as a PNG
|
// Convert and write the icon as a PNG
|
||||||
if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) {
|
if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
|
||||||
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
|
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
|
||||||
} else {
|
} else {
|
||||||
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
|
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
|
||||||
}
|
}
|
||||||
|
#elif defined(WIN32)
|
||||||
|
if (!SaveIconToFile(icon_path.string(), icon_data)) {
|
||||||
|
LOG_ERROR(Frontend, "Could not write icon to file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
#endif // __linux__
|
#endif // __linux__
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__FreeBSD__)
|
#ifdef _WIN32
|
||||||
|
// Replace characters that are illegal in Windows filenames by a dash
|
||||||
|
const std::string illegal_chars = "<>:\"/\\|?*";
|
||||||
|
for (char c : illegal_chars) {
|
||||||
|
std::replace(title.begin(), title.end(), c, '_');
|
||||||
|
}
|
||||||
|
const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
|
||||||
|
#endif
|
||||||
|
|
||||||
const std::string comment =
|
const std::string comment =
|
||||||
tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
|
tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
|
||||||
const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
|
const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
|
||||||
const std::string categories = "Game;Emulator;Qt;";
|
const std::string categories = "Game;Emulator;Qt;";
|
||||||
const std::string keywords = "Switch;Nintendo;";
|
const std::string keywords = "Switch;Nintendo;";
|
||||||
#else
|
|
||||||
const std::string comment{};
|
|
||||||
const std::string arguments{};
|
|
||||||
const std::string categories{};
|
|
||||||
const std::string keywords{};
|
|
||||||
#endif
|
|
||||||
if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
|
if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
|
||||||
yuzu_command.string(), arguments, categories, keywords)) {
|
yuzu_command.string(), arguments, categories, keywords)) {
|
||||||
QMessageBox::critical(this, tr("Create Shortcut"),
|
QMessageBox::critical(this, tr("Create Shortcut"),
|
||||||
@ -3357,6 +3389,9 @@ void GMainWindow::OnStartGame() {
|
|||||||
UpdateMenuState();
|
UpdateMenuState();
|
||||||
OnTasStateChanged();
|
OnTasStateChanged();
|
||||||
|
|
||||||
|
play_time_manager->SetProgramId(system->GetApplicationProcessProgramID());
|
||||||
|
play_time_manager->Start();
|
||||||
|
|
||||||
discord_rpc->Update();
|
discord_rpc->Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3372,6 +3407,7 @@ void GMainWindow::OnRestartGame() {
|
|||||||
|
|
||||||
void GMainWindow::OnPauseGame() {
|
void GMainWindow::OnPauseGame() {
|
||||||
emu_thread->SetRunning(false);
|
emu_thread->SetRunning(false);
|
||||||
|
play_time_manager->Stop();
|
||||||
UpdateMenuState();
|
UpdateMenuState();
|
||||||
AllowOSSleep();
|
AllowOSSleep();
|
||||||
}
|
}
|
||||||
@ -3392,6 +3428,9 @@ void GMainWindow::OnStopGame() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
play_time_manager->Stop();
|
||||||
|
// Update game list to show new play time
|
||||||
|
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||||
if (OnShutdownBegin()) {
|
if (OnShutdownBegin()) {
|
||||||
OnShutdownBeginDialog();
|
OnShutdownBeginDialog();
|
||||||
} else {
|
} else {
|
||||||
@ -3964,6 +4003,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
|
|||||||
shortcut_stream << shortcut_contents;
|
shortcut_stream << shortcut_contents;
|
||||||
shortcut_stream.close();
|
shortcut_stream.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#elif defined(WIN32)
|
||||||
|
IShellLinkW* shell_link;
|
||||||
|
auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
|
||||||
|
(void**)&shell_link);
|
||||||
|
if (FAILED(hres)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
shell_link->SetPath(
|
||||||
|
Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
|
||||||
|
shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
|
||||||
|
shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
|
||||||
|
shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
|
||||||
|
|
||||||
|
IPersistFile* persist_file;
|
||||||
|
hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
|
||||||
|
if (FAILED(hres)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
|
||||||
|
if (FAILED(hres)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
persist_file->Release();
|
||||||
|
shell_link->Release();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
|
@ -81,6 +81,10 @@ namespace DiscordRPC {
|
|||||||
class DiscordInterface;
|
class DiscordInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace PlayTime {
|
||||||
|
class PlayTimeManager;
|
||||||
|
}
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
class ContentProvider;
|
class ContentProvider;
|
||||||
class ManualContentProvider;
|
class ManualContentProvider;
|
||||||
@ -323,6 +327,7 @@ private slots:
|
|||||||
void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
|
void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
|
||||||
void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
|
void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
|
||||||
const std::string& game_path);
|
const std::string& game_path);
|
||||||
|
void OnGameListRemovePlayTimeData(u64 program_id);
|
||||||
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
|
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
|
||||||
void OnGameListVerifyIntegrity(const std::string& game_path);
|
void OnGameListVerifyIntegrity(const std::string& game_path);
|
||||||
void OnGameListCopyTID(u64 program_id);
|
void OnGameListCopyTID(u64 program_id);
|
||||||
@ -389,6 +394,7 @@ private:
|
|||||||
void RemoveVulkanDriverPipelineCache(u64 program_id);
|
void RemoveVulkanDriverPipelineCache(u64 program_id);
|
||||||
void RemoveAllTransferableShaderCaches(u64 program_id);
|
void RemoveAllTransferableShaderCaches(u64 program_id);
|
||||||
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
|
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
|
||||||
|
void RemovePlayTimeData(u64 program_id);
|
||||||
void RemoveCacheStorage(u64 program_id);
|
void RemoveCacheStorage(u64 program_id);
|
||||||
bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
|
bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
|
||||||
u64* selected_title_id, u8* selected_content_record_type);
|
u64* selected_title_id, u8* selected_content_record_type);
|
||||||
@ -428,6 +434,7 @@ private:
|
|||||||
|
|
||||||
std::unique_ptr<Core::System> system;
|
std::unique_ptr<Core::System> system;
|
||||||
std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
|
std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
|
||||||
|
std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
|
||||||
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
|
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
|
||||||
|
|
||||||
MultiplayerState* multiplayer_state = nullptr;
|
MultiplayerState* multiplayer_state = nullptr;
|
||||||
|
179
src/yuzu/play_time_manager.cpp
Normal file
179
src/yuzu/play_time_manager.cpp
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/alignment.h"
|
||||||
|
#include "common/fs/file.h"
|
||||||
|
#include "common/fs/fs.h"
|
||||||
|
#include "common/fs/path_util.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "common/thread.h"
|
||||||
|
#include "core/hle/service/acc/profile_manager.h"
|
||||||
|
#include "yuzu/play_time_manager.h"
|
||||||
|
|
||||||
|
namespace PlayTime {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct PlayTimeElement {
|
||||||
|
ProgramId program_id;
|
||||||
|
PlayTime play_time;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() {
|
||||||
|
const Service::Account::ProfileManager manager;
|
||||||
|
const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user));
|
||||||
|
if (!uuid.has_value()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) /
|
||||||
|
uuid->RawString().append(".bin");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) {
|
||||||
|
const auto filename = GetCurrentUserPlayTimePath();
|
||||||
|
|
||||||
|
if (!filename.has_value()) {
|
||||||
|
LOG_ERROR(Frontend, "Failed to get current user path");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_play_time_db.clear();
|
||||||
|
|
||||||
|
if (Common::FS::Exists(filename.value())) {
|
||||||
|
Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read,
|
||||||
|
Common::FS::FileType::BinaryFile};
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_ERROR(Frontend, "Failed to open play time file: {}",
|
||||||
|
Common::FS::PathToUTF8String(filename.value()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement);
|
||||||
|
std::vector<PlayTimeElement> elements(num_elements);
|
||||||
|
|
||||||
|
if (file.ReadSpan<PlayTimeElement>(elements) != num_elements) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [program_id, play_time] : elements) {
|
||||||
|
if (program_id != 0) {
|
||||||
|
out_play_time_db[program_id] = play_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) {
|
||||||
|
const auto filename = GetCurrentUserPlayTimePath();
|
||||||
|
|
||||||
|
if (!filename.has_value()) {
|
||||||
|
LOG_ERROR(Frontend, "Failed to get current user path");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write,
|
||||||
|
Common::FS::FileType::BinaryFile};
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_ERROR(Frontend, "Failed to open play time file: {}",
|
||||||
|
Common::FS::PathToUTF8String(filename.value()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PlayTimeElement> elements;
|
||||||
|
elements.reserve(play_time_db.size());
|
||||||
|
|
||||||
|
for (auto& [program_id, play_time] : play_time_db) {
|
||||||
|
if (program_id != 0) {
|
||||||
|
elements.push_back(PlayTimeElement{program_id, play_time});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.WriteSpan<PlayTimeElement>(elements) == elements.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
PlayTimeManager::PlayTimeManager() {
|
||||||
|
if (!ReadPlayTimeFile(database)) {
|
||||||
|
LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayTimeManager::~PlayTimeManager() {
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayTimeManager::SetProgramId(u64 program_id) {
|
||||||
|
running_program_id = program_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayTimeManager::Start() {
|
||||||
|
play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayTimeManager::Stop() {
|
||||||
|
play_time_thread = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) {
|
||||||
|
Common::SetCurrentThreadName("PlayTimeReport");
|
||||||
|
|
||||||
|
using namespace std::literals::chrono_literals;
|
||||||
|
using std::chrono::seconds;
|
||||||
|
using std::chrono::steady_clock;
|
||||||
|
|
||||||
|
auto timestamp = steady_clock::now();
|
||||||
|
|
||||||
|
const auto GetDuration = [&]() -> u64 {
|
||||||
|
const auto last_timestamp = std::exchange(timestamp, steady_clock::now());
|
||||||
|
const auto duration = std::chrono::duration_cast<seconds>(timestamp - last_timestamp);
|
||||||
|
return static_cast<u64>(duration.count());
|
||||||
|
};
|
||||||
|
|
||||||
|
while (!stop_token.stop_requested()) {
|
||||||
|
Common::StoppableTimedWait(stop_token, 30s);
|
||||||
|
|
||||||
|
database[running_program_id] += GetDuration();
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayTimeManager::Save() {
|
||||||
|
if (!WritePlayTimeFile(database)) {
|
||||||
|
LOG_ERROR(Frontend, "Failed to update play time database!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 PlayTimeManager::GetPlayTime(u64 program_id) const {
|
||||||
|
auto it = database.find(program_id);
|
||||||
|
if (it != database.end()) {
|
||||||
|
return it->second;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
|
||||||
|
database.erase(program_id);
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ReadablePlayTime(qulonglong time_seconds) {
|
||||||
|
if (time_seconds == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
|
||||||
|
const auto time_hours = static_cast<double>(time_seconds) / 3600;
|
||||||
|
const bool is_minutes = time_minutes < 60;
|
||||||
|
const char* unit = is_minutes ? "m" : "h";
|
||||||
|
const auto value = is_minutes ? time_minutes : time_hours;
|
||||||
|
|
||||||
|
return QStringLiteral("%L1 %2")
|
||||||
|
.arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
|
||||||
|
.arg(QString::fromUtf8(unit));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace PlayTime
|
44
src/yuzu/play_time_manager.h
Normal file
44
src/yuzu/play_time_manager.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/polyfill_thread.h"
|
||||||
|
|
||||||
|
namespace PlayTime {
|
||||||
|
|
||||||
|
using ProgramId = u64;
|
||||||
|
using PlayTime = u64;
|
||||||
|
using PlayTimeDatabase = std::map<ProgramId, PlayTime>;
|
||||||
|
|
||||||
|
class PlayTimeManager {
|
||||||
|
public:
|
||||||
|
explicit PlayTimeManager();
|
||||||
|
~PlayTimeManager();
|
||||||
|
|
||||||
|
YUZU_NON_COPYABLE(PlayTimeManager);
|
||||||
|
YUZU_NON_MOVEABLE(PlayTimeManager);
|
||||||
|
|
||||||
|
u64 GetPlayTime(u64 program_id) const;
|
||||||
|
void ResetProgramPlayTime(u64 program_id);
|
||||||
|
void SetProgramId(u64 program_id);
|
||||||
|
void Start();
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
PlayTimeDatabase database;
|
||||||
|
u64 running_program_id;
|
||||||
|
std::jthread play_time_thread;
|
||||||
|
void AutoTimestamp(std::stop_token stop_token);
|
||||||
|
void Save();
|
||||||
|
};
|
||||||
|
|
||||||
|
QString ReadablePlayTime(qulonglong time_seconds);
|
||||||
|
|
||||||
|
} // namespace PlayTime
|
@ -103,7 +103,7 @@ struct Values {
|
|||||||
true,
|
true,
|
||||||
true};
|
true};
|
||||||
Setting<bool> mute_when_in_background{
|
Setting<bool> mute_when_in_background{
|
||||||
linkage, false, "muteWhenInBackground", Category::Ui, Settings::Specialization::Default,
|
linkage, false, "muteWhenInBackground", Category::Audio, Settings::Specialization::Default,
|
||||||
true, true};
|
true, true};
|
||||||
Setting<bool> hide_mouse{
|
Setting<bool> hide_mouse{
|
||||||
linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
|
linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
|
||||||
@ -183,6 +183,9 @@ struct Values {
|
|||||||
Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
|
Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
|
||||||
Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
|
Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
|
||||||
|
|
||||||
|
// Play time
|
||||||
|
Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList};
|
||||||
|
|
||||||
bool configuration_applied;
|
bool configuration_applied;
|
||||||
bool reset_to_defaults;
|
bool reset_to_defaults;
|
||||||
bool shortcut_already_warned{false};
|
bool shortcut_already_warned{false};
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include "yuzu/util/util.h"
|
#include "yuzu/util/util.h"
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include "common/fs/file.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
QFont GetMonospaceFont() {
|
QFont GetMonospaceFont() {
|
||||||
QFont font(QStringLiteral("monospace"));
|
QFont font(QStringLiteral("monospace"));
|
||||||
@ -37,3 +41,76 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
|
|||||||
painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
|
painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
|
||||||
return circle_pixmap;
|
return circle_pixmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SaveIconToFile(const std::string_view path, const QImage& image) {
|
||||||
|
#if defined(WIN32)
|
||||||
|
#pragma pack(push, 2)
|
||||||
|
struct IconDir {
|
||||||
|
WORD id_reserved;
|
||||||
|
WORD id_type;
|
||||||
|
WORD id_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IconDirEntry {
|
||||||
|
BYTE width;
|
||||||
|
BYTE height;
|
||||||
|
BYTE color_count;
|
||||||
|
BYTE reserved;
|
||||||
|
WORD planes;
|
||||||
|
WORD bit_count;
|
||||||
|
DWORD bytes_in_res;
|
||||||
|
DWORD image_offset;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
QImage source_image = image.convertToFormat(QImage::Format_RGB32);
|
||||||
|
constexpr int bytes_per_pixel = 4;
|
||||||
|
const int image_size = source_image.width() * source_image.height() * bytes_per_pixel;
|
||||||
|
|
||||||
|
BITMAPINFOHEADER info_header{};
|
||||||
|
info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(),
|
||||||
|
info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1,
|
||||||
|
info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB;
|
||||||
|
|
||||||
|
const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1};
|
||||||
|
const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()),
|
||||||
|
.height = static_cast<BYTE>(source_image.height() * 2),
|
||||||
|
.color_count = 0,
|
||||||
|
.reserved = 0,
|
||||||
|
.planes = 1,
|
||||||
|
.bit_count = bytes_per_pixel * 8,
|
||||||
|
.bytes_in_res =
|
||||||
|
static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
|
||||||
|
.image_offset = sizeof(IconDir) + sizeof(IconDirEntry)};
|
||||||
|
|
||||||
|
Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
|
||||||
|
Common::FS::FileType::BinaryFile);
|
||||||
|
if (!icon_file.IsOpen()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!icon_file.Write(icon_dir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!icon_file.Write(icon_entry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!icon_file.Write(info_header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < image.height(); y++) {
|
||||||
|
const auto* line = source_image.scanLine(source_image.height() - 1 - y);
|
||||||
|
std::vector<u8> line_data(source_image.width() * bytes_per_pixel);
|
||||||
|
std::memcpy(line_data.data(), line, line_data.size());
|
||||||
|
if (!icon_file.Write(line_data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
icon_file.Close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
@ -7,14 +7,22 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
|
/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
|
||||||
QFont GetMonospaceFont();
|
[[nodiscard]] QFont GetMonospaceFont();
|
||||||
|
|
||||||
/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
|
/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
|
||||||
QString ReadableByteSize(qulonglong size);
|
[[nodiscard]] QString ReadableByteSize(qulonglong size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a circle pixmap from a specified color
|
* Creates a circle pixmap from a specified color
|
||||||
* @param color The color the pixmap shall have
|
* @param color The color the pixmap shall have
|
||||||
* @return QPixmap circle pixmap
|
* @return QPixmap circle pixmap
|
||||||
*/
|
*/
|
||||||
QPixmap CreateCirclePixmapFromColor(const QColor& color);
|
[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a windows icon to a file
|
||||||
|
* @param path The icons path
|
||||||
|
* @param image The image to save
|
||||||
|
* @return bool If the operation succeeded
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image);
|
||||||
|
Reference in New Issue
Block a user