Compare commits
116 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36e60c217a | ||
|
|
be4c7ed082 | ||
|
|
16b14aa7e3 | ||
|
|
b30aa7007e | ||
|
|
a5e9745380 | ||
|
|
78a81614ee | ||
|
|
1a28f4fa8c | ||
|
|
212a6ab937 | ||
|
|
94f4009c3b | ||
|
|
5ecf152c8e | ||
|
|
f1423fcbc1 | ||
|
|
c97ff4460c | ||
|
|
12b05c719e | ||
|
|
b4bf099793 | ||
|
|
5e343edc9e | ||
|
|
d8f745382b | ||
|
|
c33abac275 | ||
|
|
d30110348b | ||
|
|
67bc2f5ecd | ||
|
|
666d53299c | ||
|
|
ac19e3d061 | ||
|
|
a6cab532f8 | ||
|
|
d4fb8a887c | ||
|
|
527ce12ce4 | ||
|
|
d89bfec5f5 | ||
|
|
1bfc0dc2db | ||
|
|
0a5832798a | ||
|
|
7504df52fc | ||
|
|
c1ccbf332f | ||
|
|
d732142b66 | ||
|
|
c2dbdefedf | ||
|
|
cd8bb6ea9b | ||
|
|
4369af6b7e | ||
|
|
3754e0fdfd | ||
|
|
15925b8293 | ||
|
|
0ee38e1363 | ||
|
|
0162a2d5cb | ||
|
|
33c0bf9dc5 | ||
|
|
ed2134784e | ||
|
|
8041d72a1f | ||
|
|
170ac3f9ee | ||
|
|
94c70693f9 | ||
|
|
1a9df83535 | ||
|
|
f934da0e43 | ||
|
|
010ea89013 | ||
|
|
4697025b73 | ||
|
|
56e2013c1f | ||
|
|
6afe9e0105 | ||
|
|
3357e8d9ba | ||
|
|
75da830c13 | ||
|
|
99d86deb1f | ||
|
|
b326369704 | ||
|
|
8c8da93693 | ||
|
|
e9315ace9f | ||
|
|
a0933d92fc | ||
|
|
692639e9b7 | ||
|
|
6f27edccb2 | ||
|
|
bde3e667be | ||
|
|
868f7f18b9 | ||
|
|
0ce52b1da2 | ||
|
|
2c785bd06c | ||
|
|
39e60cfeb1 | ||
|
|
025d111308 | ||
|
|
1c31e2b3d2 | ||
|
|
1ad97c75a0 | ||
|
|
77fd0d47e7 | ||
|
|
1b8d798835 | ||
|
|
71ebc3e90d | ||
|
|
46945b5c96 | ||
|
|
88c9608eac | ||
|
|
31816aac38 | ||
|
|
9b9de30086 | ||
|
|
a10baacf9e | ||
|
|
d561e4acc8 | ||
|
|
4ed54738fc | ||
|
|
a397a9e9a4 | ||
|
|
b7da9d5a54 | ||
|
|
0f8401906b | ||
|
|
054393917e | ||
|
|
b5f3e7951b | ||
|
|
68658ce4b0 | ||
|
|
fd0a7c0aaf | ||
|
|
7ce4a03188 | ||
|
|
4c06d55a81 | ||
|
|
b36b627d4d | ||
|
|
1a158dfcd6 | ||
|
|
11047d7fd5 | ||
|
|
cdf541fb5b | ||
|
|
ec4e1a3685 | ||
|
|
32fd57f0c8 | ||
|
|
5ded39f5d8 | ||
|
|
9ee33350de | ||
|
|
c2c55e0811 | ||
|
|
e12c2cf8c6 | ||
|
|
a0b1235f82 | ||
|
|
f61b9f7338 | ||
|
|
63d3924b5b | ||
|
|
6ced80bb47 | ||
|
|
740310113b | ||
|
|
8a250de987 | ||
|
|
bfe45774f1 | ||
|
|
c6362543d4 | ||
|
|
f707c2dac4 | ||
|
|
7c3a263839 | ||
|
|
3a6604e8fa | ||
|
|
656de23d93 | ||
|
|
eff3f60b73 | ||
|
|
0485ee499f | ||
|
|
21bac2d7d7 | ||
|
|
6d90d99d12 | ||
|
|
e1d7b9fc2c | ||
|
|
a9ba2c2000 | ||
|
|
fc44261dd1 | ||
|
|
808704c78c | ||
|
|
c4ca802b9d | ||
|
|
c875a7984e |
39
.appveyor/UtilityFunctions.ps1
Normal file
39
.appveyor/UtilityFunctions.ps1
Normal file
@@ -0,0 +1,39 @@
|
||||
# Set-up Visual Studio Command Prompt environment for PowerShell
|
||||
pushd "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\"
|
||||
cmd /c "VsDevCmd.bat -arch=x64 & set" | foreach {
|
||||
if ($_ -match "=") {
|
||||
$v = $_.split("="); Set-Item -Force -Path "ENV:\$($v[0])" -Value "$($v[1])"
|
||||
}
|
||||
}
|
||||
popd
|
||||
|
||||
function Which ($search_path, $name) {
|
||||
($search_path).Split(";") | Get-ChildItem -Filter $name | Select -First 1 -Exp FullName
|
||||
}
|
||||
|
||||
function GetDeps ($search_path, $binary) {
|
||||
((dumpbin /dependents $binary).Where({ $_ -match "dependencies:"}, "SkipUntil") | Select-String "[^ ]*\.dll").Matches | foreach {
|
||||
Which $search_path $_.Value
|
||||
}
|
||||
}
|
||||
|
||||
function RecursivelyGetDeps ($search_path, $binary) {
|
||||
$final_deps = @()
|
||||
$deps_to_process = GetDeps $search_path $binary
|
||||
while ($deps_to_process.Count -gt 0) {
|
||||
$current, $deps_to_process = $deps_to_process
|
||||
if ($final_deps -contains $current) { continue }
|
||||
|
||||
# Is this a system dll file?
|
||||
# We use the same algorithm that cmake uses to determine this.
|
||||
if ($current -match "$([regex]::Escape($env:SystemRoot))\\sys") { continue }
|
||||
if ($current -match "$([regex]::Escape($env:WinDir))\\sys") { continue }
|
||||
if ($current -match "\\msvc[^\\]+dll") { continue }
|
||||
if ($current -match "\\api-ms-win-[^\\]+dll") { continue }
|
||||
|
||||
$final_deps += $current
|
||||
$new_deps = GetDeps $search_path $current
|
||||
$deps_to_process += ($new_deps | ?{-not ($final_deps -contains $_)})
|
||||
}
|
||||
return $final_deps
|
||||
}
|
||||
23
appveyor.yml
23
appveyor.yml
@@ -121,23 +121,16 @@ after_build:
|
||||
Get-ChildItem "$CMAKE_BINARY_DIR" -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
Copy-Item -path "$CMAKE_SOURCE_DIR/license.txt" -destination $RELEASE_DIST
|
||||
Copy-Item -path "$CMAKE_SOURCE_DIR/README.md" -destination $RELEASE_DIST
|
||||
|
||||
# copy all the dll dependencies to the release folder
|
||||
# hardcoded list because we don't build static and determining the list of dlls from the binary is a pain.
|
||||
$MingwDLLs = "Qt5Core.dll","Qt5Widgets.dll","Qt5Gui.dll","Qt5OpenGL.dll",
|
||||
# QT dll dependencies
|
||||
"libbz2-*.dll","libicudt*.dll","libicuin*.dll","libicuuc*.dll","libffi-*.dll",
|
||||
"libfreetype-*.dll","libglib-*.dll","libgobject-*.dll","libgraphite2.dll","libiconv-*.dll",
|
||||
"libharfbuzz-*.dll","libintl-*.dll","libpcre-*.dll","libpcre2-16-*.dll","libpcre16-*.dll","libpng16-*.dll",
|
||||
# Runtime/Other dependencies
|
||||
"libgcc_s_seh-*.dll","libstdc++-*.dll","libwinpthread-*.dll","SDL2.dll","zlib1.dll"
|
||||
. "./.appveyor/UtilityFunctions.ps1"
|
||||
$DLLSearchPath = "C:\msys64\mingw64\bin;$env:PATH"
|
||||
$MingwDLLs = RecursivelyGetDeps $DLLSearchPath "$RELEASE_DIST\yuzu.exe"
|
||||
$MingwDLLs += RecursivelyGetDeps $DLLSearchPath "$RELEASE_DIST\yuzu_cmd.exe"
|
||||
Write-Host "Detected the following dependencies:"
|
||||
Write-Host $MingwDLLs
|
||||
foreach ($file in $MingwDLLs) {
|
||||
Copy-Item -path "C:/msys64/mingw64/bin/$file" -force -destination "$RELEASE_DIST"
|
||||
}
|
||||
# the above list copies a few extra debug dlls that aren't needed (thanks globbing patterns!)
|
||||
# so we can remove them by hardcoding another list of extra dlls to remove
|
||||
$DebugDLLs = "libicudtd*.dll","libicuind*.dll","libicuucd*.dll"
|
||||
foreach ($file in $DebugDLLs) {
|
||||
Remove-Item -path "$RELEASE_DIST/$file"
|
||||
Copy-Item -path "$file" -force -destination "$RELEASE_DIST"
|
||||
}
|
||||
|
||||
# copy the qt windows plugin dll to platforms
|
||||
|
||||
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 6b4c6b06a9...12a102046c
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Enforce citra's whitespace policy
|
||||
# Enforce yuzu's whitespace policy
|
||||
git config --local core.whitespace tab-in-indent,trailing-space
|
||||
|
||||
paths_to_check="src/ CMakeLists.txt"
|
||||
|
||||
@@ -121,7 +121,7 @@ void CopyDir(const std::string& source_path, const std::string& dest_path);
|
||||
// Set the current directory to given directory
|
||||
bool SetCurrentDir(const std::string& directory);
|
||||
|
||||
// Returns a pointer to a string with a Citra data dir in the user's home
|
||||
// Returns a pointer to a string with a yuzu data dir in the user's home
|
||||
// directory. To be used in "multi-user" mode (that is, installed).
|
||||
const std::string& GetUserPath(const unsigned int DirIDX, const std::string& newPath = "");
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace Log {
|
||||
SUB(Service, SET) \
|
||||
SUB(Service, SM) \
|
||||
SUB(Service, SPL) \
|
||||
SUB(Service, SSL) \
|
||||
SUB(Service, Time) \
|
||||
SUB(Service, VI) \
|
||||
CLS(HW) \
|
||||
|
||||
@@ -66,6 +66,7 @@ enum class Class : ClassType {
|
||||
Service_SET, ///< The SET (Settings) service
|
||||
Service_SM, ///< The SM (Service manager) service
|
||||
Service_SPL, ///< The SPL service
|
||||
Service_SSL, ///< The SSL service
|
||||
Service_Time, ///< The time service
|
||||
Service_VI, ///< The VI (Video interface) service
|
||||
HW, ///< Low-level hardware emulation
|
||||
@@ -84,7 +85,7 @@ enum class Class : ClassType {
|
||||
Loader, ///< ROM loader
|
||||
Input, ///< Input emulation
|
||||
Network, ///< Network emulation
|
||||
WebService, ///< Interface to Citra Web Services
|
||||
WebService, ///< Interface to yuzu Web Services
|
||||
Count ///< Total number of logging classes
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ static CPUCaps Detect() {
|
||||
caps.num_cores = std::thread::hardware_concurrency();
|
||||
|
||||
// Assumes the CPU supports the CPUID instruction. Those that don't would likely not support
|
||||
// Citra at all anyway
|
||||
// yuzu at all anyway
|
||||
|
||||
int cpu_id[4];
|
||||
memset(caps.brand_string, 0, sizeof(caps.brand_string));
|
||||
|
||||
@@ -22,6 +22,8 @@ add_library(core STATIC
|
||||
file_sys/romfs_filesystem.h
|
||||
file_sys/savedata_factory.cpp
|
||||
file_sys/savedata_factory.h
|
||||
file_sys/sdmc_factory.cpp
|
||||
file_sys/sdmc_factory.h
|
||||
file_sys/storage.h
|
||||
frontend/emu_window.cpp
|
||||
frontend/emu_window.h
|
||||
@@ -187,8 +189,10 @@ add_library(core STATIC
|
||||
hle/service/sm/controller.h
|
||||
hle/service/sm/sm.cpp
|
||||
hle/service/sm/sm.h
|
||||
hle/service/sockets/bsd_u.cpp
|
||||
hle/service/sockets/bsd_u.h
|
||||
hle/service/sockets/bsd.cpp
|
||||
hle/service/sockets/bsd.h
|
||||
hle/service/sockets/nsd.cpp
|
||||
hle/service/sockets/nsd.h
|
||||
hle/service/sockets/sfdnsres.cpp
|
||||
hle/service/sockets/sfdnsres.h
|
||||
hle/service/sockets/sockets.cpp
|
||||
@@ -199,6 +203,8 @@ add_library(core STATIC
|
||||
hle/service/spl/module.h
|
||||
hle/service/spl/spl.cpp
|
||||
hle/service/spl/spl.h
|
||||
hle/service/ssl/ssl.cpp
|
||||
hle/service/ssl/ssl.h
|
||||
hle/service/time/time.cpp
|
||||
hle/service/time/time.h
|
||||
hle/service/time/time_s.cpp
|
||||
|
||||
@@ -86,21 +86,17 @@ public:
|
||||
}
|
||||
|
||||
void AddTicks(u64 ticks) override {
|
||||
if (ticks > ticks_remaining) {
|
||||
ticks_remaining = 0;
|
||||
return;
|
||||
}
|
||||
ticks -= ticks_remaining;
|
||||
CoreTiming::AddTicks(ticks - num_interpreted_instructions);
|
||||
num_interpreted_instructions = 0;
|
||||
}
|
||||
u64 GetTicksRemaining() override {
|
||||
return ticks_remaining;
|
||||
return std::max(CoreTiming::GetDowncount(), 0);
|
||||
}
|
||||
u64 GetCNTPCT() override {
|
||||
return CoreTiming::GetTicks();
|
||||
}
|
||||
|
||||
ARM_Dynarmic& parent;
|
||||
size_t ticks_remaining = 0;
|
||||
size_t num_interpreted_instructions = 0;
|
||||
u64 tpidrro_el0 = 0;
|
||||
u64 tpidr_el0 = 0;
|
||||
|
||||
@@ -148,19 +148,15 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||
|
||||
current_process = Kernel::Process::Create("main");
|
||||
|
||||
switch (Settings::values.cpu_core) {
|
||||
case Settings::CpuCore::Unicorn:
|
||||
cpu_core = std::make_shared<ARM_Unicorn>();
|
||||
break;
|
||||
case Settings::CpuCore::Dynarmic:
|
||||
default:
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
cpu_core = std::make_shared<ARM_Dynarmic>();
|
||||
#else
|
||||
cpu_core = std::make_shared<ARM_Unicorn>();
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
break;
|
||||
} else {
|
||||
cpu_core = std::make_shared<ARM_Unicorn>();
|
||||
}
|
||||
|
||||
gpu_core = std::make_unique<Tegra::GPU>();
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "core/memory.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
class EmuWindow;
|
||||
@@ -135,6 +136,14 @@ public:
|
||||
return *app_loader;
|
||||
}
|
||||
|
||||
void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) {
|
||||
debug_context = std::move(context);
|
||||
}
|
||||
|
||||
std::shared_ptr<Tegra::DebugContext> GetGPUDebugContext() const {
|
||||
return debug_context;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Initialize the emulated system.
|
||||
@@ -154,6 +163,8 @@ private:
|
||||
std::unique_ptr<Kernel::Scheduler> scheduler;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
|
||||
Kernel::SharedPtr<Kernel::Process> current_process;
|
||||
|
||||
/// When true, signals that a reschedule should happen
|
||||
|
||||
@@ -6,34 +6,28 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// FileSys namespace
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// Structure of a directory entry, from http://3dbrew.org/wiki/FSDir:Read#Entry_format
|
||||
const size_t FILENAME_LENGTH = 0x20C / 2;
|
||||
// Structure of a directory entry, from
|
||||
// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry
|
||||
const size_t FILENAME_LENGTH = 0x300;
|
||||
struct Entry {
|
||||
char16_t filename[FILENAME_LENGTH]; // Entry name (UTF-16, null-terminated)
|
||||
std::array<char, 9> short_name; // 8.3 file name ('longfilename' -> 'LONGFI~1', null-terminated)
|
||||
char unknown1; // unknown (observed values: 0x0A, 0x70, 0xFD)
|
||||
std::array<char, 4>
|
||||
extension; // 8.3 file extension (set to spaces for directories, null-terminated)
|
||||
char unknown2; // unknown (always 0x01)
|
||||
char unknown3; // unknown (0x00 or 0x08)
|
||||
char is_directory; // directory flag
|
||||
char is_hidden; // hidden flag
|
||||
char is_archive; // archive flag
|
||||
char is_read_only; // read-only flag
|
||||
u64 file_size; // file size (for files only)
|
||||
char filename[FILENAME_LENGTH];
|
||||
INSERT_PADDING_BYTES(4);
|
||||
EntryType type;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
u64 file_size;
|
||||
};
|
||||
static_assert(sizeof(Entry) == 0x228, "Directory Entry struct isn't exactly 0x228 bytes long!");
|
||||
static_assert(offsetof(Entry, short_name) == 0x20C, "Wrong offset for short_name in Entry.");
|
||||
static_assert(offsetof(Entry, extension) == 0x216, "Wrong offset for extension in Entry.");
|
||||
static_assert(offsetof(Entry, is_archive) == 0x21E, "Wrong offset for is_archive in Entry.");
|
||||
static_assert(offsetof(Entry, file_size) == 0x220, "Wrong offset for file_size in Entry.");
|
||||
static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x310 bytes long!");
|
||||
static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry.");
|
||||
static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry.");
|
||||
|
||||
class DirectoryBackend : NonCopyable {
|
||||
public:
|
||||
@@ -46,7 +40,10 @@ public:
|
||||
* @param entries Buffer to read data into
|
||||
* @return Number of entries listed
|
||||
*/
|
||||
virtual u32 Read(const u32 count, Entry* entries) = 0;
|
||||
virtual u64 Read(const u64 count, Entry* entries) = 0;
|
||||
|
||||
/// Returns the number of entries still left to read.
|
||||
virtual u64 GetEntryCount() const = 0;
|
||||
|
||||
/**
|
||||
* Close the directory
|
||||
|
||||
@@ -11,16 +11,43 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
static std::string ModeFlagsToString(Mode mode) {
|
||||
std::string mode_str;
|
||||
u32 mode_flags = static_cast<u32>(mode);
|
||||
|
||||
// Calculate the correct open mode for the file.
|
||||
if ((mode_flags & static_cast<u32>(Mode::Read)) &&
|
||||
(mode_flags & static_cast<u32>(Mode::Write))) {
|
||||
if (mode_flags & static_cast<u32>(Mode::Append))
|
||||
mode_str = "a+";
|
||||
else
|
||||
mode_str = "r+";
|
||||
} else {
|
||||
if (mode_flags & static_cast<u32>(Mode::Read))
|
||||
mode_str = "r";
|
||||
else if (mode_flags & static_cast<u32>(Mode::Append))
|
||||
mode_str = "a";
|
||||
else if (mode_flags & static_cast<u32>(Mode::Write))
|
||||
mode_str = "w";
|
||||
}
|
||||
|
||||
mode_str += "b";
|
||||
|
||||
return mode_str;
|
||||
}
|
||||
|
||||
std::string Disk_FileSystem::GetName() const {
|
||||
return "Disk";
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::string& path,
|
||||
Mode mode) const {
|
||||
ASSERT_MSG(mode == Mode::Read || mode == Mode::Write, "Other file modes are not supported");
|
||||
|
||||
// Calculate the correct open mode for the file.
|
||||
std::string mode_str = ModeFlagsToString(mode);
|
||||
|
||||
std::string full_path = base_directory + path;
|
||||
auto file = std::make_shared<FileUtil::IOFile>(full_path, mode == Mode::Read ? "rb" : "wb");
|
||||
auto file = std::make_shared<FileUtil::IOFile>(full_path, mode_str.c_str());
|
||||
|
||||
if (!file->IsOpen()) {
|
||||
return ERROR_PATH_NOT_FOUND;
|
||||
@@ -75,8 +102,15 @@ ResultCode Disk_FileSystem::CreateFile(const std::string& path, u64 size) const
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode Disk_FileSystem::CreateDirectory(const Path& path) const {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const {
|
||||
// TODO(Subv): Perform path validation to prevent escaping the emulator sandbox.
|
||||
std::string full_path = base_directory + path;
|
||||
|
||||
if (FileUtil::CreateDir(full_path)) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", full_path.c_str());
|
||||
// TODO(wwylele): Use correct error code
|
||||
return ResultCode(-1);
|
||||
}
|
||||
@@ -88,8 +122,17 @@ ResultCode Disk_FileSystem::RenameDirectory(const Path& src_path, const Path& de
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> Disk_FileSystem::OpenDirectory(
|
||||
const Path& path) const {
|
||||
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<Disk_Directory>());
|
||||
const std::string& path) const {
|
||||
|
||||
std::string full_path = base_directory + path;
|
||||
|
||||
if (!FileUtil::IsDirectory(full_path)) {
|
||||
// TODO(Subv): Find the correct error code for this.
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
auto directory = std::make_unique<Disk_Directory>(full_path);
|
||||
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
|
||||
}
|
||||
|
||||
u64 Disk_FileSystem::GetFreeSpaceSize() const {
|
||||
@@ -103,8 +146,10 @@ ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& p
|
||||
return ERROR_PATH_NOT_FOUND;
|
||||
}
|
||||
|
||||
// TODO(Subv): Find out the EntryType values
|
||||
UNIMPLEMENTED_MSG("Unimplemented GetEntryType");
|
||||
if (FileUtil::IsDirectory(full_path))
|
||||
return MakeResult(EntryType::Directory);
|
||||
|
||||
return MakeResult(EntryType::File);
|
||||
}
|
||||
|
||||
ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
|
||||
@@ -133,14 +178,50 @@ bool Disk_Storage::SetSize(const u64 size) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 Disk_Directory::Read(const u32 count, Entry* entries) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
return 0;
|
||||
Disk_Directory::Disk_Directory(const std::string& path) : directory() {
|
||||
unsigned size = FileUtil::ScanDirectoryTree(path, directory);
|
||||
directory.size = size;
|
||||
directory.isDirectory = true;
|
||||
children_iterator = directory.children.begin();
|
||||
}
|
||||
|
||||
bool Disk_Directory::Close() const {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
return true;
|
||||
u64 Disk_Directory::Read(const u64 count, Entry* entries) {
|
||||
u64 entries_read = 0;
|
||||
|
||||
while (entries_read < count && children_iterator != directory.children.cend()) {
|
||||
const FileUtil::FSTEntry& file = *children_iterator;
|
||||
const std::string& filename = file.virtualName;
|
||||
Entry& entry = entries[entries_read];
|
||||
|
||||
LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size,
|
||||
file.isDirectory);
|
||||
|
||||
// TODO(Link Mauve): use a proper conversion to UTF-16.
|
||||
for (size_t j = 0; j < FILENAME_LENGTH; ++j) {
|
||||
entry.filename[j] = filename[j];
|
||||
if (!filename[j])
|
||||
break;
|
||||
}
|
||||
|
||||
if (file.isDirectory) {
|
||||
entry.file_size = 0;
|
||||
entry.type = EntryType::Directory;
|
||||
} else {
|
||||
entry.file_size = file.size;
|
||||
entry.type = EntryType::File;
|
||||
}
|
||||
|
||||
++entries_read;
|
||||
++children_iterator;
|
||||
}
|
||||
return entries_read;
|
||||
}
|
||||
|
||||
u64 Disk_Directory::GetEntryCount() const {
|
||||
// We convert the children iterator into a const_iterator to allow template argument deduction
|
||||
// in std::distance.
|
||||
std::vector<FileUtil::FSTEntry>::const_iterator current = children_iterator;
|
||||
return std::distance(current, directory.children.end());
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -30,9 +30,10 @@ public:
|
||||
ResultCode DeleteDirectory(const Path& path) const override;
|
||||
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
|
||||
ResultCode CreateFile(const std::string& path, u64 size) const override;
|
||||
ResultCode CreateDirectory(const Path& path) const override;
|
||||
ResultCode CreateDirectory(const std::string& path) const override;
|
||||
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
|
||||
const std::string& path) const override;
|
||||
u64 GetFreeSpaceSize() const override;
|
||||
ResultVal<EntryType> GetEntryType(const std::string& path) const override;
|
||||
|
||||
@@ -59,8 +60,26 @@ private:
|
||||
|
||||
class Disk_Directory : public DirectoryBackend {
|
||||
public:
|
||||
u32 Read(const u32 count, Entry* entries) override;
|
||||
bool Close() const override;
|
||||
Disk_Directory(const std::string& path);
|
||||
|
||||
~Disk_Directory() override {
|
||||
Close();
|
||||
}
|
||||
|
||||
u64 Read(const u64 count, Entry* entries) override;
|
||||
u64 GetEntryCount() const override;
|
||||
|
||||
bool Close() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
u32 total_entries_in_directory;
|
||||
FileUtil::FSTEntry directory;
|
||||
|
||||
// We need to remember the last entry we returned, so a subsequent call to Read will continue
|
||||
// from the next one. This iterator will always point to the next unread entry.
|
||||
std::vector<FileUtil::FSTEntry>::iterator children_iterator;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -27,7 +27,7 @@ enum LowPathType : u32 {
|
||||
Wchar = 4,
|
||||
};
|
||||
|
||||
enum EntryType : u32 {
|
||||
enum EntryType : u8 {
|
||||
Directory = 0,
|
||||
File = 1,
|
||||
};
|
||||
@@ -35,6 +35,7 @@ enum EntryType : u32 {
|
||||
enum class Mode : u32 {
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
Append = 4,
|
||||
};
|
||||
|
||||
class Path {
|
||||
@@ -103,7 +104,7 @@ public:
|
||||
* @param path Path relative to the archive
|
||||
* @return Result of the operation
|
||||
*/
|
||||
virtual ResultCode CreateDirectory(const Path& path) const = 0;
|
||||
virtual ResultCode CreateDirectory(const std::string& path) const = 0;
|
||||
|
||||
/**
|
||||
* Delete a directory specified by its path
|
||||
@@ -149,7 +150,8 @@ public:
|
||||
* @param path Path relative to the archive
|
||||
* @return Opened directory, or error code
|
||||
*/
|
||||
virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0;
|
||||
virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
|
||||
const std::string& path) const = 0;
|
||||
|
||||
/**
|
||||
* Get the free space
|
||||
|
||||
@@ -55,7 +55,7 @@ ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultCode RomFS_FileSystem::CreateDirectory(const Path& path) const {
|
||||
ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const {
|
||||
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive (%s).",
|
||||
GetName().c_str());
|
||||
// TODO(wwylele): Use correct error code
|
||||
@@ -70,7 +70,8 @@ ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& d
|
||||
}
|
||||
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> RomFS_FileSystem::OpenDirectory(
|
||||
const Path& path) const {
|
||||
const std::string& path) const {
|
||||
LOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive");
|
||||
return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<ROMFSDirectory>());
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,10 @@ public:
|
||||
ResultCode DeleteDirectory(const Path& path) const override;
|
||||
ResultCode DeleteDirectoryRecursively(const Path& path) const override;
|
||||
ResultCode CreateFile(const std::string& path, u64 size) const override;
|
||||
ResultCode CreateDirectory(const Path& path) const override;
|
||||
ResultCode CreateDirectory(const std::string& path) const override;
|
||||
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
|
||||
const std::string& path) const override;
|
||||
u64 GetFreeSpaceSize() const override;
|
||||
ResultVal<EntryType> GetEntryType(const std::string& path) const override;
|
||||
|
||||
@@ -70,7 +71,10 @@ private:
|
||||
|
||||
class ROMFSDirectory : public DirectoryBackend {
|
||||
public:
|
||||
u32 Read(const u32 count, Entry* entries) override {
|
||||
u64 Read(const u64 count, Entry* entries) override {
|
||||
return 0;
|
||||
}
|
||||
u64 GetEntryCount() const override {
|
||||
return 0;
|
||||
}
|
||||
bool Close() const override {
|
||||
|
||||
40
src/core/file_sys/sdmc_factory.cpp
Normal file
40
src/core/file_sys/sdmc_factory.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/disk_filesystem.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
SDMC_Factory::SDMC_Factory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {}
|
||||
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> SDMC_Factory::Open(const Path& path) {
|
||||
// Create the SD Card directory if it doesn't already exist.
|
||||
if (!FileUtil::IsDirectory(sd_directory)) {
|
||||
FileUtil::CreateFullPath(sd_directory);
|
||||
}
|
||||
|
||||
auto archive = std::make_unique<Disk_FileSystem>(sd_directory);
|
||||
return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
|
||||
}
|
||||
|
||||
ResultCode SDMC_Factory::Format(const Path& path) {
|
||||
LOG_ERROR(Service_FS, "Unimplemented Format archive %s", GetName().c_str());
|
||||
// TODO(Subv): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
ResultVal<ArchiveFormatInfo> SDMC_Factory::GetFormatInfo(const Path& path) const {
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
|
||||
// TODO(bunnei): Find the right error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
31
src/core/file_sys/sdmc_factory.h
Normal file
31
src/core/file_sys/sdmc_factory.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
/// File system interface to the SDCard archive
|
||||
class SDMC_Factory final : public FileSystemFactory {
|
||||
public:
|
||||
explicit SDMC_Factory(std::string sd_directory);
|
||||
|
||||
std::string GetName() const override {
|
||||
return "SDMC_Factory";
|
||||
}
|
||||
ResultVal<std::unique_ptr<FileSystemBackend>> Open(const Path& path) override;
|
||||
ResultCode Format(const Path& path) override;
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
|
||||
|
||||
private:
|
||||
std::string sd_directory;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -200,6 +200,9 @@ public:
|
||||
}
|
||||
|
||||
ResultVal& operator=(const ResultVal& o) {
|
||||
if (this == &o) {
|
||||
return *this;
|
||||
}
|
||||
if (!empty()) {
|
||||
if (!o.empty()) {
|
||||
object = o.object;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "core/hle/service/apm/apm.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service {
|
||||
namespace AM {
|
||||
@@ -241,17 +242,20 @@ void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
|
||||
const bool use_docked_mode{Settings::values.use_docked_mode};
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(static_cast<u8>(OperationMode::Handheld));
|
||||
rb.Push(static_cast<u8>(use_docked_mode ? OperationMode::Docked : OperationMode::Handheld));
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
|
||||
const bool use_docked_mode{Settings::values.use_docked_mode};
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(static_cast<u32>(APM::PerformanceMode::Handheld));
|
||||
rb.Push(static_cast<u32>(use_docked_mode ? APM::PerformanceMode::Docked
|
||||
: APM::PerformanceMode::Handheld));
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
@@ -151,12 +151,52 @@ private:
|
||||
Kernel::SharedPtr<Kernel::Event> system_event;
|
||||
};
|
||||
|
||||
class IAudioDevice final : public ServiceFramework<IAudioDevice> {
|
||||
public:
|
||||
IAudioDevice() : ServiceFramework("IAudioDevice") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0x0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
|
||||
{0x1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
buffer_event =
|
||||
Kernel::Event::Create(Kernel::ResetType::OneShot, "IAudioOutBufferReleasedEvent");
|
||||
}
|
||||
|
||||
private:
|
||||
void ListAudioDeviceName(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const std::string audio_interface = "AudioInterface";
|
||||
ctx.WriteBuffer(audio_interface.c_str(), audio_interface.size());
|
||||
|
||||
IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(1);
|
||||
}
|
||||
|
||||
void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
f32 volume = static_cast<f32>(rp.Pop<u32>());
|
||||
|
||||
auto file_buffer = ctx.ReadBuffer();
|
||||
auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
|
||||
|
||||
IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event;
|
||||
};
|
||||
|
||||
AudRenU::AudRenU() : ServiceFramework("audren:u") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"},
|
||||
{1, &AudRenU::GetAudioRendererWorkBufferSize, "GetAudioRendererWorkBufferSize"},
|
||||
{2, &AudRenU::GetAudioRenderersProcessMasterVolume, "GetAudioRenderersProcessMasterVolume"},
|
||||
{3, nullptr, "SetAudioRenderersProcessMasterVolume"},
|
||||
{2, &AudRenU::GetAudioDevice, "GetAudioDevice"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
@@ -179,12 +219,13 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void AudRenU::GetAudioRenderersProcessMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
void AudRenU::GetAudioDevice(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(100);
|
||||
LOG_WARNING(Service_Audio, "(STUBBED) called");
|
||||
rb.PushIpcInterface<Audio::IAudioDevice>();
|
||||
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
}
|
||||
|
||||
} // namespace Audio
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
private:
|
||||
void OpenAudioRenderer(Kernel::HLERequestContext& ctx);
|
||||
void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx);
|
||||
void GetAudioRenderersProcessMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void GetAudioDevice(Kernel::HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
} // namespace Audio
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/filesystem/fsp_srv.h"
|
||||
|
||||
@@ -60,9 +61,13 @@ void RegisterFileSystems() {
|
||||
filesystem_map.clear();
|
||||
|
||||
std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX);
|
||||
std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX);
|
||||
|
||||
auto savedata = std::make_unique<FileSys::SaveData_Factory>(std::move(nand_directory));
|
||||
RegisterFileSystem(std::move(savedata), Type::SaveData);
|
||||
|
||||
auto sdcard = std::make_unique<FileSys::SDMC_Factory>(std::move(sd_directory));
|
||||
RegisterFileSystem(std::move(sdcard), Type::SDMC);
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace FileSystem {
|
||||
enum class Type {
|
||||
RomFS = 1,
|
||||
SaveData = 2,
|
||||
SDMC = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cinttypes>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/directory.h"
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/file_sys/storage.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
@@ -151,14 +152,66 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
class IDirectory final : public ServiceFramework<IDirectory> {
|
||||
public:
|
||||
explicit IDirectory(std::unique_ptr<FileSys::DirectoryBackend>&& backend)
|
||||
: ServiceFramework("IDirectory"), backend(std::move(backend)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IDirectory::Read, "Read"},
|
||||
{1, &IDirectory::GetEntryCount, "GetEntryCount"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<FileSys::DirectoryBackend> backend;
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u64 unk = rp.Pop<u64>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called, unk=0x%llx", unk);
|
||||
|
||||
// Calculate how many entries we can fit in the output buffer
|
||||
u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry);
|
||||
|
||||
// Read the data from the Directory backend
|
||||
std::vector<FileSys::Entry> entries(count_entries);
|
||||
u64 read_entries = backend->Read(count_entries, entries.data());
|
||||
|
||||
// Convert the data into a byte array
|
||||
std::vector<u8> output(entries.size() * sizeof(FileSys::Entry));
|
||||
std::memcpy(output.data(), entries.data(), output.size());
|
||||
|
||||
// Write the data to memory
|
||||
ctx.WriteBuffer(output);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(read_entries);
|
||||
}
|
||||
|
||||
void GetEntryCount(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
u64 count = backend->GetEntryCount();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(count);
|
||||
}
|
||||
};
|
||||
|
||||
class IFileSystem final : public ServiceFramework<IFileSystem> {
|
||||
public:
|
||||
explicit IFileSystem(std::unique_ptr<FileSys::FileSystemBackend>&& backend)
|
||||
: ServiceFramework("IFileSystem"), backend(std::move(backend)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IFileSystem::CreateFile, "CreateFile"},
|
||||
{2, &IFileSystem::CreateDirectory, "CreateDirectory"},
|
||||
{7, &IFileSystem::GetEntryType, "GetEntryType"},
|
||||
{8, &IFileSystem::OpenFile, "OpenFile"},
|
||||
{9, &IFileSystem::OpenDirectory, "OpenDirectory"},
|
||||
{10, &IFileSystem::Commit, "Commit"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
@@ -182,6 +235,20 @@ public:
|
||||
rb.Push(backend->CreateFile(name, size));
|
||||
}
|
||||
|
||||
void CreateDirectory(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto file_buffer = ctx.ReadBuffer();
|
||||
auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
|
||||
|
||||
std::string name(file_buffer.begin(), end);
|
||||
|
||||
LOG_DEBUG(Service_FS, "called directory %s", name.c_str());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(backend->CreateDirectory(name));
|
||||
}
|
||||
|
||||
void OpenFile(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
@@ -208,6 +275,33 @@ public:
|
||||
rb.PushIpcInterface<IFile>(std::move(file));
|
||||
}
|
||||
|
||||
void OpenDirectory(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
auto file_buffer = ctx.ReadBuffer();
|
||||
auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0');
|
||||
|
||||
std::string name(file_buffer.begin(), end);
|
||||
|
||||
// TODO(Subv): Implement this filter.
|
||||
u32 filter_flags = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called directory %s filter %u", name.c_str(), filter_flags);
|
||||
|
||||
auto result = backend->OpenDirectory(name);
|
||||
if (result.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result.Code());
|
||||
return;
|
||||
}
|
||||
|
||||
auto directory = std::move(result.Unwrap());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IDirectory>(std::move(directory));
|
||||
}
|
||||
|
||||
void GetEntryType(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
@@ -274,10 +368,14 @@ void FSP_SRV::Initalize(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
FileSys::Path unused;
|
||||
auto filesystem = OpenFileSystem(Type::SDMC, unused).Unwrap();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
|
||||
}
|
||||
|
||||
void FSP_SRV::CreateSaveData(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -193,7 +193,7 @@ public:
|
||||
{121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"},
|
||||
{122, &Hid::SetNpadJoyAssignmentModeSingleByDefault,
|
||||
"SetNpadJoyAssignmentModeSingleByDefault"},
|
||||
{124, nullptr, "SetNpadJoyAssignmentModeDual"},
|
||||
{124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"},
|
||||
{128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"},
|
||||
{200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"},
|
||||
{201, &Hid::SendVibrationValue, "SendVibrationValue"},
|
||||
@@ -315,6 +315,12 @@ private:
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
@@ -33,6 +33,7 @@ enum class LoadState : u32 {
|
||||
|
||||
PL_U::PL_U() : ServiceFramework("pl:u") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &PL_U::RequestLoad, "RequestLoad"},
|
||||
{1, &PL_U::GetLoadState, "GetLoadState"},
|
||||
{2, &PL_U::GetSize, "GetSize"},
|
||||
{3, &PL_U::GetSharedMemoryAddressOffset, "GetSharedMemoryAddressOffset"},
|
||||
@@ -54,6 +55,15 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
|
||||
}
|
||||
}
|
||||
|
||||
void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u32 shared_font_type{rp.Pop<u32>()};
|
||||
|
||||
LOG_DEBUG(Service_NS, "called, shared_font_type=%d", shared_font_type);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void PL_U::GetLoadState(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u32 font_id{rp.Pop<u32>()};
|
||||
|
||||
@@ -17,6 +17,7 @@ public:
|
||||
~PL_U() = default;
|
||||
|
||||
private:
|
||||
void RequestLoad(Kernel::HLERequestContext& ctx);
|
||||
void GetLoadState(Kernel::HLERequestContext& ctx);
|
||||
void GetSize(Kernel::HLERequestContext& ctx);
|
||||
void GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -26,14 +26,13 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3
|
||||
"Drawing from address %lx offset %08X Width %u Height %u Stride %u Format %u", addr,
|
||||
offset, width, height, stride, format);
|
||||
|
||||
using PixelFormat = RendererBase::FramebufferInfo::PixelFormat;
|
||||
using Flags = NVFlinger::BufferQueue::BufferTransformFlags;
|
||||
const bool flip_vertical = static_cast<u32>(transform) & static_cast<u32>(Flags::FlipV);
|
||||
const RendererBase::FramebufferInfo framebuffer_info{
|
||||
addr, offset, width, height, stride, static_cast<PixelFormat>(format), flip_vertical};
|
||||
using PixelFormat = Tegra::FramebufferConfig::PixelFormat;
|
||||
const Tegra::FramebufferConfig framebuffer{
|
||||
addr, offset, width, height, stride, static_cast<PixelFormat>(format), transform};
|
||||
|
||||
Core::System::GetInstance().perf_stats.EndGameFrame();
|
||||
VideoCore::g_renderer->SwapBuffers(framebuffer_info);
|
||||
|
||||
VideoCore::g_renderer->SwapBuffers(framebuffer);
|
||||
}
|
||||
|
||||
} // namespace Devices
|
||||
|
||||
@@ -47,6 +47,8 @@ public:
|
||||
~BufferQueue() = default;
|
||||
|
||||
enum class BufferTransformFlags : u32 {
|
||||
/// No transform flags are set
|
||||
Unset = 0x00,
|
||||
/// Flip source image horizontally (around the vertical axis)
|
||||
FlipH = 0x01,
|
||||
/// Flip source image vertically (around the horizontal axis)
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/hle/service/sockets/sockets.h"
|
||||
#include "core/hle/service/spl/module.h"
|
||||
#include "core/hle/service/ssl/ssl.h"
|
||||
#include "core/hle/service/time/time.h"
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
|
||||
@@ -192,6 +193,7 @@ void Init() {
|
||||
PCTL::InstallInterfaces(*SM::g_service_manager);
|
||||
Sockets::InstallInterfaces(*SM::g_service_manager);
|
||||
SPL::InstallInterfaces(*SM::g_service_manager);
|
||||
SSL::InstallInterfaces(*SM::g_service_manager);
|
||||
Time::InstallInterfaces(*SM::g_service_manager);
|
||||
VI::InstallInterfaces(*SM::g_service_manager, nv_flinger);
|
||||
Set::InstallInterfaces(*SM::g_service_manager);
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/service/sockets/bsd_u.h"
|
||||
#include "core/hle/service/sockets/bsd.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Sockets {
|
||||
|
||||
void BSD_U::RegisterClient(Kernel::HLERequestContext& ctx) {
|
||||
void BSD::RegisterClient(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
@@ -17,7 +17,7 @@ void BSD_U::RegisterClient(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD_U::StartMonitoring(Kernel::HLERequestContext& ctx) {
|
||||
void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
@@ -26,7 +26,7 @@ void BSD_U::StartMonitoring(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD_U::Socket(Kernel::HLERequestContext& ctx) {
|
||||
void BSD::Socket(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
u32 domain = rp.Pop<u32>();
|
||||
@@ -44,7 +44,7 @@ void BSD_U::Socket(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD_U::Connect(Kernel::HLERequestContext& ctx) {
|
||||
void BSD::Connect(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
@@ -54,7 +54,7 @@ void BSD_U::Connect(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD_U::SendTo(Kernel::HLERequestContext& ctx) {
|
||||
void BSD::SendTo(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
@@ -64,7 +64,7 @@ void BSD_U::SendTo(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD_U::Close(Kernel::HLERequestContext& ctx) {
|
||||
void BSD::Close(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
@@ -74,13 +74,15 @@ void BSD_U::Close(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
BSD_U::BSD_U() : ServiceFramework("bsd:u") {
|
||||
static const FunctionInfo functions[] = {{0, &BSD_U::RegisterClient, "RegisterClient"},
|
||||
{1, &BSD_U::StartMonitoring, "StartMonitoring"},
|
||||
{2, &BSD_U::Socket, "Socket"},
|
||||
{11, &BSD_U::SendTo, "SendTo"},
|
||||
{14, &BSD_U::Connect, "Connect"},
|
||||
{26, &BSD_U::Close, "Close"}};
|
||||
BSD::BSD(const char* name) : ServiceFramework(name) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &BSD::RegisterClient, "RegisterClient"},
|
||||
{1, &BSD::StartMonitoring, "StartMonitoring"},
|
||||
{2, &BSD::Socket, "Socket"},
|
||||
{11, &BSD::SendTo, "SendTo"},
|
||||
{14, &BSD::Connect, "Connect"},
|
||||
{26, &BSD::Close, "Close"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
namespace Service {
|
||||
namespace Sockets {
|
||||
|
||||
class BSD_U final : public ServiceFramework<BSD_U> {
|
||||
class BSD final : public ServiceFramework<BSD> {
|
||||
public:
|
||||
BSD_U();
|
||||
~BSD_U() = default;
|
||||
explicit BSD(const char* name);
|
||||
~BSD() = default;
|
||||
|
||||
private:
|
||||
void RegisterClient(Kernel::HLERequestContext& ctx);
|
||||
34
src/core/hle/service/sockets/nsd.cpp
Normal file
34
src/core/hle/service/sockets/nsd.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/service/sockets/nsd.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Sockets {
|
||||
|
||||
NSD::NSD(const char* name) : ServiceFramework(name) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{10, nullptr, "GetSettingName"},
|
||||
{11, nullptr, "GetEnvironmentIdentifier"},
|
||||
{12, nullptr, "GetDeviceId"},
|
||||
{13, nullptr, "DeleteSettings"},
|
||||
{14, nullptr, "ImportSettings"},
|
||||
{20, nullptr, "Resolve"},
|
||||
{21, nullptr, "ResolveEx"},
|
||||
{30, nullptr, "GetNasServiceSetting"},
|
||||
{31, nullptr, "GetNasServiceSettingEx"},
|
||||
{40, nullptr, "GetNasRequestFqdn"},
|
||||
{41, nullptr, "GetNasRequestFqdnEx"},
|
||||
{42, nullptr, "GetNasApiFqdn"},
|
||||
{43, nullptr, "GetNasApiFqdnEx"},
|
||||
{50, nullptr, "GetCurrentSetting"},
|
||||
{60, nullptr, "ReadSaveDataFromFsForTest"},
|
||||
{61, nullptr, "WriteSaveDataToFsForTest"},
|
||||
{62, nullptr, "DeleteSaveDataOfFsForTest"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace Sockets
|
||||
} // namespace Service
|
||||
20
src/core/hle/service/sockets/nsd.h
Normal file
20
src/core/hle/service/sockets/nsd.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Sockets {
|
||||
|
||||
class NSD final : public ServiceFramework<NSD> {
|
||||
public:
|
||||
explicit NSD(const char* name);
|
||||
~NSD() = default;
|
||||
};
|
||||
|
||||
} // namespace Sockets
|
||||
} // namespace Service
|
||||
@@ -19,16 +19,18 @@ void SFDNSRES::GetAddrInfo(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") {
|
||||
static const FunctionInfo functions[] = {{0, nullptr, "SetDnsAddressesPrivate"},
|
||||
{1, nullptr, "GetDnsAddressPrivate"},
|
||||
{2, nullptr, "GetHostByName"},
|
||||
{3, nullptr, "GetHostByAddr"},
|
||||
{4, nullptr, "GetHostStringError"},
|
||||
{5, nullptr, "GetGaiStringError"},
|
||||
{6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"},
|
||||
{7, nullptr, "GetNameInfo"},
|
||||
{8, nullptr, "RequestCancelHandle"},
|
||||
{9, nullptr, "CancelSocketCall"}};
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "SetDnsAddressesPrivate"},
|
||||
{1, nullptr, "GetDnsAddressPrivate"},
|
||||
{2, nullptr, "GetHostByName"},
|
||||
{3, nullptr, "GetHostByAddr"},
|
||||
{4, nullptr, "GetHostStringError"},
|
||||
{5, nullptr, "GetGaiStringError"},
|
||||
{6, &SFDNSRES::GetAddrInfo, "GetAddrInfo"},
|
||||
{7, nullptr, "GetNameInfo"},
|
||||
{8, nullptr, "RequestCancelHandle"},
|
||||
{9, nullptr, "CancelSocketCall"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Sockets {
|
||||
|
||||
class SFDNSRES final : public ServiceFramework<SFDNSRES> {
|
||||
public:
|
||||
SFDNSRES();
|
||||
explicit SFDNSRES();
|
||||
~SFDNSRES() = default;
|
||||
|
||||
private:
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/service/sockets/bsd_u.h"
|
||||
#include "core/hle/service/sockets/bsd.h"
|
||||
#include "core/hle/service/sockets/nsd.h"
|
||||
#include "core/hle/service/sockets/sfdnsres.h"
|
||||
#include "core/hle/service/sockets/sockets.h"
|
||||
|
||||
@@ -10,7 +11,10 @@ namespace Service {
|
||||
namespace Sockets {
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
std::make_shared<BSD_U>()->InstallAsService(service_manager);
|
||||
std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager);
|
||||
std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager);
|
||||
std::make_shared<NSD>("nsd:a")->InstallAsService(service_manager);
|
||||
std::make_shared<NSD>("nsd:u")->InstallAsService(service_manager);
|
||||
std::make_shared<SFDNSRES>()->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
|
||||
17
src/core/hle/service/ssl/ssl.cpp
Normal file
17
src/core/hle/service/ssl/ssl.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/service/ssl/ssl.h"
|
||||
|
||||
namespace Service {
|
||||
namespace SSL {
|
||||
|
||||
SSL::SSL() : ServiceFramework("ssl") {}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
std::make_shared<SSL>()->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
} // namespace SSL
|
||||
} // namespace Service
|
||||
22
src/core/hle/service/ssl/ssl.h
Normal file
22
src/core/hle/service/ssl/ssl.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service {
|
||||
namespace SSL {
|
||||
|
||||
class SSL final : public ServiceFramework<SSL> {
|
||||
public:
|
||||
explicit SSL();
|
||||
~SSL() = default;
|
||||
};
|
||||
|
||||
/// Registers all SSL services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
|
||||
} // namespace SSL
|
||||
} // namespace Service
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/memory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_setup.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
@@ -41,6 +42,9 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa
|
||||
LOG_DEBUG(HW_Memory, "Mapping %p onto %016" PRIX64 "-%016" PRIX64, memory, base * PAGE_SIZE,
|
||||
(base + size) * PAGE_SIZE);
|
||||
|
||||
RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE,
|
||||
FlushMode::FlushAndInvalidate);
|
||||
|
||||
VAddr end = base + size;
|
||||
while (base != end) {
|
||||
ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %016" PRIX64, base);
|
||||
@@ -112,91 +116,120 @@ static std::set<MemoryHookPointer> GetSpecialHandlers(VAddr vaddr, u64 size) {
|
||||
return GetSpecialHandlers(page_table, vaddr, size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
boost::optional<T> ReadSpecial(VAddr addr);
|
||||
/**
|
||||
* Gets a pointer to the exact memory at the virtual address (i.e. not page aligned)
|
||||
* using a VMA from the current process
|
||||
*/
|
||||
static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) {
|
||||
u8* direct_pointer = nullptr;
|
||||
|
||||
auto& vm_manager = process.vm_manager;
|
||||
|
||||
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;
|
||||
break;
|
||||
case Kernel::VMAType::BackingMemory:
|
||||
direct_pointer = vma.backing_memory;
|
||||
break;
|
||||
case Kernel::VMAType::Free:
|
||||
return nullptr;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return direct_pointer + (vaddr - vma.base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pointer to the exact memory at the virtual address (i.e. not page aligned)
|
||||
* using a VMA from the current process.
|
||||
*/
|
||||
static u8* GetPointerFromVMA(VAddr vaddr) {
|
||||
return GetPointerFromVMA(*Core::CurrentProcess(), vaddr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T Read(const VAddr vaddr) {
|
||||
if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES) {
|
||||
LOG_ERROR(HW_Memory, "Read%lu after page table @ 0x%016" PRIX64, sizeof(T) * 8, vaddr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
case PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "unmapped Read%zu @ 0x%016" PRIX64, sizeof(T) * 8, vaddr);
|
||||
return 0;
|
||||
case PageType::Special: {
|
||||
if (auto result = ReadSpecial<T>(vaddr))
|
||||
return *result;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case PageType::Memory: {
|
||||
const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
|
||||
ASSERT_MSG(page_pointer, "Mapped memory page without a pointer @ %016" PRIX64, vaddr);
|
||||
|
||||
const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
|
||||
if (page_pointer) {
|
||||
// NOTE: Avoid adding any extra logic to this fast-path block
|
||||
T value;
|
||||
std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T));
|
||||
return value;
|
||||
}
|
||||
|
||||
// The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
|
||||
PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
case PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "unmapped Read%lu @ 0x%08X", sizeof(T) * 8, vaddr);
|
||||
return 0;
|
||||
case PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
|
||||
break;
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
|
||||
|
||||
T value;
|
||||
std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T));
|
||||
return value;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
UNREACHABLE();
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool WriteSpecial(VAddr addr, const T data);
|
||||
|
||||
template <typename T>
|
||||
void Write(const VAddr vaddr, const T data) {
|
||||
if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES) {
|
||||
LOG_ERROR(HW_Memory, "Write%lu after page table 0x%08X @ 0x%016" PRIX64, sizeof(data) * 8,
|
||||
(u32)data, vaddr);
|
||||
return;
|
||||
}
|
||||
|
||||
const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
case PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "unmapped Write%zu 0x%08X @ 0x%016" PRIX64, sizeof(data) * 8,
|
||||
static_cast<u32>(data), vaddr);
|
||||
return;
|
||||
case PageType::Special: {
|
||||
if (WriteSpecial<T>(vaddr, data))
|
||||
return;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case PageType::Memory: {
|
||||
u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
|
||||
ASSERT_MSG(page_pointer, "Mapped memory page without a pointer @ %016" PRIX64, vaddr);
|
||||
u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
|
||||
if (page_pointer) {
|
||||
// NOTE: Avoid adding any extra logic to this fast-path block
|
||||
std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T));
|
||||
return;
|
||||
}
|
||||
|
||||
// The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
|
||||
PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
case PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "unmapped Write%lu 0x%08X @ 0x%08X", sizeof(data) * 8, (u32)data,
|
||||
vaddr);
|
||||
return;
|
||||
case PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
|
||||
break;
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
|
||||
std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {
|
||||
auto& page_table = process.vm_manager.page_table;
|
||||
|
||||
if ((vaddr >> PAGE_BITS) >= PAGE_TABLE_NUM_ENTRIES)
|
||||
const u8* page_pointer = page_table.pointers[vaddr >> PAGE_BITS];
|
||||
if (page_pointer)
|
||||
return true;
|
||||
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory)
|
||||
return true;
|
||||
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special)
|
||||
return false;
|
||||
|
||||
const PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
case PageType::Unmapped:
|
||||
return false;
|
||||
case PageType::Memory:
|
||||
return true;
|
||||
case PageType::Special: {
|
||||
for (auto handler : GetSpecialHandlers(page_table, vaddr, 1))
|
||||
if (auto result = handler->IsValidAddress(vaddr))
|
||||
return *result;
|
||||
return current_page_table->pointers[vaddr >> PAGE_BITS] != nullptr;
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -214,7 +247,11 @@ u8* GetPointer(const VAddr vaddr) {
|
||||
return page_pointer + (vaddr & PAGE_MASK);
|
||||
}
|
||||
|
||||
LOG_ERROR(HW_Memory, "unknown GetPointer @ 0x%016" PRIx64, vaddr);
|
||||
if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) {
|
||||
return GetPointerFromVMA(vaddr);
|
||||
}
|
||||
|
||||
LOG_ERROR(HW_Memory, "unknown GetPointer @ 0x%08x", vaddr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -288,6 +325,95 @@ u8* GetPhysicalPointer(PAddr address) {
|
||||
return target_pointer;
|
||||
}
|
||||
|
||||
void RasterizerMarkRegionCached(VAddr start, u64 size, bool cached) {
|
||||
if (start == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
u64 num_pages = ((start + size - 1) >> PAGE_BITS) - (start >> PAGE_BITS) + 1;
|
||||
VAddr vaddr = start;
|
||||
|
||||
for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
|
||||
PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
|
||||
if (cached) {
|
||||
// Switch page type to cached if now cached
|
||||
switch (page_type) {
|
||||
case PageType::Unmapped:
|
||||
// It is not necessary for a process to have this region mapped into its address
|
||||
// space, for example, a system module need not have a VRAM mapping.
|
||||
break;
|
||||
case PageType::Memory:
|
||||
page_type = PageType::RasterizerCachedMemory;
|
||||
current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
} else {
|
||||
// Switch page type to uncached if now uncached
|
||||
switch (page_type) {
|
||||
case PageType::Unmapped:
|
||||
// It is not necessary for a process to have this region mapped into its address
|
||||
// space, for example, a system module need not have a VRAM mapping.
|
||||
break;
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK);
|
||||
if (pointer == nullptr) {
|
||||
// It's possible that this function has been called while updating the pagetable
|
||||
// after unmapping a VMA. In that case the underlying VMA will no longer exist,
|
||||
// and we should just leave the pagetable entry blank.
|
||||
page_type = PageType::Unmapped;
|
||||
} else {
|
||||
page_type = PageType::Memory;
|
||||
current_page_table->pointers[vaddr >> PAGE_BITS] = pointer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) {
|
||||
// Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
|
||||
// null here
|
||||
if (VideoCore::g_renderer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
VAddr end = start + size;
|
||||
|
||||
auto CheckRegion = [&](VAddr region_start, VAddr region_end) {
|
||||
if (start >= region_end || end <= region_start) {
|
||||
// No overlap with region
|
||||
return;
|
||||
}
|
||||
|
||||
VAddr overlap_start = std::max(start, region_start);
|
||||
VAddr overlap_end = std::min(end, region_end);
|
||||
u64 overlap_size = overlap_end - overlap_start;
|
||||
|
||||
auto* rasterizer = VideoCore::g_renderer->Rasterizer();
|
||||
switch (mode) {
|
||||
case FlushMode::Flush:
|
||||
rasterizer->FlushRegion(overlap_start, overlap_size);
|
||||
break;
|
||||
case FlushMode::Invalidate:
|
||||
rasterizer->InvalidateRegion(overlap_start, overlap_size);
|
||||
break;
|
||||
case FlushMode::FlushAndInvalidate:
|
||||
rasterizer->FlushAndInvalidateRegion(overlap_start, overlap_size);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
CheckRegion(PROCESS_IMAGE_VADDR, PROCESS_IMAGE_VADDR_END);
|
||||
CheckRegion(HEAP_VADDR, HEAP_VADDR_END);
|
||||
}
|
||||
|
||||
u8 Read8(const VAddr addr) {
|
||||
return Read<u8>(addr);
|
||||
}
|
||||
@@ -304,17 +430,6 @@ u64 Read64(const VAddr addr) {
|
||||
return Read<u64_le>(addr);
|
||||
}
|
||||
|
||||
static bool ReadSpecialBlock(const Kernel::Process& process, const VAddr src_addr,
|
||||
void* dest_buffer, const size_t size) {
|
||||
auto& page_table = process.vm_manager.page_table;
|
||||
for (const auto& handler : GetSpecialHandlers(page_table, src_addr, size)) {
|
||||
if (handler->ReadBlock(src_addr, dest_buffer, size)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_buffer,
|
||||
const size_t size) {
|
||||
auto& page_table = process.vm_manager.page_table;
|
||||
@@ -324,21 +439,16 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
|
||||
size_t page_offset = src_addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
|
||||
const size_t copy_amount =
|
||||
std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory,
|
||||
"unmapped ReadBlock @ 0x%016" PRIX64 " (start address = 0x%" PRIx64
|
||||
", size = %zu)",
|
||||
case PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory, "unmapped ReadBlock @ 0x%08X (start address = 0x%08X, size = %zu)",
|
||||
current_vaddr, src_addr, size);
|
||||
std::memset(dest_buffer, 0, copy_amount);
|
||||
break;
|
||||
case PageType::Special: {
|
||||
if (ReadSpecialBlock(process, current_vaddr, dest_buffer, copy_amount))
|
||||
break;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case PageType::Memory: {
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
@@ -347,6 +457,12 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
|
||||
std::memcpy(dest_buffer, src_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Flush);
|
||||
std::memcpy(dest_buffer, GetPointerFromVMA(process, current_vaddr), copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
@@ -378,17 +494,6 @@ void Write64(const VAddr addr, const u64 data) {
|
||||
Write<u64_le>(addr, data);
|
||||
}
|
||||
|
||||
static bool WriteSpecialBlock(const Kernel::Process& process, const VAddr dest_addr,
|
||||
const void* src_buffer, const size_t size) {
|
||||
auto& page_table = process.vm_manager.page_table;
|
||||
for (const auto& handler : GetSpecialHandlers(page_table, dest_addr, size)) {
|
||||
if (handler->WriteBlock(dest_addr, src_buffer, size)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const void* src_buffer,
|
||||
const size_t size) {
|
||||
auto& page_table = process.vm_manager.page_table;
|
||||
@@ -397,20 +502,17 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
|
||||
size_t page_offset = dest_addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
|
||||
const size_t copy_amount =
|
||||
std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped:
|
||||
case PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"unmapped WriteBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64
|
||||
", size = %zu)",
|
||||
"unmapped WriteBlock @ 0x%08X (start address = 0x%08X, size = %zu)",
|
||||
current_vaddr, dest_addr, size);
|
||||
break;
|
||||
case PageType::Special:
|
||||
if (WriteSpecialBlock(process, current_vaddr, src_buffer, copy_amount))
|
||||
break;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case PageType::Memory: {
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
|
||||
@@ -418,6 +520,12 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
|
||||
std::memcpy(dest_ptr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Invalidate);
|
||||
std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
@@ -433,9 +541,8 @@ void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size
|
||||
WriteBlock(*Core::CurrentProcess(), dest_addr, src_buffer, size);
|
||||
}
|
||||
|
||||
void ZeroBlock(const VAddr dest_addr, const size_t size) {
|
||||
const auto& process = *Core::CurrentProcess();
|
||||
|
||||
void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const size_t size) {
|
||||
auto& page_table = process.vm_manager.page_table;
|
||||
size_t remaining_size = size;
|
||||
size_t page_index = dest_addr >> PAGE_BITS;
|
||||
size_t page_offset = dest_addr & PAGE_MASK;
|
||||
@@ -443,27 +550,29 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) {
|
||||
static const std::array<u8, PAGE_SIZE> zeros = {};
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
|
||||
const size_t copy_amount =
|
||||
std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (current_page_table->attributes[page_index]) {
|
||||
case PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory,
|
||||
"unmapped ZeroBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64
|
||||
", size = %zu)",
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory, "unmapped ZeroBlock @ 0x%08X (start address = 0x%08X, size = %zu)",
|
||||
current_vaddr, dest_addr, size);
|
||||
break;
|
||||
case PageType::Special:
|
||||
if (WriteSpecialBlock(process, current_vaddr, zeros.data(), copy_amount))
|
||||
break;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case PageType::Memory: {
|
||||
DEBUG_ASSERT(current_page_table->pointers[page_index]);
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
|
||||
u8* dest_ptr = current_page_table->pointers[page_index] + page_offset;
|
||||
u8* dest_ptr = page_table.pointers[page_index] + page_offset;
|
||||
std::memset(dest_ptr, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Invalidate);
|
||||
std::memset(GetPointerFromVMA(process, current_vaddr), 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
@@ -474,37 +583,34 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) {
|
||||
const auto& process = *Core::CurrentProcess();
|
||||
|
||||
void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, const size_t size) {
|
||||
auto& page_table = process.vm_manager.page_table;
|
||||
size_t remaining_size = size;
|
||||
size_t page_index = src_addr >> PAGE_BITS;
|
||||
size_t page_offset = src_addr & PAGE_MASK;
|
||||
|
||||
while (remaining_size > 0) {
|
||||
const size_t copy_amount = std::min<size_t>(PAGE_SIZE - page_offset, remaining_size);
|
||||
const size_t copy_amount =
|
||||
std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size);
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (current_page_table->attributes[page_index]) {
|
||||
case PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory,
|
||||
"unmapped CopyBlock @ 0x%016" PRIX64 " (start address = 0x%016" PRIX64
|
||||
", size = %zu)",
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory, "unmapped CopyBlock @ 0x%08X (start address = 0x%08X, size = %zu)",
|
||||
current_vaddr, src_addr, size);
|
||||
ZeroBlock(dest_addr, copy_amount);
|
||||
ZeroBlock(process, dest_addr, copy_amount);
|
||||
break;
|
||||
case PageType::Special: {
|
||||
std::vector<u8> buffer(copy_amount);
|
||||
if (ReadSpecialBlock(process, current_vaddr, buffer.data(), buffer.size())) {
|
||||
WriteBlock(dest_addr, buffer.data(), buffer.size());
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
case PageType::Memory: {
|
||||
DEBUG_ASSERT(current_page_table->pointers[page_index]);
|
||||
const u8* src_ptr = current_page_table->pointers[page_index] + page_offset;
|
||||
WriteBlock(dest_addr, src_ptr, copy_amount);
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
const u8* src_ptr = page_table.pointers[page_index] + page_offset;
|
||||
WriteBlock(process, dest_addr, src_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Flush);
|
||||
WriteBlock(process, dest_addr, GetPointerFromVMA(process, current_vaddr), copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -519,78 +625,6 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
boost::optional<u8> ReadSpecial<u8>(VAddr addr) {
|
||||
const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table;
|
||||
for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u8)))
|
||||
if (auto result = handler->Read8(addr))
|
||||
return *result;
|
||||
return {};
|
||||
}
|
||||
|
||||
template <>
|
||||
boost::optional<u16> ReadSpecial<u16>(VAddr addr) {
|
||||
const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table;
|
||||
for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u16)))
|
||||
if (auto result = handler->Read16(addr))
|
||||
return *result;
|
||||
return {};
|
||||
}
|
||||
|
||||
template <>
|
||||
boost::optional<u32> ReadSpecial<u32>(VAddr addr) {
|
||||
const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table;
|
||||
for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u32)))
|
||||
if (auto result = handler->Read32(addr))
|
||||
return *result;
|
||||
return {};
|
||||
}
|
||||
|
||||
template <>
|
||||
boost::optional<u64> ReadSpecial<u64>(VAddr addr) {
|
||||
const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table;
|
||||
for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u64)))
|
||||
if (auto result = handler->Read64(addr))
|
||||
return *result;
|
||||
return {};
|
||||
}
|
||||
|
||||
template <>
|
||||
bool WriteSpecial<u8>(VAddr addr, const u8 data) {
|
||||
const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table;
|
||||
for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u8)))
|
||||
if (handler->Write8(addr, data))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool WriteSpecial<u16>(VAddr addr, const u16 data) {
|
||||
const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table;
|
||||
for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u16)))
|
||||
if (handler->Write16(addr, data))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool WriteSpecial<u32>(VAddr addr, const u32 data) {
|
||||
const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table;
|
||||
for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u32)))
|
||||
if (handler->Write32(addr, data))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool WriteSpecial<u64>(VAddr addr, const u64 data) {
|
||||
const PageTable& page_table = Core::CurrentProcess()->vm_manager.page_table;
|
||||
for (const auto& handler : GetSpecialHandlers(page_table, addr, sizeof(u64)))
|
||||
if (handler->Write64(addr, data))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
|
||||
if (addr == 0) {
|
||||
return 0;
|
||||
|
||||
@@ -36,7 +36,10 @@ enum class PageType : u8 {
|
||||
Unmapped,
|
||||
/// Page is mapped to regular memory. This is the only type you can get pointers to.
|
||||
Memory,
|
||||
/// Page is mapped to a memory hook, which intercepts read and write requests.
|
||||
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
||||
/// invalidation
|
||||
RasterizerCachedMemory,
|
||||
/// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
|
||||
Special,
|
||||
};
|
||||
|
||||
@@ -242,4 +245,24 @@ boost::optional<VAddr> PhysicalToVirtualAddress(PAddr addr);
|
||||
*/
|
||||
u8* GetPhysicalPointer(PAddr address);
|
||||
|
||||
enum class FlushMode {
|
||||
/// Write back modified surfaces to RAM
|
||||
Flush,
|
||||
/// Remove region from the cache
|
||||
Invalidate,
|
||||
/// Write back modified surfaces to RAM, and also remove them from the cache
|
||||
FlushAndInvalidate,
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark each page touching the region as cached.
|
||||
*/
|
||||
void RasterizerMarkRegionCached(VAddr start, u64 size, bool cached);
|
||||
|
||||
/**
|
||||
* Flushes and invalidates any externally cached rasterizer resources touching the given virtual
|
||||
* address region.
|
||||
*/
|
||||
void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode);
|
||||
|
||||
} // namespace Memory
|
||||
|
||||
@@ -105,12 +105,10 @@ static const std::array<const char*, NumAnalogs> mapping = {{
|
||||
}};
|
||||
} // namespace NativeAnalog
|
||||
|
||||
enum class CpuCore {
|
||||
Unicorn,
|
||||
Dynarmic,
|
||||
};
|
||||
|
||||
struct Values {
|
||||
// System
|
||||
bool use_docked_mode;
|
||||
|
||||
// Controls
|
||||
std::array<std::string, NativeButton::NumButtons> buttons;
|
||||
std::array<std::string, NativeAnalog::NumAnalogs> analogs;
|
||||
@@ -118,7 +116,7 @@ struct Values {
|
||||
std::string touch_device;
|
||||
|
||||
// Core
|
||||
CpuCore cpu_core;
|
||||
bool use_cpu_jit;
|
||||
|
||||
// Data Storage
|
||||
bool use_virtual_sd;
|
||||
|
||||
@@ -154,12 +154,13 @@ TelemetrySession::TelemetrySession() {
|
||||
#endif
|
||||
|
||||
// Log user configuration information
|
||||
AddField(Telemetry::FieldType::UserConfig, "Core_CpuCore",
|
||||
static_cast<int>(Settings::values.cpu_core));
|
||||
AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
|
||||
Settings::values.resolution_factor);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit",
|
||||
Settings::values.toggle_framelimit);
|
||||
AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode",
|
||||
Settings::values.use_docked_mode);
|
||||
}
|
||||
|
||||
TelemetrySession::~TelemetrySession() {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
add_library(video_core STATIC
|
||||
command_processor.cpp
|
||||
command_processor.h
|
||||
debug_utils/debug_utils.cpp
|
||||
debug_utils/debug_utils.h
|
||||
engines/fermi_2d.cpp
|
||||
engines/fermi_2d.h
|
||||
engines/maxwell_3d.cpp
|
||||
@@ -29,8 +31,12 @@ add_library(video_core STATIC
|
||||
renderer_opengl/gl_state.h
|
||||
renderer_opengl/gl_stream_buffer.cpp
|
||||
renderer_opengl/gl_stream_buffer.h
|
||||
renderer_opengl/maxwell_to_gl.h
|
||||
renderer_opengl/renderer_opengl.cpp
|
||||
renderer_opengl/renderer_opengl.h
|
||||
textures/decoders.cpp
|
||||
textures/decoders.h
|
||||
textures/texture.h
|
||||
utils.h
|
||||
video_core.cpp
|
||||
video_core.h
|
||||
|
||||
64
src/video_core/debug_utils/debug_utils.cpp
Normal file
64
src/video_core/debug_utils/debug_utils.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/color.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
void DebugContext::DoOnEvent(Event event, void* data) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(breakpoint_mutex);
|
||||
|
||||
// TODO(Subv): Commit the rasterizer's caches so framebuffers, render targets, etc. will
|
||||
// show on debug widgets
|
||||
|
||||
// TODO: Should stop the CPU thread here once we multithread emulation.
|
||||
|
||||
active_breakpoint = event;
|
||||
at_breakpoint = true;
|
||||
|
||||
// Tell all observers that we hit a breakpoint
|
||||
for (auto& breakpoint_observer : breakpoint_observers) {
|
||||
breakpoint_observer->OnMaxwellBreakPointHit(event, data);
|
||||
}
|
||||
|
||||
// Wait until another thread tells us to Resume()
|
||||
resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; });
|
||||
}
|
||||
}
|
||||
|
||||
void DebugContext::Resume() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(breakpoint_mutex);
|
||||
|
||||
// Tell all observers that we are about to resume
|
||||
for (auto& breakpoint_observer : breakpoint_observers) {
|
||||
breakpoint_observer->OnMaxwellResume();
|
||||
}
|
||||
|
||||
// Resume the waiting thread (i.e. OnEvent())
|
||||
at_breakpoint = false;
|
||||
}
|
||||
|
||||
resume_from_breakpoint.notify_one();
|
||||
}
|
||||
|
||||
} // namespace Tegra
|
||||
163
src/video_core/debug_utils/debug_utils.h
Normal file
163
src/video_core/debug_utils/debug_utils.h
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/vector_math.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
class DebugContext {
|
||||
public:
|
||||
enum class Event {
|
||||
FirstEvent = 0,
|
||||
|
||||
MaxwellCommandLoaded = FirstEvent,
|
||||
MaxwellCommandProcessed,
|
||||
IncomingPrimitiveBatch,
|
||||
FinishedPrimitiveBatch,
|
||||
|
||||
NumEvents
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from this class to be notified of events registered to some debug context.
|
||||
* Most importantly this is used for our debugger GUI.
|
||||
*
|
||||
* To implement event handling, override the OnMaxwellBreakPointHit and OnMaxwellResume methods.
|
||||
* @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state
|
||||
* access
|
||||
* @todo Evaluate an alternative interface, in which there is only one managing observer and
|
||||
* multiple child observers running (by design) on the same thread.
|
||||
*/
|
||||
class BreakPointObserver {
|
||||
public:
|
||||
/// Constructs the object such that it observes events of the given DebugContext.
|
||||
BreakPointObserver(std::shared_ptr<DebugContext> debug_context)
|
||||
: context_weak(debug_context) {
|
||||
std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex);
|
||||
debug_context->breakpoint_observers.push_back(this);
|
||||
}
|
||||
|
||||
virtual ~BreakPointObserver() {
|
||||
auto context = context_weak.lock();
|
||||
if (context) {
|
||||
std::unique_lock<std::mutex> lock(context->breakpoint_mutex);
|
||||
context->breakpoint_observers.remove(this);
|
||||
|
||||
// If we are the last observer to be destroyed, tell the debugger context that
|
||||
// it is free to continue. In particular, this is required for a proper yuzu
|
||||
// shutdown, when the emulation thread is waiting at a breakpoint.
|
||||
if (context->breakpoint_observers.empty())
|
||||
context->Resume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to perform when a breakpoint was reached.
|
||||
* @param event Type of event which triggered the breakpoint
|
||||
* @param data Optional data pointer (if unused, this is a nullptr)
|
||||
* @note This function will perform nothing unless it is overridden in the child class.
|
||||
*/
|
||||
virtual void OnMaxwellBreakPointHit(Event event, void* data) {}
|
||||
|
||||
/**
|
||||
* Action to perform when emulation is resumed from a breakpoint.
|
||||
* @note This function will perform nothing unless it is overridden in the child class.
|
||||
*/
|
||||
virtual void OnMaxwellResume() {}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Weak context pointer. This need not be valid, so when requesting a shared_ptr via
|
||||
* context_weak.lock(), always compare the result against nullptr.
|
||||
*/
|
||||
std::weak_ptr<DebugContext> context_weak;
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple structure defining a breakpoint state
|
||||
*/
|
||||
struct BreakPoint {
|
||||
bool enabled = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Static constructor used to create a shared_ptr of a DebugContext.
|
||||
*/
|
||||
static std::shared_ptr<DebugContext> Construct() {
|
||||
return std::shared_ptr<DebugContext>(new DebugContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the emulation core when a given event has happened. If a breakpoint has been set
|
||||
* for this event, OnEvent calls the event handlers of the registered breakpoint observers.
|
||||
* The current thread then is halted until Resume() is called from another thread (or until
|
||||
* emulation is stopped).
|
||||
* @param event Event which has happened
|
||||
* @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until
|
||||
* Resume() is called.
|
||||
*/
|
||||
void OnEvent(Event event, void* data) {
|
||||
// This check is left in the header to allow the compiler to inline it.
|
||||
if (!breakpoints[(int)event].enabled)
|
||||
return;
|
||||
// For the rest of event handling, call a separate function.
|
||||
DoOnEvent(event, data);
|
||||
}
|
||||
|
||||
void DoOnEvent(Event event, void* data);
|
||||
|
||||
/**
|
||||
* Resume from the current breakpoint.
|
||||
* @warning Calling this from the same thread that OnEvent was called in will cause a deadlock.
|
||||
* Calling from any other thread is safe.
|
||||
*/
|
||||
void Resume();
|
||||
|
||||
/**
|
||||
* Delete all set breakpoints and resume emulation.
|
||||
*/
|
||||
void ClearBreakpoints() {
|
||||
for (auto& bp : breakpoints) {
|
||||
bp.enabled = false;
|
||||
}
|
||||
Resume();
|
||||
}
|
||||
|
||||
// TODO: Evaluate if access to these members should be hidden behind a public interface.
|
||||
std::array<BreakPoint, (int)Event::NumEvents> breakpoints;
|
||||
Event active_breakpoint;
|
||||
bool at_breakpoint = false;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Private default constructor to make sure people always construct this through Construct()
|
||||
* instead.
|
||||
*/
|
||||
DebugContext() = default;
|
||||
|
||||
/// Mutex protecting current breakpoint state and the observer list.
|
||||
std::mutex breakpoint_mutex;
|
||||
|
||||
/// Used by OnEvent to wait for resumption.
|
||||
std::condition_variable resume_from_breakpoint;
|
||||
|
||||
/// List of registered observers
|
||||
std::list<BreakPointObserver*> breakpoint_observers;
|
||||
};
|
||||
|
||||
} // namespace Tegra
|
||||
@@ -2,8 +2,16 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace Tegra {
|
||||
namespace Engines {
|
||||
@@ -46,6 +54,8 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
|
||||
ASSERT_MSG(method < Regs::NUM_REGS,
|
||||
"Invalid Maxwell3D register, increase the size of the Regs structure");
|
||||
|
||||
auto debug_context = Core::System::GetInstance().GetGPUDebugContext();
|
||||
|
||||
// It is an error to write to a register other than the current macro's ARG register before it
|
||||
// has finished execution.
|
||||
if (executing_macro != 0) {
|
||||
@@ -72,6 +82,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug_context) {
|
||||
debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr);
|
||||
}
|
||||
|
||||
regs.reg_array[method] = value;
|
||||
|
||||
#define MAXWELL3D_REG_INDEX(field_name) (offsetof(Regs, field_name) / sizeof(u32))
|
||||
@@ -137,6 +151,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
|
||||
}
|
||||
|
||||
#undef MAXWELL3D_REG_INDEX
|
||||
|
||||
if (debug_context) {
|
||||
debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessQueryGet() {
|
||||
@@ -159,7 +177,20 @@ void Maxwell3D::ProcessQueryGet() {
|
||||
}
|
||||
|
||||
void Maxwell3D::DrawArrays() {
|
||||
LOG_WARNING(HW_GPU, "Game requested a DrawArrays, ignoring");
|
||||
LOG_DEBUG(HW_GPU, "called, topology=%d, count=%d", regs.draw.topology.Value(),
|
||||
regs.vertex_buffer.count);
|
||||
|
||||
auto debug_context = Core::System::GetInstance().GetGPUDebugContext();
|
||||
|
||||
if (debug_context) {
|
||||
debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr);
|
||||
}
|
||||
|
||||
if (debug_context) {
|
||||
debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr);
|
||||
}
|
||||
|
||||
VideoCore::g_renderer->Rasterizer()->AccelerateDrawBatch(false /*is_indexed*/);
|
||||
}
|
||||
|
||||
void Maxwell3D::BindTextureInfoBuffer(const std::vector<u32>& parameters) {
|
||||
@@ -270,5 +301,90 @@ void Maxwell3D::ProcessCBData(u32 value) {
|
||||
regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4;
|
||||
}
|
||||
|
||||
Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
|
||||
GPUVAddr tic_base_address = regs.tic.TICAddress();
|
||||
|
||||
GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry);
|
||||
VAddr tic_address_cpu = memory_manager.PhysicalToVirtualAddress(tic_address_gpu);
|
||||
|
||||
Texture::TICEntry tic_entry;
|
||||
Memory::ReadBlock(tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry));
|
||||
|
||||
ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear,
|
||||
"TIC versions other than BlockLinear are unimplemented");
|
||||
|
||||
ASSERT_MSG(tic_entry.texture_type == Texture::TextureType::Texture2D,
|
||||
"Texture types other than Texture2D are unimplemented");
|
||||
|
||||
auto r_type = tic_entry.r_type.Value();
|
||||
auto g_type = tic_entry.g_type.Value();
|
||||
auto b_type = tic_entry.b_type.Value();
|
||||
auto a_type = tic_entry.a_type.Value();
|
||||
|
||||
// TODO(Subv): Different data types for separate components are not supported
|
||||
ASSERT(r_type == g_type && r_type == b_type && r_type == a_type);
|
||||
|
||||
return tic_entry;
|
||||
}
|
||||
|
||||
Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
|
||||
GPUVAddr tsc_base_address = regs.tsc.TSCAddress();
|
||||
|
||||
GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry);
|
||||
VAddr tsc_address_cpu = memory_manager.PhysicalToVirtualAddress(tsc_address_gpu);
|
||||
|
||||
Texture::TSCEntry tsc_entry;
|
||||
Memory::ReadBlock(tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry));
|
||||
return tsc_entry;
|
||||
}
|
||||
|
||||
std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderStage stage) const {
|
||||
std::vector<Texture::FullTextureInfo> textures;
|
||||
|
||||
auto& fragment_shader = state.shader_stages[static_cast<size_t>(stage)];
|
||||
auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index];
|
||||
ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0);
|
||||
|
||||
GPUVAddr tic_base_address = regs.tic.TICAddress();
|
||||
|
||||
GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size;
|
||||
|
||||
// Offset into the texture constbuffer where the texture info begins.
|
||||
static constexpr size_t TextureInfoOffset = 0x20;
|
||||
|
||||
for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset;
|
||||
current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) {
|
||||
|
||||
Texture::TextureHandle tex_handle{
|
||||
Memory::Read32(memory_manager.PhysicalToVirtualAddress(current_texture))};
|
||||
|
||||
Texture::FullTextureInfo tex_info{};
|
||||
// TODO(Subv): Use the shader to determine which textures are actually accessed.
|
||||
tex_info.index = (current_texture - tex_info_buffer.address - TextureInfoOffset) /
|
||||
sizeof(Texture::TextureHandle);
|
||||
|
||||
// Load the TIC data.
|
||||
if (tex_handle.tic_id != 0) {
|
||||
tex_info.enabled = true;
|
||||
|
||||
auto tic_entry = GetTICEntry(tex_handle.tic_id);
|
||||
// TODO(Subv): Workaround for BitField's move constructor being deleted.
|
||||
std::memcpy(&tex_info.tic, &tic_entry, sizeof(tic_entry));
|
||||
}
|
||||
|
||||
// Load the TSC data
|
||||
if (tex_handle.tsc_id != 0) {
|
||||
auto tsc_entry = GetTSCEntry(tex_handle.tsc_id);
|
||||
// TODO(Subv): Workaround for BitField's move constructor being deleted.
|
||||
std::memcpy(&tex_info.tsc, &tsc_entry, sizeof(tsc_entry));
|
||||
}
|
||||
|
||||
if (tex_info.enabled)
|
||||
textures.push_back(tex_info);
|
||||
}
|
||||
|
||||
return textures;
|
||||
}
|
||||
|
||||
} // namespace Engines
|
||||
} // namespace Tegra
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace Tegra {
|
||||
namespace Engines {
|
||||
@@ -20,18 +24,13 @@ public:
|
||||
explicit Maxwell3D(MemoryManager& memory_manager);
|
||||
~Maxwell3D() = default;
|
||||
|
||||
/// Write the value to the register identified by method.
|
||||
void WriteReg(u32 method, u32 value, u32 remaining_params);
|
||||
|
||||
/// Uploads the code for a GPU macro program associated with the specified entry.
|
||||
void SubmitMacroCode(u32 entry, std::vector<u32> code);
|
||||
|
||||
/// Register structure of the Maxwell3D engine.
|
||||
/// TODO(Subv): This structure will need to be made bigger as more registers are discovered.
|
||||
struct Regs {
|
||||
static constexpr size_t NUM_REGS = 0xE36;
|
||||
|
||||
static constexpr size_t NumRenderTargets = 8;
|
||||
static constexpr size_t NumViewports = 16;
|
||||
static constexpr size_t NumCBData = 16;
|
||||
static constexpr size_t NumVertexArrays = 32;
|
||||
static constexpr size_t NumVertexAttributes = 32;
|
||||
@@ -62,6 +61,192 @@ public:
|
||||
Fragment = 4,
|
||||
};
|
||||
|
||||
struct VertexAttribute {
|
||||
enum class Size : u32 {
|
||||
Size_32_32_32_32 = 0x01,
|
||||
Size_32_32_32 = 0x02,
|
||||
Size_16_16_16_16 = 0x03,
|
||||
Size_32_32 = 0x04,
|
||||
Size_16_16_16 = 0x05,
|
||||
Size_8_8_8_8 = 0x0a,
|
||||
Size_16_16 = 0x0f,
|
||||
Size_32 = 0x12,
|
||||
Size_8_8_8 = 0x13,
|
||||
Size_8_8 = 0x18,
|
||||
Size_16 = 0x1b,
|
||||
Size_8 = 0x1d,
|
||||
Size_10_10_10_2 = 0x30,
|
||||
Size_11_11_10 = 0x31,
|
||||
};
|
||||
|
||||
enum class Type : u32 {
|
||||
SignedNorm = 1,
|
||||
UnsignedNorm = 2,
|
||||
SignedInt = 3,
|
||||
UnsignedInt = 4,
|
||||
UnsignedScaled = 5,
|
||||
SignedScaled = 6,
|
||||
Float = 7,
|
||||
};
|
||||
|
||||
union {
|
||||
BitField<0, 5, u32> buffer;
|
||||
BitField<6, 1, u32> constant;
|
||||
BitField<7, 14, u32> offset;
|
||||
BitField<21, 6, Size> size;
|
||||
BitField<27, 3, Type> type;
|
||||
BitField<31, 1, u32> bgra;
|
||||
};
|
||||
|
||||
u32 ComponentCount() const {
|
||||
switch (size) {
|
||||
case Size::Size_32_32_32_32:
|
||||
return 4;
|
||||
case Size::Size_32_32_32:
|
||||
return 3;
|
||||
case Size::Size_16_16_16_16:
|
||||
return 4;
|
||||
case Size::Size_32_32:
|
||||
return 2;
|
||||
case Size::Size_16_16_16:
|
||||
return 3;
|
||||
case Size::Size_8_8_8_8:
|
||||
return 4;
|
||||
case Size::Size_16_16:
|
||||
return 2;
|
||||
case Size::Size_32:
|
||||
return 1;
|
||||
case Size::Size_8_8_8:
|
||||
return 3;
|
||||
case Size::Size_8_8:
|
||||
return 2;
|
||||
case Size::Size_16:
|
||||
return 1;
|
||||
case Size::Size_8:
|
||||
return 1;
|
||||
case Size::Size_10_10_10_2:
|
||||
return 4;
|
||||
case Size::Size_11_11_10:
|
||||
return 3;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
u32 SizeInBytes() const {
|
||||
switch (size) {
|
||||
case Size::Size_32_32_32_32:
|
||||
return 16;
|
||||
case Size::Size_32_32_32:
|
||||
return 12;
|
||||
case Size::Size_16_16_16_16:
|
||||
return 8;
|
||||
case Size::Size_32_32:
|
||||
return 8;
|
||||
case Size::Size_16_16_16:
|
||||
return 6;
|
||||
case Size::Size_8_8_8_8:
|
||||
return 4;
|
||||
case Size::Size_16_16:
|
||||
return 4;
|
||||
case Size::Size_32:
|
||||
return 4;
|
||||
case Size::Size_8_8_8:
|
||||
return 3;
|
||||
case Size::Size_8_8:
|
||||
return 2;
|
||||
case Size::Size_16:
|
||||
return 2;
|
||||
case Size::Size_8:
|
||||
return 1;
|
||||
case Size::Size_10_10_10_2:
|
||||
return 4;
|
||||
case Size::Size_11_11_10:
|
||||
return 4;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
std::string SizeString() const {
|
||||
switch (size) {
|
||||
case Size::Size_32_32_32_32:
|
||||
return "32_32_32_32";
|
||||
case Size::Size_32_32_32:
|
||||
return "32_32_32";
|
||||
case Size::Size_16_16_16_16:
|
||||
return "16_16_16_16";
|
||||
case Size::Size_32_32:
|
||||
return "32_32";
|
||||
case Size::Size_16_16_16:
|
||||
return "16_16_16";
|
||||
case Size::Size_8_8_8_8:
|
||||
return "8_8_8_8";
|
||||
case Size::Size_16_16:
|
||||
return "16_16";
|
||||
case Size::Size_32:
|
||||
return "32";
|
||||
case Size::Size_8_8_8:
|
||||
return "8_8_8";
|
||||
case Size::Size_8_8:
|
||||
return "8_8";
|
||||
case Size::Size_16:
|
||||
return "16";
|
||||
case Size::Size_8:
|
||||
return "8";
|
||||
case Size::Size_10_10_10_2:
|
||||
return "10_10_10_2";
|
||||
case Size::Size_11_11_10:
|
||||
return "11_11_10";
|
||||
}
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string TypeString() const {
|
||||
switch (type) {
|
||||
case Type::SignedNorm:
|
||||
return "SNORM";
|
||||
case Type::UnsignedNorm:
|
||||
return "UNORM";
|
||||
case Type::SignedInt:
|
||||
return "SINT";
|
||||
case Type::UnsignedInt:
|
||||
return "UINT";
|
||||
case Type::UnsignedScaled:
|
||||
return "USCALED";
|
||||
case Type::SignedScaled:
|
||||
return "SSCALED";
|
||||
case Type::Float:
|
||||
return "FLOAT";
|
||||
}
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
bool IsNormalized() const {
|
||||
return (type == Type::SignedNorm) || (type == Type::UnsignedNorm);
|
||||
}
|
||||
};
|
||||
|
||||
enum class PrimitiveTopology : u32 {
|
||||
Points = 0x0,
|
||||
Lines = 0x1,
|
||||
LineLoop = 0x2,
|
||||
LineStrip = 0x3,
|
||||
Triangles = 0x4,
|
||||
TriangleStrip = 0x5,
|
||||
TriangleFan = 0x6,
|
||||
Quads = 0x7,
|
||||
QuadStrip = 0x8,
|
||||
Polygon = 0x9,
|
||||
LinesAdjacency = 0xa,
|
||||
LineStripAdjacency = 0xb,
|
||||
TrianglesAdjacency = 0xc,
|
||||
TriangleStripAdjacency = 0xd,
|
||||
Patches = 0xe,
|
||||
};
|
||||
|
||||
union {
|
||||
struct {
|
||||
INSERT_PADDING_WORDS(0x200);
|
||||
@@ -69,9 +254,9 @@ public:
|
||||
struct {
|
||||
u32 address_high;
|
||||
u32 address_low;
|
||||
u32 horiz;
|
||||
u32 vert;
|
||||
u32 format;
|
||||
u32 width;
|
||||
u32 height;
|
||||
Tegra::RenderTargetFormat format;
|
||||
u32 block_dimensions;
|
||||
u32 array_mode;
|
||||
u32 layer_stride;
|
||||
@@ -84,7 +269,31 @@ public:
|
||||
}
|
||||
} rt[NumRenderTargets];
|
||||
|
||||
INSERT_PADDING_WORDS(0xDD);
|
||||
INSERT_PADDING_WORDS(0x80);
|
||||
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 16, u32> x;
|
||||
BitField<16, 16, u32> width;
|
||||
};
|
||||
union {
|
||||
BitField<0, 16, u32> y;
|
||||
BitField<16, 16, u32> height;
|
||||
};
|
||||
float depth_range_near;
|
||||
float depth_range_far;
|
||||
|
||||
MathUtil::Rectangle<s32> GetRect() const {
|
||||
return {
|
||||
static_cast<s32>(x), // left
|
||||
static_cast<s32>(y + height), // top
|
||||
static_cast<s32>(x + width), // right
|
||||
static_cast<s32>(y) // bottom
|
||||
};
|
||||
};
|
||||
} viewport[NumViewports];
|
||||
|
||||
INSERT_PADDING_WORDS(0x1D);
|
||||
|
||||
struct {
|
||||
u32 first;
|
||||
@@ -108,14 +317,7 @@ public:
|
||||
|
||||
INSERT_PADDING_WORDS(0x5B);
|
||||
|
||||
union {
|
||||
BitField<0, 5, u32> buffer;
|
||||
BitField<6, 1, u32> constant;
|
||||
BitField<7, 14, u32> offset;
|
||||
BitField<21, 6, u32> size;
|
||||
BitField<27, 3, u32> type;
|
||||
BitField<31, 1, u32> bgra;
|
||||
} vertex_attrib_format[NumVertexAttributes];
|
||||
VertexAttribute vertex_attrib_format[NumVertexAttributes];
|
||||
|
||||
INSERT_PADDING_WORDS(0xF);
|
||||
|
||||
@@ -163,13 +365,15 @@ public:
|
||||
}
|
||||
} code_address;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
|
||||
struct {
|
||||
u32 vertex_end_gl;
|
||||
union {
|
||||
u32 vertex_begin_gl;
|
||||
BitField<0, 16, u32> topology;
|
||||
BitField<0, 16, PrimitiveTopology> topology;
|
||||
};
|
||||
} draw;
|
||||
|
||||
INSERT_PADDING_WORDS(0x139);
|
||||
struct {
|
||||
u32 query_address_high;
|
||||
@@ -310,6 +514,15 @@ public:
|
||||
|
||||
State state{};
|
||||
|
||||
/// Write the value to the register identified by method.
|
||||
void WriteReg(u32 method, u32 value, u32 remaining_params);
|
||||
|
||||
/// Uploads the code for a GPU macro program associated with the specified entry.
|
||||
void SubmitMacroCode(u32 entry, std::vector<u32> code);
|
||||
|
||||
/// Returns a list of enabled textures for the specified shader stage.
|
||||
std::vector<Texture::FullTextureInfo> GetStageTextures(Regs::ShaderStage stage) const;
|
||||
|
||||
private:
|
||||
MemoryManager& memory_manager;
|
||||
|
||||
@@ -320,6 +533,12 @@ private:
|
||||
/// Parameters that have been submitted to the macro call so far.
|
||||
std::vector<u32> macro_params;
|
||||
|
||||
/// Retrieves information about a specific TIC entry from the TIC buffer.
|
||||
Texture::TICEntry GetTICEntry(u32 tic_index) const;
|
||||
|
||||
/// Retrieves information about a specific TSC entry from the TSC buffer.
|
||||
Texture::TSCEntry GetTSCEntry(u32 tsc_index) const;
|
||||
|
||||
/**
|
||||
* Call a macro on this engine.
|
||||
* @param method Method to call
|
||||
@@ -358,6 +577,7 @@ private:
|
||||
"Field " #field_name " has invalid position")
|
||||
|
||||
ASSERT_REG_POSITION(rt, 0x200);
|
||||
ASSERT_REG_POSITION(viewport, 0x300);
|
||||
ASSERT_REG_POSITION(vertex_buffer, 0x35D);
|
||||
ASSERT_REG_POSITION(zeta, 0x3F8);
|
||||
ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458);
|
||||
|
||||
@@ -18,4 +18,8 @@ GPU::GPU() {
|
||||
|
||||
GPU::~GPU() = default;
|
||||
|
||||
const Tegra::Engines::Maxwell3D& GPU::Get3DEngine() const {
|
||||
return *maxwell_3d;
|
||||
}
|
||||
|
||||
} // namespace Tegra
|
||||
|
||||
@@ -8,10 +8,49 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/nvflinger/buffer_queue.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
enum class RenderTargetFormat : u32 {
|
||||
NONE = 0x0,
|
||||
RGBA8_UNORM = 0xD5,
|
||||
};
|
||||
|
||||
class DebugContext;
|
||||
|
||||
/**
|
||||
* Struct describing framebuffer configuration
|
||||
*/
|
||||
struct FramebufferConfig {
|
||||
enum class PixelFormat : u32 {
|
||||
ABGR8 = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the number of bytes per pixel.
|
||||
*/
|
||||
static u32 BytesPerPixel(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::ABGR8:
|
||||
return 4;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
VAddr address;
|
||||
u32 offset;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 stride;
|
||||
PixelFormat pixel_format;
|
||||
|
||||
using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags;
|
||||
TransformFlags transform_flags;
|
||||
};
|
||||
|
||||
namespace Engines {
|
||||
class Fermi2D;
|
||||
class Maxwell3D;
|
||||
@@ -34,8 +73,15 @@ public:
|
||||
/// Processes a command list stored at the specified address in GPU memory.
|
||||
void ProcessCommandList(GPUVAddr address, u32 size);
|
||||
|
||||
/// Returns a reference to the Maxwell3D GPU engine.
|
||||
const Engines::Maxwell3D& Get3DEngine() const;
|
||||
|
||||
std::unique_ptr<MemoryManager> memory_manager;
|
||||
|
||||
Engines::Maxwell3D& Maxwell3D() {
|
||||
return *maxwell_3d;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr u32 InvalidGraphMacroEntry = 0xFFFFFFFF;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
struct ScreenInfo;
|
||||
|
||||
@@ -14,8 +15,8 @@ class RasterizerInterface {
|
||||
public:
|
||||
virtual ~RasterizerInterface() {}
|
||||
|
||||
/// Draw the current batch of triangles
|
||||
virtual void DrawTriangles() = 0;
|
||||
/// Draw the current batch of vertex arrays
|
||||
virtual void DrawArrays() = 0;
|
||||
|
||||
/// Notify rasterizer that the specified Maxwell register has been changed
|
||||
virtual void NotifyMaxwellRegisterChanged(u32 id) = 0;
|
||||
@@ -24,14 +25,14 @@ public:
|
||||
virtual void FlushAll() = 0;
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory
|
||||
virtual void FlushRegion(PAddr addr, u32 size) = 0;
|
||||
virtual void FlushRegion(VAddr addr, u64 size) = 0;
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be invalidated
|
||||
virtual void InvalidateRegion(PAddr addr, u32 size) = 0;
|
||||
virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory
|
||||
/// and invalidated
|
||||
virtual void FlushAndInvalidateRegion(PAddr addr, u32 size) = 0;
|
||||
virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
|
||||
|
||||
/// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0
|
||||
virtual bool AccelerateDisplayTransfer(const void* config) {
|
||||
@@ -49,7 +50,8 @@ public:
|
||||
}
|
||||
|
||||
/// Attempt to use a faster method to display the framebuffer to screen
|
||||
virtual bool AccelerateDisplay(const void* config, PAddr framebuffer_addr, u32 pixel_stride,
|
||||
virtual bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer,
|
||||
VAddr framebuffer_addr, u32 pixel_stride,
|
||||
ScreenInfo& screen_info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
void RendererBase::RefreshRasterizerSetting() {}
|
||||
void RendererBase::RefreshRasterizerSetting() {
|
||||
if (rasterizer == nullptr) {
|
||||
rasterizer = std::make_unique<RasterizerOpenGL>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
class EmuWindow;
|
||||
|
||||
@@ -16,40 +18,10 @@ public:
|
||||
/// Used to reference a framebuffer
|
||||
enum kFramebuffer { kFramebuffer_VirtualXFB = 0, kFramebuffer_EFB, kFramebuffer_Texture };
|
||||
|
||||
/**
|
||||
* Struct describing framebuffer metadata
|
||||
* TODO(bunnei): This struct belongs in the GPU code, but we don't have a good place for it yet.
|
||||
*/
|
||||
struct FramebufferInfo {
|
||||
enum class PixelFormat : u32 {
|
||||
ABGR8 = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the number of bytes per pixel.
|
||||
*/
|
||||
static u32 BytesPerPixel(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::ABGR8:
|
||||
return 4;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
VAddr address;
|
||||
u32 offset;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 stride;
|
||||
PixelFormat pixel_format;
|
||||
bool flip_vertical;
|
||||
};
|
||||
|
||||
virtual ~RendererBase() {}
|
||||
|
||||
/// Swap buffers (render frame)
|
||||
virtual void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) = 0;
|
||||
virtual void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) = 0;
|
||||
|
||||
/**
|
||||
* Set the emulator window to use for renderer
|
||||
@@ -74,12 +46,16 @@ public:
|
||||
return m_current_frame;
|
||||
}
|
||||
|
||||
VideoCore::RasterizerInterface* Rasterizer() const {
|
||||
return rasterizer.get();
|
||||
}
|
||||
|
||||
void RefreshRasterizerSetting();
|
||||
|
||||
protected:
|
||||
std::unique_ptr<VideoCore::RasterizerInterface> rasterizer;
|
||||
f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer
|
||||
int m_current_frame = 0; ///< Current frame, should be set by the renderer
|
||||
|
||||
private:
|
||||
bool opengl_rasterizer_active = false;
|
||||
};
|
||||
|
||||
@@ -14,11 +14,16 @@
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/renderer_opengl/maxwell_to_gl.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
using PixelFormat = SurfaceParams::PixelFormat;
|
||||
using SurfaceType = SurfaceParams::SurfaceType;
|
||||
|
||||
@@ -54,6 +59,8 @@ static void SetShaderUniformBlockBindings(GLuint shader) {
|
||||
}
|
||||
|
||||
RasterizerOpenGL::RasterizerOpenGL() {
|
||||
shader_dirty = true;
|
||||
|
||||
has_ARB_buffer_storage = false;
|
||||
has_ARB_direct_state_access = false;
|
||||
has_ARB_separate_shader_objects = false;
|
||||
@@ -106,8 +113,6 @@ RasterizerOpenGL::RasterizerOpenGL() {
|
||||
state.draw.vertex_buffer = stream_buffer->GetHandle();
|
||||
|
||||
pipeline.Create();
|
||||
vs_input_index_min = 0;
|
||||
vs_input_index_max = 0;
|
||||
state.draw.program_pipeline = pipeline.handle;
|
||||
state.draw.shader_program = 0;
|
||||
state.draw.vertex_array = hw_vao.handle;
|
||||
@@ -120,20 +125,14 @@ RasterizerOpenGL::RasterizerOpenGL() {
|
||||
glBufferData(GL_UNIFORM_BUFFER, sizeof(VSUniformData), nullptr, GL_STREAM_COPY);
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, 1, vs_uniform_buffer.handle);
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
accelerate_draw = AccelDraw::Disabled;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
// Sync fixed function OpenGL state
|
||||
SyncClipEnabled();
|
||||
SyncClipCoef();
|
||||
SyncCullMode();
|
||||
SyncBlendEnabled();
|
||||
SyncBlendFuncs();
|
||||
SyncBlendColor();
|
||||
LOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!");
|
||||
}
|
||||
|
||||
RasterizerOpenGL::~RasterizerOpenGL() {
|
||||
@@ -144,47 +143,235 @@ RasterizerOpenGL::~RasterizerOpenGL() {
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::array<GLenum, 4> vs_attrib_types{
|
||||
GL_BYTE, // VertexAttributeFormat::BYTE
|
||||
GL_UNSIGNED_BYTE, // VertexAttributeFormat::UBYTE
|
||||
GL_SHORT, // VertexAttributeFormat::SHORT
|
||||
GL_FLOAT // VertexAttributeFormat::FLOAT
|
||||
};
|
||||
|
||||
void RasterizerOpenGL::AnalyzeVertexArray(bool is_indexed) {
|
||||
UNIMPLEMENTED();
|
||||
const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
if (is_indexed) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
// TODO(bunnei): Add support for 1+ vertex arrays
|
||||
vs_input_size = regs.vertex_buffer.count * regs.vertex_array[0].stride;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupVertexArray(u8* array_ptr, GLintptr buffer_offset) {
|
||||
MICROPROFILE_SCOPE(OpenGL_VAO);
|
||||
UNIMPLEMENTED();
|
||||
const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& memory_manager = Core::System().GetInstance().GPU().memory_manager;
|
||||
|
||||
state.draw.vertex_array = hw_vao.handle;
|
||||
state.draw.vertex_buffer = stream_buffer->GetHandle();
|
||||
state.Apply();
|
||||
|
||||
// TODO(bunnei): Add support for 1+ vertex arrays
|
||||
const auto& vertex_array{regs.vertex_array[0]};
|
||||
ASSERT_MSG(vertex_array.enable, "vertex array 0 is disabled?");
|
||||
ASSERT_MSG(!vertex_array.divisor, "vertex array 0 divisor is unimplemented!");
|
||||
for (unsigned index = 1; index < Maxwell::NumVertexArrays; ++index) {
|
||||
ASSERT_MSG(!regs.vertex_array[index].enable, "vertex array %d is unimplemented!", index);
|
||||
}
|
||||
|
||||
// Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL.
|
||||
// Enables the first 16 vertex attributes always, as we don't know which ones are actually used
|
||||
// until shader time. Note, Tegra technically supports 32, but we're cappinig this to 16 for now
|
||||
// to avoid OpenGL errors.
|
||||
for (unsigned index = 0; index < 16; ++index) {
|
||||
auto& attrib = regs.vertex_attrib_format[index];
|
||||
glVertexAttribPointer(index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib),
|
||||
attrib.IsNormalized() ? GL_TRUE : GL_FALSE, vertex_array.stride,
|
||||
reinterpret_cast<GLvoid*>(buffer_offset + attrib.offset));
|
||||
glEnableVertexAttribArray(index);
|
||||
hw_vao_enabled_attributes[index] = true;
|
||||
}
|
||||
|
||||
// Copy vertex array data
|
||||
const u32 data_size{vertex_array.stride * regs.vertex_buffer.count};
|
||||
const VAddr data_addr{memory_manager->PhysicalToVirtualAddress(vertex_array.StartAddress())};
|
||||
res_cache.FlushRegion(data_addr, data_size, nullptr);
|
||||
Memory::ReadBlock(data_addr, array_ptr, data_size);
|
||||
|
||||
array_ptr += data_size;
|
||||
buffer_offset += data_size;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupVertexShader(VSUniformData* ub_ptr, GLintptr buffer_offset) {
|
||||
MICROPROFILE_SCOPE(OpenGL_VS);
|
||||
UNIMPLEMENTED();
|
||||
LOG_CRITICAL(Render_OpenGL, "Emulated shaders are not supported! Using a passthrough shader.");
|
||||
glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current_shader->shader.handle);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupFragmentShader(FSUniformData* ub_ptr, GLintptr buffer_offset) {
|
||||
MICROPROFILE_SCOPE(OpenGL_FS);
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) {
|
||||
if (!has_ARB_separate_shader_objects) {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
return false;
|
||||
}
|
||||
|
||||
accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
|
||||
DrawTriangles();
|
||||
DrawArrays();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::DrawTriangles() {
|
||||
void RasterizerOpenGL::DrawArrays() {
|
||||
if (accelerate_draw == AccelDraw::Disabled)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_Drawing);
|
||||
UNIMPLEMENTED();
|
||||
const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
// TODO(bunnei): Implement these
|
||||
const bool has_stencil = false;
|
||||
const bool using_color_fb = true;
|
||||
const bool using_depth_fb = false;
|
||||
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport[0].GetRect()};
|
||||
|
||||
const bool write_color_fb =
|
||||
state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE ||
|
||||
state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE;
|
||||
|
||||
const bool write_depth_fb =
|
||||
(state.depth.test_enabled && state.depth.write_mask == GL_TRUE) ||
|
||||
(has_stencil && state.stencil.test_enabled && state.stencil.write_mask != 0);
|
||||
|
||||
Surface color_surface;
|
||||
Surface depth_surface;
|
||||
MathUtil::Rectangle<u32> surfaces_rect;
|
||||
std::tie(color_surface, depth_surface, surfaces_rect) =
|
||||
res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect);
|
||||
|
||||
const u16 res_scale = color_surface != nullptr
|
||||
? color_surface->res_scale
|
||||
: (depth_surface == nullptr ? 1u : depth_surface->res_scale);
|
||||
|
||||
MathUtil::Rectangle<u32> draw_rect{
|
||||
static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.left) +
|
||||
viewport_rect.left * res_scale,
|
||||
surfaces_rect.left, surfaces_rect.right)), // Left
|
||||
static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.bottom) +
|
||||
viewport_rect.top * res_scale,
|
||||
surfaces_rect.bottom, surfaces_rect.top)), // Top
|
||||
static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.left) +
|
||||
viewport_rect.right * res_scale,
|
||||
surfaces_rect.left, surfaces_rect.right)), // Right
|
||||
static_cast<u32>(MathUtil::Clamp<s32>(static_cast<s32>(surfaces_rect.bottom) +
|
||||
viewport_rect.bottom * res_scale,
|
||||
surfaces_rect.bottom, surfaces_rect.top))}; // Bottom
|
||||
|
||||
// Bind the framebuffer surfaces
|
||||
BindFramebufferSurfaces(color_surface, depth_surface, has_stencil);
|
||||
|
||||
// Sync the viewport
|
||||
SyncViewport(surfaces_rect, res_scale);
|
||||
|
||||
// TODO(bunnei): Sync framebuffer_scale uniform here
|
||||
// TODO(bunnei): Sync scissorbox uniform(s) here
|
||||
// TODO(bunnei): Sync and bind the texture surfaces
|
||||
|
||||
// Sync and bind the shader
|
||||
if (shader_dirty) {
|
||||
SetShader();
|
||||
shader_dirty = false;
|
||||
}
|
||||
|
||||
// Sync the uniform data
|
||||
if (uniform_block_data.dirty) {
|
||||
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(UniformData), &uniform_block_data.data);
|
||||
uniform_block_data.dirty = false;
|
||||
}
|
||||
|
||||
// Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. Enable
|
||||
// scissor test to prevent drawing outside of the framebuffer region
|
||||
state.scissor.enabled = true;
|
||||
state.scissor.x = draw_rect.left;
|
||||
state.scissor.y = draw_rect.bottom;
|
||||
state.scissor.width = draw_rect.GetWidth();
|
||||
state.scissor.height = draw_rect.GetHeight();
|
||||
state.Apply();
|
||||
|
||||
// Draw the vertex batch
|
||||
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
|
||||
AnalyzeVertexArray(is_indexed);
|
||||
state.draw.vertex_buffer = stream_buffer->GetHandle();
|
||||
state.Apply();
|
||||
|
||||
size_t buffer_size = static_cast<size_t>(vs_input_size);
|
||||
if (is_indexed) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
buffer_size += sizeof(VSUniformData);
|
||||
|
||||
size_t ptr_pos = 0;
|
||||
u8* buffer_ptr;
|
||||
GLintptr buffer_offset;
|
||||
std::tie(buffer_ptr, buffer_offset) =
|
||||
stream_buffer->Map(static_cast<GLsizeiptr>(buffer_size), 4);
|
||||
|
||||
SetupVertexArray(buffer_ptr, buffer_offset);
|
||||
ptr_pos += vs_input_size;
|
||||
|
||||
GLintptr index_buffer_offset = 0;
|
||||
if (is_indexed) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
SetupVertexShader(reinterpret_cast<VSUniformData*>(&buffer_ptr[ptr_pos]),
|
||||
buffer_offset + static_cast<GLintptr>(ptr_pos));
|
||||
const GLintptr vs_ubo_offset = buffer_offset + static_cast<GLintptr>(ptr_pos);
|
||||
ptr_pos += sizeof(VSUniformData);
|
||||
|
||||
stream_buffer->Unmap();
|
||||
|
||||
const auto copy_buffer = [&](GLuint handle, GLintptr offset, GLsizeiptr size) {
|
||||
if (has_ARB_direct_state_access) {
|
||||
glCopyNamedBufferSubData(stream_buffer->GetHandle(), handle, offset, 0, size);
|
||||
} else {
|
||||
glBindBuffer(GL_COPY_WRITE_BUFFER, handle);
|
||||
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, offset, 0, size);
|
||||
}
|
||||
};
|
||||
|
||||
copy_buffer(vs_uniform_buffer.handle, vs_ubo_offset, sizeof(VSUniformData));
|
||||
|
||||
glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current_shader->shader.handle);
|
||||
|
||||
if (is_indexed) {
|
||||
UNREACHABLE();
|
||||
} else {
|
||||
glDrawArrays(MaxwellToGL::PrimitiveTopology(regs.draw.topology), 0,
|
||||
regs.vertex_buffer.count);
|
||||
}
|
||||
|
||||
// Disable scissor test
|
||||
state.scissor.enabled = false;
|
||||
|
||||
accelerate_draw = AccelDraw::Disabled;
|
||||
|
||||
// Unbind textures for potential future use as framebuffer attachments
|
||||
for (auto& texture_unit : state.texture_units) {
|
||||
texture_unit.texture_2d = 0;
|
||||
}
|
||||
state.Apply();
|
||||
|
||||
// Mark framebuffer surfaces as dirty
|
||||
MathUtil::Rectangle<u32> draw_rect_unscaled{
|
||||
draw_rect.left / res_scale, draw_rect.top / res_scale, draw_rect.right / res_scale,
|
||||
draw_rect.bottom / res_scale};
|
||||
|
||||
if (color_surface != nullptr && write_color_fb) {
|
||||
auto interval = color_surface->GetSubRectInterval(draw_rect_unscaled);
|
||||
res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval),
|
||||
color_surface);
|
||||
}
|
||||
if (depth_surface != nullptr && write_depth_fb) {
|
||||
auto interval = depth_surface->GetSubRectInterval(draw_rect_unscaled);
|
||||
res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval),
|
||||
depth_surface);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 id) {}
|
||||
@@ -194,17 +381,17 @@ void RasterizerOpenGL::FlushAll() {
|
||||
res_cache.FlushAll();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) {
|
||||
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
res_cache.FlushRegion(addr, size);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::InvalidateRegion(PAddr addr, u32 size) {
|
||||
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
res_cache.InvalidateRegion(addr, size, nullptr);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) {
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
res_cache.FlushRegion(addr, size);
|
||||
res_cache.InvalidateRegion(addr, size, nullptr);
|
||||
@@ -212,58 +399,180 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) {
|
||||
|
||||
bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Blits);
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RasterizerOpenGL::AccelerateTextureCopy(const void* config) {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RasterizerOpenGL::AccelerateFill(const void* config) {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RasterizerOpenGL::AccelerateDisplay(const void* config, PAddr framebuffer_addr,
|
||||
u32 pixel_stride, ScreenInfo& screen_info) {
|
||||
UNIMPLEMENTED();
|
||||
bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer,
|
||||
VAddr framebuffer_addr, u32 pixel_stride,
|
||||
ScreenInfo& screen_info) {
|
||||
if (framebuffer_addr == 0) {
|
||||
return false;
|
||||
}
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
|
||||
SurfaceParams src_params;
|
||||
src_params.addr = framebuffer_addr;
|
||||
src_params.width = std::min(framebuffer.width, pixel_stride);
|
||||
src_params.height = framebuffer.height;
|
||||
src_params.stride = pixel_stride;
|
||||
src_params.is_tiled = false;
|
||||
src_params.pixel_format =
|
||||
SurfaceParams::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format);
|
||||
src_params.UpdateParams();
|
||||
|
||||
MathUtil::Rectangle<u32> src_rect;
|
||||
Surface src_surface;
|
||||
std::tie(src_surface, src_rect) =
|
||||
res_cache.GetSurfaceSubRect(src_params, ScaleMatch::Ignore, true);
|
||||
|
||||
if (src_surface == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 scaled_width = src_surface->GetScaledWidth();
|
||||
u32 scaled_height = src_surface->GetScaledHeight();
|
||||
|
||||
screen_info.display_texcoords = MathUtil::Rectangle<float>(
|
||||
(float)src_rect.bottom / (float)scaled_height, (float)src_rect.left / (float)scaled_width,
|
||||
(float)src_rect.top / (float)scaled_height, (float)src_rect.right / (float)scaled_width);
|
||||
|
||||
screen_info.display_texture = src_surface->texture.handle;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetShader() {
|
||||
UNIMPLEMENTED();
|
||||
// TODO(bunnei): The below sets up a static test shader for passing untransformed vertices to
|
||||
// OpenGL for rendering. This should be removed/replaced when we start emulating Maxwell
|
||||
// shaders.
|
||||
|
||||
static constexpr char vertex_shader[] = R"(
|
||||
#version 150 core
|
||||
|
||||
in vec2 vert_position;
|
||||
in vec2 vert_tex_coord;
|
||||
out vec2 frag_tex_coord;
|
||||
|
||||
void main() {
|
||||
// Multiply input position by the rotscale part of the matrix and then manually translate by
|
||||
// the last column. This is equivalent to using a full 3x3 matrix and expanding the vector
|
||||
// to `vec3(vert_position.xy, 1.0)`
|
||||
gl_Position = vec4(mat2(mat3x2(0.0015625f, 0.0, 0.0, -0.0027778, -1.0, 1.0)) * vert_position + mat3x2(0.0015625f, 0.0, 0.0, -0.0027778, -1.0, 1.0)[2], 0.0, 1.0);
|
||||
frag_tex_coord = vert_tex_coord;
|
||||
}
|
||||
)";
|
||||
|
||||
static constexpr char fragment_shader[] = R"(
|
||||
#version 150 core
|
||||
|
||||
in vec2 frag_tex_coord;
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D color_texture;
|
||||
|
||||
void main() {
|
||||
color = vec4(1.0, 0.0, 1.0, 0.0);
|
||||
}
|
||||
)";
|
||||
|
||||
if (current_shader) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Render_OpenGL, "Emulated shaders are not supported! Using a passthrough shader.");
|
||||
|
||||
current_shader = &test_shader;
|
||||
if (has_ARB_separate_shader_objects) {
|
||||
test_shader.shader.Create(vertex_shader, nullptr, fragment_shader, {}, true);
|
||||
glActiveShaderProgram(pipeline.handle, test_shader.shader.handle);
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
state.draw.shader_program = test_shader.shader.handle;
|
||||
state.Apply();
|
||||
|
||||
if (has_ARB_separate_shader_objects) {
|
||||
state.draw.shader_program = 0;
|
||||
state.Apply();
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::BindFramebufferSurfaces(const Surface& color_surface,
|
||||
const Surface& depth_surface, bool has_stencil) {
|
||||
state.draw.draw_framebuffer = framebuffer.handle;
|
||||
state.Apply();
|
||||
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
color_surface != nullptr ? color_surface->texture.handle : 0, 0);
|
||||
if (depth_surface != nullptr) {
|
||||
if (has_stencil) {
|
||||
// attach both depth and stencil
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
||||
depth_surface->texture.handle, 0);
|
||||
} else {
|
||||
// attach depth
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
|
||||
depth_surface->texture.handle, 0);
|
||||
// clear stencil attachment
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
||||
}
|
||||
} else {
|
||||
// clear both depth and stencil attachment
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale) {
|
||||
const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
|
||||
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport[0].GetRect()};
|
||||
|
||||
state.viewport.x = static_cast<GLint>(surfaces_rect.left) + viewport_rect.left * res_scale;
|
||||
state.viewport.y = static_cast<GLint>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale;
|
||||
state.viewport.width = static_cast<GLsizei>(viewport_rect.GetWidth() * res_scale);
|
||||
state.viewport.height = static_cast<GLsizei>(viewport_rect.GetHeight() * res_scale);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncClipEnabled() {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncClipCoef() {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncCullMode() {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncDepthScale() {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncDepthOffset() {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncBlendEnabled() {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncBlendFuncs() {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncBlendColor() {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
@@ -29,19 +29,25 @@ public:
|
||||
RasterizerOpenGL();
|
||||
~RasterizerOpenGL() override;
|
||||
|
||||
void DrawTriangles() override;
|
||||
void DrawArrays() override;
|
||||
void NotifyMaxwellRegisterChanged(u32 id) override;
|
||||
void FlushAll() override;
|
||||
void FlushRegion(PAddr addr, u32 size) override;
|
||||
void InvalidateRegion(PAddr addr, u32 size) override;
|
||||
void FlushAndInvalidateRegion(PAddr addr, u32 size) override;
|
||||
void FlushRegion(VAddr addr, u64 size) override;
|
||||
void InvalidateRegion(VAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
|
||||
bool AccelerateDisplayTransfer(const void* config) override;
|
||||
bool AccelerateTextureCopy(const void* config) override;
|
||||
bool AccelerateFill(const void* config) override;
|
||||
bool AccelerateDisplay(const void* config, PAddr framebuffer_addr, u32 pixel_stride,
|
||||
ScreenInfo& screen_info) override;
|
||||
bool AccelerateDisplay(const Tegra::FramebufferConfig& framebuffer, VAddr framebuffer_addr,
|
||||
u32 pixel_stride, ScreenInfo& screen_info) override;
|
||||
bool AccelerateDrawBatch(bool is_indexed) override;
|
||||
|
||||
/// OpenGL shader generated for a given Maxwell register state
|
||||
struct MaxwellShader {
|
||||
/// OpenGL shader resource
|
||||
OGLShader shader;
|
||||
};
|
||||
|
||||
struct VertexShader {
|
||||
OGLShader shader;
|
||||
};
|
||||
@@ -81,6 +87,13 @@ public:
|
||||
private:
|
||||
struct SamplerInfo {};
|
||||
|
||||
/// Binds the framebuffer color and depth surface
|
||||
void BindFramebufferSurfaces(const Surface& color_surface, const Surface& depth_surface,
|
||||
bool has_stencil);
|
||||
|
||||
/// Syncs the viewport to match the guest state
|
||||
void SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale);
|
||||
|
||||
/// Syncs the clip enabled status to match the guest state
|
||||
void SyncClipEnabled();
|
||||
|
||||
@@ -117,6 +130,12 @@ private:
|
||||
|
||||
RasterizerCacheOpenGL res_cache;
|
||||
|
||||
/// Shader used for test renderering - to be removed once we have emulated shaders
|
||||
MaxwellShader test_shader{};
|
||||
|
||||
const MaxwellShader* current_shader{};
|
||||
bool shader_dirty{};
|
||||
|
||||
struct {
|
||||
UniformData data;
|
||||
bool dirty;
|
||||
@@ -127,7 +146,7 @@ private:
|
||||
OGLVertexArray hw_vao;
|
||||
std::array<bool, 16> hw_vao_enabled_attributes;
|
||||
|
||||
std::array<SamplerInfo, 3> texture_samplers;
|
||||
std::array<SamplerInfo, 32> texture_samplers;
|
||||
static constexpr size_t VERTEX_BUFFER_SIZE = 128 * 1024 * 1024;
|
||||
std::unique_ptr<OGLStreamBuffer> vertex_buffer;
|
||||
OGLBuffer uniform_buffer;
|
||||
@@ -136,8 +155,6 @@ private:
|
||||
static constexpr size_t STREAM_BUFFER_SIZE = 4 * 1024 * 1024;
|
||||
std::unique_ptr<OGLStreamBuffer> stream_buffer;
|
||||
|
||||
GLint vs_input_index_min;
|
||||
GLint vs_input_index_max;
|
||||
GLsizeiptr vs_input_size;
|
||||
|
||||
void AnalyzeVertexArray(bool is_indexed);
|
||||
|
||||
@@ -21,9 +21,13 @@
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/utils.h"
|
||||
@@ -107,67 +111,28 @@ static void MortonCopyTile(u32 stride, u8* tile_buffer, u8* gl_buffer) {
|
||||
}
|
||||
|
||||
template <bool morton_to_gl, PixelFormat format>
|
||||
static void MortonCopy(u32 stride, u32 height, u8* gl_buffer, PAddr base, PAddr start, PAddr end) {
|
||||
static void MortonCopy(u32 stride, u32 height, u8* gl_buffer, VAddr base, VAddr start, VAddr end) {
|
||||
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / 8;
|
||||
constexpr u32 tile_size = bytes_per_pixel * 64;
|
||||
|
||||
constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
|
||||
static_assert(gl_bytes_per_pixel >= bytes_per_pixel, "");
|
||||
gl_buffer += gl_bytes_per_pixel - bytes_per_pixel;
|
||||
|
||||
const PAddr aligned_down_start = base + Common::AlignDown(start - base, tile_size);
|
||||
const PAddr aligned_start = base + Common::AlignUp(start - base, tile_size);
|
||||
const PAddr aligned_end = base + Common::AlignDown(end - base, tile_size);
|
||||
|
||||
ASSERT(!morton_to_gl || (aligned_start == start && aligned_end == end));
|
||||
|
||||
const u64 begin_pixel_index = (aligned_down_start - base) / bytes_per_pixel;
|
||||
u32 x = static_cast<u32>((begin_pixel_index % (stride * 8)) / 8);
|
||||
u32 y = static_cast<u32>((begin_pixel_index / (stride * 8)) * 8);
|
||||
|
||||
gl_buffer += ((height - 8 - y) * stride + x) * gl_bytes_per_pixel;
|
||||
|
||||
auto glbuf_next_tile = [&] {
|
||||
x = (x + 8) % stride;
|
||||
gl_buffer += 8 * gl_bytes_per_pixel;
|
||||
if (!x) {
|
||||
y += 8;
|
||||
gl_buffer -= stride * 9 * gl_bytes_per_pixel;
|
||||
}
|
||||
};
|
||||
|
||||
u8* tile_buffer = Memory::GetPhysicalPointer(start);
|
||||
|
||||
if (start < aligned_start && !morton_to_gl) {
|
||||
std::array<u8, tile_size> tmp_buf;
|
||||
MortonCopyTile<morton_to_gl, format>(stride, &tmp_buf[0], gl_buffer);
|
||||
std::memcpy(tile_buffer, &tmp_buf[start - aligned_down_start],
|
||||
std::min(aligned_start, end) - start);
|
||||
|
||||
tile_buffer += aligned_start - start;
|
||||
glbuf_next_tile();
|
||||
}
|
||||
|
||||
const u8* const buffer_end = tile_buffer + aligned_end - aligned_start;
|
||||
while (tile_buffer < buffer_end) {
|
||||
MortonCopyTile<morton_to_gl, format>(stride, tile_buffer, gl_buffer);
|
||||
tile_buffer += tile_size;
|
||||
glbuf_next_tile();
|
||||
}
|
||||
|
||||
if (end > std::max(aligned_start, aligned_end) && !morton_to_gl) {
|
||||
std::array<u8, tile_size> tmp_buf;
|
||||
MortonCopyTile<morton_to_gl, format>(stride, &tmp_buf[0], gl_buffer);
|
||||
std::memcpy(tile_buffer, &tmp_buf[0], end - aligned_end);
|
||||
}
|
||||
// TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should check the
|
||||
// configuration for this and perform more generic un/swizzle
|
||||
LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
|
||||
VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel,
|
||||
Memory::GetPointer(base), gl_buffer, morton_to_gl);
|
||||
}
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> morton_to_gl_fns = {
|
||||
MortonCopy<true, PixelFormat::RGBA8>, // 0
|
||||
MortonCopy<true, PixelFormat::RGB8>, // 1
|
||||
MortonCopy<true, PixelFormat::RGB5A1>, // 2
|
||||
MortonCopy<true, PixelFormat::RGB565>, // 3
|
||||
MortonCopy<true, PixelFormat::RGBA4>, // 4
|
||||
static constexpr std::array<void (*)(u32, u32, u8*, VAddr, VAddr, VAddr), 18> morton_to_gl_fns = {
|
||||
MortonCopy<true, PixelFormat::RGBA8>,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
@@ -176,19 +141,19 @@ static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> mo
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr, // 5 - 13
|
||||
MortonCopy<true, PixelFormat::D16>, // 14
|
||||
nullptr, // 15
|
||||
MortonCopy<true, PixelFormat::D24>, // 16
|
||||
MortonCopy<true, PixelFormat::D24S8> // 17
|
||||
};
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> gl_to_morton_fns = {
|
||||
MortonCopy<false, PixelFormat::RGBA8>, // 0
|
||||
MortonCopy<false, PixelFormat::RGB8>, // 1
|
||||
MortonCopy<false, PixelFormat::RGB5A1>, // 2
|
||||
MortonCopy<false, PixelFormat::RGB565>, // 3
|
||||
MortonCopy<false, PixelFormat::RGBA4>, // 4
|
||||
static constexpr std::array<void (*)(u32, u32, u8*, VAddr, VAddr, VAddr), 18> gl_to_morton_fns = {
|
||||
MortonCopy<false, PixelFormat::RGBA8>,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
@@ -197,11 +162,6 @@ static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> gl
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr, // 5 - 13
|
||||
MortonCopy<false, PixelFormat::D16>, // 14
|
||||
nullptr, // 15
|
||||
MortonCopy<false, PixelFormat::D24>, // 16
|
||||
MortonCopy<false, PixelFormat::D24S8> // 17
|
||||
};
|
||||
|
||||
// Allocate an uninitialized texture of appropriate size and format for the surface
|
||||
@@ -290,17 +250,17 @@ static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rec
|
||||
|
||||
static bool FillSurface(const Surface& surface, const u8* fill_data,
|
||||
const MathUtil::Rectangle<u32>& fill_rect, GLuint draw_fb_handle) {
|
||||
UNIMPLEMENTED();
|
||||
return true;
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {
|
||||
SurfaceParams params = *this;
|
||||
const u32 tiled_size = is_tiled ? 8 : 1;
|
||||
const u64 stride_tiled_bytes = BytesInPixels(stride * tiled_size);
|
||||
PAddr aligned_start =
|
||||
VAddr aligned_start =
|
||||
addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes);
|
||||
PAddr aligned_end =
|
||||
VAddr aligned_end =
|
||||
addr + Common::AlignUp(boost::icl::last_next(interval) - addr, stride_tiled_bytes);
|
||||
|
||||
if (aligned_end - aligned_start > stride_tiled_bytes) {
|
||||
@@ -527,10 +487,10 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192));
|
||||
void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) {
|
||||
void CachedSurface::LoadGLBuffer(VAddr load_start, VAddr load_end) {
|
||||
ASSERT(type != SurfaceType::Fill);
|
||||
|
||||
const u8* const texture_src_data = Memory::GetPhysicalPointer(addr);
|
||||
u8* const texture_src_data = Memory::GetPointer(addr);
|
||||
if (texture_src_data == nullptr)
|
||||
return;
|
||||
|
||||
@@ -539,35 +499,30 @@ void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) {
|
||||
gl_buffer.reset(new u8[gl_buffer_size]);
|
||||
}
|
||||
|
||||
// TODO: Should probably be done in ::Memory:: and check for other regions too
|
||||
if (load_start < Memory::VRAM_VADDR_END && load_end > Memory::VRAM_VADDR_END)
|
||||
load_end = Memory::VRAM_VADDR_END;
|
||||
|
||||
if (load_start < Memory::VRAM_VADDR && load_end > Memory::VRAM_VADDR)
|
||||
load_start = Memory::VRAM_VADDR;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
|
||||
|
||||
ASSERT(load_start >= addr && load_end <= end);
|
||||
const u32 start_offset = load_start - addr;
|
||||
const u64 start_offset = load_start - addr;
|
||||
|
||||
if (!is_tiled) {
|
||||
ASSERT(type == SurfaceType::Color);
|
||||
std::memcpy(&gl_buffer[start_offset], texture_src_data + start_offset,
|
||||
load_end - load_start);
|
||||
const u32 bytes_per_pixel{GetFormatBpp() >> 3};
|
||||
|
||||
// TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should check
|
||||
// the configuration for this and perform more generic un/swizzle
|
||||
LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
|
||||
VideoCore::MortonCopyPixels128(width, height, bytes_per_pixel, 4,
|
||||
texture_src_data + start_offset, &gl_buffer[start_offset],
|
||||
true);
|
||||
} else {
|
||||
if (type == SurfaceType::Texture) {
|
||||
UNIMPLEMENTED();
|
||||
} else {
|
||||
morton_to_gl_fns[static_cast<size_t>(pixel_format)](stride, height, &gl_buffer[0], addr,
|
||||
load_start, load_end);
|
||||
}
|
||||
morton_to_gl_fns[static_cast<size_t>(pixel_format)](stride, height, &gl_buffer[0], addr,
|
||||
load_start, load_end);
|
||||
}
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) {
|
||||
u8* const dst_buffer = Memory::GetPhysicalPointer(addr);
|
||||
void CachedSurface::FlushGLBuffer(VAddr flush_start, VAddr flush_end) {
|
||||
u8* const dst_buffer = Memory::GetPointer(addr);
|
||||
if (dst_buffer == nullptr)
|
||||
return;
|
||||
|
||||
@@ -1102,18 +1057,106 @@ SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams&
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::GetTextureSurface(const void* config) {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(
|
||||
bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle<s32>& viewport_rect) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle<s32>& viewport) {
|
||||
const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto& memory_manager = Core::System().GetInstance().GPU().memory_manager;
|
||||
const auto& config = regs.rt[0];
|
||||
|
||||
// TODO(bunnei): This is hard corded to use just the first render buffer
|
||||
LOG_WARNING(Render_OpenGL, "hard-coded for render target 0!");
|
||||
|
||||
// update resolution_scale_factor and reset cache if changed
|
||||
// TODO (bunnei): This code was ported as-is from Citra, and is technically not thread-safe. We
|
||||
// need to fix this before making the renderer multi-threaded.
|
||||
static u16 resolution_scale_factor = GetResolutionScaleFactor();
|
||||
if (resolution_scale_factor != GetResolutionScaleFactor()) {
|
||||
resolution_scale_factor = GetResolutionScaleFactor();
|
||||
FlushAll();
|
||||
while (!surface_cache.empty())
|
||||
UnregisterSurface(*surface_cache.begin()->second.begin());
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<u32> viewport_clamped{
|
||||
static_cast<u32>(MathUtil::Clamp(viewport.left, 0, static_cast<s32>(config.width))),
|
||||
static_cast<u32>(MathUtil::Clamp(viewport.top, 0, static_cast<s32>(config.height))),
|
||||
static_cast<u32>(MathUtil::Clamp(viewport.right, 0, static_cast<s32>(config.width))),
|
||||
static_cast<u32>(MathUtil::Clamp(viewport.bottom, 0, static_cast<s32>(config.height)))};
|
||||
|
||||
// get color and depth surfaces
|
||||
SurfaceParams color_params;
|
||||
color_params.is_tiled = true;
|
||||
color_params.res_scale = resolution_scale_factor;
|
||||
color_params.width = config.width;
|
||||
color_params.height = config.height;
|
||||
SurfaceParams depth_params = color_params;
|
||||
|
||||
color_params.addr = memory_manager->PhysicalToVirtualAddress(config.Address());
|
||||
color_params.pixel_format = SurfaceParams::PixelFormatFromRenderTargetFormat(config.format);
|
||||
color_params.UpdateParams();
|
||||
|
||||
ASSERT_MSG(!using_depth_fb, "depth buffer is unimplemented");
|
||||
// depth_params.addr = config.GetDepthBufferPhysicalAddress();
|
||||
// depth_params.pixel_format = SurfaceParams::PixelFormatFromDepthFormat(config.depth_format);
|
||||
// depth_params.UpdateParams();
|
||||
|
||||
auto color_vp_interval = color_params.GetSubRectInterval(viewport_clamped);
|
||||
auto depth_vp_interval = depth_params.GetSubRectInterval(viewport_clamped);
|
||||
|
||||
// Make sure that framebuffers don't overlap if both color and depth are being used
|
||||
if (using_color_fb && using_depth_fb &&
|
||||
boost::icl::length(color_vp_interval & depth_vp_interval)) {
|
||||
LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; "
|
||||
"overlapping framebuffers not supported!");
|
||||
using_depth_fb = false;
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<u32> color_rect{};
|
||||
Surface color_surface = nullptr;
|
||||
if (using_color_fb)
|
||||
std::tie(color_surface, color_rect) =
|
||||
GetSurfaceSubRect(color_params, ScaleMatch::Exact, false);
|
||||
|
||||
MathUtil::Rectangle<u32> depth_rect{};
|
||||
Surface depth_surface = nullptr;
|
||||
if (using_depth_fb)
|
||||
std::tie(depth_surface, depth_rect) =
|
||||
GetSurfaceSubRect(depth_params, ScaleMatch::Exact, false);
|
||||
|
||||
MathUtil::Rectangle<u32> fb_rect{};
|
||||
if (color_surface != nullptr && depth_surface != nullptr) {
|
||||
fb_rect = color_rect;
|
||||
// Color and Depth surfaces must have the same dimensions and offsets
|
||||
if (color_rect.bottom != depth_rect.bottom || color_rect.top != depth_rect.top ||
|
||||
color_rect.left != depth_rect.left || color_rect.right != depth_rect.right) {
|
||||
color_surface = GetSurface(color_params, ScaleMatch::Exact, false);
|
||||
depth_surface = GetSurface(depth_params, ScaleMatch::Exact, false);
|
||||
fb_rect = color_surface->GetScaledRect();
|
||||
}
|
||||
} else if (color_surface != nullptr) {
|
||||
fb_rect = color_rect;
|
||||
} else if (depth_surface != nullptr) {
|
||||
fb_rect = depth_rect;
|
||||
}
|
||||
|
||||
if (color_surface != nullptr) {
|
||||
ValidateSurface(color_surface, boost::icl::first(color_vp_interval),
|
||||
boost::icl::length(color_vp_interval));
|
||||
}
|
||||
if (depth_surface != nullptr) {
|
||||
ValidateSurface(depth_surface, boost::icl::first(depth_vp_interval),
|
||||
boost::icl::length(depth_vp_interval));
|
||||
}
|
||||
|
||||
return std::make_tuple(color_surface, depth_surface, fb_rect);
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::GetFillSurface(const void* config) {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -1167,7 +1210,7 @@ void RasterizerCacheOpenGL::DuplicateSurface(const Surface& src_surface,
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, u64 size) {
|
||||
void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, VAddr addr, u64 size) {
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
@@ -1227,7 +1270,7 @@ void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr,
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u64 size, Surface flush_surface) {
|
||||
void RasterizerCacheOpenGL::FlushRegion(VAddr addr, u64 size, Surface flush_surface) {
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
@@ -1260,10 +1303,10 @@ void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u64 size, Surface flush_surf
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::FlushAll() {
|
||||
FlushRegion(0, 0xFFFFFFFF);
|
||||
FlushRegion(0, Kernel::VMManager::MAX_ADDRESS);
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u64 size, const Surface& region_owner) {
|
||||
void RasterizerCacheOpenGL::InvalidateRegion(VAddr addr, u64 size, const Surface& region_owner) {
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
@@ -1356,6 +1399,34 @@ void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) {
|
||||
surface_cache.subtract({surface->GetInterval(), SurfaceSet{surface}});
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::UpdatePagesCachedCount(PAddr addr, u64 size, int delta) {
|
||||
UNIMPLEMENTED();
|
||||
void RasterizerCacheOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
|
||||
const u64 num_pages =
|
||||
((addr + size - 1) >> Memory::PAGE_BITS) - (addr >> Memory::PAGE_BITS) + 1;
|
||||
const u64 page_start = addr >> Memory::PAGE_BITS;
|
||||
const u64 page_end = page_start + num_pages;
|
||||
|
||||
// Interval maps will erase segments if count reaches 0, so if delta is negative we have to
|
||||
// subtract after iterating
|
||||
const auto pages_interval = PageMap::interval_type::right_open(page_start, page_end);
|
||||
if (delta > 0)
|
||||
cached_pages.add({pages_interval, delta});
|
||||
|
||||
for (const auto& pair : RangeFromInterval(cached_pages, pages_interval)) {
|
||||
const auto interval = pair.first & pages_interval;
|
||||
const int count = pair.second;
|
||||
|
||||
const VAddr interval_start_addr = boost::icl::first(interval) << Memory::PAGE_BITS;
|
||||
const VAddr interval_end_addr = boost::icl::last_next(interval) << Memory::PAGE_BITS;
|
||||
const u64 interval_size = interval_end_addr - interval_start_addr;
|
||||
|
||||
if (delta > 0 && count == delta)
|
||||
Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, true);
|
||||
else if (delta < 0 && count == -delta)
|
||||
Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, false);
|
||||
else
|
||||
ASSERT(count >= 0);
|
||||
}
|
||||
|
||||
if (delta < 0)
|
||||
cached_pages.add({pages_interval, delta});
|
||||
}
|
||||
|
||||
@@ -22,15 +22,16 @@
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
|
||||
struct CachedSurface;
|
||||
using Surface = std::shared_ptr<CachedSurface>;
|
||||
using SurfaceSet = std::set<Surface>;
|
||||
|
||||
using SurfaceRegions = boost::icl::interval_set<PAddr>;
|
||||
using SurfaceMap = boost::icl::interval_map<PAddr, Surface>;
|
||||
using SurfaceCache = boost::icl::interval_map<PAddr, SurfaceSet>;
|
||||
using SurfaceRegions = boost::icl::interval_set<VAddr>;
|
||||
using SurfaceMap = boost::icl::interval_map<VAddr, Surface>;
|
||||
using SurfaceCache = boost::icl::interval_map<VAddr, SurfaceSet>;
|
||||
|
||||
using SurfaceInterval = SurfaceCache::interval_type;
|
||||
static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() &&
|
||||
@@ -40,7 +41,7 @@ static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval
|
||||
using SurfaceRect_Tuple = std::tuple<Surface, MathUtil::Rectangle<u32>>;
|
||||
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, MathUtil::Rectangle<u32>>;
|
||||
|
||||
using PageMap = boost::icl::interval_map<u32, int>;
|
||||
using PageMap = boost::icl::interval_map<u64, int>;
|
||||
|
||||
enum class ScaleMatch {
|
||||
Exact, // only accept same res scale
|
||||
@@ -115,6 +116,24 @@ struct SurfaceParams {
|
||||
return GetFormatBpp(pixel_format);
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) {
|
||||
switch (format) {
|
||||
case Tegra::RenderTargetFormat::RGBA8_UNORM:
|
||||
return PixelFormat::RGBA8;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) {
|
||||
switch (format) {
|
||||
case Tegra::FramebufferConfig::PixelFormat::ABGR8:
|
||||
return PixelFormat::RGBA8;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) {
|
||||
SurfaceType a_type = GetFormatType(pixel_format_a);
|
||||
SurfaceType b_type = GetFormatType(pixel_format_b);
|
||||
@@ -211,8 +230,8 @@ struct SurfaceParams {
|
||||
MathUtil::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const;
|
||||
MathUtil::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const;
|
||||
|
||||
PAddr addr = 0;
|
||||
PAddr end = 0;
|
||||
VAddr addr = 0;
|
||||
VAddr end = 0;
|
||||
u64 size = 0;
|
||||
|
||||
u32 width = 0;
|
||||
@@ -257,9 +276,9 @@ struct CachedSurface : SurfaceParams {
|
||||
std::unique_ptr<u8[]> gl_buffer;
|
||||
size_t gl_buffer_size = 0;
|
||||
|
||||
// Read/Write data in 3DS memory to/from gl_buffer
|
||||
void LoadGLBuffer(PAddr load_start, PAddr load_end);
|
||||
void FlushGLBuffer(PAddr flush_start, PAddr flush_end);
|
||||
// Read/Write data in Switch memory to/from gl_buffer
|
||||
void LoadGLBuffer(VAddr load_start, VAddr load_end);
|
||||
void FlushGLBuffer(VAddr flush_start, VAddr flush_end);
|
||||
|
||||
// Upload/Download data in gl_buffer in/to this surface's texture
|
||||
void UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle,
|
||||
@@ -298,7 +317,7 @@ public:
|
||||
|
||||
/// Get the color and depth surfaces based on the framebuffer configuration
|
||||
SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
|
||||
const MathUtil::Rectangle<s32>& viewport_rect);
|
||||
const MathUtil::Rectangle<s32>& viewport);
|
||||
|
||||
/// Get a surface that matches the fill config
|
||||
Surface GetFillSurface(const void* config);
|
||||
@@ -307,10 +326,10 @@ public:
|
||||
SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params);
|
||||
|
||||
/// Write any cached resources overlapping the region back to memory (if dirty)
|
||||
void FlushRegion(PAddr addr, u64 size, Surface flush_surface = nullptr);
|
||||
void FlushRegion(VAddr addr, u64 size, Surface flush_surface = nullptr);
|
||||
|
||||
/// Mark region as being invalidated by region_owner (nullptr if 3DS memory)
|
||||
void InvalidateRegion(PAddr addr, u64 size, const Surface& region_owner);
|
||||
void InvalidateRegion(VAddr addr, u64 size, const Surface& region_owner);
|
||||
|
||||
/// Flush all cached resources tracked by this cache manager
|
||||
void FlushAll();
|
||||
@@ -319,7 +338,7 @@ private:
|
||||
void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface);
|
||||
|
||||
/// Update surface's texture for given region when necessary
|
||||
void ValidateSurface(const Surface& surface, PAddr addr, u64 size);
|
||||
void ValidateSurface(const Surface& surface, VAddr addr, u64 size);
|
||||
|
||||
/// Create a new surface
|
||||
Surface CreateSurface(const SurfaceParams& params);
|
||||
@@ -331,7 +350,7 @@ private:
|
||||
void UnregisterSurface(const Surface& surface);
|
||||
|
||||
/// Increase/decrease the number of surface in pages touching the specified region
|
||||
void UpdatePagesCachedCount(PAddr addr, u64 size, int delta);
|
||||
void UpdatePagesCachedCount(VAddr addr, u64 size, int delta);
|
||||
|
||||
SurfaceCache surface_cache;
|
||||
PageMap cached_pages;
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
sanitize_mul(sanitize_mul), emit_cb(emit_cb), setemit_cb(setemit_cb) {}
|
||||
|
||||
std::string Decompile() {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
namespace GLShader {
|
||||
|
||||
std::string GenerateVertexShader(const MaxwellVSConfig& config) {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string GenerateFragmentShader(const MaxwellFSConfig& config) {
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader,
|
||||
if (result == GL_TRUE) {
|
||||
LOG_DEBUG(Render_OpenGL, "%s", &vertex_shader_error[0]);
|
||||
} else {
|
||||
LOG_ERROR(Render_OpenGL, "Error compiling vertex shader:\n%s",
|
||||
&vertex_shader_error[0]);
|
||||
LOG_CRITICAL(Render_OpenGL, "Error compiling vertex shader:\n%s",
|
||||
&vertex_shader_error[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,8 @@ GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader,
|
||||
if (result == GL_TRUE) {
|
||||
LOG_DEBUG(Render_OpenGL, "%s", &geometry_shader_error[0]);
|
||||
} else {
|
||||
LOG_ERROR(Render_OpenGL, "Error compiling geometry shader:\n%s",
|
||||
&geometry_shader_error[0]);
|
||||
LOG_CRITICAL(Render_OpenGL, "Error compiling geometry shader:\n%s",
|
||||
&geometry_shader_error[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,8 +86,8 @@ GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader,
|
||||
if (result == GL_TRUE) {
|
||||
LOG_DEBUG(Render_OpenGL, "%s", &fragment_shader_error[0]);
|
||||
} else {
|
||||
LOG_ERROR(Render_OpenGL, "Error compiling fragment shader:\n%s",
|
||||
&fragment_shader_error[0]);
|
||||
LOG_CRITICAL(Render_OpenGL, "Error compiling fragment shader:\n%s",
|
||||
&fragment_shader_error[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,20 +128,20 @@ GLuint LoadProgram(const char* vertex_shader, const char* geometry_shader,
|
||||
if (result == GL_TRUE) {
|
||||
LOG_DEBUG(Render_OpenGL, "%s", &program_error[0]);
|
||||
} else {
|
||||
LOG_ERROR(Render_OpenGL, "Error linking shader:\n%s", &program_error[0]);
|
||||
LOG_CRITICAL(Render_OpenGL, "Error linking shader:\n%s", &program_error[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// If the program linking failed at least one of the shaders was probably bad
|
||||
if (result == GL_FALSE) {
|
||||
if (vertex_shader) {
|
||||
LOG_ERROR(Render_OpenGL, "Vertex shader:\n%s", vertex_shader);
|
||||
LOG_CRITICAL(Render_OpenGL, "Vertex shader:\n%s", vertex_shader);
|
||||
}
|
||||
if (geometry_shader) {
|
||||
LOG_ERROR(Render_OpenGL, "Geometry shader:\n%s", geometry_shader);
|
||||
LOG_CRITICAL(Render_OpenGL, "Geometry shader:\n%s", geometry_shader);
|
||||
}
|
||||
if (fragment_shader) {
|
||||
LOG_ERROR(Render_OpenGL, "Fragment shader:\n%s", fragment_shader);
|
||||
LOG_CRITICAL(Render_OpenGL, "Fragment shader:\n%s", fragment_shader);
|
||||
}
|
||||
}
|
||||
ASSERT_MSG(result == GL_TRUE, "Shader not linked");
|
||||
|
||||
@@ -85,7 +85,7 @@ public:
|
||||
struct {
|
||||
GLuint texture_2d; // GL_TEXTURE_BINDING_2D
|
||||
GLuint sampler; // GL_SAMPLER_BINDING
|
||||
} texture_units[3];
|
||||
} texture_units[32];
|
||||
|
||||
struct {
|
||||
GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER
|
||||
|
||||
50
src/video_core/renderer_opengl/maxwell_to_gl.h
Normal file
50
src/video_core/renderer_opengl/maxwell_to_gl.h
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
|
||||
namespace MaxwellToGL {
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
|
||||
switch (attrib.type) {
|
||||
case Maxwell::VertexAttribute::Type::UnsignedNorm: {
|
||||
|
||||
switch (attrib.size) {
|
||||
case Maxwell::VertexAttribute::Size::Size_8_8_8_8:
|
||||
return GL_UNSIGNED_BYTE;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex size=%s", attrib.SizeString().c_str());
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
case Maxwell::VertexAttribute::Type::Float:
|
||||
return GL_FLOAT;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type=%s", attrib.TypeString().c_str());
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
|
||||
switch (topology) {
|
||||
case Maxwell::PrimitiveTopology::TriangleStrip:
|
||||
return GL_TRIANGLE_STRIP;
|
||||
}
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented primitive topology=%d", topology);
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace MaxwellToGL
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "core/settings.h"
|
||||
#include "core/tracer/recorder.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
#include "video_core/utils.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
static const char vertex_shader[] = R"(
|
||||
@@ -98,197 +99,88 @@ RendererOpenGL::RendererOpenGL() = default;
|
||||
RendererOpenGL::~RendererOpenGL() = default;
|
||||
|
||||
/// Swap buffers (render frame)
|
||||
void RendererOpenGL::SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) {
|
||||
void RendererOpenGL::SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) {
|
||||
Core::System::GetInstance().perf_stats.EndSystemFrame();
|
||||
|
||||
// Maintain the rasterizer's state as a priority
|
||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
||||
state.Apply();
|
||||
|
||||
if (framebuffer_info != boost::none) {
|
||||
// If framebuffer_info is provided, reload it from memory to a texture
|
||||
if (screen_info.texture.width != (GLsizei)framebuffer_info->width ||
|
||||
screen_info.texture.height != (GLsizei)framebuffer_info->height ||
|
||||
screen_info.texture.pixel_format != framebuffer_info->pixel_format) {
|
||||
if (framebuffer != boost::none) {
|
||||
// If framebuffer is provided, reload it from memory to a texture
|
||||
if (screen_info.texture.width != (GLsizei)framebuffer->width ||
|
||||
screen_info.texture.height != (GLsizei)framebuffer->height ||
|
||||
screen_info.texture.pixel_format != framebuffer->pixel_format) {
|
||||
// Reallocate texture if the framebuffer size has changed.
|
||||
// This is expected to not happen very often and hence should not be a
|
||||
// performance problem.
|
||||
ConfigureFramebufferTexture(screen_info.texture, *framebuffer_info);
|
||||
ConfigureFramebufferTexture(screen_info.texture, *framebuffer);
|
||||
}
|
||||
LoadFBToScreenInfo(*framebuffer_info, screen_info);
|
||||
|
||||
// Load the framebuffer from memory, draw it to the screen, and swap buffers
|
||||
LoadFBToScreenInfo(*framebuffer, screen_info);
|
||||
DrawScreen();
|
||||
render_window->SwapBuffers();
|
||||
}
|
||||
|
||||
DrawScreens();
|
||||
|
||||
Core::System::GetInstance().perf_stats.EndSystemFrame();
|
||||
|
||||
// Swap buffers
|
||||
render_window->PollEvents();
|
||||
render_window->SwapBuffers();
|
||||
|
||||
Core::System::GetInstance().frame_limiter.DoFrameLimiting(CoreTiming::GetGlobalTimeUs());
|
||||
Core::System::GetInstance().perf_stats.BeginSystemFrame();
|
||||
|
||||
// Restore the rasterizer state
|
||||
prev_state.Apply();
|
||||
RefreshRasterizerSetting();
|
||||
}
|
||||
|
||||
static inline u32 MortonInterleave128(u32 x, u32 y) {
|
||||
// 128x128 Z-Order coordinate from 2D coordinates
|
||||
static constexpr u32 xlut[] = {
|
||||
0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042,
|
||||
0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809,
|
||||
0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000,
|
||||
0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043,
|
||||
0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a,
|
||||
0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001,
|
||||
0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048,
|
||||
0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b,
|
||||
0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002,
|
||||
0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049,
|
||||
0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840,
|
||||
0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003,
|
||||
0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a,
|
||||
0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841,
|
||||
0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008,
|
||||
0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b,
|
||||
0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842,
|
||||
0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009,
|
||||
0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800,
|
||||
0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843,
|
||||
0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a,
|
||||
0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801,
|
||||
0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848,
|
||||
0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b,
|
||||
0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802,
|
||||
0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849,
|
||||
0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040,
|
||||
0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803,
|
||||
0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a,
|
||||
0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041,
|
||||
0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808,
|
||||
0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b,
|
||||
0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042,
|
||||
0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809,
|
||||
0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b,
|
||||
};
|
||||
static constexpr u32 ylut[] = {
|
||||
0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090,
|
||||
0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124,
|
||||
0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200,
|
||||
0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294,
|
||||
0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330,
|
||||
0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404,
|
||||
0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0,
|
||||
0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534,
|
||||
0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610,
|
||||
0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4,
|
||||
0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780,
|
||||
0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014,
|
||||
0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0,
|
||||
0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184,
|
||||
0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220,
|
||||
0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4,
|
||||
0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390,
|
||||
0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424,
|
||||
0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500,
|
||||
0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594,
|
||||
0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630,
|
||||
0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704,
|
||||
0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0,
|
||||
0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034,
|
||||
0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110,
|
||||
0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4,
|
||||
0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280,
|
||||
0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314,
|
||||
0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0,
|
||||
0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484,
|
||||
0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520,
|
||||
0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4,
|
||||
0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690,
|
||||
0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724,
|
||||
0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4,
|
||||
};
|
||||
return xlut[x % 128] + ylut[y % 128];
|
||||
}
|
||||
|
||||
static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
|
||||
// Calculates the offset of the position of the pixel in Morton order
|
||||
// Framebuffer images are split into 128x128 tiles.
|
||||
|
||||
const unsigned int block_height = 128;
|
||||
const unsigned int coarse_x = x & ~127;
|
||||
|
||||
u32 i = MortonInterleave128(x, y);
|
||||
|
||||
const unsigned int offset = coarse_x * block_height;
|
||||
|
||||
return (i + offset) * bytes_per_pixel;
|
||||
}
|
||||
|
||||
static void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 gl_bytes_per_pixel,
|
||||
u8* morton_data, u8* gl_data, bool morton_to_gl) {
|
||||
u8* data_ptrs[2];
|
||||
for (unsigned y = 0; y < height; ++y) {
|
||||
for (unsigned x = 0; x < width; ++x) {
|
||||
const u32 coarse_y = y & ~127;
|
||||
u32 morton_offset =
|
||||
GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel;
|
||||
u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel;
|
||||
|
||||
data_ptrs[morton_to_gl] = morton_data + morton_offset;
|
||||
data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
|
||||
|
||||
memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads framebuffer from emulated memory into the active OpenGL texture.
|
||||
*/
|
||||
void RendererOpenGL::LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info,
|
||||
void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer,
|
||||
ScreenInfo& screen_info) {
|
||||
const u32 bpp{FramebufferInfo::BytesPerPixel(framebuffer_info.pixel_format)};
|
||||
const u32 size_in_bytes{framebuffer_info.stride * framebuffer_info.height * bpp};
|
||||
const u32 bytes_per_pixel{Tegra::FramebufferConfig::BytesPerPixel(framebuffer.pixel_format)};
|
||||
const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel};
|
||||
const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset};
|
||||
|
||||
MortonCopyPixels128(framebuffer_info.width, framebuffer_info.height, bpp, 4,
|
||||
Memory::GetPointer(framebuffer_info.address), gl_framebuffer_data.data(),
|
||||
true);
|
||||
|
||||
LOG_TRACE(Render_OpenGL, "0x%08x bytes from 0x%llx(%dx%d), fmt %x", size_in_bytes,
|
||||
framebuffer_info.address, framebuffer_info.width, framebuffer_info.height,
|
||||
(int)framebuffer_info.pixel_format);
|
||||
// Framebuffer orientation handling
|
||||
framebuffer_transform_flags = framebuffer.transform_flags;
|
||||
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default
|
||||
// only allows rows to have a memory alignement of 4.
|
||||
ASSERT(framebuffer_info.stride % 4 == 0);
|
||||
ASSERT(framebuffer.stride % 4 == 0);
|
||||
|
||||
framebuffer_flip_vertical = framebuffer_info.flip_vertical;
|
||||
if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride,
|
||||
screen_info)) {
|
||||
// Reset the screen info's display texture to its own permanent texture
|
||||
screen_info.display_texture = screen_info.texture.resource.handle;
|
||||
screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
|
||||
|
||||
// Reset the screen info's display texture to its own permanent texture
|
||||
screen_info.display_texture = screen_info.texture.resource.handle;
|
||||
screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f);
|
||||
Rasterizer()->FlushRegion(framebuffer_addr, size_in_bytes);
|
||||
|
||||
// Memory::RasterizerFlushRegion(framebuffer_info.address, size_in_bytes);
|
||||
VideoCore::MortonCopyPixels128(framebuffer.width, framebuffer.height, bytes_per_pixel, 4,
|
||||
Memory::GetPointer(framebuffer_addr),
|
||||
gl_framebuffer_data.data(), true);
|
||||
|
||||
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
|
||||
state.Apply();
|
||||
state.texture_units[0].texture_2d = screen_info.texture.resource.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)framebuffer_info.stride);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride));
|
||||
|
||||
// Update existing texture
|
||||
// TODO: Test what happens on hardware when you change the framebuffer dimensions so that
|
||||
// they differ from the LCD resolution.
|
||||
// TODO: Applications could theoretically crash Citra here by specifying too large
|
||||
// framebuffer sizes. We should make sure that this cannot happen.
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer_info.width, framebuffer_info.height,
|
||||
screen_info.texture.gl_format, screen_info.texture.gl_type,
|
||||
gl_framebuffer_data.data());
|
||||
// Update existing texture
|
||||
// TODO: Test what happens on hardware when you change the framebuffer dimensions so that
|
||||
// they differ from the LCD resolution.
|
||||
// TODO: Applications could theoretically crash yuzu here by specifying too large
|
||||
// framebuffer sizes. We should make sure that this cannot happen.
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height,
|
||||
screen_info.texture.gl_format, screen_info.texture.gl_type,
|
||||
gl_framebuffer_data.data());
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.Apply();
|
||||
state.texture_units[0].texture_2d = 0;
|
||||
state.Apply();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,14 +264,14 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||
}
|
||||
|
||||
void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
|
||||
const FramebufferInfo& framebuffer_info) {
|
||||
const Tegra::FramebufferConfig& framebuffer) {
|
||||
|
||||
texture.width = framebuffer_info.width;
|
||||
texture.height = framebuffer_info.height;
|
||||
texture.width = framebuffer.width;
|
||||
texture.height = framebuffer.height;
|
||||
|
||||
GLint internal_format;
|
||||
switch (framebuffer_info.pixel_format) {
|
||||
case FramebufferInfo::PixelFormat::ABGR8:
|
||||
switch (framebuffer.pixel_format) {
|
||||
case Tegra::FramebufferConfig::PixelFormat::ABGR8:
|
||||
// Use RGBA8 and swap in the fragment shader
|
||||
internal_format = GL_RGBA;
|
||||
texture.gl_format = GL_RGBA;
|
||||
@@ -387,7 +279,7 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
|
||||
gl_framebuffer_data.resize(texture.width * texture.height * 4);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
state.texture_units[0].texture_2d = texture.resource.handle;
|
||||
@@ -401,11 +293,22 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
|
||||
state.Apply();
|
||||
}
|
||||
|
||||
void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w,
|
||||
float h) {
|
||||
void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w,
|
||||
float h) {
|
||||
const auto& texcoords = screen_info.display_texcoords;
|
||||
const auto& left = framebuffer_flip_vertical ? texcoords.right : texcoords.left;
|
||||
const auto& right = framebuffer_flip_vertical ? texcoords.left : texcoords.right;
|
||||
auto left = texcoords.left;
|
||||
auto right = texcoords.right;
|
||||
if (framebuffer_transform_flags != Tegra::FramebufferConfig::TransformFlags::Unset)
|
||||
if (framebuffer_transform_flags == Tegra::FramebufferConfig::TransformFlags::FlipV) {
|
||||
// Flip the framebuffer vertically
|
||||
left = texcoords.right;
|
||||
right = texcoords.left;
|
||||
} else {
|
||||
// Other transformations are unsupported
|
||||
LOG_CRITICAL(Render_OpenGL, "Unsupported framebuffer_transform_flags=%d",
|
||||
framebuffer_transform_flags);
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
std::array<ScreenRectVertex, 4> vertices = {{
|
||||
ScreenRectVertex(x, y, texcoords.top, right),
|
||||
@@ -427,7 +330,7 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl
|
||||
/**
|
||||
* Draws the emulated screens to the emulator window.
|
||||
*/
|
||||
void RendererOpenGL::DrawScreens() {
|
||||
void RendererOpenGL::DrawScreen() {
|
||||
const auto& layout = render_window->GetFramebufferLayout();
|
||||
const auto& screen = layout.screen;
|
||||
|
||||
@@ -443,8 +346,8 @@ void RendererOpenGL::DrawScreens() {
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glUniform1i(uniform_color_texture, 0);
|
||||
|
||||
DrawSingleScreen(screen_info, (float)screen.left, (float)screen.top, (float)screen.GetWidth(),
|
||||
(float)screen.GetHeight());
|
||||
DrawScreenTriangles(screen_info, (float)screen.left, (float)screen.top,
|
||||
(float)screen.GetWidth(), (float)screen.GetHeight());
|
||||
|
||||
m_current_frame++;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ struct TextureInfo {
|
||||
GLsizei height;
|
||||
GLenum gl_format;
|
||||
GLenum gl_type;
|
||||
RendererBase::FramebufferInfo::PixelFormat pixel_format;
|
||||
Tegra::FramebufferConfig::PixelFormat pixel_format;
|
||||
};
|
||||
|
||||
/// Structure used for storing information about the display target for each 3DS screen
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
~RendererOpenGL() override;
|
||||
|
||||
/// Swap buffers (render frame)
|
||||
void SwapBuffers(boost::optional<const FramebufferInfo&> framebuffer_info) override;
|
||||
void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) override;
|
||||
|
||||
/**
|
||||
* Set the emulator window to use for renderer
|
||||
@@ -53,13 +53,14 @@ public:
|
||||
|
||||
private:
|
||||
void InitOpenGLObjects();
|
||||
void ConfigureFramebufferTexture(TextureInfo& texture, const FramebufferInfo& framebuffer_info);
|
||||
void DrawScreens();
|
||||
void DrawSingleScreen(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
||||
void ConfigureFramebufferTexture(TextureInfo& texture,
|
||||
const Tegra::FramebufferConfig& framebuffer);
|
||||
void DrawScreen();
|
||||
void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h);
|
||||
void UpdateFramerate();
|
||||
|
||||
// Loads framebuffer from emulated memory into the display information structure
|
||||
void LoadFBToScreenInfo(const FramebufferInfo& framebuffer_info, ScreenInfo& screen_info);
|
||||
void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer, ScreenInfo& screen_info);
|
||||
// Fills active OpenGL texture with the given RGBA color.
|
||||
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a,
|
||||
const TextureInfo& texture);
|
||||
@@ -87,6 +88,6 @@ private:
|
||||
GLuint attrib_position;
|
||||
GLuint attrib_tex_coord;
|
||||
|
||||
/// Flips the framebuffer vertically when true
|
||||
bool framebuffer_flip_vertical;
|
||||
/// Used for transforming the framebuffer orientation
|
||||
Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags;
|
||||
};
|
||||
|
||||
105
src/video_core/textures/decoders.cpp
Normal file
105
src/video_core/textures/decoders.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace Tegra {
|
||||
namespace Texture {
|
||||
|
||||
/**
|
||||
* Calculates the offset of an (x, y) position within a swizzled texture.
|
||||
* Taken from the Tegra X1 TRM.
|
||||
*/
|
||||
static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel, u32 block_height) {
|
||||
u32 image_width_in_gobs = image_width * bytes_per_pixel / 64;
|
||||
u32 GOB_address = 0 + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs +
|
||||
(x * bytes_per_pixel / 64) * 512 * block_height +
|
||||
(y % (8 * block_height) / 8) * 512;
|
||||
x *= bytes_per_pixel;
|
||||
u32 address = GOB_address + ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 +
|
||||
(y % 2) * 16 + (x % 16);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
static void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
|
||||
u8* swizzled_data, u8* unswizzled_data, bool unswizzle,
|
||||
u32 block_height) {
|
||||
u8* data_ptrs[2];
|
||||
for (unsigned y = 0; y < height; ++y) {
|
||||
for (unsigned x = 0; x < width; ++x) {
|
||||
u32 swizzle_offset = GetSwizzleOffset(x, y, width, bytes_per_pixel, block_height);
|
||||
u32 pixel_index = (x + y * width) * out_bytes_per_pixel;
|
||||
|
||||
data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
|
||||
data_ptrs[!unswizzle] = &unswizzled_data[pixel_index];
|
||||
|
||||
std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 BytesPerPixel(TextureFormat format) {
|
||||
switch (format) {
|
||||
case TextureFormat::DXT1:
|
||||
// In this case a 'pixel' actually refers to a 4x4 tile.
|
||||
return 8;
|
||||
case TextureFormat::A8R8G8B8:
|
||||
return 4;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Format not implemented");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height) {
|
||||
u8* data = Memory::GetPointer(address);
|
||||
u32 bytes_per_pixel = BytesPerPixel(format);
|
||||
|
||||
static constexpr u32 DefaultBlockHeight = 16;
|
||||
|
||||
std::vector<u8> unswizzled_data(width * height * bytes_per_pixel);
|
||||
|
||||
switch (format) {
|
||||
case TextureFormat::DXT1:
|
||||
// In the DXT1 format, each 4x4 tile is swizzled instead of just individual pixel values.
|
||||
CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data,
|
||||
unswizzled_data.data(), true, DefaultBlockHeight);
|
||||
break;
|
||||
case TextureFormat::A8R8G8B8:
|
||||
CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data,
|
||||
unswizzled_data.data(), true, DefaultBlockHeight);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Format not implemented");
|
||||
break;
|
||||
}
|
||||
|
||||
return unswizzled_data;
|
||||
}
|
||||
|
||||
std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
|
||||
u32 height) {
|
||||
std::vector<u8> rgba_data;
|
||||
|
||||
// TODO(Subv): Implement.
|
||||
switch (format) {
|
||||
case TextureFormat::DXT1:
|
||||
case TextureFormat::A8R8G8B8:
|
||||
// TODO(Subv): For the time being just forward the same data without any decoding.
|
||||
rgba_data = texture_data;
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Format not implemented");
|
||||
break;
|
||||
}
|
||||
|
||||
return rgba_data;
|
||||
}
|
||||
|
||||
} // namespace Texture
|
||||
} // namespace Tegra
|
||||
26
src/video_core/textures/decoders.h
Normal file
26
src/video_core/textures/decoders.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace Tegra {
|
||||
namespace Texture {
|
||||
|
||||
/**
|
||||
* Unswizzles a swizzled texture without changing its format.
|
||||
*/
|
||||
std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height);
|
||||
|
||||
/**
|
||||
* Decodes an unswizzled texture into a A8R8G8B8 texture.
|
||||
*/
|
||||
std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
|
||||
u32 height);
|
||||
|
||||
} // namespace Texture
|
||||
} // namespace Tegra
|
||||
137
src/video_core/textures/texture.h
Normal file
137
src/video_core/textures/texture.h
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Tegra {
|
||||
namespace Texture {
|
||||
|
||||
enum class TextureFormat : u32 {
|
||||
A8R8G8B8 = 8,
|
||||
DXT1 = 0x24,
|
||||
};
|
||||
|
||||
enum class TextureType : u32 {
|
||||
Texture1D = 0,
|
||||
Texture2D = 1,
|
||||
Texture3D = 2,
|
||||
TextureCubemap = 3,
|
||||
Texture1DArray = 4,
|
||||
Texture2DArray = 5,
|
||||
Texture1DBuffer = 6,
|
||||
Texture2DNoMipmap = 7,
|
||||
TextureCubeArray = 8,
|
||||
};
|
||||
|
||||
enum class TICHeaderVersion : u32 {
|
||||
OneDBuffer = 0,
|
||||
PitchColorKey = 1,
|
||||
Pitch = 2,
|
||||
BlockLinear = 3,
|
||||
BlockLinearColorKey = 4,
|
||||
};
|
||||
|
||||
union TextureHandle {
|
||||
u32 raw;
|
||||
BitField<0, 20, u32> tic_id;
|
||||
BitField<20, 12, u32> tsc_id;
|
||||
};
|
||||
static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size");
|
||||
|
||||
struct TICEntry {
|
||||
union {
|
||||
u32 raw;
|
||||
BitField<0, 7, TextureFormat> format;
|
||||
BitField<7, 3, u32> r_type;
|
||||
BitField<10, 3, u32> g_type;
|
||||
BitField<13, 3, u32> b_type;
|
||||
BitField<16, 3, u32> a_type;
|
||||
};
|
||||
u32 address_low;
|
||||
union {
|
||||
BitField<0, 16, u32> address_high;
|
||||
BitField<21, 3, TICHeaderVersion> header_version;
|
||||
};
|
||||
INSERT_PADDING_BYTES(4);
|
||||
union {
|
||||
BitField<0, 16, u32> width_minus_1;
|
||||
BitField<23, 4, TextureType> texture_type;
|
||||
};
|
||||
u16 height_minus_1;
|
||||
INSERT_PADDING_BYTES(10);
|
||||
|
||||
GPUVAddr Address() const {
|
||||
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low);
|
||||
}
|
||||
|
||||
u32 Width() const {
|
||||
return width_minus_1 + 1;
|
||||
}
|
||||
|
||||
u32 Height() const {
|
||||
return height_minus_1 + 1;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size");
|
||||
|
||||
enum class WrapMode : u32 {
|
||||
Wrap = 0,
|
||||
Mirror = 1,
|
||||
ClampToEdge = 2,
|
||||
Border = 3,
|
||||
ClampOGL = 4,
|
||||
MirrorOnceClampToEdge = 5,
|
||||
MirrorOnceBorder = 6,
|
||||
MirrorOnceClampOGL = 7,
|
||||
};
|
||||
|
||||
enum class TextureFilter : u32 {
|
||||
Nearest = 1,
|
||||
Linear = 2,
|
||||
};
|
||||
|
||||
enum class TextureMipmapFilter : u32 {
|
||||
None = 1,
|
||||
Nearest = 2,
|
||||
Linear = 3,
|
||||
};
|
||||
|
||||
struct TSCEntry {
|
||||
union {
|
||||
BitField<0, 3, WrapMode> wrap_u;
|
||||
BitField<3, 3, WrapMode> wrap_v;
|
||||
BitField<6, 3, WrapMode> wrap_p;
|
||||
BitField<9, 1, u32> depth_compare_enabled;
|
||||
BitField<10, 3, u32> depth_compare_func;
|
||||
};
|
||||
union {
|
||||
BitField<0, 2, TextureFilter> mag_filter;
|
||||
BitField<4, 2, TextureFilter> min_filter;
|
||||
BitField<6, 2, TextureMipmapFilter> mip_filter;
|
||||
};
|
||||
INSERT_PADDING_BYTES(8);
|
||||
u32 border_color_r;
|
||||
u32 border_color_g;
|
||||
u32 border_color_b;
|
||||
u32 border_color_a;
|
||||
};
|
||||
static_assert(sizeof(TSCEntry) == 0x20, "TSCEntry has wrong size");
|
||||
|
||||
struct FullTextureInfo {
|
||||
u32 index;
|
||||
TICEntry tic;
|
||||
TSCEntry tsc;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
/// Returns the number of bytes per pixel of the input texture format.
|
||||
u32 BytesPerPixel(TextureFormat format);
|
||||
|
||||
} // namespace Texture
|
||||
} // namespace Tegra
|
||||
@@ -49,4 +49,116 @@ static inline u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) {
|
||||
return (i + offset) * bytes_per_pixel;
|
||||
}
|
||||
|
||||
static inline u32 MortonInterleave128(u32 x, u32 y) {
|
||||
// 128x128 Z-Order coordinate from 2D coordinates
|
||||
static constexpr u32 xlut[] = {
|
||||
0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042,
|
||||
0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809,
|
||||
0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000,
|
||||
0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043,
|
||||
0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a,
|
||||
0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001,
|
||||
0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048,
|
||||
0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b,
|
||||
0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002,
|
||||
0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049,
|
||||
0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840,
|
||||
0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003,
|
||||
0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a,
|
||||
0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841,
|
||||
0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008,
|
||||
0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b,
|
||||
0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842,
|
||||
0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009,
|
||||
0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800,
|
||||
0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843,
|
||||
0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a,
|
||||
0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801,
|
||||
0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848,
|
||||
0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b,
|
||||
0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802,
|
||||
0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849,
|
||||
0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040,
|
||||
0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803,
|
||||
0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a,
|
||||
0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041,
|
||||
0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808,
|
||||
0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b,
|
||||
0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042,
|
||||
0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809,
|
||||
0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b,
|
||||
};
|
||||
static constexpr u32 ylut[] = {
|
||||
0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090,
|
||||
0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124,
|
||||
0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200,
|
||||
0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294,
|
||||
0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330,
|
||||
0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404,
|
||||
0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0,
|
||||
0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534,
|
||||
0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610,
|
||||
0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4,
|
||||
0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780,
|
||||
0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014,
|
||||
0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0,
|
||||
0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184,
|
||||
0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220,
|
||||
0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4,
|
||||
0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390,
|
||||
0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424,
|
||||
0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500,
|
||||
0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594,
|
||||
0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630,
|
||||
0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704,
|
||||
0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0,
|
||||
0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034,
|
||||
0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110,
|
||||
0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4,
|
||||
0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280,
|
||||
0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314,
|
||||
0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0,
|
||||
0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484,
|
||||
0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520,
|
||||
0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4,
|
||||
0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690,
|
||||
0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724,
|
||||
0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4,
|
||||
};
|
||||
return xlut[x % 128] + ylut[y % 128];
|
||||
}
|
||||
|
||||
static inline u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
|
||||
// Calculates the offset of the position of the pixel in Morton order
|
||||
// Framebuffer images are split into 128x128 tiles.
|
||||
|
||||
const unsigned int block_height = 128;
|
||||
const unsigned int coarse_x = x & ~127;
|
||||
|
||||
u32 i = MortonInterleave128(x, y);
|
||||
|
||||
const unsigned int offset = coarse_x * block_height;
|
||||
|
||||
return (i + offset) * bytes_per_pixel;
|
||||
}
|
||||
|
||||
static inline void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel,
|
||||
u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data,
|
||||
bool morton_to_gl) {
|
||||
u8* data_ptrs[2];
|
||||
for (unsigned y = 0; y < height; ++y) {
|
||||
for (unsigned x = 0; x < width; ++x) {
|
||||
const u32 coarse_y = y & ~127;
|
||||
u32 morton_offset =
|
||||
GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel;
|
||||
u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel;
|
||||
|
||||
data_ptrs[morton_to_gl] = morton_data + morton_offset;
|
||||
data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
|
||||
|
||||
memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
|
||||
@@ -26,7 +26,7 @@ bool Init(EmuWindow* emu_window) {
|
||||
if (g_renderer->Init()) {
|
||||
LOG_DEBUG(Render, "initialized OK");
|
||||
} else {
|
||||
LOG_ERROR(Render, "initialization failed !");
|
||||
LOG_CRITICAL(Render, "initialization failed !");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -15,6 +15,8 @@ class RendererBase;
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
enum class Renderer { Software, OpenGL };
|
||||
|
||||
extern std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin
|
||||
extern EmuWindow* g_emu_window; ///< Emu window
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ add_executable(yuzu
|
||||
configuration/configure_input.h
|
||||
configuration/configure_system.cpp
|
||||
configuration/configure_system.h
|
||||
debugger/graphics/graphics_breakpoint_observer.cpp
|
||||
debugger/graphics/graphics_breakpoint_observer.h
|
||||
debugger/graphics/graphics_breakpoints.cpp
|
||||
debugger/graphics/graphics_breakpoints.h
|
||||
debugger/graphics/graphics_breakpoints_p.h
|
||||
debugger/graphics/graphics_surface.cpp
|
||||
debugger/graphics/graphics_surface.h
|
||||
debugger/profiler.cpp
|
||||
debugger/profiler.h
|
||||
debugger/registers.cpp
|
||||
|
||||
@@ -77,8 +77,7 @@ void Config::ReadValues() {
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
Settings::values.cpu_core =
|
||||
static_cast<Settings::CpuCore>(qt_config->value("cpu_core", 1).toInt());
|
||||
Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Renderer");
|
||||
@@ -94,6 +93,10 @@ void Config::ReadValues() {
|
||||
Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("System");
|
||||
Settings::values.use_docked_mode = qt_config->value("use_docked_mode", true).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Miscellaneous");
|
||||
Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString();
|
||||
qt_config->endGroup();
|
||||
@@ -171,7 +174,7 @@ void Config::SaveValues() {
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
qt_config->setValue("cpu_core", static_cast<int>(Settings::values.cpu_core));
|
||||
qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Renderer");
|
||||
@@ -188,6 +191,10 @@ void Config::SaveValues() {
|
||||
qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("System");
|
||||
qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Miscellaneous");
|
||||
qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter));
|
||||
qt_config->endGroup();
|
||||
|
||||
@@ -15,7 +15,8 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
|
||||
|
||||
this->setConfiguration();
|
||||
|
||||
ui->cpu_core_combobox->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
||||
ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
||||
ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
||||
}
|
||||
|
||||
ConfigureGeneral::~ConfigureGeneral() {}
|
||||
@@ -23,13 +24,14 @@ ConfigureGeneral::~ConfigureGeneral() {}
|
||||
void ConfigureGeneral::setConfiguration() {
|
||||
ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
|
||||
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
|
||||
ui->cpu_core_combobox->setCurrentIndex(static_cast<int>(Settings::values.cpu_core));
|
||||
ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
|
||||
ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
|
||||
}
|
||||
|
||||
void ConfigureGeneral::applyConfiguration() {
|
||||
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
|
||||
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
|
||||
Settings::values.cpu_core =
|
||||
static_cast<Settings::CpuCore>(ui->cpu_core_combobox->currentIndex());
|
||||
Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked();
|
||||
Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
@@ -13,17 +13,17 @@
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QHBoxLayout" name="HorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QVBoxLayout" name="VerticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<widget class="QGroupBox" name="GeneralGroupBox">
|
||||
<property name="title">
|
||||
<string>General</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<layout class="QHBoxLayout" name="GeneralHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<layout class="QVBoxLayout" name="GeneralVerticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_deepscan">
|
||||
<property name="text">
|
||||
@@ -44,40 +44,53 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>CPU Core</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QComboBox" name="cpu_core_combobox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Unicorn</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Dynarmic</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<widget class="QGroupBox" name="PerformanceGroupBox">
|
||||
<property name="title">
|
||||
<string>Performance</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="PerformanceHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="PerformanceVerticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_cpu_jit">
|
||||
<property name="text">
|
||||
<string>Enable CPU JIT</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<widget class="QGroupBox" name="EmulationGroupBox">
|
||||
<property name="title">
|
||||
<string>Emulation</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="EmulationHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="EmulationVerticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_docked_mode">
|
||||
<property name="text">
|
||||
<string>Enable docked mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="HotKeysGroupBox">
|
||||
<property name="title">
|
||||
<string>Hotkeys</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<layout class="QHBoxLayout" name="HotKeysHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<layout class="QVBoxLayout" name="HotKeysVerticalLayout">
|
||||
<item>
|
||||
<widget class="GHotkeysDialog" name="widget" native="true"/>
|
||||
</item>
|
||||
|
||||
27
src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp
Normal file
27
src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QMetaType>
|
||||
#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
|
||||
|
||||
BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context,
|
||||
const QString& title, QWidget* parent)
|
||||
: QDockWidget(title, parent), BreakPointObserver(debug_context) {
|
||||
qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event");
|
||||
|
||||
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
|
||||
|
||||
// NOTE: This signal is emitted from a non-GUI thread, but connect() takes
|
||||
// care of delaying its handling to the GUI thread.
|
||||
connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this,
|
||||
SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
|
||||
}
|
||||
|
||||
void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) {
|
||||
emit BreakPointHit(event, data);
|
||||
}
|
||||
|
||||
void BreakPointObserverDock::OnMaxwellResume() {
|
||||
emit Resumed();
|
||||
}
|
||||
33
src/yuzu/debugger/graphics/graphics_breakpoint_observer.h
Normal file
33
src/yuzu/debugger/graphics/graphics_breakpoint_observer.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
|
||||
/**
|
||||
* Utility class which forwards calls to OnMaxwellBreakPointHit and OnMaxwellResume to public slots.
|
||||
* This is because the Maxwell breakpoint callbacks are called from a non-GUI thread, while
|
||||
* the widget usually wants to perform reactions in the GUI thread.
|
||||
*/
|
||||
class BreakPointObserverDock : public QDockWidget,
|
||||
protected Tegra::DebugContext::BreakPointObserver {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BreakPointObserverDock(std::shared_ptr<Tegra::DebugContext> debug_context, const QString& title,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
|
||||
void OnMaxwellResume() override;
|
||||
|
||||
private slots:
|
||||
virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0;
|
||||
virtual void OnResumed() = 0;
|
||||
|
||||
signals:
|
||||
void Resumed();
|
||||
void BreakPointHit(Tegra::DebugContext::Event event, void* data);
|
||||
};
|
||||
212
src/yuzu/debugger/graphics/graphics_breakpoints.cpp
Normal file
212
src/yuzu/debugger/graphics/graphics_breakpoints.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QLabel>
|
||||
#include <QMetaType>
|
||||
#include <QPushButton>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
#include "common/assert.h"
|
||||
#include "yuzu/debugger/graphics/graphics_breakpoints.h"
|
||||
#include "yuzu/debugger/graphics/graphics_breakpoints_p.h"
|
||||
|
||||
BreakPointModel::BreakPointModel(std::shared_ptr<Tegra::DebugContext> debug_context,
|
||||
QObject* parent)
|
||||
: QAbstractListModel(parent), context_weak(debug_context),
|
||||
at_breakpoint(debug_context->at_breakpoint),
|
||||
active_breakpoint(debug_context->active_breakpoint) {}
|
||||
|
||||
int BreakPointModel::columnCount(const QModelIndex& parent) const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int BreakPointModel::rowCount(const QModelIndex& parent) const {
|
||||
return static_cast<int>(Tegra::DebugContext::Event::NumEvents);
|
||||
}
|
||||
|
||||
QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
|
||||
const auto event = static_cast<Tegra::DebugContext::Event>(index.row());
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
if (index.column() == 0) {
|
||||
static const std::map<Tegra::DebugContext::Event, QString> map = {
|
||||
{Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")},
|
||||
{Tegra::DebugContext::Event::MaxwellCommandProcessed,
|
||||
tr("Maxwell command processed")},
|
||||
{Tegra::DebugContext::Event::IncomingPrimitiveBatch,
|
||||
tr("Incoming primitive batch")},
|
||||
{Tegra::DebugContext::Event::FinishedPrimitiveBatch,
|
||||
tr("Finished primitive batch")},
|
||||
};
|
||||
|
||||
DEBUG_ASSERT(map.size() == static_cast<size_t>(Tegra::DebugContext::Event::NumEvents));
|
||||
return (map.find(event) != map.end()) ? map.at(event) : QString();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::CheckStateRole: {
|
||||
if (index.column() == 0)
|
||||
return data(index, Role_IsEnabled).toBool() ? Qt::Checked : Qt::Unchecked;
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::BackgroundRole: {
|
||||
if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) {
|
||||
return QBrush(QColor(0xE0, 0xE0, 0x10));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Role_IsEnabled: {
|
||||
auto context = context_weak.lock();
|
||||
return context && context->breakpoints[(int)event].enabled;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags BreakPointModel::flags(const QModelIndex& index) const {
|
||||
if (!index.isValid())
|
||||
return 0;
|
||||
|
||||
Qt::ItemFlags flags = Qt::ItemIsEnabled;
|
||||
if (index.column() == 0)
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
return flags;
|
||||
}
|
||||
|
||||
bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) {
|
||||
const auto event = static_cast<Tegra::DebugContext::Event>(index.row());
|
||||
|
||||
switch (role) {
|
||||
case Qt::CheckStateRole: {
|
||||
if (index.column() != 0)
|
||||
return false;
|
||||
|
||||
auto context = context_weak.lock();
|
||||
if (!context)
|
||||
return false;
|
||||
|
||||
context->breakpoints[(int)event].enabled = value == Qt::Checked;
|
||||
QModelIndex changed_index = createIndex(index.row(), 0);
|
||||
emit dataChanged(changed_index, changed_index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BreakPointModel::OnBreakPointHit(Tegra::DebugContext::Event event) {
|
||||
auto context = context_weak.lock();
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
active_breakpoint = context->active_breakpoint;
|
||||
at_breakpoint = context->at_breakpoint;
|
||||
emit dataChanged(createIndex(static_cast<int>(event), 0),
|
||||
createIndex(static_cast<int>(event), 0));
|
||||
}
|
||||
|
||||
void BreakPointModel::OnResumed() {
|
||||
auto context = context_weak.lock();
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
at_breakpoint = context->at_breakpoint;
|
||||
emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0),
|
||||
createIndex(static_cast<int>(active_breakpoint), 0));
|
||||
active_breakpoint = context->active_breakpoint;
|
||||
}
|
||||
|
||||
GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent)
|
||||
: QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver(
|
||||
debug_context) {
|
||||
setObjectName("TegraBreakPointsWidget");
|
||||
|
||||
status_text = new QLabel(tr("Emulation running"));
|
||||
resume_button = new QPushButton(tr("Resume"));
|
||||
resume_button->setEnabled(false);
|
||||
|
||||
breakpoint_model = new BreakPointModel(debug_context, this);
|
||||
breakpoint_list = new QTreeView;
|
||||
breakpoint_list->setRootIsDecorated(false);
|
||||
breakpoint_list->setHeaderHidden(true);
|
||||
breakpoint_list->setModel(breakpoint_model);
|
||||
|
||||
qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event");
|
||||
|
||||
connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this,
|
||||
SLOT(OnItemDoubleClicked(const QModelIndex&)));
|
||||
|
||||
connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested()));
|
||||
|
||||
connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this,
|
||||
SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection);
|
||||
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed()));
|
||||
|
||||
connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model,
|
||||
SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection);
|
||||
connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed()));
|
||||
|
||||
connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)),
|
||||
breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)));
|
||||
|
||||
QWidget* main_widget = new QWidget;
|
||||
auto main_layout = new QVBoxLayout;
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(status_text);
|
||||
sub_layout->addWidget(resume_button);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
main_layout->addWidget(breakpoint_list);
|
||||
main_widget->setLayout(main_layout);
|
||||
|
||||
setWidget(main_widget);
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnMaxwellBreakPointHit(Event event, void* data) {
|
||||
// Process in GUI thread
|
||||
emit BreakPointHit(event, data);
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
|
||||
status_text->setText(tr("Emulation halted at breakpoint"));
|
||||
resume_button->setEnabled(true);
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnMaxwellResume() {
|
||||
// Process in GUI thread
|
||||
emit Resumed();
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnResumed() {
|
||||
status_text->setText(tr("Emulation running"));
|
||||
resume_button->setEnabled(false);
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnResumeRequested() {
|
||||
if (auto context = context_weak.lock())
|
||||
context->Resume();
|
||||
}
|
||||
|
||||
void GraphicsBreakPointsWidget::OnItemDoubleClicked(const QModelIndex& index) {
|
||||
if (!index.isValid())
|
||||
return;
|
||||
|
||||
QModelIndex check_index = breakpoint_list->model()->index(index.row(), 0);
|
||||
QVariant enabled = breakpoint_list->model()->data(check_index, Qt::CheckStateRole);
|
||||
QVariant new_state = Qt::Unchecked;
|
||||
if (enabled == Qt::Unchecked)
|
||||
new_state = Qt::Checked;
|
||||
breakpoint_list->model()->setData(check_index, new_state, Qt::CheckStateRole);
|
||||
}
|
||||
46
src/yuzu/debugger/graphics/graphics_breakpoints.h
Normal file
46
src/yuzu/debugger/graphics/graphics_breakpoints.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QDockWidget>
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class QTreeView;
|
||||
|
||||
class BreakPointModel;
|
||||
|
||||
class GraphicsBreakPointsWidget : public QDockWidget, Tegra::DebugContext::BreakPointObserver {
|
||||
Q_OBJECT
|
||||
|
||||
using Event = Tegra::DebugContext::Event;
|
||||
|
||||
public:
|
||||
explicit GraphicsBreakPointsWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
|
||||
void OnMaxwellResume() override;
|
||||
|
||||
public slots:
|
||||
void OnBreakPointHit(Tegra::DebugContext::Event event, void* data);
|
||||
void OnItemDoubleClicked(const QModelIndex&);
|
||||
void OnResumeRequested();
|
||||
void OnResumed();
|
||||
|
||||
signals:
|
||||
void Resumed();
|
||||
void BreakPointHit(Tegra::DebugContext::Event event, void* data);
|
||||
void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
|
||||
private:
|
||||
QLabel* status_text;
|
||||
QPushButton* resume_button;
|
||||
|
||||
BreakPointModel* breakpoint_model;
|
||||
QTreeView* breakpoint_list;
|
||||
};
|
||||
36
src/yuzu/debugger/graphics/graphics_breakpoints_p.h
Normal file
36
src/yuzu/debugger/graphics/graphics_breakpoints_p.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QAbstractListModel>
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
|
||||
class BreakPointModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum {
|
||||
Role_IsEnabled = Qt::UserRole,
|
||||
};
|
||||
|
||||
BreakPointModel(std::shared_ptr<Tegra::DebugContext> context, QObject* parent);
|
||||
|
||||
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
|
||||
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
|
||||
|
||||
public slots:
|
||||
void OnBreakPointHit(Tegra::DebugContext::Event event);
|
||||
void OnResumed();
|
||||
|
||||
private:
|
||||
std::weak_ptr<Tegra::DebugContext> context_weak;
|
||||
bool at_breakpoint;
|
||||
Tegra::DebugContext::Event active_breakpoint;
|
||||
};
|
||||
451
src/yuzu/debugger/graphics/graphics_surface.cpp
Normal file
451
src/yuzu/debugger/graphics/graphics_surface.cpp
Normal file
@@ -0,0 +1,451 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QBoxLayout>
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QFileDialog>
|
||||
#include <QLabel>
|
||||
#include <QMouseEvent>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSpinBox>
|
||||
#include "core/core.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
#include "video_core/utils.h"
|
||||
#include "yuzu/debugger/graphics/graphics_surface.h"
|
||||
#include "yuzu/util/spinbox.h"
|
||||
|
||||
static Tegra::Texture::TextureFormat ConvertToTextureFormat(
|
||||
Tegra::RenderTargetFormat render_target_format) {
|
||||
switch (render_target_format) {
|
||||
case Tegra::RenderTargetFormat::RGBA8_UNORM:
|
||||
return Tegra::Texture::TextureFormat::A8R8G8B8;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented RT format");
|
||||
}
|
||||
}
|
||||
|
||||
SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
|
||||
: QLabel(parent), surface_widget(surface_widget_) {}
|
||||
SurfacePicture::~SurfacePicture() {}
|
||||
|
||||
void SurfacePicture::mousePressEvent(QMouseEvent* event) {
|
||||
// Only do something while the left mouse button is held down
|
||||
if (!(event->buttons() & Qt::LeftButton))
|
||||
return;
|
||||
|
||||
if (pixmap() == nullptr)
|
||||
return;
|
||||
|
||||
if (surface_widget)
|
||||
surface_widget->Pick(event->x() * pixmap()->width() / width(),
|
||||
event->y() * pixmap()->height() / height());
|
||||
}
|
||||
|
||||
void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
|
||||
// We also want to handle the event if the user moves the mouse while holding down the LMB
|
||||
mousePressEvent(event);
|
||||
}
|
||||
|
||||
GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
|
||||
QWidget* parent)
|
||||
: BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent),
|
||||
surface_source(Source::RenderTarget0) {
|
||||
setObjectName("MaxwellSurface");
|
||||
|
||||
surface_source_list = new QComboBox;
|
||||
surface_source_list->addItem(tr("Render Target 0"));
|
||||
surface_source_list->addItem(tr("Render Target 1"));
|
||||
surface_source_list->addItem(tr("Render Target 2"));
|
||||
surface_source_list->addItem(tr("Render Target 3"));
|
||||
surface_source_list->addItem(tr("Render Target 4"));
|
||||
surface_source_list->addItem(tr("Render Target 5"));
|
||||
surface_source_list->addItem(tr("Render Target 6"));
|
||||
surface_source_list->addItem(tr("Render Target 7"));
|
||||
surface_source_list->addItem(tr("Z Buffer"));
|
||||
surface_source_list->addItem(tr("Custom"));
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
|
||||
|
||||
surface_address_control = new CSpinBox;
|
||||
surface_address_control->SetBase(16);
|
||||
surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF);
|
||||
surface_address_control->SetPrefix("0x");
|
||||
|
||||
unsigned max_dimension = 16384; // TODO: Find actual maximum
|
||||
|
||||
surface_width_control = new QSpinBox;
|
||||
surface_width_control->setRange(0, max_dimension);
|
||||
|
||||
surface_height_control = new QSpinBox;
|
||||
surface_height_control->setRange(0, max_dimension);
|
||||
|
||||
surface_picker_x_control = new QSpinBox;
|
||||
surface_picker_x_control->setRange(0, max_dimension - 1);
|
||||
|
||||
surface_picker_y_control = new QSpinBox;
|
||||
surface_picker_y_control->setRange(0, max_dimension - 1);
|
||||
|
||||
surface_format_control = new QComboBox;
|
||||
|
||||
// Color formats sorted by Maxwell texture format index
|
||||
surface_format_control->addItem(tr("None"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("A8R8G8B8"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("Unknown"));
|
||||
surface_format_control->addItem(tr("DXT1"));
|
||||
surface_format_control->addItem(tr("DXT23"));
|
||||
surface_format_control->addItem(tr("DXT45"));
|
||||
surface_format_control->addItem(tr("DXN1"));
|
||||
surface_format_control->addItem(tr("DXN2"));
|
||||
|
||||
surface_info_label = new QLabel();
|
||||
surface_info_label->setWordWrap(true);
|
||||
|
||||
surface_picture_label = new SurfacePicture(0, this);
|
||||
surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
surface_picture_label->setScaledContents(false);
|
||||
|
||||
auto scroll_area = new QScrollArea();
|
||||
scroll_area->setBackgroundRole(QPalette::Dark);
|
||||
scroll_area->setWidgetResizable(false);
|
||||
scroll_area->setWidget(surface_picture_label);
|
||||
|
||||
save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
|
||||
|
||||
// Connections
|
||||
connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
|
||||
connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(OnSurfaceSourceChanged(int)));
|
||||
connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this,
|
||||
SLOT(OnSurfaceAddressChanged(qint64)));
|
||||
connect(surface_width_control, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(OnSurfaceWidthChanged(int)));
|
||||
connect(surface_height_control, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(OnSurfaceHeightChanged(int)));
|
||||
connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(OnSurfaceFormatChanged(int)));
|
||||
connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(OnSurfacePickerXChanged(int)));
|
||||
connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(OnSurfacePickerYChanged(int)));
|
||||
connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface()));
|
||||
|
||||
auto main_widget = new QWidget;
|
||||
auto main_layout = new QVBoxLayout;
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Source:")));
|
||||
sub_layout->addWidget(surface_source_list);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("GPU Address:")));
|
||||
sub_layout->addWidget(surface_address_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Width:")));
|
||||
sub_layout->addWidget(surface_width_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Height:")));
|
||||
sub_layout->addWidget(surface_height_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Format:")));
|
||||
sub_layout->addWidget(surface_format_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
main_layout->addWidget(scroll_area);
|
||||
|
||||
auto info_layout = new QHBoxLayout;
|
||||
{
|
||||
auto xy_layout = new QVBoxLayout;
|
||||
{
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("X:")));
|
||||
sub_layout->addWidget(surface_picker_x_control);
|
||||
xy_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Y:")));
|
||||
sub_layout->addWidget(surface_picker_y_control);
|
||||
xy_layout->addLayout(sub_layout);
|
||||
}
|
||||
}
|
||||
info_layout->addLayout(xy_layout);
|
||||
surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
info_layout->addWidget(surface_info_label);
|
||||
}
|
||||
main_layout->addLayout(info_layout);
|
||||
|
||||
main_layout->addWidget(save_surface);
|
||||
main_widget->setLayout(main_layout);
|
||||
setWidget(main_widget);
|
||||
|
||||
// Load current data - TODO: Make sure this works when emulation is not running
|
||||
if (debug_context && debug_context->at_breakpoint) {
|
||||
emit Update();
|
||||
widget()->setEnabled(debug_context->at_breakpoint);
|
||||
} else {
|
||||
widget()->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
|
||||
emit Update();
|
||||
widget()->setEnabled(true);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnResumed() {
|
||||
widget()->setEnabled(false);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) {
|
||||
surface_source = static_cast<Source>(new_value);
|
||||
emit Update();
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) {
|
||||
if (surface_address != new_value) {
|
||||
surface_address = static_cast<Tegra::GPUVAddr>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) {
|
||||
if (surface_width != static_cast<unsigned>(new_value)) {
|
||||
surface_width = static_cast<unsigned>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) {
|
||||
if (surface_height != static_cast<unsigned>(new_value)) {
|
||||
surface_height = static_cast<unsigned>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) {
|
||||
if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) {
|
||||
surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) {
|
||||
if (surface_picker_x != new_value) {
|
||||
surface_picker_x = new_value;
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) {
|
||||
if (surface_picker_y != new_value) {
|
||||
surface_picker_y = new_value;
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::Pick(int x, int y) {
|
||||
surface_picker_x_control->setValue(x);
|
||||
surface_picker_y_control->setValue(y);
|
||||
|
||||
if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 ||
|
||||
y >= static_cast<int>(surface_height)) {
|
||||
surface_info_label->setText(tr("Pixel out of bounds"));
|
||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
return;
|
||||
}
|
||||
|
||||
surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>"));
|
||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnUpdate() {
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
|
||||
QPixmap pixmap;
|
||||
|
||||
switch (surface_source) {
|
||||
case Source::RenderTarget0:
|
||||
case Source::RenderTarget1:
|
||||
case Source::RenderTarget2:
|
||||
case Source::RenderTarget3:
|
||||
case Source::RenderTarget4:
|
||||
case Source::RenderTarget5:
|
||||
case Source::RenderTarget6:
|
||||
case Source::RenderTarget7: {
|
||||
// TODO: Store a reference to the registers in the debug context instead of accessing them
|
||||
// directly...
|
||||
|
||||
auto& registers = gpu.Get3DEngine().regs;
|
||||
auto& rt = registers.rt[static_cast<size_t>(surface_source) -
|
||||
static_cast<size_t>(Source::RenderTarget0)];
|
||||
|
||||
surface_address = rt.Address();
|
||||
surface_width = rt.width;
|
||||
surface_height = rt.height;
|
||||
if (rt.format != Tegra::RenderTargetFormat::NONE) {
|
||||
surface_format = ConvertToTextureFormat(rt.format);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Source::Custom: {
|
||||
// Keep user-specified values
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
|
||||
break;
|
||||
}
|
||||
|
||||
surface_address_control->SetValue(surface_address);
|
||||
surface_width_control->setValue(surface_width);
|
||||
surface_height_control->setValue(surface_height);
|
||||
surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
|
||||
|
||||
if (surface_address == 0) {
|
||||
surface_picture_label->hide();
|
||||
surface_info_label->setText(tr("(invalid surface address)"));
|
||||
surface_info_label->setAlignment(Qt::AlignCenter);
|
||||
surface_picker_x_control->setEnabled(false);
|
||||
surface_picker_y_control->setEnabled(false);
|
||||
save_surface->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement a good way to visualize alpha components!
|
||||
|
||||
QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
|
||||
VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address);
|
||||
|
||||
auto unswizzled_data =
|
||||
Tegra::Texture::UnswizzleTexture(address, surface_format, surface_width, surface_height);
|
||||
|
||||
auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
|
||||
surface_width, surface_height);
|
||||
|
||||
surface_picture_label->show();
|
||||
|
||||
for (unsigned int y = 0; y < surface_height; ++y) {
|
||||
for (unsigned int x = 0; x < surface_width; ++x) {
|
||||
Math::Vec4<u8> color;
|
||||
color[0] = texture_data[x + y * surface_width + 0];
|
||||
color[1] = texture_data[x + y * surface_width + 1];
|
||||
color[2] = texture_data[x + y * surface_width + 2];
|
||||
color[3] = texture_data[x + y * surface_width + 3];
|
||||
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
|
||||
}
|
||||
}
|
||||
|
||||
pixmap = QPixmap::fromImage(decoded_image);
|
||||
surface_picture_label->setPixmap(pixmap);
|
||||
surface_picture_label->resize(pixmap.size());
|
||||
|
||||
// Update the info with pixel data
|
||||
surface_picker_x_control->setEnabled(true);
|
||||
surface_picker_y_control->setEnabled(true);
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
|
||||
// Enable saving the converted pixmap to file
|
||||
save_surface->setEnabled(true);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::SaveSurface() {
|
||||
QString png_filter = tr("Portable Network Graphic (*.png)");
|
||||
QString bin_filter = tr("Binary data (*.bin)");
|
||||
|
||||
QString selectedFilter;
|
||||
QString filename = QFileDialog::getSaveFileName(
|
||||
this, tr("Save Surface"),
|
||||
QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
|
||||
QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
|
||||
|
||||
if (filename.isEmpty()) {
|
||||
// If the user canceled the dialog, don't save anything.
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedFilter == png_filter) {
|
||||
const QPixmap* pixmap = surface_picture_label->pixmap();
|
||||
ASSERT_MSG(pixmap != nullptr, "No pixmap set");
|
||||
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::WriteOnly);
|
||||
if (pixmap)
|
||||
pixmap->save(&file, "PNG");
|
||||
} else if (selectedFilter == bin_filter) {
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
VAddr address = gpu.memory_manager->PhysicalToVirtualAddress(surface_address);
|
||||
|
||||
const u8* buffer = Memory::GetPointer(address);
|
||||
ASSERT_MSG(buffer != nullptr, "Memory not accessible");
|
||||
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::WriteOnly);
|
||||
int size = surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format);
|
||||
QByteArray data(reinterpret_cast<const char*>(buffer), size);
|
||||
file.write(data);
|
||||
} else {
|
||||
UNREACHABLE_MSG("Unhandled filter selected");
|
||||
}
|
||||
}
|
||||
97
src/yuzu/debugger/graphics/graphics_surface.h
Normal file
97
src/yuzu/debugger/graphics/graphics_surface.h
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
|
||||
|
||||
class QComboBox;
|
||||
class QSpinBox;
|
||||
class CSpinBox;
|
||||
|
||||
class GraphicsSurfaceWidget;
|
||||
|
||||
class SurfacePicture : public QLabel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SurfacePicture(QWidget* parent = nullptr,
|
||||
GraphicsSurfaceWidget* surface_widget = nullptr);
|
||||
~SurfacePicture();
|
||||
|
||||
protected slots:
|
||||
virtual void mouseMoveEvent(QMouseEvent* event);
|
||||
virtual void mousePressEvent(QMouseEvent* event);
|
||||
|
||||
private:
|
||||
GraphicsSurfaceWidget* surface_widget;
|
||||
};
|
||||
|
||||
class GraphicsSurfaceWidget : public BreakPointObserverDock {
|
||||
Q_OBJECT
|
||||
|
||||
using Event = Tegra::DebugContext::Event;
|
||||
|
||||
enum class Source {
|
||||
RenderTarget0 = 0,
|
||||
RenderTarget1 = 1,
|
||||
RenderTarget2 = 2,
|
||||
RenderTarget3 = 3,
|
||||
RenderTarget4 = 4,
|
||||
RenderTarget5 = 5,
|
||||
RenderTarget6 = 6,
|
||||
RenderTarget7 = 7,
|
||||
ZBuffer = 8,
|
||||
Custom = 9,
|
||||
};
|
||||
|
||||
public:
|
||||
explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
|
||||
QWidget* parent = nullptr);
|
||||
void Pick(int x, int y);
|
||||
|
||||
public slots:
|
||||
void OnSurfaceSourceChanged(int new_value);
|
||||
void OnSurfaceAddressChanged(qint64 new_value);
|
||||
void OnSurfaceWidthChanged(int new_value);
|
||||
void OnSurfaceHeightChanged(int new_value);
|
||||
void OnSurfaceFormatChanged(int new_value);
|
||||
void OnSurfacePickerXChanged(int new_value);
|
||||
void OnSurfacePickerYChanged(int new_value);
|
||||
void OnUpdate();
|
||||
|
||||
private slots:
|
||||
void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
|
||||
void OnResumed() override;
|
||||
|
||||
void SaveSurface();
|
||||
|
||||
signals:
|
||||
void Update();
|
||||
|
||||
private:
|
||||
QComboBox* surface_source_list;
|
||||
CSpinBox* surface_address_control;
|
||||
QSpinBox* surface_width_control;
|
||||
QSpinBox* surface_height_control;
|
||||
QComboBox* surface_format_control;
|
||||
|
||||
SurfacePicture* surface_picture_label;
|
||||
QSpinBox* surface_picker_x_control;
|
||||
QSpinBox* surface_picker_y_control;
|
||||
QLabel* surface_info_label;
|
||||
QPushButton* save_surface;
|
||||
|
||||
Source surface_source;
|
||||
Tegra::GPUVAddr surface_address;
|
||||
unsigned surface_width;
|
||||
unsigned surface_height;
|
||||
Tegra::Texture::TextureFormat surface_format;
|
||||
int surface_picker_x = 0;
|
||||
int surface_picker_y = 0;
|
||||
};
|
||||
@@ -25,10 +25,13 @@
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "yuzu/about_dialog.h"
|
||||
#include "yuzu/bootmanager.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/configuration/configure_dialog.h"
|
||||
#include "yuzu/debugger/graphics/graphics_breakpoints.h"
|
||||
#include "yuzu/debugger/graphics/graphics_surface.h"
|
||||
#include "yuzu/debugger/profiler.h"
|
||||
#include "yuzu/debugger/registers.h"
|
||||
#include "yuzu/debugger/wait_tree.h"
|
||||
@@ -68,6 +71,9 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
|
||||
void GMainWindow::ShowCallouts() {}
|
||||
|
||||
GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
|
||||
|
||||
debug_context = Tegra::DebugContext::Construct();
|
||||
|
||||
setAcceptDrops(true);
|
||||
ui.setupUi(this);
|
||||
statusBar()->hide();
|
||||
@@ -160,6 +166,16 @@ void GMainWindow::InitializeDebugWidgets() {
|
||||
connect(this, &GMainWindow::EmulationStopping, registersWidget,
|
||||
&RegistersWidget::OnEmulationStopping);
|
||||
|
||||
graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(debug_context, this);
|
||||
addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
|
||||
graphicsBreakpointsWidget->hide();
|
||||
debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
|
||||
|
||||
graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this);
|
||||
addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget);
|
||||
graphicsSurfaceWidget->hide();
|
||||
debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction());
|
||||
|
||||
waitTreeWidget = new WaitTreeWidget(this);
|
||||
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
|
||||
waitTreeWidget->hide();
|
||||
@@ -324,6 +340,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
|
||||
system.SetGPUDebugContext(debug_context);
|
||||
|
||||
const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
|
||||
|
||||
Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#ifndef _CITRA_QT_MAIN_HXX_
|
||||
#define _CITRA_QT_MAIN_HXX_
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QMainWindow>
|
||||
@@ -15,17 +14,18 @@ class Config;
|
||||
class EmuThread;
|
||||
class GameList;
|
||||
class GImageInfo;
|
||||
class GPUCommandStreamWidget;
|
||||
class GPUCommandListWidget;
|
||||
class GraphicsBreakPointsWidget;
|
||||
class GraphicsTracingWidget;
|
||||
class GraphicsVertexShaderWidget;
|
||||
class GraphicsSurfaceWidget;
|
||||
class GRenderWindow;
|
||||
class MicroProfileDialog;
|
||||
class ProfilerWidget;
|
||||
class RegistersWidget;
|
||||
class WaitTreeWidget;
|
||||
|
||||
namespace Tegra {
|
||||
class DebugContext;
|
||||
}
|
||||
|
||||
class GMainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -138,6 +138,8 @@ private:
|
||||
|
||||
Ui::MainWindow ui;
|
||||
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
|
||||
GRenderWindow* render_window;
|
||||
GameList* game_list;
|
||||
|
||||
@@ -150,7 +152,7 @@ private:
|
||||
|
||||
std::unique_ptr<Config> config;
|
||||
|
||||
// Whether emulation is currently running in Citra.
|
||||
// Whether emulation is currently running in yuzu.
|
||||
bool emulation_running = false;
|
||||
std::unique_ptr<EmuThread> emu_thread;
|
||||
|
||||
@@ -158,6 +160,8 @@ private:
|
||||
ProfilerWidget* profilerWidget;
|
||||
MicroProfileDialog* microProfileDialog;
|
||||
RegistersWidget* registersWidget;
|
||||
GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
|
||||
GraphicsSurfaceWidget* graphicsSurfaceWidget;
|
||||
WaitTreeWidget* waitTreeWidget;
|
||||
|
||||
QAction* actions_recent_files[max_recent_files_item];
|
||||
@@ -167,5 +171,3 @@ protected:
|
||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||
void dragMoveEvent(QDragMoveEvent* event) override;
|
||||
};
|
||||
|
||||
#endif // _CITRA_QT_MAIN_HXX_
|
||||
|
||||
@@ -90,8 +90,7 @@ void Config::ReadValues() {
|
||||
sdl2_config->Get("Controls", "touch_device", "engine:emu_window");
|
||||
|
||||
// Core
|
||||
Settings::values.cpu_core =
|
||||
static_cast<Settings::CpuCore>(sdl2_config->GetInteger("Core", "cpu_core", 1));
|
||||
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
|
||||
|
||||
// Renderer
|
||||
Settings::values.resolution_factor =
|
||||
@@ -107,6 +106,9 @@ void Config::ReadValues() {
|
||||
Settings::values.use_virtual_sd =
|
||||
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
|
||||
|
||||
// System
|
||||
Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", true);
|
||||
|
||||
// Miscellaneous
|
||||
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
|
||||
|
||||
|
||||
@@ -76,9 +76,9 @@ motion_device=
|
||||
touch_device=
|
||||
|
||||
[Core]
|
||||
# Which CPU core to use for CPU emulation
|
||||
# 0: Unicorn (slow), 1 (default): Dynarmic (faster)
|
||||
cpu_core =
|
||||
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
|
||||
# 0: Interpreter (slow), 1 (default): JIT (fast)
|
||||
use_cpu_jit =
|
||||
|
||||
[Renderer]
|
||||
# Whether to use software or hardware rendering.
|
||||
@@ -154,9 +154,9 @@ output_device =
|
||||
use_virtual_sd =
|
||||
|
||||
[System]
|
||||
# The system model that Citra will try to emulate
|
||||
# 0: Old 3DS (default), 1: New 3DS
|
||||
is_new_3ds =
|
||||
# Whether the system is docked
|
||||
# 1 (default): Yes, 0: No
|
||||
use_docked_mode =
|
||||
|
||||
# The system region that Citra will use during emulation
|
||||
# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
// Icon with lowest ID value placed first to ensure application icon
|
||||
// remains consistent on all systems.
|
||||
CITRA_ICON ICON "../../dist/yuzu.ico"
|
||||
YUZU_ICON ICON "../../dist/yuzu.ico"
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Reference in New Issue
Block a user