Compare commits

..

1 Commits

Author SHA1 Message Date
zhupengfei
a2be49305d yuzu, video_core: Screenshot functionality
Allows capturing screenshot at the current internal resolution (native for software renderer), but a setting is available to capture it in other resolutions. The screenshot is saved to a single PNG in the current layout.
2018-12-18 22:54:41 +01:00
84 changed files with 599 additions and 1533 deletions

View File

@@ -54,9 +54,8 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
double l = 0.0;
double r = 0.0;
for (std::size_t j = 0; j < h.size(); j++) {
const double lanczos_calc = Lanczos(taps, pos + j - taps + 1);
l += lanczos_calc * h[j][0];
r += lanczos_calc * h[j][1];
l += Lanczos(taps, pos + j - taps + 1) * h[j][0];
r += Lanczos(taps, pos + j - taps + 1) * h[j][1];
}
output.emplace_back(static_cast<s16>(std::clamp(l, -32768.0, 32767.0)));
output.emplace_back(static_cast<s16>(std::clamp(r, -32768.0, 32767.0)));

View File

@@ -30,7 +30,8 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& name,
Stream::ReleaseCallback&& release_callback) {
if (!sink) {
sink = CreateSinkFromID(Settings::values.sink_id, Settings::values.audio_device_id);
const SinkDetails& sink_details = GetSinkDetails(Settings::values.sink_id);
sink = sink_details.factory(Settings::values.audio_device_id);
}
return std::make_shared<Stream>(

View File

@@ -285,11 +285,8 @@ void AudioRenderer::VoiceState::RefreshBuffer() {
break;
}
// Only interpolate when necessary, expensive.
if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) {
samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate,
STREAM_SAMPLE_RATE);
}
samples =
Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, STREAM_SAMPLE_RATE);
is_refresh_pending = false;
}

View File

@@ -107,7 +107,7 @@ private:
static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
};
CubebSink::CubebSink(std::string_view target_device_name) {
CubebSink::CubebSink(std::string target_device_name) {
if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return;

View File

@@ -15,7 +15,7 @@ namespace AudioCore {
class CubebSink final : public Sink {
public:
explicit CubebSink(std::string_view device_id);
explicit CubebSink(std::string device_id);
~CubebSink() override;
SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,

View File

@@ -10,7 +10,7 @@ namespace AudioCore {
class NullSink final : public Sink {
public:
explicit NullSink(std::string_view) {}
explicit NullSink(std::string){};
~NullSink() override = default;
SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/,

View File

@@ -14,68 +14,31 @@
#include "common/logging/log.h"
namespace AudioCore {
namespace {
struct SinkDetails {
using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
using ListDevicesFn = std::vector<std::string> (*)();
/// Name for this sink.
const char* id;
/// A method to call to construct an instance of this type of sink.
FactoryFn factory;
/// A method to call to list available devices.
ListDevicesFn list_devices;
};
// sink_details is ordered in terms of desirability, with the best choice at the top.
constexpr SinkDetails sink_details[] = {
// g_sink_details is ordered in terms of desirability, with the best choice at the top.
const std::vector<SinkDetails> g_sink_details = {
#ifdef HAVE_CUBEB
SinkDetails{"cubeb",
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<CubebSink>(device_id);
},
&ListCubebSinkDevices},
SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices},
#endif
SinkDetails{"null",
[](std::string_view device_id) -> std::unique_ptr<Sink> {
return std::make_unique<NullSink>(device_id);
},
SinkDetails{"null", &std::make_unique<NullSink, std::string>,
[] { return std::vector<std::string>{"null"}; }},
};
const SinkDetails& GetSinkDetails(std::string_view sink_id) {
auto iter =
std::find_if(std::begin(sink_details), std::end(sink_details),
std::find_if(g_sink_details.begin(), g_sink_details.end(),
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
if (sink_id == "auto" || iter == std::end(sink_details)) {
if (sink_id == "auto" || iter == g_sink_details.end()) {
if (sink_id != "auto") {
LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id);
}
// Auto-select.
// sink_details is ordered in terms of desirability, with the best choice at the front.
iter = std::begin(sink_details);
// g_sink_details is ordered in terms of desirability, with the best choice at the front.
iter = g_sink_details.begin();
}
return *iter;
}
} // Anonymous namespace
std::vector<const char*> GetSinkIDs() {
std::vector<const char*> sink_ids(std::size(sink_details));
std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
[](const auto& sink) { return sink.id; });
return sink_ids;
}
std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) {
return GetSinkDetails(sink_id).list_devices();
}
std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) {
return GetSinkDetails(sink_id).factory(device_id);
}
} // namespace AudioCore

View File

@@ -4,21 +4,34 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace AudioCore {
class Sink;
/// Retrieves the IDs for all available audio sinks.
std::vector<const char*> GetSinkIDs();
struct SinkDetails {
using FactoryFn = std::function<std::unique_ptr<Sink>(std::string)>;
using ListDevicesFn = std::function<std::vector<std::string>()>;
/// Gets the list of devices for a particular sink identified by the given ID.
std::vector<std::string> GetDeviceListForSink(std::string_view sink_id);
SinkDetails(const char* id_, FactoryFn factory_, ListDevicesFn list_devices_)
: id(id_), factory(std::move(factory_)), list_devices(std::move(list_devices_)) {}
/// Creates an audio sink identified by the given device ID.
std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);
/// Name for this sink.
const char* id;
/// A method to call to construct an instance of this type of sink.
FactoryFn factory;
/// A method to call to list available devices.
ListDevicesFn list_devices;
};
extern const std::vector<SinkDetails> g_sink_details;
const SinkDetails& GetSinkDetails(std::string_view sink_id);
} // namespace AudioCore

View File

@@ -13,7 +13,7 @@
#include <vector>
#ifdef _WIN32
#include <share.h> // For _SH_DENYWR
#include <windows.h> // For OutputDebugStringW
#include <windows.h> // For OutputDebugStringA
#else
#define _SH_DENYWR 0
#endif
@@ -148,7 +148,7 @@ void FileBackend::Write(const Entry& entry) {
void DebuggerBackend::Write(const Entry& entry) {
#ifdef _WIN32
::OutputDebugStringW(Common::UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
::OutputDebugStringA(FormatLogMessage(entry).append(1, '\n').c_str());
#endif
}

View File

@@ -49,22 +49,6 @@ struct ThreadQueueList {
return T();
}
template <typename UnaryPredicate>
T get_first_filter(UnaryPredicate filter) const {
const Queue* cur = first;
while (cur != nullptr) {
if (!cur->data.empty()) {
for (const auto& item : cur->data) {
if (filter(item))
return item;
}
}
cur = cur->next_nonempty;
}
return T();
}
T pop_first() {
Queue* cur = first;
while (cur != nullptr) {

View File

@@ -8,7 +8,6 @@
#include <thread>
#include <utility>
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
@@ -41,6 +40,7 @@ namespace Core {
/*static*/ System System::s_instance;
namespace {
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
const std::string& path) {
// To account for split 00+01+etc files.
@@ -69,13 +69,11 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
}
if (FileUtil::IsDirectory(path))
return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read);
return vfs->OpenFile(path, FileSys::Mode::Read);
}
struct System::Impl {
} // Anonymous namespace
struct System::Impl {
Cpu& CurrentCpuCore() {
return cpu_core_manager.GetCurrentCore();
}

View File

@@ -9,7 +9,6 @@
#include <string>
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
#include "core/hle/kernel/object.h"
namespace Core::Frontend {
@@ -56,9 +55,6 @@ class TelemetrySession;
struct PerfStatsResults;
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
const std::string& path);
class System {
public:
System(const System&) = delete;

View File

@@ -29,8 +29,8 @@ struct Entry {
filename[copy_size] = '\0';
}
char filename[0x301];
INSERT_PADDING_BYTES(3);
char filename[0x300];
INSERT_PADDING_BYTES(4);
EntryType type;
INSERT_PADDING_BYTES(3);
u64 file_size;

View File

@@ -56,10 +56,6 @@ PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
PatchManager::~PatchManager() = default;
u64 PatchManager::GetTitleID() const {
return title_id;
}
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
@@ -77,15 +73,11 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
const auto installed = Service::FileSystem::GetUnionContents();
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = installed.GetEntry(update_tid, ContentRecordType::Program);
if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr &&
if (update != nullptr && update->GetExeFS() != nullptr &&
update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
@@ -103,9 +95,6 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
std::vector<VirtualDir> layers;
layers.reserve(patch_dirs.size() + 1);
for (const auto& subdir : patch_dirs) {
if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
continue;
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr)
layers.push_back(std::move(exefs_dir));
@@ -122,16 +111,11 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
return exefs;
}
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const {
const auto& disabled = Settings::values.disabled_addons[title_id];
static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) {
std::vector<VirtualFile> out;
out.reserve(patch_dirs.size());
for (const auto& subdir : patch_dirs) {
if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
continue;
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr) {
for (const auto& file : exefs_dir->GetFiles()) {
@@ -244,7 +228,6 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
return;
}
const auto& disabled = Settings::values.disabled_addons[title_id];
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
@@ -254,9 +237,6 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
layers.reserve(patch_dirs.size() + 1);
layers_ext.reserve(patch_dirs.size() + 1);
for (const auto& subdir : patch_dirs) {
if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
continue;
auto romfs_dir = subdir->GetSubdirectory("romfs");
if (romfs_dir != nullptr)
layers.push_back(std::move(romfs_dir));
@@ -286,12 +266,13 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
VirtualFile update_raw) const {
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
title_id, static_cast<u8>(type));
title_id, static_cast<u8>(type))
.c_str();
if (type == ContentRecordType::Program || type == ContentRecordType::Data)
LOG_INFO(Loader, "{}", log_string);
LOG_INFO(Loader, log_string);
else
LOG_DEBUG(Loader, "{}", log_string);
LOG_DEBUG(Loader, log_string);
if (romfs == nullptr)
return romfs;
@@ -301,12 +282,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = installed.GetEntryRaw(update_tid, type);
const auto& disabled = Settings::values.disabled_addons[title_id];
const auto update_disabled =
std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
if (!update_disabled && update != nullptr) {
if (update != nullptr) {
const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
@@ -314,7 +290,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
romfs = new_nca->GetRomFS();
}
} else if (!update_disabled && update_raw != nullptr) {
} else if (update_raw != nullptr) {
const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
@@ -344,30 +320,25 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
VirtualFile update_raw) const {
std::map<std::string, std::string, std::less<>> out;
const auto installed = Service::FileSystem::GetUnionContents();
const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
PatchManager update{update_tid};
auto [nacp, discard_icon_file] = update.GetControlMetadata();
const auto update_disabled =
std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
const auto update_label = update_disabled ? "[D] Update" : "Update";
if (nacp != nullptr) {
out.insert_or_assign(update_label, nacp->GetVersionString());
out.insert_or_assign("Update", nacp->GetVersionString());
} else {
if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = installed.GetEntryVersion(update_tid);
if (meta_ver.value_or(0) == 0) {
out.insert_or_assign(update_label, "");
out.insert_or_assign("Update", "");
} else {
out.insert_or_assign(
update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
"Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
}
} else if (update_raw != nullptr) {
out.insert_or_assign(update_label, "PACKED");
out.insert_or_assign("Update", "PACKED");
}
}
@@ -407,9 +378,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (types.empty())
continue;
const auto mod_disabled =
std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end();
out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types);
out.insert_or_assign(mod->GetName(), types);
}
}
@@ -432,9 +401,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
const auto dlc_disabled =
std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end();
out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list));
out.insert_or_assign("DLC", std::move(list));
}
return out;

View File

@@ -30,8 +30,6 @@ public:
explicit PatchManager(u64 title_id);
~PatchManager();
u64 GetTitleID() const;
// Currently tracked ExeFS patches:
// - Game Updates
VirtualDir PatchExeFS(VirtualDir exefs) const;
@@ -65,9 +63,6 @@ public:
std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
private:
std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const;
u64 title_id;
};

View File

@@ -18,11 +18,7 @@ std::string SaveDataDescriptor::DebugInfo() const {
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id);
}
SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
// Delete all temporary storages
// On hardware, it is expected that temporary storage be empty at first use.
dir->DeleteSubdirectoryRecursive("temp");
}
SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {}
SaveDataFactory::~SaveDataFactory() = default;
@@ -124,8 +120,6 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataType::TemporaryStorage:
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
case SaveDataType::CacheStorage:
return fmt::format("{}save/cache/{:016X}", out, title_id);
default:
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
}

View File

@@ -17,10 +17,8 @@ namespace FileSys {
enum class SaveDataSpaceId : u8 {
NandSystem = 0,
NandUser = 1,
SdCardSystem = 2,
SdCard = 2,
TemporaryStorage = 3,
SdCardUser = 4,
ProperSystem = 100,
};
enum class SaveDataType : u8 {

View File

@@ -6,6 +6,7 @@
#include "common/assert.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/settings.h"
namespace Layout {
@@ -42,4 +43,18 @@ FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) {
return res;
}
FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale) {
int width, height;
if (Settings::values.use_docked_mode) {
width = ScreenDocked::WidthDocked * res_scale;
height = ScreenDocked::HeightDocked * res_scale;
} else {
width = ScreenUndocked::Width * res_scale;
height = ScreenUndocked::Height * res_scale;
}
return DefaultFrameLayout(width, height);
}
} // namespace Layout

View File

@@ -9,6 +9,7 @@
namespace Layout {
enum ScreenUndocked : unsigned { Width = 1280, Height = 720 };
enum ScreenDocked : unsigned { WidthDocked = 1920, HeightDocked = 1080 };
/// Describes the layout of the window framebuffer
struct FramebufferLayout {
@@ -34,4 +35,10 @@ struct FramebufferLayout {
*/
FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height);
/**
* Convenience method to get frame layout by resolution scale
* @param res_scale resolution scale factor
*/
FramebufferLayout FrameLayoutFromResolutionScale(u16 res_scale);
} // namespace Layout

View File

@@ -66,7 +66,6 @@ ResultCode Process::ClearSignalState() {
void Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
program_id = metadata.GetTitleID();
ideal_processor = metadata.GetMainThreadCore();
is_64bit_process = metadata.Is64BitProgram();
vm_manager.Reset(metadata.GetAddressSpaceType());
}
@@ -150,7 +149,7 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
vm_manager
.MapMemoryBlock(vm_manager.GetTLSIORegionEndAddress() - stack_size,
std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size,
MemoryState::Stack)
MemoryState::Mapped)
.Unwrap();
vm_manager.LogLayout();

View File

@@ -262,7 +262,8 @@ public:
ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
ResultCode HeapFree(VAddr target, u32 size);
ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state);
ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size,
MemoryState state = MemoryState::Mapped);
ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size);

