Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dce624e3f1 | |||
| 2081ed7db2 | |||
| 1c5636e690 | |||
| 2afe8ac4a7 | |||
| 9cfe2414cb | |||
| c6fd56b00f | |||
| a405373144 | |||
| 1242c1ec0a | |||
| 325f3e0693 | |||
| 89be49d2f3 | |||
| 2714d9e64c | |||
| d2ade27c3f | |||
| 177c45e97d | |||
| 9c206fe94d | |||
| 1ccc0457d5 | |||
| 7a439630bb | |||
| fda8f1da20 | |||
| 0f453488e2 | |||
| a0e1fbfe14 | |||
| 60754b4728 | |||
| 2edab4e840 | |||
| 2bc6abb9a1 | |||
| 6f8ed9508d | |||
| b89fc407d7 | |||
| 948bc87a59 | |||
| ad3dca7e62 | |||
| f32e28c7b8 | |||
| 19d0951ae6 | |||
| 7939ea18e8 | |||
| c69dc5acf9 | |||
| 1c05c06e04 | |||
| 4a587b81b2 | |||
| dc3cc0002c | |||
| 42588493d5 | |||
| 7f7eb29323 | |||
| 123c065086 | |||
| 9bc71fcc5f | |||
| d647d9550c | |||
| 16d65182f9 | |||
| 06578e89b2 | |||
| f08d24e9c0 | |||
| 6683bf50b5 | |||
| e205e74e1f | |||
| 5be8b7a362 | |||
| d626bc8c62 | |||
| 26aaa86ece | |||
| 915ab81ec2 | |||
| d6accf96ff | |||
| 78653f7339 | |||
| b1ccd88434 |
+2
-4
@@ -20,6 +20,7 @@ matrix:
|
||||
install: "./.travis/linux/deps.sh"
|
||||
script: "./.travis/linux/build.sh"
|
||||
after_success: "./.travis/linux/upload.sh"
|
||||
cache: ccache
|
||||
- os: osx
|
||||
env: NAME="macos build"
|
||||
sudo: false
|
||||
@@ -27,6 +28,7 @@ matrix:
|
||||
install: "./.travis/macos/deps.sh"
|
||||
script: "./.travis/macos/build.sh"
|
||||
after_success: "./.travis/macos/upload.sh"
|
||||
cache: ccache
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
@@ -42,7 +44,3 @@ notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://api.yuzu-emu.org/code/travis/notify
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.ccache
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
docker run -e CCACHE_DIR=/ccache -v $HOME/.ccache:/ccache --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh
|
||||
mkdir -p "$HOME/.ccache"
|
||||
docker run --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh
|
||||
|
||||
@@ -5,14 +5,8 @@ apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev
|
||||
|
||||
cd /yuzu
|
||||
|
||||
export PATH=/usr/lib/ccache:$PATH
|
||||
ln -sf /usr/bin/ccache /usr/lib/ccache/cc
|
||||
ln -sf /usr/bin/ccache /usr/lib/ccache/c++
|
||||
mkdir build && cd build
|
||||
ccache --show-stats > ccache_before
|
||||
cmake .. -DYUZU_BUILD_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -G Ninja
|
||||
cmake .. -DYUZU_BUILD_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -G Ninja
|
||||
ninja
|
||||
ccache --show-stats > ccache_after
|
||||
diff -U100 ccache_before ccache_after || true
|
||||
|
||||
ctest -VV -C Release
|
||||
|
||||
@@ -5,14 +5,11 @@ set -o pipefail
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.12
|
||||
export Qt5_DIR=$(brew --prefix)/opt/qt5
|
||||
export UNICORNDIR=$(pwd)/externals/unicorn
|
||||
export PATH="/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
mkdir build && cd build
|
||||
export PATH=/usr/local/opt/ccache/libexec:$PATH
|
||||
ccache --show-stats > ccache_before
|
||||
cmake --version
|
||||
cmake .. -DYUZU_BUILD_UNICORN=ON -DCMAKE_BUILD_TYPE=Release
|
||||
cmake .. -DYUZU_BUILD_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON
|
||||
make -j4
|
||||
ccache --show-stats > ccache_after
|
||||
diff -U100 ccache_before ccache_after || true
|
||||
|
||||
ctest -VV -C Release
|
||||
|
||||
@@ -41,6 +41,19 @@ function(check_submodules_present)
|
||||
endfunction()
|
||||
check_submodules_present()
|
||||
|
||||
configure_file(${CMAKE_SOURCE_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||
${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||
COPYONLY)
|
||||
if (ENABLE_COMPATIBILITY_LIST_DOWNLOAD AND NOT EXISTS ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
||||
message(STATUS "Downloading compatibility list for yuzu...")
|
||||
file(DOWNLOAD
|
||||
https://api.yuzu-emu.org/gamedb/
|
||||
"${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json" SHOW_PROGRESS)
|
||||
endif()
|
||||
if (NOT EXISTS ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json "")
|
||||
endif()
|
||||
|
||||
# Detect current compilation architecture and create standard definitions
|
||||
# =======================================================================
|
||||
|
||||
|
||||
+2
-6
@@ -41,9 +41,9 @@ before_build:
|
||||
- ps: |
|
||||
if ($env:BUILD_TYPE -eq 'msvc') {
|
||||
# redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
|
||||
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 .. 2>&1 && exit 0'
|
||||
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1 && exit 0'
|
||||
} else {
|
||||
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release .. 2>&1"
|
||||
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1"
|
||||
}
|
||||
- cd ..
|
||||
|
||||
@@ -162,10 +162,6 @@ artifacts:
|
||||
- path: $(BUILD_ZIP)
|
||||
name: build
|
||||
type: zip
|
||||
- path: $(BUILD_SYMBOLS)
|
||||
name: debugsymbols
|
||||
- path: $(BUILD_UPDATE)
|
||||
name: update
|
||||
|
||||
deploy:
|
||||
provider: GitHub
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="compatibility_list">
|
||||
<file>compatibility_list.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
Vendored
+1
-1
Submodule externals/dynarmic updated: a42f301c28...0435ac2d80
@@ -9,6 +9,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
|
||||
+20
-8
@@ -2,24 +2,36 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/controller.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
#include "file_sys/vfs_concat.h"
|
||||
#include "file_sys/vfs_real.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
@@ -258,7 +270,7 @@ struct System::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
PerfStats::Results GetAndResetPerfStats() {
|
||||
PerfStatsResults GetAndResetPerfStats() {
|
||||
return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs());
|
||||
}
|
||||
|
||||
@@ -326,7 +338,7 @@ void System::PrepareReschedule() {
|
||||
CurrentCpuCore().PrepareReschedule();
|
||||
}
|
||||
|
||||
PerfStats::Results System::GetAndResetPerfStats() {
|
||||
PerfStatsResults System::GetAndResetPerfStats() {
|
||||
return impl->GetAndResetPerfStats();
|
||||
}
|
||||
|
||||
@@ -429,15 +441,15 @@ void System::SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) {
|
||||
impl->debug_context = std::move(context);
|
||||
}
|
||||
|
||||
std::shared_ptr<Tegra::DebugContext> System::GetGPUDebugContext() const {
|
||||
return impl->debug_context;
|
||||
Tegra::DebugContext* System::GetGPUDebugContext() const {
|
||||
return impl->debug_context.get();
|
||||
}
|
||||
|
||||
void System::SetFilesystem(FileSys::VirtualFilesystem vfs) {
|
||||
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
|
||||
impl->virtual_filesystem = std::move(vfs);
|
||||
}
|
||||
|
||||
FileSys::VirtualFilesystem System::GetFilesystem() const {
|
||||
std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
|
||||
return impl->virtual_filesystem;
|
||||
}
|
||||
|
||||
|
||||
+36
-22
@@ -4,41 +4,55 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "file_sys/vfs_real.h"
|
||||
#include "hle/service/filesystem/filesystem.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
} // namespace Core::Frontend
|
||||
|
||||
namespace FileSys {
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
namespace Kernel {
|
||||
class KernelCore;
|
||||
class Process;
|
||||
class Scheduler;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Loader {
|
||||
class AppLoader;
|
||||
enum class ResultStatus : u16;
|
||||
} // namespace Loader
|
||||
|
||||
namespace Service::SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
} // namespace Service::SM
|
||||
|
||||
namespace Tegra {
|
||||
class DebugContext;
|
||||
class GPU;
|
||||
} // namespace Tegra
|
||||
|
||||
namespace VideoCore {
|
||||
class RendererBase;
|
||||
}
|
||||
} // namespace VideoCore
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Interface;
|
||||
class Cpu;
|
||||
class ExclusiveMonitor;
|
||||
class FrameLimiter;
|
||||
class PerfStats;
|
||||
class TelemetrySession;
|
||||
|
||||
struct PerfStatsResults;
|
||||
|
||||
class System {
|
||||
public:
|
||||
@@ -125,7 +139,7 @@ public:
|
||||
void PrepareReschedule();
|
||||
|
||||
/// Gets and resets core performance statistics
|
||||
PerfStats::Results GetAndResetPerfStats();
|
||||
PerfStatsResults GetAndResetPerfStats();
|
||||
|
||||
/// Gets an ARM interface to the CPU core that is currently running
|
||||
ARM_Interface& CurrentArmInterface();
|
||||
@@ -195,11 +209,11 @@ public:
|
||||
|
||||
void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context);
|
||||
|
||||
std::shared_ptr<Tegra::DebugContext> GetGPUDebugContext() const;
|
||||
Tegra::DebugContext* GetGPUDebugContext() const;
|
||||
|
||||
void SetFilesystem(FileSys::VirtualFilesystem vfs);
|
||||
void SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs);
|
||||
|
||||
FileSys::VirtualFilesystem GetFilesystem() const;
|
||||
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
|
||||
|
||||
private:
|
||||
System();
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
#include <regex>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/encryption_layer.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <utility>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
|
||||
@@ -39,6 +40,7 @@ static std::string ModeFlagsToString(Mode mode) {
|
||||
}
|
||||
|
||||
RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {}
|
||||
RealVfsFilesystem::~RealVfsFilesystem() = default;
|
||||
|
||||
std::string RealVfsFilesystem::GetName() const {
|
||||
return "Real";
|
||||
@@ -219,6 +221,8 @@ RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FileUtil::IOF
|
||||
parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
|
||||
perms(perms_) {}
|
||||
|
||||
RealVfsFile::~RealVfsFile() = default;
|
||||
|
||||
std::string RealVfsFile::GetName() const {
|
||||
return path_components.back();
|
||||
}
|
||||
@@ -312,6 +316,8 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
|
||||
FileUtil::CreateDir(path);
|
||||
}
|
||||
|
||||
RealVfsDirectory::~RealVfsDirectory() = default;
|
||||
|
||||
std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path))
|
||||
|
||||
@@ -6,15 +6,19 @@
|
||||
|
||||
#include <string_view>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileUtil {
|
||||
class IOFile;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class RealVfsFilesystem : public VfsFilesystem {
|
||||
public:
|
||||
RealVfsFilesystem();
|
||||
~RealVfsFilesystem() override;
|
||||
|
||||
std::string GetName() const override;
|
||||
bool IsReadable() const override;
|
||||
@@ -40,10 +44,9 @@ class RealVfsFile : public VfsFile {
|
||||
friend class RealVfsDirectory;
|
||||
friend class RealVfsFilesystem;
|
||||
|
||||
RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<FileUtil::IOFile> backing,
|
||||
const std::string& path, Mode perms = Mode::Read);
|
||||
|
||||
public:
|
||||
~RealVfsFile() override;
|
||||
|
||||
std::string GetName() const override;
|
||||
size_t GetSize() const override;
|
||||
bool Resize(size_t new_size) override;
|
||||
@@ -55,6 +58,9 @@ public:
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
private:
|
||||
RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<FileUtil::IOFile> backing,
|
||||
const std::string& path, Mode perms = Mode::Read);
|
||||
|
||||
bool Close();
|
||||
|
||||
RealVfsFilesystem& base;
|
||||
@@ -70,9 +76,9 @@ private:
|
||||
class RealVfsDirectory : public VfsDirectory {
|
||||
friend class RealVfsFilesystem;
|
||||
|
||||
RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read);
|
||||
|
||||
public:
|
||||
~RealVfsDirectory() override;
|
||||
|
||||
std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
|
||||
std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
|
||||
std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
|
||||
@@ -97,6 +103,8 @@ protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read);
|
||||
|
||||
template <typename T, typename R>
|
||||
std::vector<std::shared_ptr<R>> IterateEntries() const;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -124,6 +125,8 @@ struct KernelCore::Impl {
|
||||
|
||||
timer_callback_handle_table.Clear();
|
||||
timer_callback_event_type = nullptr;
|
||||
|
||||
named_ports.clear();
|
||||
}
|
||||
|
||||
void InitializeResourceLimits(KernelCore& kernel) {
|
||||
@@ -217,6 +220,10 @@ struct KernelCore::Impl {
|
||||
// TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future,
|
||||
// allowing us to simply use a pool index or similar.
|
||||
Kernel::HandleTable thread_wakeup_callback_handle_table;
|
||||
|
||||
/// Map of named ports managed by the kernel, which can be retrieved using
|
||||
/// the ConnectToPort SVC.
|
||||
NamedPortTable named_ports;
|
||||
};
|
||||
|
||||
KernelCore::KernelCore() : impl{std::make_unique<Impl>()} {}
|
||||
@@ -257,6 +264,23 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
|
||||
impl->process_list.push_back(std::move(process));
|
||||
}
|
||||
|
||||
void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
|
||||
impl->named_ports.emplace(std::move(name), std::move(port));
|
||||
}
|
||||
|
||||
KernelCore::NamedPortTable::iterator KernelCore::FindNamedPort(const std::string& name) {
|
||||
return impl->named_ports.find(name);
|
||||
}
|
||||
|
||||
KernelCore::NamedPortTable::const_iterator KernelCore::FindNamedPort(
|
||||
const std::string& name) const {
|
||||
return impl->named_ports.find(name);
|
||||
}
|
||||
|
||||
bool KernelCore::IsValidNamedPort(NamedPortTable::const_iterator port) const {
|
||||
return port != impl->named_ports.cend();
|
||||
}
|
||||
|
||||
u32 KernelCore::CreateNewObjectID() {
|
||||
return impl->next_object_id++;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
template <typename T>
|
||||
@@ -15,6 +17,7 @@ struct EventType;
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class ClientPort;
|
||||
class HandleTable;
|
||||
class Process;
|
||||
class ResourceLimit;
|
||||
@@ -25,6 +28,9 @@ enum class ResourceLimitCategory : u8;
|
||||
|
||||
/// Represents a single instance of the kernel.
|
||||
class KernelCore {
|
||||
private:
|
||||
using NamedPortTable = std::unordered_map<std::string, SharedPtr<ClientPort>>;
|
||||
|
||||
public:
|
||||
KernelCore();
|
||||
~KernelCore();
|
||||
@@ -59,6 +65,18 @@ public:
|
||||
/// Adds the given shared pointer to an internal list of active processes.
|
||||
void AppendNewProcess(SharedPtr<Process> process);
|
||||
|
||||
/// Adds a port to the named port table
|
||||
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
|
||||
|
||||
/// Finds a port within the named port table with the given name.
|
||||
NamedPortTable::iterator FindNamedPort(const std::string& name);
|
||||
|
||||
/// Finds a port within the named port table with the given name.
|
||||
NamedPortTable::const_iterator FindNamedPort(const std::string& name) const;
|
||||
|
||||
/// Determines whether or not the given port is a valid named port.
|
||||
bool IsValidNamedPort(NamedPortTable::const_iterator port) const;
|
||||
|
||||
private:
|
||||
friend class Object;
|
||||
friend class Process;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/kernel/session.h"
|
||||
@@ -104,11 +105,10 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
|
||||
// The ServerSession received a sync request, this means that there's new data available
|
||||
// from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
|
||||
// similar.
|
||||
|
||||
auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
|
||||
Kernel::HLERequestContext context(this);
|
||||
u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress());
|
||||
context.PopulateFromIncomingCommandBuffer(cmd_buf, *Core::CurrentProcess(), handle_table);
|
||||
context.PopulateFromIncomingCommandBuffer(cmd_buf, *Core::CurrentProcess(),
|
||||
kernel.HandleTable());
|
||||
|
||||
ResultCode result = RESULT_SUCCESS;
|
||||
// If the session has been converted to a domain, handle the domain request
|
||||
|
||||
@@ -12,16 +12,20 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/hle/kernel/svc_wrap.h"
|
||||
@@ -64,19 +68,22 @@ static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
|
||||
|
||||
/// Connect to an OS service given the port name, returns the handle to the port to out
|
||||
static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address) {
|
||||
if (!Memory::IsValidVirtualAddress(port_name_address))
|
||||
if (!Memory::IsValidVirtualAddress(port_name_address)) {
|
||||
return ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
static constexpr std::size_t PortNameMaxLength = 11;
|
||||
// Read 1 char beyond the max allowed port name to detect names that are too long.
|
||||
std::string port_name = Memory::ReadCString(port_name_address, PortNameMaxLength + 1);
|
||||
if (port_name.size() > PortNameMaxLength)
|
||||
if (port_name.size() > PortNameMaxLength) {
|
||||
return ERR_PORT_NAME_TOO_LONG;
|
||||
}
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called port_name={}", port_name);
|
||||
|
||||
auto it = Service::g_kernel_named_ports.find(port_name);
|
||||
if (it == Service::g_kernel_named_ports.end()) {
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
auto it = kernel.FindNamedPort(port_name);
|
||||
if (!kernel.IsValidNamedPort(it)) {
|
||||
LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: {}", port_name);
|
||||
return ERR_NOT_FOUND;
|
||||
}
|
||||
@@ -87,7 +94,6 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address
|
||||
CASCADE_RESULT(client_session, client_port->Connect());
|
||||
|
||||
// Return the client session
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
CASCADE_RESULT(*out_handle, kernel.HandleTable().Create(client_session));
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "common/thread_queue_list.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
@@ -23,8 +24,8 @@
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
|
||||
@@ -60,17 +60,20 @@ ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64
|
||||
|
||||
ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const {
|
||||
std::string path(FileUtil::SanitizePath(path_));
|
||||
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
|
||||
if (path.empty()) {
|
||||
// TODO(DarkLordZach): Why do games call this and what should it do? Works as is but...
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
if (dir->GetFile(FileUtil::GetFilename(path)) == nullptr)
|
||||
|
||||
auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path));
|
||||
if (dir->GetFile(FileUtil::GetFilename(path)) == nullptr) {
|
||||
return FileSys::ERROR_PATH_NOT_FOUND;
|
||||
}
|
||||
if (!dir->DeleteFile(FileUtil::GetFilename(path))) {
|
||||
// TODO(DarkLordZach): Find a better error code for this
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/directory.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -26,6 +26,17 @@
|
||||
|
||||
namespace Service::FileSystem {
|
||||
|
||||
enum class FileSystemType : u8 {
|
||||
Invalid0 = 0,
|
||||
Invalid1 = 1,
|
||||
Logo = 2,
|
||||
ContentControl = 3,
|
||||
ContentManual = 4,
|
||||
ContentMeta = 5,
|
||||
ContentData = 6,
|
||||
ApplicationPackage = 7,
|
||||
};
|
||||
|
||||
class IStorage final : public ServiceFramework<IStorage> {
|
||||
public:
|
||||
explicit IStorage(FileSys::VirtualFile backend_)
|
||||
@@ -420,7 +431,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
|
||||
{0, nullptr, "MountContent"},
|
||||
{1, &FSP_SRV::Initialize, "Initialize"},
|
||||
{2, nullptr, "OpenDataFileSystemByCurrentProcess"},
|
||||
{7, nullptr, "OpenFileSystemWithPatch"},
|
||||
{7, &FSP_SRV::OpenFileSystemWithPatch, "OpenFileSystemWithPatch"},
|
||||
{8, nullptr, "OpenFileSystemWithId"},
|
||||
{9, nullptr, "OpenDataFileSystemByApplicationId"},
|
||||
{11, nullptr, "OpenBisFileSystem"},
|
||||
@@ -444,7 +455,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
|
||||
{34, nullptr, "GetCacheStorageSize"},
|
||||
{51, &FSP_SRV::MountSaveData, "MountSaveData"},
|
||||
{52, nullptr, "OpenSaveDataFileSystemBySystemSaveDataId"},
|
||||
{53, nullptr, "OpenReadOnlySaveDataFileSystem"},
|
||||
{53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"},
|
||||
{57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"},
|
||||
{58, nullptr, "ReadSaveDataFileSystemExtraData"},
|
||||
{59, nullptr, "WriteSaveDataFileSystemExtraData"},
|
||||
@@ -516,6 +527,16 @@ void FSP_SRV::Initialize(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto type = rp.PopRaw<FileSystemType>();
|
||||
const auto title_id = rp.PopRaw<u64>();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 0};
|
||||
rb.Push(ResultCode(-1));
|
||||
}
|
||||
|
||||
void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
@@ -563,6 +584,11 @@ void FSP_SRV::MountSaveData(Kernel::HLERequestContext& ctx) {
|
||||
rb.PushIpcInterface<IFileSystem>(std::move(filesystem));
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem");
|
||||
MountSaveData(ctx);
|
||||
}
|
||||
|
||||
void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
|
||||
|
||||
@@ -20,9 +20,11 @@ public:
|
||||
|
||||
private:
|
||||
void Initialize(Kernel::HLERequestContext& ctx);
|
||||
void OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx);
|
||||
void MountSdCard(Kernel::HLERequestContext& ctx);
|
||||
void CreateSaveData(Kernel::HLERequestContext& ctx);
|
||||
void MountSaveData(Kernel::HLERequestContext& ctx);
|
||||
void OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx);
|
||||
void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
|
||||
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
|
||||
void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Service::Nvidia::Devices {
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/server_port.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
@@ -114,7 +115,7 @@ void ServiceFrameworkBase::InstallAsNamedPort() {
|
||||
std::tie(server_port, client_port) =
|
||||
ServerPort::CreatePortPair(kernel, max_sessions, service_name);
|
||||
server_port->SetHleHandler(shared_from_this());
|
||||
AddNamedPort(service_name, std::move(client_port));
|
||||
kernel.AddNamedPort(service_name, std::move(client_port));
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() {
|
||||
@@ -197,11 +198,6 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Module interface
|
||||
|
||||
// TODO(yuriks): Move to kernel
|
||||
void AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
|
||||
g_kernel_named_ports.emplace(std::move(name), std::move(port));
|
||||
}
|
||||
|
||||
/// Initialize ServiceManager
|
||||
void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesystem& rfs) {
|
||||
// NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
|
||||
@@ -264,7 +260,6 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesys
|
||||
|
||||
/// Shutdown ServiceManager
|
||||
void Shutdown() {
|
||||
g_kernel_named_ports.clear();
|
||||
LOG_DEBUG(Service, "shutdown OK");
|
||||
}
|
||||
} // namespace Service
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
@@ -187,10 +186,4 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm,
|
||||
/// Shutdown ServiceManager
|
||||
void Shutdown();
|
||||
|
||||
/// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort SVC.
|
||||
extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports;
|
||||
|
||||
/// Adds a port to the named port table
|
||||
void AddNamedPort(std::string name, Kernel::SharedPtr<Kernel::ClientPort> port);
|
||||
|
||||
} // namespace Service
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/loader/elf.h"
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/loader/deconstructed_rom_directory.h"
|
||||
#include "core/loader/elf.h"
|
||||
@@ -144,6 +144,9 @@ std::ostream& operator<<(std::ostream& os, ResultStatus status) {
|
||||
return os;
|
||||
}
|
||||
|
||||
AppLoader::AppLoader(FileSys::VirtualFile file) : file(std::move(file)) {}
|
||||
AppLoader::~AppLoader() = default;
|
||||
|
||||
/**
|
||||
* Get a loader for a file with a specific type
|
||||
* @param file The file to load
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -12,7 +11,6 @@
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
@@ -114,8 +112,8 @@ std::ostream& operator<<(std::ostream& os, ResultStatus status);
|
||||
/// Interface for loading an application
|
||||
class AppLoader : NonCopyable {
|
||||
public:
|
||||
explicit AppLoader(FileSys::VirtualFile file) : file(std::move(file)) {}
|
||||
virtual ~AppLoader() {}
|
||||
explicit AppLoader(FileSys::VirtualFile file);
|
||||
virtual ~AppLoader();
|
||||
|
||||
/**
|
||||
* Returns the type of this file
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/loader/nro.h"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/loader/nso.h"
|
||||
|
||||
+15
-36
@@ -251,8 +251,8 @@ std::string ReadCString(VAddr vaddr, std::size_t max_length) {
|
||||
return string;
|
||||
}
|
||||
|
||||
void RasterizerMarkRegionCached(Tegra::GPUVAddr gpu_addr, u64 size, bool cached) {
|
||||
if (gpu_addr == 0) {
|
||||
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||
if (vaddr == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -261,19 +261,8 @@ void RasterizerMarkRegionCached(Tegra::GPUVAddr gpu_addr, u64 size, bool cached)
|
||||
// CPU pages, hence why we iterate on a CPU page basis (note: GPU page size is different). This
|
||||
// assumes the specified GPU address region is contiguous as well.
|
||||
|
||||
u64 num_pages = ((gpu_addr + size - 1) >> PAGE_BITS) - (gpu_addr >> PAGE_BITS) + 1;
|
||||
for (unsigned i = 0; i < num_pages; ++i, gpu_addr += PAGE_SIZE) {
|
||||
boost::optional<VAddr> maybe_vaddr =
|
||||
Core::System::GetInstance().GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
|
||||
// The GPU <-> CPU virtual memory mapping is not 1:1
|
||||
if (!maybe_vaddr) {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Trying to flush a cached region to an invalid physical address {:016X}",
|
||||
gpu_addr);
|
||||
continue;
|
||||
}
|
||||
VAddr vaddr = *maybe_vaddr;
|
||||
|
||||
u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1;
|
||||
for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
|
||||
PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
|
||||
if (cached) {
|
||||
@@ -344,29 +333,19 @@ void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) {
|
||||
|
||||
const VAddr overlap_start = std::max(start, region_start);
|
||||
const VAddr overlap_end = std::min(end, region_end);
|
||||
|
||||
const std::vector<Tegra::GPUVAddr> gpu_addresses =
|
||||
system_instance.GPU().MemoryManager().CpuToGpuAddress(overlap_start);
|
||||
|
||||
if (gpu_addresses.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 overlap_size = overlap_end - overlap_start;
|
||||
|
||||
for (const auto& gpu_address : gpu_addresses) {
|
||||
auto& rasterizer = system_instance.Renderer().Rasterizer();
|
||||
switch (mode) {
|
||||
case FlushMode::Flush:
|
||||
rasterizer.FlushRegion(gpu_address, overlap_size);
|
||||
break;
|
||||
case FlushMode::Invalidate:
|
||||
rasterizer.InvalidateRegion(gpu_address, overlap_size);
|
||||
break;
|
||||
case FlushMode::FlushAndInvalidate:
|
||||
rasterizer.FlushAndInvalidateRegion(gpu_address, overlap_size);
|
||||
break;
|
||||
}
|
||||
auto& rasterizer = system_instance.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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-2
@@ -11,7 +11,6 @@
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory_hook.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Kernel {
|
||||
class Process;
|
||||
@@ -179,7 +178,7 @@ enum class FlushMode {
|
||||
/**
|
||||
* Mark each page touching the region as cached.
|
||||
*/
|
||||
void RasterizerMarkRegionCached(Tegra::GPUVAddr gpu_addr, u64 size, bool cached);
|
||||
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached);
|
||||
|
||||
/**
|
||||
* Flushes and invalidates any externally cached rasterizer resources touching the given virtual
|
||||
|
||||
@@ -40,7 +40,7 @@ void PerfStats::EndGameFrame() {
|
||||
game_frames += 1;
|
||||
}
|
||||
|
||||
PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_us) {
|
||||
PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) {
|
||||
std::lock_guard<std::mutex> lock(object_mutex);
|
||||
|
||||
const auto now = Clock::now();
|
||||
@@ -49,7 +49,7 @@ PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_
|
||||
|
||||
const auto system_us_per_second = (current_system_time_us - reset_point_system_us) / interval;
|
||||
|
||||
Results results{};
|
||||
PerfStatsResults results{};
|
||||
results.system_fps = static_cast<double>(system_frames) / interval;
|
||||
results.game_fps = static_cast<double>(game_frames) / interval;
|
||||
results.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
|
||||
|
||||
+12
-12
@@ -10,6 +10,17 @@
|
||||
|
||||
namespace Core {
|
||||
|
||||
struct PerfStatsResults {
|
||||
/// System FPS (LCD VBlanks) in Hz
|
||||
double system_fps;
|
||||
/// Game FPS (GSP frame submissions) in Hz
|
||||
double game_fps;
|
||||
/// Walltime per system frame, in seconds, excluding any waits
|
||||
double frametime;
|
||||
/// Ratio of walltime / emulated time elapsed
|
||||
double emulation_speed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Class to manage and query performance/timing statistics. All public functions of this class are
|
||||
* thread-safe unless stated otherwise.
|
||||
@@ -18,22 +29,11 @@ class PerfStats {
|
||||
public:
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
|
||||
struct Results {
|
||||
/// System FPS (LCD VBlanks) in Hz
|
||||
double system_fps;
|
||||
/// Game FPS (GSP frame submissions) in Hz
|
||||
double game_fps;
|
||||
/// Walltime per system frame, in seconds, excluding any waits
|
||||
double frametime;
|
||||
/// Ratio of walltime / emulated time elapsed
|
||||
double emulation_speed;
|
||||
};
|
||||
|
||||
void BeginSystemFrame();
|
||||
void EndSystemFrame();
|
||||
void EndGameFrame();
|
||||
|
||||
Results GetAndResetStats(std::chrono::microseconds current_system_time_us);
|
||||
PerfStatsResults GetAndResetStats(std::chrono::microseconds current_system_time_us);
|
||||
|
||||
/**
|
||||
* Gets the ratio between walltime and the emulated time of the previous system frame. This is
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/file_util.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ void GPU::WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params)
|
||||
"{:08X} remaining params {}",
|
||||
method, subchannel, value, remaining_params);
|
||||
|
||||
ASSERT(subchannel < bound_engines.size());
|
||||
|
||||
if (method == static_cast<u32>(BufferMethods::BindObject)) {
|
||||
// Bind the current subchannel to the desired engine id.
|
||||
LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", subchannel, value);
|
||||
@@ -47,8 +49,6 @@ void GPU::WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params)
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(bound_engines.find(subchannel) != bound_engines.end());
|
||||
|
||||
const EngineID engine = bound_engines[subchannel];
|
||||
|
||||
switch (engine) {
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
#include <cinttypes>
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/memory.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 {
|
||||
@@ -195,8 +195,8 @@ void Maxwell3D::ProcessQueryGet() {
|
||||
// wait queues.
|
||||
LongQueryResult query_result{};
|
||||
query_result.value = result;
|
||||
// TODO(Subv): Generate a real GPU timestamp and write it here instead of 0
|
||||
query_result.timestamp = 0;
|
||||
// TODO(Subv): Generate a real GPU timestamp and write it here instead of CoreTiming
|
||||
query_result.timestamp = CoreTiming::GetTicks();
|
||||
Memory::WriteBlock(*address, &query_result, sizeof(query_result));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -147,6 +147,7 @@ enum class PredCondition : u64 {
|
||||
LessThanWithNan = 9,
|
||||
GreaterThanWithNan = 12,
|
||||
NotEqualWithNan = 13,
|
||||
GreaterEqualWithNan = 14,
|
||||
// TODO(Subv): Other condition types
|
||||
};
|
||||
|
||||
@@ -242,6 +243,9 @@ enum class TextureType : u64 {
|
||||
TextureCube = 3,
|
||||
};
|
||||
|
||||
enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, Flat = 2, Sc = 3 };
|
||||
enum class IpaSampleMode : u64 { Default = 0, Centroid = 1, Offset = 2 };
|
||||
|
||||
union Instruction {
|
||||
Instruction& operator=(const Instruction& instr) {
|
||||
value = instr.value;
|
||||
@@ -325,6 +329,16 @@ union Instruction {
|
||||
} alu;
|
||||
|
||||
union {
|
||||
BitField<51, 1, u64> saturate;
|
||||
BitField<52, 2, IpaSampleMode> sample_mode;
|
||||
BitField<54, 2, IpaInterpMode> interp_mode;
|
||||
} ipa;
|
||||
|
||||
union {
|
||||
BitField<39, 2, u64> tab5cb8_2;
|
||||
BitField<41, 3, u64> tab5c68_1;
|
||||
BitField<44, 2, u64> tab5c68_0;
|
||||
BitField<47, 1, u64> cc;
|
||||
BitField<48, 1, u64> negate_b;
|
||||
} fmul;
|
||||
|
||||
@@ -338,6 +352,10 @@ union Instruction {
|
||||
BitField<49, 1, u64> negate_a;
|
||||
} alu_integer;
|
||||
|
||||
union {
|
||||
BitField<40, 1, u64> invert;
|
||||
} popc;
|
||||
|
||||
union {
|
||||
BitField<39, 3, u64> pred;
|
||||
BitField<42, 1, u64> neg_pred;
|
||||
@@ -388,8 +406,11 @@ union Instruction {
|
||||
} flow;
|
||||
|
||||
union {
|
||||
BitField<47, 1, u64> cc;
|
||||
BitField<48, 1, u64> negate_b;
|
||||
BitField<49, 1, u64> negate_c;
|
||||
BitField<51, 2, u64> tab5980_1;
|
||||
BitField<53, 2, u64> tab5980_0;
|
||||
} ffma;
|
||||
|
||||
union {
|
||||
@@ -498,6 +519,7 @@ union Instruction {
|
||||
union {
|
||||
BitField<0, 8, Register> gpr0;
|
||||
BitField<28, 8, Register> gpr28;
|
||||
BitField<49, 1, u64> nodep;
|
||||
BitField<50, 3, u64> component_mask_selector;
|
||||
BitField<53, 4, u64> texture_info;
|
||||
|
||||
@@ -665,6 +687,9 @@ public:
|
||||
ISCADD_C, // Scale and Add
|
||||
ISCADD_R,
|
||||
ISCADD_IMM,
|
||||
POPC_C,
|
||||
POPC_R,
|
||||
POPC_IMM,
|
||||
SEL_C,
|
||||
SEL_R,
|
||||
SEL_IMM,
|
||||
@@ -886,6 +911,9 @@ private:
|
||||
INST("0100110000011---", Id::ISCADD_C, Type::ArithmeticInteger, "ISCADD_C"),
|
||||
INST("0101110000011---", Id::ISCADD_R, Type::ArithmeticInteger, "ISCADD_R"),
|
||||
INST("0011100-00011---", Id::ISCADD_IMM, Type::ArithmeticInteger, "ISCADD_IMM"),
|
||||
INST("0100110000001---", Id::POPC_C, Type::ArithmeticInteger, "POPC_C"),
|
||||
INST("0101110000001---", Id::POPC_R, Type::ArithmeticInteger, "POPC_R"),
|
||||
INST("0011100-00001---", Id::POPC_IMM, Type::ArithmeticInteger, "POPC_IMM"),
|
||||
INST("0100110010100---", Id::SEL_C, Type::ArithmeticInteger, "SEL_C"),
|
||||
INST("0101110010100---", Id::SEL_R, Type::ArithmeticInteger, "SEL_R"),
|
||||
INST("0011100-10100---", Id::SEL_IMM, Type::ArithmeticInteger, "SEL_IMM"),
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/nvflinger/buffer_queue.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
@@ -136,7 +136,7 @@ private:
|
||||
std::unique_ptr<Tegra::MemoryManager> memory_manager;
|
||||
|
||||
/// Mapping of command subchannels to their bound engine ids.
|
||||
std::unordered_map<u32, EngineID> bound_engines;
|
||||
std::array<EngineID, 8> bound_engines = {};
|
||||
|
||||
/// 3D engine
|
||||
std::unique_ptr<Engines::Maxwell3D> maxwell_3d;
|
||||
|
||||
@@ -4,113 +4,86 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
#include <boost/range/iterator_range_core.hpp>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
template <class T>
|
||||
class RasterizerCache : NonCopyable {
|
||||
public:
|
||||
/// Mark the specified region as being invalidated
|
||||
void InvalidateRegion(Tegra::GPUVAddr region_addr, size_t region_size) {
|
||||
for (auto iter = cached_objects.cbegin(); iter != cached_objects.cend();) {
|
||||
const auto& object{iter->second};
|
||||
void InvalidateRegion(VAddr addr, u64 size) {
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
++iter;
|
||||
const ObjectInterval interval{addr, addr + size};
|
||||
for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
|
||||
for (auto& cached_object : pair.second) {
|
||||
if (!cached_object)
|
||||
continue;
|
||||
|
||||
if (object->GetAddr() <= (region_addr + region_size) &&
|
||||
region_addr <= (object->GetAddr() + object->GetSizeInBytes())) {
|
||||
// Regions overlap, so invalidate
|
||||
Unregister(object);
|
||||
remove_objects.emplace(cached_object);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& remove_object : remove_objects) {
|
||||
Unregister(remove_object);
|
||||
}
|
||||
|
||||
remove_objects.clear();
|
||||
}
|
||||
|
||||
/// Invalidates everything in the cache
|
||||
void InvalidateAll() {
|
||||
while (object_cache.begin() != object_cache.end()) {
|
||||
Unregister(*object_cache.begin()->second.begin());
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Tries to get an object from the cache with the specified address
|
||||
T TryGet(Tegra::GPUVAddr addr) const {
|
||||
const auto& search{cached_objects.find(addr)};
|
||||
if (search != cached_objects.end()) {
|
||||
return search->second;
|
||||
T TryGet(VAddr addr) const {
|
||||
const ObjectInterval interval{addr};
|
||||
for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
|
||||
for (auto& cached_object : pair.second) {
|
||||
if (cached_object->GetAddr() == addr) {
|
||||
return cached_object;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Gets a reference to the cache
|
||||
const std::unordered_map<Tegra::GPUVAddr, T>& GetCache() const {
|
||||
return cached_objects;
|
||||
}
|
||||
|
||||
/// Register an object into the cache
|
||||
void Register(const T& object) {
|
||||
const auto& search{cached_objects.find(object->GetAddr())};
|
||||
if (search != cached_objects.end()) {
|
||||
// Registered already
|
||||
return;
|
||||
}
|
||||
|
||||
cached_objects[object->GetAddr()] = object;
|
||||
UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1);
|
||||
object_cache.add({GetInterval(object), ObjectSet{object}});
|
||||
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
|
||||
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1);
|
||||
}
|
||||
|
||||
/// Unregisters an object from the cache
|
||||
void Unregister(const T& object) {
|
||||
const auto& search{cached_objects.find(object->GetAddr())};
|
||||
if (search == cached_objects.end()) {
|
||||
// Unregistered already
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1);
|
||||
cached_objects.erase(search);
|
||||
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
|
||||
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1);
|
||||
object_cache.subtract({GetInterval(object), ObjectSet{object}});
|
||||
}
|
||||
|
||||
private:
|
||||
using PageMap = boost::icl::interval_map<u64, int>;
|
||||
using ObjectSet = std::set<T>;
|
||||
using ObjectCache = boost::icl::interval_map<VAddr, ObjectSet>;
|
||||
using ObjectInterval = typename ObjectCache::interval_type;
|
||||
|
||||
template <typename Map, typename Interval>
|
||||
constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
|
||||
return boost::make_iterator_range(map.equal_range(interval));
|
||||
static auto GetInterval(const T& object) {
|
||||
return ObjectInterval::right_open(object->GetAddr(),
|
||||
object->GetAddr() + object->GetSizeInBytes());
|
||||
}
|
||||
|
||||
/// Increase/decrease the number of object in pages touching the specified region
|
||||
void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {
|
||||
const u64 page_start{addr >> Tegra::MemoryManager::PAGE_BITS};
|
||||
const u64 page_end{(addr + size) >> Tegra::MemoryManager::PAGE_BITS};
|
||||
|
||||
// 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 Tegra::GPUVAddr interval_start_addr = boost::icl::first(interval)
|
||||
<< Tegra::MemoryManager::PAGE_BITS;
|
||||
const Tegra::GPUVAddr interval_end_addr = boost::icl::last_next(interval)
|
||||
<< Tegra::MemoryManager::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});
|
||||
}
|
||||
|
||||
std::unordered_map<Tegra::GPUVAddr, T> cached_objects;
|
||||
PageMap cached_pages;
|
||||
ObjectCache object_cache;
|
||||
ObjectSet remove_objects;
|
||||
};
|
||||
|
||||
@@ -27,14 +27,14 @@ public:
|
||||
virtual void FlushAll() = 0;
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
|
||||
virtual void FlushRegion(Tegra::GPUVAddr addr, u64 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(Tegra::GPUVAddr addr, u64 size) = 0;
|
||||
virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
|
||||
|
||||
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
|
||||
/// and invalidated
|
||||
virtual void FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 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) {
|
||||
@@ -60,5 +60,8 @@ public:
|
||||
virtual bool AccelerateDrawBatch(bool is_indexed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Increase/decrease the number of object in pages touching the specified region
|
||||
virtual void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {}
|
||||
};
|
||||
} // namespace VideoCore
|
||||
|
||||
@@ -274,6 +274,41 @@ bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Map, typename Interval>
|
||||
static constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
|
||||
return boost::make_iterator_range(map.equal_range(interval));
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
|
||||
const u64 page_start{addr >> Memory::PAGE_BITS};
|
||||
const u64 page_end{(addr + size + Memory::PAGE_SIZE - 1) >> Memory::PAGE_BITS};
|
||||
|
||||
// Interval maps will erase segments if count reaches 0, so if delta is negative we have to
|
||||
// subtract after iterating
|
||||
const auto pages_interval = CachedPageMap::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});
|
||||
}
|
||||
|
||||
std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb,
|
||||
bool using_depth_fb,
|
||||
bool preserve_contents) {
|
||||
@@ -397,16 +432,6 @@ void RasterizerOpenGL::Clear() {
|
||||
glClearStencil(regs.clear_stencil);
|
||||
|
||||
glClear(clear_mask);
|
||||
|
||||
// Mark framebuffer surfaces as dirty
|
||||
if (Settings::values.use_accurate_framebuffers) {
|
||||
if (dirty_color_surface != nullptr) {
|
||||
res_cache.FlushSurface(dirty_color_surface);
|
||||
}
|
||||
if (dirty_depth_surface != nullptr) {
|
||||
res_cache.FlushSurface(dirty_depth_surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<u8*, GLintptr> RasterizerOpenGL::AlignBuffer(u8* buffer_ptr, GLintptr buffer_offset,
|
||||
@@ -522,16 +547,6 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
texture_unit.Unbind();
|
||||
}
|
||||
state.Apply();
|
||||
|
||||
// Mark framebuffer surfaces as dirty
|
||||
if (Settings::values.use_accurate_framebuffers) {
|
||||
if (dirty_color_surface != nullptr) {
|
||||
res_cache.FlushSurface(dirty_color_surface);
|
||||
}
|
||||
if (dirty_depth_surface != nullptr) {
|
||||
res_cache.FlushSurface(dirty_depth_surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 method) {}
|
||||
@@ -540,17 +555,17 @@ void RasterizerOpenGL::FlushAll() {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushRegion(Tegra::GPUVAddr addr, u64 size) {
|
||||
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, u64 size) {
|
||||
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
res_cache.InvalidateRegion(addr, size);
|
||||
shader_cache.InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) {
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
@@ -40,15 +44,16 @@ public:
|
||||
void Clear() override;
|
||||
void NotifyMaxwellRegisterChanged(u32 method) override;
|
||||
void FlushAll() override;
|
||||
void FlushRegion(Tegra::GPUVAddr addr, u64 size) override;
|
||||
void InvalidateRegion(Tegra::GPUVAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 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 Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
|
||||
u32 pixel_stride) override;
|
||||
bool AccelerateDrawBatch(bool is_indexed) override;
|
||||
void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) override;
|
||||
|
||||
/// OpenGL shader generated for a given Maxwell register state
|
||||
struct MaxwellShader {
|
||||
@@ -187,6 +192,9 @@ private:
|
||||
|
||||
enum class AccelDraw { Disabled, Arrays, Indexed };
|
||||
AccelDraw accelerate_draw = AccelDraw::Disabled;
|
||||
|
||||
using CachedPageMap = boost::icl::interval_map<u64, int>;
|
||||
CachedPageMap cached_pages;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -33,11 +33,16 @@ struct FormatTuple {
|
||||
bool compressed;
|
||||
};
|
||||
|
||||
static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
auto& gpu{Core::System::GetInstance().GPU()};
|
||||
const auto cpu_addr{gpu.MemoryManager().GpuToCpuAddress(gpu_addr)};
|
||||
return cpu_addr ? *cpu_addr : 0;
|
||||
}
|
||||
|
||||
/*static*/ SurfaceParams SurfaceParams::CreateForTexture(
|
||||
const Tegra::Texture::FullTextureInfo& config) {
|
||||
|
||||
SurfaceParams params{};
|
||||
params.addr = config.tic.Address();
|
||||
params.addr = TryGetCpuAddr(config.tic.Address());
|
||||
params.is_tiled = config.tic.IsTiled();
|
||||
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
|
||||
params.pixel_format =
|
||||
@@ -55,9 +60,8 @@ struct FormatTuple {
|
||||
|
||||
/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(
|
||||
const Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig& config) {
|
||||
|
||||
SurfaceParams params{};
|
||||
params.addr = config.Address();
|
||||
params.addr = TryGetCpuAddr(config.Address());
|
||||
params.is_tiled = true;
|
||||
params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
|
||||
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
|
||||
@@ -75,9 +79,8 @@ struct FormatTuple {
|
||||
/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(u32 zeta_width, u32 zeta_height,
|
||||
Tegra::GPUVAddr zeta_address,
|
||||
Tegra::DepthFormat format) {
|
||||
|
||||
SurfaceParams params{};
|
||||
params.addr = zeta_address;
|
||||
params.addr = TryGetCpuAddr(zeta_address);
|
||||
params.is_tiled = true;
|
||||
params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
|
||||
params.pixel_format = PixelFormatFromDepthFormat(format);
|
||||
@@ -120,7 +123,11 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
|
||||
true}, // DXN2UNORM
|
||||
{GL_COMPRESSED_SIGNED_RG_RGTC2, GL_RG, GL_INT, ComponentType::SNorm, true}, // DXN2SNORM
|
||||
{GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
|
||||
true}, // BC7U
|
||||
true}, // BC7U
|
||||
{GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8,
|
||||
ComponentType::UNorm, true}, // BC6H_UF16
|
||||
{GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
|
||||
true}, // BC6H_SF16
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4
|
||||
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // G8R8U
|
||||
{GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // G8R8S
|
||||
@@ -167,11 +174,6 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType
|
||||
return format;
|
||||
}
|
||||
|
||||
VAddr SurfaceParams::GetCpuAddr() const {
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
return *gpu.MemoryManager().GpuToCpuAddress(addr);
|
||||
}
|
||||
|
||||
static bool IsPixelFormatASTC(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
@@ -210,39 +212,36 @@ static bool IsFormatBCn(PixelFormat format) {
|
||||
case PixelFormat::DXN2SNORM:
|
||||
case PixelFormat::DXN2UNORM:
|
||||
case PixelFormat::BC7U:
|
||||
case PixelFormat::BC6H_UF16:
|
||||
case PixelFormat::BC6H_SF16:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <bool morton_to_gl, PixelFormat format>
|
||||
void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_buffer,
|
||||
Tegra::GPUVAddr addr) {
|
||||
void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_buffer, VAddr addr) {
|
||||
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
|
||||
constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
|
||||
if (morton_to_gl) {
|
||||
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
|
||||
// pixel values.
|
||||
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
|
||||
const std::vector<u8> data =
|
||||
Tegra::Texture::UnswizzleTexture(*gpu.MemoryManager().GpuToCpuAddress(addr), tile_size,
|
||||
bytes_per_pixel, stride, height, block_height);
|
||||
const std::vector<u8> data = Tegra::Texture::UnswizzleTexture(
|
||||
addr, tile_size, bytes_per_pixel, stride, height, block_height);
|
||||
const size_t size_to_copy{std::min(gl_buffer.size(), data.size())};
|
||||
gl_buffer.assign(data.begin(), data.begin() + size_to_copy);
|
||||
} else {
|
||||
// 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(*gpu.MemoryManager().GpuToCpuAddress(addr)), gl_buffer.data(),
|
||||
morton_to_gl);
|
||||
VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel,
|
||||
Memory::GetPointer(addr), gl_buffer.data(), morton_to_gl);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPUVAddr),
|
||||
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
|
||||
SurfaceParams::MaxPixelFormat>
|
||||
morton_to_gl_fns = {
|
||||
// clang-format off
|
||||
@@ -266,6 +265,8 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
|
||||
MortonCopy<true, PixelFormat::DXN2UNORM>,
|
||||
MortonCopy<true, PixelFormat::DXN2SNORM>,
|
||||
MortonCopy<true, PixelFormat::BC7U>,
|
||||
MortonCopy<true, PixelFormat::BC6H_UF16>,
|
||||
MortonCopy<true, PixelFormat::BC6H_SF16>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
|
||||
MortonCopy<true, PixelFormat::G8R8U>,
|
||||
MortonCopy<true, PixelFormat::G8R8S>,
|
||||
@@ -297,7 +298,7 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPUVAddr),
|
||||
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
|
||||
SurfaceParams::MaxPixelFormat>
|
||||
gl_to_morton_fns = {
|
||||
// clang-format off
|
||||
@@ -314,8 +315,10 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
|
||||
MortonCopy<false, PixelFormat::RGBA16UI>,
|
||||
MortonCopy<false, PixelFormat::R11FG11FB10F>,
|
||||
MortonCopy<false, PixelFormat::RGBA32UI>,
|
||||
// TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/ASTC_2D_4X4 formats is not
|
||||
// supported
|
||||
// TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/BC6H_UF16/BC6H_SF16/ASTC_2D_4X4
|
||||
// formats are not supported
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
@@ -532,7 +535,7 @@ MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64
|
||||
void CachedSurface::LoadGLBuffer() {
|
||||
ASSERT(params.type != SurfaceType::Fill);
|
||||
|
||||
const u8* const texture_src_data = Memory::GetPointer(params.GetCpuAddr());
|
||||
const u8* const texture_src_data = Memory::GetPointer(params.addr);
|
||||
|
||||
ASSERT(texture_src_data);
|
||||
|
||||
@@ -557,7 +560,7 @@ void CachedSurface::LoadGLBuffer() {
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::FlushGLBuffer() {
|
||||
u8* const dst_buffer = Memory::GetPointer(params.GetCpuAddr());
|
||||
u8* const dst_buffer = Memory::GetPointer(params.addr);
|
||||
|
||||
ASSERT(dst_buffer);
|
||||
ASSERT(gl_buffer.size() ==
|
||||
@@ -754,19 +757,10 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
// Don't try to create any entries in the cache if the address of the texture is invalid.
|
||||
if (gpu.MemoryManager().GpuToCpuAddress(params.addr) == boost::none)
|
||||
return {};
|
||||
|
||||
// Look up surface in the cache based on address
|
||||
Surface surface{TryGet(params.addr)};
|
||||
if (surface) {
|
||||
if (Settings::values.use_accurate_framebuffers) {
|
||||
// If use_accurate_framebuffers is enabled, always load from memory
|
||||
FlushSurface(surface);
|
||||
Unregister(surface);
|
||||
} else if (surface->GetSurfaceParams().IsCompatibleSurface(params)) {
|
||||
if (surface->GetSurfaceParams().IsCompatibleSurface(params)) {
|
||||
// Use the cached surface as-is
|
||||
return surface;
|
||||
} else if (preserve_contents) {
|
||||
@@ -782,15 +776,9 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get a previously reserved surface
|
||||
surface = TryGetReservedSurface(params);
|
||||
|
||||
// No surface found - create a new one
|
||||
if (!surface) {
|
||||
surface = std::make_shared<CachedSurface>(params);
|
||||
ReserveSurface(surface);
|
||||
Register(surface);
|
||||
}
|
||||
// No cached surface found - get a new one
|
||||
surface = GetUncachedSurface(params);
|
||||
Register(surface);
|
||||
|
||||
// Only load surface from memory if we care about the contents
|
||||
if (preserve_contents) {
|
||||
@@ -800,13 +788,23 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
|
||||
return surface;
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::GetUncachedSurface(const SurfaceParams& params) {
|
||||
Surface surface{TryGetReservedSurface(params)};
|
||||
if (!surface) {
|
||||
// No reserved surface available, create a new one and reserve it
|
||||
surface = std::make_shared<CachedSurface>(params);
|
||||
ReserveSurface(surface);
|
||||
}
|
||||
return surface;
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
const SurfaceParams& new_params) {
|
||||
// Verify surface is compatible for blitting
|
||||
const auto& params{surface->GetSurfaceParams()};
|
||||
|
||||
// Create a new surface with the new parameters, and blit the previous surface to it
|
||||
Surface new_surface{std::make_shared<CachedSurface>(new_params)};
|
||||
// Get a new surface with the new parameters, and blit the previous surface to it
|
||||
Surface new_surface{GetUncachedSurface(new_params)};
|
||||
|
||||
// If format is unchanged, we can do a faster blit without reinterpreting pixel data
|
||||
if (params.pixel_format == new_params.pixel_format) {
|
||||
@@ -816,92 +814,73 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
return new_surface;
|
||||
}
|
||||
|
||||
auto source_format = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
auto dest_format = GetFormatTuple(new_params.pixel_format, new_params.component_type);
|
||||
// When using accurate framebuffers, always copy old data to new surface, regardless of format
|
||||
if (Settings::values.use_accurate_framebuffers) {
|
||||
auto source_format = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
auto dest_format = GetFormatTuple(new_params.pixel_format, new_params.component_type);
|
||||
|
||||
size_t buffer_size = std::max(params.SizeInBytes(), new_params.SizeInBytes());
|
||||
size_t buffer_size = std::max(params.SizeInBytes(), new_params.SizeInBytes());
|
||||
|
||||
// Use a Pixel Buffer Object to download the previous texture and then upload it to the new one
|
||||
// using the new format.
|
||||
OGLBuffer pbo;
|
||||
pbo.Create();
|
||||
// Use a Pixel Buffer Object to download the previous texture and then upload it to the new
|
||||
// one using the new format.
|
||||
OGLBuffer pbo;
|
||||
pbo.Create();
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo.handle);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
|
||||
if (source_format.compressed) {
|
||||
glGetCompressedTextureImage(surface->Texture().handle, 0,
|
||||
static_cast<GLsizei>(params.SizeInBytes()), nullptr);
|
||||
} else {
|
||||
glGetTextureImage(surface->Texture().handle, 0, source_format.format, source_format.type,
|
||||
static_cast<GLsizei>(params.SizeInBytes()), nullptr);
|
||||
}
|
||||
// If the new texture is bigger than the previous one, we need to fill in the rest with data
|
||||
// from the CPU.
|
||||
if (params.SizeInBytes() < new_params.SizeInBytes()) {
|
||||
// Upload the rest of the memory.
|
||||
if (new_params.is_tiled) {
|
||||
// TODO(Subv): We might have to de-tile the subtexture and re-tile it with the rest of
|
||||
// the data in this case. Games like Super Mario Odyssey seem to hit this case when
|
||||
// drawing, it re-uses the memory of a previous texture as a bigger framebuffer but it
|
||||
// doesn't clear it beforehand, the texture is already full of zeros.
|
||||
LOG_CRITICAL(HW_GPU, "Trying to upload extra texture data from the CPU during "
|
||||
"reinterpretation but the texture is tiled.");
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo.handle);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
|
||||
if (source_format.compressed) {
|
||||
glGetCompressedTextureImage(surface->Texture().handle, 0,
|
||||
static_cast<GLsizei>(params.SizeInBytes()), nullptr);
|
||||
} else {
|
||||
glGetTextureImage(surface->Texture().handle, 0, source_format.format,
|
||||
source_format.type, static_cast<GLsizei>(params.SizeInBytes()),
|
||||
nullptr);
|
||||
}
|
||||
size_t remaining_size = new_params.SizeInBytes() - params.SizeInBytes();
|
||||
auto address = Core::System::GetInstance().GPU().MemoryManager().GpuToCpuAddress(
|
||||
new_params.addr + params.SizeInBytes());
|
||||
std::vector<u8> data(remaining_size);
|
||||
Memory::ReadBlock(*address, data.data(), data.size());
|
||||
glBufferSubData(GL_PIXEL_PACK_BUFFER, params.SizeInBytes(), remaining_size, data.data());
|
||||
// If the new texture is bigger than the previous one, we need to fill in the rest with data
|
||||
// from the CPU.
|
||||
if (params.SizeInBytes() < new_params.SizeInBytes()) {
|
||||
// Upload the rest of the memory.
|
||||
if (new_params.is_tiled) {
|
||||
// TODO(Subv): We might have to de-tile the subtexture and re-tile it with the rest
|
||||
// of the data in this case. Games like Super Mario Odyssey seem to hit this case
|
||||
// when drawing, it re-uses the memory of a previous texture as a bigger framebuffer
|
||||
// but it doesn't clear it beforehand, the texture is already full of zeros.
|
||||
LOG_CRITICAL(HW_GPU, "Trying to upload extra texture data from the CPU during "
|
||||
"reinterpretation but the texture is tiled.");
|
||||
}
|
||||
size_t remaining_size = new_params.SizeInBytes() - params.SizeInBytes();
|
||||
std::vector<u8> data(remaining_size);
|
||||
Memory::ReadBlock(new_params.addr + params.SizeInBytes(), data.data(), data.size());
|
||||
glBufferSubData(GL_PIXEL_PACK_BUFFER, params.SizeInBytes(), remaining_size,
|
||||
data.data());
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
const auto& dest_rect{new_params.GetRect()};
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.handle);
|
||||
if (dest_format.compressed) {
|
||||
glCompressedTexSubImage2D(
|
||||
GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
static_cast<GLsizei>(new_params.SizeInBytes()), nullptr);
|
||||
} else {
|
||||
glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
dest_format.type, nullptr);
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
pbo.Release();
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
const auto& dest_rect{new_params.GetRect()};
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.handle);
|
||||
if (dest_format.compressed) {
|
||||
glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
static_cast<GLsizei>(new_params.SizeInBytes()), nullptr);
|
||||
} else {
|
||||
glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
dest_format.type, nullptr);
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
pbo.Release();
|
||||
|
||||
return new_surface;
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr cpu_addr) const {
|
||||
// Tries to find the GPU address of a framebuffer based on the CPU address. This is because
|
||||
// final output framebuffers are specified by CPU address, but internally our GPU cache uses
|
||||
// GPU addresses. We iterate through all cached framebuffers, and compare their starting CPU
|
||||
// address to the one provided. This is obviously not great, and won't work if the
|
||||
// framebuffer overlaps surfaces.
|
||||
|
||||
std::vector<Surface> surfaces;
|
||||
for (const auto& surface : GetCache()) {
|
||||
const auto& params = surface.second->GetSurfaceParams();
|
||||
const VAddr surface_cpu_addr = params.GetCpuAddr();
|
||||
if (cpu_addr >= surface_cpu_addr && cpu_addr < (surface_cpu_addr + params.size_in_bytes)) {
|
||||
ASSERT_MSG(cpu_addr == surface_cpu_addr, "overlapping surfaces are unsupported");
|
||||
surfaces.push_back(surface.second);
|
||||
}
|
||||
}
|
||||
|
||||
if (surfaces.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ASSERT_MSG(surfaces.size() == 1, ">1 surface is unsupported");
|
||||
|
||||
return surfaces[0];
|
||||
Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr addr) const {
|
||||
return TryGet(addr);
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::ReserveSurface(const Surface& surface) {
|
||||
@@ -913,7 +892,6 @@ Surface RasterizerCacheOpenGL::TryGetReservedSurface(const SurfaceParams& params
|
||||
const auto& surface_reserve_key{SurfaceReserveKey::Create(params)};
|
||||
auto search{surface_reserve.find(surface_reserve_key)};
|
||||
if (search != surface_reserve.end()) {
|
||||
Register(search->second);
|
||||
return search->second;
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -45,42 +45,44 @@ struct SurfaceParams {
|
||||
DXN2UNORM = 17,
|
||||
DXN2SNORM = 18,
|
||||
BC7U = 19,
|
||||
ASTC_2D_4X4 = 20,
|
||||
G8R8U = 21,
|
||||
G8R8S = 22,
|
||||
BGRA8 = 23,
|
||||
RGBA32F = 24,
|
||||
RG32F = 25,
|
||||
R32F = 26,
|
||||
R16F = 27,
|
||||
R16U = 28,
|
||||
R16S = 29,
|
||||
R16UI = 30,
|
||||
R16I = 31,
|
||||
RG16 = 32,
|
||||
RG16F = 33,
|
||||
RG16UI = 34,
|
||||
RG16I = 35,
|
||||
RG16S = 36,
|
||||
RGB32F = 37,
|
||||
SRGBA8 = 38,
|
||||
RG8U = 39,
|
||||
RG8S = 40,
|
||||
RG32UI = 41,
|
||||
R32UI = 42,
|
||||
BC6H_UF16 = 20,
|
||||
BC6H_SF16 = 21,
|
||||
ASTC_2D_4X4 = 22,
|
||||
G8R8U = 23,
|
||||
G8R8S = 24,
|
||||
BGRA8 = 25,
|
||||
RGBA32F = 26,
|
||||
RG32F = 27,
|
||||
R32F = 28,
|
||||
R16F = 29,
|
||||
R16U = 30,
|
||||
R16S = 31,
|
||||
R16UI = 32,
|
||||
R16I = 33,
|
||||
RG16 = 34,
|
||||
RG16F = 35,
|
||||
RG16UI = 36,
|
||||
RG16I = 37,
|
||||
RG16S = 38,
|
||||
RGB32F = 39,
|
||||
SRGBA8 = 40,
|
||||
RG8U = 41,
|
||||
RG8S = 42,
|
||||
RG32UI = 43,
|
||||
R32UI = 44,
|
||||
|
||||
MaxColorFormat,
|
||||
|
||||
// Depth formats
|
||||
Z32F = 43,
|
||||
Z16 = 44,
|
||||
Z32F = 45,
|
||||
Z16 = 46,
|
||||
|
||||
MaxDepthFormat,
|
||||
|
||||
// DepthStencil formats
|
||||
Z24S8 = 45,
|
||||
S8Z24 = 46,
|
||||
Z32FS8 = 47,
|
||||
Z24S8 = 47,
|
||||
S8Z24 = 48,
|
||||
Z32FS8 = 49,
|
||||
|
||||
MaxDepthStencilFormat,
|
||||
|
||||
@@ -138,6 +140,8 @@ struct SurfaceParams {
|
||||
4, // DXN2UNORM
|
||||
4, // DXN2SNORM
|
||||
4, // BC7U
|
||||
4, // BC6H_UF16
|
||||
4, // BC6H_SF16
|
||||
4, // ASTC_2D_4X4
|
||||
1, // G8R8U
|
||||
1, // G8R8S
|
||||
@@ -197,6 +201,8 @@ struct SurfaceParams {
|
||||
128, // DXN2UNORM
|
||||
128, // DXN2SNORM
|
||||
128, // BC7U
|
||||
128, // BC6H_UF16
|
||||
128, // BC6H_SF16
|
||||
32, // ASTC_2D_4X4
|
||||
16, // G8R8U
|
||||
16, // G8R8S
|
||||
@@ -482,6 +488,10 @@ struct SurfaceParams {
|
||||
UNREACHABLE();
|
||||
case Tegra::Texture::TextureFormat::BC7U:
|
||||
return PixelFormat::BC7U;
|
||||
case Tegra::Texture::TextureFormat::BC6H_UF16:
|
||||
return PixelFormat::BC6H_UF16;
|
||||
case Tegra::Texture::TextureFormat::BC6H_SF16:
|
||||
return PixelFormat::BC6H_SF16;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_4X4:
|
||||
return PixelFormat::ASTC_2D_4X4;
|
||||
case Tegra::Texture::TextureFormat::R16_G16:
|
||||
@@ -628,9 +638,6 @@ struct SurfaceParams {
|
||||
GetFormatBpp(pixel_format) / CHAR_BIT;
|
||||
}
|
||||
|
||||
/// Returns the CPU virtual address for this surface
|
||||
VAddr GetCpuAddr() const;
|
||||
|
||||
/// Creates SurfaceParams from a texture configuration
|
||||
static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config);
|
||||
|
||||
@@ -643,25 +650,13 @@ struct SurfaceParams {
|
||||
Tegra::GPUVAddr zeta_address,
|
||||
Tegra::DepthFormat format);
|
||||
|
||||
bool operator==(const SurfaceParams& other) const {
|
||||
return std::tie(addr, is_tiled, block_height, pixel_format, component_type, type, width,
|
||||
height, unaligned_height, size_in_bytes) ==
|
||||
std::tie(other.addr, other.is_tiled, other.block_height, other.pixel_format,
|
||||
other.component_type, other.type, other.width, other.height,
|
||||
other.unaligned_height, other.size_in_bytes);
|
||||
}
|
||||
|
||||
bool operator!=(const SurfaceParams& other) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
/// Checks if surfaces are compatible for caching
|
||||
bool IsCompatibleSurface(const SurfaceParams& other) const {
|
||||
return std::tie(pixel_format, type, cache_width, cache_height) ==
|
||||
std::tie(other.pixel_format, other.type, other.cache_width, other.cache_height);
|
||||
}
|
||||
|
||||
Tegra::GPUVAddr addr;
|
||||
VAddr addr;
|
||||
bool is_tiled;
|
||||
u32 block_height;
|
||||
PixelFormat pixel_format;
|
||||
@@ -702,7 +697,7 @@ class CachedSurface final {
|
||||
public:
|
||||
CachedSurface(const SurfaceParams& params);
|
||||
|
||||
Tegra::GPUVAddr GetAddr() const {
|
||||
VAddr GetAddr() const {
|
||||
return params.addr;
|
||||
}
|
||||
|
||||
@@ -753,13 +748,16 @@ public:
|
||||
/// Flushes the surface to Switch memory
|
||||
void FlushSurface(const Surface& surface);
|
||||
|
||||
/// Tries to find a framebuffer GPU address based on the provided CPU address
|
||||
Surface TryFindFramebufferSurface(VAddr cpu_addr) const;
|
||||
/// Tries to find a framebuffer using on the provided CPU address
|
||||
Surface TryFindFramebufferSurface(VAddr addr) const;
|
||||
|
||||
private:
|
||||
void LoadSurface(const Surface& surface);
|
||||
Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true);
|
||||
|
||||
/// Gets an uncached surface, creating it if need be
|
||||
Surface GetUncachedSurface(const SurfaceParams& params);
|
||||
|
||||
/// Recreates a surface with new parameters
|
||||
Surface RecreateSurface(const Surface& surface, const SurfaceParams& new_params);
|
||||
|
||||
|
||||
@@ -12,21 +12,17 @@
|
||||
namespace OpenGL {
|
||||
|
||||
/// Gets the address for the specified shader stage program
|
||||
static Tegra::GPUVAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
||||
static VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)];
|
||||
|
||||
return gpu.regs.code_address.CodeAddress() + shader_config.offset;
|
||||
return *gpu.memory_manager.GpuToCpuAddress(gpu.regs.code_address.CodeAddress() +
|
||||
shader_config.offset);
|
||||
}
|
||||
|
||||
/// Gets the shader program code from memory for the specified address
|
||||
static GLShader::ProgramCode GetShaderCode(Tegra::GPUVAddr addr) {
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
|
||||
static GLShader::ProgramCode GetShaderCode(VAddr addr) {
|
||||
GLShader::ProgramCode program_code(GLShader::MAX_PROGRAM_CODE_LENGTH);
|
||||
const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(addr)};
|
||||
Memory::ReadBlock(*cpu_address, program_code.data(), program_code.size() * sizeof(u64));
|
||||
|
||||
Memory::ReadBlock(addr, program_code.data(), program_code.size() * sizeof(u64));
|
||||
return program_code;
|
||||
}
|
||||
|
||||
@@ -55,7 +51,7 @@ static void SetShaderUniformBlockBindings(GLuint shader) {
|
||||
sizeof(GLShader::MaxwellUniformData));
|
||||
}
|
||||
|
||||
CachedShader::CachedShader(Tegra::GPUVAddr addr, Maxwell::ShaderProgram program_type)
|
||||
CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
|
||||
: addr{addr}, program_type{program_type}, setup{GetShaderCode(addr)} {
|
||||
|
||||
GLShader::ProgramResult program_result;
|
||||
@@ -113,7 +109,7 @@ GLint CachedShader::GetUniformLocation(const std::string& name) {
|
||||
}
|
||||
|
||||
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
|
||||
const Tegra::GPUVAddr program_addr{GetShaderAddress(program)};
|
||||
const VAddr program_addr{GetShaderAddress(program)};
|
||||
|
||||
// Look up shader in the cache based on address
|
||||
Shader shader{TryGet(program_addr)};
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
@@ -21,16 +20,16 @@ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
class CachedShader final {
|
||||
public:
|
||||
CachedShader(Tegra::GPUVAddr addr, Maxwell::ShaderProgram program_type);
|
||||
CachedShader(VAddr addr, Maxwell::ShaderProgram program_type);
|
||||
|
||||
/// Gets the address of the shader in guest memory, required for cache management
|
||||
Tegra::GPUVAddr GetAddr() const {
|
||||
VAddr GetAddr() const {
|
||||
return addr;
|
||||
}
|
||||
|
||||
/// Gets the size of the shader in guest memory, required for cache management
|
||||
size_t GetSizeInBytes() const {
|
||||
return sizeof(GLShader::ProgramCode);
|
||||
return GLShader::MAX_PROGRAM_CODE_LENGTH * sizeof(u64);
|
||||
}
|
||||
|
||||
/// Gets the shader entries for the shader
|
||||
@@ -50,7 +49,7 @@ public:
|
||||
GLint GetUniformLocation(const std::string& name);
|
||||
|
||||
private:
|
||||
Tegra::GPUVAddr addr;
|
||||
VAddr addr;
|
||||
Maxwell::ShaderProgram program_type;
|
||||
GLShader::ShaderSetup setup;
|
||||
GLShader::ShaderEntries entries;
|
||||
|
||||
@@ -729,8 +729,7 @@ private:
|
||||
{PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"},
|
||||
{PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="},
|
||||
{PredCondition::LessThanWithNan, "<"}, {PredCondition::NotEqualWithNan, "!="},
|
||||
{PredCondition::GreaterThanWithNan, ">"},
|
||||
};
|
||||
{PredCondition::GreaterThanWithNan, ">"}, {PredCondition::GreaterEqualWithNan, ">="}};
|
||||
|
||||
const auto& comparison{PredicateComparisonStrings.find(condition)};
|
||||
ASSERT_MSG(comparison != PredicateComparisonStrings.end(),
|
||||
@@ -739,7 +738,8 @@ private:
|
||||
std::string predicate{'(' + op_a + ") " + comparison->second + " (" + op_b + ')'};
|
||||
if (condition == PredCondition::LessThanWithNan ||
|
||||
condition == PredCondition::NotEqualWithNan ||
|
||||
condition == PredCondition::GreaterThanWithNan) {
|
||||
condition == PredCondition::GreaterThanWithNan ||
|
||||
condition == PredCondition::GreaterEqualWithNan) {
|
||||
predicate += " || isnan(" + op_a + ") || isnan(" + op_b + ')';
|
||||
}
|
||||
|
||||
@@ -887,6 +887,8 @@ private:
|
||||
// TEXS has two destination registers and a swizzle. The first two elements in the swizzle
|
||||
// go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1
|
||||
|
||||
ASSERT_MSG(instr.texs.nodep == 0, "TEXS nodep not implemented");
|
||||
|
||||
size_t written_components = 0;
|
||||
for (u32 component = 0; component < 4; ++component) {
|
||||
if (!instr.texs.IsComponentEnabled(component)) {
|
||||
@@ -1038,6 +1040,15 @@ private:
|
||||
case OpCode::Id::FMUL_R:
|
||||
case OpCode::Id::FMUL_IMM: {
|
||||
// FMUL does not have 'abs' bits and only the second operand has a 'neg' bit.
|
||||
ASSERT_MSG(instr.fmul.tab5cb8_2 == 0, "FMUL tab5cb8_2({}) is not implemented",
|
||||
instr.fmul.tab5cb8_2.Value());
|
||||
ASSERT_MSG(instr.fmul.tab5c68_1 == 0, "FMUL tab5cb8_1({}) is not implemented",
|
||||
instr.fmul.tab5c68_1.Value());
|
||||
ASSERT_MSG(instr.fmul.tab5c68_0 == 1, "FMUL tab5cb8_0({}) is not implemented",
|
||||
instr.fmul.tab5c68_0
|
||||
.Value()); // SMO typical sends 1 here which seems to be the default
|
||||
ASSERT_MSG(instr.fmul.cc == 0, "FMUL cc is not implemented");
|
||||
|
||||
op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b);
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1,
|
||||
instr.alu.saturate_d);
|
||||
@@ -1363,6 +1374,15 @@ private:
|
||||
"((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::POPC_C:
|
||||
case OpCode::Id::POPC_R:
|
||||
case OpCode::Id::POPC_IMM: {
|
||||
if (instr.popc.invert) {
|
||||
op_b = "~(" + op_b + ')';
|
||||
}
|
||||
regs.SetRegisterToInteger(instr.gpr0, true, 0, "bitCount(" + op_b + ')', 1, 1);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::SEL_C:
|
||||
case OpCode::Id::SEL_R:
|
||||
case OpCode::Id::SEL_IMM: {
|
||||
@@ -1427,6 +1447,12 @@ private:
|
||||
std::string op_b = instr.ffma.negate_b ? "-" : "";
|
||||
std::string op_c = instr.ffma.negate_c ? "-" : "";
|
||||
|
||||
ASSERT_MSG(instr.ffma.cc == 0, "FFMA cc not implemented");
|
||||
ASSERT_MSG(instr.ffma.tab5980_0 == 1, "FFMA tab5980_0({}) not implemented",
|
||||
instr.ffma.tab5980_0.Value()); // Seems to be 1 by default based on SMO
|
||||
ASSERT_MSG(instr.ffma.tab5980_1 == 0, "FFMA tab5980_1({}) not implemented",
|
||||
instr.ffma.tab5980_1.Value());
|
||||
|
||||
switch (opcode->GetId()) {
|
||||
case OpCode::Id::FFMA_CR: {
|
||||
op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
|
||||
@@ -2100,7 +2126,43 @@ private:
|
||||
}
|
||||
case OpCode::Id::IPA: {
|
||||
const auto& attribute = instr.attribute.fmt28;
|
||||
regs.SetRegisterToInputAttibute(instr.gpr0, attribute.element, attribute.index);
|
||||
const auto& reg = instr.gpr0;
|
||||
ASSERT_MSG(instr.ipa.sample_mode == Tegra::Shader::IpaSampleMode::Default,
|
||||
"Unhandled IPA sample mode: {}",
|
||||
static_cast<u32>(instr.ipa.sample_mode.Value()));
|
||||
ASSERT_MSG(instr.ipa.saturate == 0, "IPA saturate not implemented");
|
||||
switch (instr.ipa.interp_mode) {
|
||||
case Tegra::Shader::IpaInterpMode::Linear:
|
||||
if (stage == Maxwell3D::Regs::ShaderStage::Fragment &&
|
||||
attribute.index == Attribute::Index::Position) {
|
||||
switch (attribute.element) {
|
||||
case 0:
|
||||
shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.x;");
|
||||
break;
|
||||
case 1:
|
||||
shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.y;");
|
||||
break;
|
||||
case 2:
|
||||
shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.z;");
|
||||
break;
|
||||
case 3:
|
||||
shader.AddLine(regs.GetRegisterAsFloat(reg) + " = 1.0;");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index);
|
||||
}
|
||||
break;
|
||||
case Tegra::Shader::IpaInterpMode::Perspective:
|
||||
regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled IPA mode: {}",
|
||||
static_cast<u32>(instr.ipa.interp_mode.Value()));
|
||||
UNREACHABLE();
|
||||
regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::SSY: {
|
||||
|
||||
@@ -10,12 +10,14 @@
|
||||
#include <glad/glad.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/telemetry.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "core/tracer/recorder.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
|
||||
@@ -56,6 +56,8 @@ u32 BytesPerPixel(TextureFormat format) {
|
||||
case TextureFormat::DXT45:
|
||||
case TextureFormat::DXN2:
|
||||
case TextureFormat::BC7U:
|
||||
case TextureFormat::BC6H_UF16:
|
||||
case TextureFormat::BC6H_SF16:
|
||||
// In this case a 'pixel' actually refers to a 4x4 tile.
|
||||
return 16;
|
||||
case TextureFormat::R32_G32_B32:
|
||||
@@ -106,6 +108,8 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat
|
||||
case TextureFormat::DXN1:
|
||||
case TextureFormat::DXN2:
|
||||
case TextureFormat::BC7U:
|
||||
case TextureFormat::BC6H_UF16:
|
||||
case TextureFormat::BC6H_SF16:
|
||||
case TextureFormat::ASTC_2D_4X4:
|
||||
case TextureFormat::A8R8G8B8:
|
||||
case TextureFormat::A2B10G10R10:
|
||||
|
||||
@@ -70,6 +70,9 @@ set(UIS
|
||||
main.ui
|
||||
)
|
||||
|
||||
file(GLOB COMPAT_LIST
|
||||
${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||
${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
||||
file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*)
|
||||
file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
|
||||
|
||||
@@ -77,6 +80,7 @@ qt5_wrap_ui(UI_HDRS ${UIS})
|
||||
|
||||
target_sources(yuzu
|
||||
PRIVATE
|
||||
${COMPAT_LIST}
|
||||
${ICONS}
|
||||
${THEMES}
|
||||
${UI_HDRS}
|
||||
|
||||
@@ -9,11 +9,14 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/timer.h"
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
|
||||
WaitTreeItem::WaitTreeItem() = default;
|
||||
WaitTreeItem::~WaitTreeItem() = default;
|
||||
|
||||
QColor WaitTreeItem::GetColor() const {
|
||||
@@ -71,6 +74,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()
|
||||
}
|
||||
|
||||
WaitTreeText::WaitTreeText(const QString& t) : text(t) {}
|
||||
WaitTreeText::~WaitTreeText() = default;
|
||||
|
||||
QString WaitTreeText::GetText() const {
|
||||
return text;
|
||||
@@ -84,6 +88,8 @@ WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address) : mutex_address(mutex_
|
||||
owner = handle_table.Get<Kernel::Thread>(owner_handle);
|
||||
}
|
||||
|
||||
WaitTreeMutexInfo::~WaitTreeMutexInfo() = default;
|
||||
|
||||
QString WaitTreeMutexInfo::GetText() const {
|
||||
return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char('0'));
|
||||
}
|
||||
@@ -102,6 +108,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() cons
|
||||
}
|
||||
|
||||
WaitTreeCallstack::WaitTreeCallstack(const Kernel::Thread& thread) : thread(thread) {}
|
||||
WaitTreeCallstack::~WaitTreeCallstack() = default;
|
||||
|
||||
QString WaitTreeCallstack::GetText() const {
|
||||
return tr("Call stack");
|
||||
@@ -126,6 +133,10 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons
|
||||
}
|
||||
|
||||
WaitTreeWaitObject::WaitTreeWaitObject(const Kernel::WaitObject& o) : object(o) {}
|
||||
WaitTreeWaitObject::~WaitTreeWaitObject() = default;
|
||||
|
||||
WaitTreeExpandableItem::WaitTreeExpandableItem() = default;
|
||||
WaitTreeExpandableItem::~WaitTreeExpandableItem() = default;
|
||||
|
||||
bool WaitTreeExpandableItem::IsExpandable() const {
|
||||
return true;
|
||||
@@ -180,6 +191,8 @@ WaitTreeObjectList::WaitTreeObjectList(
|
||||
const std::vector<Kernel::SharedPtr<Kernel::WaitObject>>& list, bool w_all)
|
||||
: object_list(list), wait_all(w_all) {}
|
||||
|
||||
WaitTreeObjectList::~WaitTreeObjectList() = default;
|
||||
|
||||
QString WaitTreeObjectList::GetText() const {
|
||||
if (wait_all)
|
||||
return tr("waiting for all objects");
|
||||
@@ -194,6 +207,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeObjectList::GetChildren() con
|
||||
}
|
||||
|
||||
WaitTreeThread::WaitTreeThread(const Kernel::Thread& thread) : WaitTreeWaitObject(thread) {}
|
||||
WaitTreeThread::~WaitTreeThread() = default;
|
||||
|
||||
QString WaitTreeThread::GetText() const {
|
||||
const auto& thread = static_cast<const Kernel::Thread&>(object);
|
||||
@@ -312,6 +326,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
|
||||
}
|
||||
|
||||
WaitTreeEvent::WaitTreeEvent(const Kernel::Event& object) : WaitTreeWaitObject(object) {}
|
||||
WaitTreeEvent::~WaitTreeEvent() = default;
|
||||
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const {
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
|
||||
@@ -323,6 +338,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const {
|
||||
}
|
||||
|
||||
WaitTreeTimer::WaitTreeTimer(const Kernel::Timer& object) : WaitTreeWaitObject(object) {}
|
||||
WaitTreeTimer::~WaitTreeTimer() = default;
|
||||
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
|
||||
@@ -340,6 +356,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
|
||||
|
||||
WaitTreeThreadList::WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list)
|
||||
: thread_list(list) {}
|
||||
WaitTreeThreadList::~WaitTreeThreadList() = default;
|
||||
|
||||
QString WaitTreeThreadList::GetText() const {
|
||||
return tr("waited by thread");
|
||||
@@ -353,6 +370,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThreadList::GetChildren() con
|
||||
}
|
||||
|
||||
WaitTreeModel::WaitTreeModel(QObject* parent) : QAbstractItemModel(parent) {}
|
||||
WaitTreeModel::~WaitTreeModel() = default;
|
||||
|
||||
QModelIndex WaitTreeModel::index(int row, int column, const QModelIndex& parent) const {
|
||||
if (!hasIndex(row, column, parent))
|
||||
@@ -421,6 +439,8 @@ WaitTreeWidget::WaitTreeWidget(QWidget* parent) : QDockWidget(tr("Wait Tree"), p
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
WaitTreeWidget::~WaitTreeWidget() = default;
|
||||
|
||||
void WaitTreeWidget::OnDebugModeEntered() {
|
||||
if (!Core::System::GetInstance().IsPoweredOn())
|
||||
return;
|
||||
|
||||
@@ -4,11 +4,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QDockWidget>
|
||||
#include <QTreeView>
|
||||
#include <boost/container/flat_set.hpp>
|
||||
#include "core/core.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
class EmuThread;
|
||||
@@ -25,6 +29,7 @@ class WaitTreeThread;
|
||||
class WaitTreeItem : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
WaitTreeItem();
|
||||
~WaitTreeItem() override;
|
||||
|
||||
virtual bool IsExpandable() const;
|
||||
@@ -49,6 +54,8 @@ class WaitTreeText : public WaitTreeItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeText(const QString& text);
|
||||
~WaitTreeText() override;
|
||||
|
||||
QString GetText() const override;
|
||||
|
||||
private:
|
||||
@@ -58,6 +65,9 @@ private:
|
||||
class WaitTreeExpandableItem : public WaitTreeItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
WaitTreeExpandableItem();
|
||||
~WaitTreeExpandableItem() override;
|
||||
|
||||
bool IsExpandable() const override;
|
||||
};
|
||||
|
||||
@@ -65,6 +75,8 @@ class WaitTreeMutexInfo : public WaitTreeExpandableItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeMutexInfo(VAddr mutex_address);
|
||||
~WaitTreeMutexInfo() override;
|
||||
|
||||
QString GetText() const override;
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
|
||||
@@ -79,6 +91,8 @@ class WaitTreeCallstack : public WaitTreeExpandableItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeCallstack(const Kernel::Thread& thread);
|
||||
~WaitTreeCallstack() override;
|
||||
|
||||
QString GetText() const override;
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
|
||||
@@ -90,6 +104,8 @@ class WaitTreeWaitObject : public WaitTreeExpandableItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeWaitObject(const Kernel::WaitObject& object);
|
||||
~WaitTreeWaitObject() override;
|
||||
|
||||
static std::unique_ptr<WaitTreeWaitObject> make(const Kernel::WaitObject& object);
|
||||
QString GetText() const override;
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
@@ -105,6 +121,8 @@ class WaitTreeObjectList : public WaitTreeExpandableItem {
|
||||
public:
|
||||
WaitTreeObjectList(const std::vector<Kernel::SharedPtr<Kernel::WaitObject>>& list,
|
||||
bool wait_all);
|
||||
~WaitTreeObjectList() override;
|
||||
|
||||
QString GetText() const override;
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
|
||||
@@ -117,6 +135,8 @@ class WaitTreeThread : public WaitTreeWaitObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeThread(const Kernel::Thread& thread);
|
||||
~WaitTreeThread() override;
|
||||
|
||||
QString GetText() const override;
|
||||
QColor GetColor() const override;
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
@@ -126,6 +146,8 @@ class WaitTreeEvent : public WaitTreeWaitObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeEvent(const Kernel::Event& object);
|
||||
~WaitTreeEvent() override;
|
||||
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
};
|
||||
|
||||
@@ -133,6 +155,8 @@ class WaitTreeTimer : public WaitTreeWaitObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeTimer(const Kernel::Timer& object);
|
||||
~WaitTreeTimer() override;
|
||||
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
};
|
||||
|
||||
@@ -140,6 +164,8 @@ class WaitTreeThreadList : public WaitTreeExpandableItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list);
|
||||
~WaitTreeThreadList() override;
|
||||
|
||||
QString GetText() const override;
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
|
||||
@@ -152,6 +178,7 @@ class WaitTreeModel : public QAbstractItemModel {
|
||||
|
||||
public:
|
||||
explicit WaitTreeModel(QObject* parent = nullptr);
|
||||
~WaitTreeModel() override;
|
||||
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
QModelIndex index(int row, int column, const QModelIndex& parent) const override;
|
||||
@@ -171,6 +198,7 @@ class WaitTreeWidget : public QDockWidget {
|
||||
|
||||
public:
|
||||
explicit WaitTreeWidget(QWidget* parent = nullptr);
|
||||
~WaitTreeWidget() override;
|
||||
|
||||
public slots:
|
||||
void OnDebugModeEntered();
|
||||
|
||||
+79
-5
@@ -7,22 +7,29 @@
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QHeaderView>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
#include <QThreadPool>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "game_list.h"
|
||||
#include "game_list_p.h"
|
||||
#include "ui_settings.h"
|
||||
#include "yuzu/game_list.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
GameList::SearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {}
|
||||
|
||||
@@ -224,6 +231,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
|
||||
|
||||
item_model->insertColumns(0, COLUMN_COUNT);
|
||||
item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name");
|
||||
item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, "Compatibility");
|
||||
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type");
|
||||
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
|
||||
|
||||
@@ -325,12 +333,62 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
|
||||
|
||||
QMenu context_menu;
|
||||
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
|
||||
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
|
||||
|
||||
open_save_location->setEnabled(program_id != 0);
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0);
|
||||
|
||||
connect(open_save_location, &QAction::triggered,
|
||||
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); });
|
||||
connect(navigate_to_gamedb_entry, &QAction::triggered,
|
||||
[&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
|
||||
|
||||
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
|
||||
}
|
||||
|
||||
void GameList::LoadCompatibilityList() {
|
||||
QFile compat_list{":compatibility_list/compatibility_list.json"};
|
||||
|
||||
if (!compat_list.open(QFile::ReadOnly | QFile::Text)) {
|
||||
LOG_ERROR(Frontend, "Unable to open game compatibility list");
|
||||
return;
|
||||
}
|
||||
|
||||
if (compat_list.size() == 0) {
|
||||
LOG_WARNING(Frontend, "Game compatibility list is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray content = compat_list.readAll();
|
||||
if (content.isEmpty()) {
|
||||
LOG_ERROR(Frontend, "Unable to completely read game compatibility list");
|
||||
return;
|
||||
}
|
||||
|
||||
const QString string_content = content;
|
||||
QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8());
|
||||
QJsonArray arr = json.array();
|
||||
|
||||
for (const QJsonValue& value : arr) {
|
||||
QJsonObject game = value.toObject();
|
||||
|
||||
if (game.contains("compatibility") && game["compatibility"].isDouble()) {
|
||||
int compatibility = game["compatibility"].toInt();
|
||||
QString directory = game["directory"].toString();
|
||||
QJsonArray ids = game["releases"].toArray();
|
||||
|
||||
for (const QJsonValue& value : ids) {
|
||||
QJsonObject object = value.toObject();
|
||||
QString id = object["id"].toString();
|
||||
compatibility_list.emplace(
|
||||
id.toUpper().toStdString(),
|
||||
std::make_pair(QString::number(compatibility), directory));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
|
||||
if (!FileUtil::Exists(dir_path.toStdString()) ||
|
||||
!FileUtil::IsDirectory(dir_path.toStdString())) {
|
||||
@@ -345,7 +403,7 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
|
||||
|
||||
emit ShouldCancelWorker();
|
||||
|
||||
GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan);
|
||||
GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan, compatibility_list);
|
||||
|
||||
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
|
||||
connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
|
||||
@@ -426,6 +484,14 @@ static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
|
||||
}
|
||||
}
|
||||
|
||||
GameListWorker::GameListWorker(
|
||||
FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list)
|
||||
: vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
|
||||
compatibility_list(compatibility_list) {}
|
||||
|
||||
GameListWorker::~GameListWorker() = default;
|
||||
|
||||
void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache) {
|
||||
const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Program);
|
||||
@@ -523,11 +589,19 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
}
|
||||
}
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
// The game list uses this as compatibility number for untested games
|
||||
QString compatibility("99");
|
||||
if (it != compatibility_list.end())
|
||||
compatibility = it->second.first;
|
||||
|
||||
emit EntryReady({
|
||||
new GameListItemPath(
|
||||
FormatGameName(physical_name), icon, QString::fromStdString(name),
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItemCompat(compatibility),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(FileUtil::GetSize(physical_name)),
|
||||
|
||||
+17
-3
@@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
@@ -17,9 +19,15 @@
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include "main.h"
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
class GameListWorker;
|
||||
class GMainWindow;
|
||||
|
||||
namespace FileSys {
|
||||
class VfsFilesystem;
|
||||
}
|
||||
|
||||
enum class GameListOpenTarget { SaveData };
|
||||
|
||||
@@ -29,6 +37,7 @@ class GameList : public QWidget {
|
||||
public:
|
||||
enum {
|
||||
COLUMN_NAME,
|
||||
COLUMN_COMPATIBILITY,
|
||||
COLUMN_FILE_TYPE,
|
||||
COLUMN_SIZE,
|
||||
COLUMN_COUNT, // Number of columns
|
||||
@@ -61,13 +70,14 @@ public:
|
||||
QToolButton* button_filter_close = nullptr;
|
||||
};
|
||||
|
||||
explicit GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent = nullptr);
|
||||
explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs, GMainWindow* parent = nullptr);
|
||||
~GameList() override;
|
||||
|
||||
void clearFilter();
|
||||
void setFilterFocus();
|
||||
void setFilterVisible(bool visibility);
|
||||
|
||||
void LoadCompatibilityList();
|
||||
void PopulateAsync(const QString& dir_path, bool deep_scan);
|
||||
|
||||
void SaveInterfaceLayout();
|
||||
@@ -79,6 +89,9 @@ signals:
|
||||
void GameChosen(QString game_path);
|
||||
void ShouldCancelWorker();
|
||||
void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
|
||||
void NavigateToGamedbEntryRequested(
|
||||
u64 program_id,
|
||||
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
|
||||
|
||||
private slots:
|
||||
void onTextChanged(const QString& newText);
|
||||
@@ -92,7 +105,7 @@ private:
|
||||
void PopupContextMenu(const QPoint& menu_location);
|
||||
void RefreshGameDirectory();
|
||||
|
||||
FileSys::VirtualFilesystem vfs;
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
SearchField* search_field;
|
||||
GMainWindow* main_window = nullptr;
|
||||
QVBoxLayout* layout = nullptr;
|
||||
@@ -100,6 +113,7 @@ private:
|
||||
QStandardItemModel* item_model = nullptr;
|
||||
GameListWorker* current_worker = nullptr;
|
||||
QFileSystemWatcher* watcher = nullptr;
|
||||
std::unordered_map<std::string, std::pair<QString, QString>> compatibility_list;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(GameListOpenTarget);
|
||||
|
||||
+72
-5
@@ -4,20 +4,34 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QImage>
|
||||
#include <QObject>
|
||||
#include <QRunnable>
|
||||
#include <QStandardItem>
|
||||
#include <QString>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "ui_settings.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
#include "yuzu/util/util.h"
|
||||
|
||||
namespace FileSys {
|
||||
class NCA;
|
||||
class RegisteredCache;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
/**
|
||||
* Gets the default icon (for games without valid SMDH)
|
||||
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
|
||||
@@ -29,6 +43,17 @@ static QPixmap GetDefaultIcon(u32 size) {
|
||||
return icon;
|
||||
}
|
||||
|
||||
static auto FindMatchingCompatibilityEntry(
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list,
|
||||
u64 program_id) {
|
||||
return std::find_if(
|
||||
compatibility_list.begin(), compatibility_list.end(),
|
||||
[program_id](const std::pair<std::string, std::pair<QString, QString>>& element) {
|
||||
std::string pid = fmt::format("{:016X}", program_id);
|
||||
return element.first == pid;
|
||||
});
|
||||
}
|
||||
|
||||
class GameListItem : public QStandardItem {
|
||||
|
||||
public:
|
||||
@@ -96,6 +121,45 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class GameListItemCompat : public GameListItem {
|
||||
Q_DECLARE_TR_FUNCTIONS(GameListItemCompat)
|
||||
public:
|
||||
static const int CompatNumberRole = Qt::UserRole + 1;
|
||||
GameListItemCompat() = default;
|
||||
explicit GameListItemCompat(const QString& compatiblity) {
|
||||
struct CompatStatus {
|
||||
QString color;
|
||||
const char* text;
|
||||
const char* tooltip;
|
||||
};
|
||||
// clang-format off
|
||||
static const std::map<QString, CompatStatus> status_data = {
|
||||
{"0", {"#5c93ed", QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without\nany workarounds needed.")}},
|
||||
{"1", {"#47d35c", QT_TR_NOOP("Great"), QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish. May require some\nworkarounds.")}},
|
||||
{"2", {"#94b242", QT_TR_NOOP("Okay"), QT_TR_NOOP("Game functions with major graphical or audio glitches, but game is playable from start to finish with\nworkarounds.")}},
|
||||
{"3", {"#f2d624", QT_TR_NOOP("Bad"), QT_TR_NOOP("Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches\neven with workarounds.")}},
|
||||
{"4", {"#FF0000", QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start\nScreen.")}},
|
||||
{"5", {"#828282", QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}},
|
||||
{"99", {"#000000", QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}};
|
||||
// clang-format on
|
||||
|
||||
auto iterator = status_data.find(compatiblity);
|
||||
if (iterator == status_data.end()) {
|
||||
LOG_WARNING(Frontend, "Invalid compatibility number {}", compatiblity.toStdString());
|
||||
return;
|
||||
}
|
||||
CompatStatus status = iterator->second;
|
||||
setData(compatiblity, CompatNumberRole);
|
||||
setText(QObject::tr(status.text));
|
||||
setToolTip(QObject::tr(status.tooltip));
|
||||
setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole);
|
||||
}
|
||||
|
||||
bool operator<(const QStandardItem& other) const override {
|
||||
return data(CompatNumberRole) < other.data(CompatNumberRole);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A specialization of GameListItem for size values.
|
||||
* This class ensures that for every numerical size value it holds (in bytes), a correct
|
||||
@@ -141,8 +205,10 @@ class GameListWorker : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan)
|
||||
: vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan) {}
|
||||
GameListWorker(
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
|
||||
~GameListWorker() override;
|
||||
|
||||
public slots:
|
||||
/// Starts the processing of directory tree information.
|
||||
@@ -165,11 +231,12 @@ signals:
|
||||
void Finished(QStringList watch_list);
|
||||
|
||||
private:
|
||||
FileSys::VirtualFilesystem vfs;
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
|
||||
QStringList watch_list;
|
||||
QString dir_path;
|
||||
bool deep_scan;
|
||||
const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list;
|
||||
std::atomic_bool stop_processing;
|
||||
|
||||
void AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache);
|
||||
|
||||
+33
-10
@@ -16,25 +16,28 @@
|
||||
#include <QMessageBox>
|
||||
#include <QtGui>
|
||||
#include <QtWidgets>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging/text_formatter.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/telemetry.h"
|
||||
#include "core/core.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "yuzu/about_dialog.h"
|
||||
#include "yuzu/bootmanager.h"
|
||||
@@ -46,6 +49,7 @@
|
||||
#include "yuzu/debugger/profiler.h"
|
||||
#include "yuzu/debugger/wait_tree.h"
|
||||
#include "yuzu/game_list.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
@@ -134,6 +138,7 @@ GMainWindow::GMainWindow()
|
||||
|
||||
// Necessary to load titles from nand in gamelist.
|
||||
Service::FileSystem::CreateFactories(vfs);
|
||||
game_list->LoadCompatibilityList();
|
||||
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
|
||||
|
||||
// Show one-time "callout" messages to the user
|
||||
@@ -349,6 +354,8 @@ void GMainWindow::RestoreUIState() {
|
||||
void GMainWindow::ConnectWidgetEvents() {
|
||||
connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
|
||||
connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
|
||||
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
|
||||
&GMainWindow::OnGameListNavigateToGamedbEntry);
|
||||
|
||||
connect(this, &GMainWindow::EmulationStarting, render_window,
|
||||
&GRenderWindow::OnEmulationStarting);
|
||||
@@ -413,7 +420,7 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
|
||||
}
|
||||
}
|
||||
|
||||
bool GMainWindow::SupportsRequiredGLExtensions() {
|
||||
QStringList GMainWindow::GetUnsupportedGLExtensions() {
|
||||
QStringList unsupported_ext;
|
||||
|
||||
if (!GLAD_GL_ARB_program_interface_query)
|
||||
@@ -440,7 +447,7 @@ bool GMainWindow::SupportsRequiredGLExtensions() {
|
||||
for (const QString& ext : unsupported_ext)
|
||||
LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
|
||||
|
||||
return unsupported_ext.empty();
|
||||
return unsupported_ext;
|
||||
}
|
||||
|
||||
bool GMainWindow::LoadROM(const QString& filename) {
|
||||
@@ -458,11 +465,13 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SupportsRequiredGLExtensions()) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Error while initializing OpenGL Core!"),
|
||||
tr("Your GPU may not support one or more required OpenGL extensions. Please "
|
||||
"ensure you have the latest graphics driver. See the log for more details."));
|
||||
QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
|
||||
if (!unsupported_gl_extensions.empty()) {
|
||||
QMessageBox::critical(this, tr("Error while initializing OpenGL Core!"),
|
||||
tr("Your GPU may not support one or more required OpenGL"
|
||||
"extensions. Please ensure you have the latest graphics "
|
||||
"driver.<br><br>Unsupported extensions:<br>") +
|
||||
unsupported_gl_extensions.join("<br>"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -678,6 +687,20 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
|
||||
}
|
||||
|
||||
void GMainWindow::OnGameListNavigateToGamedbEntry(
|
||||
u64 program_id,
|
||||
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) {
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
QString directory;
|
||||
|
||||
if (it != compatibility_list.end())
|
||||
directory = it->second.second;
|
||||
|
||||
QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory));
|
||||
}
|
||||
|
||||
void GMainWindow::OnMenuLoadFile() {
|
||||
QString extensions;
|
||||
for (const auto& piece : game_list->supported_file_extensions)
|
||||
|
||||
+13
-2
@@ -5,8 +5,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "ui_main.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
@@ -23,6 +27,10 @@ class ProfilerWidget;
|
||||
class WaitTreeWidget;
|
||||
enum class GameListOpenTarget;
|
||||
|
||||
namespace FileSys {
|
||||
class VfsFilesystem;
|
||||
}
|
||||
|
||||
namespace Tegra {
|
||||
class DebugContext;
|
||||
}
|
||||
@@ -80,7 +88,7 @@ private:
|
||||
void ConnectWidgetEvents();
|
||||
void ConnectMenuEvents();
|
||||
|
||||
bool SupportsRequiredGLExtensions();
|
||||
QStringList GetUnsupportedGLExtensions();
|
||||
bool LoadROM(const QString& filename);
|
||||
void BootGame(const QString& filename);
|
||||
void ShutdownGame();
|
||||
@@ -124,6 +132,9 @@ private slots:
|
||||
/// Called whenever a user selects a game in the game list widget.
|
||||
void OnGameListLoadFile(QString game_path);
|
||||
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
|
||||
void OnGameListNavigateToGamedbEntry(
|
||||
u64 program_id,
|
||||
std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list);
|
||||
void OnMenuLoadFile();
|
||||
void OnMenuLoadFolder();
|
||||
void OnMenuInstallToNAND();
|
||||
@@ -166,7 +177,7 @@ private:
|
||||
QString game_path;
|
||||
|
||||
// FS
|
||||
FileSys::VirtualFilesystem vfs;
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
|
||||
// Debugger panes
|
||||
ProfilerWidget* profilerWidget;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <QPainter>
|
||||
#include "yuzu/util/util.h"
|
||||
|
||||
QFont GetMonospaceFont() {
|
||||
@@ -24,3 +25,13 @@ QString ReadableByteSize(qulonglong size) {
|
||||
.arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
|
||||
.arg(units[digit_groups]);
|
||||
}
|
||||
|
||||
QPixmap CreateCirclePixmapFromColor(const QColor& color) {
|
||||
QPixmap circle_pixmap(16, 16);
|
||||
circle_pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&circle_pixmap);
|
||||
painter.setPen(color);
|
||||
painter.setBrush(color);
|
||||
painter.drawEllipse(0, 0, 15, 15);
|
||||
return circle_pixmap;
|
||||
}
|
||||
|
||||
@@ -12,3 +12,10 @@ QFont GetMonospaceFont();
|
||||
|
||||
/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
|
||||
QString ReadableByteSize(qulonglong size);
|
||||
|
||||
/**
|
||||
* Creates a circle pixmap from a specified color
|
||||
* @param color The color the pixmap shall have
|
||||
* @return QPixmap circle pixmap
|
||||
*/
|
||||
QPixmap CreateCirclePixmapFromColor(const QColor& color);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/filter.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -17,10 +18,13 @@
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/telemetry.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "yuzu_cmd/config.h"
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user