Compare commits
190 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbdd6cd1c0 | ||
|
|
4b27680639 | ||
|
|
9e689a81f8 | ||
|
|
fb9124b6cd | ||
|
|
f732cd5a4b | ||
|
|
36259c01c2 | ||
|
|
7fb7d3c218 | ||
|
|
65c748fbd3 | ||
|
|
63a5f48e7e | ||
|
|
2a9e388290 | ||
|
|
313cc36fec | ||
|
|
48807e9a24 | ||
|
|
2321656dbe | ||
|
|
e721c344ae | ||
|
|
6ec48af222 | ||
|
|
f12eb40834 | ||
|
|
7ad11e3867 | ||
|
|
772c86a260 | ||
|
|
3b9d89839d | ||
|
|
30b176f92b | ||
|
|
3f3a93f13b | ||
|
|
54a02d14fd | ||
|
|
447bdac298 | ||
|
|
cca663792f | ||
|
|
3c7eed16dc | ||
|
|
70b595a63b | ||
|
|
ad50cd7df9 | ||
|
|
e23110bd9f | ||
|
|
abdce723eb | ||
|
|
55481df50f | ||
|
|
beb3d77a79 | ||
|
|
e86af37ecb | ||
|
|
da5a537029 | ||
|
|
58032e0085 | ||
|
|
2521007c09 | ||
|
|
6be79bab37 | ||
|
|
b50557d1f0 | ||
|
|
4705d1b523 | ||
|
|
965608e6d1 | ||
|
|
c72ef5f405 | ||
|
|
9968c0883a | ||
|
|
79163fca80 | ||
|
|
306a24aad7 | ||
|
|
d992909636 | ||
|
|
6e1db6b703 | ||
|
|
8eae66907e | ||
|
|
f6f1a8f26a | ||
|
|
1ca2b504bf | ||
|
|
dd9ace502b | ||
|
|
3f3c3ca5f9 | ||
|
|
cae9708a00 | ||
|
|
f477c5dfdd | ||
|
|
efa7d8d04b | ||
|
|
bb4a1e059c | ||
|
|
dfe4b3f723 | ||
|
|
7d417d501d | ||
|
|
c2146c4eef | ||
|
|
fd6549be73 | ||
|
|
0b03e8a98f | ||
|
|
5829ba1ccc | ||
|
|
4ed2774c26 | ||
|
|
f67039c067 | ||
|
|
223ca80753 | ||
|
|
5aeabd9a17 | ||
|
|
88bc39374f | ||
|
|
c0abc7124d | ||
|
|
fb234560b0 | ||
|
|
18d24fbdd0 | ||
|
|
36665ce0b2 | ||
|
|
58c8a44e7a | ||
|
|
19dc36ce06 | ||
|
|
192f1f7ebe | ||
|
|
5d005b87a3 | ||
|
|
3c4238657d | ||
|
|
01ff38cca8 | ||
|
|
73dcb13619 | ||
|
|
d018ac2c60 | ||
|
|
54684feffa | ||
|
|
3898c3903e | ||
|
|
675aa5f719 | ||
|
|
b889167b2c | ||
|
|
e447d8aafa | ||
|
|
6ff9008230 | ||
|
|
c96450f6e2 | ||
|
|
9d2d349d7b | ||
|
|
7565389700 | ||
|
|
e723441e37 | ||
|
|
34841a41c3 | ||
|
|
0837290992 | ||
|
|
75de730e28 | ||
|
|
10a83653ee | ||
|
|
4504302abc | ||
|
|
4b2ff1e00e | ||
|
|
0b6df52109 | ||
|
|
b8b05a484a | ||
|
|
4d63f97945 | ||
|
|
de982deb25 | ||
|
|
e49ae3bf92 | ||
|
|
d1812316e1 | ||
|
|
51ba60b27e | ||
|
|
97c8c9f49a | ||
|
|
6acdae0e4c | ||
|
|
d7587842eb | ||
|
|
198a0395bb | ||
|
|
fed773a86c | ||
|
|
082740d34d | ||
|
|
03d489dcf5 | ||
|
|
9422cf7c10 | ||
|
|
fac3706253 | ||
|
|
7232a1ed16 | ||
|
|
3dd7643214 | ||
|
|
4db28f72f6 | ||
|
|
2d83553ea7 | ||
|
|
cb728797b0 | ||
|
|
a56f687793 | ||
|
|
b01f9c8a70 | ||
|
|
561ce29c98 | ||
|
|
b7de31ac97 | ||
|
|
6f69f06873 | ||
|
|
9f755218a1 | ||
|
|
3809041c24 | ||
|
|
60bf761afb | ||
|
|
228f516bb4 | ||
|
|
9251354152 | ||
|
|
0966665fc2 | ||
|
|
ea1525dab1 | ||
|
|
bdf9faab33 | ||
|
|
e60ed2bb3e | ||
|
|
fcac55d5bf | ||
|
|
175aa343ff | ||
|
|
1bf4154e7d | ||
|
|
9097301d92 | ||
|
|
06c4ce8645 | ||
|
|
007ffbef1c | ||
|
|
58c0d37422 | ||
|
|
07f7ce1da2 | ||
|
|
b8c75a845b | ||
|
|
6c81c8f5b7 | ||
|
|
4e81fc8296 | ||
|
|
d267948a73 | ||
|
|
6162cb922e | ||
|
|
4530511ee4 | ||
|
|
a79831d9d0 | ||
|
|
7731a0e2d1 | ||
|
|
c2ed348bdd | ||
|
|
9098905dd1 | ||
|
|
d65a4af895 | ||
|
|
6bd034eae9 | ||
|
|
2131f71573 | ||
|
|
41b4674458 | ||
|
|
07cc7e0c12 | ||
|
|
1bbc9debfb | ||
|
|
5192521dc3 | ||
|
|
94f2be5473 | ||
|
|
a4a58be2d4 | ||
|
|
345e73f2fe | ||
|
|
28d7c2f5a5 | ||
|
|
b347543e83 | ||
|
|
4e2071b6d9 | ||
|
|
ba677ccb5a | ||
|
|
de0b1cb2b2 | ||
|
|
e0002599ac | ||
|
|
324e470879 | ||
|
|
549fd18ac4 | ||
|
|
16e8625a30 | ||
|
|
2b30000a1e | ||
|
|
03d10ea3b4 | ||
|
|
1af4414861 | ||
|
|
d86f9cd709 | ||
|
|
b711cdce78 | ||
|
|
bc930754cc | ||
|
|
3d471e732d | ||
|
|
3b26206dbd | ||
|
|
6b0695b3cd | ||
|
|
6c410104f4 | ||
|
|
fa59a7b4d8 | ||
|
|
1b4503c571 | ||
|
|
5f3aacdc37 | ||
|
|
2787a0c287 | ||
|
|
4b396f375c | ||
|
|
0cefb7bcb4 | ||
|
|
84139586c9 | ||
|
|
fb94871791 | ||
|
|
bab21e8cb3 | ||
|
|
4111971cbd | ||
|
|
ed82fa3a91 | ||
|
|
c9983ad9a7 | ||
|
|
1b7d619914 | ||
|
|
90792cdb6e | ||
|
|
4ec8a3df08 |
@@ -1,4 +1,3 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
mkdir -p "$HOME/.ccache"
|
||||
docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache yuzuemu/build-environments:linux-mingw /bin/bash /yuzu/.travis/linux-mingw/docker.sh
|
||||
mkdir "$HOME/.ccache" || true
|
||||
docker run --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache yuzuemu/build-environments:linux-mingw /bin/bash -ex /yuzu/.travis/linux-mingw/docker.sh
|
||||
|
||||
@@ -7,18 +7,6 @@ include(CMakeDependentOption)
|
||||
|
||||
project(yuzu)
|
||||
|
||||
# Get Git submodule dependencies
|
||||
find_package(Git QUIET)
|
||||
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
RESULT_VARIABLE GIT_SUBMOD_RESULT)
|
||||
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
|
||||
message(FATAL_ERROR "git submodule update --init --recursive failed with ${GIT_SUBMOD_RESULT}, "
|
||||
"please checkout submodules manually with \"git submodule update --init --recursive\"")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Set bundled sdl2/qt as dependent options.
|
||||
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
|
||||
option(ENABLE_SDL2 "Enable the SDL2 frontend" ON)
|
||||
@@ -45,6 +33,22 @@ if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
|
||||
DESTINATION ${PROJECT_SOURCE_DIR}/.git/hooks)
|
||||
endif()
|
||||
|
||||
# Sanity check : Check that all submodules are present
|
||||
# =======================================================================
|
||||
|
||||
function(check_submodules_present)
|
||||
file(READ "${PROJECT_SOURCE_DIR}/.gitmodules" gitmodules)
|
||||
string(REGEX MATCHALL "path *= *[^ \t\r\n]*" gitmodules ${gitmodules})
|
||||
foreach(module ${gitmodules})
|
||||
string(REGEX REPLACE "path *= *" "" module ${module})
|
||||
if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
|
||||
message(FATAL_ERROR "Git submodule ${module} not found. "
|
||||
"Please run: git submodule update --init --recursive")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
check_submodules_present()
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||
COPYONLY)
|
||||
|
||||
@@ -70,6 +70,7 @@ set(HASH_FILES
|
||||
"${VIDEO_CORE}/shader/decode/half_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/half_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/hfma2.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/image.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/memory.cpp"
|
||||
|
||||
@@ -105,7 +105,7 @@ void Stream::PlayNextBuffer() {
|
||||
|
||||
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
|
||||
|
||||
core_timing.ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
|
||||
core_timing.ScheduleEvent(GetBufferReleaseCycles(*active_buffer), release_event, {});
|
||||
}
|
||||
|
||||
void Stream::ReleaseActiveBuffer() {
|
||||
|
||||
@@ -44,6 +44,7 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
"${VIDEO_CORE}/shader/decode/half_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/half_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/hfma2.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/image.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/memory.cpp"
|
||||
@@ -74,6 +75,7 @@ add_library(common STATIC
|
||||
assert.h
|
||||
detached_tasks.cpp
|
||||
detached_tasks.h
|
||||
binary_find.h
|
||||
bit_field.h
|
||||
bit_util.h
|
||||
cityhash.cpp
|
||||
|
||||
@@ -19,6 +19,12 @@ constexpr T AlignDown(T value, std::size_t size) {
|
||||
return static_cast<T>(value - value % size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr T AlignBits(T value, std::size_t align) {
|
||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||
return static_cast<T>((value + ((1ULL << align) - 1)) >> align << align);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr bool Is4KBAligned(T value) {
|
||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||
|
||||
21
src/common/binary_find.h
Normal file
21
src/common/binary_find.h
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <class ForwardIt, class T, class Compare = std::less<>>
|
||||
ForwardIt BinaryFind(ForwardIt first, ForwardIt last, const T& value, Compare comp = {}) {
|
||||
// Note: BOTH type T and the type after ForwardIt is dereferenced
|
||||
// must be implicitly convertible to BOTH Type1 and Type2, used in Compare.
|
||||
// This is stricter than lower_bound requirement (see above)
|
||||
|
||||
first = std::lower_bound(first, last, value, comp);
|
||||
return first != last && !comp(value, *first) ? first : last;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -97,4 +97,48 @@ inline u32 CountTrailingZeroes64(u64 value) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
inline u32 MostSignificantBit32(const u32 value) {
|
||||
unsigned long result;
|
||||
_BitScanReverse(&result, value);
|
||||
return static_cast<u32>(result);
|
||||
}
|
||||
|
||||
inline u32 MostSignificantBit64(const u64 value) {
|
||||
unsigned long result;
|
||||
_BitScanReverse64(&result, value);
|
||||
return static_cast<u32>(result);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
inline u32 MostSignificantBit32(const u32 value) {
|
||||
return 31U - static_cast<u32>(__builtin_clz(value));
|
||||
}
|
||||
|
||||
inline u32 MostSignificantBit64(const u64 value) {
|
||||
return 63U - static_cast<u32>(__builtin_clzll(value));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
inline u32 Log2Floor32(const u32 value) {
|
||||
return MostSignificantBit32(value);
|
||||
}
|
||||
|
||||
inline u32 Log2Ceil32(const u32 value) {
|
||||
const u32 log2_f = Log2Floor32(value);
|
||||
return log2_f + ((value ^ (1U << log2_f)) != 0U);
|
||||
}
|
||||
|
||||
inline u32 Log2Floor64(const u64 value) {
|
||||
return MostSignificantBit64(value);
|
||||
}
|
||||
|
||||
inline u32 Log2Ceil64(const u64 value) {
|
||||
const u64 log2_f = static_cast<u64>(Log2Floor64(value));
|
||||
return static_cast<u32>(log2_f + ((value ^ (1ULL << log2_f)) != 0ULL));
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#if !defined(ARCHITECTURE_x86_64)
|
||||
|
||||
@@ -175,6 +175,7 @@ add_library(core STATIC
|
||||
hle/service/acc/acc_u0.h
|
||||
hle/service/acc/acc_u1.cpp
|
||||
hle/service/acc/acc_u1.h
|
||||
hle/service/acc/errors.h
|
||||
hle/service/acc/profile_manager.cpp
|
||||
hle/service/acc/profile_manager.h
|
||||
hle/service/am/am.cpp
|
||||
@@ -270,6 +271,7 @@ add_library(core STATIC
|
||||
hle/service/filesystem/fsp_srv.h
|
||||
hle/service/fgm/fgm.cpp
|
||||
hle/service/fgm/fgm.h
|
||||
hle/service/friend/errors.h
|
||||
hle/service/friend/friend.cpp
|
||||
hle/service/friend/friend.h
|
||||
hle/service/friend/interface.cpp
|
||||
@@ -429,6 +431,8 @@ add_library(core STATIC
|
||||
hle/service/time/interface.h
|
||||
hle/service/time/time.cpp
|
||||
hle/service/time/time.h
|
||||
hle/service/time/time_sharedmemory.cpp
|
||||
hle/service/time/time_sharedmemory.h
|
||||
hle/service/usb/usb.cpp
|
||||
hle/service/usb/usb.h
|
||||
hle/service/vi/display/vi_display.cpp
|
||||
@@ -476,6 +480,8 @@ add_library(core STATIC
|
||||
settings.h
|
||||
telemetry_session.cpp
|
||||
telemetry_session.h
|
||||
tools/freezer.cpp
|
||||
tools/freezer.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(core)
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "core/reporter.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "core/tools/freezer.h"
|
||||
#include "file_sys/cheat_engine.h"
|
||||
#include "file_sys/patch_manager.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
@@ -300,6 +301,7 @@ struct System::Impl {
|
||||
bool is_powered_on = false;
|
||||
|
||||
std::unique_ptr<FileSys::CheatEngine> cheat_engine;
|
||||
std::unique_ptr<Tools::Freezer> memory_freezer;
|
||||
|
||||
/// Frontend applets
|
||||
Service::AM::Applets::AppletManager applet_manager;
|
||||
|
||||
@@ -53,7 +53,7 @@ bool CpuBarrier::Rendezvous() {
|
||||
Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
|
||||
std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_timing{system.CoreTiming()}, core_index{core_index} {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
if (Settings::values.cpu_jit_enabled) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index);
|
||||
#else
|
||||
@@ -70,7 +70,7 @@ Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_ba
|
||||
Cpu::~Cpu() = default;
|
||||
|
||||
std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
if (Settings::values.cpu_jit_enabled) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
return std::make_unique<DynarmicExclusiveMonitor>(num_cores);
|
||||
#else
|
||||
|
||||
@@ -56,12 +56,12 @@ void CoreTiming::Initialize() {
|
||||
}
|
||||
|
||||
void CoreTiming::Shutdown() {
|
||||
MoveEvents();
|
||||
ClearPendingEvents();
|
||||
UnregisterAllEvents();
|
||||
}
|
||||
|
||||
EventType* CoreTiming::RegisterEvent(const std::string& name, TimedCallback callback) {
|
||||
std::lock_guard guard{inner_mutex};
|
||||
// check for existing type with same name.
|
||||
// we want event type names to remain unique so that we can use them for serialization.
|
||||
ASSERT_MSG(event_types.find(name) == event_types.end(),
|
||||
@@ -82,6 +82,7 @@ void CoreTiming::UnregisterAllEvents() {
|
||||
|
||||
void CoreTiming::ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata) {
|
||||
ASSERT(event_type != nullptr);
|
||||
std::lock_guard guard{inner_mutex};
|
||||
const s64 timeout = GetTicks() + cycles_into_future;
|
||||
|
||||
// If this event needs to be scheduled before the next advance(), force one early
|
||||
@@ -93,12 +94,8 @@ void CoreTiming::ScheduleEvent(s64 cycles_into_future, const EventType* event_ty
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
|
||||
void CoreTiming::ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type,
|
||||
u64 userdata) {
|
||||
ts_queue.Push(Event{global_timer + cycles_into_future, 0, userdata, event_type});
|
||||
}
|
||||
|
||||
void CoreTiming::UnscheduleEvent(const EventType* event_type, u64 userdata) {
|
||||
std::lock_guard guard{inner_mutex};
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
return e.type == event_type && e.userdata == userdata;
|
||||
});
|
||||
@@ -110,10 +107,6 @@ void CoreTiming::UnscheduleEvent(const EventType* event_type, u64 userdata) {
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata) {
|
||||
unschedule_queue.Push(std::make_pair(event_type, userdata));
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetTicks() const {
|
||||
u64 ticks = static_cast<u64>(global_timer);
|
||||
if (!is_global_timer_sane) {
|
||||
@@ -135,6 +128,7 @@ void CoreTiming::ClearPendingEvents() {
|
||||
}
|
||||
|
||||
void CoreTiming::RemoveEvent(const EventType* event_type) {
|
||||
std::lock_guard guard{inner_mutex};
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(),
|
||||
[&](const Event& e) { return e.type == event_type; });
|
||||
|
||||
@@ -145,11 +139,6 @@ void CoreTiming::RemoveEvent(const EventType* event_type) {
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::RemoveNormalAndThreadsafeEvent(const EventType* event_type) {
|
||||
MoveEvents();
|
||||
RemoveEvent(event_type);
|
||||
}
|
||||
|
||||
void CoreTiming::ForceExceptionCheck(s64 cycles) {
|
||||
cycles = std::max<s64>(0, cycles);
|
||||
if (downcount <= cycles) {
|
||||
@@ -162,19 +151,8 @@ void CoreTiming::ForceExceptionCheck(s64 cycles) {
|
||||
downcount = static_cast<int>(cycles);
|
||||
}
|
||||
|
||||
void CoreTiming::MoveEvents() {
|
||||
for (Event ev; ts_queue.Pop(ev);) {
|
||||
ev.fifo_order = event_fifo_id++;
|
||||
event_queue.emplace_back(std::move(ev));
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::Advance() {
|
||||
MoveEvents();
|
||||
for (std::pair<const EventType*, u64> ev; unschedule_queue.Pop(ev);) {
|
||||
UnscheduleEvent(ev.first, ev.second);
|
||||
}
|
||||
std::unique_lock<std::mutex> guard(inner_mutex);
|
||||
|
||||
const int cycles_executed = slice_length - downcount;
|
||||
global_timer += cycles_executed;
|
||||
@@ -186,7 +164,9 @@ void CoreTiming::Advance() {
|
||||
Event evt = std::move(event_queue.front());
|
||||
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
event_queue.pop_back();
|
||||
inner_mutex.unlock();
|
||||
evt.type->callback(evt.userdata, global_timer - evt.time);
|
||||
inner_mutex.lock();
|
||||
}
|
||||
|
||||
is_global_timer_sane = false;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -67,7 +68,7 @@ public:
|
||||
///
|
||||
EventType* RegisterEvent(const std::string& name, TimedCallback callback);
|
||||
|
||||
/// Unregisters all registered events thus far.
|
||||
/// Unregisters all registered events thus far. Note: not thread unsafe
|
||||
void UnregisterAllEvents();
|
||||
|
||||
/// After the first Advance, the slice lengths and the downcount will be reduced whenever an
|
||||
@@ -76,20 +77,10 @@ public:
|
||||
/// Scheduling from a callback will not update the downcount until the Advance() completes.
|
||||
void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata = 0);
|
||||
|
||||
/// This is to be called when outside of hle threads, such as the graphics thread, wants to
|
||||
/// schedule things to be executed on the main thread.
|
||||
///
|
||||
/// @note This doesn't change slice_length and thus events scheduled by this might be
|
||||
/// called with a delay of up to MAX_SLICE_LENGTH
|
||||
void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type,
|
||||
u64 userdata = 0);
|
||||
|
||||
void UnscheduleEvent(const EventType* event_type, u64 userdata);
|
||||
void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata);
|
||||
|
||||
/// We only permit one event of each type in the queue at a time.
|
||||
void RemoveEvent(const EventType* event_type);
|
||||
void RemoveNormalAndThreadsafeEvent(const EventType* event_type);
|
||||
|
||||
void ForceExceptionCheck(s64 cycles);
|
||||
|
||||
@@ -120,7 +111,6 @@ private:
|
||||
|
||||
/// Clear all pending events. This should ONLY be done on exit.
|
||||
void ClearPendingEvents();
|
||||
void MoveEvents();
|
||||
|
||||
s64 global_timer = 0;
|
||||
s64 idled_cycles = 0;
|
||||
@@ -143,14 +133,9 @@ private:
|
||||
// remain stable regardless of rehashes/resizing.
|
||||
std::unordered_map<std::string, EventType> event_types;
|
||||
|
||||
// The queue for storing the events from other threads threadsafe until they will be added
|
||||
// to the event_queue by the emu thread
|
||||
Common::MPSCQueue<Event> ts_queue;
|
||||
|
||||
// The queue for unscheduling the events from other threads threadsafe
|
||||
Common::MPSCQueue<std::pair<const EventType*, u64>> unschedule_queue;
|
||||
|
||||
EventType* ev_lost = nullptr;
|
||||
|
||||
std::mutex inner_mutex;
|
||||
};
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -35,9 +35,9 @@ enum class ContentRecordType : u8 {
|
||||
Program = 1,
|
||||
Data = 2,
|
||||
Control = 3,
|
||||
Manual = 4,
|
||||
Legal = 5,
|
||||
Patch = 6,
|
||||
HtmlDocument = 4,
|
||||
LegalInformation = 5,
|
||||
DeltaFragment = 6,
|
||||
};
|
||||
|
||||
struct ContentRecord {
|
||||
|
||||
@@ -99,7 +99,7 @@ ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
return ContentRecordType::Data;
|
||||
case NCAContentType::Manual:
|
||||
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
|
||||
return ContentRecordType::Manual;
|
||||
return ContentRecordType::HtmlDocument;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid NCAContentType={:02X}", static_cast<u8>(type));
|
||||
}
|
||||
@@ -397,8 +397,8 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
|
||||
});
|
||||
|
||||
if (meta_iter == ncas.end()) {
|
||||
LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and "
|
||||
"is therefore malformed. Double check your encryption keys.");
|
||||
LOG_ERROR(Loader, "The file you are attempting to install does not have a metadata NCA and "
|
||||
"is therefore malformed. Check your encryption keys.");
|
||||
return InstallResult::ErrorMetaFailed;
|
||||
}
|
||||
|
||||
@@ -415,6 +415,9 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
|
||||
const auto cnmt_file = section0->GetFiles()[0];
|
||||
const CNMT cnmt(cnmt_file);
|
||||
for (const auto& record : cnmt.GetContentRecords()) {
|
||||
// Ignore DeltaFragments, they are not useful to us
|
||||
if (record.type == ContentRecordType::DeltaFragment)
|
||||
continue;
|
||||
const auto nca = GetNCAFromNSPForID(nsp, record.nca_id);
|
||||
if (nca == nullptr)
|
||||
return InstallResult::ErrorCopyFailed;
|
||||
|
||||
@@ -248,10 +248,13 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
|
||||
auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
|
||||
|
||||
if (next_file == nullptr) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"NCA with ID {}.nca is listed in content metadata, but cannot "
|
||||
"be found in PFS. NSP appears to be corrupted.",
|
||||
id_string);
|
||||
if (rec.type != ContentRecordType::DeltaFragment) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"NCA with ID {}.nca is listed in content metadata, but cannot "
|
||||
"be found in PFS. NSP appears to be corrupted.",
|
||||
id_string);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,38 @@
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
ParentalControlsApplet::~ParentalControlsApplet() = default;
|
||||
|
||||
DefaultParentalControlsApplet::~DefaultParentalControlsApplet() = default;
|
||||
|
||||
void DefaultParentalControlsApplet::VerifyPIN(std::function<void(bool)> finished,
|
||||
bool suspend_future_verification_temporarily) {
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend to verify PIN (normal), "
|
||||
"suspend_future_verification_temporarily={}, verifying as correct.",
|
||||
suspend_future_verification_temporarily);
|
||||
finished(true);
|
||||
}
|
||||
|
||||
void DefaultParentalControlsApplet::VerifyPINForSettings(std::function<void(bool)> finished) {
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend to verify PIN (settings), verifying as correct.");
|
||||
finished(true);
|
||||
}
|
||||
|
||||
void DefaultParentalControlsApplet::RegisterPIN(std::function<void()> finished) {
|
||||
LOG_INFO(Service_AM, "Application requested frontend to register new PIN");
|
||||
finished();
|
||||
}
|
||||
|
||||
void DefaultParentalControlsApplet::ChangePIN(std::function<void()> finished) {
|
||||
LOG_INFO(Service_AM, "Application requested frontend to change PIN to new value");
|
||||
finished();
|
||||
}
|
||||
|
||||
PhotoViewerApplet::~PhotoViewerApplet() = default;
|
||||
|
||||
DefaultPhotoViewerApplet::~DefaultPhotoViewerApplet() {}
|
||||
DefaultPhotoViewerApplet::~DefaultPhotoViewerApplet() = default;
|
||||
|
||||
void DefaultPhotoViewerApplet::ShowPhotosForApplication(u64 title_id,
|
||||
std::function<void()> finished) const {
|
||||
@@ -24,4 +53,72 @@ void DefaultPhotoViewerApplet::ShowAllPhotos(std::function<void()> finished) con
|
||||
finished();
|
||||
}
|
||||
|
||||
ECommerceApplet::~ECommerceApplet() = default;
|
||||
|
||||
DefaultECommerceApplet::~DefaultECommerceApplet() = default;
|
||||
|
||||
void DefaultECommerceApplet::ShowApplicationInformation(
|
||||
std::function<void()> finished, u64 title_id, std::optional<u128> user_id,
|
||||
std::optional<bool> full_display, std::optional<std::string> extra_parameter) {
|
||||
const auto value = user_id.value_or(u128{});
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend show application information for EShop, "
|
||||
"title_id={:016X}, user_id={:016X}{:016X}, full_display={}, extra_parameter={}",
|
||||
title_id, value[1], value[0],
|
||||
full_display.has_value() ? fmt::format("{}", *full_display) : "null",
|
||||
extra_parameter.value_or("null"));
|
||||
finished();
|
||||
}
|
||||
|
||||
void DefaultECommerceApplet::ShowAddOnContentList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id,
|
||||
std::optional<bool> full_display) {
|
||||
const auto value = user_id.value_or(u128{});
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend show add on content list for EShop, "
|
||||
"title_id={:016X}, user_id={:016X}{:016X}, full_display={}",
|
||||
title_id, value[1], value[0],
|
||||
full_display.has_value() ? fmt::format("{}", *full_display) : "null");
|
||||
finished();
|
||||
}
|
||||
|
||||
void DefaultECommerceApplet::ShowSubscriptionList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id) {
|
||||
const auto value = user_id.value_or(u128{});
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend show subscription list for EShop, title_id={:016X}, "
|
||||
"user_id={:016X}{:016X}",
|
||||
title_id, value[1], value[0]);
|
||||
finished();
|
||||
}
|
||||
|
||||
void DefaultECommerceApplet::ShowConsumableItemList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id) {
|
||||
const auto value = user_id.value_or(u128{});
|
||||
LOG_INFO(
|
||||
Service_AM,
|
||||
"Application requested frontend show consumable item list for EShop, title_id={:016X}, "
|
||||
"user_id={:016X}{:016X}",
|
||||
title_id, value[1], value[0]);
|
||||
finished();
|
||||
}
|
||||
|
||||
void DefaultECommerceApplet::ShowShopHome(std::function<void()> finished, u128 user_id,
|
||||
bool full_display) {
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend show home menu for EShop, user_id={:016X}{:016X}, "
|
||||
"full_display={}",
|
||||
user_id[1], user_id[0], full_display);
|
||||
finished();
|
||||
}
|
||||
|
||||
void DefaultECommerceApplet::ShowSettings(std::function<void()> finished, u128 user_id,
|
||||
bool full_display) {
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend show settings menu for EShop, user_id={:016X}{:016X}, "
|
||||
"full_display={}",
|
||||
user_id[1], user_id[0], full_display);
|
||||
finished();
|
||||
}
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -5,10 +5,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
class ParentalControlsApplet {
|
||||
public:
|
||||
virtual ~ParentalControlsApplet();
|
||||
|
||||
// Prompts the user to enter a PIN and calls the callback with whether or not it matches the
|
||||
// correct PIN. If the bool is passed, and the PIN was recently entered correctly, the frontend
|
||||
// should not prompt and simply return true.
|
||||
virtual void VerifyPIN(std::function<void(bool)> finished,
|
||||
bool suspend_future_verification_temporarily) = 0;
|
||||
|
||||
// Prompts the user to enter a PIN and calls the callback for correctness. Frontends can
|
||||
// optionally alert the user that this is to change parental controls settings.
|
||||
virtual void VerifyPINForSettings(std::function<void(bool)> finished) = 0;
|
||||
|
||||
// Prompts the user to create a new PIN for pctl and stores it with the service.
|
||||
virtual void RegisterPIN(std::function<void()> finished) = 0;
|
||||
|
||||
// Prompts the user to verify the current PIN and then store a new one into pctl.
|
||||
virtual void ChangePIN(std::function<void()> finished) = 0;
|
||||
};
|
||||
|
||||
class DefaultParentalControlsApplet final : public ParentalControlsApplet {
|
||||
public:
|
||||
~DefaultParentalControlsApplet() override;
|
||||
|
||||
void VerifyPIN(std::function<void(bool)> finished,
|
||||
bool suspend_future_verification_temporarily) override;
|
||||
void VerifyPINForSettings(std::function<void(bool)> finished) override;
|
||||
void RegisterPIN(std::function<void()> finished) override;
|
||||
void ChangePIN(std::function<void()> finished) override;
|
||||
};
|
||||
|
||||
class PhotoViewerApplet {
|
||||
public:
|
||||
virtual ~PhotoViewerApplet();
|
||||
@@ -25,4 +58,55 @@ public:
|
||||
void ShowAllPhotos(std::function<void()> finished) const override;
|
||||
};
|
||||
|
||||
class ECommerceApplet {
|
||||
public:
|
||||
virtual ~ECommerceApplet();
|
||||
|
||||
// Shows a page with application icons, description, name, and price.
|
||||
virtual void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id = {},
|
||||
std::optional<bool> full_display = {},
|
||||
std::optional<std::string> extra_parameter = {}) = 0;
|
||||
|
||||
// Shows a page with all of the add on content available for a game, with name, description, and
|
||||
// price.
|
||||
virtual void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id = {},
|
||||
std::optional<bool> full_display = {}) = 0;
|
||||
|
||||
// Shows a page with all of the subscriptions (recurring payments) for a game, with name,
|
||||
// description, price, and renewal period.
|
||||
virtual void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id = {}) = 0;
|
||||
|
||||
// Shows a page with a list of any additional game related purchasable items (DLC,
|
||||
// subscriptions, etc) for a particular game, with name, description, type, and price.
|
||||
virtual void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id = {}) = 0;
|
||||
|
||||
// Shows the home page of the shop.
|
||||
virtual void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) = 0;
|
||||
|
||||
// Shows the user settings page of the shop.
|
||||
virtual void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) = 0;
|
||||
};
|
||||
|
||||
class DefaultECommerceApplet : public ECommerceApplet {
|
||||
public:
|
||||
~DefaultECommerceApplet() override;
|
||||
|
||||
void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id, std::optional<bool> full_display,
|
||||
std::optional<std::string> extra_parameter) override;
|
||||
void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id,
|
||||
std::optional<bool> full_display) override;
|
||||
void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id) override;
|
||||
void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id) override;
|
||||
void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) override;
|
||||
void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) override;
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -11,9 +11,9 @@ WebBrowserApplet::~WebBrowserApplet() = default;
|
||||
|
||||
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
|
||||
|
||||
void DefaultWebBrowserApplet::OpenPage(std::string_view filename,
|
||||
std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) {
|
||||
void DefaultWebBrowserApplet::OpenPageLocal(std::string_view filename,
|
||||
std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) {
|
||||
LOG_INFO(Service_AM,
|
||||
"(STUBBED) called - No suitable web browser implementation found to open website page "
|
||||
"at '{}'!",
|
||||
|
||||
@@ -13,16 +13,16 @@ class WebBrowserApplet {
|
||||
public:
|
||||
virtual ~WebBrowserApplet();
|
||||
|
||||
virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) = 0;
|
||||
virtual void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) = 0;
|
||||
};
|
||||
|
||||
class DefaultWebBrowserApplet final : public WebBrowserApplet {
|
||||
public:
|
||||
~DefaultWebBrowserApplet() override;
|
||||
|
||||
void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) override;
|
||||
void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) override;
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include "common/alignment.h"
|
||||
@@ -48,8 +49,58 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority) {
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
SharedPtr<Process> Process::Create(Core::System& system, std::string name,
|
||||
Process::ProcessType type) {
|
||||
// Represents a page used for thread-local storage.
|
||||
//
|
||||
// Each TLS page contains slots that may be used by processes and threads.
|
||||
// Every process and thread is created with a slot in some arbitrary page
|
||||
// (whichever page happens to have an available slot).
|
||||
class TLSPage {
|
||||
public:
|
||||
static constexpr std::size_t num_slot_entries = Memory::PAGE_SIZE / Memory::TLS_ENTRY_SIZE;
|
||||
|
||||
explicit TLSPage(VAddr address) : base_address{address} {}
|
||||
|
||||
bool HasAvailableSlots() const {
|
||||
return !is_slot_used.all();
|
||||
}
|
||||
|
||||
VAddr GetBaseAddress() const {
|
||||
return base_address;
|
||||
}
|
||||
|
||||
std::optional<VAddr> ReserveSlot() {
|
||||
for (std::size_t i = 0; i < is_slot_used.size(); i++) {
|
||||
if (is_slot_used[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
is_slot_used[i] = true;
|
||||
return base_address + (i * Memory::TLS_ENTRY_SIZE);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void ReleaseSlot(VAddr address) {
|
||||
// Ensure that all given addresses are consistent with how TLS pages
|
||||
// are intended to be used when releasing slots.
|
||||
ASSERT(IsWithinPage(address));
|
||||
ASSERT((address % Memory::TLS_ENTRY_SIZE) == 0);
|
||||
|
||||
const std::size_t index = (address - base_address) / Memory::TLS_ENTRY_SIZE;
|
||||
is_slot_used[index] = false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsWithinPage(VAddr address) const {
|
||||
return base_address <= address && address < base_address + Memory::PAGE_SIZE;
|
||||
}
|
||||
|
||||
VAddr base_address;
|
||||
std::bitset<num_slot_entries> is_slot_used;
|
||||
};
|
||||
|
||||
SharedPtr<Process> Process::Create(Core::System& system, std::string name, ProcessType type) {
|
||||
auto& kernel = system.Kernel();
|
||||
|
||||
SharedPtr<Process> process(new Process(system));
|
||||
@@ -181,61 +232,55 @@ void Process::PrepareForTermination() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a free location for the TLS section of a thread.
|
||||
* @param tls_slots The TLS page array of the thread's owner process.
|
||||
* Returns a tuple of (page, slot, alloc_needed) where:
|
||||
* page: The index of the first allocated TLS page that has free slots.
|
||||
* slot: The index of the first free slot in the indicated page.
|
||||
* alloc_needed: Whether there's a need to allocate a new TLS page (All pages are full).
|
||||
* Attempts to find a TLS page that contains a free slot for
|
||||
* use by a thread.
|
||||
*
|
||||
* @returns If a page with an available slot is found, then an iterator
|
||||
* pointing to the page is returned. Otherwise the end iterator
|
||||
* is returned instead.
|
||||
*/
|
||||
static std::tuple<std::size_t, std::size_t, bool> FindFreeThreadLocalSlot(
|
||||
const std::vector<std::bitset<8>>& tls_slots) {
|
||||
// Iterate over all the allocated pages, and try to find one where not all slots are used.
|
||||
for (std::size_t page = 0; page < tls_slots.size(); ++page) {
|
||||
const auto& page_tls_slots = tls_slots[page];
|
||||
if (!page_tls_slots.all()) {
|
||||
// We found a page with at least one free slot, find which slot it is
|
||||
for (std::size_t slot = 0; slot < page_tls_slots.size(); ++slot) {
|
||||
if (!page_tls_slots.test(slot)) {
|
||||
return std::make_tuple(page, slot, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_tuple(0, 0, true);
|
||||
static auto FindTLSPageWithAvailableSlots(std::vector<TLSPage>& tls_pages) {
|
||||
return std::find_if(tls_pages.begin(), tls_pages.end(),
|
||||
[](const auto& page) { return page.HasAvailableSlots(); });
|
||||
}
|
||||
|
||||
VAddr Process::MarkNextAvailableTLSSlotAsUsed(Thread& thread) {
|
||||
auto [available_page, available_slot, needs_allocation] = FindFreeThreadLocalSlot(tls_slots);
|
||||
const VAddr tls_begin = vm_manager.GetTLSIORegionBaseAddress();
|
||||
VAddr Process::CreateTLSRegion() {
|
||||
auto tls_page_iter = FindTLSPageWithAvailableSlots(tls_pages);
|
||||
|
||||
if (needs_allocation) {
|
||||
tls_slots.emplace_back(0); // The page is completely available at the start
|
||||
available_page = tls_slots.size() - 1;
|
||||
available_slot = 0; // Use the first slot in the new page
|
||||
if (tls_page_iter == tls_pages.cend()) {
|
||||
const auto region_address =
|
||||
vm_manager.FindFreeRegion(vm_manager.GetTLSIORegionBaseAddress(),
|
||||
vm_manager.GetTLSIORegionEndAddress(), Memory::PAGE_SIZE);
|
||||
ASSERT(region_address.Succeeded());
|
||||
|
||||
// Allocate some memory from the end of the linear heap for this region.
|
||||
auto& tls_memory = thread.GetTLSMemory();
|
||||
tls_memory->insert(tls_memory->end(), Memory::PAGE_SIZE, 0);
|
||||
const auto map_result = vm_manager.MapMemoryBlock(
|
||||
*region_address, std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE), 0,
|
||||
Memory::PAGE_SIZE, MemoryState::ThreadLocal);
|
||||
ASSERT(map_result.Succeeded());
|
||||
|
||||
vm_manager.RefreshMemoryBlockMappings(tls_memory.get());
|
||||
tls_pages.emplace_back(*region_address);
|
||||
|
||||
vm_manager.MapMemoryBlock(tls_begin + available_page * Memory::PAGE_SIZE, tls_memory, 0,
|
||||
Memory::PAGE_SIZE, MemoryState::ThreadLocal);
|
||||
const auto reserve_result = tls_pages.back().ReserveSlot();
|
||||
ASSERT(reserve_result.has_value());
|
||||
|
||||
return *reserve_result;
|
||||
}
|
||||
|
||||
tls_slots[available_page].set(available_slot);
|
||||
|
||||
return tls_begin + available_page * Memory::PAGE_SIZE + available_slot * Memory::TLS_ENTRY_SIZE;
|
||||
return *tls_page_iter->ReserveSlot();
|
||||
}
|
||||
|
||||
void Process::FreeTLSSlot(VAddr tls_address) {
|
||||
const VAddr tls_base = tls_address - vm_manager.GetTLSIORegionBaseAddress();
|
||||
const VAddr tls_page = tls_base / Memory::PAGE_SIZE;
|
||||
const VAddr tls_slot = (tls_base % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
|
||||
void Process::FreeTLSRegion(VAddr tls_address) {
|
||||
const VAddr aligned_address = Common::AlignDown(tls_address, Memory::PAGE_SIZE);
|
||||
auto iter =
|
||||
std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) {
|
||||
return page.GetBaseAddress() == aligned_address;
|
||||
});
|
||||
|
||||
tls_slots[tls_page].reset(tls_slot);
|
||||
// Something has gone very wrong if we're freeing a region
|
||||
// with no actual page available.
|
||||
ASSERT(iter != tls_pages.cend());
|
||||
|
||||
iter->ReleaseSlot(tls_address);
|
||||
}
|
||||
|
||||
void Process::LoadModule(CodeSet module_, VAddr base_addr) {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <cstddef>
|
||||
#include <list>
|
||||
#include <string>
|
||||
@@ -32,6 +31,7 @@ namespace Kernel {
|
||||
class KernelCore;
|
||||
class ResourceLimit;
|
||||
class Thread;
|
||||
class TLSPage;
|
||||
|
||||
struct CodeSet;
|
||||
|
||||
@@ -260,10 +260,10 @@ public:
|
||||
// Thread-local storage management
|
||||
|
||||
// Marks the next available region as used and returns the address of the slot.
|
||||
VAddr MarkNextAvailableTLSSlotAsUsed(Thread& thread);
|
||||
[[nodiscard]] VAddr CreateTLSRegion();
|
||||
|
||||
// Frees a used TLS slot identified by the given address
|
||||
void FreeTLSSlot(VAddr tls_address);
|
||||
void FreeTLSRegion(VAddr tls_address);
|
||||
|
||||
private:
|
||||
explicit Process(Core::System& system);
|
||||
@@ -290,7 +290,7 @@ private:
|
||||
u64 code_memory_size = 0;
|
||||
|
||||
/// Current status of the process
|
||||
ProcessStatus status;
|
||||
ProcessStatus status{};
|
||||
|
||||
/// The ID of this process
|
||||
u64 process_id = 0;
|
||||
@@ -310,7 +310,7 @@ private:
|
||||
/// holds the TLS for a specific thread. This vector contains which parts are in use for each
|
||||
/// page as a bitmask.
|
||||
/// This vector will grow as more pages are allocated for new threads.
|
||||
std::vector<std::bitset<8>> tls_slots;
|
||||
std::vector<TLSPage> tls_pages;
|
||||
|
||||
/// Contains the parsed process capability descriptors.
|
||||
ProcessCapabilities capabilities;
|
||||
@@ -339,7 +339,7 @@ private:
|
||||
Mutex mutex;
|
||||
|
||||
/// Random values for svcGetInfo RandomEntropy
|
||||
std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy;
|
||||
std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy{};
|
||||
|
||||
/// List of threads that are running with this process as their owner.
|
||||
std::list<const Thread*> thread_list;
|
||||
|
||||
@@ -98,9 +98,9 @@ ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_add
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!vm_manager.IsWithinNewMapRegion(dst_addr, size)) {
|
||||
if (!vm_manager.IsWithinStackRegion(dst_addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Destination is not within the new map region, addr=0x{:016X}, size=0x{:016X}",
|
||||
"Destination is not within the stack region, addr=0x{:016X}, size=0x{:016X}",
|
||||
dst_addr, size);
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
@@ -726,8 +726,8 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
|
||||
// 2.0.0+
|
||||
ASLRRegionBaseAddr = 12,
|
||||
ASLRRegionSize = 13,
|
||||
NewMapRegionBaseAddr = 14,
|
||||
NewMapRegionSize = 15,
|
||||
StackRegionBaseAddr = 14,
|
||||
StackRegionSize = 15,
|
||||
// 3.0.0+
|
||||
IsVirtualAddressMemoryEnabled = 16,
|
||||
PersonalMmHeapUsage = 17,
|
||||
@@ -752,8 +752,8 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
|
||||
case GetInfoType::HeapRegionSize:
|
||||
case GetInfoType::ASLRRegionBaseAddr:
|
||||
case GetInfoType::ASLRRegionSize:
|
||||
case GetInfoType::NewMapRegionBaseAddr:
|
||||
case GetInfoType::NewMapRegionSize:
|
||||
case GetInfoType::StackRegionBaseAddr:
|
||||
case GetInfoType::StackRegionSize:
|
||||
case GetInfoType::TotalPhysicalMemoryAvailable:
|
||||
case GetInfoType::TotalPhysicalMemoryUsed:
|
||||
case GetInfoType::IsVirtualAddressMemoryEnabled:
|
||||
@@ -806,12 +806,12 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
|
||||
*result = process->VMManager().GetASLRRegionSize();
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
case GetInfoType::NewMapRegionBaseAddr:
|
||||
*result = process->VMManager().GetNewMapRegionBaseAddress();
|
||||
case GetInfoType::StackRegionBaseAddr:
|
||||
*result = process->VMManager().GetStackRegionBaseAddress();
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
case GetInfoType::NewMapRegionSize:
|
||||
*result = process->VMManager().GetNewMapRegionSize();
|
||||
case GetInfoType::StackRegionSize:
|
||||
*result = process->VMManager().GetStackRegionSize();
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
case GetInfoType::TotalPhysicalMemoryAvailable:
|
||||
|
||||
@@ -65,7 +65,7 @@ void Thread::Stop() {
|
||||
owner_process->UnregisterThread(this);
|
||||
|
||||
// Mark the TLS slot in the thread's page as free.
|
||||
owner_process->FreeTLSSlot(tls_address);
|
||||
owner_process->FreeTLSRegion(tls_address);
|
||||
}
|
||||
|
||||
void Thread::WakeAfterDelay(s64 nanoseconds) {
|
||||
@@ -76,13 +76,13 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
|
||||
// This function might be called from any thread so we have to be cautious and use the
|
||||
// thread-safe version of ScheduleEvent.
|
||||
const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds});
|
||||
Core::System::GetInstance().CoreTiming().ScheduleEventThreadsafe(
|
||||
Core::System::GetInstance().CoreTiming().ScheduleEvent(
|
||||
cycles, kernel.ThreadWakeupCallbackEventType(), callback_handle);
|
||||
}
|
||||
|
||||
void Thread::CancelWakeupTimer() {
|
||||
Core::System::GetInstance().CoreTiming().UnscheduleEventThreadsafe(
|
||||
kernel.ThreadWakeupCallbackEventType(), callback_handle);
|
||||
Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(),
|
||||
callback_handle);
|
||||
}
|
||||
|
||||
static std::optional<s32> GetNextProcessorId(u64 mask) {
|
||||
@@ -205,9 +205,9 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
thread->name = std::move(name);
|
||||
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
|
||||
thread->owner_process = &owner_process;
|
||||
thread->tls_address = thread->owner_process->CreateTLSRegion();
|
||||
thread->scheduler = &system.Scheduler(processor_id);
|
||||
thread->scheduler->AddThread(thread);
|
||||
thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
|
||||
|
||||
thread->owner_process->RegisterThread(thread.get());
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -78,9 +77,6 @@ enum class ThreadActivity : u32 {
|
||||
|
||||
class Thread final : public WaitObject {
|
||||
public:
|
||||
using TLSMemory = std::vector<u8>;
|
||||
using TLSMemoryPtr = std::shared_ptr<TLSMemory>;
|
||||
|
||||
using MutexWaitingThreads = std::vector<SharedPtr<Thread>>;
|
||||
|
||||
using ThreadContext = Core::ARM_Interface::ThreadContext;
|
||||
@@ -169,14 +165,6 @@ public:
|
||||
return thread_id;
|
||||
}
|
||||
|
||||
TLSMemoryPtr& GetTLSMemory() {
|
||||
return tls_memory;
|
||||
}
|
||||
|
||||
const TLSMemoryPtr& GetTLSMemory() const {
|
||||
return tls_memory;
|
||||
}
|
||||
|
||||
/// Resumes a thread from waiting
|
||||
void ResumeFromWait();
|
||||
|
||||
@@ -463,11 +451,9 @@ private:
|
||||
u32 ideal_core{0xFFFFFFFF};
|
||||
u64 affinity_mask{0x1};
|
||||
|
||||
TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>();
|
||||
ThreadActivity activity = ThreadActivity::Normal;
|
||||
|
||||
std::string name;
|
||||
|
||||
ThreadActivity activity = ThreadActivity::Normal;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -152,22 +152,33 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
|
||||
}
|
||||
|
||||
ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
|
||||
// Find the first Free VMA.
|
||||
const VAddr base = GetASLRRegionBaseAddress();
|
||||
const VMAHandle vma_handle = std::find_if(vma_map.begin(), vma_map.end(), [&](const auto& vma) {
|
||||
if (vma.second.type != VMAType::Free)
|
||||
return false;
|
||||
return FindFreeRegion(GetASLRRegionBaseAddress(), GetASLRRegionEndAddress(), size);
|
||||
}
|
||||
|
||||
const VAddr vma_end = vma.second.base + vma.second.size;
|
||||
return vma_end > base && vma_end >= base + size;
|
||||
});
|
||||
ResultVal<VAddr> VMManager::FindFreeRegion(VAddr begin, VAddr end, u64 size) const {
|
||||
ASSERT(begin < end);
|
||||
ASSERT(size <= end - begin);
|
||||
|
||||
if (vma_handle == vma_map.end()) {
|
||||
const VMAHandle vma_handle =
|
||||
std::find_if(vma_map.begin(), vma_map.end(), [begin, end, size](const auto& vma) {
|
||||
if (vma.second.type != VMAType::Free) {
|
||||
return false;
|
||||
}
|
||||
const VAddr vma_base = vma.second.base;
|
||||
const VAddr vma_end = vma_base + vma.second.size;
|
||||
const VAddr assumed_base = (begin < vma_base) ? vma_base : begin;
|
||||
const VAddr used_range = assumed_base + size;
|
||||
|
||||
return vma_base <= assumed_base && assumed_base < used_range && used_range < end &&
|
||||
used_range <= vma_end;
|
||||
});
|
||||
|
||||
if (vma_handle == vma_map.cend()) {
|
||||
// TODO(Subv): Find the correct error code here.
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
const VAddr target = std::max(base, vma_handle->second.base);
|
||||
const VAddr target = std::max(begin, vma_handle->second.base);
|
||||
return MakeResult<VAddr>(target);
|
||||
}
|
||||
|
||||
@@ -614,9 +625,11 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
|
||||
void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType type) {
|
||||
u64 map_region_size = 0;
|
||||
u64 heap_region_size = 0;
|
||||
u64 new_map_region_size = 0;
|
||||
u64 stack_region_size = 0;
|
||||
u64 tls_io_region_size = 0;
|
||||
|
||||
u64 stack_and_tls_io_end = 0;
|
||||
|
||||
switch (type) {
|
||||
case FileSys::ProgramAddressSpaceType::Is32Bit:
|
||||
case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
|
||||
@@ -632,6 +645,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
|
||||
map_region_size = 0;
|
||||
heap_region_size = 0x80000000;
|
||||
}
|
||||
stack_and_tls_io_end = 0x40000000;
|
||||
break;
|
||||
case FileSys::ProgramAddressSpaceType::Is36Bit:
|
||||
address_space_width = 36;
|
||||
@@ -641,6 +655,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
|
||||
aslr_region_end = aslr_region_base + 0xFF8000000;
|
||||
map_region_size = 0x180000000;
|
||||
heap_region_size = 0x180000000;
|
||||
stack_and_tls_io_end = 0x80000000;
|
||||
break;
|
||||
case FileSys::ProgramAddressSpaceType::Is39Bit:
|
||||
address_space_width = 39;
|
||||
@@ -650,7 +665,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
|
||||
aslr_region_end = aslr_region_base + 0x7FF8000000;
|
||||
map_region_size = 0x1000000000;
|
||||
heap_region_size = 0x180000000;
|
||||
new_map_region_size = 0x80000000;
|
||||
stack_region_size = 0x80000000;
|
||||
tls_io_region_size = 0x1000000000;
|
||||
break;
|
||||
default:
|
||||
@@ -658,6 +673,8 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 stack_and_tls_io_begin = aslr_region_base;
|
||||
|
||||
address_space_base = 0;
|
||||
address_space_end = 1ULL << address_space_width;
|
||||
|
||||
@@ -668,15 +685,20 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
|
||||
heap_region_end = heap_region_base + heap_region_size;
|
||||
heap_end = heap_region_base;
|
||||
|
||||
new_map_region_base = heap_region_end;
|
||||
new_map_region_end = new_map_region_base + new_map_region_size;
|
||||
stack_region_base = heap_region_end;
|
||||
stack_region_end = stack_region_base + stack_region_size;
|
||||
|
||||
tls_io_region_base = new_map_region_end;
|
||||
tls_io_region_base = stack_region_end;
|
||||
tls_io_region_end = tls_io_region_base + tls_io_region_size;
|
||||
|
||||
if (new_map_region_size == 0) {
|
||||
new_map_region_base = address_space_base;
|
||||
new_map_region_end = address_space_end;
|
||||
if (stack_region_size == 0) {
|
||||
stack_region_base = stack_and_tls_io_begin;
|
||||
stack_region_end = stack_and_tls_io_end;
|
||||
}
|
||||
|
||||
if (tls_io_region_size == 0) {
|
||||
tls_io_region_base = stack_and_tls_io_begin;
|
||||
tls_io_region_end = stack_and_tls_io_end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,21 +890,21 @@ bool VMManager::IsWithinMapRegion(VAddr address, u64 size) const {
|
||||
return IsInsideAddressRange(address, size, GetMapRegionBaseAddress(), GetMapRegionEndAddress());
|
||||
}
|
||||
|
||||
VAddr VMManager::GetNewMapRegionBaseAddress() const {
|
||||
return new_map_region_base;
|
||||
VAddr VMManager::GetStackRegionBaseAddress() const {
|
||||
return stack_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetNewMapRegionEndAddress() const {
|
||||
return new_map_region_end;
|
||||
VAddr VMManager::GetStackRegionEndAddress() const {
|
||||
return stack_region_end;
|
||||
}
|
||||
|
||||
u64 VMManager::GetNewMapRegionSize() const {
|
||||
return new_map_region_end - new_map_region_base;
|
||||
u64 VMManager::GetStackRegionSize() const {
|
||||
return stack_region_end - stack_region_base;
|
||||
}
|
||||
|
||||
bool VMManager::IsWithinNewMapRegion(VAddr address, u64 size) const {
|
||||
return IsInsideAddressRange(address, size, GetNewMapRegionBaseAddress(),
|
||||
GetNewMapRegionEndAddress());
|
||||
bool VMManager::IsWithinStackRegion(VAddr address, u64 size) const {
|
||||
return IsInsideAddressRange(address, size, GetStackRegionBaseAddress(),
|
||||
GetStackRegionEndAddress());
|
||||
}
|
||||
|
||||
VAddr VMManager::GetTLSIORegionBaseAddress() const {
|
||||
|
||||
@@ -362,13 +362,38 @@ public:
|
||||
ResultVal<VMAHandle> MapBackingMemory(VAddr target, u8* memory, u64 size, MemoryState state);
|
||||
|
||||
/**
|
||||
* Finds the first free address that can hold a region of the desired size.
|
||||
* Finds the first free memory region of the given size within
|
||||
* the user-addressable ASLR memory region.
|
||||
*
|
||||
* @param size Size of the desired region.
|
||||
* @return The found free address.
|
||||
* @param size The size of the desired region in bytes.
|
||||
*
|
||||
* @returns If successful, the base address of the free region with
|
||||
* the given size.
|
||||
*/
|
||||
ResultVal<VAddr> FindFreeRegion(u64 size) const;
|
||||
|
||||
/**
|
||||
* Finds the first free address range that can hold a region of the desired size
|
||||
*
|
||||
* @param begin The starting address of the range.
|
||||
* This is treated as an inclusive beginning address.
|
||||
*
|
||||
* @param end The ending address of the range.
|
||||
* This is treated as an exclusive ending address.
|
||||
*
|
||||
* @param size The size of the free region to attempt to locate,
|
||||
* in bytes.
|
||||
*
|
||||
* @returns If successful, the base address of the free region with
|
||||
* the given size.
|
||||
*
|
||||
* @returns If unsuccessful, a result containing an error code.
|
||||
*
|
||||
* @pre The starting address must be less than the ending address.
|
||||
* @pre The size must not exceed the address range itself.
|
||||
*/
|
||||
ResultVal<VAddr> FindFreeRegion(VAddr begin, VAddr end, u64 size) const;
|
||||
|
||||
/**
|
||||
* Maps a memory-mapped IO region at a given address.
|
||||
*
|
||||
@@ -571,17 +596,17 @@ public:
|
||||
/// Determines whether or not the specified range is within the map region.
|
||||
bool IsWithinMapRegion(VAddr address, u64 size) const;
|
||||
|
||||
/// Gets the base address of the new map region.
|
||||
VAddr GetNewMapRegionBaseAddress() const;
|
||||
/// Gets the base address of the stack region.
|
||||
VAddr GetStackRegionBaseAddress() const;
|
||||
|
||||
/// Gets the end address of the new map region.
|
||||
VAddr GetNewMapRegionEndAddress() const;
|
||||
/// Gets the end address of the stack region.
|
||||
VAddr GetStackRegionEndAddress() const;
|
||||
|
||||
/// Gets the total size of the new map region in bytes.
|
||||
u64 GetNewMapRegionSize() const;
|
||||
/// Gets the total size of the stack region in bytes.
|
||||
u64 GetStackRegionSize() const;
|
||||
|
||||
/// Determines whether or not the given address range is within the new map region
|
||||
bool IsWithinNewMapRegion(VAddr address, u64 size) const;
|
||||
/// Determines whether or not the given address range is within the stack region
|
||||
bool IsWithinStackRegion(VAddr address, u64 size) const;
|
||||
|
||||
/// Gets the base address of the TLS IO region.
|
||||
VAddr GetTLSIORegionBaseAddress() const;
|
||||
@@ -701,8 +726,8 @@ private:
|
||||
VAddr map_region_base = 0;
|
||||
VAddr map_region_end = 0;
|
||||
|
||||
VAddr new_map_region_base = 0;
|
||||
VAddr new_map_region_end = 0;
|
||||
VAddr stack_region_base = 0;
|
||||
VAddr stack_region_end = 0;
|
||||
|
||||
VAddr tls_io_region_base = 0;
|
||||
VAddr tls_io_region_end = 0;
|
||||
|
||||
@@ -15,13 +15,18 @@
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/acc/acc.h"
|
||||
#include "core/hle/service/acc/acc_aa.h"
|
||||
#include "core/hle/service/acc/acc_su.h"
|
||||
#include "core/hle/service/acc/acc_u0.h"
|
||||
#include "core/hle/service/acc/acc_u1.h"
|
||||
#include "core/hle/service/acc/errors.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/hle/service/glue/arp.h"
|
||||
#include "core/hle/service/glue/manager.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Service::Account {
|
||||
@@ -217,10 +222,72 @@ void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestCon
|
||||
rb.Push(profile_manager->CanSystemRegisterUser());
|
||||
}
|
||||
|
||||
void Module::Interface::InitializeApplicationInfoOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_ACC, "(STUBBED) called");
|
||||
void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto pid = rp.Pop<u64>();
|
||||
|
||||
LOG_DEBUG(Service_ACC, "called, process_id={}", pid);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(InitializeApplicationInfoBase(pid));
|
||||
}
|
||||
|
||||
void Module::Interface::InitializeApplicationInfoRestricted(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto pid = rp.Pop<u64>();
|
||||
|
||||
LOG_WARNING(Service_ACC, "(Partial implementation) called, process_id={}", pid);
|
||||
|
||||
// TODO(ogniK): We require checking if the user actually owns the title and what not. As of
|
||||
// currently, we assume the user owns the title. InitializeApplicationInfoBase SHOULD be called
|
||||
// first then we do extra checks if the game is a digital copy.
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(InitializeApplicationInfoBase(pid));
|
||||
}
|
||||
|
||||
ResultCode Module::Interface::InitializeApplicationInfoBase(u64 process_id) {
|
||||
if (application_info) {
|
||||
LOG_ERROR(Service_ACC, "Application already initialized");
|
||||
return ERR_ACCOUNTINFO_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
const auto& list = system.Kernel().GetProcessList();
|
||||
const auto iter = std::find_if(list.begin(), list.end(), [&process_id](const auto& process) {
|
||||
return process->GetProcessID() == process_id;
|
||||
});
|
||||
|
||||
if (iter == list.end()) {
|
||||
LOG_ERROR(Service_ACC, "Failed to find process ID");
|
||||
application_info.application_type = ApplicationType::Unknown;
|
||||
|
||||
return ERR_ACCOUNTINFO_BAD_APPLICATION;
|
||||
}
|
||||
|
||||
const auto launch_property = system.GetARPManager().GetLaunchProperty((*iter)->GetTitleID());
|
||||
|
||||
if (launch_property.Failed()) {
|
||||
LOG_ERROR(Service_ACC, "Failed to get launch property");
|
||||
return ERR_ACCOUNTINFO_BAD_APPLICATION;
|
||||
}
|
||||
|
||||
switch (launch_property->base_game_storage_id) {
|
||||
case FileSys::StorageId::GameCard:
|
||||
application_info.application_type = ApplicationType::GameCard;
|
||||
break;
|
||||
case FileSys::StorageId::Host:
|
||||
case FileSys::StorageId::NandUser:
|
||||
case FileSys::StorageId::SdCard:
|
||||
application_info.application_type = ApplicationType::Digital;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Service_ACC, "Invalid game storage ID");
|
||||
return ERR_ACCOUNTINFO_BAD_APPLICATION;
|
||||
}
|
||||
|
||||
LOG_WARNING(Service_ACC, "ApplicationInfo init required");
|
||||
// TODO(ogniK): Actual initalization here
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/glue/manager.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::Account {
|
||||
@@ -25,12 +26,33 @@ public:
|
||||
void ListOpenUsers(Kernel::HLERequestContext& ctx);
|
||||
void GetLastOpenedUser(Kernel::HLERequestContext& ctx);
|
||||
void GetProfile(Kernel::HLERequestContext& ctx);
|
||||
void InitializeApplicationInfoOld(Kernel::HLERequestContext& ctx);
|
||||
void InitializeApplicationInfo(Kernel::HLERequestContext& ctx);
|
||||
void InitializeApplicationInfoRestricted(Kernel::HLERequestContext& ctx);
|
||||
void GetBaasAccountManagerForApplication(Kernel::HLERequestContext& ctx);
|
||||
void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx);
|
||||
void TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx);
|
||||
void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx);
|
||||
|
||||
private:
|
||||
ResultCode InitializeApplicationInfoBase(u64 process_id);
|
||||
|
||||
enum class ApplicationType : u32_le {
|
||||
GameCard = 0,
|
||||
Digital = 1,
|
||||
Unknown = 3,
|
||||
};
|
||||
|
||||
struct ApplicationInfo {
|
||||
Service::Glue::ApplicationLaunchProperty launch_property;
|
||||
ApplicationType application_type;
|
||||
|
||||
constexpr explicit operator bool() const {
|
||||
return launch_property.title_id != 0x0;
|
||||
}
|
||||
};
|
||||
|
||||
ApplicationInfo application_info{};
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Module> module;
|
||||
std::shared_ptr<ProfileManager> profile_manager;
|
||||
|
||||
@@ -22,7 +22,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
|
||||
{51, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"},
|
||||
{60, nullptr, "ListOpenContextStoredUsers"},
|
||||
{99, nullptr, "DebugActivateOpenContextRetention"},
|
||||
{100, &ACC_U0::InitializeApplicationInfoOld, "InitializeApplicationInfoOld"},
|
||||
{100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"},
|
||||
{101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"},
|
||||
{102, nullptr, "AuthenticateApplicationAsync"},
|
||||
{103, nullptr, "CheckNetworkServiceAvailabilityAsync"},
|
||||
@@ -31,7 +31,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
|
||||
{120, nullptr, "CreateGuestLoginRequest"},
|
||||
{130, nullptr, "LoadOpenContext"},
|
||||
{131, nullptr, "ListOpenContextStoredUsers"},
|
||||
{140, nullptr, "InitializeApplicationInfo"},
|
||||
{140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"},
|
||||
{141, nullptr, "ListQualifiedUsers"},
|
||||
{150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"},
|
||||
};
|
||||
|
||||
14
src/core/hle/service/acc/errors.h
Normal file
14
src/core/hle/service/acc/errors.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::Account {
|
||||
|
||||
constexpr ResultCode ERR_ACCOUNTINFO_BAD_APPLICATION{ErrorModule::Account, 22};
|
||||
constexpr ResultCode ERR_ACCOUNTINFO_ALREADY_INITIALIZED{ErrorModule::Account, 41};
|
||||
|
||||
} // namespace Service::Account
|
||||
@@ -270,7 +270,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
|
||||
{70, nullptr, "ReportMultimediaError"},
|
||||
{71, nullptr, "GetCurrentIlluminanceEx"},
|
||||
{80, nullptr, "SetWirelessPriorityMode"},
|
||||
{90, nullptr, "GetAccumulatedSuspendedTickValue"},
|
||||
{90, &ISelfController::GetAccumulatedSuspendedTickValue, "GetAccumulatedSuspendedTickValue"},
|
||||
{91, &ISelfController::GetAccumulatedSuspendedTickChangedEvent, "GetAccumulatedSuspendedTickChangedEvent"},
|
||||
{100, nullptr, "SetAlbumImageTakenNotificationEnabled"},
|
||||
{1000, nullptr, "GetDebugStorageChannel"},
|
||||
@@ -283,10 +283,14 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
|
||||
launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
|
||||
"ISelfController:LaunchableEvent");
|
||||
|
||||
// TODO(ogniK): Figure out where, when and why this event gets signalled
|
||||
// This event is created by AM on the first time GetAccumulatedSuspendedTickChangedEvent() is
|
||||
// called. Yuzu can just create it unconditionally, since it doesn't need to support multiple
|
||||
// ISelfControllers. The event is signaled on creation, and on transition from suspended -> not
|
||||
// suspended if the event has previously been created by a call to
|
||||
// GetAccumulatedSuspendedTickChangedEvent.
|
||||
accumulated_suspended_tick_changed_event = Kernel::WritableEvent::CreateEventPair(
|
||||
kernel, Kernel::ResetType::Manual, "ISelfController:AccumulatedSuspendedTickChangedEvent");
|
||||
accumulated_suspended_tick_changed_event.writable->Signal(); // Is signalled on creation
|
||||
accumulated_suspended_tick_changed_event.writable->Signal();
|
||||
}
|
||||
|
||||
ISelfController::~ISelfController() = default;
|
||||
@@ -449,11 +453,19 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c
|
||||
rb.Push<u32>(idle_time_detection_extension);
|
||||
}
|
||||
|
||||
void ISelfController::GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_AM, "called.");
|
||||
|
||||
// This command returns the total number of system ticks since ISelfController creation
|
||||
// where the game was suspended. Since Yuzu doesn't implement game suspension, this command
|
||||
// can just always return 0 ticks.
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u64>(0);
|
||||
}
|
||||
|
||||
void ISelfController::GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx) {
|
||||
// The implementation of this function is fine as is, the reason we're labelling it as stubbed
|
||||
// is because we're currently unsure when and where accumulated_suspended_tick_changed_event is
|
||||
// actually signalled for the time being.
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_AM, "called.");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -887,7 +899,9 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") {
|
||||
ILibraryAppletCreator::ILibraryAppletCreator(u64 current_process_title_id)
|
||||
: ServiceFramework("ILibraryAppletCreator"),
|
||||
current_process_title_id(current_process_title_id) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
|
||||
{1, nullptr, "TerminateAllLibraryApplets"},
|
||||
@@ -910,7 +924,7 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx)
|
||||
static_cast<u32>(applet_id), applet_mode);
|
||||
|
||||
const auto& applet_manager{Core::System::GetInstance().GetAppletManager()};
|
||||
const auto applet = applet_manager.GetApplet(applet_id);
|
||||
const auto applet = applet_manager.GetApplet(applet_id, current_process_title_id);
|
||||
|
||||
if (applet == nullptr) {
|
||||
LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id));
|
||||
@@ -1234,13 +1248,13 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger) {
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) {
|
||||
auto message_queue = std::make_shared<AppletMessageQueue>();
|
||||
message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on
|
||||
// game boot
|
||||
|
||||
std::make_shared<AppletAE>(nvflinger, message_queue)->InstallAsService(service_manager);
|
||||
std::make_shared<AppletOE>(nvflinger, message_queue)->InstallAsService(service_manager);
|
||||
std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
|
||||
std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
|
||||
std::make_shared<IdleSys>()->InstallAsService(service_manager);
|
||||
std::make_shared<OMM>()->InstallAsService(service_manager);
|
||||
std::make_shared<SPSM>()->InstallAsService(service_manager);
|
||||
|
||||
@@ -133,6 +133,7 @@ private:
|
||||
void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
|
||||
void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
|
||||
void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
|
||||
void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx);
|
||||
void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
@@ -201,13 +202,15 @@ private:
|
||||
|
||||
class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
|
||||
public:
|
||||
ILibraryAppletCreator();
|
||||
ILibraryAppletCreator(u64 current_process_title_id);
|
||||
~ILibraryAppletCreator() override;
|
||||
|
||||
private:
|
||||
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
|
||||
void CreateStorage(Kernel::HLERequestContext& ctx);
|
||||
void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx);
|
||||
|
||||
u64 current_process_title_id;
|
||||
};
|
||||
|
||||
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
|
||||
@@ -264,7 +267,7 @@ public:
|
||||
|
||||
/// Registers all AM services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system);
|
||||
|
||||
} // namespace AM
|
||||
} // namespace Service
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applet_ae.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
@@ -13,9 +14,10 @@ namespace Service::AM {
|
||||
class ILibraryAppletProxy final : public ServiceFramework<ILibraryAppletProxy> {
|
||||
public:
|
||||
explicit ILibraryAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue)
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue,
|
||||
Core::System& system)
|
||||
: ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)),
|
||||
msg_queue(std::move(msg_queue)) {
|
||||
msg_queue(std::move(msg_queue)), system(system) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
|
||||
@@ -96,7 +98,7 @@ private:
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ILibraryAppletCreator>();
|
||||
rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID());
|
||||
}
|
||||
|
||||
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
|
||||
@@ -109,14 +111,15 @@ private:
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
class ISystemAppletProxy final : public ServiceFramework<ISystemAppletProxy> {
|
||||
public:
|
||||
explicit ISystemAppletProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue)
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
|
||||
: ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)),
|
||||
msg_queue(std::move(msg_queue)) {
|
||||
msg_queue(std::move(msg_queue)), system(system) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"},
|
||||
@@ -191,7 +194,7 @@ private:
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ILibraryAppletCreator>();
|
||||
rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID());
|
||||
}
|
||||
|
||||
void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) {
|
||||
@@ -219,6 +222,7 @@ private:
|
||||
}
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
|
||||
@@ -226,7 +230,7 @@ void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ISystemAppletProxy>(nvflinger, msg_queue);
|
||||
rb.PushIpcInterface<ISystemAppletProxy>(nvflinger, msg_queue, system);
|
||||
}
|
||||
|
||||
void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
|
||||
@@ -234,7 +238,7 @@ void AppletAE::OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue);
|
||||
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system);
|
||||
}
|
||||
|
||||
void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
|
||||
@@ -242,13 +246,13 @@ void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue);
|
||||
rb.PushIpcInterface<ILibraryAppletProxy>(nvflinger, msg_queue, system);
|
||||
}
|
||||
|
||||
AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue)
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
|
||||
: ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)),
|
||||
msg_queue(std::move(msg_queue)) {
|
||||
msg_queue(std::move(msg_queue)), system(system) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"},
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace AM {
|
||||
class AppletAE final : public ServiceFramework<AppletAE> {
|
||||
public:
|
||||
explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue);
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system);
|
||||
~AppletAE() override;
|
||||
|
||||
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
|
||||
@@ -30,6 +30,7 @@ private:
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
} // namespace AM
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applet_oe.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
@@ -13,9 +14,9 @@ namespace Service::AM {
|
||||
class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
|
||||
public:
|
||||
explicit IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue)
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
|
||||
: ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)),
|
||||
msg_queue(std::move(msg_queue)) {
|
||||
msg_queue(std::move(msg_queue)), system(system) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"},
|
||||
@@ -87,7 +88,7 @@ private:
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ILibraryAppletCreator>();
|
||||
rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID());
|
||||
}
|
||||
|
||||
void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
|
||||
@@ -100,6 +101,7 @@ private:
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
|
||||
@@ -107,13 +109,13 @@ void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue);
|
||||
rb.PushIpcInterface<IApplicationProxy>(nvflinger, msg_queue, system);
|
||||
}
|
||||
|
||||
AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue)
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system)
|
||||
: ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)),
|
||||
msg_queue(std::move(msg_queue)) {
|
||||
msg_queue(std::move(msg_queue)), system(system) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"},
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace AM {
|
||||
class AppletOE final : public ServiceFramework<AppletOE> {
|
||||
public:
|
||||
explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue);
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue, Core::System& system);
|
||||
~AppletOE() override;
|
||||
|
||||
const std::shared_ptr<AppletMessageQueue>& GetMessageQueue() const;
|
||||
@@ -28,6 +28,7 @@ private:
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
|
||||
std::shared_ptr<AppletMessageQueue> msg_queue;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
} // namespace AM
|
||||
|
||||
@@ -139,12 +139,14 @@ void Applet::Initialize() {
|
||||
|
||||
AppletFrontendSet::AppletFrontendSet() = default;
|
||||
|
||||
AppletFrontendSet::AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer,
|
||||
ProfileSelect profile_select,
|
||||
SoftwareKeyboard software_keyboard, WebBrowser web_browser)
|
||||
: error{std::move(error)}, photo_viewer{std::move(photo_viewer)}, profile_select{std::move(
|
||||
profile_select)},
|
||||
software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)} {}
|
||||
AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
|
||||
PhotoViewer photo_viewer, ProfileSelect profile_select,
|
||||
SoftwareKeyboard software_keyboard, WebBrowser web_browser,
|
||||
ECommerceApplet e_commerce)
|
||||
: parental_controls{std::move(parental_controls)}, error{std::move(error)},
|
||||
photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)},
|
||||
software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)},
|
||||
e_commerce{std::move(e_commerce)} {}
|
||||
|
||||
AppletFrontendSet::~AppletFrontendSet() = default;
|
||||
|
||||
@@ -157,6 +159,8 @@ AppletManager::AppletManager() = default;
|
||||
AppletManager::~AppletManager() = default;
|
||||
|
||||
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
|
||||
if (set.parental_controls != nullptr)
|
||||
frontend.parental_controls = std::move(set.parental_controls);
|
||||
if (set.error != nullptr)
|
||||
frontend.error = std::move(set.error);
|
||||
if (set.photo_viewer != nullptr)
|
||||
@@ -167,17 +171,21 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
|
||||
frontend.software_keyboard = std::move(set.software_keyboard);
|
||||
if (set.web_browser != nullptr)
|
||||
frontend.web_browser = std::move(set.web_browser);
|
||||
if (set.e_commerce != nullptr)
|
||||
frontend.e_commerce = std::move(set.e_commerce);
|
||||
}
|
||||
|
||||
void AppletManager::SetDefaultAppletFrontendSet() {
|
||||
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
|
||||
frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
|
||||
frontend.profile_select = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
|
||||
frontend.software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
|
||||
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
|
||||
ClearAll();
|
||||
SetDefaultAppletsIfMissing();
|
||||
}
|
||||
|
||||
void AppletManager::SetDefaultAppletsIfMissing() {
|
||||
if (frontend.parental_controls == nullptr) {
|
||||
frontend.parental_controls =
|
||||
std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
|
||||
}
|
||||
|
||||
if (frontend.error == nullptr) {
|
||||
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
|
||||
}
|
||||
@@ -198,14 +206,20 @@ void AppletManager::SetDefaultAppletsIfMissing() {
|
||||
if (frontend.web_browser == nullptr) {
|
||||
frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
|
||||
}
|
||||
|
||||
if (frontend.e_commerce == nullptr) {
|
||||
frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
|
||||
}
|
||||
}
|
||||
|
||||
void AppletManager::ClearAll() {
|
||||
frontend = {};
|
||||
}
|
||||
|
||||
std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
|
||||
std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, u64 current_process_title_id) const {
|
||||
switch (id) {
|
||||
case AppletId::Auth:
|
||||
return std::make_shared<Auth>(*frontend.parental_controls);
|
||||
case AppletId::Error:
|
||||
return std::make_shared<Error>(*frontend.error);
|
||||
case AppletId::ProfileSelect:
|
||||
@@ -214,8 +228,11 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
|
||||
return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard);
|
||||
case AppletId::PhotoViewer:
|
||||
return std::make_shared<PhotoViewer>(*frontend.photo_viewer);
|
||||
case AppletId::LibAppletShop:
|
||||
return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id,
|
||||
frontend.e_commerce.get());
|
||||
case AppletId::LibAppletOff:
|
||||
return std::make_shared<WebBrowser>(*frontend.web_browser);
|
||||
return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id);
|
||||
default:
|
||||
UNIMPLEMENTED_MSG(
|
||||
"No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
union ResultCode;
|
||||
|
||||
namespace Core::Frontend {
|
||||
class ECommerceApplet;
|
||||
class ErrorApplet;
|
||||
class ParentalControlsApplet;
|
||||
class PhotoViewerApplet;
|
||||
class ProfileSelectApplet;
|
||||
class SoftwareKeyboardApplet;
|
||||
@@ -145,15 +147,19 @@ protected:
|
||||
};
|
||||
|
||||
struct AppletFrontendSet {
|
||||
using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
|
||||
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
|
||||
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
|
||||
using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
|
||||
using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
|
||||
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
|
||||
using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
|
||||
|
||||
AppletFrontendSet();
|
||||
AppletFrontendSet(ErrorApplet error, PhotoViewer photo_viewer, ProfileSelect profile_select,
|
||||
SoftwareKeyboard software_keyboard, WebBrowser web_browser);
|
||||
AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error,
|
||||
PhotoViewer photo_viewer, ProfileSelect profile_select,
|
||||
SoftwareKeyboard software_keyboard, WebBrowser web_browser,
|
||||
ECommerceApplet e_commerce);
|
||||
~AppletFrontendSet();
|
||||
|
||||
AppletFrontendSet(const AppletFrontendSet&) = delete;
|
||||
@@ -162,11 +168,13 @@ struct AppletFrontendSet {
|
||||
AppletFrontendSet(AppletFrontendSet&&) noexcept;
|
||||
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
|
||||
|
||||
ParentalControlsApplet parental_controls;
|
||||
ErrorApplet error;
|
||||
PhotoViewer photo_viewer;
|
||||
ProfileSelect profile_select;
|
||||
SoftwareKeyboard software_keyboard;
|
||||
WebBrowser web_browser;
|
||||
ECommerceApplet e_commerce;
|
||||
};
|
||||
|
||||
class AppletManager {
|
||||
@@ -179,7 +187,7 @@ public:
|
||||
void SetDefaultAppletsIfMissing();
|
||||
void ClearAll();
|
||||
|
||||
std::shared_ptr<Applet> GetApplet(AppletId id) const;
|
||||
std::shared_ptr<Applet> GetApplet(AppletId id, u64 current_process_title_id) const;
|
||||
|
||||
private:
|
||||
AppletFrontendSet frontend;
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
constexpr ResultCode ERROR_INVALID_PIN{ErrorModule::PCTL, 221};
|
||||
|
||||
static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) {
|
||||
std::unique_ptr<IStorage> storage = broker.PopNormalDataToApplet();
|
||||
for (; storage != nullptr; storage = broker.PopNormalDataToApplet()) {
|
||||
@@ -35,6 +37,120 @@ static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix)
|
||||
}
|
||||
}
|
||||
|
||||
Auth::Auth(Core::Frontend::ParentalControlsApplet& frontend) : frontend(frontend) {}
|
||||
|
||||
Auth::~Auth() = default;
|
||||
|
||||
void Auth::Initialize() {
|
||||
Applet::Initialize();
|
||||
complete = false;
|
||||
|
||||
const auto storage = broker.PopNormalDataToApplet();
|
||||
ASSERT(storage != nullptr);
|
||||
const auto data = storage->GetData();
|
||||
ASSERT(data.size() >= 0xC);
|
||||
|
||||
struct Arg {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
AuthAppletType type;
|
||||
u8 arg0;
|
||||
u8 arg1;
|
||||
u8 arg2;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
};
|
||||
static_assert(sizeof(Arg) == 0xC, "Arg (AuthApplet) has incorrect size.");
|
||||
|
||||
Arg arg{};
|
||||
std::memcpy(&arg, data.data(), sizeof(Arg));
|
||||
|
||||
type = arg.type;
|
||||
arg0 = arg.arg0;
|
||||
arg1 = arg.arg1;
|
||||
arg2 = arg.arg2;
|
||||
}
|
||||
|
||||
bool Auth::TransactionComplete() const {
|
||||
return complete;
|
||||
}
|
||||
|
||||
ResultCode Auth::GetStatus() const {
|
||||
return successful ? RESULT_SUCCESS : ERROR_INVALID_PIN;
|
||||
}
|
||||
|
||||
void Auth::ExecuteInteractive() {
|
||||
UNREACHABLE_MSG("Unexpected interactive applet data.");
|
||||
}
|
||||
|
||||
void Auth::Execute() {
|
||||
if (complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto unimplemented_log = [this] {
|
||||
UNIMPLEMENTED_MSG("Unimplemented Auth applet type for type={:08X}, arg0={:02X}, "
|
||||
"arg1={:02X}, arg2={:02X}",
|
||||
static_cast<u32>(type), arg0, arg1, arg2);
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case AuthAppletType::ShowParentalAuthentication: {
|
||||
const auto callback = [this](bool successful) { AuthFinished(successful); };
|
||||
|
||||
if (arg0 == 1 && arg1 == 0 && arg2 == 1) {
|
||||
// ShowAuthenticatorForConfiguration
|
||||
frontend.VerifyPINForSettings(callback);
|
||||
} else if (arg1 == 0 && arg2 == 0) {
|
||||
// ShowParentalAuthentication(bool)
|
||||
frontend.VerifyPIN(callback, static_cast<bool>(arg0));
|
||||
} else {
|
||||
unimplemented_log();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AuthAppletType::RegisterParentalPasscode: {
|
||||
const auto callback = [this] { AuthFinished(true); };
|
||||
|
||||
if (arg0 == 0 && arg1 == 0 && arg2 == 0) {
|
||||
// RegisterParentalPasscode
|
||||
frontend.RegisterPIN(callback);
|
||||
} else {
|
||||
unimplemented_log();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AuthAppletType::ChangeParentalPasscode: {
|
||||
const auto callback = [this] { AuthFinished(true); };
|
||||
|
||||
if (arg0 == 0 && arg1 == 0 && arg2 == 0) {
|
||||
// ChangeParentalPasscode
|
||||
frontend.ChangePIN(callback);
|
||||
} else {
|
||||
unimplemented_log();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unimplemented_log();
|
||||
}
|
||||
}
|
||||
|
||||
void Auth::AuthFinished(bool successful) {
|
||||
this->successful = successful;
|
||||
|
||||
struct Return {
|
||||
ResultCode result_code;
|
||||
};
|
||||
static_assert(sizeof(Return) == 0x4, "Return (AuthApplet) has incorrect size.");
|
||||
|
||||
Return return_{GetStatus()};
|
||||
|
||||
std::vector<u8> out(sizeof(Return));
|
||||
std::memcpy(out.data(), &return_, sizeof(Return));
|
||||
|
||||
broker.PushNormalDataFromApplet(IStorage{out});
|
||||
broker.SignalStateChanged();
|
||||
}
|
||||
|
||||
PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {}
|
||||
|
||||
PhotoViewer::~PhotoViewer() = default;
|
||||
|
||||
@@ -8,6 +8,36 @@
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
enum class AuthAppletType : u32 {
|
||||
ShowParentalAuthentication,
|
||||
RegisterParentalPasscode,
|
||||
ChangeParentalPasscode,
|
||||
};
|
||||
|
||||
class Auth final : public Applet {
|
||||
public:
|
||||
explicit Auth(Core::Frontend::ParentalControlsApplet& frontend);
|
||||
~Auth() override;
|
||||
|
||||
void Initialize() override;
|
||||
bool TransactionComplete() const override;
|
||||
ResultCode GetStatus() const override;
|
||||
void ExecuteInteractive() override;
|
||||
void Execute() override;
|
||||
|
||||
void AuthFinished(bool successful = true);
|
||||
|
||||
private:
|
||||
Core::Frontend::ParentalControlsApplet& frontend;
|
||||
bool complete = false;
|
||||
bool successful = false;
|
||||
|
||||
AuthAppletType type = AuthAppletType::ShowParentalAuthentication;
|
||||
u8 arg0 = 0;
|
||||
u8 arg1 = 0;
|
||||
u8 arg2 = 0;
|
||||
};
|
||||
|
||||
enum class PhotoViewerAppletMode : u8 {
|
||||
CurrentApp = 0,
|
||||
AllApps = 1,
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/system_archive/system_archive.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/frontend/applets/general_frontend.h"
|
||||
#include "core/frontend/applets/web_browser.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/am/applets/web_browser.h"
|
||||
@@ -28,74 +30,187 @@
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
// TODO(DarkLordZach): There are other arguments in the WebBuffer structure that are currently not
|
||||
// parsed, for example footer mode and left stick mode. Some of these are not particularly relevant,
|
||||
// but some may be worth an implementation.
|
||||
constexpr u16 WEB_ARGUMENT_URL_TYPE = 0x6;
|
||||
|
||||
struct WebBufferHeader {
|
||||
u16 count;
|
||||
INSERT_PADDING_BYTES(6);
|
||||
enum class WebArgTLVType : u16 {
|
||||
InitialURL = 0x1,
|
||||
ShopArgumentsURL = 0x2, ///< TODO(DarkLordZach): This is not the official name.
|
||||
CallbackURL = 0x3,
|
||||
CallbackableURL = 0x4,
|
||||
ApplicationID = 0x5,
|
||||
DocumentPath = 0x6,
|
||||
DocumentKind = 0x7,
|
||||
SystemDataID = 0x8,
|
||||
ShareStartPage = 0x9,
|
||||
Whitelist = 0xA,
|
||||
News = 0xB,
|
||||
UserID = 0xE,
|
||||
AlbumEntry0 = 0xF,
|
||||
ScreenShotEnabled = 0x10,
|
||||
EcClientCertEnabled = 0x11,
|
||||
Unk12 = 0x12,
|
||||
PlayReportEnabled = 0x13,
|
||||
Unk14 = 0x14,
|
||||
Unk15 = 0x15,
|
||||
BootDisplayKind = 0x17,
|
||||
BackgroundKind = 0x18,
|
||||
FooterEnabled = 0x19,
|
||||
PointerEnabled = 0x1A,
|
||||
LeftStickMode = 0x1B,
|
||||
KeyRepeatFrame1 = 0x1C,
|
||||
KeyRepeatFrame2 = 0x1D,
|
||||
BootAsMediaPlayerInv = 0x1E,
|
||||
DisplayUrlKind = 0x1F,
|
||||
BootAsMediaPlayer = 0x21,
|
||||
ShopJumpEnabled = 0x22,
|
||||
MediaAutoPlayEnabled = 0x23,
|
||||
LobbyParameter = 0x24,
|
||||
ApplicationAlbumEntry = 0x26,
|
||||
JsExtensionEnabled = 0x27,
|
||||
AdditionalCommentText = 0x28,
|
||||
TouchEnabledOnContents = 0x29,
|
||||
UserAgentAdditionalString = 0x2A,
|
||||
AdditionalMediaData0 = 0x2B,
|
||||
MediaPlayerAutoCloseEnabled = 0x2C,
|
||||
PageCacheEnabled = 0x2D,
|
||||
WebAudioEnabled = 0x2E,
|
||||
Unk2F = 0x2F,
|
||||
YouTubeVideoWhitelist = 0x31,
|
||||
FooterFixedKind = 0x32,
|
||||
PageFadeEnabled = 0x33,
|
||||
MediaCreatorApplicationRatingAge = 0x34,
|
||||
BootLoadingIconEnabled = 0x35,
|
||||
PageScrollIndicationEnabled = 0x36,
|
||||
MediaPlayerSpeedControlEnabled = 0x37,
|
||||
AlbumEntry1 = 0x38,
|
||||
AlbumEntry2 = 0x39,
|
||||
AlbumEntry3 = 0x3A,
|
||||
AdditionalMediaData1 = 0x3B,
|
||||
AdditionalMediaData2 = 0x3C,
|
||||
AdditionalMediaData3 = 0x3D,
|
||||
BootFooterButton = 0x3E,
|
||||
OverrideWebAudioVolume = 0x3F,
|
||||
OverrideMediaAudioVolume = 0x40,
|
||||
BootMode = 0x41,
|
||||
WebSessionEnabled = 0x42,
|
||||
};
|
||||
static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size.");
|
||||
|
||||
struct WebArgumentHeader {
|
||||
u16 type;
|
||||
enum class ShimKind : u32 {
|
||||
Shop = 1,
|
||||
Login = 2,
|
||||
Offline = 3,
|
||||
Share = 4,
|
||||
Web = 5,
|
||||
Wifi = 6,
|
||||
Lobby = 7,
|
||||
};
|
||||
|
||||
enum class ShopWebTarget {
|
||||
ApplicationInfo,
|
||||
AddOnContentList,
|
||||
SubscriptionList,
|
||||
ConsumableItemList,
|
||||
Home,
|
||||
Settings,
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t SHIM_KIND_COUNT = 0x8;
|
||||
|
||||
struct WebArgHeader {
|
||||
u16 count;
|
||||
INSERT_PADDING_BYTES(2);
|
||||
ShimKind kind;
|
||||
};
|
||||
static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
|
||||
|
||||
struct WebArgTLV {
|
||||
WebArgTLVType type;
|
||||
u16 size;
|
||||
u32 offset;
|
||||
};
|
||||
static_assert(sizeof(WebArgumentHeader) == 0x8, "WebArgumentHeader has incorrect size.");
|
||||
static_assert(sizeof(WebArgTLV) == 0x8, "WebArgTLV has incorrect size.");
|
||||
|
||||
struct WebArgumentResult {
|
||||
struct WebCommonReturnValue {
|
||||
u32 result_code;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
std::array<char, 0x1000> last_url;
|
||||
u64 last_url_size;
|
||||
};
|
||||
static_assert(sizeof(WebArgumentResult) == 0x1010, "WebArgumentResult has incorrect size.");
|
||||
static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
|
||||
|
||||
static std::vector<u8> GetArgumentDataForTagType(const std::vector<u8>& data, u16 type) {
|
||||
WebBufferHeader header;
|
||||
ASSERT(sizeof(WebBufferHeader) <= data.size());
|
||||
std::memcpy(&header, data.data(), sizeof(WebBufferHeader));
|
||||
struct WebWifiPageArg {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
std::array<char, 0x100> connection_test_url;
|
||||
std::array<char, 0x400> initial_url;
|
||||
std::array<u8, 0x10> nifm_network_uuid;
|
||||
u32 nifm_requirement;
|
||||
};
|
||||
static_assert(sizeof(WebWifiPageArg) == 0x518, "WebWifiPageArg has incorrect size.");
|
||||
|
||||
u64 offset = sizeof(WebBufferHeader);
|
||||
for (u16 i = 0; i < header.count; ++i) {
|
||||
WebArgumentHeader arg;
|
||||
ASSERT(offset + sizeof(WebArgumentHeader) <= data.size());
|
||||
std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader));
|
||||
offset += sizeof(WebArgumentHeader);
|
||||
struct WebWifiReturnValue {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u32 result;
|
||||
};
|
||||
static_assert(sizeof(WebWifiReturnValue) == 0x8, "WebWifiReturnValue has incorrect size.");
|
||||
|
||||
if (arg.type == type) {
|
||||
std::vector<u8> out(arg.size);
|
||||
offset += arg.offset;
|
||||
ASSERT(offset + arg.size <= data.size());
|
||||
std::memcpy(out.data(), data.data() + offset, out.size());
|
||||
enum class OfflineWebSource : u32 {
|
||||
OfflineHtmlPage = 0x1,
|
||||
ApplicationLegalInformation = 0x2,
|
||||
SystemDataPage = 0x3,
|
||||
};
|
||||
|
||||
std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>& arg) {
|
||||
if (arg.size() < sizeof(WebArgHeader))
|
||||
return {};
|
||||
|
||||
WebArgHeader header{};
|
||||
std::memcpy(&header, arg.data(), sizeof(WebArgHeader));
|
||||
|
||||
std::map<WebArgTLVType, std::vector<u8>> out;
|
||||
u64 offset = sizeof(WebArgHeader);
|
||||
for (std::size_t i = 0; i < header.count; ++i) {
|
||||
if (arg.size() < (offset + sizeof(WebArgTLV)))
|
||||
return out;
|
||||
}
|
||||
|
||||
offset += arg.offset + arg.size;
|
||||
WebArgTLV tlv{};
|
||||
std::memcpy(&tlv, arg.data() + offset, sizeof(WebArgTLV));
|
||||
offset += sizeof(WebArgTLV);
|
||||
|
||||
offset += tlv.offset;
|
||||
if (arg.size() < (offset + tlv.size))
|
||||
return out;
|
||||
|
||||
std::vector<u8> data(tlv.size);
|
||||
std::memcpy(data.data(), arg.data() + offset, tlv.size);
|
||||
offset += tlv.size;
|
||||
|
||||
out.insert_or_assign(tlv.type, data);
|
||||
}
|
||||
|
||||
return {};
|
||||
return out;
|
||||
}
|
||||
|
||||
static FileSys::VirtualFile GetManualRomFS() {
|
||||
auto& loader{Core::System::GetInstance().GetAppLoader()};
|
||||
|
||||
FileSys::VirtualFile out;
|
||||
if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
|
||||
return out;
|
||||
|
||||
FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordType type) {
|
||||
const auto& installed{Core::System::GetInstance().GetContentProvider()};
|
||||
const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
|
||||
FileSys::ContentRecordType::Manual);
|
||||
const auto res = installed.GetEntry(title_id, type);
|
||||
|
||||
if (res != nullptr)
|
||||
if (res != nullptr) {
|
||||
return res->GetRomFS();
|
||||
}
|
||||
|
||||
if (type == FileSys::ContentRecordType::Data) {
|
||||
return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend) : frontend(frontend) {}
|
||||
} // Anonymous namespace
|
||||
|
||||
WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id,
|
||||
Core::Frontend::ECommerceApplet* frontend_e_commerce)
|
||||
: frontend(frontend), frontend_e_commerce(frontend_e_commerce),
|
||||
current_process_title_id(current_process_title_id) {}
|
||||
|
||||
WebBrowser::~WebBrowser() = default;
|
||||
|
||||
@@ -111,24 +226,12 @@ void WebBrowser::Initialize() {
|
||||
ASSERT(web_arg_storage != nullptr);
|
||||
const auto& web_arg = web_arg_storage->GetData();
|
||||
|
||||
const auto url_data = GetArgumentDataForTagType(web_arg, WEB_ARGUMENT_URL_TYPE);
|
||||
filename = Common::StringFromFixedZeroTerminatedBuffer(
|
||||
reinterpret_cast<const char*>(url_data.data()), url_data.size());
|
||||
ASSERT(web_arg.size() >= 0x8);
|
||||
std::memcpy(&kind, web_arg.data() + 0x4, sizeof(ShimKind));
|
||||
|
||||
temporary_dir = FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
|
||||
"web_applet_manual",
|
||||
FileUtil::DirectorySeparator::PlatformDefault);
|
||||
FileUtil::DeleteDirRecursively(temporary_dir);
|
||||
args = GetWebArguments(web_arg);
|
||||
|
||||
manual_romfs = GetManualRomFS();
|
||||
if (manual_romfs == nullptr) {
|
||||
status = ResultCode(-1);
|
||||
LOG_ERROR(Service_AM, "Failed to find manual for current process!");
|
||||
}
|
||||
|
||||
filename =
|
||||
FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename,
|
||||
FileUtil::DirectorySeparator::PlatformDefault);
|
||||
InitializeInternal();
|
||||
}
|
||||
|
||||
bool WebBrowser::TransactionComplete() const {
|
||||
@@ -144,24 +247,25 @@ void WebBrowser::ExecuteInteractive() {
|
||||
}
|
||||
|
||||
void WebBrowser::Execute() {
|
||||
if (complete)
|
||||
if (complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status != RESULT_SUCCESS) {
|
||||
complete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
|
||||
ExecuteInternal();
|
||||
}
|
||||
|
||||
void WebBrowser::UnpackRomFS() {
|
||||
if (unpacked)
|
||||
return;
|
||||
|
||||
ASSERT(manual_romfs != nullptr);
|
||||
ASSERT(offline_romfs != nullptr);
|
||||
const auto dir =
|
||||
FileSys::ExtractRomFS(manual_romfs, FileSys::RomFSExtractionType::SingleDiscard);
|
||||
FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
|
||||
const auto& vfs{Core::System::GetInstance().GetFilesystem()};
|
||||
const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
|
||||
FileSys::VfsRawCopyD(dir, temp_dir);
|
||||
@@ -172,17 +276,275 @@ void WebBrowser::UnpackRomFS() {
|
||||
void WebBrowser::Finalize() {
|
||||
complete = true;
|
||||
|
||||
WebArgumentResult out{};
|
||||
WebCommonReturnValue out{};
|
||||
out.result_code = 0;
|
||||
out.last_url_size = 0;
|
||||
|
||||
std::vector<u8> data(sizeof(WebArgumentResult));
|
||||
std::memcpy(data.data(), &out, sizeof(WebArgumentResult));
|
||||
std::vector<u8> data(sizeof(WebCommonReturnValue));
|
||||
std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue));
|
||||
|
||||
broker.PushNormalDataFromApplet(IStorage{data});
|
||||
broker.SignalStateChanged();
|
||||
|
||||
if (!temporary_dir.empty() && FileUtil::IsDirectory(temporary_dir)) {
|
||||
FileUtil::DeleteDirRecursively(temporary_dir);
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowser::InitializeInternal() {
|
||||
using WebAppletInitializer = void (WebBrowser::*)();
|
||||
|
||||
constexpr std::array<WebAppletInitializer, SHIM_KIND_COUNT> functions{
|
||||
nullptr, &WebBrowser::InitializeShop,
|
||||
nullptr, &WebBrowser::InitializeOffline,
|
||||
nullptr, nullptr,
|
||||
nullptr, nullptr,
|
||||
};
|
||||
|
||||
const auto index = static_cast<u32>(kind);
|
||||
|
||||
if (index > functions.size() || functions[index] == nullptr) {
|
||||
LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto function = functions[index];
|
||||
(this->*function)();
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteInternal() {
|
||||
using WebAppletExecutor = void (WebBrowser::*)();
|
||||
|
||||
constexpr std::array<WebAppletExecutor, SHIM_KIND_COUNT> functions{
|
||||
nullptr, &WebBrowser::ExecuteShop,
|
||||
nullptr, &WebBrowser::ExecuteOffline,
|
||||
nullptr, nullptr,
|
||||
nullptr, nullptr,
|
||||
};
|
||||
|
||||
const auto index = static_cast<u32>(kind);
|
||||
|
||||
if (index > functions.size() || functions[index] == nullptr) {
|
||||
LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto function = functions[index];
|
||||
(this->*function)();
|
||||
}
|
||||
|
||||
void WebBrowser::InitializeShop() {
|
||||
if (frontend_e_commerce == nullptr) {
|
||||
LOG_ERROR(Service_AM, "Missing ECommerce Applet frontend!");
|
||||
status = ResultCode(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto user_id_data = args.find(WebArgTLVType::UserID);
|
||||
|
||||
user_id = std::nullopt;
|
||||
if (user_id_data != args.end()) {
|
||||
user_id = u128{};
|
||||
std::memcpy(user_id->data(), user_id_data->second.data(), sizeof(u128));
|
||||
}
|
||||
|
||||
const auto url = args.find(WebArgTLVType::ShopArgumentsURL);
|
||||
|
||||
if (url == args.end()) {
|
||||
LOG_ERROR(Service_AM, "Missing EShop Arguments URL for initialization!");
|
||||
status = ResultCode(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> split_query;
|
||||
Common::SplitString(Common::StringFromFixedZeroTerminatedBuffer(
|
||||
reinterpret_cast<const char*>(url->second.data()), url->second.size()),
|
||||
'?', split_query);
|
||||
|
||||
// 2 -> Main URL '?' Query Parameters
|
||||
// Less is missing info, More is malformed
|
||||
if (split_query.size() != 2) {
|
||||
LOG_ERROR(Service_AM, "EShop Arguments has more than one question mark, malformed");
|
||||
status = ResultCode(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> queries;
|
||||
Common::SplitString(split_query[1], '&', queries);
|
||||
|
||||
const auto split_single_query =
|
||||
[](const std::string& in) -> std::pair<std::string, std::string> {
|
||||
const auto index = in.find('=');
|
||||
if (index == std::string::npos || index == in.size() - 1) {
|
||||
return {in, ""};
|
||||
}
|
||||
|
||||
return {in.substr(0, index), in.substr(index + 1)};
|
||||
};
|
||||
|
||||
std::transform(queries.begin(), queries.end(),
|
||||
std::inserter(shop_query, std::next(shop_query.begin())), split_single_query);
|
||||
|
||||
const auto scene = shop_query.find("scene");
|
||||
|
||||
if (scene == shop_query.end()) {
|
||||
LOG_ERROR(Service_AM, "No scene parameter was passed via shop query!");
|
||||
status = ResultCode(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::map<std::string, ShopWebTarget, std::less<>> target_map{
|
||||
{"product_detail", ShopWebTarget::ApplicationInfo},
|
||||
{"aocs", ShopWebTarget::AddOnContentList},
|
||||
{"subscriptions", ShopWebTarget::SubscriptionList},
|
||||
{"consumption", ShopWebTarget::ConsumableItemList},
|
||||
{"settings", ShopWebTarget::Settings},
|
||||
{"top", ShopWebTarget::Home},
|
||||
};
|
||||
|
||||
const auto target = target_map.find(scene->second);
|
||||
if (target == target_map.end()) {
|
||||
LOG_ERROR(Service_AM, "Scene for shop query is invalid! (scene={})", scene->second);
|
||||
status = ResultCode(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
shop_web_target = target->second;
|
||||
|
||||
const auto title_id_data = shop_query.find("dst_app_id");
|
||||
if (title_id_data != shop_query.end()) {
|
||||
title_id = std::stoull(title_id_data->second, nullptr, 0x10);
|
||||
}
|
||||
|
||||
const auto mode_data = shop_query.find("mode");
|
||||
if (mode_data != shop_query.end()) {
|
||||
shop_full_display = mode_data->second == "full";
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowser::InitializeOffline() {
|
||||
if (args.find(WebArgTLVType::DocumentPath) == args.end() ||
|
||||
args.find(WebArgTLVType::DocumentKind) == args.end() ||
|
||||
args.find(WebArgTLVType::ApplicationID) == args.end()) {
|
||||
status = ResultCode(-1);
|
||||
LOG_ERROR(Service_AM, "Missing necessary parameters for initialization!");
|
||||
}
|
||||
|
||||
const auto url_data = args[WebArgTLVType::DocumentPath];
|
||||
filename = Common::StringFromFixedZeroTerminatedBuffer(
|
||||
reinterpret_cast<const char*>(url_data.data()), url_data.size());
|
||||
|
||||
OfflineWebSource source;
|
||||
ASSERT(args[WebArgTLVType::DocumentKind].size() >= 4);
|
||||
std::memcpy(&source, args[WebArgTLVType::DocumentKind].data(), sizeof(OfflineWebSource));
|
||||
|
||||
constexpr std::array<const char*, 3> WEB_SOURCE_NAMES{
|
||||
"manual",
|
||||
"legal",
|
||||
"system",
|
||||
};
|
||||
|
||||
temporary_dir =
|
||||
FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + "web_applet_" +
|
||||
WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
|
||||
FileUtil::DirectorySeparator::PlatformDefault);
|
||||
FileUtil::DeleteDirRecursively(temporary_dir);
|
||||
|
||||
u64 title_id = 0; // 0 corresponds to current process
|
||||
ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8);
|
||||
std::memcpy(&title_id, args[WebArgTLVType::ApplicationID].data(), sizeof(u64));
|
||||
FileSys::ContentRecordType type = FileSys::ContentRecordType::Data;
|
||||
|
||||
switch (source) {
|
||||
case OfflineWebSource::OfflineHtmlPage:
|
||||
// While there is an AppID TLV field, in official SW this is always ignored.
|
||||
title_id = 0;
|
||||
type = FileSys::ContentRecordType::HtmlDocument;
|
||||
break;
|
||||
case OfflineWebSource::ApplicationLegalInformation:
|
||||
type = FileSys::ContentRecordType::LegalInformation;
|
||||
break;
|
||||
case OfflineWebSource::SystemDataPage:
|
||||
type = FileSys::ContentRecordType::Data;
|
||||
break;
|
||||
}
|
||||
|
||||
if (title_id == 0) {
|
||||
title_id = current_process_title_id;
|
||||
}
|
||||
|
||||
offline_romfs = GetApplicationRomFS(title_id, type);
|
||||
if (offline_romfs == nullptr) {
|
||||
status = ResultCode(-1);
|
||||
LOG_ERROR(Service_AM, "Failed to find offline data for request!");
|
||||
}
|
||||
|
||||
std::string path_additional_directory;
|
||||
if (source == OfflineWebSource::OfflineHtmlPage) {
|
||||
path_additional_directory = std::string(DIR_SEP).append("html-document");
|
||||
}
|
||||
|
||||
filename =
|
||||
FileUtil::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
|
||||
FileUtil::DirectorySeparator::PlatformDefault);
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteShop() {
|
||||
const auto callback = [this]() { Finalize(); };
|
||||
|
||||
const auto check_optional_parameter = [this](const auto& p) {
|
||||
if (!p.has_value()) {
|
||||
LOG_ERROR(Service_AM, "Missing one or more necessary parameters for execution!");
|
||||
status = ResultCode(-1);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
switch (shop_web_target) {
|
||||
case ShopWebTarget::ApplicationInfo:
|
||||
if (!check_optional_parameter(title_id))
|
||||
return;
|
||||
frontend_e_commerce->ShowApplicationInformation(callback, *title_id, user_id,
|
||||
shop_full_display, shop_extra_parameter);
|
||||
break;
|
||||
case ShopWebTarget::AddOnContentList:
|
||||
if (!check_optional_parameter(title_id))
|
||||
return;
|
||||
frontend_e_commerce->ShowAddOnContentList(callback, *title_id, user_id, shop_full_display);
|
||||
break;
|
||||
case ShopWebTarget::ConsumableItemList:
|
||||
if (!check_optional_parameter(title_id))
|
||||
return;
|
||||
frontend_e_commerce->ShowConsumableItemList(callback, *title_id, user_id);
|
||||
break;
|
||||
case ShopWebTarget::Home:
|
||||
if (!check_optional_parameter(user_id))
|
||||
return;
|
||||
if (!check_optional_parameter(shop_full_display))
|
||||
return;
|
||||
frontend_e_commerce->ShowShopHome(callback, *user_id, *shop_full_display);
|
||||
break;
|
||||
case ShopWebTarget::Settings:
|
||||
if (!check_optional_parameter(user_id))
|
||||
return;
|
||||
if (!check_optional_parameter(shop_full_display))
|
||||
return;
|
||||
frontend_e_commerce->ShowSettings(callback, *user_id, *shop_full_display);
|
||||
break;
|
||||
case ShopWebTarget::SubscriptionList:
|
||||
if (!check_optional_parameter(title_id))
|
||||
return;
|
||||
frontend_e_commerce->ShowSubscriptionList(callback, *title_id, user_id);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteOffline() {
|
||||
frontend.OpenPageLocal(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
|
||||
}
|
||||
|
||||
} // namespace Service::AM::Applets
|
||||
|
||||
@@ -4,15 +4,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
enum class ShimKind : u32;
|
||||
enum class ShopWebTarget;
|
||||
enum class WebArgTLVType : u16;
|
||||
|
||||
class WebBrowser final : public Applet {
|
||||
public:
|
||||
WebBrowser(Core::Frontend::WebBrowserApplet& frontend);
|
||||
WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id,
|
||||
Core::Frontend::ECommerceApplet* frontend_e_commerce = nullptr);
|
||||
|
||||
~WebBrowser() override;
|
||||
|
||||
void Initialize() override;
|
||||
@@ -32,15 +39,41 @@ public:
|
||||
void Finalize();
|
||||
|
||||
private:
|
||||
void InitializeInternal();
|
||||
void ExecuteInternal();
|
||||
|
||||
// Specific initializers for the types of web applets
|
||||
void InitializeShop();
|
||||
void InitializeOffline();
|
||||
|
||||
// Specific executors for the types of web applets
|
||||
void ExecuteShop();
|
||||
void ExecuteOffline();
|
||||
|
||||
Core::Frontend::WebBrowserApplet& frontend;
|
||||
|
||||
// Extra frontends for specialized functions
|
||||
Core::Frontend::ECommerceApplet* frontend_e_commerce;
|
||||
|
||||
bool complete = false;
|
||||
bool unpacked = false;
|
||||
ResultCode status = RESULT_SUCCESS;
|
||||
|
||||
FileSys::VirtualFile manual_romfs;
|
||||
u64 current_process_title_id;
|
||||
|
||||
ShimKind kind;
|
||||
std::map<WebArgTLVType, std::vector<u8>> args;
|
||||
|
||||
FileSys::VirtualFile offline_romfs;
|
||||
std::string temporary_dir;
|
||||
std::string filename;
|
||||
|
||||
ShopWebTarget shop_web_target;
|
||||
std::map<std::string, std::string, std::less<>> shop_query;
|
||||
std::optional<u64> title_id = 0;
|
||||
std::optional<u128> user_id;
|
||||
std::optional<bool> shop_full_display;
|
||||
std::string shop_extra_parameter;
|
||||
};
|
||||
|
||||
} // namespace Service::AM::Applets
|
||||
|
||||
@@ -167,13 +167,12 @@ public:
|
||||
{3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"},
|
||||
{4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"},
|
||||
{5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"},
|
||||
{6, &IAudioDevice::ListAudioDeviceName,
|
||||
"ListAudioDeviceNameAuto"}, // TODO(ogniK): Confirm if autos are identical to non auto
|
||||
{6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"},
|
||||
{7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"},
|
||||
{8, nullptr, "GetAudioDeviceOutputVolumeAuto"},
|
||||
{10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"},
|
||||
{11, nullptr, "QueryAudioDeviceInputEvent"},
|
||||
{12, nullptr, "QueryAudioDeviceOutputEvent"},
|
||||
{12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"},
|
||||
{13, nullptr, "GetAudioSystemMasterVolumeSetting"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
@@ -181,6 +180,11 @@ public:
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
|
||||
"IAudioOutBufferReleasedEvent");
|
||||
|
||||
// Should only be signalled when an audio output device has been changed, example: speaker
|
||||
// to headset
|
||||
audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair(
|
||||
kernel, Kernel::ResetType::Automatic, "IAudioDevice:AudioOutputDeviceSwitchedEvent");
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -237,7 +241,16 @@ private:
|
||||
rb.Push<u32>(1);
|
||||
}
|
||||
|
||||
void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyObjects(audio_output_device_switch_event.readable);
|
||||
}
|
||||
|
||||
Kernel::EventPair buffer_event;
|
||||
Kernel::EventPair audio_output_device_switch_event;
|
||||
|
||||
}; // namespace Audio
|
||||
|
||||
|
||||
12
src/core/hle/service/friend/errors.h
Normal file
12
src/core/hle/service/friend/errors.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::Friend {
|
||||
|
||||
constexpr ResultCode ERR_NO_NOTIFICATIONS{ErrorModule::Account, 15};
|
||||
}
|
||||
@@ -2,8 +2,13 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <queue>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/uuid.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/readable_event.h"
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
#include "core/hle/service/friend/errors.h"
|
||||
#include "core/hle/service/friend/friend.h"
|
||||
#include "core/hle/service/friend/interface.h"
|
||||
|
||||
@@ -109,6 +114,105 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
class INotificationService final : public ServiceFramework<INotificationService> {
|
||||
public:
|
||||
INotificationService(Common::UUID uuid) : ServiceFramework("INotificationService"), uuid(uuid) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &INotificationService::GetEvent, "GetEvent"},
|
||||
{1, &INotificationService::Clear, "Clear"},
|
||||
{2, &INotificationService::Pop, "Pop"}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void GetEvent(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_ACC, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
if (!is_event_created) {
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
notification_event = Kernel::WritableEvent::CreateEventPair(
|
||||
kernel, Kernel::ResetType::Manual, "INotificationService:NotifyEvent");
|
||||
is_event_created = true;
|
||||
}
|
||||
rb.PushCopyObjects(notification_event.readable);
|
||||
}
|
||||
|
||||
void Clear(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_ACC, "called");
|
||||
while (!notifications.empty()) {
|
||||
notifications.pop();
|
||||
}
|
||||
std::memset(&states, 0, sizeof(States));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void Pop(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_ACC, "called");
|
||||
|
||||
if (notifications.empty()) {
|
||||
LOG_ERROR(Service_ACC, "No notifications in queue!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERR_NO_NOTIFICATIONS);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto notification = notifications.front();
|
||||
notifications.pop();
|
||||
|
||||
switch (notification.notification_type) {
|
||||
case NotificationTypes::HasUpdatedFriendsList:
|
||||
states.has_updated_friends = false;
|
||||
break;
|
||||
case NotificationTypes::HasReceivedFriendRequest:
|
||||
states.has_received_friend_request = false;
|
||||
break;
|
||||
default:
|
||||
// HOS seems not have an error case for an unknown notification
|
||||
LOG_WARNING(Service_ACC, "Unknown notification {:08X}",
|
||||
static_cast<u32>(notification.notification_type));
|
||||
break;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw<SizedNotificationInfo>(notification);
|
||||
}
|
||||
|
||||
enum class NotificationTypes : u32 {
|
||||
HasUpdatedFriendsList = 0x65,
|
||||
HasReceivedFriendRequest = 0x1
|
||||
};
|
||||
|
||||
struct SizedNotificationInfo {
|
||||
NotificationTypes notification_type;
|
||||
INSERT_PADDING_WORDS(
|
||||
1); // TODO(ogniK): This doesn't seem to be used within any IPC returns as of now
|
||||
u64_le account_id;
|
||||
};
|
||||
static_assert(sizeof(SizedNotificationInfo) == 0x10,
|
||||
"SizedNotificationInfo is an incorrect size");
|
||||
|
||||
struct States {
|
||||
bool has_updated_friends;
|
||||
bool has_received_friend_request;
|
||||
};
|
||||
|
||||
Common::UUID uuid;
|
||||
bool is_event_created = false;
|
||||
Kernel::EventPair notification_event;
|
||||
std::queue<SizedNotificationInfo> notifications;
|
||||
States states{};
|
||||
};
|
||||
|
||||
void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -116,6 +220,17 @@ void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_ACC, "called");
|
||||
}
|
||||
|
||||
void Module::Interface::CreateNotificationService(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto uuid = rp.PopRaw<Common::UUID>();
|
||||
|
||||
LOG_DEBUG(Service_ACC, "called, uuid={}", uuid.Format());
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<INotificationService>(uuid);
|
||||
}
|
||||
|
||||
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
|
||||
: ServiceFramework(name), module(std::move(module)) {}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
~Interface() override;
|
||||
|
||||
void CreateFriendService(Kernel::HLERequestContext& ctx);
|
||||
void CreateNotificationService(Kernel::HLERequestContext& ctx);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Module> module;
|
||||
|
||||
@@ -10,7 +10,7 @@ Friend::Friend(std::shared_ptr<Module> module, const char* name)
|
||||
: Interface(std::move(module), name) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &Friend::CreateFriendService, "CreateFriendService"},
|
||||
{1, nullptr, "CreateNotificationService"},
|
||||
{1, &Friend::CreateNotificationService, "CreateNotificationService"},
|
||||
{2, nullptr, "CreateDaemonSuspendSessionService"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
@@ -204,7 +204,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
|
||||
SM::ServiceManager::InstallInterfaces(sm);
|
||||
|
||||
Account::InstallInterfaces(system);
|
||||
AM::InstallInterfaces(*sm, nv_flinger);
|
||||
AM::InstallInterfaces(*sm, nv_flinger, system);
|
||||
AOC::InstallInterfaces(*sm);
|
||||
APM::InstallInterfaces(*sm);
|
||||
Audio::InstallInterfaces(*sm);
|
||||
@@ -249,7 +249,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
|
||||
Sockets::InstallInterfaces(*sm);
|
||||
SPL::InstallInterfaces(*sm);
|
||||
SSL::InstallInterfaces(*sm);
|
||||
Time::InstallInterfaces(*sm);
|
||||
Time::InstallInterfaces(system);
|
||||
USB::InstallInterfaces(*sm);
|
||||
VI::InstallInterfaces(*sm, nv_flinger);
|
||||
WLAN::InstallInterfaces(*sm);
|
||||
|
||||
@@ -95,6 +95,14 @@ void SET::GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx) {
|
||||
PushResponseLanguageCode(ctx, post4_0_0_max_entries);
|
||||
}
|
||||
|
||||
void SET::GetQuestFlag(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_SET, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(static_cast<u32>(Settings::values.quest_flag));
|
||||
}
|
||||
|
||||
void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_SET, "called {}", Settings::values.language_index);
|
||||
|
||||
@@ -114,7 +122,7 @@ SET::SET() : ServiceFramework("set") {
|
||||
{5, &SET::GetAvailableLanguageCodes2, "GetAvailableLanguageCodes2"},
|
||||
{6, &SET::GetAvailableLanguageCodeCount2, "GetAvailableLanguageCodeCount2"},
|
||||
{7, nullptr, "GetKeyCodeMap"},
|
||||
{8, nullptr, "GetQuestFlag"},
|
||||
{8, &SET::GetQuestFlag, "GetQuestFlag"},
|
||||
{9, nullptr, "GetKeyCodeMap2"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -42,6 +42,7 @@ private:
|
||||
void GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx);
|
||||
void GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx);
|
||||
void GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx);
|
||||
void GetQuestFlag(Kernel::HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
} // namespace Service::Set
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
|
||||
namespace Service::Time {
|
||||
|
||||
Time::Time(std::shared_ptr<Module> time, const char* name)
|
||||
: Module::Interface(std::move(time), name) {
|
||||
Time::Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory,
|
||||
const char* name)
|
||||
: Module::Interface(std::move(time), std::move(shared_memory), name) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"},
|
||||
@@ -16,12 +17,12 @@ Time::Time(std::shared_ptr<Module> time, const char* name)
|
||||
{3, &Time::GetTimeZoneService, "GetTimeZoneService"},
|
||||
{4, &Time::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"},
|
||||
{5, nullptr, "GetEphemeralNetworkSystemClock"},
|
||||
{20, nullptr, "GetSharedMemoryNativeHandle"},
|
||||
{20, &Time::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"},
|
||||
{30, nullptr, "GetStandardNetworkClockOperationEventReadableHandle"},
|
||||
{31, nullptr, "GetEphemeralNetworkClockOperationEventReadableHandle"},
|
||||
{50, nullptr, "SetStandardSteadyClockInternalOffset"},
|
||||
{100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"},
|
||||
{101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"},
|
||||
{100, &Time::IsStandardUserSystemClockAutomaticCorrectionEnabled, "IsStandardUserSystemClockAutomaticCorrectionEnabled"},
|
||||
{101, &Time::SetStandardUserSystemClockAutomaticCorrectionEnabled, "SetStandardUserSystemClockAutomaticCorrectionEnabled"},
|
||||
{102, nullptr, "GetStandardUserSystemClockInitialYear"},
|
||||
{200, nullptr, "IsStandardNetworkSystemClockAccuracySufficient"},
|
||||
{201, nullptr, "GetStandardUserSystemClockAutomaticCorrectionUpdatedTime"},
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
|
||||
namespace Service::Time {
|
||||
|
||||
class SharedMemory;
|
||||
|
||||
class Time final : public Module::Interface {
|
||||
public:
|
||||
explicit Time(std::shared_ptr<Module> time, const char* name);
|
||||
explicit Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory,
|
||||
const char* name);
|
||||
~Time() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/service/time/interface.h"
|
||||
#include "core/hle/service/time/time.h"
|
||||
#include "core/hle/service/time/time_sharedmemory.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::Time {
|
||||
@@ -61,9 +62,18 @@ static u64 CalendarToPosix(const CalendarTime& calendar_time,
|
||||
return static_cast<u64>(epoch_time);
|
||||
}
|
||||
|
||||
enum class ClockContextType {
|
||||
StandardSteady,
|
||||
StandardUserSystem,
|
||||
StandardNetworkSystem,
|
||||
StandardLocalSystem,
|
||||
};
|
||||
|
||||
class ISystemClock final : public ServiceFramework<ISystemClock> {
|
||||
public:
|
||||
ISystemClock() : ServiceFramework("ISystemClock") {
|
||||
ISystemClock(std::shared_ptr<Service::Time::SharedMemory> shared_memory,
|
||||
ClockContextType clock_type)
|
||||
: ServiceFramework("ISystemClock"), shared_memory(shared_memory), clock_type(clock_type) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ISystemClock::GetCurrentTime, "GetCurrentTime"},
|
||||
{1, nullptr, "SetCurrentTime"},
|
||||
@@ -72,6 +82,8 @@ public:
|
||||
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
UpdateSharedMemoryContext(system_clock_context);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -87,34 +99,63 @@ private:
|
||||
void GetSystemClockContext(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_Time, "(STUBBED) called");
|
||||
|
||||
SystemClockContext system_clock_ontext{};
|
||||
// TODO(ogniK): This should be updated periodically however since we have it stubbed we'll
|
||||
// only update when we get a new context
|
||||
UpdateSharedMemoryContext(system_clock_context);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, (sizeof(SystemClockContext) / 4) + 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(system_clock_ontext);
|
||||
rb.PushRaw(system_clock_context);
|
||||
}
|
||||
|
||||
void UpdateSharedMemoryContext(const SystemClockContext& clock_context) {
|
||||
switch (clock_type) {
|
||||
case ClockContextType::StandardLocalSystem:
|
||||
shared_memory->SetStandardLocalSystemClockContext(clock_context);
|
||||
break;
|
||||
case ClockContextType::StandardNetworkSystem:
|
||||
shared_memory->SetStandardNetworkSystemClockContext(clock_context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SystemClockContext system_clock_context{};
|
||||
std::shared_ptr<Service::Time::SharedMemory> shared_memory;
|
||||
ClockContextType clock_type;
|
||||
};
|
||||
|
||||
class ISteadyClock final : public ServiceFramework<ISteadyClock> {
|
||||
public:
|
||||
ISteadyClock() : ServiceFramework("ISteadyClock") {
|
||||
ISteadyClock(std::shared_ptr<SharedMemory> shared_memory)
|
||||
: ServiceFramework("ISteadyClock"), shared_memory(shared_memory) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
shared_memory->SetStandardSteadyClockTimepoint(GetCurrentTimePoint());
|
||||
}
|
||||
|
||||
private:
|
||||
void GetCurrentTimePoint(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Time, "called");
|
||||
|
||||
const auto& core_timing = Core::System::GetInstance().CoreTiming();
|
||||
const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
|
||||
const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000),
|
||||
{}};
|
||||
const auto time_point = GetCurrentTimePoint();
|
||||
// TODO(ogniK): This should be updated periodically
|
||||
shared_memory->SetStandardSteadyClockTimepoint(time_point);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(steady_clock_time_point);
|
||||
rb.PushRaw(time_point);
|
||||
}
|
||||
|
||||
SteadyClockTimePoint GetCurrentTimePoint() const {
|
||||
const auto& core_timing = Core::System::GetInstance().CoreTiming();
|
||||
const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks());
|
||||
return {static_cast<u64_le>(ms.count() / 1000), {}};
|
||||
}
|
||||
|
||||
std::shared_ptr<SharedMemory> shared_memory;
|
||||
};
|
||||
|
||||
class ITimeZoneService final : public ServiceFramework<ITimeZoneService> {
|
||||
@@ -233,7 +274,7 @@ void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ct
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ISystemClock>();
|
||||
rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardUserSystem);
|
||||
}
|
||||
|
||||
void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) {
|
||||
@@ -241,7 +282,7 @@ void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext&
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ISystemClock>();
|
||||
rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardNetworkSystem);
|
||||
}
|
||||
|
||||
void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) {
|
||||
@@ -249,7 +290,7 @@ void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ISteadyClock>();
|
||||
rb.PushIpcInterface<ISteadyClock>(shared_memory);
|
||||
}
|
||||
|
||||
void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) {
|
||||
@@ -265,7 +306,7 @@ void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& c
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<ISystemClock>();
|
||||
rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardLocalSystem);
|
||||
}
|
||||
|
||||
void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) {
|
||||
@@ -333,16 +374,52 @@ void Module::Interface::CalculateStandardUserSystemClockDifferenceByUser(
|
||||
rb.PushRaw<u64>(difference);
|
||||
}
|
||||
|
||||
Module::Interface::Interface(std::shared_ptr<Module> time, const char* name)
|
||||
: ServiceFramework(name), time(std::move(time)) {}
|
||||
void Module::Interface::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Time, "called");
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyObjects(shared_memory->GetSharedMemoryHolder());
|
||||
}
|
||||
|
||||
void Module::Interface::IsStandardUserSystemClockAutomaticCorrectionEnabled(
|
||||
Kernel::HLERequestContext& ctx) {
|
||||
// ogniK(TODO): When clock contexts are implemented, the value should be read from the context
|
||||
// instead of our shared memory holder
|
||||
LOG_DEBUG(Service_Time, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(shared_memory->GetStandardUserSystemClockAutomaticCorrectionEnabled());
|
||||
}
|
||||
|
||||
void Module::Interface::SetStandardUserSystemClockAutomaticCorrectionEnabled(
|
||||
Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto enabled = rp.Pop<u8>();
|
||||
|
||||
LOG_WARNING(Service_Time, "(PARTIAL IMPLEMENTATION) called");
|
||||
|
||||
// TODO(ogniK): Update clock contexts and correct timespans
|
||||
|
||||
shared_memory->SetStandardUserSystemClockAutomaticCorrectionEnabled(enabled > 0);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
Module::Interface::Interface(std::shared_ptr<Module> time,
|
||||
std::shared_ptr<SharedMemory> shared_memory, const char* name)
|
||||
: ServiceFramework(name), time(std::move(time)), shared_memory(std::move(shared_memory)) {}
|
||||
|
||||
Module::Interface::~Interface() = default;
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
void InstallInterfaces(Core::System& system) {
|
||||
auto time = std::make_shared<Module>();
|
||||
std::make_shared<Time>(time, "time:a")->InstallAsService(service_manager);
|
||||
std::make_shared<Time>(time, "time:s")->InstallAsService(service_manager);
|
||||
std::make_shared<Time>(time, "time:u")->InstallAsService(service_manager);
|
||||
auto shared_mem = std::make_shared<SharedMemory>(system);
|
||||
|
||||
std::make_shared<Time>(time, shared_mem, "time:a")->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<Time>(time, shared_mem, "time:s")->InstallAsService(system.ServiceManager());
|
||||
std::make_shared<Time>(std::move(time), shared_mem, "time:u")
|
||||
->InstallAsService(system.ServiceManager());
|
||||
}
|
||||
|
||||
} // namespace Service::Time
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
namespace Service::Time {
|
||||
|
||||
class SharedMemory;
|
||||
|
||||
struct LocationName {
|
||||
std::array<u8, 0x24> name;
|
||||
};
|
||||
@@ -77,7 +79,8 @@ class Module final {
|
||||
public:
|
||||
class Interface : public ServiceFramework<Interface> {
|
||||
public:
|
||||
explicit Interface(std::shared_ptr<Module> time, const char* name);
|
||||
explicit Interface(std::shared_ptr<Module> time,
|
||||
std::shared_ptr<SharedMemory> shared_memory, const char* name);
|
||||
~Interface() override;
|
||||
|
||||
void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx);
|
||||
@@ -87,13 +90,17 @@ public:
|
||||
void GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx);
|
||||
void GetClockSnapshot(Kernel::HLERequestContext& ctx);
|
||||
void CalculateStandardUserSystemClockDifferenceByUser(Kernel::HLERequestContext& ctx);
|
||||
void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx);
|
||||
void IsStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx);
|
||||
void SetStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Module> time;
|
||||
std::shared_ptr<SharedMemory> shared_memory;
|
||||
};
|
||||
};
|
||||
|
||||
/// Registers all Time services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
void InstallInterfaces(Core::System& system);
|
||||
|
||||
} // namespace Service::Time
|
||||
|
||||
68
src/core/hle/service/time/time_sharedmemory.cpp
Normal file
68
src/core/hle/service/time/time_sharedmemory.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/time/time_sharedmemory.h"
|
||||
|
||||
namespace Service::Time {
|
||||
const std::size_t SHARED_MEMORY_SIZE = 0x1000;
|
||||
|
||||
SharedMemory::SharedMemory(Core::System& system) : system(system) {
|
||||
shared_memory_holder = Kernel::SharedMemory::Create(
|
||||
system.Kernel(), nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
|
||||
Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "Time:SharedMemory");
|
||||
|
||||
// Seems static from 1.0.0 -> 8.1.0. Specific games seem to check this value and crash
|
||||
// if it's set to anything else
|
||||
shared_memory_format.format_version = 14;
|
||||
std::memcpy(shared_memory_holder->GetPointer(), &shared_memory_format, sizeof(Format));
|
||||
}
|
||||
|
||||
SharedMemory::~SharedMemory() = default;
|
||||
|
||||
Kernel::SharedPtr<Kernel::SharedMemory> SharedMemory::GetSharedMemoryHolder() const {
|
||||
return shared_memory_holder;
|
||||
}
|
||||
|
||||
void SharedMemory::SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint) {
|
||||
shared_memory_format.standard_steady_clock_timepoint.StoreData(
|
||||
shared_memory_holder->GetPointer(), timepoint);
|
||||
}
|
||||
|
||||
void SharedMemory::SetStandardLocalSystemClockContext(const SystemClockContext& context) {
|
||||
shared_memory_format.standard_local_system_clock_context.StoreData(
|
||||
shared_memory_holder->GetPointer(), context);
|
||||
}
|
||||
|
||||
void SharedMemory::SetStandardNetworkSystemClockContext(const SystemClockContext& context) {
|
||||
shared_memory_format.standard_network_system_clock_context.StoreData(
|
||||
shared_memory_holder->GetPointer(), context);
|
||||
}
|
||||
|
||||
void SharedMemory::SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled) {
|
||||
shared_memory_format.standard_user_system_clock_automatic_correction.StoreData(
|
||||
shared_memory_holder->GetPointer(), enabled);
|
||||
}
|
||||
|
||||
SteadyClockTimePoint SharedMemory::GetStandardSteadyClockTimepoint() {
|
||||
return shared_memory_format.standard_steady_clock_timepoint.ReadData(
|
||||
shared_memory_holder->GetPointer());
|
||||
}
|
||||
|
||||
SystemClockContext SharedMemory::GetStandardLocalSystemClockContext() {
|
||||
return shared_memory_format.standard_local_system_clock_context.ReadData(
|
||||
shared_memory_holder->GetPointer());
|
||||
}
|
||||
|
||||
SystemClockContext SharedMemory::GetStandardNetworkSystemClockContext() {
|
||||
return shared_memory_format.standard_network_system_clock_context.ReadData(
|
||||
shared_memory_holder->GetPointer());
|
||||
}
|
||||
|
||||
bool SharedMemory::GetStandardUserSystemClockAutomaticCorrectionEnabled() {
|
||||
return shared_memory_format.standard_user_system_clock_automatic_correction.ReadData(
|
||||
shared_memory_holder->GetPointer());
|
||||
}
|
||||
|
||||
} // namespace Service::Time
|
||||
74
src/core/hle/service/time/time_sharedmemory.h
Normal file
74
src/core/hle/service/time/time_sharedmemory.h
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/service/time/time.h"
|
||||
|
||||
namespace Service::Time {
|
||||
class SharedMemory {
|
||||
public:
|
||||
explicit SharedMemory(Core::System& system);
|
||||
~SharedMemory();
|
||||
|
||||
// Return the shared memory handle
|
||||
Kernel::SharedPtr<Kernel::SharedMemory> GetSharedMemoryHolder() const;
|
||||
|
||||
// Set memory barriers in shared memory and update them
|
||||
void SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint);
|
||||
void SetStandardLocalSystemClockContext(const SystemClockContext& context);
|
||||
void SetStandardNetworkSystemClockContext(const SystemClockContext& context);
|
||||
void SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled);
|
||||
|
||||
// Pull from memory barriers in the shared memory
|
||||
SteadyClockTimePoint GetStandardSteadyClockTimepoint();
|
||||
SystemClockContext GetStandardLocalSystemClockContext();
|
||||
SystemClockContext GetStandardNetworkSystemClockContext();
|
||||
bool GetStandardUserSystemClockAutomaticCorrectionEnabled();
|
||||
|
||||
// TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this?
|
||||
template <typename T, std::size_t Offset>
|
||||
struct MemoryBarrier {
|
||||
static_assert(std::is_trivially_constructible_v<T>, "T must be trivially constructable");
|
||||
u32_le read_attempt{};
|
||||
std::array<T, 2> data{};
|
||||
|
||||
// These are not actually memory barriers at the moment as we don't have multicore and all
|
||||
// HLE is mutexed. This will need to properly be implemented when we start updating the time
|
||||
// points on threads. As of right now, we'll be updated both values synchronously and just
|
||||
// incrementing the read_attempt to indicate that we waited.
|
||||
void StoreData(u8* shared_memory, T data_to_store) {
|
||||
std::memcpy(this, shared_memory + Offset, sizeof(*this));
|
||||
read_attempt++;
|
||||
data[read_attempt & 1] = data_to_store;
|
||||
std::memcpy(shared_memory + Offset, this, sizeof(*this));
|
||||
}
|
||||
|
||||
// For reading we're just going to read the last stored value. If there was no value stored
|
||||
// it will just end up reading an empty value as intended.
|
||||
T ReadData(u8* shared_memory) {
|
||||
std::memcpy(this, shared_memory + Offset, sizeof(*this));
|
||||
return data[(read_attempt - 1) & 1];
|
||||
}
|
||||
};
|
||||
|
||||
// Shared memory format
|
||||
struct Format {
|
||||
MemoryBarrier<SteadyClockTimePoint, 0x0> standard_steady_clock_timepoint;
|
||||
MemoryBarrier<SystemClockContext, 0x38> standard_local_system_clock_context;
|
||||
MemoryBarrier<SystemClockContext, 0x80> standard_network_system_clock_context;
|
||||
MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction;
|
||||
u32_le format_version;
|
||||
};
|
||||
static_assert(sizeof(Format) == 0xd8, "Format is an invalid size");
|
||||
|
||||
private:
|
||||
Kernel::SharedPtr<Kernel::SharedMemory> shared_memory_holder{};
|
||||
Core::System& system;
|
||||
Format shared_memory_format{};
|
||||
};
|
||||
|
||||
} // namespace Service::Time
|
||||
@@ -168,7 +168,8 @@ ResultStatus AppLoader_NSP::ReadControlData(FileSys::NACP& nacp) {
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NSP::ReadManualRomFS(FileSys::VirtualFile& file) {
|
||||
const auto nca = nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Manual);
|
||||
const auto nca =
|
||||
nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::HtmlDocument);
|
||||
if (nsp->GetStatus() != ResultStatus::Success || nca == nullptr)
|
||||
return ResultStatus::ErrorNoRomFS;
|
||||
file = nca->GetRomFS();
|
||||
|
||||
@@ -134,7 +134,7 @@ ResultStatus AppLoader_XCI::ReadControlData(FileSys::NACP& control) {
|
||||
|
||||
ResultStatus AppLoader_XCI::ReadManualRomFS(FileSys::VirtualFile& file) {
|
||||
const auto nca = xci->GetSecurePartitionNSP()->GetNCA(xci->GetProgramTitleID(),
|
||||
FileSys::ContentRecordType::Manual);
|
||||
FileSys::ContentRecordType::HtmlDocument);
|
||||
if (xci->GetStatus() != ResultStatus::Success || nca == nullptr)
|
||||
return ResultStatus::ErrorXCIMissingPartition;
|
||||
file = nca->GetRomFS();
|
||||
|
||||
@@ -16,11 +16,9 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_setup.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace Memory {
|
||||
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class Process;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/time.h>
|
||||
#include <json.hpp>
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/scm_rev.h"
|
||||
@@ -14,7 +19,6 @@
|
||||
#include "core/hle/result.h"
|
||||
#include "core/reporter.h"
|
||||
#include "core/settings.h"
|
||||
#include "fmt/time.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -30,9 +34,11 @@ std::string GetTimestamp() {
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
void SaveToFile(const json& json, const std::string& filename) {
|
||||
if (!FileUtil::CreateFullPath(filename))
|
||||
void SaveToFile(json json, const std::string& filename) {
|
||||
if (!FileUtil::CreateFullPath(filename)) {
|
||||
LOG_ERROR(Core, "Failed to create path for '{}' to save report!", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
std::ofstream file(
|
||||
FileUtil::SanitizePath(filename, FileUtil::DirectorySeparator::PlatformDefault));
|
||||
@@ -61,8 +67,11 @@ json GetReportCommonData(u64 title_id, ResultCode result, const std::string& tim
|
||||
{"result_description", fmt::format("{:08X}", result.description.Value())},
|
||||
{"timestamp", timestamp},
|
||||
};
|
||||
if (user_id.has_value())
|
||||
|
||||
if (user_id.has_value()) {
|
||||
out["user_id"] = fmt::format("{:016X}{:016X}", (*user_id)[1], (*user_id)[0]);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -171,14 +180,14 @@ json GetHLERequestContextData(Kernel::HLERequestContext& ctx) {
|
||||
out["buffer_descriptor_c"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorC());
|
||||
out["buffer_descriptor_x"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorX());
|
||||
|
||||
return std::move(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
namespace Core {
|
||||
|
||||
Reporter::Reporter(Core::System& system) : system(system) {}
|
||||
Reporter::Reporter(System& system) : system(system) {}
|
||||
|
||||
Reporter::~Reporter() = default;
|
||||
|
||||
@@ -187,8 +196,9 @@ void Reporter::SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u
|
||||
const std::array<u64, 31>& registers,
|
||||
const std::array<u64, 32>& backtrace, u32 backtrace_size,
|
||||
const std::string& arch, u32 unk10) const {
|
||||
if (!IsReportingEnabled())
|
||||
if (!IsReportingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = GetTimestamp();
|
||||
json out;
|
||||
@@ -212,8 +222,9 @@ void Reporter::SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u
|
||||
|
||||
void Reporter::SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
|
||||
std::optional<std::vector<u8>> resolved_buffer) const {
|
||||
if (!IsReportingEnabled())
|
||||
if (!IsReportingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = GetTimestamp();
|
||||
const auto title_id = system.CurrentProcess()->GetTitleID();
|
||||
@@ -238,8 +249,9 @@ void Reporter::SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64
|
||||
void Reporter::SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id,
|
||||
const std::string& name,
|
||||
const std::string& service_name) const {
|
||||
if (!IsReportingEnabled())
|
||||
if (!IsReportingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = GetTimestamp();
|
||||
const auto title_id = system.CurrentProcess()->GetTitleID();
|
||||
@@ -259,8 +271,9 @@ void Reporter::SaveUnimplementedAppletReport(
|
||||
u32 applet_id, u32 common_args_version, u32 library_version, u32 theme_color,
|
||||
bool startup_sound, u64 system_tick, std::vector<std::vector<u8>> normal_channel,
|
||||
std::vector<std::vector<u8>> interactive_channel) const {
|
||||
if (!IsReportingEnabled())
|
||||
if (!IsReportingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = GetTimestamp();
|
||||
const auto title_id = system.CurrentProcess()->GetTitleID();
|
||||
@@ -293,8 +306,9 @@ void Reporter::SaveUnimplementedAppletReport(
|
||||
|
||||
void Reporter::SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vector<u8>> data,
|
||||
std::optional<u128> user_id) const {
|
||||
if (!IsReportingEnabled())
|
||||
if (!IsReportingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = GetTimestamp();
|
||||
json out;
|
||||
@@ -316,8 +330,9 @@ void Reporter::SavePlayReport(u64 title_id, u64 process_id, std::vector<std::vec
|
||||
void Reporter::SaveErrorReport(u64 title_id, ResultCode result,
|
||||
std::optional<std::string> custom_text_main,
|
||||
std::optional<std::string> custom_text_detail) const {
|
||||
if (!IsReportingEnabled())
|
||||
if (!IsReportingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = GetTimestamp();
|
||||
json out;
|
||||
@@ -336,8 +351,9 @@ void Reporter::SaveErrorReport(u64 title_id, ResultCode result,
|
||||
}
|
||||
|
||||
void Reporter::SaveUserReport() const {
|
||||
if (!IsReportingEnabled())
|
||||
if (!IsReportingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = GetTimestamp();
|
||||
const auto title_id = system.CurrentProcess()->GetTitleID();
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -16,9 +18,11 @@ class HLERequestContext;
|
||||
|
||||
namespace Core {
|
||||
|
||||
class System;
|
||||
|
||||
class Reporter {
|
||||
public:
|
||||
explicit Reporter(Core::System& system);
|
||||
explicit Reporter(System& system);
|
||||
~Reporter();
|
||||
|
||||
void SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, u64 sp,
|
||||
@@ -50,7 +54,7 @@ public:
|
||||
private:
|
||||
bool IsReportingEnabled() const;
|
||||
|
||||
Core::System& system;
|
||||
System& system;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -85,7 +85,7 @@ void LogSettings() {
|
||||
LogSetting("System_RngSeed", Settings::values.rng_seed.value_or(0));
|
||||
LogSetting("System_CurrentUser", Settings::values.current_user);
|
||||
LogSetting("System_LanguageIndex", Settings::values.language_index);
|
||||
LogSetting("Core_UseCpuJit", Settings::values.use_cpu_jit);
|
||||
LogSetting("Core_CpuJitEnabled", Settings::values.cpu_jit_enabled);
|
||||
LogSetting("Core_UseMultiCore", Settings::values.use_multi_core);
|
||||
LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor);
|
||||
LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit);
|
||||
|
||||
@@ -378,7 +378,7 @@ struct Values {
|
||||
std::atomic_bool is_device_reload_pending{true};
|
||||
|
||||
// Core
|
||||
bool use_cpu_jit;
|
||||
bool cpu_jit_enabled;
|
||||
bool use_multi_core;
|
||||
|
||||
// Data Storage
|
||||
@@ -416,6 +416,7 @@ struct Values {
|
||||
bool dump_exefs;
|
||||
bool dump_nso;
|
||||
bool reporting_services;
|
||||
bool quest_flag;
|
||||
|
||||
// WebService
|
||||
bool enable_telemetry;
|
||||
|
||||
@@ -168,7 +168,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
|
||||
AddField(Telemetry::FieldType::UserConfig, "Audio_SinkId", Settings::values.sink_id);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching",
|
||||
Settings::values.enable_audio_stretching);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.cpu_jit_enabled);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Core_UseMultiCore",
|
||||
Settings::values.use_multi_core);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
|
||||
|
||||
188
src/core/tools/freezer.cpp
Normal file
188
src/core/tools/freezer.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/tools/freezer.h"
|
||||
|
||||
namespace Tools {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
|
||||
|
||||
u64 MemoryReadWidth(u32 width, VAddr addr) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return Memory::Read8(addr);
|
||||
case 2:
|
||||
return Memory::Read16(addr);
|
||||
case 4:
|
||||
return Memory::Read32(addr);
|
||||
case 8:
|
||||
return Memory::Read64(addr);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryWriteWidth(u32 width, VAddr addr, u64 value) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
Memory::Write8(addr, static_cast<u8>(value));
|
||||
break;
|
||||
case 2:
|
||||
Memory::Write16(addr, static_cast<u16>(value));
|
||||
break;
|
||||
case 4:
|
||||
Memory::Write32(addr, static_cast<u32>(value));
|
||||
break;
|
||||
case 8:
|
||||
Memory::Write64(addr, value);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
Freezer::Freezer(Core::Timing::CoreTiming& core_timing) : core_timing(core_timing) {
|
||||
event = core_timing.RegisterEvent(
|
||||
"MemoryFreezer::FrameCallback",
|
||||
[this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
|
||||
core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event);
|
||||
}
|
||||
|
||||
Freezer::~Freezer() {
|
||||
core_timing.UnscheduleEvent(event, 0);
|
||||
}
|
||||
|
||||
void Freezer::SetActive(bool active) {
|
||||
if (!this->active.exchange(active)) {
|
||||
FillEntryReads();
|
||||
core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event);
|
||||
LOG_DEBUG(Common_Memory, "Memory freezer activated!");
|
||||
} else {
|
||||
LOG_DEBUG(Common_Memory, "Memory freezer deactivated!");
|
||||
}
|
||||
}
|
||||
|
||||
bool Freezer::IsActive() const {
|
||||
return active.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void Freezer::Clear() {
|
||||
std::lock_guard lock{entries_mutex};
|
||||
|
||||
LOG_DEBUG(Common_Memory, "Clearing all frozen memory values.");
|
||||
|
||||
entries.clear();
|
||||
}
|
||||
|
||||
u64 Freezer::Freeze(VAddr address, u32 width) {
|
||||
std::lock_guard lock{entries_mutex};
|
||||
|
||||
const auto current_value = MemoryReadWidth(width, address);
|
||||
entries.push_back({address, width, current_value});
|
||||
|
||||
LOG_DEBUG(Common_Memory,
|
||||
"Freezing memory for address={:016X}, width={:02X}, current_value={:016X}", address,
|
||||
width, current_value);
|
||||
|
||||
return current_value;
|
||||
}
|
||||
|
||||
void Freezer::Unfreeze(VAddr address) {
|
||||
std::lock_guard lock{entries_mutex};
|
||||
|
||||
LOG_DEBUG(Common_Memory, "Unfreezing memory for address={:016X}", address);
|
||||
|
||||
entries.erase(
|
||||
std::remove_if(entries.begin(), entries.end(),
|
||||
[&address](const Entry& entry) { return entry.address == address; }),
|
||||
entries.end());
|
||||
}
|
||||
|
||||
bool Freezer::IsFrozen(VAddr address) const {
|
||||
std::lock_guard lock{entries_mutex};
|
||||
|
||||
return std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
|
||||
return entry.address == address;
|
||||
}) != entries.end();
|
||||
}
|
||||
|
||||
void Freezer::SetFrozenValue(VAddr address, u64 value) {
|
||||
std::lock_guard lock{entries_mutex};
|
||||
|
||||
const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
|
||||
return entry.address == address;
|
||||
});
|
||||
|
||||
if (iter == entries.end()) {
|
||||
LOG_ERROR(Common_Memory,
|
||||
"Tried to set freeze value for address={:016X} that is not frozen!", address);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Common_Memory,
|
||||
"Manually overridden freeze value for address={:016X}, width={:02X} to value={:016X}",
|
||||
iter->address, iter->width, value);
|
||||
iter->value = value;
|
||||
}
|
||||
|
||||
std::optional<Freezer::Entry> Freezer::GetEntry(VAddr address) const {
|
||||
std::lock_guard lock{entries_mutex};
|
||||
|
||||
const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) {
|
||||
return entry.address == address;
|
||||
});
|
||||
|
||||
if (iter == entries.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return *iter;
|
||||
}
|
||||
|
||||
std::vector<Freezer::Entry> Freezer::GetEntries() const {
|
||||
std::lock_guard lock{entries_mutex};
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
void Freezer::FrameCallback(u64 userdata, s64 cycles_late) {
|
||||
if (!IsActive()) {
|
||||
LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock{entries_mutex};
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
LOG_DEBUG(Common_Memory,
|
||||
"Enforcing memory freeze at address={:016X}, value={:016X}, width={:02X}",
|
||||
entry.address, entry.value, entry.width);
|
||||
MemoryWriteWidth(entry.width, entry.address, entry.value);
|
||||
}
|
||||
|
||||
core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS - cycles_late, event);
|
||||
}
|
||||
|
||||
void Freezer::FillEntryReads() {
|
||||
std::lock_guard lock{entries_mutex};
|
||||
|
||||
LOG_DEBUG(Common_Memory, "Updating memory freeze entries to current values.");
|
||||
|
||||
for (auto& entry : entries) {
|
||||
entry.value = MemoryReadWidth(entry.width, entry.address);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Tools
|
||||
82
src/core/tools/freezer.h
Normal file
82
src/core/tools/freezer.h
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace Tools {
|
||||
|
||||
/**
|
||||
* This class allows the user to prevent an application from writing new values to certain memory
|
||||
* locations. This has a variety of uses when attempting to reverse a game.
|
||||
*
|
||||
* One example could be a cheat to prevent Mario from taking damage in SMO. One could freeze the
|
||||
* memory address that the game uses to store Mario's health so when he takes damage (and the game
|
||||
* tries to write the new health value to memory), the value won't change.
|
||||
*/
|
||||
class Freezer {
|
||||
public:
|
||||
struct Entry {
|
||||
VAddr address;
|
||||
u32 width;
|
||||
u64 value;
|
||||
};
|
||||
|
||||
explicit Freezer(Core::Timing::CoreTiming& core_timing);
|
||||
~Freezer();
|
||||
|
||||
// Enables or disables the entire memory freezer.
|
||||
void SetActive(bool active);
|
||||
|
||||
// Returns whether or not the freezer is active.
|
||||
bool IsActive() const;
|
||||
|
||||
// Removes all entries from the freezer.
|
||||
void Clear();
|
||||
|
||||
// Freezes a value to its current memory address. The value the memory is kept at will be the
|
||||
// value that is read during this function. Width can be 1, 2, 4, or 8 (in bytes).
|
||||
u64 Freeze(VAddr address, u32 width);
|
||||
|
||||
// Unfreezes the memory value at address. If the address isn't frozen, this is a no-op.
|
||||
void Unfreeze(VAddr address);
|
||||
|
||||
// Returns whether or not the address is frozen.
|
||||
bool IsFrozen(VAddr address) const;
|
||||
|
||||
// Sets the value that address should be frozen to. This doesn't change the width set by using
|
||||
// Freeze(). If the value isn't frozen, this will not freeze it and is thus a no-op.
|
||||
void SetFrozenValue(VAddr address, u64 value);
|
||||
|
||||
// Returns the entry corresponding to the address if the address is frozen, otherwise
|
||||
// std::nullopt.
|
||||
std::optional<Entry> GetEntry(VAddr address) const;
|
||||
|
||||
// Returns all the entries in the freezer, an empty vector means nothing is frozen.
|
||||
std::vector<Entry> GetEntries() const;
|
||||
|
||||
private:
|
||||
void FrameCallback(u64 userdata, s64 cycles_late);
|
||||
void FillEntryReads();
|
||||
|
||||
std::atomic_bool active{false};
|
||||
|
||||
mutable std::mutex entries_mutex;
|
||||
std::vector<Entry> entries;
|
||||
|
||||
Core::Timing::EventType* event;
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
};
|
||||
|
||||
} // namespace Tools
|
||||
@@ -99,24 +99,24 @@ TEST_CASE("CoreTiming[Threadsave]", "[core]") {
|
||||
core_timing.Advance();
|
||||
|
||||
// D -> B -> C -> A -> E
|
||||
core_timing.ScheduleEventThreadsafe(1000, cb_a, CB_IDS[0]);
|
||||
// Manually force since ScheduleEventThreadsafe doesn't call it
|
||||
core_timing.ScheduleEvent(1000, cb_a, CB_IDS[0]);
|
||||
// Manually force since ScheduleEvent doesn't call it
|
||||
core_timing.ForceExceptionCheck(1000);
|
||||
REQUIRE(1000 == core_timing.GetDowncount());
|
||||
core_timing.ScheduleEventThreadsafe(500, cb_b, CB_IDS[1]);
|
||||
// Manually force since ScheduleEventThreadsafe doesn't call it
|
||||
core_timing.ScheduleEvent(500, cb_b, CB_IDS[1]);
|
||||
// Manually force since ScheduleEvent doesn't call it
|
||||
core_timing.ForceExceptionCheck(500);
|
||||
REQUIRE(500 == core_timing.GetDowncount());
|
||||
core_timing.ScheduleEventThreadsafe(800, cb_c, CB_IDS[2]);
|
||||
// Manually force since ScheduleEventThreadsafe doesn't call it
|
||||
core_timing.ScheduleEvent(800, cb_c, CB_IDS[2]);
|
||||
// Manually force since ScheduleEvent doesn't call it
|
||||
core_timing.ForceExceptionCheck(800);
|
||||
REQUIRE(500 == core_timing.GetDowncount());
|
||||
core_timing.ScheduleEventThreadsafe(100, cb_d, CB_IDS[3]);
|
||||
// Manually force since ScheduleEventThreadsafe doesn't call it
|
||||
core_timing.ScheduleEvent(100, cb_d, CB_IDS[3]);
|
||||
// Manually force since ScheduleEvent doesn't call it
|
||||
core_timing.ForceExceptionCheck(100);
|
||||
REQUIRE(100 == core_timing.GetDowncount());
|
||||
core_timing.ScheduleEventThreadsafe(1200, cb_e, CB_IDS[4]);
|
||||
// Manually force since ScheduleEventThreadsafe doesn't call it
|
||||
core_timing.ScheduleEvent(1200, cb_e, CB_IDS[4]);
|
||||
// Manually force since ScheduleEvent doesn't call it
|
||||
core_timing.ForceExceptionCheck(1200);
|
||||
REQUIRE(100 == core_timing.GetDowncount());
|
||||
|
||||
|
||||
@@ -41,12 +41,12 @@ add_library(video_core STATIC
|
||||
renderer_opengl/gl_buffer_cache.h
|
||||
renderer_opengl/gl_device.cpp
|
||||
renderer_opengl/gl_device.h
|
||||
renderer_opengl/gl_framebuffer_cache.cpp
|
||||
renderer_opengl/gl_framebuffer_cache.h
|
||||
renderer_opengl/gl_global_cache.cpp
|
||||
renderer_opengl/gl_global_cache.h
|
||||
renderer_opengl/gl_rasterizer.cpp
|
||||
renderer_opengl/gl_rasterizer.h
|
||||
renderer_opengl/gl_rasterizer_cache.cpp
|
||||
renderer_opengl/gl_rasterizer_cache.h
|
||||
renderer_opengl/gl_resource_manager.cpp
|
||||
renderer_opengl/gl_resource_manager.h
|
||||
renderer_opengl/gl_sampler_cache.cpp
|
||||
@@ -67,6 +67,8 @@ add_library(video_core STATIC
|
||||
renderer_opengl/gl_state.h
|
||||
renderer_opengl/gl_stream_buffer.cpp
|
||||
renderer_opengl/gl_stream_buffer.h
|
||||
renderer_opengl/gl_texture_cache.cpp
|
||||
renderer_opengl/gl_texture_cache.h
|
||||
renderer_opengl/maxwell_to_gl.h
|
||||
renderer_opengl/renderer_opengl.cpp
|
||||
renderer_opengl/renderer_opengl.h
|
||||
@@ -88,6 +90,7 @@ add_library(video_core STATIC
|
||||
shader/decode/conversion.cpp
|
||||
shader/decode/memory.cpp
|
||||
shader/decode/texture.cpp
|
||||
shader/decode/image.cpp
|
||||
shader/decode/float_set_predicate.cpp
|
||||
shader/decode/integer_set_predicate.cpp
|
||||
shader/decode/half_set_predicate.cpp
|
||||
@@ -109,6 +112,13 @@ add_library(video_core STATIC
|
||||
shader/track.cpp
|
||||
surface.cpp
|
||||
surface.h
|
||||
texture_cache/surface_base.cpp
|
||||
texture_cache/surface_base.h
|
||||
texture_cache/surface_params.cpp
|
||||
texture_cache/surface_params.h
|
||||
texture_cache/surface_view.cpp
|
||||
texture_cache/surface_view.h
|
||||
texture_cache/texture_cache.h
|
||||
textures/astc.cpp
|
||||
textures/astc.h
|
||||
textures/convert.cpp
|
||||
@@ -116,8 +126,6 @@ add_library(video_core STATIC
|
||||
textures/decoders.cpp
|
||||
textures/decoders.h
|
||||
textures/texture.h
|
||||
texture_cache.cpp
|
||||
texture_cache.h
|
||||
video_core.cpp
|
||||
video_core.h
|
||||
)
|
||||
|
||||
@@ -36,10 +36,10 @@ void State::ProcessData(const u32 data, const bool is_last_call) {
|
||||
} else {
|
||||
UNIMPLEMENTED_IF(regs.dest.z != 0);
|
||||
UNIMPLEMENTED_IF(regs.dest.depth != 1);
|
||||
UNIMPLEMENTED_IF(regs.dest.BlockWidth() != 1);
|
||||
UNIMPLEMENTED_IF(regs.dest.BlockDepth() != 1);
|
||||
UNIMPLEMENTED_IF(regs.dest.BlockWidth() != 0);
|
||||
UNIMPLEMENTED_IF(regs.dest.BlockDepth() != 0);
|
||||
const std::size_t dst_size = Tegra::Texture::CalculateSize(
|
||||
true, 1, regs.dest.width, regs.dest.height, 1, regs.dest.BlockHeight(), 1);
|
||||
true, 1, regs.dest.width, regs.dest.height, 1, regs.dest.BlockHeight(), 0);
|
||||
tmp_buffer.resize(dst_size);
|
||||
memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
|
||||
Tegra::Texture::SwizzleKepler(regs.dest.width, regs.dest.height, regs.dest.x, regs.dest.y,
|
||||
|
||||
@@ -39,15 +39,15 @@ struct Registers {
|
||||
}
|
||||
|
||||
u32 BlockWidth() const {
|
||||
return 1U << block_width.Value();
|
||||
return block_width.Value();
|
||||
}
|
||||
|
||||
u32 BlockHeight() const {
|
||||
return 1U << block_height.Value();
|
||||
return block_height.Value();
|
||||
}
|
||||
|
||||
u32 BlockDepth() const {
|
||||
return 1U << block_depth.Value();
|
||||
return block_depth.Value();
|
||||
}
|
||||
} dest;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
@@ -35,21 +34,31 @@ void Fermi2D::HandleSurfaceCopy() {
|
||||
static_cast<u32>(regs.operation));
|
||||
|
||||
// TODO(Subv): Only raw copies are implemented.
|
||||
ASSERT(regs.operation == Regs::Operation::SrcCopy);
|
||||
ASSERT(regs.operation == Operation::SrcCopy);
|
||||
|
||||
const u32 src_blit_x1{static_cast<u32>(regs.blit_src_x >> 32)};
|
||||
const u32 src_blit_y1{static_cast<u32>(regs.blit_src_y >> 32)};
|
||||
const u32 src_blit_x2{
|
||||
static_cast<u32>((regs.blit_src_x + (regs.blit_dst_width * regs.blit_du_dx)) >> 32)};
|
||||
const u32 src_blit_y2{
|
||||
static_cast<u32>((regs.blit_src_y + (regs.blit_dst_height * regs.blit_dv_dy)) >> 32)};
|
||||
|
||||
u32 src_blit_x2, src_blit_y2;
|
||||
if (regs.blit_control.origin == Origin::Corner) {
|
||||
src_blit_x2 =
|
||||
static_cast<u32>((regs.blit_src_x + (regs.blit_du_dx * regs.blit_dst_width)) >> 32);
|
||||
src_blit_y2 =
|
||||
static_cast<u32>((regs.blit_src_y + (regs.blit_dv_dy * regs.blit_dst_height)) >> 32);
|
||||
} else {
|
||||
src_blit_x2 = static_cast<u32>((regs.blit_src_x >> 32) + regs.blit_dst_width);
|
||||
src_blit_y2 = static_cast<u32>((regs.blit_src_y >> 32) + regs.blit_dst_height);
|
||||
}
|
||||
const Common::Rectangle<u32> src_rect{src_blit_x1, src_blit_y1, src_blit_x2, src_blit_y2};
|
||||
const Common::Rectangle<u32> dst_rect{regs.blit_dst_x, regs.blit_dst_y,
|
||||
regs.blit_dst_x + regs.blit_dst_width,
|
||||
regs.blit_dst_y + regs.blit_dst_height};
|
||||
Config copy_config;
|
||||
copy_config.operation = regs.operation;
|
||||
copy_config.filter = regs.blit_control.filter;
|
||||
copy_config.src_rect = src_rect;
|
||||
copy_config.dst_rect = dst_rect;
|
||||
|
||||
if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst, src_rect, dst_rect)) {
|
||||
if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
namespace Tegra {
|
||||
@@ -38,6 +39,26 @@ public:
|
||||
/// Write the value to the register identified by method.
|
||||
void CallMethod(const GPU::MethodCall& method_call);
|
||||
|
||||
enum class Origin : u32 {
|
||||
Center = 0,
|
||||
Corner = 1,
|
||||
};
|
||||
|
||||
enum class Filter : u32 {
|
||||
PointSample = 0, // Nearest
|
||||
Linear = 1,
|
||||
};
|
||||
|
||||
enum class Operation : u32 {
|
||||
SrcCopyAnd = 0,
|
||||
ROPAnd = 1,
|
||||
Blend = 2,
|
||||
SrcCopy = 3,
|
||||
ROP = 4,
|
||||
SrcCopyPremult = 5,
|
||||
BlendPremult = 6,
|
||||
};
|
||||
|
||||
struct Regs {
|
||||
static constexpr std::size_t NUM_REGS = 0x258;
|
||||
|
||||
@@ -63,32 +84,19 @@ public:
|
||||
}
|
||||
|
||||
u32 BlockWidth() const {
|
||||
// The block width is stored in log2 format.
|
||||
return 1 << block_width;
|
||||
return block_width.Value();
|
||||
}
|
||||
|
||||
u32 BlockHeight() const {
|
||||
// The block height is stored in log2 format.
|
||||
return 1 << block_height;
|
||||
return block_height.Value();
|
||||
}
|
||||
|
||||
u32 BlockDepth() const {
|
||||
// The block depth is stored in log2 format.
|
||||
return 1 << block_depth;
|
||||
return block_depth.Value();
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size");
|
||||
|
||||
enum class Operation : u32 {
|
||||
SrcCopyAnd = 0,
|
||||
ROPAnd = 1,
|
||||
Blend = 2,
|
||||
SrcCopy = 3,
|
||||
ROP = 4,
|
||||
SrcCopyPremult = 5,
|
||||
BlendPremult = 6,
|
||||
};
|
||||
|
||||
union {
|
||||
struct {
|
||||
INSERT_PADDING_WORDS(0x80);
|
||||
@@ -105,7 +113,11 @@ public:
|
||||
|
||||
INSERT_PADDING_WORDS(0x177);
|
||||
|
||||
u32 blit_control;
|
||||
union {
|
||||
u32 raw;
|
||||
BitField<0, 1, Origin> origin;
|
||||
BitField<4, 1, Filter> filter;
|
||||
} blit_control;
|
||||
|
||||
INSERT_PADDING_WORDS(0x8);
|
||||
|
||||
@@ -124,6 +136,13 @@ public:
|
||||
};
|
||||
} regs{};
|
||||
|
||||
struct Config {
|
||||
Operation operation;
|
||||
Filter filter;
|
||||
Common::Rectangle<u32> src_rect;
|
||||
Common::Rectangle<u32> dst_rect;
|
||||
};
|
||||
|
||||
private:
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
MemoryManager& memory_manager;
|
||||
|
||||
@@ -430,14 +430,10 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
|
||||
Texture::TICEntry tic_entry;
|
||||
memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
|
||||
|
||||
ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear ||
|
||||
tic_entry.header_version == Texture::TICHeaderVersion::Pitch,
|
||||
"TIC versions other than BlockLinear or Pitch are unimplemented");
|
||||
|
||||
const auto r_type = tic_entry.r_type.Value();
|
||||
const auto g_type = tic_entry.g_type.Value();
|
||||
const auto b_type = tic_entry.b_type.Value();
|
||||
const auto a_type = tic_entry.a_type.Value();
|
||||
const auto r_type{tic_entry.r_type.Value()};
|
||||
const auto g_type{tic_entry.g_type.Value()};
|
||||
const auto b_type{tic_entry.b_type.Value()};
|
||||
const auto a_type{tic_entry.a_type.Value()};
|
||||
|
||||
// TODO(Subv): Different data types for separate components are not supported
|
||||
DEBUG_ASSERT(r_type == g_type && r_type == b_type && r_type == a_type);
|
||||
|
||||
@@ -111,7 +111,7 @@ void MaxwellDMA::HandleCopy() {
|
||||
|
||||
memory_manager.WriteBlock(dest, write_buffer.data(), dst_size);
|
||||
} else {
|
||||
ASSERT(regs.dst_params.BlockDepth() == 1);
|
||||
ASSERT(regs.dst_params.BlockDepth() == 0);
|
||||
|
||||
const u32 src_bytes_per_pixel = regs.src_pitch / regs.x_count;
|
||||
|
||||
|
||||
@@ -59,11 +59,11 @@ public:
|
||||
};
|
||||
|
||||
u32 BlockHeight() const {
|
||||
return 1 << block_height;
|
||||
return block_height.Value();
|
||||
}
|
||||
|
||||
u32 BlockDepth() const {
|
||||
return 1 << block_depth;
|
||||
return block_depth.Value();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
@@ -126,6 +127,15 @@ union Sampler {
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
union Image {
|
||||
Image() = default;
|
||||
|
||||
constexpr explicit Image(u64 value) : value{value} {}
|
||||
|
||||
BitField<36, 13, u64> index;
|
||||
u64 value;
|
||||
};
|
||||
|
||||
} // namespace Tegra::Shader
|
||||
|
||||
namespace std {
|
||||
@@ -344,6 +354,26 @@ enum class TextureMiscMode : u64 {
|
||||
PTP,
|
||||
};
|
||||
|
||||
enum class SurfaceDataMode : u64 {
|
||||
P = 0,
|
||||
D_BA = 1,
|
||||
};
|
||||
|
||||
enum class OutOfBoundsStore : u64 {
|
||||
Ignore = 0,
|
||||
Clamp = 1,
|
||||
Trap = 2,
|
||||
};
|
||||
|
||||
enum class ImageType : u64 {
|
||||
Texture1D = 0,
|
||||
TextureBuffer = 1,
|
||||
Texture1DArray = 2,
|
||||
Texture2D = 3,
|
||||
Texture2DArray = 4,
|
||||
Texture3D = 5,
|
||||
};
|
||||
|
||||
enum class IsberdMode : u64 {
|
||||
None = 0,
|
||||
Patch = 1,
|
||||
@@ -398,7 +428,7 @@ enum class LmemLoadCacheManagement : u64 {
|
||||
CV = 3,
|
||||
};
|
||||
|
||||
enum class LmemStoreCacheManagement : u64 {
|
||||
enum class StoreCacheManagement : u64 {
|
||||
Default = 0,
|
||||
CG = 1,
|
||||
CS = 2,
|
||||
@@ -811,7 +841,7 @@ union Instruction {
|
||||
} ld_l;
|
||||
|
||||
union {
|
||||
BitField<44, 2, LmemStoreCacheManagement> cache_management;
|
||||
BitField<44, 2, StoreCacheManagement> cache_management;
|
||||
} st_l;
|
||||
|
||||
union {
|
||||
@@ -1231,6 +1261,20 @@ union Instruction {
|
||||
}
|
||||
} texs;
|
||||
|
||||
union {
|
||||
BitField<28, 1, u64> is_array;
|
||||
BitField<29, 2, TextureType> texture_type;
|
||||
BitField<35, 1, u64> aoffi;
|
||||
BitField<49, 1, u64> nodep_flag;
|
||||
BitField<50, 1, u64> ms; // Multisample?
|
||||
BitField<54, 1, u64> cl;
|
||||
BitField<55, 1, u64> process_mode;
|
||||
|
||||
TextureProcessMode GetTextureProcessMode() const {
|
||||
return process_mode == 0 ? TextureProcessMode::LZ : TextureProcessMode::LL;
|
||||
}
|
||||
} tld;
|
||||
|
||||
union {
|
||||
BitField<49, 1, u64> nodep_flag;
|
||||
BitField<53, 4, u64> texture_info;
|
||||
@@ -1280,6 +1324,35 @@ union Instruction {
|
||||
}
|
||||
} tlds;
|
||||
|
||||
union {
|
||||
BitField<24, 2, StoreCacheManagement> cache_management;
|
||||
BitField<33, 3, ImageType> image_type;
|
||||
BitField<49, 2, OutOfBoundsStore> out_of_bounds_store;
|
||||
BitField<51, 1, u64> is_immediate;
|
||||
BitField<52, 1, SurfaceDataMode> mode;
|
||||
|
||||
BitField<20, 3, StoreType> store_data_layout;
|
||||
BitField<20, 4, u64> component_mask_selector;
|
||||
|
||||
bool IsComponentEnabled(std::size_t component) const {
|
||||
ASSERT(mode == SurfaceDataMode::P);
|
||||
constexpr u8 R = 0b0001;
|
||||
constexpr u8 G = 0b0010;
|
||||
constexpr u8 B = 0b0100;
|
||||
constexpr u8 A = 0b1000;
|
||||
constexpr std::array<u8, 16> mask = {
|
||||
0, (R), (G), (R | G), (B), (R | B),
|
||||
(G | B), (R | G | B), (A), (R | A), (G | A), (R | G | A),
|
||||
(B | A), (R | B | A), (G | B | A), (R | G | B | A)};
|
||||
return std::bitset<4>{mask.at(component_mask_selector)}.test(component);
|
||||
}
|
||||
|
||||
StoreType GetStoreDataLayout() const {
|
||||
ASSERT(mode == SurfaceDataMode::D_BA);
|
||||
return store_data_layout;
|
||||
}
|
||||
} sust;
|
||||
|
||||
union {
|
||||
BitField<20, 24, u64> target;
|
||||
BitField<5, 1, u64> constant_buffer;
|
||||
@@ -1371,6 +1444,7 @@ union Instruction {
|
||||
|
||||
Attribute attribute;
|
||||
Sampler sampler;
|
||||
Image image;
|
||||
|
||||
u64 value;
|
||||
};
|
||||
@@ -1408,11 +1482,13 @@ public:
|
||||
TXQ, // Texture Query
|
||||
TXQ_B, // Texture Query Bindless
|
||||
TEXS, // Texture Fetch with scalar/non-vec4 source/destinations
|
||||
TLD, // Texture Load
|
||||
TLDS, // Texture Load with scalar/non-vec4 source/destinations
|
||||
TLD4, // Texture Load 4
|
||||
TLD4S, // Texture Load 4 with scalar / non - vec4 source / destinations
|
||||
TMML_B, // Texture Mip Map Level
|
||||
TMML, // Texture Mip Map Level
|
||||
SUST, // Surface Store
|
||||
EXIT,
|
||||
IPA,
|
||||
OUT_R, // Emit vertex/primitive
|
||||
@@ -1543,6 +1619,7 @@ public:
|
||||
Synch,
|
||||
Memory,
|
||||
Texture,
|
||||
Image,
|
||||
FloatSet,
|
||||
FloatSetPredicate,
|
||||
IntegerSet,
|
||||
@@ -1682,11 +1759,13 @@ private:
|
||||
INST("1101111101001---", Id::TXQ, Type::Texture, "TXQ"),
|
||||
INST("1101111101010---", Id::TXQ_B, Type::Texture, "TXQ_B"),
|
||||
INST("1101-00---------", Id::TEXS, Type::Texture, "TEXS"),
|
||||
INST("11011100--11----", Id::TLD, Type::Texture, "TLD"),
|
||||
INST("1101101---------", Id::TLDS, Type::Texture, "TLDS"),
|
||||
INST("110010----111---", Id::TLD4, Type::Texture, "TLD4"),
|
||||
INST("1101111100------", Id::TLD4S, Type::Texture, "TLD4S"),
|
||||
INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),
|
||||
INST("1101111101011---", Id::TMML, Type::Texture, "TMML"),
|
||||
INST("11101011001-----", Id::SUST, Type::Image, "SUST"),
|
||||
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
|
||||
INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"),
|
||||
INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"),
|
||||
|
||||
@@ -202,11 +202,12 @@ const u8* MemoryManager::GetPointer(GPUVAddr addr) const {
|
||||
}
|
||||
|
||||
bool MemoryManager::IsBlockContinuous(const GPUVAddr start, const std::size_t size) const {
|
||||
const GPUVAddr end = start + size;
|
||||
const std::size_t inner_size = size - 1;
|
||||
const GPUVAddr end = start + inner_size;
|
||||
const auto host_ptr_start = reinterpret_cast<std::uintptr_t>(GetPointer(start));
|
||||
const auto host_ptr_end = reinterpret_cast<std::uintptr_t>(GetPointer(end));
|
||||
const auto range = static_cast<std::size_t>(host_ptr_end - host_ptr_start);
|
||||
return range == size;
|
||||
return range == inner_size;
|
||||
}
|
||||
|
||||
void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, const std::size_t size) const {
|
||||
|
||||
@@ -169,6 +169,8 @@ protected:
|
||||
object->MarkAsModified(false, *this);
|
||||
}
|
||||
|
||||
std::recursive_mutex mutex;
|
||||
|
||||
private:
|
||||
/// Returns a list of cached objects from the specified memory region, ordered by access time
|
||||
std::vector<T> GetSortedObjectsFromRegion(CacheAddr addr, u64 size) {
|
||||
@@ -208,5 +210,4 @@ private:
|
||||
IntervalCache interval_cache; ///< Cache of objects
|
||||
u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
std::recursive_mutex mutex;
|
||||
};
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
enum class LoadCallbackStage {
|
||||
@@ -46,8 +50,7 @@ public:
|
||||
/// Attempt to use a faster method to perform a surface copy
|
||||
virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& dst,
|
||||
const Common::Rectangle<u32>& src_rect,
|
||||
const Common::Rectangle<u32>& dst_rect) {
|
||||
const Tegra::Engines::Fermi2D::Config& copy_config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, std::size_t size)
|
||||
|
||||
GLintptr OGLBufferCache::UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment,
|
||||
bool cache) {
|
||||
std::lock_guard lock{mutex};
|
||||
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
|
||||
|
||||
// Cache management is a big overhead, so only cache entries with a given size.
|
||||
@@ -62,6 +63,7 @@ GLintptr OGLBufferCache::UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::
|
||||
|
||||
GLintptr OGLBufferCache::UploadHostMemory(const void* raw_pointer, std::size_t size,
|
||||
std::size_t alignment) {
|
||||
std::lock_guard lock{mutex};
|
||||
AlignBuffer(alignment);
|
||||
std::memcpy(buffer_ptr, raw_pointer, size);
|
||||
const GLintptr uploaded_offset = buffer_offset;
|
||||
|
||||
75
src/video_core/renderer_opengl/gl_framebuffer_cache.cpp
Normal file
75
src/video_core/renderer_opengl/gl_framebuffer_cache.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include "common/cityhash.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_opengl/gl_framebuffer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
FramebufferCacheOpenGL::FramebufferCacheOpenGL() = default;
|
||||
|
||||
FramebufferCacheOpenGL::~FramebufferCacheOpenGL() = default;
|
||||
|
||||
GLuint FramebufferCacheOpenGL::GetFramebuffer(const FramebufferCacheKey& key) {
|
||||
const auto [entry, is_cache_miss] = cache.try_emplace(key);
|
||||
auto& framebuffer{entry->second};
|
||||
if (is_cache_miss) {
|
||||
framebuffer = CreateFramebuffer(key);
|
||||
}
|
||||
return framebuffer.handle;
|
||||
}
|
||||
|
||||
OGLFramebuffer FramebufferCacheOpenGL::CreateFramebuffer(const FramebufferCacheKey& key) {
|
||||
OGLFramebuffer framebuffer;
|
||||
framebuffer.Create();
|
||||
|
||||
// TODO(Rodrigo): Use DSA here after Nvidia fixes their framebuffer DSA bugs.
|
||||
local_state.draw.draw_framebuffer = framebuffer.handle;
|
||||
local_state.ApplyFramebufferState();
|
||||
|
||||
if (key.is_single_buffer) {
|
||||
if (key.color_attachments[0] != GL_NONE && key.colors[0]) {
|
||||
key.colors[0]->Attach(key.color_attachments[0], GL_DRAW_FRAMEBUFFER);
|
||||
glDrawBuffer(key.color_attachments[0]);
|
||||
} else {
|
||||
glDrawBuffer(GL_NONE);
|
||||
}
|
||||
} else {
|
||||
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
if (key.colors[index]) {
|
||||
key.colors[index]->Attach(GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
|
||||
GL_DRAW_FRAMEBUFFER);
|
||||
}
|
||||
}
|
||||
glDrawBuffers(key.colors_count, key.color_attachments.data());
|
||||
}
|
||||
|
||||
if (key.zeta) {
|
||||
key.zeta->Attach(key.stencil_enable ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT,
|
||||
GL_DRAW_FRAMEBUFFER);
|
||||
}
|
||||
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
std::size_t FramebufferCacheKey::Hash() const {
|
||||
static_assert(sizeof(*this) % sizeof(u64) == 0, "Unaligned struct");
|
||||
return static_cast<std::size_t>(
|
||||
Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
|
||||
}
|
||||
|
||||
bool FramebufferCacheKey::operator==(const FramebufferCacheKey& rhs) const {
|
||||
return std::tie(is_single_buffer, stencil_enable, colors_count, color_attachments, colors,
|
||||
zeta) == std::tie(rhs.is_single_buffer, rhs.stencil_enable, rhs.colors_count,
|
||||
rhs.color_attachments, rhs.colors, rhs.zeta);
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
68
src/video_core/renderer_opengl/gl_framebuffer_cache.h
Normal file
68
src/video_core/renderer_opengl/gl_framebuffer_cache.h
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_texture_cache.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
struct alignas(sizeof(u64)) FramebufferCacheKey {
|
||||
bool is_single_buffer = false;
|
||||
bool stencil_enable = false;
|
||||
u16 colors_count = 0;
|
||||
|
||||
std::array<GLenum, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> color_attachments{};
|
||||
std::array<View, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> colors;
|
||||
View zeta;
|
||||
|
||||
std::size_t Hash() const;
|
||||
|
||||
bool operator==(const FramebufferCacheKey& rhs) const;
|
||||
|
||||
bool operator!=(const FramebufferCacheKey& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<OpenGL::FramebufferCacheKey> {
|
||||
std::size_t operator()(const OpenGL::FramebufferCacheKey& k) const noexcept {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class FramebufferCacheOpenGL {
|
||||
public:
|
||||
FramebufferCacheOpenGL();
|
||||
~FramebufferCacheOpenGL();
|
||||
|
||||
GLuint GetFramebuffer(const FramebufferCacheKey& key);
|
||||
|
||||
private:
|
||||
OGLFramebuffer CreateFramebuffer(const FramebufferCacheKey& key);
|
||||
|
||||
OpenGLState local_state;
|
||||
std::unordered_map<FramebufferCacheKey, OGLFramebuffer> cache;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
@@ -76,6 +76,7 @@ GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer)
|
||||
GlobalRegion GlobalRegionCacheOpenGL::GetGlobalRegion(
|
||||
const GLShader::GlobalMemoryEntry& global_region,
|
||||
Tegra::Engines::Maxwell3D::Regs::ShaderStage stage) {
|
||||
std::lock_guard lock{mutex};
|
||||
|
||||
auto& gpu{Core::System::GetInstance().GPU()};
|
||||
auto& memory_manager{gpu.MemoryManager()};
|
||||
|
||||
@@ -29,8 +29,10 @@
|
||||
namespace OpenGL {
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
using PixelFormat = VideoCore::Surface::PixelFormat;
|
||||
using SurfaceType = VideoCore::Surface::SurfaceType;
|
||||
|
||||
using VideoCore::Surface::PixelFormat;
|
||||
using VideoCore::Surface::SurfaceTarget;
|
||||
using VideoCore::Surface::SurfaceType;
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Format Setup", MP_RGB(128, 128, 192));
|
||||
MICROPROFILE_DEFINE(OpenGL_VB, "OpenGL", "Vertex Buffer Setup", MP_RGB(128, 128, 192));
|
||||
@@ -78,29 +80,9 @@ struct DrawParameters {
|
||||
}
|
||||
};
|
||||
|
||||
struct FramebufferCacheKey {
|
||||
bool is_single_buffer = false;
|
||||
bool stencil_enable = false;
|
||||
|
||||
std::array<GLenum, Maxwell::NumRenderTargets> color_attachments{};
|
||||
std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> colors{};
|
||||
u32 colors_count = 0;
|
||||
|
||||
GLuint zeta = 0;
|
||||
|
||||
auto Tie() const {
|
||||
return std::tie(is_single_buffer, stencil_enable, color_attachments, colors, colors_count,
|
||||
zeta);
|
||||
}
|
||||
|
||||
bool operator<(const FramebufferCacheKey& rhs) const {
|
||||
return Tie() < rhs.Tie();
|
||||
}
|
||||
};
|
||||
|
||||
RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
|
||||
ScreenInfo& info)
|
||||
: res_cache{*this}, shader_cache{*this, system, emu_window, device},
|
||||
: texture_cache{system, *this, device}, shader_cache{*this, system, emu_window, device},
|
||||
global_cache{*this}, system{system}, screen_info{info},
|
||||
buffer_cache(*this, STREAM_BUFFER_SIZE) {
|
||||
OpenGLState::ApplyDefaultState();
|
||||
@@ -121,11 +103,6 @@ void RasterizerOpenGL::CheckExtensions() {
|
||||
Render_OpenGL,
|
||||
"Anisotropic filter is not supported! This can cause graphical issues in some games.");
|
||||
}
|
||||
if (!GLAD_GL_ARB_buffer_storage) {
|
||||
LOG_WARNING(
|
||||
Render_OpenGL,
|
||||
"Buffer storage control is not supported! This can cause performance degradation.");
|
||||
}
|
||||
}
|
||||
|
||||
GLuint RasterizerOpenGL::SetupVertexFormat() {
|
||||
@@ -302,8 +279,14 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
static_cast<GLsizeiptr>(sizeof(ubo)));
|
||||
|
||||
Shader shader{shader_cache.GetStageProgram(program)};
|
||||
const auto [program_handle, next_bindings] =
|
||||
shader->GetProgramHandle(primitive_mode, base_bindings);
|
||||
|
||||
const auto stage_enum{static_cast<Maxwell::ShaderStage>(stage)};
|
||||
SetupDrawConstBuffers(stage_enum, shader);
|
||||
SetupGlobalRegions(stage_enum, shader);
|
||||
const auto texture_buffer_usage{SetupTextures(stage_enum, shader, base_bindings)};
|
||||
|
||||
const ProgramVariant variant{base_bindings, primitive_mode, texture_buffer_usage};
|
||||
const auto [program_handle, next_bindings] = shader->GetProgramHandle(variant);
|
||||
|
||||
switch (program) {
|
||||
case Maxwell::ShaderProgram::VertexA:
|
||||
@@ -321,11 +304,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
shader_config.enable.Value(), shader_config.offset);
|
||||
}
|
||||
|
||||
const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage);
|
||||
SetupDrawConstBuffers(stage_enum, shader);
|
||||
SetupGlobalRegions(stage_enum, shader);
|
||||
SetupTextures(stage_enum, shader, base_bindings);
|
||||
|
||||
// Workaround for Intel drivers.
|
||||
// When a clip distance is enabled but not set in the shader it crops parts of the screen
|
||||
// (sometimes it's half the screen, sometimes three quarters). To avoid this, enable the
|
||||
@@ -351,44 +329,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
gpu.dirty_flags.shaders = false;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey,
|
||||
OpenGLState& current_state) {
|
||||
const auto [entry, is_cache_miss] = framebuffer_cache.try_emplace(fbkey);
|
||||
auto& framebuffer = entry->second;
|
||||
|
||||
if (is_cache_miss)
|
||||
framebuffer.Create();
|
||||
|
||||
current_state.draw.draw_framebuffer = framebuffer.handle;
|
||||
current_state.ApplyFramebufferState();
|
||||
|
||||
if (!is_cache_miss)
|
||||
return;
|
||||
|
||||
if (fbkey.is_single_buffer) {
|
||||
if (fbkey.color_attachments[0] != GL_NONE) {
|
||||
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, fbkey.color_attachments[0], fbkey.colors[0],
|
||||
0);
|
||||
}
|
||||
glDrawBuffer(fbkey.color_attachments[0]);
|
||||
} else {
|
||||
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
if (fbkey.colors[index]) {
|
||||
glFramebufferTexture(GL_DRAW_FRAMEBUFFER,
|
||||
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
|
||||
fbkey.colors[index], 0);
|
||||
}
|
||||
}
|
||||
glDrawBuffers(fbkey.colors_count, fbkey.color_attachments.data());
|
||||
}
|
||||
|
||||
if (fbkey.zeta) {
|
||||
GLenum zeta_attachment =
|
||||
fbkey.stencil_enable ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
|
||||
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, zeta_attachment, fbkey.zeta, 0);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
|
||||
@@ -478,9 +418,13 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
|
||||
}
|
||||
current_framebuffer_config_state = fb_config_state;
|
||||
|
||||
Surface depth_surface;
|
||||
texture_cache.GuardRenderTargets(true);
|
||||
|
||||
View depth_surface{};
|
||||
if (using_depth_fb) {
|
||||
depth_surface = res_cache.GetDepthBufferSurface(preserve_contents);
|
||||
depth_surface = texture_cache.GetDepthBufferSurface(preserve_contents);
|
||||
} else {
|
||||
texture_cache.SetEmptyDepthBuffer();
|
||||
}
|
||||
|
||||
UNIMPLEMENTED_IF(regs.rt_separate_frag_data == 0);
|
||||
@@ -493,13 +437,13 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
|
||||
if (using_color_fb) {
|
||||
if (single_color_target) {
|
||||
// Used when just a single color attachment is enabled, e.g. for clearing a color buffer
|
||||
Surface color_surface =
|
||||
res_cache.GetColorBufferSurface(*single_color_target, preserve_contents);
|
||||
View color_surface{
|
||||
texture_cache.GetColorBufferSurface(*single_color_target, preserve_contents)};
|
||||
|
||||
if (color_surface) {
|
||||
// Assume that a surface will be written to if it is used as a framebuffer, even if
|
||||
// the shader doesn't actually write to it.
|
||||
color_surface->MarkAsModified(true, res_cache);
|
||||
texture_cache.MarkColorBufferInUse(*single_color_target);
|
||||
// Workaround for and issue in nvidia drivers
|
||||
// https://devtalk.nvidia.com/default/topic/776591/opengl/gl_framebuffer_srgb-functions-incorrectly/
|
||||
state.framebuffer_srgb.enabled |= color_surface->GetSurfaceParams().srgb_conversion;
|
||||
@@ -508,16 +452,21 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
|
||||
fbkey.is_single_buffer = true;
|
||||
fbkey.color_attachments[0] =
|
||||
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target);
|
||||
fbkey.colors[0] = color_surface != nullptr ? color_surface->Texture().handle : 0;
|
||||
fbkey.colors[0] = color_surface;
|
||||
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
if (index != *single_color_target) {
|
||||
texture_cache.SetEmptyColorBuffer(index);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Multiple color attachments are enabled
|
||||
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents);
|
||||
View color_surface{texture_cache.GetColorBufferSurface(index, preserve_contents)};
|
||||
|
||||
if (color_surface) {
|
||||
// Assume that a surface will be written to if it is used as a framebuffer, even
|
||||
// if the shader doesn't actually write to it.
|
||||
color_surface->MarkAsModified(true, res_cache);
|
||||
texture_cache.MarkColorBufferInUse(index);
|
||||
// Enable sRGB only for supported formats
|
||||
// Workaround for and issue in nvidia drivers
|
||||
// https://devtalk.nvidia.com/default/topic/776591/opengl/gl_framebuffer_srgb-functions-incorrectly/
|
||||
@@ -527,8 +476,7 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
|
||||
|
||||
fbkey.color_attachments[index] =
|
||||
GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
|
||||
fbkey.colors[index] =
|
||||
color_surface != nullptr ? color_surface->Texture().handle : 0;
|
||||
fbkey.colors[index] = color_surface;
|
||||
}
|
||||
fbkey.is_single_buffer = false;
|
||||
fbkey.colors_count = regs.rt_control.count;
|
||||
@@ -541,14 +489,16 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
|
||||
if (depth_surface) {
|
||||
// Assume that a surface will be written to if it is used as a framebuffer, even if
|
||||
// the shader doesn't actually write to it.
|
||||
depth_surface->MarkAsModified(true, res_cache);
|
||||
texture_cache.MarkDepthBufferInUse();
|
||||
|
||||
fbkey.zeta = depth_surface->Texture().handle;
|
||||
fbkey.zeta = depth_surface;
|
||||
fbkey.stencil_enable = regs.stencil_enable &&
|
||||
depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil;
|
||||
}
|
||||
|
||||
SetupCachedFramebuffer(fbkey, current_state);
|
||||
texture_cache.GuardRenderTargets(false);
|
||||
|
||||
current_state.draw.draw_framebuffer = framebuffer_cache.GetFramebuffer(fbkey);
|
||||
SyncViewport(current_state);
|
||||
|
||||
return current_depth_stencil_usage = {static_cast<bool>(depth_surface), fbkey.stencil_enable};
|
||||
@@ -630,6 +580,7 @@ void RasterizerOpenGL::Clear() {
|
||||
clear_state.ApplyDepth();
|
||||
clear_state.ApplyStencilTest();
|
||||
clear_state.ApplyViewport();
|
||||
clear_state.ApplyFramebufferState();
|
||||
|
||||
if (use_color) {
|
||||
glClearBufferfv(GL_COLOR, regs.clear_buffers.RT, regs.clear_color);
|
||||
@@ -652,7 +603,6 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
|
||||
ConfigureFramebuffers(state);
|
||||
SyncColorMask();
|
||||
SyncFragmentColorClampState();
|
||||
SyncMultiSampleState();
|
||||
@@ -697,16 +647,22 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
SetupVertexBuffer(vao);
|
||||
|
||||
DrawParameters params = SetupDraw();
|
||||
texture_cache.GuardSamplers(true);
|
||||
SetupShaders(params.primitive_mode);
|
||||
texture_cache.GuardSamplers(false);
|
||||
|
||||
ConfigureFramebuffers(state);
|
||||
|
||||
buffer_cache.Unmap();
|
||||
|
||||
shader_program_manager->ApplyTo(state);
|
||||
state.Apply();
|
||||
|
||||
res_cache.SignalPreDrawCall();
|
||||
if (texture_cache.TextureBarrier()) {
|
||||
glTextureBarrier();
|
||||
}
|
||||
|
||||
params.DispatchDraw();
|
||||
res_cache.SignalPostDrawCall();
|
||||
|
||||
accelerate_draw = AccelDraw::Disabled;
|
||||
}
|
||||
@@ -718,7 +674,7 @@ void RasterizerOpenGL::FlushRegion(CacheAddr addr, u64 size) {
|
||||
if (!addr || !size) {
|
||||
return;
|
||||
}
|
||||
res_cache.FlushRegion(addr, size);
|
||||
texture_cache.FlushRegion(addr, size);
|
||||
global_cache.FlushRegion(addr, size);
|
||||
}
|
||||
|
||||
@@ -727,23 +683,24 @@ void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) {
|
||||
if (!addr || !size) {
|
||||
return;
|
||||
}
|
||||
res_cache.InvalidateRegion(addr, size);
|
||||
texture_cache.InvalidateRegion(addr, size);
|
||||
shader_cache.InvalidateRegion(addr, size);
|
||||
global_cache.InvalidateRegion(addr, size);
|
||||
buffer_cache.InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
|
||||
FlushRegion(addr, size);
|
||||
if (Settings::values.use_accurate_gpu_emulation) {
|
||||
FlushRegion(addr, size);
|
||||
}
|
||||
InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& dst,
|
||||
const Common::Rectangle<u32>& src_rect,
|
||||
const Common::Rectangle<u32>& dst_rect) {
|
||||
const Tegra::Engines::Fermi2D::Config& copy_config) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Blits);
|
||||
res_cache.FermiCopySurface(src, dst, src_rect, dst_rect);
|
||||
texture_cache.DoFermiCopy(src, dst, copy_config);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -755,7 +712,8 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
|
||||
const auto& surface{res_cache.TryFindFramebufferSurface(Memory::GetPointer(framebuffer_addr))};
|
||||
const auto surface{
|
||||
texture_cache.TryFindFramebufferSurface(Memory::GetPointer(framebuffer_addr))};
|
||||
if (!surface) {
|
||||
return {};
|
||||
}
|
||||
@@ -771,7 +729,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
|
||||
LOG_WARNING(Render_OpenGL, "Framebuffer pixel_format is different");
|
||||
}
|
||||
|
||||
screen_info.display_texture = surface->Texture().handle;
|
||||
screen_info.display_texture = surface->GetTexture();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -837,8 +795,8 @@ void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::Shade
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader,
|
||||
BaseBindings base_bindings) {
|
||||
TextureBufferUsage RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader,
|
||||
BaseBindings base_bindings) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Texture);
|
||||
const auto& gpu = system.GPU();
|
||||
const auto& maxwell3d = gpu.Maxwell3D();
|
||||
@@ -847,6 +805,8 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s
|
||||
ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.texture_units),
|
||||
"Exceeded the number of active textures.");
|
||||
|
||||
TextureBufferUsage texture_buffer_usage{0};
|
||||
|
||||
for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
|
||||
const auto& entry = entries[bindpoint];
|
||||
Tegra::Texture::FullTextureInfo texture;
|
||||
@@ -860,18 +820,26 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s
|
||||
}
|
||||
const u32 current_bindpoint = base_bindings.sampler + bindpoint;
|
||||
|
||||
state.texture_units[current_bindpoint].sampler = sampler_cache.GetSampler(texture.tsc);
|
||||
auto& unit{state.texture_units[current_bindpoint]};
|
||||
unit.sampler = sampler_cache.GetSampler(texture.tsc);
|
||||
|
||||
if (Surface surface = res_cache.GetTextureSurface(texture, entry); surface) {
|
||||
state.texture_units[current_bindpoint].texture =
|
||||
surface->Texture(entry.IsArray()).handle;
|
||||
surface->UpdateSwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source,
|
||||
if (const auto view{texture_cache.GetTextureSurface(texture, entry)}; view) {
|
||||
if (view->GetSurfaceParams().IsBuffer()) {
|
||||
// Record that this texture is a texture buffer.
|
||||
texture_buffer_usage.set(bindpoint);
|
||||
} else {
|
||||
// Apply swizzle to textures that are not buffers.
|
||||
view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source,
|
||||
texture.tic.w_source);
|
||||
}
|
||||
state.texture_units[current_bindpoint].texture = view->GetTexture();
|
||||
} else {
|
||||
// Can occur when texture addr is null or its memory is unmapped/invalid
|
||||
state.texture_units[current_bindpoint].texture = 0;
|
||||
unit.texture = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return texture_buffer_usage;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
|
||||
|
||||
@@ -23,14 +23,15 @@
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_opengl/gl_buffer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_device.h"
|
||||
#include "video_core/renderer_opengl/gl_framebuffer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_global_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_sampler_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_texture_cache.h"
|
||||
#include "video_core/renderer_opengl/utils.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -41,11 +42,14 @@ namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
|
||||
namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
struct ScreenInfo;
|
||||
struct DrawParameters;
|
||||
struct FramebufferCacheKey;
|
||||
|
||||
class RasterizerOpenGL : public VideoCore::RasterizerInterface {
|
||||
public:
|
||||
@@ -61,8 +65,7 @@ public:
|
||||
void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
|
||||
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& dst,
|
||||
const Common::Rectangle<u32>& src_rect,
|
||||
const Common::Rectangle<u32>& dst_rect) override;
|
||||
const Tegra::Engines::Fermi2D::Config& copy_config) override;
|
||||
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
|
||||
u32 pixel_stride) override;
|
||||
bool AccelerateDrawBatch(bool is_indexed) override;
|
||||
@@ -95,6 +98,8 @@ private:
|
||||
|
||||
/**
|
||||
* Configures the color and depth framebuffer states.
|
||||
* @param must_reconfigure If true, tells the framebuffer to skip the cache and reconfigure
|
||||
* again. Used by the texture cache to solve texception conflicts
|
||||
* @param use_color_fb If true, configure color framebuffers.
|
||||
* @param using_depth_fb If true, configure the depth/stencil framebuffer.
|
||||
* @param preserve_contents If true, tries to preserve data from a previously used framebuffer.
|
||||
@@ -118,9 +123,10 @@ private:
|
||||
void SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
|
||||
const Shader& shader);
|
||||
|
||||
/// Configures the current textures to use for the draw command.
|
||||
void SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader,
|
||||
BaseBindings base_bindings);
|
||||
/// Configures the current textures to use for the draw command. Returns shaders texture buffer
|
||||
/// usage.
|
||||
TextureBufferUsage SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
|
||||
const Shader& shader, BaseBindings base_bindings);
|
||||
|
||||
/// Syncs the viewport and depth range to match the guest state
|
||||
void SyncViewport(OpenGLState& current_state);
|
||||
@@ -181,10 +187,11 @@ private:
|
||||
const Device device;
|
||||
OpenGLState state;
|
||||
|
||||
RasterizerCacheOpenGL res_cache;
|
||||
TextureCacheOpenGL texture_cache;
|
||||
ShaderCacheOpenGL shader_cache;
|
||||
GlobalRegionCacheOpenGL global_cache;
|
||||
SamplerCacheOpenGL sampler_cache;
|
||||
FramebufferCacheOpenGL framebuffer_cache;
|
||||
|
||||
Core::System& system;
|
||||
ScreenInfo& screen_info;
|
||||
@@ -195,7 +202,6 @@ private:
|
||||
OGLVertexArray>
|
||||
vertex_array_cache;
|
||||
|
||||
std::map<FramebufferCacheKey, OGLFramebuffer> framebuffer_cache;
|
||||
FramebufferConfigState current_framebuffer_config_state;
|
||||
std::pair<bool, bool> current_depth_stencil_usage{};
|
||||
|
||||
@@ -218,8 +224,6 @@ private:
|
||||
|
||||
void SetupShaders(GLenum primitive_mode);
|
||||
|
||||
void SetupCachedFramebuffer(const FramebufferCacheKey& fbkey, OpenGLState& current_state);
|
||||
|
||||
enum class AccelDraw { Disabled, Arrays, Indexed };
|
||||
AccelDraw accelerate_draw = AccelDraw::Disabled;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,572 +0,0 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/bit_util.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/engines/maxwell_3d.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"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class CachedSurface;
|
||||
using Surface = std::shared_ptr<CachedSurface>;
|
||||
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, Common::Rectangle<u32>>;
|
||||
|
||||
using SurfaceTarget = VideoCore::Surface::SurfaceTarget;
|
||||
using SurfaceType = VideoCore::Surface::SurfaceType;
|
||||
using PixelFormat = VideoCore::Surface::PixelFormat;
|
||||
using ComponentType = VideoCore::Surface::ComponentType;
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
struct SurfaceParams {
|
||||
enum class SurfaceClass {
|
||||
Uploaded,
|
||||
RenderTarget,
|
||||
DepthBuffer,
|
||||
Copy,
|
||||
};
|
||||
|
||||
static std::string SurfaceTargetName(SurfaceTarget target) {
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1D:
|
||||
return "Texture1D";
|
||||
case SurfaceTarget::Texture2D:
|
||||
return "Texture2D";
|
||||
case SurfaceTarget::Texture3D:
|
||||
return "Texture3D";
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
return "Texture1DArray";
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
return "Texture2DArray";
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
return "TextureCubemap";
|
||||
case SurfaceTarget::TextureCubeArray:
|
||||
return "TextureCubeArray";
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
|
||||
UNREACHABLE();
|
||||
return fmt::format("TextureUnknown({})", static_cast<u32>(target));
|
||||
}
|
||||
}
|
||||
|
||||
u32 GetFormatBpp() const {
|
||||
return VideoCore::Surface::GetFormatBpp(pixel_format);
|
||||
}
|
||||
|
||||
/// Returns the rectangle corresponding to this surface
|
||||
Common::Rectangle<u32> GetRect(u32 mip_level = 0) const;
|
||||
|
||||
/// Returns the total size of this surface in bytes, adjusted for compression
|
||||
std::size_t SizeInBytesRaw(bool ignore_tiled = false) const {
|
||||
const u32 compression_factor{GetCompressionFactor(pixel_format)};
|
||||
const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)};
|
||||
const size_t uncompressed_size{
|
||||
Tegra::Texture::CalculateSize((ignore_tiled ? false : is_tiled), bytes_per_pixel, width,
|
||||
height, depth, block_height, block_depth)};
|
||||
|
||||
// Divide by compression_factor^2, as height and width are factored by this
|
||||
return uncompressed_size / (compression_factor * compression_factor);
|
||||
}
|
||||
|
||||
/// Returns the size of this surface as an OpenGL texture in bytes
|
||||
std::size_t SizeInBytesGL() const {
|
||||
return SizeInBytesRaw(true);
|
||||
}
|
||||
|
||||
/// Returns the size of this surface as a cube face in bytes
|
||||
std::size_t SizeInBytesCubeFace() const {
|
||||
return size_in_bytes / 6;
|
||||
}
|
||||
|
||||
/// Returns the size of this surface as an OpenGL cube face in bytes
|
||||
std::size_t SizeInBytesCubeFaceGL() const {
|
||||
return size_in_bytes_gl / 6;
|
||||
}
|
||||
|
||||
/// Returns the exact size of memory occupied by the texture in VRAM, including mipmaps.
|
||||
std::size_t MemorySize() const {
|
||||
std::size_t size = InnerMemorySize(false, is_layered);
|
||||
if (is_layered)
|
||||
return size * depth;
|
||||
return size;
|
||||
}
|
||||
|
||||
/// Returns true if the parameters constitute a valid rasterizer surface.
|
||||
bool IsValid() const {
|
||||
return gpu_addr && host_ptr && height && width;
|
||||
}
|
||||
|
||||
/// Returns the exact size of the memory occupied by a layer in a texture in VRAM, including
|
||||
/// mipmaps.
|
||||
std::size_t LayerMemorySize() const {
|
||||
return InnerMemorySize(false, true);
|
||||
}
|
||||
|
||||
/// Returns the size of a layer of this surface in OpenGL.
|
||||
std::size_t LayerSizeGL(u32 mip_level) const {
|
||||
return InnerMipmapMemorySize(mip_level, true, is_layered, false);
|
||||
}
|
||||
|
||||
std::size_t GetMipmapSizeGL(u32 mip_level, bool ignore_compressed = true) const {
|
||||
std::size_t size = InnerMipmapMemorySize(mip_level, true, is_layered, ignore_compressed);
|
||||
if (is_layered)
|
||||
return size * depth;
|
||||
return size;
|
||||
}
|
||||
|
||||
std::size_t GetMipmapLevelOffset(u32 mip_level) const {
|
||||
std::size_t offset = 0;
|
||||
for (u32 i = 0; i < mip_level; i++)
|
||||
offset += InnerMipmapMemorySize(i, false, is_layered);
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::size_t GetMipmapLevelOffsetGL(u32 mip_level) const {
|
||||
std::size_t offset = 0;
|
||||
for (u32 i = 0; i < mip_level; i++)
|
||||
offset += InnerMipmapMemorySize(i, true, is_layered);
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::size_t GetMipmapSingleSize(u32 mip_level) const {
|
||||
return InnerMipmapMemorySize(mip_level, false, is_layered);
|
||||
}
|
||||
|
||||
u32 MipWidth(u32 mip_level) const {
|
||||
return std::max(1U, width >> mip_level);
|
||||
}
|
||||
|
||||
u32 MipWidthGobAligned(u32 mip_level) const {
|
||||
return Common::AlignUp(std::max(1U, width >> mip_level), 64U * 8U / GetFormatBpp());
|
||||
}
|
||||
|
||||
u32 MipHeight(u32 mip_level) const {
|
||||
return std::max(1U, height >> mip_level);
|
||||
}
|
||||
|
||||
u32 MipDepth(u32 mip_level) const {
|
||||
return is_layered ? depth : std::max(1U, depth >> mip_level);
|
||||
}
|
||||
|
||||
// Auto block resizing algorithm from:
|
||||
// https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c
|
||||
u32 MipBlockHeight(u32 mip_level) const {
|
||||
if (mip_level == 0)
|
||||
return block_height;
|
||||
u32 alt_height = MipHeight(mip_level);
|
||||
u32 h = GetDefaultBlockHeight(pixel_format);
|
||||
u32 blocks_in_y = (alt_height + h - 1) / h;
|
||||
u32 bh = 16;
|
||||
while (bh > 1 && blocks_in_y <= bh * 4) {
|
||||
bh >>= 1;
|
||||
}
|
||||
return bh;
|
||||
}
|
||||
|
||||
u32 MipBlockDepth(u32 mip_level) const {
|
||||
if (mip_level == 0) {
|
||||
return block_depth;
|
||||
}
|
||||
|
||||
if (is_layered) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const u32 mip_depth = MipDepth(mip_level);
|
||||
u32 bd = 32;
|
||||
while (bd > 1 && mip_depth * 2 <= bd) {
|
||||
bd >>= 1;
|
||||
}
|
||||
|
||||
if (bd == 32) {
|
||||
const u32 bh = MipBlockHeight(mip_level);
|
||||
if (bh >= 4) {
|
||||
return 16;
|
||||
}
|
||||
}
|
||||
|
||||
return bd;
|
||||
}
|
||||
|
||||
u32 RowAlign(u32 mip_level) const {
|
||||
const u32 m_width = MipWidth(mip_level);
|
||||
const u32 bytes_per_pixel = GetBytesPerPixel(pixel_format);
|
||||
const u32 l2 = Common::CountTrailingZeroes32(m_width * bytes_per_pixel);
|
||||
return (1U << l2);
|
||||
}
|
||||
|
||||
/// Creates SurfaceParams from a texture configuration
|
||||
static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config,
|
||||
const GLShader::SamplerEntry& entry);
|
||||
|
||||
/// Creates SurfaceParams from a framebuffer configuration
|
||||
static SurfaceParams CreateForFramebuffer(std::size_t index);
|
||||
|
||||
/// Creates SurfaceParams for a depth buffer configuration
|
||||
static SurfaceParams CreateForDepthBuffer(
|
||||
u32 zeta_width, u32 zeta_height, GPUVAddr zeta_address, Tegra::DepthFormat format,
|
||||
u32 block_width, u32 block_height, u32 block_depth,
|
||||
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type);
|
||||
|
||||
/// Creates SurfaceParams for a Fermi2D surface copy
|
||||
static SurfaceParams CreateForFermiCopySurface(
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& config);
|
||||
|
||||
/// Checks if surfaces are compatible for caching
|
||||
bool IsCompatibleSurface(const SurfaceParams& other) const {
|
||||
if (std::tie(pixel_format, type, width, height, target, depth, is_tiled) ==
|
||||
std::tie(other.pixel_format, other.type, other.width, other.height, other.target,
|
||||
other.depth, other.is_tiled)) {
|
||||
if (!is_tiled)
|
||||
return true;
|
||||
return std::tie(block_height, block_depth, tile_width_spacing) ==
|
||||
std::tie(other.block_height, other.block_depth, other.tile_width_spacing);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Initializes parameters for caching, should be called after everything has been initialized
|
||||
void InitCacheParameters(GPUVAddr gpu_addr);
|
||||
|
||||
std::string TargetName() const {
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1D:
|
||||
return "1D";
|
||||
case SurfaceTarget::Texture2D:
|
||||
return "2D";
|
||||
case SurfaceTarget::Texture3D:
|
||||
return "3D";
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
return "1DArray";
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
return "2DArray";
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
return "Cube";
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
|
||||
UNREACHABLE();
|
||||
return fmt::format("TUK({})", static_cast<u32>(target));
|
||||
}
|
||||
}
|
||||
|
||||
std::string ClassName() const {
|
||||
switch (identity) {
|
||||
case SurfaceClass::Uploaded:
|
||||
return "UP";
|
||||
case SurfaceClass::RenderTarget:
|
||||
return "RT";
|
||||
case SurfaceClass::DepthBuffer:
|
||||
return "DB";
|
||||
case SurfaceClass::Copy:
|
||||
return "CP";
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented surface_class={}", static_cast<u32>(identity));
|
||||
UNREACHABLE();
|
||||
return fmt::format("CUK({})", static_cast<u32>(identity));
|
||||
}
|
||||
}
|
||||
|
||||
std::string IdentityString() const {
|
||||
return ClassName() + '_' + TargetName() + '_' + (is_tiled ? 'T' : 'L');
|
||||
}
|
||||
|
||||
bool is_tiled;
|
||||
u32 block_width;
|
||||
u32 block_height;
|
||||
u32 block_depth;
|
||||
u32 tile_width_spacing;
|
||||
PixelFormat pixel_format;
|
||||
ComponentType component_type;
|
||||
SurfaceType type;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 depth;
|
||||
u32 unaligned_height;
|
||||
u32 pitch;
|
||||
SurfaceTarget target;
|
||||
SurfaceClass identity;
|
||||
u32 max_mip_level;
|
||||
bool is_layered;
|
||||
bool is_array;
|
||||
bool srgb_conversion;
|
||||
// Parameters used for caching
|
||||
u8* host_ptr;
|
||||
GPUVAddr gpu_addr;
|
||||
std::size_t size_in_bytes;
|
||||
std::size_t size_in_bytes_gl;
|
||||
|
||||
// Render target specific parameters, not used in caching
|
||||
struct {
|
||||
u32 index;
|
||||
u32 array_mode;
|
||||
u32 volume;
|
||||
u32 layer_stride;
|
||||
u32 base_layer;
|
||||
} rt;
|
||||
|
||||
private:
|
||||
std::size_t InnerMipmapMemorySize(u32 mip_level, bool force_gl = false, bool layer_only = false,
|
||||
bool uncompressed = false) const;
|
||||
std::size_t InnerMemorySize(bool force_gl = false, bool layer_only = false,
|
||||
bool uncompressed = false) const;
|
||||
};
|
||||
|
||||
}; // namespace OpenGL
|
||||
|
||||
/// Hashable variation of SurfaceParams, used for a key in the surface cache
|
||||
struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> {
|
||||
static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) {
|
||||
SurfaceReserveKey res;
|
||||
res.state = params;
|
||||
res.state.identity = {}; // Ignore the origin of the texture
|
||||
res.state.gpu_addr = {}; // Ignore GPU vaddr in caching
|
||||
res.state.rt = {}; // Ignore rt config in caching
|
||||
return res;
|
||||
}
|
||||
};
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<SurfaceReserveKey> {
|
||||
std::size_t operator()(const SurfaceReserveKey& k) const {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class RasterizerOpenGL;
|
||||
|
||||
// This is used to store temporary big buffers,
|
||||
// instead of creating/destroying all the time
|
||||
struct RasterizerTemporaryMemory {
|
||||
std::vector<std::vector<u8>> gl_buffer;
|
||||
};
|
||||
|
||||
class CachedSurface final : public RasterizerCacheObject {
|
||||
public:
|
||||
explicit CachedSurface(const SurfaceParams& params);
|
||||
|
||||
VAddr GetCpuAddr() const override {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
return cached_size_in_bytes;
|
||||
}
|
||||
|
||||
std::size_t GetMemorySize() const {
|
||||
return memory_size;
|
||||
}
|
||||
|
||||
const OGLTexture& Texture() const {
|
||||
return texture;
|
||||
}
|
||||
|
||||
const OGLTexture& Texture(bool as_array) {
|
||||
if (params.is_array == as_array) {
|
||||
return texture;
|
||||
} else {
|
||||
EnsureTextureDiscrepantView();
|
||||
return discrepant_view;
|
||||
}
|
||||
}
|
||||
|
||||
GLenum Target() const {
|
||||
return gl_target;
|
||||
}
|
||||
|
||||
const SurfaceParams& GetSurfaceParams() const {
|
||||
return params;
|
||||
}
|
||||
|
||||
// Read/Write data in Switch memory to/from gl_buffer
|
||||
void LoadGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem);
|
||||
void FlushGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem);
|
||||
|
||||
// Upload data in gl_buffer to this surface's texture
|
||||
void UploadGLTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, GLuint read_fb_handle,
|
||||
GLuint draw_fb_handle);
|
||||
|
||||
void UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x,
|
||||
Tegra::Texture::SwizzleSource swizzle_y,
|
||||
Tegra::Texture::SwizzleSource swizzle_z,
|
||||
Tegra::Texture::SwizzleSource swizzle_w);
|
||||
|
||||
void MarkReinterpreted() {
|
||||
reinterpreted = true;
|
||||
}
|
||||
|
||||
bool IsReinterpreted() const {
|
||||
return reinterpreted;
|
||||
}
|
||||
|
||||
void MarkForReload(bool reload) {
|
||||
must_reload = reload;
|
||||
}
|
||||
|
||||
bool MustReload() const {
|
||||
return must_reload;
|
||||
}
|
||||
|
||||
bool IsUploaded() const {
|
||||
return params.identity == SurfaceParams::SurfaceClass::Uploaded;
|
||||
}
|
||||
|
||||
private:
|
||||
void UploadGLMipmapTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, u32 mip_map,
|
||||
GLuint read_fb_handle, GLuint draw_fb_handle);
|
||||
|
||||
void EnsureTextureDiscrepantView();
|
||||
|
||||
OGLTexture texture;
|
||||
OGLTexture discrepant_view;
|
||||
SurfaceParams params{};
|
||||
GLenum gl_target{};
|
||||
GLenum gl_internal_format{};
|
||||
std::size_t cached_size_in_bytes{};
|
||||
std::array<GLenum, 4> swizzle{GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
|
||||
std::size_t memory_size;
|
||||
bool reinterpreted = false;
|
||||
bool must_reload = false;
|
||||
VAddr cpu_addr{};
|
||||
};
|
||||
|
||||
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
|
||||
public:
|
||||
explicit RasterizerCacheOpenGL(RasterizerOpenGL& rasterizer);
|
||||
|
||||
/// Get a surface based on the texture configuration
|
||||
Surface GetTextureSurface(const Tegra::Texture::FullTextureInfo& config,
|
||||
const GLShader::SamplerEntry& entry);
|
||||
|
||||
/// Get the depth surface based on the framebuffer configuration
|
||||
Surface GetDepthBufferSurface(bool preserve_contents);
|
||||
|
||||
/// Get the color surface based on the framebuffer configuration and the specified render target
|
||||
Surface GetColorBufferSurface(std::size_t index, bool preserve_contents);
|
||||
|
||||
/// Tries to find a framebuffer using on the provided CPU address
|
||||
Surface TryFindFramebufferSurface(const u8* host_ptr) const;
|
||||
|
||||
/// Copies the contents of one surface to another
|
||||
void FermiCopySurface(const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& dst_config,
|
||||
const Common::Rectangle<u32>& src_rect,
|
||||
const Common::Rectangle<u32>& dst_rect);
|
||||
|
||||
void SignalPreDrawCall();
|
||||
void SignalPostDrawCall();
|
||||
|
||||
protected:
|
||||
void FlushObjectInner(const Surface& object) override {
|
||||
object->FlushGLBuffer(temporal_memory);
|
||||
}
|
||||
|
||||
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& old_surface, const SurfaceParams& new_params);
|
||||
|
||||
/// Reserves a unique surface that can be reused later
|
||||
void ReserveSurface(const Surface& surface);
|
||||
|
||||
/// Tries to get a reserved surface for the specified parameters
|
||||
Surface TryGetReservedSurface(const SurfaceParams& params);
|
||||
|
||||
// Partialy reinterpret a surface based on a triggering_surface that collides with it.
|
||||
// returns true if the reinterpret was successful, false in case it was not.
|
||||
bool PartialReinterpretSurface(Surface triggering_surface, Surface intersect);
|
||||
|
||||
/// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data
|
||||
void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface);
|
||||
void FastLayeredCopySurface(const Surface& src_surface, const Surface& dst_surface);
|
||||
void FastCopySurface(const Surface& src_surface, const Surface& dst_surface);
|
||||
void CopySurface(const Surface& src_surface, const Surface& dst_surface,
|
||||
const GLuint copy_pbo_handle, const GLenum src_attachment = 0,
|
||||
const GLenum dst_attachment = 0, const std::size_t cubemap_face = 0);
|
||||
|
||||
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
|
||||
/// previously been used. This is to prevent surfaces from being constantly created and
|
||||
/// destroyed when used with different surface parameters.
|
||||
std::unordered_map<SurfaceReserveKey, Surface> surface_reserve;
|
||||
|
||||
OGLFramebuffer read_framebuffer;
|
||||
OGLFramebuffer draw_framebuffer;
|
||||
|
||||
bool texception = false;
|
||||
|
||||
/// Use a Pixel Buffer Object to download the previous texture and then upload it to the new one
|
||||
/// using the new format.
|
||||
OGLBuffer copy_pbo;
|
||||
|
||||
std::array<Surface, Maxwell::NumRenderTargets> last_color_buffers;
|
||||
std::array<Surface, Maxwell::NumRenderTargets> current_color_buffers;
|
||||
Surface last_depth_buffer;
|
||||
|
||||
RasterizerTemporaryMemory temporal_memory;
|
||||
|
||||
using SurfaceIntervalCache = boost::icl::interval_map<CacheAddr, Surface>;
|
||||
using SurfaceInterval = typename SurfaceIntervalCache::interval_type;
|
||||
|
||||
static auto GetReinterpretInterval(const Surface& object) {
|
||||
return SurfaceInterval::right_open(object->GetCacheAddr() + 1,
|
||||
object->GetCacheAddr() + object->GetMemorySize() - 1);
|
||||
}
|
||||
|
||||
// Reinterpreted surfaces are very fragil as the game may keep rendering into them.
|
||||
SurfaceIntervalCache reinterpreted_surfaces;
|
||||
|
||||
void RegisterReinterpretSurface(Surface reinterpret_surface) {
|
||||
auto interval = GetReinterpretInterval(reinterpret_surface);
|
||||
reinterpreted_surfaces.insert({interval, reinterpret_surface});
|
||||
reinterpret_surface->MarkReinterpreted();
|
||||
}
|
||||
|
||||
Surface CollideOnReinterpretedSurface(CacheAddr addr) const {
|
||||
const SurfaceInterval interval{addr};
|
||||
for (auto& pair :
|
||||
boost::make_iterator_range(reinterpreted_surfaces.equal_range(interval))) {
|
||||
return pair.second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Register(const Surface& object) override {
|
||||
RasterizerCache<Surface>::Register(object);
|
||||
}
|
||||
|
||||
/// Unregisters an object from the cache
|
||||
void Unregister(const Surface& object) override {
|
||||
if (object->IsReinterpreted()) {
|
||||
auto interval = GetReinterpretInterval(object);
|
||||
reinterpreted_surfaces.erase(interval);
|
||||
}
|
||||
RasterizerCache<Surface>::Unregister(object);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
@@ -33,6 +33,24 @@ void OGLTexture::Release() {
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLTextureView::Create() {
|
||||
if (handle != 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
glGenTextures(1, &handle);
|
||||
}
|
||||
|
||||
void OGLTextureView::Release() {
|
||||
if (handle == 0)
|
||||
return;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteTextures(1, &handle);
|
||||
OpenGLState::GetCurState().UnbindTexture(handle).Apply();
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLSampler::Create() {
|
||||
if (handle != 0)
|
||||
return;
|
||||
@@ -130,6 +148,12 @@ void OGLBuffer::Release() {
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLBuffer::MakeStreamCopy(std::size_t buffer_size) {
|
||||
ASSERT_OR_EXECUTE((handle != 0 && buffer_size != 0), { return; });
|
||||
|
||||
glNamedBufferData(handle, buffer_size, nullptr, GL_STREAM_COPY);
|
||||
}
|
||||
|
||||
void OGLSync::Create() {
|
||||
if (handle != 0)
|
||||
return;
|
||||
|
||||
@@ -36,6 +36,31 @@ public:
|
||||
GLuint handle = 0;
|
||||
};
|
||||
|
||||
class OGLTextureView : private NonCopyable {
|
||||
public:
|
||||
OGLTextureView() = default;
|
||||
|
||||
OGLTextureView(OGLTextureView&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLTextureView() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLTextureView& operator=(OGLTextureView&& o) noexcept {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Creates a new internal OpenGL resource and stores the handle
|
||||
void Create();
|
||||
|
||||
/// Deletes the internal OpenGL resource
|
||||
void Release();
|
||||
|
||||
GLuint handle = 0;
|
||||
};
|
||||
|
||||
class OGLSampler : private NonCopyable {
|
||||
public:
|
||||
OGLSampler() = default;
|
||||
@@ -161,6 +186,9 @@ public:
|
||||
/// Deletes the internal OpenGL resource
|
||||
void Release();
|
||||
|
||||
// Converts the buffer into a stream copy buffer with a fixed size
|
||||
void MakeStreamCopy(std::size_t buffer_size);
|
||||
|
||||
GLuint handle = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ public:
|
||||
~SamplerCacheOpenGL();
|
||||
|
||||
protected:
|
||||
OGLSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const;
|
||||
OGLSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const override;
|
||||
|
||||
GLuint ToSamplerType(const OGLSampler& sampler) const;
|
||||
GLuint ToSamplerType(const OGLSampler& sampler) const override;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -103,15 +103,22 @@ constexpr std::tuple<const char*, const char*, u32> GetPrimitiveDescription(GLen
|
||||
/// Calculates the size of a program stream
|
||||
std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) {
|
||||
constexpr std::size_t start_offset = 10;
|
||||
// This is the encoded version of BRA that jumps to itself. All Nvidia
|
||||
// shaders end with one.
|
||||
constexpr u64 self_jumping_branch = 0xE2400FFFFF07000FULL;
|
||||
constexpr u64 mask = 0xFFFFFFFFFF7FFFFFULL;
|
||||
std::size_t offset = start_offset;
|
||||
std::size_t size = start_offset * sizeof(u64);
|
||||
while (offset < program.size()) {
|
||||
const u64 instruction = program[offset];
|
||||
if (!IsSchedInstruction(offset, start_offset)) {
|
||||
if (instruction == 0 || (instruction >> 52) == 0x50b) {
|
||||
if ((instruction & mask) == self_jumping_branch) {
|
||||
// End on Maxwell's "nop" instruction
|
||||
break;
|
||||
}
|
||||
if (instruction == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
size += sizeof(u64);
|
||||
offset++;
|
||||
@@ -168,8 +175,12 @@ GLShader::ProgramResult CreateProgram(const Device& device, Maxwell::ShaderProgr
|
||||
}
|
||||
|
||||
CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEntries& entries,
|
||||
Maxwell::ShaderProgram program_type, BaseBindings base_bindings,
|
||||
GLenum primitive_mode, bool hint_retrievable = false) {
|
||||
Maxwell::ShaderProgram program_type, const ProgramVariant& variant,
|
||||
bool hint_retrievable = false) {
|
||||
auto base_bindings{variant.base_bindings};
|
||||
const auto primitive_mode{variant.primitive_mode};
|
||||
const auto texture_buffer_usage{variant.texture_buffer_usage};
|
||||
|
||||
std::string source = "#version 430 core\n"
|
||||
"#extension GL_ARB_separate_shader_objects : enable\n\n";
|
||||
source += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++);
|
||||
@@ -186,6 +197,18 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn
|
||||
source += fmt::format("#define SAMPLER_BINDING_{} {}\n", sampler.GetIndex(),
|
||||
base_bindings.sampler++);
|
||||
}
|
||||
for (const auto& image : entries.images) {
|
||||
source +=
|
||||
fmt::format("#define IMAGE_BINDING_{} {}\n", image.GetIndex(), base_bindings.image++);
|
||||
}
|
||||
|
||||
// Transform 1D textures to texture samplers by declaring its preprocessor macros.
|
||||
for (std::size_t i = 0; i < texture_buffer_usage.size(); ++i) {
|
||||
if (!texture_buffer_usage.test(i)) {
|
||||
continue;
|
||||
}
|
||||
source += fmt::format("#define SAMPLER_{}_IS_BUFFER", i);
|
||||
}
|
||||
|
||||
if (program_type == Maxwell::ShaderProgram::Geometry) {
|
||||
const auto [glsl_topology, debug_name, max_vertices] =
|
||||
@@ -221,60 +244,51 @@ std::set<GLenum> GetSupportedFormats() {
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
CachedShader::CachedShader(const Device& device, VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr)
|
||||
: RasterizerCacheObject{host_ptr}, host_ptr{host_ptr}, cpu_addr{cpu_addr},
|
||||
unique_identifier{unique_identifier}, program_type{program_type}, disk_cache{disk_cache},
|
||||
precompiled_programs{precompiled_programs} {
|
||||
const std::size_t code_size{CalculateProgramSize(program_code)};
|
||||
const std::size_t code_size_b{program_code_b.empty() ? 0
|
||||
: CalculateProgramSize(program_code_b)};
|
||||
GLShader::ProgramResult program_result{
|
||||
CreateProgram(device, program_type, program_code, program_code_b)};
|
||||
if (program_result.first.empty()) {
|
||||
CachedShader::CachedShader(const ShaderParameters& params, Maxwell::ShaderProgram program_type,
|
||||
GLShader::ProgramResult result)
|
||||
: RasterizerCacheObject{params.host_ptr}, host_ptr{params.host_ptr}, cpu_addr{params.cpu_addr},
|
||||
unique_identifier{params.unique_identifier}, program_type{program_type},
|
||||
disk_cache{params.disk_cache}, precompiled_programs{params.precompiled_programs},
|
||||
entries{result.second}, code{std::move(result.first)}, shader_length{entries.shader_length} {}
|
||||
|
||||
Shader CachedShader::CreateStageFromMemory(const ShaderParameters& params,
|
||||
Maxwell::ShaderProgram program_type,
|
||||
ProgramCode&& program_code,
|
||||
ProgramCode&& program_code_b) {
|
||||
const auto code_size{CalculateProgramSize(program_code)};
|
||||
const auto code_size_b{CalculateProgramSize(program_code_b)};
|
||||
auto result{CreateProgram(params.device, program_type, program_code, program_code_b)};
|
||||
if (result.first.empty()) {
|
||||
// TODO(Rodrigo): Unimplemented shader stages hit here, avoid using these for now
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
code = program_result.first;
|
||||
entries = program_result.second;
|
||||
shader_length = entries.shader_length;
|
||||
params.disk_cache.SaveRaw(ShaderDiskCacheRaw(
|
||||
params.unique_identifier, program_type, static_cast<u32>(code_size / sizeof(u64)),
|
||||
static_cast<u32>(code_size_b / sizeof(u64)), std::move(program_code),
|
||||
std::move(program_code_b)));
|
||||
|
||||
const ShaderDiskCacheRaw raw(unique_identifier, program_type,
|
||||
static_cast<u32>(code_size / sizeof(u64)),
|
||||
static_cast<u32>(code_size_b / sizeof(u64)),
|
||||
std::move(program_code), std::move(program_code_b));
|
||||
disk_cache.SaveRaw(raw);
|
||||
return std::shared_ptr<CachedShader>(new CachedShader(params, program_type, std::move(result)));
|
||||
}
|
||||
|
||||
CachedShader::CachedShader(VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
GLShader::ProgramResult result, u8* host_ptr)
|
||||
: RasterizerCacheObject{host_ptr}, cpu_addr{cpu_addr}, unique_identifier{unique_identifier},
|
||||
program_type{program_type}, disk_cache{disk_cache}, precompiled_programs{
|
||||
precompiled_programs} {
|
||||
code = std::move(result.first);
|
||||
entries = result.second;
|
||||
shader_length = entries.shader_length;
|
||||
Shader CachedShader::CreateStageFromCache(const ShaderParameters& params,
|
||||
Maxwell::ShaderProgram program_type,
|
||||
GLShader::ProgramResult result) {
|
||||
return std::shared_ptr<CachedShader>(new CachedShader(params, program_type, std::move(result)));
|
||||
}
|
||||
|
||||
std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive_mode,
|
||||
BaseBindings base_bindings) {
|
||||
std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVariant& variant) {
|
||||
GLuint handle{};
|
||||
if (program_type == Maxwell::ShaderProgram::Geometry) {
|
||||
handle = GetGeometryShader(primitive_mode, base_bindings);
|
||||
handle = GetGeometryShader(variant);
|
||||
} else {
|
||||
const auto [entry, is_cache_miss] = programs.try_emplace(base_bindings);
|
||||
const auto [entry, is_cache_miss] = programs.try_emplace(variant);
|
||||
auto& program = entry->second;
|
||||
if (is_cache_miss) {
|
||||
program = TryLoadProgram(primitive_mode, base_bindings);
|
||||
program = TryLoadProgram(variant);
|
||||
if (!program) {
|
||||
program =
|
||||
SpecializeShader(code, entries, program_type, base_bindings, primitive_mode);
|
||||
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
|
||||
program = SpecializeShader(code, entries, program_type, variant);
|
||||
disk_cache.SaveUsage(GetUsage(variant));
|
||||
}
|
||||
|
||||
LabelGLObject(GL_PROGRAM, program->handle, cpu_addr);
|
||||
@@ -283,6 +297,7 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive
|
||||
handle = program->handle;
|
||||
}
|
||||
|
||||
auto base_bindings{variant.base_bindings};
|
||||
base_bindings.cbuf += static_cast<u32>(entries.const_buffers.size()) + RESERVED_UBOS;
|
||||
base_bindings.gmem += static_cast<u32>(entries.global_memory_entries.size());
|
||||
base_bindings.sampler += static_cast<u32>(entries.samplers.size());
|
||||
@@ -290,43 +305,42 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive
|
||||
return {handle, base_bindings};
|
||||
}
|
||||
|
||||
GLuint CachedShader::GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings) {
|
||||
const auto [entry, is_cache_miss] = geometry_programs.try_emplace(base_bindings);
|
||||
GLuint CachedShader::GetGeometryShader(const ProgramVariant& variant) {
|
||||
const auto [entry, is_cache_miss] = geometry_programs.try_emplace(variant);
|
||||
auto& programs = entry->second;
|
||||
|
||||
switch (primitive_mode) {
|
||||
switch (variant.primitive_mode) {
|
||||
case GL_POINTS:
|
||||
return LazyGeometryProgram(programs.points, base_bindings, primitive_mode);
|
||||
return LazyGeometryProgram(programs.points, variant);
|
||||
case GL_LINES:
|
||||
case GL_LINE_STRIP:
|
||||
return LazyGeometryProgram(programs.lines, base_bindings, primitive_mode);
|
||||
return LazyGeometryProgram(programs.lines, variant);
|
||||
case GL_LINES_ADJACENCY:
|
||||
case GL_LINE_STRIP_ADJACENCY:
|
||||
return LazyGeometryProgram(programs.lines_adjacency, base_bindings, primitive_mode);
|
||||
return LazyGeometryProgram(programs.lines_adjacency, variant);
|
||||
case GL_TRIANGLES:
|
||||
case GL_TRIANGLE_STRIP:
|
||||
case GL_TRIANGLE_FAN:
|
||||
return LazyGeometryProgram(programs.triangles, base_bindings, primitive_mode);
|
||||
return LazyGeometryProgram(programs.triangles, variant);
|
||||
case GL_TRIANGLES_ADJACENCY:
|
||||
case GL_TRIANGLE_STRIP_ADJACENCY:
|
||||
return LazyGeometryProgram(programs.triangles_adjacency, base_bindings, primitive_mode);
|
||||
return LazyGeometryProgram(programs.triangles_adjacency, variant);
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown primitive mode.");
|
||||
return LazyGeometryProgram(programs.points, base_bindings, primitive_mode);
|
||||
return LazyGeometryProgram(programs.points, variant);
|
||||
}
|
||||
}
|
||||
|
||||
GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings,
|
||||
GLenum primitive_mode) {
|
||||
GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program,
|
||||
const ProgramVariant& variant) {
|
||||
if (target_program) {
|
||||
return target_program->handle;
|
||||
}
|
||||
const auto [glsl_name, debug_name, vertices] = GetPrimitiveDescription(primitive_mode);
|
||||
target_program = TryLoadProgram(primitive_mode, base_bindings);
|
||||
const auto [glsl_name, debug_name, vertices] = GetPrimitiveDescription(variant.primitive_mode);
|
||||
target_program = TryLoadProgram(variant);
|
||||
if (!target_program) {
|
||||
target_program =
|
||||
SpecializeShader(code, entries, program_type, base_bindings, primitive_mode);
|
||||
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
|
||||
target_program = SpecializeShader(code, entries, program_type, variant);
|
||||
disk_cache.SaveUsage(GetUsage(variant));
|
||||
}
|
||||
|
||||
LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name);
|
||||
@@ -334,18 +348,19 @@ GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBind
|
||||
return target_program->handle;
|
||||
};
|
||||
|
||||
CachedProgram CachedShader::TryLoadProgram(GLenum primitive_mode,
|
||||
BaseBindings base_bindings) const {
|
||||
const auto found = precompiled_programs.find(GetUsage(primitive_mode, base_bindings));
|
||||
CachedProgram CachedShader::TryLoadProgram(const ProgramVariant& variant) const {
|
||||
const auto found = precompiled_programs.find(GetUsage(variant));
|
||||
if (found == precompiled_programs.end()) {
|
||||
return {};
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
|
||||
ShaderDiskCacheUsage CachedShader::GetUsage(GLenum primitive_mode,
|
||||
BaseBindings base_bindings) const {
|
||||
return {unique_identifier, base_bindings, primitive_mode};
|
||||
ShaderDiskCacheUsage CachedShader::GetUsage(const ProgramVariant& variant) const {
|
||||
ShaderDiskCacheUsage usage;
|
||||
usage.unique_identifier = unique_identifier;
|
||||
usage.variant = variant;
|
||||
return usage;
|
||||
}
|
||||
|
||||
ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
|
||||
@@ -411,8 +426,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
}
|
||||
if (!shader) {
|
||||
shader = SpecializeShader(unspecialized.code, unspecialized.entries,
|
||||
unspecialized.program_type, usage.bindings,
|
||||
usage.primitive, true);
|
||||
unspecialized.program_type, usage.variant, true);
|
||||
}
|
||||
|
||||
std::scoped_lock lock(mutex);
|
||||
@@ -570,18 +584,17 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
|
||||
memory_manager.GetPointer(program_addr_b));
|
||||
}
|
||||
|
||||
const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
|
||||
const VAddr cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)};
|
||||
const auto unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
|
||||
const auto cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)};
|
||||
const ShaderParameters params{disk_cache, precompiled_programs, device, cpu_addr,
|
||||
host_ptr, unique_identifier};
|
||||
|
||||
const auto found = precompiled_shaders.find(unique_identifier);
|
||||
if (found != precompiled_shaders.end()) {
|
||||
// Create a shader from the cache
|
||||
shader = std::make_shared<CachedShader>(cpu_addr, unique_identifier, program, disk_cache,
|
||||
precompiled_programs, found->second, host_ptr);
|
||||
if (found == precompiled_shaders.end()) {
|
||||
shader = CachedShader::CreateStageFromMemory(params, program, std::move(program_code),
|
||||
std::move(program_code_b));
|
||||
} else {
|
||||
// Create a shader from guest memory
|
||||
shader = std::make_shared<CachedShader>(
|
||||
device, cpu_addr, unique_identifier, program, disk_cache, precompiled_programs,
|
||||
std::move(program_code), std::move(program_code_b), host_ptr);
|
||||
shader = CachedShader::CreateStageFromCache(params, program, found->second);
|
||||
}
|
||||
Register(shader);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
@@ -41,17 +42,24 @@ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
using PrecompiledPrograms = std::unordered_map<ShaderDiskCacheUsage, CachedProgram>;
|
||||
using PrecompiledShaders = std::unordered_map<u64, GLShader::ProgramResult>;
|
||||
|
||||
struct ShaderParameters {
|
||||
ShaderDiskCacheOpenGL& disk_cache;
|
||||
const PrecompiledPrograms& precompiled_programs;
|
||||
const Device& device;
|
||||
VAddr cpu_addr;
|
||||
u8* host_ptr;
|
||||
u64 unique_identifier;
|
||||
};
|
||||
|
||||
class CachedShader final : public RasterizerCacheObject {
|
||||
public:
|
||||
explicit CachedShader(const Device& device, VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr);
|
||||
static Shader CreateStageFromMemory(const ShaderParameters& params,
|
||||
Maxwell::ShaderProgram program_type,
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b);
|
||||
|
||||
explicit CachedShader(VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
GLShader::ProgramResult result, u8* host_ptr);
|
||||
static Shader CreateStageFromCache(const ShaderParameters& params,
|
||||
Maxwell::ShaderProgram program_type,
|
||||
GLShader::ProgramResult result);
|
||||
|
||||
VAddr GetCpuAddr() const override {
|
||||
return cpu_addr;
|
||||
@@ -67,10 +75,12 @@ public:
|
||||
}
|
||||
|
||||
/// Gets the GL program handle for the shader
|
||||
std::tuple<GLuint, BaseBindings> GetProgramHandle(GLenum primitive_mode,
|
||||
BaseBindings base_bindings);
|
||||
std::tuple<GLuint, BaseBindings> GetProgramHandle(const ProgramVariant& variant);
|
||||
|
||||
private:
|
||||
explicit CachedShader(const ShaderParameters& params, Maxwell::ShaderProgram program_type,
|
||||
GLShader::ProgramResult result);
|
||||
|
||||
// Geometry programs. These are needed because GLSL needs an input topology but it's not
|
||||
// declared by the hardware. Workaround this issue by generating a different shader per input
|
||||
// topology class.
|
||||
@@ -82,15 +92,14 @@ private:
|
||||
CachedProgram triangles_adjacency;
|
||||
};
|
||||
|
||||
GLuint GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings);
|
||||
GLuint GetGeometryShader(const ProgramVariant& variant);
|
||||
|
||||
/// Generates a geometry shader or returns one that already exists.
|
||||
GLuint LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings,
|
||||
GLenum primitive_mode);
|
||||
GLuint LazyGeometryProgram(CachedProgram& target_program, const ProgramVariant& variant);
|
||||
|
||||
CachedProgram TryLoadProgram(GLenum primitive_mode, BaseBindings base_bindings) const;
|
||||
CachedProgram TryLoadProgram(const ProgramVariant& variant) const;
|
||||
|
||||
ShaderDiskCacheUsage GetUsage(GLenum primitive_mode, BaseBindings base_bindings) const;
|
||||
ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const;
|
||||
|
||||
u8* host_ptr{};
|
||||
VAddr cpu_addr{};
|
||||
@@ -99,13 +108,12 @@ private:
|
||||
ShaderDiskCacheOpenGL& disk_cache;
|
||||
const PrecompiledPrograms& precompiled_programs;
|
||||
|
||||
std::size_t shader_length{};
|
||||
GLShader::ShaderEntries entries;
|
||||
|
||||
std::string code;
|
||||
std::size_t shader_length{};
|
||||
|
||||
std::unordered_map<BaseBindings, CachedProgram> programs;
|
||||
std::unordered_map<BaseBindings, GeometryPrograms> geometry_programs;
|
||||
std::unordered_map<ProgramVariant, CachedProgram> programs;
|
||||
std::unordered_map<ProgramVariant, GeometryPrograms> geometry_programs;
|
||||
|
||||
std::unordered_map<u32, GLuint> cbuf_resource_cache;
|
||||
std::unordered_map<u32, GLuint> gmem_resource_cache;
|
||||
|
||||
@@ -180,6 +180,7 @@ public:
|
||||
DeclareGlobalMemory();
|
||||
DeclareSamplers();
|
||||
DeclarePhysicalAttributeReader();
|
||||
DeclareImages();
|
||||
|
||||
code.AddLine("void execute_{}() {{", suffix);
|
||||
++code.scope;
|
||||
@@ -234,6 +235,9 @@ public:
|
||||
for (const auto& sampler : ir.GetSamplers()) {
|
||||
entries.samplers.emplace_back(sampler);
|
||||
}
|
||||
for (const auto& image : ir.GetImages()) {
|
||||
entries.images.emplace_back(image);
|
||||
}
|
||||
for (const auto& gmem_pair : ir.GetGlobalMemory()) {
|
||||
const auto& [base, usage] = gmem_pair;
|
||||
entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset,
|
||||
@@ -453,9 +457,13 @@ private:
|
||||
void DeclareSamplers() {
|
||||
const auto& samplers = ir.GetSamplers();
|
||||
for (const auto& sampler : samplers) {
|
||||
std::string sampler_type = [&sampler] {
|
||||
const std::string name{GetSampler(sampler)};
|
||||
const std::string description{"layout (binding = SAMPLER_BINDING_" +
|
||||
std::to_string(sampler.GetIndex()) + ") uniform"};
|
||||
std::string sampler_type = [&]() {
|
||||
switch (sampler.GetType()) {
|
||||
case Tegra::Shader::TextureType::Texture1D:
|
||||
// Special cased, read below.
|
||||
return "sampler1D";
|
||||
case Tegra::Shader::TextureType::Texture2D:
|
||||
return "sampler2D";
|
||||
@@ -475,8 +483,19 @@ private:
|
||||
sampler_type += "Shadow";
|
||||
}
|
||||
|
||||
code.AddLine("layout (binding = SAMPLER_BINDING_{}) uniform {} {};", sampler.GetIndex(),
|
||||
sampler_type, GetSampler(sampler));
|
||||
if (sampler.GetType() == Tegra::Shader::TextureType::Texture1D) {
|
||||
// 1D textures can be aliased to texture buffers, hide the declarations behind a
|
||||
// preprocessor flag and use one or the other from the GPU state. This has to be
|
||||
// done because shaders don't have enough information to determine the texture type.
|
||||
EmitIfdefIsBuffer(sampler);
|
||||
code.AddLine("{} samplerBuffer {};", description, name);
|
||||
code.AddLine("#else");
|
||||
code.AddLine("{} {} {};", description, sampler_type, name);
|
||||
code.AddLine("#endif");
|
||||
} else {
|
||||
// The other texture types (2D, 3D and cubes) don't have this issue.
|
||||
code.AddLine("{} {} {};", description, sampler_type, name);
|
||||
}
|
||||
}
|
||||
if (!samplers.empty()) {
|
||||
code.AddNewLine();
|
||||
@@ -516,6 +535,37 @@ private:
|
||||
code.AddNewLine();
|
||||
}
|
||||
|
||||
void DeclareImages() {
|
||||
const auto& images{ir.GetImages()};
|
||||
for (const auto& image : images) {
|
||||
const std::string image_type = [&]() {
|
||||
switch (image.GetType()) {
|
||||
case Tegra::Shader::ImageType::Texture1D:
|
||||
return "image1D";
|
||||
case Tegra::Shader::ImageType::TextureBuffer:
|
||||
return "bufferImage";
|
||||
case Tegra::Shader::ImageType::Texture1DArray:
|
||||
return "image1DArray";
|
||||
case Tegra::Shader::ImageType::Texture2D:
|
||||
return "image2D";
|
||||
case Tegra::Shader::ImageType::Texture2DArray:
|
||||
return "image2DArray";
|
||||
case Tegra::Shader::ImageType::Texture3D:
|
||||
return "image3D";
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return "image1D";
|
||||
}
|
||||
}();
|
||||
code.AddLine("layout (binding = IMAGE_BINDING_{}) coherent volatile writeonly uniform "
|
||||
"{} {};",
|
||||
image.GetIndex(), image_type, GetImage(image));
|
||||
}
|
||||
if (!images.empty()) {
|
||||
code.AddNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
void VisitBlock(const NodeBlock& bb) {
|
||||
for (const auto& node : bb) {
|
||||
if (const std::string expr = Visit(node); !expr.empty()) {
|
||||
@@ -1439,13 +1489,61 @@ private:
|
||||
else if (next < count)
|
||||
expr += ", ";
|
||||
}
|
||||
|
||||
// Store a copy of the expression without the lod to be used with texture buffers
|
||||
std::string expr_buffer = expr;
|
||||
|
||||
if (meta->lod) {
|
||||
expr += ", ";
|
||||
expr += CastOperand(Visit(meta->lod), Type::Int);
|
||||
}
|
||||
expr += ')';
|
||||
expr += GetSwizzle(meta->element);
|
||||
|
||||
return expr + GetSwizzle(meta->element);
|
||||
expr_buffer += ')';
|
||||
expr_buffer += GetSwizzle(meta->element);
|
||||
|
||||
const std::string tmp{code.GenerateTemporary()};
|
||||
EmitIfdefIsBuffer(meta->sampler);
|
||||
code.AddLine("float {} = {};", tmp, expr_buffer);
|
||||
code.AddLine("#else");
|
||||
code.AddLine("float {} = {};", tmp, expr);
|
||||
code.AddLine("#endif");
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
std::string ImageStore(Operation operation) {
|
||||
constexpr std::array<const char*, 4> constructors{"int(", "ivec2(", "ivec3(", "ivec4("};
|
||||
const auto meta{std::get<MetaImage>(operation.GetMeta())};
|
||||
|
||||
std::string expr = "imageStore(";
|
||||
expr += GetImage(meta.image);
|
||||
expr += ", ";
|
||||
|
||||
const std::size_t coords_count{operation.GetOperandsCount()};
|
||||
expr += constructors.at(coords_count - 1);
|
||||
for (std::size_t i = 0; i < coords_count; ++i) {
|
||||
expr += VisitOperand(operation, i, Type::Int);
|
||||
if (i + 1 < coords_count) {
|
||||
expr += ", ";
|
||||
}
|
||||
}
|
||||
expr += "), ";
|
||||
|
||||
const std::size_t values_count{meta.values.size()};
|
||||
UNIMPLEMENTED_IF(values_count != 4);
|
||||
expr += "vec4(";
|
||||
for (std::size_t i = 0; i < values_count; ++i) {
|
||||
expr += Visit(meta.values.at(i));
|
||||
if (i + 1 < values_count) {
|
||||
expr += ", ";
|
||||
}
|
||||
}
|
||||
expr += "));";
|
||||
|
||||
code.AddLine(expr);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string Branch(Operation operation) {
|
||||
@@ -1688,6 +1786,8 @@ private:
|
||||
&GLSLDecompiler::TextureQueryLod,
|
||||
&GLSLDecompiler::TexelFetch,
|
||||
|
||||
&GLSLDecompiler::ImageStore,
|
||||
|
||||
&GLSLDecompiler::Branch,
|
||||
&GLSLDecompiler::PushFlowStack,
|
||||
&GLSLDecompiler::PopFlowStack,
|
||||
@@ -1756,6 +1856,14 @@ private:
|
||||
return GetDeclarationWithSuffix(static_cast<u32>(sampler.GetIndex()), "sampler");
|
||||
}
|
||||
|
||||
std::string GetImage(const Image& image) const {
|
||||
return GetDeclarationWithSuffix(static_cast<u32>(image.GetIndex()), "image");
|
||||
}
|
||||
|
||||
void EmitIfdefIsBuffer(const Sampler& sampler) {
|
||||
code.AddLine("#ifdef SAMPLER_{}_IS_BUFFER", sampler.GetIndex());
|
||||
}
|
||||
|
||||
std::string GetDeclarationWithSuffix(u32 index, const std::string& name) const {
|
||||
return fmt::format("{}_{}_{}", name, index, suffix);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ struct ShaderEntries;
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
using ProgramResult = std::pair<std::string, ShaderEntries>;
|
||||
using SamplerEntry = VideoCommon::Shader::Sampler;
|
||||
using ImageEntry = VideoCommon::Shader::Image;
|
||||
|
||||
class ConstBufferEntry : public VideoCommon::Shader::ConstBuffer {
|
||||
public:
|
||||
@@ -74,6 +75,7 @@ struct ShaderEntries {
|
||||
std::vector<ConstBufferEntry> const_buffers;
|
||||
std::vector<SamplerEntry> samplers;
|
||||
std::vector<SamplerEntry> bindless_samplers;
|
||||
std::vector<ImageEntry> images;
|
||||
std::vector<GlobalMemoryEntry> global_memory_entries;
|
||||
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
|
||||
std::size_t shader_length{};
|
||||
|
||||
@@ -34,11 +34,11 @@ enum class PrecompiledEntryKind : u32 {
|
||||
Dump,
|
||||
};
|
||||
|
||||
constexpr u32 NativeVersion = 1;
|
||||
constexpr u32 NativeVersion = 4;
|
||||
|
||||
// Making sure sizes doesn't change by accident
|
||||
static_assert(sizeof(BaseBindings) == 12);
|
||||
static_assert(sizeof(ShaderDiskCacheUsage) == 24);
|
||||
static_assert(sizeof(BaseBindings) == 16);
|
||||
static_assert(sizeof(ShaderDiskCacheUsage) == 40);
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -332,11 +332,28 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
|
||||
static_cast<Tegra::Shader::TextureType>(type), is_array, is_shadow, is_bindless);
|
||||
}
|
||||
|
||||
u32 images_count{};
|
||||
if (!LoadObjectFromPrecompiled(images_count)) {
|
||||
return {};
|
||||
}
|
||||
for (u32 i = 0; i < images_count; ++i) {
|
||||
u64 offset{};
|
||||
u64 index{};
|
||||
u32 type{};
|
||||
u8 is_bindless{};
|
||||
if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||
|
||||
!LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless)) {
|
||||
return {};
|
||||
}
|
||||
entry.entries.images.emplace_back(
|
||||
static_cast<std::size_t>(offset), static_cast<std::size_t>(index),
|
||||
static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0);
|
||||
}
|
||||
|
||||
u32 global_memory_count{};
|
||||
if (!LoadObjectFromPrecompiled(global_memory_count)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < global_memory_count; ++i) {
|
||||
u32 cbuf_index{};
|
||||
u32 cbuf_offset{};
|
||||
@@ -360,7 +377,6 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
|
||||
if (!LoadObjectFromPrecompiled(shader_length)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
entry.entries.shader_length = static_cast<std::size_t>(shader_length);
|
||||
|
||||
return entry;
|
||||
@@ -400,6 +416,18 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:
|
||||
}
|
||||
}
|
||||
|
||||
if (!SaveObjectToPrecompiled(static_cast<u32>(entries.images.size()))) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& image : entries.images) {
|
||||
if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) ||
|
||||
!SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SaveObjectToPrecompiled(static_cast<u32>(entries.global_memory_entries.size()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
@@ -30,22 +31,26 @@ class IOFile;
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
using ProgramCode = std::vector<u64>;
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
struct ShaderDiskCacheUsage;
|
||||
struct ShaderDiskCacheDump;
|
||||
|
||||
using ShaderDumpsMap = std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>;
|
||||
|
||||
/// Allocated bindings used by an OpenGL shader program
|
||||
using ProgramCode = std::vector<u64>;
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
using TextureBufferUsage = std::bitset<64>;
|
||||
|
||||
/// Allocated bindings used by an OpenGL shader program.
|
||||
struct BaseBindings {
|
||||
u32 cbuf{};
|
||||
u32 gmem{};
|
||||
u32 sampler{};
|
||||
u32 image{};
|
||||
|
||||
bool operator==(const BaseBindings& rhs) const {
|
||||
return std::tie(cbuf, gmem, sampler) == std::tie(rhs.cbuf, rhs.gmem, rhs.sampler);
|
||||
return std::tie(cbuf, gmem, sampler, image) ==
|
||||
std::tie(rhs.cbuf, rhs.gmem, rhs.sampler, rhs.image);
|
||||
}
|
||||
|
||||
bool operator!=(const BaseBindings& rhs) const {
|
||||
@@ -53,15 +58,29 @@ struct BaseBindings {
|
||||
}
|
||||
};
|
||||
|
||||
/// Describes how a shader is used
|
||||
/// Describes the different variants a single program can be compiled.
|
||||
struct ProgramVariant {
|
||||
BaseBindings base_bindings;
|
||||
GLenum primitive_mode{};
|
||||
TextureBufferUsage texture_buffer_usage{};
|
||||
|
||||
bool operator==(const ProgramVariant& rhs) const {
|
||||
return std::tie(base_bindings, primitive_mode, texture_buffer_usage) ==
|
||||
std::tie(rhs.base_bindings, rhs.primitive_mode, rhs.texture_buffer_usage);
|
||||
}
|
||||
|
||||
bool operator!=(const ProgramVariant& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
};
|
||||
|
||||
/// Describes how a shader is used.
|
||||
struct ShaderDiskCacheUsage {
|
||||
u64 unique_identifier{};
|
||||
BaseBindings bindings;
|
||||
GLenum primitive{};
|
||||
ProgramVariant variant;
|
||||
|
||||
bool operator==(const ShaderDiskCacheUsage& rhs) const {
|
||||
return std::tie(unique_identifier, bindings, primitive) ==
|
||||
std::tie(rhs.unique_identifier, rhs.bindings, rhs.primitive);
|
||||
return std::tie(unique_identifier, variant) == std::tie(rhs.unique_identifier, rhs.variant);
|
||||
}
|
||||
|
||||
bool operator!=(const ShaderDiskCacheUsage& rhs) const {
|
||||
@@ -76,7 +95,19 @@ namespace std {
|
||||
template <>
|
||||
struct hash<OpenGL::BaseBindings> {
|
||||
std::size_t operator()(const OpenGL::BaseBindings& bindings) const noexcept {
|
||||
return bindings.cbuf | bindings.gmem << 8 | bindings.sampler << 16;
|
||||
return static_cast<std::size_t>(bindings.cbuf) ^
|
||||
(static_cast<std::size_t>(bindings.gmem) << 8) ^
|
||||
(static_cast<std::size_t>(bindings.sampler) << 16) ^
|
||||
(static_cast<std::size_t>(bindings.image) << 24);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<OpenGL::ProgramVariant> {
|
||||
std::size_t operator()(const OpenGL::ProgramVariant& variant) const noexcept {
|
||||
return std::hash<OpenGL::BaseBindings>()(variant.base_bindings) ^
|
||||
std::hash<OpenGL::TextureBufferUsage>()(variant.texture_buffer_usage) ^
|
||||
(static_cast<std::size_t>(variant.primitive_mode) << 6);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,7 +115,7 @@ template <>
|
||||
struct hash<OpenGL::ShaderDiskCacheUsage> {
|
||||
std::size_t operator()(const OpenGL::ShaderDiskCacheUsage& usage) const noexcept {
|
||||
return static_cast<std::size_t>(usage.unique_identifier) ^
|
||||
std::hash<OpenGL::BaseBindings>()(usage.bindings) ^ usage.primitive << 16;
|
||||
std::hash<OpenGL::ProgramVariant>()(usage.variant);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -275,26 +306,17 @@ private:
|
||||
return LoadArrayFromPrecompiled(&object, 1);
|
||||
}
|
||||
|
||||
bool LoadObjectFromPrecompiled(bool& object) {
|
||||
u8 value;
|
||||
const bool read_ok = LoadArrayFromPrecompiled(&value, 1);
|
||||
if (!read_ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
object = value != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Core system
|
||||
Core::System& system;
|
||||
// Stored transferable shaders
|
||||
std::map<u64, std::unordered_set<ShaderDiskCacheUsage>> transferable;
|
||||
// Stores whole precompiled cache which will be read from/saved to the precompiled cache file
|
||||
|
||||
// Stores whole precompiled cache which will be read from or saved to the precompiled chache
|
||||
// file
|
||||
FileSys::VectorVfsFile precompiled_cache_virtual_file;
|
||||
// Stores the current offset of the precompiled cache file for IO purposes
|
||||
std::size_t precompiled_cache_virtual_file_offset = 0;
|
||||
|
||||
// Stored transferable shaders
|
||||
std::unordered_map<u64, std::unordered_set<ShaderDiskCacheUsage>> transferable;
|
||||
|
||||
// The cache has been loaded at boot
|
||||
bool tried_to_load{};
|
||||
};
|
||||
|
||||
@@ -15,7 +15,8 @@ MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning",
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
OGLStreamBuffer::OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent)
|
||||
OGLStreamBuffer::OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent,
|
||||
bool use_persistent)
|
||||
: buffer_size(size) {
|
||||
gl_buffer.Create();
|
||||
|
||||
@@ -29,7 +30,7 @@ OGLStreamBuffer::OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool p
|
||||
allocate_size *= 2;
|
||||
}
|
||||
|
||||
if (GLAD_GL_ARB_buffer_storage) {
|
||||
if (use_persistent) {
|
||||
persistent = true;
|
||||
coherent = prefer_coherent;
|
||||
const GLbitfield flags =
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user