View File

@@ -9,7 +9,6 @@
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
@@ -180,69 +179,4 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
ready_queue.prepare(priority);
}
Thread* Scheduler::GetNextSuggestedThread(u32 core, u32 maximum_priority) const {
std::lock_guard<std::mutex> lock(scheduler_mutex);
const u32 mask = 1U << core;
return ready_queue.get_first_filter([mask, maximum_priority](Thread const* thread) {
return (thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority;
});
}
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
WaitCurrentThread_Sleep();
GetCurrentThread()->WakeAfterDelay(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
WaitCurrentThread_Sleep();
GetCurrentThread()->WakeAfterDelay(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 =
Core::System::GetInstance().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!");
}
} // namespace Kernel

View File

@@ -51,75 +51,6 @@ public:
/// 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);
/// Returns a list of all threads managed by the scheduler
const std::vector<SharedPtr<Thread>>& GetThreadList() const {
return thread_list;

View File

@@ -17,13 +17,13 @@ namespace Kernel {
SharedMemory::SharedMemory(KernelCore& kernel) : Object{kernel} {}
SharedMemory::~SharedMemory() = default;
SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_process, u64 size,
MemoryPermission permissions,
SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Process> owner_process,
u64 size, MemoryPermission permissions,
MemoryPermission other_permissions, VAddr address,
MemoryRegion region, std::string name) {
SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel));
shared_memory->owner_process = owner_process;
shared_memory->owner_process = std::move(owner_process);
shared_memory->name = std::move(name);
shared_memory->size = size;
shared_memory->permissions = permissions;
@@ -39,15 +39,15 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_
shared_memory->backing_block.get());
}
} else {
const auto& vm_manager = shared_memory->owner_process->VMManager();
auto& vm_manager = shared_memory->owner_process->VMManager();
// The memory is already available and mapped in the owner process.
const auto vma = vm_manager.FindVMA(address);
ASSERT_MSG(vm_manager.IsValidHandle(vma), "Invalid memory address");
auto vma = vm_manager.FindVMA(address);
ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address");
ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address");
// The returned VMA might be a bigger one encompassing the desired address.
const auto vma_offset = address - vma->first;
auto vma_offset = address - vma->first;
ASSERT_MSG(vma_offset + size <= vma->second.size,
"Shared memory exceeds bounds of mapped block");

View File

@@ -45,8 +45,8 @@ public:
* linear heap.
* @param name Optional object name, used for debugging purposes.
*/
static SharedPtr<SharedMemory> Create(KernelCore& kernel, Process* owner_process, u64 size,
MemoryPermission permissions,
static SharedPtr<SharedMemory> Create(KernelCore& kernel, SharedPtr<Process> owner_process,
u64 size, MemoryPermission permissions,
MemoryPermission other_permissions, VAddr address = 0,
MemoryRegion region = MemoryRegion::BASE,
std::string name = "Unknown");
@@ -139,7 +139,7 @@ private:
/// Permission restrictions applied to other processes mapping the block.
MemoryPermission other_permissions{};
/// Process that created this shared memory block.
Process* owner_process;
SharedPtr<Process> owner_process;
/// Address of shared memory block in the owner process if specified.
VAddr base_address = 0;
/// Name of shared memory object.

View File

@@ -35,7 +35,6 @@
#include "core/hle/lock.h"
#include "core/hle/result.h"
#include "core/hle/service/service.h"
#include "core/memory.h"
namespace Kernel {
namespace {
@@ -240,7 +239,7 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) {
}
const VMManager::VMAHandle iter = vm_manager.FindVMA(addr);
if (!vm_manager.IsValidHandle(iter)) {
if (iter == vm_manager.vma_map.end()) {
LOG_ERROR(Kernel_SVC, "Unable to find VMA for address=0x{:016X}", addr);
return ERR_INVALID_ADDRESS_STATE;
}
@@ -274,7 +273,7 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
return result;
}
return current_process->MirrorMemory(dst_addr, src_addr, size, MemoryState::Stack);
return current_process->MirrorMemory(dst_addr, src_addr, size);
}
/// Unmaps a region that was previously mapped with svcMapMemory
@@ -1067,9 +1066,10 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
return shared_memory->Unmap(*current_process, addr);
}
static ResultCode QueryProcessMemory(VAddr memory_info_address, VAddr page_info_address,
Handle process_handle, VAddr address) {
LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address);
/// Query process memory
static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_info*/,
Handle process_handle, u64 addr) {
LOG_TRACE(Kernel_SVC, "called process=0x{:08X} addr={:X}", process_handle, addr);
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
SharedPtr<Process> process = handle_table.Get<Process>(process_handle);
if (!process) {
@@ -1077,34 +1077,26 @@ static ResultCode QueryProcessMemory(VAddr memory_info_address, VAddr page_info_
process_handle);
return ERR_INVALID_HANDLE;
}
const auto& vm_manager = process->VMManager();
const MemoryInfo memory_info = vm_manager.QueryMemory(address);
Memory::Write64(memory_info_address, memory_info.base_address);
Memory::Write64(memory_info_address + 8, memory_info.size);
Memory::Write32(memory_info_address + 16, memory_info.state);
Memory::Write32(memory_info_address + 20, memory_info.attributes);
Memory::Write32(memory_info_address + 24, memory_info.permission);
Memory::Write32(memory_info_address + 32, memory_info.ipc_ref_count);
Memory::Write32(memory_info_address + 28, memory_info.device_ref_count);
Memory::Write32(memory_info_address + 36, 0);
// Page info appears to be currently unused by the kernel and is always set to zero.
Memory::Write32(page_info_address, 0);
auto vma = process->VMManager().FindVMA(addr);
memory_info->attributes = 0;
if (vma == process->VMManager().vma_map.end()) {
memory_info->base_address = 0;
memory_info->permission = static_cast<u32>(VMAPermission::None);
memory_info->size = 0;
memory_info->type = static_cast<u32>(MemoryState::Unmapped);
} else {
memory_info->base_address = vma->second.base;
memory_info->permission = static_cast<u32>(vma->second.permissions);
memory_info->size = vma->second.size;
memory_info->type = static_cast<u32>(vma->second.meminfo_state);
}
return RESULT_SUCCESS;
}
static ResultCode QueryMemory(VAddr memory_info_address, VAddr page_info_address,
VAddr query_address) {
LOG_TRACE(Kernel_SVC,
"called, memory_info_address=0x{:016X}, page_info_address=0x{:016X}, "
"query_address=0x{:016X}",
memory_info_address, page_info_address, query_address);
return QueryProcessMemory(memory_info_address, page_info_address, CurrentProcess,
query_address);
/// Query memory
static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAddr addr) {
LOG_TRACE(Kernel_SVC, "called, addr={:X}", addr);
return QueryProcessMemory(memory_info, page_info, CurrentProcess, addr);
}
/// Exits the current process
@@ -1208,38 +1200,18 @@ static void ExitThread() {
static void SleepThread(s64 nanoseconds) {
LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
enum class SleepType : s64 {
YieldWithoutLoadBalancing = 0,
YieldWithLoadBalancing = -1,
YieldAndWaitForLoadBalancing = -2,
};
// Don't attempt to yield execution if there are no available threads to run,
// this way we avoid a useless reschedule to the idle thread.
if (nanoseconds == 0 && !Core::System::GetInstance().CurrentScheduler().HaveReadyThreads())
return;
if (nanoseconds <= 0) {
auto& scheduler{Core::System::GetInstance().CurrentScheduler()};
switch (static_cast<SleepType>(nanoseconds)) {
case SleepType::YieldWithoutLoadBalancing:
scheduler.YieldWithoutLoadBalancing(GetCurrentThread());
break;
case SleepType::YieldWithLoadBalancing:
scheduler.YieldWithLoadBalancing(GetCurrentThread());
break;
case SleepType::YieldAndWaitForLoadBalancing:
scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread());
break;
default:
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
}
} else {
// Sleep current thread and check for next thread to schedule
WaitCurrentThread_Sleep();
// Sleep current thread and check for next thread to schedule
WaitCurrentThread_Sleep();
// Create an event to wake the thread up after the specified nanosecond delay has passed
GetCurrentThread()->WakeAfterDelay(nanoseconds);
}
// Create an event to wake the thread up after the specified nanosecond delay has passed
GetCurrentThread()->WakeAfterDelay(nanoseconds);
// Reschedule all CPU cores
for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i)
Core::System::GetInstance().CpuCore(i).PrepareReschedule();
Core::System::GetInstance().PrepareReschedule();
}
/// Wait process wide key atomic
@@ -1511,9 +1483,9 @@ static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32
}
auto& kernel = Core::System::GetInstance().Kernel();
auto process = kernel.CurrentProcess();
auto& handle_table = process->GetHandleTable();
const auto shared_mem_handle = SharedMemory::Create(kernel, process, size, perms, perms, addr);
auto& handle_table = Core::CurrentProcess()->GetHandleTable();
const auto shared_mem_handle = SharedMemory::Create(
kernel, handle_table.Get<Process>(CurrentProcess), size, perms, perms, addr);
CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle));
return RESULT_SUCCESS;
@@ -1623,9 +1595,10 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
}
auto& kernel = Core::System::GetInstance().Kernel();
auto process = kernel.CurrentProcess();
auto& handle_table = process->GetHandleTable();
auto shared_mem_handle = SharedMemory::Create(kernel, process, size, local_perms, remote_perms);
auto& handle_table = Core::CurrentProcess()->GetHandleTable();
auto shared_mem_handle =
SharedMemory::Create(kernel, handle_table.Get<Process>(KernelHandle::CurrentProcess), size,
local_perms, remote_perms);
CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle));
return RESULT_SUCCESS;
@@ -1931,7 +1904,7 @@ static const FunctionDef SVC_Table[] = {
{0x73, nullptr, "SetProcessMemoryPermission"},
{0x74, nullptr, "MapProcessMemory"},
{0x75, nullptr, "UnmapProcessMemory"},
{0x76, SvcWrap<QueryProcessMemory>, "QueryProcessMemory"},
{0x76, nullptr, "QueryProcessMemory"},
{0x77, nullptr, "MapProcessCodeMemory"},
{0x78, nullptr, "UnmapProcessCodeMemory"},
{0x79, nullptr, "CreateProcess"},

View File

@@ -8,6 +8,22 @@
namespace Kernel {
struct MemoryInfo {
u64 base_address;
u64 size;
u32 type;
u32 attributes;
u32 permission;
u32 device_refcount;
u32 ipc_refcount;
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size.");
struct PageInfo {
u64 flags;
};
void CallSVC(u32 immediate);
} // namespace Kernel

View File

@@ -7,7 +7,9 @@
#include "common/common_types.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/hle/kernel/svc.h"
#include "core/hle/result.h"
#include "core/memory.h"
namespace Kernel {
@@ -127,12 +129,7 @@ void SvcWrap() {
template <ResultCode func(u64, u64, u32, u32)>
void SvcWrap() {
FuncReturn(
func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw);
}
template <ResultCode func(u64, u64, u32, u64)>
void SvcWrap() {
FuncReturn(func(Param(0), Param(1), static_cast<u32>(Param(2)), Param(3)).raw);
func(Param(0), Param(1), static_cast<u32>(Param(3)), static_cast<u32>(Param(3))).raw);
}
template <ResultCode func(u32, u64, u32)>
@@ -194,6 +191,21 @@ void SvcWrap() {
FuncReturn(retval);
}
template <ResultCode func(MemoryInfo*, PageInfo*, u64)>
void SvcWrap() {
MemoryInfo memory_info = {};
PageInfo page_info = {};
u32 retval = func(&memory_info, &page_info, Param(2)).raw;
Memory::Write64(Param(0), memory_info.base_address);
Memory::Write64(Param(0) + 8, memory_info.size);
Memory::Write32(Param(0) + 16, memory_info.type);
Memory::Write32(Param(0) + 20, memory_info.attributes);
Memory::Write32(Param(0) + 24, memory_info.permission);
FuncReturn(retval);
}
template <ResultCode func(u32*, u64, u64, u32)>
void SvcWrap() {
u32 param_1 = 0;

View File

@@ -26,7 +26,6 @@ enum ThreadPriority : u32 {
THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
THREADPRIO_LOWEST = 63, ///< Lowest thread priority
THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities.
};
enum ThreadProcessorId : s32 {

View File

@@ -25,14 +25,14 @@ static const char* GetMemoryStateName(MemoryState state) {
"CodeMutable", "Heap",
"Shared", "Unknown1",
"ModuleCodeStatic", "ModuleCodeMutable",
"IpcBuffer0", "Stack",
"IpcBuffer0", "Mapped",
"ThreadLocal", "TransferMemoryIsolated",
"TransferMemory", "ProcessMemory",
"Inaccessible", "IpcBuffer1",
"Unknown2", "IpcBuffer1",
"IpcBuffer3", "KernelStack",
};
return names[ToSvcMemoryState(state)];
return names[static_cast<int>(state)];
}
bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
@@ -87,10 +87,6 @@ VMManager::VMAHandle VMManager::FindVMA(VAddr target) const {
}
}
bool VMManager::IsValidHandle(VMAHandle handle) const {
return handle != vma_map.cend();
}
ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
std::shared_ptr<std::vector<u8>> block,
std::size_t offset, u64 size,
@@ -302,25 +298,6 @@ ResultCode VMManager::HeapFree(VAddr target, u64 size) {
return RESULT_SUCCESS;
}
MemoryInfo VMManager::QueryMemory(VAddr address) const {
const auto vma = FindVMA(address);
MemoryInfo memory_info{};
if (IsValidHandle(vma)) {
memory_info.base_address = vma->second.base;
memory_info.permission = static_cast<u32>(vma->second.permissions);
memory_info.size = vma->second.size;
memory_info.state = ToSvcMemoryState(vma->second.meminfo_state);
} else {
memory_info.base_address = address_space_end;
memory_info.permission = static_cast<u32>(VMAPermission::None);
memory_info.size = 0 - address_space_end;
memory_info.state = static_cast<u32>(MemoryState::Inaccessible);
}
return memory_info;
}
ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) {
const auto vma = FindVMA(src_addr);

View File

@@ -43,129 +43,26 @@ enum class VMAPermission : u8 {
ReadWriteExecute = Read | Write | Execute,
};
// clang-format off
/// Represents memory states and any relevant flags, as used by the kernel.
/// svcQueryMemory interprets these by masking away all but the first eight
/// bits when storing memory state into a MemoryInfo instance.
/// Set of values returned in MemoryInfo.state by svcQueryMemory.
enum class MemoryState : u32 {
Mask = 0xFF,
FlagProtect = 1U << 8,
FlagDebug = 1U << 9,
FlagIPC0 = 1U << 10,
FlagIPC3 = 1U << 11,
FlagIPC1 = 1U << 12,
FlagMapped = 1U << 13,
FlagCode = 1U << 14,
FlagAlias = 1U << 15,
FlagModule = 1U << 16,
FlagTransfer = 1U << 17,
FlagQueryPhysicalAddressAllowed = 1U << 18,
FlagSharedDevice = 1U << 19,
FlagSharedDeviceAligned = 1U << 20,
FlagIPCBuffer = 1U << 21,
FlagMemoryPoolAllocated = 1U << 22,
FlagMapProcess = 1U << 23,
FlagUncached = 1U << 24,
FlagCodeMemory = 1U << 25,
// Convenience flag sets to reduce repetition
IPCFlags = FlagIPC0 | FlagIPC3 | FlagIPC1,
CodeFlags = FlagDebug | IPCFlags | FlagMapped | FlagCode | FlagQueryPhysicalAddressAllowed |
FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
DataFlags = FlagProtect | IPCFlags | FlagMapped | FlagAlias | FlagTransfer |
FlagQueryPhysicalAddressAllowed | FlagSharedDevice | FlagSharedDeviceAligned |
FlagMemoryPoolAllocated | FlagIPCBuffer | FlagUncached,
Unmapped = 0x00,
Io = 0x01 | FlagMapped,
Normal = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed,
CodeStatic = 0x03 | CodeFlags | FlagMapProcess,
CodeMutable = 0x04 | CodeFlags | FlagMapProcess | FlagCodeMemory,
Heap = 0x05 | DataFlags | FlagCodeMemory,
Shared = 0x06 | FlagMapped | FlagMemoryPoolAllocated,
ModuleCodeStatic = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
ModuleCodeMutable = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
IpcBuffer0 = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated |
IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned,
Stack = 0x0B | FlagMapped | IPCFlags | FlagQueryPhysicalAddressAllowed |
FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
ThreadLocal = 0x0C | FlagMapped | FlagMemoryPoolAllocated,
TransferMemoryIsolated = 0x0D | IPCFlags | FlagMapped | FlagQueryPhysicalAddressAllowed |
FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated |
FlagUncached,
TransferMemory = 0x0E | FlagIPC3 | FlagIPC1 | FlagMapped | FlagQueryPhysicalAddressAllowed |
FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
ProcessMemory = 0x0F | FlagIPC3 | FlagIPC1 | FlagMapped | FlagMemoryPoolAllocated,
// Used to signify an inaccessible or invalid memory region with memory queries
Inaccessible = 0x10,
IpcBuffer1 = 0x11 | FlagIPC3 | FlagIPC1 | FlagMapped | FlagQueryPhysicalAddressAllowed |
FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
IpcBuffer3 = 0x12 | FlagIPC3 | FlagMapped | FlagQueryPhysicalAddressAllowed |
FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
KernelStack = 0x13 | FlagMapped,
};
// clang-format on
constexpr MemoryState operator|(MemoryState lhs, MemoryState rhs) {
return static_cast<MemoryState>(u32(lhs) | u32(rhs));
}
constexpr MemoryState operator&(MemoryState lhs, MemoryState rhs) {
return static_cast<MemoryState>(u32(lhs) & u32(rhs));
}
constexpr MemoryState operator^(MemoryState lhs, MemoryState rhs) {
return static_cast<MemoryState>(u32(lhs) ^ u32(rhs));
}
constexpr MemoryState operator~(MemoryState lhs) {
return static_cast<MemoryState>(~u32(lhs));
}
constexpr MemoryState& operator|=(MemoryState& lhs, MemoryState rhs) {
lhs = lhs | rhs;
return lhs;
}
constexpr MemoryState& operator&=(MemoryState& lhs, MemoryState rhs) {
lhs = lhs & rhs;
return lhs;
}
constexpr MemoryState& operator^=(MemoryState& lhs, MemoryState rhs) {
lhs = lhs ^ rhs;
return lhs;
}
constexpr u32 ToSvcMemoryState(MemoryState state) {
return static_cast<u32>(state & MemoryState::Mask);
}
struct MemoryInfo {
u64 base_address;
u64 size;
u32 state;
u32 attributes;
u32 permission;
u32 ipc_ref_count;
u32 device_ref_count;
};
static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size.");
struct PageInfo {
u32 flags;
Unmapped = 0x0,
Io = 0x1,
Normal = 0x2,
CodeStatic = 0x3,
CodeMutable = 0x4,
Heap = 0x5,
Shared = 0x6,
ModuleCodeStatic = 0x8,
ModuleCodeMutable = 0x9,
IpcBuffer0 = 0xA,
Mapped = 0xB,
ThreadLocal = 0xC,
TransferMemoryIsolated = 0xD,
TransferMemory = 0xE,
ProcessMemory = 0xF,
IpcBuffer1 = 0x11,
IpcBuffer3 = 0x12,
KernelStack = 0x13,
};
/**
@@ -216,10 +113,16 @@ struct VirtualMemoryArea {
* - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/
*/
class VMManager final {
using VMAMap = std::map<VAddr, VirtualMemoryArea>;
public:
using VMAHandle = VMAMap::const_iterator;
/**
* A map covering the entirety of the managed address space, keyed by the `base` field of each
* VMA. It must always be modified by splitting or merging VMAs, so that the invariant
* `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be
* merged when possible so that no two similar and adjacent regions exist that have not been
* merged.
*/
std::map<VAddr, VirtualMemoryArea> vma_map;
using VMAHandle = decltype(vma_map)::const_iterator;
VMManager();
~VMManager();
@@ -230,9 +133,6 @@ public:
/// Finds the VMA in which the given address is included in, or `vma_map.end()`.
VMAHandle FindVMA(VAddr target) const;
/// Indicates whether or not the given handle is within the VMA map.
bool IsValidHandle(VMAHandle handle) const;
// TODO(yuriks): Should these functions actually return the handle?
/**
@@ -289,15 +189,8 @@ public:
ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
ResultCode HeapFree(VAddr target, u64 size);
ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state);
/// Queries the memory manager for information about the given address.
///
/// @param address The address to query the memory manager about for information.
///
/// @return A MemoryInfo instance containing information about the given address.
///
MemoryInfo QueryMemory(VAddr address) const;
ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size,
MemoryState state = MemoryState::Mapped);
/**
* Scans all VMAs and updates the page table range of any that use the given vector as backing
@@ -388,7 +281,7 @@ public:
Memory::PageTable page_table;
private:
using VMAIter = VMAMap::iterator;
using VMAIter = decltype(vma_map)::iterator;
/// Converts a VMAHandle to a mutable VMAIter.
VMAIter StripIterConstness(const VMAHandle& iter);
@@ -435,15 +328,6 @@ private:
/// Clears out the page table
void ClearPageTable();
/**
* A map covering the entirety of the managed address space, keyed by the `base` field of each
* VMA. It must always be modified by splitting or merging VMAs, so that the invariant
* `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be
* merged when possible so that no two similar and adjacent regions exist that have not been
* merged.
*/
VMAMap vma_map;
u32 address_space_width = 0;
VAddr address_space_base = 0;
VAddr address_space_end = 0;

View File

@@ -20,7 +20,6 @@
#include "core/hle/service/aoc/aoc_u.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/settings.h"
namespace Service::AOC {
@@ -77,13 +76,6 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) {
rb.Push<u32>(0);
return;
}
rb.Push<u32>(static_cast<u32>(
std::count_if(add_on_content.begin(), add_on_content.end(),
[current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })));
@@ -104,10 +96,6 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF));
}
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end())
out = {};
if (out.size() < offset) {
IPC::ResponseBuilder rb{ctx, 2};
// TODO(DarkLordZach): Find the correct error code.

View File

@@ -45,12 +45,8 @@ public:
explicit IStorage(FileSys::VirtualFile backend_)
: ServiceFramework("IStorage"), backend(std::move(backend_)) {
static const FunctionInfo functions[] = {
{0, &IStorage::Read, "Read"},
{1, nullptr, "Write"},
{2, nullptr, "Flush"},
{3, nullptr, "SetSize"},
{4, &IStorage::GetSize, "GetSize"},
{5, nullptr, "OperateRange"},
{0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, {2, nullptr, "Flush"},
{3, nullptr, "SetSize"}, {4, nullptr, "GetSize"}, {5, nullptr, "OperateRange"},
};
RegisterHandlers(functions);
}
@@ -87,15 +83,6 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void GetSize(Kernel::HLERequestContext& ctx) {
const u64 size = backend->GetSize();
LOG_DEBUG(Service_FS, "called, size={}", size);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.Push<u64>(size);
}
};
class IFile final : public ServiceFramework<IFile> {
@@ -809,18 +796,9 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext&
void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_FS, "(STUBBED) called");
enum class LogMode : u32 {
Off,
Log,
RedirectToSdCard,
LogToSdCard = Log | RedirectToSdCard,
};
// Given we always want to receive logging information,
// we always specify logging as enabled.
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.PushEnum(LogMode::Log);
rb.Push<u32>(5);
}
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {

View File

@@ -408,13 +408,13 @@ private:
using SHA256Hash = std::array<u8, 0x20>;
struct NROHeader {
INSERT_PADDING_WORDS(1);
u32_le entrypoint_insn;
u32_le mod_offset;
INSERT_PADDING_WORDS(2);
u32_le magic;
u32_le version;
INSERT_PADDING_WORDS(1);
u32_le nro_size;
u32_le flags;
INSERT_PADDING_WORDS(1);
u32_le text_offset;
u32_le text_size;
u32_le ro_offset;
@@ -430,10 +430,9 @@ private:
struct NRRHeader {
u32_le magic;
INSERT_PADDING_BYTES(12);
INSERT_PADDING_BYTES(0x1C);
u64_le title_id_mask;
u64_le title_id_pattern;
INSERT_PADDING_BYTES(16);
std::array<u8, 0x100> modulus;
std::array<u8, 0x100> signature_1;
std::array<u8, 0x100> signature_2;

View File

@@ -137,10 +137,6 @@ u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<
}
static void PushGPUEntries(Tegra::CommandList&& entries) {
if (entries.empty()) {
return;
}
auto& dma_pusher{Core::System::GetInstance().GPU().DmaPusher()};
dma_pusher.Push(std::move(entries));
dma_pusher.DispatchCalls();

View File

@@ -70,6 +70,10 @@
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/wlan/wlan.h"
using Kernel::ClientPort;
using Kernel::ServerPort;
using Kernel::SharedPtr;
namespace Service {
/**
@@ -97,33 +101,33 @@ ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_ses
ServiceFrameworkBase::~ServiceFrameworkBase() = default;
void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) {
ASSERT(!port_installed);
auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap();
ASSERT(port == nullptr);
port = service_manager.RegisterService(service_name, max_sessions).Unwrap();
port->SetHleHandler(shared_from_this());
port_installed = true;
}
void ServiceFrameworkBase::InstallAsNamedPort() {
ASSERT(!port_installed);
ASSERT(port == nullptr);
auto& kernel = Core::System::GetInstance().Kernel();
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
SharedPtr<ServerPort> server_port;
SharedPtr<ClientPort> client_port;
std::tie(server_port, client_port) =
ServerPort::CreatePortPair(kernel, max_sessions, service_name);
server_port->SetHleHandler(shared_from_this());
kernel.AddNamedPort(service_name, std::move(client_port));
port_installed = true;
}
Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() {
ASSERT(!port_installed);
ASSERT(port == nullptr);
auto& kernel = Core::System::GetInstance().Kernel();
auto [server_port, client_port] =
Kernel::SharedPtr<Kernel::ServerPort> server_port;
Kernel::SharedPtr<Kernel::ClientPort> client_port;
std::tie(server_port, client_port) =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
auto port = MakeResult(std::move(server_port)).Unwrap();
port = MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port)).Unwrap();
port->SetHleHandler(shared_from_this());
port_installed = true;
return client_port;
}
@@ -148,7 +152,8 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext
}
buf.push_back('}');
UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf));
LOG_ERROR(Service, "unknown / unimplemented {}", fmt::to_string(buf));
UNIMPLEMENTED();
}
void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) {

View File

@@ -96,9 +96,11 @@ private:
/// Maximum number of concurrent sessions that this service can handle.
u32 max_sessions;
/// Flag to store if a port was already create/installed to detect multiple install attempts,
/// which is not supported.
bool port_installed = false;
/**
* Port where incoming connections will be received. Only created when InstallAsService() or
* InstallAsNamedPort() are called.
*/
Kernel::SharedPtr<Kernel::ServerPort> port;
/// Function used to safely up-cast pointers to the derived class before invoking a handler.
InvokerFn* handler_invoker;

View File

@@ -54,11 +54,13 @@ ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService
return ERR_ALREADY_REGISTERED;
auto& kernel = Core::System::GetInstance().Kernel();
auto [server_port, client_port] =
Kernel::SharedPtr<Kernel::ServerPort> server_port;
Kernel::SharedPtr<Kernel::ClientPort> client_port;
std::tie(server_port, client_port) =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name);
registered_services.emplace(std::move(name), std::move(client_port));
return MakeResult(std::move(server_port));
return MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port));
}
ResultCode ServiceManager::UnregisterService(const std::string& name) {
@@ -81,7 +83,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> ServiceManager::GetServicePort(
return ERR_SERVICE_NOT_REGISTERED;
}
return MakeResult(it->second);
return MakeResult<Kernel::SharedPtr<Kernel::ClientPort>>(it->second);
}
ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ServiceManager::ConnectToService(

View File

@@ -33,7 +33,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() const override {
FileType GetFileType() override {
return IdentifyType(file);
}

View File

@@ -22,7 +22,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() const override {
FileType GetFileType() override {
return IdentifyType(file);
}

View File

@@ -12,7 +12,6 @@
#include <vector>
#include "common/common_types.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/vfs.h"
namespace Kernel {
@@ -132,7 +131,7 @@ public:
* Returns the type of this file
* @return FileType corresponding to the loaded file
*/
virtual FileType GetFileType() const = 0;
virtual FileType GetFileType() = 0;
/**
* Load the application and return the created Process instance
@@ -244,15 +243,6 @@ public:
return ResultStatus::ErrorNotImplemented;
}
/**
* Get the developer of the application
* @param developer Reference to store the application developer into
* @return ResultStatus result of function
*/
virtual ResultStatus ReadDeveloper(std::string& developer) {
return ResultStatus::ErrorNotImplemented;
}
protected:
FileSys::VirtualFile file;
bool is_loaded = false;

View File

@@ -37,7 +37,7 @@ FileType AppLoader_NAX::IdentifyType(const FileSys::VirtualFile& file) {
return IdentifyTypeImpl(nax);
}
FileType AppLoader_NAX::GetFileType() const {
FileType AppLoader_NAX::GetFileType() {
return IdentifyTypeImpl(*nax);
}

View File

@@ -31,7 +31,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() const override;
FileType GetFileType() override;
ResultStatus Load(Kernel::Process& process) override;

View File

@@ -29,7 +29,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() const override {
FileType GetFileType() override {
return IdentifyType(file);
}

View File

@@ -33,7 +33,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() const override {
FileType GetFileType() override {
return IdentifyType(file);
}

View File

@@ -37,7 +37,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() const override {
FileType GetFileType() override {
return IdentifyType(file);
}

View File

@@ -151,11 +151,4 @@ ResultStatus AppLoader_NSP::ReadTitle(std::string& title) {
title = nacp_file->GetApplicationName();
return ResultStatus::Success;
}
ResultStatus AppLoader_NSP::ReadDeveloper(std::string& developer) {
if (nacp_file == nullptr)
return ResultStatus::ErrorNoControl;
developer = nacp_file->GetDeveloperName();
return ResultStatus::Success;
}
} // namespace Loader

View File

@@ -31,7 +31,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() const override {
FileType GetFileType() override {
return IdentifyType(file);
}
@@ -43,7 +43,6 @@ public:
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
ResultStatus ReadDeveloper(std::string& developer) override;
private:
std::unique_ptr<FileSys::NSP> nsp;

View File

@@ -120,11 +120,4 @@ ResultStatus AppLoader_XCI::ReadTitle(std::string& title) {
title = nacp_file->GetApplicationName();
return ResultStatus::Success;
}
ResultStatus AppLoader_XCI::ReadDeveloper(std::string& developer) {
if (nacp_file == nullptr)
return ResultStatus::ErrorNoControl;
developer = nacp_file->GetDeveloperName();
return ResultStatus::Success;
}
} // namespace Loader

View File

@@ -31,7 +31,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
FileType GetFileType() const override {
FileType GetFileType() override {
return IdentifyType(file);
}
@@ -43,7 +43,6 @@ public:
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
ResultStatus ReadDeveloper(std::string& developer) override;
private:
std::unique_ptr<FileSys::XCI> xci;

View File

@@ -125,13 +125,14 @@ void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPoin
* using a VMA from the current process
*/
static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) {
const auto& vm_manager = process.VMManager();
const auto it = vm_manager.FindVMA(vaddr);
DEBUG_ASSERT(vm_manager.IsValidHandle(it));
u8* direct_pointer = nullptr;
const auto& vma = it->second;
auto& vm_manager = process.VMManager();
auto it = vm_manager.FindVMA(vaddr);
ASSERT(it != vm_manager.vma_map.end());
auto& vma = it->second;
switch (vma.type) {
case Kernel::VMAType::AllocatedMemoryBlock:
direct_pointer = vma.backing_block->data() + vma.offset;

View File

@@ -6,10 +6,8 @@
#include <array>
#include <atomic>
#include <map>
#include <optional>
#include <string>
#include <vector>
#include "common/common_types.h"
namespace Settings {
@@ -413,9 +411,6 @@ struct Values {
std::string web_api_url;
std::string yuzu_username;
std::string yuzu_token;
// Add-Ons
std::map<u64, std::vector<std::string>> disabled_addons;
} extern values;
void Apply();

View File

@@ -103,8 +103,13 @@ bool VerifyLogin(const std::string& username, const std::string& token) {
TelemetrySession::TelemetrySession() {
#ifdef ENABLE_WEB_SERVICE
backend = std::make_unique<WebService::TelemetryJson>(
Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);
if (Settings::values.enable_telemetry) {
backend = std::make_unique<WebService::TelemetryJson>(Settings::values.web_api_url,
Settings::values.yuzu_username,
Settings::values.yuzu_token);
} else {
backend = std::make_unique<Telemetry::NullVisitor>();
}
#else
backend = std::make_unique<Telemetry::NullVisitor>();
#endif
@@ -175,8 +180,7 @@ TelemetrySession::~TelemetrySession() {
// This is just a placeholder to wrap up the session once the core completes and this is
// destroyed. This will be moved elsewhere once we are actually doing real I/O with the service.
field_collection.Accept(*backend);
if (Settings::values.enable_telemetry)
backend->Complete();
backend->Complete();
backend = nullptr;
}

View File

@@ -575,7 +575,7 @@ union Instruction {
union {
BitField<39, 2, u64> tab5cb8_2;
BitField<41, 3, u64> postfactor;
BitField<41, 3, u64> tab5c68_1;
BitField<44, 2, u64> tab5c68_0;
BitField<48, 1, u64> negate_b;
} fmul;
@@ -609,7 +609,7 @@ union Instruction {
BitField<31, 1, u64> negate_b;
BitField<30, 1, u64> abs_b;
BitField<28, 2, HalfType> type_b;
BitField<47, 2, HalfType> type_b;
BitField<35, 2, HalfType> type_c;
} alu_half;

View File

@@ -27,4 +27,16 @@ void RendererBase::UpdateCurrentFramebufferLayout() {
render_window.UpdateCurrentFramebufferLayout(layout.width, layout.height);
}
void RendererBase::RequestScreenshot(void* data, std::function<void()> callback,
const Layout::FramebufferLayout& layout) {
if (renderer_settings.screenshot_requested) {
LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request");
return;
}
renderer_settings.screenshot_bits = data;
renderer_settings.screenshot_complete_callback = std::move(callback);
renderer_settings.screenshot_framebuffer_layout = layout;
renderer_settings.screenshot_requested = true;
}
} // namespace VideoCore

View File

@@ -9,6 +9,7 @@
#include <optional>
#include "common/common_types.h"
#include "core/frontend/emu_window.h"
#include "video_core/gpu.h"
#include "video_core/rasterizer_interface.h"
@@ -21,6 +22,12 @@ namespace VideoCore {
struct RendererSettings {
std::atomic_bool use_framelimiter{false};
std::atomic_bool set_background_color{false};
// Screenshot
std::atomic<bool> screenshot_requested{false};
void* screenshot_bits;
std::function<void()> screenshot_complete_callback;
Layout::FramebufferLayout screenshot_framebuffer_layout;
};
class RendererBase : NonCopyable {
@@ -57,9 +64,29 @@ public:
return *rasterizer;
}
Core::Frontend::EmuWindow& GetRenderWindow() {
return render_window;
}
const Core::Frontend::EmuWindow& GetRenderWindow() const {
return render_window;
}
RendererSettings& Settings() {
return renderer_settings;
}
const RendererSettings& Settings() const {
return renderer_settings;
}
/// Refreshes the settings common to all renderers
void RefreshBaseSettings();
/// Request a screenshot of the next frame
void RequestScreenshot(void* data, std::function<void()> callback,
const Layout::FramebufferLayout& layout);
protected:
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
std::unique_ptr<RasterizerInterface> rasterizer;

View File

@@ -2,9 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/functional/hash.hpp>
#include "common/assert.h"
#include "common/hash.h"
#include "core/core.h"
#include "core/memory.h"
#include "video_core/engines/maxwell_3d.h"
@@ -68,17 +66,14 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
// stage here.
setup.SetProgramB(GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));
case Maxwell::ShaderProgram::VertexB:
CalculateProperties();
program_result = GLShader::GenerateVertexShader(setup);
gl_type = GL_VERTEX_SHADER;
break;
case Maxwell::ShaderProgram::Geometry:
CalculateProperties();
program_result = GLShader::GenerateGeometryShader(setup);
gl_type = GL_GEOMETRY_SHADER;
break;
case Maxwell::ShaderProgram::Fragment:
CalculateProperties();
program_result = GLShader::GenerateFragmentShader(setup);
gl_type = GL_FRAGMENT_SHADER;
break;
@@ -145,46 +140,6 @@ GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program,
return target_program.handle;
};
static bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
// sched instructions appear once every 4 instructions.
static constexpr std::size_t SchedPeriod = 4;
const std::size_t absolute_offset = offset - main_offset;
return (absolute_offset % SchedPeriod) == 0;
}
static std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) {
constexpr std::size_t start_offset = 10;
std::size_t offset = start_offset;
std::size_t size = start_offset * sizeof(u64);
while (offset < program.size()) {
const u64 inst = program[offset];
if (!IsSchedInstruction(offset, start_offset)) {
if (inst == 0 || (inst >> 52) == 0x50b) {
break;
}
}
size += sizeof(inst);
offset++;
}
return size;
}
void CachedShader::CalculateProperties() {
setup.program.real_size = CalculateProgramSize(setup.program.code);
setup.program.real_size_b = 0;
setup.program.unique_identifier = Common::CityHash64(
reinterpret_cast<const char*>(setup.program.code.data()), setup.program.real_size);
if (program_type == Maxwell::ShaderProgram::VertexA) {
std::size_t seed = 0;
boost::hash_combine(seed, setup.program.unique_identifier);
setup.program.real_size_b = CalculateProgramSize(setup.program.code_b);
const u64 identifier_b = Common::CityHash64(
reinterpret_cast<const char*>(setup.program.code_b.data()), setup.program.real_size_b);
boost::hash_combine(seed, identifier_b);
setup.program.unique_identifier = static_cast<u64>(seed);
}
}
ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {}
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {

View File

@@ -81,8 +81,6 @@ private:
GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology,
u32 max_vertices, const std::string& debug_name);
void CalculateProperties();
VAddr addr;
std::size_t shader_length;
Maxwell::ShaderProgram program_type;

View File

@@ -928,7 +928,7 @@ private:
case Attribute::Index::FrontFacing:
// TODO(Subv): Find out what the values are for the other elements.
ASSERT(stage == Maxwell3D::Regs::ShaderStage::Fragment);
return "vec4(0, 0, 0, intBitsToFloat(gl_FrontFacing ? -1 : 0))";
return "vec4(0, 0, 0, uintBitsToFloat(gl_FrontFacing ? 1 : 0))";
default:
const u32 index{static_cast<u32>(attribute) -
static_cast<u32>(Attribute::Index::Attribute_0)};
@@ -1681,7 +1681,7 @@ private:
for (size_t i = 0; i < coord_count; ++i) {
const bool last = (i == (coord_count - 1)) && (coord_count > 1);
coord += regs.GetRegisterAsFloat(last ? last_coord_register : coord_register + i);
if (i < coord_count - 1) {
if (!last) {
coord += ',';
}
}
@@ -1702,99 +1702,6 @@ private:
is_array, (coord_count > 2 ? 1 : 0)));
}
std::pair<std::string, std::string> GetTLD4Code(const Instruction& instr,
const Tegra::Shader::TextureType texture_type,
const bool depth_compare, const bool is_array) {
const size_t coord_count = TextureCoordinates(texture_type);
const size_t total_coord_count = coord_count + (is_array ? 1 : 0);
const size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0);
constexpr std::array<const char*, 5> coord_container{
{"", "", "vec2 coord = vec2(", "vec3 coord = vec3(", "vec4 coord = vec4("}};
// If enabled arrays index is always stored in the gpr8 field
const u64 array_register = instr.gpr8.Value();
// First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
const u64 coord_register = array_register + (is_array ? 1 : 0);
std::string coord = coord_container[total_coord_count];
for (size_t i = 0; i < coord_count;) {
coord += regs.GetRegisterAsFloat(coord_register + i);
++i;
if (i != coord_count) {
coord += ',';
}
}
if (is_array) {
coord += ',' + regs.GetRegisterAsInteger(array_register);
}
coord += ");";
const std::string sampler =
GetSampler(instr.sampler, texture_type, is_array, depth_compare);
std::string texture = "textureGather(" + sampler + ", coord, ";
if (depth_compare) {
// Depth is always stored in the register signaled by gpr20
texture += regs.GetRegisterAsFloat(instr.gpr20.Value()) + ')';
} else {
texture += std::to_string(instr.tld4.component) + ')';
}
return std::make_pair(coord, texture);
}
std::pair<std::string, std::string> GetTLDSCode(const Instruction& instr,
const Tegra::Shader::TextureType texture_type,
const bool is_array) {
const size_t coord_count = TextureCoordinates(texture_type);
const size_t total_coord_count = coord_count + (is_array ? 1 : 0);
const bool lod_enabled =
instr.tlds.GetTextureProcessMode() == Tegra::Shader::TextureProcessMode::LL;
constexpr std::array<const char*, 4> coord_container{
{"", "int coord = (", "ivec2 coord = ivec2(", "ivec3 coord = ivec3("}};
std::string coord = coord_container[total_coord_count];
// If enabled arrays index is always stored in the gpr8 field
const u64 array_register = instr.gpr8.Value();
// if is array gpr20 is used
const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value();
const u64 last_coord_register =
((coord_count > 2) || (coord_count == 2 && !lod_enabled)) && !is_array
? static_cast<u64>(instr.gpr20.Value())
: coord_register + 1;
for (size_t i = 0; i < coord_count; ++i) {
const bool last = (i == (coord_count - 1)) && (coord_count > 1);
coord += regs.GetRegisterAsInteger(last ? last_coord_register : coord_register + i);
if (i < coord_count - 1) {
coord += ',';
}
}
if (is_array) {
coord += ',' + regs.GetRegisterAsInteger(array_register);
}
coord += ");";
const std::string sampler = GetSampler(instr.sampler, texture_type, is_array, false);
std::string texture = "texelFetch(" + sampler + ", coords";
if (lod_enabled) {
// When lod is used always is in grp20
texture += ", " + regs.GetRegisterAsInteger(instr.gpr20) + ')';
} else {
texture += ", 0)";
}
return std::make_pair(coord, texture);
}
/**
* Compiles a single instruction from Tegra to GLSL.
* @param offset the offset of the Tegra shader instruction.
@@ -1867,6 +1774,9 @@ private:
UNIMPLEMENTED_IF_MSG(instr.fmul.tab5cb8_2 != 0,
"FMUL tab5cb8_2({}) is not implemented",
instr.fmul.tab5cb8_2.Value());
UNIMPLEMENTED_IF_MSG(instr.fmul.tab5c68_1 != 0,
"FMUL tab5cb8_1({}) is not implemented",
instr.fmul.tab5c68_1.Value());
UNIMPLEMENTED_IF_MSG(
instr.fmul.tab5c68_0 != 1, "FMUL tab5cb8_0({}) is not implemented",
instr.fmul.tab5c68_0
@@ -1876,26 +1786,7 @@ private:
op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b);
std::string postfactor_op;
if (instr.fmul.postfactor != 0) {
s8 postfactor = static_cast<s8>(instr.fmul.postfactor);
// postfactor encoded as 3-bit 1's complement in instruction,
// interpreted with below logic.
if (postfactor >= 4) {
postfactor = 7 - postfactor;
} else {
postfactor = 0 - postfactor;
}
if (postfactor > 0) {
postfactor_op = " * " + std::to_string(1 << postfactor);
} else {
postfactor_op = " / " + std::to_string(1 << -postfactor);
}
}
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b + postfactor_op, 1, 1,
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1,
instr.alu.saturate_d, 0, true);
break;
}
@@ -2934,6 +2825,9 @@ private:
const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()};
const bool is_array{instr.tlds.IsArrayTexture()};
ASSERT(texture_type == Tegra::Shader::TextureType::Texture2D);
ASSERT(is_array == false);
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
@@ -2941,16 +2835,54 @@ private:
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ),
"MZ is not implemented");
const auto [coord, texture] = GetTLDSCode(instr, texture_type, is_array);
u32 extra_op_offset = 0;
const auto scope = shader.Scope();
ShaderScopedScope scope = shader.Scope();
shader.AddLine(coord);
shader.AddLine("vec4 texture_tmp = " + texture + ';');
WriteTexsInstructionFloat(instr, "texture_tmp");
switch (texture_type) {
case Tegra::Shader::TextureType::Texture1D: {
const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
shader.AddLine("float coords = " + x + ';');
break;
}
case Tegra::Shader::TextureType::Texture2D: {
UNIMPLEMENTED_IF_MSG(is_array, "Unhandled 2d array texture");
const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
const std::string y = regs.GetRegisterAsInteger(instr.gpr20);
// shader.AddLine("ivec2 coords = ivec2(" + x + ", " + y + ");");
shader.AddLine("ivec2 coords = ivec2(" + x + ", " + y + ");");
extra_op_offset = 1;
break;
}
default:
UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type));
}
const std::string sampler =
GetSampler(instr.sampler, texture_type, is_array, false);
const std::string texture = [&]() {
switch (instr.tlds.GetTextureProcessMode()) {
case Tegra::Shader::TextureProcessMode::LZ:
return "texelFetch(" + sampler + ", coords, 0)";
case Tegra::Shader::TextureProcessMode::LL:
shader.AddLine(
"float lod = " +
regs.GetRegisterAsInteger(instr.gpr20.Value() + extra_op_offset) + ';');
return "texelFetch(" + sampler + ", coords, lod)";
default:
UNIMPLEMENTED_MSG("Unhandled texture process mode {}",
static_cast<u32>(instr.tlds.GetTextureProcessMode()));
return "texelFetch(" + sampler + ", coords, 0)";
}
}();
WriteTexsInstructionFloat(instr, texture);
break;
}
case OpCode::Id::TLD4: {
ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D);
ASSERT(instr.tld4.array == 0);
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
@@ -2960,29 +2892,56 @@ private:
"NDV is not implemented");
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP),
"PTP is not implemented");
auto texture_type = instr.tld4.texture_type.Value();
const bool depth_compare =
instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
const bool is_array = instr.tld4.array != 0;
const auto [coord, texture] =
GetTLD4Code(instr, texture_type, depth_compare, is_array);
auto texture_type = instr.tld4.texture_type.Value();
u32 num_coordinates = TextureCoordinates(texture_type);
if (depth_compare)
num_coordinates += 1;
const auto scope = shader.Scope();
shader.AddLine(coord);
std::size_t dest_elem{};
switch (num_coordinates) {
case 2: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
break;
}
case 3: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z + ");");
break;
}
default:
UNIMPLEMENTED_MSG("Unhandled coordinates number {}",
static_cast<u32>(num_coordinates));
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
texture_type = Tegra::Shader::TextureType::Texture2D;
}
shader.AddLine("vec4 texture_tmp = " + texture + ';');
for (std::size_t elem = 0; elem < 4; ++elem) {
if (!instr.tex.IsComponentEnabled(elem)) {
// Skip disabled components
continue;
const std::string sampler =
GetSampler(instr.sampler, texture_type, false, depth_compare);
const std::string texture = "textureGather(" + sampler + ", coords, " +
std::to_string(instr.tld4.component) + ')';
if (depth_compare) {
regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false);
} else {
std::size_t dest_elem{};
for (std::size_t elem = 0; elem < 4; ++elem) {
if (!instr.tex.IsComponentEnabled(elem)) {
// Skip disabled components
continue;
}
regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
++dest_elem;
}
regs.SetRegisterToFloat(instr.gpr0, elem, "texture_tmp", 1, 4, false,
dest_elem);
++dest_elem;
}
break;
}
@@ -2996,31 +2955,28 @@ private:
const auto scope = shader.Scope();
std::string coords;
const bool depth_compare =
instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
// TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
const std::string sampler = GetSampler(
instr.sampler, Tegra::Shader::TextureType::Texture2D, false, depth_compare);
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
coords = "vec2 coords = vec2(" + op_a + ", ";
std::string texture = "textureGather(" + sampler + ", coords, ";
if (!depth_compare) {
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
coords += op_b + ");";
texture += std::to_string(instr.tld4s.component) + ')';
if (depth_compare) {
// Note: TLD4S coordinate encoding works just like TEXS's
const std::string op_y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
shader.AddLine("vec3 coords = vec3(" + op_a + ", " + op_y + ", " + op_b + ");");
} else {
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20);
coords += op_b + ");";
texture += op_c + ')';
shader.AddLine("vec2 coords = vec2(" + op_a + ", " + op_b + ");");
}
shader.AddLine(coords);
shader.AddLine("vec4 texture_tmp = " + texture + ';');
WriteTexsInstructionFloat(instr, "texture_tmp");
std::string texture = "textureGather(" + sampler + ", coords, " +
std::to_string(instr.tld4s.component) + ')';
if (depth_compare) {
texture = "vec4(" + texture + ')';
}
WriteTexsInstructionFloat(instr, texture);
break;
}
case OpCode::Id::TXQ: {

View File

@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/format.h>
#include "common/assert.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
@@ -17,8 +16,6 @@ static constexpr u32 PROGRAM_OFFSET{10};
ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
std::string out = "#version 430 core\n";
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
out += "// Shader Unique Id: VS" + id + "\n\n";
out += Decompiler::GetCommonDeclarations();
out += R"(
@@ -87,8 +84,6 @@ void main() {
ProgramResult GenerateGeometryShader(const ShaderSetup& setup) {
// Version is intentionally skipped in shader generation, it's added by the lazy compilation.
std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n";
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
out += "// Shader Unique Id: GS" + id + "\n\n";
out += Decompiler::GetCommonDeclarations();
out += "bool exec_geometry();\n";
@@ -122,8 +117,6 @@ void main() {
ProgramResult GenerateFragmentShader(const ShaderSetup& setup) {
std::string out = "#version 430 core\n";
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
out += "// Shader Unique Id: FS" + id + "\n\n";
out += Decompiler::GetCommonDeclarations();
out += "bool exec_fragment();\n";

View File

@@ -177,9 +177,6 @@ struct ShaderSetup {
struct {
ProgramCode code;
ProgramCode code_b; // Used for dual vertex shaders
u64 unique_identifier;
std::size_t real_size;
std::size_t real_size_b;
} program;
/// Used in scenarios where we have a dual vertex shaders

View File

@@ -138,7 +138,12 @@ void RendererOpenGL::SwapBuffers(
// Load the framebuffer from memory, draw it to the screen, and swap buffers
LoadFBToScreenInfo(*framebuffer);
DrawScreen();
if (renderer_settings.screenshot_requested)
CaptureScreenshot();
DrawScreen(render_window.GetFramebufferLayout());
render_window.SwapBuffers();
}
@@ -383,14 +388,13 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
/**
* Draws the emulated screens to the emulator window.
*/
void RendererOpenGL::DrawScreen() {
void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
if (renderer_settings.set_background_color) {
// Update background color before drawing
glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
0.0f);
}
const auto& layout = render_window.GetFramebufferLayout();
const auto& screen = layout.screen;
glViewport(0, 0, layout.width, layout.height);
@@ -414,6 +418,37 @@ void RendererOpenGL::DrawScreen() {
/// Updates the framerate
void RendererOpenGL::UpdateFramerate() {}
void RendererOpenGL::CaptureScreenshot() {
// Draw the current frame to the screenshot framebuffer
screenshot_framebuffer.Create();
GLuint old_read_fb = state.draw.read_framebuffer;
GLuint old_draw_fb = state.draw.draw_framebuffer;
state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle;
state.Apply();
Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
GLuint renderbuffer;
glGenRenderbuffers(1, &renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB8, layout.width, layout.height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
DrawScreen(layout);
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
renderer_settings.screenshot_bits);
screenshot_framebuffer.Release();
state.draw.read_framebuffer = old_read_fb;
state.draw.draw_framebuffer = old_draw_fb;
state.Apply();
glDeleteRenderbuffers(1, &renderbuffer);
renderer_settings.screenshot_complete_callback();
renderer_settings.screenshot_requested = false;
}
static const char* GetSource(GLenum source) {
#define RET(s) \
case GL_DEBUG_SOURCE_##s: \

View File

@@ -16,6 +16,10 @@ namespace Core::Frontend {
class EmuWindow;
}
namespace Layout {
class FramebufferLayout;
}
namespace OpenGL {
/// Structure used for storing information about the textures for the Switch screen
@@ -66,10 +70,12 @@ private:
void ConfigureFramebufferTexture(TextureInfo& texture,
const Tegra::FramebufferConfig& framebuffer);
void DrawScreen();
void DrawScreen(const Layout::FramebufferLayout& layout);
void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h);
void UpdateFramerate();
void CaptureScreenshot();
// Loads framebuffer from emulated memory into the display information structure
void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer);
// Fills active OpenGL texture with the given RGBA color.
@@ -82,6 +88,7 @@ private:
OGLVertexArray vertex_array;
OGLBuffer vertex_buffer;
OGLProgram shader;
OGLFramebuffer screenshot_framebuffer;
/// Display information for Switch screen
ScreenInfo screen_info;

View File

@@ -3,6 +3,8 @@
// Refer to the license.txt file included.
#include <memory>
#include "core/core.h"
#include "core/settings.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/video_core.h"
@@ -13,4 +15,10 @@ std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_wind
return std::make_unique<OpenGL::RendererOpenGL>(emu_window);
}
u16 GetResolutionScaleFactor(const RendererBase& renderer) {
return !Settings::values.resolution_factor
? renderer.GetRenderWindow().GetFramebufferLayout().GetScalingRatio()
: Settings::values.resolution_factor;
}
} // namespace VideoCore

View File

@@ -22,4 +22,6 @@ class RendererBase;
*/
std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window);
u16 GetResolutionScaleFactor(const RendererBase& renderer);
} // namespace VideoCore

View File

@@ -35,8 +35,6 @@ add_executable(yuzu
configuration/configure_mouse_advanced.h
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_per_general.cpp
configuration/configure_per_general.h
configuration/configure_touchscreen_advanced.cpp
configuration/configure_touchscreen_advanced.h
configuration/configure_web.cpp
@@ -88,7 +86,6 @@ set(UIS
configuration/configure_input.ui
configuration/configure_input_player.ui
configuration/configure_mouse_advanced.ui
configuration/configure_per_general.ui
configuration/configure_system.ui
configuration/configure_touchscreen_advanced.ui
configuration/configure_web.ui

View File

@@ -14,6 +14,8 @@
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#include "yuzu/bootmanager.h"
EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {}
@@ -333,6 +335,22 @@ void GRenderWindow::InitRenderTarget() {
BackupGeometry();
}
void GRenderWindow::CaptureScreenshot(u16 res_scale, const QString& screenshot_path) {
auto& renderer = Core::System::GetInstance().Renderer();
if (!res_scale)
res_scale = VideoCore::GetResolutionScaleFactor(renderer);
const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
renderer.RequestScreenshot(screenshot_image.bits(),
[=] {
screenshot_image.mirrored(false, true).save(screenshot_path);
LOG_INFO(Frontend, "The screenshot is saved.");
},
layout);
}
void GRenderWindow::OnMinimalClientAreaChangeRequest(
const std::pair<unsigned, unsigned>& minimal_size) {
setMinimumSize(minimal_size.first, minimal_size.second);

View File

@@ -8,6 +8,7 @@
#include <condition_variable>
#include <mutex>
#include <QGLWidget>
#include <QImage>
#include <QThread>
#include "common/thread.h"
#include "core/core.h"
@@ -139,6 +140,8 @@ public:
void InitRenderTarget();
void CaptureScreenshot(u16 res_scale, const QString& screenshot_path);
public slots:
void moveContext(); // overridden
@@ -165,6 +168,9 @@ private:
EmuThread* emu_thread;
/// Temporary storage of the screenshot taken
QImage screenshot_image;
protected:
void showEvent(QShowEvent* event) override;
};

View File

@@ -441,25 +441,12 @@ void Config::ReadValues() {
Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
qt_config->endGroup();
const auto size = qt_config->beginReadArray("DisabledAddOns");
for (int i = 0; i < size; ++i) {
qt_config->setArrayIndex(i);
const auto title_id = qt_config->value("title_id", 0).toULongLong();
std::vector<std::string> out;
const auto d_size = qt_config->beginReadArray("disabled");
for (int j = 0; j < d_size; ++j) {
qt_config->setArrayIndex(j);
out.push_back(qt_config->value("d", "").toString().toStdString());
}
qt_config->endArray();
Settings::values.disabled_addons.insert_or_assign(title_id, out);
}
qt_config->endArray();
qt_config->beginGroup("UI");
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
UISettings::values.enable_discord_presence =
qt_config->value("enable_discord_presence", true).toBool();
UISettings::values.screenshot_resolution_factor =
static_cast<u16>(qt_config->value("screenshot_resolution_factor", 0).toUInt());
qt_config->beginGroup("UIGameList");
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
@@ -660,24 +647,11 @@ void Config::SaveValues() {
qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
qt_config->endGroup();
qt_config->beginWriteArray("DisabledAddOns");
int i = 0;
for (const auto& elem : Settings::values.disabled_addons) {
qt_config->setArrayIndex(i);
qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first));
qt_config->beginWriteArray("disabled");
for (std::size_t j = 0; j < elem.second.size(); ++j) {
qt_config->setArrayIndex(j);
qt_config->setValue("d", QString::fromStdString(elem.second[j]));
}
qt_config->endArray();
++i;
}
qt_config->endArray();
qt_config->beginGroup("UI");
qt_config->setValue("theme", UISettings::values.theme);
qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
qt_config->setValue("screenshot_resolution_factor",
UISettings::values.screenshot_resolution_factor);
qt_config->beginGroup("UIGameList");
qt_config->setValue("show_unknown", UISettings::values.show_unknown);
@@ -699,6 +673,7 @@ void Config::SaveValues() {
qt_config->beginGroup("Paths");
qt_config->setValue("romsPath", UISettings::values.roms_path);
qt_config->setValue("symbolsPath", UISettings::values.symbols_path);
qt_config->setValue("screenshotPath", UISettings::values.screenshot_path);
qt_config->setValue("gameListRootDir", UISettings::values.gamedir);
qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan);
qt_config->setValue("recentFiles", UISettings::values.recent_files);

View File

@@ -17,8 +17,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
ui->output_sink_combo_box->clear();
ui->output_sink_combo_box->addItem("auto");
for (const char* id : AudioCore::GetSinkIDs()) {
ui->output_sink_combo_box->addItem(id);
for (const auto& sink_detail : AudioCore::g_sink_details) {
ui->output_sink_combo_box->addItem(sink_detail.id);
}
connect(ui->volume_slider, &QSlider::valueChanged, this,
@@ -97,7 +97,8 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {
ui->audio_device_combo_box->addItem(AudioCore::auto_device_name);
const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) {
const std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices();
for (const auto& device : device_list) {
ui->audio_device_combo_box->addItem(QString::fromStdString(device));
}
}

View File

@@ -1,170 +0,0 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <utility>
#include <QHeaderView>
#include <QMenu>
#include <QMessageBox>
#include <QStandardItemModel>
#include <QString>
#include <QTimer>
#include <QTreeView>
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/xts_archive.h"
#include "core/loader/loader.h"
#include "ui_configure_per_general.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_per_general.h"
#include "yuzu/ui_settings.h"
#include "yuzu/util/util.h"
ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
setWindowTitle(tr("Properties"));
layout = new QVBoxLayout;
tree_view = new QTreeView;
item_model = new QStandardItemModel(tree_view);
tree_view->setModel(item_model);
tree_view->setAlternatingRowColors(true);
tree_view->setSelectionMode(QHeaderView::SingleSelection);
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setSortingEnabled(true);
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
tree_view->setUniformRowHeights(true);
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
item_model->insertColumns(0, 2);
item_model->setHeaderData(0, Qt::Horizontal, "Patch Name");
item_model->setHeaderData(1, Qt::Horizontal, "Version");
// We must register all custom types with the Qt Automoc system so that we are able to use it
// with signals/slots. In this case, QList falls under the umbrells of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(tree_view);
ui->scrollArea->setLayout(layout);
scene = new QGraphicsScene;
ui->icon_view->setScene(scene);
connect(item_model, &QStandardItemModel::itemChanged,
[] { UISettings::values.is_game_list_reload_pending.exchange(true); });
this->loadConfiguration();
}
ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default;
void ConfigurePerGameGeneral::applyConfiguration() {
std::vector<std::string> disabled_addons;
for (const auto& item : list_items) {
const auto disabled = item.front()->checkState() == Qt::Unchecked;
if (disabled)
disabled_addons.push_back(item.front()->text().toStdString());
}
Settings::values.disabled_addons[title_id] = disabled_addons;
}
void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) {
this->file = std::move(file);
this->loadConfiguration();
}
void ConfigurePerGameGeneral::loadConfiguration() {
if (file == nullptr)
return;
const auto loader = Loader::GetLoader(file);
ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str());
FileSys::PatchManager pm{title_id};
const auto control = pm.GetControlMetadata();
if (control.first != nullptr) {
ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName()));
ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName()));
} else {
std::string title;
if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
ui->display_name->setText(QString::fromStdString(title));
std::string developer;
if (loader->ReadDeveloper(developer) == Loader::ResultStatus::Success)
ui->display_developer->setText(QString::fromStdString(developer));
ui->display_version->setText(QStringLiteral("1.0.0"));
}
if (control.second != nullptr) {
scene->clear();
QPixmap map;
const auto bytes = control.second->ReadAllBytes();
map.loadFromData(bytes.data(), bytes.size());
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} else {
std::vector<u8> bytes;
if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
scene->clear();
QPixmap map;
map.loadFromData(bytes.data(), bytes.size());
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
}
FileSys::VirtualFile update_raw;
loader->ReadUpdateRaw(update_raw);
const auto& disabled = Settings::values.disabled_addons[title_id];
for (const auto& patch : pm.GetPatchVersionNames(update_raw)) {
QStandardItem* first_item = new QStandardItem;
const auto name = QString::fromStdString(patch.first).replace("[D] ", "");
first_item->setText(name);
first_item->setCheckable(true);
const auto patch_disabled =
std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
list_items.push_back(QList<QStandardItem*>{
first_item, new QStandardItem{QString::fromStdString(patch.second)}});
item_model->appendRow(list_items.back());
}
tree_view->setColumnWidth(0, 5 * tree_view->width() / 16);
ui->display_filename->setText(QString::fromStdString(file->GetName()));
ui->display_format->setText(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
const auto valueText = ReadableByteSize(file->GetSize());
ui->display_size->setText(valueText);
}

View File

@@ -1,50 +0,0 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <vector>
#include <QKeyEvent>
#include <QList>
#include <QWidget>
#include "core/file_sys/vfs_types.h"
class QTreeView;
class QGraphicsScene;
class QStandardItem;
class QStandardItemModel;
namespace Ui {
class ConfigurePerGameGeneral;
}
class ConfigurePerGameGeneral : public QDialog {
Q_OBJECT
public:
explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id);
~ConfigurePerGameGeneral() override;
/// Save all button configurations to settings file
void applyConfiguration();
void loadFromFile(FileSys::VirtualFile file);
private:
std::unique_ptr<Ui::ConfigurePerGameGeneral> ui;
FileSys::VirtualFile file;
u64 title_id;
QVBoxLayout* layout;
QTreeView* tree_view;
QStandardItemModel* item_model;
QGraphicsScene* scene;
std::vector<QList<QStandardItem*>> list_items;
void loadConfiguration();
};

