Compare commits
13 Commits
mainline-0
...
mainline-0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46034a46ed | ||
|
|
7015abb4d4 | ||
|
|
7c2e163a3c | ||
|
|
8901869cd9 | ||
|
|
959c62c91d | ||
|
|
96ebdfebf4 | ||
|
|
2d8b1f4aa6 | ||
|
|
509a01c9d2 | ||
|
|
bc435a0fd6 | ||
|
|
ff69debc67 | ||
|
|
004086aea8 | ||
|
|
fdecfc2002 | ||
|
|
59b00970e6 |
@@ -85,10 +85,12 @@ set(HASH_FILES
|
||||
"${VIDEO_CORE}/shader/decode/xmad.cpp"
|
||||
"${VIDEO_CORE}/shader/ast.cpp"
|
||||
"${VIDEO_CORE}/shader/ast.h"
|
||||
"${VIDEO_CORE}/shader/control_flow.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.h"
|
||||
"${VIDEO_CORE}/shader/compiler_settings.cpp"
|
||||
"${VIDEO_CORE}/shader/compiler_settings.h"
|
||||
"${VIDEO_CORE}/shader/const_buffer_locker.cpp"
|
||||
"${VIDEO_CORE}/shader/const_buffer_locker.h"
|
||||
"${VIDEO_CORE}/shader/control_flow.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.h"
|
||||
"${VIDEO_CORE}/shader/decode.cpp"
|
||||
"${VIDEO_CORE}/shader/expr.cpp"
|
||||
"${VIDEO_CORE}/shader/expr.h"
|
||||
|
||||
@@ -74,10 +74,12 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
"${VIDEO_CORE}/shader/decode/xmad.cpp"
|
||||
"${VIDEO_CORE}/shader/ast.cpp"
|
||||
"${VIDEO_CORE}/shader/ast.h"
|
||||
"${VIDEO_CORE}/shader/control_flow.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.h"
|
||||
"${VIDEO_CORE}/shader/compiler_settings.cpp"
|
||||
"${VIDEO_CORE}/shader/compiler_settings.h"
|
||||
"${VIDEO_CORE}/shader/const_buffer_locker.cpp"
|
||||
"${VIDEO_CORE}/shader/const_buffer_locker.h"
|
||||
"${VIDEO_CORE}/shader/control_flow.cpp"
|
||||
"${VIDEO_CORE}/shader/control_flow.h"
|
||||
"${VIDEO_CORE}/shader/decode.cpp"
|
||||
"${VIDEO_CORE}/shader/expr.cpp"
|
||||
"${VIDEO_CORE}/shader/expr.h"
|
||||
|
||||
@@ -28,18 +28,14 @@ __declspec(noinline, noreturn)
|
||||
}
|
||||
|
||||
#define ASSERT(_a_) \
|
||||
do \
|
||||
if (!(_a_)) { \
|
||||
assert_noinline_call([] { LOG_CRITICAL(Debug, "Assertion Failed!"); }); \
|
||||
} \
|
||||
while (0)
|
||||
if (!(_a_)) { \
|
||||
LOG_CRITICAL(Debug, "Assertion Failed!"); \
|
||||
}
|
||||
|
||||
#define ASSERT_MSG(_a_, ...) \
|
||||
do \
|
||||
if (!(_a_)) { \
|
||||
assert_noinline_call([&] { LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); }); \
|
||||
} \
|
||||
while (0)
|
||||
if (!(_a_)) { \
|
||||
LOG_CRITICAL(Debug, "Assertion Failed! " __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define UNREACHABLE() ASSERT_MSG(false, "Unreachable code!")
|
||||
#define UNREACHABLE_MSG(...) ASSERT_MSG(false, __VA_ARGS__)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include "common/cityhash.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -68,4 +70,13 @@ struct HashableStruct {
|
||||
}
|
||||
};
|
||||
|
||||
struct PairHash {
|
||||
template <class T1, class T2>
|
||||
std::size_t operator()(const std::pair<T1, T2>& pair) const {
|
||||
std::size_t seed = std::hash<T1>()(pair.first);
|
||||
boost::hash_combine(seed, std::hash<T2>()(pair.second));
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -304,6 +304,13 @@ public:
|
||||
return levels[priority == Depth ? 63 : priority].back();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
used_priorities = 0;
|
||||
for (std::size_t i = 0; i < Depth; i++) {
|
||||
levels[i].clear();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
using const_list_iterator = typename std::list<T>::const_iterator;
|
||||
|
||||
|
||||
@@ -108,6 +108,8 @@ add_library(core STATIC
|
||||
file_sys/vfs_offset.h
|
||||
file_sys/vfs_real.cpp
|
||||
file_sys/vfs_real.h
|
||||
file_sys/vfs_ro_layer.cpp
|
||||
file_sys/vfs_ro_layer.h
|
||||
file_sys/vfs_static.h
|
||||
file_sys/vfs_types.h
|
||||
file_sys/vfs_vector.cpp
|
||||
|
||||
@@ -256,6 +256,8 @@ struct System::Impl {
|
||||
is_powered_on = false;
|
||||
exit_lock = false;
|
||||
|
||||
gpu_core->WaitIdle();
|
||||
|
||||
// Shutdown emulation session
|
||||
renderer.reset();
|
||||
GDBStub::Shutdown();
|
||||
@@ -404,6 +406,12 @@ void System::PrepareReschedule() {
|
||||
CurrentCpuCore().PrepareReschedule();
|
||||
}
|
||||
|
||||
void System::PrepareReschedule(const u32 core_index) {
|
||||
if (core_index < GlobalScheduler().CpuCoresCount()) {
|
||||
CpuCore(core_index).PrepareReschedule();
|
||||
}
|
||||
}
|
||||
|
||||
PerfStatsResults System::GetAndResetPerfStats() {
|
||||
return impl->GetAndResetPerfStats();
|
||||
}
|
||||
@@ -444,6 +452,16 @@ const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
|
||||
return CpuCore(core_index).Scheduler();
|
||||
}
|
||||
|
||||
/// Gets the global scheduler
|
||||
Kernel::GlobalScheduler& System::GlobalScheduler() {
|
||||
return impl->kernel.GlobalScheduler();
|
||||
}
|
||||
|
||||
/// Gets the global scheduler
|
||||
const Kernel::GlobalScheduler& System::GlobalScheduler() const {
|
||||
return impl->kernel.GlobalScheduler();
|
||||
}
|
||||
|
||||
Kernel::Process* System::CurrentProcess() {
|
||||
return impl->kernel.CurrentProcess();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
namespace Kernel {
|
||||
class GlobalScheduler;
|
||||
class KernelCore;
|
||||
class Process;
|
||||
class Scheduler;
|
||||
@@ -184,6 +185,9 @@ public:
|
||||
/// Prepare the core emulation for a reschedule
|
||||
void PrepareReschedule();
|
||||
|
||||
/// Prepare the core emulation for a reschedule
|
||||
void PrepareReschedule(u32 core_index);
|
||||
|
||||
/// Gets and resets core performance statistics
|
||||
PerfStatsResults GetAndResetPerfStats();
|
||||
|
||||
@@ -238,6 +242,12 @@ public:
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
const Kernel::Scheduler& Scheduler(std::size_t core_index) const;
|
||||
|
||||
/// Gets the global scheduler
|
||||
Kernel::GlobalScheduler& GlobalScheduler();
|
||||
|
||||
/// Gets the global scheduler
|
||||
const Kernel::GlobalScheduler& GlobalScheduler() const;
|
||||
|
||||
/// Provides a pointer to the current process
|
||||
Kernel::Process* CurrentProcess();
|
||||
|
||||
|
||||
@@ -52,7 +52,8 @@ bool CpuBarrier::Rendezvous() {
|
||||
|
||||
Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
|
||||
std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_timing{system.CoreTiming()}, core_index{core_index} {
|
||||
: cpu_barrier{cpu_barrier}, global_scheduler{system.GlobalScheduler()},
|
||||
core_timing{system.CoreTiming()}, core_index{core_index} {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index);
|
||||
#else
|
||||
@@ -60,7 +61,7 @@ Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_ba
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface);
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface, core_index);
|
||||
}
|
||||
|
||||
Cpu::~Cpu() = default;
|
||||
@@ -81,21 +82,21 @@ void Cpu::RunLoop(bool tight_loop) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reschedule();
|
||||
|
||||
// If we don't have a currently active thread then don't execute instructions,
|
||||
// instead advance to the next event and try to yield to the next thread
|
||||
if (Kernel::GetCurrentThread() == nullptr) {
|
||||
LOG_TRACE(Core, "Core-{} idling", core_index);
|
||||
core_timing.Idle();
|
||||
core_timing.Advance();
|
||||
PrepareReschedule();
|
||||
} else {
|
||||
if (tight_loop) {
|
||||
arm_interface->Run();
|
||||
} else {
|
||||
arm_interface->Step();
|
||||
}
|
||||
core_timing.Advance();
|
||||
}
|
||||
core_timing.Advance();
|
||||
|
||||
Reschedule();
|
||||
}
|
||||
@@ -106,18 +107,18 @@ void Cpu::SingleStep() {
|
||||
|
||||
void Cpu::PrepareReschedule() {
|
||||
arm_interface->PrepareReschedule();
|
||||
reschedule_pending = true;
|
||||
}
|
||||
|
||||
void Cpu::Reschedule() {
|
||||
if (!reschedule_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
reschedule_pending = false;
|
||||
// Lock the global kernel mutex when we manipulate the HLE state
|
||||
std::lock_guard lock{HLE::g_hle_lock};
|
||||
scheduler->Reschedule();
|
||||
std::lock_guard lock(HLE::g_hle_lock);
|
||||
|
||||
global_scheduler.SelectThread(core_index);
|
||||
scheduler->TryDoContextSwitch();
|
||||
}
|
||||
|
||||
void Cpu::Shutdown() {
|
||||
scheduler->Shutdown();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
class GlobalScheduler;
|
||||
class Scheduler;
|
||||
}
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@@ -83,6 +84,8 @@ public:
|
||||
return core_index;
|
||||
}
|
||||
|
||||
void Shutdown();
|
||||
|
||||
static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores);
|
||||
|
||||
private:
|
||||
@@ -90,6 +93,7 @@ private:
|
||||
|
||||
std::unique_ptr<ARM_Interface> arm_interface;
|
||||
CpuBarrier& cpu_barrier;
|
||||
Kernel::GlobalScheduler& global_scheduler;
|
||||
std::unique_ptr<Kernel::Scheduler> scheduler;
|
||||
Timing::CoreTiming& core_timing;
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ void CpuCoreManager::Shutdown() {
|
||||
|
||||
thread_to_cpu.clear();
|
||||
for (auto& cpu_core : cores) {
|
||||
cpu_core->Shutdown();
|
||||
cpu_core.reset();
|
||||
}
|
||||
|
||||
|
||||
181
src/core/file_sys/vfs_ro_layer.cpp
Normal file
181
src/core/file_sys/vfs_ro_layer.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/file_sys/vfs_ro_layer.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
ReadOnlyVfsFileLayer::ReadOnlyVfsFileLayer(VirtualFile base) : base(std::move(base)) {}
|
||||
|
||||
ReadOnlyVfsFileLayer::~ReadOnlyVfsFileLayer() = default;
|
||||
|
||||
std::string ReadOnlyVfsFileLayer::GetName() const {
|
||||
return base->GetName();
|
||||
}
|
||||
|
||||
std::size_t ReadOnlyVfsFileLayer::GetSize() const {
|
||||
return base->GetSize();
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsFileLayer::Resize(std::size_t new_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsFileLayer::GetContainingDirectory() const {
|
||||
// Make containing read-only to prevent escaping the layer by getting containing and then
|
||||
// getting this file again.
|
||||
return std::make_shared<ReadOnlyVfsDirectoryLayer>(base->GetContainingDirectory());
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsFileLayer::IsWritable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsFileLayer::IsReadable() const {
|
||||
return base->IsReadable();
|
||||
}
|
||||
|
||||
std::size_t ReadOnlyVfsFileLayer::Read(u8* data, std::size_t length, std::size_t offset) const {
|
||||
return base->Read(data, length, offset);
|
||||
}
|
||||
|
||||
std::size_t ReadOnlyVfsFileLayer::Write(const u8* data, std::size_t length, std::size_t offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsFileLayer::Rename(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ReadOnlyVfsFileLayer::GetFullPath() const {
|
||||
return base->GetFullPath();
|
||||
}
|
||||
|
||||
ReadOnlyVfsDirectoryLayer::ReadOnlyVfsDirectoryLayer(VirtualDir base) : base(std::move(base)) {}
|
||||
|
||||
ReadOnlyVfsDirectoryLayer::~ReadOnlyVfsDirectoryLayer() = default;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> ReadOnlyVfsDirectoryLayer::GetFiles() const {
|
||||
std::vector<VirtualFile> out;
|
||||
const auto in = base->GetFiles();
|
||||
std::transform(in.begin(), in.end(), std::back_inserter(out),
|
||||
[](const VirtualFile& i) { return std::make_shared<ReadOnlyVfsFileLayer>(i); });
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> ReadOnlyVfsDirectoryLayer::GetSubdirectories() const {
|
||||
std::vector<VirtualDir> out;
|
||||
const auto in = base->GetSubdirectories();
|
||||
std::transform(in.begin(), in.end(), std::back_inserter(out), [](const VirtualDir& i) {
|
||||
return std::make_shared<ReadOnlyVfsDirectoryLayer>(i);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string ReadOnlyVfsDirectoryLayer::GetName() const {
|
||||
return base->GetName();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectoryLayer::GetParentDirectory() const {
|
||||
return std::make_shared<ReadOnlyVfsDirectoryLayer>(base->GetParentDirectory());
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> ReadOnlyVfsDirectoryLayer::GetFileRelative(std::string_view path) const {
|
||||
return std::make_shared<ReadOnlyVfsFileLayer>(base->GetFileRelative(path));
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> ReadOnlyVfsDirectoryLayer::GetFileAbsolute(std::string_view path) const {
|
||||
return std::make_shared<ReadOnlyVfsFileLayer>(base->GetFileAbsolute(path));
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectoryLayer::GetDirectoryRelative(
|
||||
std::string_view path) const {
|
||||
return std::make_shared<ReadOnlyVfsDirectoryLayer>(base->GetDirectoryRelative(path));
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectoryLayer::GetDirectoryAbsolute(
|
||||
std::string_view path) const {
|
||||
return std::make_shared<ReadOnlyVfsDirectoryLayer>(base->GetDirectoryAbsolute(path));
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> ReadOnlyVfsDirectoryLayer::GetFile(std::string_view name) const {
|
||||
return std::make_shared<ReadOnlyVfsFileLayer>(base->GetFile(name));
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectoryLayer::GetSubdirectory(
|
||||
std::string_view name) const {
|
||||
return std::make_shared<ReadOnlyVfsDirectoryLayer>(base->GetSubdirectory(name));
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectoryLayer::IsRoot() const {
|
||||
return base->IsRoot();
|
||||
}
|
||||
|
||||
std::size_t ReadOnlyVfsDirectoryLayer::GetSize() const {
|
||||
return base->GetSize();
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectoryLayer::Copy(std::string_view src, std::string_view dest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ReadOnlyVfsDirectoryLayer::GetFullPath() const {
|
||||
return base->GetFullPath();
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectoryLayer::IsWritable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectoryLayer::IsReadable() const {
|
||||
return base->IsReadable();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectoryLayer::CreateSubdirectory(std::string_view name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> ReadOnlyVfsDirectoryLayer::CreateFile(std::string_view name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> ReadOnlyVfsDirectoryLayer::CreateFileAbsolute(std::string_view path) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> ReadOnlyVfsDirectoryLayer::CreateFileRelative(std::string_view path) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectoryLayer::CreateDirectoryAbsolute(
|
||||
std::string_view path) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectoryLayer::CreateDirectoryRelative(
|
||||
std::string_view path) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectoryLayer::DeleteSubdirectory(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectoryLayer::DeleteSubdirectoryRecursive(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectoryLayer::CleanSubdirectoryRecursive(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectoryLayer::DeleteFile(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectoryLayer::Rename(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
73
src/core/file_sys/vfs_ro_layer.h
Normal file
73
src/core/file_sys/vfs_ro_layer.h
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// Class that wraps a VfsFile making it read-only
|
||||
class ReadOnlyVfsFileLayer : public VfsFile {
|
||||
public:
|
||||
explicit ReadOnlyVfsFileLayer(VirtualFile base);
|
||||
~ReadOnlyVfsFileLayer() override;
|
||||
|
||||
std::string GetName() const override;
|
||||
std::size_t GetSize() const override;
|
||||
bool Resize(std::size_t new_size) override;
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
|
||||
std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
std::string GetFullPath() const override;
|
||||
|
||||
private:
|
||||
VirtualFile base;
|
||||
};
|
||||
|
||||
// Class that wraps a VfsDirectory making it and its children read only.
|
||||
class ReadOnlyVfsDirectoryLayer : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit ReadOnlyVfsDirectoryLayer(VirtualDir base);
|
||||
~ReadOnlyVfsDirectoryLayer() override;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
std::string GetName() const override;
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
|
||||
std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
|
||||
std::shared_ptr<VfsFile> GetFileAbsolute(std::string_view path) const override;
|
||||
std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
|
||||
std::shared_ptr<VfsDirectory> GetDirectoryAbsolute(std::string_view path) const override;
|
||||
std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
|
||||
std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
|
||||
bool IsRoot() const override;
|
||||
std::size_t GetSize() const override;
|
||||
bool Copy(std::string_view src, std::string_view dest) override;
|
||||
std::string GetFullPath() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
|
||||
std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
|
||||
std::shared_ptr<VfsFile> CreateFileAbsolute(std::string_view path) override;
|
||||
std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override;
|
||||
std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path) override;
|
||||
std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override;
|
||||
bool DeleteSubdirectory(std::string_view name) override;
|
||||
bool DeleteSubdirectoryRecursive(std::string_view name) override;
|
||||
bool CleanSubdirectoryRecursive(std::string_view name) override;
|
||||
bool DeleteFile(std::string_view name) override;
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
private:
|
||||
VirtualDir base;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -202,13 +202,11 @@ void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) {
|
||||
}
|
||||
|
||||
static Kernel::Thread* FindThreadById(s64 id) {
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
|
||||
for (auto& thread : threads) {
|
||||
if (thread->GetThreadID() == static_cast<u64>(id)) {
|
||||
current_core = core;
|
||||
return thread.get();
|
||||
}
|
||||
const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList();
|
||||
for (auto& thread : threads) {
|
||||
if (thread->GetThreadID() == static_cast<u64>(id)) {
|
||||
current_core = thread->GetProcessorID();
|
||||
return thread.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
@@ -647,11 +645,9 @@ static void HandleQuery() {
|
||||
SendReply(buffer.c_str());
|
||||
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
|
||||
std::string val = "m";
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
val += fmt::format("{:x},", thread->GetThreadID());
|
||||
}
|
||||
const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
val += fmt::format("{:x},", thread->GetThreadID());
|
||||
}
|
||||
val.pop_back();
|
||||
SendReply(val.c_str());
|
||||
@@ -661,13 +657,11 @@ static void HandleQuery() {
|
||||
std::string buffer;
|
||||
buffer += "l<?xml version=\"1.0\"?>";
|
||||
buffer += "<threads>";
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
buffer +=
|
||||
fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
|
||||
thread->GetThreadID(), core, thread->GetThreadID());
|
||||
}
|
||||
const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
buffer +=
|
||||
fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
|
||||
thread->GetThreadID(), thread->GetProcessorID(), thread->GetThreadID());
|
||||
}
|
||||
buffer += "</threads>";
|
||||
SendReply(buffer.c_str());
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Kernel {
|
||||
namespace {
|
||||
// Wake up num_to_wake (or all) threads in a vector.
|
||||
void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_to_wake) {
|
||||
auto& system = Core::System::GetInstance();
|
||||
// Only process up to 'target' threads, unless 'target' is <= 0, in which case process
|
||||
// them all.
|
||||
std::size_t last = waiting_threads.size();
|
||||
@@ -35,6 +36,7 @@ void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_
|
||||
waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
waiting_threads[i]->SetArbiterWaitAddress(0);
|
||||
waiting_threads[i]->ResumeFromWait();
|
||||
system.PrepareReschedule(waiting_threads[i]->GetProcessorID());
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
@@ -89,12 +91,20 @@ ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr a
|
||||
|
||||
// Determine the modified value depending on the waiting count.
|
||||
s32 updated_value;
|
||||
if (waiting_threads.empty()) {
|
||||
updated_value = value + 1;
|
||||
} else if (num_to_wake <= 0 || waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
|
||||
updated_value = value - 1;
|
||||
if (num_to_wake <= 0) {
|
||||
if (waiting_threads.empty()) {
|
||||
updated_value = value + 1;
|
||||
} else {
|
||||
updated_value = value - 1;
|
||||
}
|
||||
} else {
|
||||
updated_value = value;
|
||||
if (waiting_threads.empty()) {
|
||||
updated_value = value + 1;
|
||||
} else if (waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
|
||||
updated_value = value - 1;
|
||||
} else {
|
||||
updated_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<s32>(Memory::Read32(address)) != value) {
|
||||
@@ -169,30 +179,22 @@ ResultCode AddressArbiter::WaitForAddressImpl(VAddr address, s64 timeout) {
|
||||
|
||||
current_thread->WakeAfterDelay(timeout);
|
||||
|
||||
system.CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
|
||||
system.PrepareReschedule(current_thread->GetProcessorID());
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
std::vector<SharedPtr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress(VAddr address) const {
|
||||
const auto RetrieveWaitingThreads = [this](std::size_t core_index,
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr arb_addr) {
|
||||
const auto& scheduler = system.Scheduler(core_index);
|
||||
const auto& thread_list = scheduler.GetThreadList();
|
||||
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetArbiterWaitAddress() == arb_addr) {
|
||||
waiting_threads.push_back(thread);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieve all threads that are waiting for this address.
|
||||
std::vector<SharedPtr<Thread>> threads;
|
||||
RetrieveWaitingThreads(0, threads, address);
|
||||
RetrieveWaitingThreads(1, threads, address);
|
||||
RetrieveWaitingThreads(2, threads, address);
|
||||
RetrieveWaitingThreads(3, threads, address);
|
||||
const auto& scheduler = system.GlobalScheduler();
|
||||
const auto& thread_list = scheduler.GetThreadList();
|
||||
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetArbiterWaitAddress() == address) {
|
||||
threads.push_back(thread);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort them by priority, such that the highest priority ones come first.
|
||||
std::sort(threads.begin(), threads.end(),
|
||||
|
||||
@@ -12,12 +12,15 @@
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/hle/result.h"
|
||||
@@ -58,12 +61,8 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_
|
||||
if (thread->HasWakeupCallback()) {
|
||||
resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (thread->GetMutexWaitAddress() != 0 || thread->GetCondVarWaitAddress() != 0 ||
|
||||
thread->GetWaitHandle() != 0) {
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex ||
|
||||
thread->GetStatus() == ThreadStatus::WaitCondVar);
|
||||
} else if (thread->GetStatus() == ThreadStatus::WaitMutex ||
|
||||
thread->GetStatus() == ThreadStatus::WaitCondVar) {
|
||||
thread->SetMutexWaitAddress(0);
|
||||
thread->SetCondVarWaitAddress(0);
|
||||
thread->SetWaitHandle(0);
|
||||
@@ -83,18 +82,23 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_
|
||||
}
|
||||
|
||||
if (resume) {
|
||||
if (thread->GetStatus() == ThreadStatus::WaitCondVar ||
|
||||
thread->GetStatus() == ThreadStatus::WaitArb) {
|
||||
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
|
||||
}
|
||||
thread->ResumeFromWait();
|
||||
}
|
||||
}
|
||||
|
||||
struct KernelCore::Impl {
|
||||
explicit Impl(Core::System& system) : system{system} {}
|
||||
explicit Impl(Core::System& system) : system{system}, global_scheduler{system} {}
|
||||
|
||||
void Initialize(KernelCore& kernel) {
|
||||
Shutdown();
|
||||
|
||||
InitializeSystemResourceLimit(kernel);
|
||||
InitializeThreads();
|
||||
InitializePreemption();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
@@ -110,6 +114,9 @@ struct KernelCore::Impl {
|
||||
|
||||
thread_wakeup_callback_handle_table.Clear();
|
||||
thread_wakeup_event_type = nullptr;
|
||||
preemption_event = nullptr;
|
||||
|
||||
global_scheduler.Shutdown();
|
||||
|
||||
named_ports.clear();
|
||||
}
|
||||
@@ -132,6 +139,18 @@ struct KernelCore::Impl {
|
||||
system.CoreTiming().RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
|
||||
}
|
||||
|
||||
void InitializePreemption() {
|
||||
preemption_event = system.CoreTiming().RegisterEvent(
|
||||
"PreemptionCallback", [this](u64 userdata, s64 cycles_late) {
|
||||
global_scheduler.PreemptThreads();
|
||||
s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10));
|
||||
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
|
||||
});
|
||||
|
||||
s64 time_interval = Core::Timing::msToCycles(std::chrono::milliseconds(10));
|
||||
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
|
||||
}
|
||||
|
||||
std::atomic<u32> next_object_id{0};
|
||||
std::atomic<u64> next_kernel_process_id{Process::InitialKIPIDMin};
|
||||
std::atomic<u64> next_user_process_id{Process::ProcessIDMin};
|
||||
@@ -140,10 +159,12 @@ struct KernelCore::Impl {
|
||||
// Lists all processes that exist in the current session.
|
||||
std::vector<SharedPtr<Process>> process_list;
|
||||
Process* current_process = nullptr;
|
||||
Kernel::GlobalScheduler global_scheduler;
|
||||
|
||||
SharedPtr<ResourceLimit> system_resource_limit;
|
||||
|
||||
Core::Timing::EventType* thread_wakeup_event_type = nullptr;
|
||||
Core::Timing::EventType* preemption_event = nullptr;
|
||||
// TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future,
|
||||
// allowing us to simply use a pool index or similar.
|
||||
Kernel::HandleTable thread_wakeup_callback_handle_table;
|
||||
@@ -203,6 +224,14 @@ const std::vector<SharedPtr<Process>>& KernelCore::GetProcessList() const {
|
||||
return impl->process_list;
|
||||
}
|
||||
|
||||
Kernel::GlobalScheduler& KernelCore::GlobalScheduler() {
|
||||
return impl->global_scheduler;
|
||||
}
|
||||
|
||||
const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const {
|
||||
return impl->global_scheduler;
|
||||
}
|
||||
|
||||
void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
|
||||
impl->named_ports.emplace(std::move(name), std::move(port));
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Kernel {
|
||||
|
||||
class AddressArbiter;
|
||||
class ClientPort;
|
||||
class GlobalScheduler;
|
||||
class HandleTable;
|
||||
class Process;
|
||||
class ResourceLimit;
|
||||
@@ -75,6 +76,12 @@ public:
|
||||
/// Retrieves the list of processes.
|
||||
const std::vector<SharedPtr<Process>>& GetProcessList() const;
|
||||
|
||||
/// Gets the sole instance of the global scheduler
|
||||
Kernel::GlobalScheduler& GlobalScheduler();
|
||||
|
||||
/// Gets the sole instance of the global scheduler
|
||||
const Kernel::GlobalScheduler& GlobalScheduler() const;
|
||||
|
||||
/// Adds a port to the named port table
|
||||
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
@@ -78,7 +79,7 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
// thread.
|
||||
ASSERT(requesting_thread == current_thread);
|
||||
|
||||
const u32 addr_value = Memory::Read32(address);
|
||||
u32 addr_value = Memory::Read32(address);
|
||||
|
||||
// If the mutex isn't being held, just return success.
|
||||
if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
|
||||
@@ -89,6 +90,20 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
// This a workaround where an unknown bug writes the mutex value to give ownership to a cond var
|
||||
// waiting thread.
|
||||
if (holding_thread->GetStatus() == ThreadStatus::WaitCondVar) {
|
||||
if (holding_thread->GetMutexWaitAddress() == address) {
|
||||
Release(address, holding_thread.get());
|
||||
addr_value = Memory::Read32(address);
|
||||
if (addr_value == 0)
|
||||
return RESULT_SUCCESS;
|
||||
else {
|
||||
holding_thread = handle_table.Get<Thread>(addr_value & Mutex::MutexOwnerMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until the mutex is released
|
||||
current_thread->SetMutexWaitAddress(address);
|
||||
current_thread->SetWaitHandle(requesting_thread_handle);
|
||||
@@ -104,14 +119,13 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode Mutex::Release(VAddr address) {
|
||||
ResultCode Mutex::Release(VAddr address, Thread* holding_thread) {
|
||||
// The mutex address must be 4-byte aligned
|
||||
if ((address % sizeof(u32)) != 0) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(current_thread, address);
|
||||
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(holding_thread, address);
|
||||
|
||||
// There are no more threads waiting for the mutex, release it completely.
|
||||
if (thread == nullptr) {
|
||||
@@ -120,7 +134,7 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
}
|
||||
|
||||
// Transfer the ownership of the mutex from the previous owner to the new one.
|
||||
TransferMutexOwnership(address, current_thread, thread);
|
||||
TransferMutexOwnership(address, holding_thread, thread);
|
||||
|
||||
u32 mutex_value = thread->GetWaitHandle();
|
||||
|
||||
@@ -139,6 +153,12 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
thread->SetCondVarWaitAddress(0);
|
||||
thread->SetMutexWaitAddress(0);
|
||||
thread->SetWaitHandle(0);
|
||||
thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
|
||||
if (thread->GetProcessorID() >= 0)
|
||||
system.CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
if (holding_thread->GetProcessorID() >= 0)
|
||||
system.CpuCore(holding_thread->GetProcessorID()).PrepareReschedule();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
Handle requesting_thread_handle);
|
||||
|
||||
/// Releases the mutex at the specified address.
|
||||
ResultCode Release(VAddr address);
|
||||
ResultCode Release(VAddr address, Thread* holding_thread);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
|
||||
@@ -213,10 +213,7 @@ void Process::PrepareForTermination() {
|
||||
}
|
||||
};
|
||||
|
||||
stop_threads(system.Scheduler(0).GetThreadList());
|
||||
stop_threads(system.Scheduler(1).GetThreadList());
|
||||
stop_threads(system.Scheduler(2).GetThreadList());
|
||||
stop_threads(system.Scheduler(3).GetThreadList());
|
||||
stop_threads(system.GlobalScheduler().GetThreadList());
|
||||
|
||||
FreeTLSRegion(tls_region_address);
|
||||
tls_region_address = 0;
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
//
|
||||
// SelectThreads, Yield functions originally by TuxSH.
|
||||
// licensed under GPLv2 or later under exception provided by the author.
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -17,56 +22,405 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
std::mutex Scheduler::scheduler_mutex;
|
||||
GlobalScheduler::GlobalScheduler(Core::System& system) : system{system} {
|
||||
is_reselection_pending = false;
|
||||
}
|
||||
|
||||
Scheduler::Scheduler(Core::System& system, Core::ARM_Interface& cpu_core)
|
||||
: cpu_core{cpu_core}, system{system} {}
|
||||
void GlobalScheduler::AddThread(SharedPtr<Thread> thread) {
|
||||
thread_list.push_back(std::move(thread));
|
||||
}
|
||||
|
||||
Scheduler::~Scheduler() {
|
||||
for (auto& thread : thread_list) {
|
||||
thread->Stop();
|
||||
void GlobalScheduler::RemoveThread(const Thread* thread) {
|
||||
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
|
||||
thread_list.end());
|
||||
}
|
||||
|
||||
/*
|
||||
* UnloadThread selects a core and forces it to unload its current thread's context
|
||||
*/
|
||||
void GlobalScheduler::UnloadThread(s32 core) {
|
||||
Scheduler& sched = system.Scheduler(core);
|
||||
sched.UnloadThread();
|
||||
}
|
||||
|
||||
/*
|
||||
* SelectThread takes care of selecting the new scheduled thread.
|
||||
* It does it in 3 steps:
|
||||
* - First a thread is selected from the top of the priority queue. If no thread
|
||||
* is obtained then we move to step two, else we are done.
|
||||
* - Second we try to get a suggested thread that's not assigned to any core or
|
||||
* that is not the top thread in that core.
|
||||
* - Third is no suggested thread is found, we do a second pass and pick a running
|
||||
* thread in another core and swap it with its current thread.
|
||||
*/
|
||||
void GlobalScheduler::SelectThread(u32 core) {
|
||||
const auto update_thread = [](Thread* thread, Scheduler& sched) {
|
||||
if (thread != sched.selected_thread) {
|
||||
if (thread == nullptr) {
|
||||
++sched.idle_selection_count;
|
||||
}
|
||||
sched.selected_thread = thread;
|
||||
}
|
||||
sched.is_context_switch_pending = sched.selected_thread != sched.current_thread;
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
};
|
||||
Scheduler& sched = system.Scheduler(core);
|
||||
Thread* current_thread = nullptr;
|
||||
// Step 1: Get top thread in schedule queue.
|
||||
current_thread = scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front();
|
||||
if (current_thread) {
|
||||
update_thread(current_thread, sched);
|
||||
return;
|
||||
}
|
||||
// Step 2: Try selecting a suggested thread.
|
||||
Thread* winner = nullptr;
|
||||
std::set<s32> sug_cores;
|
||||
for (auto thread : suggested_queue[core]) {
|
||||
s32 this_core = thread->GetProcessorID();
|
||||
Thread* thread_on_core = nullptr;
|
||||
if (this_core >= 0) {
|
||||
thread_on_core = scheduled_queue[this_core].front();
|
||||
}
|
||||
if (this_core < 0 || thread != thread_on_core) {
|
||||
winner = thread;
|
||||
break;
|
||||
}
|
||||
sug_cores.insert(this_core);
|
||||
}
|
||||
// if we got a suggested thread, select it, else do a second pass.
|
||||
if (winner && winner->GetPriority() > 2) {
|
||||
if (winner->IsRunning()) {
|
||||
UnloadThread(winner->GetProcessorID());
|
||||
}
|
||||
TransferToCore(winner->GetPriority(), core, winner);
|
||||
update_thread(winner, sched);
|
||||
return;
|
||||
}
|
||||
// Step 3: Select a suggested thread from another core
|
||||
for (auto& src_core : sug_cores) {
|
||||
auto it = scheduled_queue[src_core].begin();
|
||||
it++;
|
||||
if (it != scheduled_queue[src_core].end()) {
|
||||
Thread* thread_on_core = scheduled_queue[src_core].front();
|
||||
Thread* to_change = *it;
|
||||
if (thread_on_core->IsRunning() || to_change->IsRunning()) {
|
||||
UnloadThread(src_core);
|
||||
}
|
||||
TransferToCore(thread_on_core->GetPriority(), core, thread_on_core);
|
||||
current_thread = thread_on_core;
|
||||
break;
|
||||
}
|
||||
}
|
||||
update_thread(current_thread, sched);
|
||||
}
|
||||
|
||||
/*
|
||||
* YieldThread takes a thread and moves it to the back of the it's priority list
|
||||
* This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
bool GlobalScheduler::YieldThread(Thread* yielding_thread) {
|
||||
// Note: caller should use critical section, etc.
|
||||
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
|
||||
const u32 priority = yielding_thread->GetPriority();
|
||||
|
||||
// Yield the thread
|
||||
ASSERT_MSG(yielding_thread == scheduled_queue[core_id].front(priority),
|
||||
"Thread yielding without being in front");
|
||||
scheduled_queue[core_id].yield(priority);
|
||||
|
||||
Thread* winner = scheduled_queue[core_id].front(priority);
|
||||
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
|
||||
}
|
||||
|
||||
/*
|
||||
* YieldThreadAndBalanceLoad takes a thread and moves it to the back of the it's priority list.
|
||||
* Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
|
||||
* a better priority than the next thread in the core.
|
||||
* This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) {
|
||||
// Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
|
||||
// etc.
|
||||
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
|
||||
const u32 priority = yielding_thread->GetPriority();
|
||||
|
||||
// Yield the thread
|
||||
ASSERT_MSG(yielding_thread == scheduled_queue[core_id].front(priority),
|
||||
"Thread yielding without being in front");
|
||||
scheduled_queue[core_id].yield(priority);
|
||||
|
||||
std::array<Thread*, NUM_CPU_CORES> current_threads;
|
||||
for (u32 i = 0; i < NUM_CPU_CORES; i++) {
|
||||
current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
|
||||
}
|
||||
|
||||
Thread* next_thread = scheduled_queue[core_id].front(priority);
|
||||
Thread* winner = nullptr;
|
||||
for (auto& thread : suggested_queue[core_id]) {
|
||||
const s32 source_core = thread->GetProcessorID();
|
||||
if (source_core >= 0) {
|
||||
if (current_threads[source_core] != nullptr) {
|
||||
if (thread == current_threads[source_core] ||
|
||||
current_threads[source_core]->GetPriority() < min_regular_priority) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks() ||
|
||||
next_thread->GetPriority() < thread->GetPriority()) {
|
||||
if (thread->GetPriority() <= priority) {
|
||||
winner = thread;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (winner != nullptr) {
|
||||
if (winner != yielding_thread) {
|
||||
if (winner->IsRunning()) {
|
||||
UnloadThread(winner->GetProcessorID());
|
||||
}
|
||||
TransferToCore(winner->GetPriority(), core_id, winner);
|
||||
}
|
||||
} else {
|
||||
winner = next_thread;
|
||||
}
|
||||
|
||||
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
|
||||
}
|
||||
|
||||
/*
|
||||
* YieldThreadAndWaitForLoadBalancing takes a thread and moves it out of the scheduling queue
|
||||
* and into the suggested queue. If no thread can be squeduled afterwards in that core,
|
||||
* a suggested thread is obtained instead.
|
||||
* This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread) {
|
||||
// Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
|
||||
// etc.
|
||||
Thread* winner = nullptr;
|
||||
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
|
||||
|
||||
// Remove the thread from its scheduled mlq, put it on the corresponding "suggested" one instead
|
||||
TransferToCore(yielding_thread->GetPriority(), -1, yielding_thread);
|
||||
|
||||
// If the core is idle, perform load balancing, excluding the threads that have just used this
|
||||
// function...
|
||||
if (scheduled_queue[core_id].empty()) {
|
||||
// Here, "current_threads" is calculated after the ""yield"", unlike yield -1
|
||||
std::array<Thread*, NUM_CPU_CORES> current_threads;
|
||||
for (u32 i = 0; i < NUM_CPU_CORES; i++) {
|
||||
current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
|
||||
}
|
||||
for (auto& thread : suggested_queue[core_id]) {
|
||||
const s32 source_core = thread->GetProcessorID();
|
||||
if (source_core < 0 || thread == current_threads[source_core]) {
|
||||
continue;
|
||||
}
|
||||
if (current_threads[source_core] == nullptr ||
|
||||
current_threads[source_core]->GetPriority() >= min_regular_priority) {
|
||||
winner = thread;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (winner != nullptr) {
|
||||
if (winner != yielding_thread) {
|
||||
if (winner->IsRunning()) {
|
||||
UnloadThread(winner->GetProcessorID());
|
||||
}
|
||||
TransferToCore(winner->GetPriority(), core_id, winner);
|
||||
}
|
||||
} else {
|
||||
winner = yielding_thread;
|
||||
}
|
||||
}
|
||||
|
||||
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
|
||||
}
|
||||
|
||||
void GlobalScheduler::PreemptThreads() {
|
||||
for (std::size_t core_id = 0; core_id < NUM_CPU_CORES; core_id++) {
|
||||
const u32 priority = preemption_priorities[core_id];
|
||||
|
||||
if (scheduled_queue[core_id].size(priority) > 0) {
|
||||
scheduled_queue[core_id].front(priority)->IncrementYieldCount();
|
||||
scheduled_queue[core_id].yield(priority);
|
||||
if (scheduled_queue[core_id].size(priority) > 1) {
|
||||
scheduled_queue[core_id].front(priority)->IncrementYieldCount();
|
||||
}
|
||||
}
|
||||
|
||||
Thread* current_thread =
|
||||
scheduled_queue[core_id].empty() ? nullptr : scheduled_queue[core_id].front();
|
||||
Thread* winner = nullptr;
|
||||
for (auto& thread : suggested_queue[core_id]) {
|
||||
const s32 source_core = thread->GetProcessorID();
|
||||
if (thread->GetPriority() != priority) {
|
||||
continue;
|
||||
}
|
||||
if (source_core >= 0) {
|
||||
Thread* next_thread = scheduled_queue[source_core].empty()
|
||||
? nullptr
|
||||
: scheduled_queue[source_core].front();
|
||||
if (next_thread != nullptr && next_thread->GetPriority() < 2) {
|
||||
break;
|
||||
}
|
||||
if (next_thread == thread) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (current_thread != nullptr &&
|
||||
current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) {
|
||||
winner = thread;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (winner != nullptr) {
|
||||
if (winner->IsRunning()) {
|
||||
UnloadThread(winner->GetProcessorID());
|
||||
}
|
||||
TransferToCore(winner->GetPriority(), core_id, winner);
|
||||
current_thread =
|
||||
winner->GetPriority() <= current_thread->GetPriority() ? winner : current_thread;
|
||||
}
|
||||
|
||||
if (current_thread != nullptr && current_thread->GetPriority() > priority) {
|
||||
for (auto& thread : suggested_queue[core_id]) {
|
||||
const s32 source_core = thread->GetProcessorID();
|
||||
if (thread->GetPriority() < priority) {
|
||||
continue;
|
||||
}
|
||||
if (source_core >= 0) {
|
||||
Thread* next_thread = scheduled_queue[source_core].empty()
|
||||
? nullptr
|
||||
: scheduled_queue[source_core].front();
|
||||
if (next_thread != nullptr && next_thread->GetPriority() < 2) {
|
||||
break;
|
||||
}
|
||||
if (next_thread == thread) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (current_thread != nullptr &&
|
||||
current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) {
|
||||
winner = thread;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (winner != nullptr) {
|
||||
if (winner->IsRunning()) {
|
||||
UnloadThread(winner->GetProcessorID());
|
||||
}
|
||||
TransferToCore(winner->GetPriority(), core_id, winner);
|
||||
current_thread = winner;
|
||||
}
|
||||
}
|
||||
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalScheduler::Suggest(u32 priority, u32 core, Thread* thread) {
|
||||
suggested_queue[core].add(thread, priority);
|
||||
}
|
||||
|
||||
void GlobalScheduler::Unsuggest(u32 priority, u32 core, Thread* thread) {
|
||||
suggested_queue[core].remove(thread, priority);
|
||||
}
|
||||
|
||||
void GlobalScheduler::Schedule(u32 priority, u32 core, Thread* thread) {
|
||||
ASSERT_MSG(thread->GetProcessorID() == core, "Thread must be assigned to this core.");
|
||||
scheduled_queue[core].add(thread, priority);
|
||||
}
|
||||
|
||||
void GlobalScheduler::SchedulePrepend(u32 priority, u32 core, Thread* thread) {
|
||||
ASSERT_MSG(thread->GetProcessorID() == core, "Thread must be assigned to this core.");
|
||||
scheduled_queue[core].add(thread, priority, false);
|
||||
}
|
||||
|
||||
void GlobalScheduler::Reschedule(u32 priority, u32 core, Thread* thread) {
|
||||
scheduled_queue[core].remove(thread, priority);
|
||||
scheduled_queue[core].add(thread, priority);
|
||||
}
|
||||
|
||||
void GlobalScheduler::Unschedule(u32 priority, u32 core, Thread* thread) {
|
||||
scheduled_queue[core].remove(thread, priority);
|
||||
}
|
||||
|
||||
void GlobalScheduler::TransferToCore(u32 priority, s32 destination_core, Thread* thread) {
|
||||
const bool schedulable = thread->GetPriority() < THREADPRIO_COUNT;
|
||||
const s32 source_core = thread->GetProcessorID();
|
||||
if (source_core == destination_core || !schedulable) {
|
||||
return;
|
||||
}
|
||||
thread->SetProcessorID(destination_core);
|
||||
if (source_core >= 0) {
|
||||
Unschedule(priority, source_core, thread);
|
||||
}
|
||||
if (destination_core >= 0) {
|
||||
Unsuggest(priority, destination_core, thread);
|
||||
Schedule(priority, destination_core, thread);
|
||||
}
|
||||
if (source_core >= 0) {
|
||||
Suggest(priority, source_core, thread);
|
||||
}
|
||||
}
|
||||
|
||||
bool GlobalScheduler::AskForReselectionOrMarkRedundant(Thread* current_thread, Thread* winner) {
|
||||
if (current_thread == winner) {
|
||||
current_thread->IncrementYieldCount();
|
||||
return true;
|
||||
} else {
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalScheduler::Shutdown() {
|
||||
for (std::size_t core = 0; core < NUM_CPU_CORES; core++) {
|
||||
scheduled_queue[core].clear();
|
||||
suggested_queue[core].clear();
|
||||
}
|
||||
thread_list.clear();
|
||||
}
|
||||
|
||||
GlobalScheduler::~GlobalScheduler() = default;
|
||||
|
||||
Scheduler::Scheduler(Core::System& system, Core::ARM_Interface& cpu_core, u32 core_id)
|
||||
: system(system), cpu_core(cpu_core), core_id(core_id) {}
|
||||
|
||||
Scheduler::~Scheduler() = default;
|
||||
|
||||
bool Scheduler::HaveReadyThreads() const {
|
||||
std::lock_guard lock{scheduler_mutex};
|
||||
return !ready_queue.empty();
|
||||
return system.GlobalScheduler().HaveReadyThreads(core_id);
|
||||
}
|
||||
|
||||
Thread* Scheduler::GetCurrentThread() const {
|
||||
return current_thread.get();
|
||||
}
|
||||
|
||||
Thread* Scheduler::GetSelectedThread() const {
|
||||
return selected_thread.get();
|
||||
}
|
||||
|
||||
void Scheduler::SelectThreads() {
|
||||
system.GlobalScheduler().SelectThread(core_id);
|
||||
}
|
||||
|
||||
u64 Scheduler::GetLastContextSwitchTicks() const {
|
||||
return last_context_switch_time;
|
||||
}
|
||||
|
||||
Thread* Scheduler::PopNextReadyThread() {
|
||||
Thread* next = nullptr;
|
||||
Thread* thread = GetCurrentThread();
|
||||
|
||||
if (thread && thread->GetStatus() == ThreadStatus::Running) {
|
||||
if (ready_queue.empty()) {
|
||||
return thread;
|
||||
}
|
||||
// We have to do better than the current thread.
|
||||
// This call returns null when that's not possible.
|
||||
next = ready_queue.front();
|
||||
if (next == nullptr || next->GetPriority() >= thread->GetPriority()) {
|
||||
next = thread;
|
||||
}
|
||||
} else {
|
||||
if (ready_queue.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
next = ready_queue.front();
|
||||
void Scheduler::TryDoContextSwitch() {
|
||||
if (is_context_switch_pending) {
|
||||
SwitchContext();
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
Thread* previous_thread = GetCurrentThread();
|
||||
void Scheduler::UnloadThread() {
|
||||
Thread* const previous_thread = GetCurrentThread();
|
||||
Process* const previous_process = system.Kernel().CurrentProcess();
|
||||
|
||||
UpdateLastContextSwitchTime(previous_thread, previous_process);
|
||||
@@ -80,23 +434,52 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
if (previous_thread->GetStatus() == ThreadStatus::Running) {
|
||||
// This is only the case when a reschedule is triggered without the current thread
|
||||
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
|
||||
ready_queue.add(previous_thread, previous_thread->GetPriority(), false);
|
||||
previous_thread->SetStatus(ThreadStatus::Ready);
|
||||
}
|
||||
previous_thread->SetIsRunning(false);
|
||||
}
|
||||
current_thread = nullptr;
|
||||
}
|
||||
|
||||
void Scheduler::SwitchContext() {
|
||||
Thread* const previous_thread = GetCurrentThread();
|
||||
Thread* const new_thread = GetSelectedThread();
|
||||
|
||||
is_context_switch_pending = false;
|
||||
if (new_thread == previous_thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
Process* const previous_process = system.Kernel().CurrentProcess();
|
||||
|
||||
UpdateLastContextSwitchTime(previous_thread, previous_process);
|
||||
|
||||
// Save context for previous thread
|
||||
if (previous_thread) {
|
||||
cpu_core.SaveContext(previous_thread->GetContext());
|
||||
// Save the TPIDR_EL0 system register in case it was modified.
|
||||
previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
|
||||
|
||||
if (previous_thread->GetStatus() == ThreadStatus::Running) {
|
||||
// This is only the case when a reschedule is triggered without the current thread
|
||||
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
|
||||
previous_thread->SetStatus(ThreadStatus::Ready);
|
||||
}
|
||||
previous_thread->SetIsRunning(false);
|
||||
}
|
||||
|
||||
// Load context of new thread
|
||||
if (new_thread) {
|
||||
ASSERT_MSG(new_thread->GetProcessorID() == this->core_id,
|
||||
"Thread must be assigned to this core.");
|
||||
ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready,
|
||||
"Thread must be ready to become running.");
|
||||
|
||||
// Cancel any outstanding wakeup events for this thread
|
||||
new_thread->CancelWakeupTimer();
|
||||
|
||||
current_thread = new_thread;
|
||||
|
||||
ready_queue.remove(new_thread, new_thread->GetPriority());
|
||||
new_thread->SetStatus(ThreadStatus::Running);
|
||||
new_thread->SetIsRunning(true);
|
||||
|
||||
auto* const thread_owner_process = current_thread->GetOwnerProcess();
|
||||
if (previous_process != thread_owner_process) {
|
||||
@@ -130,124 +513,9 @@ void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
|
||||
last_context_switch_time = most_recent_switch_ticks;
|
||||
}
|
||||
|
||||
void Scheduler::Reschedule() {
|
||||
std::lock_guard lock{scheduler_mutex};
|
||||
|
||||
Thread* cur = GetCurrentThread();
|
||||
Thread* next = PopNextReadyThread();
|
||||
|
||||
if (cur && next) {
|
||||
LOG_TRACE(Kernel, "context switch {} -> {}", cur->GetObjectId(), next->GetObjectId());
|
||||
} else if (cur) {
|
||||
LOG_TRACE(Kernel, "context switch {} -> idle", cur->GetObjectId());
|
||||
} else if (next) {
|
||||
LOG_TRACE(Kernel, "context switch idle -> {}", next->GetObjectId());
|
||||
}
|
||||
|
||||
SwitchContext(next);
|
||||
}
|
||||
|
||||
void Scheduler::AddThread(SharedPtr<Thread> thread) {
|
||||
std::lock_guard lock{scheduler_mutex};
|
||||
|
||||
thread_list.push_back(std::move(thread));
|
||||
}
|
||||
|
||||
void Scheduler::RemoveThread(Thread* thread) {
|
||||
std::lock_guard lock{scheduler_mutex};
|
||||
|
||||
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
|
||||
thread_list.end());
|
||||
}
|
||||
|
||||
void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
|
||||
std::lock_guard lock{scheduler_mutex};
|
||||
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
|
||||
ready_queue.add(thread, priority);
|
||||
}
|
||||
|
||||
void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
|
||||
std::lock_guard lock{scheduler_mutex};
|
||||
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
|
||||
ready_queue.remove(thread, priority);
|
||||
}
|
||||
|
||||
void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
|
||||
std::lock_guard lock{scheduler_mutex};
|
||||
if (thread->GetPriority() == priority) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If thread was ready, adjust queues
|
||||
if (thread->GetStatus() == ThreadStatus::Ready)
|
||||
ready_queue.adjust(thread, thread->GetPriority(), priority);
|
||||
}
|
||||
|
||||
Thread* Scheduler::GetNextSuggestedThread(u32 core, u32 maximum_priority) const {
|
||||
std::lock_guard lock{scheduler_mutex};
|
||||
|
||||
const u32 mask = 1U << core;
|
||||
for (auto* thread : ready_queue) {
|
||||
if ((thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority) {
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
|
||||
ASSERT(thread != nullptr);
|
||||
// Avoid yielding if the thread isn't even running.
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::Running);
|
||||
|
||||
// Sanity check that the priority is valid
|
||||
ASSERT(thread->GetPriority() < THREADPRIO_COUNT);
|
||||
|
||||
// Yield this thread -- sleep for zero time and force reschedule to different thread
|
||||
GetCurrentThread()->Sleep(0);
|
||||
}
|
||||
|
||||
void Scheduler::YieldWithLoadBalancing(Thread* thread) {
|
||||
ASSERT(thread != nullptr);
|
||||
const auto priority = thread->GetPriority();
|
||||
const auto core = static_cast<u32>(thread->GetProcessorID());
|
||||
|
||||
// Avoid yielding if the thread isn't even running.
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::Running);
|
||||
|
||||
// Sanity check that the priority is valid
|
||||
ASSERT(priority < THREADPRIO_COUNT);
|
||||
|
||||
// Sleep for zero time to be able to force reschedule to different thread
|
||||
GetCurrentThread()->Sleep(0);
|
||||
|
||||
Thread* suggested_thread = nullptr;
|
||||
|
||||
// Search through all of the cpu cores (except this one) for a suggested thread.
|
||||
// Take the first non-nullptr one
|
||||
for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) {
|
||||
const auto res =
|
||||
system.CpuCore(cur_core).Scheduler().GetNextSuggestedThread(core, priority);
|
||||
|
||||
// If scheduler provides a suggested thread
|
||||
if (res != nullptr) {
|
||||
// And its better than the current suggested thread (or is the first valid one)
|
||||
if (suggested_thread == nullptr ||
|
||||
suggested_thread->GetPriority() > res->GetPriority()) {
|
||||
suggested_thread = res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If a suggested thread was found, queue that for this core
|
||||
if (suggested_thread != nullptr)
|
||||
suggested_thread->ChangeCore(core, suggested_thread->GetAffinityMask());
|
||||
}
|
||||
|
||||
void Scheduler::YieldAndWaitForLoadBalancing(Thread* thread) {
|
||||
UNIMPLEMENTED_MSG("Wait for load balancing thread yield type is not implemented!");
|
||||
void Scheduler::Shutdown() {
|
||||
current_thread = nullptr;
|
||||
selected_thread = nullptr;
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -20,124 +20,172 @@ namespace Kernel {
|
||||
|
||||
class Process;
|
||||
|
||||
class Scheduler final {
|
||||
class GlobalScheduler final {
|
||||
public:
|
||||
explicit Scheduler(Core::System& system, Core::ARM_Interface& cpu_core);
|
||||
~Scheduler();
|
||||
|
||||
/// Returns whether there are any threads that are ready to run.
|
||||
bool HaveReadyThreads() const;
|
||||
|
||||
/// Reschedules to the next available thread (call after current thread is suspended)
|
||||
void Reschedule();
|
||||
|
||||
/// Gets the current running thread
|
||||
Thread* GetCurrentThread() const;
|
||||
|
||||
/// Gets the timestamp for the last context switch in ticks.
|
||||
u64 GetLastContextSwitchTicks() const;
|
||||
static constexpr u32 NUM_CPU_CORES = 4;
|
||||
|
||||
explicit GlobalScheduler(Core::System& system);
|
||||
~GlobalScheduler();
|
||||
/// Adds a new thread to the scheduler
|
||||
void AddThread(SharedPtr<Thread> thread);
|
||||
|
||||
/// Removes a thread from the scheduler
|
||||
void RemoveThread(Thread* thread);
|
||||
|
||||
/// Schedules a thread that has become "ready"
|
||||
void ScheduleThread(Thread* thread, u32 priority);
|
||||
|
||||
/// Unschedules a thread that was already scheduled
|
||||
void UnscheduleThread(Thread* thread, u32 priority);
|
||||
|
||||
/// Sets the priority of a thread in the scheduler
|
||||
void SetThreadPriority(Thread* thread, u32 priority);
|
||||
|
||||
/// Gets the next suggested thread for load balancing
|
||||
Thread* GetNextSuggestedThread(u32 core, u32 minimum_priority) const;
|
||||
|
||||
/**
|
||||
* YieldWithoutLoadBalancing -- analogous to normal yield on a system
|
||||
* Moves the thread to the end of the ready queue for its priority, and then reschedules the
|
||||
* system to the new head of the queue.
|
||||
*
|
||||
* Example (Single Core -- but can be extrapolated to multi):
|
||||
* ready_queue[prio=0]: ThreadA, ThreadB, ThreadC (->exec order->)
|
||||
* Currently Running: ThreadR
|
||||
*
|
||||
* ThreadR calls YieldWithoutLoadBalancing
|
||||
*
|
||||
* ThreadR is moved to the end of ready_queue[prio=0]:
|
||||
* ready_queue[prio=0]: ThreadA, ThreadB, ThreadC, ThreadR (->exec order->)
|
||||
* Currently Running: Nothing
|
||||
*
|
||||
* System is rescheduled (ThreadA is popped off of queue):
|
||||
* ready_queue[prio=0]: ThreadB, ThreadC, ThreadR (->exec order->)
|
||||
* Currently Running: ThreadA
|
||||
*
|
||||
* If the queue is empty at time of call, no yielding occurs. This does not cross between cores
|
||||
* or priorities at all.
|
||||
*/
|
||||
void YieldWithoutLoadBalancing(Thread* thread);
|
||||
|
||||
/**
|
||||
* YieldWithLoadBalancing -- yield but with better selection of the new running thread
|
||||
* Moves the current thread to the end of the ready queue for its priority, then selects a
|
||||
* 'suggested thread' (a thread on a different core that could run on this core) from the
|
||||
* scheduler, changes its core, and reschedules the current core to that thread.
|
||||
*
|
||||
* Example (Dual Core -- can be extrapolated to Quad Core, this is just normal yield if it were
|
||||
* single core):
|
||||
* ready_queue[core=0][prio=0]: ThreadA, ThreadB (affinities not pictured as irrelevant
|
||||
* ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
|
||||
* Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
|
||||
*
|
||||
* ThreadQ calls YieldWithLoadBalancing
|
||||
*
|
||||
* ThreadQ is moved to the end of ready_queue[core=0][prio=0]:
|
||||
* ready_queue[core=0][prio=0]: ThreadA, ThreadB
|
||||
* ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
|
||||
* Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
|
||||
*
|
||||
* A list of suggested threads for each core is compiled
|
||||
* Suggested Threads: {ThreadC on Core 1}
|
||||
* If this were quad core (as the switch is), there could be between 0 and 3 threads in this
|
||||
* list. If there are more than one, the thread is selected by highest prio.
|
||||
*
|
||||
* ThreadC is core changed to Core 0:
|
||||
* ready_queue[core=0][prio=0]: ThreadC, ThreadA, ThreadB, ThreadQ
|
||||
* ready_queue[core=1][prio=0]: ThreadD
|
||||
* Currently Running: None on Core 0 || ThreadP on Core 1
|
||||
*
|
||||
* System is rescheduled (ThreadC is popped off of queue):
|
||||
* ready_queue[core=0][prio=0]: ThreadA, ThreadB, ThreadQ
|
||||
* ready_queue[core=1][prio=0]: ThreadD
|
||||
* Currently Running: ThreadC on Core 0 || ThreadP on Core 1
|
||||
*
|
||||
* If no suggested threads can be found this will behave just as normal yield. If there are
|
||||
* multiple candidates for the suggested thread on a core, the highest prio is taken.
|
||||
*/
|
||||
void YieldWithLoadBalancing(Thread* thread);
|
||||
|
||||
/// Currently unknown -- asserts as unimplemented on call
|
||||
void YieldAndWaitForLoadBalancing(Thread* thread);
|
||||
void RemoveThread(const Thread* thread);
|
||||
|
||||
/// Returns a list of all threads managed by the scheduler
|
||||
const std::vector<SharedPtr<Thread>>& GetThreadList() const {
|
||||
return thread_list;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Pops and returns the next thread from the thread queue
|
||||
* @return A pointer to the next ready thread
|
||||
*/
|
||||
Thread* PopNextReadyThread();
|
||||
// Add a thread to the suggested queue of a cpu core. Suggested threads may be
|
||||
// picked if no thread is scheduled to run on the core.
|
||||
void Suggest(u32 priority, u32 core, Thread* thread);
|
||||
|
||||
// Remove a thread to the suggested queue of a cpu core. Suggested threads may be
|
||||
// picked if no thread is scheduled to run on the core.
|
||||
void Unsuggest(u32 priority, u32 core, Thread* thread);
|
||||
|
||||
// Add a thread to the scheduling queue of a cpu core. The thread is added at the
|
||||
// back the queue in its priority level
|
||||
void Schedule(u32 priority, u32 core, Thread* thread);
|
||||
|
||||
// Add a thread to the scheduling queue of a cpu core. The thread is added at the
|
||||
// front the queue in its priority level
|
||||
void SchedulePrepend(u32 priority, u32 core, Thread* thread);
|
||||
|
||||
// Reschedule an already scheduled thread based on a new priority
|
||||
void Reschedule(u32 priority, u32 core, Thread* thread);
|
||||
|
||||
// Unschedule a thread.
|
||||
void Unschedule(u32 priority, u32 core, Thread* thread);
|
||||
|
||||
// Transfers a thread into an specific core. If the destination_core is -1
|
||||
// it will be unscheduled from its source code and added into its suggested
|
||||
// queue.
|
||||
void TransferToCore(u32 priority, s32 destination_core, Thread* thread);
|
||||
|
||||
/*
|
||||
* UnloadThread selects a core and forces it to unload its current thread's context
|
||||
*/
|
||||
void UnloadThread(s32 core);
|
||||
|
||||
/*
|
||||
* SelectThread takes care of selecting the new scheduled thread.
|
||||
* It does it in 3 steps:
|
||||
* - First a thread is selected from the top of the priority queue. If no thread
|
||||
* is obtained then we move to step two, else we are done.
|
||||
* - Second we try to get a suggested thread that's not assigned to any core or
|
||||
* that is not the top thread in that core.
|
||||
* - Third is no suggested thread is found, we do a second pass and pick a running
|
||||
* thread in another core and swap it with its current thread.
|
||||
*/
|
||||
void SelectThread(u32 core);
|
||||
|
||||
bool HaveReadyThreads(u32 core_id) const {
|
||||
return !scheduled_queue[core_id].empty();
|
||||
}
|
||||
|
||||
/*
|
||||
* YieldThread takes a thread and moves it to the back of the it's priority list
|
||||
* This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
bool YieldThread(Thread* thread);
|
||||
|
||||
/*
|
||||
* YieldThreadAndBalanceLoad takes a thread and moves it to the back of the it's priority list.
|
||||
* Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
|
||||
* a better priority than the next thread in the core.
|
||||
* This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
bool YieldThreadAndBalanceLoad(Thread* thread);
|
||||
|
||||
/*
|
||||
* YieldThreadAndWaitForLoadBalancing takes a thread and moves it out of the scheduling queue
|
||||
* and into the suggested queue. If no thread can be squeduled afterwards in that core,
|
||||
* a suggested thread is obtained instead.
|
||||
* This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
bool YieldThreadAndWaitForLoadBalancing(Thread* thread);
|
||||
|
||||
/*
|
||||
* PreemptThreads this operation rotates the scheduling queues of threads at
|
||||
* a preemption priority and then does some core rebalancing. Preemption priorities
|
||||
* can be found in the array 'preemption_priorities'. This operation happens
|
||||
* every 10ms.
|
||||
*/
|
||||
void PreemptThreads();
|
||||
|
||||
u32 CpuCoresCount() const {
|
||||
return NUM_CPU_CORES;
|
||||
}
|
||||
|
||||
void SetReselectionPending() {
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool IsReselectionPending() const {
|
||||
return is_reselection_pending.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void Shutdown();
|
||||
|
||||
private:
|
||||
bool AskForReselectionOrMarkRedundant(Thread* current_thread, Thread* winner);
|
||||
|
||||
static constexpr u32 min_regular_priority = 2;
|
||||
std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, NUM_CPU_CORES> scheduled_queue;
|
||||
std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, NUM_CPU_CORES> suggested_queue;
|
||||
std::atomic<bool> is_reselection_pending;
|
||||
|
||||
// `preemption_priorities` are the priority levels at which the global scheduler
|
||||
// preempts threads every 10 ms. They are ordered from Core 0 to Core 3
|
||||
std::array<u32, NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62};
|
||||
|
||||
/// Lists all thread ids that aren't deleted/etc.
|
||||
std::vector<SharedPtr<Thread>> thread_list;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
class Scheduler final {
|
||||
public:
|
||||
explicit Scheduler(Core::System& system, Core::ARM_Interface& cpu_core, u32 core_id);
|
||||
~Scheduler();
|
||||
|
||||
/// Returns whether there are any threads that are ready to run.
|
||||
bool HaveReadyThreads() const;
|
||||
|
||||
/// Reschedules to the next available thread (call after current thread is suspended)
|
||||
void TryDoContextSwitch();
|
||||
|
||||
/// Unloads currently running thread
|
||||
void UnloadThread();
|
||||
|
||||
/// Select the threads in top of the scheduling multilist.
|
||||
void SelectThreads();
|
||||
|
||||
/// Gets the current running thread
|
||||
Thread* GetCurrentThread() const;
|
||||
|
||||
/// Gets the currently selected thread from the top of the multilevel queue
|
||||
Thread* GetSelectedThread() const;
|
||||
|
||||
/// Gets the timestamp for the last context switch in ticks.
|
||||
u64 GetLastContextSwitchTicks() const;
|
||||
|
||||
bool ContextSwitchPending() const {
|
||||
return is_context_switch_pending;
|
||||
}
|
||||
|
||||
/// Shutdowns the scheduler.
|
||||
void Shutdown();
|
||||
|
||||
private:
|
||||
friend class GlobalScheduler;
|
||||
/**
|
||||
* Switches the CPU's active thread context to that of the specified thread
|
||||
* @param new_thread The thread to switch to
|
||||
*/
|
||||
void SwitchContext(Thread* new_thread);
|
||||
void SwitchContext();
|
||||
|
||||
/**
|
||||
* Called on every context switch to update the internal timestamp
|
||||
@@ -152,19 +200,16 @@ private:
|
||||
*/
|
||||
void UpdateLastContextSwitchTime(Thread* thread, Process* process);
|
||||
|
||||
/// Lists all thread ids that aren't deleted/etc.
|
||||
std::vector<SharedPtr<Thread>> thread_list;
|
||||
|
||||
/// Lists only ready thread ids.
|
||||
Common::MultiLevelQueue<Thread*, THREADPRIO_LOWEST + 1> ready_queue;
|
||||
|
||||
SharedPtr<Thread> current_thread = nullptr;
|
||||
|
||||
Core::ARM_Interface& cpu_core;
|
||||
u64 last_context_switch_time = 0;
|
||||
SharedPtr<Thread> selected_thread = nullptr;
|
||||
|
||||
Core::System& system;
|
||||
static std::mutex scheduler_mutex;
|
||||
Core::ARM_Interface& cpu_core;
|
||||
u64 last_context_switch_time = 0;
|
||||
u64 idle_selection_count = 0;
|
||||
const u32 core_id;
|
||||
|
||||
bool is_context_switch_pending = false;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -516,7 +516,7 @@ static ResultCode WaitSynchronization(Core::System& system, Handle* index, VAddr
|
||||
thread->WakeAfterDelay(nano_seconds);
|
||||
thread->SetWakeupCallback(DefaultThreadWakeupCallback);
|
||||
|
||||
system.CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
system.PrepareReschedule(thread->GetProcessorID());
|
||||
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
@@ -534,6 +534,7 @@ static ResultCode CancelSynchronization(Core::System& system, Handle thread_hand
|
||||
}
|
||||
|
||||
thread->CancelWait();
|
||||
system.PrepareReschedule(thread->GetProcessorID());
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -577,7 +578,8 @@ static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) {
|
||||
}
|
||||
|
||||
auto* const current_process = system.Kernel().CurrentProcess();
|
||||
return current_process->GetMutex().Release(mutex_addr);
|
||||
return current_process->GetMutex().Release(mutex_addr,
|
||||
system.CurrentScheduler().GetCurrentThread());
|
||||
}
|
||||
|
||||
enum class BreakType : u32 {
|
||||
@@ -1066,6 +1068,8 @@ static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 act
|
||||
}
|
||||
|
||||
thread->SetActivity(static_cast<ThreadActivity>(activity));
|
||||
|
||||
system.PrepareReschedule(thread->GetProcessorID());
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1147,7 +1151,7 @@ static ResultCode SetThreadPriority(Core::System& system, Handle handle, u32 pri
|
||||
|
||||
thread->SetPriority(priority);
|
||||
|
||||
system.CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
system.PrepareReschedule(thread->GetProcessorID());
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1503,7 +1507,7 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
|
||||
thread->SetName(
|
||||
fmt::format("thread[entry_point={:X}, handle={:X}]", entry_point, *new_thread_handle));
|
||||
|
||||
system.CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
system.PrepareReschedule(thread->GetProcessorID());
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -1525,7 +1529,7 @@ static ResultCode StartThread(Core::System& system, Handle thread_handle) {
|
||||
thread->ResumeFromWait();
|
||||
|
||||
if (thread->GetStatus() == ThreadStatus::Ready) {
|
||||
system.CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
system.PrepareReschedule(thread->GetProcessorID());
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
@@ -1537,7 +1541,7 @@ static void ExitThread(Core::System& system) {
|
||||
|
||||
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
current_thread->Stop();
|
||||
system.CurrentScheduler().RemoveThread(current_thread);
|
||||
system.GlobalScheduler().RemoveThread(current_thread);
|
||||
system.PrepareReschedule();
|
||||
}
|
||||
|
||||
@@ -1553,17 +1557,18 @@ static void SleepThread(Core::System& system, s64 nanoseconds) {
|
||||
|
||||
auto& scheduler = system.CurrentScheduler();
|
||||
auto* const current_thread = scheduler.GetCurrentThread();
|
||||
bool is_redundant = false;
|
||||
|
||||
if (nanoseconds <= 0) {
|
||||
switch (static_cast<SleepType>(nanoseconds)) {
|
||||
case SleepType::YieldWithoutLoadBalancing:
|
||||
scheduler.YieldWithoutLoadBalancing(current_thread);
|
||||
is_redundant = current_thread->YieldSimple();
|
||||
break;
|
||||
case SleepType::YieldWithLoadBalancing:
|
||||
scheduler.YieldWithLoadBalancing(current_thread);
|
||||
is_redundant = current_thread->YieldAndBalanceLoad();
|
||||
break;
|
||||
case SleepType::YieldAndWaitForLoadBalancing:
|
||||
scheduler.YieldAndWaitForLoadBalancing(current_thread);
|
||||
is_redundant = current_thread->YieldAndWaitForLoadBalancing();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
|
||||
@@ -1572,10 +1577,13 @@ static void SleepThread(Core::System& system, s64 nanoseconds) {
|
||||
current_thread->Sleep(nanoseconds);
|
||||
}
|
||||
|
||||
// Reschedule all CPU cores
|
||||
for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) {
|
||||
system.CpuCore(i).PrepareReschedule();
|
||||
if (is_redundant) {
|
||||
// If it's redundant, the core is pretty much idle. Some games keep idling
|
||||
// a core while it's doing nothing, we advance timing to avoid costly continuous
|
||||
// calls.
|
||||
system.CoreTiming().AddTicks(2000);
|
||||
}
|
||||
system.PrepareReschedule(current_thread->GetProcessorID());
|
||||
}
|
||||
|
||||
/// Wait process wide key atomic
|
||||
@@ -1601,17 +1609,21 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
|
||||
|
||||
auto* const current_process = system.Kernel().CurrentProcess();
|
||||
const auto& handle_table = current_process->GetHandleTable();
|
||||
SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
|
||||
ASSERT(thread);
|
||||
|
||||
const auto release_result = current_process->GetMutex().Release(mutex_addr);
|
||||
SharedPtr<Thread> current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
|
||||
const auto release_result =
|
||||
current_process->GetMutex().Release(mutex_addr, current_thread.get());
|
||||
if (release_result.IsError()) {
|
||||
return release_result;
|
||||
}
|
||||
|
||||
SharedPtr<Thread> current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
current_thread->SetCondVarWaitAddress(condition_variable_addr);
|
||||
current_thread->SetMutexWaitAddress(mutex_addr);
|
||||
current_thread->SetWaitHandle(thread_handle);
|
||||
@@ -1622,7 +1634,7 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
|
||||
|
||||
// Note: Deliberately don't attempt to inherit the lock owner's priority.
|
||||
|
||||
system.CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
|
||||
system.PrepareReschedule(current_thread->GetProcessorID());
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1632,24 +1644,19 @@ static ResultCode SignalProcessWideKey(Core::System& system, VAddr condition_var
|
||||
LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}",
|
||||
condition_variable_addr, target);
|
||||
|
||||
const auto RetrieveWaitingThreads = [&system](std::size_t core_index,
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr condvar_addr) {
|
||||
const auto& scheduler = system.Scheduler(core_index);
|
||||
const auto& thread_list = scheduler.GetThreadList();
|
||||
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetCondVarWaitAddress() == condvar_addr)
|
||||
waiting_threads.push_back(thread);
|
||||
}
|
||||
};
|
||||
ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
|
||||
|
||||
// Retrieve a list of all threads that are waiting for this condition variable.
|
||||
std::vector<SharedPtr<Thread>> waiting_threads;
|
||||
RetrieveWaitingThreads(0, waiting_threads, condition_variable_addr);
|
||||
RetrieveWaitingThreads(1, waiting_threads, condition_variable_addr);
|
||||
RetrieveWaitingThreads(2, waiting_threads, condition_variable_addr);
|
||||
RetrieveWaitingThreads(3, waiting_threads, condition_variable_addr);
|
||||
const auto& scheduler = system.GlobalScheduler();
|
||||
const auto& thread_list = scheduler.GetThreadList();
|
||||
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetCondVarWaitAddress() == condition_variable_addr) {
|
||||
waiting_threads.push_back(thread);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort them by priority, such that the highest priority ones come first.
|
||||
std::sort(waiting_threads.begin(), waiting_threads.end(),
|
||||
[](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
|
||||
@@ -1679,18 +1686,20 @@ static ResultCode SignalProcessWideKey(Core::System& system, VAddr condition_var
|
||||
|
||||
// Atomically read the value of the mutex.
|
||||
u32 mutex_val = 0;
|
||||
u32 update_val = 0;
|
||||
const VAddr mutex_address = thread->GetMutexWaitAddress();
|
||||
do {
|
||||
monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
|
||||
monitor.SetExclusive(current_core, mutex_address);
|
||||
|
||||
// If the mutex is not yet acquired, acquire it.
|
||||
mutex_val = Memory::Read32(thread->GetMutexWaitAddress());
|
||||
mutex_val = Memory::Read32(mutex_address);
|
||||
|
||||
if (mutex_val != 0) {
|
||||
monitor.ClearExclusive();
|
||||
break;
|
||||
update_val = mutex_val | Mutex::MutexHasWaitersFlag;
|
||||
} else {
|
||||
update_val = thread->GetWaitHandle();
|
||||
}
|
||||
} while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
|
||||
thread->GetWaitHandle()));
|
||||
} while (!monitor.ExclusiveWrite32(current_core, mutex_address, update_val));
|
||||
if (mutex_val == 0) {
|
||||
// We were able to acquire the mutex, resume this thread.
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitCondVar);
|
||||
@@ -1704,20 +1713,9 @@ static ResultCode SignalProcessWideKey(Core::System& system, VAddr condition_var
|
||||
thread->SetLockOwner(nullptr);
|
||||
thread->SetMutexWaitAddress(0);
|
||||
thread->SetWaitHandle(0);
|
||||
system.CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
system.PrepareReschedule(thread->GetProcessorID());
|
||||
} else {
|
||||
// Atomically signal that the mutex now has a waiting thread.
|
||||
do {
|
||||
monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
|
||||
|
||||
// Ensure that the mutex value is still what we expect.
|
||||
u32 value = Memory::Read32(thread->GetMutexWaitAddress());
|
||||
// TODO(Subv): When this happens, the kernel just clears the exclusive state and
|
||||
// retries the initial read for this thread.
|
||||
ASSERT_MSG(mutex_val == value, "Unhandled synchronization primitive case");
|
||||
} while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
|
||||
mutex_val | Mutex::MutexHasWaitersFlag));
|
||||
|
||||
// The mutex is already owned by some other thread, make this thread wait on it.
|
||||
const Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
|
||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||
@@ -1728,6 +1726,7 @@ static ResultCode SignalProcessWideKey(Core::System& system, VAddr condition_var
|
||||
thread->SetStatus(ThreadStatus::WaitMutex);
|
||||
|
||||
owner->AddMutexWaiter(thread);
|
||||
system.PrepareReschedule(thread->GetProcessorID());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1754,7 +1753,12 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type,
|
||||
|
||||
const auto arbitration_type = static_cast<AddressArbiter::ArbitrationType>(type);
|
||||
auto& address_arbiter = system.Kernel().CurrentProcess()->GetAddressArbiter();
|
||||
return address_arbiter.WaitForAddress(address, arbitration_type, value, timeout);
|
||||
const ResultCode result =
|
||||
address_arbiter.WaitForAddress(address, arbitration_type, value, timeout);
|
||||
if (result == RESULT_SUCCESS) {
|
||||
system.PrepareReschedule();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Signals to an address (via Address Arbiter)
|
||||
@@ -2040,7 +2044,10 @@ static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle,
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
system.PrepareReschedule(thread->GetProcessorID());
|
||||
thread->ChangeCore(core, affinity_mask);
|
||||
system.PrepareReschedule(thread->GetProcessorID());
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -2151,6 +2158,7 @@ static ResultCode SignalEvent(Core::System& system, Handle handle) {
|
||||
}
|
||||
|
||||
writable_event->Signal();
|
||||
system.PrepareReschedule();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,15 +45,7 @@ void Thread::Stop() {
|
||||
callback_handle);
|
||||
kernel.ThreadWakeupCallbackHandleTable().Close(callback_handle);
|
||||
callback_handle = 0;
|
||||
|
||||
// Clean up thread from ready queue
|
||||
// This is only needed when the thread is terminated forcefully (SVC TerminateProcess)
|
||||
if (status == ThreadStatus::Ready || status == ThreadStatus::Paused) {
|
||||
scheduler->UnscheduleThread(this, current_priority);
|
||||
}
|
||||
|
||||
status = ThreadStatus::Dead;
|
||||
|
||||
SetStatus(ThreadStatus::Dead);
|
||||
WakeupAllWaitingThreads();
|
||||
|
||||
// Clean up any dangling references in objects that this thread was waiting for
|
||||
@@ -132,17 +124,16 @@ void Thread::ResumeFromWait() {
|
||||
wakeup_callback = nullptr;
|
||||
|
||||
if (activity == ThreadActivity::Paused) {
|
||||
status = ThreadStatus::Paused;
|
||||
SetStatus(ThreadStatus::Paused);
|
||||
return;
|
||||
}
|
||||
|
||||
status = ThreadStatus::Ready;
|
||||
|
||||
ChangeScheduler();
|
||||
SetStatus(ThreadStatus::Ready);
|
||||
}
|
||||
|
||||
void Thread::CancelWait() {
|
||||
ASSERT(GetStatus() == ThreadStatus::WaitSynch);
|
||||
ClearWaitObjects();
|
||||
SetWaitSynchronizationResult(ERR_SYNCHRONIZATION_CANCELED);
|
||||
ResumeFromWait();
|
||||
}
|
||||
@@ -205,9 +196,9 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
thread->name = std::move(name);
|
||||
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
|
||||
thread->owner_process = &owner_process;
|
||||
auto& scheduler = kernel.GlobalScheduler();
|
||||
scheduler.AddThread(thread);
|
||||
thread->tls_address = thread->owner_process->CreateTLSRegion();
|
||||
thread->scheduler = &system.Scheduler(processor_id);
|
||||
thread->scheduler->AddThread(thread);
|
||||
|
||||
thread->owner_process->RegisterThread(thread.get());
|
||||
|
||||
@@ -250,6 +241,22 @@ void Thread::SetStatus(ThreadStatus new_status) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (new_status) {
|
||||
case ThreadStatus::Ready:
|
||||
case ThreadStatus::Running:
|
||||
SetSchedulingStatus(ThreadSchedStatus::Runnable);
|
||||
break;
|
||||
case ThreadStatus::Dormant:
|
||||
SetSchedulingStatus(ThreadSchedStatus::None);
|
||||
break;
|
||||
case ThreadStatus::Dead:
|
||||
SetSchedulingStatus(ThreadSchedStatus::Exited);
|
||||
break;
|
||||
default:
|
||||
SetSchedulingStatus(ThreadSchedStatus::Paused);
|
||||
break;
|
||||
}
|
||||
|
||||
if (status == ThreadStatus::Running) {
|
||||
last_running_ticks = Core::System::GetInstance().CoreTiming().GetTicks();
|
||||
}
|
||||
@@ -311,8 +318,7 @@ void Thread::UpdatePriority() {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler->SetThreadPriority(this, new_priority);
|
||||
current_priority = new_priority;
|
||||
SetCurrentPriority(new_priority);
|
||||
|
||||
if (!lock_owner) {
|
||||
return;
|
||||
@@ -328,47 +334,7 @@ void Thread::UpdatePriority() {
|
||||
}
|
||||
|
||||
void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
ideal_core = core;
|
||||
affinity_mask = mask;
|
||||
ChangeScheduler();
|
||||
}
|
||||
|
||||
void Thread::ChangeScheduler() {
|
||||
if (status != ThreadStatus::Ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
std::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)};
|
||||
|
||||
if (!new_processor_id) {
|
||||
new_processor_id = processor_id;
|
||||
}
|
||||
if (ideal_core != -1 && system.Scheduler(ideal_core).GetCurrentThread() == nullptr) {
|
||||
new_processor_id = ideal_core;
|
||||
}
|
||||
|
||||
ASSERT(*new_processor_id < 4);
|
||||
|
||||
// Add thread to new core's scheduler
|
||||
auto& next_scheduler = system.Scheduler(*new_processor_id);
|
||||
|
||||
if (*new_processor_id != processor_id) {
|
||||
// Remove thread from previous core's scheduler
|
||||
scheduler->RemoveThread(this);
|
||||
next_scheduler.AddThread(this);
|
||||
}
|
||||
|
||||
processor_id = *new_processor_id;
|
||||
|
||||
// If the thread was ready, unschedule from the previous core and schedule on the new core
|
||||
scheduler->UnscheduleThread(this, current_priority);
|
||||
next_scheduler.ScheduleThread(this, current_priority);
|
||||
|
||||
// Change thread's scheduler
|
||||
scheduler = &next_scheduler;
|
||||
|
||||
system.CpuCore(processor_id).PrepareReschedule();
|
||||
SetCoreAndAffinityMask(core, mask);
|
||||
}
|
||||
|
||||
bool Thread::AllWaitObjectsReady() const {
|
||||
@@ -388,10 +354,8 @@ void Thread::SetActivity(ThreadActivity value) {
|
||||
|
||||
if (value == ThreadActivity::Paused) {
|
||||
// Set status if not waiting
|
||||
if (status == ThreadStatus::Ready) {
|
||||
status = ThreadStatus::Paused;
|
||||
} else if (status == ThreadStatus::Running) {
|
||||
status = ThreadStatus::Paused;
|
||||
if (status == ThreadStatus::Ready || status == ThreadStatus::Running) {
|
||||
SetStatus(ThreadStatus::Paused);
|
||||
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
|
||||
}
|
||||
} else if (status == ThreadStatus::Paused) {
|
||||
@@ -408,6 +372,170 @@ void Thread::Sleep(s64 nanoseconds) {
|
||||
WakeAfterDelay(nanoseconds);
|
||||
}
|
||||
|
||||
bool Thread::YieldSimple() {
|
||||
auto& scheduler = kernel.GlobalScheduler();
|
||||
return scheduler.YieldThread(this);
|
||||
}
|
||||
|
||||
bool Thread::YieldAndBalanceLoad() {
|
||||
auto& scheduler = kernel.GlobalScheduler();
|
||||
return scheduler.YieldThreadAndBalanceLoad(this);
|
||||
}
|
||||
|
||||
bool Thread::YieldAndWaitForLoadBalancing() {
|
||||
auto& scheduler = kernel.GlobalScheduler();
|
||||
return scheduler.YieldThreadAndWaitForLoadBalancing(this);
|
||||
}
|
||||
|
||||
void Thread::SetSchedulingStatus(ThreadSchedStatus new_status) {
|
||||
const u32 old_flags = scheduling_state;
|
||||
scheduling_state = (scheduling_state & static_cast<u32>(ThreadSchedMasks::HighMask)) |
|
||||
static_cast<u32>(new_status);
|
||||
AdjustSchedulingOnStatus(old_flags);
|
||||
}
|
||||
|
||||
void Thread::SetCurrentPriority(u32 new_priority) {
|
||||
const u32 old_priority = std::exchange(current_priority, new_priority);
|
||||
AdjustSchedulingOnPriority(old_priority);
|
||||
}
|
||||
|
||||
ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
|
||||
const auto HighestSetCore = [](u64 mask, u32 max_cores) {
|
||||
for (s32 core = max_cores - 1; core >= 0; core--) {
|
||||
if (((mask >> core) & 1) != 0) {
|
||||
return core;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
const bool use_override = affinity_override_count != 0;
|
||||
if (new_core == THREADPROCESSORID_DONT_UPDATE) {
|
||||
new_core = use_override ? ideal_core_override : ideal_core;
|
||||
if ((new_affinity_mask & (1ULL << new_core)) == 0) {
|
||||
return ERR_INVALID_COMBINATION;
|
||||
}
|
||||
}
|
||||
if (use_override) {
|
||||
ideal_core_override = new_core;
|
||||
affinity_mask_override = new_affinity_mask;
|
||||
} else {
|
||||
const u64 old_affinity_mask = std::exchange(affinity_mask, new_affinity_mask);
|
||||
ideal_core = new_core;
|
||||
if (old_affinity_mask != new_affinity_mask) {
|
||||
const s32 old_core = processor_id;
|
||||
if (processor_id >= 0 && ((affinity_mask >> processor_id) & 1) == 0) {
|
||||
if (ideal_core < 0) {
|
||||
processor_id = HighestSetCore(affinity_mask, GlobalScheduler::NUM_CPU_CORES);
|
||||
} else {
|
||||
processor_id = ideal_core;
|
||||
}
|
||||
}
|
||||
AdjustSchedulingOnAffinity(old_affinity_mask, old_core);
|
||||
}
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void Thread::AdjustSchedulingOnStatus(u32 old_flags) {
|
||||
if (old_flags == scheduling_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& scheduler = kernel.GlobalScheduler();
|
||||
if (static_cast<ThreadSchedStatus>(old_flags & static_cast<u32>(ThreadSchedMasks::LowMask)) ==
|
||||
ThreadSchedStatus::Runnable) {
|
||||
// In this case the thread was running, now it's pausing/exitting
|
||||
if (processor_id >= 0) {
|
||||
scheduler.Unschedule(current_priority, processor_id, this);
|
||||
}
|
||||
|
||||
for (s32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
|
||||
if (core != processor_id && ((affinity_mask >> core) & 1) != 0) {
|
||||
scheduler.Unsuggest(current_priority, static_cast<u32>(core), this);
|
||||
}
|
||||
}
|
||||
} else if (GetSchedulingStatus() == ThreadSchedStatus::Runnable) {
|
||||
// The thread is now set to running from being stopped
|
||||
if (processor_id >= 0) {
|
||||
scheduler.Schedule(current_priority, processor_id, this);
|
||||
}
|
||||
|
||||
for (s32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
|
||||
if (core != processor_id && ((affinity_mask >> core) & 1) != 0) {
|
||||
scheduler.Suggest(current_priority, static_cast<u32>(core), this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scheduler.SetReselectionPending();
|
||||
}
|
||||
|
||||
void Thread::AdjustSchedulingOnPriority(u32 old_priority) {
|
||||
if (GetSchedulingStatus() != ThreadSchedStatus::Runnable) {
|
||||
return;
|
||||
}
|
||||
auto& scheduler = Core::System::GetInstance().GlobalScheduler();
|
||||
if (processor_id >= 0) {
|
||||
scheduler.Unschedule(old_priority, processor_id, this);
|
||||
}
|
||||
|
||||
for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
|
||||
if (core != processor_id && ((affinity_mask >> core) & 1) != 0) {
|
||||
scheduler.Unsuggest(old_priority, core, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Add thread to the new priority queues.
|
||||
Thread* current_thread = GetCurrentThread();
|
||||
|
||||
if (processor_id >= 0) {
|
||||
if (current_thread == this) {
|
||||
scheduler.SchedulePrepend(current_priority, processor_id, this);
|
||||
} else {
|
||||
scheduler.Schedule(current_priority, processor_id, this);
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
|
||||
if (core != processor_id && ((affinity_mask >> core) & 1) != 0) {
|
||||
scheduler.Suggest(current_priority, core, this);
|
||||
}
|
||||
}
|
||||
|
||||
scheduler.SetReselectionPending();
|
||||
}
|
||||
|
||||
void Thread::AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core) {
|
||||
auto& scheduler = Core::System::GetInstance().GlobalScheduler();
|
||||
if (GetSchedulingStatus() != ThreadSchedStatus::Runnable ||
|
||||
current_priority >= THREADPRIO_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
|
||||
if (((old_affinity_mask >> core) & 1) != 0) {
|
||||
if (core == old_core) {
|
||||
scheduler.Unschedule(current_priority, core, this);
|
||||
} else {
|
||||
scheduler.Unsuggest(current_priority, core, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 core = 0; core < GlobalScheduler::NUM_CPU_CORES; core++) {
|
||||
if (((affinity_mask >> core) & 1) != 0) {
|
||||
if (core == processor_id) {
|
||||
scheduler.Schedule(current_priority, core, this);
|
||||
} else {
|
||||
scheduler.Suggest(current_priority, core, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scheduler.SetReselectionPending();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,6 +75,26 @@ enum class ThreadActivity : u32 {
|
||||
Paused = 1,
|
||||
};
|
||||
|
||||
enum class ThreadSchedStatus : u32 {
|
||||
None = 0,
|
||||
Paused = 1,
|
||||
Runnable = 2,
|
||||
Exited = 3,
|
||||
};
|
||||
|
||||
enum class ThreadSchedFlags : u32 {
|
||||
ProcessPauseFlag = 1 << 4,
|
||||
ThreadPauseFlag = 1 << 5,
|
||||
ProcessDebugPauseFlag = 1 << 6,
|
||||
KernelInitPauseFlag = 1 << 8,
|
||||
};
|
||||
|
||||
enum class ThreadSchedMasks : u32 {
|
||||
LowMask = 0x000f,
|
||||
HighMask = 0xfff0,
|
||||
ForcePauseMask = 0x0070,
|
||||
};
|
||||
|
||||
class Thread final : public WaitObject {
|
||||
public:
|
||||
using MutexWaitingThreads = std::vector<SharedPtr<Thread>>;
|
||||
@@ -278,6 +298,10 @@ public:
|
||||
return processor_id;
|
||||
}
|
||||
|
||||
void SetProcessorID(s32 new_core) {
|
||||
processor_id = new_core;
|
||||
}
|
||||
|
||||
Process* GetOwnerProcess() {
|
||||
return owner_process;
|
||||
}
|
||||
@@ -295,6 +319,9 @@ public:
|
||||
}
|
||||
|
||||
void ClearWaitObjects() {
|
||||
for (const auto& waiting_object : wait_objects) {
|
||||
waiting_object->RemoveWaitingThread(this);
|
||||
}
|
||||
wait_objects.clear();
|
||||
}
|
||||
|
||||
@@ -383,11 +410,47 @@ public:
|
||||
/// Sleeps this thread for the given amount of nanoseconds.
|
||||
void Sleep(s64 nanoseconds);
|
||||
|
||||
/// Yields this thread without rebalancing loads.
|
||||
bool YieldSimple();
|
||||
|
||||
/// Yields this thread and does a load rebalancing.
|
||||
bool YieldAndBalanceLoad();
|
||||
|
||||
/// Yields this thread and if the core is left idle, loads are rebalanced
|
||||
bool YieldAndWaitForLoadBalancing();
|
||||
|
||||
void IncrementYieldCount() {
|
||||
yield_count++;
|
||||
}
|
||||
|
||||
u64 GetYieldCount() const {
|
||||
return yield_count;
|
||||
}
|
||||
|
||||
ThreadSchedStatus GetSchedulingStatus() const {
|
||||
return static_cast<ThreadSchedStatus>(scheduling_state &
|
||||
static_cast<u32>(ThreadSchedMasks::LowMask));
|
||||
}
|
||||
|
||||
bool IsRunning() const {
|
||||
return is_running;
|
||||
}
|
||||
|
||||
void SetIsRunning(bool value) {
|
||||
is_running = value;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Thread(KernelCore& kernel);
|
||||
~Thread() override;
|
||||
|
||||
void ChangeScheduler();
|
||||
void SetSchedulingStatus(ThreadSchedStatus new_status);
|
||||
void SetCurrentPriority(u32 new_priority);
|
||||
ResultCode SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask);
|
||||
|
||||
void AdjustSchedulingOnStatus(u32 old_flags);
|
||||
void AdjustSchedulingOnPriority(u32 old_priority);
|
||||
void AdjustSchedulingOnAffinity(u64 old_affinity_mask, s32 old_core);
|
||||
|
||||
Core::ARM_Interface::ThreadContext context{};
|
||||
|
||||
@@ -409,6 +472,8 @@ private:
|
||||
|
||||
u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
|
||||
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
|
||||
u64 yield_count = 0; ///< Number of redundant yields carried by this thread.
|
||||
///< a redundant yield is one where no scheduling is changed
|
||||
|
||||
s32 processor_id = 0;
|
||||
|
||||
@@ -453,6 +518,13 @@ private:
|
||||
|
||||
ThreadActivity activity = ThreadActivity::Normal;
|
||||
|
||||
s32 ideal_core_override = -1;
|
||||
u64 affinity_mask_override = 0x1;
|
||||
u32 affinity_override_count = 0;
|
||||
|
||||
u32 scheduling_state = 0;
|
||||
bool is_running = false;
|
||||
|
||||
std::string name;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ SharedPtr<TransferMemory> TransferMemory::Create(KernelCore& kernel, VAddr base_
|
||||
transfer_memory->owner_permissions = permissions;
|
||||
transfer_memory->owner_process = kernel.CurrentProcess();
|
||||
|
||||
transfer_memory->MapMemory(base_address, size, permissions);
|
||||
|
||||
return transfer_memory;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
@@ -48,17 +51,8 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() const {
|
||||
if (ShouldWait(thread.get()))
|
||||
continue;
|
||||
|
||||
// A thread is ready to run if it's either in ThreadStatus::WaitSynch
|
||||
// and the rest of the objects it is waiting on are ready.
|
||||
bool ready_to_run = true;
|
||||
if (thread_status == ThreadStatus::WaitSynch) {
|
||||
ready_to_run = thread->AllWaitObjectsReady();
|
||||
}
|
||||
|
||||
if (ready_to_run) {
|
||||
candidate = thread.get();
|
||||
candidate_priority = thread->GetPriority();
|
||||
}
|
||||
candidate = thread.get();
|
||||
candidate_priority = thread->GetPriority();
|
||||
}
|
||||
|
||||
return candidate;
|
||||
@@ -82,9 +76,6 @@ void WaitObject::WakeupWaitingThread(SharedPtr<Thread> thread) {
|
||||
|
||||
const std::size_t index = thread->GetWaitObjectIndex(this);
|
||||
|
||||
for (const auto& object : thread->GetWaitObjects()) {
|
||||
object->RemoveWaitingThread(thread.get());
|
||||
}
|
||||
thread->ClearWaitObjects();
|
||||
|
||||
thread->CancelWakeupTimer();
|
||||
@@ -95,6 +86,7 @@ void WaitObject::WakeupWaitingThread(SharedPtr<Thread> thread) {
|
||||
}
|
||||
if (resume) {
|
||||
thread->ResumeFromWait();
|
||||
Core::System::GetInstance().PrepareReschedule(thread->GetProcessorID());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -847,17 +847,16 @@ private:
|
||||
void PopInteractiveOutData(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
const auto storage = applet->GetBroker().PopInteractiveDataToGame();
|
||||
if (storage == nullptr) {
|
||||
LOG_ERROR(Service_AM,
|
||||
"storage is a nullptr. There is no data in the current interactive channel");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERR_NO_DATA_IN_CHANNEL);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IStorage>(std::move(*storage));
|
||||
}
|
||||
|
||||
@@ -27,9 +27,9 @@ AppletDataBroker::AppletDataBroker(Kernel::KernelCore& kernel) {
|
||||
state_changed_event = Kernel::WritableEvent::CreateEventPair(
|
||||
kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent");
|
||||
pop_out_data_event = Kernel::WritableEvent::CreateEventPair(
|
||||
kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:PopDataOutEvent");
|
||||
kernel, Kernel::ResetType::Automatic, "ILibraryAppletAccessor:PopDataOutEvent");
|
||||
pop_interactive_out_data_event = Kernel::WritableEvent::CreateEventPair(
|
||||
kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:PopInteractiveDataOutEvent");
|
||||
kernel, Kernel::ResetType::Automatic, "ILibraryAppletAccessor:PopInteractiveDataOutEvent");
|
||||
}
|
||||
|
||||
AppletDataBroker::~AppletDataBroker() = default;
|
||||
|
||||
@@ -91,6 +91,7 @@ void SoftwareKeyboard::ExecuteInteractive() {
|
||||
|
||||
if (status == INTERACTIVE_STATUS_OK) {
|
||||
complete = true;
|
||||
broker.SignalStateChanged();
|
||||
} else {
|
||||
std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string;
|
||||
std::memcpy(string.data(), data.data() + 4, string.size() * 2);
|
||||
|
||||
@@ -40,7 +40,10 @@ static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base,
|
||||
if (dir_name.empty() || dir_name == "." || dir_name == "/" || dir_name == "\\")
|
||||
return base;
|
||||
|
||||
return base->GetDirectoryRelative(dir_name);
|
||||
const auto res = base->GetDirectoryRelative(dir_name);
|
||||
if (res == nullptr)
|
||||
return base->CreateDirectoryRelative(dir_name);
|
||||
return res;
|
||||
}
|
||||
|
||||
VfsDirectoryServiceWrapper::VfsDirectoryServiceWrapper(FileSys::VirtualDir backing_)
|
||||
@@ -722,8 +725,7 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
|
||||
void InstallInterfaces(Core::System& system) {
|
||||
std::make_shared<FSP_LDR>()->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<FSP_PR>()->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<FSP_SRV>(system.GetFileSystemController(), system.GetReporter())
|
||||
->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<FSP_SRV>(system)->InstallAsService(system.ServiceManager());
|
||||
}
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -14,17 +14,22 @@
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/directory.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/system_archive/system_archive.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_ro_layer.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/readable_event.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/filesystem/fsp_srv.h"
|
||||
#include "core/reporter.h"
|
||||
@@ -54,6 +59,12 @@ enum class FileSystemType : u8 {
|
||||
ApplicationPackage = 7,
|
||||
};
|
||||
|
||||
enum class SaveDataOpenMode {
|
||||
Normal,
|
||||
ReadOnly,
|
||||
System,
|
||||
};
|
||||
|
||||
class IStorage final : public ServiceFramework<IStorage> {
|
||||
public:
|
||||
explicit IStorage(FileSys::VirtualFile backend_)
|
||||
@@ -503,14 +514,17 @@ private:
|
||||
|
||||
class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
|
||||
public:
|
||||
explicit ISaveDataInfoReader(FileSys::SaveDataSpaceId space, FileSystemController& fsc)
|
||||
explicit ISaveDataInfoReader(FileSystemController& fsc,
|
||||
std::vector<FileSys::SaveDataSpaceId> spaces)
|
||||
: ServiceFramework("ISaveDataInfoReader"), fsc(fsc) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
FindAllSaves(space);
|
||||
for (const auto& space : spaces) {
|
||||
FindAllSaves(space);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadSaveDataInfo(Kernel::HLERequestContext& ctx) {
|
||||
@@ -650,8 +664,31 @@ private:
|
||||
u64 next_entry_index = 0;
|
||||
};
|
||||
|
||||
FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
|
||||
: ServiceFramework("fsp-srv"), fsc(fsc), reporter(reporter) {
|
||||
class IEventNotifier final : public ServiceFramework<IEventNotifier> {
|
||||
public:
|
||||
explicit IEventNotifier(Kernel::SharedPtr<Kernel::ReadableEvent> event)
|
||||
: ServiceFramework{"IEventNotifier"}, event(std::move(event)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IEventNotifier::GetEventHandle, "GetEventHandle"},
|
||||
};
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void GetEventHandle(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyObjects(event);
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::ReadableEvent> event;
|
||||
};
|
||||
|
||||
FSP_SRV::FSP_SRV(Core::System& system)
|
||||
: ServiceFramework("fsp-srv"), system(system), fsc(system.GetFileSystemController()) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "OpenFileSystem"},
|
||||
@@ -660,15 +697,15 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
|
||||
{7, &FSP_SRV::OpenFileSystemWithPatch, "OpenFileSystemWithPatch"},
|
||||
{8, nullptr, "OpenFileSystemWithId"},
|
||||
{9, nullptr, "OpenDataFileSystemByApplicationId"},
|
||||
{11, nullptr, "OpenBisFileSystem"},
|
||||
{12, nullptr, "OpenBisStorage"},
|
||||
{13, nullptr, "InvalidateBisCache"},
|
||||
{11, &FSP_SRV::OpenBisFileSystem, "OpenBisFileSystem"},
|
||||
{12, &FSP_SRV::OpenBisStorage, "OpenBisStorage"},
|
||||
{13, &FSP_SRV::InvalidateBisCache, "InvalidateBisCache"},
|
||||
{17, nullptr, "OpenHostFileSystem"},
|
||||
{18, &FSP_SRV::OpenSdCardFileSystem, "OpenSdCardFileSystem"},
|
||||
{19, nullptr, "FormatSdCardFileSystem"},
|
||||
{21, nullptr, "DeleteSaveDataFileSystem"},
|
||||
{22, &FSP_SRV::CreateSaveDataFileSystem, "CreateSaveDataFileSystem"},
|
||||
{23, nullptr, "CreateSaveDataFileSystemBySystemSaveDataId"},
|
||||
{23, &FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId, "CreateSaveDataFileSystemBySystemSaveDataId"},
|
||||
{24, nullptr, "RegisterSaveDataFileSystemAtomicDeletion"},
|
||||
{25, nullptr, "DeleteSaveDataFileSystemBySaveDataSpaceId"},
|
||||
{26, nullptr, "FormatSdCardDryRun"},
|
||||
@@ -681,12 +718,12 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
|
||||
{34, nullptr, "GetCacheStorageSize"},
|
||||
{35, nullptr, "CreateSaveDataFileSystemByHashSalt"},
|
||||
{51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"},
|
||||
{52, nullptr, "OpenSaveDataFileSystemBySystemSaveDataId"},
|
||||
{52, &FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId, "OpenSaveDataFileSystemBySystemSaveDataId"},
|
||||
{53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"},
|
||||
{57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"},
|
||||
{58, nullptr, "ReadSaveDataFileSystemExtraData"},
|
||||
{59, nullptr, "WriteSaveDataFileSystemExtraData"},
|
||||
{60, nullptr, "OpenSaveDataInfoReader"},
|
||||
{60, &FSP_SRV::OpenSaveDataInfoReader, "OpenSaveDataInfoReader"},
|
||||
{61, &FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId, "OpenSaveDataInfoReaderBySaveDataSpaceId"},
|
||||
{62, nullptr, "OpenCacheStorageList"},
|
||||
{64, nullptr, "OpenSaveDataInternalStorageFileSystem"},
|
||||
@@ -699,8 +736,8 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
|
||||
{82, nullptr, "OpenSaveDataTransferManagerVersion2"},
|
||||
{83, nullptr, "OpenSaveDataTransferProhibiterForCloudBackUp"},
|
||||
{84, nullptr, "ListApplicationAccessibleSaveDataOwnerId"},
|
||||
{100, nullptr, "OpenImageDirectoryFileSystem"},
|
||||
{110, nullptr, "OpenContentStorageFileSystem"},
|
||||
{100, &FSP_SRV::OpenImageDirectoryFileSystem, "OpenImageDirectoryFileSystem"},
|
||||
{110, &FSP_SRV::OpenContentStorageFileSystem, "OpenContentStorageFileSystem"},
|
||||
{120, nullptr, "OpenCloudBackupWorkStorageFileSystem"},
|
||||
{130, nullptr, "OpenCustomStorageFileSystem"},
|
||||
{200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"},
|
||||
@@ -710,8 +747,8 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
|
||||
{204, nullptr, "OpenDataFileSystemByProgramIndex"},
|
||||
{205, nullptr, "OpenDataStorageByProgramIndex"},
|
||||
{400, nullptr, "OpenDeviceOperator"},
|
||||
{500, nullptr, "OpenSdCardDetectionEventNotifier"},
|
||||
{501, nullptr, "OpenGameCardDetectionEventNotifier"},
|
||||
{500, &FSP_SRV::OpenSdCardDetectionEventNotifier, "OpenSdCardDetectionEventNotifier"},
|
||||
{501, &FSP_SRV::OpenGameCardDetectionEventNotifier, "OpenGameCardDetectionEventNotifier"},
|
||||
{510, nullptr, "OpenSystemDataUpdateEventNotifier"},
|
||||
{511, nullptr, "NotifySystemDataUpdateEvent"},
|
||||
{520, nullptr, "SimulateGameCardDetectionEvent"},
|
||||
@@ -733,7 +770,7 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
|
||||
{615, nullptr, "QuerySaveDataInternalStorageTotalSize"},
|
||||
{616, nullptr, "GetSaveDataCommitId"},
|
||||
{617, nullptr, "UnregisterExternalKey"},
|
||||
{620, nullptr, "SetSdCardEncryptionSeed"},
|
||||
{620, &FSP_SRV::SetSdCardEncryptionSeed, "SetSdCardEncryptionSeed"},
|
||||
{630, nullptr, "SetSdCardAccessibility"},
|
||||
{631, nullptr, "IsSdCardAccessible"},
|
||||
{640, nullptr, "IsSignedSystemPartitionOnSdCardValid"},
|
||||
@@ -762,6 +799,12 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter)
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
|
||||
auto& kernel{system.Kernel()};
|
||||
sd_card_detection_event = Kernel::WritableEvent::CreateEventPair(
|
||||
kernel, Kernel::ResetType::Automatic, "fsp-srv:SdCardDetectionEvent");
|
||||
game_card_detection_event = Kernel::WritableEvent::CreateEventPair(
|
||||
kernel, Kernel::ResetType::Automatic, "fsp-srv:GameCardDetectionEvent");
|
||||
}
|
||||
|
||||
FSP_SRV::~FSP_SRV() = default;
|
||||
@@ -781,11 +824,134 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
const auto type = rp.PopRaw<FileSystemType>();
|
||||
const auto title_id = rp.PopRaw<u64>();
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called with type={}, title_id={:016X}",
|
||||
static_cast<u8>(type), title_id);
|
||||
LOG_DEBUG(Service_FS, "called with type={}, title_id={:016X}", static_cast<u8>(type), title_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
|
||||
rb.Push(ResultCode(-1));
|
||||
const auto& prov{system.GetContentProvider()};
|
||||
|
||||
FileSys::PatchManager pm{title_id};
|
||||
FileSys::ContentRecordType cr_type;
|
||||
|
||||
switch (type) {
|
||||
case FileSystemType::ApplicationPackage:
|
||||
case FileSystemType::Logo:
|
||||
cr_type = FileSys::ContentRecordType::Program;
|
||||
break;
|
||||
case FileSystemType::ContentControl:
|
||||
cr_type = FileSys::ContentRecordType::Control;
|
||||
break;
|
||||
case FileSystemType::ContentManual:
|
||||
cr_type = FileSys::ContentRecordType::HtmlDocument;
|
||||
break;
|
||||
case FileSystemType::ContentMeta:
|
||||
cr_type = FileSys::ContentRecordType::Meta;
|
||||
break;
|
||||
case FileSystemType::ContentData:
|
||||
cr_type = FileSys::ContentRecordType::Data;
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(Service_FS, "called with invalid filesystem type!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(FileSys::ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& nca{prov.GetEntry(title_id, cr_type)};
|
||||
if (nca == nullptr) {
|
||||
LOG_WARNING(Service_FS, "NCA requested doesn't exist in content provider!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(FileSys::ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
FileSys::VirtualDir dir;
|
||||
|
||||
if (type == FileSystemType::ApplicationPackage) {
|
||||
dir = nca->GetExeFS();
|
||||
if (dir != nullptr)
|
||||
dir = pm.PatchExeFS(dir);
|
||||
} else if (type == FileSystemType::Logo) {
|
||||
dir = nca->GetSubdirectories()[1];
|
||||
} else if (type == FileSystemType::ContentControl || type == FileSystemType::ContentManual ||
|
||||
type == FileSystemType::ContentData) {
|
||||
if (nca->GetRomFS() != nullptr) {
|
||||
const auto romfs = pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), cr_type);
|
||||
if (romfs != nullptr)
|
||||
dir = FileSys::ExtractRomFS(romfs);
|
||||
}
|
||||
} else {
|
||||
dir = nca->GetSubdirectories()[0];
|
||||
}
|
||||
|
||||
if (dir == nullptr) {
|
||||
LOG_WARNING(Service_FS, "couldn't get requested NCA section!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(FileSys::ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface(std::make_shared<IFileSystem>(
|
||||
dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::Host)));
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenBisFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto partition = rp.PopRaw<FileSys::BisPartitionId>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called with partition_id={:08X}", static_cast<u32>(partition));
|
||||
|
||||
auto dir = fsc.OpenBISPartition(partition);
|
||||
|
||||
if (dir.Failed()) {
|
||||
LOG_ERROR(Service_FS,
|
||||
"Failed to mount BIS filesystem for partition_id={:08X}! Could be invalid "
|
||||
"argument or uninitialized system.",
|
||||
static_cast<u32>(partition));
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(dir.Code());
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
IFileSystem fs(dir.Unwrap(), SizeGetter::FromStorageId(fsc, FileSys::StorageId::Host));
|
||||
rb.PushIpcInterface<IFileSystem>(std::move(fs));
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenBisStorage(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto partition = rp.PopRaw<FileSys::BisPartitionId>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called with partition_id={:08X}", static_cast<u32>(partition));
|
||||
|
||||
auto file = fsc.OpenBISPartitionStorage(partition);
|
||||
|
||||
if (file.Failed()) {
|
||||
LOG_ERROR(Service_FS,
|
||||
"Failed to mount BIS storage for partition_id={:08X}! Could be invalid "
|
||||
"argument or uninitialized system.",
|
||||
static_cast<u32>(partition));
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(file.Code());
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
IStorage fs(file.Unwrap());
|
||||
rb.PushIpcInterface<IStorage>(std::move(fs));
|
||||
}
|
||||
|
||||
void FSP_SRV::InvalidateBisCache(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
// Exists for SDK compatibility -- We do not emulate a BIS cache.
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
@@ -815,44 +981,103 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_FS, "called.");
|
||||
void FSP_SRV::CreateSaveDataFileSystemBySystemSaveDataId(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto save_struct = rp.PopRaw<FileSys::SaveDataDescriptor>();
|
||||
auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called save_struct = {}", save_struct.DebugInfo());
|
||||
|
||||
const auto dir = fsc.CreateSaveData(FileSys::SaveDataSpaceId::NandSystem, save_struct);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(dir.Code());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
FileSys::StorageId StorageFromSaveDataSpace(FileSys::SaveDataSpaceId space) {
|
||||
switch (space) {
|
||||
case FileSys::SaveDataSpaceId::NandSystem:
|
||||
case FileSys::SaveDataSpaceId::ProperSystem:
|
||||
case FileSys::SaveDataSpaceId::TemporaryStorage:
|
||||
return FileSys::StorageId::NandSystem;
|
||||
case FileSys::SaveDataSpaceId::NandUser:
|
||||
return FileSys::StorageId::NandUser;
|
||||
case FileSys::SaveDataSpaceId::SdCardSystem:
|
||||
case FileSys::SaveDataSpaceId::SdCardUser:
|
||||
return FileSys::StorageId::SdCard;
|
||||
default:
|
||||
return FileSys::StorageId::None;
|
||||
}
|
||||
}
|
||||
|
||||
template <SaveDataOpenMode mode>
|
||||
void OpenSaveDataFileSystemGeneric(Kernel::HLERequestContext& ctx, FileSystemController& fsc) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
struct Parameters {
|
||||
FileSys::SaveDataSpaceId save_data_space_id;
|
||||
FileSys::SaveDataDescriptor descriptor;
|
||||
};
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto parameters = rp.PopRaw<Parameters>();
|
||||
|
||||
auto dir = fsc.OpenSaveData(parameters.save_data_space_id, parameters.descriptor);
|
||||
|
||||
if (dir.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
FileSys::StorageId id;
|
||||
if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::NandUser) {
|
||||
id = FileSys::StorageId::NandUser;
|
||||
} else if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardSystem ||
|
||||
parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardUser) {
|
||||
id = FileSys::StorageId::SdCard;
|
||||
} else {
|
||||
id = FileSys::StorageId::NandSystem;
|
||||
auto diru = dir.Unwrap();
|
||||
|
||||
if (diru == nullptr) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
IFileSystem filesystem(std::move(dir.Unwrap()), SizeGetter::FromStorageId(fsc, id));
|
||||
if constexpr (mode == SaveDataOpenMode::ReadOnly) {
|
||||
diru = std::make_shared<FileSys::ReadOnlyVfsDirectoryLayer>(diru);
|
||||
}
|
||||
|
||||
IFileSystem filesystem(
|
||||
std::move(diru),
|
||||
SizeGetter::FromStorageId(fsc, StorageFromSaveDataSpace(parameters.save_data_space_id)));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
OpenSaveDataFileSystemGeneric<SaveDataOpenMode::Normal>(ctx, fsc);
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
|
||||
OpenSaveDataFileSystem(ctx);
|
||||
OpenSaveDataFileSystemGeneric<SaveDataOpenMode::ReadOnly>(ctx, fsc);
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(Kernel::HLERequestContext& ctx) {
|
||||
OpenSaveDataFileSystemGeneric<SaveDataOpenMode::System>(ctx, fsc);
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenSaveDataInfoReader(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ISaveDataInfoReader>(
|
||||
std::make_shared<ISaveDataInfoReader>(fsc, std::vector<FileSys::SaveDataSpaceId>{
|
||||
FileSys::SaveDataSpaceId::NandSystem,
|
||||
FileSys::SaveDataSpaceId::NandUser,
|
||||
FileSys::SaveDataSpaceId::TemporaryStorage,
|
||||
FileSys::SaveDataSpaceId::SdCardUser,
|
||||
}));
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx) {
|
||||
@@ -862,7 +1087,64 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext&
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space, fsc));
|
||||
rb.PushIpcInterface<ISaveDataInfoReader>(
|
||||
std::make_shared<ISaveDataInfoReader>(fsc, std::vector<FileSys::SaveDataSpaceId>{space}));
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenImageDirectoryFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto storage = rp.PopRaw<ImageDirectoryId>();
|
||||
LOG_DEBUG(Service_FS, "called, storage={:08X}", static_cast<u32>(storage));
|
||||
|
||||
auto dir = fsc.GetImageDirectory(storage);
|
||||
|
||||
if (dir == nullptr) {
|
||||
LOG_ERROR(Service_FS, "The image directory requested was invalid!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(FileSys::ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface(std::make_shared<IFileSystem>(
|
||||
std::move(dir), SizeGetter::FromStorageId(fsc, storage == ImageDirectoryId::NAND
|
||||
? FileSys::StorageId::NandUser
|
||||
: FileSys::StorageId::SdCard)));
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenContentStorageFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto storage = rp.PopRaw<ContentStorageId>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called, storage={:08X}", static_cast<u32>(storage));
|
||||
|
||||
auto dir = fsc.GetContentDirectory(storage);
|
||||
|
||||
if (dir == nullptr) {
|
||||
LOG_ERROR(Service_FS, "The content storage requested was invalid!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(FileSys::ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
FileSys::StorageId storage_id = FileSys::StorageId::None;
|
||||
switch (storage) {
|
||||
case ContentStorageId::SdCard:
|
||||
storage_id = FileSys::StorageId::SdCard;
|
||||
break;
|
||||
case ContentStorageId::User:
|
||||
storage_id = FileSys::StorageId::NandUser;
|
||||
break;
|
||||
case ContentStorageId::System:
|
||||
storage_id = FileSys::StorageId::NandSystem;
|
||||
break;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface(
|
||||
std::make_shared<IFileSystem>(std::move(dir), SizeGetter::FromStorageId(fsc, storage_id)));
|
||||
}
|
||||
|
||||
void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
|
||||
@@ -961,7 +1243,7 @@ void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
LOG_DEBUG(Service_FS, "called, log='{}'", log);
|
||||
|
||||
reporter.SaveFilesystemAccessReport(log_mode, std::move(log));
|
||||
system.GetReporter().SaveFilesystemAccessReport(log_mode, std::move(log));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -976,4 +1258,30 @@ void FSP_SRV::GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(access_log_program_index);
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenSdCardDetectionEventNotifier(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface(std::make_shared<IEventNotifier>(sd_card_detection_event.readable));
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenGameCardDetectionEventNotifier(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface(std::make_shared<IEventNotifier>(game_card_detection_event.readable));
|
||||
}
|
||||
|
||||
void FSP_SRV::SetSdCardEncryptionSeed(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto seed = rp.PopRaw<u128>();
|
||||
|
||||
LOG_INFO(Service_FS, "called with seed={:016X}{:016X}", seed[1], seed[0]);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -32,17 +33,25 @@ enum class LogMode : u32 {
|
||||
|
||||
class FSP_SRV final : public ServiceFramework<FSP_SRV> {
|
||||
public:
|
||||
explicit FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter);
|
||||
explicit FSP_SRV(Core::System& system);
|
||||
~FSP_SRV() override;
|
||||
|
||||
private:
|
||||
void SetCurrentProcess(Kernel::HLERequestContext& ctx);
|
||||
void OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx);
|
||||
void OpenBisFileSystem(Kernel::HLERequestContext& ctx);
|
||||
void OpenBisStorage(Kernel::HLERequestContext& ctx);
|
||||
void InvalidateBisCache(Kernel::HLERequestContext& ctx);
|
||||
void OpenSdCardFileSystem(Kernel::HLERequestContext& ctx);
|
||||
void CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx);
|
||||
void CreateSaveDataFileSystemBySystemSaveDataId(Kernel::HLERequestContext& ctx);
|
||||
void OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx);
|
||||
void OpenSaveDataFileSystemBySystemSaveDataId(Kernel::HLERequestContext& ctx);
|
||||
void OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx);
|
||||
void OpenSaveDataInfoReader(Kernel::HLERequestContext& ctx);
|
||||
void OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx);
|
||||
void OpenImageDirectoryFileSystem(Kernel::HLERequestContext& ctx);
|
||||
void OpenContentStorageFileSystem(Kernel::HLERequestContext& ctx);
|
||||
void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
|
||||
void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
|
||||
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
|
||||
@@ -50,7 +59,11 @@ private:
|
||||
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
|
||||
void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx);
|
||||
void GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx);
|
||||
void OpenSdCardDetectionEventNotifier(Kernel::HLERequestContext& ctx);
|
||||
void OpenGameCardDetectionEventNotifier(Kernel::HLERequestContext& ctx);
|
||||
void SetSdCardEncryptionSeed(Kernel::HLERequestContext& ctx);
|
||||
|
||||
Core::System& system;
|
||||
FileSystemController& fsc;
|
||||
|
||||
FileSys::VirtualFile romfs;
|
||||
@@ -58,7 +71,8 @@ private:
|
||||
u32 access_log_program_index = 0;
|
||||
LogMode log_mode = LogMode::LogToSdCard;
|
||||
|
||||
const Core::Reporter& reporter;
|
||||
Kernel::EventPair sd_card_detection_event;
|
||||
Kernel::EventPair game_card_detection_event;
|
||||
};
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -203,13 +203,13 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
|
||||
{120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"},
|
||||
{121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"},
|
||||
{122, &Hid::SetNpadJoyAssignmentModeSingleByDefault, "SetNpadJoyAssignmentModeSingleByDefault"},
|
||||
{123, nullptr, "SetNpadJoyAssignmentModeSingleByDefault"},
|
||||
{123, &Hid::SetNpadJoyAssignmentModeSingle, "SetNpadJoyAssignmentModeSingle"},
|
||||
{124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"},
|
||||
{125, &Hid::MergeSingleJoyAsDualJoy, "MergeSingleJoyAsDualJoy"},
|
||||
{126, &Hid::StartLrAssignmentMode, "StartLrAssignmentMode"},
|
||||
{127, &Hid::StopLrAssignmentMode, "StopLrAssignmentMode"},
|
||||
{128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
|
||||
{129, nullptr, "GetNpadHandheldActivationMode"},
|
||||
{129, &Hid::GetNpadHandheldActivationMode, "GetNpadHandheldActivationMode"},
|
||||
{130, &Hid::SwapNpadAssignment, "SwapNpadAssignment"},
|
||||
{131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"},
|
||||
{132, nullptr, "EnableUnintendedHomeButtonInputProtection"},
|
||||
@@ -557,10 +557,126 @@ void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", npad_id,
|
||||
applet_resource_user_id);
|
||||
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) {
|
||||
// TODO: Check the differences between this and SetNpadJoyAssignmentModeSingleByDefault
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto npad_id{rp.Pop<u32>()};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
const auto npad_joy_device_type{rp.Pop<u64>()};
|
||||
|
||||
LOG_WARNING(Service_HID,
|
||||
"(STUBBED) called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}",
|
||||
npad_id, applet_resource_user_id, npad_joy_device_type);
|
||||
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto npad_id{rp.Pop<u32>()};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
|
||||
applet_resource_user_id);
|
||||
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto unknown_1{rp.Pop<u32>()};
|
||||
const auto unknown_2{rp.Pop<u32>()};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_WARNING(Service_HID,
|
||||
"(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
|
||||
unknown_1, unknown_2, applet_resource_user_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::StartLrAssignmentMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
controller.StartLRAssignmentMode();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
controller.StopLRAssignmentMode();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
const auto mode{rp.Pop<u64>()};
|
||||
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}",
|
||||
applet_resource_user_id, mode);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
|
||||
applet_resource_user_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto npad_1{rp.Pop<u32>()};
|
||||
const auto npad_2{rp.Pop<u32>()};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, npad_1={}, npad_2={}",
|
||||
applet_resource_user_id, npad_1, npad_2);
|
||||
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
if (controller.SwapNpadAssignment(npad_1, npad_2)) {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
} else {
|
||||
LOG_ERROR(Service_HID, "Npads are not connected!");
|
||||
rb.Push(ERR_NPAD_NOT_CONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
@@ -635,47 +751,6 @@ void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
|
||||
applet_resource->GetController<Controller_NPad>(HidController::NPad).GetLastVibration());
|
||||
}
|
||||
|
||||
void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto npad_id{rp.Pop<u32>()};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id,
|
||||
applet_resource_user_id);
|
||||
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto unknown_1{rp.Pop<u32>()};
|
||||
const auto unknown_2{rp.Pop<u32>()};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_WARNING(Service_HID,
|
||||
"(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
|
||||
unknown_1, unknown_2, applet_resource_user_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
const auto mode{rp.Pop<u64>()};
|
||||
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}",
|
||||
applet_resource_user_id, mode);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
|
||||
@@ -769,49 +844,6 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::StartLrAssignmentMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
controller.StartLRAssignmentMode();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
controller.StopLRAssignmentMode();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto npad_1{rp.Pop<u32>()};
|
||||
const auto npad_2{rp.Pop<u32>()};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, npad_1={}, npad_2={}",
|
||||
applet_resource_user_id, npad_1, npad_2);
|
||||
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
if (controller.SwapNpadAssignment(npad_1, npad_2)) {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
} else {
|
||||
LOG_ERROR(Service_HID, "Npads are not connected!");
|
||||
rb.Push(ERR_NPAD_NOT_CONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
class HidDbg final : public ServiceFramework<HidDbg> {
|
||||
public:
|
||||
explicit HidDbg() : ServiceFramework{"hid:dbg"} {
|
||||
|
||||
@@ -106,14 +106,19 @@ private:
|
||||
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
|
||||
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
|
||||
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx);
|
||||
void SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx);
|
||||
void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx);
|
||||
void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx);
|
||||
void StartLrAssignmentMode(Kernel::HLERequestContext& ctx);
|
||||
void StopLrAssignmentMode(Kernel::HLERequestContext& ctx);
|
||||
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
|
||||
void GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
|
||||
void SwapNpadAssignment(Kernel::HLERequestContext& ctx);
|
||||
void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
|
||||
void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
|
||||
void SendVibrationValue(Kernel::HLERequestContext& ctx);
|
||||
void SendVibrationValues(Kernel::HLERequestContext& ctx);
|
||||
void GetActualVibrationValue(Kernel::HLERequestContext& ctx);
|
||||
void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx);
|
||||
void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx);
|
||||
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
|
||||
void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
|
||||
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx);
|
||||
void PermitVibration(Kernel::HLERequestContext& ctx);
|
||||
@@ -123,9 +128,6 @@ private:
|
||||
void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
|
||||
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
|
||||
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
|
||||
void StartLrAssignmentMode(Kernel::HLERequestContext& ctx);
|
||||
void StopLrAssignmentMode(Kernel::HLERequestContext& ctx);
|
||||
void SwapNpadAssignment(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<IAppletResource> applet_resource;
|
||||
Core::System& system;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "core/hle/service/lbl/lbl.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace Service::LBL {
|
||||
|
||||
@@ -18,21 +20,21 @@ public:
|
||||
explicit LBL() : ServiceFramework{"lbl"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "SaveCurrentSetting"},
|
||||
{1, nullptr, "LoadCurrentSetting"},
|
||||
{2, nullptr, "SetCurrentBrightnessSetting"},
|
||||
{3, nullptr, "GetCurrentBrightnessSetting"},
|
||||
{4, nullptr, "ApplyCurrentBrightnessSettingToBacklight"},
|
||||
{5, nullptr, "GetBrightnessSettingAppliedToBacklight"},
|
||||
{6, nullptr, "SwitchBacklightOn"},
|
||||
{7, nullptr, "SwitchBacklightOff"},
|
||||
{8, nullptr, "GetBacklightSwitchStatus"},
|
||||
{9, nullptr, "EnableDimming"},
|
||||
{10, nullptr, "DisableDimming"},
|
||||
{11, nullptr, "IsDimmingEnabled"},
|
||||
{12, nullptr, "EnableAutoBrightnessControl"},
|
||||
{13, nullptr, "DisableAutoBrightnessControl"},
|
||||
{14, nullptr, "IsAutoBrightnessControlEnabled"},
|
||||
{0, &LBL::SaveCurrentSetting, "SaveCurrentSetting"},
|
||||
{1, &LBL::LoadCurrentSetting, "LoadCurrentSetting"},
|
||||
{2, &LBL::SetCurrentBrightnessSetting, "SetCurrentBrightnessSetting"},
|
||||
{3, &LBL::GetCurrentBrightnessSetting, "GetCurrentBrightnessSetting"},
|
||||
{4, &LBL::ApplyCurrentBrightnessSettingToBacklight, "ApplyCurrentBrightnessSettingToBacklight"},
|
||||
{5, &LBL::GetBrightnessSettingAppliedToBacklight, "GetBrightnessSettingAppliedToBacklight"},
|
||||
{6, &LBL::SwitchBacklightOn, "SwitchBacklightOn"},
|
||||
{7, &LBL::SwitchBacklightOff, "SwitchBacklightOff"},
|
||||
{8, &LBL::GetBacklightSwitchStatus, "GetBacklightSwitchStatus"},
|
||||
{9, &LBL::EnableDimming, "EnableDimming"},
|
||||
{10, &LBL::DisableDimming, "DisableDimming"},
|
||||
{11, &LBL::IsDimmingEnabled, "IsDimmingEnabled"},
|
||||
{12, &LBL::EnableAutoBrightnessControl, "EnableAutoBrightnessControl"},
|
||||
{13, &LBL::DisableAutoBrightnessControl, "DisableAutoBrightnessControl"},
|
||||
{14, &LBL::IsAutoBrightnessControlEnabled, "IsAutoBrightnessControlEnabled"},
|
||||
{15, nullptr, "SetAmbientLightSensorValue"},
|
||||
{16, nullptr, "GetAmbientLightSensorValue"},
|
||||
{17, nullptr, "SetBrightnessReflectionDelayLevel"},
|
||||
@@ -42,8 +44,8 @@ public:
|
||||
{21, nullptr, "SetCurrentAmbientLightSensorMapping"},
|
||||
{22, nullptr, "GetCurrentAmbientLightSensorMapping"},
|
||||
{23, nullptr, "IsAmbientLightSensorAvailable"},
|
||||
{24, nullptr, "SetCurrentBrightnessSettingForVrMode"},
|
||||
{25, nullptr, "GetCurrentBrightnessSettingForVrMode"},
|
||||
{24, &LBL::SetCurrentBrightnessSettingForVrMode, "SetCurrentBrightnessSettingForVrMode"},
|
||||
{25, &LBL::GetCurrentBrightnessSettingForVrMode, "GetCurrentBrightnessSettingForVrMode"},
|
||||
{26, &LBL::EnableVrMode, "EnableVrMode"},
|
||||
{27, &LBL::DisableVrMode, "DisableVrMode"},
|
||||
{28, &LBL::IsVrModeEnabled, "IsVrModeEnabled"},
|
||||
@@ -53,13 +55,209 @@ public:
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
void LoadFromSettings() {
|
||||
current_brightness = Settings::values.backlight_brightness;
|
||||
current_vr_mode_brightness = Settings::values.backlight_brightness;
|
||||
|
||||
if (auto_brightness_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vr_mode_enabled) {
|
||||
Renderer().SetCurrentBrightness(current_vr_mode_brightness);
|
||||
} else {
|
||||
Renderer().SetCurrentBrightness(current_brightness);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
f32 GetAutoBrightnessValue() const {
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
VideoCore::RendererBase& Renderer() {
|
||||
return Core::System::GetInstance().Renderer();
|
||||
}
|
||||
|
||||
void SaveCurrentSetting(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
Settings::values.backlight_brightness = current_brightness;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void LoadCurrentSetting(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
LoadFromSettings();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void SetCurrentBrightnessSetting(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto value = rp.PopRaw<f32>();
|
||||
|
||||
LOG_DEBUG(Service_LBL, "called, value={:.3f}", value);
|
||||
|
||||
current_brightness = std::clamp(value, 0.0f, 1.0f);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void GetCurrentBrightnessSetting(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(current_brightness);
|
||||
}
|
||||
|
||||
void ApplyCurrentBrightnessSettingToBacklight(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
if (!auto_brightness_enabled) {
|
||||
Renderer().SetCurrentBrightness(vr_mode_enabled ? current_vr_mode_brightness
|
||||
: current_brightness);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void GetBrightnessSettingAppliedToBacklight(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(Renderer().GetCurrentResultantBrightness());
|
||||
}
|
||||
|
||||
void SwitchBacklightOn(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto fade_time = rp.PopRaw<u64>();
|
||||
|
||||
LOG_DEBUG(Service_LBL, "called, fade_time={:016X}", fade_time);
|
||||
|
||||
Renderer().SetBacklightStatus(true, fade_time);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void SwitchBacklightOff(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto fade_time = rp.PopRaw<u64>();
|
||||
|
||||
LOG_DEBUG(Service_LBL, "called, fade_time={:016X}", fade_time);
|
||||
|
||||
Renderer().SetBacklightStatus(false, fade_time);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void GetBacklightSwitchStatus(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(Renderer().GetBacklightStatus());
|
||||
}
|
||||
|
||||
void EnableDimming(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
dimming_enabled = true;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void DisableDimming(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "callled");
|
||||
|
||||
dimming_enabled = false;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void IsDimmingEnabled(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(dimming_enabled);
|
||||
}
|
||||
|
||||
void EnableAutoBrightnessControl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
auto_brightness_enabled = true;
|
||||
Renderer().SetCurrentBrightness(GetAutoBrightnessValue());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void DisableAutoBrightnessControl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
auto_brightness_enabled = false;
|
||||
Renderer().SetCurrentBrightness(current_brightness);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void IsAutoBrightnessControlEnabled(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(auto_brightness_enabled);
|
||||
}
|
||||
|
||||
void SetCurrentBrightnessSettingForVrMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto value = rp.PopRaw<f32>();
|
||||
|
||||
LOG_DEBUG(Service_LBL, "called, value={:.3f}", value);
|
||||
|
||||
current_vr_mode_brightness = std::clamp(value, 0.0f, 1.0f);
|
||||
|
||||
if (vr_mode_enabled && !auto_brightness_enabled) {
|
||||
Renderer().SetCurrentBrightness(value);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void GetCurrentBrightnessSettingForVrMode(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(current_vr_mode_brightness);
|
||||
}
|
||||
|
||||
void EnableVrMode(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_LBL, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
if (!vr_mode_enabled && !auto_brightness_enabled &&
|
||||
current_brightness != current_vr_mode_brightness) {
|
||||
Renderer().SetCurrentBrightness(current_vr_mode_brightness);
|
||||
}
|
||||
|
||||
vr_mode_enabled = true;
|
||||
}
|
||||
|
||||
@@ -69,6 +267,11 @@ private:
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
if (vr_mode_enabled && !auto_brightness_enabled &&
|
||||
current_brightness != current_vr_mode_brightness) {
|
||||
Renderer().SetCurrentBrightness(current_brightness);
|
||||
}
|
||||
|
||||
vr_mode_enabled = false;
|
||||
}
|
||||
|
||||
@@ -80,9 +283,27 @@ private:
|
||||
rb.Push(vr_mode_enabled);
|
||||
}
|
||||
|
||||
bool auto_brightness_enabled = false;
|
||||
bool dimming_enabled = true;
|
||||
|
||||
f32 current_brightness = GetAutoBrightnessValue();
|
||||
f32 current_vr_mode_brightness = GetAutoBrightnessValue();
|
||||
|
||||
bool vr_mode_enabled = false;
|
||||
};
|
||||
|
||||
void RequestLoadCurrentSetting(SM::ServiceManager& sm) {
|
||||
if (&sm == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto lbl = sm.GetService<LBL>("lbl");
|
||||
|
||||
if (lbl) {
|
||||
lbl->LoadFromSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& sm) {
|
||||
std::make_shared<LBL>()->InstallAsService(sm);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ class ServiceManager;
|
||||
|
||||
namespace Service::LBL {
|
||||
|
||||
// Requests the LBL service passed to load brightness values from Settings
|
||||
void RequestLoadCurrentSetting(SM::ServiceManager& sm);
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& sm);
|
||||
|
||||
} // namespace Service::LBL
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
#include "core/hle/service/nifm/nifm.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::NIFM {
|
||||
|
||||
@@ -88,7 +89,12 @@ private:
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushEnum(RequestState::Connected);
|
||||
|
||||
if (Settings::values.bcat_backend == "none") {
|
||||
rb.PushEnum(RequestState::NotSubmitted);
|
||||
} else {
|
||||
rb.PushEnum(RequestState::Connected);
|
||||
}
|
||||
}
|
||||
|
||||
void GetResult(Kernel::HLERequestContext& ctx) {
|
||||
@@ -196,14 +202,22 @@ private:
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(1);
|
||||
if (Settings::values.bcat_backend == "none") {
|
||||
rb.Push<u8>(0);
|
||||
} else {
|
||||
rb.Push<u8>(1);
|
||||
}
|
||||
}
|
||||
void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(1);
|
||||
if (Settings::values.bcat_backend == "none") {
|
||||
rb.Push<u8>(0);
|
||||
} else {
|
||||
rb.Push<u8>(1);
|
||||
}
|
||||
}
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
||||
#include "core/perf_stats.h"
|
||||
@@ -38,7 +39,10 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3
|
||||
transform, crop_rect};
|
||||
|
||||
system.GetPerfStats().EndGameFrame();
|
||||
system.GetPerfStats().EndSystemFrame();
|
||||
system.GPU().SwapBuffers(&framebuffer);
|
||||
system.FrameLimiter().DoFrameLimiting(system.CoreTiming().GetGlobalTimeUs());
|
||||
system.GetPerfStats().BeginSystemFrame();
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -63,16 +63,26 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>&
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
|
||||
u32 event_id = params.value & 0x00FF;
|
||||
|
||||
if (event_id >= MaxNvEvents) {
|
||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
|
||||
auto event = events_interface.events[event_id];
|
||||
auto& gpu = system.GPU();
|
||||
// This is mostly to take into account unimplemented features. As synced
|
||||
// gpu is always synced.
|
||||
if (!gpu.IsAsync()) {
|
||||
event.writable->Signal();
|
||||
return NvResult::Success;
|
||||
}
|
||||
auto lock = gpu.LockSync();
|
||||
const u32 current_syncpoint_value = gpu.GetSyncpointValue(params.syncpt_id);
|
||||
const s32 diff = current_syncpoint_value - params.threshold;
|
||||
if (diff >= 0) {
|
||||
event.writable->Signal();
|
||||
params.value = current_syncpoint_value;
|
||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||
return NvResult::Success;
|
||||
@@ -88,27 +98,6 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>&
|
||||
return NvResult::Timeout;
|
||||
}
|
||||
|
||||
u32 event_id;
|
||||
if (is_async) {
|
||||
event_id = params.value & 0x00FF;
|
||||
if (event_id >= MaxNvEvents) {
|
||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||
return NvResult::BadParameter;
|
||||
}
|
||||
} else {
|
||||
if (ctrl.fresh_call) {
|
||||
const auto result = events_interface.GetFreeEvent();
|
||||
if (result) {
|
||||
event_id = *result;
|
||||
} else {
|
||||
LOG_CRITICAL(Service_NVDRV, "No Free Events available!");
|
||||
event_id = params.value & 0x00FF;
|
||||
}
|
||||
} else {
|
||||
event_id = ctrl.event_id;
|
||||
}
|
||||
}
|
||||
|
||||
EventState status = events_interface.status[event_id];
|
||||
if (event_id < MaxNvEvents || status == EventState::Free || status == EventState::Registered) {
|
||||
events_interface.SetEventStatus(event_id, EventState::Waiting);
|
||||
@@ -120,7 +109,7 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>&
|
||||
params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000;
|
||||
}
|
||||
params.value |= event_id;
|
||||
events_interface.events[event_id].writable->Clear();
|
||||
event.writable->Clear();
|
||||
gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value);
|
||||
if (!is_async && ctrl.fresh_call) {
|
||||
ctrl.must_delay = true;
|
||||
|
||||
@@ -22,6 +22,18 @@ u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std::
|
||||
switch (static_cast<IoctlCommand>(command.raw)) {
|
||||
case IoctlCommand::IocSetNVMAPfdCommand:
|
||||
return SetNVMAPfd(input, output);
|
||||
case IoctlCommand::IocSubmit:
|
||||
return Submit(input, output);
|
||||
case IoctlCommand::IocGetSyncpoint:
|
||||
return GetSyncpoint(input, output);
|
||||
case IoctlCommand::IocGetWaitbase:
|
||||
return GetWaitbase(input, output);
|
||||
case IoctlCommand::IocMapBuffer:
|
||||
return MapBuffer(input, output);
|
||||
case IoctlCommand::IocMapBufferEx:
|
||||
return MapBufferEx(input, output);
|
||||
case IoctlCommand::IocUnmapBufferEx:
|
||||
return UnmapBufferEx(input, output);
|
||||
}
|
||||
|
||||
UNIMPLEMENTED_MSG("Unimplemented ioctl");
|
||||
@@ -30,11 +42,67 @@ u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std::
|
||||
|
||||
u32 nvhost_nvdec::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlSetNvmapFD params{};
|
||||
std::memcpy(¶ms, input.data(), input.size());
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlSetNvmapFD));
|
||||
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
|
||||
|
||||
nvmap_fd = params.nvmap_fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::Submit(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlSubmit params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlSubmit));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlGetSyncpoint params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint));
|
||||
LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
|
||||
params.value = 0; // Seems to be hard coded at 0
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetSyncpoint));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlGetWaitbase params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase));
|
||||
LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
|
||||
params.value = 0; // Seems to be hard coded at 0
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetWaitbase));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::MapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlMapBuffer params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2,
|
||||
params.address_1);
|
||||
params.address_1 = 0;
|
||||
params.address_2 = 0;
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlMapBufferEx params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlMapBufferEx));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2,
|
||||
params.address_1);
|
||||
params.address_1 = 0;
|
||||
params.address_2 = 0;
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBufferEx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::UnmapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlUnmapBufferEx params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlUnmapBufferEx));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlUnmapBufferEx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -23,16 +23,66 @@ public:
|
||||
private:
|
||||
enum class IoctlCommand : u32_le {
|
||||
IocSetNVMAPfdCommand = 0x40044801,
|
||||
IocSubmit = 0xC0400001,
|
||||
IocGetSyncpoint = 0xC0080002,
|
||||
IocGetWaitbase = 0xC0080003,
|
||||
IocMapBuffer = 0xC01C0009,
|
||||
IocMapBufferEx = 0xC0A40009,
|
||||
IocUnmapBufferEx = 0xC0A4000A,
|
||||
};
|
||||
|
||||
struct IoctlSetNvmapFD {
|
||||
u32_le nvmap_fd;
|
||||
};
|
||||
static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
|
||||
static_assert(sizeof(IoctlSetNvmapFD) == 0x4, "IoctlSetNvmapFD is incorrect size");
|
||||
|
||||
struct IoctlSubmit {
|
||||
INSERT_PADDING_BYTES(0x40); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlSubmit) == 0x40, "IoctlSubmit has incorrect size");
|
||||
|
||||
struct IoctlGetSyncpoint {
|
||||
u32 unknown; // seems to be ignored? Nintendo added this
|
||||
u32 value;
|
||||
};
|
||||
static_assert(sizeof(IoctlGetSyncpoint) == 0x08, "IoctlGetSyncpoint has incorrect size");
|
||||
|
||||
struct IoctlGetWaitbase {
|
||||
u32 unknown; // seems to be ignored? Nintendo added this
|
||||
u32 value;
|
||||
};
|
||||
static_assert(sizeof(IoctlGetWaitbase) == 0x08, "IoctlGetWaitbase has incorrect size");
|
||||
|
||||
struct IoctlMapBuffer {
|
||||
u32 unknown;
|
||||
u32 address_1;
|
||||
u32 address_2;
|
||||
INSERT_PADDING_BYTES(0x10); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlMapBuffer) == 0x1C, "IoctlMapBuffer is incorrect size");
|
||||
|
||||
struct IoctlMapBufferEx {
|
||||
u32 unknown;
|
||||
u32 address_1;
|
||||
u32 address_2;
|
||||
INSERT_PADDING_BYTES(0x98); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlMapBufferEx) == 0xA4, "IoctlMapBufferEx has incorrect size");
|
||||
|
||||
struct IoctlUnmapBufferEx {
|
||||
INSERT_PADDING_BYTES(0xA4); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlUnmapBufferEx) == 0xA4, "IoctlUnmapBufferEx has incorrect size");
|
||||
|
||||
u32_le nvmap_fd{};
|
||||
|
||||
u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 Submit(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 MapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 UnmapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
};
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -22,6 +22,18 @@ u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::ve
|
||||
switch (static_cast<IoctlCommand>(command.raw)) {
|
||||
case IoctlCommand::IocSetNVMAPfdCommand:
|
||||
return SetNVMAPfd(input, output);
|
||||
case IoctlCommand::IocSubmit:
|
||||
return Submit(input, output);
|
||||
case IoctlCommand::IocGetSyncpoint:
|
||||
return GetSyncpoint(input, output);
|
||||
case IoctlCommand::IocGetWaitbase:
|
||||
return GetWaitbase(input, output);
|
||||
case IoctlCommand::IocMapBuffer:
|
||||
return MapBuffer(input, output);
|
||||
case IoctlCommand::IocMapBufferEx:
|
||||
return MapBuffer(input, output);
|
||||
case IoctlCommand::IocUnmapBufferEx:
|
||||
return UnmapBufferEx(input, output);
|
||||
}
|
||||
|
||||
UNIMPLEMENTED_MSG("Unimplemented ioctl");
|
||||
@@ -30,11 +42,67 @@ u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::ve
|
||||
|
||||
u32 nvhost_vic::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlSetNvmapFD params{};
|
||||
std::memcpy(¶ms, input.data(), input.size());
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlSetNvmapFD));
|
||||
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
|
||||
|
||||
nvmap_fd = params.nvmap_fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::Submit(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlSubmit params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlSubmit));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlGetSyncpoint params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint));
|
||||
LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
|
||||
params.value = 0; // Seems to be hard coded at 0
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetSyncpoint));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlGetWaitbase params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase));
|
||||
LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
|
||||
params.value = 0; // Seems to be hard coded at 0
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetWaitbase));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::MapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlMapBuffer params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2,
|
||||
params.address_1);
|
||||
params.address_1 = 0;
|
||||
params.address_2 = 0;
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlMapBufferEx params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlMapBufferEx));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2,
|
||||
params.address_1);
|
||||
params.address_1 = 0;
|
||||
params.address_2 = 0;
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBufferEx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::UnmapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlUnmapBufferEx params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlUnmapBufferEx));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlUnmapBufferEx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -23,6 +23,12 @@ public:
|
||||
private:
|
||||
enum class IoctlCommand : u32_le {
|
||||
IocSetNVMAPfdCommand = 0x40044801,
|
||||
IocSubmit = 0xC0400001,
|
||||
IocGetSyncpoint = 0xC0080002,
|
||||
IocGetWaitbase = 0xC0080003,
|
||||
IocMapBuffer = 0xC01C0009,
|
||||
IocMapBufferEx = 0xC03C0009,
|
||||
IocUnmapBufferEx = 0xC03C000A,
|
||||
};
|
||||
|
||||
struct IoctlSetNvmapFD {
|
||||
@@ -30,9 +36,53 @@ private:
|
||||
};
|
||||
static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
|
||||
|
||||
struct IoctlSubmit {
|
||||
INSERT_PADDING_BYTES(0x40); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlSubmit) == 0x40, "IoctlSubmit is incorrect size");
|
||||
|
||||
struct IoctlGetSyncpoint {
|
||||
u32 unknown; // seems to be ignored? Nintendo added this
|
||||
u32 value;
|
||||
};
|
||||
static_assert(sizeof(IoctlGetSyncpoint) == 0x8, "IoctlGetSyncpoint is incorrect size");
|
||||
|
||||
struct IoctlGetWaitbase {
|
||||
u32 unknown; // seems to be ignored? Nintendo added this
|
||||
u32 value;
|
||||
};
|
||||
static_assert(sizeof(IoctlGetWaitbase) == 0x8, "IoctlGetWaitbase is incorrect size");
|
||||
|
||||
struct IoctlMapBuffer {
|
||||
u32 unknown;
|
||||
u32 address_1;
|
||||
u32 address_2;
|
||||
INSERT_PADDING_BYTES(0x10); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlMapBuffer) == 0x1C, "IoctlMapBuffer is incorrect size");
|
||||
|
||||
struct IoctlMapBufferEx {
|
||||
u32 unknown;
|
||||
u32 address_1;
|
||||
u32 address_2;
|
||||
INSERT_PADDING_BYTES(0x30); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlMapBufferEx) == 0x3C, "IoctlMapBufferEx is incorrect size");
|
||||
|
||||
struct IoctlUnmapBufferEx {
|
||||
INSERT_PADDING_BYTES(0x3C); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlUnmapBufferEx) == 0x3C, "IoctlUnmapBufferEx is incorrect size");
|
||||
|
||||
u32_le nvmap_fd{};
|
||||
|
||||
u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 Submit(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 MapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 UnmapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
};
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -134,7 +134,9 @@ void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
if (event_id < MaxNvEvents) {
|
||||
rb.PushCopyObjects(nvdrv->GetEvent(event_id));
|
||||
auto event = nvdrv->GetEvent(event_id);
|
||||
event->Clear();
|
||||
rb.PushCopyObjects(event);
|
||||
rb.Push<u32>(NvResult::Success);
|
||||
} else {
|
||||
rb.Push<u32>(0);
|
||||
|
||||
@@ -40,8 +40,8 @@ Module::Module(Core::System& system) {
|
||||
auto& kernel = system.Kernel();
|
||||
for (u32 i = 0; i < MaxNvEvents; i++) {
|
||||
std::string event_label = fmt::format("NVDRV::NvEvent_{}", i);
|
||||
events_interface.events[i] = Kernel::WritableEvent::CreateEventPair(
|
||||
kernel, Kernel::ResetType::Automatic, event_label);
|
||||
events_interface.events[i] =
|
||||
Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, event_label);
|
||||
events_interface.status[i] = EventState::Free;
|
||||
events_interface.registered[i] = false;
|
||||
}
|
||||
|
||||
@@ -187,14 +187,18 @@ void NVFlinger::Compose() {
|
||||
MicroProfileFlip();
|
||||
|
||||
if (!buffer) {
|
||||
// There was no queued buffer to draw, render previous frame
|
||||
system.GetPerfStats().EndGameFrame();
|
||||
system.GPU().SwapBuffers({});
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& igbp_buffer = buffer->get().igbp_buffer;
|
||||
|
||||
const auto& gpu = system.GPU();
|
||||
const auto& multi_fence = buffer->get().multi_fence;
|
||||
for (u32 fence_id = 0; fence_id < multi_fence.num_fences; fence_id++) {
|
||||
const auto& fence = multi_fence.fences[fence_id];
|
||||
gpu.WaitFence(fence.id, fence.value);
|
||||
}
|
||||
|
||||
// Now send the buffer to the GPU for drawing.
|
||||
// TODO(Subv): Support more than just disp0. The display device selection is probably based
|
||||
// on which display we're drawing (Default, Internal, External, etc)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "core/core.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/lbl/lbl.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
@@ -70,6 +72,7 @@ void Apply() {
|
||||
auto& system_instance = Core::System::GetInstance();
|
||||
if (system_instance.IsPoweredOn()) {
|
||||
system_instance.Renderer().RefreshBaseSettings();
|
||||
Service::LBL::RequestLoadCurrentSetting(system_instance.ServiceManager());
|
||||
}
|
||||
|
||||
Service::HID::ReloadInputDevices();
|
||||
|
||||
@@ -428,6 +428,8 @@ struct Values {
|
||||
float bg_green;
|
||||
float bg_blue;
|
||||
|
||||
float backlight_brightness = 0.5f;
|
||||
|
||||
std::string log_filter;
|
||||
|
||||
bool use_dev_keys;
|
||||
|
||||
@@ -6,6 +6,7 @@ add_library(video_core STATIC
|
||||
dma_pusher.h
|
||||
debug_utils/debug_utils.cpp
|
||||
debug_utils/debug_utils.h
|
||||
engines/const_buffer_engine_interface.h
|
||||
engines/const_buffer_info.h
|
||||
engines/engine_upload.cpp
|
||||
engines/engine_upload.h
|
||||
@@ -107,10 +108,12 @@ add_library(video_core STATIC
|
||||
shader/decode/other.cpp
|
||||
shader/ast.cpp
|
||||
shader/ast.h
|
||||
shader/control_flow.cpp
|
||||
shader/control_flow.h
|
||||
shader/compiler_settings.cpp
|
||||
shader/compiler_settings.h
|
||||
shader/const_buffer_locker.cpp
|
||||
shader/const_buffer_locker.h
|
||||
shader/control_flow.cpp
|
||||
shader/control_flow.h
|
||||
shader/decode.cpp
|
||||
shader/expr.cpp
|
||||
shader/expr.h
|
||||
|
||||
116
src/video_core/engines/const_buffer_engine_interface.h
Normal file
116
src/video_core/engines/const_buffer_engine_interface.h
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/shader_bytecode.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
enum class ShaderType : u32 {
|
||||
Vertex = 0,
|
||||
TesselationControl = 1,
|
||||
TesselationEval = 2,
|
||||
Geometry = 3,
|
||||
Fragment = 4,
|
||||
Compute = 5,
|
||||
};
|
||||
|
||||
struct SamplerDescriptor {
|
||||
union {
|
||||
BitField<0, 20, Tegra::Shader::TextureType> texture_type;
|
||||
BitField<20, 1, u32> is_array;
|
||||
BitField<21, 1, u32> is_buffer;
|
||||
BitField<22, 1, u32> is_shadow;
|
||||
u32 raw{};
|
||||
};
|
||||
|
||||
bool operator==(const SamplerDescriptor& rhs) const noexcept {
|
||||
return raw == rhs.raw;
|
||||
}
|
||||
|
||||
static SamplerDescriptor FromTicTexture(Tegra::Texture::TextureType tic_texture_type) {
|
||||
SamplerDescriptor result;
|
||||
switch (tic_texture_type) {
|
||||
case Tegra::Texture::TextureType::Texture1D:
|
||||
result.texture_type.Assign(Tegra::Shader::TextureType::Texture1D);
|
||||
result.is_array.Assign(0);
|
||||
result.is_buffer.Assign(0);
|
||||
result.is_shadow.Assign(0);
|
||||
return result;
|
||||
case Tegra::Texture::TextureType::Texture2D:
|
||||
result.texture_type.Assign(Tegra::Shader::TextureType::Texture2D);
|
||||
result.is_array.Assign(0);
|
||||
result.is_buffer.Assign(0);
|
||||
result.is_shadow.Assign(0);
|
||||
return result;
|
||||
case Tegra::Texture::TextureType::Texture3D:
|
||||
result.texture_type.Assign(Tegra::Shader::TextureType::Texture3D);
|
||||
result.is_array.Assign(0);
|
||||
result.is_buffer.Assign(0);
|
||||
result.is_shadow.Assign(0);
|
||||
return result;
|
||||
case Tegra::Texture::TextureType::TextureCubemap:
|
||||
result.texture_type.Assign(Tegra::Shader::TextureType::TextureCube);
|
||||
result.is_array.Assign(0);
|
||||
result.is_buffer.Assign(0);
|
||||
result.is_shadow.Assign(0);
|
||||
return result;
|
||||
case Tegra::Texture::TextureType::Texture1DArray:
|
||||
result.texture_type.Assign(Tegra::Shader::TextureType::Texture1D);
|
||||
result.is_array.Assign(1);
|
||||
result.is_buffer.Assign(0);
|
||||
result.is_shadow.Assign(0);
|
||||
return result;
|
||||
case Tegra::Texture::TextureType::Texture2DArray:
|
||||
result.texture_type.Assign(Tegra::Shader::TextureType::Texture2D);
|
||||
result.is_array.Assign(1);
|
||||
result.is_buffer.Assign(0);
|
||||
result.is_shadow.Assign(0);
|
||||
return result;
|
||||
case Tegra::Texture::TextureType::Texture1DBuffer: {
|
||||
result.texture_type.Assign(Tegra::Shader::TextureType::Texture1D);
|
||||
result.is_array.Assign(0);
|
||||
result.is_buffer.Assign(1);
|
||||
result.is_shadow.Assign(0);
|
||||
return result;
|
||||
}
|
||||
case Tegra::Texture::TextureType::Texture2DNoMipmap:
|
||||
result.texture_type.Assign(Tegra::Shader::TextureType::Texture2D);
|
||||
result.is_array.Assign(0);
|
||||
result.is_buffer.Assign(0);
|
||||
result.is_shadow.Assign(0);
|
||||
return result;
|
||||
case Tegra::Texture::TextureType::TextureCubeArray:
|
||||
result.texture_type.Assign(Tegra::Shader::TextureType::TextureCube);
|
||||
result.is_array.Assign(1);
|
||||
result.is_buffer.Assign(0);
|
||||
result.is_shadow.Assign(0);
|
||||
return result;
|
||||
default:
|
||||
result.texture_type.Assign(Tegra::Shader::TextureType::Texture2D);
|
||||
result.is_array.Assign(0);
|
||||
result.is_buffer.Assign(0);
|
||||
result.is_shadow.Assign(0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<SamplerDescriptor>);
|
||||
|
||||
class ConstBufferEngineInterface {
|
||||
public:
|
||||
virtual ~ConstBufferEngineInterface() {}
|
||||
virtual u32 AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const = 0;
|
||||
virtual SamplerDescriptor AccessBoundSampler(ShaderType stage, u64 offset) const = 0;
|
||||
virtual SamplerDescriptor AccessBindlessSampler(ShaderType stage, u64 const_buffer,
|
||||
u64 offset) const = 0;
|
||||
virtual u32 GetBoundBuffer() const = 0;
|
||||
};
|
||||
|
||||
} // namespace Tegra::Engines
|
||||
@@ -47,10 +47,20 @@ void Fermi2D::HandleSurfaceCopy() {
|
||||
src_blit_x2 = static_cast<u32>((regs.blit_src_x >> 32) + regs.blit_dst_width);
|
||||
src_blit_y2 = static_cast<u32>((regs.blit_src_y >> 32) + regs.blit_dst_height);
|
||||
}
|
||||
const Common::Rectangle<u32> src_rect{src_blit_x1, src_blit_y1, src_blit_x2, src_blit_y2};
|
||||
const Common::Rectangle<u32> dst_rect{regs.blit_dst_x, regs.blit_dst_y,
|
||||
regs.blit_dst_x + regs.blit_dst_width,
|
||||
regs.blit_dst_y + regs.blit_dst_height};
|
||||
const u32 dst_blit_x2 = regs.blit_dst_x + regs.blit_dst_width;
|
||||
const u32 dst_blit_y2 = regs.blit_dst_x + regs.blit_dst_height;
|
||||
const u32 excess_src_x2 = std::max<s32>(0, dst_blit_x2 - regs.dst.width);
|
||||
const u32 excess_src_y2 = std::max<s32>(0, dst_blit_y2 - regs.dst.height);
|
||||
const u32 excess_dst_x2 = std::max<s32>(0, src_blit_x2 - regs.src.width);
|
||||
const u32 excess_dst_y2 = std::max<s32>(0, src_blit_y2 - regs.src.height);
|
||||
|
||||
const Common::Rectangle<u32> src_rect{
|
||||
src_blit_x1, src_blit_y1, std::min<u32>(regs.src.width, src_blit_x2) - excess_src_x2,
|
||||
std::min<u32>(regs.src.height, src_blit_y2) - excess_src_y2};
|
||||
const Common::Rectangle<u32> dst_rect{
|
||||
regs.blit_dst_x, regs.blit_dst_y,
|
||||
std::min<u32>(regs.dst.width, dst_blit_x2) - excess_dst_x2,
|
||||
std::min<u32>(regs.dst.height, dst_blit_y2) - excess_dst_y2};
|
||||
Config copy_config;
|
||||
copy_config.operation = regs.operation;
|
||||
copy_config.filter = regs.blit_control.filter;
|
||||
|
||||
@@ -70,13 +70,31 @@ Texture::FullTextureInfo KeplerCompute::GetTextureInfo(const Texture::TextureHan
|
||||
GetTSCEntry(tex_handle.tsc_id)};
|
||||
}
|
||||
|
||||
u32 KeplerCompute::AccessConstBuffer32(u64 const_buffer, u64 offset) const {
|
||||
u32 KeplerCompute::AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const {
|
||||
ASSERT(stage == ShaderType::Compute);
|
||||
const auto& buffer = launch_description.const_buffer_config[const_buffer];
|
||||
u32 result;
|
||||
std::memcpy(&result, memory_manager.GetPointer(buffer.Address() + offset), sizeof(u32));
|
||||
return result;
|
||||
}
|
||||
|
||||
SamplerDescriptor KeplerCompute::AccessBoundSampler(ShaderType stage, u64 offset) const {
|
||||
return AccessBindlessSampler(stage, regs.tex_cb_index, offset * sizeof(Texture::TextureHandle));
|
||||
}
|
||||
|
||||
SamplerDescriptor KeplerCompute::AccessBindlessSampler(ShaderType stage, u64 const_buffer,
|
||||
u64 offset) const {
|
||||
ASSERT(stage == ShaderType::Compute);
|
||||
const auto& tex_info_buffer = launch_description.const_buffer_config[const_buffer];
|
||||
const GPUVAddr tex_info_address = tex_info_buffer.Address() + offset;
|
||||
|
||||
const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)};
|
||||
const Texture::FullTextureInfo tex_info = GetTextureInfo(tex_handle, offset);
|
||||
SamplerDescriptor result = SamplerDescriptor::FromTicTexture(tex_info.tic.texture_type.Value());
|
||||
result.is_shadow.Assign(tex_info.tsc.depth_compare_enabled.Value());
|
||||
return result;
|
||||
}
|
||||
|
||||
void KeplerCompute::ProcessLaunch() {
|
||||
const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address();
|
||||
memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/const_buffer_engine_interface.h"
|
||||
#include "video_core/engines/engine_upload.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
@@ -37,7 +38,7 @@ namespace Tegra::Engines {
|
||||
#define KEPLER_COMPUTE_REG_INDEX(field_name) \
|
||||
(offsetof(Tegra::Engines::KeplerCompute::Regs, field_name) / sizeof(u32))
|
||||
|
||||
class KeplerCompute final {
|
||||
class KeplerCompute final : public ConstBufferEngineInterface {
|
||||
public:
|
||||
explicit KeplerCompute(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
MemoryManager& memory_manager);
|
||||
@@ -201,7 +202,16 @@ public:
|
||||
Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle,
|
||||
std::size_t offset) const;
|
||||
|
||||
u32 AccessConstBuffer32(u64 const_buffer, u64 offset) const;
|
||||
u32 AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const override;
|
||||
|
||||
SamplerDescriptor AccessBoundSampler(ShaderType stage, u64 offset) const override;
|
||||
|
||||
SamplerDescriptor AccessBindlessSampler(ShaderType stage, u64 const_buffer,
|
||||
u64 offset) const override;
|
||||
|
||||
u32 GetBoundBuffer() const override {
|
||||
return regs.tex_cb_index;
|
||||
}
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
|
||||
@@ -846,7 +846,8 @@ void Maxwell3D::ProcessClearBuffers() {
|
||||
rasterizer.Clear();
|
||||
}
|
||||
|
||||
u32 Maxwell3D::AccessConstBuffer32(Regs::ShaderStage stage, u64 const_buffer, u64 offset) const {
|
||||
u32 Maxwell3D::AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const {
|
||||
ASSERT(stage != ShaderType::Compute);
|
||||
const auto& shader_stage = state.shader_stages[static_cast<std::size_t>(stage)];
|
||||
const auto& buffer = shader_stage.const_buffers[const_buffer];
|
||||
u32 result;
|
||||
@@ -854,4 +855,22 @@ u32 Maxwell3D::AccessConstBuffer32(Regs::ShaderStage stage, u64 const_buffer, u6
|
||||
return result;
|
||||
}
|
||||
|
||||
SamplerDescriptor Maxwell3D::AccessBoundSampler(ShaderType stage, u64 offset) const {
|
||||
return AccessBindlessSampler(stage, regs.tex_cb_index, offset * sizeof(Texture::TextureHandle));
|
||||
}
|
||||
|
||||
SamplerDescriptor Maxwell3D::AccessBindlessSampler(ShaderType stage, u64 const_buffer,
|
||||
u64 offset) const {
|
||||
ASSERT(stage != ShaderType::Compute);
|
||||
const auto& shader = state.shader_stages[static_cast<std::size_t>(stage)];
|
||||
const auto& tex_info_buffer = shader.const_buffers[const_buffer];
|
||||
const GPUVAddr tex_info_address = tex_info_buffer.address + offset;
|
||||
|
||||
const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)};
|
||||
const Texture::FullTextureInfo tex_info = GetTextureInfo(tex_handle, offset);
|
||||
SamplerDescriptor result = SamplerDescriptor::FromTicTexture(tex_info.tic.texture_type.Value());
|
||||
result.is_shadow.Assign(tex_info.tsc.depth_compare_enabled.Value());
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Tegra::Engines
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/engines/const_buffer_engine_interface.h"
|
||||
#include "video_core/engines/const_buffer_info.h"
|
||||
#include "video_core/engines/engine_upload.h"
|
||||
#include "video_core/gpu.h"
|
||||
@@ -44,7 +45,7 @@ namespace Tegra::Engines {
|
||||
#define MAXWELL3D_REG_INDEX(field_name) \
|
||||
(offsetof(Tegra::Engines::Maxwell3D::Regs, field_name) / sizeof(u32))
|
||||
|
||||
class Maxwell3D final {
|
||||
class Maxwell3D final : public ConstBufferEngineInterface {
|
||||
public:
|
||||
explicit Maxwell3D(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
MemoryManager& memory_manager);
|
||||
@@ -1257,7 +1258,16 @@ public:
|
||||
/// Returns the texture information for a specific texture in a specific shader stage.
|
||||
Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, std::size_t offset) const;
|
||||
|
||||
u32 AccessConstBuffer32(Regs::ShaderStage stage, u64 const_buffer, u64 offset) const;
|
||||
u32 AccessConstBuffer32(ShaderType stage, u64 const_buffer, u64 offset) const override;
|
||||
|
||||
SamplerDescriptor AccessBoundSampler(ShaderType stage, u64 offset) const override;
|
||||
|
||||
SamplerDescriptor AccessBindlessSampler(ShaderType stage, u64 const_buffer,
|
||||
u64 offset) const override;
|
||||
|
||||
u32 GetBoundBuffer() const override {
|
||||
return regs.tex_cb_index;
|
||||
}
|
||||
|
||||
/// Memory for macro code - it's undetermined how big this is, however 1MB is much larger than
|
||||
/// we've seen used.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/memory.h"
|
||||
@@ -17,6 +18,8 @@
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
|
||||
|
||||
GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer, bool is_async)
|
||||
: system{system}, renderer{renderer}, is_async{is_async} {
|
||||
auto& rasterizer{renderer.Rasterizer()};
|
||||
@@ -63,6 +66,16 @@ const DmaPusher& GPU::DmaPusher() const {
|
||||
return *dma_pusher;
|
||||
}
|
||||
|
||||
void GPU::WaitFence(u32 syncpoint_id, u32 value) const {
|
||||
// Synced GPU, is always in sync
|
||||
if (!is_async) {
|
||||
return;
|
||||
}
|
||||
MICROPROFILE_SCOPE(GPU_wait);
|
||||
while (syncpoints[syncpoint_id].load(std::memory_order_relaxed) < value) {
|
||||
}
|
||||
}
|
||||
|
||||
void GPU::IncrementSyncPoint(const u32 syncpoint_id) {
|
||||
syncpoints[syncpoint_id]++;
|
||||
std::lock_guard lock{sync_mutex};
|
||||
|
||||
@@ -177,6 +177,12 @@ public:
|
||||
/// Returns a reference to the GPU DMA pusher.
|
||||
Tegra::DmaPusher& DmaPusher();
|
||||
|
||||
// Waits for the GPU to finish working
|
||||
virtual void WaitIdle() const = 0;
|
||||
|
||||
/// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame.
|
||||
void WaitFence(u32 syncpoint_id, u32 value) const;
|
||||
|
||||
void IncrementSyncPoint(u32 syncpoint_id);
|
||||
|
||||
u32 GetSyncpointValue(u32 syncpoint_id) const;
|
||||
|
||||
@@ -44,4 +44,8 @@ void GPUAsynch::TriggerCpuInterrupt(const u32 syncpoint_id, const u32 value) con
|
||||
interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value);
|
||||
}
|
||||
|
||||
void GPUAsynch::WaitIdle() const {
|
||||
gpu_thread.WaitIdle();
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
@@ -25,6 +25,7 @@ public:
|
||||
void FlushRegion(CacheAddr addr, u64 size) override;
|
||||
void InvalidateRegion(CacheAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
|
||||
void WaitIdle() const override;
|
||||
|
||||
protected:
|
||||
void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const override;
|
||||
|
||||
@@ -24,6 +24,7 @@ public:
|
||||
void FlushRegion(CacheAddr addr, u64 size) override;
|
||||
void InvalidateRegion(CacheAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
|
||||
void WaitIdle() const override {}
|
||||
|
||||
protected:
|
||||
void TriggerCpuInterrupt([[maybe_unused]] u32 syncpoint_id,
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/frontend/scope_acquire_window_context.h"
|
||||
#include "video_core/dma_pusher.h"
|
||||
#include "video_core/gpu.h"
|
||||
@@ -68,14 +66,10 @@ ThreadManager::~ThreadManager() {
|
||||
|
||||
void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher) {
|
||||
thread = std::thread{RunThread, std::ref(renderer), std::ref(dma_pusher), std::ref(state)};
|
||||
synchronization_event = system.CoreTiming().RegisterEvent(
|
||||
"GPUThreadSynch", [this](u64 fence, s64) { state.WaitForSynchronization(fence); });
|
||||
}
|
||||
|
||||
void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
|
||||
const u64 fence{PushCommand(SubmitListCommand(std::move(entries)))};
|
||||
const s64 synchronization_ticks{Core::Timing::usToCycles(std::chrono::microseconds{9000})};
|
||||
system.CoreTiming().ScheduleEvent(synchronization_ticks, synchronization_event, fence);
|
||||
PushCommand(SubmitListCommand(std::move(entries)));
|
||||
}
|
||||
|
||||
void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
|
||||
@@ -96,16 +90,15 @@ void ThreadManager::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
|
||||
InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
void ThreadManager::WaitIdle() const {
|
||||
while (state.last_fence > state.signaled_fence.load(std::memory_order_relaxed)) {
|
||||
}
|
||||
}
|
||||
|
||||
u64 ThreadManager::PushCommand(CommandData&& command_data) {
|
||||
const u64 fence{++state.last_fence};
|
||||
state.queue.Push(CommandDataContainer(std::move(command_data), fence));
|
||||
return fence;
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
|
||||
void SynchState::WaitForSynchronization(u64 fence) {
|
||||
while (signaled_fence.load() < fence)
|
||||
;
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::GPUThread
|
||||
|
||||
@@ -21,9 +21,6 @@ class DmaPusher;
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
namespace Timing {
|
||||
struct EventType;
|
||||
} // namespace Timing
|
||||
} // namespace Core
|
||||
|
||||
namespace VideoCommon::GPUThread {
|
||||
@@ -89,8 +86,6 @@ struct CommandDataContainer {
|
||||
struct SynchState final {
|
||||
std::atomic_bool is_running{true};
|
||||
|
||||
void WaitForSynchronization(u64 fence);
|
||||
|
||||
using CommandQueue = Common::SPSCQueue<CommandDataContainer>;
|
||||
CommandQueue queue;
|
||||
u64 last_fence{};
|
||||
@@ -121,6 +116,9 @@ public:
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
|
||||
void FlushAndInvalidateRegion(CacheAddr addr, u64 size);
|
||||
|
||||
// Wait until the gpu thread is idle.
|
||||
void WaitIdle() const;
|
||||
|
||||
private:
|
||||
/// Pushes a command to be executed by the GPU thread
|
||||
u64 PushCommand(CommandData&& command_data);
|
||||
@@ -128,7 +126,6 @@ private:
|
||||
private:
|
||||
SynchState state;
|
||||
Core::System& system;
|
||||
Core::Timing::EventType* synchronization_event{};
|
||||
std::thread thread;
|
||||
std::thread::id thread_id;
|
||||
};
|
||||
|
||||
@@ -93,6 +93,7 @@ static constexpr ConversionArray morton_to_linear_fns = {
|
||||
MortonCopy<true, PixelFormat::DXT23_SRGB>,
|
||||
MortonCopy<true, PixelFormat::DXT45_SRGB>,
|
||||
MortonCopy<true, PixelFormat::BC7U_SRGB>,
|
||||
MortonCopy<true, PixelFormat::R4G4B4A4U>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
|
||||
@@ -101,6 +102,16 @@ static constexpr ConversionArray morton_to_linear_fns = {
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_10X8>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_6X6>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_6X6_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_10X10>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_10X10_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_12X12>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_12X12_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X6>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X6_SRGB>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_6X5>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_6X5_SRGB>,
|
||||
MortonCopy<true, PixelFormat::Z32F>,
|
||||
MortonCopy<true, PixelFormat::Z16>,
|
||||
MortonCopy<true, PixelFormat::Z24S8>,
|
||||
@@ -162,6 +173,17 @@ static constexpr ConversionArray linear_to_morton_fns = {
|
||||
MortonCopy<false, PixelFormat::DXT23_SRGB>,
|
||||
MortonCopy<false, PixelFormat::DXT45_SRGB>,
|
||||
MortonCopy<false, PixelFormat::BC7U_SRGB>,
|
||||
MortonCopy<false, PixelFormat::R4G4B4A4U>,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
|
||||
@@ -40,4 +40,35 @@ void RendererBase::RequestScreenshot(void* data, std::function<void()> callback,
|
||||
renderer_settings.screenshot_requested = true;
|
||||
}
|
||||
|
||||
f32 RendererBase::GetCurrentResultantBrightness() const {
|
||||
return renderer_settings.current_brightness / 2.0f;
|
||||
}
|
||||
|
||||
void RendererBase::SetBacklightStatus(bool enabled, u64 fade_transition_time) {
|
||||
if (fade_transition_time == 0) {
|
||||
// Needed to ensure the renderer recognizes that a change must occur.
|
||||
fade_transition_time = 1;
|
||||
}
|
||||
|
||||
if (enabled && renderer_settings.current_brightness == 0) {
|
||||
renderer_settings.current_brightness = current_brightness_backup;
|
||||
renderer_settings.backlight_fade_time = fade_transition_time;
|
||||
} else if (!enabled && renderer_settings.current_brightness != 0) {
|
||||
current_brightness_backup = renderer_settings.current_brightness;
|
||||
renderer_settings.current_brightness = 0;
|
||||
renderer_settings.backlight_fade_time = fade_transition_time;
|
||||
}
|
||||
}
|
||||
|
||||
bool RendererBase::GetBacklightStatus() const {
|
||||
return renderer_settings.current_brightness != 0;
|
||||
}
|
||||
|
||||
void RendererBase::SetCurrentBrightness(f32 value) {
|
||||
if (value != renderer_settings.current_brightness) {
|
||||
renderer_settings.current_brightness = value * 2.0f;
|
||||
renderer_settings.backlight_fade_time = 1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
|
||||
@@ -28,6 +28,10 @@ struct RendererSettings {
|
||||
void* screenshot_bits;
|
||||
std::function<void()> screenshot_complete_callback;
|
||||
Layout::FramebufferLayout screenshot_framebuffer_layout;
|
||||
|
||||
// Backlight & Brightness
|
||||
std::atomic<f32> current_brightness{1.f};
|
||||
std::atomic<u64> backlight_fade_time{0};
|
||||
};
|
||||
|
||||
class RendererBase : NonCopyable {
|
||||
@@ -86,6 +90,17 @@ public:
|
||||
void RequestScreenshot(void* data, std::function<void()> callback,
|
||||
const Layout::FramebufferLayout& layout);
|
||||
|
||||
// Gets the current brightness, even if it has been changed from the set value. Most of the time
|
||||
// for yuzu this will simply match what was returned, but implementations are free to change the
|
||||
// value in settings.
|
||||
f32 GetCurrentResultantBrightness() const;
|
||||
|
||||
void SetBacklightStatus(bool enabled, u64 fade_transition_time);
|
||||
|
||||
bool GetBacklightStatus() const;
|
||||
|
||||
void SetCurrentBrightness(f32 value);
|
||||
|
||||
protected:
|
||||
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
||||
std::unique_ptr<RasterizerInterface> rasterizer;
|
||||
@@ -97,6 +112,9 @@ protected:
|
||||
private:
|
||||
/// Updates the framebuffer layout of the contained render window handle.
|
||||
void UpdateCurrentFramebufferLayout();
|
||||
|
||||
// Value of brightness before backlight switch used to preserve value.
|
||||
f32 current_brightness_backup;
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
||||
|
||||
@@ -348,6 +348,7 @@ static constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
|
||||
std::lock_guard lock{pages_mutex};
|
||||
const u64 page_start{addr >> Memory::PAGE_BITS};
|
||||
const u64 page_end{(addr + size + Memory::PAGE_SIZE - 1) >> Memory::PAGE_BITS};
|
||||
|
||||
@@ -974,7 +975,8 @@ TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stag
|
||||
}
|
||||
const auto cbuf = entry.GetBindlessCBuf();
|
||||
Tegra::Texture::TextureHandle tex_handle;
|
||||
tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second);
|
||||
Tegra::Engines::ShaderType shader_type = static_cast<Tegra::Engines::ShaderType>(stage);
|
||||
tex_handle.raw = maxwell3d.AccessConstBuffer32(shader_type, cbuf.first, cbuf.second);
|
||||
return maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset());
|
||||
}();
|
||||
|
||||
@@ -1004,7 +1006,8 @@ TextureBufferUsage RasterizerOpenGL::SetupComputeTextures(const Shader& kernel)
|
||||
}
|
||||
const auto cbuf = entry.GetBindlessCBuf();
|
||||
Tegra::Texture::TextureHandle tex_handle;
|
||||
tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second);
|
||||
tex_handle.raw = compute.AccessConstBuffer32(Tegra::Engines::ShaderType::Compute,
|
||||
cbuf.first, cbuf.second);
|
||||
return compute.GetTextureInfo(tex_handle, entry.GetOffset());
|
||||
}();
|
||||
|
||||
@@ -1049,7 +1052,8 @@ void RasterizerOpenGL::SetupComputeImages(const Shader& shader) {
|
||||
}
|
||||
const auto cbuf = entry.GetBindlessCBuf();
|
||||
Tegra::Texture::TextureHandle tex_handle;
|
||||
tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second);
|
||||
tex_handle.raw = compute.AccessConstBuffer32(Tegra::Engines::ShaderType::Compute,
|
||||
cbuf.first, cbuf.second);
|
||||
return compute.GetTextureInfo(tex_handle, entry.GetOffset()).tic;
|
||||
}();
|
||||
SetupImage(bindpoint, tic, entry);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
@@ -230,6 +231,8 @@ private:
|
||||
|
||||
using CachedPageMap = boost::icl::interval_map<u64, int>;
|
||||
CachedPageMap cached_pages;
|
||||
|
||||
std::mutex pages_mutex;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/hash.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "video_core/engines/kepler_compute.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
@@ -21,18 +24,20 @@
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
using Tegra::Engines::ShaderType;
|
||||
using VideoCommon::Shader::ConstBufferLocker;
|
||||
using VideoCommon::Shader::ProgramCode;
|
||||
using VideoCommon::Shader::ShaderIR;
|
||||
|
||||
namespace {
|
||||
|
||||
// One UBO is always reserved for emulation values on staged shaders
|
||||
constexpr u32 STAGE_RESERVED_UBOS = 1;
|
||||
|
||||
struct UnspecializedShader {
|
||||
std::string code;
|
||||
GLShader::ShaderEntries entries;
|
||||
ProgramType program_type;
|
||||
};
|
||||
constexpr u32 STAGE_MAIN_OFFSET = 10;
|
||||
constexpr u32 KERNEL_MAIN_OFFSET = 0;
|
||||
|
||||
namespace {
|
||||
constexpr VideoCommon::Shader::CompilerSettings COMPILER_SETTINGS{};
|
||||
|
||||
/// Gets the address for the specified shader stage program
|
||||
GPUVAddr GetShaderAddress(Core::System& system, Maxwell::ShaderProgram program) {
|
||||
@@ -41,6 +46,39 @@ GPUVAddr GetShaderAddress(Core::System& system, Maxwell::ShaderProgram program)
|
||||
return gpu.regs.code_address.CodeAddress() + shader_config.offset;
|
||||
}
|
||||
|
||||
/// Gets if the current instruction offset is a scheduler instruction
|
||||
constexpr bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
|
||||
// Sched instructions appear once every 4 instructions.
|
||||
constexpr std::size_t SchedPeriod = 4;
|
||||
const std::size_t absolute_offset = offset - main_offset;
|
||||
return (absolute_offset % SchedPeriod) == 0;
|
||||
}
|
||||
|
||||
/// Calculates the size of a program stream
|
||||
std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) {
|
||||
constexpr std::size_t start_offset = 10;
|
||||
// This is the encoded version of BRA that jumps to itself. All Nvidia
|
||||
// shaders end with one.
|
||||
constexpr u64 self_jumping_branch = 0xE2400FFFFF07000FULL;
|
||||
constexpr u64 mask = 0xFFFFFFFFFF7FFFFFULL;
|
||||
std::size_t offset = start_offset;
|
||||
while (offset < program.size()) {
|
||||
const u64 instruction = program[offset];
|
||||
if (!IsSchedInstruction(offset, start_offset)) {
|
||||
if ((instruction & mask) == self_jumping_branch) {
|
||||
// End on Maxwell's "nop" instruction
|
||||
break;
|
||||
}
|
||||
if (instruction == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
// The last instruction is included in the program size
|
||||
return std::min(offset + 1, program.size());
|
||||
}
|
||||
|
||||
/// Gets the shader program code from memory for the specified address
|
||||
ProgramCode GetShaderCode(Tegra::MemoryManager& memory_manager, const GPUVAddr gpu_addr,
|
||||
const u8* host_ptr) {
|
||||
@@ -51,6 +89,7 @@ ProgramCode GetShaderCode(Tegra::MemoryManager& memory_manager, const GPUVAddr g
|
||||
});
|
||||
memory_manager.ReadBlockUnsafe(gpu_addr, program_code.data(),
|
||||
program_code.size() * sizeof(u64));
|
||||
program_code.resize(CalculateProgramSize(program_code));
|
||||
return program_code;
|
||||
}
|
||||
|
||||
@@ -71,14 +110,6 @@ constexpr GLenum GetShaderType(ProgramType program_type) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets if the current instruction offset is a scheduler instruction
|
||||
constexpr bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
|
||||
// Sched instructions appear once every 4 instructions.
|
||||
constexpr std::size_t SchedPeriod = 4;
|
||||
const std::size_t absolute_offset = offset - main_offset;
|
||||
return (absolute_offset % SchedPeriod) == 0;
|
||||
}
|
||||
|
||||
/// Describes primitive behavior on geometry shaders
|
||||
constexpr std::tuple<const char*, const char*, u32> GetPrimitiveDescription(GLenum primitive_mode) {
|
||||
switch (primitive_mode) {
|
||||
@@ -121,110 +152,142 @@ ProgramType GetProgramType(Maxwell::ShaderProgram program) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/// Calculates the size of a program stream
|
||||
std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) {
|
||||
constexpr std::size_t start_offset = 10;
|
||||
// This is the encoded version of BRA that jumps to itself. All Nvidia
|
||||
// shaders end with one.
|
||||
constexpr u64 self_jumping_branch = 0xE2400FFFFF07000FULL;
|
||||
constexpr u64 mask = 0xFFFFFFFFFF7FFFFFULL;
|
||||
std::size_t offset = start_offset;
|
||||
std::size_t size = start_offset * sizeof(u64);
|
||||
while (offset < program.size()) {
|
||||
const u64 instruction = program[offset];
|
||||
if (!IsSchedInstruction(offset, start_offset)) {
|
||||
if ((instruction & mask) == self_jumping_branch) {
|
||||
// End on Maxwell's "nop" instruction
|
||||
break;
|
||||
}
|
||||
if (instruction == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
size += sizeof(u64);
|
||||
offset++;
|
||||
}
|
||||
// The last instruction is included in the program size
|
||||
return std::min(size + sizeof(u64), program.size() * sizeof(u64));
|
||||
}
|
||||
|
||||
/// Hashes one (or two) program streams
|
||||
u64 GetUniqueIdentifier(ProgramType program_type, const ProgramCode& code,
|
||||
const ProgramCode& code_b, std::size_t size_a = 0, std::size_t size_b = 0) {
|
||||
if (size_a == 0) {
|
||||
size_a = CalculateProgramSize(code);
|
||||
const ProgramCode& code_b) {
|
||||
u64 unique_identifier = boost::hash_value(code);
|
||||
if (program_type == ProgramType::VertexA) {
|
||||
// VertexA programs include two programs
|
||||
boost::hash_combine(unique_identifier, boost::hash_value(code_b));
|
||||
}
|
||||
u64 unique_identifier = Common::CityHash64(reinterpret_cast<const char*>(code.data()), size_a);
|
||||
if (program_type != ProgramType::VertexA) {
|
||||
return unique_identifier;
|
||||
}
|
||||
// VertexA programs include two programs
|
||||
|
||||
std::size_t seed = 0;
|
||||
boost::hash_combine(seed, unique_identifier);
|
||||
|
||||
if (size_b == 0) {
|
||||
size_b = CalculateProgramSize(code_b);
|
||||
}
|
||||
const u64 identifier_b =
|
||||
Common::CityHash64(reinterpret_cast<const char*>(code_b.data()), size_b);
|
||||
boost::hash_combine(seed, identifier_b);
|
||||
return static_cast<u64>(seed);
|
||||
return unique_identifier;
|
||||
}
|
||||
|
||||
/// Creates an unspecialized program from code streams
|
||||
GLShader::ProgramResult CreateProgram(const Device& device, ProgramType program_type,
|
||||
ProgramCode program_code, ProgramCode program_code_b) {
|
||||
GLShader::ShaderSetup setup(program_code);
|
||||
setup.program.size_a = CalculateProgramSize(program_code);
|
||||
setup.program.size_b = 0;
|
||||
if (program_type == ProgramType::VertexA) {
|
||||
// VertexB is always enabled, so when VertexA is enabled, we have two vertex shaders.
|
||||
// Conventional HW does not support this, so we combine VertexA and VertexB into one
|
||||
// stage here.
|
||||
setup.SetProgramB(program_code_b);
|
||||
setup.program.size_b = CalculateProgramSize(program_code_b);
|
||||
}
|
||||
setup.program.unique_identifier = GetUniqueIdentifier(
|
||||
program_type, program_code, program_code_b, setup.program.size_a, setup.program.size_b);
|
||||
|
||||
std::string GenerateGLSL(const Device& device, ProgramType program_type, const ShaderIR& ir,
|
||||
const std::optional<ShaderIR>& ir_b) {
|
||||
switch (program_type) {
|
||||
case ProgramType::VertexA:
|
||||
case ProgramType::VertexB:
|
||||
return GLShader::GenerateVertexShader(device, setup);
|
||||
return GLShader::GenerateVertexShader(device, ir, ir_b ? &*ir_b : nullptr);
|
||||
case ProgramType::Geometry:
|
||||
return GLShader::GenerateGeometryShader(device, setup);
|
||||
return GLShader::GenerateGeometryShader(device, ir);
|
||||
case ProgramType::Fragment:
|
||||
return GLShader::GenerateFragmentShader(device, setup);
|
||||
return GLShader::GenerateFragmentShader(device, ir);
|
||||
case ProgramType::Compute:
|
||||
return GLShader::GenerateComputeShader(device, setup);
|
||||
return GLShader::GenerateComputeShader(device, ir);
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented program_type={}", static_cast<u32>(program_type));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEntries& entries,
|
||||
ProgramType program_type, const ProgramVariant& variant,
|
||||
bool hint_retrievable = false) {
|
||||
constexpr const char* GetProgramTypeName(ProgramType program_type) {
|
||||
switch (program_type) {
|
||||
case ProgramType::VertexA:
|
||||
case ProgramType::VertexB:
|
||||
return "VS";
|
||||
case ProgramType::TessellationControl:
|
||||
return "TCS";
|
||||
case ProgramType::TessellationEval:
|
||||
return "TES";
|
||||
case ProgramType::Geometry:
|
||||
return "GS";
|
||||
case ProgramType::Fragment:
|
||||
return "FS";
|
||||
case ProgramType::Compute:
|
||||
return "CS";
|
||||
}
|
||||
return "UNK";
|
||||
}
|
||||
|
||||
Tegra::Engines::ShaderType GetEnginesShaderType(ProgramType program_type) {
|
||||
switch (program_type) {
|
||||
case ProgramType::VertexA:
|
||||
case ProgramType::VertexB:
|
||||
return Tegra::Engines::ShaderType::Vertex;
|
||||
case ProgramType::TessellationControl:
|
||||
return Tegra::Engines::ShaderType::TesselationControl;
|
||||
case ProgramType::TessellationEval:
|
||||
return Tegra::Engines::ShaderType::TesselationEval;
|
||||
case ProgramType::Geometry:
|
||||
return Tegra::Engines::ShaderType::Geometry;
|
||||
case ProgramType::Fragment:
|
||||
return Tegra::Engines::ShaderType::Fragment;
|
||||
case ProgramType::Compute:
|
||||
return Tegra::Engines::ShaderType::Compute;
|
||||
}
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string GetShaderId(u64 unique_identifier, ProgramType program_type) {
|
||||
return fmt::format("{}{:016X}", GetProgramTypeName(program_type), unique_identifier);
|
||||
}
|
||||
|
||||
Tegra::Engines::ConstBufferEngineInterface& GetConstBufferEngineInterface(
|
||||
Core::System& system, ProgramType program_type) {
|
||||
if (program_type == ProgramType::Compute) {
|
||||
return system.GPU().KeplerCompute();
|
||||
} else {
|
||||
return system.GPU().Maxwell3D();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<ConstBufferLocker> MakeLocker(Core::System& system, ProgramType program_type) {
|
||||
return std::make_unique<ConstBufferLocker>(GetEnginesShaderType(program_type),
|
||||
GetConstBufferEngineInterface(system, program_type));
|
||||
}
|
||||
|
||||
void FillLocker(ConstBufferLocker& locker, const ShaderDiskCacheUsage& usage) {
|
||||
for (const auto& key : usage.keys) {
|
||||
const auto [buffer, offset] = key.first;
|
||||
locker.InsertKey(buffer, offset, key.second);
|
||||
}
|
||||
for (const auto& [offset, sampler] : usage.bound_samplers) {
|
||||
locker.InsertBoundSampler(offset, sampler);
|
||||
}
|
||||
for (const auto& [key, sampler] : usage.bindless_samplers) {
|
||||
const auto [buffer, offset] = key;
|
||||
locker.InsertBindlessSampler(buffer, offset, sampler);
|
||||
}
|
||||
}
|
||||
|
||||
CachedProgram BuildShader(const Device& device, u64 unique_identifier, ProgramType program_type,
|
||||
const ProgramCode& program_code, const ProgramCode& program_code_b,
|
||||
const ProgramVariant& variant, ConstBufferLocker& locker,
|
||||
bool hint_retrievable = false) {
|
||||
LOG_INFO(Render_OpenGL, "called. {}", GetShaderId(unique_identifier, program_type));
|
||||
|
||||
const bool is_compute = program_type == ProgramType::Compute;
|
||||
const u32 main_offset = is_compute ? KERNEL_MAIN_OFFSET : STAGE_MAIN_OFFSET;
|
||||
const ShaderIR ir(program_code, main_offset, COMPILER_SETTINGS, locker);
|
||||
std::optional<ShaderIR> ir_b;
|
||||
if (!program_code_b.empty()) {
|
||||
ir_b.emplace(program_code_b, main_offset, COMPILER_SETTINGS, locker);
|
||||
}
|
||||
const auto entries = GLShader::GetEntries(ir);
|
||||
|
||||
auto base_bindings{variant.base_bindings};
|
||||
const auto primitive_mode{variant.primitive_mode};
|
||||
const auto texture_buffer_usage{variant.texture_buffer_usage};
|
||||
|
||||
std::string source = R"(#version 430 core
|
||||
std::string source = fmt::format(R"(// {}
|
||||
#version 430 core
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
#extension GL_ARB_shader_viewport_layer_array : enable
|
||||
#extension GL_EXT_shader_image_load_formatted : enable
|
||||
#extension GL_NV_gpu_shader5 : enable
|
||||
#extension GL_NV_shader_thread_group : enable
|
||||
#extension GL_NV_shader_thread_shuffle : enable
|
||||
)";
|
||||
if (program_type == ProgramType::Compute) {
|
||||
)",
|
||||
GetShaderId(unique_identifier, program_type));
|
||||
if (is_compute) {
|
||||
source += "#extension GL_ARB_compute_variable_group_size : require\n";
|
||||
}
|
||||
source += '\n';
|
||||
|
||||
if (program_type != ProgramType::Compute) {
|
||||
if (!is_compute) {
|
||||
source += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++);
|
||||
}
|
||||
|
||||
@@ -268,7 +331,7 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn
|
||||
}
|
||||
|
||||
source += '\n';
|
||||
source += code;
|
||||
source += GenerateGLSL(device, program_type, ir, ir_b);
|
||||
|
||||
OGLShader shader;
|
||||
shader.Create(source.c_str(), GetShaderType(program_type));
|
||||
@@ -278,85 +341,97 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn
|
||||
return program;
|
||||
}
|
||||
|
||||
std::set<GLenum> GetSupportedFormats() {
|
||||
std::set<GLenum> supported_formats;
|
||||
|
||||
std::unordered_set<GLenum> GetSupportedFormats() {
|
||||
GLint num_formats{};
|
||||
glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats);
|
||||
|
||||
std::vector<GLint> formats(num_formats);
|
||||
glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, formats.data());
|
||||
|
||||
for (const GLint format : formats)
|
||||
std::unordered_set<GLenum> supported_formats;
|
||||
for (const GLint format : formats) {
|
||||
supported_formats.insert(static_cast<GLenum>(format));
|
||||
}
|
||||
return supported_formats;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
CachedShader::CachedShader(const ShaderParameters& params, ProgramType program_type,
|
||||
GLShader::ProgramResult result)
|
||||
: RasterizerCacheObject{params.host_ptr}, cpu_addr{params.cpu_addr},
|
||||
unique_identifier{params.unique_identifier}, program_type{program_type},
|
||||
disk_cache{params.disk_cache}, precompiled_programs{params.precompiled_programs},
|
||||
entries{result.second}, code{std::move(result.first)}, shader_length{entries.shader_length} {}
|
||||
GLShader::ShaderEntries entries, ProgramCode program_code,
|
||||
ProgramCode program_code_b)
|
||||
: RasterizerCacheObject{params.host_ptr}, system{params.system},
|
||||
disk_cache{params.disk_cache}, device{params.device}, cpu_addr{params.cpu_addr},
|
||||
unique_identifier{params.unique_identifier}, program_type{program_type}, entries{entries},
|
||||
program_code{std::move(program_code)}, program_code_b{std::move(program_code_b)} {
|
||||
if (!params.precompiled_variants) {
|
||||
return;
|
||||
}
|
||||
for (const auto& pair : *params.precompiled_variants) {
|
||||
auto locker = MakeLocker(system, program_type);
|
||||
const auto& usage = pair->first;
|
||||
FillLocker(*locker, usage);
|
||||
|
||||
std::unique_ptr<LockerVariant>* locker_variant = nullptr;
|
||||
const auto it =
|
||||
std::find_if(locker_variants.begin(), locker_variants.end(), [&](const auto& variant) {
|
||||
return variant->locker->HasEqualKeys(*locker);
|
||||
});
|
||||
if (it == locker_variants.end()) {
|
||||
locker_variant = &locker_variants.emplace_back();
|
||||
*locker_variant = std::make_unique<LockerVariant>();
|
||||
locker_variant->get()->locker = std::move(locker);
|
||||
} else {
|
||||
locker_variant = &*it;
|
||||
}
|
||||
locker_variant->get()->programs.emplace(usage.variant, pair->second);
|
||||
}
|
||||
}
|
||||
|
||||
Shader CachedShader::CreateStageFromMemory(const ShaderParameters& params,
|
||||
Maxwell::ShaderProgram program_type,
|
||||
ProgramCode&& program_code,
|
||||
ProgramCode&& program_code_b) {
|
||||
const auto code_size{CalculateProgramSize(program_code)};
|
||||
const auto code_size_b{CalculateProgramSize(program_code_b)};
|
||||
auto result{
|
||||
CreateProgram(params.device, GetProgramType(program_type), program_code, program_code_b)};
|
||||
if (result.first.empty()) {
|
||||
// TODO(Rodrigo): Unimplemented shader stages hit here, avoid using these for now
|
||||
return {};
|
||||
}
|
||||
|
||||
ProgramCode program_code, ProgramCode program_code_b) {
|
||||
params.disk_cache.SaveRaw(ShaderDiskCacheRaw(
|
||||
params.unique_identifier, GetProgramType(program_type),
|
||||
static_cast<u32>(code_size / sizeof(u64)), static_cast<u32>(code_size_b / sizeof(u64)),
|
||||
std::move(program_code), std::move(program_code_b)));
|
||||
params.unique_identifier, GetProgramType(program_type), program_code, program_code_b));
|
||||
|
||||
ConstBufferLocker locker(GetEnginesShaderType(GetProgramType(program_type)));
|
||||
const ShaderIR ir(program_code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, locker);
|
||||
// TODO(Rodrigo): Handle VertexA shaders
|
||||
// std::optional<ShaderIR> ir_b;
|
||||
// if (!program_code_b.empty()) {
|
||||
// ir_b.emplace(program_code_b, STAGE_MAIN_OFFSET);
|
||||
// }
|
||||
return std::shared_ptr<CachedShader>(
|
||||
new CachedShader(params, GetProgramType(program_type), std::move(result)));
|
||||
new CachedShader(params, GetProgramType(program_type), GLShader::GetEntries(ir),
|
||||
std::move(program_code), std::move(program_code_b)));
|
||||
}
|
||||
|
||||
Shader CachedShader::CreateStageFromCache(const ShaderParameters& params,
|
||||
Maxwell::ShaderProgram program_type,
|
||||
GLShader::ProgramResult result) {
|
||||
return std::shared_ptr<CachedShader>(
|
||||
new CachedShader(params, GetProgramType(program_type), std::move(result)));
|
||||
Shader CachedShader::CreateKernelFromMemory(const ShaderParameters& params, ProgramCode code) {
|
||||
params.disk_cache.SaveRaw(
|
||||
ShaderDiskCacheRaw(params.unique_identifier, ProgramType::Compute, code));
|
||||
|
||||
ConstBufferLocker locker(Tegra::Engines::ShaderType::Compute);
|
||||
const ShaderIR ir(code, KERNEL_MAIN_OFFSET, COMPILER_SETTINGS, locker);
|
||||
return std::shared_ptr<CachedShader>(new CachedShader(
|
||||
params, ProgramType::Compute, GLShader::GetEntries(ir), std::move(code), {}));
|
||||
}
|
||||
|
||||
Shader CachedShader::CreateKernelFromMemory(const ShaderParameters& params, ProgramCode&& code) {
|
||||
auto result{CreateProgram(params.device, ProgramType::Compute, code, {})};
|
||||
|
||||
const auto code_size{CalculateProgramSize(code)};
|
||||
params.disk_cache.SaveRaw(ShaderDiskCacheRaw(params.unique_identifier, ProgramType::Compute,
|
||||
static_cast<u32>(code_size / sizeof(u64)), 0,
|
||||
std::move(code), {}));
|
||||
|
||||
return std::shared_ptr<CachedShader>(
|
||||
new CachedShader(params, ProgramType::Compute, std::move(result)));
|
||||
}
|
||||
|
||||
Shader CachedShader::CreateKernelFromCache(const ShaderParameters& params,
|
||||
GLShader::ProgramResult result) {
|
||||
return std::shared_ptr<CachedShader>(
|
||||
new CachedShader(params, ProgramType::Compute, std::move(result)));
|
||||
Shader CachedShader::CreateFromCache(const ShaderParameters& params,
|
||||
const UnspecializedShader& unspecialized) {
|
||||
return std::shared_ptr<CachedShader>(new CachedShader(params, unspecialized.program_type,
|
||||
unspecialized.entries, unspecialized.code,
|
||||
unspecialized.code_b));
|
||||
}
|
||||
|
||||
std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVariant& variant) {
|
||||
const auto [entry, is_cache_miss] = programs.try_emplace(variant);
|
||||
UpdateVariant();
|
||||
|
||||
const auto [entry, is_cache_miss] = curr_variant->programs.try_emplace(variant);
|
||||
auto& program = entry->second;
|
||||
if (is_cache_miss) {
|
||||
program = TryLoadProgram(variant);
|
||||
if (!program) {
|
||||
program = SpecializeShader(code, entries, program_type, variant);
|
||||
disk_cache.SaveUsage(GetUsage(variant));
|
||||
}
|
||||
program = BuildShader(device, unique_identifier, program_type, program_code, program_code_b,
|
||||
variant, *curr_variant->locker);
|
||||
disk_cache.SaveUsage(GetUsage(variant, *curr_variant->locker));
|
||||
|
||||
LabelGLObject(GL_PROGRAM, program->handle, cpu_addr);
|
||||
}
|
||||
@@ -372,18 +447,33 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVar
|
||||
return {program->handle, base_bindings};
|
||||
}
|
||||
|
||||
CachedProgram CachedShader::TryLoadProgram(const ProgramVariant& variant) const {
|
||||
const auto found = precompiled_programs.find(GetUsage(variant));
|
||||
if (found == precompiled_programs.end()) {
|
||||
return {};
|
||||
void CachedShader::UpdateVariant() {
|
||||
if (curr_variant && !curr_variant->locker->IsConsistent()) {
|
||||
curr_variant = nullptr;
|
||||
}
|
||||
if (!curr_variant) {
|
||||
for (auto& variant : locker_variants) {
|
||||
if (variant->locker->IsConsistent()) {
|
||||
curr_variant = variant.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!curr_variant) {
|
||||
auto& new_variant = locker_variants.emplace_back();
|
||||
new_variant = std::make_unique<LockerVariant>();
|
||||
new_variant->locker = MakeLocker(system, program_type);
|
||||
curr_variant = new_variant.get();
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
|
||||
ShaderDiskCacheUsage CachedShader::GetUsage(const ProgramVariant& variant) const {
|
||||
ShaderDiskCacheUsage CachedShader::GetUsage(const ProgramVariant& variant,
|
||||
const ConstBufferLocker& locker) const {
|
||||
ShaderDiskCacheUsage usage;
|
||||
usage.unique_identifier = unique_identifier;
|
||||
usage.variant = variant;
|
||||
usage.keys = locker.GetKeys();
|
||||
usage.bound_samplers = locker.GetBoundSamplers();
|
||||
usage.bindless_samplers = locker.GetBindlessSamplers();
|
||||
return usage;
|
||||
}
|
||||
|
||||
@@ -399,18 +489,15 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
return;
|
||||
}
|
||||
const auto [raws, shader_usages] = *transferable;
|
||||
|
||||
auto [decompiled, dumps] = disk_cache.LoadPrecompiled();
|
||||
|
||||
const auto supported_formats{GetSupportedFormats()};
|
||||
const auto unspecialized_shaders{
|
||||
GenerateUnspecializedShaders(stop_loading, callback, raws, decompiled)};
|
||||
if (stop_loading) {
|
||||
if (!GenerateUnspecializedShaders(stop_loading, callback, raws) || stop_loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Track if precompiled cache was altered during loading to know if we have to serialize the
|
||||
// virtual precompiled cache file back to the hard drive
|
||||
const auto dumps = disk_cache.LoadPrecompiled();
|
||||
const auto supported_formats = GetSupportedFormats();
|
||||
|
||||
// Track if precompiled cache was altered during loading to know if we have to
|
||||
// serialize the virtual precompiled cache file back to the hard drive
|
||||
bool precompiled_cache_altered = false;
|
||||
|
||||
// Inform the frontend about shader build initialization
|
||||
@@ -433,9 +520,6 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
return;
|
||||
}
|
||||
const auto& usage{shader_usages[i]};
|
||||
LOG_INFO(Render_OpenGL, "Building shader {:016x} (index {} of {})",
|
||||
usage.unique_identifier, i, shader_usages.size());
|
||||
|
||||
const auto& unspecialized{unspecialized_shaders.at(usage.unique_identifier)};
|
||||
const auto dump{dumps.find(usage)};
|
||||
|
||||
@@ -449,21 +533,28 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
}
|
||||
}
|
||||
if (!shader) {
|
||||
shader = SpecializeShader(unspecialized.code, unspecialized.entries,
|
||||
unspecialized.program_type, usage.variant, true);
|
||||
auto locker{MakeLocker(system, unspecialized.program_type)};
|
||||
FillLocker(*locker, usage);
|
||||
shader = BuildShader(device, usage.unique_identifier, unspecialized.program_type,
|
||||
unspecialized.code, unspecialized.code_b, usage.variant,
|
||||
*locker, true);
|
||||
}
|
||||
|
||||
std::scoped_lock lock(mutex);
|
||||
std::scoped_lock lock{mutex};
|
||||
if (callback) {
|
||||
callback(VideoCore::LoadCallbackStage::Build, ++built_shaders,
|
||||
shader_usages.size());
|
||||
}
|
||||
|
||||
precompiled_programs.emplace(usage, std::move(shader));
|
||||
|
||||
// TODO(Rodrigo): Is there a better way to do this?
|
||||
precompiled_variants[usage.unique_identifier].push_back(
|
||||
precompiled_programs.find(usage));
|
||||
}
|
||||
};
|
||||
|
||||
const auto num_workers{static_cast<std::size_t>(std::thread::hardware_concurrency() + 1)};
|
||||
const auto num_workers{static_cast<std::size_t>(std::thread::hardware_concurrency() + 1ULL)};
|
||||
const std::size_t bucket_size{shader_usages.size() / num_workers};
|
||||
std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> contexts(num_workers);
|
||||
std::vector<std::thread> threads(num_workers);
|
||||
@@ -483,7 +574,6 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
if (compilation_failed) {
|
||||
// Invalidate the precompiled cache if a shader dumped shader was rejected
|
||||
disk_cache.InvalidatePrecompiled();
|
||||
dumps.clear();
|
||||
precompiled_cache_altered = true;
|
||||
return;
|
||||
}
|
||||
@@ -491,8 +581,8 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(Rodrigo): Do state tracking for transferable shaders and do a dummy draw before
|
||||
// precompiling them
|
||||
// TODO(Rodrigo): Do state tracking for transferable shaders and do a dummy draw
|
||||
// before precompiling them
|
||||
|
||||
for (std::size_t i = 0; i < shader_usages.size(); ++i) {
|
||||
const auto& usage{shader_usages[i]};
|
||||
@@ -508,9 +598,13 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
}
|
||||
}
|
||||
|
||||
CachedProgram ShaderCacheOpenGL::GeneratePrecompiledProgram(
|
||||
const ShaderDiskCacheDump& dump, const std::set<GLenum>& supported_formats) {
|
||||
const PrecompiledVariants* ShaderCacheOpenGL::GetPrecompiledVariants(u64 unique_identifier) const {
|
||||
const auto it = precompiled_variants.find(unique_identifier);
|
||||
return it == precompiled_variants.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
CachedProgram ShaderCacheOpenGL::GeneratePrecompiledProgram(
|
||||
const ShaderDiskCacheDump& dump, const std::unordered_set<GLenum>& supported_formats) {
|
||||
if (supported_formats.find(dump.binary_format) == supported_formats.end()) {
|
||||
LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format - removing");
|
||||
return {};
|
||||
@@ -532,56 +626,52 @@ CachedProgram ShaderCacheOpenGL::GeneratePrecompiledProgram(
|
||||
return shader;
|
||||
}
|
||||
|
||||
std::unordered_map<u64, UnspecializedShader> ShaderCacheOpenGL::GenerateUnspecializedShaders(
|
||||
bool ShaderCacheOpenGL::GenerateUnspecializedShaders(
|
||||
const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback,
|
||||
const std::vector<ShaderDiskCacheRaw>& raws,
|
||||
const std::unordered_map<u64, ShaderDiskCacheDecompiled>& decompiled) {
|
||||
std::unordered_map<u64, UnspecializedShader> unspecialized;
|
||||
|
||||
const std::vector<ShaderDiskCacheRaw>& raws) {
|
||||
if (callback) {
|
||||
callback(VideoCore::LoadCallbackStage::Decompile, 0, raws.size());
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < raws.size(); ++i) {
|
||||
if (stop_loading) {
|
||||
return {};
|
||||
return false;
|
||||
}
|
||||
const auto& raw{raws[i]};
|
||||
const u64 unique_identifier{raw.GetUniqueIdentifier()};
|
||||
const u64 calculated_hash{
|
||||
GetUniqueIdentifier(raw.GetProgramType(), raw.GetProgramCode(), raw.GetProgramCodeB())};
|
||||
if (unique_identifier != calculated_hash) {
|
||||
LOG_ERROR(
|
||||
Render_OpenGL,
|
||||
"Invalid hash in entry={:016x} (obtained hash={:016x}) - removing shader cache",
|
||||
raw.GetUniqueIdentifier(), calculated_hash);
|
||||
LOG_ERROR(Render_OpenGL,
|
||||
"Invalid hash in entry={:016x} (obtained hash={:016x}) - "
|
||||
"removing shader cache",
|
||||
raw.GetUniqueIdentifier(), calculated_hash);
|
||||
disk_cache.InvalidateTransferable();
|
||||
return {};
|
||||
return false;
|
||||
}
|
||||
|
||||
GLShader::ProgramResult result;
|
||||
if (const auto it = decompiled.find(unique_identifier); it != decompiled.end()) {
|
||||
// If it's stored in the precompiled file, avoid decompiling it here
|
||||
const auto& stored_decompiled{it->second};
|
||||
result = {stored_decompiled.code, stored_decompiled.entries};
|
||||
} else {
|
||||
// Otherwise decompile the shader at boot and save the result to the decompiled file
|
||||
result = CreateProgram(device, raw.GetProgramType(), raw.GetProgramCode(),
|
||||
raw.GetProgramCodeB());
|
||||
disk_cache.SaveDecompiled(unique_identifier, result.first, result.second);
|
||||
}
|
||||
const u32 main_offset =
|
||||
raw.GetProgramType() == ProgramType::Compute ? KERNEL_MAIN_OFFSET : STAGE_MAIN_OFFSET;
|
||||
ConstBufferLocker locker(GetEnginesShaderType(raw.GetProgramType()));
|
||||
const ShaderIR ir(raw.GetProgramCode(), main_offset, COMPILER_SETTINGS, locker);
|
||||
// TODO(Rodrigo): Handle VertexA shaders
|
||||
// std::optional<ShaderIR> ir_b;
|
||||
// if (raw.HasProgramA()) {
|
||||
// ir_b.emplace(raw.GetProgramCodeB(), main_offset);
|
||||
// }
|
||||
|
||||
precompiled_shaders.insert({unique_identifier, result});
|
||||
|
||||
unspecialized.insert(
|
||||
{raw.GetUniqueIdentifier(),
|
||||
{std::move(result.first), std::move(result.second), raw.GetProgramType()}});
|
||||
UnspecializedShader unspecialized;
|
||||
unspecialized.entries = GLShader::GetEntries(ir);
|
||||
unspecialized.program_type = raw.GetProgramType();
|
||||
unspecialized.code = raw.GetProgramCode();
|
||||
unspecialized.code_b = raw.GetProgramCodeB();
|
||||
unspecialized_shaders.emplace(raw.GetUniqueIdentifier(), unspecialized);
|
||||
|
||||
if (callback) {
|
||||
callback(VideoCore::LoadCallbackStage::Decompile, i, raws.size());
|
||||
}
|
||||
}
|
||||
return unspecialized;
|
||||
return true;
|
||||
}
|
||||
|
||||
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
|
||||
@@ -590,37 +680,35 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
|
||||
}
|
||||
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const GPUVAddr program_addr{GetShaderAddress(system, program)};
|
||||
const GPUVAddr address{GetShaderAddress(system, program)};
|
||||
|
||||
// Look up shader in the cache based on address
|
||||
const auto host_ptr{memory_manager.GetPointer(program_addr)};
|
||||
const auto host_ptr{memory_manager.GetPointer(address)};
|
||||
Shader shader{TryGet(host_ptr)};
|
||||
if (shader) {
|
||||
return last_shaders[static_cast<std::size_t>(program)] = shader;
|
||||
}
|
||||
|
||||
// No shader found - create a new one
|
||||
ProgramCode program_code{GetShaderCode(memory_manager, program_addr, host_ptr)};
|
||||
ProgramCode program_code_b;
|
||||
const bool is_program_a{program == Maxwell::ShaderProgram::VertexA};
|
||||
if (is_program_a) {
|
||||
const GPUVAddr program_addr_b{GetShaderAddress(system, Maxwell::ShaderProgram::VertexB)};
|
||||
program_code_b = GetShaderCode(memory_manager, program_addr_b,
|
||||
memory_manager.GetPointer(program_addr_b));
|
||||
ProgramCode code{GetShaderCode(memory_manager, address, host_ptr)};
|
||||
ProgramCode code_b;
|
||||
if (program == Maxwell::ShaderProgram::VertexA) {
|
||||
const GPUVAddr address_b{GetShaderAddress(system, Maxwell::ShaderProgram::VertexB)};
|
||||
code_b = GetShaderCode(memory_manager, address_b, memory_manager.GetPointer(address_b));
|
||||
}
|
||||
|
||||
const auto unique_identifier =
|
||||
GetUniqueIdentifier(GetProgramType(program), program_code, program_code_b);
|
||||
const auto cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)};
|
||||
const ShaderParameters params{disk_cache, precompiled_programs, device, cpu_addr,
|
||||
host_ptr, unique_identifier};
|
||||
const auto unique_identifier = GetUniqueIdentifier(GetProgramType(program), code, code_b);
|
||||
const auto precompiled_variants = GetPrecompiledVariants(unique_identifier);
|
||||
const auto cpu_addr{*memory_manager.GpuToCpuAddress(address)};
|
||||
const ShaderParameters params{system, disk_cache, precompiled_variants, device,
|
||||
cpu_addr, host_ptr, unique_identifier};
|
||||
|
||||
const auto found = precompiled_shaders.find(unique_identifier);
|
||||
if (found == precompiled_shaders.end()) {
|
||||
shader = CachedShader::CreateStageFromMemory(params, program, std::move(program_code),
|
||||
std::move(program_code_b));
|
||||
const auto found = unspecialized_shaders.find(unique_identifier);
|
||||
if (found == unspecialized_shaders.end()) {
|
||||
shader = CachedShader::CreateStageFromMemory(params, program, std::move(code),
|
||||
std::move(code_b));
|
||||
} else {
|
||||
shader = CachedShader::CreateStageFromCache(params, program, found->second);
|
||||
shader = CachedShader::CreateFromCache(params, found->second);
|
||||
}
|
||||
Register(shader);
|
||||
|
||||
@@ -638,15 +726,16 @@ Shader ShaderCacheOpenGL::GetComputeKernel(GPUVAddr code_addr) {
|
||||
// No kernel found - create a new one
|
||||
auto code{GetShaderCode(memory_manager, code_addr, host_ptr)};
|
||||
const auto unique_identifier{GetUniqueIdentifier(ProgramType::Compute, code, {})};
|
||||
const auto precompiled_variants = GetPrecompiledVariants(unique_identifier);
|
||||
const auto cpu_addr{*memory_manager.GpuToCpuAddress(code_addr)};
|
||||
const ShaderParameters params{disk_cache, precompiled_programs, device, cpu_addr,
|
||||
host_ptr, unique_identifier};
|
||||
const ShaderParameters params{system, disk_cache, precompiled_variants, device,
|
||||
cpu_addr, host_ptr, unique_identifier};
|
||||
|
||||
const auto found = precompiled_shaders.find(unique_identifier);
|
||||
if (found == precompiled_shaders.end()) {
|
||||
const auto found = unspecialized_shaders.find(unique_identifier);
|
||||
if (found == unspecialized_shaders.end()) {
|
||||
kernel = CachedShader::CreateKernelFromMemory(params, std::move(code));
|
||||
} else {
|
||||
kernel = CachedShader::CreateKernelFromCache(params, found->second);
|
||||
kernel = CachedShader::CreateFromCache(params, found->second);
|
||||
}
|
||||
|
||||
Register(kernel);
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <glad/glad.h>
|
||||
@@ -20,6 +21,8 @@
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_disk_cache.h"
|
||||
#include "video_core/shader/const_buffer_locker.h"
|
||||
#include "video_core/shader/shader_ir.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@@ -40,11 +43,19 @@ using Shader = std::shared_ptr<CachedShader>;
|
||||
using CachedProgram = std::shared_ptr<OGLProgram>;
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
using PrecompiledPrograms = std::unordered_map<ShaderDiskCacheUsage, CachedProgram>;
|
||||
using PrecompiledShaders = std::unordered_map<u64, GLShader::ProgramResult>;
|
||||
using PrecompiledVariants = std::vector<PrecompiledPrograms::iterator>;
|
||||
|
||||
struct UnspecializedShader {
|
||||
GLShader::ShaderEntries entries;
|
||||
ProgramType program_type;
|
||||
ProgramCode code;
|
||||
ProgramCode code_b;
|
||||
};
|
||||
|
||||
struct ShaderParameters {
|
||||
Core::System& system;
|
||||
ShaderDiskCacheOpenGL& disk_cache;
|
||||
const PrecompiledPrograms& precompiled_programs;
|
||||
const PrecompiledVariants* precompiled_variants;
|
||||
const Device& device;
|
||||
VAddr cpu_addr;
|
||||
u8* host_ptr;
|
||||
@@ -55,23 +66,18 @@ class CachedShader final : public RasterizerCacheObject {
|
||||
public:
|
||||
static Shader CreateStageFromMemory(const ShaderParameters& params,
|
||||
Maxwell::ShaderProgram program_type,
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b);
|
||||
ProgramCode program_code, ProgramCode program_code_b);
|
||||
static Shader CreateKernelFromMemory(const ShaderParameters& params, ProgramCode code);
|
||||
|
||||
static Shader CreateStageFromCache(const ShaderParameters& params,
|
||||
Maxwell::ShaderProgram program_type,
|
||||
GLShader::ProgramResult result);
|
||||
|
||||
static Shader CreateKernelFromMemory(const ShaderParameters& params, ProgramCode&& code);
|
||||
|
||||
static Shader CreateKernelFromCache(const ShaderParameters& params,
|
||||
GLShader::ProgramResult result);
|
||||
static Shader CreateFromCache(const ShaderParameters& params,
|
||||
const UnspecializedShader& unspecialized);
|
||||
|
||||
VAddr GetCpuAddr() const override {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
return shader_length;
|
||||
return program_code.size() * sizeof(u64);
|
||||
}
|
||||
|
||||
/// Gets the shader entries for the shader
|
||||
@@ -83,24 +89,36 @@ public:
|
||||
std::tuple<GLuint, BaseBindings> GetProgramHandle(const ProgramVariant& variant);
|
||||
|
||||
private:
|
||||
struct LockerVariant {
|
||||
std::unique_ptr<VideoCommon::Shader::ConstBufferLocker> locker;
|
||||
std::unordered_map<ProgramVariant, CachedProgram> programs;
|
||||
};
|
||||
|
||||
explicit CachedShader(const ShaderParameters& params, ProgramType program_type,
|
||||
GLShader::ProgramResult result);
|
||||
GLShader::ShaderEntries entries, ProgramCode program_code,
|
||||
ProgramCode program_code_b);
|
||||
|
||||
CachedProgram TryLoadProgram(const ProgramVariant& variant) const;
|
||||
void UpdateVariant();
|
||||
|
||||
ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const;
|
||||
ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant,
|
||||
const VideoCommon::Shader::ConstBufferLocker& locker) const;
|
||||
|
||||
Core::System& system;
|
||||
ShaderDiskCacheOpenGL& disk_cache;
|
||||
const Device& device;
|
||||
|
||||
VAddr cpu_addr{};
|
||||
|
||||
u64 unique_identifier{};
|
||||
ProgramType program_type{};
|
||||
ShaderDiskCacheOpenGL& disk_cache;
|
||||
const PrecompiledPrograms& precompiled_programs;
|
||||
|
||||
GLShader::ShaderEntries entries;
|
||||
std::string code;
|
||||
std::size_t shader_length{};
|
||||
|
||||
std::unordered_map<ProgramVariant, CachedProgram> programs;
|
||||
ProgramCode program_code;
|
||||
ProgramCode program_code_b;
|
||||
|
||||
LockerVariant* curr_variant = nullptr;
|
||||
std::vector<std::unique_ptr<LockerVariant>> locker_variants;
|
||||
};
|
||||
|
||||
class ShaderCacheOpenGL final : public RasterizerCache<Shader> {
|
||||
@@ -123,21 +141,26 @@ protected:
|
||||
void FlushObjectInner(const Shader& object) override {}
|
||||
|
||||
private:
|
||||
std::unordered_map<u64, UnspecializedShader> GenerateUnspecializedShaders(
|
||||
const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback,
|
||||
const std::vector<ShaderDiskCacheRaw>& raws,
|
||||
const std::unordered_map<u64, ShaderDiskCacheDecompiled>& decompiled);
|
||||
bool GenerateUnspecializedShaders(const std::atomic_bool& stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback,
|
||||
const std::vector<ShaderDiskCacheRaw>& raws);
|
||||
|
||||
CachedProgram GeneratePrecompiledProgram(const ShaderDiskCacheDump& dump,
|
||||
const std::set<GLenum>& supported_formats);
|
||||
const std::unordered_set<GLenum>& supported_formats);
|
||||
|
||||
const PrecompiledVariants* GetPrecompiledVariants(u64 unique_identifier) const;
|
||||
|
||||
Core::System& system;
|
||||
Core::Frontend::EmuWindow& emu_window;
|
||||
const Device& device;
|
||||
|
||||
ShaderDiskCacheOpenGL disk_cache;
|
||||
|
||||
PrecompiledShaders precompiled_shaders;
|
||||
PrecompiledPrograms precompiled_programs;
|
||||
std::unordered_map<u64, PrecompiledVariants> precompiled_variants;
|
||||
|
||||
std::unordered_map<u64, UnspecializedShader> unspecialized_shaders;
|
||||
|
||||
std::array<Shader, Maxwell::MaxShaderProgram> last_shaders;
|
||||
};
|
||||
|
||||
|
||||
@@ -415,27 +415,6 @@ public:
|
||||
return code.GetResult();
|
||||
}
|
||||
|
||||
ShaderEntries GetShaderEntries() const {
|
||||
ShaderEntries entries;
|
||||
for (const auto& cbuf : ir.GetConstantBuffers()) {
|
||||
entries.const_buffers.emplace_back(cbuf.second.GetMaxOffset(), cbuf.second.IsIndirect(),
|
||||
cbuf.first);
|
||||
}
|
||||
for (const auto& sampler : ir.GetSamplers()) {
|
||||
entries.samplers.emplace_back(sampler);
|
||||
}
|
||||
for (const auto& [offset, image] : ir.GetImages()) {
|
||||
entries.images.emplace_back(image);
|
||||
}
|
||||
for (const auto& [base, usage] : ir.GetGlobalMemory()) {
|
||||
entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset,
|
||||
usage.is_read, usage.is_written);
|
||||
}
|
||||
entries.clip_distances = ir.GetClipDistances();
|
||||
entries.shader_length = ir.GetLength();
|
||||
return entries;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ASTDecompiler;
|
||||
friend class ExprDecompiler;
|
||||
@@ -2337,6 +2316,11 @@ public:
|
||||
inner += expr.value ? "true" : "false";
|
||||
}
|
||||
|
||||
void operator()(VideoCommon::Shader::ExprGprEqual& expr) {
|
||||
inner +=
|
||||
"( ftou(" + decomp.GetRegister(expr.gpr) + ") == " + std::to_string(expr.value) + ')';
|
||||
}
|
||||
|
||||
std::string& GetResult() {
|
||||
return inner;
|
||||
}
|
||||
@@ -2475,25 +2459,46 @@ void GLSLDecompiler::DecompileAST() {
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
std::string GetCommonDeclarations() {
|
||||
return fmt::format(
|
||||
"#define ftoi floatBitsToInt\n"
|
||||
"#define ftou floatBitsToUint\n"
|
||||
"#define itof intBitsToFloat\n"
|
||||
"#define utof uintBitsToFloat\n\n"
|
||||
"bvec2 HalfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n"
|
||||
" bvec2 is_nan1 = isnan(pair1);\n"
|
||||
" bvec2 is_nan2 = isnan(pair2);\n"
|
||||
" return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || "
|
||||
"is_nan2.y);\n"
|
||||
"}}\n\n");
|
||||
ShaderEntries GetEntries(const VideoCommon::Shader::ShaderIR& ir) {
|
||||
ShaderEntries entries;
|
||||
for (const auto& cbuf : ir.GetConstantBuffers()) {
|
||||
entries.const_buffers.emplace_back(cbuf.second.GetMaxOffset(), cbuf.second.IsIndirect(),
|
||||
cbuf.first);
|
||||
}
|
||||
for (const auto& sampler : ir.GetSamplers()) {
|
||||
entries.samplers.emplace_back(sampler);
|
||||
}
|
||||
for (const auto& [offset, image] : ir.GetImages()) {
|
||||
entries.images.emplace_back(image);
|
||||
}
|
||||
for (const auto& [base, usage] : ir.GetGlobalMemory()) {
|
||||
entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset, usage.is_read,
|
||||
usage.is_written);
|
||||
}
|
||||
entries.clip_distances = ir.GetClipDistances();
|
||||
entries.shader_length = ir.GetLength();
|
||||
return entries;
|
||||
}
|
||||
|
||||
ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage,
|
||||
const std::string& suffix) {
|
||||
std::string GetCommonDeclarations() {
|
||||
return R"(#define ftoi floatBitsToInt
|
||||
#define ftou floatBitsToUint
|
||||
#define itof intBitsToFloat
|
||||
#define utof uintBitsToFloat
|
||||
|
||||
bvec2 HalfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {
|
||||
bvec2 is_nan1 = isnan(pair1);
|
||||
bvec2 is_nan2 = isnan(pair2);
|
||||
return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || is_nan2.y);
|
||||
}
|
||||
)";
|
||||
}
|
||||
|
||||
std::string Decompile(const Device& device, const ShaderIR& ir, ProgramType stage,
|
||||
const std::string& suffix) {
|
||||
GLSLDecompiler decompiler(device, ir, stage, suffix);
|
||||
decompiler.Decompile();
|
||||
return {decompiler.GetResult(), decompiler.GetShaderEntries()};
|
||||
return decompiler.GetResult();
|
||||
}
|
||||
|
||||
} // namespace OpenGL::GLShader
|
||||
|
||||
@@ -34,10 +34,7 @@ enum class ProgramType : u32 {
|
||||
|
||||
namespace OpenGL::GLShader {
|
||||
|
||||
struct ShaderEntries;
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
using ProgramResult = std::pair<std::string, ShaderEntries>;
|
||||
using SamplerEntry = VideoCommon::Shader::Sampler;
|
||||
using ImageEntry = VideoCommon::Shader::Image;
|
||||
|
||||
@@ -93,9 +90,11 @@ struct ShaderEntries {
|
||||
std::size_t shader_length{};
|
||||
};
|
||||
|
||||
ShaderEntries GetEntries(const VideoCommon::Shader::ShaderIR& ir);
|
||||
|
||||
std::string GetCommonDeclarations();
|
||||
|
||||
ProgramResult Decompile(const Device& device, const VideoCommon::Shader::ShaderIR& ir,
|
||||
ProgramType stage, const std::string& suffix);
|
||||
std::string Decompile(const Device& device, const VideoCommon::Shader::ShaderIR& ir,
|
||||
ProgramType stage, const std::string& suffix);
|
||||
|
||||
} // namespace OpenGL::GLShader
|
||||
|
||||
@@ -22,6 +22,29 @@
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
using VideoCommon::Shader::BindlessSamplerMap;
|
||||
using VideoCommon::Shader::BoundSamplerMap;
|
||||
using VideoCommon::Shader::KeyMap;
|
||||
|
||||
namespace {
|
||||
|
||||
struct ConstBufferKey {
|
||||
u32 cbuf;
|
||||
u32 offset;
|
||||
u32 value;
|
||||
};
|
||||
|
||||
struct BoundSamplerKey {
|
||||
u32 offset;
|
||||
Tegra::Engines::SamplerDescriptor sampler;
|
||||
};
|
||||
|
||||
struct BindlessSamplerKey {
|
||||
u32 cbuf;
|
||||
u32 offset;
|
||||
Tegra::Engines::SamplerDescriptor sampler;
|
||||
};
|
||||
|
||||
using ShaderCacheVersionHash = std::array<u8, 64>;
|
||||
|
||||
enum class TransferableEntryKind : u32 {
|
||||
@@ -29,18 +52,10 @@ enum class TransferableEntryKind : u32 {
|
||||
Usage,
|
||||
};
|
||||
|
||||
enum class PrecompiledEntryKind : u32 {
|
||||
Decompiled,
|
||||
Dump,
|
||||
};
|
||||
|
||||
constexpr u32 NativeVersion = 4;
|
||||
constexpr u32 NativeVersion = 5;
|
||||
|
||||
// Making sure sizes doesn't change by accident
|
||||
static_assert(sizeof(BaseBindings) == 16);
|
||||
static_assert(sizeof(ShaderDiskCacheUsage) == 40);
|
||||
|
||||
namespace {
|
||||
|
||||
ShaderCacheVersionHash GetShaderCacheVersionHash() {
|
||||
ShaderCacheVersionHash hash{};
|
||||
@@ -49,13 +64,11 @@ ShaderCacheVersionHash GetShaderCacheVersionHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // Anonymous namespace
|
||||
|
||||
ShaderDiskCacheRaw::ShaderDiskCacheRaw(u64 unique_identifier, ProgramType program_type,
|
||||
u32 program_code_size, u32 program_code_size_b,
|
||||
ProgramCode program_code, ProgramCode program_code_b)
|
||||
: unique_identifier{unique_identifier}, program_type{program_type},
|
||||
program_code_size{program_code_size}, program_code_size_b{program_code_size_b},
|
||||
program_code{std::move(program_code)}, program_code_b{std::move(program_code_b)} {}
|
||||
|
||||
ShaderDiskCacheRaw::ShaderDiskCacheRaw() = default;
|
||||
@@ -90,15 +103,16 @@ bool ShaderDiskCacheRaw::Load(FileUtil::IOFile& file) {
|
||||
bool ShaderDiskCacheRaw::Save(FileUtil::IOFile& file) const {
|
||||
if (file.WriteObject(unique_identifier) != 1 ||
|
||||
file.WriteObject(static_cast<u32>(program_type)) != 1 ||
|
||||
file.WriteObject(program_code_size) != 1 || file.WriteObject(program_code_size_b) != 1) {
|
||||
file.WriteObject(static_cast<u32>(program_code.size())) != 1 ||
|
||||
file.WriteObject(static_cast<u32>(program_code_b.size())) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.WriteArray(program_code.data(), program_code_size) != program_code_size)
|
||||
if (file.WriteArray(program_code.data(), program_code.size()) != program_code.size())
|
||||
return false;
|
||||
|
||||
if (HasProgramA() &&
|
||||
file.WriteArray(program_code_b.data(), program_code_size_b) != program_code_size_b) {
|
||||
file.WriteArray(program_code_b.data(), program_code_b.size()) != program_code_b.size()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -127,13 +141,13 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
|
||||
u32 version{};
|
||||
if (file.ReadBytes(&version, sizeof(version)) != sizeof(version)) {
|
||||
LOG_ERROR(Render_OpenGL,
|
||||
"Failed to get transferable cache version for title id={} - skipping",
|
||||
"Failed to get transferable cache version for title id={}, skipping",
|
||||
GetTitleID());
|
||||
return {};
|
||||
}
|
||||
|
||||
if (version < NativeVersion) {
|
||||
LOG_INFO(Render_OpenGL, "Transferable shader cache is old - removing");
|
||||
LOG_INFO(Render_OpenGL, "Transferable shader cache is old, removing");
|
||||
file.Close();
|
||||
InvalidateTransferable();
|
||||
is_usable = true;
|
||||
@@ -141,17 +155,18 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
|
||||
}
|
||||
if (version > NativeVersion) {
|
||||
LOG_WARNING(Render_OpenGL, "Transferable shader cache was generated with a newer version "
|
||||
"of the emulator - skipping");
|
||||
"of the emulator, skipping");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Version is valid, load the shaders
|
||||
constexpr const char error_loading[] = "Failed to load transferable raw entry, skipping";
|
||||
std::vector<ShaderDiskCacheRaw> raws;
|
||||
std::vector<ShaderDiskCacheUsage> usages;
|
||||
while (file.Tell() < file.GetSize()) {
|
||||
TransferableEntryKind kind{};
|
||||
if (file.ReadBytes(&kind, sizeof(u32)) != sizeof(u32)) {
|
||||
LOG_ERROR(Render_OpenGL, "Failed to read transferable file - skipping");
|
||||
LOG_ERROR(Render_OpenGL, "Failed to read transferable file, skipping");
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -159,7 +174,7 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
|
||||
case TransferableEntryKind::Raw: {
|
||||
ShaderDiskCacheRaw entry;
|
||||
if (!entry.Load(file)) {
|
||||
LOG_ERROR(Render_OpenGL, "Failed to load transferable raw entry - skipping");
|
||||
LOG_ERROR(Render_OpenGL, error_loading);
|
||||
return {};
|
||||
}
|
||||
transferable.insert({entry.GetUniqueIdentifier(), {}});
|
||||
@@ -167,16 +182,45 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
|
||||
break;
|
||||
}
|
||||
case TransferableEntryKind::Usage: {
|
||||
ShaderDiskCacheUsage usage{};
|
||||
if (file.ReadBytes(&usage, sizeof(usage)) != sizeof(usage)) {
|
||||
LOG_ERROR(Render_OpenGL, "Failed to load transferable usage entry - skipping");
|
||||
ShaderDiskCacheUsage usage;
|
||||
|
||||
u32 num_keys{};
|
||||
u32 num_bound_samplers{};
|
||||
u32 num_bindless_samplers{};
|
||||
if (file.ReadArray(&usage.unique_identifier, 1) != 1 ||
|
||||
file.ReadArray(&usage.variant, 1) != 1 || file.ReadArray(&num_keys, 1) != 1 ||
|
||||
file.ReadArray(&num_bound_samplers, 1) != 1 ||
|
||||
file.ReadArray(&num_bindless_samplers, 1) != 1) {
|
||||
LOG_ERROR(Render_OpenGL, error_loading);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<ConstBufferKey> keys(num_keys);
|
||||
std::vector<BoundSamplerKey> bound_samplers(num_bound_samplers);
|
||||
std::vector<BindlessSamplerKey> bindless_samplers(num_bindless_samplers);
|
||||
if (file.ReadArray(keys.data(), keys.size()) != keys.size() ||
|
||||
file.ReadArray(bound_samplers.data(), bound_samplers.size()) !=
|
||||
bound_samplers.size() ||
|
||||
file.ReadArray(bindless_samplers.data(), bindless_samplers.size()) !=
|
||||
bindless_samplers.size()) {
|
||||
LOG_ERROR(Render_OpenGL, error_loading);
|
||||
return {};
|
||||
}
|
||||
for (const auto& key : keys) {
|
||||
usage.keys.insert({{key.cbuf, key.offset}, key.value});
|
||||
}
|
||||
for (const auto& key : bound_samplers) {
|
||||
usage.bound_samplers.emplace(key.offset, key.sampler);
|
||||
}
|
||||
for (const auto& key : bindless_samplers) {
|
||||
usage.bindless_samplers.insert({{key.cbuf, key.offset}, key.sampler});
|
||||
}
|
||||
|
||||
usages.push_back(std::move(usage));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Render_OpenGL, "Unknown transferable shader cache entry kind={} - skipping",
|
||||
LOG_ERROR(Render_OpenGL, "Unknown transferable shader cache entry kind={}, skipping",
|
||||
static_cast<u32>(kind));
|
||||
return {};
|
||||
}
|
||||
@@ -186,13 +230,14 @@ ShaderDiskCacheOpenGL::LoadTransferable() {
|
||||
return {{std::move(raws), std::move(usages)}};
|
||||
}
|
||||
|
||||
std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>
|
||||
std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>
|
||||
ShaderDiskCacheOpenGL::LoadPrecompiled() {
|
||||
if (!is_usable) {
|
||||
return {};
|
||||
}
|
||||
|
||||
FileUtil::IOFile file(GetPrecompiledPath(), "rb");
|
||||
std::string path = GetPrecompiledPath();
|
||||
FileUtil::IOFile file(path, "rb");
|
||||
if (!file.IsOpen()) {
|
||||
LOG_INFO(Render_OpenGL, "No precompiled shader cache found for game with title id={}",
|
||||
GetTitleID());
|
||||
@@ -202,7 +247,7 @@ ShaderDiskCacheOpenGL::LoadPrecompiled() {
|
||||
const auto result = LoadPrecompiledFile(file);
|
||||
if (!result) {
|
||||
LOG_INFO(Render_OpenGL,
|
||||
"Failed to load precompiled cache for game with title id={} - removing",
|
||||
"Failed to load precompiled cache for game with title id={}, removing",
|
||||
GetTitleID());
|
||||
file.Close();
|
||||
InvalidatePrecompiled();
|
||||
@@ -211,7 +256,7 @@ ShaderDiskCacheOpenGL::LoadPrecompiled() {
|
||||
return *result;
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>>
|
||||
std::optional<std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>>
|
||||
ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) {
|
||||
// Read compressed file from disk and decompress to virtual precompiled cache file
|
||||
std::vector<u8> compressed(file.GetSize());
|
||||
@@ -231,238 +276,56 @@ ShaderDiskCacheOpenGL::LoadPrecompiledFile(FileUtil::IOFile& file) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unordered_map<u64, ShaderDiskCacheDecompiled> decompiled;
|
||||
ShaderDumpsMap dumps;
|
||||
while (precompiled_cache_virtual_file_offset < precompiled_cache_virtual_file.GetSize()) {
|
||||
PrecompiledEntryKind kind{};
|
||||
if (!LoadObjectFromPrecompiled(kind)) {
|
||||
u32 num_keys{};
|
||||
u32 num_bound_samplers{};
|
||||
u32 num_bindless_samplers{};
|
||||
ShaderDiskCacheUsage usage;
|
||||
if (!LoadObjectFromPrecompiled(usage.unique_identifier) ||
|
||||
!LoadObjectFromPrecompiled(usage.variant) || !LoadObjectFromPrecompiled(num_keys) ||
|
||||
!LoadObjectFromPrecompiled(num_bound_samplers) ||
|
||||
!LoadObjectFromPrecompiled(num_bindless_samplers)) {
|
||||
return {};
|
||||
}
|
||||
std::vector<ConstBufferKey> keys(num_keys);
|
||||
std::vector<BoundSamplerKey> bound_samplers(num_bound_samplers);
|
||||
std::vector<BindlessSamplerKey> bindless_samplers(num_bindless_samplers);
|
||||
if (!LoadArrayFromPrecompiled(keys.data(), keys.size()) ||
|
||||
!LoadArrayFromPrecompiled(bound_samplers.data(), bound_samplers.size()) !=
|
||||
bound_samplers.size() ||
|
||||
!LoadArrayFromPrecompiled(bindless_samplers.data(), bindless_samplers.size()) !=
|
||||
bindless_samplers.size()) {
|
||||
return {};
|
||||
}
|
||||
for (const auto& key : keys) {
|
||||
usage.keys.insert({{key.cbuf, key.offset}, key.value});
|
||||
}
|
||||
for (const auto& key : bound_samplers) {
|
||||
usage.bound_samplers.emplace(key.offset, key.sampler);
|
||||
}
|
||||
for (const auto& key : bindless_samplers) {
|
||||
usage.bindless_samplers.insert({{key.cbuf, key.offset}, key.sampler});
|
||||
}
|
||||
|
||||
ShaderDiskCacheDump dump;
|
||||
if (!LoadObjectFromPrecompiled(dump.binary_format)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (kind) {
|
||||
case PrecompiledEntryKind::Decompiled: {
|
||||
u64 unique_identifier{};
|
||||
if (!LoadObjectFromPrecompiled(unique_identifier)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto entry = LoadDecompiledEntry();
|
||||
if (!entry) {
|
||||
return {};
|
||||
}
|
||||
decompiled.insert({unique_identifier, std::move(*entry)});
|
||||
break;
|
||||
}
|
||||
case PrecompiledEntryKind::Dump: {
|
||||
ShaderDiskCacheUsage usage;
|
||||
if (!LoadObjectFromPrecompiled(usage)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ShaderDiskCacheDump dump;
|
||||
if (!LoadObjectFromPrecompiled(dump.binary_format)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
u32 binary_length{};
|
||||
if (!LoadObjectFromPrecompiled(binary_length)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
dump.binary.resize(binary_length);
|
||||
if (!LoadArrayFromPrecompiled(dump.binary.data(), dump.binary.size())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
dumps.insert({usage, dump});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
u32 binary_length{};
|
||||
if (!LoadObjectFromPrecompiled(binary_length)) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {{decompiled, dumps}};
|
||||
}
|
||||
|
||||
std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEntry() {
|
||||
u32 code_size{};
|
||||
if (!LoadObjectFromPrecompiled(code_size)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string code(code_size, '\0');
|
||||
if (!LoadArrayFromPrecompiled(code.data(), code.size())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ShaderDiskCacheDecompiled entry;
|
||||
entry.code = std::move(code);
|
||||
|
||||
u32 const_buffers_count{};
|
||||
if (!LoadObjectFromPrecompiled(const_buffers_count)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < const_buffers_count; ++i) {
|
||||
u32 max_offset{};
|
||||
u32 index{};
|
||||
bool is_indirect{};
|
||||
if (!LoadObjectFromPrecompiled(max_offset) || !LoadObjectFromPrecompiled(index) ||
|
||||
!LoadObjectFromPrecompiled(is_indirect)) {
|
||||
dump.binary.resize(binary_length);
|
||||
if (!LoadArrayFromPrecompiled(dump.binary.data(), dump.binary.size())) {
|
||||
return {};
|
||||
}
|
||||
entry.entries.const_buffers.emplace_back(max_offset, is_indirect, index);
|
||||
}
|
||||
|
||||
u32 samplers_count{};
|
||||
if (!LoadObjectFromPrecompiled(samplers_count)) {
|
||||
return {};
|
||||
dumps.emplace(std::move(usage), dump);
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < samplers_count; ++i) {
|
||||
u64 offset{};
|
||||
u64 index{};
|
||||
u32 type{};
|
||||
bool is_array{};
|
||||
bool is_shadow{};
|
||||
bool is_bindless{};
|
||||
if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||
|
||||
!LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_array) ||
|
||||
!LoadObjectFromPrecompiled(is_shadow) || !LoadObjectFromPrecompiled(is_bindless)) {
|
||||
return {};
|
||||
}
|
||||
entry.entries.samplers.emplace_back(
|
||||
static_cast<std::size_t>(offset), static_cast<std::size_t>(index),
|
||||
static_cast<Tegra::Shader::TextureType>(type), is_array, is_shadow, is_bindless);
|
||||
}
|
||||
|
||||
u32 images_count{};
|
||||
if (!LoadObjectFromPrecompiled(images_count)) {
|
||||
return {};
|
||||
}
|
||||
for (u32 i = 0; i < images_count; ++i) {
|
||||
u64 offset{};
|
||||
u64 index{};
|
||||
u32 type{};
|
||||
u8 is_bindless{};
|
||||
u8 is_written{};
|
||||
u8 is_read{};
|
||||
u8 is_atomic{};
|
||||
if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||
|
||||
!LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) ||
|
||||
!LoadObjectFromPrecompiled(is_written) || !LoadObjectFromPrecompiled(is_read) ||
|
||||
!LoadObjectFromPrecompiled(is_atomic)) {
|
||||
return {};
|
||||
}
|
||||
entry.entries.images.emplace_back(
|
||||
static_cast<std::size_t>(offset), static_cast<std::size_t>(index),
|
||||
static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0, is_written != 0,
|
||||
is_read != 0, is_atomic != 0);
|
||||
}
|
||||
|
||||
u32 global_memory_count{};
|
||||
if (!LoadObjectFromPrecompiled(global_memory_count)) {
|
||||
return {};
|
||||
}
|
||||
for (u32 i = 0; i < global_memory_count; ++i) {
|
||||
u32 cbuf_index{};
|
||||
u32 cbuf_offset{};
|
||||
bool is_read{};
|
||||
bool is_written{};
|
||||
if (!LoadObjectFromPrecompiled(cbuf_index) || !LoadObjectFromPrecompiled(cbuf_offset) ||
|
||||
!LoadObjectFromPrecompiled(is_read) || !LoadObjectFromPrecompiled(is_written)) {
|
||||
return {};
|
||||
}
|
||||
entry.entries.global_memory_entries.emplace_back(cbuf_index, cbuf_offset, is_read,
|
||||
is_written);
|
||||
}
|
||||
|
||||
for (auto& clip_distance : entry.entries.clip_distances) {
|
||||
if (!LoadObjectFromPrecompiled(clip_distance)) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
u64 shader_length{};
|
||||
if (!LoadObjectFromPrecompiled(shader_length)) {
|
||||
return {};
|
||||
}
|
||||
entry.entries.shader_length = static_cast<std::size_t>(shader_length);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std::string& code,
|
||||
const GLShader::ShaderEntries& entries) {
|
||||
if (!SaveObjectToPrecompiled(static_cast<u32>(PrecompiledEntryKind::Decompiled)) ||
|
||||
!SaveObjectToPrecompiled(unique_identifier) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(code.size())) ||
|
||||
!SaveArrayToPrecompiled(code.data(), code.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SaveObjectToPrecompiled(static_cast<u32>(entries.const_buffers.size()))) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& cbuf : entries.const_buffers) {
|
||||
if (!SaveObjectToPrecompiled(static_cast<u32>(cbuf.GetMaxOffset())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(cbuf.GetIndex())) ||
|
||||
!SaveObjectToPrecompiled(cbuf.IsIndirect())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SaveObjectToPrecompiled(static_cast<u32>(entries.samplers.size()))) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& sampler : entries.samplers) {
|
||||
if (!SaveObjectToPrecompiled(static_cast<u64>(sampler.GetOffset())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u64>(sampler.GetIndex())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(sampler.GetType())) ||
|
||||
!SaveObjectToPrecompiled(sampler.IsArray()) ||
|
||||
!SaveObjectToPrecompiled(sampler.IsShadow()) ||
|
||||
!SaveObjectToPrecompiled(sampler.IsBindless())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SaveObjectToPrecompiled(static_cast<u32>(entries.images.size()))) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& image : entries.images) {
|
||||
if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0)) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u8>(image.IsAtomic() ? 1 : 0))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SaveObjectToPrecompiled(static_cast<u32>(entries.global_memory_entries.size()))) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& gmem : entries.global_memory_entries) {
|
||||
if (!SaveObjectToPrecompiled(static_cast<u32>(gmem.GetCbufIndex())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(gmem.GetCbufOffset())) ||
|
||||
!SaveObjectToPrecompiled(gmem.IsRead()) || !SaveObjectToPrecompiled(gmem.IsWritten())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const bool clip_distance : entries.clip_distances) {
|
||||
if (!SaveObjectToPrecompiled(clip_distance)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SaveObjectToPrecompiled(static_cast<u64>(entries.shader_length))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return dumps;
|
||||
}
|
||||
|
||||
void ShaderDiskCacheOpenGL::InvalidateTransferable() {
|
||||
@@ -494,10 +357,11 @@ void ShaderDiskCacheOpenGL::SaveRaw(const ShaderDiskCacheRaw& entry) {
|
||||
}
|
||||
|
||||
FileUtil::IOFile file = AppendTransferableFile();
|
||||
if (!file.IsOpen())
|
||||
if (!file.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
if (file.WriteObject(TransferableEntryKind::Raw) != 1 || !entry.Save(file)) {
|
||||
LOG_ERROR(Render_OpenGL, "Failed to save raw transferable cache entry - removing");
|
||||
LOG_ERROR(Render_OpenGL, "Failed to save raw transferable cache entry, removing");
|
||||
file.Close();
|
||||
InvalidateTransferable();
|
||||
return;
|
||||
@@ -523,29 +387,39 @@ void ShaderDiskCacheOpenGL::SaveUsage(const ShaderDiskCacheUsage& usage) {
|
||||
FileUtil::IOFile file = AppendTransferableFile();
|
||||
if (!file.IsOpen())
|
||||
return;
|
||||
|
||||
if (file.WriteObject(TransferableEntryKind::Usage) != 1 || file.WriteObject(usage) != 1) {
|
||||
LOG_ERROR(Render_OpenGL, "Failed to save usage transferable cache entry - removing");
|
||||
const auto Close = [&] {
|
||||
LOG_ERROR(Render_OpenGL, "Failed to save usage transferable cache entry, removing");
|
||||
file.Close();
|
||||
InvalidateTransferable();
|
||||
};
|
||||
|
||||
if (file.WriteObject(TransferableEntryKind::Usage) != 1 ||
|
||||
file.WriteObject(usage.unique_identifier) != 1 || file.WriteObject(usage.variant) != 1 ||
|
||||
file.WriteObject(static_cast<u32>(usage.keys.size())) != 1 ||
|
||||
file.WriteObject(static_cast<u32>(usage.bound_samplers.size())) != 1 ||
|
||||
file.WriteObject(static_cast<u32>(usage.bindless_samplers.size())) != 1) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderDiskCacheOpenGL::SaveDecompiled(u64 unique_identifier, const std::string& code,
|
||||
const GLShader::ShaderEntries& entries) {
|
||||
if (!is_usable) {
|
||||
return;
|
||||
for (const auto& [pair, value] : usage.keys) {
|
||||
const auto [cbuf, offset] = pair;
|
||||
if (file.WriteObject(ConstBufferKey{cbuf, offset, value}) != 1) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (precompiled_cache_virtual_file.GetSize() == 0) {
|
||||
SavePrecompiledHeaderToVirtualPrecompiledCache();
|
||||
for (const auto& [offset, sampler] : usage.bound_samplers) {
|
||||
if (file.WriteObject(BoundSamplerKey{offset, sampler}) != 1) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SaveDecompiledFile(unique_identifier, code, entries)) {
|
||||
LOG_ERROR(Render_OpenGL,
|
||||
"Failed to save decompiled entry to the precompiled file - removing");
|
||||
InvalidatePrecompiled();
|
||||
for (const auto& [pair, sampler] : usage.bindless_samplers) {
|
||||
const auto [cbuf, offset] = pair;
|
||||
if (file.WriteObject(BindlessSamplerKey{cbuf, offset, sampler}) != 1) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,6 +428,13 @@ void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint p
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(Rodrigo): This is a design smell. I shouldn't be having to manually write the header
|
||||
// when writing the dump. This should be done the moment I get access to write to the virtual
|
||||
// file.
|
||||
if (precompiled_cache_virtual_file.GetSize() == 0) {
|
||||
SavePrecompiledHeaderToVirtualPrecompiledCache();
|
||||
}
|
||||
|
||||
GLint binary_length{};
|
||||
glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binary_length);
|
||||
|
||||
@@ -561,21 +442,51 @@ void ShaderDiskCacheOpenGL::SaveDump(const ShaderDiskCacheUsage& usage, GLuint p
|
||||
std::vector<u8> binary(binary_length);
|
||||
glGetProgramBinary(program, binary_length, nullptr, &binary_format, binary.data());
|
||||
|
||||
if (!SaveObjectToPrecompiled(static_cast<u32>(PrecompiledEntryKind::Dump)) ||
|
||||
!SaveObjectToPrecompiled(usage) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(binary_format)) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(binary_length)) ||
|
||||
!SaveArrayToPrecompiled(binary.data(), binary.size())) {
|
||||
LOG_ERROR(Render_OpenGL, "Failed to save binary program file in shader={:016x} - removing",
|
||||
const auto Close = [&] {
|
||||
LOG_ERROR(Render_OpenGL, "Failed to save binary program file in shader={:016X}, removing",
|
||||
usage.unique_identifier);
|
||||
InvalidatePrecompiled();
|
||||
};
|
||||
|
||||
if (!SaveObjectToPrecompiled(usage.unique_identifier) ||
|
||||
!SaveObjectToPrecompiled(usage.variant) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(usage.keys.size())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(usage.bound_samplers.size())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(usage.bindless_samplers.size()))) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
for (const auto& [pair, value] : usage.keys) {
|
||||
const auto [cbuf, offset] = pair;
|
||||
if (SaveObjectToPrecompiled(ConstBufferKey{cbuf, offset, value}) != 1) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const auto& [offset, sampler] : usage.bound_samplers) {
|
||||
if (SaveObjectToPrecompiled(BoundSamplerKey{offset, sampler}) != 1) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const auto& [pair, sampler] : usage.bindless_samplers) {
|
||||
const auto [cbuf, offset] = pair;
|
||||
if (SaveObjectToPrecompiled(BindlessSamplerKey{cbuf, offset, sampler}) != 1) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!SaveObjectToPrecompiled(static_cast<u32>(binary_format)) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(binary_length)) ||
|
||||
!SaveArrayToPrecompiled(binary.data(), binary.size())) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
FileUtil::IOFile ShaderDiskCacheOpenGL::AppendTransferableFile() const {
|
||||
if (!EnsureDirectories())
|
||||
if (!EnsureDirectories()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto transferable_path{GetTransferablePath()};
|
||||
const bool existed = FileUtil::Exists(transferable_path);
|
||||
@@ -607,8 +518,8 @@ void ShaderDiskCacheOpenGL::SavePrecompiledHeaderToVirtualPrecompiledCache() {
|
||||
|
||||
void ShaderDiskCacheOpenGL::SaveVirtualPrecompiledFile() {
|
||||
precompiled_cache_virtual_file_offset = 0;
|
||||
const std::vector<u8>& uncompressed = precompiled_cache_virtual_file.ReadAllBytes();
|
||||
const std::vector<u8>& compressed =
|
||||
const std::vector<u8> uncompressed = precompiled_cache_virtual_file.ReadAllBytes();
|
||||
const std::vector<u8> compressed =
|
||||
Common::Compression::CompressDataZSTDDefault(uncompressed.data(), uncompressed.size());
|
||||
|
||||
const auto precompiled_path{GetPrecompiledPath()};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/shader/const_buffer_locker.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@@ -53,6 +55,7 @@ struct BaseBindings {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<BaseBindings>);
|
||||
|
||||
/// Describes the different variants a single program can be compiled.
|
||||
struct ProgramVariant {
|
||||
@@ -70,13 +73,20 @@ struct ProgramVariant {
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(std::is_trivially_copyable_v<ProgramVariant>);
|
||||
|
||||
/// Describes how a shader is used.
|
||||
struct ShaderDiskCacheUsage {
|
||||
u64 unique_identifier{};
|
||||
ProgramVariant variant;
|
||||
VideoCommon::Shader::KeyMap keys;
|
||||
VideoCommon::Shader::BoundSamplerMap bound_samplers;
|
||||
VideoCommon::Shader::BindlessSamplerMap bindless_samplers;
|
||||
|
||||
bool operator==(const ShaderDiskCacheUsage& rhs) const {
|
||||
return std::tie(unique_identifier, variant) == std::tie(rhs.unique_identifier, rhs.variant);
|
||||
return std::tie(unique_identifier, variant, keys, bound_samplers, bindless_samplers) ==
|
||||
std::tie(rhs.unique_identifier, rhs.variant, rhs.keys, rhs.bound_samplers,
|
||||
rhs.bindless_samplers);
|
||||
}
|
||||
|
||||
bool operator!=(const ShaderDiskCacheUsage& rhs) const {
|
||||
@@ -123,8 +133,7 @@ namespace OpenGL {
|
||||
class ShaderDiskCacheRaw {
|
||||
public:
|
||||
explicit ShaderDiskCacheRaw(u64 unique_identifier, ProgramType program_type,
|
||||
u32 program_code_size, u32 program_code_size_b,
|
||||
ProgramCode program_code, ProgramCode program_code_b);
|
||||
ProgramCode program_code, ProgramCode program_code_b = {});
|
||||
ShaderDiskCacheRaw();
|
||||
~ShaderDiskCacheRaw();
|
||||
|
||||
@@ -155,22 +164,14 @@ public:
|
||||
private:
|
||||
u64 unique_identifier{};
|
||||
ProgramType program_type{};
|
||||
u32 program_code_size{};
|
||||
u32 program_code_size_b{};
|
||||
|
||||
ProgramCode program_code;
|
||||
ProgramCode program_code_b;
|
||||
};
|
||||
|
||||
/// Contains decompiled data from a shader
|
||||
struct ShaderDiskCacheDecompiled {
|
||||
std::string code;
|
||||
GLShader::ShaderEntries entries;
|
||||
};
|
||||
|
||||
/// Contains an OpenGL dumped binary program
|
||||
struct ShaderDiskCacheDump {
|
||||
GLenum binary_format;
|
||||
GLenum binary_format{};
|
||||
std::vector<u8> binary;
|
||||
};
|
||||
|
||||
@@ -184,9 +185,7 @@ public:
|
||||
LoadTransferable();
|
||||
|
||||
/// Loads current game's precompiled cache. Invalidates on failure.
|
||||
std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>,
|
||||
std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>>
|
||||
LoadPrecompiled();
|
||||
std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump> LoadPrecompiled();
|
||||
|
||||
/// Removes the transferable (and precompiled) cache file.
|
||||
void InvalidateTransferable();
|
||||
@@ -200,10 +199,6 @@ public:
|
||||
/// Saves shader usage to the transferable file. Does not check for collisions.
|
||||
void SaveUsage(const ShaderDiskCacheUsage& usage);
|
||||
|
||||
/// Saves a decompiled entry to the precompiled file. Does not check for collisions.
|
||||
void SaveDecompiled(u64 unique_identifier, const std::string& code,
|
||||
const GLShader::ShaderEntries& entries);
|
||||
|
||||
/// Saves a dump entry to the precompiled file. Does not check for collisions.
|
||||
void SaveDump(const ShaderDiskCacheUsage& usage, GLuint program);
|
||||
|
||||
@@ -212,18 +207,9 @@ public:
|
||||
|
||||
private:
|
||||
/// Loads the transferable cache. Returns empty on failure.
|
||||
std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>,
|
||||
std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>>>
|
||||
std::optional<std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>>
|
||||
LoadPrecompiledFile(FileUtil::IOFile& file);
|
||||
|
||||
/// Loads a decompiled cache entry from m_precompiled_cache_virtual_file. Returns empty on
|
||||
/// failure.
|
||||
std::optional<ShaderDiskCacheDecompiled> LoadDecompiledEntry();
|
||||
|
||||
/// Saves a decompiled entry to the passed file. Returns true on success.
|
||||
bool SaveDecompiledFile(u64 unique_identifier, const std::string& code,
|
||||
const GLShader::ShaderEntries& entries);
|
||||
|
||||
/// Opens current game's transferable file and write it's header if it doesn't exist
|
||||
FileUtil::IOFile AppendTransferableFile() const;
|
||||
|
||||
|
||||
@@ -16,17 +16,8 @@ using VideoCommon::Shader::CompilerSettings;
|
||||
using VideoCommon::Shader::ProgramCode;
|
||||
using VideoCommon::Shader::ShaderIR;
|
||||
|
||||
static constexpr u32 PROGRAM_OFFSET = 10;
|
||||
static constexpr u32 COMPUTE_OFFSET = 0;
|
||||
|
||||
static constexpr CompilerSettings settings{CompileDepth::NoFlowStack, true};
|
||||
|
||||
ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup) {
|
||||
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
|
||||
|
||||
std::string out = "// Shader Unique Id: VS" + id + "\n\n";
|
||||
out += GetCommonDeclarations();
|
||||
|
||||
std::string GenerateVertexShader(const Device& device, const ShaderIR& ir, const ShaderIR* ir_b) {
|
||||
std::string out = GetCommonDeclarations();
|
||||
out += R"(
|
||||
layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config {
|
||||
vec4 viewport_flip;
|
||||
@@ -34,17 +25,10 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config {
|
||||
};
|
||||
|
||||
)";
|
||||
|
||||
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
|
||||
const auto stage = setup.IsDualProgram() ? ProgramType::VertexA : ProgramType::VertexB;
|
||||
ProgramResult program = Decompile(device, program_ir, stage, "vertex");
|
||||
out += program.first;
|
||||
|
||||
if (setup.IsDualProgram()) {
|
||||
const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET, setup.program.size_b,
|
||||
settings);
|
||||
ProgramResult program_b = Decompile(device, program_ir_b, ProgramType::VertexB, "vertex_b");
|
||||
out += program_b.first;
|
||||
const auto stage = ir_b ? ProgramType::VertexA : ProgramType::VertexB;
|
||||
out += Decompile(device, ir, stage, "vertex");
|
||||
if (ir_b) {
|
||||
out += Decompile(device, *ir_b, ProgramType::VertexB, "vertex_b");
|
||||
}
|
||||
|
||||
out += R"(
|
||||
@@ -52,7 +36,7 @@ void main() {
|
||||
execute_vertex();
|
||||
)";
|
||||
|
||||
if (setup.IsDualProgram()) {
|
||||
if (ir_b) {
|
||||
out += " execute_vertex_b();";
|
||||
}
|
||||
|
||||
@@ -66,17 +50,13 @@ void main() {
|
||||
// Viewport can be flipped, which is unsupported by glViewport
|
||||
gl_Position.xy *= viewport_flip.xy;
|
||||
}
|
||||
})";
|
||||
|
||||
return {std::move(out), std::move(program.second)};
|
||||
}
|
||||
)";
|
||||
return out;
|
||||
}
|
||||
|
||||
ProgramResult GenerateGeometryShader(const Device& device, const ShaderSetup& setup) {
|
||||
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
|
||||
|
||||
std::string out = "// Shader Unique Id: GS" + id + "\n\n";
|
||||
out += GetCommonDeclarations();
|
||||
|
||||
std::string GenerateGeometryShader(const Device& device, const ShaderIR& ir) {
|
||||
std::string out = GetCommonDeclarations();
|
||||
out += R"(
|
||||
layout (std140, binding = EMULATION_UBO_BINDING) uniform gs_config {
|
||||
vec4 viewport_flip;
|
||||
@@ -84,25 +64,18 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform gs_config {
|
||||
};
|
||||
|
||||
)";
|
||||
|
||||
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
|
||||
ProgramResult program = Decompile(device, program_ir, ProgramType::Geometry, "geometry");
|
||||
out += program.first;
|
||||
out += Decompile(device, ir, ProgramType::Geometry, "geometry");
|
||||
|
||||
out += R"(
|
||||
void main() {
|
||||
execute_geometry();
|
||||
};)";
|
||||
|
||||
return {std::move(out), std::move(program.second)};
|
||||
}
|
||||
)";
|
||||
return out;
|
||||
}
|
||||
|
||||
ProgramResult GenerateFragmentShader(const Device& device, const ShaderSetup& setup) {
|
||||
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
|
||||
|
||||
std::string out = "// Shader Unique Id: FS" + id + "\n\n";
|
||||
out += GetCommonDeclarations();
|
||||
|
||||
std::string GenerateFragmentShader(const Device& device, const ShaderIR& ir) {
|
||||
std::string out = GetCommonDeclarations();
|
||||
out += R"(
|
||||
layout (location = 0) out vec4 FragColor0;
|
||||
layout (location = 1) out vec4 FragColor1;
|
||||
@@ -119,36 +92,25 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform fs_config {
|
||||
};
|
||||
|
||||
)";
|
||||
|
||||
const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a, settings);
|
||||
ProgramResult program = Decompile(device, program_ir, ProgramType::Fragment, "fragment");
|
||||
out += program.first;
|
||||
out += Decompile(device, ir, ProgramType::Fragment, "fragment");
|
||||
|
||||
out += R"(
|
||||
void main() {
|
||||
execute_fragment();
|
||||
}
|
||||
|
||||
)";
|
||||
return {std::move(out), std::move(program.second)};
|
||||
return out;
|
||||
}
|
||||
|
||||
ProgramResult GenerateComputeShader(const Device& device, const ShaderSetup& setup) {
|
||||
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
|
||||
|
||||
std::string out = "// Shader Unique Id: CS" + id + "\n\n";
|
||||
out += GetCommonDeclarations();
|
||||
|
||||
const ShaderIR program_ir(setup.program.code, COMPUTE_OFFSET, setup.program.size_a, settings);
|
||||
ProgramResult program = Decompile(device, program_ir, ProgramType::Compute, "compute");
|
||||
out += program.first;
|
||||
|
||||
std::string GenerateComputeShader(const Device& device, const ShaderIR& ir) {
|
||||
std::string out = GetCommonDeclarations();
|
||||
out += Decompile(device, ir, ProgramType::Compute, "compute");
|
||||
out += R"(
|
||||
void main() {
|
||||
execute_compute();
|
||||
}
|
||||
)";
|
||||
return {std::move(out), std::move(program.second)};
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace OpenGL::GLShader
|
||||
|
||||
@@ -17,44 +17,18 @@ class Device;
|
||||
namespace OpenGL::GLShader {
|
||||
|
||||
using VideoCommon::Shader::ProgramCode;
|
||||
|
||||
struct ShaderSetup {
|
||||
explicit ShaderSetup(ProgramCode program_code) {
|
||||
program.code = std::move(program_code);
|
||||
}
|
||||
|
||||
struct {
|
||||
ProgramCode code;
|
||||
ProgramCode code_b; // Used for dual vertex shaders
|
||||
u64 unique_identifier;
|
||||
std::size_t size_a;
|
||||
std::size_t size_b;
|
||||
} program;
|
||||
|
||||
/// Used in scenarios where we have a dual vertex shaders
|
||||
void SetProgramB(ProgramCode program_b) {
|
||||
program.code_b = std::move(program_b);
|
||||
has_program_b = true;
|
||||
}
|
||||
|
||||
bool IsDualProgram() const {
|
||||
return has_program_b;
|
||||
}
|
||||
|
||||
private:
|
||||
bool has_program_b{};
|
||||
};
|
||||
using VideoCommon::Shader::ShaderIR;
|
||||
|
||||
/// Generates the GLSL vertex shader program source code for the given VS program
|
||||
ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup);
|
||||
std::string GenerateVertexShader(const Device& device, const ShaderIR& ir, const ShaderIR* ir_b);
|
||||
|
||||
/// Generates the GLSL geometry shader program source code for the given GS program
|
||||
ProgramResult GenerateGeometryShader(const Device& device, const ShaderSetup& setup);
|
||||
std::string GenerateGeometryShader(const Device& device, const ShaderIR& ir);
|
||||
|
||||
/// Generates the GLSL fragment shader program source code for the given FS program
|
||||
ProgramResult GenerateFragmentShader(const Device& device, const ShaderSetup& setup);
|
||||
std::string GenerateFragmentShader(const Device& device, const ShaderIR& ir);
|
||||
|
||||
/// Generates the GLSL compute shader program source code for the given CS program
|
||||
ProgramResult GenerateComputeShader(const Device& device, const ShaderSetup& setup);
|
||||
std::string GenerateComputeShader(const Device& device, const ShaderIR& ir);
|
||||
|
||||
} // namespace OpenGL::GLShader
|
||||
|
||||
@@ -111,7 +111,8 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format
|
||||
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
|
||||
true}, // DXT45_SRGB
|
||||
{GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
|
||||
true}, // BC7U_SRGB
|
||||
true}, // BC7U_SRGB
|
||||
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV, ComponentType::UNorm, false}, // R4G4B4A4U
|
||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4_SRGB
|
||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8_SRGB
|
||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5_SRGB
|
||||
@@ -120,6 +121,16 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format
|
||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5_SRGB
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X8
|
||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X8_SRGB
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_6X6
|
||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_6X6_SRGB
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X10
|
||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X10_SRGB
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_12X12
|
||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_12X12_SRGB
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X6
|
||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X6_SRGB
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_6X5
|
||||
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_6X5_SRGB
|
||||
|
||||
// Depth formats
|
||||
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F
|
||||
|
||||
@@ -54,11 +54,13 @@ in vec2 frag_tex_coord;
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D color_texture;
|
||||
uniform vec4 backlight;
|
||||
|
||||
void main() {
|
||||
// Swap RGBA -> ABGR so we don't have to do this on the CPU. This needs to change if we have to
|
||||
// support more framebuffer pixel formats.
|
||||
color = texture(color_texture, frag_tex_coord);
|
||||
// Also multiply the color by the backlight multiplier supplied.
|
||||
color = texture(color_texture, frag_tex_coord) * backlight;
|
||||
}
|
||||
)";
|
||||
|
||||
@@ -102,8 +104,6 @@ RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::Syst
|
||||
RendererOpenGL::~RendererOpenGL() = default;
|
||||
|
||||
void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
|
||||
system.GetPerfStats().EndSystemFrame();
|
||||
|
||||
// Maintain the rasterizer's state as a priority
|
||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
||||
state.AllDirty();
|
||||
@@ -123,8 +123,13 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
|
||||
// Load the framebuffer from memory, draw it to the screen, and swap buffers
|
||||
LoadFBToScreenInfo(*framebuffer);
|
||||
|
||||
if (renderer_settings.screenshot_requested)
|
||||
if (renderer_settings.screenshot_requested) {
|
||||
CaptureScreenshot();
|
||||
}
|
||||
|
||||
if (renderer_settings.backlight_fade_time > 0) {
|
||||
UpdateBacklight();
|
||||
}
|
||||
|
||||
DrawScreen(render_window.GetFramebufferLayout());
|
||||
|
||||
@@ -135,9 +140,6 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
|
||||
|
||||
render_window.PollEvents();
|
||||
|
||||
system.FrameLimiter().DoFrameLimiting(system.CoreTiming().GetGlobalTimeUs());
|
||||
system.GetPerfStats().BeginSystemFrame();
|
||||
|
||||
// Restore the rasterizer state
|
||||
prev_state.AllDirty();
|
||||
prev_state.Apply();
|
||||
@@ -210,9 +212,13 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||
state.Apply();
|
||||
uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix");
|
||||
uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture");
|
||||
uniform_backlight = glGetUniformLocation(shader.handle, "backlight");
|
||||
attrib_position = glGetAttribLocation(shader.handle, "vert_position");
|
||||
attrib_tex_coord = glGetAttribLocation(shader.handle, "vert_tex_coord");
|
||||
|
||||
// Initialize backlight
|
||||
glUniform4f(uniform_backlight, 1.f, 1.f, 1.f, 1.f);
|
||||
|
||||
// Generate VBO handle for drawing
|
||||
vertex_buffer.Create();
|
||||
|
||||
@@ -421,6 +427,29 @@ void RendererOpenGL::CaptureScreenshot() {
|
||||
renderer_settings.screenshot_requested = false;
|
||||
}
|
||||
|
||||
void RendererOpenGL::UpdateBacklight() {
|
||||
constexpr u64 PER_FRAME_FADE_TIME = 1000000000.0f / 60;
|
||||
|
||||
const auto fade_time = renderer_settings.backlight_fade_time.load(std::memory_order_relaxed);
|
||||
auto value = renderer_settings.current_brightness.load(std::memory_order_relaxed);
|
||||
if (fade_time <= PER_FRAME_FADE_TIME) {
|
||||
glUniform4f(uniform_backlight, value, value, value, value);
|
||||
renderer_settings.backlight_fade_time = 0;
|
||||
fade_time_max = 0;
|
||||
} else {
|
||||
if (fade_time_max == 0) {
|
||||
fade_time_max = fade_time;
|
||||
value_max = value;
|
||||
}
|
||||
|
||||
value += (value_max - value) * PER_FRAME_FADE_TIME / fade_time_max;
|
||||
|
||||
glUniform4f(uniform_backlight, value, value, value, value);
|
||||
renderer_settings.backlight_fade_time -= PER_FRAME_FADE_TIME;
|
||||
renderer_settings.current_brightness = value;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* GetSource(GLenum source) {
|
||||
#define RET(s) \
|
||||
case GL_DEBUG_SOURCE_##s: \
|
||||
|
||||
@@ -70,6 +70,7 @@ private:
|
||||
void UpdateFramerate();
|
||||
|
||||
void CaptureScreenshot();
|
||||
void UpdateBacklight();
|
||||
|
||||
// Loads framebuffer from emulated memory into the display information structure
|
||||
void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer);
|
||||
@@ -97,6 +98,7 @@ private:
|
||||
// Shader uniform location indices
|
||||
GLuint uniform_modelview_matrix;
|
||||
GLuint uniform_color_texture;
|
||||
GLuint uniform_backlight;
|
||||
|
||||
// Shader attribute input indices
|
||||
GLuint attrib_position;
|
||||
@@ -105,6 +107,10 @@ private:
|
||||
/// Used for transforming the framebuffer orientation
|
||||
Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags;
|
||||
Common::Rectangle<int> framebuffer_crop_rect;
|
||||
|
||||
// Used for backlight transitions
|
||||
u64 fade_time_max = 0;
|
||||
f32 value_max = 0;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -1701,6 +1701,13 @@ public:
|
||||
return expr.value ? decomp.v_true : decomp.v_false;
|
||||
}
|
||||
|
||||
Id operator()(VideoCommon::Shader::ExprGprEqual& expr) {
|
||||
const Id target = decomp.Constant(decomp.t_uint, expr.value);
|
||||
const Id gpr = decomp.BitcastTo<Type::Uint>(
|
||||
decomp.Emit(decomp.OpLoad(decomp.t_float, decomp.registers.at(expr.gpr))));
|
||||
return decomp.Emit(decomp.OpLogicalEqual(decomp.t_uint, gpr, target));
|
||||
}
|
||||
|
||||
Id Visit(VideoCommon::Shader::Expr& node) {
|
||||
return std::visit(*this, *node);
|
||||
}
|
||||
|
||||
@@ -225,6 +225,10 @@ public:
|
||||
inner += expr.value ? "true" : "false";
|
||||
}
|
||||
|
||||
void operator()(const ExprGprEqual& expr) {
|
||||
inner += "( gpr_" + std::to_string(expr.gpr) + " == " + std::to_string(expr.value) + ')';
|
||||
}
|
||||
|
||||
const std::string& GetResult() const {
|
||||
return inner;
|
||||
}
|
||||
|
||||
108
src/video_core/shader/const_buffer_locker.cpp
Normal file
108
src/video_core/shader/const_buffer_locker.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/shader/const_buffer_locker.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
|
||||
using Tegra::Engines::SamplerDescriptor;
|
||||
|
||||
ConstBufferLocker::ConstBufferLocker(Tegra::Engines::ShaderType shader_stage)
|
||||
: stage{shader_stage} {}
|
||||
|
||||
ConstBufferLocker::ConstBufferLocker(Tegra::Engines::ShaderType shader_stage,
|
||||
Tegra::Engines::ConstBufferEngineInterface& engine)
|
||||
: stage{shader_stage}, engine{&engine} {}
|
||||
|
||||
std::optional<u32> ConstBufferLocker::ObtainKey(u32 buffer, u32 offset) {
|
||||
const std::pair<u32, u32> key = {buffer, offset};
|
||||
const auto iter = keys.find(key);
|
||||
if (iter != keys.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
if (!engine) {
|
||||
return {};
|
||||
}
|
||||
const u32 value = engine->AccessConstBuffer32(stage, buffer, offset);
|
||||
keys.emplace(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::optional<SamplerDescriptor> ConstBufferLocker::ObtainBoundSampler(u32 offset) {
|
||||
const u32 key = offset;
|
||||
const auto iter = bound_samplers.find(key);
|
||||
if (iter != bound_samplers.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
if (!engine) {
|
||||
return {};
|
||||
}
|
||||
const SamplerDescriptor value = engine->AccessBoundSampler(stage, offset);
|
||||
bound_samplers.emplace(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::optional<Tegra::Engines::SamplerDescriptor> ConstBufferLocker::ObtainBindlessSampler(
|
||||
u32 buffer, u32 offset) {
|
||||
const std::pair key = {buffer, offset};
|
||||
const auto iter = bindless_samplers.find(key);
|
||||
if (iter != bindless_samplers.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
if (!engine) {
|
||||
return {};
|
||||
}
|
||||
const SamplerDescriptor value = engine->AccessBindlessSampler(stage, buffer, offset);
|
||||
bindless_samplers.emplace(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
void ConstBufferLocker::InsertKey(u32 buffer, u32 offset, u32 value) {
|
||||
keys.insert_or_assign({buffer, offset}, value);
|
||||
}
|
||||
|
||||
void ConstBufferLocker::InsertBoundSampler(u32 offset, SamplerDescriptor sampler) {
|
||||
bound_samplers.insert_or_assign(offset, sampler);
|
||||
}
|
||||
|
||||
void ConstBufferLocker::InsertBindlessSampler(u32 buffer, u32 offset, SamplerDescriptor sampler) {
|
||||
bindless_samplers.insert_or_assign({buffer, offset}, sampler);
|
||||
}
|
||||
|
||||
bool ConstBufferLocker::IsConsistent() const {
|
||||
if (!engine) {
|
||||
return false;
|
||||
}
|
||||
return std::all_of(keys.begin(), keys.end(),
|
||||
[this](const auto& pair) {
|
||||
const auto [cbuf, offset] = pair.first;
|
||||
const auto value = pair.second;
|
||||
return value == engine->AccessConstBuffer32(stage, cbuf, offset);
|
||||
}) &&
|
||||
std::all_of(bound_samplers.begin(), bound_samplers.end(),
|
||||
[this](const auto& sampler) {
|
||||
const auto [key, value] = sampler;
|
||||
return value == engine->AccessBoundSampler(stage, key);
|
||||
}) &&
|
||||
std::all_of(bindless_samplers.begin(), bindless_samplers.end(),
|
||||
[this](const auto& sampler) {
|
||||
const auto [cbuf, offset] = sampler.first;
|
||||
const auto value = sampler.second;
|
||||
return value == engine->AccessBindlessSampler(stage, cbuf, offset);
|
||||
});
|
||||
}
|
||||
|
||||
bool ConstBufferLocker::HasEqualKeys(const ConstBufferLocker& rhs) const {
|
||||
return keys == rhs.keys && bound_samplers == rhs.bound_samplers &&
|
||||
bindless_samplers == rhs.bindless_samplers;
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
73
src/video_core/shader/const_buffer_locker.h
Normal file
73
src/video_core/shader/const_buffer_locker.h
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
#include "video_core/engines/const_buffer_engine_interface.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
|
||||
using KeyMap = std::unordered_map<std::pair<u32, u32>, u32, Common::PairHash>;
|
||||
using BoundSamplerMap = std::unordered_map<u32, Tegra::Engines::SamplerDescriptor>;
|
||||
using BindlessSamplerMap =
|
||||
std::unordered_map<std::pair<u32, u32>, Tegra::Engines::SamplerDescriptor, Common::PairHash>;
|
||||
|
||||
class ConstBufferLocker {
|
||||
public:
|
||||
explicit ConstBufferLocker(Tegra::Engines::ShaderType shader_stage);
|
||||
|
||||
explicit ConstBufferLocker(Tegra::Engines::ShaderType shader_stage,
|
||||
Tegra::Engines::ConstBufferEngineInterface& engine);
|
||||
|
||||
/// Retrieves a key from the locker, if it's registered, it will give the registered value, if
|
||||
/// not it will obtain it from maxwell3d and register it.
|
||||
std::optional<u32> ObtainKey(u32 buffer, u32 offset);
|
||||
|
||||
std::optional<Tegra::Engines::SamplerDescriptor> ObtainBoundSampler(u32 offset);
|
||||
|
||||
std::optional<Tegra::Engines::SamplerDescriptor> ObtainBindlessSampler(u32 buffer, u32 offset);
|
||||
|
||||
/// Inserts a key.
|
||||
void InsertKey(u32 buffer, u32 offset, u32 value);
|
||||
|
||||
/// Inserts a bound sampler key.
|
||||
void InsertBoundSampler(u32 offset, Tegra::Engines::SamplerDescriptor sampler);
|
||||
|
||||
/// Inserts a bindless sampler key.
|
||||
void InsertBindlessSampler(u32 buffer, u32 offset, Tegra::Engines::SamplerDescriptor sampler);
|
||||
|
||||
/// Checks keys and samplers against engine's current const buffers. Returns true if they are
|
||||
/// the same value, false otherwise;
|
||||
bool IsConsistent() const;
|
||||
|
||||
/// Returns true if the keys are equal to the other ones in the locker.
|
||||
bool HasEqualKeys(const ConstBufferLocker& rhs) const;
|
||||
|
||||
/// Gives an getter to the const buffer keys in the database.
|
||||
const KeyMap& GetKeys() const {
|
||||
return keys;
|
||||
}
|
||||
|
||||
/// Gets samplers database.
|
||||
const BoundSamplerMap& GetBoundSamplers() const {
|
||||
return bound_samplers;
|
||||
}
|
||||
|
||||
/// Gets bindless samplers database.
|
||||
const BindlessSamplerMap& GetBindlessSamplers() const {
|
||||
return bindless_samplers;
|
||||
}
|
||||
|
||||
private:
|
||||
const Tegra::Engines::ShaderType stage;
|
||||
Tegra::Engines::ConstBufferEngineInterface* engine = nullptr;
|
||||
KeyMap keys;
|
||||
BoundSamplerMap bound_samplers;
|
||||
BindlessSamplerMap bindless_samplers;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
@@ -35,14 +35,24 @@ struct BlockStack {
|
||||
std::stack<u32> pbk_stack{};
|
||||
};
|
||||
|
||||
struct BlockBranchInfo {
|
||||
Condition condition{};
|
||||
s32 address{exit_branch};
|
||||
bool kill{};
|
||||
bool is_sync{};
|
||||
bool is_brk{};
|
||||
bool ignore{};
|
||||
};
|
||||
template <typename T, typename... Args>
|
||||
BlockBranchInfo MakeBranchInfo(Args&&... args) {
|
||||
static_assert(std::is_convertible_v<T, BranchData>);
|
||||
return std::make_shared<BranchData>(T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
bool BlockBranchInfoAreEqual(BlockBranchInfo first, BlockBranchInfo second) {
|
||||
return false; //(*first) == (*second);
|
||||
}
|
||||
|
||||
bool BlockBranchIsIgnored(BlockBranchInfo first) {
|
||||
bool ignore = false;
|
||||
if (std::holds_alternative<SingleBranch>(*first)) {
|
||||
auto branch = std::get_if<SingleBranch>(first.get());
|
||||
ignore = branch->ignore;
|
||||
}
|
||||
return ignore;
|
||||
}
|
||||
|
||||
struct BlockInfo {
|
||||
u32 start{};
|
||||
@@ -56,10 +66,11 @@ struct BlockInfo {
|
||||
};
|
||||
|
||||
struct CFGRebuildState {
|
||||
explicit CFGRebuildState(const ProgramCode& program_code, const std::size_t program_size,
|
||||
const u32 start)
|
||||
: start{start}, program_code{program_code}, program_size{program_size} {}
|
||||
explicit CFGRebuildState(const ProgramCode& program_code, u32 start, ConstBufferLocker& locker)
|
||||
: program_code{program_code}, start{start}, locker{locker} {}
|
||||
|
||||
const ProgramCode& program_code;
|
||||
ConstBufferLocker& locker;
|
||||
u32 start{};
|
||||
std::vector<BlockInfo> block_info{};
|
||||
std::list<u32> inspect_queries{};
|
||||
@@ -69,8 +80,6 @@ struct CFGRebuildState {
|
||||
std::map<u32, u32> ssy_labels{};
|
||||
std::map<u32, u32> pbk_labels{};
|
||||
std::unordered_map<u32, BlockStack> stacks{};
|
||||
const ProgramCode& program_code;
|
||||
const std::size_t program_size;
|
||||
ASTManager* manager;
|
||||
};
|
||||
|
||||
@@ -124,10 +133,116 @@ enum class ParseResult : u32 {
|
||||
AbnormalFlow,
|
||||
};
|
||||
|
||||
struct BranchIndirectInfo {
|
||||
u32 buffer{};
|
||||
u32 offset{};
|
||||
u32 entries{};
|
||||
s32 relative_position{};
|
||||
};
|
||||
|
||||
std::optional<BranchIndirectInfo> TrackBranchIndirectInfo(const CFGRebuildState& state,
|
||||
u32 start_address, u32 current_position) {
|
||||
const u32 shader_start = state.start;
|
||||
u32 pos = current_position;
|
||||
BranchIndirectInfo result{};
|
||||
u64 track_register = 0;
|
||||
|
||||
// Step 0 Get BRX Info
|
||||
const Instruction instr = {state.program_code[pos]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
if (opcode->get().GetId() != OpCode::Id::BRX) {
|
||||
return {};
|
||||
}
|
||||
if (instr.brx.constant_buffer != 0) {
|
||||
return {};
|
||||
}
|
||||
track_register = instr.gpr8.Value();
|
||||
result.relative_position = instr.brx.GetBranchExtend();
|
||||
pos--;
|
||||
bool found_track = false;
|
||||
|
||||
// Step 1 Track LDC
|
||||
while (pos >= shader_start) {
|
||||
if (IsSchedInstruction(pos, shader_start)) {
|
||||
pos--;
|
||||
continue;
|
||||
}
|
||||
const Instruction instr = {state.program_code[pos]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
if (opcode->get().GetId() == OpCode::Id::LD_C) {
|
||||
if (instr.gpr0.Value() == track_register &&
|
||||
instr.ld_c.type.Value() == Tegra::Shader::UniformType::Single) {
|
||||
result.buffer = instr.cbuf36.index;
|
||||
result.offset = instr.cbuf36.GetOffset();
|
||||
track_register = instr.gpr8.Value();
|
||||
pos--;
|
||||
found_track = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos--;
|
||||
}
|
||||
|
||||
if (!found_track) {
|
||||
return {};
|
||||
}
|
||||
found_track = false;
|
||||
|
||||
// Step 2 Track SHL
|
||||
while (pos >= shader_start) {
|
||||
if (IsSchedInstruction(pos, shader_start)) {
|
||||
pos--;
|
||||
continue;
|
||||
}
|
||||
const Instruction instr = {state.program_code[pos]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
if (opcode->get().GetId() == OpCode::Id::SHL_IMM) {
|
||||
if (instr.gpr0.Value() == track_register) {
|
||||
track_register = instr.gpr8.Value();
|
||||
pos--;
|
||||
found_track = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos--;
|
||||
}
|
||||
|
||||
if (!found_track) {
|
||||
return {};
|
||||
}
|
||||
found_track = false;
|
||||
|
||||
// Step 3 Track IMNMX
|
||||
while (pos >= shader_start) {
|
||||
if (IsSchedInstruction(pos, shader_start)) {
|
||||
pos--;
|
||||
continue;
|
||||
}
|
||||
const Instruction instr = {state.program_code[pos]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
if (opcode->get().GetId() == OpCode::Id::IMNMX_IMM) {
|
||||
if (instr.gpr0.Value() == track_register) {
|
||||
track_register = instr.gpr8.Value();
|
||||
result.entries = instr.alu.GetSignedImm20_20() + 1;
|
||||
pos--;
|
||||
found_track = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos--;
|
||||
}
|
||||
|
||||
if (!found_track) {
|
||||
return {};
|
||||
}
|
||||
return {result};
|
||||
}
|
||||
|
||||
std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address) {
|
||||
u32 offset = static_cast<u32>(address);
|
||||
const u32 end_address = static_cast<u32>(state.program_size / sizeof(Instruction));
|
||||
const u32 end_address = static_cast<u32>(state.program_code.size());
|
||||
ParseInfo parse_info{};
|
||||
SingleBranch single_branch{};
|
||||
|
||||
const auto insert_label = [](CFGRebuildState& state, u32 address) {
|
||||
const auto pair = state.labels.emplace(address);
|
||||
@@ -140,13 +255,14 @@ std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address)
|
||||
if (offset >= end_address) {
|
||||
// ASSERT_OR_EXECUTE can't be used, as it ignores the break
|
||||
ASSERT_MSG(false, "Shader passed the current limit!");
|
||||
parse_info.branch_info.address = exit_branch;
|
||||
parse_info.branch_info.ignore = false;
|
||||
|
||||
single_branch.address = exit_branch;
|
||||
single_branch.ignore = false;
|
||||
break;
|
||||
}
|
||||
if (state.registered.count(offset) != 0) {
|
||||
parse_info.branch_info.address = offset;
|
||||
parse_info.branch_info.ignore = true;
|
||||
single_branch.address = offset;
|
||||
single_branch.ignore = true;
|
||||
break;
|
||||
}
|
||||
if (IsSchedInstruction(offset, state.start)) {
|
||||
@@ -163,24 +279,26 @@ std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address)
|
||||
switch (opcode->get().GetId()) {
|
||||
case OpCode::Id::EXIT: {
|
||||
const auto pred_index = static_cast<u32>(instr.pred.pred_index);
|
||||
parse_info.branch_info.condition.predicate =
|
||||
GetPredicate(pred_index, instr.negate_pred != 0);
|
||||
if (parse_info.branch_info.condition.predicate == Pred::NeverExecute) {
|
||||
single_branch.condition.predicate = GetPredicate(pred_index, instr.negate_pred != 0);
|
||||
if (single_branch.condition.predicate == Pred::NeverExecute) {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
const ConditionCode cc = instr.flow_condition_code;
|
||||
parse_info.branch_info.condition.cc = cc;
|
||||
single_branch.condition.cc = cc;
|
||||
if (cc == ConditionCode::F) {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
parse_info.branch_info.address = exit_branch;
|
||||
parse_info.branch_info.kill = false;
|
||||
parse_info.branch_info.is_sync = false;
|
||||
parse_info.branch_info.is_brk = false;
|
||||
parse_info.branch_info.ignore = false;
|
||||
single_branch.address = exit_branch;
|
||||
single_branch.kill = false;
|
||||
single_branch.is_sync = false;
|
||||
single_branch.is_brk = false;
|
||||
single_branch.ignore = false;
|
||||
parse_info.end_address = offset;
|
||||
parse_info.branch_info = MakeBranchInfo<SingleBranch>(
|
||||
single_branch.condition, single_branch.address, single_branch.kill,
|
||||
single_branch.is_sync, single_branch.is_brk, single_branch.ignore);
|
||||
|
||||
return {ParseResult::ControlCaught, parse_info};
|
||||
}
|
||||
@@ -189,99 +307,107 @@ std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address)
|
||||
return {ParseResult::AbnormalFlow, parse_info};
|
||||
}
|
||||
const auto pred_index = static_cast<u32>(instr.pred.pred_index);
|
||||
parse_info.branch_info.condition.predicate =
|
||||
GetPredicate(pred_index, instr.negate_pred != 0);
|
||||
if (parse_info.branch_info.condition.predicate == Pred::NeverExecute) {
|
||||
single_branch.condition.predicate = GetPredicate(pred_index, instr.negate_pred != 0);
|
||||
if (single_branch.condition.predicate == Pred::NeverExecute) {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
const ConditionCode cc = instr.flow_condition_code;
|
||||
parse_info.branch_info.condition.cc = cc;
|
||||
single_branch.condition.cc = cc;
|
||||
if (cc == ConditionCode::F) {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
const u32 branch_offset = offset + instr.bra.GetBranchTarget();
|
||||
if (branch_offset == 0) {
|
||||
parse_info.branch_info.address = exit_branch;
|
||||
single_branch.address = exit_branch;
|
||||
} else {
|
||||
parse_info.branch_info.address = branch_offset;
|
||||
single_branch.address = branch_offset;
|
||||
}
|
||||
insert_label(state, branch_offset);
|
||||
parse_info.branch_info.kill = false;
|
||||
parse_info.branch_info.is_sync = false;
|
||||
parse_info.branch_info.is_brk = false;
|
||||
parse_info.branch_info.ignore = false;
|
||||
single_branch.kill = false;
|
||||
single_branch.is_sync = false;
|
||||
single_branch.is_brk = false;
|
||||
single_branch.ignore = false;
|
||||
parse_info.end_address = offset;
|
||||
parse_info.branch_info = MakeBranchInfo<SingleBranch>(
|
||||
single_branch.condition, single_branch.address, single_branch.kill,
|
||||
single_branch.is_sync, single_branch.is_brk, single_branch.ignore);
|
||||
|
||||
return {ParseResult::ControlCaught, parse_info};
|
||||
}
|
||||
case OpCode::Id::SYNC: {
|
||||
const auto pred_index = static_cast<u32>(instr.pred.pred_index);
|
||||
parse_info.branch_info.condition.predicate =
|
||||
GetPredicate(pred_index, instr.negate_pred != 0);
|
||||
if (parse_info.branch_info.condition.predicate == Pred::NeverExecute) {
|
||||
single_branch.condition.predicate = GetPredicate(pred_index, instr.negate_pred != 0);
|
||||
if (single_branch.condition.predicate == Pred::NeverExecute) {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
const ConditionCode cc = instr.flow_condition_code;
|
||||
parse_info.branch_info.condition.cc = cc;
|
||||
single_branch.condition.cc = cc;
|
||||
if (cc == ConditionCode::F) {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
parse_info.branch_info.address = unassigned_branch;
|
||||
parse_info.branch_info.kill = false;
|
||||
parse_info.branch_info.is_sync = true;
|
||||
parse_info.branch_info.is_brk = false;
|
||||
parse_info.branch_info.ignore = false;
|
||||
single_branch.address = unassigned_branch;
|
||||
single_branch.kill = false;
|
||||
single_branch.is_sync = true;
|
||||
single_branch.is_brk = false;
|
||||
single_branch.ignore = false;
|
||||
parse_info.end_address = offset;
|
||||
parse_info.branch_info = MakeBranchInfo<SingleBranch>(
|
||||
single_branch.condition, single_branch.address, single_branch.kill,
|
||||
single_branch.is_sync, single_branch.is_brk, single_branch.ignore);
|
||||
|
||||
return {ParseResult::ControlCaught, parse_info};
|
||||
}
|
||||
case OpCode::Id::BRK: {
|
||||
const auto pred_index = static_cast<u32>(instr.pred.pred_index);
|
||||
parse_info.branch_info.condition.predicate =
|
||||
GetPredicate(pred_index, instr.negate_pred != 0);
|
||||
if (parse_info.branch_info.condition.predicate == Pred::NeverExecute) {
|
||||
single_branch.condition.predicate = GetPredicate(pred_index, instr.negate_pred != 0);
|
||||
if (single_branch.condition.predicate == Pred::NeverExecute) {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
const ConditionCode cc = instr.flow_condition_code;
|
||||
parse_info.branch_info.condition.cc = cc;
|
||||
single_branch.condition.cc = cc;
|
||||
if (cc == ConditionCode::F) {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
parse_info.branch_info.address = unassigned_branch;
|
||||
parse_info.branch_info.kill = false;
|
||||
parse_info.branch_info.is_sync = false;
|
||||
parse_info.branch_info.is_brk = true;
|
||||
parse_info.branch_info.ignore = false;
|
||||
single_branch.address = unassigned_branch;
|
||||
single_branch.kill = false;
|
||||
single_branch.is_sync = false;
|
||||
single_branch.is_brk = true;
|
||||
single_branch.ignore = false;
|
||||
parse_info.end_address = offset;
|
||||
parse_info.branch_info = MakeBranchInfo<SingleBranch>(
|
||||
single_branch.condition, single_branch.address, single_branch.kill,
|
||||
single_branch.is_sync, single_branch.is_brk, single_branch.ignore);
|
||||
|
||||
return {ParseResult::ControlCaught, parse_info};
|
||||
}
|
||||
case OpCode::Id::KIL: {
|
||||
const auto pred_index = static_cast<u32>(instr.pred.pred_index);
|
||||
parse_info.branch_info.condition.predicate =
|
||||
GetPredicate(pred_index, instr.negate_pred != 0);
|
||||
if (parse_info.branch_info.condition.predicate == Pred::NeverExecute) {
|
||||
single_branch.condition.predicate = GetPredicate(pred_index, instr.negate_pred != 0);
|
||||
if (single_branch.condition.predicate == Pred::NeverExecute) {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
const ConditionCode cc = instr.flow_condition_code;
|
||||
parse_info.branch_info.condition.cc = cc;
|
||||
single_branch.condition.cc = cc;
|
||||
if (cc == ConditionCode::F) {
|
||||
offset++;
|
||||
continue;
|
||||
}
|
||||
parse_info.branch_info.address = exit_branch;
|
||||
parse_info.branch_info.kill = true;
|
||||
parse_info.branch_info.is_sync = false;
|
||||
parse_info.branch_info.is_brk = false;
|
||||
parse_info.branch_info.ignore = false;
|
||||
single_branch.address = exit_branch;
|
||||
single_branch.kill = true;
|
||||
single_branch.is_sync = false;
|
||||
single_branch.is_brk = false;
|
||||
single_branch.ignore = false;
|
||||
parse_info.end_address = offset;
|
||||
parse_info.branch_info = MakeBranchInfo<SingleBranch>(
|
||||
single_branch.condition, single_branch.address, single_branch.kill,
|
||||
single_branch.is_sync, single_branch.is_brk, single_branch.ignore);
|
||||
|
||||
return {ParseResult::ControlCaught, parse_info};
|
||||
}
|
||||
@@ -298,6 +424,29 @@ std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address)
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::BRX: {
|
||||
auto tmp = TrackBranchIndirectInfo(state, address, offset);
|
||||
if (tmp) {
|
||||
auto result = *tmp;
|
||||
std::vector<CaseBranch> branches{};
|
||||
s32 pc_target = offset + result.relative_position;
|
||||
for (u32 i = 0; i < result.entries; i++) {
|
||||
auto k = state.locker.ObtainKey(result.buffer, result.offset + i * 4);
|
||||
if (!k) {
|
||||
return {ParseResult::AbnormalFlow, parse_info};
|
||||
}
|
||||
u32 value = *k;
|
||||
u32 target = static_cast<u32>((value >> 3) + pc_target);
|
||||
insert_label(state, target);
|
||||
branches.emplace_back(value, target);
|
||||
}
|
||||
parse_info.end_address = offset;
|
||||
parse_info.branch_info =
|
||||
MakeBranchInfo<MultiBranch>(static_cast<u32>(instr.gpr8.Value()), branches);
|
||||
|
||||
return {ParseResult::ControlCaught, parse_info};
|
||||
} else {
|
||||
LOG_WARNING(HW_GPU, "BRX Track Unsuccesful");
|
||||
}
|
||||
return {ParseResult::AbnormalFlow, parse_info};
|
||||
}
|
||||
default:
|
||||
@@ -306,10 +455,13 @@ std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address)
|
||||
|
||||
offset++;
|
||||
}
|
||||
parse_info.branch_info.kill = false;
|
||||
parse_info.branch_info.is_sync = false;
|
||||
parse_info.branch_info.is_brk = false;
|
||||
single_branch.kill = false;
|
||||
single_branch.is_sync = false;
|
||||
single_branch.is_brk = false;
|
||||
parse_info.end_address = offset - 1;
|
||||
parse_info.branch_info = MakeBranchInfo<SingleBranch>(
|
||||
single_branch.condition, single_branch.address, single_branch.kill, single_branch.is_sync,
|
||||
single_branch.is_brk, single_branch.ignore);
|
||||
return {ParseResult::BlockEnd, parse_info};
|
||||
}
|
||||
|
||||
@@ -333,9 +485,10 @@ bool TryInspectAddress(CFGRebuildState& state) {
|
||||
BlockInfo& current_block = state.block_info[block_index];
|
||||
current_block.end = address - 1;
|
||||
new_block.branch = current_block.branch;
|
||||
BlockBranchInfo forward_branch{};
|
||||
forward_branch.address = address;
|
||||
forward_branch.ignore = true;
|
||||
BlockBranchInfo forward_branch = MakeBranchInfo<SingleBranch>();
|
||||
auto branch = std::get_if<SingleBranch>(forward_branch.get());
|
||||
branch->address = address;
|
||||
branch->ignore = true;
|
||||
current_block.branch = forward_branch;
|
||||
return true;
|
||||
}
|
||||
@@ -350,12 +503,15 @@ bool TryInspectAddress(CFGRebuildState& state) {
|
||||
|
||||
BlockInfo& block_info = CreateBlockInfo(state, address, parse_info.end_address);
|
||||
block_info.branch = parse_info.branch_info;
|
||||
if (parse_info.branch_info.condition.IsUnconditional()) {
|
||||
if (std::holds_alternative<SingleBranch>(*block_info.branch)) {
|
||||
auto branch = std::get_if<SingleBranch>(block_info.branch.get());
|
||||
if (branch->condition.IsUnconditional()) {
|
||||
return true;
|
||||
}
|
||||
const u32 fallthrough_address = parse_info.end_address + 1;
|
||||
state.inspect_queries.push_front(fallthrough_address);
|
||||
return true;
|
||||
}
|
||||
|
||||
const u32 fallthrough_address = parse_info.end_address + 1;
|
||||
state.inspect_queries.push_front(fallthrough_address);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -393,31 +549,42 @@ bool TryQuery(CFGRebuildState& state) {
|
||||
state.queries.pop_front();
|
||||
gather_labels(q2.ssy_stack, state.ssy_labels, block);
|
||||
gather_labels(q2.pbk_stack, state.pbk_labels, block);
|
||||
if (!block.branch.condition.IsUnconditional()) {
|
||||
q2.address = block.end + 1;
|
||||
state.queries.push_back(q2);
|
||||
}
|
||||
if (std::holds_alternative<SingleBranch>(*block.branch)) {
|
||||
auto branch = std::get_if<SingleBranch>(block.branch.get());
|
||||
if (!branch->condition.IsUnconditional()) {
|
||||
q2.address = block.end + 1;
|
||||
state.queries.push_back(q2);
|
||||
}
|
||||
|
||||
Query conditional_query{q2};
|
||||
if (block.branch.is_sync) {
|
||||
if (block.branch.address == unassigned_branch) {
|
||||
block.branch.address = conditional_query.ssy_stack.top();
|
||||
Query conditional_query{q2};
|
||||
if (branch->is_sync) {
|
||||
if (branch->address == unassigned_branch) {
|
||||
branch->address = conditional_query.ssy_stack.top();
|
||||
}
|
||||
conditional_query.ssy_stack.pop();
|
||||
}
|
||||
conditional_query.ssy_stack.pop();
|
||||
}
|
||||
if (block.branch.is_brk) {
|
||||
if (block.branch.address == unassigned_branch) {
|
||||
block.branch.address = conditional_query.pbk_stack.top();
|
||||
if (branch->is_brk) {
|
||||
if (branch->address == unassigned_branch) {
|
||||
branch->address = conditional_query.pbk_stack.top();
|
||||
}
|
||||
conditional_query.pbk_stack.pop();
|
||||
}
|
||||
conditional_query.pbk_stack.pop();
|
||||
conditional_query.address = branch->address;
|
||||
state.queries.push_back(std::move(conditional_query));
|
||||
return true;
|
||||
}
|
||||
auto multi_branch = std::get_if<MultiBranch>(block.branch.get());
|
||||
for (auto& branch_case : multi_branch->branches) {
|
||||
Query conditional_query{q2};
|
||||
conditional_query.address = branch_case.address;
|
||||
state.queries.push_back(std::move(conditional_query));
|
||||
}
|
||||
conditional_query.address = block.branch.address;
|
||||
state.queries.push_back(std::move(conditional_query));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch) {
|
||||
void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch_info) {
|
||||
const auto get_expr = ([&](const Condition& cond) -> Expr {
|
||||
Expr result{};
|
||||
if (cond.cc != ConditionCode::T) {
|
||||
@@ -444,15 +611,24 @@ void InsertBranch(ASTManager& mm, const BlockBranchInfo& branch) {
|
||||
}
|
||||
return MakeExpr<ExprBoolean>(true);
|
||||
});
|
||||
if (branch.address < 0) {
|
||||
if (branch.kill) {
|
||||
mm.InsertReturn(get_expr(branch.condition), true);
|
||||
if (std::holds_alternative<SingleBranch>(*branch_info)) {
|
||||
auto branch = std::get_if<SingleBranch>(branch_info.get());
|
||||
if (branch->address < 0) {
|
||||
if (branch->kill) {
|
||||
mm.InsertReturn(get_expr(branch->condition), true);
|
||||
return;
|
||||
}
|
||||
mm.InsertReturn(get_expr(branch->condition), false);
|
||||
return;
|
||||
}
|
||||
mm.InsertReturn(get_expr(branch.condition), false);
|
||||
mm.InsertGoto(get_expr(branch->condition), branch->address);
|
||||
return;
|
||||
}
|
||||
mm.InsertGoto(get_expr(branch.condition), branch.address);
|
||||
auto multi_branch = std::get_if<MultiBranch>(branch_info.get());
|
||||
for (auto& branch_case : multi_branch->branches) {
|
||||
mm.InsertGoto(MakeExpr<ExprGprEqual>(multi_branch->gpr, branch_case.cmp_value),
|
||||
branch_case.address);
|
||||
}
|
||||
}
|
||||
|
||||
void DecompileShader(CFGRebuildState& state) {
|
||||
@@ -464,25 +640,26 @@ void DecompileShader(CFGRebuildState& state) {
|
||||
if (state.labels.count(block.start) != 0) {
|
||||
state.manager->InsertLabel(block.start);
|
||||
}
|
||||
u32 end = block.branch.ignore ? block.end + 1 : block.end;
|
||||
const bool ignore = BlockBranchIsIgnored(block.branch);
|
||||
u32 end = ignore ? block.end + 1 : block.end;
|
||||
state.manager->InsertBlock(block.start, end);
|
||||
if (!block.branch.ignore) {
|
||||
if (!ignore) {
|
||||
InsertBranch(*state.manager, block.branch);
|
||||
}
|
||||
}
|
||||
state.manager->Decompile();
|
||||
}
|
||||
|
||||
std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 program_size,
|
||||
u32 start_address,
|
||||
const CompilerSettings& settings) {
|
||||
std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 start_address,
|
||||
const CompilerSettings& settings,
|
||||
ConstBufferLocker& locker) {
|
||||
auto result_out = std::make_unique<ShaderCharacteristics>();
|
||||
if (settings.depth == CompileDepth::BruteForce) {
|
||||
result_out->settings.depth = CompileDepth::BruteForce;
|
||||
return result_out;
|
||||
}
|
||||
|
||||
CFGRebuildState state{program_code, program_size, start_address};
|
||||
CFGRebuildState state{program_code, start_address, locker};
|
||||
// Inspect Code and generate blocks
|
||||
state.labels.clear();
|
||||
state.labels.emplace(start_address);
|
||||
@@ -547,11 +724,9 @@ std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code,
|
||||
ShaderBlock new_block{};
|
||||
new_block.start = block.start;
|
||||
new_block.end = block.end;
|
||||
new_block.ignore_branch = block.branch.ignore;
|
||||
new_block.ignore_branch = BlockBranchIsIgnored(block.branch);
|
||||
if (!new_block.ignore_branch) {
|
||||
new_block.branch.cond = block.branch.condition;
|
||||
new_block.branch.kills = block.branch.kill;
|
||||
new_block.branch.address = block.branch.address;
|
||||
new_block.branch = block.branch;
|
||||
}
|
||||
result_out->end = std::max(result_out->end, block.end);
|
||||
result_out->blocks.push_back(new_block);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <list>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <variant>
|
||||
|
||||
#include "video_core/engines/shader_bytecode.h"
|
||||
#include "video_core/shader/ast.h"
|
||||
@@ -37,29 +38,57 @@ struct Condition {
|
||||
}
|
||||
};
|
||||
|
||||
class SingleBranch {
|
||||
public:
|
||||
SingleBranch() = default;
|
||||
SingleBranch(Condition condition, s32 address, bool kill, bool is_sync, bool is_brk,
|
||||
bool ignore)
|
||||
: condition{condition}, address{address}, kill{kill}, is_sync{is_sync}, is_brk{is_brk},
|
||||
ignore{ignore} {}
|
||||
|
||||
bool operator==(const SingleBranch& b) const {
|
||||
return std::tie(condition, address, kill, is_sync, is_brk, ignore) ==
|
||||
std::tie(b.condition, b.address, b.kill, b.is_sync, b.is_brk, b.ignore);
|
||||
}
|
||||
|
||||
Condition condition{};
|
||||
s32 address{exit_branch};
|
||||
bool kill{};
|
||||
bool is_sync{};
|
||||
bool is_brk{};
|
||||
bool ignore{};
|
||||
};
|
||||
|
||||
struct CaseBranch {
|
||||
CaseBranch(u32 cmp_value, u32 address) : cmp_value{cmp_value}, address{address} {}
|
||||
u32 cmp_value;
|
||||
u32 address;
|
||||
};
|
||||
|
||||
class MultiBranch {
|
||||
public:
|
||||
MultiBranch(u32 gpr, std::vector<CaseBranch>& branches)
|
||||
: gpr{gpr}, branches{std::move(branches)} {}
|
||||
|
||||
u32 gpr{};
|
||||
std::vector<CaseBranch> branches{};
|
||||
};
|
||||
|
||||
using BranchData = std::variant<SingleBranch, MultiBranch>;
|
||||
using BlockBranchInfo = std::shared_ptr<BranchData>;
|
||||
|
||||
bool BlockBranchInfoAreEqual(BlockBranchInfo first, BlockBranchInfo second);
|
||||
|
||||
struct ShaderBlock {
|
||||
struct Branch {
|
||||
Condition cond{};
|
||||
bool kills{};
|
||||
s32 address{};
|
||||
|
||||
bool operator==(const Branch& b) const {
|
||||
return std::tie(cond, kills, address) == std::tie(b.cond, b.kills, b.address);
|
||||
}
|
||||
|
||||
bool operator!=(const Branch& b) const {
|
||||
return !operator==(b);
|
||||
}
|
||||
};
|
||||
|
||||
u32 start{};
|
||||
u32 end{};
|
||||
bool ignore_branch{};
|
||||
Branch branch{};
|
||||
BlockBranchInfo branch{};
|
||||
|
||||
bool operator==(const ShaderBlock& sb) const {
|
||||
return std::tie(start, end, ignore_branch, branch) ==
|
||||
std::tie(sb.start, sb.end, sb.ignore_branch, sb.branch);
|
||||
return std::tie(start, end, ignore_branch) ==
|
||||
std::tie(sb.start, sb.end, sb.ignore_branch) &&
|
||||
BlockBranchInfoAreEqual(branch, sb.branch);
|
||||
}
|
||||
|
||||
bool operator!=(const ShaderBlock& sb) const {
|
||||
@@ -76,8 +105,8 @@ struct ShaderCharacteristics {
|
||||
CompilerSettings settings{};
|
||||
};
|
||||
|
||||
std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 program_size,
|
||||
u32 start_address,
|
||||
const CompilerSettings& settings);
|
||||
std::unique_ptr<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, u32 start_address,
|
||||
const CompilerSettings& settings,
|
||||
ConstBufferLocker& locker);
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
|
||||
@@ -33,7 +33,7 @@ constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) {
|
||||
return (absolute_offset % SchedPeriod) == 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // Anonymous namespace
|
||||
|
||||
class ASTDecoder {
|
||||
public:
|
||||
@@ -102,7 +102,7 @@ void ShaderIR::Decode() {
|
||||
std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header));
|
||||
|
||||
decompiled = false;
|
||||
auto info = ScanFlow(program_code, program_size, main_offset, settings);
|
||||
auto info = ScanFlow(program_code, main_offset, settings, locker);
|
||||
auto& shader_info = *info;
|
||||
coverage_begin = shader_info.start;
|
||||
coverage_end = shader_info.end;
|
||||
@@ -155,7 +155,7 @@ void ShaderIR::Decode() {
|
||||
[[fallthrough]];
|
||||
case CompileDepth::BruteForce: {
|
||||
coverage_begin = main_offset;
|
||||
const u32 shader_end = static_cast<u32>(program_size / sizeof(u64));
|
||||
const u32 shader_end = program_code.size();
|
||||
coverage_end = shader_end;
|
||||
for (u32 label = main_offset; label < shader_end; label++) {
|
||||
basic_blocks.insert({label, DecodeRange(label, label + 1)});
|
||||
@@ -198,24 +198,39 @@ void ShaderIR::InsertControlFlow(NodeBlock& bb, const ShaderBlock& block) {
|
||||
}
|
||||
return result;
|
||||
};
|
||||
if (block.branch.address < 0) {
|
||||
if (block.branch.kills) {
|
||||
Node n = Operation(OperationCode::Discard);
|
||||
n = apply_conditions(block.branch.cond, n);
|
||||
if (std::holds_alternative<SingleBranch>(*block.branch)) {
|
||||
auto branch = std::get_if<SingleBranch>(block.branch.get());
|
||||
if (branch->address < 0) {
|
||||
if (branch->kill) {
|
||||
Node n = Operation(OperationCode::Discard);
|
||||
n = apply_conditions(branch->condition, n);
|
||||
bb.push_back(n);
|
||||
global_code.push_back(n);
|
||||
return;
|
||||
}
|
||||
Node n = Operation(OperationCode::Exit);
|
||||
n = apply_conditions(branch->condition, n);
|
||||
bb.push_back(n);
|
||||
global_code.push_back(n);
|
||||
return;
|
||||
}
|
||||
Node n = Operation(OperationCode::Exit);
|
||||
n = apply_conditions(block.branch.cond, n);
|
||||
Node n = Operation(OperationCode::Branch, Immediate(branch->address));
|
||||
n = apply_conditions(branch->condition, n);
|
||||
bb.push_back(n);
|
||||
global_code.push_back(n);
|
||||
return;
|
||||
}
|
||||
Node n = Operation(OperationCode::Branch, Immediate(block.branch.address));
|
||||
n = apply_conditions(block.branch.cond, n);
|
||||
bb.push_back(n);
|
||||
global_code.push_back(n);
|
||||
auto multi_branch = std::get_if<MultiBranch>(block.branch.get());
|
||||
Node op_a = GetRegister(multi_branch->gpr);
|
||||
for (auto& branch_case : multi_branch->branches) {
|
||||
Node n = Operation(OperationCode::Branch, Immediate(branch_case.address));
|
||||
Node op_b = Immediate(branch_case.cmp_value);
|
||||
Node condition =
|
||||
GetPredicateComparisonInteger(Tegra::Shader::PredCondition::Equal, false, op_a, op_b);
|
||||
auto result = Conditional(condition, {n});
|
||||
bb.push_back(result);
|
||||
global_code.push_back(result);
|
||||
}
|
||||
}
|
||||
|
||||
u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) {
|
||||
|
||||
@@ -141,7 +141,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
const Node component = Immediate(static_cast<u32>(instr.tld4s.component));
|
||||
|
||||
const auto& sampler =
|
||||
GetSampler(instr.sampler, TextureType::Texture2D, false, depth_compare);
|
||||
GetSampler(instr.sampler, {{TextureType::Texture2D, false, depth_compare}});
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
@@ -165,10 +165,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
// Sadly, not all texture instructions specify the type of texture their sampler
|
||||
// uses. This must be fixed at a later instance.
|
||||
const auto& sampler =
|
||||
is_bindless
|
||||
? GetBindlessSampler(instr.gpr8, Tegra::Shader::TextureType::Texture2D, false,
|
||||
false)
|
||||
: GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
|
||||
is_bindless ? GetBindlessSampler(instr.gpr8, {}) : GetSampler(instr.sampler, {});
|
||||
|
||||
u32 indexer = 0;
|
||||
switch (instr.txq.query_type) {
|
||||
@@ -207,9 +204,9 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
|
||||
auto texture_type = instr.tmml.texture_type.Value();
|
||||
const bool is_array = instr.tmml.array != 0;
|
||||
const auto& sampler = is_bindless
|
||||
? GetBindlessSampler(instr.gpr20, texture_type, is_array, false)
|
||||
: GetSampler(instr.sampler, texture_type, is_array, false);
|
||||
const auto& sampler =
|
||||
is_bindless ? GetBindlessSampler(instr.gpr20, {{texture_type, is_array, false}})
|
||||
: GetSampler(instr.sampler, {{texture_type, is_array, false}});
|
||||
|
||||
std::vector<Node> coords;
|
||||
|
||||
@@ -285,10 +282,30 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
return pc;
|
||||
}
|
||||
|
||||
const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler, TextureType type,
|
||||
bool is_array, bool is_shadow) {
|
||||
const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler,
|
||||
std::optional<SamplerInfo> sampler_info) {
|
||||
const auto offset = static_cast<std::size_t>(sampler.index.Value());
|
||||
|
||||
Tegra::Shader::TextureType type;
|
||||
bool is_array;
|
||||
bool is_shadow;
|
||||
if (sampler_info) {
|
||||
type = sampler_info->type;
|
||||
is_array = sampler_info->is_array;
|
||||
is_shadow = sampler_info->is_shadow;
|
||||
} else {
|
||||
auto sampler = locker.ObtainBoundSampler(offset);
|
||||
if (sampler) {
|
||||
type = sampler->texture_type.Value();
|
||||
is_array = sampler->is_array.Value() != 0;
|
||||
is_shadow = sampler->is_shadow.Value() != 0;
|
||||
} else {
|
||||
type = Tegra::Shader::TextureType::Texture2D;
|
||||
is_array = false;
|
||||
is_shadow = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If this sampler has already been used, return the existing mapping.
|
||||
const auto itr =
|
||||
std::find_if(used_samplers.begin(), used_samplers.end(),
|
||||
@@ -305,13 +322,32 @@ const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler, Textu
|
||||
return *used_samplers.emplace(entry).first;
|
||||
}
|
||||
|
||||
const Sampler& ShaderIR::GetBindlessSampler(const Tegra::Shader::Register& reg, TextureType type,
|
||||
bool is_array, bool is_shadow) {
|
||||
const Sampler& ShaderIR::GetBindlessSampler(const Tegra::Shader::Register& reg,
|
||||
std::optional<SamplerInfo> sampler_info) {
|
||||
const Node sampler_register = GetRegister(reg);
|
||||
const auto [base_sampler, cbuf_index, cbuf_offset] =
|
||||
TrackCbuf(sampler_register, global_code, static_cast<s64>(global_code.size()));
|
||||
ASSERT(base_sampler != nullptr);
|
||||
const auto cbuf_key = (static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset);
|
||||
Tegra::Shader::TextureType type;
|
||||
bool is_array;
|
||||
bool is_shadow;
|
||||
if (sampler_info) {
|
||||
type = sampler_info->type;
|
||||
is_array = sampler_info->is_array;
|
||||
is_shadow = sampler_info->is_shadow;
|
||||
} else {
|
||||
auto sampler = locker.ObtainBindlessSampler(cbuf_index, cbuf_offset);
|
||||
if (sampler) {
|
||||
type = sampler->texture_type.Value();
|
||||
is_array = sampler->is_array.Value() != 0;
|
||||
is_shadow = sampler->is_shadow.Value() != 0;
|
||||
} else {
|
||||
type = Tegra::Shader::TextureType::Texture2D;
|
||||
is_array = false;
|
||||
is_shadow = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If this sampler has already been used, return the existing mapping.
|
||||
const auto itr =
|
||||
@@ -411,9 +447,9 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
|
||||
(texture_type == TextureType::TextureCube && is_array && is_shadow),
|
||||
"This method is not supported.");
|
||||
|
||||
const auto& sampler = is_bindless
|
||||
? GetBindlessSampler(*bindless_reg, texture_type, is_array, is_shadow)
|
||||
: GetSampler(instr.sampler, texture_type, is_array, is_shadow);
|
||||
const auto& sampler =
|
||||
is_bindless ? GetBindlessSampler(*bindless_reg, {{texture_type, is_array, is_shadow}})
|
||||
: GetSampler(instr.sampler, {{texture_type, is_array, is_shadow}});
|
||||
|
||||
const bool lod_needed = process_mode == TextureProcessMode::LZ ||
|
||||
process_mode == TextureProcessMode::LL ||
|
||||
@@ -577,7 +613,7 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de
|
||||
dc = GetRegister(parameter_register++);
|
||||
}
|
||||
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, depth_compare);
|
||||
const auto& sampler = GetSampler(instr.sampler, {{texture_type, is_array, depth_compare}});
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
@@ -610,7 +646,7 @@ Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) {
|
||||
// const Node aoffi_register{is_aoffi ? GetRegister(gpr20_cursor++) : nullptr};
|
||||
// const Node multisample{is_multisample ? GetRegister(gpr20_cursor++) : nullptr};
|
||||
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
|
||||
const auto& sampler = GetSampler(instr.sampler, {{texture_type, is_array, false}});
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
@@ -646,7 +682,7 @@ Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is
|
||||
// When lod is used always is in gpr20
|
||||
const Node lod = lod_enabled ? GetRegister(instr.gpr20) : Immediate(0);
|
||||
|
||||
const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false);
|
||||
const auto& sampler = GetSampler(instr.sampler, {{texture_type, is_array, false}});
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
|
||||
@@ -17,13 +17,14 @@ using Tegra::Shader::Pred;
|
||||
class ExprAnd;
|
||||
class ExprBoolean;
|
||||
class ExprCondCode;
|
||||
class ExprGprEqual;
|
||||
class ExprNot;
|
||||
class ExprOr;
|
||||
class ExprPredicate;
|
||||
class ExprVar;
|
||||
|
||||
using ExprData =
|
||||
std::variant<ExprVar, ExprCondCode, ExprPredicate, ExprNot, ExprOr, ExprAnd, ExprBoolean>;
|
||||
using ExprData = std::variant<ExprVar, ExprCondCode, ExprPredicate, ExprNot, ExprOr, ExprAnd,
|
||||
ExprBoolean, ExprGprEqual>;
|
||||
using Expr = std::shared_ptr<ExprData>;
|
||||
|
||||
class ExprAnd final {
|
||||
@@ -118,6 +119,18 @@ public:
|
||||
bool value;
|
||||
};
|
||||
|
||||
class ExprGprEqual final {
|
||||
public:
|
||||
ExprGprEqual(u32 gpr, u32 value) : gpr{gpr}, value{value} {}
|
||||
|
||||
bool operator==(const ExprGprEqual& b) const {
|
||||
return gpr == b.gpr && value == b.value;
|
||||
}
|
||||
|
||||
u32 gpr;
|
||||
u32 value;
|
||||
};
|
||||
|
||||
template <typename T, typename... Args>
|
||||
Expr MakeExpr(Args&&... args) {
|
||||
static_assert(std::is_convertible_v<T, ExprData>);
|
||||
|
||||
@@ -22,10 +22,9 @@ using Tegra::Shader::PredCondition;
|
||||
using Tegra::Shader::PredOperation;
|
||||
using Tegra::Shader::Register;
|
||||
|
||||
ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, const std::size_t size,
|
||||
CompilerSettings settings)
|
||||
: program_code{program_code}, main_offset{main_offset}, program_size{size}, basic_blocks{},
|
||||
program_manager{true, true}, settings{settings} {
|
||||
ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, CompilerSettings settings,
|
||||
ConstBufferLocker& locker)
|
||||
: program_code{program_code}, main_offset{main_offset}, settings{settings}, locker{locker} {
|
||||
Decode();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "video_core/engines/shader_header.h"
|
||||
#include "video_core/shader/ast.h"
|
||||
#include "video_core/shader/compiler_settings.h"
|
||||
#include "video_core/shader/const_buffer_locker.h"
|
||||
#include "video_core/shader/node.h"
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
@@ -66,8 +67,8 @@ struct GlobalMemoryUsage {
|
||||
|
||||
class ShaderIR final {
|
||||
public:
|
||||
explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, std::size_t size,
|
||||
CompilerSettings settings);
|
||||
explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, CompilerSettings settings,
|
||||
ConstBufferLocker& locker);
|
||||
~ShaderIR();
|
||||
|
||||
const std::map<u32, NodeBlock>& GetBasicBlocks() const {
|
||||
@@ -172,6 +173,13 @@ public:
|
||||
|
||||
private:
|
||||
friend class ASTDecoder;
|
||||
|
||||
struct SamplerInfo {
|
||||
Tegra::Shader::TextureType type;
|
||||
bool is_array;
|
||||
bool is_shadow;
|
||||
};
|
||||
|
||||
void Decode();
|
||||
|
||||
NodeBlock DecodeRange(u32 begin, u32 end);
|
||||
@@ -296,12 +304,11 @@ private:
|
||||
|
||||
/// Accesses a texture sampler
|
||||
const Sampler& GetSampler(const Tegra::Shader::Sampler& sampler,
|
||||
Tegra::Shader::TextureType type, bool is_array, bool is_shadow);
|
||||
std::optional<SamplerInfo> sampler_info);
|
||||
|
||||
// Accesses a texture sampler for a bindless texture.
|
||||
const Sampler& GetBindlessSampler(const Tegra::Shader::Register& reg,
|
||||
Tegra::Shader::TextureType type, bool is_array,
|
||||
bool is_shadow);
|
||||
std::optional<SamplerInfo> sampler_info);
|
||||
|
||||
/// Accesses an image.
|
||||
Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type);
|
||||
@@ -376,7 +383,9 @@ private:
|
||||
|
||||
const ProgramCode& program_code;
|
||||
const u32 main_offset;
|
||||
const std::size_t program_size;
|
||||
const CompilerSettings settings;
|
||||
ConstBufferLocker& locker;
|
||||
|
||||
bool decompiled{};
|
||||
bool disable_flow_stack{};
|
||||
|
||||
@@ -385,8 +394,7 @@ private:
|
||||
|
||||
std::map<u32, NodeBlock> basic_blocks;
|
||||
NodeBlock global_code;
|
||||
ASTManager program_manager;
|
||||
CompilerSettings settings{};
|
||||
ASTManager program_manager{true, true};
|
||||
|
||||
std::set<u32> used_registers;
|
||||
std::set<Tegra::Shader::Pred> used_predicates;
|
||||
|
||||
@@ -212,6 +212,14 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Tegra::Texture::TextureFormat::A4B4G4R4:
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::UNORM:
|
||||
return PixelFormat::R4G4B4A4U;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Tegra::Texture::TextureFormat::R8:
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::UNORM:
|
||||
@@ -350,6 +358,16 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
|
||||
return is_srgb ? PixelFormat::ASTC_2D_8X5_SRGB : PixelFormat::ASTC_2D_8X5;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_10X8:
|
||||
return is_srgb ? PixelFormat::ASTC_2D_10X8_SRGB : PixelFormat::ASTC_2D_10X8;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_6X6:
|
||||
return is_srgb ? PixelFormat::ASTC_2D_6X6_SRGB : PixelFormat::ASTC_2D_6X6;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_10X10:
|
||||
return is_srgb ? PixelFormat::ASTC_2D_10X10_SRGB : PixelFormat::ASTC_2D_10X10;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_12X12:
|
||||
return is_srgb ? PixelFormat::ASTC_2D_12X12_SRGB : PixelFormat::ASTC_2D_12X12;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_8X6:
|
||||
return is_srgb ? PixelFormat::ASTC_2D_8X6_SRGB : PixelFormat::ASTC_2D_8X6;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_6X5:
|
||||
return is_srgb ? PixelFormat::ASTC_2D_6X5_SRGB : PixelFormat::ASTC_2D_6X5;
|
||||
case Tegra::Texture::TextureFormat::R16_G16:
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::FLOAT:
|
||||
@@ -510,6 +528,16 @@ bool IsPixelFormatASTC(PixelFormat format) {
|
||||
case PixelFormat::ASTC_2D_8X5_SRGB:
|
||||
case PixelFormat::ASTC_2D_10X8:
|
||||
case PixelFormat::ASTC_2D_10X8_SRGB:
|
||||
case PixelFormat::ASTC_2D_6X6:
|
||||
case PixelFormat::ASTC_2D_6X6_SRGB:
|
||||
case PixelFormat::ASTC_2D_10X10:
|
||||
case PixelFormat::ASTC_2D_10X10_SRGB:
|
||||
case PixelFormat::ASTC_2D_12X12:
|
||||
case PixelFormat::ASTC_2D_12X12_SRGB:
|
||||
case PixelFormat::ASTC_2D_8X6:
|
||||
case PixelFormat::ASTC_2D_8X6_SRGB:
|
||||
case PixelFormat::ASTC_2D_6X5:
|
||||
case PixelFormat::ASTC_2D_6X5_SRGB:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -530,6 +558,11 @@ bool IsPixelFormatSRGB(PixelFormat format) {
|
||||
case PixelFormat::ASTC_2D_5X4_SRGB:
|
||||
case PixelFormat::ASTC_2D_5X5_SRGB:
|
||||
case PixelFormat::ASTC_2D_10X8_SRGB:
|
||||
case PixelFormat::ASTC_2D_6X6_SRGB:
|
||||
case PixelFormat::ASTC_2D_10X10_SRGB:
|
||||
case PixelFormat::ASTC_2D_12X12_SRGB:
|
||||
case PixelFormat::ASTC_2D_8X6_SRGB:
|
||||
case PixelFormat::ASTC_2D_6X5_SRGB:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -67,27 +67,38 @@ enum class PixelFormat {
|
||||
DXT23_SRGB = 49,
|
||||
DXT45_SRGB = 50,
|
||||
BC7U_SRGB = 51,
|
||||
ASTC_2D_4X4_SRGB = 52,
|
||||
ASTC_2D_8X8_SRGB = 53,
|
||||
ASTC_2D_8X5_SRGB = 54,
|
||||
ASTC_2D_5X4_SRGB = 55,
|
||||
ASTC_2D_5X5 = 56,
|
||||
ASTC_2D_5X5_SRGB = 57,
|
||||
ASTC_2D_10X8 = 58,
|
||||
ASTC_2D_10X8_SRGB = 59,
|
||||
R4G4B4A4U = 52,
|
||||
ASTC_2D_4X4_SRGB = 53,
|
||||
ASTC_2D_8X8_SRGB = 54,
|
||||
ASTC_2D_8X5_SRGB = 55,
|
||||
ASTC_2D_5X4_SRGB = 56,
|
||||
ASTC_2D_5X5 = 57,
|
||||
ASTC_2D_5X5_SRGB = 58,
|
||||
ASTC_2D_10X8 = 59,
|
||||
ASTC_2D_10X8_SRGB = 60,
|
||||
ASTC_2D_6X6 = 61,
|
||||
ASTC_2D_6X6_SRGB = 62,
|
||||
ASTC_2D_10X10 = 63,
|
||||
ASTC_2D_10X10_SRGB = 64,
|
||||
ASTC_2D_12X12 = 65,
|
||||
ASTC_2D_12X12_SRGB = 66,
|
||||
ASTC_2D_8X6 = 67,
|
||||
ASTC_2D_8X6_SRGB = 68,
|
||||
ASTC_2D_6X5 = 69,
|
||||
ASTC_2D_6X5_SRGB = 70,
|
||||
|
||||
MaxColorFormat,
|
||||
|
||||
// Depth formats
|
||||
Z32F = 60,
|
||||
Z16 = 61,
|
||||
Z32F = 71,
|
||||
Z16 = 72,
|
||||
|
||||
MaxDepthFormat,
|
||||
|
||||
// DepthStencil formats
|
||||
Z24S8 = 62,
|
||||
S8Z24 = 63,
|
||||
Z32FS8 = 64,
|
||||
Z24S8 = 73,
|
||||
S8Z24 = 74,
|
||||
Z32FS8 = 75,
|
||||
|
||||
MaxDepthStencilFormat,
|
||||
|
||||
@@ -177,6 +188,7 @@ constexpr std::array<u32, MaxPixelFormat> compression_factor_shift_table = {{
|
||||
2, // DXT23_SRGB
|
||||
2, // DXT45_SRGB
|
||||
2, // BC7U_SRGB
|
||||
0, // R4G4B4A4U
|
||||
2, // ASTC_2D_4X4_SRGB
|
||||
2, // ASTC_2D_8X8_SRGB
|
||||
2, // ASTC_2D_8X5_SRGB
|
||||
@@ -185,6 +197,16 @@ constexpr std::array<u32, MaxPixelFormat> compression_factor_shift_table = {{
|
||||
2, // ASTC_2D_5X5_SRGB
|
||||
2, // ASTC_2D_10X8
|
||||
2, // ASTC_2D_10X8_SRGB
|
||||
2, // ASTC_2D_6X6
|
||||
2, // ASTC_2D_6X6_SRGB
|
||||
2, // ASTC_2D_10X10
|
||||
2, // ASTC_2D_10X10_SRGB
|
||||
2, // ASTC_2D_12X12
|
||||
2, // ASTC_2D_12X12_SRGB
|
||||
2, // ASTC_2D_8X6
|
||||
2, // ASTC_2D_8X6_SRGB
|
||||
2, // ASTC_2D_6X5
|
||||
2, // ASTC_2D_6X5_SRGB
|
||||
0, // Z32F
|
||||
0, // Z16
|
||||
0, // Z24S8
|
||||
@@ -261,6 +283,7 @@ constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
|
||||
4, // DXT23_SRGB
|
||||
4, // DXT45_SRGB
|
||||
4, // BC7U_SRGB
|
||||
1, // R4G4B4A4U
|
||||
4, // ASTC_2D_4X4_SRGB
|
||||
8, // ASTC_2D_8X8_SRGB
|
||||
8, // ASTC_2D_8X5_SRGB
|
||||
@@ -269,6 +292,16 @@ constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
|
||||
5, // ASTC_2D_5X5_SRGB
|
||||
10, // ASTC_2D_10X8
|
||||
10, // ASTC_2D_10X8_SRGB
|
||||
6, // ASTC_2D_6X6
|
||||
6, // ASTC_2D_6X6_SRGB
|
||||
10, // ASTC_2D_10X10
|
||||
10, // ASTC_2D_10X10_SRGB
|
||||
12, // ASTC_2D_12X12
|
||||
12, // ASTC_2D_12X12_SRGB
|
||||
8, // ASTC_2D_8X6
|
||||
8, // ASTC_2D_8X6_SRGB
|
||||
6, // ASTC_2D_6X5
|
||||
6, // ASTC_2D_6X5_SRGB
|
||||
1, // Z32F
|
||||
1, // Z16
|
||||
1, // Z24S8
|
||||
@@ -285,71 +318,82 @@ static constexpr u32 GetDefaultBlockWidth(PixelFormat format) {
|
||||
}
|
||||
|
||||
constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
|
||||
1, // ABGR8U
|
||||
1, // ABGR8S
|
||||
1, // ABGR8UI
|
||||
1, // B5G6R5U
|
||||
1, // A2B10G10R10U
|
||||
1, // A1B5G5R5U
|
||||
1, // R8U
|
||||
1, // R8UI
|
||||
1, // RGBA16F
|
||||
1, // RGBA16U
|
||||
1, // RGBA16UI
|
||||
1, // R11FG11FB10F
|
||||
1, // RGBA32UI
|
||||
4, // DXT1
|
||||
4, // DXT23
|
||||
4, // DXT45
|
||||
4, // DXN1
|
||||
4, // DXN2UNORM
|
||||
4, // DXN2SNORM
|
||||
4, // BC7U
|
||||
4, // BC6H_UF16
|
||||
4, // BC6H_SF16
|
||||
4, // ASTC_2D_4X4
|
||||
1, // BGRA8
|
||||
1, // RGBA32F
|
||||
1, // RG32F
|
||||
1, // R32F
|
||||
1, // R16F
|
||||
1, // R16U
|
||||
1, // R16S
|
||||
1, // R16UI
|
||||
1, // R16I
|
||||
1, // RG16
|
||||
1, // RG16F
|
||||
1, // RG16UI
|
||||
1, // RG16I
|
||||
1, // RG16S
|
||||
1, // RGB32F
|
||||
1, // RGBA8_SRGB
|
||||
1, // RG8U
|
||||
1, // RG8S
|
||||
1, // RG32UI
|
||||
1, // RGBX16F
|
||||
1, // R32UI
|
||||
8, // ASTC_2D_8X8
|
||||
5, // ASTC_2D_8X5
|
||||
4, // ASTC_2D_5X4
|
||||
1, // BGRA8_SRGB
|
||||
4, // DXT1_SRGB
|
||||
4, // DXT23_SRGB
|
||||
4, // DXT45_SRGB
|
||||
4, // BC7U_SRGB
|
||||
4, // ASTC_2D_4X4_SRGB
|
||||
8, // ASTC_2D_8X8_SRGB
|
||||
5, // ASTC_2D_8X5_SRGB
|
||||
4, // ASTC_2D_5X4_SRGB
|
||||
5, // ASTC_2D_5X5
|
||||
5, // ASTC_2D_5X5_SRGB
|
||||
8, // ASTC_2D_10X8
|
||||
8, // ASTC_2D_10X8_SRGB
|
||||
1, // Z32F
|
||||
1, // Z16
|
||||
1, // Z24S8
|
||||
1, // S8Z24
|
||||
1, // Z32FS8
|
||||
1, // ABGR8U
|
||||
1, // ABGR8S
|
||||
1, // ABGR8UI
|
||||
1, // B5G6R5U
|
||||
1, // A2B10G10R10U
|
||||
1, // A1B5G5R5U
|
||||
1, // R8U
|
||||
1, // R8UI
|
||||
1, // RGBA16F
|
||||
1, // RGBA16U
|
||||
1, // RGBA16UI
|
||||
1, // R11FG11FB10F
|
||||
1, // RGBA32UI
|
||||
4, // DXT1
|
||||
4, // DXT23
|
||||
4, // DXT45
|
||||
4, // DXN1
|
||||
4, // DXN2UNORM
|
||||
4, // DXN2SNORM
|
||||
4, // BC7U
|
||||
4, // BC6H_UF16
|
||||
4, // BC6H_SF16
|
||||
4, // ASTC_2D_4X4
|
||||
1, // BGRA8
|
||||
1, // RGBA32F
|
||||
1, // RG32F
|
||||
1, // R32F
|
||||
1, // R16F
|
||||
1, // R16U
|
||||
1, // R16S
|
||||
1, // R16UI
|
||||
1, // R16I
|
||||
1, // RG16
|
||||
1, // RG16F
|
||||
1, // RG16UI
|
||||
1, // RG16I
|
||||
1, // RG16S
|
||||
1, // RGB32F
|
||||
1, // RGBA8_SRGB
|
||||
1, // RG8U
|
||||
1, // RG8S
|
||||
1, // RG32UI
|
||||
1, // RGBX16F
|
||||
1, // R32UI
|
||||
8, // ASTC_2D_8X8
|
||||
5, // ASTC_2D_8X5
|
||||
4, // ASTC_2D_5X4
|
||||
1, // BGRA8_SRGB
|
||||
4, // DXT1_SRGB
|
||||
4, // DXT23_SRGB
|
||||
4, // DXT45_SRGB
|
||||
4, // BC7U_SRGB
|
||||
1, // R4G4B4A4U
|
||||
4, // ASTC_2D_4X4_SRGB
|
||||
8, // ASTC_2D_8X8_SRGB
|
||||
5, // ASTC_2D_8X5_SRGB
|
||||
4, // ASTC_2D_5X4_SRGB
|
||||
5, // ASTC_2D_5X5
|
||||
5, // ASTC_2D_5X5_SRGB
|
||||
8, // ASTC_2D_10X8
|
||||
8, // ASTC_2D_10X8_SRGB
|
||||
6, // ASTC_2D_6X6
|
||||
6, // ASTC_2D_6X6_SRGB
|
||||
10, // ASTC_2D_10X10
|
||||
10, // ASTC_2D_10X10_SRGB
|
||||
12, // ASTC_2D_12X12
|
||||
12, // ASTC_2D_12X12_SRGB
|
||||
6, // ASTC_2D_8X6
|
||||
6, // ASTC_2D_8X6_SRGB
|
||||
5, // ASTC_2D_6X5
|
||||
5, // ASTC_2D_6X5_SRGB
|
||||
1, // Z32F
|
||||
1, // Z16
|
||||
1, // Z24S8
|
||||
1, // S8Z24
|
||||
1, // Z32FS8
|
||||
}};
|
||||
|
||||
static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
|
||||
@@ -413,6 +457,7 @@ constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
|
||||
128, // DXT23_SRGB
|
||||
128, // DXT45_SRGB
|
||||
128, // BC7U
|
||||
16, // R4G4B4A4U
|
||||
128, // ASTC_2D_4X4_SRGB
|
||||
128, // ASTC_2D_8X8_SRGB
|
||||
128, // ASTC_2D_8X5_SRGB
|
||||
@@ -421,6 +466,16 @@ constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
|
||||
128, // ASTC_2D_5X5_SRGB
|
||||
128, // ASTC_2D_10X8
|
||||
128, // ASTC_2D_10X8_SRGB
|
||||
128, // ASTC_2D_6X6
|
||||
128, // ASTC_2D_6X6_SRGB
|
||||
128, // ASTC_2D_10X10
|
||||
128, // ASTC_2D_10X10_SRGB
|
||||
128, // ASTC_2D_12X12
|
||||
128, // ASTC_2D_12X12_SRGB
|
||||
128, // ASTC_2D_8X6
|
||||
128, // ASTC_2D_8X6_SRGB
|
||||
128, // ASTC_2D_6X5
|
||||
128, // ASTC_2D_6X5_SRGB
|
||||
32, // Z32F
|
||||
16, // Z16
|
||||
32, // Z24S8
|
||||
@@ -504,6 +559,7 @@ constexpr std::array<SurfaceCompression, MaxPixelFormat> compression_type_table
|
||||
SurfaceCompression::Compressed, // DXT23_SRGB
|
||||
SurfaceCompression::Compressed, // DXT45_SRGB
|
||||
SurfaceCompression::Compressed, // BC7U_SRGB
|
||||
SurfaceCompression::None, // R4G4B4A4U
|
||||
SurfaceCompression::Converted, // ASTC_2D_4X4_SRGB
|
||||
SurfaceCompression::Converted, // ASTC_2D_8X8_SRGB
|
||||
SurfaceCompression::Converted, // ASTC_2D_8X5_SRGB
|
||||
@@ -512,6 +568,16 @@ constexpr std::array<SurfaceCompression, MaxPixelFormat> compression_type_table
|
||||
SurfaceCompression::Converted, // ASTC_2D_5X5_SRGB
|
||||
SurfaceCompression::Converted, // ASTC_2D_10X8
|
||||
SurfaceCompression::Converted, // ASTC_2D_10X8_SRGB
|
||||
SurfaceCompression::Converted, // ASTC_2D_6X6
|
||||
SurfaceCompression::Converted, // ASTC_2D_6X6_SRGB
|
||||
SurfaceCompression::Converted, // ASTC_2D_10X10
|
||||
SurfaceCompression::Converted, // ASTC_2D_10X10_SRGB
|
||||
SurfaceCompression::Converted, // ASTC_2D_12X12
|
||||
SurfaceCompression::Converted, // ASTC_2D_12X12_SRGB
|
||||
SurfaceCompression::Converted, // ASTC_2D_8X6
|
||||
SurfaceCompression::Converted, // ASTC_2D_8X6_SRGB
|
||||
SurfaceCompression::Converted, // ASTC_2D_6X5
|
||||
SurfaceCompression::Converted, // ASTC_2D_6X5_SRGB
|
||||
SurfaceCompression::None, // Z32F
|
||||
SurfaceCompression::None, // Z16
|
||||
SurfaceCompression::None, // Z24S8
|
||||
|
||||
@@ -62,6 +62,8 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
||||
}
|
||||
UpdateBackgroundColorButton(new_bg_color);
|
||||
});
|
||||
connect(ui->brightness_reset, &QPushButton::pressed, this,
|
||||
[this] { ui->brightness_slider->setValue(100); });
|
||||
}
|
||||
|
||||
ConfigureGraphics::~ConfigureGraphics() = default;
|
||||
@@ -80,6 +82,7 @@ void ConfigureGraphics::SetConfiguration() {
|
||||
ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
|
||||
UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
|
||||
Settings::values.bg_blue));
|
||||
ui->brightness_slider->setValue(Settings::values.backlight_brightness * 100 + 50);
|
||||
}
|
||||
|
||||
void ConfigureGraphics::ApplyConfiguration() {
|
||||
@@ -93,6 +96,7 @@ void ConfigureGraphics::ApplyConfiguration() {
|
||||
Settings::values.bg_red = static_cast<float>(bg_color.redF());
|
||||
Settings::values.bg_green = static_cast<float>(bg_color.greenF());
|
||||
Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
|
||||
Settings::values.backlight_brightness = (ui->brightness_slider->value() - 50.0f) / 100.0f;
|
||||
}
|
||||
|
||||
void ConfigureGraphics::changeEvent(QEvent* event) {
|
||||
|
||||
@@ -111,6 +111,68 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Brightness</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="brightness_slider">
|
||||
<property name="minimum">
|
||||
<number>50</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>150</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::NoTicks</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="brightness_reset">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -66,10 +66,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()
|
||||
};
|
||||
|
||||
const auto& system = Core::System::GetInstance();
|
||||
add_threads(system.Scheduler(0).GetThreadList());
|
||||
add_threads(system.Scheduler(1).GetThreadList());
|
||||
add_threads(system.Scheduler(2).GetThreadList());
|
||||
add_threads(system.Scheduler(3).GetThreadList());
|
||||
add_threads(system.GlobalScheduler().GetThreadList());
|
||||
|
||||
return item_list;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user