View File

@@ -1,276 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigurePerGameGeneral</class>
<widget class="QDialog" name="ConfigurePerGameGeneral">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>520</height>
</rect>
</property>
<property name="windowTitle">
<string>ConfigurePerGameGeneral</string>
</property>
<layout class="QHBoxLayout" name="HorizontalLayout">
<item>
<layout class="QVBoxLayout" name="VerticalLayout">
<item>
<widget class="QGroupBox" name="GeneralGroupBox">
<property name="title">
<string>Info</string>
</property>
<layout class="QHBoxLayout" name="GeneralHorizontalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="6" column="1" colspan="2">
<widget class="QLineEdit" name="display_filename">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="display_name">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Developer</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<widget class="QLineEdit" name="display_size">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Filename</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Version</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Format</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="display_version">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="display_format">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Size</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="display_developer">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Title ID</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="display_title_id">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2" rowspan="5">
<widget class="QGraphicsView" name="icon_view">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="interactive">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="PerformanceGroupBox">
<property name="title">
<string>Add-Ons</string>
</property>
<layout class="QHBoxLayout" name="PerformanceHorizontalLayout">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>169</height>
</rect>
</property>
</widget>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="PerformanceVerticalLayout"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigurePerGameGeneral</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>269</x>
<y>567</y>
</hint>
<hint type="destinationlabel">
<x>269</x>
<y>294</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigurePerGameGeneral</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>269</x>
<y>567</y>
</hint>
<hint type="destinationlabel">
<x>269</x>
<y>294</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -75,7 +75,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()
return item_list;
}
WaitTreeText::WaitTreeText(QString t) : text(std::move(t)) {}
WaitTreeText::WaitTreeText(const QString& t) : text(t) {}
WaitTreeText::~WaitTreeText() = default;
QString WaitTreeText::GetText() const {

View File

@@ -52,7 +52,7 @@ private:
class WaitTreeText : public WaitTreeItem {
Q_OBJECT
public:
explicit WaitTreeText(QString text);
explicit WaitTreeText(const QString& text);
~WaitTreeText() override;
QString GetText() const override;

View File

@@ -333,8 +333,6 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
context_menu.addSeparator();
QAction* properties = context_menu.addAction(tr("Properties"));
open_save_location->setEnabled(program_id != 0);
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -348,7 +346,6 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
connect(navigate_to_gamedb_entry, &QAction::triggered,
[&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); });
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
}

View File

@@ -70,7 +70,6 @@ signals:
void CopyTIDRequested(u64 program_id);
void NavigateToGamedbEntryRequested(u64 program_id,
const CompatibilityList& compatibility_list);
void OpenPerGameGeneralRequested(const std::string& file);
private slots:
void onTextChanged(const QString& newText);

View File

@@ -62,7 +62,7 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
FileSys::VirtualFile update_raw;
loader.ReadUpdateRaw(update_raw);
for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
const bool is_update = kv.first == "Update" || kv.first == "[D] Update";
const bool is_update = kv.first == "Update";
if (!updatable && is_update) {
continue;
}
@@ -99,14 +99,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
compatibility = it->second.first;
}
const auto file_type = loader.GetFileType();
const auto file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type));
QList<QStandardItem*> list{
new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name),
file_type_string, program_id),
new GameListItemPath(
FormatGameName(path), icon, QString::fromStdString(name),
QString::fromStdString(Loader::GetFileTypeString(loader.GetFileType())), program_id),
new GameListItemCompat(compatibility),
new GameListItem(file_type_string),
new GameListItem(QString::fromStdString(Loader::GetFileTypeString(loader.GetFileType()))),
new GameListItemSize(FileUtil::GetSize(path)),
};
@@ -198,16 +196,12 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
const bool is_dir = FileUtil::IsDirectory(physical_name);
if (!is_dir &&
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
if (!loader) {
std::unique_ptr<Loader::AppLoader> loader =
Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown ||
loader->GetFileType() == Loader::FileType::Error) &&
!UISettings::values.show_unknown))
return true;
}
const auto file_type = loader->GetFileType();
if ((file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) &&
!UISettings::values.show_unknown) {
return true;
}
std::vector<u8> icon;
const auto res1 = loader->ReadIcon(icon);

View File

@@ -9,7 +9,6 @@
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/software_keyboard.h"
#include "configuration/configure_per_general.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/hle/service/acc/profile_manager.h"
@@ -335,6 +334,9 @@ void GMainWindow::InitializeHotkeys() {
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2),
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot",
QKeySequence(QKeySequence::Print));
hotkey_registry.LoadHotkeys();
connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
@@ -394,6 +396,12 @@ void GMainWindow::InitializeHotkeys() {
OnLoadAmiibo();
}
});
connect(hotkey_registry.GetHotkey("Main Window", "Capture Screenshot", this),
&QShortcut::activated, this, [&] {
if (emu_thread->IsRunning()) {
OnCaptureScreenshot();
}
});
}
void GMainWindow::SetDefaultUIGeometry() {
@@ -442,8 +450,6 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
&GMainWindow::OnGameListNavigateToGamedbEntry);
connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
&GMainWindow::OnGameListOpenPerGameProperties);
connect(this, &GMainWindow::EmulationStarting, render_window,
&GRenderWindow::OnEmulationStarting);
@@ -491,6 +497,10 @@ void GMainWindow::ConnectMenuEvents() {
hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key());
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
// Movie
connect(ui.action_Capture_Screenshot, &QAction::triggered, this,
&GMainWindow::OnCaptureScreenshot);
// Help
connect(ui.action_Open_yuzu_Folder, &QAction::triggered, this, &GMainWindow::OnOpenYuzuFolder);
connect(ui.action_Rederive, &QAction::triggered, this,
@@ -727,6 +737,7 @@ void GMainWindow::ShutdownGame() {
ui.action_Restart->setEnabled(false);
ui.action_Report_Compatibility->setEnabled(false);
ui.action_Load_Amiibo->setEnabled(false);
ui.action_Capture_Screenshot->setEnabled(false);
render_window->hide();
game_list->show();
game_list->setFilterFocus();
@@ -991,32 +1002,6 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory));
}
void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
u64 title_id{};
const auto v_file = Core::GetGameFileFromPath(vfs, file);
const auto loader = Loader::GetLoader(v_file);
if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
QMessageBox::information(this, tr("Properties"),
tr("The game properties could not be loaded."));
return;
}
ConfigurePerGameGeneral dialog(this, title_id);
dialog.loadFromFile(v_file);
auto result = dialog.exec();
if (result == QDialog::Accepted) {
dialog.applyConfiguration();
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
if (reload) {
game_list->PopulateAsync(UISettings::values.gamedir,
UISettings::values.gamedir_deepscan);
}
config->Save();
}
}
void GMainWindow::OnMenuLoadFile() {
const QString extensions =
QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main");
@@ -1290,6 +1275,7 @@ void GMainWindow::OnStartGame() {
discord_rpc->Update();
ui.action_Load_Amiibo->setEnabled(true);
ui.action_Capture_Screenshot->setEnabled(true);
}
void GMainWindow::OnPauseGame() {
@@ -1298,6 +1284,7 @@ void GMainWindow::OnPauseGame() {
ui.action_Start->setEnabled(true);
ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(true);
ui.action_Capture_Screenshot->setEnabled(false);
}
void GMainWindow::OnStopGame() {
@@ -1460,6 +1447,18 @@ void GMainWindow::OnToggleFilterBar() {
}
}
void GMainWindow::OnCaptureScreenshot() {
OnPauseGame();
const QString path =
QFileDialog::getSaveFileName(this, tr("Capture Screenshot"),
UISettings::values.screenshot_path, tr("PNG Image (*.png)"));
if (!path.isEmpty()) {
UISettings::values.screenshot_path = QFileInfo(path).path();
render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
}
OnStartGame();
}
void GMainWindow::UpdateStatusBar() {
if (emu_thread == nullptr) {
status_bar_update_timer.stop();

View File

@@ -168,7 +168,6 @@ private slots:
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
void OnGameListOpenPerGameProperties(const std::string& file);
void OnMenuLoadFile();
void OnMenuLoadFolder();
void OnMenuInstallToNAND();
@@ -187,6 +186,7 @@ private slots:
void ShowFullscreen();
void HideFullscreen();
void ToggleWindowMode();
void OnCaptureScreenshot();
void OnCoreError(Core::System::ResultStatus, std::string);
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);

View File

@@ -101,11 +101,13 @@
<addaction name="action_Show_Status_Bar"/>
<addaction name="menu_View_Debugging"/>
</widget>
<widget class ="QMenu" name="menu_Tools">
<widget class="QMenu" name="menu_Tools">
<property name="title">
<string>Tools</string>
</property>
<addaction name="action_Rederive" />
<addaction name="action_Rederive"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
</widget>
<widget class="QMenu" name="menu_Help">
<property name="title">
@@ -118,7 +120,7 @@
<addaction name="menu_File"/>
<addaction name="menu_Emulation"/>
<addaction name="menu_View"/>
<addaction name="menu_Tools" />
<addaction name="menu_Tools"/>
<addaction name="menu_Help"/>
</widget>
<action name="action_Install_File_NAND">
@@ -173,11 +175,11 @@
<string>&amp;Stop</string>
</property>
</action>
<action name="action_Rederive">
<property name="text">
<string>Reinitialize keys...</string>
</property>
</action>
<action name="action_Rederive">
<property name="text">
<string>Reinitialize keys...</string>
</property>
</action>
<action name="action_About">
<property name="text">
<string>About yuzu</string>
@@ -252,39 +254,47 @@
<string>Fullscreen</string>
</property>
</action>
<action name="action_Restart">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Restart</string>
</property>
</action>
<action name="action_Load_Amiibo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Load Amiibo...</string>
</property>
<action name="action_Restart">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Restart</string>
</property>
</action>
<action name="action_Report_Compatibility">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Report Compatibility</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
<action name="action_Open_yuzu_Folder">
<property name="text">
<string>Open yuzu Folder</string>
</property>
</action>
</widget>
<action name="action_Load_Amiibo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Load Amiibo...</string>
</property>
</action>
<action name="action_Report_Compatibility">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Report Compatibility</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
<action name="action_Open_yuzu_Folder">
<property name="text">
<string>Open yuzu Folder</string>
</property>
</action>
<action name="action_Capture_Screenshot">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Capture Screenshot</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -10,6 +10,7 @@
#include <QByteArray>
#include <QString>
#include <QStringList>
#include "common/common_types.h"
namespace UISettings {
@@ -42,8 +43,11 @@ struct Values {
// Discord RPC
bool enable_discord_presence;
u16 screenshot_resolution_factor;
QString roms_path;
QString symbols_path;
QString screenshot_path;
QString gamedir;
bool gamedir_deepscan;
QStringList recent_files;

View File

@@ -3,7 +3,6 @@
// Refer to the license.txt file included.
#include <memory>
#include <sstream>
#include <SDL.h>
#include <inih/cpp/INIReader.h>
#include "common/file_util.h"
@@ -370,23 +369,6 @@ void Config::ReadValues() {
Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false);
Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false);
const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
std::stringstream ss(title_list);
std::string line;
while (std::getline(ss, line, '|')) {
const auto title_id = std::stoul(line, nullptr, 16);
const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
std::stringstream inner_ss(disabled_list);
std::string inner_line;
std::vector<std::string> out;
while (std::getline(inner_ss, inner_line, '|')) {
out.push_back(inner_line);
}
Settings::values.disabled_addons.insert_or_assign(title_id, out);
}
// Web Service
Settings::values.enable_telemetry =
sdl2_config->GetBoolean("WebService", "enable_telemetry", true);

View File

@@ -221,12 +221,5 @@ web_api_url = https://api.yuzu-emu.org
# See https://profile.yuzu-emu.org/ for more info
yuzu_username =
yuzu_token =
[AddOns]
# Used to disable add-ons
# List of title IDs of games that will have add-ons disabled (separated by '|'):
title_ids =
# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
)";
}