diff options
Diffstat (limited to '')
463 files changed, 23718 insertions, 9059 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 04018233f..f18239edb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -88,6 +88,7 @@ add_subdirectory(tests) if (ENABLE_SDL2) add_subdirectory(yuzu_cmd) + add_subdirectory(yuzu_tester) endif() if (ENABLE_QT) diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index 9a0939883..e6f38d600 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -73,13 +73,15 @@ private: EffectInStatus info{}; }; AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, AudioRendererParameter params, - Kernel::SharedPtr<Kernel::WritableEvent> buffer_event) + Kernel::SharedPtr<Kernel::WritableEvent> buffer_event, + std::size_t instance_number) : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count), effects(params.effect_count) { audio_out = std::make_unique<AudioCore::AudioOut>(); stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, - "AudioRenderer", [=]() { buffer_event->Signal(); }); + fmt::format("AudioRenderer-Instance{}", instance_number), + [=]() { buffer_event->Signal(); }); audio_out->StartStream(stream); QueueMixedBuffer(0); @@ -105,6 +107,11 @@ Stream::State AudioRenderer::GetStreamState() const { return stream->GetState(); } +static constexpr u32 VersionFromRevision(u32_le rev) { + // "REV7" -> 7 + return ((rev >> 24) & 0xff) - 0x30; +} + std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { // Copy UpdateDataHeader struct UpdateDataHeader config{}; @@ -164,6 +171,11 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ // Copy output header UpdateDataHeader response_data{worker_params}; std::vector<u8> output_params(response_data.total_size); + const auto audren_revision = VersionFromRevision(config.revision); + if (audren_revision >= 5) { + response_data.frame_count = 0x10; + response_data.total_size += 0x10; + } std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader)); // Copy output memory pool entries @@ -217,13 +229,15 @@ std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(std::size_t sample_co if (offset == samples.size()) { offset = 0; - if (!wave_buffer.is_looping) { + if (!wave_buffer.is_looping && wave_buffer.buffer_sz) { SetWaveIndex(wave_index + 1); } - out_status.wave_buffer_consumed++; + if (wave_buffer.buffer_sz) { + out_status.wave_buffer_consumed++; + } - if (wave_buffer.end_of_stream) { + if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) { info.play_state = PlayState::Paused; } } diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index b2e5d336c..4f14b91cd 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h @@ -194,28 +194,32 @@ struct UpdateDataHeader { mixes_size = 0x0; sinks_size = config.sink_count * 0x20; performance_manager_size = 0x10; + frame_count = 0; total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size + effects_size + sinks_size + performance_manager_size; } - u32_le revision; - u32_le behavior_size; - u32_le memory_pools_size; - u32_le voices_size; - u32_le voice_resource_size; - u32_le effects_size; - u32_le mixes_size; - u32_le sinks_size; - u32_le performance_manager_size; - INSERT_PADDING_WORDS(6); - u32_le total_size; + u32_le revision{}; + u32_le behavior_size{}; + u32_le memory_pools_size{}; + u32_le voices_size{}; + u32_le voice_resource_size{}; + u32_le effects_size{}; + u32_le mixes_size{}; + u32_le sinks_size{}; + u32_le performance_manager_size{}; + INSERT_PADDING_WORDS(1); + u32_le frame_count{}; + INSERT_PADDING_WORDS(4); + u32_le total_size{}; }; static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size"); class AudioRenderer { public: AudioRenderer(Core::Timing::CoreTiming& core_timing, AudioRendererParameter params, - Kernel::SharedPtr<Kernel::WritableEvent> buffer_event); + Kernel::SharedPtr<Kernel::WritableEvent> buffer_event, + std::size_t instance_number); ~AudioRenderer(); std::vector<u8> UpdateAudioRenderer(const std::vector<u8>& input_params); diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp index 11481a776..6a5f53a57 100644 --- a/src/audio_core/stream.cpp +++ b/src/audio_core/stream.cpp @@ -51,6 +51,10 @@ void Stream::Stop() { UNIMPLEMENTED(); } +void Stream::SetVolume(float volume) { + game_volume = volume; +} + Stream::State Stream::GetState() const { return state; } @@ -62,8 +66,8 @@ s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const { return Core::Timing::usToCycles(us); } -static void VolumeAdjustSamples(std::vector<s16>& samples) { - const float volume{std::clamp(Settings::values.volume, 0.0f, 1.0f)}; +static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) { + const float volume{std::clamp(Settings::values.volume - (1.0f - game_volume), 0.0f, 1.0f)}; if (volume == 1.0f) { return; @@ -97,11 +101,11 @@ void Stream::PlayNextBuffer() { active_buffer = queued_buffers.front(); queued_buffers.pop(); - VolumeAdjustSamples(active_buffer->GetSamples()); + VolumeAdjustSamples(active_buffer->GetSamples(), game_volume); 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() { diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h index 05071243b..8106cea43 100644 --- a/src/audio_core/stream.h +++ b/src/audio_core/stream.h @@ -61,6 +61,12 @@ public: /// Returns a vector of recently released buffers specified by tag std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count); + void SetVolume(float volume); + + float GetVolume() const { + return game_volume; + } + /// Returns true if the stream is currently playing bool IsPlaying() const { return state == State::Playing; @@ -94,6 +100,7 @@ private: u32 sample_rate; ///< Sample rate of the stream Format format; ///< Format of the stream + float game_volume = 1.0f; ///< The volume the game currently has set ReleaseCallback release_callback; ///< Buffer release callback for the stream State state{State::Stopped}; ///< Playback state of the stream Core::Timing::EventType* release_event{}; ///< Core timing release event for the stream diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 198b3fe07..dfed8b51d 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -10,6 +10,9 @@ if (DEFINED ENV{CI}) elseif(DEFINED ENV{APPVEYOR}) set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME}) set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME}) + elseif(DEFINED ENV{AZURE}) + set(BUILD_REPOSITORY $ENV{AZURE_REPO_NAME}) + set(BUILD_TAG $ENV{AZURE_REPO_TAG}) endif() endif() add_custom_command(OUTPUT scm_rev.cpp @@ -44,6 +47,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" @@ -54,7 +58,10 @@ add_custom_command(OUTPUT scm_rev.cpp "${VIDEO_CORE}/shader/decode/register_set_predicate.cpp" "${VIDEO_CORE}/shader/decode/shift.cpp" "${VIDEO_CORE}/shader/decode/video.cpp" + "${VIDEO_CORE}/shader/decode/warp.cpp" "${VIDEO_CORE}/shader/decode/xmad.cpp" + "${VIDEO_CORE}/shader/control_flow.cpp" + "${VIDEO_CORE}/shader/control_flow.h" "${VIDEO_CORE}/shader/decode.cpp" "${VIDEO_CORE}/shader/node.h" "${VIDEO_CORE}/shader/node_helper.cpp" @@ -74,6 +81,7 @@ add_library(common STATIC assert.h detached_tasks.cpp detached_tasks.h + binary_find.h bit_field.h bit_util.h cityhash.cpp diff --git a/src/common/alignment.h b/src/common/alignment.h index d94a2291f..88d5d3a65 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -3,6 +3,7 @@ #pragma once #include <cstddef> +#include <memory> #include <type_traits> namespace Common { @@ -20,6 +21,12 @@ constexpr T AlignDown(T value, std::size_t 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."); return (value & 0xFFF) == 0; @@ -31,4 +38,63 @@ constexpr bool IsWordAligned(T value) { return (value & 0b11) == 0; } +template <typename T, std::size_t Align = 16> +class AlignmentAllocator { +public: + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + using pointer = T*; + using const_pointer = const T*; + + using reference = T&; + using const_reference = const T&; + +public: + pointer address(reference r) noexcept { + return std::addressof(r); + } + + const_pointer address(const_reference r) const noexcept { + return std::addressof(r); + } + + pointer allocate(size_type n) { + return static_cast<pointer>(::operator new (n, std::align_val_t{Align})); + } + + void deallocate(pointer p, size_type) { + ::operator delete (p, std::align_val_t{Align}); + } + + void construct(pointer p, const value_type& wert) { + new (p) value_type(wert); + } + + void destroy(pointer p) { + p->~value_type(); + } + + size_type max_size() const noexcept { + return size_type(-1) / sizeof(value_type); + } + + template <typename T2> + struct rebind { + using other = AlignmentAllocator<T2, Align>; + }; + + bool operator!=(const AlignmentAllocator<T, Align>& other) const noexcept { + return !(*this == other); + } + + // Returns true if and only if storage allocated from *this + // can be deallocated from other, and vice versa. + // Always returns true for stateless allocators. + bool operator==(const AlignmentAllocator<T, Align>& other) const noexcept { + return true; + } +}; + } // namespace Common diff --git a/src/common/binary_find.h b/src/common/binary_find.h new file mode 100644 index 000000000..5cc523bf9 --- /dev/null +++ b/src/common/binary_find.h @@ -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 diff --git a/src/common/bit_util.h b/src/common/bit_util.h index d032df413..6f7d5a947 100644 --- a/src/common/bit_util.h +++ b/src/common/bit_util.h @@ -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 diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 8b0d34da6..04ecac959 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -4,6 +4,7 @@ #pragma once +#include <algorithm> #include <string> #if !defined(ARCHITECTURE_x86_64) diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp index 5b63f9e81..c2f6cf0f6 100644 --- a/src/common/hex_util.cpp +++ b/src/common/hex_util.cpp @@ -30,13 +30,6 @@ std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) { return out; } -std::string HexVectorToString(const std::vector<u8>& vector, bool upper) { - std::string out; - for (u8 c : vector) - out += fmt::format(upper ? "{:02X}" : "{:02x}", c); - return out; -} - std::array<u8, 16> operator""_array16(const char* str, std::size_t len) { if (len != 32) { LOG_ERROR(Common, diff --git a/src/common/hex_util.h b/src/common/hex_util.h index 68f003cb6..bb4736f96 100644 --- a/src/common/hex_util.h +++ b/src/common/hex_util.h @@ -7,6 +7,7 @@ #include <array> #include <cstddef> #include <string> +#include <type_traits> #include <vector> #include <fmt/format.h> #include "common/common_types.h" @@ -30,13 +31,20 @@ std::array<u8, Size> HexStringToArray(std::string_view str) { return out; } -std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true); +template <typename ContiguousContainer> +std::string HexToString(const ContiguousContainer& data, bool upper = true) { + static_assert(std::is_same_v<typename ContiguousContainer::value_type, u8>, + "Underlying type within the contiguous container must be u8."); + + constexpr std::size_t pad_width = 2; -template <std::size_t Size> -std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) { std::string out; - for (u8 c : array) + out.reserve(std::size(data) * pad_width); + + for (const u8 c : data) { out += fmt::format(upper ? "{:02X}" : "{:02x}", c); + } + return out; } diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index a03179520..1111cfbad 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -255,6 +255,7 @@ void DebuggerBackend::Write(const Entry& entry) { CLS(Input) \ CLS(Network) \ CLS(Loader) \ + CLS(CheatEngine) \ CLS(Crypto) \ CLS(WebService) diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 8ed6d5050..259708116 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -117,6 +117,7 @@ enum class Class : ClassType { Audio_DSP, ///< The HLE implementation of the DSP Audio_Sink, ///< Emulator audio output backend Loader, ///< ROM loader + CheatEngine, ///< Memory manipulation and engine VM functions Crypto, ///< Cryptographic engine/functions Input, ///< Input emulation Network, ///< Network emulation diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a2e2e976e..a6b56c9c6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -33,8 +33,6 @@ add_library(core STATIC file_sys/bis_factory.h file_sys/card_image.cpp file_sys/card_image.h - file_sys/cheat_engine.cpp - file_sys/cheat_engine.h file_sys/content_archive.cpp file_sys/content_archive.h file_sys/control_metadata.cpp @@ -45,6 +43,8 @@ add_library(core STATIC file_sys/fsmitm_romfsbuild.h file_sys/ips_layer.cpp file_sys/ips_layer.h + file_sys/kernel_executable.cpp + file_sys/kernel_executable.h file_sys/mode.h file_sys/nca_metadata.cpp file_sys/nca_metadata.h @@ -68,6 +68,8 @@ add_library(core STATIC file_sys/sdmc_factory.h file_sys/submission_package.cpp file_sys/submission_package.h + file_sys/system_archive/mii_model.cpp + file_sys/system_archive/mii_model.h file_sys/system_archive/ng_word.cpp file_sys/system_archive/ng_word.h file_sys/system_archive/system_archive.cpp @@ -109,6 +111,8 @@ add_library(core STATIC frontend/scope_acquire_window_context.h gdbstub/gdbstub.cpp gdbstub/gdbstub.h + hardware_interrupt_manager.cpp + hardware_interrupt_manager.h hle/ipc.h hle/ipc_helpers.h hle/kernel/address_arbiter.cpp @@ -173,6 +177,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 @@ -205,10 +210,10 @@ add_library(core STATIC hle/service/aoc/aoc_u.h hle/service/apm/apm.cpp hle/service/apm/apm.h + hle/service/apm/controller.cpp + hle/service/apm/controller.h hle/service/apm/interface.cpp hle/service/apm/interface.h - hle/service/arp/arp.cpp - hle/service/arp/arp.h hle/service/audio/audctl.cpp hle/service/audio/audctl.h hle/service/audio/auddbg.cpp @@ -270,10 +275,20 @@ 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 hle/service/friend/interface.h + hle/service/glue/arp.cpp + hle/service/glue/arp.h + hle/service/glue/bgtc.cpp + hle/service/glue/bgtc.h + hle/service/glue/errors.h + hle/service/glue/glue.cpp + hle/service/glue/glue.h + hle/service/glue/manager.cpp + hle/service/glue/manager.h hle/service/grc/grc.cpp hle/service/grc/grc.h hle/service/hid/hid.cpp @@ -282,6 +297,7 @@ add_library(core STATIC hle/service/hid/irs.h hle/service/hid/xcd.cpp hle/service/hid/xcd.h + hle/service/hid/errors.h hle/service/hid/controllers/controller_base.cpp hle/service/hid/controllers/controller_base.h hle/service/hid/controllers/debug_pad.cpp @@ -358,6 +374,7 @@ add_library(core STATIC hle/service/nvdrv/devices/nvmap.h hle/service/nvdrv/interface.cpp hle/service/nvdrv/interface.h + hle/service/nvdrv/nvdata.h hle/service/nvdrv/nvdrv.cpp hle/service/nvdrv/nvdrv.h hle/service/nvdrv/nvmemp.cpp @@ -420,6 +437,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 @@ -440,6 +459,8 @@ add_library(core STATIC loader/deconstructed_rom_directory.h loader/elf.cpp loader/elf.h + loader/kip.cpp + loader/kip.h loader/loader.cpp loader/loader.h loader/nax.cpp @@ -454,24 +475,30 @@ add_library(core STATIC loader/nsp.h loader/xci.cpp loader/xci.h + memory/cheat_engine.cpp + memory/cheat_engine.h + memory/dmnt_cheat_types.h + memory/dmnt_cheat_vm.cpp + memory/dmnt_cheat_vm.h memory.cpp memory.h memory_setup.h perf_stats.cpp perf_stats.h + reporter.cpp + reporter.h settings.cpp settings.h telemetry_session.cpp telemetry_session.h - tracer/citrace.h - tracer/recorder.cpp - tracer/recorder.h + tools/freezer.cpp + tools/freezer.h ) create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) -target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt mbedtls opus unicorn open_source_archives) +target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn open_source_archives) if (ENABLE_WEB_SERVICE) target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) target_link_libraries(core PRIVATE web_service) diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index 2223cbeed..372612c9b 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -2,26 +2,213 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <map> +#include <optional> +#include "common/bit_field.h" #include "common/common_types.h" #include "common/logging/log.h" #include "core/arm/arm_interface.h" +#include "core/core.h" +#include "core/loader/loader.h" #include "core/memory.h" namespace Core { -void ARM_Interface::LogBacktrace() const { - VAddr fp = GetReg(29); - VAddr lr = GetReg(30); - const VAddr sp = GetReg(13); - const VAddr pc = GetPC(); - LOG_ERROR(Core_ARM, "Backtrace, sp={:016X}, pc={:016X}", sp, pc); +namespace { + +constexpr u64 ELF_DYNAMIC_TAG_NULL = 0; +constexpr u64 ELF_DYNAMIC_TAG_STRTAB = 5; +constexpr u64 ELF_DYNAMIC_TAG_SYMTAB = 6; +constexpr u64 ELF_DYNAMIC_TAG_SYMENT = 11; + +enum class ELFSymbolType : u8 { + None = 0, + Object = 1, + Function = 2, + Section = 3, + File = 4, + Common = 5, + TLS = 6, +}; + +enum class ELFSymbolBinding : u8 { + Local = 0, + Global = 1, + Weak = 2, +}; + +enum class ELFSymbolVisibility : u8 { + Default = 0, + Internal = 1, + Hidden = 2, + Protected = 3, +}; + +struct ELFSymbol { + u32 name_index; + union { + u8 info; + + BitField<0, 4, ELFSymbolType> type; + BitField<4, 4, ELFSymbolBinding> binding; + }; + ELFSymbolVisibility visibility; + u16 sh_index; + u64 value; + u64 size; +}; +static_assert(sizeof(ELFSymbol) == 0x18, "ELFSymbol has incorrect size."); + +using Symbols = std::vector<std::pair<ELFSymbol, std::string>>; + +Symbols GetSymbols(VAddr text_offset) { + const auto mod_offset = text_offset + Memory::Read32(text_offset + 4); + + if (mod_offset < text_offset || (mod_offset & 0b11) != 0 || + Memory::Read32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) { + return {}; + } + + const auto dynamic_offset = Memory::Read32(mod_offset + 0x4) + mod_offset; + + VAddr string_table_offset{}; + VAddr symbol_table_offset{}; + u64 symbol_entry_size{}; + + VAddr dynamic_index = dynamic_offset; + while (true) { + const auto tag = Memory::Read64(dynamic_index); + const auto value = Memory::Read64(dynamic_index + 0x8); + dynamic_index += 0x10; + + if (tag == ELF_DYNAMIC_TAG_NULL) { + break; + } + + if (tag == ELF_DYNAMIC_TAG_STRTAB) { + string_table_offset = value; + } else if (tag == ELF_DYNAMIC_TAG_SYMTAB) { + symbol_table_offset = value; + } else if (tag == ELF_DYNAMIC_TAG_SYMENT) { + symbol_entry_size = value; + } + } + + if (string_table_offset == 0 || symbol_table_offset == 0 || symbol_entry_size == 0) { + return {}; + } + + const auto string_table_address = text_offset + string_table_offset; + const auto symbol_table_address = text_offset + symbol_table_offset; + + Symbols out; + + VAddr symbol_index = symbol_table_address; + while (symbol_index < string_table_address) { + ELFSymbol symbol{}; + Memory::ReadBlock(symbol_index, &symbol, sizeof(ELFSymbol)); + + VAddr string_offset = string_table_address + symbol.name_index; + std::string name; + for (u8 c = Memory::Read8(string_offset); c != 0; c = Memory::Read8(++string_offset)) { + name += static_cast<char>(c); + } + + symbol_index += symbol_entry_size; + out.push_back({symbol, name}); + } + + return out; +} + +std::optional<std::string> GetSymbolName(const Symbols& symbols, VAddr func_address) { + const auto iter = + std::find_if(symbols.begin(), symbols.end(), [func_address](const auto& pair) { + const auto& [symbol, name] = pair; + const auto end_address = symbol.value + symbol.size; + return func_address >= symbol.value && func_address < end_address; + }); + + if (iter == symbols.end()) { + return std::nullopt; + } + + return iter->second; +} + +} // Anonymous namespace + +constexpr u64 SEGMENT_BASE = 0x7100000000ull; + +std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const { + std::vector<BacktraceEntry> out; + + auto fp = GetReg(29); + auto lr = GetReg(30); + while (true) { - LOG_ERROR(Core_ARM, "{:016X}", lr); + out.push_back({"", 0, lr, 0}); if (!fp) { break; } lr = Memory::Read64(fp + 8) - 4; fp = Memory::Read64(fp); } + + std::map<VAddr, std::string> modules; + auto& loader{System::GetInstance().GetAppLoader()}; + if (loader.ReadNSOModules(modules) != Loader::ResultStatus::Success) { + return {}; + } + + std::map<std::string, Symbols> symbols; + for (const auto& module : modules) { + symbols.insert_or_assign(module.second, GetSymbols(module.first)); + } + + for (auto& entry : out) { + VAddr base = 0; + for (auto iter = modules.rbegin(); iter != modules.rend(); ++iter) { + const auto& module{*iter}; + if (entry.original_address >= module.first) { + entry.module = module.second; + base = module.first; + break; + } + } + + entry.offset = entry.original_address - base; + entry.address = SEGMENT_BASE + entry.offset; + + if (entry.module.empty()) + entry.module = "unknown"; + + const auto symbol_set = symbols.find(entry.module); + if (symbol_set != symbols.end()) { + const auto symbol = GetSymbolName(symbol_set->second, entry.offset); + if (symbol.has_value()) { + // TODO(DarkLordZach): Add demangling of symbol names. + entry.name = *symbol; + } + } + } + + return out; +} + +void ARM_Interface::LogBacktrace() const { + const VAddr sp = GetReg(13); + const VAddr pc = GetPC(); + LOG_ERROR(Core_ARM, "Backtrace, sp={:016X}, pc={:016X}", sp, pc); + LOG_ERROR(Core_ARM, "{:20}{:20}{:20}{:20}{}", "Module Name", "Address", "Original Address", + "Offset", "Symbol"); + LOG_ERROR(Core_ARM, ""); + + const auto backtrace = GetBacktrace(); + for (const auto& entry : backtrace) { + LOG_ERROR(Core_ARM, "{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address, + entry.original_address, entry.offset, entry.name); + } } + } // namespace Core diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index 978b1518f..45e94e625 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include <vector> #include "common/common_types.h" namespace Common { @@ -43,13 +44,6 @@ public: /// Step CPU by one instruction virtual void Step() = 0; - /// Maps a backing memory region for the CPU - virtual void MapBackingMemory(VAddr address, std::size_t size, u8* memory, - Kernel::VMAPermission perms) = 0; - - /// Unmaps a region of memory that was previously mapped using MapBackingMemory - virtual void UnmapMemory(VAddr address, std::size_t size) = 0; - /// Clear all instruction cache virtual void ClearInstructionCache() = 0; @@ -152,6 +146,16 @@ public: /// Prepare core for thread reschedule (if needed to correctly handle state) virtual void PrepareReschedule() = 0; + struct BacktraceEntry { + std::string module; + u64 address; + u64 original_address; + u64 offset; + std::string name; + }; + + std::vector<BacktraceEntry> GetBacktrace() const; + /// fp (= r29) points to the last frame record. /// Note that this is the frame record for the *previous* frame, not the current one. /// Note we need to subtract 4 from our last read to get the proper address diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index e6f6822d2..2a08d4cea 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -177,15 +177,6 @@ ARM_Dynarmic::ARM_Dynarmic(System& system, ExclusiveMonitor& exclusive_monitor, ARM_Dynarmic::~ARM_Dynarmic() = default; -void ARM_Dynarmic::MapBackingMemory(u64 address, std::size_t size, u8* memory, - Kernel::VMAPermission perms) { - inner_unicorn.MapBackingMemory(address, size, memory, perms); -} - -void ARM_Dynarmic::UnmapMemory(u64 address, std::size_t size) { - inner_unicorn.UnmapMemory(address, size); -} - void ARM_Dynarmic::SetPC(u64 pc) { jit->SetPC(pc); } diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index b701e97a3..504d46c68 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -23,9 +23,6 @@ public: ARM_Dynarmic(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index); ~ARM_Dynarmic() override; - void MapBackingMemory(VAddr address, std::size_t size, u8* memory, - Kernel::VMAPermission perms) override; - void UnmapMemory(u64 address, std::size_t size) override; void SetPC(u64 pc) override; u64 GetPC() const override; u64 GetReg(int index) const override; diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp index 4e07fe8b5..97d5c2a8a 100644 --- a/src/core/arm/unicorn/arm_unicorn.cpp +++ b/src/core/arm/unicorn/arm_unicorn.cpp @@ -50,11 +50,14 @@ static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_ static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value, void* user_data) { + auto* const system = static_cast<System*>(user_data); + ARM_Interface::ThreadContext ctx{}; - Core::CurrentArmInterface().SaveContext(ctx); + system->CurrentArmInterface().SaveContext(ctx); ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr, ctx.pc, ctx.cpu_registers[30]); - return {}; + + return false; } ARM_Unicorn::ARM_Unicorn(System& system) : system{system} { @@ -65,7 +68,7 @@ ARM_Unicorn::ARM_Unicorn(System& system) : system{system} { uc_hook hook{}; CHECKED(uc_hook_add(uc, &hook, UC_HOOK_INTR, (void*)InterruptHook, this, 0, -1)); - CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, 0, -1)); + CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, &system, 0, -1)); if (GDBStub::IsServerEnabled()) { CHECKED(uc_hook_add(uc, &hook, UC_HOOK_CODE, (void*)CodeHook, this, 0, -1)); last_bkpt_hit = false; @@ -76,15 +79,6 @@ ARM_Unicorn::~ARM_Unicorn() { CHECKED(uc_close(uc)); } -void ARM_Unicorn::MapBackingMemory(VAddr address, std::size_t size, u8* memory, - Kernel::VMAPermission perms) { - CHECKED(uc_mem_map_ptr(uc, address, size, static_cast<u32>(perms), memory)); -} - -void ARM_Unicorn::UnmapMemory(VAddr address, std::size_t size) { - CHECKED(uc_mem_unmap(uc, address, size)); -} - void ARM_Unicorn::SetPC(u64 pc) { CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &pc)); } diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h index 34e974b4d..fe2ffd70c 100644 --- a/src/core/arm/unicorn/arm_unicorn.h +++ b/src/core/arm/unicorn/arm_unicorn.h @@ -18,9 +18,6 @@ public: explicit ARM_Unicorn(System& system); ~ARM_Unicorn() override; - void MapBackingMemory(VAddr address, std::size_t size, u8* memory, - Kernel::VMAPermission perms) override; - void UnmapMemory(VAddr address, std::size_t size) override; void SetPC(u64 pc) override; u64 GetPC() const override; u64 GetReg(int index) const override; diff --git a/src/core/core.cpp b/src/core/core.cpp index ff0721079..92ba42fb9 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -14,30 +14,66 @@ #include "core/core_cpu.h" #include "core/core_timing.h" #include "core/cpu_core_manager.h" +#include "core/file_sys/bis_factory.h" +#include "core/file_sys/card_image.h" #include "core/file_sys/mode.h" +#include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" +#include "core/file_sys/romfs_factory.h" +#include "core/file_sys/savedata_factory.h" +#include "core/file_sys/sdmc_factory.h" #include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_real.h" #include "core/gdbstub/gdbstub.h" +#include "core/hardware_interrupt_manager.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/apm/controller.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/glue/manager.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" #include "core/loader/loader.h" +#include "core/memory/cheat_engine.h" #include "core/perf_stats.h" +#include "core/reporter.h" #include "core/settings.h" #include "core/telemetry_session.h" -#include "file_sys/cheat_engine.h" +#include "core/tools/freezer.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" namespace Core { +namespace { + +FileSys::StorageId GetStorageIdForFrontendSlot( + std::optional<FileSys::ContentProviderUnionSlot> slot) { + if (!slot.has_value()) { + return FileSys::StorageId::None; + } + + switch (*slot) { + case FileSys::ContentProviderUnionSlot::UserNAND: + return FileSys::StorageId::NandUser; + case FileSys::ContentProviderUnionSlot::SysNAND: + return FileSys::StorageId::NandSystem; + case FileSys::ContentProviderUnionSlot::SDMC: + return FileSys::StorageId::SdCard; + case FileSys::ContentProviderUnionSlot::FrontendManual: + return FileSys::StorageId::Host; + default: + return FileSys::StorageId::None; + } +} + +} // Anonymous namespace + /*static*/ System System::s_instance; FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, @@ -74,7 +110,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, return vfs->OpenFile(path, FileSys::Mode::Read); } struct System::Impl { - explicit Impl(System& system) : kernel{system}, cpu_core_manager{system} {} + explicit Impl(System& system) + : kernel{system}, cpu_core_manager{system}, applet_manager{system}, reporter{system} {} Cpu& CurrentCpuCore() { return cpu_core_manager.GetCurrentCore(); @@ -109,27 +146,27 @@ struct System::Impl { /// Create default implementations of applets if one is not provided. applet_manager.SetDefaultAppletsIfMissing(); + /// Reset all glue registrations + arp_manager.ResetAll(); + telemetry_session = std::make_unique<Core::TelemetrySession>(); service_manager = std::make_shared<Service::SM::ServiceManager>(); - Service::Init(service_manager, system, *virtual_filesystem); + Service::Init(service_manager, system); GDBStub::Init(); renderer = VideoCore::CreateRenderer(emu_window, system); if (!renderer->Init()) { return ResultStatus::ErrorVideoCore; } - + interrupt_manager = std::make_unique<Core::Hardware::InterruptManager>(system); gpu_core = VideoCore::CreateGPU(system); is_powered_on = true; + exit_lock = false; LOG_DEBUG(Core, "Initialized OK"); - // Reset counters and set time origin to current frame - GetAndResetPerfStats(); - perf_stats.BeginSystemFrame(); - return ResultStatus::Success; } @@ -150,7 +187,8 @@ struct System::Impl { } telemetry_session->AddInitialInfo(*app_loader); - auto main_process = Kernel::Process::Create(system, "main"); + auto main_process = + Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland); const auto [load_result, load_parameters] = app_loader->Load(*main_process); if (load_result != Loader::ResultStatus::Success) { LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result)); @@ -159,6 +197,7 @@ struct System::Impl { return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) + static_cast<u32>(load_result)); } + AddGlueRegistrationForProcess(*app_loader, *main_process); kernel.MakeCurrentProcess(main_process.get()); // Main process has been loaded and been made current. @@ -166,10 +205,34 @@ struct System::Impl { gpu_core->Start(); cpu_core_manager.StartThreads(); + // Initialize cheat engine + if (cheat_engine) { + cheat_engine->Initialize(); + } + // All threads are started, begin main process execution, now that we're in the clear. main_process->Run(load_parameters->main_thread_priority, load_parameters->main_thread_stack_size); + if (Settings::values.gamecard_inserted) { + if (Settings::values.gamecard_current_game) { + fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath)); + } else if (!Settings::values.gamecard_path.empty()) { + fs_controller.SetGameCard( + GetGameFileFromPath(virtual_filesystem, Settings::values.gamecard_path)); + } + } + + u64 title_id{0}; + if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { + LOG_ERROR(Core, "Failed to find title id for ROM (Error {})", + static_cast<u32>(load_result)); + } + perf_stats = std::make_unique<PerfStats>(title_id); + // Reset counters and set time origin to current frame + GetAndResetPerfStats(); + perf_stats->BeginSystemFrame(); + status = ResultStatus::Success; return status; } @@ -183,8 +246,11 @@ struct System::Impl { perf_results.game_fps); telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime", perf_results.frametime * 1000.0); + telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS", + perf_stats->GetMeanFrametime()); is_powered_on = false; + exit_lock = false; // Shutdown emulation session renderer.reset(); @@ -193,6 +259,7 @@ struct System::Impl { service_manager.reset(); cheat_engine.reset(); telemetry_session.reset(); + perf_stats.reset(); gpu_core.reset(); // Close all CPU/threading state @@ -217,6 +284,31 @@ struct System::Impl { return app_loader->ReadTitle(out); } + void AddGlueRegistrationForProcess(Loader::AppLoader& loader, Kernel::Process& process) { + std::vector<u8> nacp_data; + FileSys::NACP nacp; + if (loader.ReadControlData(nacp) == Loader::ResultStatus::Success) { + nacp_data = nacp.GetRawBytes(); + } else { + nacp_data.resize(sizeof(FileSys::RawNACP)); + } + + Service::Glue::ApplicationLaunchProperty launch{}; + launch.title_id = process.GetTitleID(); + + FileSys::PatchManager pm{launch.title_id}; + launch.version = pm.GetGameVersion().value_or(0); + + // TODO(DarkLordZach): When FSController/Game Card Support is added, if + // current_process_game_card use correct StorageId + launch.base_game_storage_id = GetStorageIdForFrontendSlot(content_provider->GetSlotForEntry( + launch.title_id, FileSys::ContentRecordType::Program)); + launch.update_storage_id = GetStorageIdForFrontendSlot(content_provider->GetSlotForEntry( + FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program)); + + arp_manager.Register(launch.title_id, launch, std::move(nacp_data)); + } + void SetStatus(ResultStatus new_status, const char* details = nullptr) { status = new_status; if (details) { @@ -225,7 +317,7 @@ struct System::Impl { } PerfStatsResults GetAndResetPerfStats() { - return perf_stats.GetAndResetStats(core_timing.GetGlobalTimeUs()); + return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs()); } Timing::CoreTiming core_timing; @@ -234,29 +326,41 @@ struct System::Impl { FileSys::VirtualFilesystem virtual_filesystem; /// ContentProviderUnion instance std::unique_ptr<FileSys::ContentProviderUnion> content_provider; + Service::FileSystem::FileSystemController fs_controller; /// AppLoader used to load the current executing application std::unique_ptr<Loader::AppLoader> app_loader; std::unique_ptr<VideoCore::RendererBase> renderer; std::unique_ptr<Tegra::GPU> gpu_core; std::shared_ptr<Tegra::DebugContext> debug_context; + std::unique_ptr<Core::Hardware::InterruptManager> interrupt_manager; CpuCoreManager cpu_core_manager; bool is_powered_on = false; + bool exit_lock = false; - std::unique_ptr<FileSys::CheatEngine> cheat_engine; + std::unique_ptr<Memory::CheatEngine> cheat_engine; + std::unique_ptr<Tools::Freezer> memory_freezer; /// Frontend applets Service::AM::Applets::AppletManager applet_manager; + /// APM (Performance) services + Service::APM::Controller apm_controller{core_timing}; + + /// Glue services + Service::Glue::ARPManager arp_manager; + /// Service manager std::shared_ptr<Service::SM::ServiceManager> service_manager; /// Telemetry session for this emulation session std::unique_ptr<Core::TelemetrySession> telemetry_session; + Reporter reporter; + ResultStatus status = ResultStatus::Success; std::string status_details = ""; - Core::PerfStats perf_stats; + std::unique_ptr<Core::PerfStats> perf_stats; Core::FrameLimiter frame_limiter; }; @@ -376,6 +480,14 @@ const Tegra::GPU& System::GPU() const { return *impl->gpu_core; } +Core::Hardware::InterruptManager& System::InterruptManager() { + return *impl->interrupt_manager; +} + +const Core::Hardware::InterruptManager& System::InterruptManager() const { + return *impl->interrupt_manager; +} + VideoCore::RendererBase& System::Renderer() { return *impl->renderer; } @@ -401,11 +513,11 @@ const Timing::CoreTiming& System::CoreTiming() const { } Core::PerfStats& System::GetPerfStats() { - return impl->perf_stats; + return *impl->perf_stats; } const Core::PerfStats& System::GetPerfStats() const { - return impl->perf_stats; + return *impl->perf_stats; } Core::FrameLimiter& System::FrameLimiter() { @@ -440,13 +552,6 @@ Tegra::DebugContext* System::GetGPUDebugContext() const { return impl->debug_context.get(); } -void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list, - const std::string& build_id, VAddr code_region_start, - VAddr code_region_end) { - impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id, - code_region_start, code_region_end); -} - void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { impl->virtual_filesystem = std::move(vfs); } @@ -455,6 +560,13 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const { return impl->virtual_filesystem; } +void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list, + const std::array<u8, 32>& build_id, VAddr main_region_begin, + u64 main_region_size) { + impl->cheat_engine = std::make_unique<Memory::CheatEngine>(*this, list, build_id); + impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size); +} + void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) { impl->applet_manager.SetAppletFrontendSet(std::move(set)); } @@ -483,6 +595,14 @@ const FileSys::ContentProvider& System::GetContentProvider() const { return *impl->content_provider; } +Service::FileSystem::FileSystemController& System::GetFileSystemController() { + return impl->fs_controller; +} + +const Service::FileSystem::FileSystemController& System::GetFileSystemController() const { + return impl->fs_controller; +} + void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot, FileSys::ContentProvider* provider) { impl->content_provider->SetSlot(slot, provider); @@ -492,6 +612,34 @@ void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) { impl->content_provider->ClearSlot(slot); } +const Reporter& System::GetReporter() const { + return impl->reporter; +} + +Service::Glue::ARPManager& System::GetARPManager() { + return impl->arp_manager; +} + +const Service::Glue::ARPManager& System::GetARPManager() const { + return impl->arp_manager; +} + +Service::APM::Controller& System::GetAPMController() { + return impl->apm_controller; +} + +const Service::APM::Controller& System::GetAPMController() const { + return impl->apm_controller; +} + +void System::SetExitLock(bool locked) { + impl->exit_lock = locked; +} + +bool System::GetExitLock() const { + return impl->exit_lock; +} + System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { return impl->Init(*this, emu_window); } diff --git a/src/core/core.h b/src/core/core.h index 20959de54..ff10ebe12 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -8,6 +8,7 @@ #include <memory> #include <string> +#include <map> #include "common/common_types.h" #include "core/file_sys/vfs_types.h" #include "core/hle/kernel/object.h" @@ -17,7 +18,6 @@ class EmuWindow; } // namespace Core::Frontend namespace FileSys { -class CheatList; class ContentProvider; class ContentProviderUnion; enum class ContentProviderUnionSlot; @@ -35,6 +35,10 @@ class AppLoader; enum class ResultStatus : u16; } // namespace Loader +namespace Memory { +struct CheatEntry; +} // namespace Memory + namespace Service { namespace AM::Applets { @@ -42,6 +46,18 @@ struct AppletFrontendSet; class AppletManager; } // namespace AM::Applets +namespace APM { +class Controller; +} + +namespace FileSystem { +class FileSystemController; +} // namespace FileSystem + +namespace Glue { +class ARPManager; +} + namespace SM { class ServiceManager; } // namespace SM @@ -61,6 +77,10 @@ namespace Core::Timing { class CoreTiming; } +namespace Core::Hardware { +class InterruptManager; +} + namespace Core { class ARM_Interface; @@ -68,6 +88,7 @@ class Cpu; class ExclusiveMonitor; class FrameLimiter; class PerfStats; +class Reporter; class TelemetrySession; struct PerfStatsResults; @@ -224,6 +245,12 @@ public: /// Provides a constant reference to the core timing instance. const Timing::CoreTiming& CoreTiming() const; + /// Provides a reference to the interrupt manager instance. + Core::Hardware::InterruptManager& InterruptManager(); + + /// Provides a constant reference to the interrupt manager instance. + const Core::Hardware::InterruptManager& InterruptManager() const; + /// Provides a reference to the kernel instance. Kernel::KernelCore& Kernel(); @@ -262,8 +289,9 @@ public: std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; - void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id, - VAddr code_region_start, VAddr code_region_end); + void RegisterCheatList(const std::vector<Memory::CheatEntry>& list, + const std::array<u8, 0x20>& build_id, VAddr main_region_begin, + u64 main_region_size); void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set); @@ -279,11 +307,29 @@ public: const FileSys::ContentProvider& GetContentProvider() const; + Service::FileSystem::FileSystemController& GetFileSystemController(); + + const Service::FileSystem::FileSystemController& GetFileSystemController() const; + void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot, FileSys::ContentProvider* provider); void ClearContentProvider(FileSys::ContentProviderUnionSlot slot); + const Reporter& GetReporter() const; + + Service::Glue::ARPManager& GetARPManager(); + + const Service::Glue::ARPManager& GetARPManager() const; + + Service::APM::Controller& GetAPMController(); + + const Service::APM::Controller& GetAPMController() const; + + void SetExitLock(bool locked); + + bool GetExitLock() const; + private: System(); @@ -307,10 +353,6 @@ private: static System s_instance; }; -inline ARM_Interface& CurrentArmInterface() { - return System::GetInstance().CurrentArmInterface(); -} - inline Kernel::Process* CurrentProcess() { return System::GetInstance().CurrentProcess(); } diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp index ba63c3e61..21c410e34 100644 --- a/src/core/core_cpu.cpp +++ b/src/core/core_cpu.cpp @@ -53,16 +53,12 @@ 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) { #ifdef ARCHITECTURE_x86_64 - arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index); + arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index); #else - arm_interface = std::make_unique<ARM_Unicorn>(system); - LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); + arm_interface = std::make_unique<ARM_Unicorn>(system); + LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); #endif - } else { - arm_interface = std::make_unique<ARM_Unicorn>(system); - } scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface); } @@ -70,15 +66,12 @@ 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) { #ifdef ARCHITECTURE_x86_64 - return std::make_unique<DynarmicExclusiveMonitor>(num_cores); + return std::make_unique<DynarmicExclusiveMonitor>(num_cores); #else - return nullptr; // TODO(merry): Passthrough exclusive monitor + // TODO(merry): Passthrough exclusive monitor + return nullptr; #endif - } else { - return nullptr; // TODO(merry): Passthrough exclusive monitor - } } void Cpu::RunLoop(bool tight_loop) { diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 41adb2302..a58f7b131 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -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; diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 9d2efde37..161c7007d 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -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 diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index dc006e2bb..46aceec3d 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -37,6 +37,7 @@ namespace Core::Crypto { constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; +constexpr u64 FULL_TICKET_SIZE = 0x400; using namespace Common; @@ -55,6 +56,99 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{ {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, }; +namespace { +template <std::size_t Size> +bool IsAllZeroArray(const std::array<u8, Size>& array) { + return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; }); +} +} // namespace + +u64 GetSignatureTypeDataSize(SignatureType type) { + switch (type) { + case SignatureType::RSA_4096_SHA1: + case SignatureType::RSA_4096_SHA256: + return 0x200; + case SignatureType::RSA_2048_SHA1: + case SignatureType::RSA_2048_SHA256: + return 0x100; + case SignatureType::ECDSA_SHA1: + case SignatureType::ECDSA_SHA256: + return 0x3C; + } + UNREACHABLE(); +} + +u64 GetSignatureTypePaddingSize(SignatureType type) { + switch (type) { + case SignatureType::RSA_4096_SHA1: + case SignatureType::RSA_4096_SHA256: + case SignatureType::RSA_2048_SHA1: + case SignatureType::RSA_2048_SHA256: + return 0x3C; + case SignatureType::ECDSA_SHA1: + case SignatureType::ECDSA_SHA256: + return 0x40; + } + UNREACHABLE(); +} + +SignatureType Ticket::GetSignatureType() const { + if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { + return ticket->sig_type; + } + if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { + return ticket->sig_type; + } + if (auto ticket = std::get_if<ECDSATicket>(&data)) { + return ticket->sig_type; + } + + UNREACHABLE(); +} + +TicketData& Ticket::GetData() { + if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { + return ticket->data; + } + if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { + return ticket->data; + } + if (auto ticket = std::get_if<ECDSATicket>(&data)) { + return ticket->data; + } + + UNREACHABLE(); +} + +const TicketData& Ticket::GetData() const { + if (auto ticket = std::get_if<RSA4096Ticket>(&data)) { + return ticket->data; + } + if (auto ticket = std::get_if<RSA2048Ticket>(&data)) { + return ticket->data; + } + if (auto ticket = std::get_if<ECDSATicket>(&data)) { + return ticket->data; + } + + UNREACHABLE(); +} + +u64 Ticket::GetSize() const { + const auto sig_type = GetSignatureType(); + + return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) + + GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData); +} + +Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& rights_id) { + RSA2048Ticket out{}; + out.sig_type = SignatureType::RSA_2048_SHA256; + out.data.rights_id = rights_id; + out.data.title_key_common = title_key; + return Ticket{out}; +} + Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { Key128 out{}; @@ -135,6 +229,27 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { } } +RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { + if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) + return {}; + + const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); + + std::vector<u8> extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10); + std::array<u8, 0x230> extended_dec{}; + AESCipher<Key128> rsa_1(eticket_final, Mode::CTR); + rsa_1.SetIV(extended_iv); + rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, + extended_dec.data(), Op::Decrypt); + + RSAKeyPair<2048> rsa_key{}; + std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); + std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); + std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); + + return rsa_key; +} + Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB); Key128 mac_key{}; @@ -237,7 +352,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke return Loader::ResultStatus::Success; } -std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { +std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) { if (!ticket_save.IsOpen()) return {}; @@ -246,14 +361,14 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { return {}; } - std::vector<TicketRaw> out; + std::vector<Ticket> out; for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && buffer[offset + 3] == 0x0) { out.emplace_back(); auto& next = out.back(); - std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw)); - offset += next.size(); + std::memcpy(&next, buffer.data() + offset, sizeof(Ticket)); + offset += FULL_TICKET_SIZE; } } @@ -305,29 +420,23 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { return offset; } -std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, +std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, const RSAKeyPair<2048>& key) { - u32 cert_authority; - std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority)); - if (cert_authority == 0) + const auto issuer = ticket.GetData().issuer; + if (issuer == std::array<u8, 0x40>{}) return {}; - if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) { - LOG_INFO(Crypto, - "Attempting to parse ticket with non-standard certificate authority {:08X}.", - cert_authority); + if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { + LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); } - Key128 rights_id; - std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128)); + Key128 rights_id = ticket.GetData().rights_id; if (rights_id == Key128{}) return {}; - Key128 key_temp{}; - - if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) { - std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size()); - return std::make_pair(rights_id, key_temp); + if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), + ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { + return std::make_pair(rights_id, ticket.GetData().title_key_common); } mbedtls_mpi D; // RSA Private Exponent @@ -342,7 +451,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); - mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100); + mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100); mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); @@ -366,6 +475,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, return {}; ASSERT(*offset > 0); + Key128 key_temp{}; std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); return std::make_pair(rights_id, key_temp); @@ -450,6 +560,8 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); + } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { + eticket_extended_kek = Common::HexStringToArray<576>(out[1]); } else { for (const auto& kv : KEYS_VARIABLE_LENGTH) { if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) @@ -572,7 +684,7 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, << "# If you are experiencing issues involving keys, it may help to delete this file\n"; } - file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key)); + file << fmt::format("\n{} = {}", keyname, Common::HexToString(key)); AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title); } @@ -583,7 +695,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { Key128 rights_id; std::memcpy(rights_id.data(), &field2, sizeof(u64)); std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64)); - WriteKeyToFile(KeyCategory::Title, Common::HexArrayToString(rights_id), key); + WriteKeyToFile(KeyCategory::Title, Common::HexToString(rights_id), key); } auto category = KeyCategory::Standard; @@ -862,20 +974,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { // Titlekeys data.DecryptProdInfo(GetBISKey(0)); - const auto eticket_extended_kek = data.GetETicketExtendedKek(); + eticket_extended_kek = data.GetETicketExtendedKek(); + WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); + PopulateTickets(); +} - std::vector<u8> extended_iv(0x10); - std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size()); - std::array<u8, 0x230> extended_dec{}; - AESCipher<Key128> rsa_1(eticket_final, Mode::CTR); - rsa_1.SetIV(extended_iv); - rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, - extended_dec.data(), Op::Decrypt); +void KeyManager::PopulateTickets() { + const auto rsa_key = GetETicketRSAKey(); - RSAKeyPair<2048> rsa_key{}; - std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); - std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); - std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); + if (rsa_key == RSAKeyPair<2048>{}) + return; + + if (!common_tickets.empty() && !personal_tickets.empty()) + return; const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/80000000000000e1", @@ -886,19 +997,41 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { const auto blob2 = GetTicketblob(save2); auto res = GetTicketblob(save1); + const auto idx = res.size(); res.insert(res.end(), blob2.begin(), blob2.end()); - for (const auto& raw : res) { - const auto pair = ParseTicket(raw, rsa_key); + for (std::size_t i = 0; i < res.size(); ++i) { + const auto common = i < idx; + const auto pair = ParseTicket(res[i], rsa_key); if (!pair) continue; const auto& [rid, key] = *pair; u128 rights_id; std::memcpy(rights_id.data(), rid.data(), rid.size()); + + if (common) { + common_tickets[rights_id] = res[i]; + } else { + personal_tickets[rights_id] = res[i]; + } + SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); } } +void KeyManager::SynthesizeTickets() { + for (const auto& key : s128_keys) { + if (key.first.type != S128KeyType::Titlekey) { + continue; + } + u128 rights_id{key.first.field1, key.first.field2}; + Key128 rights_id_2; + std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size()); + const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2); + common_tickets.insert_or_assign(rights_id, ticket); + } +} + void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { if (key == Key128{}) return; @@ -997,6 +1130,46 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { DeriveBase(); } +const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const { + return common_tickets; +} + +const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const { + return personal_tickets; +} + +bool KeyManager::AddTicketCommon(Ticket raw) { + const auto rsa_key = GetETicketRSAKey(); + if (rsa_key == RSAKeyPair<2048>{}) + return false; + + const auto pair = ParseTicket(raw, rsa_key); + if (!pair) + return false; + const auto& [rid, key] = *pair; + u128 rights_id; + std::memcpy(rights_id.data(), rid.data(), rid.size()); + common_tickets[rights_id] = raw; + SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + return true; +} + +bool KeyManager::AddTicketPersonalized(Ticket raw) { + const auto rsa_key = GetETicketRSAKey(); + if (rsa_key == RSAKeyPair<2048>{}) + return false; + + const auto pair = ParseTicket(raw, rsa_key); + if (!pair) + return false; + const auto& [rid, key] = *pair; + u128 rights_id; + std::memcpy(rights_id.data(), rid.data(), rid.size()); + common_tickets[rights_id] = raw; + SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + return true; +} + const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, {"eticket_rsa_kek_source", diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 22f268c65..7265c4171 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -9,8 +9,10 @@ #include <optional> #include <string> +#include <variant> #include <boost/container/flat_map.hpp> #include <fmt/format.h> +#include "common/common_funcs.h" #include "common/common_types.h" #include "core/crypto/partition_data_manager.h" #include "core/file_sys/vfs_types.h" @@ -30,7 +32,79 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; using Key128 = std::array<u8, 0x10>; using Key256 = std::array<u8, 0x20>; using SHA256Hash = std::array<u8, 0x20>; -using TicketRaw = std::array<u8, 0x400>; + +enum class SignatureType { + RSA_4096_SHA1 = 0x10000, + RSA_2048_SHA1 = 0x10001, + ECDSA_SHA1 = 0x10002, + RSA_4096_SHA256 = 0x10003, + RSA_2048_SHA256 = 0x10004, + ECDSA_SHA256 = 0x10005, +}; + +u64 GetSignatureTypeDataSize(SignatureType type); +u64 GetSignatureTypePaddingSize(SignatureType type); + +enum class TitleKeyType : u8 { + Common = 0, + Personalized = 1, +}; + +struct TicketData { + std::array<u8, 0x40> issuer; + union { + std::array<u8, 0x100> title_key_block; + + struct { + Key128 title_key_common; + std::array<u8, 0xF0> title_key_common_pad; + }; + }; + + INSERT_PADDING_BYTES(0x1); + TitleKeyType type; + INSERT_PADDING_BYTES(0x3); + u8 revision; + INSERT_PADDING_BYTES(0xA); + u64 ticket_id; + u64 device_id; + std::array<u8, 0x10> rights_id; + u32 account_id; + INSERT_PADDING_BYTES(0x14C); +}; +static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size."); + +struct RSA4096Ticket { + SignatureType sig_type; + std::array<u8, 0x200> sig_data; + INSERT_PADDING_BYTES(0x3C); + TicketData data; +}; + +struct RSA2048Ticket { + SignatureType sig_type; + std::array<u8, 0x100> sig_data; + INSERT_PADDING_BYTES(0x3C); + TicketData data; +}; + +struct ECDSATicket { + SignatureType sig_type; + std::array<u8, 0x3C> sig_data; + INSERT_PADDING_BYTES(0x40); + TicketData data; +}; + +struct Ticket { + std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; + + SignatureType GetSignatureType() const; + TicketData& GetData(); + const TicketData& GetData() const; + u64 GetSize() const; + + static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id); +}; static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); @@ -43,6 +117,19 @@ struct RSAKeyPair { std::array<u8, 4> exponent; }; +template <size_t bit_size, size_t byte_size> +bool operator==(const RSAKeyPair<bit_size, byte_size>& lhs, + const RSAKeyPair<bit_size, byte_size>& rhs) { + return std::tie(lhs.encryption_key, lhs.decryption_key, lhs.modulus, lhs.exponent) == + std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent); +} + +template <size_t bit_size, size_t byte_size> +bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs, + const RSAKeyPair<bit_size, byte_size>& rhs) { + return !(lhs == rhs); +} + enum class KeyCategory : u8 { Standard, Title, @@ -151,22 +238,35 @@ public: static bool KeyFileExists(bool title); - // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save - // 8*43 and the private file to exist. + // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system + // save 8*43 and the private file to exist. void DeriveSDSeedLazy(); bool BaseDeriveNecessary() const; void DeriveBase(); void DeriveETicket(PartitionDataManager& data); + void PopulateTickets(); + void SynthesizeTickets(); void PopulateFromPartitionData(PartitionDataManager& data); + const std::map<u128, Ticket>& GetCommonTickets() const; + const std::map<u128, Ticket>& GetPersonalizedTickets() const; + + bool AddTicketCommon(Ticket raw); + bool AddTicketPersonalized(Ticket raw); + private: std::map<KeyIndex<S128KeyType>, Key128> s128_keys; std::map<KeyIndex<S256KeyType>, Key256> s256_keys; + // Map from rights ID to ticket + std::map<u128, Ticket> common_tickets; + std::map<u128, Ticket> personal_tickets; + std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; + std::array<u8, 576> eticket_extended_kek{}; bool dev_mode; void LoadFromFile(const std::string& filename, bool is_title_keys); @@ -178,6 +278,8 @@ private: void DeriveGeneralPurposeKeys(std::size_t crypto_revision); + RSAKeyPair<2048> GetETicketRSAKey() const; + void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); @@ -195,11 +297,11 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo std::optional<Key128> DeriveSDSeed(); Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); -std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save); +std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save); -// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset -// 0x140-0x144 is zero) -std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, +// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority +// (offset 0x140-0x144 is zero) +std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, const RSAKeyPair<2048>& eticket_extended_key); } // namespace Core::Crypto diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp index ed0775444..594cd82c5 100644 --- a/src/core/crypto/partition_data_manager.cpp +++ b/src/core/crypto/partition_data_manager.cpp @@ -22,8 +22,10 @@ #include "core/crypto/key_manager.h" #include "core/crypto/partition_data_manager.h" #include "core/crypto/xts_encryption_layer.h" +#include "core/file_sys/kernel_executable.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs_vector.h" using namespace Common; @@ -45,36 +47,6 @@ struct Package2Header { }; static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size."); -struct INIHeader { - u32_le magic; - u32_le size; - u32_le process_count; - INSERT_PADDING_BYTES(4); -}; -static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size."); - -struct SectionHeader { - u32_le offset; - u32_le size_decompressed; - u32_le size_compressed; - u32_le attribute; -}; -static_assert(sizeof(SectionHeader) == 0x10, "SectionHeader has incorrect size."); - -struct KIPHeader { - u32_le magic; - std::array<char, 12> name; - u64_le title_id; - u32_le category; - u8 priority; - u8 core; - INSERT_PADDING_BYTES(1); - u8 flags; - std::array<SectionHeader, 6> sections; - std::array<u32, 0x20> capabilities; -}; -static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size."); - const std::array<SHA256Hash, 0x10> source_hashes{ "B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source "7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source @@ -170,65 +142,6 @@ const std::array<SHA256Hash, 0x20> master_key_hashes{ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F }; -static std::vector<u8> DecompressBLZ(const std::vector<u8>& in) { - const auto data_size = in.size() - 0xC; - - u32 compressed_size{}; - u32 init_index{}; - u32 additional_size{}; - std::memcpy(&compressed_size, in.data() + data_size, sizeof(u32)); - std::memcpy(&init_index, in.data() + data_size + 0x4, sizeof(u32)); - std::memcpy(&additional_size, in.data() + data_size + 0x8, sizeof(u32)); - - std::vector<u8> out(in.size() + additional_size); - - if (compressed_size == in.size()) - std::memcpy(out.data(), in.data() + in.size() - compressed_size, compressed_size); - else - std::memcpy(out.data(), in.data(), compressed_size); - - auto index = in.size() - init_index; - auto out_index = out.size(); - - while (out_index > 0) { - --index; - auto control = in[index]; - for (size_t i = 0; i < 8; ++i) { - if ((control & 0x80) > 0) { - ASSERT(index >= 2); - index -= 2; - u64 segment_offset = in[index] | in[index + 1] << 8; - u64 segment_size = ((segment_offset >> 12) & 0xF) + 3; - segment_offset &= 0xFFF; - segment_offset += 3; - - if (out_index < segment_size) - segment_size = out_index; - - ASSERT(out_index >= segment_size); - - out_index -= segment_size; - - for (size_t j = 0; j < segment_size; ++j) { - ASSERT(out_index + j + segment_offset < out.size()); - out[out_index + j] = out[out_index + j + segment_offset]; - } - } else { - ASSERT(out_index >= 1); - --out_index; - --index; - out[out_index] = in[index]; - } - - control <<= 1; - if (out_index == 0) - return out; - } - } - - return out; -} - static u8 CalculateMaxKeyblobSourceHash() { for (s8 i = 0x1F; i >= 0; --i) { if (keyblob_source_hashes[i] != SHA256Hash{}) @@ -478,37 +391,22 @@ void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& packa cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()}); cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt); - INIHeader ini; - std::memcpy(&ini, c.data(), sizeof(INIHeader)); - if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1')) + const auto ini_file = std::make_shared<FileSys::VectorVfsFile>(c); + const FileSys::INI ini{ini_file}; + if (ini.GetStatus() != Loader::ResultStatus::Success) return; - u64 offset = sizeof(INIHeader); - for (size_t i = 0; i < ini.process_count; ++i) { - KIPHeader kip; - std::memcpy(&kip, c.data() + offset, sizeof(KIPHeader)); - if (kip.magic != Common::MakeMagic('K', 'I', 'P', '1')) + for (const auto& kip : ini.GetKIPs()) { + if (kip.GetStatus() != Loader::ResultStatus::Success) return; - const auto name = - Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size()); - - if (name != "FS" && name != "spl") { - offset += sizeof(KIPHeader) + kip.sections[0].size_compressed + - kip.sections[1].size_compressed + kip.sections[2].size_compressed; + if (kip.GetName() != "FS" && kip.GetName() != "spl") { continue; } - const u64 initial_offset = sizeof(KIPHeader) + offset; - const auto text_begin = c.cbegin() + initial_offset; - const auto text_end = text_begin + kip.sections[0].size_compressed; - const std::vector<u8> text = DecompressBLZ({text_begin, text_end}); - - const auto rodata_end = text_end + kip.sections[1].size_compressed; - const std::vector<u8> rodata = DecompressBLZ({text_end, rodata_end}); - - const auto data_end = rodata_end + kip.sections[2].size_compressed; - const std::vector<u8> data = DecompressBLZ({rodata_end, data_end}); + const auto& text = kip.GetTextSection(); + const auto& rodata = kip.GetRODataSection(); + const auto& data = kip.GetDataSection(); std::vector<u8> out; out.reserve(text.size() + rodata.size() + data.size()); @@ -516,12 +414,9 @@ void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& packa out.insert(out.end(), rodata.begin(), rodata.end()); out.insert(out.end(), data.begin(), data.end()); - offset += sizeof(KIPHeader) + kip.sections[0].size_compressed + - kip.sections[1].size_compressed + kip.sections[2].size_compressed; - - if (name == "FS") + if (kip.GetName() == "FS") package2_fs[static_cast<size_t>(type)] = std::move(out); - else if (name == "spl") + else if (kip.GetName() == "spl") package2_spl[static_cast<size_t>(type)] = std::move(out); } } @@ -585,6 +480,10 @@ void PartitionDataManager::DecryptProdInfo(std::array<u8, 0x20> bis_key) { prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key); } +FileSys::VirtualFile PartitionDataManager::GetDecryptedProdInfo() const { + return prodinfo_decrypted; +} + std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const { std::array<u8, 0x240> out{}; if (prodinfo_decrypted != nullptr) diff --git a/src/core/crypto/partition_data_manager.h b/src/core/crypto/partition_data_manager.h index 0ad007c72..7a7b5d038 100644 --- a/src/core/crypto/partition_data_manager.h +++ b/src/core/crypto/partition_data_manager.h @@ -84,6 +84,7 @@ public: bool HasProdInfo() const; FileSys::VirtualFile GetProdInfoRaw() const; void DecryptProdInfo(std::array<u8, 0x20> bis_key); + FileSys::VirtualFile GetDecryptedProdInfo() const; std::array<u8, 0x240> GetETicketExtendedKek() const; private: diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index e29f70b3a..8f758d6d9 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -3,8 +3,12 @@ // Refer to the license.txt file included. #include <fmt/format.h> +#include "common/file_util.h" +#include "core/core.h" #include "core/file_sys/bis_factory.h" +#include "core/file_sys/mode.h" #include "core/file_sys/registered_cache.h" +#include "core/settings.h" namespace FileSys { @@ -14,10 +18,22 @@ BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_, VirtualDir sysnand_cache(std::make_unique<RegisteredCache>( GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))), usrnand_cache(std::make_unique<RegisteredCache>( - GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {} + GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))), + sysnand_placeholder(std::make_unique<PlaceholderCache>( + GetOrCreateDirectoryRelative(nand_root, "/system/Contents/placehld"))), + usrnand_placeholder(std::make_unique<PlaceholderCache>( + GetOrCreateDirectoryRelative(nand_root, "/user/Contents/placehld"))) {} BISFactory::~BISFactory() = default; +VirtualDir BISFactory::GetSystemNANDContentDirectory() const { + return GetOrCreateDirectoryRelative(nand_root, "/system/Contents"); +} + +VirtualDir BISFactory::GetUserNANDContentDirectory() const { + return GetOrCreateDirectoryRelative(nand_root, "/user/Contents"); +} + RegisteredCache* BISFactory::GetSystemNANDContents() const { return sysnand_cache.get(); } @@ -26,9 +42,17 @@ RegisteredCache* BISFactory::GetUserNANDContents() const { return usrnand_cache.get(); } +PlaceholderCache* BISFactory::GetSystemNANDPlaceholder() const { + return sysnand_placeholder.get(); +} + +PlaceholderCache* BISFactory::GetUserNANDPlaceholder() const { + return usrnand_placeholder.get(); +} + VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const { // LayeredFS doesn't work on updates and title id-less homebrew - if (title_id == 0 || (title_id & 0x800) > 0) + if (title_id == 0 || (title_id & 0xFFF) == 0x800) return nullptr; return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id)); } @@ -39,4 +63,77 @@ VirtualDir BISFactory::GetModificationDumpRoot(u64 title_id) const { return GetOrCreateDirectoryRelative(dump_root, fmt::format("/{:016X}", title_id)); } +VirtualDir BISFactory::OpenPartition(BisPartitionId id) const { + switch (id) { + case BisPartitionId::CalibrationFile: + return GetOrCreateDirectoryRelative(nand_root, "/prodinfof"); + case BisPartitionId::SafeMode: + return GetOrCreateDirectoryRelative(nand_root, "/safe"); + case BisPartitionId::System: + return GetOrCreateDirectoryRelative(nand_root, "/system"); + case BisPartitionId::User: + return GetOrCreateDirectoryRelative(nand_root, "/user"); + default: + return nullptr; + } +} + +VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const { + Core::Crypto::KeyManager keys; + Core::Crypto::PartitionDataManager pdm{ + Core::System::GetInstance().GetFilesystem()->OpenDirectory( + FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), Mode::Read)}; + keys.PopulateFromPartitionData(pdm); + + switch (id) { + case BisPartitionId::CalibrationBinary: + return pdm.GetDecryptedProdInfo(); + case BisPartitionId::BootConfigAndPackage2Part1: + case BisPartitionId::BootConfigAndPackage2Part2: + case BisPartitionId::BootConfigAndPackage2Part3: + case BisPartitionId::BootConfigAndPackage2Part4: + case BisPartitionId::BootConfigAndPackage2Part5: + case BisPartitionId::BootConfigAndPackage2Part6: { + const auto new_id = static_cast<u8>(id) - + static_cast<u8>(BisPartitionId::BootConfigAndPackage2Part1) + + static_cast<u8>(Core::Crypto::Package2Type::NormalMain); + return pdm.GetPackage2Raw(static_cast<Core::Crypto::Package2Type>(new_id)); + } + default: + return nullptr; + } +} + +VirtualDir BISFactory::GetImageDirectory() const { + return GetOrCreateDirectoryRelative(nand_root, "/user/Album"); +} + +u64 BISFactory::GetSystemNANDFreeSpace() const { + const auto sys_dir = GetOrCreateDirectoryRelative(nand_root, "/system"); + if (sys_dir == nullptr) + return 0; + + return GetSystemNANDTotalSpace() - sys_dir->GetSize(); +} + +u64 BISFactory::GetSystemNANDTotalSpace() const { + return static_cast<u64>(Settings::values.nand_system_size); +} + +u64 BISFactory::GetUserNANDFreeSpace() const { + const auto usr_dir = GetOrCreateDirectoryRelative(nand_root, "/user"); + if (usr_dir == nullptr) + return 0; + + return GetUserNANDTotalSpace() - usr_dir->GetSize(); +} + +u64 BISFactory::GetUserNANDTotalSpace() const { + return static_cast<u64>(Settings::values.nand_user_size); +} + +u64 BISFactory::GetFullNANDTotalSpace() const { + return static_cast<u64>(Settings::values.nand_total_size); +} + } // namespace FileSys diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h index 453c11ad2..bdfe728c9 100644 --- a/src/core/file_sys/bis_factory.h +++ b/src/core/file_sys/bis_factory.h @@ -10,7 +10,25 @@ namespace FileSys { +enum class BisPartitionId : u32 { + UserDataRoot = 20, + CalibrationBinary = 27, + CalibrationFile = 28, + BootConfigAndPackage2Part1 = 21, + BootConfigAndPackage2Part2 = 22, + BootConfigAndPackage2Part3 = 23, + BootConfigAndPackage2Part4 = 24, + BootConfigAndPackage2Part5 = 25, + BootConfigAndPackage2Part6 = 26, + SafeMode = 29, + System = 31, + SystemProperEncryption = 32, + SystemProperPartition = 33, + User = 30, +}; + class RegisteredCache; +class PlaceholderCache; /// File system interface to the Built-In Storage /// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND @@ -20,12 +38,29 @@ public: explicit BISFactory(VirtualDir nand_root, VirtualDir load_root, VirtualDir dump_root); ~BISFactory(); + VirtualDir GetSystemNANDContentDirectory() const; + VirtualDir GetUserNANDContentDirectory() const; + RegisteredCache* GetSystemNANDContents() const; RegisteredCache* GetUserNANDContents() const; + PlaceholderCache* GetSystemNANDPlaceholder() const; + PlaceholderCache* GetUserNANDPlaceholder() const; + VirtualDir GetModificationLoadRoot(u64 title_id) const; VirtualDir GetModificationDumpRoot(u64 title_id) const; + VirtualDir OpenPartition(BisPartitionId id) const; + VirtualFile OpenPartitionStorage(BisPartitionId id) const; + + VirtualDir GetImageDirectory() const; + + u64 GetSystemNANDFreeSpace() const; + u64 GetSystemNANDTotalSpace() const; + u64 GetUserNANDFreeSpace() const; + u64 GetUserNANDTotalSpace() const; + u64 GetFullNANDTotalSpace() const; + private: VirtualDir nand_root; VirtualDir load_root; @@ -33,6 +68,9 @@ private: std::unique_ptr<RegisteredCache> sysnand_cache; std::unique_ptr<RegisteredCache> usrnand_cache; + + std::unique_ptr<PlaceholderCache> sysnand_placeholder; + std::unique_ptr<PlaceholderCache> usrnand_placeholder; }; } // namespace FileSys diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 2c145bd09..db54113a0 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -12,17 +12,26 @@ #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/romfs.h" #include "core/file_sys/submission_package.h" +#include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs_vector.h" #include "core/loader/loader.h" namespace FileSys { -constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"}; +constexpr u64 GAMECARD_CERTIFICATE_OFFSET = 0x7000; +constexpr std::array partition_names{ + "update", + "normal", + "secure", + "logo", +}; XCI::XCI(VirtualFile file_) : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, - partitions(0x4) { + partitions(partition_names.size()) { if (file->ReadObject(&header) != sizeof(GamecardHeader)) { status = Loader::ResultStatus::ErrorBadXCIHeader; return; @@ -43,23 +52,24 @@ XCI::XCI(VirtualFile file_) for (XCIPartition partition : {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { - auto raw = main_hfs.GetFile(partition_names[static_cast<std::size_t>(partition)]); - if (raw != nullptr) - partitions[static_cast<std::size_t>(partition)] = - std::make_shared<PartitionFilesystem>(raw); + const auto partition_idx = static_cast<std::size_t>(partition); + auto raw = main_hfs.GetFile(partition_names[partition_idx]); + + if (raw != nullptr) { + partitions[partition_idx] = std::make_shared<PartitionFilesystem>(std::move(raw)); + } } secure_partition = std::make_shared<NSP>( main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)])); - const auto secure_ncas = secure_partition->GetNCAsCollapsed(); - std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas)); - + ncas = secure_partition->GetNCAsCollapsed(); program = secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program); program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID()); - if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) + if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) { program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA; + } auto result = AddNCAFromPartition(XCIPartition::Update); if (result != Loader::ResultStatus::Success) { @@ -147,8 +157,9 @@ std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const { VirtualFile XCI::GetNCAFileByType(NCAContentType type) const { auto nca = GetNCAByType(type); - if (nca != nullptr) + if (nca != nullptr) { return nca->GetBaseFile(); + } return nullptr; } @@ -168,18 +179,43 @@ VirtualDir XCI::GetParentDirectory() const { return file->GetContainingDirectory(); } +VirtualDir XCI::ConcatenatedPseudoDirectory() { + const auto out = std::make_shared<VectorVfsDirectory>(); + for (const auto& part_id : {XCIPartition::Normal, XCIPartition::Logo, XCIPartition::Secure}) { + const auto& part = GetPartition(part_id); + if (part == nullptr) + continue; + + for (const auto& file : part->GetFiles()) + out->AddFile(file); + } + + return out; +} + +std::array<u8, 0x200> XCI::GetCertificate() const { + std::array<u8, 0x200> out; + file->Read(out.data(), out.size(), GAMECARD_CERTIFICATE_OFFSET); + return out; +} + Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { - if (partitions[static_cast<std::size_t>(part)] == nullptr) { + const auto partition_index = static_cast<std::size_t>(part); + const auto& partition = partitions[partition_index]; + + if (partition == nullptr) { return Loader::ResultStatus::ErrorXCIMissingPartition; } - for (const VirtualFile& file : partitions[static_cast<std::size_t>(part)]->GetFiles()) { - if (file->GetExtension() != "nca") + for (const VirtualFile& file : partition->GetFiles()) { + if (file->GetExtension() != "nca") { continue; + } + auto nca = std::make_shared<NCA>(file, nullptr, 0, keys); - // TODO(DarkLordZach): Add proper Rev1+ Support - if (nca->IsUpdate()) + if (nca->IsUpdate()) { continue; + } if (nca->GetType() == NCAContentType::Program) { program_nca_status = nca->GetStatus(); } @@ -188,7 +224,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { } else { const u16 error_id = static_cast<u16>(nca->GetStatus()); LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})", - partition_names[static_cast<std::size_t>(part)], nca->GetName(), error_id, + partition_names[partition_index], nca->GetName(), error_id, nca->GetStatus()); } } diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index a350496f7..3e6b92ff3 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -91,6 +91,8 @@ public: VirtualDir GetLogoPartition() const; u64 GetProgramTitleID() const; + u32 GetSystemUpdateVersion(); + u64 GetSystemUpdateTitleID() const; bool HasProgramNCA() const; VirtualFile GetProgramNCAFile() const; @@ -106,6 +108,11 @@ public: VirtualDir GetParentDirectory() const override; + // Creates a directory that contains all the NCAs in the gamecard + VirtualDir ConcatenatedPseudoDirectory(); + + std::array<u8, 0x200> GetCertificate() const; + private: Loader::ResultStatus AddNCAFromPartition(XCIPartition part); @@ -120,6 +127,8 @@ private: std::shared_ptr<NCA> program; std::vector<std::shared_ptr<NCA>> ncas; + u64 update_normal_partition_end; + Core::Crypto::KeyManager keys; }; } // namespace FileSys diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp deleted file mode 100644 index b06c2f20a..000000000 --- a/src/core/file_sys/cheat_engine.cpp +++ /dev/null @@ -1,492 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <locale> -#include "common/hex_util.h" -#include "common/microprofile.h" -#include "common/swap.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/core_timing_util.h" -#include "core/file_sys/cheat_engine.h" -#include "core/hle/kernel/process.h" -#include "core/hle/service/hid/controllers/npad.h" -#include "core/hle/service/hid/hid.h" -#include "core/hle/service/sm/sm.h" - -namespace FileSys { - -constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); -constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; - -u64 Cheat::Address() const { - u64 out; - std::memcpy(&out, raw.data(), sizeof(u64)); - return Common::swap64(out) & 0xFFFFFFFFFF; -} - -u64 Cheat::ValueWidth(u64 offset) const { - return Value(offset, width); -} - -u64 Cheat::Value(u64 offset, u64 width) const { - u64 out; - std::memcpy(&out, raw.data() + offset, sizeof(u64)); - out = Common::swap64(out); - if (width == 8) - return out; - return out & ((1ull << (width * CHAR_BIT)) - 1); -} - -u32 Cheat::KeypadValue() const { - u32 out; - std::memcpy(&out, raw.data(), sizeof(u32)); - return Common::swap32(out) & 0x0FFFFFFF; -} - -void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, - VAddr heap_end, MemoryWriter writer, MemoryReader reader) { - this->main_region_begin = main_begin; - this->main_region_end = main_end; - this->heap_region_begin = heap_begin; - this->heap_region_end = heap_end; - this->writer = writer; - this->reader = reader; -} - -MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); - -void CheatList::Execute() { - MICROPROFILE_SCOPE(Cheat_Engine); - - std::fill(scratch.begin(), scratch.end(), 0); - in_standard = false; - for (std::size_t i = 0; i < master_list.size(); ++i) { - LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first); - current_block = i; - ExecuteBlock(master_list[i].second); - } - - in_standard = true; - for (std::size_t i = 0; i < standard_list.size(); ++i) { - LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first); - current_block = i; - ExecuteBlock(standard_list[i].second); - } -} - -CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard) - : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {} - -bool CheatList::EvaluateConditional(const Cheat& cheat) const { - using ComparisonFunction = bool (*)(u64, u64); - constexpr std::array<ComparisonFunction, 6> comparison_functions{ - [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; }, - [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; }, - [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; }, - }; - - if (cheat.type == CodeType::ConditionalInput) { - const auto applet_resource = - system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource(); - if (applet_resource == nullptr) { - LOG_WARNING( - Common_Filesystem, - "Attempted to evaluate input conditional, but applet resource is not initialized!"); - return false; - } - - const auto press_state = - applet_resource - ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad) - .GetAndResetPressState(); - return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0; - } - - ASSERT(cheat.type == CodeType::Conditional); - - const auto offset = - cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; - ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6); - auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())]; - const auto addr = cheat.Address() + offset; - - return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8)); -} - -void CheatList::ProcessBlockPairs(const Block& block) { - block_pairs.clear(); - - u64 scope = 0; - std::map<u64, u64> pairs; - - for (std::size_t i = 0; i < block.size(); ++i) { - const auto& cheat = block[i]; - - switch (cheat.type) { - case CodeType::Conditional: - case CodeType::ConditionalInput: - pairs.insert_or_assign(scope, i); - ++scope; - break; - case CodeType::EndConditional: { - --scope; - const auto idx = pairs.at(scope); - block_pairs.insert_or_assign(idx, i); - break; - } - case CodeType::Loop: { - if (cheat.end_of_loop) { - --scope; - const auto idx = pairs.at(scope); - block_pairs.insert_or_assign(idx, i); - } else { - pairs.insert_or_assign(scope, i); - ++scope; - } - break; - } - } - } -} - -void CheatList::WriteImmediate(const Cheat& cheat) { - const auto offset = - cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; - const auto& register_3 = scratch.at(cheat.register_3); - - const auto addr = cheat.Address() + offset + register_3; - LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr, - cheat.Value(8, cheat.width)); - writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8)); -} - -void CheatList::BeginConditional(const Cheat& cheat) { - if (EvaluateConditional(cheat)) { - return; - } - - const auto iter = block_pairs.find(current_index); - ASSERT(iter != block_pairs.end()); - current_index = iter->second - 1; -} - -void CheatList::EndConditional(const Cheat& cheat) { - LOG_DEBUG(Common_Filesystem, "Ending conditional block."); -} - -void CheatList::Loop(const Cheat& cheat) { - if (cheat.end_of_loop.Value()) - ASSERT(!cheat.end_of_loop.Value()); - - auto& register_3 = scratch.at(cheat.register_3); - const auto iter = block_pairs.find(current_index); - ASSERT(iter != block_pairs.end()); - ASSERT(iter->first < iter->second); - - const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32))); - for (s32 i = initial_value; i >= 0; --i) { - register_3 = static_cast<u64>(i); - for (std::size_t c = iter->first + 1; c < iter->second; ++c) { - current_index = c; - ExecuteSingleCheat( - (in_standard ? standard_list : master_list)[current_block].second[c]); - } - } - - current_index = iter->second; -} - -void CheatList::LoadImmediate(const Cheat& cheat) { - auto& register_3 = scratch.at(cheat.register_3); - - LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3, - cheat.Value(4, 8)); - register_3 = cheat.Value(4, 8); -} - -void CheatList::LoadIndexed(const Cheat& cheat) { - const auto offset = - cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; - auto& register_3 = scratch.at(cheat.register_3); - - const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address(); - LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}", - cheat.register_3, addr); - register_3 = reader(cheat.width, SanitizeAddress(addr)); -} - -void CheatList::StoreIndexed(const Cheat& cheat) { - const auto& register_3 = scratch.at(cheat.register_3); - - const auto addr = - register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0); - LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", - cheat.Value(4, cheat.width), addr); - writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4)); -} - -void CheatList::RegisterArithmetic(const Cheat& cheat) { - using ArithmeticFunction = u64 (*)(u64, u64); - constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{ - [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; }, - [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; }, - [](u64 a, u64 b) { return a >> b; }, - }; - - using ArithmeticOverflowCheck = bool (*)(u64, u64); - constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{ - [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b - [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b - [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b - [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b - [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b - }; - - static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks), - "Missing or have extra arithmetic overflow checks compared to functions!"); - - auto& register_3 = scratch.at(cheat.register_3); - - ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5); - auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())]; - auto* overflow_function = - arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())]; - LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}", - cheat.register_3, cheat.ValueWidth(4)); - - if (overflow_function(register_3, cheat.ValueWidth(4))) { - LOG_WARNING(Common_Filesystem, - "overflow will occur when performing arithmetic operation={:02X} with operands " - "a={:016X}, b={:016X}!", - static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4)); - } - - register_3 = function(register_3, cheat.ValueWidth(4)); -} - -void CheatList::BeginConditionalInput(const Cheat& cheat) { - if (EvaluateConditional(cheat)) - return; - - const auto iter = block_pairs.find(current_index); - ASSERT(iter != block_pairs.end()); - current_index = iter->second - 1; -} - -VAddr CheatList::SanitizeAddress(VAddr in) const { - if ((in < main_region_begin || in >= main_region_end) && - (in < heap_region_begin || in >= heap_region_end)) { - LOG_ERROR(Common_Filesystem, - "Cheat attempting to access memory at invalid address={:016X}, if this persists, " - "the cheat may be incorrect. However, this may be normal early in execution if " - "the game has not properly set up yet.", - in); - return 0; ///< Invalid addresses will hard crash - } - - return in; -} - -void CheatList::ExecuteSingleCheat(const Cheat& cheat) { - using CheatOperationFunction = void (CheatList::*)(const Cheat&); - constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{ - &CheatList::WriteImmediate, &CheatList::BeginConditional, - &CheatList::EndConditional, &CheatList::Loop, - &CheatList::LoadImmediate, &CheatList::LoadIndexed, - &CheatList::StoreIndexed, &CheatList::RegisterArithmetic, - &CheatList::BeginConditionalInput, - }; - - const auto index = static_cast<u8>(cheat.type.Value()); - ASSERT(index < sizeof(cheat_operation_functions)); - const auto op = cheat_operation_functions[index]; - (this->*op)(cheat); -} - -void CheatList::ExecuteBlock(const Block& block) { - encountered_loops.clear(); - - ProcessBlockPairs(block); - for (std::size_t i = 0; i < block.size(); ++i) { - current_index = i; - ExecuteSingleCheat(block[i]); - i = current_index; - } -} - -CheatParser::~CheatParser() = default; - -CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master, - CheatList::ProgramSegment standard) const { - return {system, std::move(master), std::move(standard)}; -} - -TextCheatParser::~TextCheatParser() = default; - -CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const { - std::stringstream ss; - ss.write(reinterpret_cast<const char*>(data.data()), data.size()); - - std::vector<std::string> lines; - std::string stream_line; - while (std::getline(ss, stream_line)) { - // Remove a trailing \r - if (!stream_line.empty() && stream_line.back() == '\r') - stream_line.pop_back(); - lines.push_back(std::move(stream_line)); - } - - CheatList::ProgramSegment master_list; - CheatList::ProgramSegment standard_list; - - for (std::size_t i = 0; i < lines.size(); ++i) { - auto line = lines[i]; - - if (!line.empty() && (line[0] == '[' || line[0] == '{')) { - const auto master = line[0] == '{'; - const auto begin = master ? line.find('{') : line.find('['); - const auto end = master ? line.rfind('}') : line.rfind(']'); - - ASSERT(begin != std::string::npos && end != std::string::npos); - - const std::string patch_name{line.begin() + begin + 1, line.begin() + end}; - CheatList::Block block{}; - - while (i < lines.size() - 1) { - line = lines[++i]; - if (!line.empty() && (line[0] == '[' || line[0] == '{')) { - --i; - break; - } - - if (line.size() < 8) - continue; - - Cheat out{}; - out.raw = ParseSingleLineCheat(line); - block.push_back(out); - } - - (master ? master_list : standard_list).emplace_back(patch_name, block); - } - } - - return MakeCheatList(system, master_list, standard_list); -} - -std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const { - std::array<u8, 16> out{}; - - if (line.size() < 8) - return out; - - const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8}); - std::memcpy(out.data(), word1.data(), sizeof(u32)); - - if (line.size() < 17 || line[8] != ' ') - return out; - - const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8}); - std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32)); - - if (line.size() < 26 || line[17] != ' ') { - // Perform shifting in case value is truncated early. - const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); - if (type == CodeType::Loop || type == CodeType::LoadImmediate || - type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) { - std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32)); - std::memset(out.data() + 4, 0, sizeof(u32)); - } - - return out; - } - - const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8}); - std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32)); - - if (line.size() < 35 || line[26] != ' ') { - // Perform shifting in case value is truncated early. - const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); - if (type == CodeType::WriteImmediate || type == CodeType::Conditional) { - std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32)); - std::memset(out.data() + 8, 0, sizeof(u32)); - } - - return out; - } - - const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8}); - std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32)); - - return out; -} - -namespace { -u64 MemoryReadImpl(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 MemoryWriteImpl(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 - -CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_, - const std::string& build_id, VAddr code_region_start, - VAddr code_region_end) - : cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} { - event = core_timing.RegisterEvent( - "CheatEngine::FrameCallback::" + build_id, - [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); - core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); - - const auto& vm_manager = system.CurrentProcess()->VMManager(); - for (auto& list : this->cheats) { - list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(), - code_region_end, vm_manager.GetHeapRegionEndAddress(), - &MemoryWriteImpl, &MemoryReadImpl); - } -} - -CheatEngine::~CheatEngine() { - core_timing.UnscheduleEvent(event, 0); -} - -void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { - for (auto& list : cheats) { - list.Execute(); - } - - core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); -} - -} // namespace FileSys diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h deleted file mode 100644 index ac22a82cb..000000000 --- a/src/core/file_sys/cheat_engine.h +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <map> -#include <set> -#include <vector> -#include "common/bit_field.h" -#include "common/common_types.h" - -namespace Core { -class System; -} - -namespace Core::Timing { -class CoreTiming; -struct EventType; -} // namespace Core::Timing - -namespace FileSys { - -enum class CodeType : u32 { - // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY - // Writes a T sized value Y to the address A added to the value of register R in memory domain M - WriteImmediate = 0, - - // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY - // Compares the T sized value Y to the value at address A in memory domain M using the - // conditional function C. If success, continues execution. If failure, jumps to the matching - // EndConditional statement. - Conditional = 1, - - // 20000000 - // Terminates a Conditional or ConditionalInput block. - EndConditional = 2, - - // 300R0000 VVVVVVVV - // Starts looping V times, storing the current count in register R. - // Loop block is terminated with a matching 310R0000. - Loop = 3, - - // 400R0000 VVVVVVVV VVVVVVVV - // Sets the value of register R to the value V. - LoadImmediate = 4, - - // 5TMRI0AA AAAAAAAA - // Sets the value of register R to the value of width T at address A in memory domain M, with - // the current value of R added to the address if I == 1. - LoadIndexed = 5, - - // 6T0RIFG0 VVVVVVVV VVVVVVVV - // Writes the value V of width T to the memory address stored in register R. Adds the value of - // register G to the final calculation if F is nonzero. Increments the value of register R by T - // after operation if I is nonzero. - StoreIndexed = 6, - - // 7T0RA000 VVVVVVVV - // Performs the arithmetic operation A on the value in register R and the value V of width T, - // storing the result in register R. - RegisterArithmetic = 7, - - // 8KKKKKKK - // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are, - // execution continues. If none are, execution skips to the next EndConditional command. - ConditionalInput = 8, -}; - -enum class MemoryType : u32 { - // Addressed relative to start of main NSO - MainNSO = 0, - - // Addressed relative to start of heap - Heap = 1, -}; - -enum class ArithmeticOp : u32 { - Add = 0, - Sub = 1, - Mult = 2, - LShift = 3, - RShift = 4, -}; - -enum class ComparisonOp : u32 { - GreaterThan = 1, - GreaterThanEqual = 2, - LessThan = 3, - LessThanEqual = 4, - Equal = 5, - Inequal = 6, -}; - -union Cheat { - std::array<u8, 16> raw; - - BitField<4, 4, CodeType> type; - BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes. - BitField<0, 4, u32> end_of_loop; - BitField<12, 4, MemoryType> memory_type; - BitField<8, 4, u32> register_3; - BitField<8, 4, ComparisonOp> comparison_op; - BitField<20, 4, u32> load_from_register; - BitField<20, 4, u32> increment_register; - BitField<20, 4, ArithmeticOp> arithmetic_op; - BitField<16, 4, u32> add_additional_register; - BitField<28, 4, u32> register_6; - - u64 Address() const; - u64 ValueWidth(u64 offset) const; - u64 Value(u64 offset, u64 width) const; - u32 KeypadValue() const; -}; - -class CheatParser; - -// Represents a full collection of cheats for a game. The Execute function should be called every -// interval that all cheats should be executed. Clients should not directly instantiate this class -// (hence private constructor), they should instead receive an instance from CheatParser, which -// guarantees the list is always in an acceptable state. -class CheatList { -public: - friend class CheatParser; - - using Block = std::vector<Cheat>; - using ProgramSegment = std::vector<std::pair<std::string, Block>>; - - // (width in bytes, address, value) - using MemoryWriter = void (*)(u32, VAddr, u64); - // (width in bytes, address) -> value - using MemoryReader = u64 (*)(u32, VAddr); - - void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end, - MemoryWriter writer, MemoryReader reader); - - void Execute(); - -private: - CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard); - - void ProcessBlockPairs(const Block& block); - void ExecuteSingleCheat(const Cheat& cheat); - - void ExecuteBlock(const Block& block); - - bool EvaluateConditional(const Cheat& cheat) const; - - // Individual cheat operations - void WriteImmediate(const Cheat& cheat); - void BeginConditional(const Cheat& cheat); - void EndConditional(const Cheat& cheat); - void Loop(const Cheat& cheat); - void LoadImmediate(const Cheat& cheat); - void LoadIndexed(const Cheat& cheat); - void StoreIndexed(const Cheat& cheat); - void RegisterArithmetic(const Cheat& cheat); - void BeginConditionalInput(const Cheat& cheat); - - VAddr SanitizeAddress(VAddr in) const; - - // Master Codes are defined as codes that cannot be disabled and are run prior to all - // others. - ProgramSegment master_list; - // All other codes - ProgramSegment standard_list; - - bool in_standard = false; - - // 16 (0x0-0xF) scratch registers that can be used by cheats - std::array<u64, 16> scratch{}; - - MemoryWriter writer = nullptr; - MemoryReader reader = nullptr; - - u64 main_region_begin{}; - u64 heap_region_begin{}; - u64 main_region_end{}; - u64 heap_region_end{}; - - u64 current_block{}; - // The current index of the cheat within the current Block - u64 current_index{}; - - // The 'stack' of the program. When a conditional or loop statement is encountered, its index is - // pushed onto this queue. When a end block is encountered, the condition is checked. - std::map<u64, u64> block_pairs; - - std::set<u64> encountered_loops; - - const Core::System* system; -}; - -// Intermediary class that parses a text file or other disk format for storing cheats into a -// CheatList object, that can be used for execution. -class CheatParser { -public: - virtual ~CheatParser(); - - virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0; - -protected: - CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master, - CheatList::ProgramSegment standard) const; -}; - -// CheatParser implementation that parses text files -class TextCheatParser final : public CheatParser { -public: - ~TextCheatParser() override; - - CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override; - -private: - std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const; -}; - -// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming -class CheatEngine final { -public: - CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id, - VAddr code_region_start, VAddr code_region_end); - ~CheatEngine(); - -private: - void FrameCallback(u64 userdata, s64 cycles_late); - - std::vector<CheatList> cheats; - - Core::Timing::EventType* event; - Core::Timing::CoreTiming& core_timing; -}; - -} // namespace FileSys diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 5aa3b600b..ea5c92f61 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -452,13 +452,13 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s switch (s_header.raw.header.crypto_type) { case NCASectionCryptoType::NONE: - LOG_DEBUG(Crypto, "called with mode=NONE"); + LOG_TRACE(Crypto, "called with mode=NONE"); return in; case NCASectionCryptoType::CTR: // During normal BKTR decryption, this entire function is skipped. This is for the metadata, // which uses the same CTR as usual. case NCASectionCryptoType::BKTR: - LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); + LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); { std::optional<Core::Crypto::Key128> key = {}; if (has_rights_id) { @@ -528,6 +528,14 @@ u64 NCA::GetTitleId() const { return header.title_id; } +std::array<u8, 16> NCA::GetRightsId() const { + return header.rights_id; +} + +u32 NCA::GetSDKVersion() const { + return header.sdk_version; +} + bool NCA::IsUpdate() const { return is_update; } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 15b9e6624..e249079b5 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -112,6 +112,8 @@ public: NCAContentType GetType() const; u64 GetTitleId() const; + std::array<u8, 0x10> GetRightsId() const; + u32 GetSDKVersion() const; bool IsUpdate() const; VirtualFile GetRomFS() const; diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index 04da30825..f155a1341 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -87,6 +87,10 @@ u64 NACP::GetDefaultJournalSaveSize() const { return raw.user_account_save_data_journal_size; } +bool NACP::GetUserAccountSwitchLock() const { + return raw.user_account_switch_lock != 0; +} + u32 NACP::GetSupportedLanguages() const { return raw.supported_languages; } diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 1be34ed55..2d8c251ac 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -30,7 +30,8 @@ struct RawNACP { std::array<LanguageEntry, 16> language_entries; std::array<u8, 0x25> isbn; u8 startup_user_account; - INSERT_PADDING_BYTES(2); + u8 user_account_switch_lock; + u8 addon_content_registration_type; u32_le application_attribute; u32_le supported_languages; u32_le parental_control; @@ -111,6 +112,7 @@ public: u64 GetDefaultJournalSaveSize() const; u32 GetSupportedLanguages() const; std::vector<u8> GetRawBytes() const; + bool GetUserAccountSwitchLock() const; private: RawNACP raw{}; diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index 485c4913a..a08a70efd 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -287,7 +287,6 @@ void IPSwitchCompiler::Parse() { } else { // hex replacement const auto value = patch_line.substr(9); - replace.reserve(value.size() / 2); replace = Common::HexStringToVector(value, is_little_endian); } @@ -295,7 +294,7 @@ void IPSwitchCompiler::Parse() { LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} " "with byte string '{}'", - patch_text->GetName(), offset, Common::HexVectorToString(replace)); + patch_text->GetName(), offset, Common::HexToString(replace)); } patch.records.insert_or_assign(offset, std::move(replace)); diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp new file mode 100644 index 000000000..371300684 --- /dev/null +++ b/src/core/file_sys/kernel_executable.cpp @@ -0,0 +1,228 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/string_util.h" +#include "core/file_sys/kernel_executable.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +constexpr u32 INI_MAX_KIPS = 0x50; + +namespace { +bool DecompressBLZ(std::vector<u8>& data) { + if (data.size() < 0xC) + return {}; + + const auto data_size = data.size() - 0xC; + + u32 compressed_size{}; + u32 init_index{}; + u32 additional_size{}; + std::memcpy(&compressed_size, data.data() + data_size, sizeof(u32)); + std::memcpy(&init_index, data.data() + data_size + 0x4, sizeof(u32)); + std::memcpy(&additional_size, data.data() + data_size + 0x8, sizeof(u32)); + + const auto start_offset = data.size() - compressed_size; + data.resize(compressed_size + additional_size + start_offset); + + std::size_t index = compressed_size - init_index; + std::size_t out_index = compressed_size + additional_size; + + while (out_index > 0) { + --index; + auto control = data[index + start_offset]; + for (size_t i = 0; i < 8; ++i) { + if (((control << i) & 0x80) > 0) { + if (index < 2) { + return false; + } + index -= 2; + std::size_t segment_offset = + data[index + start_offset] | data[index + start_offset + 1] << 8; + std::size_t segment_size = ((segment_offset >> 12) & 0xF) + 3; + segment_offset &= 0xFFF; + segment_offset += 3; + + if (out_index < segment_size) + segment_size = out_index; + + if (out_index < segment_size) { + return false; + } + + out_index -= segment_size; + + for (size_t j = 0; j < segment_size; ++j) { + if (out_index + j + segment_offset + start_offset >= data.size()) { + return false; + } + data[out_index + j + start_offset] = + data[out_index + j + segment_offset + start_offset]; + } + } else { + if (out_index < 1) { + return false; + } + --out_index; + --index; + data[out_index + start_offset] = data[index + start_offset]; + } + + if (out_index == 0) + break; + } + } + + return true; +} +} // Anonymous namespace + +KIP::KIP(const VirtualFile& file) : status(Loader::ResultStatus::Success) { + if (file == nullptr) { + status = Loader::ResultStatus::ErrorNullFile; + return; + } + + if (file->GetSize() < sizeof(KIPHeader) || file->ReadObject(&header) != sizeof(KIPHeader)) { + status = Loader::ResultStatus::ErrorBadKIPHeader; + return; + } + + if (header.magic != Common::MakeMagic('K', 'I', 'P', '1')) { + status = Loader::ResultStatus::ErrorBadKIPHeader; + return; + } + + u64 offset = sizeof(KIPHeader); + for (std::size_t i = 0; i < header.sections.size(); ++i) { + auto compressed = file->ReadBytes(header.sections[i].compressed_size, offset); + offset += header.sections[i].compressed_size; + + if (header.sections[i].compressed_size == 0 && header.sections[i].decompressed_size != 0) { + decompressed_sections[i] = std::vector<u8>(header.sections[i].decompressed_size); + } else if (header.sections[i].compressed_size == header.sections[i].decompressed_size) { + decompressed_sections[i] = std::move(compressed); + } else { + decompressed_sections[i] = compressed; + if (!DecompressBLZ(decompressed_sections[i])) { + status = Loader::ResultStatus::ErrorBLZDecompressionFailed; + return; + } + } + } +} + +Loader::ResultStatus KIP::GetStatus() const { + return status; +} + +std::string KIP::GetName() const { + return Common::StringFromFixedZeroTerminatedBuffer(header.name.data(), header.name.size()); +} + +u64 KIP::GetTitleID() const { + return header.title_id; +} + +std::vector<u8> KIP::GetSectionDecompressed(u8 index) const { + return decompressed_sections[index]; +} + +bool KIP::Is64Bit() const { + return (header.flags & 0x8) != 0; +} + +bool KIP::Is39BitAddressSpace() const { + return (header.flags & 0x10) != 0; +} + +bool KIP::IsService() const { + return (header.flags & 0x20) != 0; +} + +std::vector<u32> KIP::GetKernelCapabilities() const { + return std::vector<u32>(header.capabilities.begin(), header.capabilities.end()); +} + +s32 KIP::GetMainThreadPriority() const { + return header.main_thread_priority; +} + +u32 KIP::GetMainThreadStackSize() const { + return header.sections[1].attribute; +} + +u32 KIP::GetMainThreadCpuCore() const { + return header.default_core; +} + +const std::vector<u8>& KIP::GetTextSection() const { + return decompressed_sections[0]; +} + +const std::vector<u8>& KIP::GetRODataSection() const { + return decompressed_sections[1]; +} + +const std::vector<u8>& KIP::GetDataSection() const { + return decompressed_sections[2]; +} + +u32 KIP::GetTextOffset() const { + return header.sections[0].offset; +} + +u32 KIP::GetRODataOffset() const { + return header.sections[1].offset; +} + +u32 KIP::GetDataOffset() const { + return header.sections[2].offset; +} + +u32 KIP::GetBSSSize() const { + return header.sections[3].decompressed_size; +} + +u32 KIP::GetBSSOffset() const { + return header.sections[3].offset; +} + +INI::INI(const VirtualFile& file) : status(Loader::ResultStatus::Success) { + if (file->GetSize() < sizeof(INIHeader) || file->ReadObject(&header) != sizeof(INIHeader)) { + status = Loader::ResultStatus::ErrorBadINIHeader; + return; + } + + if (header.magic != Common::MakeMagic('I', 'N', 'I', '1')) { + status = Loader::ResultStatus::ErrorBadINIHeader; + return; + } + + if (header.kip_count > INI_MAX_KIPS) { + status = Loader::ResultStatus::ErrorINITooManyKIPs; + return; + } + + u64 offset = sizeof(INIHeader); + for (std::size_t i = 0; i < header.kip_count; ++i) { + const auto kip_file = + std::make_shared<OffsetVfsFile>(file, file->GetSize() - offset, offset); + KIP kip(kip_file); + if (kip.GetStatus() == Loader::ResultStatus::Success) { + kips.push_back(std::move(kip)); + } + } +} + +Loader::ResultStatus INI::GetStatus() const { + return status; +} + +const std::vector<KIP>& INI::GetKIPs() const { + return kips; +} + +} // namespace FileSys diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h new file mode 100644 index 000000000..324a57384 --- /dev/null +++ b/src/core/file_sys/kernel_executable.h @@ -0,0 +1,99 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/swap.h" +#include "core/file_sys/vfs_types.h" +#include "core/loader/loader.h" + +namespace FileSys { + +struct KIPSectionHeader { + u32_le offset; + u32_le decompressed_size; + u32_le compressed_size; + u32_le attribute; +}; +static_assert(sizeof(KIPSectionHeader) == 0x10, "KIPSectionHeader has incorrect size."); + +struct KIPHeader { + u32_le magic; + std::array<char, 0xC> name; + u64_le title_id; + u32_le process_category; + u8 main_thread_priority; + u8 default_core; + INSERT_PADDING_BYTES(1); + u8 flags; + std::array<KIPSectionHeader, 6> sections; + std::array<u32, 0x20> capabilities; +}; +static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size."); + +struct INIHeader { + u32_le magic; + u32_le size; + u32_le kip_count; + INSERT_PADDING_BYTES(0x4); +}; +static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size."); + +// Kernel Internal Process +class KIP { +public: + explicit KIP(const VirtualFile& file); + + Loader::ResultStatus GetStatus() const; + + std::string GetName() const; + u64 GetTitleID() const; + std::vector<u8> GetSectionDecompressed(u8 index) const; + + // Executable Flags + bool Is64Bit() const; + bool Is39BitAddressSpace() const; + bool IsService() const; + + std::vector<u32> GetKernelCapabilities() const; + + s32 GetMainThreadPriority() const; + u32 GetMainThreadStackSize() const; + u32 GetMainThreadCpuCore() const; + + const std::vector<u8>& GetTextSection() const; + const std::vector<u8>& GetRODataSection() const; + const std::vector<u8>& GetDataSection() const; + + u32 GetTextOffset() const; + u32 GetRODataOffset() const; + u32 GetDataOffset() const; + + u32 GetBSSSize() const; + u32 GetBSSOffset() const; + +private: + Loader::ResultStatus status; + + KIPHeader header{}; + std::array<std::vector<u8>, 6> decompressed_sections; +}; + +class INI { +public: + explicit INI(const VirtualFile& file); + + Loader::ResultStatus GetStatus() const; + + const std::vector<KIP>& GetKIPs() const; + +private: + Loader::ResultStatus status; + + INIHeader header{}; + std::vector<KIP> kips; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index 50bf38471..1f82fff0a 100644 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h @@ -4,6 +4,7 @@ #pragma once +#include <array> #include <memory> #include <vector> #include "common/common_funcs.h" @@ -34,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 { @@ -69,11 +70,15 @@ struct CNMTHeader { u64_le title_id; u32_le title_version; TitleType type; - INSERT_PADDING_BYTES(1); + u8 reserved; u16_le table_offset; u16_le number_content_entries; u16_le number_meta_entries; - INSERT_PADDING_BYTES(12); + u8 attributes; + std::array<u8, 2> reserved2; + u8 is_committed; + u32_le required_download_system_version; + std::array<u8, 4> reserved3; }; static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size."); diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 78dbadee3..df0ecb15c 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -22,6 +22,7 @@ #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" #include "core/loader/nso.h" +#include "core/memory/cheat_engine.h" #include "core/settings.h" namespace FileSys { @@ -63,7 +64,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { if (Settings::values.dump_exefs) { LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id); - const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id); + const auto dump_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id); if (dump_dir != nullptr) { const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs"); VfsRawCopyD(exefs, exefs_dir); @@ -88,7 +90,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { } // LayeredExeFS - const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + const auto load_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); if (load_dir != nullptr && load_dir->GetSize() > 0) { auto patch_dirs = load_dir->GetSubdirectories(); std::sort( @@ -142,7 +145,7 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD if (!compiler.IsValid()) continue; - auto this_build_id = Common::HexArrayToString(compiler.GetBuildID()); + auto this_build_id = Common::HexToString(compiler.GetBuildID()); this_build_id = this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1); @@ -168,13 +171,14 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st return nso; } - const auto build_id_raw = Common::HexArrayToString(header.build_id); + const auto build_id_raw = Common::HexToString(header.build_id); const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); if (Settings::values.dump_nso) { LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id, title_id); - const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id); + const auto dump_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id); if (dump_dir != nullptr) { const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso"); const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id)); @@ -186,7 +190,13 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id); - const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + const auto load_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + if (load_dir == nullptr) { + LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); + return nso; + } + auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); @@ -219,12 +229,18 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st } bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { - const auto build_id_raw = Common::HexArrayToString(build_id_); + const auto build_id_raw = Common::HexToString(build_id_); const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); - const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + const auto load_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + if (load_dir == nullptr) { + LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); + return false; + } + auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); @@ -232,10 +248,11 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { return !CollectPatches(patch_dirs, build_id).empty(); } -static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id, - const std::array<u8, 0x20>& build_id_, - const VirtualDir& base_path, bool upper) { - const auto build_id_raw = Common::HexArrayToString(build_id_, upper); +namespace { +std::optional<std::vector<Memory::CheatEntry>> ReadCheatFileFromFolder( + const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_, + const VirtualDir& base_path, bool upper) { + const auto build_id_raw = Common::HexToString(build_id_, upper); const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); @@ -252,31 +269,39 @@ static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& syst return std::nullopt; } - TextCheatParser parser; - return parser.Parse(system, data); + Memory::TextCheatParser parser; + return parser.Parse( + system, std::string_view(reinterpret_cast<const char* const>(data.data()), data.size())); } -std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system, - const std::array<u8, 32>& build_id_) const { - const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); +} // Anonymous namespace + +std::vector<Memory::CheatEntry> PatchManager::CreateCheatList( + const Core::System& system, const std::array<u8, 32>& build_id_) const { + const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id); + if (load_dir == nullptr) { + LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); + return {}; + } + auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); - std::vector<CheatList> out; - out.reserve(patch_dirs.size()); + std::vector<Memory::CheatEntry> out; for (const auto& subdir : patch_dirs) { auto cheats_dir = subdir->GetSubdirectory("cheats"); if (cheats_dir != nullptr) { auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true); if (res.has_value()) { - out.push_back(std::move(*res)); + std::copy(res->begin(), res->end(), std::back_inserter(out)); continue; } res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false); - if (res.has_value()) - out.push_back(std::move(*res)); + if (res.has_value()) { + std::copy(res->begin(), res->end(), std::back_inserter(out)); + } } } @@ -284,7 +309,8 @@ std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system, } static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { - const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + const auto load_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || load_dir == nullptr || load_dir->GetSize() <= 0) { return; @@ -393,6 +419,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) { std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames( VirtualFile update_raw) const { + if (title_id == 0) + return {}; std::map<std::string, std::string, std::less<>> out; const auto& installed = Core::System::GetInstance().GetContentProvider(); const auto& disabled = Settings::values.disabled_addons[title_id]; @@ -423,7 +451,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam } // General Mods (LayeredFS and IPS) - const auto mod_dir = Service::FileSystem::GetModificationLoadRoot(title_id); + const auto mod_dir = + Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); if (mod_dir != nullptr && mod_dir->GetSize() > 0) { for (const auto& mod : mod_dir->GetSubdirectories()) { std::string types; @@ -493,6 +522,16 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam return out; } +std::optional<u32> PatchManager::GetGameVersion() const { + const auto& installed = Core::System::GetInstance().GetContentProvider(); + const auto update_tid = GetUpdateTitleID(title_id); + if (installed.HasEntry(update_tid, ContentRecordType::Program)) { + return installed.GetEntryVersion(update_tid); + } + + return installed.GetEntryVersion(title_id); +} + std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { const auto& installed = Core::System::GetInstance().GetContentProvider(); diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 769f8c6f0..e857e6e82 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -8,9 +8,9 @@ #include <memory> #include <string> #include "common/common_types.h" -#include "core/file_sys/cheat_engine.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs.h" +#include "core/memory/dmnt_cheat_types.h" namespace Core { class System; @@ -51,8 +51,8 @@ public: bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; // Creates a CheatList object with all - std::vector<CheatList> CreateCheatList(const Core::System& system, - const std::array<u8, 0x20>& build_id) const; + std::vector<Memory::CheatEntry> CreateCheatList(const Core::System& system, + const std::array<u8, 0x20>& build_id) const; // Currently tracked RomFS patches: // - Game Updates @@ -66,8 +66,13 @@ public: std::map<std::string, std::string, std::less<>> GetPatchVersionNames( VirtualFile update_raw = nullptr) const; - // Given title_id of the program, attempts to get the control data of the update and parse it, - // falling back to the base control data. + // If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails, + // it will fallback to the Meta-type NCA of the base game. If that fails, the result will be + // std::nullopt + std::optional<u32> GetGameVersion() const; + + // Given title_id of the program, attempts to get the control data of the update and parse + // it, falling back to the base control data. std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const; // Version of GetControlMetadata that takes an arbitrary NCA diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index d863253f8..7310b3602 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -51,6 +51,21 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { return Loader::ResultStatus::Success; } +void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, + u8 main_thread_prio, u8 main_thread_core, + u32 main_thread_stack_size, u64 title_id, + u64 filesystem_permissions, + KernelCapabilityDescriptors capabilities) { + npdm_header.has_64_bit_instructions.Assign(is_64_bit); + npdm_header.address_space_type.Assign(address_space); + npdm_header.main_thread_priority = main_thread_prio; + npdm_header.main_thread_cpu = main_thread_core; + npdm_header.main_stack_size = main_thread_stack_size; + aci_header.title_id = title_id; + aci_file_access.permissions = filesystem_permissions; + aci_kernel_capabilities = std ::move(capabilities); +} + bool ProgramMetadata::Is64BitProgram() const { return npdm_header.has_64_bit_instructions; } @@ -79,6 +94,10 @@ u64 ProgramMetadata::GetFilesystemPermissions() const { return aci_file_access.permissions; } +u32 ProgramMetadata::GetSystemResourceSize() const { + return npdm_header.system_resource_size; +} + const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const { return aci_kernel_capabilities; } diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index 7de5b9cf9..88ec97d85 100644 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h @@ -46,6 +46,11 @@ public: Loader::ResultStatus Load(VirtualFile file); + // Load from parameters instead of NPDM file, used for KIP + void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, u8 main_thread_prio, + u8 main_thread_core, u32 main_thread_stack_size, u64 title_id, + u64 filesystem_permissions, KernelCapabilityDescriptors capabilities); + bool Is64BitProgram() const; ProgramAddressSpaceType GetAddressSpaceType() const; u8 GetMainThreadPriority() const; @@ -53,6 +58,7 @@ public: u32 GetMainThreadStackSize() const; u64 GetTitleID() const; u64 GetFilesystemPermissions() const; + u32 GetSystemResourceSize() const; const KernelCapabilityDescriptors& GetKernelCapabilities() const; void Print() const; @@ -71,7 +77,8 @@ private: u8 reserved_3; u8 main_thread_priority; u8 main_thread_cpu; - std::array<u8, 8> reserved_4; + std::array<u8, 4> reserved_4; + u32_le system_resource_size; u32_le process_category; u32_le main_stack_size; std::array<u8, 0x10> application_name; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 3946ff871..ac3fbd849 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <random> #include <regex> #include <mbedtls/sha256.h> #include "common/assert.h" @@ -48,18 +49,22 @@ static bool FollowsTwoDigitDirFormat(std::string_view name) { static bool FollowsNcaIdFormat(std::string_view name) { static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript | std::regex_constants::icase); - return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex); + static const std::regex nca_id_cnmt_regex( + "[0-9A-F]{32}\\.cnmt.nca", std::regex_constants::ECMAScript | std::regex_constants::icase); + return (name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex)) || + (name.size() == 41 && std::regex_match(name.begin(), name.end(), nca_id_cnmt_regex)); } static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper, - bool within_two_digit) { + bool within_two_digit, bool cnmt_suffix) { if (!within_two_digit) - return fmt::format("/{}.nca", Common::HexArrayToString(nca_id, second_hex_upper)); + return fmt::format(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca", + Common::HexToString(nca_id, second_hex_upper)); Core::Crypto::SHA256Hash hash{}; mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); - return fmt::format("/000000{:02X}/{}.nca", hash[0], - Common::HexArrayToString(nca_id, second_hex_upper)); + return fmt::format(cnmt_suffix ? "/000000{:02X}/{}.cnmt.nca" : "/000000{:02X}/{}.nca", hash[0], + Common::HexToString(nca_id, second_hex_upper)); } static std::string GetCNMTName(TitleType type, u64 title_id) { @@ -98,7 +103,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)); } @@ -126,6 +131,156 @@ std::vector<ContentProviderEntry> ContentProvider::ListEntries() const { return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt); } +PlaceholderCache::PlaceholderCache(VirtualDir dir_) : dir(std::move(dir_)) {} + +bool PlaceholderCache::Create(const NcaID& id, u64 size) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + + if (dir->GetFileRelative(path) != nullptr) { + return false; + } + + Core::Crypto::SHA256Hash hash{}; + mbedtls_sha256(id.data(), id.size(), hash.data(), 0); + const auto dirname = fmt::format("000000{:02X}", hash[0]); + + const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname); + + if (dir2 == nullptr) + return false; + + const auto file = dir2->CreateFile(fmt::format("{}.nca", Common::HexToString(id, false))); + + if (file == nullptr) + return false; + + return file->Resize(size); +} + +bool PlaceholderCache::Delete(const NcaID& id) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + + if (dir->GetFileRelative(path) == nullptr) { + return false; + } + + Core::Crypto::SHA256Hash hash{}; + mbedtls_sha256(id.data(), id.size(), hash.data(), 0); + const auto dirname = fmt::format("000000{:02X}", hash[0]); + + const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname); + + const auto res = dir2->DeleteFile(fmt::format("{}.nca", Common::HexToString(id, false))); + + return res; +} + +bool PlaceholderCache::Exists(const NcaID& id) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + + return dir->GetFileRelative(path) != nullptr; +} + +bool PlaceholderCache::Write(const NcaID& id, u64 offset, const std::vector<u8>& data) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + const auto file = dir->GetFileRelative(path); + + if (file == nullptr) + return false; + + return file->WriteBytes(data, offset) == data.size(); +} + +bool PlaceholderCache::Register(RegisteredCache* cache, const NcaID& placeholder, + const NcaID& install) const { + const auto path = GetRelativePathFromNcaID(placeholder, false, true, false); + const auto file = dir->GetFileRelative(path); + + if (file == nullptr) + return false; + + const auto res = cache->RawInstallNCA(NCA{file}, &VfsRawCopy, false, install); + + if (res != InstallResult::Success) + return false; + + return Delete(placeholder); +} + +bool PlaceholderCache::CleanAll() const { + return dir->GetParentDirectory()->CleanSubdirectoryRecursive(dir->GetName()); +} + +std::optional<std::array<u8, 0x10>> PlaceholderCache::GetRightsID(const NcaID& id) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + const auto file = dir->GetFileRelative(path); + + if (file == nullptr) + return std::nullopt; + + NCA nca{file}; + + if (nca.GetStatus() != Loader::ResultStatus::Success && + nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { + return std::nullopt; + } + + const auto rights_id = nca.GetRightsId(); + if (rights_id == NcaID{}) + return std::nullopt; + + return rights_id; +} + +u64 PlaceholderCache::Size(const NcaID& id) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + const auto file = dir->GetFileRelative(path); + + if (file == nullptr) + return 0; + + return file->GetSize(); +} + +bool PlaceholderCache::SetSize(const NcaID& id, u64 new_size) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + const auto file = dir->GetFileRelative(path); + + if (file == nullptr) + return false; + + return file->Resize(new_size); +} + +std::vector<NcaID> PlaceholderCache::List() const { + std::vector<NcaID> out; + for (const auto& sdir : dir->GetSubdirectories()) { + for (const auto& file : sdir->GetFiles()) { + const auto name = file->GetName(); + if (name.length() == 36 && name[32] == '.' && name[33] == 'n' && name[34] == 'c' && + name[35] == 'a') { + out.push_back(Common::HexStringToArray<0x10>(name.substr(0, 32))); + } + } + } + return out; +} + +NcaID PlaceholderCache::Generate() { + std::random_device device; + std::mt19937 gen(device()); + std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max()); + + NcaID out{}; + + const auto v1 = distribution(gen); + const auto v2 = distribution(gen); + std::memcpy(out.data(), &v1, sizeof(u64)); + std::memcpy(out.data() + sizeof(u64), &v2, sizeof(u64)); + + return out; +} + VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const { const auto file = dir->GetFileRelative(path); @@ -168,14 +323,18 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, VirtualFile RegisteredCache::GetFileAtID(NcaID id) const { VirtualFile file; - // Try all four modes of file storage: - // (bit 1 = uppercase/lower, bit 0 = within a two-digit dir) - // 00: /000000**/{:032X}.nca - // 01: /{:032X}.nca - // 10: /000000**/{:032x}.nca - // 11: /{:032x}.nca - for (u8 i = 0; i < 4; ++i) { - const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0); + // Try all five relevant modes of file storage: + // (bit 2 = uppercase/lower, bit 1 = within a two-digit dir, bit 0 = .cnmt suffix) + // 000: /000000**/{:032X}.nca + // 010: /{:032X}.nca + // 100: /000000**/{:032x}.nca + // 110: /{:032x}.nca + // 111: /{:032x}.cnmt.nca + for (u8 i = 0; i < 8; ++i) { + if ((i % 2) == 1 && i != 7) + continue; + const auto path = + GetRelativePathFromNcaID(id, (i & 0b100) == 0, (i & 0b010) == 0, (i & 0b001) == 0b001); file = OpenFileOrDirectoryConcat(dir, path); if (file != nullptr) return file; @@ -376,10 +535,11 @@ std::vector<ContentProviderEntry> RegisteredCache::ListEntriesFilter( } static std::shared_ptr<NCA> GetNCAFromNSPForID(const NSP& nsp, const NcaID& id) { - const auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false))); - if (file == nullptr) + auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexToString(id, false))); + if (file == nullptr) { return nullptr; - return std::make_shared<NCA>(file); + } + return std::make_shared<NCA>(std::move(file)); } InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_exists, @@ -395,8 +555,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; } @@ -413,6 +573,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; @@ -467,7 +630,7 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti memcpy(id.data(), hash.data(), 16); } - std::string path = GetRelativePathFromNcaID(id, false, true); + std::string path = GetRelativePathFromNcaID(id, false, true, false); if (GetFileAtID(id) != nullptr && !overwrite_if_exists) { LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping..."); @@ -643,6 +806,20 @@ ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnion return out; } +std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry( + u64 title_id, ContentRecordType type) const { + const auto iter = + std::find_if(providers.begin(), providers.end(), [title_id, type](const auto& provider) { + return provider.second != nullptr && provider.second->HasEntry(title_id, type); + }); + + if (iter == providers.end()) { + return std::nullopt; + } + + return iter->first; +} + ManualContentProvider::~ManualContentProvider() = default; void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type, diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index ec9052653..d1eec240e 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -25,6 +25,8 @@ enum class NCAContentType : u8; enum class TitleType : u8; struct ContentRecord; +struct MetaRecord; +class RegisteredCache; using NcaID = std::array<u8, 0x10>; using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; @@ -89,6 +91,27 @@ protected: Core::Crypto::KeyManager keys; }; +class PlaceholderCache { +public: + explicit PlaceholderCache(VirtualDir dir); + + bool Create(const NcaID& id, u64 size) const; + bool Delete(const NcaID& id) const; + bool Exists(const NcaID& id) const; + bool Write(const NcaID& id, u64 offset, const std::vector<u8>& data) const; + bool Register(RegisteredCache* cache, const NcaID& placeholder, const NcaID& install) const; + bool CleanAll() const; + std::optional<std::array<u8, 0x10>> GetRightsID(const NcaID& id) const; + u64 Size(const NcaID& id) const; + bool SetSize(const NcaID& id, u64 new_size) const; + std::vector<NcaID> List() const; + + static NcaID Generate(); + +private: + VirtualDir dir; +}; + /* * A class that catalogues NCAs in the registered directory structure. * Nintendo's registered format follows this structure: @@ -103,6 +126,8 @@ protected: * when 4GB splitting can be ignored.) */ class RegisteredCache : public ContentProvider { + friend class PlaceholderCache; + public: // Parsing function defines the conversion from raw file to NCA. If there are other steps // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom @@ -199,6 +224,9 @@ public: std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {}, std::optional<u64> title_id = {}) const; + std::optional<ContentProviderUnionSlot> GetSlotForEntry(u64 title_id, + ContentRecordType type) const; + private: std::map<ContentProviderUnionSlot, ContentProvider*> providers; }; diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index b2ccb2926..84cd4684c 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -7,6 +7,7 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "core/core.h" +#include "core/file_sys/card_image.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" @@ -34,7 +35,7 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) { this->update_raw = std::move(update_raw); } -ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { +ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() const { if (!updatable) return MakeResult<VirtualFile>(file); @@ -43,7 +44,8 @@ ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw)); } -ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) { +ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, + ContentRecordType type) const { std::shared_ptr<NCA> res; switch (storage) { @@ -51,13 +53,17 @@ ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, Conte res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type); break; case StorageId::NandSystem: - res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type); + res = + Core::System::GetInstance().GetFileSystemController().GetSystemNANDContents()->GetEntry( + title_id, type); break; case StorageId::NandUser: - res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type); + res = Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->GetEntry( + title_id, type); break; case StorageId::SdCard: - res = Service::FileSystem::GetSDMCContents()->GetEntry(title_id, type); + res = Core::System::GetInstance().GetFileSystemController().GetSDMCContents()->GetEntry( + title_id, type); break; default: UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage)); diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index 7724c0b23..da63a313a 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -33,8 +33,8 @@ public: ~RomFSFactory(); void SetPackedUpdate(VirtualFile update_raw); - ResultVal<VirtualFile> OpenCurrentProcess(); - ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type); + ResultVal<VirtualFile> OpenCurrentProcess() const; + ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const; private: VirtualFile file; diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 7974b031d..f77cc02ac 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -15,22 +15,8 @@ namespace FileSys { constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size"; -std::string SaveDataDescriptor::DebugInfo() const { - return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}, " - "rank={}, index={}]", - static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id, - static_cast<u8>(rank), index); -} - -SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) { - // Delete all temporary storages - // On hardware, it is expected that temporary storage be empty at first use. - dir->DeleteSubdirectoryRecursive("temp"); -} - -SaveDataFactory::~SaveDataFactory() = default; - -ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) { +namespace { +void PrintSaveDataDescriptorWarnings(SaveDataDescriptor meta) { if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) { if (meta.zero_1 != 0) { LOG_WARNING(Service_FS, @@ -65,23 +51,51 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, const SaveDat "non-zero ({:016X}{:016X})", meta.user_id[1], meta.user_id[0]); } +} +} // Anonymous namespace - std::string save_directory = - GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); +std::string SaveDataDescriptor::DebugInfo() const { + return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, " + "save_id={:016X}, " + "rank={}, index={}]", + static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id, + static_cast<u8>(rank), index); +} - // TODO(DarkLordZach): Try to not create when opening, there are dedicated create save methods. - // But, user_ids don't match so this works for now. +SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) { + // Delete all temporary storages + // On hardware, it is expected that temporary storage be empty at first use. + dir->DeleteSubdirectoryRecursive("temp"); +} - auto out = dir->GetDirectoryRelative(save_directory); +SaveDataFactory::~SaveDataFactory() = default; + +ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space, + const SaveDataDescriptor& meta) const { + PrintSaveDataDescriptorWarnings(meta); + + const auto save_directory = + GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); + + auto out = dir->CreateDirectoryRelative(save_directory); + // Return an error if the save data doesn't actually exist. if (out == nullptr) { - // TODO(bunnei): This is a work-around to always create a save data directory if it does not - // already exist. This is a hack, as we do not understand yet how this works on hardware. - // Without a save data directory, many games will assert on boot. This should not have any - // bad side-effects. - out = dir->CreateDirectoryRelative(save_directory); + // TODO(DarkLordZach): Find out correct error code. + return ResultCode(-1); } + return MakeResult<VirtualDir>(std::move(out)); +} + +ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, + const SaveDataDescriptor& meta) const { + + const auto save_directory = + GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); + + auto out = dir->GetDirectoryRelative(save_directory); + // Return an error if the save data doesn't actually exist. if (out == nullptr) { // TODO(Subv): Find out correct error code. @@ -152,7 +166,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id, } void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, - SaveDataSize new_value) { + SaveDataSize new_value) const { const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0); const auto dir = GetOrCreateDirectoryRelative(this->dir, path); diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index b73654571..991e57aa1 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -64,7 +64,8 @@ public: explicit SaveDataFactory(VirtualDir dir); ~SaveDataFactory(); - ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta); + ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataDescriptor& meta) const; + ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataDescriptor& meta) const; VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const; @@ -73,7 +74,8 @@ public: u128 user_id, u64 save_id); SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const; - void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, SaveDataSize new_value); + void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, + SaveDataSize new_value) const; private: VirtualDir dir; diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index bd3a57058..5113a1ca6 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -6,6 +6,7 @@ #include "core/file_sys/registered_cache.h" #include "core/file_sys/sdmc_factory.h" #include "core/file_sys/xts_archive.h" +#include "core/settings.h" namespace FileSys { @@ -14,16 +15,38 @@ SDMCFactory::SDMCFactory(VirtualDir dir_) GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"), [](const VirtualFile& file, const NcaID& id) { return NAX{file, id}.GetDecrypted(); - })) {} + })), + placeholder(std::make_unique<PlaceholderCache>( + GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/placehld"))) {} SDMCFactory::~SDMCFactory() = default; -ResultVal<VirtualDir> SDMCFactory::Open() { +ResultVal<VirtualDir> SDMCFactory::Open() const { return MakeResult<VirtualDir>(dir); } +VirtualDir SDMCFactory::GetSDMCContentDirectory() const { + return GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents"); +} + RegisteredCache* SDMCFactory::GetSDMCContents() const { return contents.get(); } +PlaceholderCache* SDMCFactory::GetSDMCPlaceholder() const { + return placeholder.get(); +} + +VirtualDir SDMCFactory::GetImageDirectory() const { + return GetOrCreateDirectoryRelative(dir, "/Nintendo/Album"); +} + +u64 SDMCFactory::GetSDMCFreeSpace() const { + return GetSDMCTotalSpace() - dir->GetSize(); +} + +u64 SDMCFactory::GetSDMCTotalSpace() const { + return static_cast<u64>(Settings::values.sdmc_size); +} + } // namespace FileSys diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index 42794ba5b..42dc4e08a 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -11,6 +11,7 @@ namespace FileSys { class RegisteredCache; +class PlaceholderCache; /// File system interface to the SDCard archive class SDMCFactory { @@ -18,13 +19,23 @@ public: explicit SDMCFactory(VirtualDir dir); ~SDMCFactory(); - ResultVal<VirtualDir> Open(); + ResultVal<VirtualDir> Open() const; + + VirtualDir GetSDMCContentDirectory() const; + RegisteredCache* GetSDMCContents() const; + PlaceholderCache* GetSDMCPlaceholder() const; + + VirtualDir GetImageDirectory() const; + + u64 GetSDMCFreeSpace() const; + u64 GetSDMCTotalSpace() const; private: VirtualDir dir; std::unique_ptr<RegisteredCache> contents; + std::unique_ptr<PlaceholderCache> placeholder; }; } // namespace FileSys diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index c69caae0f..ef3084681 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -14,6 +14,7 @@ #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/program_metadata.h" #include "core/file_sys/submission_package.h" #include "core/loader/loader.h" @@ -78,6 +79,10 @@ Loader::ResultStatus NSP::GetStatus() const { } Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const { + if (IsExtractedType() && GetExeFS() != nullptr && FileSys::IsDirectoryExeFS(GetExeFS())) { + return Loader::ResultStatus::Success; + } + const auto iter = program_status.find(title_id); if (iter == program_status.end()) return Loader::ResultStatus::ErrorNSPMissingProgramNCA; @@ -85,12 +90,29 @@ Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const { } u64 NSP::GetFirstTitleID() const { + if (IsExtractedType()) { + return GetProgramTitleID(); + } + if (program_status.empty()) return 0; return program_status.begin()->first; } u64 NSP::GetProgramTitleID() const { + if (IsExtractedType()) { + if (GetExeFS() == nullptr || !IsDirectoryExeFS(GetExeFS())) { + return 0; + } + + ProgramMetadata meta; + if (meta.Load(GetExeFS()->GetFile("main.npdm")) == Loader::ResultStatus::Success) { + return meta.GetTitleID(); + } else { + return 0; + } + } + const auto out = GetFirstTitleID(); if ((out & 0x800) == 0) return out; @@ -102,6 +124,10 @@ u64 NSP::GetProgramTitleID() const { } std::vector<u64> NSP::GetTitleIDs() const { + if (IsExtractedType()) { + return {GetProgramTitleID()}; + } + std::vector<u64> out; out.reserve(ncas.size()); for (const auto& kv : ncas) @@ -222,7 +248,8 @@ void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) { void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { for (const auto& outer_file : files) { - if (outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") { + if (outer_file->GetName().size() < 9 || + outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") { continue; } @@ -235,27 +262,33 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { const auto section0 = nca->GetSubdirectories()[0]; for (const auto& inner_file : section0->GetFiles()) { - if (inner_file->GetExtension() != "cnmt") + if (inner_file->GetExtension() != "cnmt") { continue; + } const CNMT cnmt(inner_file); auto& ncas_title = ncas[cnmt.GetTitleID()]; ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca; for (const auto& rec : cnmt.GetContentRecords()) { - const auto id_string = Common::HexArrayToString(rec.nca_id, false); - const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); + const auto id_string = Common::HexToString(rec.nca_id, false); + 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; } - auto next_nca = std::make_shared<NCA>(next_file, nullptr, 0, keys); - if (next_nca->GetType() == NCAContentType::Program) + auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0, keys); + if (next_nca->GetType() == NCAContentType::Program) { program_status[cnmt.GetTitleID()] = next_nca->GetStatus(); + } if (next_nca->GetStatus() == Loader::ResultStatus::Success || (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS && (cnmt.GetTitleID() & 0x800) != 0)) { diff --git a/src/core/file_sys/system_archive/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp new file mode 100644 index 000000000..6a9add87c --- /dev/null +++ b/src/core/file_sys/system_archive/mii_model.cpp @@ -0,0 +1,46 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/file_sys/system_archive/mii_model.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys::SystemArchive { + +namespace MiiModelData { + +constexpr std::array<u8, 0x10> NFTR_STANDARD{'N', 'F', 'T', 'R', 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +constexpr std::array<u8, 0x10> NFSR_STANDARD{'N', 'F', 'S', 'R', 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +constexpr auto TEXTURE_LOW_LINEAR = NFTR_STANDARD; +constexpr auto TEXTURE_LOW_SRGB = NFTR_STANDARD; +constexpr auto TEXTURE_MID_LINEAR = NFTR_STANDARD; +constexpr auto TEXTURE_MID_SRGB = NFTR_STANDARD; +constexpr auto SHAPE_HIGH = NFSR_STANDARD; +constexpr auto SHAPE_MID = NFSR_STANDARD; + +} // namespace MiiModelData + +VirtualDir MiiModel() { + auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, + std::vector<VirtualDir>{}, "data"); + + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>( + MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>( + MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>( + MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>( + MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>( + MiiModelData::SHAPE_HIGH, "ShapeHigh.dat")); + out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>( + MiiModelData::SHAPE_MID, "ShapeMid.dat")); + + return std::move(out); +} + +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/mii_model.h b/src/core/file_sys/system_archive/mii_model.h new file mode 100644 index 000000000..6c2d9398b --- /dev/null +++ b/src/core/file_sys/system_archive/mii_model.h @@ -0,0 +1,13 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/vfs_types.h" + +namespace FileSys::SystemArchive { + +VirtualDir MiiModel(); + +} // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp index c9722ed77..6d8445383 100644 --- a/src/core/file_sys/system_archive/system_archive.cpp +++ b/src/core/file_sys/system_archive/system_archive.cpp @@ -4,6 +4,7 @@ #include "common/logging/log.h" #include "core/file_sys/romfs.h" +#include "core/file_sys/system_archive/mii_model.h" #include "core/file_sys/system_archive/ng_word.h" #include "core/file_sys/system_archive/system_archive.h" #include "core/file_sys/system_archive/system_version.h" @@ -24,7 +25,7 @@ struct SystemArchiveDescriptor { constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{ {0x0100000000000800, "CertStore", nullptr}, {0x0100000000000801, "ErrorMessage", nullptr}, - {0x0100000000000802, "MiiModel", nullptr}, + {0x0100000000000802, "MiiModel", &MiiModel}, {0x0100000000000803, "BrowserDll", nullptr}, {0x0100000000000804, "Help", nullptr}, {0x0100000000000805, "SharedFont", nullptr}, diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index eec51c64e..4bc5cb2ee 100644 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp @@ -66,7 +66,7 @@ NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id) Core::Crypto::SHA256Hash hash{}; mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0], - Common::HexArrayToString(nca_id, false))); + Common::HexToString(nca_id, false))); } NAX::~NAX() = default; diff --git a/src/core/frontend/applets/general_frontend.cpp b/src/core/frontend/applets/general_frontend.cpp index b974f2289..c30b36de7 100644 --- a/src/core/frontend/applets/general_frontend.cpp +++ b/src/core/frontend/applets/general_frontend.cpp @@ -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 diff --git a/src/core/frontend/applets/general_frontend.h b/src/core/frontend/applets/general_frontend.h index d4506c999..4b63f828e 100644 --- a/src/core/frontend/applets/general_frontend.h +++ b/src/core/frontend/applets/general_frontend.h @@ -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 diff --git a/src/core/frontend/applets/web_browser.cpp b/src/core/frontend/applets/web_browser.cpp index 3a3d3d0bf..528295ffc 100644 --- a/src/core/frontend/applets/web_browser.cpp +++ b/src/core/frontend/applets/web_browser.cpp @@ -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 '{}'!", diff --git a/src/core/frontend/applets/web_browser.h b/src/core/frontend/applets/web_browser.h index f952856af..110e33bc4 100644 --- a/src/core/frontend/applets/web_browser.h +++ b/src/core/frontend/applets/web_browser.h @@ -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 diff --git a/src/core/hardware_interrupt_manager.cpp b/src/core/hardware_interrupt_manager.cpp new file mode 100644 index 000000000..c2115db2d --- /dev/null +++ b/src/core/hardware_interrupt_manager.cpp @@ -0,0 +1,30 @@ +// Copyright 2019 Yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hardware_interrupt_manager.h" +#include "core/hle/service/nvdrv/interface.h" +#include "core/hle/service/sm/sm.h" + +namespace Core::Hardware { + +InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) { + gpu_interrupt_event = + system.CoreTiming().RegisterEvent("GPUInterrupt", [this](u64 message, s64) { + auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv"); + const u32 syncpt = static_cast<u32>(message >> 32); + const u32 value = static_cast<u32>(message); + nvdrv->SignalGPUInterruptSyncpt(syncpt, value); + }); +} + +InterruptManager::~InterruptManager() = default; + +void InterruptManager::GPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) { + const u64 msg = (static_cast<u64>(syncpoint_id) << 32ULL) | value; + system.CoreTiming().ScheduleEvent(10, gpu_interrupt_event, msg); +} + +} // namespace Core::Hardware diff --git a/src/core/hardware_interrupt_manager.h b/src/core/hardware_interrupt_manager.h new file mode 100644 index 000000000..494db883a --- /dev/null +++ b/src/core/hardware_interrupt_manager.h @@ -0,0 +1,31 @@ +// Copyright 2019 Yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Core { +class System; +} + +namespace Core::Timing { +struct EventType; +} + +namespace Core::Hardware { + +class InterruptManager { +public: + explicit InterruptManager(Core::System& system); + ~InterruptManager(); + + void GPUInterruptSyncpt(u32 syncpoint_id, u32 value); + +private: + Core::System& system; + Core::Timing::EventType* gpu_interrupt_event{}; +}; + +} // namespace Core::Hardware diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h index 879957dcb..d8ad54030 100644 --- a/src/core/hle/kernel/code_set.h +++ b/src/core/hle/kernel/code_set.h @@ -8,6 +8,7 @@ #include <vector> #include "common/common_types.h" +#include "core/hle/kernel/physical_memory.h" namespace Kernel { @@ -77,7 +78,7 @@ struct CodeSet final { } /// The overall data that backs this code set. - std::vector<u8> memory; + Kernel::PhysicalMemory memory; /// The segments that comprise this code set. std::array<Segment, 3> segments; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 757e5f21f..799e5e0d8 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -99,7 +99,8 @@ struct KernelCore::Impl { void Shutdown() { next_object_id = 0; - next_process_id = Process::ProcessIDMin; + next_kernel_process_id = Process::InitialKIPIDMin; + next_user_process_id = Process::ProcessIDMin; next_thread_id = 1; process_list.clear(); @@ -132,7 +133,8 @@ struct KernelCore::Impl { } std::atomic<u32> next_object_id{0}; - std::atomic<u64> next_process_id{Process::ProcessIDMin}; + std::atomic<u64> next_kernel_process_id{Process::InitialKIPIDMin}; + std::atomic<u64> next_user_process_id{Process::ProcessIDMin}; std::atomic<u64> next_thread_id{1}; // Lists all processes that exist in the current session. @@ -226,8 +228,12 @@ u64 KernelCore::CreateNewThreadID() { return impl->next_thread_id++; } -u64 KernelCore::CreateNewProcessID() { - return impl->next_process_id++; +u64 KernelCore::CreateNewKernelProcessID() { + return impl->next_kernel_process_id++; +} + +u64 KernelCore::CreateNewUserProcessID() { + return impl->next_user_process_id++; } Core::Timing::EventType* KernelCore::ThreadWakeupCallbackEventType() const { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 6b8738599..0cc44ee76 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -96,7 +96,10 @@ private: u32 CreateNewObjectID(); /// Creates a new process ID, incrementing the internal process ID counter; - u64 CreateNewProcessID(); + u64 CreateNewKernelProcessID(); + + /// Creates a new process ID, incrementing the internal process ID counter; + u64 CreateNewUserProcessID(); /// Creates a new thread ID, incrementing the internal thread ID counter. u64 CreateNewThreadID(); diff --git a/src/core/hle/kernel/physical_memory.h b/src/core/hle/kernel/physical_memory.h new file mode 100644 index 000000000..090565310 --- /dev/null +++ b/src/core/hle/kernel/physical_memory.h @@ -0,0 +1,19 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/alignment.h" + +namespace Kernel { + +// This encapsulation serves 2 purposes: +// - First, to encapsulate host physical memory under a single type and set an +// standard for managing it. +// - Second to ensure all host backing memory used is aligned to 256 bytes due +// to strict alignment restrictions on GPU memory. + +using PhysicalMemory = std::vector<u8, Common::AlignmentAllocator<u8, 256>>; + +} // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 2b81a8d4f..e80a12ac3 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <bitset> #include <memory> #include <random> #include "common/alignment.h" @@ -48,7 +49,58 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, u32 priority) { } } // Anonymous namespace -SharedPtr<Process> Process::Create(Core::System& system, std::string name) { +// 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)); @@ -56,7 +108,8 @@ SharedPtr<Process> Process::Create(Core::System& system, std::string name) { process->resource_limit = kernel.GetSystemResourceLimit(); process->status = ProcessStatus::Created; process->program_id = 0; - process->process_id = kernel.CreateNewProcessID(); + process->process_id = type == ProcessType::KernelInternal ? kernel.CreateNewKernelProcessID() + : kernel.CreateNewUserProcessID(); process->capabilities.InitializeForMetadatalessProcess(); std::mt19937 rng(Settings::values.rng_seed.value_or(0)); @@ -76,20 +129,17 @@ u64 Process::GetTotalPhysicalMemoryAvailable() const { return vm_manager.GetTotalPhysicalMemoryAvailable(); } -u64 Process::GetTotalPhysicalMemoryAvailableWithoutMmHeap() const { - // TODO: Subtract the personal heap size from this when the - // personal heap is implemented. - return GetTotalPhysicalMemoryAvailable(); +u64 Process::GetTotalPhysicalMemoryAvailableWithoutSystemResource() const { + return GetTotalPhysicalMemoryAvailable() - GetSystemResourceSize(); } u64 Process::GetTotalPhysicalMemoryUsed() const { - return vm_manager.GetCurrentHeapSize() + main_thread_stack_size + code_memory_size; + return vm_manager.GetCurrentHeapSize() + main_thread_stack_size + code_memory_size + + GetSystemResourceUsage(); } -u64 Process::GetTotalPhysicalMemoryUsedWithoutMmHeap() const { - // TODO: Subtract the personal heap size from this when the - // personal heap is implemented. - return GetTotalPhysicalMemoryUsed(); +u64 Process::GetTotalPhysicalMemoryUsedWithoutSystemResource() const { + return GetTotalPhysicalMemoryUsed() - GetSystemResourceUsage(); } void Process::RegisterThread(const Thread* thread) { @@ -119,6 +169,7 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) { program_id = metadata.GetTitleID(); ideal_core = metadata.GetMainThreadCore(); is_64bit_process = metadata.Is64BitProgram(); + system_resource_size = metadata.GetSystemResourceSize(); vm_manager.Reset(metadata.GetAddressSpaceType()); @@ -133,19 +184,11 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) { } void Process::Run(s32 main_thread_priority, u64 stack_size) { - // The kernel always ensures that the given stack size is page aligned. - main_thread_stack_size = Common::AlignUp(stack_size, Memory::PAGE_SIZE); - - // Allocate and map the main thread stack - // TODO(bunnei): This is heap area that should be allocated by the kernel and not mapped as part - // of the user address space. - const VAddr mapping_address = vm_manager.GetTLSIORegionEndAddress() - main_thread_stack_size; - vm_manager - .MapMemoryBlock(mapping_address, std::make_shared<std::vector<u8>>(main_thread_stack_size), - 0, main_thread_stack_size, MemoryState::Stack) - .Unwrap(); + AllocateMainThreadStack(stack_size); + tls_region_address = CreateTLSRegion(); vm_manager.LogLayout(); + ChangeStatus(ProcessStatus::Running); SetupMainThread(*this, kernel, main_thread_priority); @@ -175,69 +218,66 @@ void Process::PrepareForTermination() { stop_threads(system.Scheduler(2).GetThreadList()); stop_threads(system.Scheduler(3).GetThreadList()); + FreeTLSRegion(tls_region_address); + tls_region_address = 0; + ChangeStatus(ProcessStatus::Exited); } /** - * 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<PhysicalMemory>(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()); - tls_slots[available_page].set(available_slot); + return *reserve_result; + } - 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) { - const auto memory = std::make_shared<std::vector<u8>>(std::move(module_.memory)); + const auto memory = std::make_shared<PhysicalMemory>(std::move(module_.memory)); const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions, MemoryState memory_state) { @@ -280,4 +320,16 @@ void Process::ChangeStatus(ProcessStatus new_status) { WakeupAllWaitingThreads(); } +void Process::AllocateMainThreadStack(u64 stack_size) { + // The kernel always ensures that the given stack size is page aligned. + main_thread_stack_size = Common::AlignUp(stack_size, Memory::PAGE_SIZE); + + // Allocate and map the main thread stack + const VAddr mapping_address = vm_manager.GetTLSIORegionEndAddress() - main_thread_stack_size; + vm_manager + .MapMemoryBlock(mapping_address, std::make_shared<PhysicalMemory>(main_thread_stack_size), + 0, main_thread_stack_size, MemoryState::Stack) + .Unwrap(); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 29e016983..c2df451f3 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -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; @@ -73,9 +73,15 @@ public: ProcessIDMax = 0xFFFFFFFFFFFFFFFF, }; + // Used to determine how process IDs are assigned. + enum class ProcessType { + KernelInternal, + Userland, + }; + static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4; - static SharedPtr<Process> Create(Core::System& system, std::string name); + static SharedPtr<Process> Create(Core::System& system, std::string name, ProcessType type); std::string GetTypeName() const override { return "Process"; @@ -129,6 +135,11 @@ public: return mutex; } + /// Gets the address to the process' dedicated TLS region. + VAddr GetTLSRegionAddress() const { + return tls_region_address; + } + /// Gets the current status of the process ProcessStatus GetStatus() const { return status; @@ -162,8 +173,24 @@ public: return capabilities.GetPriorityMask(); } - u32 IsVirtualMemoryEnabled() const { - return is_virtual_address_memory_enabled; + /// Gets the amount of secure memory to allocate for memory management. + u32 GetSystemResourceSize() const { + return system_resource_size; + } + + /// Gets the amount of secure memory currently in use for memory management. + u32 GetSystemResourceUsage() const { + // On hardware, this returns the amount of system resource memory that has + // been used by the kernel. This is problematic for Yuzu to emulate, because + // system resource memory is used for page tables -- and yuzu doesn't really + // have a way to calculate how much memory is required for page tables for + // the current process at any given time. + // TODO: Is this even worth implementing? Games may retrieve this value via + // an SDK function that gets used + available system resource size for debug + // or diagnostic purposes. However, it seems unlikely that a game would make + // decisions based on how much system memory is dedicated to its page tables. + // Is returning a value other than zero wise? + return 0; } /// Whether this process is an AArch64 or AArch32 process. @@ -190,15 +217,15 @@ public: u64 GetTotalPhysicalMemoryAvailable() const; /// Retrieves the total physical memory available to this process in bytes, - /// without the size of the personal heap added to it. - u64 GetTotalPhysicalMemoryAvailableWithoutMmHeap() const; + /// without the size of the personal system resource heap added to it. + u64 GetTotalPhysicalMemoryAvailableWithoutSystemResource() const; /// Retrieves the total physical memory used by this process in bytes. u64 GetTotalPhysicalMemoryUsed() const; /// Retrieves the total physical memory used by this process in bytes, - /// without the size of the personal heap added to it. - u64 GetTotalPhysicalMemoryUsedWithoutMmHeap() const; + /// without the size of the personal system resource heap added to it. + u64 GetTotalPhysicalMemoryUsedWithoutSystemResource() const; /// Gets the list of all threads created with this process as their owner. const std::list<const Thread*>& GetThreadList() const { @@ -254,10 +281,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); @@ -274,6 +301,9 @@ private: /// a process signal. void ChangeStatus(ProcessStatus new_status); + /// Allocates the main thread stack for the process, given the stack size in bytes. + void AllocateMainThreadStack(u64 stack_size); + /// Memory manager for this process. Kernel::VMManager vm_manager; @@ -284,7 +314,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; @@ -292,19 +322,23 @@ private: /// Title ID corresponding to the process u64 program_id = 0; + /// Specifies additional memory to be reserved for the process's memory management by the + /// system. When this is non-zero, secure memory is allocated and used for page table allocation + /// instead of using the normal global page tables/memory block management. + u32 system_resource_size = 0; + /// Resource limit descriptor for this process SharedPtr<ResourceLimit> resource_limit; /// The ideal CPU core for this process, threads are scheduled on this core by default. u8 ideal_core = 0; - u32 is_virtual_address_memory_enabled = 0; /// The Thread Local Storage area is allocated as processes create threads, /// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part /// 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; @@ -332,8 +366,11 @@ private: /// variable related facilities. Mutex mutex; + /// Address indicating the location of the process' dedicated TLS region. + VAddr tls_region_address = 0; + /// 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; diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index f15c5ee36..a815c4eea 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -28,7 +28,7 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_ shared_memory->other_permissions = other_permissions; if (address == 0) { - shared_memory->backing_block = std::make_shared<std::vector<u8>>(size); + shared_memory->backing_block = std::make_shared<Kernel::PhysicalMemory>(size); shared_memory->backing_block_offset = 0; // Refresh the address mappings for the current process. @@ -59,8 +59,8 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_ } SharedPtr<SharedMemory> SharedMemory::CreateForApplet( - KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, std::size_t offset, u64 size, - MemoryPermission permissions, MemoryPermission other_permissions, std::string name) { + KernelCore& kernel, std::shared_ptr<Kernel::PhysicalMemory> heap_block, std::size_t offset, + u64 size, MemoryPermission permissions, MemoryPermission other_permissions, std::string name) { SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel)); shared_memory->owner_process = nullptr; diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index c2b6155e1..01ca6dcd2 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -10,6 +10,7 @@ #include "common/common_types.h" #include "core/hle/kernel/object.h" +#include "core/hle/kernel/physical_memory.h" #include "core/hle/kernel/process.h" #include "core/hle/result.h" @@ -62,12 +63,10 @@ public: * block. * @param name Optional object name, used for debugging purposes. */ - static SharedPtr<SharedMemory> CreateForApplet(KernelCore& kernel, - std::shared_ptr<std::vector<u8>> heap_block, - std::size_t offset, u64 size, - MemoryPermission permissions, - MemoryPermission other_permissions, - std::string name = "Unknown Applet"); + static SharedPtr<SharedMemory> CreateForApplet( + KernelCore& kernel, std::shared_ptr<Kernel::PhysicalMemory> heap_block, std::size_t offset, + u64 size, MemoryPermission permissions, MemoryPermission other_permissions, + std::string name = "Unknown Applet"); std::string GetTypeName() const override { return "SharedMemory"; @@ -135,7 +134,7 @@ private: ~SharedMemory() override; /// Backing memory for this shared memory block. - std::shared_ptr<std::vector<u8>> backing_block; + std::shared_ptr<PhysicalMemory> backing_block; /// Offset into the backing block for this shared memory. std::size_t backing_block_offset = 0; /// Size of the memory block. Page-aligned. diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index f9c606bc5..1fd1a732a 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -38,6 +38,7 @@ #include "core/hle/result.h" #include "core/hle/service/service.h" #include "core/memory.h" +#include "core/reporter.h" namespace Kernel { namespace { @@ -97,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; } @@ -317,7 +318,14 @@ static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_ad return result; } - return vm_manager.UnmapRange(dst_addr, size); + const auto unmap_res = vm_manager.UnmapRange(dst_addr, size); + + // Reprotect the source mapping on success + if (unmap_res.IsSuccess()) { + ASSERT(vm_manager.ReprotectRange(src_addr, size, VMAPermission::ReadWrite).IsSuccess()); + } + + return unmap_res; } /// Connect to an OS service given the port name, returns the handle to the port to out @@ -594,6 +602,7 @@ struct BreakReason { static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { BreakReason break_reason{reason}; bool has_dumped_buffer{}; + std::vector<u8> debug_buffer; const auto handle_debug_buffer = [&](VAddr addr, u64 sz) { if (sz == 0 || addr == 0 || has_dumped_buffer) { @@ -605,7 +614,7 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { LOG_CRITICAL(Debug_Emulated, "debug_buffer_err_code={:X}", Memory::Read32(addr)); } else { // We don't know what's in here so we'll hexdump it - std::vector<u8> debug_buffer(sz); + debug_buffer.resize(sz); Memory::ReadBlock(addr, debug_buffer.data(), sz); std::string hexdump; for (std::size_t i = 0; i < debug_buffer.size(); i++) { @@ -664,6 +673,10 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { break; } + system.GetReporter().SaveSvcBreakReport( + static_cast<u32>(break_reason.break_type.Value()), break_reason.signal_debugger, info1, + info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt); + if (!break_reason.signal_debugger) { LOG_CRITICAL( Debug_Emulated, @@ -720,19 +733,19 @@ 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, + SystemResourceSize = 16, + SystemResourceUsage = 17, TitleId = 18, // 4.0.0+ PrivilegedProcessId = 19, // 5.0.0+ UserExceptionContextAddr = 20, // 6.0.0+ - TotalPhysicalMemoryAvailableWithoutMmHeap = 21, - TotalPhysicalMemoryUsedWithoutMmHeap = 22, + TotalPhysicalMemoryAvailableWithoutSystemResource = 21, + TotalPhysicalMemoryUsedWithoutSystemResource = 22, }; const auto info_id_type = static_cast<GetInfoType>(info_id); @@ -746,16 +759,16 @@ 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: - case GetInfoType::PersonalMmHeapUsage: + case GetInfoType::SystemResourceSize: + case GetInfoType::SystemResourceUsage: case GetInfoType::TitleId: case GetInfoType::UserExceptionContextAddr: - case GetInfoType::TotalPhysicalMemoryAvailableWithoutMmHeap: - case GetInfoType::TotalPhysicalMemoryUsedWithoutMmHeap: { + case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource: + case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource: { if (info_sub_id != 0) { return ERR_INVALID_ENUM_VALUE; } @@ -800,12 +813,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: @@ -816,8 +829,13 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha *result = process->GetTotalPhysicalMemoryUsed(); return RESULT_SUCCESS; - case GetInfoType::IsVirtualAddressMemoryEnabled: - *result = process->IsVirtualMemoryEnabled(); + case GetInfoType::SystemResourceSize: + *result = process->GetSystemResourceSize(); + return RESULT_SUCCESS; + + case GetInfoType::SystemResourceUsage: + LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage"); + *result = process->GetSystemResourceUsage(); return RESULT_SUCCESS; case GetInfoType::TitleId: @@ -825,17 +843,15 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha return RESULT_SUCCESS; case GetInfoType::UserExceptionContextAddr: - LOG_WARNING(Kernel_SVC, - "(STUBBED) Attempted to query user exception context address, returned 0"); - *result = 0; + *result = process->GetTLSRegionAddress(); return RESULT_SUCCESS; - case GetInfoType::TotalPhysicalMemoryAvailableWithoutMmHeap: - *result = process->GetTotalPhysicalMemoryAvailable(); + case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource: + *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource(); return RESULT_SUCCESS; - case GetInfoType::TotalPhysicalMemoryUsedWithoutMmHeap: - *result = process->GetTotalPhysicalMemoryUsedWithoutMmHeap(); + case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource: + *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource(); return RESULT_SUCCESS; default: @@ -940,6 +956,86 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha } } +/// Maps memory at a desired address +static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { + LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); + + if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); + return ERR_INVALID_ADDRESS; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); + return ERR_INVALID_SIZE; + } + + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is zero"); + return ERR_INVALID_SIZE; + } + + if (!(addr < addr + size)) { + LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); + return ERR_INVALID_MEMORY_RANGE; + } + + Process* const current_process = system.Kernel().CurrentProcess(); + auto& vm_manager = current_process->VMManager(); + + if (current_process->GetSystemResourceSize() == 0) { + LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); + return ERR_INVALID_STATE; + } + + if (!vm_manager.IsWithinMapRegion(addr, size)) { + LOG_ERROR(Kernel_SVC, "Range not within map region"); + return ERR_INVALID_MEMORY_RANGE; + } + + return vm_manager.MapPhysicalMemory(addr, size); +} + +/// Unmaps memory previously mapped via MapPhysicalMemory +static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) { + LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size); + + if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); + return ERR_INVALID_ADDRESS; + } + + if (!Common::Is4KBAligned(size)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size); + return ERR_INVALID_SIZE; + } + + if (size == 0) { + LOG_ERROR(Kernel_SVC, "Size is zero"); + return ERR_INVALID_SIZE; + } + + if (!(addr < addr + size)) { + LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address"); + return ERR_INVALID_MEMORY_RANGE; + } + + Process* const current_process = system.Kernel().CurrentProcess(); + auto& vm_manager = current_process->VMManager(); + + if (current_process->GetSystemResourceSize() == 0) { + LOG_ERROR(Kernel_SVC, "System Resource Size is zero"); + return ERR_INVALID_STATE; + } + + if (!vm_manager.IsWithinMapRegion(addr, size)) { + LOG_ERROR(Kernel_SVC, "Range not within map region"); + return ERR_INVALID_MEMORY_RANGE; + } + + return vm_manager.UnmapPhysicalMemory(addr, size); +} + /// Sets the thread activity static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 activity) { LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", handle, activity); @@ -1641,8 +1737,8 @@ static ResultCode SignalProcessWideKey(Core::System& system, VAddr condition_var // Wait for an address (via Address Arbiter) static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type, s32 value, s64 timeout) { - LOG_WARNING(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, timeout={}", - address, type, value, timeout); + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, timeout={}", address, + type, value, timeout); // If the passed address is a kernel virtual address, return invalid memory state. if (Memory::IsKernelVirtualAddress(address)) { @@ -1664,8 +1760,8 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type, // Signals to an address (via Address Arbiter) static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type, s32 value, s32 num_to_wake) { - LOG_WARNING(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}", - address, type, value, num_to_wake); + LOG_TRACE(Kernel_SVC, "called, address=0x{:X}, type=0x{:X}, value=0x{:X}, num_to_wake=0x{:X}", + address, type, value, num_to_wake); // If the passed address is a kernel virtual address, return invalid memory state. if (Memory::IsKernelVirtualAddress(address)) { @@ -2297,8 +2393,8 @@ static const FunctionDef SVC_Table[] = { {0x29, SvcWrap<GetInfo>, "GetInfo"}, {0x2A, nullptr, "FlushEntireDataCache"}, {0x2B, nullptr, "FlushDataCache"}, - {0x2C, nullptr, "MapPhysicalMemory"}, - {0x2D, nullptr, "UnmapPhysicalMemory"}, + {0x2C, SvcWrap<MapPhysicalMemory>, "MapPhysicalMemory"}, + {0x2D, SvcWrap<UnmapPhysicalMemory>, "UnmapPhysicalMemory"}, {0x2E, nullptr, "GetFutureThreadInfo"}, {0x2F, nullptr, "GetLastThreadInfo"}, {0x30, SvcWrap<GetResourceLimitLimitValue>, "GetResourceLimitLimitValue"}, diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 865473c6f..c2d8d0dc3 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -32,6 +32,11 @@ void SvcWrap(Core::System& system) { FuncReturn(system, func(system, Param(system, 0)).raw); } +template <ResultCode func(Core::System&, u64, u64)> +void SvcWrap(Core::System& system) { + FuncReturn(system, func(system, Param(system, 0), Param(system, 1)).raw); +} + template <ResultCode func(Core::System&, u32)> void SvcWrap(Core::System& system) { FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw); diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index c73a40977..ec529e7f2 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -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()); diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index b4b9cda7c..07e989637 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -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; }; /** diff --git a/src/core/hle/kernel/transfer_memory.cpp b/src/core/hle/kernel/transfer_memory.cpp index 26c4e5e67..1113c815e 100644 --- a/src/core/hle/kernel/transfer_memory.cpp +++ b/src/core/hle/kernel/transfer_memory.cpp @@ -47,7 +47,7 @@ ResultCode TransferMemory::MapMemory(VAddr address, u64 size, MemoryPermission p return ERR_INVALID_STATE; } - backing_block = std::make_shared<std::vector<u8>>(size); + backing_block = std::make_shared<PhysicalMemory>(size); const auto map_state = owner_permissions == MemoryPermission::None ? MemoryState::TransferMemoryIsolated diff --git a/src/core/hle/kernel/transfer_memory.h b/src/core/hle/kernel/transfer_memory.h index a140b1e2b..6be9dc094 100644 --- a/src/core/hle/kernel/transfer_memory.h +++ b/src/core/hle/kernel/transfer_memory.h @@ -8,6 +8,7 @@ #include <vector> #include "core/hle/kernel/object.h" +#include "core/hle/kernel/physical_memory.h" union ResultCode; @@ -82,7 +83,7 @@ private: ~TransferMemory() override; /// Memory block backing this instance. - std::shared_ptr<std::vector<u8>> backing_block; + std::shared_ptr<PhysicalMemory> backing_block; /// The base address for the memory managed by this instance. VAddr base_address = 0; diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 6d6980aba..c7af87073 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -5,13 +5,15 @@ #include <algorithm> #include <iterator> #include <utility> +#include "common/alignment.h" #include "common/assert.h" #include "common/logging/log.h" #include "common/memory_hook.h" -#include "core/arm/arm_interface.h" #include "core/core.h" #include "core/file_sys/program_metadata.h" #include "core/hle/kernel/errors.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/vm_manager.h" #include "core/memory.h" #include "core/memory_setup.h" @@ -49,10 +51,14 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const { type != next.type) { return false; } - if (type == VMAType::AllocatedMemoryBlock && - (backing_block != next.backing_block || offset + size != next.offset)) { + if ((attribute & MemoryAttribute::DeviceMapped) == MemoryAttribute::DeviceMapped) { + // TODO: Can device mapped memory be merged sanely? + // Not merging it may cause inaccuracies versus hardware when memory layout is queried. return false; } + if (type == VMAType::AllocatedMemoryBlock) { + return true; + } if (type == VMAType::BackingMemory && backing_memory + size != next.backing_memory) { return false; } @@ -68,9 +74,7 @@ VMManager::VMManager(Core::System& system) : system{system} { Reset(FileSys::ProgramAddressSpaceType::Is39Bit); } -VMManager::~VMManager() { - Reset(FileSys::ProgramAddressSpaceType::Is39Bit); -} +VMManager::~VMManager() = default; void VMManager::Reset(FileSys::ProgramAddressSpaceType type) { Clear(); @@ -100,9 +104,9 @@ bool VMManager::IsValidHandle(VMAHandle handle) const { } ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target, - std::shared_ptr<std::vector<u8>> block, + std::shared_ptr<PhysicalMemory> block, std::size_t offset, u64 size, - MemoryState state) { + MemoryState state, VMAPermission perm) { ASSERT(block != nullptr); ASSERT(offset + size <= block->size()); @@ -111,17 +115,8 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target, VirtualMemoryArea& final_vma = vma_handle->second; ASSERT(final_vma.size == size); - system.ArmInterface(0).MapBackingMemory(target, size, block->data() + offset, - VMAPermission::ReadWriteExecute); - system.ArmInterface(1).MapBackingMemory(target, size, block->data() + offset, - VMAPermission::ReadWriteExecute); - system.ArmInterface(2).MapBackingMemory(target, size, block->data() + offset, - VMAPermission::ReadWriteExecute); - system.ArmInterface(3).MapBackingMemory(target, size, block->data() + offset, - VMAPermission::ReadWriteExecute); - final_vma.type = VMAType::AllocatedMemoryBlock; - final_vma.permissions = VMAPermission::ReadWrite; + final_vma.permissions = perm; final_vma.state = state; final_vma.backing_block = std::move(block); final_vma.offset = offset; @@ -139,11 +134,6 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me VirtualMemoryArea& final_vma = vma_handle->second; ASSERT(final_vma.size == size); - system.ArmInterface(0).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); - system.ArmInterface(1).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); - system.ArmInterface(2).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); - system.ArmInterface(3).MapBackingMemory(target, size, memory, VMAPermission::ReadWriteExecute); - final_vma.type = VMAType::BackingMemory; final_vma.permissions = VMAPermission::ReadWrite; final_vma.state = state; @@ -154,22 +144,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); } @@ -221,11 +222,6 @@ ResultCode VMManager::UnmapRange(VAddr target, u64 size) { ASSERT(FindVMA(target)->second.size >= size); - system.ArmInterface(0).UnmapMemory(target, size); - system.ArmInterface(1).UnmapMemory(target, size); - system.ArmInterface(2).UnmapMemory(target, size); - system.ArmInterface(3).UnmapMemory(target, size); - return RESULT_SUCCESS; } @@ -265,7 +261,7 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) { if (heap_memory == nullptr) { // Initialize heap - heap_memory = std::make_shared<std::vector<u8>>(size); + heap_memory = std::make_shared<PhysicalMemory>(size); heap_end = heap_region_base + size; } else { UnmapRange(heap_region_base, GetCurrentHeapSize()); @@ -299,6 +295,162 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) { return MakeResult<VAddr>(heap_region_base); } +ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { + // Check how much memory we've already mapped. + const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size); + if (mapped_size_result.Failed()) { + return mapped_size_result.Code(); + } + + // If we've already mapped the desired amount, return early. + const std::size_t mapped_size = *mapped_size_result; + if (mapped_size == size) { + return RESULT_SUCCESS; + } + + // Check that we can map the memory we want. + const auto res_limit = system.CurrentProcess()->GetResourceLimit(); + const u64 physmem_remaining = res_limit->GetMaxResourceValue(ResourceType::PhysicalMemory) - + res_limit->GetCurrentResourceValue(ResourceType::PhysicalMemory); + if (physmem_remaining < (size - mapped_size)) { + return ERR_RESOURCE_LIMIT_EXCEEDED; + } + + // Keep track of the memory regions we unmap. + std::vector<std::pair<u64, u64>> mapped_regions; + ResultCode result = RESULT_SUCCESS; + + // Iterate, trying to map memory. + { + const auto end_addr = target + size; + const auto last_addr = end_addr - 1; + VAddr cur_addr = target; + + auto iter = FindVMA(target); + ASSERT(iter != vma_map.end()); + + while (true) { + const auto& vma = iter->second; + const auto vma_start = vma.base; + const auto vma_end = vma_start + vma.size; + const auto vma_last = vma_end - 1; + + // Map the memory block + const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr); + if (vma.state == MemoryState::Unmapped) { + const auto map_res = + MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size), 0, + map_size, MemoryState::Heap, VMAPermission::ReadWrite); + result = map_res.Code(); + if (result.IsError()) { + break; + } + + mapped_regions.emplace_back(cur_addr, map_size); + } + + // Break once we hit the end of the range. + if (last_addr <= vma_last) { + break; + } + + // Advance to the next block. + cur_addr = vma_end; + iter = FindVMA(cur_addr); + ASSERT(iter != vma_map.end()); + } + } + + // If we failed, unmap memory. + if (result.IsError()) { + for (const auto [unmap_address, unmap_size] : mapped_regions) { + ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(), + "Failed to unmap memory range."); + } + + return result; + } + + // Update amount of mapped physical memory. + physical_memory_mapped += size - mapped_size; + + return RESULT_SUCCESS; +} + +ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { + // Check how much memory is currently mapped. + const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size); + if (mapped_size_result.Failed()) { + return mapped_size_result.Code(); + } + + // If we've already unmapped all the memory, return early. + const std::size_t mapped_size = *mapped_size_result; + if (mapped_size == 0) { + return RESULT_SUCCESS; + } + + // Keep track of the memory regions we unmap. + std::vector<std::pair<u64, u64>> unmapped_regions; + ResultCode result = RESULT_SUCCESS; + + // Try to unmap regions. + { + const auto end_addr = target + size; + const auto last_addr = end_addr - 1; + VAddr cur_addr = target; + + auto iter = FindVMA(target); + ASSERT(iter != vma_map.end()); + + while (true) { + const auto& vma = iter->second; + const auto vma_start = vma.base; + const auto vma_end = vma_start + vma.size; + const auto vma_last = vma_end - 1; + + // Unmap the memory block + const auto unmap_size = std::min(end_addr - cur_addr, vma_end - cur_addr); + if (vma.state == MemoryState::Heap) { + result = UnmapRange(cur_addr, unmap_size); + if (result.IsError()) { + break; + } + + unmapped_regions.emplace_back(cur_addr, unmap_size); + } + + // Break once we hit the end of the range. + if (last_addr <= vma_last) { + break; + } + + // Advance to the next block. + cur_addr = vma_end; + iter = FindVMA(cur_addr); + ASSERT(iter != vma_map.end()); + } + } + + // If we failed, re-map regions. + // TODO: Preserve memory contents? + if (result.IsError()) { + for (const auto [map_address, map_size] : unmapped_regions) { + const auto remap_res = + MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size), 0, map_size, + MemoryState::Heap, VMAPermission::None); + ASSERT_MSG(remap_res.Succeeded(), "Failed to remap a memory block."); + } + + return result; + } + + // Update mapped amount + physical_memory_mapped -= mapped_size; + + return RESULT_SUCCESS; +} + ResultCode VMManager::MapCodeMemory(VAddr dst_address, VAddr src_address, u64 size) { constexpr auto ignore_attribute = MemoryAttribute::LockedForIPC | MemoryAttribute::DeviceMapped; const auto src_check_result = CheckRangeState( @@ -438,7 +590,7 @@ ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, Mem ASSERT_MSG(vma_offset + size <= vma->second.size, "Shared memory exceeds bounds of mapped block"); - const std::shared_ptr<std::vector<u8>>& backing_block = vma->second.backing_block; + const std::shared_ptr<PhysicalMemory>& backing_block = vma->second.backing_block; const std::size_t backing_block_offset = vma->second.offset + vma_offset; CASCADE_RESULT(auto new_vma, @@ -446,12 +598,12 @@ ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, Mem // Protect mirror with permissions from old region Reprotect(new_vma, vma->second.permissions); // Remove permissions from old region - Reprotect(vma, VMAPermission::None); + ReprotectRange(src_addr, size, VMAPermission::None); return RESULT_SUCCESS; } -void VMManager::RefreshMemoryBlockMappings(const std::vector<u8>* block) { +void VMManager::RefreshMemoryBlockMappings(const PhysicalMemory* block) { // If this ever proves to have a noticeable performance impact, allow users of the function to // specify a specific range of addresses to limit the scan to. for (const auto& p : vma_map) { @@ -579,14 +731,14 @@ VMManager::VMAIter VMManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) { VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) { const VMAIter next_vma = std::next(iter); if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) { - iter->second.size += next_vma->second.size; + MergeAdjacentVMA(iter->second, next_vma->second); vma_map.erase(next_vma); } if (iter != vma_map.begin()) { VMAIter prev_vma = std::prev(iter); if (prev_vma->second.CanBeMergedWith(iter->second)) { - prev_vma->second.size += iter->second.size; + MergeAdjacentVMA(prev_vma->second, iter->second); vma_map.erase(iter); iter = prev_vma; } @@ -595,6 +747,44 @@ VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) { return iter; } +void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right) { + ASSERT(left.CanBeMergedWith(right)); + + // Always merge allocated memory blocks, even when they don't share the same backing block. + if (left.type == VMAType::AllocatedMemoryBlock && + (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) { + const auto right_begin = right.backing_block->begin() + right.offset; + const auto right_end = right_begin + right.size; + + // Check if we can save work. + if (left.offset == 0 && left.size == left.backing_block->size()) { + // Fast case: left is an entire backing block. + left.backing_block->insert(left.backing_block->end(), right_begin, right_end); + } else { + // Slow case: make a new memory block for left and right. + const auto left_begin = left.backing_block->begin() + left.offset; + const auto left_end = left_begin + left.size; + const auto left_size = static_cast<std::size_t>(std::distance(left_begin, left_end)); + const auto right_size = static_cast<std::size_t>(std::distance(right_begin, right_end)); + + auto new_memory = std::make_shared<PhysicalMemory>(); + new_memory->reserve(left_size + right_size); + new_memory->insert(new_memory->end(), left_begin, left_end); + new_memory->insert(new_memory->end(), right_begin, right_end); + + left.backing_block = std::move(new_memory); + left.offset = 0; + } + + // Page table update is needed, because backing memory changed. + left.size += right.size; + UpdatePageTableForVMA(left); + } else { + // Just update the size. + left.size += right.size; + } +} + void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { switch (vma.type) { case VMAType::Free: @@ -616,9 +806,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: @@ -634,6 +826,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; @@ -643,6 +836,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; @@ -652,7 +846,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: @@ -660,6 +854,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; @@ -670,15 +866,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; } } @@ -758,6 +959,84 @@ VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, Memo std::make_tuple(initial_state, initial_permissions, initial_attributes & ~ignore_mask)); } +ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address, + std::size_t size) const { + const VAddr end_addr = address + size; + const VAddr last_addr = end_addr - 1; + std::size_t mapped_size = 0; + + VAddr cur_addr = address; + auto iter = FindVMA(cur_addr); + ASSERT(iter != vma_map.end()); + + while (true) { + const auto& vma = iter->second; + const VAddr vma_start = vma.base; + const VAddr vma_end = vma_start + vma.size; + const VAddr vma_last = vma_end - 1; + + // Add size if relevant. + if (vma.state != MemoryState::Unmapped) { + mapped_size += std::min(end_addr - cur_addr, vma_end - cur_addr); + } + + // Break once we hit the end of the range. + if (last_addr <= vma_last) { + break; + } + + // Advance to the next block. + cur_addr = vma_end; + iter = std::next(iter); + ASSERT(iter != vma_map.end()); + } + + return MakeResult(mapped_size); +} + +ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr address, + std::size_t size) const { + const VAddr end_addr = address + size; + const VAddr last_addr = end_addr - 1; + std::size_t mapped_size = 0; + + VAddr cur_addr = address; + auto iter = FindVMA(cur_addr); + ASSERT(iter != vma_map.end()); + + while (true) { + const auto& vma = iter->second; + const auto vma_start = vma.base; + const auto vma_end = vma_start + vma.size; + const auto vma_last = vma_end - 1; + const auto state = vma.state; + const auto attr = vma.attribute; + + // Memory within region must be free or mapped heap. + if (!((state == MemoryState::Heap && attr == MemoryAttribute::None) || + (state == MemoryState::Unmapped))) { + return ERR_INVALID_ADDRESS_STATE; + } + + // Add size if relevant. + if (state != MemoryState::Unmapped) { + mapped_size += std::min(end_addr - cur_addr, vma_end - cur_addr); + } + + // Break once we hit the end of the range. + if (last_addr <= vma_last) { + break; + } + + // Advance to the next block. + cur_addr = vma_end; + iter = std::next(iter); + ASSERT(iter != vma_map.end()); + } + + return MakeResult(mapped_size); +} + u64 VMManager::GetTotalPhysicalMemoryAvailable() const { LOG_WARNING(Kernel, "(STUBBED) called"); return 0xF8000000; @@ -870,21 +1149,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 { diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index dfbf7a894..850a7ebc3 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -11,6 +11,7 @@ #include "common/common_types.h" #include "common/memory_hook.h" #include "common/page_table.h" +#include "core/hle/kernel/physical_memory.h" #include "core/hle/result.h" #include "core/memory.h" @@ -290,7 +291,7 @@ struct VirtualMemoryArea { // Settings for type = AllocatedMemoryBlock /// Memory block backing this VMA. - std::shared_ptr<std::vector<u8>> backing_block = nullptr; + std::shared_ptr<PhysicalMemory> backing_block = nullptr; /// Offset into the backing_memory the mapping starts from. std::size_t offset = 0; @@ -348,8 +349,9 @@ public: * @param size Size of the mapping. * @param state MemoryState tag to attach to the VMA. */ - ResultVal<VMAHandle> MapMemoryBlock(VAddr target, std::shared_ptr<std::vector<u8>> block, - std::size_t offset, u64 size, MemoryState state); + ResultVal<VMAHandle> MapMemoryBlock(VAddr target, std::shared_ptr<PhysicalMemory> block, + std::size_t offset, u64 size, MemoryState state, + VMAPermission perm = VMAPermission::ReadWrite); /** * Maps an unmanaged host memory pointer at a given address. @@ -362,14 +364,39 @@ 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. * * @param target The guest address to start the mapping at. @@ -425,6 +452,34 @@ public: /// ResultVal<VAddr> SetHeapSize(u64 size); + /// Maps memory at a given address. + /// + /// @param target The virtual address to map memory at. + /// @param size The amount of memory to map. + /// + /// @note The destination address must lie within the Map region. + /// + /// @note This function requires that SystemResourceSize be non-zero, + /// however, this is just because if it were not then the + /// resulting page tables could be exploited on hardware by + /// a malicious program. SystemResource usage does not need + /// to be explicitly checked or updated here. + ResultCode MapPhysicalMemory(VAddr target, u64 size); + + /// Unmaps memory at a given address. + /// + /// @param target The virtual address to unmap memory at. + /// @param size The amount of memory to unmap. + /// + /// @note The destination address must lie within the Map region. + /// + /// @note This function requires that SystemResourceSize be non-zero, + /// however, this is just because if it were not then the + /// resulting page tables could be exploited on hardware by + /// a malicious program. SystemResource usage does not need + /// to be explicitly checked or updated here. + ResultCode UnmapPhysicalMemory(VAddr target, u64 size); + /// Maps a region of memory as code memory. /// /// @param dst_address The base address of the region to create the aliasing memory region. @@ -493,7 +548,7 @@ public: * Scans all VMAs and updates the page table range of any that use the given vector as backing * memory. This should be called after any operation that causes reallocation of the vector. */ - void RefreshMemoryBlockMappings(const std::vector<u8>* block); + void RefreshMemoryBlockMappings(const PhysicalMemory* block); /// Dumps the address space layout to the log, for debugging void LogLayout() const; @@ -571,17 +626,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; @@ -632,6 +687,11 @@ private: */ VMAIter MergeAdjacent(VMAIter vma); + /** + * Merges two adjacent VMAs. + */ + void MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right); + /// Updates the pages corresponding to this VMA so they match the VMA's attributes. void UpdatePageTableForVMA(const VirtualMemoryArea& vma); @@ -676,6 +736,13 @@ private: MemoryAttribute attribute_mask, MemoryAttribute attribute, MemoryAttribute ignore_mask) const; + /// Gets the amount of memory currently mapped (state != Unmapped) in a range. + ResultVal<std::size_t> SizeOfAllocatedVMAsInRange(VAddr address, std::size_t size) const; + + /// Gets the amount of memory unmappable by UnmapPhysicalMemory in a range. + ResultVal<std::size_t> SizeOfUnmappablePhysicalMemoryInRange(VAddr address, + std::size_t size) const; + /** * A map covering the entirety of the managed address space, keyed by the `base` field of each * VMA. It must always be modified by splitting or merging VMAs, so that the invariant @@ -701,8 +768,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; @@ -711,12 +778,17 @@ private: // the entire virtual address space extents that bound the allocations, including any holes. // This makes deallocation and reallocation of holes fast and keeps process memory contiguous // in the emulator address space, allowing Memory::GetPointer to be reasonably safe. - std::shared_ptr<std::vector<u8>> heap_memory; + std::shared_ptr<PhysicalMemory> heap_memory; // The end of the currently allocated heap. This is not an inclusive // end of the range. This is essentially 'base_address + current_size'. VAddr heap_end = 0; + // The current amount of memory mapped via MapPhysicalMemory. + // This is used here (and in Nintendo's kernel) only for debugging, and does not impact + // any behavior. + u64 physical_memory_mapped = 0; + Core::System& system; }; } // namespace Kernel diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index cb66e344b..0c0f7ed6e 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -12,16 +12,28 @@ #include "common/swap.h" #include "core/constants.h" #include "core/core_timing.h" +#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 { +constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 30}; +constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100}; + static std::string GetImagePath(Common::UUID uuid) { return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; @@ -32,22 +44,33 @@ static constexpr u32 SanitizeJPEGSize(std::size_t size) { return static_cast<u32>(std::min(size, max_jpeg_image_size)); } -class IProfile final : public ServiceFramework<IProfile> { +class IProfileCommon : public ServiceFramework<IProfileCommon> { public: - explicit IProfile(Common::UUID user_id, ProfileManager& profile_manager) - : ServiceFramework("IProfile"), profile_manager(profile_manager), user_id(user_id) { + explicit IProfileCommon(const char* name, bool editor_commands, Common::UUID user_id, + ProfileManager& profile_manager) + : ServiceFramework(name), profile_manager(profile_manager), user_id(user_id) { static const FunctionInfo functions[] = { - {0, &IProfile::Get, "Get"}, - {1, &IProfile::GetBase, "GetBase"}, - {10, &IProfile::GetImageSize, "GetImageSize"}, - {11, &IProfile::LoadImage, "LoadImage"}, + {0, &IProfileCommon::Get, "Get"}, + {1, &IProfileCommon::GetBase, "GetBase"}, + {10, &IProfileCommon::GetImageSize, "GetImageSize"}, + {11, &IProfileCommon::LoadImage, "LoadImage"}, }; + RegisterHandlers(functions); + + if (editor_commands) { + static const FunctionInfo editor_functions[] = { + {100, &IProfileCommon::Store, "Store"}, + {101, &IProfileCommon::StoreWithImage, "StoreWithImage"}, + }; + + RegisterHandlers(editor_functions); + } } -private: +protected: void Get(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); + LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; ProfileData data{}; if (profile_manager.GetProfileBaseAndData(user_id, profile_base, data)) { @@ -66,7 +89,7 @@ private: } void GetBase(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); + LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); ProfileBase profile_base{}; if (profile_manager.GetProfileBase(user_id, profile_base)) { IPC::ResponseBuilder rb{ctx, 16}; @@ -90,7 +113,7 @@ private: LOG_WARNING(Service_ACC, "Failed to load user provided image! Falling back to built-in backup..."); ctx.WriteBuffer(Core::Constants::ACCOUNT_BACKUP_JPEG); - rb.Push<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size()); + rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size())); return; } @@ -112,16 +135,97 @@ private: if (!image.IsOpen()) { LOG_WARNING(Service_ACC, "Failed to load user provided image! Falling back to built-in backup..."); - rb.Push<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size()); + rb.Push(SanitizeJPEGSize(Core::Constants::ACCOUNT_BACKUP_JPEG.size())); } else { - rb.Push<u32>(SanitizeJPEGSize(image.GetSize())); + rb.Push(SanitizeJPEGSize(image.GetSize())); + } + } + + void Store(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto base = rp.PopRaw<ProfileBase>(); + + const auto user_data = ctx.ReadBuffer(); + + LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}", + Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(base.username.data()), base.username.size()), + base.timestamp, base.user_uuid.Format()); + + if (user_data.size() < sizeof(ProfileData)) { + LOG_ERROR(Service_ACC, "ProfileData buffer too small!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_BUFFER_SIZE); + return; + } + + ProfileData data; + std::memcpy(&data, user_data.data(), sizeof(ProfileData)); + + if (!profile_manager.SetProfileBaseAndData(user_id, base, data)) { + LOG_ERROR(Service_ACC, "Failed to update profile data and base!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_FAILED_SAVE_DATA); + return; } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); } - const ProfileManager& profile_manager; + void StoreWithImage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto base = rp.PopRaw<ProfileBase>(); + + const auto user_data = ctx.ReadBuffer(); + const auto image_data = ctx.ReadBuffer(1); + + LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}", + Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(base.username.data()), base.username.size()), + base.timestamp, base.user_uuid.Format()); + + if (user_data.size() < sizeof(ProfileData)) { + LOG_ERROR(Service_ACC, "ProfileData buffer too small!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_BUFFER_SIZE); + return; + } + + ProfileData data; + std::memcpy(&data, user_data.data(), sizeof(ProfileData)); + + FileUtil::IOFile image(GetImagePath(user_id), "wb"); + + if (!image.IsOpen() || !image.Resize(image_data.size()) || + image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() || + !profile_manager.SetProfileBaseAndData(user_id, base, data)) { + LOG_ERROR(Service_ACC, "Failed to update profile data, base, and image!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_FAILED_SAVE_DATA); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + ProfileManager& profile_manager; Common::UUID user_id; ///< The user id this profile refers to. }; +class IProfile final : public IProfileCommon { +public: + IProfile(Common::UUID user_id, ProfileManager& profile_manager) + : IProfileCommon("IProfile", false, user_id, profile_manager) {} +}; + +class IProfileEditor final : public IProfileCommon { +public: + IProfileEditor(Common::UUID user_id, ProfileManager& profile_manager) + : IProfileCommon("IProfileEditor", true, user_id, profile_manager) {} +}; + class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { public: IManagerForApplication() : ServiceFramework("IManagerForApplication") { @@ -159,7 +263,7 @@ private: }; void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called"); + LOG_DEBUG(Service_ACC, "called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(static_cast<u32>(profile_manager->GetUserCount())); @@ -168,7 +272,7 @@ void Module::Interface::GetUserCount(Kernel::HLERequestContext& ctx) { void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; Common::UUID user_id = rp.PopRaw<Common::UUID>(); - LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); + LOG_DEBUG(Service_ACC, "called user_id={}", user_id.Format()); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); @@ -176,21 +280,21 @@ void Module::Interface::GetUserExistence(Kernel::HLERequestContext& ctx) { } void Module::Interface::ListAllUsers(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called"); + LOG_DEBUG(Service_ACC, "called"); ctx.WriteBuffer(profile_manager->GetAllUsers()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Module::Interface::ListOpenUsers(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called"); + LOG_DEBUG(Service_ACC, "called"); ctx.WriteBuffer(profile_manager->GetOpenUsers()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void Module::Interface::GetLastOpenedUser(Kernel::HLERequestContext& ctx) { - LOG_INFO(Service_ACC, "called"); + LOG_DEBUG(Service_ACC, "called"); IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); rb.PushRaw<Common::UUID>(profile_manager->GetLastOpenedUser()); @@ -214,9 +318,71 @@ void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestCon } void Module::Interface::InitializeApplicationInfo(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + 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) { @@ -226,6 +392,42 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo rb.PushIpcInterface<IManagerForApplication>(); } +void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ACC, "called"); + FileSys::NACP nacp; + const auto res = system.GetAppLoader().ReadControlData(nacp); + + bool is_locked = false; + + if (res != Loader::ResultStatus::Success) { + FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; + auto nacp_unique = pm.GetControlMetadata().first; + + if (nacp_unique != nullptr) { + is_locked = nacp_unique->GetUserAccountSwitchLock(); + } else { + LOG_ERROR(Service_ACC, "nacp_unique is null!"); + } + } else { + is_locked = nacp.GetUserAccountSwitchLock(); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(is_locked); +} + +void Module::Interface::GetProfileEditor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + Common::UUID user_id = rp.PopRaw<Common::UUID>(); + + LOG_DEBUG(Service_ACC, "called, user_id={}", user_id.Format()); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IProfileEditor>(user_id, *profile_manager); +} + void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ACC, "called"); // A u8 is passed into this function which we can safely ignore. It's to determine if we have @@ -251,19 +453,25 @@ void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContex } Module::Interface::Interface(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager, const char* name) + std::shared_ptr<ProfileManager> profile_manager, Core::System& system, + const char* name) : ServiceFramework(name), module(std::move(module)), - profile_manager(std::move(profile_manager)) {} + profile_manager(std::move(profile_manager)), system(system) {} Module::Interface::~Interface() = default; -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(Core::System& system) { auto module = std::make_shared<Module>(); auto profile_manager = std::make_shared<ProfileManager>(); - std::make_shared<ACC_AA>(module, profile_manager)->InstallAsService(service_manager); - std::make_shared<ACC_SU>(module, profile_manager)->InstallAsService(service_manager); - std::make_shared<ACC_U0>(module, profile_manager)->InstallAsService(service_manager); - std::make_shared<ACC_U1>(module, profile_manager)->InstallAsService(service_manager); + + std::make_shared<ACC_AA>(module, profile_manager, system) + ->InstallAsService(system.ServiceManager()); + std::make_shared<ACC_SU>(module, profile_manager, system) + ->InstallAsService(system.ServiceManager()); + std::make_shared<ACC_U0>(module, profile_manager, system) + ->InstallAsService(system.ServiceManager()); + std::make_shared<ACC_U1>(module, profile_manager, system) + ->InstallAsService(system.ServiceManager()); } } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index 89b2104fa..7a7dc9ec6 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -4,6 +4,7 @@ #pragma once +#include "core/hle/service/glue/manager.h" #include "core/hle/service/service.h" namespace Service::Account { @@ -15,7 +16,8 @@ public: class Interface : public ServiceFramework<Interface> { public: explicit Interface(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager, const char* name); + std::shared_ptr<ProfileManager> profile_manager, Core::System& system, + const char* name); ~Interface() override; void GetUserCount(Kernel::HLERequestContext& ctx); @@ -25,17 +27,41 @@ public: void GetLastOpenedUser(Kernel::HLERequestContext& ctx); void GetProfile(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); + void GetProfileEditor(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; + Core::System& system; }; }; /// Registers all ACC services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(Core::System& system); } // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp index e84d9f7cf..3bac6bcd1 100644 --- a/src/core/hle/service/acc/acc_aa.cpp +++ b/src/core/hle/service/acc/acc_aa.cpp @@ -6,8 +6,9 @@ namespace Service::Account { -ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) - : Module::Interface(std::move(module), std::move(profile_manager), "acc:aa") { +ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system) + : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:aa") { static const FunctionInfo functions[] = { {0, nullptr, "EnsureCacheAsync"}, {1, nullptr, "LoadCache"}, diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h index 9edb0421b..932c04890 100644 --- a/src/core/hle/service/acc/acc_aa.h +++ b/src/core/hle/service/acc/acc_aa.h @@ -10,8 +10,8 @@ namespace Service::Account { class ACC_AA final : public Module::Interface { public: - explicit ACC_AA(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager); + explicit ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system); ~ACC_AA() override; }; diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp index d66233cad..0d1663657 100644 --- a/src/core/hle/service/acc/acc_su.cpp +++ b/src/core/hle/service/acc/acc_su.cpp @@ -6,8 +6,9 @@ namespace Service::Account { -ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) - : Module::Interface(std::move(module), std::move(profile_manager), "acc:su") { +ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system) + : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:su") { // clang-format off static const FunctionInfo functions[] = { {0, &ACC_SU::GetUserCount, "GetUserCount"}, @@ -40,7 +41,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p {202, nullptr, "CancelUserRegistration"}, {203, nullptr, "DeleteUser"}, {204, nullptr, "SetUserPosition"}, - {205, nullptr, "GetProfileEditor"}, + {205, &ACC_SU::GetProfileEditor, "GetProfileEditor"}, {206, nullptr, "CompleteUserRegistrationForcibly"}, {210, nullptr, "CreateFloatingRegistrationRequest"}, {230, nullptr, "AuthenticateServiceAsync"}, diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h index fcced063a..0a700d9bf 100644 --- a/src/core/hle/service/acc/acc_su.h +++ b/src/core/hle/service/acc/acc_su.h @@ -10,8 +10,8 @@ namespace Service::Account { class ACC_SU final : public Module::Interface { public: - explicit ACC_SU(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager); + explicit ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system); ~ACC_SU() override; }; diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp index 182f7c7e5..0ac19f4ff 100644 --- a/src/core/hle/service/acc/acc_u0.cpp +++ b/src/core/hle/service/acc/acc_u0.cpp @@ -6,8 +6,9 @@ namespace Service::Account { -ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) - : Module::Interface(std::move(module), std::move(profile_manager), "acc:u0") { +ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system) + : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:u0") { // clang-format off static const FunctionInfo functions[] = { {0, &ACC_U0::GetUserCount, "GetUserCount"}, @@ -30,9 +31,9 @@ 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, nullptr, "IsUserAccountSwitchLocked"}, + {150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, }; // clang-format on diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h index a1290e0bd..3bd9c3164 100644 --- a/src/core/hle/service/acc/acc_u0.h +++ b/src/core/hle/service/acc/acc_u0.h @@ -10,8 +10,8 @@ namespace Service::Account { class ACC_U0 final : public Module::Interface { public: - explicit ACC_U0(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager); + explicit ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system); ~ACC_U0() override; }; diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp index 2dd17d935..6520b3968 100644 --- a/src/core/hle/service/acc/acc_u1.cpp +++ b/src/core/hle/service/acc/acc_u1.cpp @@ -6,8 +6,9 @@ namespace Service::Account { -ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager) - : Module::Interface(std::move(module), std::move(profile_manager), "acc:u1") { +ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system) + : Module::Interface(std::move(module), std::move(profile_manager), system, "acc:u1") { // clang-format off static const FunctionInfo functions[] = { {0, &ACC_U1::GetUserCount, "GetUserCount"}, diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h index 9e79daee3..829f8a744 100644 --- a/src/core/hle/service/acc/acc_u1.h +++ b/src/core/hle/service/acc/acc_u1.h @@ -10,8 +10,8 @@ namespace Service::Account { class ACC_U1 final : public Module::Interface { public: - explicit ACC_U1(std::shared_ptr<Module> module, - std::shared_ptr<ProfileManager> profile_manager); + explicit ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> profile_manager, + Core::System& system); ~ACC_U1() override; }; diff --git a/src/core/hle/service/acc/errors.h b/src/core/hle/service/acc/errors.h new file mode 100644 index 000000000..1f0577239 --- /dev/null +++ b/src/core/hle/service/acc/errors.h @@ -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 diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 49aa5908b..8f9986326 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -305,6 +305,17 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) { return true; } +bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new, + const ProfileData& data_new) { + const auto index = GetUserIndex(uuid); + if (index.has_value() && SetProfileBase(uuid, profile_new)) { + profiles[*index].data = data_new; + return true; + } + + return false; +} + void ProfileManager::ParseUserSaveFile() { FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h index fd7abb541..5a6d28925 100644 --- a/src/core/hle/service/acc/profile_manager.h +++ b/src/core/hle/service/acc/profile_manager.h @@ -91,6 +91,8 @@ public: bool RemoveUser(Common::UUID uuid); bool SetProfileBase(Common::UUID uuid, const ProfileBase& profile_new); + bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new, + const ProfileData& data_new); private: void ParseUserSaveFile(); diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 3f201c821..797c9a06f 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -29,7 +29,8 @@ #include "core/hle/service/am/omm.h" #include "core/hle/service/am/spsm.h" #include "core/hle/service/am/tcap.h" -#include "core/hle/service/apm/apm.h" +#include "core/hle/service/apm/controller.h" +#include "core/hle/service/apm/interface.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ns/ns.h" #include "core/hle/service/nvflinger/nvflinger.h" @@ -55,7 +56,8 @@ struct LaunchParameters { }; static_assert(sizeof(LaunchParameters) == 0x88); -IWindowController::IWindowController() : ServiceFramework("IWindowController") { +IWindowController::IWindowController(Core::System& system_) + : ServiceFramework("IWindowController"), system{system_} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateWindow"}, @@ -74,7 +76,7 @@ IWindowController::IWindowController() : ServiceFramework("IWindowController") { IWindowController::~IWindowController() = default; void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { - const u64 process_id = Core::System::GetInstance().Kernel().CurrentProcess()->GetProcessID(); + const u64 process_id = system.CurrentProcess()->GetProcessID(); LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id); @@ -230,11 +232,12 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} { IDebugFunctions::~IDebugFunctions() = default; -ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) - : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) { +ISelfController::ISelfController(Core::System& system, + std::shared_ptr<NVFlinger::NVFlinger> nvflinger) + : ServiceFramework("ISelfController"), system(system), nvflinger(std::move(nvflinger)) { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "Exit"}, + {0, &ISelfController::Exit, "Exit"}, {1, &ISelfController::LockExit, "LockExit"}, {2, &ISelfController::UnlockExit, "UnlockExit"}, {3, &ISelfController::EnterFatalSection, "EnterFatalSection"}, @@ -265,13 +268,13 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger {65, nullptr, "ReportUserIsActive"}, {66, nullptr, "GetCurrentIlluminance"}, {67, nullptr, "IsIlluminanceAvailable"}, - {68, nullptr, "SetAutoSleepDisabled"}, - {69, nullptr, "IsAutoSleepDisabled"}, + {68, &ISelfController::SetAutoSleepDisabled, "SetAutoSleepDisabled"}, + {69, &ISelfController::IsAutoSleepDisabled, "IsAutoSleepDisabled"}, {70, nullptr, "ReportMultimediaError"}, {71, nullptr, "GetCurrentIlluminanceEx"}, {80, nullptr, "SetWirelessPriorityMode"}, - {90, nullptr, "GetAccumulatedSuspendedTickValue"}, - {91, nullptr, "GetAccumulatedSuspendedTickChangedEvent"}, + {90, &ISelfController::GetAccumulatedSuspendedTickValue, "GetAccumulatedSuspendedTickValue"}, + {91, &ISelfController::GetAccumulatedSuspendedTickChangedEvent, "GetAccumulatedSuspendedTickChangedEvent"}, {100, nullptr, "SetAlbumImageTakenNotificationEnabled"}, {1000, nullptr, "GetDebugStorageChannel"}, }; @@ -279,22 +282,44 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system.Kernel(); launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, "ISelfController:LaunchableEvent"); + + // 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(); } ISelfController::~ISelfController() = default; +void ISelfController::Exit(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called"); + + system.Shutdown(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void ISelfController::LockExit(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called"); + + system.SetExitLock(true); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } void ISelfController::UnlockExit(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called"); + + system.SetExitLock(false); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -444,8 +469,54 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c rb.Push<u32>(idle_time_detection_extension); } -AppletMessageQueue::AppletMessageQueue() { - auto& kernel = Core::System::GetInstance().Kernel(); +void ISelfController::SetAutoSleepDisabled(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + is_auto_sleep_disabled = rp.Pop<bool>(); + + // On the system itself, if the previous state of is_auto_sleep_disabled + // differed from the current value passed in, it'd signify the internal + // window manager to update (and also increment some statistics like update counts) + // + // It'd also indicate this change to an idle handling context. + // + // However, given we're emulating this behavior, most of this can be ignored + // and it's sufficient to simply set the member variable for querying via + // IsAutoSleepDisabled(). + + LOG_DEBUG(Service_AM, "called. is_auto_sleep_disabled={}", is_auto_sleep_disabled); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void ISelfController::IsAutoSleepDisabled(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called."); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(is_auto_sleep_disabled); +} + +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) { + LOG_DEBUG(Service_AM, "called."); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable); +} + +AppletMessageQueue::AppletMessageQueue(Kernel::KernelCore& kernel) { on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, "AMMessageQueue:OnMessageRecieved"); on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair( @@ -492,8 +563,13 @@ void AppletMessageQueue::OperationModeChanged() { on_operation_mode_changed.writable->Signal(); } -ICommonStateGetter::ICommonStateGetter(std::shared_ptr<AppletMessageQueue> msg_queue) - : ServiceFramework("ICommonStateGetter"), msg_queue(std::move(msg_queue)) { +void AppletMessageQueue::RequestExit() { + PushMessage(AppletMessage::ExitRequested); +} + +ICommonStateGetter::ICommonStateGetter(Core::System& system, + std::shared_ptr<AppletMessageQueue> msg_queue) + : ServiceFramework("ICommonStateGetter"), system(system), msg_queue(std::move(msg_queue)) { // clang-format off static const FunctionInfo functions[] = { {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, @@ -526,7 +602,7 @@ ICommonStateGetter::ICommonStateGetter(std::shared_ptr<AppletMessageQueue> msg_q {63, nullptr, "GetHdcpAuthenticationStateChangeEvent"}, {64, nullptr, "SetTvPowerStateMatchingMode"}, {65, nullptr, "GetApplicationIdByContentActionName"}, - {66, nullptr, "SetCpuBoostMode"}, + {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"}, {80, nullptr, "PerformSystemButtonPressingIfInFocus"}, {90, nullptr, "SetPerformanceConfigurationChangedNotification"}, {91, nullptr, "GetCurrentPerformanceConfiguration"}, @@ -607,6 +683,16 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext& } } +void ICommonStateGetter::SetCpuBoostMode(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_AM, "called, forwarding to APM:SYS"); + + const auto& sm = system.ServiceManager(); + const auto apm_sys = sm.GetService<APM::APM_Sys>("apm:sys"); + ASSERT(apm_sys != nullptr); + + apm_sys->SetCpuBoostMode(ctx); +} + IStorage::IStorage(std::vector<u8> buffer) : ServiceFramework("IStorage"), buffer(std::move(buffer)) { // clang-format off @@ -635,13 +721,11 @@ void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { } void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) { - const bool use_docked_mode{Settings::values.use_docked_mode}; - LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode); + LOG_DEBUG(Service_AM, "called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u32>(use_docked_mode ? APM::PerformanceMode::Docked - : APM::PerformanceMode::Handheld)); + rb.PushEnum(system.GetAPMController().GetCurrentPerformanceMode()); } class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> { @@ -871,7 +955,8 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") { +ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_) + : ServiceFramework("ILibraryAppletCreator"), system{system_} { static const FunctionInfo functions[] = { {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"}, {1, nullptr, "TerminateAllLibraryApplets"}, @@ -893,7 +978,7 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", static_cast<u32>(applet_id), applet_mode); - const auto& applet_manager{Core::System::GetInstance().GetAppletManager()}; + const auto& applet_manager{system.GetAppletManager()}; const auto applet = applet_manager.GetApplet(applet_id); if (applet == nullptr) { @@ -931,8 +1016,7 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex const auto handle{rp.Pop<Kernel::Handle>()}; const auto transfer_mem = - Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>( - handle); + system.CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>(handle); if (transfer_mem == nullptr) { LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle); @@ -950,7 +1034,8 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory))); } -IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { +IApplicationFunctions::IApplicationFunctions(Core::System& system_) + : ServiceFramework("IApplicationFunctions"), system{system_} { // clang-format off static const FunctionInfo functions[] = { {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, @@ -989,6 +1074,7 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF {120, nullptr, "ExecuteProgram"}, {121, nullptr, "ClearUserChannel"}, {122, nullptr, "UnpopToUserChannel"}, + {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"}, {500, nullptr, "StartContinuousRecordingFlushForDebug"}, {1000, nullptr, "CreateMovieMaker"}, {1001, nullptr, "PrepareForJit"}, @@ -996,6 +1082,10 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF // clang-format on RegisterHandlers(functions); + + auto& kernel = system.Kernel(); + gpu_error_detected_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::Manual, "IApplicationFunctions:GpuErrorDetectedSystemEvent"); } IApplicationFunctions::~IApplicationFunctions() = default; @@ -1070,13 +1160,21 @@ void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest( void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - u128 uid = rp.PopRaw<u128>(); // What does this do? - LOG_WARNING(Service, "(STUBBED) called uid = {:016X}{:016X}", uid[1], uid[0]); + u128 user_id = rp.PopRaw<u128>(); + + LOG_DEBUG(Service_AM, "called, uid={:016X}{:016X}", user_id[1], user_id[0]); + + FileSys::SaveDataDescriptor descriptor{}; + descriptor.title_id = Core::CurrentProcess()->GetTitleID(); + descriptor.user_id = user_id; + descriptor.type = FileSys::SaveDataType::SaveData; + const auto res = system.GetFileSystemController().CreateSaveData( + FileSys::SaveDataSpaceId::NandUser, descriptor); IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); + rb.Push(res.Code()); rb.Push<u64>(0); -} // namespace Service::AM +} void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) { // Takes an input u32 Result, no output. @@ -1107,7 +1205,7 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { // Get supported languages from NACP, if possible // Default to 0 (all languages supported) u32 supported_languages = 0; - FileSys::PatchManager pm{Core::System::GetInstance().CurrentProcess()->GetTitleID()}; + FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; const auto res = pm.GetControlMetadata(); if (res.first != nullptr) { @@ -1115,8 +1213,8 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { } // Call IApplicationManagerInterface implementation. - auto& service_manager = Core::System::GetInstance().ServiceManager(); - auto ns_am2 = service_manager.GetService<Service::NS::NS>("ns:am2"); + auto& service_manager = system.ServiceManager(); + auto ns_am2 = service_manager.GetService<NS::NS>("ns:am2"); auto app_man = ns_am2->GetApplicationManagerInterface(); // Get desired application language @@ -1188,8 +1286,8 @@ void IApplicationFunctions::ExtendSaveData(Kernel::HLERequestContext& ctx) { "new_journal={:016X}", static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size); - FileSystem::WriteSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id, - {new_normal_size, new_journal_size}); + system.GetFileSystemController().WriteSaveDataSize( + type, system.CurrentProcess()->GetTitleID(), user_id, {new_normal_size, new_journal_size}); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); @@ -1208,8 +1306,8 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type), user_id[1], user_id[0]); - const auto size = - FileSystem::ReadSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id); + const auto size = system.GetFileSystemController().ReadSaveDataSize( + type, system.CurrentProcess()->GetTitleID(), user_id); IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); @@ -1217,14 +1315,22 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) { rb.Push(size.journal); } +void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(gpu_error_detected_event.readable); +} + void InstallInterfaces(SM::ServiceManager& service_manager, - std::shared_ptr<NVFlinger::NVFlinger> nvflinger) { - auto message_queue = std::make_shared<AppletMessageQueue>(); - message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on - // game boot + std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) { + auto message_queue = std::make_shared<AppletMessageQueue>(system.Kernel()); + // Needed on game boot + message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); - 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); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 991b7d47c..a3baeb673 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -10,12 +10,15 @@ #include "core/hle/kernel/writable_event.h" #include "core/hle/service/service.h" -namespace Service { -namespace NVFlinger { +namespace Kernel { +class KernelCore; +} + +namespace Service::NVFlinger { class NVFlinger; } -namespace AM { +namespace Service::AM { enum SystemLanguage { Japanese = 0, @@ -42,12 +45,13 @@ class AppletMessageQueue { public: enum class AppletMessage : u32 { NoMessage = 0, + ExitRequested = 4, FocusStateChanged = 15, OperationModeChanged = 30, PerformanceModeChanged = 31, }; - AppletMessageQueue(); + explicit AppletMessageQueue(Kernel::KernelCore& kernel); ~AppletMessageQueue(); const Kernel::SharedPtr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const; @@ -56,6 +60,7 @@ public: AppletMessage PopMessage(); std::size_t GetMessageCount() const; void OperationModeChanged(); + void RequestExit(); private: std::queue<AppletMessage> messages; @@ -65,12 +70,14 @@ private: class IWindowController final : public ServiceFramework<IWindowController> { public: - IWindowController(); + explicit IWindowController(Core::System& system_); ~IWindowController() override; private: void GetAppletResourceUserId(Kernel::HLERequestContext& ctx); void AcquireForegroundRights(Kernel::HLERequestContext& ctx); + + Core::System& system; }; class IAudioController final : public ServiceFramework<IAudioController> { @@ -113,10 +120,12 @@ public: class ISelfController final : public ServiceFramework<ISelfController> { public: - explicit ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger); + explicit ISelfController(Core::System& system_, + std::shared_ptr<NVFlinger::NVFlinger> nvflinger_); ~ISelfController() override; private: + void Exit(Kernel::HLERequestContext& ctx); void LockExit(Kernel::HLERequestContext& ctx); void UnlockExit(Kernel::HLERequestContext& ctx); void EnterFatalSection(Kernel::HLERequestContext& ctx); @@ -133,16 +142,26 @@ private: void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); + void SetAutoSleepDisabled(Kernel::HLERequestContext& ctx); + void IsAutoSleepDisabled(Kernel::HLERequestContext& ctx); + void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx); + void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx); std::shared_ptr<NVFlinger::NVFlinger> nvflinger; Kernel::EventPair launchable_event; + Kernel::EventPair accumulated_suspended_tick_changed_event; + u32 idle_time_detection_extension = 0; u64 num_fatal_sections_entered = 0; + bool is_auto_sleep_disabled = false; + + Core::System& system; }; class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { public: - explicit ICommonStateGetter(std::shared_ptr<AppletMessageQueue> msg_queue); + explicit ICommonStateGetter(Core::System& system, + std::shared_ptr<AppletMessageQueue> msg_queue); ~ICommonStateGetter() override; private: @@ -164,7 +183,9 @@ private: void GetPerformanceMode(Kernel::HLERequestContext& ctx); void GetBootMode(Kernel::HLERequestContext& ctx); void GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx); + void SetCpuBoostMode(Kernel::HLERequestContext& ctx); + Core::System& system; std::shared_ptr<AppletMessageQueue> msg_queue; }; @@ -198,18 +219,20 @@ private: class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { public: - ILibraryAppletCreator(); + explicit ILibraryAppletCreator(Core::System& system_); ~ILibraryAppletCreator() override; private: void CreateLibraryApplet(Kernel::HLERequestContext& ctx); void CreateStorage(Kernel::HLERequestContext& ctx); void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); + + Core::System& system; }; class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { public: - IApplicationFunctions(); + explicit IApplicationFunctions(Core::System& system_); ~IApplicationFunctions() override; private: @@ -230,6 +253,10 @@ private: void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); void EndBlockingHomeButton(Kernel::HLERequestContext& ctx); void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); + void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); + + Kernel::EventPair gpu_error_detected_event; + Core::System& system; }; class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { @@ -261,7 +288,6 @@ 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 +} // namespace Service::AM diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index 488add8e7..e454b77d8 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -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"}, @@ -40,7 +42,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ICommonStateGetter>(msg_queue); + rb.PushIpcInterface<ICommonStateGetter>(system, msg_queue); } void GetSelfController(Kernel::HLERequestContext& ctx) { @@ -48,7 +50,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISelfController>(nvflinger); + rb.PushIpcInterface<ISelfController>(system, nvflinger); } void GetWindowController(Kernel::HLERequestContext& ctx) { @@ -56,7 +58,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IWindowController>(); + rb.PushIpcInterface<IWindowController>(system); } void GetAudioController(Kernel::HLERequestContext& ctx) { @@ -96,7 +98,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILibraryAppletCreator>(); + rb.PushIpcInterface<ILibraryAppletCreator>(system); } void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { @@ -104,19 +106,20 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationFunctions>(); + rb.PushIpcInterface<IApplicationFunctions>(system); } 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"}, @@ -143,7 +146,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ICommonStateGetter>(msg_queue); + rb.PushIpcInterface<ICommonStateGetter>(system, msg_queue); } void GetSelfController(Kernel::HLERequestContext& ctx) { @@ -151,7 +154,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISelfController>(nvflinger); + rb.PushIpcInterface<ISelfController>(system, nvflinger); } void GetWindowController(Kernel::HLERequestContext& ctx) { @@ -159,7 +162,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IWindowController>(); + rb.PushIpcInterface<IWindowController>(system); } void GetAudioController(Kernel::HLERequestContext& ctx) { @@ -191,7 +194,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILibraryAppletCreator>(); + rb.PushIpcInterface<ILibraryAppletCreator>(system); } 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"}, diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h index 902db2665..2e3e45915 100644 --- a/src/core/hle/service/am/applet_ae.h +++ b/src/core/hle/service/am/applet_ae.h @@ -9,16 +9,22 @@ #include "core/hle/service/service.h" namespace Service { +namespace FileSystem { +class FileSystemController; +} + namespace NVFlinger { class NVFlinger; } namespace AM { +class AppletMessageQueue; + 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 +36,7 @@ private: std::shared_ptr<NVFlinger::NVFlinger> nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; + Core::System& system; }; } // namespace AM diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp index d3a0a1568..a2ffaa440 100644 --- a/src/core/hle/service/am/applet_oe.cpp +++ b/src/core/hle/service/am/applet_oe.cpp @@ -13,9 +13,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"}, @@ -63,7 +63,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IWindowController>(); + rb.PushIpcInterface<IWindowController>(system); } void GetSelfController(Kernel::HLERequestContext& ctx) { @@ -71,7 +71,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISelfController>(nvflinger); + rb.PushIpcInterface<ISelfController>(system, nvflinger); } void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { @@ -79,7 +79,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ICommonStateGetter>(msg_queue); + rb.PushIpcInterface<ICommonStateGetter>(system, msg_queue); } void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { @@ -87,7 +87,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ILibraryAppletCreator>(); + rb.PushIpcInterface<ILibraryAppletCreator>(system); } void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { @@ -95,11 +95,12 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IApplicationFunctions>(); + rb.PushIpcInterface<IApplicationFunctions>(system); } std::shared_ptr<NVFlinger::NVFlinger> nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; + Core::System& system; }; void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) { @@ -107,13 +108,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"}, }; diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h index bbd0108ef..758da792d 100644 --- a/src/core/hle/service/am/applet_oe.h +++ b/src/core/hle/service/am/applet_oe.h @@ -9,16 +9,22 @@ #include "core/hle/service/service.h" namespace Service { +namespace FileSystem { +class FileSystemController; +} + namespace NVFlinger { class NVFlinger; } namespace AM { +class AppletMessageQueue; + 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 +34,7 @@ private: std::shared_ptr<NVFlinger::NVFlinger> nvflinger; std::shared_ptr<AppletMessageQueue> msg_queue; + Core::System& system; }; } // namespace AM diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index 14fa92318..d2e35362f 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -23,8 +23,7 @@ namespace Service::AM::Applets { -AppletDataBroker::AppletDataBroker() { - auto& kernel = Core::System::GetInstance().Kernel(); +AppletDataBroker::AppletDataBroker(Kernel::KernelCore& kernel) { state_changed_event = Kernel::WritableEvent::CreateEventPair( kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent"); pop_out_data_event = Kernel::WritableEvent::CreateEventPair( @@ -35,12 +34,28 @@ AppletDataBroker::AppletDataBroker() { AppletDataBroker::~AppletDataBroker() = default; +AppletDataBroker::RawChannelData AppletDataBroker::PeekDataToAppletForDebug() const { + std::vector<std::vector<u8>> out_normal; + + for (const auto& storage : in_channel) { + out_normal.push_back(storage->GetData()); + } + + std::vector<std::vector<u8>> out_interactive; + + for (const auto& storage : in_interactive_channel) { + out_interactive.push_back(storage->GetData()); + } + + return {std::move(out_normal), std::move(out_interactive)}; +} + std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() { if (out_channel.empty()) return nullptr; auto out = std::move(out_channel.front()); - out_channel.pop(); + out_channel.pop_front(); return out; } @@ -49,7 +64,7 @@ std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() { return nullptr; auto out = std::move(in_channel.front()); - in_channel.pop(); + in_channel.pop_front(); return out; } @@ -58,7 +73,7 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() { return nullptr; auto out = std::move(out_interactive_channel.front()); - out_interactive_channel.pop(); + out_interactive_channel.pop_front(); return out; } @@ -67,25 +82,25 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() { return nullptr; auto out = std::move(in_interactive_channel.front()); - in_interactive_channel.pop(); + in_interactive_channel.pop_front(); return out; } void AppletDataBroker::PushNormalDataFromGame(IStorage storage) { - in_channel.push(std::make_unique<IStorage>(storage)); + in_channel.push_back(std::make_unique<IStorage>(storage)); } void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) { - out_channel.push(std::make_unique<IStorage>(storage)); + out_channel.push_back(std::make_unique<IStorage>(storage)); pop_out_data_event.writable->Signal(); } void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) { - in_interactive_channel.push(std::make_unique<IStorage>(storage)); + in_interactive_channel.push_back(std::make_unique<IStorage>(storage)); } void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) { - out_interactive_channel.push(std::make_unique<IStorage>(storage)); + out_interactive_channel.push_back(std::make_unique<IStorage>(storage)); pop_interactive_out_data_event.writable->Signal(); } @@ -105,7 +120,7 @@ Kernel::SharedPtr<Kernel::ReadableEvent> AppletDataBroker::GetStateChangedEvent( return state_changed_event.readable; } -Applet::Applet() = default; +Applet::Applet(Kernel::KernelCore& kernel_) : broker{kernel_} {} Applet::~Applet() = default; @@ -123,12 +138,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; @@ -136,11 +153,13 @@ AppletFrontendSet::AppletFrontendSet(AppletFrontendSet&&) noexcept = default; AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default; -AppletManager::AppletManager() = default; +AppletManager::AppletManager(Core::System& system_) : system{system_} {} 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) @@ -151,17 +170,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>(); } @@ -182,6 +205,10 @@ 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() { @@ -190,21 +217,26 @@ void AppletManager::ClearAll() { std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const { switch (id) { + case AppletId::Auth: + return std::make_shared<Auth>(system, *frontend.parental_controls); case AppletId::Error: - return std::make_shared<Error>(*frontend.error); + return std::make_shared<Error>(system, *frontend.error); case AppletId::ProfileSelect: - return std::make_shared<ProfileSelect>(*frontend.profile_select); + return std::make_shared<ProfileSelect>(system, *frontend.profile_select); case AppletId::SoftwareKeyboard: - return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard); + return std::make_shared<SoftwareKeyboard>(system, *frontend.software_keyboard); case AppletId::PhotoViewer: - return std::make_shared<PhotoViewer>(*frontend.photo_viewer); + return std::make_shared<PhotoViewer>(system, *frontend.photo_viewer); + case AppletId::LibAppletShop: + return std::make_shared<WebBrowser>(system, *frontend.web_browser, + frontend.e_commerce.get()); case AppletId::LibAppletOff: - return std::make_shared<WebBrowser>(*frontend.web_browser); + return std::make_shared<WebBrowser>(system, *frontend.web_browser); default: UNIMPLEMENTED_MSG( "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", static_cast<u8>(id)); - return std::make_shared<StubApplet>(); + return std::make_shared<StubApplet>(system, id); } } diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index b46e10a4a..764c3418c 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -12,14 +12,24 @@ union ResultCode; +namespace Core { +class System; +} + namespace Core::Frontend { +class ECommerceApplet; class ErrorApplet; +class ParentalControlsApplet; class PhotoViewerApplet; class ProfileSelectApplet; class SoftwareKeyboardApplet; class WebBrowserApplet; } // namespace Core::Frontend +namespace Kernel { +class KernelCore; +} + namespace Service::AM { class IStorage; @@ -51,9 +61,17 @@ enum class AppletId : u32 { class AppletDataBroker final { public: - AppletDataBroker(); + explicit AppletDataBroker(Kernel::KernelCore& kernel_); ~AppletDataBroker(); + struct RawChannelData { + std::vector<std::vector<u8>> normal; + std::vector<std::vector<u8>> interactive; + }; + + // Retrieves but does not pop the data sent to applet. + RawChannelData PeekDataToAppletForDebug() const; + std::unique_ptr<IStorage> PopNormalDataToGame(); std::unique_ptr<IStorage> PopNormalDataToApplet(); @@ -76,16 +94,16 @@ private: // Queues are named from applet's perspective // PopNormalDataToApplet and PushNormalDataFromGame - std::queue<std::unique_ptr<IStorage>> in_channel; + std::deque<std::unique_ptr<IStorage>> in_channel; // PopNormalDataToGame and PushNormalDataFromApplet - std::queue<std::unique_ptr<IStorage>> out_channel; + std::deque<std::unique_ptr<IStorage>> out_channel; // PopInteractiveDataToApplet and PushInteractiveDataFromGame - std::queue<std::unique_ptr<IStorage>> in_interactive_channel; + std::deque<std::unique_ptr<IStorage>> in_interactive_channel; // PopInteractiveDataToGame and PushInteractiveDataFromApplet - std::queue<std::unique_ptr<IStorage>> out_interactive_channel; + std::deque<std::unique_ptr<IStorage>> out_interactive_channel; Kernel::EventPair state_changed_event; @@ -98,7 +116,7 @@ private: class Applet { public: - Applet(); + explicit Applet(Kernel::KernelCore& kernel_); virtual ~Applet(); virtual void Initialize(); @@ -137,15 +155,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; @@ -154,16 +176,18 @@ 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 { public: - AppletManager(); + explicit AppletManager(Core::System& system_); ~AppletManager(); void SetAppletFrontendSet(AppletFrontendSet set); @@ -175,6 +199,7 @@ public: private: AppletFrontendSet frontend; + Core::System& system; }; } // namespace Applets diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp index 04774bedc..a7db26725 100644 --- a/src/core/hle/service/am/applets/error.cpp +++ b/src/core/hle/service/am/applets/error.cpp @@ -9,8 +9,10 @@ #include "common/string_util.h" #include "core/core.h" #include "core/frontend/applets/error.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/error.h" +#include "core/reporter.h" namespace Service::AM::Applets { @@ -83,7 +85,8 @@ ResultCode Decode64BitError(u64 error) { } // Anonymous namespace -Error::Error(const Core::Frontend::ErrorApplet& frontend) : frontend(frontend) {} +Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} Error::~Error() = default; @@ -143,9 +146,12 @@ void Error::Execute() { } const auto callback = [this] { DisplayCompleted(); }; + const auto title_id = system.CurrentProcess()->GetTitleID(); + const auto& reporter{system.GetReporter()}; switch (mode) { case ErrorAppletMode::ShowError: + reporter.SaveErrorReport(title_id, error_code); frontend.ShowError(error_code, callback); break; case ErrorAppletMode::ShowSystemError: @@ -156,14 +162,18 @@ void Error::Execute() { const auto& detail_text = system ? args->system_error.detail_text : args->application_error.detail_text; - frontend.ShowCustomErrorText( - error_code, - Common::StringFromFixedZeroTerminatedBuffer(main_text.data(), main_text.size()), - Common::StringFromFixedZeroTerminatedBuffer(detail_text.data(), detail_text.size()), - callback); + const auto main_text_string = + Common::StringFromFixedZeroTerminatedBuffer(main_text.data(), main_text.size()); + const auto detail_text_string = + Common::StringFromFixedZeroTerminatedBuffer(detail_text.data(), detail_text.size()); + + reporter.SaveErrorReport(title_id, error_code, main_text_string, detail_text_string); + frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback); break; } case ErrorAppletMode::ShowErrorRecord: + reporter.SaveErrorReport(title_id, error_code, + fmt::format("{:016X}", args->error_record.posix_time)); frontend.ShowErrorWithTimestamp( error_code, std::chrono::seconds{args->error_record.posix_time}, callback); break; diff --git a/src/core/hle/service/am/applets/error.h b/src/core/hle/service/am/applets/error.h index a3590d181..a105cdb0c 100644 --- a/src/core/hle/service/am/applets/error.h +++ b/src/core/hle/service/am/applets/error.h @@ -7,6 +7,10 @@ #include "core/hle/result.h" #include "core/hle/service/am/applets/applets.h" +namespace Core { +class System; +} + namespace Service::AM::Applets { enum class ErrorAppletMode : u8 { @@ -21,7 +25,7 @@ enum class ErrorAppletMode : u8 { class Error final : public Applet { public: - explicit Error(const Core::Frontend::ErrorApplet& frontend); + explicit Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_); ~Error() override; void Initialize() override; @@ -42,6 +46,7 @@ private: std::unique_ptr<ErrorArguments> args; bool complete = false; + Core::System& system; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp index c591b9ac2..328438a1d 100644 --- a/src/core/hle/service/am/applets/general_backend.cpp +++ b/src/core/hle/service/am/applets/general_backend.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <string> +#include <string_view> #include "common/assert.h" #include "common/hex_util.h" @@ -13,28 +13,147 @@ #include "core/hle/result.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/general_backend.h" +#include "core/reporter.h" namespace Service::AM::Applets { -static void LogCurrentStorage(AppletDataBroker& broker, std::string prefix) { +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()) { const auto data = storage->GetData(); LOG_INFO(Service_AM, - "called (STUBBED), during {} recieved normal data with size={:08X}, data={}", - prefix, data.size(), Common::HexVectorToString(data)); + "called (STUBBED), during {} received normal data with size={:08X}, data={}", + prefix, data.size(), Common::HexToString(data)); } storage = broker.PopInteractiveDataToApplet(); for (; storage != nullptr; storage = broker.PopInteractiveDataToApplet()) { const auto data = storage->GetData(); LOG_INFO(Service_AM, - "called (STUBBED), during {} recieved interactive data with size={:08X}, data={}", - prefix, data.size(), Common::HexVectorToString(data)); + "called (STUBBED), during {} received interactive data with size={:08X}, data={}", + prefix, data.size(), Common::HexToString(data)); } } -PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {} +Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_) + : Applet{system_.Kernel()}, 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(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} PhotoViewer::~PhotoViewer() = default; @@ -68,7 +187,7 @@ void PhotoViewer::Execute() { const auto callback = [this] { ViewFinished(); }; switch (mode) { case PhotoViewerAppletMode::CurrentApp: - frontend.ShowPhotosForApplication(Core::CurrentProcess()->GetTitleID(), callback); + frontend.ShowPhotosForApplication(system.CurrentProcess()->GetTitleID(), callback); break; case PhotoViewerAppletMode::AllApps: frontend.ShowAllPhotos(callback); @@ -83,13 +202,21 @@ void PhotoViewer::ViewFinished() { broker.SignalStateChanged(); } -StubApplet::StubApplet() = default; +StubApplet::StubApplet(Core::System& system_, AppletId id_) + : Applet{system_.Kernel()}, id(id_), system{system_} {} StubApplet::~StubApplet() = default; void StubApplet::Initialize() { LOG_WARNING(Service_AM, "called (STUBBED)"); Applet::Initialize(); + + const auto data = broker.PeekDataToAppletForDebug(); + system.GetReporter().SaveUnimplementedAppletReport( + static_cast<u32>(id), common_args.arguments_version, common_args.library_version, + common_args.theme_color, common_args.play_startup_sound, common_args.system_tick, + data.normal, data.interactive); + LogCurrentStorage(broker, "Initialize"); } diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h index 2dd255d7c..cfa2df369 100644 --- a/src/core/hle/service/am/applets/general_backend.h +++ b/src/core/hle/service/am/applets/general_backend.h @@ -6,8 +6,42 @@ #include "core/hle/service/am/applets/applets.h" +namespace Core { +class System; +} + namespace Service::AM::Applets { +enum class AuthAppletType : u32 { + ShowParentalAuthentication, + RegisterParentalPasscode, + ChangeParentalPasscode, +}; + +class Auth final : public Applet { +public: + explicit Auth(Core::System& system_, 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, @@ -15,7 +49,7 @@ enum class PhotoViewerAppletMode : u8 { class PhotoViewer final : public Applet { public: - explicit PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend); + explicit PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_); ~PhotoViewer() override; void Initialize() override; @@ -30,11 +64,12 @@ private: const Core::Frontend::PhotoViewerApplet& frontend; bool complete = false; PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp; + Core::System& system; }; class StubApplet final : public Applet { public: - StubApplet(); + explicit StubApplet(Core::System& system_, AppletId id_); ~StubApplet() override; void Initialize() override; @@ -43,6 +78,10 @@ public: ResultCode GetStatus() const override; void ExecuteInteractive() override; void Execute() override; + +private: + AppletId id; + Core::System& system; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp index 57b5419e8..3eba696ca 100644 --- a/src/core/hle/service/am/applets/profile_select.cpp +++ b/src/core/hle/service/am/applets/profile_select.cpp @@ -15,8 +15,9 @@ namespace Service::AM::Applets { constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; -ProfileSelect::ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend) - : frontend(frontend) {} +ProfileSelect::ProfileSelect(Core::System& system_, + const Core::Frontend::ProfileSelectApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_) {} ProfileSelect::~ProfileSelect() = default; diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h index 563cd744a..16364ead7 100644 --- a/src/core/hle/service/am/applets/profile_select.h +++ b/src/core/hle/service/am/applets/profile_select.h @@ -11,6 +11,10 @@ #include "core/hle/result.h" #include "core/hle/service/am/applets/applets.h" +namespace Core { +class System; +} + namespace Service::AM::Applets { struct UserSelectionConfig { @@ -29,7 +33,8 @@ static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has inco class ProfileSelect final : public Applet { public: - explicit ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend); + explicit ProfileSelect(Core::System& system_, + const Core::Frontend::ProfileSelectApplet& frontend_); ~ProfileSelect() override; void Initialize() override; diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp index e197990f7..748559cd0 100644 --- a/src/core/hle/service/am/applets/software_keyboard.cpp +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -39,8 +39,9 @@ static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters( return params; } -SoftwareKeyboard::SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend) - : frontend(frontend) {} +SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, + const Core::Frontend::SoftwareKeyboardApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_) {} SoftwareKeyboard::~SoftwareKeyboard() = default; diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h index 0fbc43e51..ef4801fc6 100644 --- a/src/core/hle/service/am/applets/software_keyboard.h +++ b/src/core/hle/service/am/applets/software_keyboard.h @@ -16,6 +16,10 @@ union ResultCode; +namespace Core { +class System; +} + namespace Service::AM::Applets { enum class KeysetDisable : u32 { @@ -55,7 +59,8 @@ static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect siz class SoftwareKeyboard final : public Applet { public: - explicit SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend); + explicit SoftwareKeyboard(Core::System& system_, + const Core::Frontend::SoftwareKeyboardApplet& frontend_); ~SoftwareKeyboard() override; void Initialize() override; diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp index 7878f5136..32283e819 100644 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ b/src/core/hle/service/am/applets/web_browser.cpp @@ -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,188 @@ 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; +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, +}; + +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 { -struct WebBufferHeader { +constexpr std::size_t SHIM_KIND_COUNT = 0x8; + +struct WebArgHeader { u16 count; - INSERT_PADDING_BYTES(6); + INSERT_PADDING_BYTES(2); + ShimKind kind; }; -static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size."); +static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size."); -struct WebArgumentHeader { - u16 type; +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 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)); - - 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); - - 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()); +static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); + +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."); + +struct WebWifiReturnValue { + INSERT_PADDING_BYTES(4); + u32 result; +}; +static_assert(sizeof(WebWifiReturnValue) == 0x8, "WebWifiReturnValue has incorrect 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; + + WebArgTLV tlv{}; + std::memcpy(&tlv, arg.data() + offset, sizeof(WebArgTLV)); + offset += sizeof(WebArgTLV); + + offset += tlv.offset; + if (arg.size() < (offset + tlv.size)) return out; - } - offset += arg.offset + arg.size; + 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 GetApplicationRomFS(const Core::System& system, u64 title_id, + FileSys::ContentRecordType type) { + const auto& installed{system.GetContentProvider()}; + const auto res = installed.GetEntry(title_id, type); - FileSys::VirtualFile out; - if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success) - return out; + if (res != nullptr) { + return res->GetRomFS(); + } - const auto& installed{Core::System::GetInstance().GetContentProvider()}; - const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(), - FileSys::ContentRecordType::Manual); + if (type == FileSys::ContentRecordType::Data) { + return FileSys::SystemArchive::SynthesizeSystemArchive(title_id); + } - if (res != nullptr) - return res->GetRomFS(); return nullptr; } -WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend) : frontend(frontend) {} +} // Anonymous namespace + +WebBrowser::WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_, + Core::Frontend::ECommerceApplet* frontend_e_commerce_) + : Applet{system_.Kernel()}, frontend(frontend_), + frontend_e_commerce(frontend_e_commerce_), system{system_} {} WebBrowser::~WebBrowser() = default; @@ -111,24 +227,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); - - manual_romfs = GetManualRomFS(); - if (manual_romfs == nullptr) { - status = ResultCode(-1); - LOG_ERROR(Service_AM, "Failed to find manual for current process!"); - } + args = GetWebArguments(web_arg); - filename = - FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename, - FileUtil::DirectorySeparator::PlatformDefault); + InitializeInternal(); } bool WebBrowser::TransactionComplete() const { @@ -144,25 +248,26 @@ 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); - const auto& vfs{Core::System::GetInstance().GetFilesystem()}; + FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); + const auto& vfs{system.GetFilesystem()}; const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite); FileSys::VfsRawCopyD(dir, temp_dir); @@ -172,17 +277,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 = system.CurrentProcess()->GetTitleID(); + } + + offline_romfs = GetApplicationRomFS(system, 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 diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h index 7e0f34c7d..8d4027411 100644 --- a/src/core/hle/service/am/applets/web_browser.h +++ b/src/core/hle/service/am/applets/web_browser.h @@ -4,15 +4,26 @@ #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 Core { +class System; +} + 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::System& system_, Core::Frontend::WebBrowserApplet& frontend_, + Core::Frontend::ECommerceApplet* frontend_e_commerce_ = nullptr); + ~WebBrowser() override; void Initialize() override; @@ -32,15 +43,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; + 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; + + Core::System& system; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index d3e97776b..e9cf1e840 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -29,9 +29,9 @@ static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { return (title_id & DLC_BASE_TITLE_ID_MASK) == base; } -static std::vector<u64> AccumulateAOCTitleIDs() { +static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) { std::vector<u64> add_on_content; - const auto& rcu = Core::System::GetInstance().GetContentProvider(); + const auto& rcu = system.GetContentProvider(); const auto list = rcu.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); std::transform(list.begin(), list.end(), std::back_inserter(add_on_content), @@ -47,7 +47,8 @@ static std::vector<u64> AccumulateAOCTitleIDs() { return add_on_content; } -AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs()) { +AOC_U::AOC_U(Core::System& system) + : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs(system)), system(system) { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CountAddOnContentByApplicationId"}, @@ -65,7 +66,7 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system.Kernel(); aoc_change_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, "GetAddOnContentListChanged:Event"); } @@ -86,7 +87,7 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + const auto current = system.CurrentProcess()->GetTitleID(); const auto& disabled = Settings::values.disabled_addons[current]; if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) { @@ -113,7 +114,7 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count, process_id); - const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + const auto current = system.CurrentProcess()->GetTitleID(); std::vector<u32> out; const auto& disabled = Settings::values.disabled_addons[current]; @@ -159,7 +160,7 @@ void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - const auto title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + const auto title_id = system.CurrentProcess()->GetTitleID(); FileSys::PatchManager pm{title_id}; const auto res = pm.GetControlMetadata(); @@ -196,8 +197,8 @@ void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) { rb.PushCopyObjects(aoc_change_event.readable); } -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<AOC_U>()->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared<AOC_U>(system)->InstallAsService(service_manager); } } // namespace Service::AOC diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h index 5effea730..848b2f416 100644 --- a/src/core/hle/service/aoc/aoc_u.h +++ b/src/core/hle/service/aoc/aoc_u.h @@ -14,7 +14,7 @@ namespace Service::AOC { class AOC_U final : public ServiceFramework<AOC_U> { public: - AOC_U(); + explicit AOC_U(Core::System& system); ~AOC_U() override; private: @@ -26,9 +26,10 @@ private: std::vector<u64> add_on_content; Kernel::EventPair aoc_change_event; + Core::System& system; }; /// Registers all AOC services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::AOC diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp index f3c09bbb1..85bbf5988 100644 --- a/src/core/hle/service/apm/apm.cpp +++ b/src/core/hle/service/apm/apm.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/log.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/apm/apm.h" #include "core/hle/service/apm/interface.h" @@ -12,11 +11,15 @@ namespace Service::APM { Module::Module() = default; Module::~Module() = default; -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(Core::System& system) { auto module_ = std::make_shared<Module>(); - std::make_shared<APM>(module_, "apm")->InstallAsService(service_manager); - std::make_shared<APM>(module_, "apm:p")->InstallAsService(service_manager); - std::make_shared<APM_Sys>()->InstallAsService(service_manager); + std::make_shared<APM>(module_, system.GetAPMController(), "apm") + ->InstallAsService(system.ServiceManager()); + std::make_shared<APM>(module_, system.GetAPMController(), "apm:p") + ->InstallAsService(system.ServiceManager()); + std::make_shared<APM>(module_, system.GetAPMController(), "apm:am") + ->InstallAsService(system.ServiceManager()); + std::make_shared<APM_Sys>(system.GetAPMController())->InstallAsService(system.ServiceManager()); } } // namespace Service::APM diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h index 4d7d5bb7c..cf4c2bb11 100644 --- a/src/core/hle/service/apm/apm.h +++ b/src/core/hle/service/apm/apm.h @@ -8,11 +8,6 @@ namespace Service::APM { -enum class PerformanceMode : u8 { - Handheld = 0, - Docked = 1, -}; - class Module final { public: Module(); @@ -20,6 +15,6 @@ public: }; /// Registers all AM services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(Core::System& system); } // namespace Service::APM diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp new file mode 100644 index 000000000..4376612eb --- /dev/null +++ b/src/core/hle/service/apm/controller.cpp @@ -0,0 +1,68 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/core_timing.h" +#include "core/hle/service/apm/controller.h" +#include "core/settings.h" + +namespace Service::APM { + +constexpr PerformanceConfiguration DEFAULT_PERFORMANCE_CONFIGURATION = + PerformanceConfiguration::Config7; + +Controller::Controller(Core::Timing::CoreTiming& core_timing) + : core_timing(core_timing), configs{ + {PerformanceMode::Handheld, DEFAULT_PERFORMANCE_CONFIGURATION}, + {PerformanceMode::Docked, DEFAULT_PERFORMANCE_CONFIGURATION}, + } {} + +Controller::~Controller() = default; + +void Controller::SetPerformanceConfiguration(PerformanceMode mode, + PerformanceConfiguration config) { + static const std::map<PerformanceConfiguration, u32> PCONFIG_TO_SPEED_MAP{ + {PerformanceConfiguration::Config1, 1020}, {PerformanceConfiguration::Config2, 1020}, + {PerformanceConfiguration::Config3, 1224}, {PerformanceConfiguration::Config4, 1020}, + {PerformanceConfiguration::Config5, 1020}, {PerformanceConfiguration::Config6, 1224}, + {PerformanceConfiguration::Config7, 1020}, {PerformanceConfiguration::Config8, 1020}, + {PerformanceConfiguration::Config9, 1020}, {PerformanceConfiguration::Config10, 1020}, + {PerformanceConfiguration::Config11, 1020}, {PerformanceConfiguration::Config12, 1020}, + {PerformanceConfiguration::Config13, 1785}, {PerformanceConfiguration::Config14, 1785}, + {PerformanceConfiguration::Config15, 1020}, {PerformanceConfiguration::Config16, 1020}, + }; + + SetClockSpeed(PCONFIG_TO_SPEED_MAP.find(config)->second); + configs.insert_or_assign(mode, config); +} + +void Controller::SetFromCpuBoostMode(CpuBoostMode mode) { + constexpr std::array<PerformanceConfiguration, 3> BOOST_MODE_TO_CONFIG_MAP{{ + PerformanceConfiguration::Config7, + PerformanceConfiguration::Config13, + PerformanceConfiguration::Config15, + }}; + + SetPerformanceConfiguration(PerformanceMode::Docked, + BOOST_MODE_TO_CONFIG_MAP.at(static_cast<u32>(mode))); +} + +PerformanceMode Controller::GetCurrentPerformanceMode() { + return Settings::values.use_docked_mode ? PerformanceMode::Docked : PerformanceMode::Handheld; +} + +PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { + if (configs.find(mode) == configs.end()) { + configs.insert_or_assign(mode, DEFAULT_PERFORMANCE_CONFIGURATION); + } + + return configs[mode]; +} + +void Controller::SetClockSpeed(u32 mhz) { + LOG_INFO(Service_APM, "called, mhz={:08X}", mhz); + // TODO(DarkLordZach): Actually signal core_timing to change clock speed. +} + +} // namespace Service::APM diff --git a/src/core/hle/service/apm/controller.h b/src/core/hle/service/apm/controller.h new file mode 100644 index 000000000..8ac80eaea --- /dev/null +++ b/src/core/hle/service/apm/controller.h @@ -0,0 +1,70 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <map> +#include "common/common_types.h" + +namespace Core::Timing { +class CoreTiming; +} + +namespace Service::APM { + +enum class PerformanceConfiguration : u32 { + Config1 = 0x00010000, + Config2 = 0x00010001, + Config3 = 0x00010002, + Config4 = 0x00020000, + Config5 = 0x00020001, + Config6 = 0x00020002, + Config7 = 0x00020003, + Config8 = 0x00020004, + Config9 = 0x00020005, + Config10 = 0x00020006, + Config11 = 0x92220007, + Config12 = 0x92220008, + Config13 = 0x92220009, + Config14 = 0x9222000A, + Config15 = 0x9222000B, + Config16 = 0x9222000C, +}; + +enum class CpuBoostMode : u32 { + Disabled = 0, + Full = 1, // CPU + GPU -> Config 13, 14, 15, or 16 + Partial = 2, // GPU Only -> Config 15 or 16 +}; + +enum class PerformanceMode : u8 { + Handheld = 0, + Docked = 1, +}; + +// Class to manage the state and change of the emulated system performance. +// Specifically, this deals with PerformanceMode, which corresponds to the system being docked or +// undocked, and PerformanceConfig which specifies the exact CPU, GPU, and Memory clocks to operate +// at. Additionally, this manages 'Boost Mode', which allows games to temporarily overclock the +// system during times of high load -- this simply maps to different PerformanceConfigs to use. +class Controller { +public: + Controller(Core::Timing::CoreTiming& core_timing); + ~Controller(); + + void SetPerformanceConfiguration(PerformanceMode mode, PerformanceConfiguration config); + void SetFromCpuBoostMode(CpuBoostMode mode); + + PerformanceMode GetCurrentPerformanceMode(); + PerformanceConfiguration GetCurrentPerformanceConfiguration(PerformanceMode mode); + +private: + void SetClockSpeed(u32 mhz); + + std::map<PerformanceMode, PerformanceConfiguration> configs; + + Core::Timing::CoreTiming& core_timing; +}; + +} // namespace Service::APM diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp index d058c0245..06f0f8edd 100644 --- a/src/core/hle/service/apm/interface.cpp +++ b/src/core/hle/service/apm/interface.cpp @@ -5,43 +5,32 @@ #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/apm/apm.h" +#include "core/hle/service/apm/controller.h" #include "core/hle/service/apm/interface.h" namespace Service::APM { class ISession final : public ServiceFramework<ISession> { public: - ISession() : ServiceFramework("ISession") { + ISession(Controller& controller) : ServiceFramework("ISession"), controller(controller) { static const FunctionInfo functions[] = { {0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"}, {1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"}, + {2, nullptr, "SetCpuOverclockEnabled"}, }; RegisterHandlers(functions); } private: - enum class PerformanceConfiguration : u32 { - Config1 = 0x00010000, - Config2 = 0x00010001, - Config3 = 0x00010002, - Config4 = 0x00020000, - Config5 = 0x00020001, - Config6 = 0x00020002, - Config7 = 0x00020003, - Config8 = 0x00020004, - Config9 = 0x00020005, - Config10 = 0x00020006, - Config11 = 0x92220007, - Config12 = 0x92220008, - }; - void SetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - auto mode = static_cast<PerformanceMode>(rp.Pop<u32>()); - u32 config = rp.Pop<u32>(); - LOG_WARNING(Service_APM, "(STUBBED) called mode={} config={}", static_cast<u32>(mode), - config); + const auto mode = rp.PopEnum<PerformanceMode>(); + const auto config = rp.PopEnum<PerformanceConfiguration>(); + LOG_DEBUG(Service_APM, "called mode={} config={}", static_cast<u32>(mode), + static_cast<u32>(config)); + + controller.SetPerformanceConfiguration(mode, config); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -50,20 +39,23 @@ private: void GetPerformanceConfiguration(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - auto mode = static_cast<PerformanceMode>(rp.Pop<u32>()); - LOG_WARNING(Service_APM, "(STUBBED) called mode={}", static_cast<u32>(mode)); + const auto mode = rp.PopEnum<PerformanceMode>(); + LOG_DEBUG(Service_APM, "called mode={}", static_cast<u32>(mode)); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(static_cast<u32>(PerformanceConfiguration::Config1)); + rb.PushEnum(controller.GetCurrentPerformanceConfiguration(mode)); } + + Controller& controller; }; -APM::APM(std::shared_ptr<Module> apm, const char* name) - : ServiceFramework(name), apm(std::move(apm)) { +APM::APM(std::shared_ptr<Module> apm, Controller& controller, const char* name) + : ServiceFramework(name), apm(std::move(apm)), controller(controller) { static const FunctionInfo functions[] = { {0, &APM::OpenSession, "OpenSession"}, - {1, nullptr, "GetPerformanceMode"}, + {1, &APM::GetPerformanceMode, "GetPerformanceMode"}, + {6, nullptr, "IsCpuOverclockEnabled"}, }; RegisterHandlers(functions); } @@ -75,10 +67,17 @@ void APM::OpenSession(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISession>(); + rb.PushIpcInterface<ISession>(controller); +} + +void APM::GetPerformanceMode(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_APM, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.PushEnum(controller.GetCurrentPerformanceMode()); } -APM_Sys::APM_Sys() : ServiceFramework{"apm:sys"} { +APM_Sys::APM_Sys(Controller& controller) : ServiceFramework{"apm:sys"}, controller(controller) { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestPerformanceMode"}, @@ -87,8 +86,8 @@ APM_Sys::APM_Sys() : ServiceFramework{"apm:sys"} { {3, nullptr, "GetLastThrottlingState"}, {4, nullptr, "ClearLastThrottlingState"}, {5, nullptr, "LoadAndApplySettings"}, - {6, nullptr, "SetCpuBoostMode"}, - {7, nullptr, "GetCurrentPerformanceConfiguration"}, + {6, &APM_Sys::SetCpuBoostMode, "SetCpuBoostMode"}, + {7, &APM_Sys::GetCurrentPerformanceConfiguration, "GetCurrentPerformanceConfiguration"}, }; // clang-format on @@ -102,7 +101,28 @@ void APM_Sys::GetPerformanceEvent(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISession>(); + rb.PushIpcInterface<ISession>(controller); +} + +void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto mode = rp.PopEnum<CpuBoostMode>(); + + LOG_DEBUG(Service_APM, "called, mode={:08X}", static_cast<u32>(mode)); + + controller.SetFromCpuBoostMode(mode); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void APM_Sys::GetCurrentPerformanceConfiguration(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_APM, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum( + controller.GetCurrentPerformanceConfiguration(controller.GetCurrentPerformanceMode())); } } // namespace Service::APM diff --git a/src/core/hle/service/apm/interface.h b/src/core/hle/service/apm/interface.h index 773541aa4..de1b89437 100644 --- a/src/core/hle/service/apm/interface.h +++ b/src/core/hle/service/apm/interface.h @@ -8,24 +8,34 @@ namespace Service::APM { +class Controller; +class Module; + class APM final : public ServiceFramework<APM> { public: - explicit APM(std::shared_ptr<Module> apm, const char* name); + explicit APM(std::shared_ptr<Module> apm, Controller& controller, const char* name); ~APM() override; private: void OpenSession(Kernel::HLERequestContext& ctx); + void GetPerformanceMode(Kernel::HLERequestContext& ctx); std::shared_ptr<Module> apm; + Controller& controller; }; class APM_Sys final : public ServiceFramework<APM_Sys> { public: - explicit APM_Sys(); + explicit APM_Sys(Controller& controller); ~APM_Sys() override; + void SetCpuBoostMode(Kernel::HLERequestContext& ctx); + private: void GetPerformanceEvent(Kernel::HLERequestContext& ctx); + void GetCurrentPerformanceConfiguration(Kernel::HLERequestContext& ctx); + + Controller& controller; }; } // namespace Service::APM diff --git a/src/core/hle/service/arp/arp.cpp b/src/core/hle/service/arp/arp.cpp deleted file mode 100644 index e675b0188..000000000 --- a/src/core/hle/service/arp/arp.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <memory> - -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/service/arp/arp.h" -#include "core/hle/service/service.h" -#include "core/hle/service/sm/sm.h" - -namespace Service::ARP { - -class ARP_R final : public ServiceFramework<ARP_R> { -public: - explicit ARP_R() : ServiceFramework{"arp:r"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "GetApplicationLaunchProperty"}, - {1, nullptr, "GetApplicationLaunchPropertyWithApplicationId"}, - {2, nullptr, "GetApplicationControlProperty"}, - {3, nullptr, "GetApplicationControlPropertyWithApplicationId"}, - }; - // clang-format on - - RegisterHandlers(functions); - } -}; - -class IRegistrar final : public ServiceFramework<IRegistrar> { -public: - explicit IRegistrar() : ServiceFramework{"IRegistrar"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "Issue"}, - {1, nullptr, "SetApplicationLaunchProperty"}, - {2, nullptr, "SetApplicationControlProperty"}, - }; - // clang-format on - - RegisterHandlers(functions); - } -}; - -class ARP_W final : public ServiceFramework<ARP_W> { -public: - explicit ARP_W() : ServiceFramework{"arp:w"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"}, - {1, nullptr, "DeleteProperties"}, - }; - // clang-format on - - RegisterHandlers(functions); - } - -private: - void AcquireRegistrar(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_ARP, "called"); - - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IRegistrar>(); - } -}; - -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<ARP_R>()->InstallAsService(sm); - std::make_shared<ARP_W>()->InstallAsService(sm); -} - -} // namespace Service::ARP diff --git a/src/core/hle/service/arp/arp.h b/src/core/hle/service/arp/arp.h deleted file mode 100644 index 9d100187c..000000000 --- a/src/core/hle/service/arp/arp.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -namespace Service::SM { -class ServiceManager; -} - -namespace Service::ARP { - -/// Registers all ARP services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); - -} // namespace Service::ARP diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index 128df7db5..1781bec83 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -19,16 +19,16 @@ namespace Service::Audio { -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { std::make_shared<AudCtl>()->InstallAsService(service_manager); std::make_shared<AudOutA>()->InstallAsService(service_manager); - std::make_shared<AudOutU>()->InstallAsService(service_manager); + std::make_shared<AudOutU>(system)->InstallAsService(service_manager); std::make_shared<AudInA>()->InstallAsService(service_manager); std::make_shared<AudInU>()->InstallAsService(service_manager); std::make_shared<AudRecA>()->InstallAsService(service_manager); std::make_shared<AudRecU>()->InstallAsService(service_manager); std::make_shared<AudRenA>()->InstallAsService(service_manager); - std::make_shared<AudRenU>()->InstallAsService(service_manager); + std::make_shared<AudRenU>(system)->InstallAsService(service_manager); std::make_shared<CodecCtl>()->InstallAsService(service_manager); std::make_shared<HwOpus>()->InstallAsService(service_manager); diff --git a/src/core/hle/service/audio/audio.h b/src/core/hle/service/audio/audio.h index f5bd3bf5f..b6d13912e 100644 --- a/src/core/hle/service/audio/audio.h +++ b/src/core/hle/service/audio/audio.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -11,6 +15,6 @@ class ServiceManager; namespace Service::Audio { /// Registers all Audio services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 6ba41b20a..fb84a8f13 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -40,8 +40,8 @@ enum class AudioState : u32 { class IAudioOut final : public ServiceFramework<IAudioOut> { public: - IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core, std::string&& device_name, - std::string&& unique_name) + IAudioOut(Core::System& system, AudoutParams audio_params, AudioCore::AudioOut& audio_core, + std::string&& device_name, std::string&& unique_name) : ServiceFramework("IAudioOut"), audio_core(audio_core), device_name(std::move(device_name)), audio_params(audio_params) { // clang-format off @@ -58,14 +58,13 @@ public: {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"}, {10, nullptr, "GetAudioOutPlayedSampleCount"}, {11, nullptr, "FlushAudioOutBuffers"}, - {12, nullptr, "SetAudioOutVolume"}, - {13, nullptr, "GetAudioOutVolume"}, + {12, &IAudioOut::SetAudioOutVolume, "SetAudioOutVolume"}, + {13, &IAudioOut::GetAudioOutVolume, "GetAudioOutVolume"}, }; // clang-format on RegisterHandlers(functions); // This is the event handle used to check if the audio buffer was released - auto& system = Core::System::GetInstance(); buffer_event = Kernel::WritableEvent::CreateEventPair( system.Kernel(), Kernel::ResetType::Manual, "IAudioOutBufferReleased"); @@ -183,6 +182,25 @@ private: rb.Push(static_cast<u32>(stream->GetQueueSize())); } + void SetAudioOutVolume(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const float volume = rp.Pop<float>(); + LOG_DEBUG(Service_Audio, "called, volume={}", volume); + + stream->SetVolume(volume); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetAudioOutVolume(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(stream->GetVolume()); + } + AudioCore::AudioOut& audio_core; AudioCore::StreamPtr stream; std::string device_name; @@ -193,6 +211,22 @@ private: Kernel::EventPair buffer_event; }; +AudOutU::AudOutU(Core::System& system_) : ServiceFramework("audout:u"), system{system_} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"}, + {1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"}, + {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"}, + {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}, + }; + // clang-format on + + RegisterHandlers(functions); + audio_core = std::make_unique<AudioCore::AudioOut>(); +} + +AudOutU::~AudOutU() = default; + void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); @@ -229,7 +263,7 @@ void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { std::string unique_name{fmt::format("{}-{}", device_name, audio_out_interfaces.size())}; auto audio_out_interface = std::make_shared<IAudioOut>( - params, *audio_core, std::move(device_name), std::move(unique_name)); + system, params, *audio_core, std::move(device_name), std::move(unique_name)); IPC::ResponseBuilder rb{ctx, 6, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -237,20 +271,9 @@ void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { rb.Push<u32>(params.channel_count); rb.Push<u32>(static_cast<u32>(AudioCore::Codec::PcmFormat::Int16)); rb.Push<u32>(static_cast<u32>(AudioState::Stopped)); - rb.PushIpcInterface<Audio::IAudioOut>(audio_out_interface); + rb.PushIpcInterface<IAudioOut>(audio_out_interface); audio_out_interfaces.push_back(std::move(audio_out_interface)); } -AudOutU::AudOutU() : ServiceFramework("audout:u") { - static const FunctionInfo functions[] = {{0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"}, - {1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"}, - {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"}, - {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}}; - RegisterHandlers(functions); - audio_core = std::make_unique<AudioCore::AudioOut>(); -} - -AudOutU::~AudOutU() = default; - } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index aed4c43b2..c9f532ccd 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h @@ -11,6 +11,10 @@ namespace AudioCore { class AudioOut; } +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -21,15 +25,17 @@ class IAudioOut; class AudOutU final : public ServiceFramework<AudOutU> { public: - AudOutU(); + explicit AudOutU(Core::System& system_); ~AudOutU() override; private: + void ListAudioOutsImpl(Kernel::HLERequestContext& ctx); + void OpenAudioOutImpl(Kernel::HLERequestContext& ctx); + std::vector<std::shared_ptr<IAudioOut>> audio_out_interfaces; std::unique_ptr<AudioCore::AudioOut> audio_core; - void ListAudioOutsImpl(Kernel::HLERequestContext& ctx); - void OpenAudioOutImpl(Kernel::HLERequestContext& ctx); + Core::System& system; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 75db0c2dc..f162249ed 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -5,6 +5,7 @@ #include <algorithm> #include <array> #include <memory> +#include <string_view> #include "audio_core/audio_renderer.h" #include "common/alignment.h" @@ -25,7 +26,8 @@ namespace Service::Audio { class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { public: - explicit IAudioRenderer(AudioCore::AudioRendererParameter audren_params) + explicit IAudioRenderer(Core::System& system, AudioCore::AudioRendererParameter audren_params, + const std::size_t instance_number) : ServiceFramework("IAudioRenderer") { // clang-format off static const FunctionInfo functions[] = { @@ -45,11 +47,10 @@ public: // clang-format on RegisterHandlers(functions); - auto& system = Core::System::GetInstance(); system_event = Kernel::WritableEvent::CreateEventPair( system.Kernel(), Kernel::ResetType::Manual, "IAudioRenderer:SystemEvent"); - renderer = std::make_unique<AudioCore::AudioRenderer>(system.CoreTiming(), audren_params, - system_event.writable); + renderer = std::make_unique<AudioCore::AudioRenderer>( + system.CoreTiming(), audren_params, system_event.writable, instance_number); } private: @@ -159,40 +160,81 @@ private: class IAudioDevice final : public ServiceFramework<IAudioDevice> { public: - IAudioDevice() : ServiceFramework("IAudioDevice") { + explicit IAudioDevice(Core::System& system, u32_le revision_num) + : ServiceFramework("IAudioDevice"), revision{revision_num} { static const FunctionInfo functions[] = { {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, - {2, nullptr, "GetAudioDeviceOutputVolume"}, + {2, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolume"}, {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"}, + {8, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolumeAuto"}, {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, - {11, nullptr, "QueryAudioDeviceInputEvent"}, - {12, nullptr, "QueryAudioDeviceOutputEvent"}, + {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"}, + {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, {13, nullptr, "GetAudioSystemMasterVolumeSetting"}, }; RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system.Kernel(); buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, "IAudioOutBufferReleasedEvent"); + + // Should be similar to audio_output_device_switch_event + audio_input_device_switch_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::Automatic, "IAudioDevice:AudioInputDeviceSwitchedEvent"); + + // 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: + using AudioDeviceName = std::array<char, 256>; + static constexpr std::array<std::string_view, 4> audio_device_names{{ + "AudioStereoJackOutput", + "AudioBuiltInSpeakerOutput", + "AudioTvOutput", + "AudioUsbDeviceOutput", + }}; + enum class DeviceType { + AHUBHeadphones, + AHUBSpeakers, + HDA, + USBOutput, + }; + void ListAudioDeviceName(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called"); + + const bool usb_output_supported = + IsFeatureSupported(AudioFeatures::AudioUSBDeviceOutput, revision); + const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioDeviceName); - constexpr std::array<char, 15> audio_interface{{"AudioInterface"}}; - ctx.WriteBuffer(audio_interface); + std::vector<AudioDeviceName> name_buffer; + name_buffer.reserve(audio_device_names.size()); + + for (std::size_t i = 0; i < count && i < audio_device_names.size(); i++) { + const auto type = static_cast<DeviceType>(i); + + if (!usb_output_supported && type == DeviceType::USBOutput) { + continue; + } + + const auto& device_name = audio_device_names[i]; + auto& entry = name_buffer.emplace_back(); + device_name.copy(entry.data(), device_name.size()); + } + + ctx.WriteBuffer(name_buffer); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(1); + rb.Push(static_cast<u32>(name_buffer.size())); } void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { @@ -208,15 +250,32 @@ private: rb.Push(RESULT_SUCCESS); } + void GetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto device_name_buffer = ctx.ReadBuffer(); + const std::string name = Common::StringFromBuffer(device_name_buffer); + + LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(1.0f); + } + void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Audio, "(STUBBED) called"); - constexpr std::array<char, 12> audio_interface{{"AudioDevice"}}; - ctx.WriteBuffer(audio_interface); + // Currently set to always be TV audio output. + const auto& device_name = audio_device_names[2]; - IPC::ResponseBuilder rb{ctx, 3}; + AudioDeviceName out_device_name{}; + device_name.copy(out_device_name.data(), device_name.size()); + + ctx.WriteBuffer(out_device_name); + + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(1); } void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) { @@ -237,11 +296,31 @@ private: rb.Push<u32>(1); } + // Should be similar to QueryAudioDeviceOutputEvent + void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_Audio, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(audio_input_device_switch_event.readable); + } + + 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); + } + + u32_le revision = 0; Kernel::EventPair buffer_event; + Kernel::EventPair audio_input_device_switch_event; + Kernel::EventPair audio_output_device_switch_event; }; // namespace Audio -AudRenU::AudRenU() : ServiceFramework("audren:u") { +AudRenU::AudRenU(Core::System& system_) : ServiceFramework("audren:u"), system{system_} { // clang-format off static const FunctionInfo functions[] = { {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, @@ -314,7 +393,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the portion of the size related to the mix data (and the sorting thereof). - const auto calculate_mix_info_size = [this](const AudioCore::AudioRendererParameter& params) { + const auto calculate_mix_info_size = [](const AudioCore::AudioRendererParameter& params) { // The size of the mixing info data structure. constexpr u64 mix_info_size = 0x940; @@ -386,7 +465,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { // Calculates the part of the size related to the splitter context. const auto calculate_splitter_context_size = - [this](const AudioCore::AudioRendererParameter& params) -> u64 { + [](const AudioCore::AudioRendererParameter& params) -> u64 { if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { return 0; } @@ -433,7 +512,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size related to performance statistics. - const auto calculate_perf_size = [this](const AudioCore::AudioRendererParameter& params) { + const auto calculate_perf_size = [](const AudioCore::AudioRendererParameter& params) { // Extra size value appended to the end of the calculation. constexpr u64 appended = 128; @@ -460,78 +539,76 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { }; // Calculates the part of the size that relates to the audio command buffer. - const auto calculate_command_buffer_size = - [this](const AudioCore::AudioRendererParameter& params) { - constexpr u64 alignment = (buffer_alignment_size - 1) * 2; + const auto calculate_command_buffer_size = [](const AudioCore::AudioRendererParameter& params) { + constexpr u64 alignment = (buffer_alignment_size - 1) * 2; - if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) { - constexpr u64 command_buffer_size = 0x18000; + if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) { + constexpr u64 command_buffer_size = 0x18000; - return command_buffer_size + alignment; - } + return command_buffer_size + alignment; + } - // When the variadic command buffer is supported, this means - // the command generator for the audio renderer can issue commands - // that are (as one would expect), variable in size. So what we need to do - // is determine the maximum possible size for a few command data structures - // then multiply them by the amount of present commands indicated by the given - // respective audio parameters. + // When the variadic command buffer is supported, this means + // the command generator for the audio renderer can issue commands + // that are (as one would expect), variable in size. So what we need to do + // is determine the maximum possible size for a few command data structures + // then multiply them by the amount of present commands indicated by the given + // respective audio parameters. - constexpr u64 max_biquad_filters = 2; - constexpr u64 max_mix_buffers = 24; + constexpr u64 max_biquad_filters = 2; + constexpr u64 max_mix_buffers = 24; - constexpr u64 biquad_filter_command_size = 0x2C; + constexpr u64 biquad_filter_command_size = 0x2C; - constexpr u64 depop_mix_command_size = 0x24; - constexpr u64 depop_setup_command_size = 0x50; + constexpr u64 depop_mix_command_size = 0x24; + constexpr u64 depop_setup_command_size = 0x50; - constexpr u64 effect_command_max_size = 0x540; + constexpr u64 effect_command_max_size = 0x540; - constexpr u64 mix_command_size = 0x1C; - constexpr u64 mix_ramp_command_size = 0x24; - constexpr u64 mix_ramp_grouped_command_size = 0x13C; + constexpr u64 mix_command_size = 0x1C; + constexpr u64 mix_ramp_command_size = 0x24; + constexpr u64 mix_ramp_grouped_command_size = 0x13C; - constexpr u64 perf_command_size = 0x28; + constexpr u64 perf_command_size = 0x28; - constexpr u64 sink_command_size = 0x130; + constexpr u64 sink_command_size = 0x130; - constexpr u64 submix_command_max_size = - depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers; + constexpr u64 submix_command_max_size = + depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers; - constexpr u64 volume_command_size = 0x1C; - constexpr u64 volume_ramp_command_size = 0x20; + constexpr u64 volume_command_size = 0x1C; + constexpr u64 volume_ramp_command_size = 0x20; - constexpr u64 voice_biquad_filter_command_size = - biquad_filter_command_size * max_biquad_filters; - constexpr u64 voice_data_command_size = 0x9C; - const u64 voice_command_max_size = - (params.splitter_count * depop_setup_command_size) + - (voice_data_command_size + voice_biquad_filter_command_size + - volume_ramp_command_size + mix_ramp_grouped_command_size); + constexpr u64 voice_biquad_filter_command_size = + biquad_filter_command_size * max_biquad_filters; + constexpr u64 voice_data_command_size = 0x9C; + const u64 voice_command_max_size = + (params.splitter_count * depop_setup_command_size) + + (voice_data_command_size + voice_biquad_filter_command_size + volume_ramp_command_size + + mix_ramp_grouped_command_size); - // Now calculate the individual elements that comprise the size and add them together. - const u64 effect_commands_size = params.effect_count * effect_command_max_size; + // Now calculate the individual elements that comprise the size and add them together. + const u64 effect_commands_size = params.effect_count * effect_command_max_size; - const u64 final_mix_commands_size = - depop_mix_command_size + volume_command_size * max_mix_buffers; + const u64 final_mix_commands_size = + depop_mix_command_size + volume_command_size * max_mix_buffers; - const u64 perf_commands_size = - perf_command_size * - (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); + const u64 perf_commands_size = + perf_command_size * (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); - const u64 sink_commands_size = params.sink_count * sink_command_size; + const u64 sink_commands_size = params.sink_count * sink_command_size; - const u64 splitter_commands_size = - params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; + const u64 splitter_commands_size = + params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; - const u64 submix_commands_size = params.submix_count * submix_command_max_size; + const u64 submix_commands_size = params.submix_count * submix_command_max_size; - const u64 voice_commands_size = params.voice_count * voice_command_max_size; + const u64 voice_commands_size = params.voice_count * voice_command_max_size; - return effect_commands_size + final_mix_commands_size + perf_commands_size + - sink_commands_size + splitter_commands_size + submix_commands_size + - voice_commands_size + alignment; - }; + return effect_commands_size + final_mix_commands_size + perf_commands_size + + sink_commands_size + splitter_commands_size + submix_commands_size + + voice_commands_size + alignment; + }; IPC::RequestParser rp{ctx}; const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); @@ -564,12 +641,16 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { } void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + IPC::RequestParser rp{ctx}; + const u64 aruid = rp.Pop<u64>(); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + LOG_DEBUG(Service_Audio, "called. aruid={:016X}", aruid); + // Revisionless variant of GetAudioDeviceServiceWithRevisionInfo that + // always assumes the initial release revision (REV1). + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<Audio::IAudioDevice>(); + rb.PushIpcInterface<IAudioDevice>(system, Common::MakeMagic('R', 'E', 'V', '1')); } void AudRenU::OpenAudioRendererAuto(Kernel::HLERequestContext& ctx) { @@ -579,13 +660,19 @@ void AudRenU::OpenAudioRendererAuto(Kernel::HLERequestContext& ctx) { } void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + struct Parameters { + u32 revision; + u64 aruid; + }; - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + IPC::RequestParser rp{ctx}; + const auto [revision, aruid] = rp.PopRaw<Parameters>(); + + LOG_DEBUG(Service_Audio, "called. revision={:08X}, aruid={:016X}", revision, aruid); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<Audio::IAudioDevice>(); // TODO(ogniK): Figure out what is different - // based on the current revision + rb.PushIpcInterface<IAudioDevice>(system, revision); } void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) { @@ -594,14 +681,16 @@ void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IAudioRenderer>(params); + rb.PushIpcInterface<IAudioRenderer>(system, params, audren_instance_count++); } -bool AudRenU::IsFeatureSupported(AudioFeatures feature, u32_le revision) const { +bool IsFeatureSupported(AudioFeatures feature, u32_le revision) { // Byte swap const u32_be version_num = revision - Common::MakeMagic('R', 'E', 'V', '0'); switch (feature) { + case AudioFeatures::AudioUSBDeviceOutput: + return version_num >= 4U; case AudioFeatures::Splitter: return version_num >= 2U; case AudioFeatures::PerformanceMetricsVersion2: diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index 1d3c8df61..4e0ccc792 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -14,7 +18,7 @@ namespace Service::Audio { class AudRenU final : public ServiceFramework<AudRenU> { public: - explicit AudRenU(); + explicit AudRenU(Core::System& system_); ~AudRenU() override; private: @@ -26,13 +30,19 @@ private: void OpenAudioRendererImpl(Kernel::HLERequestContext& ctx); - enum class AudioFeatures : u32 { - Splitter, - PerformanceMetricsVersion2, - VariadicCommandBuffer, - }; + std::size_t audren_instance_count = 0; + Core::System& system; +}; - bool IsFeatureSupported(AudioFeatures feature, u32_le revision) const; +// Describes a particular audio feature that may be supported in a particular revision. +enum class AudioFeatures : u32 { + AudioUSBDeviceOutput, + Splitter, + PerformanceMetricsVersion2, + VariadicCommandBuffer, }; +// Tests if a particular audio feature is supported with a given audio revision. +bool IsFeatureSupported(AudioFeatures feature, u32_le revision); + } // namespace Service::Audio diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp index 3c7ca2c44..afce581e5 100644 --- a/src/core/hle/service/btdrv/btdrv.cpp +++ b/src/core/hle/service/btdrv/btdrv.cpp @@ -16,7 +16,7 @@ namespace Service::BtDrv { class Bt final : public ServiceFramework<Bt> { public: - explicit Bt() : ServiceFramework{"bt"} { + explicit Bt(Core::System& system) : ServiceFramework{"bt"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "LeClientReadCharacteristic"}, @@ -33,7 +33,7 @@ public: // clang-format on RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system.Kernel(); register_event = Kernel::WritableEvent::CreateEventPair( kernel, Kernel::ResetType::Automatic, "BT:RegisterEvent"); } @@ -163,9 +163,9 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { std::make_shared<BtDrv>()->InstallAsService(sm); - std::make_shared<Bt>()->InstallAsService(sm); + std::make_shared<Bt>(system)->InstallAsService(sm); } } // namespace Service::BtDrv diff --git a/src/core/hle/service/btdrv/btdrv.h b/src/core/hle/service/btdrv/btdrv.h index 164e56f43..191410dbc 100644 --- a/src/core/hle/service/btdrv/btdrv.h +++ b/src/core/hle/service/btdrv/btdrv.h @@ -8,9 +8,13 @@ namespace Service::SM { class ServiceManager; } +namespace Core { +class System; +} + namespace Service::BtDrv { /// Registers all BtDrv services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::BtDrv diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp index b439ee7ec..920fc6ff7 100644 --- a/src/core/hle/service/btm/btm.cpp +++ b/src/core/hle/service/btm/btm.cpp @@ -17,7 +17,7 @@ namespace Service::BTM { class IBtmUserCore final : public ServiceFramework<IBtmUserCore> { public: - explicit IBtmUserCore() : ServiceFramework{"IBtmUserCore"} { + explicit IBtmUserCore(Core::System& system) : ServiceFramework{"IBtmUserCore"} { // clang-format off static const FunctionInfo functions[] = { {0, &IBtmUserCore::AcquireBleScanEvent, "AcquireBleScanEvent"}, @@ -56,7 +56,7 @@ public: // clang-format on RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system.Kernel(); scan_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, "IBtmUserCore:ScanEvent"); connection_event = Kernel::WritableEvent::CreateEventPair( @@ -108,7 +108,7 @@ private: class BTM_USR final : public ServiceFramework<BTM_USR> { public: - explicit BTM_USR() : ServiceFramework{"btm:u"} { + explicit BTM_USR(Core::System& system) : ServiceFramework{"btm:u"}, system(system) { // clang-format off static const FunctionInfo functions[] = { {0, &BTM_USR::GetCore, "GetCore"}, @@ -123,8 +123,10 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IBtmUserCore>(); + rb.PushIpcInterface<IBtmUserCore>(system); } + + Core::System& system; }; class BTM final : public ServiceFramework<BTM> { @@ -268,11 +270,11 @@ private: } }; -void InstallInterfaces(SM::ServiceManager& sm) { +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { std::make_shared<BTM>()->InstallAsService(sm); std::make_shared<BTM_DBG>()->InstallAsService(sm); std::make_shared<BTM_SYS>()->InstallAsService(sm); - std::make_shared<BTM_USR>()->InstallAsService(sm); + std::make_shared<BTM_USR>(system)->InstallAsService(sm); } } // namespace Service::BTM diff --git a/src/core/hle/service/btm/btm.h b/src/core/hle/service/btm/btm.h index e6425a7e3..c6b878043 100644 --- a/src/core/hle/service/btm/btm.h +++ b/src/core/hle/service/btm/btm.h @@ -8,8 +8,12 @@ namespace Service::SM { class ServiceManager; } +namespace Core { +class System; +}; + namespace Service::BTM { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::BTM diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 6701cb913..af70d174d 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -2,32 +2,37 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/crypto/key_manager.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/service/service.h" namespace Service::ES { +constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2}; +constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3}; + class ETicket final : public ServiceFramework<ETicket> { public: explicit ETicket() : ServiceFramework{"es"} { // clang-format off static const FunctionInfo functions[] = { - {1, nullptr, "ImportTicket"}, + {1, &ETicket::ImportTicket, "ImportTicket"}, {2, nullptr, "ImportTicketCertificateSet"}, {3, nullptr, "DeleteTicket"}, {4, nullptr, "DeletePersonalizedTicket"}, {5, nullptr, "DeleteAllCommonTicket"}, {6, nullptr, "DeleteAllPersonalizedTicket"}, {7, nullptr, "DeleteAllPersonalizedTicketEx"}, - {8, nullptr, "GetTitleKey"}, - {9, nullptr, "CountCommonTicket"}, - {10, nullptr, "CountPersonalizedTicket"}, - {11, nullptr, "ListCommonTicket"}, - {12, nullptr, "ListPersonalizedTicket"}, + {8, &ETicket::GetTitleKey, "GetTitleKey"}, + {9, &ETicket::CountCommonTicket, "CountCommonTicket"}, + {10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"}, + {11, &ETicket::ListCommonTicket, "ListCommonTicket"}, + {12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"}, {13, nullptr, "ListMissingPersonalizedTicket"}, - {14, nullptr, "GetCommonTicketSize"}, - {15, nullptr, "GetPersonalizedTicketSize"}, - {16, nullptr, "GetCommonTicketData"}, - {17, nullptr, "GetPersonalizedTicketData"}, + {14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"}, + {15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"}, + {16, &ETicket::GetCommonTicketData, "GetCommonTicketData"}, + {17, &ETicket::GetPersonalizedTicketData, "GetPersonalizedTicketData"}, {18, nullptr, "OwnTicket"}, {19, nullptr, "GetTicketInfo"}, {20, nullptr, "ListLightTicketInfo"}, @@ -51,7 +56,212 @@ public: }; // clang-format on RegisterHandlers(functions); + + keys.PopulateTickets(); + keys.SynthesizeTickets(); + } + +private: + bool CheckRightsId(Kernel::HLERequestContext& ctx, const u128& rights_id) { + if (rights_id == u128{}) { + LOG_ERROR(Service_ETicket, "The rights ID was invalid!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_RIGHTS_ID); + return false; + } + + return true; + } + + void ImportTicket(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto ticket = ctx.ReadBuffer(); + const auto cert = ctx.ReadBuffer(1); + + if (ticket.size() < sizeof(Core::Crypto::Ticket)) { + LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + Core::Crypto::Ticket raw{}; + std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket)); + + if (!keys.AddTicketPersonalized(raw)) { + LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ARGUMENT); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetTitleKey(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw<u128>(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto key = + keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); + + if (key == Core::Crypto::Key128{}) { + LOG_ERROR(Service_ETicket, + "The titlekey doesn't exist in the KeyManager or the rights ID was invalid!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_RIGHTS_ID); + return; + } + + ctx.WriteBuffer(key.data(), key.size()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void CountCommonTicket(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ETicket, "called"); + + const auto count = keys.GetCommonTickets().size(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(count); + } + + void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ETicket, "called"); + + const auto count = keys.GetPersonalizedTickets().size(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(count); + } + + void ListCommonTicket(Kernel::HLERequestContext& ctx) { + u32 out_entries; + if (keys.GetCommonTickets().empty()) + out_entries = 0; + else + out_entries = ctx.GetWriteBufferSize() / sizeof(u128); + + LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries); + + keys.PopulateTickets(); + const auto tickets = keys.GetCommonTickets(); + std::vector<u128> ids; + std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids), + [](const auto& pair) { return pair.first; }); + + out_entries = std::min<u32>(ids.size(), out_entries); + ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128)); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(out_entries); } + + void ListPersonalizedTicket(Kernel::HLERequestContext& ctx) { + u32 out_entries; + if (keys.GetPersonalizedTickets().empty()) + out_entries = 0; + else + out_entries = ctx.GetWriteBufferSize() / sizeof(u128); + + LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries); + + keys.PopulateTickets(); + const auto tickets = keys.GetPersonalizedTickets(); + std::vector<u128> ids; + std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids), + [](const auto& pair) { return pair.first; }); + + out_entries = std::min<u32>(ids.size(), out_entries); + ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128)); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(out_entries); + } + + void GetCommonTicketSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw<u128>(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetCommonTickets().at(rights_id); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(ticket.GetSize()); + } + + void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw<u128>(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetPersonalizedTickets().at(rights_id); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(ticket.GetSize()); + } + + void GetCommonTicketData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw<u128>(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetCommonTickets().at(rights_id); + + const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize()); + ctx.WriteBuffer(&ticket, write_size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(write_size); + } + + void GetPersonalizedTicketData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto rights_id = rp.PopRaw<u128>(); + + LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]); + + if (!CheckRightsId(ctx, rights_id)) + return; + + const auto ticket = keys.GetPersonalizedTickets().at(rights_id); + + const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize()); + ctx.WriteBuffer(&ticket, write_size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push<u64>(write_size); + } + + Core::Crypto::KeyManager keys; }; void InstallInterfaces(SM::ServiceManager& service_manager) { diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index 2c229bcad..b2ebf6240 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -5,7 +5,7 @@ #include <array> #include <cstring> #include <ctime> -#include <fmt/time.h> +#include <fmt/chrono.h> #include "common/file_util.h" #include "common/logging/log.h" #include "common/scm_rev.h" @@ -16,11 +16,12 @@ #include "core/hle/service/fatal/fatal.h" #include "core/hle/service/fatal/fatal_p.h" #include "core/hle/service/fatal/fatal_u.h" +#include "core/reporter.h" namespace Service::Fatal { -Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) - : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name) + : ServiceFramework(name), module(std::move(module)), system(system) {} Module::Interface::~Interface() = default; @@ -63,7 +64,8 @@ enum class FatalType : u32 { ErrorScreen = 2, }; -static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) { +static void GenerateErrorReport(Core::System& system, ResultCode error_code, + const FatalInfo& info) { const auto title_id = Core::CurrentProcess()->GetTitleID(); std::string crash_report = fmt::format( "Yuzu {}-{} crash report\n" @@ -100,35 +102,19 @@ static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) { LOG_ERROR(Service_Fatal, "{}", crash_report); - const std::string crashreport_dir = - FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "crash_logs"; - - if (!FileUtil::CreateFullPath(crashreport_dir)) { - LOG_ERROR( - Service_Fatal, - "Unable to create crash report directory. Possible log directory permissions issue."); - return; - } - - const std::time_t t = std::time(nullptr); - const std::string crashreport_filename = - fmt::format("{}/{:016x}-{:%F-%H%M%S}.log", crashreport_dir, title_id, *std::localtime(&t)); - - auto file = FileUtil::IOFile(crashreport_filename, "wb"); - if (file.IsOpen()) { - file.WriteString(crash_report); - LOG_ERROR(Service_Fatal, "Saving error report to {}", crashreport_filename); - } else { - LOG_ERROR(Service_Fatal, "Failed to save error report to {}", crashreport_filename); - } + system.GetReporter().SaveCrashReport( + title_id, error_code, info.set_flags, info.program_entry_point, info.sp, info.pc, + info.pstate, info.afsr0, info.afsr1, info.esr, info.far, info.registers, info.backtrace, + info.backtrace_size, info.ArchAsString(), info.unk10); } -static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const FatalInfo& info) { +static void ThrowFatalError(Core::System& system, ResultCode error_code, FatalType fatal_type, + const FatalInfo& info) { LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code 0x{:X}", static_cast<u32>(fatal_type), error_code.raw); switch (fatal_type) { case FatalType::ErrorReportAndScreen: - GenerateErrorReport(error_code, info); + GenerateErrorReport(system, error_code, info); [[fallthrough]]; case FatalType::ErrorScreen: // Since we have no fatal:u error screen. We should just kill execution instead @@ -136,7 +122,7 @@ static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const F break; // Should not throw a fatal screen but should generate an error report case FatalType::ErrorReport: - GenerateErrorReport(error_code, info); + GenerateErrorReport(system, error_code, info); break; } } @@ -146,7 +132,7 @@ void Module::Interface::ThrowFatal(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto error_code = rp.Pop<ResultCode>(); - ThrowFatalError(error_code, FatalType::ErrorScreen, {}); + ThrowFatalError(system, error_code, FatalType::ErrorScreen, {}); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -157,7 +143,8 @@ void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) { const auto error_code = rp.Pop<ResultCode>(); const auto fatal_type = rp.PopEnum<FatalType>(); - ThrowFatalError(error_code, fatal_type, {}); // No info is passed with ThrowFatalWithPolicy + ThrowFatalError(system, error_code, fatal_type, + {}); // No info is passed with ThrowFatalWithPolicy IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -173,15 +160,15 @@ void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) ASSERT_MSG(fatal_info.size() == sizeof(FatalInfo), "Invalid fatal info buffer size!"); std::memcpy(&info, fatal_info.data(), sizeof(FatalInfo)); - ThrowFatalError(error_code, fatal_type, info); + ThrowFatalError(system, error_code, fatal_type, info); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { auto module = std::make_shared<Module>(); - std::make_shared<Fatal_P>(module)->InstallAsService(service_manager); - std::make_shared<Fatal_U>(module)->InstallAsService(service_manager); + std::make_shared<Fatal_P>(module, system)->InstallAsService(service_manager); + std::make_shared<Fatal_U>(module, system)->InstallAsService(service_manager); } } // namespace Service::Fatal diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h index 09371ff7f..bd9339dfc 100644 --- a/src/core/hle/service/fatal/fatal.h +++ b/src/core/hle/service/fatal/fatal.h @@ -6,13 +6,17 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Fatal { class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, const char* name); + explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name); ~Interface() override; void ThrowFatal(Kernel::HLERequestContext& ctx); @@ -21,9 +25,10 @@ public: protected: std::shared_ptr<Module> module; + Core::System& system; }; }; -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::Fatal diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp index 9e5f872ff..066ccf6b0 100644 --- a/src/core/hle/service/fatal/fatal_p.cpp +++ b/src/core/hle/service/fatal/fatal_p.cpp @@ -6,8 +6,8 @@ namespace Service::Fatal { -Fatal_P::Fatal_P(std::shared_ptr<Module> module) - : Module::Interface(std::move(module), "fatal:p") {} +Fatal_P::Fatal_P(std::shared_ptr<Module> module, Core::System& system) + : Module::Interface(std::move(module), system, "fatal:p") {} Fatal_P::~Fatal_P() = default; diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h index 6e9c5979f..c6d953cb5 100644 --- a/src/core/hle/service/fatal/fatal_p.h +++ b/src/core/hle/service/fatal/fatal_p.h @@ -10,7 +10,7 @@ namespace Service::Fatal { class Fatal_P final : public Module::Interface { public: - explicit Fatal_P(std::shared_ptr<Module> module); + explicit Fatal_P(std::shared_ptr<Module> module, Core::System& system); ~Fatal_P() override; }; diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp index 1572a2051..8d72ed485 100644 --- a/src/core/hle/service/fatal/fatal_u.cpp +++ b/src/core/hle/service/fatal/fatal_u.cpp @@ -6,7 +6,8 @@ namespace Service::Fatal { -Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") { +Fatal_U::Fatal_U(std::shared_ptr<Module> module, Core::System& system) + : Module::Interface(std::move(module), system, "fatal:u") { static const FunctionInfo functions[] = { {0, &Fatal_U::ThrowFatal, "ThrowFatal"}, {1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"}, diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h index 72cb6d076..34c5c7f95 100644 --- a/src/core/hle/service/fatal/fatal_u.h +++ b/src/core/hle/service/fatal/fatal_u.h @@ -10,7 +10,7 @@ namespace Service::Fatal { class Fatal_U final : public Module::Interface { public: - explicit Fatal_U(std::shared_ptr<Module> module); + explicit Fatal_U(std::shared_ptr<Module> module, Core::System& system); ~Fatal_U() override; }; diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 1ebfeb4bf..14cd0e322 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -8,6 +8,7 @@ #include "common/file_util.h" #include "core/core.h" #include "core/file_sys/bis_factory.h" +#include "core/file_sys/card_image.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/errors.h" #include "core/file_sys/mode.h" @@ -25,14 +26,10 @@ #include "core/hle/service/filesystem/fsp_pr.h" #include "core/hle/service/filesystem/fsp_srv.h" #include "core/loader/loader.h" +#include "core/settings.h" namespace Service::FileSystem { -// Size of emulated sd card free space, reported in bytes. -// Just using 32GB because thats reasonable -// TODO(DarkLordZach): Eventually make this configurable in settings. -constexpr u64 EMULATED_SD_REPORTED_SIZE = 32000000000; - // A default size for normal/journal save data size if application control metadata cannot be found. // This should be large enough to satisfy even the most extreme requirements (~4.2GB) constexpr u64 SUFFICIENT_SAVE_DATA_SIZE = 0xF0000000; @@ -226,13 +223,6 @@ ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const s return MakeResult(dir); } -u64 VfsDirectoryServiceWrapper::GetFreeSpaceSize() const { - if (backing->IsWritable()) - return EMULATED_SD_REPORTED_SIZE; - - return 0; -} - ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType( const std::string& path_) const { std::string path(FileUtil::SanitizePath(path_)); @@ -251,44 +241,39 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType( return FileSys::ERROR_PATH_NOT_FOUND; } -/** - * Map of registered file systems, identified by type. Once an file system is registered here, it - * is never removed until UnregisterFileSystems is called. - */ -static std::unique_ptr<FileSys::RomFSFactory> romfs_factory; -static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory; -static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory; -static std::unique_ptr<FileSys::BISFactory> bis_factory; +FileSystemController::FileSystemController() = default; + +FileSystemController::~FileSystemController() = default; -ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) { - ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS"); +ResultCode FileSystemController::RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) { romfs_factory = std::move(factory); LOG_DEBUG(Service_FS, "Registered RomFS"); return RESULT_SUCCESS; } -ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory) { - ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second save data"); +ResultCode FileSystemController::RegisterSaveData( + std::unique_ptr<FileSys::SaveDataFactory>&& factory) { + ASSERT_MSG(save_data_factory == nullptr, "Tried to register a second save data"); save_data_factory = std::move(factory); LOG_DEBUG(Service_FS, "Registered save data"); return RESULT_SUCCESS; } -ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) { +ResultCode FileSystemController::RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) { ASSERT_MSG(sdmc_factory == nullptr, "Tried to register a second SDMC"); sdmc_factory = std::move(factory); LOG_DEBUG(Service_FS, "Registered SDMC"); return RESULT_SUCCESS; } -ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) { +ResultCode FileSystemController::RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) { ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS"); bis_factory = std::move(factory); LOG_DEBUG(Service_FS, "Registered BIS"); return RESULT_SUCCESS; } -void SetPackedUpdate(FileSys::VirtualFile update_raw) { +void FileSystemController::SetPackedUpdate(FileSys::VirtualFile update_raw) { LOG_TRACE(Service_FS, "Setting packed update for romfs"); if (romfs_factory == nullptr) @@ -297,7 +282,7 @@ void SetPackedUpdate(FileSys::VirtualFile update_raw) { romfs_factory->SetPackedUpdate(std::move(update_raw)); } -ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() { +ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess() const { LOG_TRACE(Service_FS, "Opening RomFS for current process"); if (romfs_factory == nullptr) { @@ -308,8 +293,8 @@ ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() { return romfs_factory->OpenCurrentProcess(); } -ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, - FileSys::ContentRecordType type) { +ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS( + u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const { LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}", title_id, static_cast<u8>(storage_id), static_cast<u8>(type)); @@ -321,8 +306,20 @@ ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId stora return romfs_factory->Open(title_id, storage_id, type); } -ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, - const FileSys::SaveDataDescriptor& descriptor) { +ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData( + FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const { + LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", + static_cast<u8>(space), save_struct.DebugInfo()); + + if (save_data_factory == nullptr) { + return FileSys::ERROR_ENTITY_NOT_FOUND; + } + + return save_data_factory->Create(space, save_struct); +} + +ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData( + FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& descriptor) const { LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", static_cast<u8>(space), descriptor.DebugInfo()); @@ -333,7 +330,8 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, return save_data_factory->Open(space, descriptor); } -ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) { +ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace( + FileSys::SaveDataSpaceId space) const { LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", static_cast<u8>(space)); if (save_data_factory == nullptr) { @@ -343,7 +341,7 @@ ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) return MakeResult(save_data_factory->GetSaveDataSpaceDirectory(space)); } -ResultVal<FileSys::VirtualDir> OpenSDMC() { +ResultVal<FileSys::VirtualDir> FileSystemController::OpenSDMC() const { LOG_TRACE(Service_FS, "Opening SDMC"); if (sdmc_factory == nullptr) { @@ -353,7 +351,92 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() { return sdmc_factory->Open(); } -FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id) { +ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition( + FileSys::BisPartitionId id) const { + LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", static_cast<u32>(id)); + + if (bis_factory == nullptr) { + return FileSys::ERROR_ENTITY_NOT_FOUND; + } + + auto part = bis_factory->OpenPartition(id); + if (part == nullptr) { + return FileSys::ERROR_INVALID_ARGUMENT; + } + + return MakeResult<FileSys::VirtualDir>(std::move(part)); +} + +ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage( + FileSys::BisPartitionId id) const { + LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", static_cast<u32>(id)); + + if (bis_factory == nullptr) { + return FileSys::ERROR_ENTITY_NOT_FOUND; + } + + auto part = bis_factory->OpenPartitionStorage(id); + if (part == nullptr) { + return FileSys::ERROR_INVALID_ARGUMENT; + } + + return MakeResult<FileSys::VirtualFile>(std::move(part)); +} + +u64 FileSystemController::GetFreeSpaceSize(FileSys::StorageId id) const { + switch (id) { + case FileSys::StorageId::None: + case FileSys::StorageId::GameCard: + return 0; + case FileSys::StorageId::SdCard: + if (sdmc_factory == nullptr) + return 0; + return sdmc_factory->GetSDMCFreeSpace(); + case FileSys::StorageId::Host: + if (bis_factory == nullptr) + return 0; + return bis_factory->GetSystemNANDFreeSpace() + bis_factory->GetUserNANDFreeSpace(); + case FileSys::StorageId::NandSystem: + if (bis_factory == nullptr) + return 0; + return bis_factory->GetSystemNANDFreeSpace(); + case FileSys::StorageId::NandUser: + if (bis_factory == nullptr) + return 0; + return bis_factory->GetUserNANDFreeSpace(); + } + + return 0; +} + +u64 FileSystemController::GetTotalSpaceSize(FileSys::StorageId id) const { + switch (id) { + case FileSys::StorageId::None: + case FileSys::StorageId::GameCard: + return 0; + case FileSys::StorageId::SdCard: + if (sdmc_factory == nullptr) + return 0; + return sdmc_factory->GetSDMCTotalSpace(); + case FileSys::StorageId::Host: + if (bis_factory == nullptr) + return 0; + return bis_factory->GetFullNANDTotalSpace(); + case FileSys::StorageId::NandSystem: + if (bis_factory == nullptr) + return 0; + return bis_factory->GetSystemNANDTotalSpace(); + case FileSys::StorageId::NandUser: + if (bis_factory == nullptr) + return 0; + return bis_factory->GetUserNANDTotalSpace(); + } + + return 0; +} + +FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataType type, + u64 title_id, u128 user_id) const { if (save_data_factory == nullptr) { return {0, 0}; } @@ -385,13 +468,32 @@ FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, return value; } -void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id, - FileSys::SaveDataSize new_value) { +void FileSystemController::WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id, + FileSys::SaveDataSize new_value) const { if (save_data_factory != nullptr) save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value); } -FileSys::RegisteredCache* GetSystemNANDContents() { +void FileSystemController::SetGameCard(FileSys::VirtualFile file) { + gamecard = std::make_unique<FileSys::XCI>(file); + const auto dir = gamecard->ConcatenatedPseudoDirectory(); + gamecard_registered = std::make_unique<FileSys::RegisteredCache>(dir); + gamecard_placeholder = std::make_unique<FileSys::PlaceholderCache>(dir); +} + +FileSys::XCI* FileSystemController::GetGameCard() const { + return gamecard.get(); +} + +FileSys::RegisteredCache* FileSystemController::GetGameCardContents() const { + return gamecard_registered.get(); +} + +FileSys::PlaceholderCache* FileSystemController::GetGameCardPlaceholder() const { + return gamecard_placeholder.get(); +} + +FileSys::RegisteredCache* FileSystemController::GetSystemNANDContents() const { LOG_TRACE(Service_FS, "Opening System NAND Contents"); if (bis_factory == nullptr) @@ -400,7 +502,7 @@ FileSys::RegisteredCache* GetSystemNANDContents() { return bis_factory->GetSystemNANDContents(); } -FileSys::RegisteredCache* GetUserNANDContents() { +FileSys::RegisteredCache* FileSystemController::GetUserNANDContents() const { LOG_TRACE(Service_FS, "Opening User NAND Contents"); if (bis_factory == nullptr) @@ -409,7 +511,7 @@ FileSys::RegisteredCache* GetUserNANDContents() { return bis_factory->GetUserNANDContents(); } -FileSys::RegisteredCache* GetSDMCContents() { +FileSys::RegisteredCache* FileSystemController::GetSDMCContents() const { LOG_TRACE(Service_FS, "Opening SDMC Contents"); if (sdmc_factory == nullptr) @@ -418,7 +520,143 @@ FileSys::RegisteredCache* GetSDMCContents() { return sdmc_factory->GetSDMCContents(); } -FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) { +FileSys::PlaceholderCache* FileSystemController::GetSystemNANDPlaceholder() const { + LOG_TRACE(Service_FS, "Opening System NAND Placeholder"); + + if (bis_factory == nullptr) + return nullptr; + + return bis_factory->GetSystemNANDPlaceholder(); +} + +FileSys::PlaceholderCache* FileSystemController::GetUserNANDPlaceholder() const { + LOG_TRACE(Service_FS, "Opening User NAND Placeholder"); + + if (bis_factory == nullptr) + return nullptr; + + return bis_factory->GetUserNANDPlaceholder(); +} + +FileSys::PlaceholderCache* FileSystemController::GetSDMCPlaceholder() const { + LOG_TRACE(Service_FS, "Opening SDMC Placeholder"); + + if (sdmc_factory == nullptr) + return nullptr; + + return sdmc_factory->GetSDMCPlaceholder(); +} + +FileSys::RegisteredCache* FileSystemController::GetRegisteredCacheForStorage( + FileSys::StorageId id) const { + switch (id) { + case FileSys::StorageId::None: + case FileSys::StorageId::Host: + UNIMPLEMENTED(); + return nullptr; + case FileSys::StorageId::GameCard: + return GetGameCardContents(); + case FileSys::StorageId::NandSystem: + return GetSystemNANDContents(); + case FileSys::StorageId::NandUser: + return GetUserNANDContents(); + case FileSys::StorageId::SdCard: + return GetSDMCContents(); + } + + return nullptr; +} + +FileSys::PlaceholderCache* FileSystemController::GetPlaceholderCacheForStorage( + FileSys::StorageId id) const { + switch (id) { + case FileSys::StorageId::None: + case FileSys::StorageId::Host: + UNIMPLEMENTED(); + return nullptr; + case FileSys::StorageId::GameCard: + return GetGameCardPlaceholder(); + case FileSys::StorageId::NandSystem: + return GetSystemNANDPlaceholder(); + case FileSys::StorageId::NandUser: + return GetUserNANDPlaceholder(); + case FileSys::StorageId::SdCard: + return GetSDMCPlaceholder(); + } + + return nullptr; +} + +FileSys::VirtualDir FileSystemController::GetSystemNANDContentDirectory() const { + LOG_TRACE(Service_FS, "Opening system NAND content directory"); + + if (bis_factory == nullptr) + return nullptr; + + return bis_factory->GetSystemNANDContentDirectory(); +} + +FileSys::VirtualDir FileSystemController::GetUserNANDContentDirectory() const { + LOG_TRACE(Service_FS, "Opening user NAND content directory"); + + if (bis_factory == nullptr) + return nullptr; + + return bis_factory->GetUserNANDContentDirectory(); +} + +FileSys::VirtualDir FileSystemController::GetSDMCContentDirectory() const { + LOG_TRACE(Service_FS, "Opening SDMC content directory"); + + if (sdmc_factory == nullptr) + return nullptr; + + return sdmc_factory->GetSDMCContentDirectory(); +} + +FileSys::VirtualDir FileSystemController::GetNANDImageDirectory() const { + LOG_TRACE(Service_FS, "Opening NAND image directory"); + + if (bis_factory == nullptr) + return nullptr; + + return bis_factory->GetImageDirectory(); +} + +FileSys::VirtualDir FileSystemController::GetSDMCImageDirectory() const { + LOG_TRACE(Service_FS, "Opening SDMC image directory"); + + if (sdmc_factory == nullptr) + return nullptr; + + return sdmc_factory->GetImageDirectory(); +} + +FileSys::VirtualDir FileSystemController::GetContentDirectory(ContentStorageId id) const { + switch (id) { + case ContentStorageId::System: + return GetSystemNANDContentDirectory(); + case ContentStorageId::User: + return GetUserNANDContentDirectory(); + case ContentStorageId::SdCard: + return GetSDMCContentDirectory(); + } + + return nullptr; +} + +FileSys::VirtualDir FileSystemController::GetImageDirectory(ImageDirectoryId id) const { + switch (id) { + case ImageDirectoryId::NAND: + return GetNANDImageDirectory(); + case ImageDirectoryId::SdCard: + return GetSDMCImageDirectory(); + } + + return nullptr; +} + +FileSys::VirtualDir FileSystemController::GetModificationLoadRoot(u64 title_id) const { LOG_TRACE(Service_FS, "Opening mod load root for tid={:016X}", title_id); if (bis_factory == nullptr) @@ -427,7 +665,7 @@ FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) { return bis_factory->GetModificationLoadRoot(title_id); } -FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) { +FileSys::VirtualDir FileSystemController::GetModificationDumpRoot(u64 title_id) const { LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id); if (bis_factory == nullptr) @@ -436,7 +674,7 @@ FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) { return bis_factory->GetModificationDumpRoot(title_id); } -void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) { +void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) { if (overwrite) { bis_factory = nullptr; save_data_factory = nullptr; @@ -472,12 +710,11 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) { } } -void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs) { - romfs_factory = nullptr; - CreateFactories(vfs, false); - std::make_shared<FSP_LDR>()->InstallAsService(service_manager); - std::make_shared<FSP_PR>()->InstallAsService(service_manager); - std::make_shared<FSP_SRV>()->InstallAsService(service_manager); +void InstallInterfaces(Core::System& system) { + std::make_shared<FSP_LDR>()->InstallAsService(system.ServiceManager()); + std::make_shared<FSP_PR>()->InstallAsService(system.ServiceManager()); + std::make_shared<FSP_SRV>(system.GetFileSystemController(), system.GetReporter()) + ->InstallAsService(system.ServiceManager()); } } // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 6481f237c..3e0c03ec0 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -14,10 +14,13 @@ namespace FileSys { class BISFactory; class RegisteredCache; class RegisteredCacheUnion; +class PlaceholderCache; class RomFSFactory; class SaveDataFactory; class SDMCFactory; +class XCI; +enum class BisPartitionId : u32; enum class ContentRecordType : u8; enum class Mode : u32; enum class SaveDataSpaceId : u8; @@ -36,36 +39,93 @@ class ServiceManager; namespace FileSystem { -ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory); -ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory); -ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); -ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); - -void SetPackedUpdate(FileSys::VirtualFile update_raw); -ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess(); -ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, - FileSys::ContentRecordType type); -ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, - const FileSys::SaveDataDescriptor& descriptor); -ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space); -ResultVal<FileSys::VirtualDir> OpenSDMC(); - -FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id); -void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id, - FileSys::SaveDataSize new_value); +enum class ContentStorageId : u32 { + System, + User, + SdCard, +}; -FileSys::RegisteredCache* GetSystemNANDContents(); -FileSys::RegisteredCache* GetUserNANDContents(); -FileSys::RegisteredCache* GetSDMCContents(); +enum class ImageDirectoryId : u32 { + NAND, + SdCard, +}; -FileSys::VirtualDir GetModificationLoadRoot(u64 title_id); -FileSys::VirtualDir GetModificationDumpRoot(u64 title_id); +class FileSystemController { +public: + FileSystemController(); + ~FileSystemController(); + + ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory); + ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory); + ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); + ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); + + void SetPackedUpdate(FileSys::VirtualFile update_raw); + ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const; + ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, + FileSys::ContentRecordType type) const; + ResultVal<FileSys::VirtualDir> CreateSaveData( + FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const; + ResultVal<FileSys::VirtualDir> OpenSaveData( + FileSys::SaveDataSpaceId space, const FileSys::SaveDataDescriptor& save_struct) const; + ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) const; + ResultVal<FileSys::VirtualDir> OpenSDMC() const; + ResultVal<FileSys::VirtualDir> OpenBISPartition(FileSys::BisPartitionId id) const; + ResultVal<FileSys::VirtualFile> OpenBISPartitionStorage(FileSys::BisPartitionId id) const; + + u64 GetFreeSpaceSize(FileSys::StorageId id) const; + u64 GetTotalSpaceSize(FileSys::StorageId id) const; + + FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id, + u128 user_id) const; + void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id, + FileSys::SaveDataSize new_value) const; + + void SetGameCard(FileSys::VirtualFile file); + FileSys::XCI* GetGameCard() const; + + FileSys::RegisteredCache* GetSystemNANDContents() const; + FileSys::RegisteredCache* GetUserNANDContents() const; + FileSys::RegisteredCache* GetSDMCContents() const; + FileSys::RegisteredCache* GetGameCardContents() const; + + FileSys::PlaceholderCache* GetSystemNANDPlaceholder() const; + FileSys::PlaceholderCache* GetUserNANDPlaceholder() const; + FileSys::PlaceholderCache* GetSDMCPlaceholder() const; + FileSys::PlaceholderCache* GetGameCardPlaceholder() const; + + FileSys::RegisteredCache* GetRegisteredCacheForStorage(FileSys::StorageId id) const; + FileSys::PlaceholderCache* GetPlaceholderCacheForStorage(FileSys::StorageId id) const; + + FileSys::VirtualDir GetSystemNANDContentDirectory() const; + FileSys::VirtualDir GetUserNANDContentDirectory() const; + FileSys::VirtualDir GetSDMCContentDirectory() const; + + FileSys::VirtualDir GetNANDImageDirectory() const; + FileSys::VirtualDir GetSDMCImageDirectory() const; + + FileSys::VirtualDir GetContentDirectory(ContentStorageId id) const; + FileSys::VirtualDir GetImageDirectory(ImageDirectoryId id) const; + + FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) const; + FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) const; + + // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function + // above is called. + void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true); -// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function -// above is called. -void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true); +private: + std::unique_ptr<FileSys::RomFSFactory> romfs_factory; + std::unique_ptr<FileSys::SaveDataFactory> save_data_factory; + std::unique_ptr<FileSys::SDMCFactory> sdmc_factory; + std::unique_ptr<FileSys::BISFactory> bis_factory; + + std::unique_ptr<FileSys::XCI> gamecard; + std::unique_ptr<FileSys::RegisteredCache> gamecard_registered; + std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder; +}; -void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs); +void InstallInterfaces(Core::System& system); // A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of // pointers and booleans. This makes using a VfsDirectory with switch services much easier and @@ -160,12 +220,6 @@ public: ResultVal<FileSys::VirtualDir> OpenDirectory(const std::string& path); /** - * Get the free space - * @return The number of free bytes in the archive - */ - u64 GetFreeSpaceSize() const; - - /** * Get the type of the specified path * @return The type of the specified path or error code */ diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index e7df8fd98..eb982ad49 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -19,6 +19,7 @@ #include "core/file_sys/mode.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" +#include "core/file_sys/romfs_factory.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/system_archive/system_archive.h" #include "core/file_sys/vfs.h" @@ -26,9 +27,22 @@ #include "core/hle/kernel/process.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/fsp_srv.h" +#include "core/reporter.h" namespace Service::FileSystem { +struct SizeGetter { + std::function<u64()> get_free_size; + std::function<u64()> get_total_size; + + static SizeGetter FromStorageId(const FileSystemController& fsc, FileSys::StorageId id) { + return { + [&fsc, id] { return fsc.GetFreeSpaceSize(id); }, + [&fsc, id] { return fsc.GetTotalSpaceSize(id); }, + }; + } +}; + enum class FileSystemType : u8 { Invalid0 = 0, Invalid1 = 1, @@ -288,8 +302,8 @@ private: class IFileSystem final : public ServiceFramework<IFileSystem> { public: - explicit IFileSystem(FileSys::VirtualDir backend) - : ServiceFramework("IFileSystem"), backend(std::move(backend)) { + explicit IFileSystem(FileSys::VirtualDir backend, SizeGetter size) + : ServiceFramework("IFileSystem"), backend(std::move(backend)), size(std::move(size)) { static const FunctionInfo functions[] = { {0, &IFileSystem::CreateFile, "CreateFile"}, {1, &IFileSystem::DeleteFile, "DeleteFile"}, @@ -466,14 +480,31 @@ public: rb.Push(RESULT_SUCCESS); } + void GetFreeSpaceSize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(size.get_free_size()); + } + + void GetTotalSpaceSize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(size.get_total_size()); + } + private: VfsDirectoryServiceWrapper backend; + SizeGetter size; }; class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> { public: - explicit ISaveDataInfoReader(FileSys::SaveDataSpaceId space) - : ServiceFramework("ISaveDataInfoReader") { + explicit ISaveDataInfoReader(FileSys::SaveDataSpaceId space, FileSystemController& fsc) + : ServiceFramework("ISaveDataInfoReader"), fsc(fsc) { static const FunctionInfo functions[] = { {0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"}, }; @@ -519,8 +550,13 @@ private: } void FindAllSaves(FileSys::SaveDataSpaceId space) { - const auto save_root = OpenSaveDataSpace(space); - ASSERT(save_root.Succeeded()); + const auto save_root = fsc.OpenSaveDataSpace(space); + + if (save_root.Failed() || *save_root == nullptr) { + LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", + static_cast<u8>(space)); + return; + } for (const auto& type : (*save_root)->GetSubdirectories()) { if (type->GetName() == "save") { @@ -609,11 +645,13 @@ private: }; static_assert(sizeof(SaveDataInfo) == 0x60, "SaveDataInfo has incorrect size."); + FileSystemController& fsc; std::vector<SaveDataInfo> info; u64 next_entry_index = 0; }; -FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { +FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter) + : ServiceFramework("fsp-srv"), fsc(fsc), reporter(reporter) { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "OpenFileSystem"}, @@ -710,14 +748,14 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { {1001, nullptr, "SetSaveDataSize"}, {1002, nullptr, "SetSaveDataRootPath"}, {1003, nullptr, "DisableAutoSaveDataCreation"}, - {1004, nullptr, "SetGlobalAccessLogMode"}, + {1004, &FSP_SRV::SetGlobalAccessLogMode, "SetGlobalAccessLogMode"}, {1005, &FSP_SRV::GetGlobalAccessLogMode, "GetGlobalAccessLogMode"}, - {1006, nullptr, "OutputAccessLogToSdCard"}, + {1006, &FSP_SRV::OutputAccessLogToSdCard, "OutputAccessLogToSdCard"}, {1007, nullptr, "RegisterUpdatePartition"}, {1008, nullptr, "OpenRegisteredUpdatePartition"}, {1009, nullptr, "GetAndClearMemoryReportInfo"}, {1010, nullptr, "SetDataStorageRedirectTarget"}, - {1011, nullptr, "OutputAccessLogToSdCard2"}, + {1011, &FSP_SRV::GetAccessLogVersionInfo, "GetAccessLogVersionInfo"}, {1100, nullptr, "OverrideSaveDataTransferTokenSignVerificationKey"}, {1110, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId2"}, {1200, nullptr, "OpenMultiCommitManager"}, @@ -753,7 +791,8 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) { void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called"); - IFileSystem filesystem(OpenSDMC().Unwrap()); + IFileSystem filesystem(fsc.OpenSDMC().Unwrap(), + SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -767,8 +806,10 @@ void FSP_SRV::CreateSaveDataFileSystem(Kernel::HLERequestContext& ctx) { auto save_create_struct = rp.PopRaw<std::array<u8, 0x40>>(); u128 uid = rp.PopRaw<u128>(); - LOG_WARNING(Service_FS, "(STUBBED) called save_struct = {}, uid = {:016X}{:016X}", - save_struct.DebugInfo(), uid[1], uid[0]); + LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(), + uid[1], uid[0]); + + fsc.CreateSaveData(FileSys::SaveDataSpaceId::NandUser, save_struct); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -785,14 +826,24 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto parameters = rp.PopRaw<Parameters>(); - auto dir = OpenSaveData(parameters.save_data_space_id, parameters.descriptor); + auto dir = fsc.OpenSaveData(parameters.save_data_space_id, parameters.descriptor); if (dir.Failed()) { IPC::ResponseBuilder rb{ctx, 2, 0, 0}; rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND); return; } - IFileSystem filesystem(std::move(dir.Unwrap())); + FileSys::StorageId id; + if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::NandUser) { + id = FileSys::StorageId::NandUser; + } else if (parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardSystem || + parameters.save_data_space_id == FileSys::SaveDataSpaceId::SdCardUser) { + id = FileSys::StorageId::SdCard; + } else { + id = FileSys::StorageId::NandSystem; + } + + IFileSystem filesystem(std::move(dir.Unwrap()), SizeGetter::FromStorageId(fsc, id)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -811,30 +862,31 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space)); + rb.PushIpcInterface<ISaveDataInfoReader>(std::make_shared<ISaveDataInfoReader>(space, fsc)); } -void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_FS, "(STUBBED) called"); +void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + log_mode = rp.PopEnum<LogMode>(); - enum class LogMode : u32 { - Off, - Log, - RedirectToSdCard, - LogToSdCard = Log | RedirectToSdCard, - }; + LOG_DEBUG(Service_FS, "called, log_mode={:08X}", static_cast<u32>(log_mode)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); - // Given we always want to receive logging information, - // we always specify logging as enabled. IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.PushEnum(LogMode::Log); + rb.PushEnum(log_mode); } void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called"); - auto romfs = OpenRomFSCurrentProcess(); + auto romfs = fsc.OpenRomFSCurrentProcess(); if (romfs.Failed()) { // TODO (bunnei): Find the right error code to use here LOG_CRITICAL(Service_FS, "no file system interface available!"); @@ -859,7 +911,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}", static_cast<u8>(storage_id), unknown, title_id); - auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); + auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); if (data.Failed()) { const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id); @@ -902,4 +954,26 @@ void FSP_SRV::OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ct rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND); } +void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) { + const auto raw = ctx.ReadBuffer(); + auto log = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(raw.data()), raw.size()); + + LOG_DEBUG(Service_FS, "called, log='{}'", log); + + reporter.SaveFilesystemAccessReport(log_mode, std::move(log)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void FSP_SRV::GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_FS, "called"); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(AccessLogVersion::Latest); + rb.Push(access_log_program_index); +} + } // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index d7572ba7a..d52b55999 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -7,15 +7,32 @@ #include <memory> #include "core/hle/service/service.h" +namespace Core { +class Reporter; +} + namespace FileSys { class FileSystemBackend; } namespace Service::FileSystem { +enum class AccessLogVersion : u32 { + V7_0_0 = 2, + + Latest = V7_0_0, +}; + +enum class LogMode : u32 { + Off, + Log, + RedirectToSdCard, + LogToSdCard = Log | RedirectToSdCard, +}; + class FSP_SRV final : public ServiceFramework<FSP_SRV> { public: - explicit FSP_SRV(); + explicit FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter); ~FSP_SRV() override; private: @@ -26,13 +43,22 @@ private: void OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx); void OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx); void OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx); + void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx); void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); + void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx); + void GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx); + + FileSystemController& fsc; FileSys::VirtualFile romfs; u64 current_process_id = 0; + u32 access_log_program_index = 0; + LogMode log_mode = LogMode::LogToSdCard; + + const Core::Reporter& reporter; }; } // namespace Service::FileSystem diff --git a/src/core/hle/service/friend/errors.h b/src/core/hle/service/friend/errors.h new file mode 100644 index 000000000..b3996e275 --- /dev/null +++ b/src/core/hle/service/friend/errors.h @@ -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}; +} diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index 5100e376c..42b4ee861 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -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" @@ -17,7 +22,7 @@ public: {0, nullptr, "GetCompletionEvent"}, {1, nullptr, "Cancel"}, {10100, nullptr, "GetFriendListIds"}, - {10101, nullptr, "GetFriendList"}, + {10101, &IFriendService::GetFriendList, "GetFriendList"}, {10102, nullptr, "UpdateFriendInfo"}, {10110, nullptr, "GetFriendProfileImage"}, {10200, nullptr, "SendFriendRequestForApplication"}, @@ -94,6 +99,23 @@ public: } private: + enum class PresenceFilter : u32 { + None = 0, + Online = 1, + OnlinePlay = 2, + OnlineOrOnlinePlay = 3, + }; + + struct SizedFriendFilter { + PresenceFilter presence; + u8 is_favorite; + u8 same_app; + u8 same_app_played; + u8 arbitary_app_played; + u64 group_id; + }; + static_assert(sizeof(SizedFriendFilter) == 0x10, "SizedFriendFilter is an invalid size"); + void DeclareCloseOnlinePlaySession(Kernel::HLERequestContext& ctx) { // Stub used by Splatoon 2 LOG_WARNING(Service_ACC, "(STUBBED) called"); @@ -107,6 +129,118 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + + void GetFriendList(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto friend_offset = rp.Pop<u32>(); + const auto uuid = rp.PopRaw<Common::UUID>(); + [[maybe_unused]] const auto filter = rp.PopRaw<SizedFriendFilter>(); + const auto pid = rp.Pop<u64>(); + LOG_WARNING(Service_ACC, "(STUBBED) called, offset={}, uuid={}, pid={}", friend_offset, + uuid.Format(), pid); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + + rb.Push<u32>(0); // Friend count + // TODO(ogniK): Return a buffer of u64s which are the "NetworkServiceAccountId" + } +}; + +class INotificationService final : public ServiceFramework<INotificationService> { +public: + INotificationService(Common::UUID uuid, Core::System& system) + : 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); + + notification_event = Kernel::WritableEvent::CreateEventPair( + system.Kernel(), Kernel::ResetType::Manual, "INotificationService:NotifyEvent"); + } + +private: + void GetEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ACC, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + 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) { @@ -116,18 +250,29 @@ void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ACC, "called"); } -Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) - : ServiceFramework(name), module(std::move(module)) {} +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, system); +} + +Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name) + : ServiceFramework(name), module(std::move(module)), system(system) {} Module::Interface::~Interface() = default; -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { auto module = std::make_shared<Module>(); - std::make_shared<Friend>(module, "friend:a")->InstallAsService(service_manager); - std::make_shared<Friend>(module, "friend:m")->InstallAsService(service_manager); - std::make_shared<Friend>(module, "friend:s")->InstallAsService(service_manager); - std::make_shared<Friend>(module, "friend:u")->InstallAsService(service_manager); - std::make_shared<Friend>(module, "friend:v")->InstallAsService(service_manager); + std::make_shared<Friend>(module, system, "friend:a")->InstallAsService(service_manager); + std::make_shared<Friend>(module, system, "friend:m")->InstallAsService(service_manager); + std::make_shared<Friend>(module, system, "friend:s")->InstallAsService(service_manager); + std::make_shared<Friend>(module, system, "friend:u")->InstallAsService(service_manager); + std::make_shared<Friend>(module, system, "friend:v")->InstallAsService(service_manager); } } // namespace Service::Friend diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h index e762840cb..24f3fc969 100644 --- a/src/core/hle/service/friend/friend.h +++ b/src/core/hle/service/friend/friend.h @@ -6,23 +6,29 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Friend { class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, const char* name); + explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name); ~Interface() override; void CreateFriendService(Kernel::HLERequestContext& ctx); + void CreateNotificationService(Kernel::HLERequestContext& ctx); protected: std::shared_ptr<Module> module; + Core::System& system; }; }; /// Registers all Friend services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::Friend diff --git a/src/core/hle/service/friend/interface.cpp b/src/core/hle/service/friend/interface.cpp index 5a6840af5..58155f652 100644 --- a/src/core/hle/service/friend/interface.cpp +++ b/src/core/hle/service/friend/interface.cpp @@ -6,11 +6,11 @@ namespace Service::Friend { -Friend::Friend(std::shared_ptr<Module> module, const char* name) - : Interface(std::move(module), name) { +Friend::Friend(std::shared_ptr<Module> module, Core::System& system, const char* name) + : Interface(std::move(module), system, name) { static const FunctionInfo functions[] = { {0, &Friend::CreateFriendService, "CreateFriendService"}, - {1, nullptr, "CreateNotificationService"}, + {1, &Friend::CreateNotificationService, "CreateNotificationService"}, {2, nullptr, "CreateDaemonSuspendSessionService"}, }; RegisterHandlers(functions); diff --git a/src/core/hle/service/friend/interface.h b/src/core/hle/service/friend/interface.h index 1963def39..465a35770 100644 --- a/src/core/hle/service/friend/interface.h +++ b/src/core/hle/service/friend/interface.h @@ -10,7 +10,7 @@ namespace Service::Friend { class Friend final : public Module::Interface { public: - explicit Friend(std::shared_ptr<Module> module, const char* name); + explicit Friend(std::shared_ptr<Module> module, Core::System& system, const char* name); ~Friend() override; }; diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp new file mode 100644 index 000000000..b591ce31b --- /dev/null +++ b/src/core/hle/service/glue/arp.cpp @@ -0,0 +1,297 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> + +#include "common/logging/log.h" +#include "core/file_sys/control_metadata.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" +#include "core/hle/service/glue/arp.h" +#include "core/hle/service/glue/errors.h" +#include "core/hle/service/glue/manager.h" +#include "core/hle/service/service.h" + +namespace Service::Glue { + +namespace { +std::optional<u64> GetTitleIDForProcessID(const Core::System& system, u64 process_id) { + 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()) { + return std::nullopt; + } + + return (*iter)->GetTitleID(); +} +} // Anonymous namespace + +ARP_R::ARP_R(const Core::System& system, const ARPManager& manager) + : ServiceFramework{"arp:r"}, system(system), manager(manager) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &ARP_R::GetApplicationLaunchProperty, "GetApplicationLaunchProperty"}, + {1, &ARP_R::GetApplicationLaunchPropertyWithApplicationId, "GetApplicationLaunchPropertyWithApplicationId"}, + {2, &ARP_R::GetApplicationControlProperty, "GetApplicationControlProperty"}, + {3, &ARP_R::GetApplicationControlPropertyWithApplicationId, "GetApplicationControlPropertyWithApplicationId"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +ARP_R::~ARP_R() = default; + +void ARP_R::GetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto process_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id); + + const auto title_id = GetTitleIDForProcessID(system, process_id); + if (!title_id.has_value()) { + LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_NOT_REGISTERED); + return; + } + + const auto res = manager.GetLaunchProperty(*title_id); + + if (res.Failed()) { + LOG_ERROR(Service_ARP, "Failed to get launch property!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(*res); +} + +void ARP_R::GetApplicationLaunchPropertyWithApplicationId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id); + + const auto res = manager.GetLaunchProperty(title_id); + + if (res.Failed()) { + LOG_ERROR(Service_ARP, "Failed to get launch property!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(*res); +} + +void ARP_R::GetApplicationControlProperty(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto process_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id); + + const auto title_id = GetTitleIDForProcessID(system, process_id); + if (!title_id.has_value()) { + LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_NOT_REGISTERED); + return; + } + + const auto res = manager.GetControlProperty(*title_id); + + if (res.Failed()) { + LOG_ERROR(Service_ARP, "Failed to get control property!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + ctx.WriteBuffer(*res); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void ARP_R::GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id); + + const auto res = manager.GetControlProperty(title_id); + + if (res.Failed()) { + LOG_ERROR(Service_ARP, "Failed to get control property!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res.Code()); + return; + } + + ctx.WriteBuffer(*res); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +class IRegistrar final : public ServiceFramework<IRegistrar> { + friend class ARP_W; + +public: + explicit IRegistrar( + std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issuer) + : ServiceFramework{"IRegistrar"}, issue_process_id(std::move(issuer)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IRegistrar::Issue, "Issue"}, + {1, &IRegistrar::SetApplicationLaunchProperty, "SetApplicationLaunchProperty"}, + {2, &IRegistrar::SetApplicationControlProperty, "SetApplicationControlProperty"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Issue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto process_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id); + + if (process_id == 0) { + LOG_ERROR(Service_ARP, "Must have non-zero process ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_PROCESS_ID); + return; + } + + if (issued) { + LOG_ERROR(Service_ARP, + "Attempted to issue registrar, but registrar is already issued!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_ACCESS); + return; + } + + issue_process_id(process_id, launch, std::move(control)); + issued = true; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ARP, "called"); + + if (issued) { + LOG_ERROR( + Service_ARP, + "Attempted to set application launch property, but registrar is already issued!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_ACCESS); + return; + } + + IPC::RequestParser rp{ctx}; + launch = rp.PopRaw<ApplicationLaunchProperty>(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetApplicationControlProperty(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ARP, "called"); + + if (issued) { + LOG_ERROR( + Service_ARP, + "Attempted to set application control property, but registrar is already issued!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_ACCESS); + return; + } + + control = ctx.ReadBuffer(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issue_process_id; + bool issued = false; + ApplicationLaunchProperty launch; + std::vector<u8> control; +}; + +ARP_W::ARP_W(const Core::System& system, ARPManager& manager) + : ServiceFramework{"arp:w"}, system(system), manager(manager) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"}, + {1, &ARP_W::DeleteProperties, "DeleteProperties"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +ARP_W::~ARP_W() = default; + +void ARP_W::AcquireRegistrar(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ARP, "called"); + + registrar = std::make_shared<IRegistrar>( + [this](u64 process_id, ApplicationLaunchProperty launch, std::vector<u8> control) { + const auto res = GetTitleIDForProcessID(system, process_id); + if (!res.has_value()) { + return ERR_NOT_REGISTERED; + } + + return manager.Register(*res, launch, std::move(control)); + }); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(registrar); +} + +void ARP_W::DeleteProperties(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto process_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id); + + if (process_id == 0) { + LOG_ERROR(Service_ARP, "Must have non-zero process ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_INVALID_PROCESS_ID); + return; + } + + const auto title_id = GetTitleIDForProcessID(system, process_id); + + if (!title_id.has_value()) { + LOG_ERROR(Service_ARP, "No title ID for process ID!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_NOT_REGISTERED); + return; + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(manager.Unregister(*title_id)); +} + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/arp.h b/src/core/hle/service/glue/arp.h new file mode 100644 index 000000000..d5f8a7e7a --- /dev/null +++ b/src/core/hle/service/glue/arp.h @@ -0,0 +1,43 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::Glue { + +class ARPManager; +class IRegistrar; + +class ARP_R final : public ServiceFramework<ARP_R> { +public: + explicit ARP_R(const Core::System& system, const ARPManager& manager); + ~ARP_R() override; + +private: + void GetApplicationLaunchProperty(Kernel::HLERequestContext& ctx); + void GetApplicationLaunchPropertyWithApplicationId(Kernel::HLERequestContext& ctx); + void GetApplicationControlProperty(Kernel::HLERequestContext& ctx); + void GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx); + + const Core::System& system; + const ARPManager& manager; +}; + +class ARP_W final : public ServiceFramework<ARP_W> { +public: + explicit ARP_W(const Core::System& system, ARPManager& manager); + ~ARP_W() override; + +private: + void AcquireRegistrar(Kernel::HLERequestContext& ctx); + void DeleteProperties(Kernel::HLERequestContext& ctx); + + const Core::System& system; + ARPManager& manager; + std::shared_ptr<IRegistrar> registrar; +}; + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/bgtc.cpp b/src/core/hle/service/glue/bgtc.cpp new file mode 100644 index 000000000..cd89d088f --- /dev/null +++ b/src/core/hle/service/glue/bgtc.cpp @@ -0,0 +1,50 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/glue/bgtc.h" + +namespace Service::Glue { + +BGTC_T::BGTC_T() : ServiceFramework{"bgtc:t"} { + // clang-format off + static const FunctionInfo functions[] = { + {1, nullptr, "NotifyTaskStarting"}, + {2, nullptr, "NotifyTaskFinished"}, + {3, nullptr, "GetTriggerEvent"}, + {4, nullptr, "IsInHalfAwake"}, + {5, nullptr, "NotifyClientName"}, + {6, nullptr, "IsInFullAwake"}, + {11, nullptr, "ScheduleTask"}, + {12, nullptr, "GetScheduledTaskInterval"}, + {13, nullptr, "UnscheduleTask"}, + {14, nullptr, "GetScheduleEvent"}, + {15, nullptr, "SchedulePeriodicTask"}, + {101, nullptr, "GetOperationMode"}, + {102, nullptr, "WillDisconnectNetworkWhenEnteringSleep"}, + {103, nullptr, "WillStayHalfAwakeInsteadSleep"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +BGTC_T::~BGTC_T() = default; + +BGTC_SC::BGTC_SC() : ServiceFramework{"bgtc:sc"} { + // clang-format off + static const FunctionInfo functions[] = { + {1, nullptr, "GetState"}, + {2, nullptr, "GetStateChangedEvent"}, + {3, nullptr, "NotifyEnteringHalfAwake"}, + {4, nullptr, "NotifyLeavingHalfAwake"}, + {5, nullptr, "SetIsUsingSleepUnsupportedDevices"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +BGTC_SC::~BGTC_SC() = default; + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/bgtc.h b/src/core/hle/service/glue/bgtc.h new file mode 100644 index 000000000..81844f03e --- /dev/null +++ b/src/core/hle/service/glue/bgtc.h @@ -0,0 +1,23 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::Glue { + +class BGTC_T final : public ServiceFramework<BGTC_T> { +public: + BGTC_T(); + ~BGTC_T() override; +}; + +class BGTC_SC final : public ServiceFramework<BGTC_SC> { +public: + BGTC_SC(); + ~BGTC_SC() override; +}; + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/errors.h b/src/core/hle/service/glue/errors.h new file mode 100644 index 000000000..c2874c585 --- /dev/null +++ b/src/core/hle/service/glue/errors.h @@ -0,0 +1,16 @@ +// 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::Glue { + +constexpr ResultCode ERR_INVALID_RESOURCE{ErrorModule::ARP, 0x1E}; +constexpr ResultCode ERR_INVALID_PROCESS_ID{ErrorModule::ARP, 0x1F}; +constexpr ResultCode ERR_INVALID_ACCESS{ErrorModule::ARP, 0x2A}; +constexpr ResultCode ERR_NOT_REGISTERED{ErrorModule::ARP, 0x66}; + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/glue.cpp b/src/core/hle/service/glue/glue.cpp new file mode 100644 index 000000000..c728e815c --- /dev/null +++ b/src/core/hle/service/glue/glue.cpp @@ -0,0 +1,25 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include "core/core.h" +#include "core/hle/service/glue/arp.h" +#include "core/hle/service/glue/bgtc.h" +#include "core/hle/service/glue/glue.h" + +namespace Service::Glue { + +void InstallInterfaces(Core::System& system) { + // ARP + std::make_shared<ARP_R>(system, system.GetARPManager()) + ->InstallAsService(system.ServiceManager()); + std::make_shared<ARP_W>(system, system.GetARPManager()) + ->InstallAsService(system.ServiceManager()); + + // BackGround Task Controller + std::make_shared<BGTC_T>()->InstallAsService(system.ServiceManager()); + std::make_shared<BGTC_SC>()->InstallAsService(system.ServiceManager()); +} + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/glue.h b/src/core/hle/service/glue/glue.h new file mode 100644 index 000000000..112cd238b --- /dev/null +++ b/src/core/hle/service/glue/glue.h @@ -0,0 +1,16 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Core { +class System; +} // namespace Core + +namespace Service::Glue { + +/// Registers all Glue services with the specified service manager. +void InstallInterfaces(Core::System& system); + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/manager.cpp b/src/core/hle/service/glue/manager.cpp new file mode 100644 index 000000000..6da52d2d6 --- /dev/null +++ b/src/core/hle/service/glue/manager.cpp @@ -0,0 +1,78 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/glue/errors.h" +#include "core/hle/service/glue/manager.h" + +namespace Service::Glue { + +struct ARPManager::MapEntry { + ApplicationLaunchProperty launch; + std::vector<u8> control; +}; + +ARPManager::ARPManager() = default; + +ARPManager::~ARPManager() = default; + +ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id) const { + if (title_id == 0) { + return ERR_INVALID_PROCESS_ID; + } + + const auto iter = entries.find(title_id); + if (iter == entries.end()) { + return ERR_NOT_REGISTERED; + } + + return MakeResult<ApplicationLaunchProperty>(iter->second.launch); +} + +ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const { + if (title_id == 0) { + return ERR_INVALID_PROCESS_ID; + } + + const auto iter = entries.find(title_id); + if (iter == entries.end()) { + return ERR_NOT_REGISTERED; + } + + return MakeResult<std::vector<u8>>(iter->second.control); +} + +ResultCode ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch, + std::vector<u8> control) { + if (title_id == 0) { + return ERR_INVALID_PROCESS_ID; + } + + const auto iter = entries.find(title_id); + if (iter != entries.end()) { + return ERR_INVALID_ACCESS; + } + + entries.insert_or_assign(title_id, MapEntry{launch, std::move(control)}); + return RESULT_SUCCESS; +} + +ResultCode ARPManager::Unregister(u64 title_id) { + if (title_id == 0) { + return ERR_INVALID_PROCESS_ID; + } + + const auto iter = entries.find(title_id); + if (iter == entries.end()) { + return ERR_NOT_REGISTERED; + } + + entries.erase(iter); + return RESULT_SUCCESS; +} + +void ARPManager::ResetAll() { + entries.clear(); +} + +} // namespace Service::Glue diff --git a/src/core/hle/service/glue/manager.h b/src/core/hle/service/glue/manager.h new file mode 100644 index 000000000..a7f5ce3ee --- /dev/null +++ b/src/core/hle/service/glue/manager.h @@ -0,0 +1,63 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <map> +#include <vector> +#include "common/common_types.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/romfs_factory.h" +#include "core/hle/result.h" + +namespace Service::Glue { + +struct ApplicationLaunchProperty { + u64 title_id; + u32 version; + FileSys::StorageId base_game_storage_id; + FileSys::StorageId update_storage_id; + u8 program_index; + u8 reserved; +}; +static_assert(sizeof(ApplicationLaunchProperty) == 0x10, + "ApplicationLaunchProperty has incorrect size."); + +// A class to manage state related to the arp:w and arp:r services, specifically the registration +// and unregistration of launch and control properties. +class ARPManager { +public: + ARPManager(); + ~ARPManager(); + + // Returns the ApplicationLaunchProperty corresponding to the provided title ID if it was + // previously registered, otherwise ERR_NOT_REGISTERED if it was never registered or + // ERR_INVALID_PROCESS_ID if the title ID is 0. + ResultVal<ApplicationLaunchProperty> GetLaunchProperty(u64 title_id) const; + + // Returns a vector of the raw bytes of NACP data (necessarily 0x4000 in size) corresponding to + // the provided title ID if it was previously registered, otherwise ERR_NOT_REGISTERED if it was + // never registered or ERR_INVALID_PROCESS_ID if the title ID is 0. + ResultVal<std::vector<u8>> GetControlProperty(u64 title_id) const; + + // Adds a new entry to the internal database with the provided parameters, returning + // ERR_INVALID_ACCESS if attempting to re-register a title ID without an intermediate Unregister + // step, and ERR_INVALID_PROCESS_ID if the title ID is 0. + ResultCode Register(u64 title_id, ApplicationLaunchProperty launch, std::vector<u8> control); + + // Removes the registration for the provided title ID from the database, returning + // ERR_NOT_REGISTERED if it doesn't exist in the database and ERR_INVALID_PROCESS_ID if the + // title ID is 0. + ResultCode Unregister(u64 title_id); + + // Removes all entries from the database, always succeeds. Should only be used when resetting + // system state. + void ResetAll(); + +private: + struct MapEntry; + std::map<u64, MapEntry> entries; +}; + +} // namespace Service::Glue diff --git a/src/core/hle/service/hid/controllers/controller_base.cpp b/src/core/hle/service/hid/controllers/controller_base.cpp index 0993a7815..8091db9d7 100644 --- a/src/core/hle/service/hid/controllers/controller_base.cpp +++ b/src/core/hle/service/hid/controllers/controller_base.cpp @@ -6,7 +6,7 @@ namespace Service::HID { -ControllerBase::ControllerBase() = default; +ControllerBase::ControllerBase(Core::System& system) : system(system) {} ControllerBase::~ControllerBase() = default; void ControllerBase::ActivateController() { diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h index 5e5097a03..8bc69c372 100644 --- a/src/core/hle/service/hid/controllers/controller_base.h +++ b/src/core/hle/service/hid/controllers/controller_base.h @@ -11,10 +11,14 @@ namespace Core::Timing { class CoreTiming; } +namespace Core { +class System; +} + namespace Service::HID { class ControllerBase { public: - ControllerBase(); + explicit ControllerBase(Core::System& system); virtual ~ControllerBase(); // Called when the controller is initialized @@ -46,5 +50,7 @@ protected: s64_le entry_count; }; static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size"); + + Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp index c5c2e032a..8e8263f5b 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.cpp +++ b/src/core/hle/service/hid/controllers/debug_pad.cpp @@ -14,7 +14,8 @@ constexpr s32 HID_JOYSTICK_MAX = 0x7fff; constexpr s32 HID_JOYSTICK_MIN = -0x7fff; enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right }; -Controller_DebugPad::Controller_DebugPad() = default; +Controller_DebugPad::Controller_DebugPad(Core::System& system) + : ControllerBase(system), system(system) {} Controller_DebugPad::~Controller_DebugPad() = default; void Controller_DebugPad::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h index e584b92ec..6c4de817e 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.h +++ b/src/core/hle/service/hid/controllers/debug_pad.h @@ -16,7 +16,7 @@ namespace Service::HID { class Controller_DebugPad final : public ControllerBase { public: - Controller_DebugPad(); + explicit Controller_DebugPad(Core::System& system); ~Controller_DebugPad() override; // Called when the controller is initialized @@ -89,5 +89,6 @@ private: buttons; std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> analogs; + Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp index a179252e3..80da0a0d3 100644 --- a/src/core/hle/service/hid/controllers/gesture.cpp +++ b/src/core/hle/service/hid/controllers/gesture.cpp @@ -10,7 +10,8 @@ namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00; -Controller_Gesture::Controller_Gesture() = default; +Controller_Gesture::Controller_Gesture(Core::System& system) + : ControllerBase(system), system(system) {} Controller_Gesture::~Controller_Gesture() = default; void Controller_Gesture::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h index f305fe90f..396897527 100644 --- a/src/core/hle/service/hid/controllers/gesture.h +++ b/src/core/hle/service/hid/controllers/gesture.h @@ -12,7 +12,7 @@ namespace Service::HID { class Controller_Gesture final : public ControllerBase { public: - Controller_Gesture(); + explicit Controller_Gesture(Core::System& system); ~Controller_Gesture() override; // Called when the controller is initialized @@ -59,5 +59,6 @@ private: std::array<GestureState, 17> gesture_states; }; SharedMemory shared_memory{}; + Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp index 92d7bfb52..e587b2e15 100644 --- a/src/core/hle/service/hid/controllers/keyboard.cpp +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -12,7 +12,8 @@ namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800; constexpr u8 KEYS_PER_BYTE = 8; -Controller_Keyboard::Controller_Keyboard() = default; +Controller_Keyboard::Controller_Keyboard(Core::System& system) + : ControllerBase(system), system(system) {} Controller_Keyboard::~Controller_Keyboard() = default; void Controller_Keyboard::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h index 73cd2c7bb..ef586f7eb 100644 --- a/src/core/hle/service/hid/controllers/keyboard.h +++ b/src/core/hle/service/hid/controllers/keyboard.h @@ -15,7 +15,7 @@ namespace Service::HID { class Controller_Keyboard final : public ControllerBase { public: - Controller_Keyboard(); + explicit Controller_Keyboard(Core::System& system); ~Controller_Keyboard() override; // Called when the controller is initialized @@ -53,5 +53,6 @@ private: keyboard_keys; std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardMods> keyboard_mods; + Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp index 11ab096d9..88f2ca4c1 100644 --- a/src/core/hle/service/hid/controllers/mouse.cpp +++ b/src/core/hle/service/hid/controllers/mouse.cpp @@ -11,7 +11,7 @@ namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400; -Controller_Mouse::Controller_Mouse() = default; +Controller_Mouse::Controller_Mouse(Core::System& system) : ControllerBase(system), system(system) {} Controller_Mouse::~Controller_Mouse() = default; void Controller_Mouse::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h index 9d46eecbe..df2da6ae3 100644 --- a/src/core/hle/service/hid/controllers/mouse.h +++ b/src/core/hle/service/hid/controllers/mouse.h @@ -14,7 +14,7 @@ namespace Service::HID { class Controller_Mouse final : public ControllerBase { public: - Controller_Mouse(); + explicit Controller_Mouse(Core::System& system); ~Controller_Mouse() override; // Called when the controller is initialized @@ -53,5 +53,6 @@ private: std::unique_ptr<Input::MouseDevice> mouse_device; std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeMouseButton::NumMouseButtons> mouse_button_devices; + Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index fdd6d79a2..44b668fbf 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -93,7 +93,7 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) { }; } -Controller_NPad::Controller_NPad() = default; +Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {} Controller_NPad::~Controller_NPad() = default; void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { @@ -168,9 +168,11 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { } void Controller_NPad::OnInit() { - auto& kernel = Core::System::GetInstance().Kernel(); - styleset_changed_event = Kernel::WritableEvent::CreateEventPair( - kernel, Kernel::ResetType::Automatic, "npad:NpadStyleSetChanged"); + auto& kernel = system.Kernel(); + for (std::size_t i = 0; i < styleset_changed_events.size(); i++) { + styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::Automatic, fmt::format("npad:NpadStyleSetChanged_{}", i)); + } if (!IsControllerActivated()) { return; @@ -453,7 +455,7 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) { had_controller_update = true; } if (had_controller_update) { - styleset_changed_event.writable->Signal(); + styleset_changed_events[i].writable->Signal(); } } } @@ -468,7 +470,6 @@ std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const { } void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) { - styleset_changed_event.writable->Signal(); hold_type = joy_hold_type; } @@ -479,7 +480,10 @@ Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const { void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) { const std::size_t npad_index = NPadIdToIndex(npad_id); ASSERT(npad_index < shared_memory_entries.size()); - shared_memory_entries[npad_index].pad_assignment = assignment_mode; + if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) { + styleset_changed_events[npad_index].writable->Signal(); + shared_memory_entries[npad_index].pad_assignment = assignment_mode; + } } void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, @@ -498,11 +502,13 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, last_processed_vibration = vibrations.back(); } -Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent() const { +Kernel::SharedPtr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent( + u32 npad_id) const { // TODO(ogniK): Figure out the best time to signal this event. This event seems that it should // be signalled at least once, and signaled after a new controller is connected? - styleset_changed_event.writable->Signal(); - return styleset_changed_event.readable; + const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)]; + styleset_event.writable->Signal(); + return styleset_event.readable; } Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { @@ -548,6 +554,37 @@ void Controller_NPad::DisconnectNPad(u32 npad_id) { connected_controllers[NPadIdToIndex(npad_id)].is_connected = false; } +void Controller_NPad::StartLRAssignmentMode() { + // Nothing internally is used for lr assignment mode. Since we have the ability to set the + // controller types from boot, it doesn't really matter about showing a selection screen + is_in_lr_assignment_mode = true; +} + +void Controller_NPad::StopLRAssignmentMode() { + is_in_lr_assignment_mode = false; +} + +bool Controller_NPad::SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2) { + if (npad_id_1 == NPAD_HANDHELD || npad_id_2 == NPAD_HANDHELD || npad_id_1 == NPAD_UNKNOWN || + npad_id_2 == NPAD_UNKNOWN) { + return true; + } + const auto npad_index_1 = NPadIdToIndex(npad_id_1); + const auto npad_index_2 = NPadIdToIndex(npad_id_2); + + if (!IsControllerSupported(connected_controllers[npad_index_1].type) || + !IsControllerSupported(connected_controllers[npad_index_2].type)) { + return false; + } + + std::swap(connected_controllers[npad_index_1].type, connected_controllers[npad_index_2].type); + + InitNewlyAddedControler(npad_index_1); + InitNewlyAddedControler(npad_index_2); + + return true; +} + bool Controller_NPad::IsControllerSupported(NPadControllerType controller) { if (controller == NPadControllerType::Handheld) { // Handheld is not even a supported type, lets stop here @@ -605,10 +642,15 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) { return LedPattern{0, 0, 0, 0}; }; } + void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { can_controllers_vibrate = can_vibrate; } +bool Controller_NPad::IsVibrationEnabled() const { + return can_controllers_vibrate; +} + void Controller_NPad::ClearAllConnectedControllers() { for (auto& controller : connected_controllers) { if (controller.is_connected && controller.type != NPadControllerType::None) { @@ -617,6 +659,7 @@ void Controller_NPad::ClearAllConnectedControllers() { } } } + void Controller_NPad::DisconnectAllConnectedControllers() { std::for_each(connected_controllers.begin(), connected_controllers.end(), [](ControllerHolder& controller) { controller.is_connected = false; }); diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 4ff50b3cd..1bc3d55d6 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -20,7 +20,7 @@ constexpr u32 NPAD_UNKNOWN = 16; // TODO(ogniK): What is this? class Controller_NPad final : public ControllerBase { public: - Controller_NPad(); + explicit Controller_NPad(Core::System& system); ~Controller_NPad() override; // Called when the controller is initialized @@ -109,7 +109,7 @@ public: void VibrateController(const std::vector<u32>& controller_ids, const std::vector<Vibration>& vibrations); - Kernel::SharedPtr<Kernel::ReadableEvent> GetStyleSetChangedEvent() const; + Kernel::SharedPtr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const; Vibration GetLastVibration() const; void AddNewController(NPadControllerType controller); @@ -119,11 +119,16 @@ public: void DisconnectNPad(u32 npad_id); LedPattern GetLedPattern(u32 npad_id); void SetVibrationEnabled(bool can_vibrate); + bool IsVibrationEnabled() const; void ClearAllConnectedControllers(); void DisconnectAllConnectedControllers(); void ConnectAllDisconnectedControllers(); void ClearAllControllers(); + void StartLRAssignmentMode(); + void StopLRAssignmentMode(); + bool SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2); + // Logical OR for all buttons presses on all controllers // Specifically for cheat engine and other features. u32 GetAndResetPressState(); @@ -310,7 +315,8 @@ private: sticks; std::vector<u32> supported_npad_id_types{}; NpadHoldType hold_type{NpadHoldType::Vertical}; - Kernel::EventPair styleset_changed_event; + // Each controller should have their own styleset changed event + std::array<Kernel::EventPair, 10> styleset_changed_events; Vibration last_processed_vibration{}; std::array<ControllerHolder, 10> connected_controllers{}; bool can_controllers_vibrate{true}; @@ -321,5 +327,7 @@ private: void RequestPadStateUpdate(u32 npad_id); std::array<ControllerPad, 10> npad_pad_states{}; bool IsControllerSupported(NPadControllerType controller); + bool is_in_lr_assignment_mode{false}; + Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp index 946948f5e..9b829341e 100644 --- a/src/core/hle/service/hid/controllers/stubbed.cpp +++ b/src/core/hle/service/hid/controllers/stubbed.cpp @@ -9,7 +9,8 @@ namespace Service::HID { -Controller_Stubbed::Controller_Stubbed() = default; +Controller_Stubbed::Controller_Stubbed(Core::System& system) + : ControllerBase(system), system(system) {} Controller_Stubbed::~Controller_Stubbed() = default; void Controller_Stubbed::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/stubbed.h b/src/core/hle/service/hid/controllers/stubbed.h index 24469f03e..37d7d8538 100644 --- a/src/core/hle/service/hid/controllers/stubbed.h +++ b/src/core/hle/service/hid/controllers/stubbed.h @@ -10,7 +10,7 @@ namespace Service::HID { class Controller_Stubbed final : public ControllerBase { public: - Controller_Stubbed(); + explicit Controller_Stubbed(Core::System& system); ~Controller_Stubbed() override; // Called when the controller is initialized @@ -30,5 +30,6 @@ public: private: bool smart_update{}; std::size_t common_offset{}; + Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp index 1a8445a43..25912fd69 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.cpp +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -13,7 +13,8 @@ namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400; -Controller_Touchscreen::Controller_Touchscreen() = default; +Controller_Touchscreen::Controller_Touchscreen(Core::System& system) + : ControllerBase(system), system(system) {} Controller_Touchscreen::~Controller_Touchscreen() = default; void Controller_Touchscreen::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index 76fc340e9..3429c84db 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -14,7 +14,7 @@ namespace Service::HID { class Controller_Touchscreen final : public ControllerBase { public: - Controller_Touchscreen(); + explicit Controller_Touchscreen(Core::System& system); ~Controller_Touchscreen() override; // Called when the controller is initialized @@ -69,5 +69,6 @@ private: TouchScreenSharedMemory shared_memory{}; std::unique_ptr<Input::TouchDevice> touch_device; s64_le last_touch{}; + Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp index 1a9da9576..1bce044b4 100644 --- a/src/core/hle/service/hid/controllers/xpad.cpp +++ b/src/core/hle/service/hid/controllers/xpad.cpp @@ -10,7 +10,7 @@ namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00; -Controller_XPad::Controller_XPad() = default; +Controller_XPad::Controller_XPad(Core::System& system) : ControllerBase(system), system(system) {} Controller_XPad::~Controller_XPad() = default; void Controller_XPad::OnInit() {} diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h index 2864e6617..c445ebec0 100644 --- a/src/core/hle/service/hid/controllers/xpad.h +++ b/src/core/hle/service/hid/controllers/xpad.h @@ -12,7 +12,7 @@ namespace Service::HID { class Controller_XPad final : public ControllerBase { public: - Controller_XPad(); + explicit Controller_XPad(Core::System& system); ~Controller_XPad() override; // Called when the controller is initialized @@ -56,5 +56,6 @@ private: }; static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size"); SharedMemory shared_memory{}; + Core::System& system; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h new file mode 100644 index 000000000..3583642e7 --- /dev/null +++ b/src/core/hle/service/hid/errors.h @@ -0,0 +1,13 @@ +// 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::HID { + +constexpr ResultCode ERR_NPAD_NOT_CONNECTED{ErrorModule::HID, 710}; + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index a4ad95d96..8d76ba746 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -16,6 +16,7 @@ #include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/writable_event.h" +#include "core/hle/service/hid/errors.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/irs.h" #include "core/hle/service/hid/xcd.h" @@ -41,13 +42,14 @@ constexpr s64 accelerometer_update_ticks = static_cast<s64>(Core::Timing::BASE_C constexpr s64 gyroscope_update_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 100); constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; -IAppletResource::IAppletResource() : ServiceFramework("IAppletResource") { +IAppletResource::IAppletResource(Core::System& system) + : ServiceFramework("IAppletResource"), system(system) { static const FunctionInfo functions[] = { {0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"}, }; RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system.Kernel(); shared_mem = Kernel::SharedMemory::Create( kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory"); @@ -73,7 +75,7 @@ IAppletResource::IAppletResource() : ServiceFramework("IAppletResource") { GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000); // Register update callbacks - auto& core_timing = Core::System::GetInstance().CoreTiming(); + auto& core_timing = system.CoreTiming(); pad_update_event = core_timing.RegisterEvent("HID::UpdatePadCallback", [this](u64 userdata, s64 cycles_late) { UpdateControllers(userdata, cycles_late); @@ -95,7 +97,7 @@ void IAppletResource::DeactivateController(HidController controller) { } IAppletResource ::~IAppletResource() { - Core::System::GetInstance().CoreTiming().UnscheduleEvent(pad_update_event, 0); + system.CoreTiming().UnscheduleEvent(pad_update_event, 0); } void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { @@ -107,7 +109,7 @@ void IAppletResource::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { } void IAppletResource::UpdateControllers(u64 userdata, s64 cycles_late) { - auto& core_timing = Core::System::GetInstance().CoreTiming(); + auto& core_timing = system.CoreTiming(); const bool should_reload = Settings::values.is_device_reload_pending.exchange(false); for (const auto& controller : controllers) { @@ -140,13 +142,13 @@ private: std::shared_ptr<IAppletResource> Hid::GetAppletResource() { if (applet_resource == nullptr) { - applet_resource = std::make_shared<IAppletResource>(); + applet_resource = std::make_shared<IAppletResource>(system); } return applet_resource; } -Hid::Hid() : ServiceFramework("hid") { +Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { // clang-format off static const FunctionInfo functions[] = { {0, &Hid::CreateAppletResource, "CreateAppletResource"}, @@ -202,11 +204,11 @@ Hid::Hid() : ServiceFramework("hid") { {123, nullptr, "SetNpadJoyAssignmentModeSingleByDefault"}, {124, &Hid::SetNpadJoyAssignmentModeDual, "SetNpadJoyAssignmentModeDual"}, {125, &Hid::MergeSingleJoyAsDualJoy, "MergeSingleJoyAsDualJoy"}, - {126, nullptr, "StartLrAssignmentMode"}, - {127, nullptr, "StopLrAssignmentMode"}, + {126, &Hid::StartLrAssignmentMode, "StartLrAssignmentMode"}, + {127, &Hid::StopLrAssignmentMode, "StopLrAssignmentMode"}, {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"}, {129, nullptr, "GetNpadHandheldActivationMode"}, - {130, nullptr, "SwapNpadAssignment"}, + {130, &Hid::SwapNpadAssignment, "SwapNpadAssignment"}, {131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"}, {132, nullptr, "EnableUnintendedHomeButtonInputProtection"}, {133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"}, @@ -215,8 +217,8 @@ Hid::Hid() : ServiceFramework("hid") { {201, &Hid::SendVibrationValue, "SendVibrationValue"}, {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"}, {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"}, - {204, nullptr, "PermitVibration"}, - {205, nullptr, "IsVibrationPermitted"}, + {204, &Hid::PermitVibration, "PermitVibration"}, + {205, &Hid::IsVibrationPermitted, "IsVibrationPermitted"}, {206, &Hid::SendVibrationValues, "SendVibrationValues"}, {207, nullptr, "SendVibrationGcErmCommand"}, {208, nullptr, "GetActualVibrationGcErmCommand"}, @@ -285,7 +287,7 @@ void Hid::CreateAppletResource(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); if (applet_resource == nullptr) { - applet_resource = std::make_shared<IAppletResource>(); + applet_resource = std::make_shared<IAppletResource>(system); } IPC::ResponseBuilder rb{ctx, 2, 0, 1}; @@ -478,7 +480,7 @@ void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad) - .GetStyleSetChangedEvent()); + .GetStyleSetChangedEvent(npad_id)); } void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) { @@ -678,6 +680,27 @@ void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface<IActiveVibrationDeviceList>(); } +void Hid::PermitVibration(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto can_vibrate{rp.Pop<bool>()}; + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetVibrationEnabled(can_vibrate); + + LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push( + applet_resource->GetController<Controller_NPad>(HidController::NPad).IsVibrationEnabled()); +} + void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; @@ -733,6 +756,49 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } +void Hid::StartLrAssignmentMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + controller.StartLRAssignmentMode(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + controller.StopLRAssignmentMode(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto npad_1{rp.Pop<u32>()}; + const auto npad_2{rp.Pop<u32>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, npad_1={}, npad_2={}", + applet_resource_user_id, npad_1, npad_2); + + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + IPC::ResponseBuilder rb{ctx, 2}; + if (controller.SwapNpadAssignment(npad_1, npad_2)) { + rb.Push(RESULT_SUCCESS); + } else { + LOG_ERROR(Service_HID, "Npads are not connected!"); + rb.Push(ERR_NPAD_NOT_CONNECTED); + } +} + class HidDbg final : public ServiceFramework<HidDbg> { public: explicit HidDbg() : ServiceFramework{"hid:dbg"} { @@ -988,14 +1054,14 @@ void ReloadInputDevices() { Settings::values.is_device_reload_pending.store(true); } -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<Hid>()->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared<Hid>(system)->InstallAsService(service_manager); std::make_shared<HidBus>()->InstallAsService(service_manager); std::make_shared<HidDbg>()->InstallAsService(service_manager); std::make_shared<HidSys>()->InstallAsService(service_manager); std::make_shared<HidTmp>()->InstallAsService(service_manager); - std::make_shared<IRS>()->InstallAsService(service_manager); + std::make_shared<IRS>(system)->InstallAsService(service_manager); std::make_shared<IRS_SYS>()->InstallAsService(service_manager); std::make_shared<XCD_SYS>()->InstallAsService(service_manager); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index d3660cad2..35b663679 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -42,7 +42,7 @@ enum class HidController : std::size_t { class IAppletResource final : public ServiceFramework<IAppletResource> { public: - IAppletResource(); + explicit IAppletResource(Core::System& system); ~IAppletResource() override; void ActivateController(HidController controller); @@ -61,7 +61,7 @@ public: private: template <typename T> void MakeController(HidController controller) { - controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>(); + controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>(system); } void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx); @@ -70,6 +70,7 @@ private: Kernel::SharedPtr<Kernel::SharedMemory> shared_mem; Core::Timing::EventType* pad_update_event; + Core::System& system; std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)> controllers{}; @@ -77,7 +78,7 @@ private: class Hid final : public ServiceFramework<Hid> { public: - Hid(); + explicit Hid(Core::System& system); ~Hid() override; std::shared_ptr<IAppletResource> GetAppletResource(); @@ -114,19 +115,25 @@ private: void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx); + void PermitVibration(Kernel::HLERequestContext& ctx); + void IsVibrationPermitted(Kernel::HLERequestContext& ctx); void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); void StopSixAxisSensor(Kernel::HLERequestContext& ctx); void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx); void SetPalmaBoostMode(Kernel::HLERequestContext& ctx); + void StartLrAssignmentMode(Kernel::HLERequestContext& ctx); + void StopLrAssignmentMode(Kernel::HLERequestContext& ctx); + void SwapNpadAssignment(Kernel::HLERequestContext& ctx); std::shared_ptr<IAppletResource> applet_resource; + Core::System& system; }; /// Reload input devices. Used when input configuration changed void ReloadInputDevices(); /// Registers all HID services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::HID diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index 2c4625c99..5e79e2c1a 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -11,7 +11,7 @@ namespace Service::HID { -IRS::IRS() : ServiceFramework{"irs"} { +IRS::IRS(Core::System& system) : ServiceFramework{"irs"}, system(system) { // clang-format off static const FunctionInfo functions[] = { {302, &IRS::ActivateIrsensor, "ActivateIrsensor"}, @@ -37,7 +37,7 @@ IRS::IRS() : ServiceFramework{"irs"} { RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system.Kernel(); shared_mem = Kernel::SharedMemory::Create( kernel, nullptr, 0x8000, Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "IRS:SharedMemory"); @@ -98,7 +98,7 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 5}; rb.Push(RESULT_SUCCESS); - rb.PushRaw<u64>(Core::System::GetInstance().CoreTiming().GetTicks()); + rb.PushRaw<u64>(system.CoreTiming().GetTicks()); rb.PushRaw<u32>(0); } diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h index 12de6bfb3..eb4e898dd 100644 --- a/src/core/hle/service/hid/irs.h +++ b/src/core/hle/service/hid/irs.h @@ -15,7 +15,7 @@ namespace Service::HID { class IRS final : public ServiceFramework<IRS> { public: - explicit IRS(); + explicit IRS(Core::System& system); ~IRS() override; private: @@ -39,6 +39,7 @@ private: void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx); Kernel::SharedPtr<Kernel::SharedMemory> shared_mem; const u32 device_handle{0xABCD}; + Core::System& system; }; class IRS_SYS final : public ServiceFramework<IRS_SYS> { diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 5af925515..3164ca26e 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -78,7 +78,7 @@ public: class RelocatableObject final : public ServiceFramework<RelocatableObject> { public: - explicit RelocatableObject() : ServiceFramework{"ldr:ro"} { + explicit RelocatableObject(Core::System& system) : ServiceFramework{"ldr:ro"}, system(system) { // clang-format off static const FunctionInfo functions[] = { {0, &RelocatableObject::LoadNro, "LoadNro"}, @@ -310,7 +310,7 @@ public: if (!IsValidNROHash(hash)) { LOG_ERROR(Service_LDR, "NRO hash is not present in any currently loaded NRRs (hash={})!", - Common::HexArrayToString(hash)); + Common::HexToString(hash)); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERROR_MISSING_NRR_HASH); return; @@ -345,14 +345,16 @@ public: vm_manager .MirrorMemory(*map_address, nro_address, nro_size, Kernel::MemoryState::ModuleCode) .IsSuccess()); - ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess()); + ASSERT(vm_manager.ReprotectRange(nro_address, nro_size, Kernel::VMAPermission::None) + .IsSuccess()); if (bss_size > 0) { ASSERT(vm_manager .MirrorMemory(*map_address + nro_size, bss_address, bss_size, Kernel::MemoryState::ModuleCode) .IsSuccess()); - ASSERT(vm_manager.UnmapRange(bss_address, bss_size).IsSuccess()); + ASSERT(vm_manager.ReprotectRange(bss_address, bss_size, Kernel::VMAPermission::None) + .IsSuccess()); } vm_manager.ReprotectRange(*map_address, header.text_size, @@ -362,9 +364,10 @@ public: vm_manager.ReprotectRange(*map_address + header.rw_offset, header.rw_size, Kernel::VMAPermission::ReadWrite); - Core::System::GetInstance().InvalidateCpuInstructionCaches(); + system.InvalidateCpuInstructionCaches(); - nro.insert_or_assign(*map_address, NROInfo{hash, nro_size + bss_size}); + nro.insert_or_assign(*map_address, + NROInfo{hash, nro_address, nro_size, bss_address, bss_size}); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); @@ -409,11 +412,25 @@ public: } auto& vm_manager = Core::CurrentProcess()->VMManager(); - const auto& nro_size = iter->second.size; + const auto& nro_info = iter->second; - ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess()); + // Unmap the mirrored memory + ASSERT( + vm_manager.UnmapRange(nro_address, nro_info.nro_size + nro_info.bss_size).IsSuccess()); + + // Reprotect the source memory + ASSERT(vm_manager + .ReprotectRange(nro_info.nro_address, nro_info.nro_size, + Kernel::VMAPermission::ReadWrite) + .IsSuccess()); + if (nro_info.bss_size > 0) { + ASSERT(vm_manager + .ReprotectRange(nro_info.bss_address, nro_info.bss_size, + Kernel::VMAPermission::ReadWrite) + .IsSuccess()); + } - Core::System::GetInstance().InvalidateCpuInstructionCaches(); + system.InvalidateCpuInstructionCaches(); nro.erase(iter); IPC::ResponseBuilder rb{ctx, 2}; @@ -473,7 +490,10 @@ private: struct NROInfo { SHA256Hash hash; - u64 size; + VAddr nro_address; + u64 nro_size; + VAddr bss_address; + u64 bss_size; }; bool initialized = false; @@ -496,13 +516,14 @@ private: Common::Is4KBAligned(header.text_size) && Common::Is4KBAligned(header.ro_size) && Common::Is4KBAligned(header.rw_size); } + Core::System& system; }; -void InstallInterfaces(SM::ServiceManager& sm) { +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { std::make_shared<DebugMonitor>()->InstallAsService(sm); std::make_shared<ProcessManager>()->InstallAsService(sm); std::make_shared<Shell>()->InstallAsService(sm); - std::make_shared<RelocatableObject>()->InstallAsService(sm); + std::make_shared<RelocatableObject>(system)->InstallAsService(sm); } } // namespace Service::LDR diff --git a/src/core/hle/service/ldr/ldr.h b/src/core/hle/service/ldr/ldr.h index 412410c4f..7ac8c0b65 100644 --- a/src/core/hle/service/ldr/ldr.h +++ b/src/core/hle/service/ldr/ldr.h @@ -11,6 +11,6 @@ class ServiceManager; namespace Service::LDR { /// Registers all LDR services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::LDR diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index ce84e25ed..0b3923ad9 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp @@ -48,7 +48,7 @@ public: {19, nullptr, "Export"}, {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, {21, &IDatabaseService::GetIndex, "GetIndex"}, - {22, nullptr, "SetInterfaceVersion"}, + {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, {23, nullptr, "Convert"}, }; // clang-format on @@ -350,8 +350,22 @@ private: rb.Push(index); } + void SetInterfaceVersion(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + current_interface_version = rp.PopRaw<u32>(); + + LOG_DEBUG(Service_Mii, "called, interface_version={:08X}", current_interface_version); + + UNIMPLEMENTED_IF(current_interface_version != 1); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + MiiManager db; + u32 current_interface_version = 0; + // Last read offsets of Get functions std::array<u32, 4> offsets{}; }; diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 131b01d62..8d0353075 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp @@ -175,6 +175,10 @@ MiiStoreData ConvertInfoToStoreData(const MiiInfo& info) { } // namespace std::ostream& operator<<(std::ostream& os, Source source) { + if (static_cast<std::size_t>(source) >= SOURCE_NAMES.size()) { + return os << "[UNKNOWN SOURCE]"; + } + os << SOURCE_NAMES.at(static_cast<std::size_t>(source)); return os; } diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index a5cb06f8a..a42c22d44 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -23,9 +23,9 @@ constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP, constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152); } // namespace ErrCodes -Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) - : ServiceFramework(name), module(std::move(module)) { - auto& kernel = Core::System::GetInstance().Kernel(); +Module::Interface::Interface(std::shared_ptr<Module> module, Core::System& system, const char* name) + : ServiceFramework(name), module(std::move(module)), system(system) { + auto& kernel = system.Kernel(); nfc_tag_load = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, "IUser:NFCTagDetected"); } @@ -34,8 +34,8 @@ Module::Interface::~Interface() = default; class IUser final : public ServiceFramework<IUser> { public: - IUser(Module::Interface& nfp_interface) - : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) { + IUser(Module::Interface& nfp_interface, Core::System& system) + : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface), system(system) { static const FunctionInfo functions[] = { {0, &IUser::Initialize, "Initialize"}, {1, &IUser::Finalize, "Finalize"}, @@ -65,7 +65,7 @@ public: }; RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system.Kernel(); deactivate_event = Kernel::WritableEvent::CreateEventPair( kernel, Kernel::ResetType::Automatic, "IUser:DeactivateEvent"); availability_change_event = Kernel::WritableEvent::CreateEventPair( @@ -324,6 +324,7 @@ private: Kernel::EventPair deactivate_event; Kernel::EventPair availability_change_event; const Module::Interface& nfp_interface; + Core::System& system; }; void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) { @@ -331,7 +332,7 @@ void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IUser>(*this); + rb.PushIpcInterface<IUser>(*this, system); } bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) { @@ -353,9 +354,9 @@ const Module::Interface::AmiiboFile& Module::Interface::GetAmiiboBuffer() const return amiibo; } -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { auto module = std::make_shared<Module>(); - std::make_shared<NFP_User>(module)->InstallAsService(service_manager); + std::make_shared<NFP_User>(module, system)->InstallAsService(service_manager); } } // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index a1817e991..9718ef745 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h @@ -16,7 +16,7 @@ class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> module, const char* name); + explicit Interface(std::shared_ptr<Module> module, Core::System& system, const char* name); ~Interface() override; struct ModelInfo { @@ -43,9 +43,10 @@ public: protected: std::shared_ptr<Module> module; + Core::System& system; }; }; -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp index 784a87c1b..298184f17 100644 --- a/src/core/hle/service/nfp/nfp_user.cpp +++ b/src/core/hle/service/nfp/nfp_user.cpp @@ -6,8 +6,8 @@ namespace Service::NFP { -NFP_User::NFP_User(std::shared_ptr<Module> module) - : Module::Interface(std::move(module), "nfp:user") { +NFP_User::NFP_User(std::shared_ptr<Module> module, Core::System& system) + : Module::Interface(std::move(module), system, "nfp:user") { static const FunctionInfo functions[] = { {0, &NFP_User::CreateUserInterface, "CreateUserInterface"}, }; diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h index 65d9aaf48..1686ebf20 100644 --- a/src/core/hle/service/nfp/nfp_user.h +++ b/src/core/hle/service/nfp/nfp_user.h @@ -10,7 +10,7 @@ namespace Service::NFP { class NFP_User final : public Module::Interface { public: - explicit NFP_User(std::shared_ptr<Module> module); + explicit NFP_User(std::shared_ptr<Module> module, Core::System& system); ~NFP_User() override; }; diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 76b12b482..24d1813a7 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -31,7 +31,7 @@ public: class IRequest final : public ServiceFramework<IRequest> { public: - explicit IRequest() : ServiceFramework("IRequest") { + explicit IRequest(Core::System& system) : ServiceFramework("IRequest") { static const FunctionInfo functions[] = { {0, &IRequest::GetRequestState, "GetRequestState"}, {1, &IRequest::GetResult, "GetResult"}, @@ -61,7 +61,7 @@ public: }; RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system.Kernel(); event1 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, "IRequest:Event1"); event2 = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, @@ -130,7 +130,7 @@ public: class IGeneralService final : public ServiceFramework<IGeneralService> { public: - IGeneralService(); + IGeneralService(Core::System& system); private: void GetClientId(Kernel::HLERequestContext& ctx) { @@ -155,7 +155,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IRequest>(); + rb.PushIpcInterface<IRequest>(system); } void RemoveNetworkProfile(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); @@ -198,9 +198,11 @@ private: rb.Push(RESULT_SUCCESS); rb.Push<u8>(0); } + Core::System& system; }; -IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") { +IGeneralService::IGeneralService(Core::System& system) + : ServiceFramework("IGeneralService"), system(system) { static const FunctionInfo functions[] = { {1, &IGeneralService::GetClientId, "GetClientId"}, {2, &IGeneralService::CreateScanRequest, "CreateScanRequest"}, @@ -245,7 +247,8 @@ IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") { class NetworkInterface final : public ServiceFramework<NetworkInterface> { public: - explicit NetworkInterface(const char* name) : ServiceFramework{name} { + explicit NetworkInterface(const char* name, Core::System& system) + : ServiceFramework{name}, system(system) { static const FunctionInfo functions[] = { {4, &NetworkInterface::CreateGeneralServiceOld, "CreateGeneralServiceOld"}, {5, &NetworkInterface::CreateGeneralService, "CreateGeneralService"}, @@ -258,7 +261,7 @@ public: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IGeneralService>(); + rb.PushIpcInterface<IGeneralService>(system); } void CreateGeneralService(Kernel::HLERequestContext& ctx) { @@ -266,14 +269,17 @@ public: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IGeneralService>(); + rb.PushIpcInterface<IGeneralService>(system); } + +private: + Core::System& system; }; -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<NetworkInterface>("nifm:a")->InstallAsService(service_manager); - std::make_shared<NetworkInterface>("nifm:s")->InstallAsService(service_manager); - std::make_shared<NetworkInterface>("nifm:u")->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared<NetworkInterface>("nifm:a", system)->InstallAsService(service_manager); + std::make_shared<NetworkInterface>("nifm:s", system)->InstallAsService(service_manager); + std::make_shared<NetworkInterface>("nifm:u", system)->InstallAsService(service_manager); } } // namespace Service::NIFM diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h index 4616b3b48..6857e18f9 100644 --- a/src/core/hle/service/nifm/nifm.h +++ b/src/core/hle/service/nifm/nifm.h @@ -8,9 +8,13 @@ namespace Service::SM { class ServiceManager; } +namespace Core { +class System; +} + namespace Service::NIFM { /// Registers all NIFM services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::NIFM diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index f319a3ca1..75d414952 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp @@ -126,7 +126,7 @@ public: class IEnsureNetworkClockAvailabilityService final : public ServiceFramework<IEnsureNetworkClockAvailabilityService> { public: - IEnsureNetworkClockAvailabilityService() + explicit IEnsureNetworkClockAvailabilityService(Core::System& system) : ServiceFramework("IEnsureNetworkClockAvailabilityService") { static const FunctionInfo functions[] = { {0, &IEnsureNetworkClockAvailabilityService::StartTask, "StartTask"}, @@ -139,7 +139,7 @@ public: }; RegisterHandlers(functions); - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system.Kernel(); finished_event = Kernel::WritableEvent::CreateEventPair( kernel, Kernel::ResetType::Automatic, "IEnsureNetworkClockAvailabilityService:FinishEvent"); @@ -200,7 +200,7 @@ private: class NTC final : public ServiceFramework<NTC> { public: - explicit NTC() : ServiceFramework{"ntc"} { + explicit NTC(Core::System& system) : ServiceFramework{"ntc"}, system(system) { // clang-format off static const FunctionInfo functions[] = { {0, &NTC::OpenEnsureNetworkClockAvailabilityService, "OpenEnsureNetworkClockAvailabilityService"}, @@ -218,7 +218,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IEnsureNetworkClockAvailabilityService>(); + rb.PushIpcInterface<IEnsureNetworkClockAvailabilityService>(system); } // TODO(ogniK): Do we need these? @@ -235,13 +235,14 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + Core::System& system; }; -void InstallInterfaces(SM::ServiceManager& sm) { +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { std::make_shared<NIM>()->InstallAsService(sm); std::make_shared<NIM_ECA>()->InstallAsService(sm); std::make_shared<NIM_SHP>()->InstallAsService(sm); - std::make_shared<NTC>()->InstallAsService(sm); + std::make_shared<NTC>(system)->InstallAsService(sm); } } // namespace Service::NIM diff --git a/src/core/hle/service/nim/nim.h b/src/core/hle/service/nim/nim.h index 2a2a92df0..dbe25dc01 100644 --- a/src/core/hle/service/nim/nim.h +++ b/src/core/hle/service/nim/nim.h @@ -8,8 +8,12 @@ namespace Service::SM { class ServiceManager; } +namespace Core { +class System; +} + namespace Service::NIM { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::NIM diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index ce88a2941..15c156ce1 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -617,7 +617,8 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared<NS>("ns:am2")->InstallAsService(service_manager); std::make_shared<NS>("ns:ec")->InstallAsService(service_manager); std::make_shared<NS>("ns:rid")->InstallAsService(service_manager); @@ -628,7 +629,7 @@ void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<NS_SU>()->InstallAsService(service_manager); std::make_shared<NS_VM>()->InstallAsService(service_manager); - std::make_shared<PL_U>()->InstallAsService(service_manager); + std::make_shared<PL_U>(system)->InstallAsService(service_manager); } } // namespace Service::NS diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h index 0e8256cb4..13a64ad88 100644 --- a/src/core/hle/service/ns/ns.h +++ b/src/core/hle/service/ns/ns.h @@ -6,7 +6,13 @@ #include "core/hle/service/service.h" -namespace Service::NS { +namespace Service { + +namespace FileSystem { +class FileSystemController; +} // namespace FileSystem + +namespace NS { class IAccountProxyInterface final : public ServiceFramework<IAccountProxyInterface> { public: @@ -91,6 +97,7 @@ private: }; /// Registers all NS services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); -} // namespace Service::NS +} // namespace NS +} // namespace Service diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index ad176f89d..7dcdb4a07 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -77,7 +77,7 @@ enum class LoadState : u32 { Done = 1, }; -static void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, +static void DecryptSharedFont(const std::vector<u32>& input, Kernel::PhysicalMemory& output, std::size_t& offset) { ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, "Shared fonts exceeds 17mb!"); @@ -94,7 +94,7 @@ static void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& ou offset += transformed_font.size() * sizeof(u32); } -static void EncryptSharedFont(const std::vector<u8>& input, std::vector<u8>& output, +static void EncryptSharedFont(const std::vector<u8>& input, Kernel::PhysicalMemory& output, std::size_t& offset) { ASSERT_MSG(offset + input.size() + 8 < SHARED_FONT_MEM_SIZE, "Shared fonts exceeds 17mb!"); const u32 KEY = EXPECTED_MAGIC ^ EXPECTED_RESULT; @@ -121,7 +121,7 @@ struct PL_U::Impl { return shared_font_regions.at(index); } - void BuildSharedFontsRawRegions(const std::vector<u8>& input) { + void BuildSharedFontsRawRegions(const Kernel::PhysicalMemory& input) { // As we can derive the xor key we can just populate the offsets // based on the shared memory dump unsigned cur_offset = 0; @@ -144,13 +144,15 @@ struct PL_U::Impl { Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; /// Backing memory for the shared font data - std::shared_ptr<std::vector<u8>> shared_font; + std::shared_ptr<Kernel::PhysicalMemory> shared_font; // Automatically populated based on shared_fonts dump or system archives. std::vector<FontRegion> shared_font_regions; }; -PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} { +PL_U::PL_U(Core::System& system) + : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()}, system(system) { + static const FunctionInfo functions[] = { {0, &PL_U::RequestLoad, "RequestLoad"}, {1, &PL_U::GetLoadState, "GetLoadState"}, @@ -160,13 +162,16 @@ PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} { {5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"}, }; RegisterHandlers(functions); + + auto& fsc = system.GetFileSystemController(); + // Attempt to load shared font data from disk - const auto* nand = FileSystem::GetSystemNANDContents(); + const auto* nand = fsc.GetSystemNANDContents(); std::size_t offset = 0; // Rebuild shared fonts from data ncas if (nand->HasEntry(static_cast<u64>(FontArchives::Standard), FileSys::ContentRecordType::Data)) { - impl->shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE); + impl->shared_font = std::make_shared<Kernel::PhysicalMemory>(SHARED_FONT_MEM_SIZE); for (auto font : SHARED_FONTS) { const auto nca = nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data); @@ -207,7 +212,7 @@ PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} { } } else { - impl->shared_font = std::make_shared<std::vector<u8>>( + impl->shared_font = std::make_shared<Kernel::PhysicalMemory>( SHARED_FONT_MEM_SIZE); // Shared memory needs to always be allocated and a fixed size const std::string user_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir); @@ -324,7 +329,7 @@ void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { Kernel::MemoryState::Shared); // Create shared font memory object - auto& kernel = Core::System::GetInstance().Kernel(); + auto& kernel = system.Kernel(); impl->shared_font_mem = Kernel::SharedMemory::Create( kernel, Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE, diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h index 253f26a2a..1063f4204 100644 --- a/src/core/hle/service/ns/pl_u.h +++ b/src/core/hle/service/ns/pl_u.h @@ -7,11 +7,17 @@ #include <memory> #include "core/hle/service/service.h" -namespace Service::NS { +namespace Service { + +namespace FileSystem { +class FileSystemController; +} // namespace FileSystem + +namespace NS { class PL_U final : public ServiceFramework<PL_U> { public: - PL_U(); + explicit PL_U(Core::System& system); ~PL_U() override; private: @@ -24,6 +30,9 @@ private: struct Impl; std::unique_ptr<Impl> impl; + Core::System& system; }; -} // namespace Service::NS +} // namespace NS + +} // namespace Service diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index 4f6042b00..1b52511a5 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -8,6 +8,12 @@ #include "common/bit_field.h" #include "common/common_types.h" #include "common/swap.h" +#include "core/hle/service/nvdrv/nvdata.h" +#include "core/hle/service/service.h" + +namespace Core { +class System; +} namespace Service::Nvidia::Devices { @@ -15,7 +21,7 @@ namespace Service::Nvidia::Devices { /// implement the ioctl interface. class nvdevice { public: - nvdevice() = default; + explicit nvdevice(Core::System& system) : system{system} {}; virtual ~nvdevice() = default; union Ioctl { u32_le raw; @@ -33,7 +39,12 @@ public: * @param output A buffer where the output data will be written to. * @returns The result code of the ioctl. */ - virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) = 0; + virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) = 0; + +protected: + Core::System& system; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index 20c7c39aa..f764388bc 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -13,10 +13,13 @@ namespace Service::Nvidia::Devices { -nvdisp_disp0::nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} +nvdisp_disp0::nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev) + : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} nvdisp_disp0 ::~nvdisp_disp0() = default; -u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { UNIMPLEMENTED_MSG("Unimplemented ioctl"); return 0; } @@ -34,9 +37,8 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3 addr, offset, width, height, stride, static_cast<PixelFormat>(format), transform, crop_rect}; - auto& instance = Core::System::GetInstance(); - instance.GetPerfStats().EndGameFrame(); - instance.GPU().SwapBuffers(framebuffer); + system.GetPerfStats().EndGameFrame(); + system.GPU().SwapBuffers(&framebuffer); } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index 12f3ef825..6fcdeee84 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -17,10 +17,12 @@ class nvmap; class nvdisp_disp0 final : public nvdevice { public: - explicit nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev); + explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); ~nvdisp_disp0() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; /// Performs a screen flip, drawing the buffer pointed to by the handle. void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride, diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index af62d33d2..6bc053f27 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -22,10 +22,13 @@ enum { }; } -nvhost_as_gpu::nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} +nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev) + : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} nvhost_as_gpu::~nvhost_as_gpu() = default; -u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); @@ -65,7 +68,7 @@ u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages, params.page_size, params.flags); - auto& gpu = Core::System::GetInstance().GPU(); + auto& gpu = system.GPU(); const u64 size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)}; if (params.flags & 1) { params.offset = gpu.MemoryManager().AllocateSpace(params.offset, size, 1); @@ -85,7 +88,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) std::vector<IoctlRemapEntry> entries(num_entries); std::memcpy(entries.data(), input.data(), input.size()); - auto& gpu = Core::System::GetInstance().GPU(); + auto& gpu = system.GPU(); for (const auto& entry : entries) { LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}", entry.offset, entry.nvmap_handle, entry.pages); @@ -136,7 +139,7 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou // case to prevent unexpected behavior. ASSERT(object->id == params.nvmap_handle); - auto& gpu = Core::System::GetInstance().GPU(); + auto& gpu = system.GPU(); if (params.flags & 1) { params.offset = gpu.MemoryManager().MapBufferEx(object->addr, params.offset, object->size); @@ -173,8 +176,7 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou return 0; } - params.offset = Core::System::GetInstance().GPU().MemoryManager().UnmapBuffer(params.offset, - itr->second.size); + params.offset = system.GPU().MemoryManager().UnmapBuffer(params.offset, itr->second.size); buffer_mappings.erase(itr->second.offset); std::memcpy(output.data(), ¶ms, output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index eb14b1da8..169fb8f0e 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -17,10 +17,12 @@ class nvmap; class nvhost_as_gpu final : public nvdevice { public: - explicit nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev); + explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); ~nvhost_as_gpu() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp index b39fb9ef9..ff6b1abae 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -7,14 +7,21 @@ #include "common/assert.h" #include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" #include "core/hle/service/nvdrv/devices/nvhost_ctrl.h" +#include "video_core/gpu.h" namespace Service::Nvidia::Devices { -nvhost_ctrl::nvhost_ctrl() = default; +nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface) + : nvdevice(system), events_interface{events_interface} {} nvhost_ctrl::~nvhost_ctrl() = default; -u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); @@ -22,11 +29,15 @@ u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector< case IoctlCommand::IocGetConfigCommand: return NvOsGetConfigU32(input, output); case IoctlCommand::IocCtrlEventWaitCommand: - return IocCtrlEventWait(input, output, false); + return IocCtrlEventWait(input, output, false, ctrl); case IoctlCommand::IocCtrlEventWaitAsyncCommand: - return IocCtrlEventWait(input, output, true); + return IocCtrlEventWait(input, output, true, ctrl); case IoctlCommand::IocCtrlEventRegisterCommand: return IocCtrlEventRegister(input, output); + case IoctlCommand::IocCtrlEventUnregisterCommand: + return IocCtrlEventUnregister(input, output); + case IoctlCommand::IocCtrlEventSignalCommand: + return IocCtrlEventSignal(input, output); } UNIMPLEMENTED_MSG("Unimplemented ioctl"); return 0; @@ -41,23 +52,137 @@ u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& } u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, - bool is_async) { + bool is_async, IoctlCtrl& ctrl) { IocCtrlEventWaitParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); - LOG_WARNING(Service_NVDRV, - "(STUBBED) called, syncpt_id={}, threshold={}, timeout={}, is_async={}", - params.syncpt_id, params.threshold, params.timeout, is_async); + LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}", + params.syncpt_id, params.threshold, params.timeout, is_async); - // TODO(Subv): Implement actual syncpt waiting. - params.value = 0; + if (params.syncpt_id >= MaxSyncPoints) { + return NvResult::BadParameter; + } + + auto& gpu = system.GPU(); + // This is mostly to take into account unimplemented features. As synced + // gpu is always synced. + if (!gpu.IsAsync()) { + return NvResult::Success; + } + auto lock = gpu.LockSync(); + const u32 current_syncpoint_value = gpu.GetSyncpointValue(params.syncpt_id); + const s32 diff = current_syncpoint_value - params.threshold; + if (diff >= 0) { + params.value = current_syncpoint_value; + std::memcpy(output.data(), ¶ms, sizeof(params)); + return NvResult::Success; + } + const u32 target_value = current_syncpoint_value - diff; + + if (!is_async) { + params.value = 0; + } + + if (params.timeout == 0) { + std::memcpy(output.data(), ¶ms, sizeof(params)); + return NvResult::Timeout; + } + + u32 event_id; + if (is_async) { + event_id = params.value & 0x00FF; + if (event_id >= MaxNvEvents) { + std::memcpy(output.data(), ¶ms, sizeof(params)); + return NvResult::BadParameter; + } + } else { + if (ctrl.fresh_call) { + const auto result = events_interface.GetFreeEvent(); + if (result) { + event_id = *result; + } else { + LOG_CRITICAL(Service_NVDRV, "No Free Events available!"); + event_id = params.value & 0x00FF; + } + } else { + event_id = ctrl.event_id; + } + } + + EventState status = events_interface.status[event_id]; + if (event_id < MaxNvEvents || status == EventState::Free || status == EventState::Registered) { + events_interface.SetEventStatus(event_id, EventState::Waiting); + events_interface.assigned_syncpt[event_id] = params.syncpt_id; + events_interface.assigned_value[event_id] = target_value; + if (is_async) { + params.value = params.syncpt_id << 4; + } else { + params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000; + } + params.value |= event_id; + events_interface.events[event_id].writable->Clear(); + gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value); + if (!is_async && ctrl.fresh_call) { + ctrl.must_delay = true; + ctrl.timeout = params.timeout; + ctrl.event_id = event_id; + return NvResult::Timeout; + } + std::memcpy(output.data(), ¶ms, sizeof(params)); + return NvResult::Timeout; + } std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::BadParameter; } u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) { - LOG_WARNING(Service_NVDRV, "(STUBBED) called"); - // TODO(bunnei): Implement this. - return 0; + IocCtrlEventRegisterParams params{}; + std::memcpy(¶ms, input.data(), sizeof(params)); + const u32 event_id = params.user_event_id & 0x00FF; + LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id); + if (event_id >= MaxNvEvents) { + return NvResult::BadParameter; + } + if (events_interface.registered[event_id]) { + return NvResult::BadParameter; + } + events_interface.RegisterEvent(event_id); + return NvResult::Success; +} + +u32 nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output) { + IocCtrlEventUnregisterParams params{}; + std::memcpy(¶ms, input.data(), sizeof(params)); + const u32 event_id = params.user_event_id & 0x00FF; + LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id); + if (event_id >= MaxNvEvents) { + return NvResult::BadParameter; + } + if (!events_interface.registered[event_id]) { + return NvResult::BadParameter; + } + events_interface.UnregisterEvent(event_id); + return NvResult::Success; +} + +u32 nvhost_ctrl::IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output) { + IocCtrlEventSignalParams params{}; + std::memcpy(¶ms, input.data(), sizeof(params)); + // TODO(Blinkhawk): This is normally called when an NvEvents timeout on WaitSynchronization + // It is believed from RE to cancel the GPU Event. However, better research is required + u32 event_id = params.user_event_id & 0x00FF; + LOG_WARNING(Service_NVDRV, "(STUBBED) called, user_event_id: {:X}", event_id); + if (event_id >= MaxNvEvents) { + return NvResult::BadParameter; + } + if (events_interface.status[event_id] == EventState::Waiting) { + auto& gpu = system.GPU(); + if (gpu.CancelSyncptInterrupt(events_interface.assigned_syncpt[event_id], + events_interface.assigned_value[event_id])) { + events_interface.LiberateEvent(event_id); + events_interface.events[event_id].writable->Signal(); + } + } + return NvResult::Success; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h index 6d0de2212..9898623de 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h @@ -8,15 +8,18 @@ #include <vector> #include "common/common_types.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" +#include "core/hle/service/nvdrv/nvdrv.h" namespace Service::Nvidia::Devices { class nvhost_ctrl final : public nvdevice { public: - nvhost_ctrl(); + explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface); ~nvhost_ctrl() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { @@ -132,9 +135,16 @@ private: u32 NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output); - u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async); + u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async, + IoctlCtrl& ctrl); u32 IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output); + + u32 IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output); + + u32 IocCtrlEventSignal(const std::vector<u8>& input, std::vector<u8>& output); + + EventInterface& events_interface; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index 0e28755bd..389ace76f 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -12,16 +12,18 @@ namespace Service::Nvidia::Devices { -nvhost_ctrl_gpu::nvhost_ctrl_gpu() = default; +nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {} nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default; -u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, + const std::vector<u8>& input2, std::vector<u8>& output, + std::vector<u8>& output2, IoctlCtrl& ctrl, IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); switch (static_cast<IoctlCommand>(command.raw)) { case IoctlCommand::IocGetCharacteristicsCommand: - return GetCharacteristics(input, output); + return GetCharacteristics(input, output, output2, version); case IoctlCommand::IocGetTPCMasksCommand: return GetTPCMasks(input, output); case IoctlCommand::IocGetActiveSlotMaskCommand: @@ -43,7 +45,8 @@ u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vec return 0; } -u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& output2, IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called"); IoctlCharacteristics params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -84,7 +87,13 @@ u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector<u8>& input, std::vecto params.gc.gr_compbit_store_base_hw = 0x0; params.gpu_characteristics_buf_size = 0xA0; params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED) - std::memcpy(output.data(), ¶ms, output.size()); + + if (version == IoctlVersion::Version3) { + std::memcpy(output.data(), input.data(), output.size()); + std::memcpy(output2.data(), ¶ms.gc, output2.size()); + } else { + std::memcpy(output.data(), ¶ms, output.size()); + } return 0; } @@ -185,7 +194,7 @@ u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u8>& o IoctlGetGpuTime params{}; std::memcpy(¶ms, input.data(), input.size()); - const auto ns = Core::Timing::CyclesToNs(Core::System::GetInstance().CoreTiming().GetTicks()); + const auto ns = Core::Timing::CyclesToNs(system.CoreTiming().GetTicks()); params.gpu_time = static_cast<u64_le>(ns.count()); std::memcpy(output.data(), ¶ms, output.size()); return 0; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h index 240435eea..642b0a2cb 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h @@ -13,10 +13,12 @@ namespace Service::Nvidia::Devices { class nvhost_ctrl_gpu final : public nvdevice { public: - nvhost_ctrl_gpu(); + explicit nvhost_ctrl_gpu(Core::System& system); ~nvhost_ctrl_gpu() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { @@ -161,7 +163,8 @@ private: }; static_assert(sizeof(IoctlGetGpuTime) == 8, "IoctlGetGpuTime is incorrect size"); - u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output); + u32 GetCharacteristics(const std::vector<u8>& input, std::vector<u8>& output, + std::vector<u8>& output2, IoctlVersion version); u32 GetTPCMasks(const std::vector<u8>& input, std::vector<u8>& output); u32 GetActiveSlotMask(const std::vector<u8>& input, std::vector<u8>& output); u32 ZCullGetCtxSize(const std::vector<u8>& input, std::vector<u8>& output); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 8ce7bc7a5..2b8d1bef6 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -13,10 +13,13 @@ namespace Service::Nvidia::Devices { -nvhost_gpu::nvhost_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} +nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev) + : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} nvhost_gpu::~nvhost_gpu() = default; -u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); @@ -48,7 +51,7 @@ u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u return SubmitGPFIFO(input, output); } if (command.cmd == NVGPU_IOCTL_CHANNEL_KICKOFF_PB) { - return KickoffPB(input, output); + return KickoffPB(input, output, input2, version); } } @@ -119,8 +122,10 @@ u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& ou params.num_entries, params.flags, params.unk0, params.unk1, params.unk2, params.unk3); - params.fence_out.id = 0; - params.fence_out.value = 0; + auto& gpu = system.GPU(); + params.fence_out.id = assigned_syncpoints; + params.fence_out.value = gpu.GetSyncpointValue(assigned_syncpoints); + assigned_syncpoints++; std::memcpy(output.data(), ¶ms, output.size()); return 0; } @@ -142,8 +147,8 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp } IoctlSubmitGpfifo params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo)); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}", - params.address, params.num_entries, params.flags); + LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address, + params.num_entries, params.flags.raw); ASSERT_MSG(input.size() == sizeof(IoctlSubmitGpfifo) + params.num_entries * sizeof(Tegra::CommandListHeader), @@ -153,31 +158,52 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp std::memcpy(entries.data(), &input[sizeof(IoctlSubmitGpfifo)], params.num_entries * sizeof(Tegra::CommandListHeader)); - Core::System::GetInstance().GPU().PushGPUEntries(std::move(entries)); + UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0); + UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0); + + auto& gpu = system.GPU(); + u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id); + if (params.flags.increment.Value()) { + params.fence_out.value += current_syncpoint_value; + } else { + params.fence_out.value = current_syncpoint_value; + } + gpu.PushGPUEntries(std::move(entries)); - params.fence_out.id = 0; - params.fence_out.value = 0; std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmitGpfifo)); return 0; } -u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output, + const std::vector<u8>& input2, IoctlVersion version) { if (input.size() < sizeof(IoctlSubmitGpfifo)) { UNIMPLEMENTED(); } IoctlSubmitGpfifo params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo)); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}", - params.address, params.num_entries, params.flags); + LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address, + params.num_entries, params.flags.raw); Tegra::CommandList entries(params.num_entries); - Memory::ReadBlock(params.address, entries.data(), - params.num_entries * sizeof(Tegra::CommandListHeader)); - - Core::System::GetInstance().GPU().PushGPUEntries(std::move(entries)); + if (version == IoctlVersion::Version2) { + std::memcpy(entries.data(), input2.data(), + params.num_entries * sizeof(Tegra::CommandListHeader)); + } else { + Memory::ReadBlock(params.address, entries.data(), + params.num_entries * sizeof(Tegra::CommandListHeader)); + } + UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0); + UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0); + + auto& gpu = system.GPU(); + u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id); + if (params.flags.increment.Value()) { + params.fence_out.value += current_syncpoint_value; + } else { + params.fence_out.value = current_syncpoint_value; + } + gpu.PushGPUEntries(std::move(entries)); - params.fence_out.id = 0; - params.fence_out.value = 0; std::memcpy(output.data(), ¶ms, output.size()); return 0; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index 62beb5c0c..d056dd046 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -10,6 +10,7 @@ #include "common/common_types.h" #include "common/swap.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" +#include "core/hle/service/nvdrv/nvdata.h" namespace Service::Nvidia::Devices { @@ -20,10 +21,12 @@ constexpr u32 NVGPU_IOCTL_CHANNEL_KICKOFF_PB(0x1b); class nvhost_gpu final : public nvdevice { public: - explicit nvhost_gpu(std::shared_ptr<nvmap> nvmap_dev); + explicit nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); ~nvhost_gpu() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { @@ -113,11 +116,7 @@ private: static_assert(sizeof(IoctlGetErrorNotification) == 16, "IoctlGetErrorNotification is incorrect size"); - struct IoctlFence { - u32_le id; - u32_le value; - }; - static_assert(sizeof(IoctlFence) == 8, "IoctlFence is incorrect size"); + static_assert(sizeof(Fence) == 8, "Fence is incorrect size"); struct IoctlAllocGpfifoEx { u32_le num_entries; @@ -132,13 +131,13 @@ private: static_assert(sizeof(IoctlAllocGpfifoEx) == 32, "IoctlAllocGpfifoEx is incorrect size"); struct IoctlAllocGpfifoEx2 { - u32_le num_entries; // in - u32_le flags; // in - u32_le unk0; // in (1 works) - IoctlFence fence_out; // out - u32_le unk1; // in - u32_le unk2; // in - u32_le unk3; // in + u32_le num_entries; // in + u32_le flags; // in + u32_le unk0; // in (1 works) + Fence fence_out; // out + u32_le unk1; // in + u32_le unk2; // in + u32_le unk3; // in }; static_assert(sizeof(IoctlAllocGpfifoEx2) == 32, "IoctlAllocGpfifoEx2 is incorrect size"); @@ -153,10 +152,16 @@ private: struct IoctlSubmitGpfifo { u64_le address; // pointer to gpfifo entry structs u32_le num_entries; // number of fence objects being submitted - u32_le flags; - IoctlFence fence_out; // returned new fence object for others to wait on - }; - static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(IoctlFence), + union { + u32_le raw; + BitField<0, 1, u32_le> add_wait; // append a wait sync_point to the list + BitField<1, 1, u32_le> add_increment; // append an increment to the list + BitField<2, 1, u32_le> new_hw_format; // Mostly ignored + BitField<8, 1, u32_le> increment; // increment the returned fence + } flags; + Fence fence_out; // returned new fence object for others to wait on + }; + static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(Fence), "IoctlSubmitGpfifo is incorrect size"); struct IoctlGetWaitbase { @@ -179,11 +184,13 @@ private: u32 AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8>& output); u32 AllocateObjectContext(const std::vector<u8>& input, std::vector<u8>& output); u32 SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output); - u32 KickoffPB(const std::vector<u8>& input, std::vector<u8>& output); + u32 KickoffPB(const std::vector<u8>& input, std::vector<u8>& output, + const std::vector<u8>& input2, IoctlVersion version); u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); u32 ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output); std::shared_ptr<nvmap> nvmap_dev; + u32 assigned_syncpoints{}; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp index f5e8ea7c3..bdae8b887 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp @@ -10,10 +10,12 @@ namespace Service::Nvidia::Devices { -nvhost_nvdec::nvhost_nvdec() = default; +nvhost_nvdec::nvhost_nvdec(Core::System& system) : nvdevice(system) {} nvhost_nvdec::~nvhost_nvdec() = default; -u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h index 0e7b284f8..cbdac8069 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h @@ -13,10 +13,12 @@ namespace Service::Nvidia::Devices { class nvhost_nvdec final : public nvdevice { public: - nvhost_nvdec(); + explicit nvhost_nvdec(Core::System& system); ~nvhost_nvdec() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp index 3e0951ab0..96e7b7dab 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp @@ -10,10 +10,12 @@ namespace Service::Nvidia::Devices { -nvhost_nvjpg::nvhost_nvjpg() = default; +nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {} nvhost_nvjpg::~nvhost_nvjpg() = default; -u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h index 89fd5e95e..98dcac52f 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h @@ -13,10 +13,12 @@ namespace Service::Nvidia::Devices { class nvhost_nvjpg final : public nvdevice { public: - nvhost_nvjpg(); + explicit nvhost_nvjpg(Core::System& system); ~nvhost_nvjpg() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp index d544f0f31..c695b8863 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp @@ -10,10 +10,12 @@ namespace Service::Nvidia::Devices { -nvhost_vic::nvhost_vic() = default; +nvhost_vic::nvhost_vic(Core::System& system) : nvdevice(system) {} nvhost_vic::~nvhost_vic() = default; -u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h index fc24c3f9c..bec32bea1 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h @@ -13,10 +13,12 @@ namespace Service::Nvidia::Devices { class nvhost_vic final : public nvdevice { public: - nvhost_vic(); + explicit nvhost_vic(Core::System& system); ~nvhost_vic() override; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; private: enum class IoctlCommand : u32_le { diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index 1ec796fc6..8c742316c 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -18,7 +18,7 @@ enum { }; } -nvmap::nvmap() = default; +nvmap::nvmap(Core::System& system) : nvdevice(system) {} nvmap::~nvmap() = default; VAddr nvmap::GetObjectAddress(u32 handle) const { @@ -28,7 +28,9 @@ VAddr nvmap::GetObjectAddress(u32 handle) const { return object->addr; } -u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { switch (static_cast<IoctlCommand>(command.raw)) { case IoctlCommand::Create: return IocCreate(input, output); diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index 396230c19..73c2e8809 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -16,13 +16,15 @@ namespace Service::Nvidia::Devices { class nvmap final : public nvdevice { public: - nvmap(); + explicit nvmap(Core::System& system); ~nvmap() override; /// Returns the allocated address of an nvmap object given its handle. VAddr GetObjectAddress(u32 handle) const; - u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; + u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) override; /// Represents an nvmap object. struct Object { diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp index b60fc748b..5e0c23602 100644 --- a/src/core/hle/service/nvdrv/interface.cpp +++ b/src/core/hle/service/nvdrv/interface.cpp @@ -8,12 +8,18 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/thread.h" #include "core/hle/kernel/writable_event.h" #include "core/hle/service/nvdrv/interface.h" +#include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvdrv/nvdrv.h" namespace Service::Nvidia { +void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) { + nvdrv->SignalSyncpt(syncpoint_id, value); +} + void NVDRV::Open(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NVDRV, "called"); @@ -27,20 +33,75 @@ void NVDRV::Open(Kernel::HLERequestContext& ctx) { rb.Push<u32>(0); } -void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); - +void NVDRV::IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version) { IPC::RequestParser rp{ctx}; u32 fd = rp.Pop<u32>(); u32 command = rp.Pop<u32>(); - std::vector<u8> output(ctx.GetWriteBufferSize()); - + /// Ioctl 3 has 2 outputs, first in the input params, second is the result + std::vector<u8> output(ctx.GetWriteBufferSize(0)); + std::vector<u8> output2; + if (version == IoctlVersion::Version3) { + output2.resize((ctx.GetWriteBufferSize(1))); + } + + /// Ioctl2 has 2 inputs. It's used to pass data directly instead of providing a pointer. + /// KickOfPB uses this + auto input = ctx.ReadBuffer(0); + + std::vector<u8> input2; + if (version == IoctlVersion::Version2) { + input2 = ctx.ReadBuffer(1); + } + + IoctlCtrl ctrl{}; + + u32 result = nvdrv->Ioctl(fd, command, input, input2, output, output2, ctrl, version); + + if (ctrl.must_delay) { + ctrl.fresh_call = false; + ctx.SleepClientThread("NVServices::DelayedResponse", ctrl.timeout, + [=](Kernel::SharedPtr<Kernel::Thread> thread, + Kernel::HLERequestContext& ctx, + Kernel::ThreadWakeupReason reason) { + IoctlCtrl ctrl2{ctrl}; + std::vector<u8> tmp_output = output; + std::vector<u8> tmp_output2 = output2; + u32 result = nvdrv->Ioctl(fd, command, input, input2, tmp_output, + tmp_output2, ctrl2, version); + ctx.WriteBuffer(tmp_output, 0); + if (version == IoctlVersion::Version3) { + ctx.WriteBuffer(tmp_output2, 1); + } + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(result); + }, + nvdrv->GetEventWriteable(ctrl.event_id)); + } else { + ctx.WriteBuffer(output); + if (version == IoctlVersion::Version3) { + ctx.WriteBuffer(output2, 1); + } + } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(nvdrv->Ioctl(fd, command, ctx.ReadBuffer(), output)); + rb.Push(result); +} + +void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlBase(ctx, IoctlVersion::Version1); +} - ctx.WriteBuffer(output); +void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlBase(ctx, IoctlVersion::Version2); +} + +void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlBase(ctx, IoctlVersion::Version3); } void NVDRV::Close(Kernel::HLERequestContext& ctx) { @@ -66,13 +127,19 @@ void NVDRV::Initialize(Kernel::HLERequestContext& ctx) { void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; u32 fd = rp.Pop<u32>(); - u32 event_id = rp.Pop<u32>(); + // TODO(Blinkhawk): Figure the meaning of the flag at bit 16 + u32 event_id = rp.Pop<u32>() & 0x000000FF; LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id); IPC::ResponseBuilder rb{ctx, 3, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(query_event.readable); - rb.Push<u32>(0); + if (event_id < MaxNvEvents) { + rb.PushCopyObjects(nvdrv->GetEvent(event_id)); + rb.Push<u32>(NvResult::Success); + } else { + rb.Push<u32>(0); + rb.Push<u32>(NvResult::BadParameter); + } } void NVDRV::SetClientPID(Kernel::HLERequestContext& ctx) { @@ -122,15 +189,11 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name) {8, &NVDRV::SetClientPID, "SetClientPID"}, {9, &NVDRV::DumpGraphicsMemoryInfo, "DumpGraphicsMemoryInfo"}, {10, nullptr, "InitializeDevtools"}, - {11, &NVDRV::Ioctl, "Ioctl2"}, - {12, nullptr, "Ioctl3"}, + {11, &NVDRV::Ioctl2, "Ioctl2"}, + {12, &NVDRV::Ioctl3, "Ioctl3"}, {13, &NVDRV::FinishInitialize, "FinishInitialize"}, }; RegisterHandlers(functions); - - auto& kernel = Core::System::GetInstance().Kernel(); - query_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, - "NVDRV::query_event"); } NVDRV::~NVDRV() = default; diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h index 5b4889910..9269ce00c 100644 --- a/src/core/hle/service/nvdrv/interface.h +++ b/src/core/hle/service/nvdrv/interface.h @@ -19,9 +19,13 @@ public: NVDRV(std::shared_ptr<Module> nvdrv, const char* name); ~NVDRV() override; + void SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value); + private: void Open(Kernel::HLERequestContext& ctx); void Ioctl(Kernel::HLERequestContext& ctx); + void Ioctl2(Kernel::HLERequestContext& ctx); + void Ioctl3(Kernel::HLERequestContext& ctx); void Close(Kernel::HLERequestContext& ctx); void Initialize(Kernel::HLERequestContext& ctx); void QueryEvent(Kernel::HLERequestContext& ctx); @@ -29,12 +33,11 @@ private: void FinishInitialize(Kernel::HLERequestContext& ctx); void GetStatus(Kernel::HLERequestContext& ctx); void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx); + void IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version); std::shared_ptr<Module> nvdrv; u64 pid{}; - - Kernel::EventPair query_event; }; } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h new file mode 100644 index 000000000..529b03471 --- /dev/null +++ b/src/core/hle/service/nvdrv/nvdata.h @@ -0,0 +1,54 @@ +#pragma once + +#include <array> +#include "common/common_types.h" + +namespace Service::Nvidia { + +constexpr u32 MaxSyncPoints = 192; +constexpr u32 MaxNvEvents = 64; + +struct Fence { + s32 id; + u32 value; +}; + +static_assert(sizeof(Fence) == 8, "Fence has wrong size"); + +struct MultiFence { + u32 num_fences; + std::array<Fence, 4> fences; +}; + +enum NvResult : u32 { + Success = 0, + BadParameter = 4, + Timeout = 5, + ResourceError = 15, +}; + +enum class EventState { + Free = 0, + Registered = 1, + Waiting = 2, + Busy = 3, +}; + +enum class IoctlVersion : u32 { + Version1, + Version2, + Version3, +}; + +struct IoctlCtrl { + // First call done to the servioce for services that call itself again after a call. + bool fresh_call{true}; + // Tells the Ioctl Wrapper that it must delay the IPC response and send the thread to sleep + bool must_delay{}; + // Timeout for the delay + s64 timeout{}; + // NV Event Id + s32 event_id{-1}; +}; + +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 6e4b8f2c6..307a7e928 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -4,7 +4,10 @@ #include <utility> +#include <fmt/format.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/nvdrv/devices/nvdevice.h" #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" #include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h" @@ -22,8 +25,9 @@ namespace Service::Nvidia { -void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger) { - auto module_ = std::make_shared<Module>(); +void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger, + Core::System& system) { + auto module_ = std::make_shared<Module>(system); std::make_shared<NVDRV>(module_, "nvdrv")->InstallAsService(service_manager); std::make_shared<NVDRV>(module_, "nvdrv:a")->InstallAsService(service_manager); std::make_shared<NVDRV>(module_, "nvdrv:s")->InstallAsService(service_manager); @@ -32,17 +36,25 @@ void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger nvflinger.SetNVDrvInstance(module_); } -Module::Module() { - auto nvmap_dev = std::make_shared<Devices::nvmap>(); - devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(nvmap_dev); - devices["/dev/nvhost-gpu"] = std::make_shared<Devices::nvhost_gpu>(nvmap_dev); - devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(); +Module::Module(Core::System& system) { + auto& kernel = system.Kernel(); + for (u32 i = 0; i < MaxNvEvents; i++) { + std::string event_label = fmt::format("NVDRV::NvEvent_{}", i); + events_interface.events[i] = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::Automatic, event_label); + events_interface.status[i] = EventState::Free; + events_interface.registered[i] = false; + } + auto nvmap_dev = std::make_shared<Devices::nvmap>(system); + devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(system, nvmap_dev); + devices["/dev/nvhost-gpu"] = std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev); + devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(system); devices["/dev/nvmap"] = nvmap_dev; - devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(nvmap_dev); - devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>(); - devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>(); - devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>(); - devices["/dev/nvhost-vic"] = std::make_shared<Devices::nvhost_vic>(); + devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(system, nvmap_dev); + devices["/dev/nvhost-ctrl"] = std::make_shared<Devices::nvhost_ctrl>(system, events_interface); + devices["/dev/nvhost-nvdec"] = std::make_shared<Devices::nvhost_nvdec>(system); + devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>(system); + devices["/dev/nvhost-vic"] = std::make_shared<Devices::nvhost_vic>(system); } Module::~Module() = default; @@ -59,12 +71,14 @@ u32 Module::Open(const std::string& device_name) { return fd; } -u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output) { +u32 Module::Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version) { auto itr = open_files.find(fd); ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device"); auto& device = itr->second; - return device->ioctl({command}, input, output); + return device->ioctl({command}, input, input2, output, output2, ctrl, version); } ResultCode Module::Close(u32 fd) { @@ -77,4 +91,22 @@ ResultCode Module::Close(u32 fd) { return RESULT_SUCCESS; } +void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) { + for (u32 i = 0; i < MaxNvEvents; i++) { + if (events_interface.assigned_syncpt[i] == syncpoint_id && + events_interface.assigned_value[i] == value) { + events_interface.LiberateEvent(i); + events_interface.events[i].writable->Signal(); + } + } +} + +Kernel::SharedPtr<Kernel::ReadableEvent> Module::GetEvent(const u32 event_id) const { + return events_interface.events[event_id].readable; +} + +Kernel::SharedPtr<Kernel::WritableEvent> Module::GetEventWriteable(const u32 event_id) const { + return events_interface.events[event_id].writable; +} + } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index 53564f696..f8bb28969 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -8,8 +8,14 @@ #include <unordered_map> #include <vector> #include "common/common_types.h" +#include "core/hle/kernel/writable_event.h" +#include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::NVFlinger { class NVFlinger; } @@ -20,16 +26,72 @@ namespace Devices { class nvdevice; } -struct IoctlFence { - u32 id; - u32 value; +struct EventInterface { + // Mask representing currently busy events + u64 events_mask{}; + // Each kernel event associated to an NV event + std::array<Kernel::EventPair, MaxNvEvents> events; + // The status of the current NVEvent + std::array<EventState, MaxNvEvents> status{}; + // Tells if an NVEvent is registered or not + std::array<bool, MaxNvEvents> registered{}; + // When an NVEvent is waiting on GPU interrupt, this is the sync_point + // associated with it. + std::array<u32, MaxNvEvents> assigned_syncpt{}; + // This is the value of the GPU interrupt for which the NVEvent is waiting + // for. + std::array<u32, MaxNvEvents> assigned_value{}; + // Constant to denote an unasigned syncpoint. + static constexpr u32 unassigned_syncpt = 0xFFFFFFFF; + std::optional<u32> GetFreeEvent() const { + u64 mask = events_mask; + for (u32 i = 0; i < MaxNvEvents; i++) { + const bool is_free = (mask & 0x1) == 0; + if (is_free) { + if (status[i] == EventState::Registered || status[i] == EventState::Free) { + return {i}; + } + } + mask = mask >> 1; + } + return {}; + } + void SetEventStatus(const u32 event_id, EventState new_status) { + EventState old_status = status[event_id]; + if (old_status == new_status) { + return; + } + status[event_id] = new_status; + if (new_status == EventState::Registered) { + registered[event_id] = true; + } + if (new_status == EventState::Waiting || new_status == EventState::Busy) { + events_mask |= (1ULL << event_id); + } + } + void RegisterEvent(const u32 event_id) { + registered[event_id] = true; + if (status[event_id] == EventState::Free) { + status[event_id] = EventState::Registered; + } + } + void UnregisterEvent(const u32 event_id) { + registered[event_id] = false; + if (status[event_id] == EventState::Registered) { + status[event_id] = EventState::Free; + } + } + void LiberateEvent(const u32 event_id) { + status[event_id] = registered[event_id] ? EventState::Registered : EventState::Free; + events_mask &= ~(1ULL << event_id); + assigned_syncpt[event_id] = unassigned_syncpt; + assigned_value[event_id] = 0; + } }; -static_assert(sizeof(IoctlFence) == 8, "IoctlFence has wrong size"); - class Module final { public: - Module(); + Module(Core::System& system); ~Module(); /// Returns a pointer to one of the available devices, identified by its name. @@ -44,10 +106,18 @@ public: /// Opens a device node and returns a file descriptor to it. u32 Open(const std::string& device_name); /// Sends an ioctl command to the specified file descriptor. - u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, std::vector<u8>& output); + u32 Ioctl(u32 fd, u32 command, const std::vector<u8>& input, const std::vector<u8>& input2, + std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, + IoctlVersion version); /// Closes a device file descriptor and returns operation success. ResultCode Close(u32 fd); + void SignalSyncpt(const u32 syncpoint_id, const u32 value); + + Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent(u32 event_id) const; + + Kernel::SharedPtr<Kernel::WritableEvent> GetEventWriteable(u32 event_id) const; + private: /// Id to use for the next open file descriptor. u32 next_fd = 1; @@ -57,9 +127,12 @@ private: /// Mapping of device node names to their implementation. std::unordered_map<std::string, std::shared_ptr<Devices::nvdevice>> devices; + + EventInterface events_interface; }; /// Registers all NVDRV services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger); +void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger, + Core::System& system); } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp index 5731e815f..e1a07d3ee 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue.cpp @@ -34,7 +34,8 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) buffer_wait_event.writable->Signal(); } -std::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) { +std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width, + u32 height) { auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) { // Only consider free buffers. Buffers become free once again after they've been Acquired // and Released by the compositor, see the NVFlinger::Compose method. @@ -51,7 +52,7 @@ std::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) { } itr->status = Buffer::Status::Dequeued; - return itr->slot; + return {{itr->slot, &itr->multi_fence}}; } const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const { @@ -63,7 +64,8 @@ const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const { } void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform, - const Common::Rectangle<int>& crop_rect) { + const Common::Rectangle<int>& crop_rect, u32 swap_interval, + Service::Nvidia::MultiFence& multi_fence) { auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) { return buffer.slot == slot; }); ASSERT(itr != queue.end()); @@ -71,12 +73,21 @@ void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform, itr->status = Buffer::Status::Queued; itr->transform = transform; itr->crop_rect = crop_rect; + itr->swap_interval = swap_interval; + itr->multi_fence = multi_fence; + queue_sequence.push_back(slot); } std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() { - auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) { - return buffer.status == Buffer::Status::Queued; - }); + auto itr = queue.end(); + // Iterate to find a queued buffer matching the requested slot. + while (itr == queue.end() && !queue_sequence.empty()) { + u32 slot = queue_sequence.front(); + itr = std::find_if(queue.begin(), queue.end(), [&slot](const Buffer& buffer) { + return buffer.status == Buffer::Status::Queued && buffer.slot == slot; + }); + queue_sequence.pop_front(); + } if (itr == queue.end()) return {}; itr->status = Buffer::Status::Acquired; diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h index e1ccb6171..356bedb81 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.h +++ b/src/core/hle/service/nvflinger/buffer_queue.h @@ -4,6 +4,7 @@ #pragma once +#include <list> #include <optional> #include <vector> @@ -12,6 +13,7 @@ #include "common/swap.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/writable_event.h" +#include "core/hle/service/nvdrv/nvdata.h" namespace Service::NVFlinger { @@ -68,13 +70,17 @@ public: IGBPBuffer igbp_buffer; BufferTransformFlags transform; Common::Rectangle<int> crop_rect; + u32 swap_interval; + Service::Nvidia::MultiFence multi_fence; }; void SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer); - std::optional<u32> DequeueBuffer(u32 width, u32 height); + std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> DequeueBuffer(u32 width, + u32 height); const IGBPBuffer& RequestBuffer(u32 slot) const; void QueueBuffer(u32 slot, BufferTransformFlags transform, - const Common::Rectangle<int>& crop_rect); + const Common::Rectangle<int>& crop_rect, u32 swap_interval, + Service::Nvidia::MultiFence& multi_fence); std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer(); void ReleaseBuffer(u32 slot); u32 Query(QueryType type); @@ -92,6 +98,7 @@ private: u64 layer_id; std::vector<Buffer> queue; + std::list<u32> queue_sequence; Kernel::EventPair buffer_wait_event; }; diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 3c5c53e24..2e4d707b9 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -29,27 +29,28 @@ namespace Service::NVFlinger { constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); constexpr s64 frame_ticks_30fps = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 30); -NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_timing} { - displays.emplace_back(0, "Default"); - displays.emplace_back(1, "External"); - displays.emplace_back(2, "Edid"); - displays.emplace_back(3, "Internal"); - displays.emplace_back(4, "Null"); +NVFlinger::NVFlinger(Core::System& system) : system(system) { + displays.emplace_back(0, "Default", system); + displays.emplace_back(1, "External", system); + displays.emplace_back(2, "Edid", system); + displays.emplace_back(3, "Internal", system); + displays.emplace_back(4, "Null", system); // Schedule the screen composition events - const auto ticks = Settings::values.force_30fps_mode ? frame_ticks_30fps : frame_ticks; - - composition_event = core_timing.RegisterEvent( - "ScreenComposition", [this, ticks](u64 userdata, s64 cycles_late) { + composition_event = system.CoreTiming().RegisterEvent( + "ScreenComposition", [this](u64 userdata, s64 cycles_late) { Compose(); - this->core_timing.ScheduleEvent(ticks - cycles_late, composition_event); + const auto ticks = + Settings::values.force_30fps_mode ? frame_ticks_30fps : GetNextTicks(); + this->system.CoreTiming().ScheduleEvent(std::max<s64>(0LL, ticks - cycles_late), + composition_event); }); - core_timing.ScheduleEvent(ticks, composition_event); + system.CoreTiming().ScheduleEvent(frame_ticks, composition_event); } NVFlinger::~NVFlinger() { - core_timing.UnscheduleEvent(composition_event, 0); + system.CoreTiming().UnscheduleEvent(composition_event, 0); } void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) { @@ -186,11 +187,9 @@ void NVFlinger::Compose() { MicroProfileFlip(); if (!buffer) { - auto& system_instance = Core::System::GetInstance(); - // There was no queued buffer to draw, render previous frame - system_instance.GetPerfStats().EndGameFrame(); - system_instance.GPU().SwapBuffers({}); + system.GetPerfStats().EndGameFrame(); + system.GPU().SwapBuffers({}); continue; } @@ -206,8 +205,14 @@ void NVFlinger::Compose() { igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, buffer->get().transform, buffer->get().crop_rect); + swap_interval = buffer->get().swap_interval; buffer_queue.ReleaseBuffer(buffer->get().slot); } } +s64 NVFlinger::GetNextTicks() const { + constexpr s64 max_hertz = 120LL; + return (Core::Timing::BASE_CLOCK_RATE * (1LL << swap_interval)) / max_hertz; +} + } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index c0a83fffb..5d7e3bfb8 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -38,7 +38,7 @@ class BufferQueue; class NVFlinger final { public: - explicit NVFlinger(Core::Timing::CoreTiming& core_timing); + explicit NVFlinger(Core::System& system); ~NVFlinger(); /// Sets the NVDrv module instance to use to send buffers to the GPU. @@ -74,6 +74,8 @@ public: /// finished. void Compose(); + s64 GetNextTicks() const; + private: /// Finds the display identified by the specified ID. VI::Display* FindDisplay(u64 display_id); @@ -98,11 +100,12 @@ private: /// layers. u32 next_buffer_queue_id = 1; + u32 swap_interval = 1; + /// Event that handles screen composition. Core::Timing::EventType* composition_event; - /// Core timing instance for registering/unregistering the composition event. - Core::Timing::CoreTiming& core_timing; + Core::System& system; }; } // namespace Service::NVFlinger diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp index ebcc41a43..fe6b5f798 100644 --- a/src/core/hle/service/pm/pm.cpp +++ b/src/core/hle/service/pm/pm.cpp @@ -3,11 +3,44 @@ // Refer to the license.txt file included. #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/pm/pm.h" #include "core/hle/service/service.h" namespace Service::PM { +namespace { + +constexpr ResultCode ERROR_PROCESS_NOT_FOUND{ErrorModule::PM, 1}; + +constexpr u64 NO_PROCESS_FOUND_PID{0}; + +std::optional<Kernel::SharedPtr<Kernel::Process>> SearchProcessList( + const std::vector<Kernel::SharedPtr<Kernel::Process>>& process_list, + std::function<bool(const Kernel::SharedPtr<Kernel::Process>&)> predicate) { + const auto iter = std::find_if(process_list.begin(), process_list.end(), predicate); + + if (iter == process_list.end()) { + return std::nullopt; + } + + return *iter; +} + +void GetApplicationPidGeneric(Kernel::HLERequestContext& ctx, + const std::vector<Kernel::SharedPtr<Kernel::Process>>& process_list) { + const auto process = SearchProcessList(process_list, [](const auto& process) { + return process->GetProcessID() == Kernel::Process::ProcessIDMin; + }); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(process.has_value() ? (*process)->GetProcessID() : NO_PROCESS_FOUND_PID); +} + +} // Anonymous namespace + class BootMode final : public ServiceFramework<BootMode> { public: explicit BootMode() : ServiceFramework{"pm:bm"} { @@ -41,14 +74,15 @@ private: class DebugMonitor final : public ServiceFramework<DebugMonitor> { public: - explicit DebugMonitor() : ServiceFramework{"pm:dmnt"} { + explicit DebugMonitor(const Kernel::KernelCore& kernel) + : ServiceFramework{"pm:dmnt"}, kernel(kernel) { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetDebugProcesses"}, {1, nullptr, "StartDebugProcess"}, - {2, nullptr, "GetTitlePid"}, + {2, &DebugMonitor::GetTitlePid, "GetTitlePid"}, {3, nullptr, "EnableDebugForTitleId"}, - {4, nullptr, "GetApplicationPid"}, + {4, &DebugMonitor::GetApplicationPid, "GetApplicationPid"}, {5, nullptr, "EnableDebugForApplication"}, {6, nullptr, "DisableDebug"}, }; @@ -56,21 +90,77 @@ public: RegisterHandlers(functions); } + +private: + void GetTitlePid(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_PM, "called, title_id={:016X}", title_id); + + const auto process = + SearchProcessList(kernel.GetProcessList(), [title_id](const auto& process) { + return process->GetTitleID() == title_id; + }); + + if (!process.has_value()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_PROCESS_NOT_FOUND); + return; + } + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push((*process)->GetProcessID()); + } + + void GetApplicationPid(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PM, "called"); + GetApplicationPidGeneric(ctx, kernel.GetProcessList()); + } + + const Kernel::KernelCore& kernel; }; class Info final : public ServiceFramework<Info> { public: - explicit Info() : ServiceFramework{"pm:info"} { + explicit Info(const std::vector<Kernel::SharedPtr<Kernel::Process>>& process_list) + : ServiceFramework{"pm:info"}, process_list(process_list) { static const FunctionInfo functions[] = { - {0, nullptr, "GetTitleId"}, + {0, &Info::GetTitleId, "GetTitleId"}, }; RegisterHandlers(functions); } + +private: + void GetTitleId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto process_id = rp.PopRaw<u64>(); + + LOG_DEBUG(Service_PM, "called, process_id={:016X}", process_id); + + const auto process = SearchProcessList(process_list, [process_id](const auto& process) { + return process->GetProcessID() == process_id; + }); + + if (!process.has_value()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_PROCESS_NOT_FOUND); + return; + } + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push((*process)->GetTitleID()); + } + + const std::vector<Kernel::SharedPtr<Kernel::Process>>& process_list; }; class Shell final : public ServiceFramework<Shell> { public: - explicit Shell() : ServiceFramework{"pm:shell"} { + explicit Shell(const Kernel::KernelCore& kernel) + : ServiceFramework{"pm:shell"}, kernel(kernel) { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "LaunchProcess"}, @@ -79,21 +169,31 @@ public: {3, nullptr, "GetProcessEventWaiter"}, {4, nullptr, "GetProcessEventType"}, {5, nullptr, "NotifyBootFinished"}, - {6, nullptr, "GetApplicationPid"}, + {6, &Shell::GetApplicationPid, "GetApplicationPid"}, {7, nullptr, "BoostSystemMemoryResourceLimit"}, {8, nullptr, "EnableAdditionalSystemThreads"}, + {9, nullptr, "GetUnimplementedEventHandle"}, }; // clang-format on RegisterHandlers(functions); } + +private: + void GetApplicationPid(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_PM, "called"); + GetApplicationPidGeneric(ctx, kernel.GetProcessList()); + } + + const Kernel::KernelCore& kernel; }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared<BootMode>()->InstallAsService(sm); - std::make_shared<DebugMonitor>()->InstallAsService(sm); - std::make_shared<Info>()->InstallAsService(sm); - std::make_shared<Shell>()->InstallAsService(sm); +void InstallInterfaces(Core::System& system) { + std::make_shared<BootMode>()->InstallAsService(system.ServiceManager()); + std::make_shared<DebugMonitor>(system.Kernel())->InstallAsService(system.ServiceManager()); + std::make_shared<Info>(system.Kernel().GetProcessList()) + ->InstallAsService(system.ServiceManager()); + std::make_shared<Shell>(system.Kernel())->InstallAsService(system.ServiceManager()); } } // namespace Service::PM diff --git a/src/core/hle/service/pm/pm.h b/src/core/hle/service/pm/pm.h index cc8d3f215..852e7050c 100644 --- a/src/core/hle/service/pm/pm.h +++ b/src/core/hle/service/pm/pm.h @@ -4,8 +4,8 @@ #pragma once -namespace Service::SM { -class ServiceManager; +namespace Core { +class System; } namespace Service::PM { @@ -16,6 +16,6 @@ enum class SystemBootMode { }; /// Registers all PM services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(Core::System& system); } // namespace Service::PM diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index e4fcee9f8..18d895263 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -2,26 +2,31 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/hex_util.h" #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/process.h" +#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/prepo/prepo.h" #include "core/hle/service/service.h" +#include "core/reporter.h" namespace Service::PlayReport { class PlayReport final : public ServiceFramework<PlayReport> { public: - explicit PlayReport(const char* name) : ServiceFramework{name} { + explicit PlayReport(const char* name, Core::System& system) + : ServiceFramework{name}, system(system) { // clang-format off static const FunctionInfo functions[] = { - {10100, nullptr, "SaveReportOld"}, - {10101, &PlayReport::SaveReportWithUserOld, "SaveReportWithUserOld"}, - {10102, nullptr, "SaveReport"}, - {10103, nullptr, "SaveReportWithUser"}, + {10100, &PlayReport::SaveReport<Core::Reporter::PlayReportType::Old>, "SaveReportOld"}, + {10101, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::Old>, "SaveReportWithUserOld"}, + {10102, &PlayReport::SaveReport<Core::Reporter::PlayReportType::New>, "SaveReport"}, + {10103, &PlayReport::SaveReportWithUser<Core::Reporter::PlayReportType::New>, "SaveReportWithUser"}, {10200, nullptr, "RequestImmediateTransmission"}, {10300, nullptr, "GetTransmissionStatus"}, - {20100, nullptr, "SaveSystemReport"}, - {20101, nullptr, "SaveSystemReportWithUser"}, + {20100, &PlayReport::SaveSystemReport, "SaveSystemReport"}, + {20101, &PlayReport::SaveSystemReportWithUser, "SaveSystemReportWithUser"}, {20200, nullptr, "SetOperationMode"}, {30100, nullptr, "ClearStorage"}, {30200, nullptr, "ClearStatistics"}, @@ -39,21 +44,96 @@ public: } private: - void SaveReportWithUserOld(Kernel::HLERequestContext& ctx) { - // TODO(ogniK): Do we want to add play report? - LOG_WARNING(Service_PREPO, "(STUBBED) called"); + template <Core::Reporter::PlayReportType Type> + void SaveReport(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto process_id = rp.PopRaw<u64>(); + + const auto data1 = ctx.ReadBuffer(0); + const auto data2 = ctx.ReadBuffer(1); + + LOG_DEBUG(Service_PREPO, + "called, type={:02X}, process_id={:016X}, data1_size={:016X}, data2_size={:016X}", + static_cast<u8>(Type), process_id, data1.size(), data2.size()); + + const auto& reporter{system.GetReporter()}; + reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2}, + process_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + template <Core::Reporter::PlayReportType Type> + void SaveReportWithUser(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto user_id = rp.PopRaw<u128>(); + const auto process_id = rp.PopRaw<u64>(); + + const auto data1 = ctx.ReadBuffer(0); + const auto data2 = ctx.ReadBuffer(1); + + LOG_DEBUG( + Service_PREPO, + "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, data1_size={:016X}, " + "data2_size={:016X}", + static_cast<u8>(Type), user_id[1], user_id[0], process_id, data1.size(), data2.size()); + + const auto& reporter{system.GetReporter()}; + reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), {data1, data2}, + process_id, user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SaveSystemReport(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto title_id = rp.PopRaw<u64>(); + + const auto data1 = ctx.ReadBuffer(0); + const auto data2 = ctx.ReadBuffer(1); + + LOG_DEBUG(Service_PREPO, "called, title_id={:016X}, data1_size={:016X}, data2_size={:016X}", + title_id, data1.size(), data2.size()); + + const auto& reporter{system.GetReporter()}; + reporter.SavePlayReport(Core::Reporter::PlayReportType::System, title_id, {data1, data2}); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SaveSystemReportWithUser(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto user_id = rp.PopRaw<u128>(); + const auto title_id = rp.PopRaw<u64>(); + + const auto data1 = ctx.ReadBuffer(0); + const auto data2 = ctx.ReadBuffer(1); + + LOG_DEBUG(Service_PREPO, + "called, user_id={:016X}{:016X}, title_id={:016X}, data1_size={:016X}, " + "data2_size={:016X}", + user_id[1], user_id[0], title_id, data1.size(), data2.size()); + + const auto& reporter{system.GetReporter()}; + reporter.SavePlayReport(Core::Reporter::PlayReportType::System, title_id, {data1, data2}, + std::nullopt, user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + + Core::System& system; }; -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared<PlayReport>("prepo:a")->InstallAsService(service_manager); - std::make_shared<PlayReport>("prepo:a2")->InstallAsService(service_manager); - std::make_shared<PlayReport>("prepo:m")->InstallAsService(service_manager); - std::make_shared<PlayReport>("prepo:s")->InstallAsService(service_manager); - std::make_shared<PlayReport>("prepo:u")->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared<PlayReport>("prepo:a", system)->InstallAsService(service_manager); + std::make_shared<PlayReport>("prepo:a2", system)->InstallAsService(service_manager); + std::make_shared<PlayReport>("prepo:m", system)->InstallAsService(service_manager); + std::make_shared<PlayReport>("prepo:s", system)->InstallAsService(service_manager); + std::make_shared<PlayReport>("prepo:u", system)->InstallAsService(service_manager); } } // namespace Service::PlayReport diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h index 0e7b01331..a5682ee26 100644 --- a/src/core/hle/service/prepo/prepo.h +++ b/src/core/hle/service/prepo/prepo.h @@ -8,8 +8,12 @@ namespace Service::SM { class ServiceManager; } +namespace Core { +class System; +} + namespace Service::PlayReport { -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::PlayReport diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 00806b0ed..831a427de 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -19,7 +19,6 @@ #include "core/hle/service/am/am.h" #include "core/hle/service/aoc/aoc_u.h" #include "core/hle/service/apm/apm.h" -#include "core/hle/service/arp/arp.h" #include "core/hle/service/audio/audio.h" #include "core/hle/service/bcat/module.h" #include "core/hle/service/bpc/bpc.h" @@ -33,6 +32,7 @@ #include "core/hle/service/fgm/fgm.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/friend/friend.h" +#include "core/hle/service/glue/glue.h" #include "core/hle/service/grc/grc.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/lbl/lbl.h" @@ -68,6 +68,7 @@ #include "core/hle/service/usb/usb.h" #include "core/hle/service/vi/vi.h" #include "core/hle/service/wlan/wlan.h" +#include "core/reporter.h" namespace Service { @@ -148,6 +149,8 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext } buf.push_back('}'); + Core::System::GetInstance().GetReporter().SaveUnimplementedFunctionReport( + ctx, ctx.GetCommand(), function_name, service_name); UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf)); } @@ -192,61 +195,61 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co // Module interface /// Initialize ServiceManager -void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system, - FileSys::VfsFilesystem& vfs) { +void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) { // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it // here and pass it into the respective InstallInterfaces functions. - auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system.CoreTiming()); + auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system); + system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false); SM::ServiceManager::InstallInterfaces(sm); - Account::InstallInterfaces(*sm); - AM::InstallInterfaces(*sm, nv_flinger); - AOC::InstallInterfaces(*sm); - APM::InstallInterfaces(*sm); - ARP::InstallInterfaces(*sm); - Audio::InstallInterfaces(*sm); + Account::InstallInterfaces(system); + AM::InstallInterfaces(*sm, nv_flinger, system); + AOC::InstallInterfaces(*sm, system); + APM::InstallInterfaces(system); + Audio::InstallInterfaces(*sm, system); BCAT::InstallInterfaces(*sm); BPC::InstallInterfaces(*sm); - BtDrv::InstallInterfaces(*sm); - BTM::InstallInterfaces(*sm); + BtDrv::InstallInterfaces(*sm, system); + BTM::InstallInterfaces(*sm, system); Capture::InstallInterfaces(*sm); ERPT::InstallInterfaces(*sm); ES::InstallInterfaces(*sm); EUPLD::InstallInterfaces(*sm); - Fatal::InstallInterfaces(*sm); + Fatal::InstallInterfaces(*sm, system); FGM::InstallInterfaces(*sm); - FileSystem::InstallInterfaces(*sm, vfs); - Friend::InstallInterfaces(*sm); + FileSystem::InstallInterfaces(system); + Friend::InstallInterfaces(*sm, system); + Glue::InstallInterfaces(system); GRC::InstallInterfaces(*sm); - HID::InstallInterfaces(*sm); + HID::InstallInterfaces(*sm, system); LBL::InstallInterfaces(*sm); LDN::InstallInterfaces(*sm); - LDR::InstallInterfaces(*sm); + LDR::InstallInterfaces(*sm, system); LM::InstallInterfaces(*sm); Migration::InstallInterfaces(*sm); Mii::InstallInterfaces(*sm); MM::InstallInterfaces(*sm); NCM::InstallInterfaces(*sm); NFC::InstallInterfaces(*sm); - NFP::InstallInterfaces(*sm); - NIFM::InstallInterfaces(*sm); - NIM::InstallInterfaces(*sm); + NFP::InstallInterfaces(*sm, system); + NIFM::InstallInterfaces(*sm, system); + NIM::InstallInterfaces(*sm, system); NPNS::InstallInterfaces(*sm); - NS::InstallInterfaces(*sm); - Nvidia::InstallInterfaces(*sm, *nv_flinger); + NS::InstallInterfaces(*sm, system); + Nvidia::InstallInterfaces(*sm, *nv_flinger, system); PCIe::InstallInterfaces(*sm); PCTL::InstallInterfaces(*sm); PCV::InstallInterfaces(*sm); - PlayReport::InstallInterfaces(*sm); - PM::InstallInterfaces(*sm); + PlayReport::InstallInterfaces(*sm, system); + PM::InstallInterfaces(system); PSC::InstallInterfaces(*sm); PSM::InstallInterfaces(*sm); Set::InstallInterfaces(*sm); 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); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index abbfe5524..aef964861 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -18,10 +18,6 @@ namespace Core { class System; } -namespace FileSys { -class VfsFilesystem; -} - namespace Kernel { class ClientPort; class ServerPort; @@ -31,6 +27,10 @@ class HLERequestContext; namespace Service { +namespace FileSystem { +class FileSystemController; +} // namespace FileSystem + namespace SM { class ServiceManager; } @@ -182,8 +182,7 @@ private: }; /// Initialize ServiceManager -void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system, - FileSys::VfsFilesystem& vfs); +void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system); /// Shutdown ServiceManager void Shutdown(); diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index 298d85011..b54214421 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -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 diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h index 31f9cb296..b154e08aa 100644 --- a/src/core/hle/service/set/set.h +++ b/src/core/hle/service/set/set.h @@ -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 diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp index 8d122ae33..9565e7de5 100644 --- a/src/core/hle/service/time/interface.cpp +++ b/src/core/hle/service/time/interface.cpp @@ -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, + Core::System& system, const char* name) + : Module::Interface(std::move(time), std::move(shared_memory), system, 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"}, diff --git a/src/core/hle/service/time/interface.h b/src/core/hle/service/time/interface.h index cd6b44dec..5c63a07f4 100644 --- a/src/core/hle/service/time/interface.h +++ b/src/core/hle/service/time/interface.h @@ -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, + Core::System& system, const char* name); ~Time() override; }; diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index 346bad80d..1b9ab8401 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -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,64 @@ 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, Core::System& system) + : ServiceFramework("ISteadyClock"), shared_memory(shared_memory), system(system) { 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 = system.CoreTiming(); + const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks()); + return {static_cast<u64_le>(ms.count() / 1000), {}}; } + + std::shared_ptr<SharedMemory> shared_memory; + Core::System& system; }; class ITimeZoneService final : public ServiceFramework<ITimeZoneService> { @@ -233,7 +275,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 +283,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 +291,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, system); } void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) { @@ -265,7 +307,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) { @@ -284,7 +326,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { return; } - const auto& core_timing = Core::System::GetInstance().CoreTiming(); + const auto& core_timing = system.CoreTiming(); const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks()); const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000), {}}; @@ -333,16 +375,56 @@ 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, Core::System& system, + const char* name) + : ServiceFramework(name), time(std::move(time)), shared_memory(std::move(shared_memory)), + system(system) {} 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, system, "time:a") + ->InstallAsService(system.ServiceManager()); + std::make_shared<Time>(time, shared_mem, system, "time:s") + ->InstallAsService(system.ServiceManager()); + std::make_shared<Time>(std::move(time), shared_mem, system, "time:u") + ->InstallAsService(system.ServiceManager()); } } // namespace Service::Time diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h index f11affe95..c32d32860 100644 --- a/src/core/hle/service/time/time.h +++ b/src/core/hle/service/time/time.h @@ -10,6 +10,8 @@ namespace Service::Time { +class SharedMemory; + struct LocationName { std::array<u8, 0x24> name; }; @@ -77,7 +79,9 @@ 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, Core::System& system, + const char* name); ~Interface() override; void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx); @@ -87,13 +91,18 @@ 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; + Core::System& system; }; }; /// Registers all Time services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(Core::System& system); } // namespace Service::Time diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp new file mode 100644 index 000000000..bfc81b83c --- /dev/null +++ b/src/core/hle/service/time/time_sharedmemory.cpp @@ -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 diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h new file mode 100644 index 000000000..cb8253541 --- /dev/null +++ b/src/core/hle/service/time/time_sharedmemory.h @@ -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 diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp index a8d088305..006a6d9ff 100644 --- a/src/core/hle/service/vi/display/vi_display.cpp +++ b/src/core/hle/service/vi/display/vi_display.cpp @@ -15,8 +15,8 @@ namespace Service::VI { -Display::Display(u64 id, std::string name) : id{id}, name{std::move(name)} { - auto& kernel = Core::System::GetInstance().Kernel(); +Display::Display(u64 id, std::string name, Core::System& system) : id{id}, name{std::move(name)} { + auto& kernel = system.Kernel(); vsync_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, fmt::format("Display VSync Event {}", id)); } diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h index 2acd46ff8..f56b5badc 100644 --- a/src/core/hle/service/vi/display/vi_display.h +++ b/src/core/hle/service/vi/display/vi_display.h @@ -26,7 +26,7 @@ public: /// @param id The unique ID for this display. /// @param name The name for this display. /// - Display(u64 id, std::string name); + Display(u64 id, std::string name, Core::System& system); ~Display(); Display(const Display&) = delete; diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index f1fa6ccd1..199b30635 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -21,6 +21,7 @@ #include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/writable_event.h" +#include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/buffer_queue.h" #include "core/hle/service/nvflinger/nvflinger.h" @@ -328,32 +329,22 @@ public: Data data; }; -struct BufferProducerFence { - u32 is_valid; - std::array<Nvidia::IoctlFence, 4> fences; -}; -static_assert(sizeof(BufferProducerFence) == 36, "BufferProducerFence has wrong size"); - class IGBPDequeueBufferResponseParcel : public Parcel { public: - explicit IGBPDequeueBufferResponseParcel(u32 slot) : slot(slot) {} + explicit IGBPDequeueBufferResponseParcel(u32 slot, Service::Nvidia::MultiFence& multi_fence) + : slot(slot), multi_fence(multi_fence) {} ~IGBPDequeueBufferResponseParcel() override = default; protected: void SerializeData() override { - // TODO(Subv): Find out how this Fence is used. - BufferProducerFence fence = {}; - fence.is_valid = 1; - for (auto& fence_ : fence.fences) - fence_.id = -1; - Write(slot); Write<u32_le>(1); - WriteObject(fence); + WriteObject(multi_fence); Write<u32_le>(0); } u32_le slot; + Service::Nvidia::MultiFence multi_fence; }; class IGBPRequestBufferRequestParcel : public Parcel { @@ -400,12 +391,6 @@ public: data = Read<Data>(); } - struct Fence { - u32_le id; - u32_le value; - }; - static_assert(sizeof(Fence) == 8, "Fence has wrong size"); - struct Data { u32_le slot; INSERT_PADDING_WORDS(3); @@ -418,15 +403,15 @@ public: s32_le scaling_mode; NVFlinger::BufferQueue::BufferTransformFlags transform; u32_le sticky_transform; - INSERT_PADDING_WORDS(2); - u32_le fence_is_valid; - std::array<Fence, 2> fences; + INSERT_PADDING_WORDS(1); + u32_le swap_interval; + Service::Nvidia::MultiFence multi_fence; Common::Rectangle<int> GetCropRect() const { return {crop_left, crop_top, crop_right, crop_bottom}; } }; - static_assert(sizeof(Data) == 80, "ParcelData has wrong size"); + static_assert(sizeof(Data) == 96, "ParcelData has wrong size"); Data data; }; @@ -547,11 +532,11 @@ private: IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()}; const u32 width{request.data.width}; const u32 height{request.data.height}; - std::optional<u32> slot = buffer_queue.DequeueBuffer(width, height); + auto result = buffer_queue.DequeueBuffer(width, height); - if (slot) { + if (result) { // Buffer is available - IGBPDequeueBufferResponseParcel response{*slot}; + IGBPDequeueBufferResponseParcel response{result->first, *result->second}; ctx.WriteBuffer(response.Serialize()); } else { // Wait the current thread until a buffer becomes available @@ -561,10 +546,10 @@ private: Kernel::ThreadWakeupReason reason) { // Repeat TransactParcel DequeueBuffer when a buffer is available auto& buffer_queue = nv_flinger->FindBufferQueue(id); - std::optional<u32> slot = buffer_queue.DequeueBuffer(width, height); - ASSERT_MSG(slot != std::nullopt, "Could not dequeue buffer."); + auto result = buffer_queue.DequeueBuffer(width, height); + ASSERT_MSG(result != std::nullopt, "Could not dequeue buffer."); - IGBPDequeueBufferResponseParcel response{*slot}; + IGBPDequeueBufferResponseParcel response{result->first, *result->second}; ctx.WriteBuffer(response.Serialize()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -582,7 +567,8 @@ private: IGBPQueueBufferRequestParcel request{ctx.ReadBuffer()}; buffer_queue.QueueBuffer(request.data.slot, request.data.transform, - request.data.GetCropRect()); + request.data.GetCropRect(), request.data.swap_interval, + request.data.multi_fence); IGBPQueueBufferResponseParcel response{1280, 720}; ctx.WriteBuffer(response.Serialize()); diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 10b13fb1d..d19c3623c 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -7,6 +7,7 @@ #include "common/common_funcs.h" #include "common/file_util.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -141,6 +142,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect const FileSys::PatchManager pm(metadata.GetTitleID()); // Load NSO modules + modules.clear(); const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); VAddr next_load_addr = base_address; for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", @@ -159,6 +161,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect } next_load_addr = *tentative_next_load_addr; + modules.insert_or_assign(load_addr, module); LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); // Register module with GDBStub GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false); @@ -174,7 +177,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect // Register the RomFS if a ".romfs" file was found if (romfs_iter != files.end() && *romfs_iter != nullptr) { romfs = *romfs_iter; - Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this)); + Core::System::GetInstance().GetFileSystemController().RegisterRomFS( + std::make_unique<FileSys::RomFSFactory>(*this)); } is_loaded = true; @@ -212,4 +216,13 @@ bool AppLoader_DeconstructedRomDirectory::IsRomFSUpdatable() const { return false; } +ResultStatus AppLoader_DeconstructedRomDirectory::ReadNSOModules(Modules& modules) { + if (!is_loaded) { + return ResultStatus::ErrorNotInitialized; + } + + modules = this->modules; + return ResultStatus::Success; +} + } // namespace Loader diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index 1a65c16a4..1c0a354a4 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -45,6 +45,8 @@ public: ResultStatus ReadTitle(std::string& title) override; bool IsRomFSUpdatable() const override; + ResultStatus ReadNSOModules(Modules& modules) override; + private: FileSys::ProgramMetadata metadata; FileSys::VirtualFile romfs; @@ -54,6 +56,8 @@ private: std::string name; u64 title_id{}; bool override_update; + + Modules modules; }; } // namespace Loader diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 6d4b02375..f1795fdd6 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -295,7 +295,7 @@ Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) { } } - std::vector<u8> program_image(total_image_size); + Kernel::PhysicalMemory program_image(total_image_size); std::size_t current_image_position = 0; Kernel::CodeSet codeset; diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp new file mode 100644 index 000000000..474b55cb1 --- /dev/null +++ b/src/core/loader/kip.cpp @@ -0,0 +1,102 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/file_sys/kernel_executable.h" +#include "core/file_sys/program_metadata.h" +#include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/code_set.h" +#include "core/hle/kernel/process.h" +#include "core/loader/kip.h" + +namespace Loader { + +namespace { +constexpr u32 PageAlignSize(u32 size) { + return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; +} +} // Anonymous namespace + +AppLoader_KIP::AppLoader_KIP(FileSys::VirtualFile file_) + : AppLoader(std::move(file_)), kip(std::make_unique<FileSys::KIP>(file)) {} + +AppLoader_KIP::~AppLoader_KIP() = default; + +FileType AppLoader_KIP::IdentifyType(const FileSys::VirtualFile& file) { + u32_le magic{}; + if (file->GetSize() < sizeof(u32) || file->ReadObject(&magic) != sizeof(u32)) { + return FileType::Error; + } + + if (magic == Common::MakeMagic('K', 'I', 'P', '1')) { + return FileType::KIP; + } + + return FileType::Error; +} + +FileType AppLoader_KIP::GetFileType() const { + return (kip != nullptr && kip->GetStatus() == ResultStatus::Success) ? FileType::KIP + : FileType::Error; +} + +AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) { + if (is_loaded) { + return {ResultStatus::ErrorAlreadyLoaded, {}}; + } + + if (kip == nullptr) { + return {ResultStatus::ErrorNullFile, {}}; + } + + if (kip->GetStatus() != ResultStatus::Success) { + return {kip->GetStatus(), {}}; + } + + const auto get_kip_address_space_type = [](const auto& kip) { + return kip.Is64Bit() + ? (kip.Is39BitAddressSpace() ? FileSys::ProgramAddressSpaceType::Is39Bit + : FileSys::ProgramAddressSpaceType::Is36Bit) + : FileSys::ProgramAddressSpaceType::Is32Bit; + }; + + const auto address_space = get_kip_address_space_type(*kip); + + FileSys::ProgramMetadata metadata; + metadata.LoadManual(kip->Is64Bit(), address_space, kip->GetMainThreadPriority(), + kip->GetMainThreadCpuCore(), kip->GetMainThreadStackSize(), + kip->GetTitleID(), 0xFFFFFFFFFFFFFFFF, kip->GetKernelCapabilities()); + + const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); + Kernel::CodeSet codeset; + Kernel::PhysicalMemory program_image; + + const auto load_segment = [&program_image](Kernel::CodeSet::Segment& segment, + const std::vector<u8>& data, u32 offset) { + segment.addr = offset; + segment.offset = offset; + segment.size = PageAlignSize(static_cast<u32>(data.size())); + program_image.resize(offset); + program_image.insert(program_image.end(), data.begin(), data.end()); + }; + + load_segment(codeset.CodeSegment(), kip->GetTextSection(), kip->GetTextOffset()); + load_segment(codeset.RODataSegment(), kip->GetRODataSection(), kip->GetRODataOffset()); + load_segment(codeset.DataSegment(), kip->GetDataSection(), kip->GetDataOffset()); + + program_image.resize(PageAlignSize(kip->GetBSSOffset()) + kip->GetBSSSize()); + codeset.DataSegment().size += kip->GetBSSSize(); + + GDBStub::RegisterModule(kip->GetName(), base_address, base_address + program_image.size()); + + codeset.memory = std::move(program_image); + process.LoadModule(std::move(codeset), base_address); + + LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address); + + is_loaded = true; + return {ResultStatus::Success, + LoadParameters{kip->GetMainThreadPriority(), kip->GetMainThreadStackSize()}}; +} + +} // namespace Loader diff --git a/src/core/loader/kip.h b/src/core/loader/kip.h new file mode 100644 index 000000000..12ca40269 --- /dev/null +++ b/src/core/loader/kip.h @@ -0,0 +1,35 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/loader/loader.h" + +namespace FileSys { +class KIP; +} + +namespace Loader { + +class AppLoader_KIP final : public AppLoader { +public: + explicit AppLoader_KIP(FileSys::VirtualFile file); + ~AppLoader_KIP() override; + + /** + * Returns the type of the file + * @param file std::shared_ptr<VfsFile> open file + * @return FileType found, or FileType::Error if this loader doesn't know it + */ + static FileType IdentifyType(const FileSys::VirtualFile& file); + + FileType GetFileType() const override; + + LoadResult Load(Kernel::Process& process) override; + +private: + std::unique_ptr<FileSys::KIP> kip; +}; + +} // namespace Loader diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index d8cc30959..59ca7091a 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -11,6 +11,7 @@ #include "core/hle/kernel/process.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/elf.h" +#include "core/loader/kip.h" #include "core/loader/nax.h" #include "core/loader/nca.h" #include "core/loader/nro.h" @@ -36,6 +37,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) { CHECK_TYPE(XCI) CHECK_TYPE(NAX) CHECK_TYPE(NSP) + CHECK_TYPE(KIP) #undef CHECK_TYPE @@ -63,6 +65,8 @@ FileType GuessFromFilename(const std::string& name) { return FileType::XCI; if (extension == "nsp") return FileType::NSP; + if (extension == "kip") + return FileType::KIP; return FileType::Unknown; } @@ -83,6 +87,8 @@ std::string GetFileTypeString(FileType type) { return "NAX"; case FileType::NSP: return "NSP"; + case FileType::KIP: + return "KIP"; case FileType::DeconstructedRomDirectory: return "Directory"; case FileType::Error: @@ -93,7 +99,7 @@ std::string GetFileTypeString(FileType type) { return "unknown"; } -constexpr std::array<const char*, 62> RESULT_MESSAGES{ +constexpr std::array<const char*, 66> RESULT_MESSAGES{ "The operation completed successfully.", "The loader requested to load is already loaded.", "The operation is not implemented.", @@ -156,6 +162,10 @@ constexpr std::array<const char*, 62> RESULT_MESSAGES{ "The BKTR-type NCA has a bad Subsection bucket.", "The BKTR-type NCA is missing the base RomFS.", "The NSP or XCI does not contain an update in addition to the base game.", + "The KIP file has a bad header.", + "The KIP BLZ decompression of the section failed unexpectedly.", + "The INI file has a bad header.", + "The INI file contains more than the maximum allowable number of KIP files.", }; std::ostream& operator<<(std::ostream& os, ResultStatus status) { @@ -205,6 +215,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT case FileType::NSP: return std::make_unique<AppLoader_NSP>(std::move(file)); + // NX KIP (Kernel Internal Process) file format + case FileType::KIP: + return std::make_unique<AppLoader_KIP>(std::move(file)); + // NX deconstructed ROM directory. case FileType::DeconstructedRomDirectory: return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 869406b75..227ecc704 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -37,6 +37,7 @@ enum class FileType { NSP, XCI, NAX, + KIP, DeconstructedRomDirectory, }; @@ -124,6 +125,10 @@ enum class ResultStatus : u16 { ErrorBadSubsectionBuckets, ErrorMissingBKTRBaseRomFS, ErrorNoPackedUpdate, + ErrorBadKIPHeader, + ErrorBLZDecompressionFailed, + ErrorBadINIHeader, + ErrorINITooManyKIPs, }; std::ostream& operator<<(std::ostream& os, ResultStatus status); @@ -267,6 +272,12 @@ public: return ResultStatus::ErrorNotImplemented; } + using Modules = std::map<VAddr, std::string>; + + virtual ResultStatus ReadNSOModules(Modules& modules) { + return ResultStatus::ErrorNotImplemented; + } + protected: FileSys::VirtualFile file; bool is_loaded = false; diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index 34efef09a..a152981a0 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -94,4 +94,8 @@ ResultStatus AppLoader_NAX::ReadLogo(std::vector<u8>& buffer) { return nca_loader->ReadLogo(buffer); } +ResultStatus AppLoader_NAX::ReadNSOModules(Modules& modules) { + return nca_loader->ReadNSOModules(modules); +} + } // namespace Loader diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index 00f1659c1..eaec9bf58 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -42,6 +42,8 @@ public: ResultStatus ReadBanner(std::vector<u8>& buffer) override; ResultStatus ReadLogo(std::vector<u8>& buffer) override; + ResultStatus ReadNSOModules(Modules& modules) override; + private: std::unique_ptr<FileSys::NAX> nax; std::unique_ptr<AppLoader_NCA> nca_loader; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index b3f8f1083..5a0469978 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -6,6 +6,7 @@ #include "common/file_util.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/romfs_factory.h" #include "core/hle/kernel/process.h" @@ -57,7 +58,8 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) { } if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) { - Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this)); + Core::System::GetInstance().GetFileSystemController().RegisterRomFS( + std::make_unique<FileSys::RomFSFactory>(*this)); } is_loaded = true; @@ -105,4 +107,13 @@ ResultStatus AppLoader_NCA::ReadLogo(std::vector<u8>& buffer) { buffer = logo->GetFile("NintendoLogo.png")->ReadAllBytes(); return ResultStatus::Success; } + +ResultStatus AppLoader_NCA::ReadNSOModules(Modules& modules) { + if (directory_loader == nullptr) { + return ResultStatus::ErrorNotInitialized; + } + + return directory_loader->ReadNSOModules(modules); +} + } // namespace Loader diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 94f0ed677..e47dc0e47 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -42,6 +42,8 @@ public: ResultStatus ReadBanner(std::vector<u8>& buffer) override; ResultStatus ReadLogo(std::vector<u8>& buffer) override; + ResultStatus ReadNSOModules(Modules& modules) override; + private: std::unique_ptr<FileSys::NCA> nca; std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader; diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 6a0ca389b..175898b91 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -10,6 +10,7 @@ #include "common/file_util.h" #include "common/logging/log.h" #include "common/swap.h" +#include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/romfs_factory.h" #include "core/file_sys/vfs_offset.h" @@ -143,7 +144,7 @@ static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data, } // Build program image - std::vector<u8> program_image(PageAlignSize(nro_header.file_size)); + Kernel::PhysicalMemory program_image(PageAlignSize(nro_header.file_size)); std::memcpy(program_image.data(), data.data(), program_image.size()); if (program_image.size() != PageAlignSize(nro_header.file_size)) { return {}; @@ -214,7 +215,8 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) { } if (romfs != nullptr) { - Service::FileSystem::RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(*this)); + Core::System::GetInstance().GetFileSystemController().RegisterRomFS( + std::make_unique<FileSys::RomFSFactory>(*this)); } is_loaded = true; @@ -258,6 +260,15 @@ ResultStatus AppLoader_NRO::ReadTitle(std::string& title) { return ResultStatus::Success; } +ResultStatus AppLoader_NRO::ReadControlData(FileSys::NACP& control) { + if (nacp == nullptr) { + return ResultStatus::ErrorNoControl; + } + + control = *nacp; + return ResultStatus::Success; +} + bool AppLoader_NRO::IsRomFSUpdatable() const { return false; } diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index 1ffdae805..71811bc29 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h @@ -43,6 +43,7 @@ public: ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; ResultStatus ReadTitle(std::string& title) override; + ResultStatus ReadControlData(FileSys::NACP& control) override; bool IsRomFSUpdatable() const override; private: diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 62c090353..e75c700ad 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -89,7 +89,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, // Build program image Kernel::CodeSet codeset; - std::vector<u8> program_image; + Kernel::PhysicalMemory program_image; for (std::size_t i = 0; i < nso_header.segments.size(); ++i) { std::vector<u8> data = file.ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset); @@ -152,8 +152,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, auto& system = Core::System::GetInstance(); const auto cheats = pm->CreateCheatList(system, nso_header.build_id); if (!cheats.empty()) { - system.RegisterCheatList(cheats, Common::HexArrayToString(nso_header.build_id), - load_base, load_base + program_image.size()); + system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size); } } @@ -172,11 +171,15 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } + modules.clear(); + // Load module const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); if (!LoadModule(process, *file, base_address, true)) { return {ResultStatus::ErrorLoadingNSO, {}}; } + + modules.insert_or_assign(base_address, file->GetName()); LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address); is_loaded = true; @@ -184,4 +187,9 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) { LoadParameters{Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE}}; } +ResultStatus AppLoader_NSO::ReadNSOModules(Modules& modules) { + modules = this->modules; + return ResultStatus::Success; +} + } // namespace Loader diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index fdce9191c..58cbe162d 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -85,6 +85,11 @@ public: std::optional<FileSys::PatchManager> pm = {}); LoadResult Load(Kernel::Process& process) override; + + ResultStatus ReadNSOModules(Modules& modules) override; + +private: + Modules modules; }; } // namespace Loader diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index ad56bbb38..13950fc08 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -5,6 +5,7 @@ #include <vector> #include "common/common_types.h" +#include "core/core.h" #include "core/file_sys/card_image.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" @@ -26,20 +27,18 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file) if (nsp->GetStatus() != ResultStatus::Success) return; - if (nsp->IsExtractedType()) - return; - - const auto control_nca = - nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); - if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) - return; - - std::tie(nacp_file, icon_file) = - FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca); if (nsp->IsExtractedType()) { secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); } else { + const auto control_nca = + nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); + if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) + return; + + std::tie(nacp_file, icon_file) = + FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca); + if (title_id == 0) return; @@ -56,11 +55,11 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) { if (nsp.GetStatus() == ResultStatus::Success) { // Extracted Type case if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr && - FileSys::IsDirectoryExeFS(nsp.GetExeFS()) && nsp.GetRomFS() != nullptr) { + FileSys::IsDirectoryExeFS(nsp.GetExeFS())) { return FileType::NSP; } - // Non-Ectracted Type case + // Non-Extracted Type case if (!nsp.IsExtractedType() && nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr && AppLoader_NCA::IdentifyType(nsp.GetNCAFile( @@ -77,7 +76,7 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) { return {ResultStatus::ErrorAlreadyLoaded, {}}; } - if (title_id == 0) { + if (!nsp->IsExtractedType() && title_id == 0) { return {ResultStatus::ErrorNSPMissingProgramNCA, {}}; } @@ -91,7 +90,8 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) { return {nsp_program_status, {}}; } - if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) { + if (!nsp->IsExtractedType() && + nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) { if (!Core::Crypto::KeyManager::KeyFileExists(false)) { return {ResultStatus::ErrorMissingProductionKeyFile, {}}; } @@ -106,7 +106,8 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) { FileSys::VirtualFile update_raw; if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) { - Service::FileSystem::SetPackedUpdate(std::move(update_raw)); + Core::System::GetInstance().GetFileSystemController().SetPackedUpdate( + std::move(update_raw)); } is_loaded = true; @@ -168,7 +169,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(); @@ -183,4 +185,8 @@ ResultStatus AppLoader_NSP::ReadLogo(std::vector<u8>& buffer) { return secondary_loader->ReadLogo(buffer); } +ResultStatus AppLoader_NSP::ReadNSOModules(Modules& modules) { + return secondary_loader->ReadNSOModules(modules); +} + } // namespace Loader diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 85e870bdf..868b028d3 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -49,6 +49,8 @@ public: ResultStatus ReadBanner(std::vector<u8>& buffer) override; ResultStatus ReadLogo(std::vector<u8>& buffer) override; + ResultStatus ReadNSOModules(Modules& modules) override; + private: std::unique_ptr<FileSys::NSP> nsp; std::unique_ptr<AppLoader> secondary_loader; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 1e285a053..7186ad1ff 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -5,6 +5,7 @@ #include <vector> #include "common/common_types.h" +#include "core/core.h" #include "core/file_sys/card_image.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" @@ -72,7 +73,8 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) { FileSys::VirtualFile update_raw; if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) { - Service::FileSystem::SetPackedUpdate(std::move(update_raw)); + Core::System::GetInstance().GetFileSystemController().SetPackedUpdate( + std::move(update_raw)); } is_loaded = true; @@ -134,7 +136,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(); @@ -149,4 +151,8 @@ ResultStatus AppLoader_XCI::ReadLogo(std::vector<u8>& buffer) { return nca_loader->ReadLogo(buffer); } +ResultStatus AppLoader_XCI::ReadNSOModules(Modules& modules) { + return nca_loader->ReadNSOModules(modules); +} + } // namespace Loader diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index ae7145b14..618ae2f47 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -49,6 +49,8 @@ public: ResultStatus ReadBanner(std::vector<u8>& buffer) override; ResultStatus ReadLogo(std::vector<u8>& buffer) override; + ResultStatus ReadNSOModules(Modules& modules) override; + private: std::unique_ptr<FileSys::XCI> xci; std::unique_ptr<AppLoader_NCA> nca_loader; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index f18f6226b..9e030789d 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -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 { @@ -45,8 +43,13 @@ static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* me // During boot, current_page_table might not be set yet, in which case we need not flush if (Core::System::GetInstance().IsPoweredOn()) { - Core::System::GetInstance().GPU().FlushAndInvalidateRegion(base << PAGE_BITS, - size * PAGE_SIZE); + auto& gpu = Core::System::GetInstance().GPU(); + for (u64 i = 0; i < size; i++) { + const auto page = base + i; + if (page_table.attributes[page] == Common::PageType::RasterizerCachedMemory) { + gpu.FlushAndInvalidateRegion(page << PAGE_BITS, PAGE_SIZE); + } + } } VAddr end = base + size; diff --git a/src/core/memory.h b/src/core/memory.h index 04e2c5f1d..09008e1dd 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -8,10 +8,6 @@ #include <string> #include "common/common_types.h" -namespace Common { -struct PageTable; -} - namespace Kernel { class Process; } diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp new file mode 100644 index 000000000..b56cb0627 --- /dev/null +++ b/src/core/memory/cheat_engine.cpp @@ -0,0 +1,234 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <locale> +#include "common/hex_util.h" +#include "common/microprofile.h" +#include "common/swap.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hle/kernel/process.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" +#include "core/memory/cheat_engine.h" + +namespace Memory { + +constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 12); +constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; + +StandardVmCallbacks::StandardVmCallbacks(const Core::System& system, + const CheatProcessMetadata& metadata) + : system(system), metadata(metadata) {} + +StandardVmCallbacks::~StandardVmCallbacks() = default; + +void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) { + ReadBlock(SanitizeAddress(address), data, size); +} + +void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) { + WriteBlock(SanitizeAddress(address), data, size); +} + +u64 StandardVmCallbacks::HidKeysDown() { + const auto applet_resource = + system.ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource(); + if (applet_resource == nullptr) { + LOG_WARNING(CheatEngine, + "Attempted to read input state, but applet resource is not initialized!"); + return false; + } + + const auto press_state = + applet_resource + ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad) + .GetAndResetPressState(); + return press_state & KEYPAD_BITMASK; +} + +void StandardVmCallbacks::DebugLog(u8 id, u64 value) { + LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value); +} + +void StandardVmCallbacks::CommandLog(std::string_view data) { + LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}", + data.back() == '\n' ? data.substr(0, data.size() - 1) : data); +} + +VAddr StandardVmCallbacks::SanitizeAddress(VAddr in) const { + if ((in < metadata.main_nso_extents.base || + in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) && + (in < metadata.heap_extents.base || + in >= metadata.heap_extents.base + metadata.heap_extents.size)) { + LOG_ERROR(CheatEngine, + "Cheat attempting to access memory at invalid address={:016X}, if this " + "persists, " + "the cheat may be incorrect. However, this may be normal early in execution if " + "the game has not properly set up yet.", + in); + return 0; ///< Invalid addresses will hard crash + } + + return in; +} + +CheatParser::~CheatParser() = default; + +TextCheatParser::~TextCheatParser() = default; + +namespace { +template <char match> +std::string_view ExtractName(std::string_view data, std::size_t start_index) { + auto end_index = start_index; + while (data[end_index] != match) { + ++end_index; + if (end_index > data.size() || + (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) { + return {}; + } + } + + return data.substr(start_index, end_index - start_index); +} +} // Anonymous namespace + +std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system, + std::string_view data) const { + std::vector<CheatEntry> out(1); + std::optional<u64> current_entry = std::nullopt; + + for (std::size_t i = 0; i < data.size(); ++i) { + if (::isspace(data[i])) { + continue; + } + + if (data[i] == '{') { + current_entry = 0; + + if (out[*current_entry].definition.num_opcodes > 0) { + return {}; + } + + const auto name = ExtractName<'}'>(data, i + 1); + if (name.empty()) { + return {}; + } + + std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), + std::min<std::size_t>(out[*current_entry].definition.readable_name.size(), + name.size())); + out[*current_entry] + .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = + '\0'; + + i += name.length() + 1; + } else if (data[i] == '[') { + current_entry = out.size(); + out.emplace_back(); + + const auto name = ExtractName<']'>(data, i + 1); + if (name.empty()) { + return {}; + } + + std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), + std::min<std::size_t>(out[*current_entry].definition.readable_name.size(), + name.size())); + out[*current_entry] + .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = + '\0'; + + i += name.length() + 1; + } else if (::isxdigit(data[i])) { + if (!current_entry || out[*current_entry].definition.num_opcodes >= + out[*current_entry].definition.opcodes.size()) { + return {}; + } + + const auto hex = std::string(data.substr(i, 8)); + if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) { + return {}; + } + + out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = + std::stoul(hex, nullptr, 0x10); + + i += 8; + } else { + return {}; + } + } + + out[0].enabled = out[0].definition.num_opcodes > 0; + out[0].cheat_id = 0; + + for (u32 i = 1; i < out.size(); ++i) { + out[i].enabled = out[i].definition.num_opcodes > 0; + out[i].cheat_id = i; + } + + return out; +} + +CheatEngine::CheatEngine(Core::System& system, std::vector<CheatEntry> cheats, + const std::array<u8, 0x20>& build_id) + : system{system}, core_timing{system.CoreTiming()}, vm{std::make_unique<StandardVmCallbacks>( + system, metadata)}, + cheats(std::move(cheats)) { + metadata.main_nso_build_id = build_id; +} + +CheatEngine::~CheatEngine() { + core_timing.UnscheduleEvent(event, 0); +} + +void CheatEngine::Initialize() { + event = core_timing.RegisterEvent( + "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id), + [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); + core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); + + metadata.process_id = system.CurrentProcess()->GetProcessID(); + metadata.title_id = system.CurrentProcess()->GetTitleID(); + + const auto& vm_manager = system.CurrentProcess()->VMManager(); + metadata.heap_extents = {vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionSize()}; + metadata.address_space_extents = {vm_manager.GetAddressSpaceBaseAddress(), + vm_manager.GetAddressSpaceSize()}; + metadata.alias_extents = {vm_manager.GetMapRegionBaseAddress(), vm_manager.GetMapRegionSize()}; + + is_pending_reload.exchange(true); +} + +void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) { + metadata.main_nso_extents = {main_region_begin, main_region_size}; +} + +void CheatEngine::Reload(std::vector<CheatEntry> cheats) { + this->cheats = std::move(cheats); + is_pending_reload.exchange(true); +} + +MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); + +void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { + if (is_pending_reload.exchange(false)) { + vm.LoadProgram(cheats); + } + + if (vm.GetProgramSize() == 0) { + return; + } + + MICROPROFILE_SCOPE(Cheat_Engine); + + vm.Execute(metadata); + + core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); +} + +} // namespace Memory diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h new file mode 100644 index 000000000..0f012e9b5 --- /dev/null +++ b/src/core/memory/cheat_engine.h @@ -0,0 +1,86 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <vector> +#include "common/common_types.h" +#include "core/memory/dmnt_cheat_types.h" +#include "core/memory/dmnt_cheat_vm.h" + +namespace Core { +class System; +} + +namespace Core::Timing { +class CoreTiming; +struct EventType; +} // namespace Core::Timing + +namespace Memory { + +class StandardVmCallbacks : public DmntCheatVm::Callbacks { +public: + StandardVmCallbacks(const Core::System& system, const CheatProcessMetadata& metadata); + ~StandardVmCallbacks() override; + + void MemoryRead(VAddr address, void* data, u64 size) override; + void MemoryWrite(VAddr address, const void* data, u64 size) override; + u64 HidKeysDown() override; + void DebugLog(u8 id, u64 value) override; + void CommandLog(std::string_view data) override; + +private: + VAddr SanitizeAddress(VAddr address) const; + + const CheatProcessMetadata& metadata; + const Core::System& system; +}; + +// Intermediary class that parses a text file or other disk format for storing cheats into a +// CheatList object, that can be used for execution. +class CheatParser { +public: + virtual ~CheatParser(); + + virtual std::vector<CheatEntry> Parse(const Core::System& system, + std::string_view data) const = 0; +}; + +// CheatParser implementation that parses text files +class TextCheatParser final : public CheatParser { +public: + ~TextCheatParser() override; + + std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override; +}; + +// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming +class CheatEngine final { +public: + CheatEngine(Core::System& system_, std::vector<CheatEntry> cheats_, + const std::array<u8, 0x20>& build_id); + ~CheatEngine(); + + void Initialize(); + void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size); + + void Reload(std::vector<CheatEntry> cheats); + +private: + void FrameCallback(u64 userdata, s64 cycles_late); + + DmntCheatVm vm; + CheatProcessMetadata metadata; + + std::vector<CheatEntry> cheats; + std::atomic_bool is_pending_reload{false}; + + Core::Timing::EventType* event{}; + Core::Timing::CoreTiming& core_timing; + Core::System& system; +}; + +} // namespace Memory diff --git a/src/core/memory/dmnt_cheat_types.h b/src/core/memory/dmnt_cheat_types.h new file mode 100644 index 000000000..bf68fa0fe --- /dev/null +++ b/src/core/memory/dmnt_cheat_types.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Adapted by DarkLordZach for use/interaction with yuzu + * + * Modifications 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" + +namespace Memory { + +struct MemoryRegionExtents { + u64 base{}; + u64 size{}; +}; + +struct CheatProcessMetadata { + u64 process_id{}; + u64 title_id{}; + MemoryRegionExtents main_nso_extents{}; + MemoryRegionExtents heap_extents{}; + MemoryRegionExtents alias_extents{}; + MemoryRegionExtents address_space_extents{}; + std::array<u8, 0x20> main_nso_build_id{}; +}; + +struct CheatDefinition { + std::array<char, 0x40> readable_name{}; + u32 num_opcodes{}; + std::array<u32, 0x100> opcodes{}; +}; + +struct CheatEntry { + bool enabled{}; + u32 cheat_id{}; + CheatDefinition definition{}; +}; + +} // namespace Memory diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp new file mode 100644 index 000000000..cc16d15a4 --- /dev/null +++ b/src/core/memory/dmnt_cheat_vm.cpp @@ -0,0 +1,1212 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Adapted by DarkLordZach for use/interaction with yuzu + * + * Modifications Copyright 2019 yuzu emulator team + * Licensed under GPLv2 or any later version + * Refer to the license.txt file included. + */ + +#include "common/assert.h" +#include "common/scope_exit.h" +#include "core/memory/dmnt_cheat_types.h" +#include "core/memory/dmnt_cheat_vm.h" + +namespace Memory { + +DmntCheatVm::DmntCheatVm(std::unique_ptr<Callbacks> callbacks) : callbacks(std::move(callbacks)) {} + +DmntCheatVm::~DmntCheatVm() = default; + +void DmntCheatVm::DebugLog(u32 log_id, u64 value) { + callbacks->DebugLog(static_cast<u8>(log_id), value); +} + +void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) { + if (auto store_static = std::get_if<StoreStaticOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Store Static"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width)); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast<u32>(store_static->mem_type))); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address)); + callbacks->CommandLog(fmt::format("Value: {:X}", store_static->value.bit64)); + } else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Begin Conditional"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width)); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast<u32>(begin_cond->mem_type))); + callbacks->CommandLog( + fmt::format("Cond Type: {:X}", static_cast<u32>(begin_cond->cond_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address)); + callbacks->CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64)); + } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: End Conditional"); + } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&opcode.opcode)) { + if (ctrl_loop->start_loop) { + callbacks->CommandLog("Opcode: Start Loop"); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index)); + callbacks->CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters)); + } else { + callbacks->CommandLog("Opcode: End Loop"); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index)); + } + } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Load Register Static"); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index)); + callbacks->CommandLog(fmt::format("Value: {:X}", ldr_static->value)); + } else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Load Register Memory"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width)); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index)); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast<u32>(ldr_memory->mem_type))); + callbacks->CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address)); + } else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Store Static to Address"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width)); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index)); + if (str_static->add_offset_reg) { + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index)); + } + callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg)); + callbacks->CommandLog(fmt::format("Value: {:X}", str_static->value)); + } else if (auto perform_math_static = + std::get_if<PerformArithmeticStaticOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Perform Static Arithmetic"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width)); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index)); + callbacks->CommandLog( + fmt::format("Math Type: {:X}", static_cast<u32>(perform_math_static->math_type))); + callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_static->value)); + } else if (auto begin_keypress_cond = + std::get_if<BeginKeypressConditionalOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Begin Keypress Conditional"); + callbacks->CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask)); + } else if (auto perform_math_reg = + std::get_if<PerformArithmeticRegisterOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Perform Register Arithmetic"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width)); + callbacks->CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index)); + callbacks->CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index)); + if (perform_math_reg->has_immediate) { + callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64)); + } else { + callbacks->CommandLog( + fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index)); + } + } else if (auto str_register = std::get_if<StoreRegisterToAddressOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Store Register to Address"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width)); + callbacks->CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index)); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index)); + callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg)); + switch (str_register->ofs_type) { + case StoreRegisterOffsetType::None: + break; + case StoreRegisterOffsetType::Reg: + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index)); + break; + case StoreRegisterOffsetType::Imm: + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address)); + break; + case StoreRegisterOffsetType::MemReg: + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type))); + break; + case StoreRegisterOffsetType::MemImm: + case StoreRegisterOffsetType::MemImmReg: + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address)); + break; + } + } else if (auto begin_reg_cond = std::get_if<BeginRegisterConditionalOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Begin Register Conditional"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width)); + callbacks->CommandLog( + fmt::format("Cond Type: {:X}", static_cast<u32>(begin_reg_cond->cond_type))); + callbacks->CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index)); + switch (begin_reg_cond->comp_type) { + case CompareRegisterValueType::StaticValue: + callbacks->CommandLog("Comp Type: Static Value"); + callbacks->CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64)); + break; + case CompareRegisterValueType::OtherRegister: + callbacks->CommandLog("Comp Type: Other Register"); + callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index)); + break; + case CompareRegisterValueType::MemoryRelAddr: + callbacks->CommandLog("Comp Type: Memory Relative Address"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address)); + break; + case CompareRegisterValueType::MemoryOfsReg: + callbacks->CommandLog("Comp Type: Memory Offset Register"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type))); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index)); + break; + case CompareRegisterValueType::RegisterRelAddr: + callbacks->CommandLog("Comp Type: Register Relative Address"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address)); + break; + case CompareRegisterValueType::RegisterOfsReg: + callbacks->CommandLog("Comp Type: Register Offset Register"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index)); + break; + } + } else if (auto save_restore_reg = std::get_if<SaveRestoreRegisterOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Save or Restore Register"); + callbacks->CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index)); + callbacks->CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index)); + callbacks->CommandLog( + fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_reg->op_type))); + } else if (auto save_restore_regmask = + std::get_if<SaveRestoreRegisterMaskOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Save or Restore Register Mask"); + callbacks->CommandLog( + fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_regmask->op_type))); + for (std::size_t i = 0; i < NumRegisters; i++) { + callbacks->CommandLog( + fmt::format("Act[{:02X}]: {:d}", i, save_restore_regmask->should_operate[i])); + } + } else if (auto debug_log = std::get_if<DebugLogOpcode>(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Debug Log"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width)); + callbacks->CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id)); + callbacks->CommandLog( + fmt::format("Val Type: {:X}", static_cast<u32>(debug_log->val_type))); + switch (debug_log->val_type) { + case DebugLogValueType::RegisterValue: + callbacks->CommandLog("Val Type: Register Value"); + callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index)); + break; + case DebugLogValueType::MemoryRelAddr: + callbacks->CommandLog("Val Type: Memory Relative Address"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address)); + break; + case DebugLogValueType::MemoryOfsReg: + callbacks->CommandLog("Val Type: Memory Offset Register"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type))); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index)); + break; + case DebugLogValueType::RegisterRelAddr: + callbacks->CommandLog("Val Type: Register Relative Address"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address)); + break; + case DebugLogValueType::RegisterOfsReg: + callbacks->CommandLog("Val Type: Register Offset Register"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index)); + break; + } + } else if (auto instr = std::get_if<UnrecognizedInstruction>(&opcode.opcode)) { + callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast<u32>(instr->opcode))); + } +} + +DmntCheatVm::Callbacks::~Callbacks() = default; + +bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { + // If we've ever seen a decode failure, return false. + bool valid = decode_success; + CheatVmOpcode opcode = {}; + SCOPE_EXIT({ + decode_success &= valid; + if (valid) { + out = opcode; + } + }); + + // Helper function for getting instruction dwords. + const auto GetNextDword = [&] { + if (instruction_ptr >= num_opcodes) { + valid = false; + return static_cast<u32>(0); + } + return program[instruction_ptr++]; + }; + + // Helper function for parsing a VmInt. + const auto GetNextVmInt = [&](const u32 bit_width) { + VmInt val{}; + + const u32 first_dword = GetNextDword(); + switch (bit_width) { + case 1: + val.bit8 = static_cast<u8>(first_dword); + break; + case 2: + val.bit16 = static_cast<u16>(first_dword); + break; + case 4: + val.bit32 = first_dword; + break; + case 8: + val.bit64 = (static_cast<u64>(first_dword) << 32ul) | static_cast<u64>(GetNextDword()); + break; + } + + return val; + }; + + // Read opcode. + const u32 first_dword = GetNextDword(); + if (!valid) { + return valid; + } + + auto opcode_type = static_cast<CheatVmOpcodeType>(((first_dword >> 28) & 0xF)); + if (opcode_type >= CheatVmOpcodeType::ExtendedWidth) { + opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) | + ((first_dword >> 24) & 0xF)); + } + if (opcode_type >= CheatVmOpcodeType::DoubleExtendedWidth) { + opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) | + ((first_dword >> 20) & 0xF)); + } + + // detect condition start. + switch (opcode_type) { + case CheatVmOpcodeType::BeginConditionalBlock: + case CheatVmOpcodeType::BeginKeypressConditionalBlock: + case CheatVmOpcodeType::BeginRegisterConditionalBlock: + opcode.begin_conditional_block = true; + break; + default: + opcode.begin_conditional_block = false; + break; + } + + switch (opcode_type) { + case CheatVmOpcodeType::StoreStatic: { + StoreStaticOpcode store_static{}; + // 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) + // Read additional words. + const u32 second_dword = GetNextDword(); + store_static.bit_width = (first_dword >> 24) & 0xF; + store_static.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF); + store_static.offset_register = ((first_dword >> 16) & 0xF); + store_static.rel_address = + (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword); + store_static.value = GetNextVmInt(store_static.bit_width); + opcode.opcode = store_static; + } break; + case CheatVmOpcodeType::BeginConditionalBlock: { + BeginConditionalOpcode begin_cond{}; + // 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) + // Read additional words. + const u32 second_dword = GetNextDword(); + begin_cond.bit_width = (first_dword >> 24) & 0xF; + begin_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF); + begin_cond.cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF); + begin_cond.rel_address = + (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword); + begin_cond.value = GetNextVmInt(begin_cond.bit_width); + opcode.opcode = begin_cond; + } break; + case CheatVmOpcodeType::EndConditionalBlock: { + // 20000000 + // There's actually nothing left to process here! + opcode.opcode = EndConditionalOpcode{}; + } break; + case CheatVmOpcodeType::ControlLoop: { + ControlLoopOpcode ctrl_loop{}; + // 300R0000 VVVVVVVV + // 310R0000 + // Parse register, whether loop start or loop end. + ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0; + ctrl_loop.reg_index = ((first_dword >> 20) & 0xF); + + // Read number of iters if loop start. + if (ctrl_loop.start_loop) { + ctrl_loop.num_iters = GetNextDword(); + } + opcode.opcode = ctrl_loop; + } break; + case CheatVmOpcodeType::LoadRegisterStatic: { + LoadRegisterStaticOpcode ldr_static{}; + // 400R0000 VVVVVVVV VVVVVVVV + // Read additional words. + ldr_static.reg_index = ((first_dword >> 16) & 0xF); + ldr_static.value = + (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword()); + opcode.opcode = ldr_static; + } break; + case CheatVmOpcodeType::LoadRegisterMemory: { + LoadRegisterMemoryOpcode ldr_memory{}; + // 5TMRI0AA AAAAAAAA + // Read additional words. + const u32 second_dword = GetNextDword(); + ldr_memory.bit_width = (first_dword >> 24) & 0xF; + ldr_memory.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF); + ldr_memory.reg_index = ((first_dword >> 16) & 0xF); + ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0; + ldr_memory.rel_address = + (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword); + opcode.opcode = ldr_memory; + } break; + case CheatVmOpcodeType::StoreStaticToAddress: { + StoreStaticToAddressOpcode str_static{}; + // 6T0RIor0 VVVVVVVV VVVVVVVV + // Read additional words. + str_static.bit_width = (first_dword >> 24) & 0xF; + str_static.reg_index = ((first_dword >> 16) & 0xF); + str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0; + str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0; + str_static.offset_reg_index = ((first_dword >> 4) & 0xF); + str_static.value = + (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword()); + opcode.opcode = str_static; + } break; + case CheatVmOpcodeType::PerformArithmeticStatic: { + PerformArithmeticStaticOpcode perform_math_static{}; + // 7T0RC000 VVVVVVVV + // Read additional words. + perform_math_static.bit_width = (first_dword >> 24) & 0xF; + perform_math_static.reg_index = ((first_dword >> 16) & 0xF); + perform_math_static.math_type = + static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF); + perform_math_static.value = GetNextDword(); + opcode.opcode = perform_math_static; + } break; + case CheatVmOpcodeType::BeginKeypressConditionalBlock: { + BeginKeypressConditionalOpcode begin_keypress_cond{}; + // 8kkkkkkk + // Just parse the mask. + begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF; + } break; + case CheatVmOpcodeType::PerformArithmeticRegister: { + PerformArithmeticRegisterOpcode perform_math_reg{}; + // 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) + perform_math_reg.bit_width = (first_dword >> 24) & 0xF; + perform_math_reg.math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF); + perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF); + perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF); + perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0; + if (perform_math_reg.has_immediate) { + perform_math_reg.src_reg_2_index = 0; + perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width); + } else { + perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF); + } + opcode.opcode = perform_math_reg; + } break; + case CheatVmOpcodeType::StoreRegisterToAddress: { + StoreRegisterToAddressOpcode str_register{}; + // ATSRIOxa (aaaaaaaa) + // A = opcode 10 + // T = bit width + // S = src register index + // R = address register index + // I = 1 if increment address register, 0 if not increment address register + // O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region, + // 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region + + // Relative Address + // x = offset register (for offset type 1), memory type (for offset type 3) + // a = relative address (for offset type 2+3) + str_register.bit_width = (first_dword >> 24) & 0xF; + str_register.str_reg_index = ((first_dword >> 20) & 0xF); + str_register.addr_reg_index = ((first_dword >> 16) & 0xF); + str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0; + str_register.ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF)); + str_register.ofs_reg_index = ((first_dword >> 4) & 0xF); + switch (str_register.ofs_type) { + case StoreRegisterOffsetType::None: + case StoreRegisterOffsetType::Reg: + // Nothing more to do + break; + case StoreRegisterOffsetType::Imm: + str_register.rel_address = + ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + break; + case StoreRegisterOffsetType::MemReg: + str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); + break; + case StoreRegisterOffsetType::MemImm: + case StoreRegisterOffsetType::MemImmReg: + str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); + str_register.rel_address = + ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + break; + default: + str_register.ofs_type = StoreRegisterOffsetType::None; + break; + } + opcode.opcode = str_register; + } break; + case CheatVmOpcodeType::BeginRegisterConditionalBlock: { + BeginRegisterConditionalOpcode begin_reg_cond{}; + // C0TcSX## + // C0TcS0Ma aaaaaaaa + // C0TcS1Mr + // C0TcS2Ra aaaaaaaa + // C0TcS3Rr + // C0TcS400 VVVVVVVV (VVVVVVVV) + // C0TcS5X0 + // C0 = opcode 0xC0 + // T = bit width + // c = condition type. + // S = source register. + // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset + // register, + // 2 = register with relative offset, 3 = register with offset register, 4 = static + // value, 5 = other register. + // M = memory type. + // R = address register. + // a = relative address. + // r = offset register. + // X = other register. + // V = value. + begin_reg_cond.bit_width = (first_dword >> 20) & 0xF; + begin_reg_cond.cond_type = + static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF); + begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF); + begin_reg_cond.comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF); + + switch (begin_reg_cond.comp_type) { + case CompareRegisterValueType::StaticValue: + begin_reg_cond.value = GetNextVmInt(begin_reg_cond.bit_width); + break; + case CompareRegisterValueType::OtherRegister: + begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF); + break; + case CompareRegisterValueType::MemoryRelAddr: + begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); + begin_reg_cond.rel_address = + ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + break; + case CompareRegisterValueType::MemoryOfsReg: + begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); + begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + break; + case CompareRegisterValueType::RegisterRelAddr: + begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + begin_reg_cond.rel_address = + ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + break; + case CompareRegisterValueType::RegisterOfsReg: + begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + break; + } + opcode.opcode = begin_reg_cond; + } break; + case CheatVmOpcodeType::SaveRestoreRegister: { + SaveRestoreRegisterOpcode save_restore_reg{}; + // C10D0Sx0 + // C1 = opcode 0xC1 + // D = destination index. + // S = source index. + // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring + // a register. + // NOTE: If we add more save slots later, current encoding is backwards compatible. + save_restore_reg.dst_index = (first_dword >> 16) & 0xF; + save_restore_reg.src_index = (first_dword >> 8) & 0xF; + save_restore_reg.op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF); + opcode.opcode = save_restore_reg; + } break; + case CheatVmOpcodeType::SaveRestoreRegisterMask: { + SaveRestoreRegisterMaskOpcode save_restore_regmask{}; + // C2x0XXXX + // C2 = opcode 0xC2 + // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring. + // X = 16-bit bitmask, bit i --> save or restore register i. + save_restore_regmask.op_type = + static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF); + for (std::size_t i = 0; i < NumRegisters; i++) { + save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0; + } + opcode.opcode = save_restore_regmask; + } break; + case CheatVmOpcodeType::DebugLog: { + DebugLogOpcode debug_log{}; + // FFFTIX## + // FFFTI0Ma aaaaaaaa + // FFFTI1Mr + // FFFTI2Ra aaaaaaaa + // FFFTI3Rr + // FFFTI4X0 + // FFF = opcode 0xFFF + // T = bit width. + // I = log id. + // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset + // register, + // 2 = register with relative offset, 3 = register with offset register, 4 = register + // value. + // M = memory type. + // R = address register. + // a = relative address. + // r = offset register. + // X = value register. + debug_log.bit_width = (first_dword >> 16) & 0xF; + debug_log.log_id = ((first_dword >> 12) & 0xF); + debug_log.val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF); + + switch (debug_log.val_type) { + case DebugLogValueType::RegisterValue: + debug_log.val_reg_index = ((first_dword >> 4) & 0xF); + break; + case DebugLogValueType::MemoryRelAddr: + debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); + debug_log.rel_address = + ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + break; + case DebugLogValueType::MemoryOfsReg: + debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); + debug_log.ofs_reg_index = (first_dword & 0xF); + break; + case DebugLogValueType::RegisterRelAddr: + debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + debug_log.rel_address = + ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); + break; + case DebugLogValueType::RegisterOfsReg: + debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + debug_log.ofs_reg_index = (first_dword & 0xF); + break; + } + opcode.opcode = debug_log; + } break; + case CheatVmOpcodeType::ExtendedWidth: + case CheatVmOpcodeType::DoubleExtendedWidth: + default: + // Unrecognized instruction cannot be decoded. + valid = false; + opcode.opcode = UnrecognizedInstruction{opcode_type}; + break; + } + + // End decoding. + return valid; +} + +void DmntCheatVm::SkipConditionalBlock() { + if (condition_depth > 0) { + // We want to continue until we're out of the current block. + const std::size_t desired_depth = condition_depth - 1; + + CheatVmOpcode skip_opcode{}; + while (condition_depth > desired_depth && DecodeNextOpcode(skip_opcode)) { + // Decode instructions until we see end of the current conditional block. + // NOTE: This is broken in gateway's implementation. + // Gateway currently checks for "0x2" instead of "0x20000000" + // In addition, they do a linear scan instead of correctly decoding opcodes. + // This causes issues if "0x2" appears as an immediate in the conditional block... + + // We also support nesting of conditional blocks, and Gateway does not. + if (skip_opcode.begin_conditional_block) { + condition_depth++; + } else if (std::holds_alternative<EndConditionalOpcode>(skip_opcode.opcode)) { + condition_depth--; + } + } + } else { + // Skipping, but condition_depth = 0. + // This is an error condition. + // However, I don't actually believe it is possible for this to happen. + // I guess we'll throw a fatal error here, so as to encourage me to fix the VM + // in the event that someone triggers it? I don't know how you'd do that. + UNREACHABLE_MSG("Invalid condition depth in DMNT Cheat VM"); + } +} + +u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) { + switch (bit_width) { + case 1: + return value.bit8; + case 2: + return value.bit16; + case 4: + return value.bit32; + case 8: + return value.bit64; + default: + // Invalid bit width -> return 0. + return 0; + } +} + +u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata, + MemoryAccessType mem_type, u64 rel_address) { + switch (mem_type) { + case MemoryAccessType::MainNso: + default: + return metadata.main_nso_extents.base + rel_address; + case MemoryAccessType::Heap: + return metadata.heap_extents.base + rel_address; + } +} + +void DmntCheatVm::ResetState() { + registers.fill(0); + saved_values.fill(0); + loop_tops.fill(0); + instruction_ptr = 0; + condition_depth = 0; + decode_success = true; +} + +bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) { + // Reset opcode count. + num_opcodes = 0; + + for (std::size_t i = 0; i < entries.size(); i++) { + if (entries[i].enabled) { + // Bounds check. + if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) { + num_opcodes = 0; + return false; + } + + for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) { + program[num_opcodes++] = entries[i].definition.opcodes[n]; + } + } + } + + return true; +} + +void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) { + CheatVmOpcode cur_opcode{}; + + // Get Keys down. + u64 kDown = callbacks->HidKeysDown(); + + callbacks->CommandLog("Started VM execution."); + callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base)); + callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base)); + callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF))); + + // Clear VM state. + ResetState(); + + // Loop until program finishes. + while (DecodeNextOpcode(cur_opcode)) { + callbacks->CommandLog( + fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr))); + + for (std::size_t i = 0; i < NumRegisters; i++) { + callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i])); + } + + for (std::size_t i = 0; i < NumRegisters; i++) { + callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i])); + } + LogOpcode(cur_opcode); + + // Increment conditional depth, if relevant. + if (cur_opcode.begin_conditional_block) { + condition_depth++; + } + + if (auto store_static = std::get_if<StoreStaticOpcode>(&cur_opcode.opcode)) { + // Calculate address, write value to memory. + u64 dst_address = GetCheatProcessAddress(metadata, store_static->mem_type, + store_static->rel_address + + registers[store_static->offset_register]); + u64 dst_value = GetVmInt(store_static->value, store_static->bit_width); + switch (store_static->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryWrite(dst_address, &dst_value, store_static->bit_width); + break; + } + } else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&cur_opcode.opcode)) { + // Read value from memory. + u64 src_address = + GetCheatProcessAddress(metadata, begin_cond->mem_type, begin_cond->rel_address); + u64 src_value = 0; + switch (store_static->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(src_address, &src_value, begin_cond->bit_width); + break; + } + // Check against condition. + u64 cond_value = GetVmInt(begin_cond->value, begin_cond->bit_width); + bool cond_met = false; + switch (begin_cond->cond_type) { + case ConditionalComparisonType::GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType::GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType::LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType::LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType::EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType::NE: + cond_met = src_value != cond_value; + break; + } + // Skip conditional block if condition not met. + if (!cond_met) { + SkipConditionalBlock(); + } + } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&cur_opcode.opcode)) { + // Decrement the condition depth. + // We will assume, graciously, that mismatched conditional block ends are a nop. + if (condition_depth > 0) { + condition_depth--; + } + } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&cur_opcode.opcode)) { + if (ctrl_loop->start_loop) { + // Start a loop. + registers[ctrl_loop->reg_index] = ctrl_loop->num_iters; + loop_tops[ctrl_loop->reg_index] = instruction_ptr; + } else { + // End a loop. + registers[ctrl_loop->reg_index]--; + if (registers[ctrl_loop->reg_index] != 0) { + instruction_ptr = loop_tops[ctrl_loop->reg_index]; + } + } + } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&cur_opcode.opcode)) { + // Set a register to a static value. + registers[ldr_static->reg_index] = ldr_static->value; + } else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&cur_opcode.opcode)) { + // Choose source address. + u64 src_address; + if (ldr_memory->load_from_reg) { + src_address = registers[ldr_memory->reg_index] + ldr_memory->rel_address; + } else { + src_address = + GetCheatProcessAddress(metadata, ldr_memory->mem_type, ldr_memory->rel_address); + } + // Read into register. Gateway only reads on valid bitwidth. + switch (ldr_memory->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(src_address, ®isters[ldr_memory->reg_index], + ldr_memory->bit_width); + break; + } + } else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&cur_opcode.opcode)) { + // Calculate address. + u64 dst_address = registers[str_static->reg_index]; + u64 dst_value = str_static->value; + if (str_static->add_offset_reg) { + dst_address += registers[str_static->offset_reg_index]; + } + // Write value to memory. Gateway only writes on valid bitwidth. + switch (str_static->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryWrite(dst_address, &dst_value, str_static->bit_width); + break; + } + // Increment register if relevant. + if (str_static->increment_reg) { + registers[str_static->reg_index] += str_static->bit_width; + } + } else if (auto perform_math_static = + std::get_if<PerformArithmeticStaticOpcode>(&cur_opcode.opcode)) { + // Do requested math. + switch (perform_math_static->math_type) { + case RegisterArithmeticType::Addition: + registers[perform_math_static->reg_index] += + static_cast<u64>(perform_math_static->value); + break; + case RegisterArithmeticType::Subtraction: + registers[perform_math_static->reg_index] -= + static_cast<u64>(perform_math_static->value); + break; + case RegisterArithmeticType::Multiplication: + registers[perform_math_static->reg_index] *= + static_cast<u64>(perform_math_static->value); + break; + case RegisterArithmeticType::LeftShift: + registers[perform_math_static->reg_index] <<= + static_cast<u64>(perform_math_static->value); + break; + case RegisterArithmeticType::RightShift: + registers[perform_math_static->reg_index] >>= + static_cast<u64>(perform_math_static->value); + break; + default: + // Do not handle extensions here. + break; + } + // Apply bit width. + switch (perform_math_static->bit_width) { + case 1: + registers[perform_math_static->reg_index] = + static_cast<u8>(registers[perform_math_static->reg_index]); + break; + case 2: + registers[perform_math_static->reg_index] = + static_cast<u16>(registers[perform_math_static->reg_index]); + break; + case 4: + registers[perform_math_static->reg_index] = + static_cast<u32>(registers[perform_math_static->reg_index]); + break; + case 8: + registers[perform_math_static->reg_index] = + static_cast<u64>(registers[perform_math_static->reg_index]); + break; + } + } else if (auto begin_keypress_cond = + std::get_if<BeginKeypressConditionalOpcode>(&cur_opcode.opcode)) { + // Check for keypress. + if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) { + // Keys not pressed. Skip conditional block. + SkipConditionalBlock(); + } + } else if (auto perform_math_reg = + std::get_if<PerformArithmeticRegisterOpcode>(&cur_opcode.opcode)) { + const u64 operand_1_value = registers[perform_math_reg->src_reg_1_index]; + const u64 operand_2_value = + perform_math_reg->has_immediate + ? GetVmInt(perform_math_reg->value, perform_math_reg->bit_width) + : registers[perform_math_reg->src_reg_2_index]; + + u64 res_val = 0; + // Do requested math. + switch (perform_math_reg->math_type) { + case RegisterArithmeticType::Addition: + res_val = operand_1_value + operand_2_value; + break; + case RegisterArithmeticType::Subtraction: + res_val = operand_1_value - operand_2_value; + break; + case RegisterArithmeticType::Multiplication: + res_val = operand_1_value * operand_2_value; + break; + case RegisterArithmeticType::LeftShift: + res_val = operand_1_value << operand_2_value; + break; + case RegisterArithmeticType::RightShift: + res_val = operand_1_value >> operand_2_value; + break; + case RegisterArithmeticType::LogicalAnd: + res_val = operand_1_value & operand_2_value; + break; + case RegisterArithmeticType::LogicalOr: + res_val = operand_1_value | operand_2_value; + break; + case RegisterArithmeticType::LogicalNot: + res_val = ~operand_1_value; + break; + case RegisterArithmeticType::LogicalXor: + res_val = operand_1_value ^ operand_2_value; + break; + case RegisterArithmeticType::None: + res_val = operand_1_value; + break; + } + + // Apply bit width. + switch (perform_math_reg->bit_width) { + case 1: + res_val = static_cast<u8>(res_val); + break; + case 2: + res_val = static_cast<u16>(res_val); + break; + case 4: + res_val = static_cast<u32>(res_val); + break; + case 8: + res_val = static_cast<u64>(res_val); + break; + } + + // Save to register. + registers[perform_math_reg->dst_reg_index] = res_val; + } else if (auto str_register = + std::get_if<StoreRegisterToAddressOpcode>(&cur_opcode.opcode)) { + // Calculate address. + u64 dst_value = registers[str_register->str_reg_index]; + u64 dst_address = registers[str_register->addr_reg_index]; + switch (str_register->ofs_type) { + case StoreRegisterOffsetType::None: + // Nothing more to do + break; + case StoreRegisterOffsetType::Reg: + dst_address += registers[str_register->ofs_reg_index]; + break; + case StoreRegisterOffsetType::Imm: + dst_address += str_register->rel_address; + break; + case StoreRegisterOffsetType::MemReg: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + registers[str_register->addr_reg_index]); + break; + case StoreRegisterOffsetType::MemImm: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + str_register->rel_address); + break; + case StoreRegisterOffsetType::MemImmReg: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + registers[str_register->addr_reg_index] + + str_register->rel_address); + break; + } + + // Write value to memory. Write only on valid bitwidth. + switch (str_register->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryWrite(dst_address, &dst_value, str_register->bit_width); + break; + } + + // Increment register if relevant. + if (str_register->increment_reg) { + registers[str_register->addr_reg_index] += str_register->bit_width; + } + } else if (auto begin_reg_cond = + std::get_if<BeginRegisterConditionalOpcode>(&cur_opcode.opcode)) { + // Get value from register. + u64 src_value = 0; + switch (begin_reg_cond->bit_width) { + case 1: + src_value = static_cast<u8>(registers[begin_reg_cond->val_reg_index] & 0xFFul); + break; + case 2: + src_value = static_cast<u16>(registers[begin_reg_cond->val_reg_index] & 0xFFFFul); + break; + case 4: + src_value = + static_cast<u32>(registers[begin_reg_cond->val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + src_value = static_cast<u64>(registers[begin_reg_cond->val_reg_index] & + 0xFFFFFFFFFFFFFFFFul); + break; + } + + // Read value from memory. + u64 cond_value = 0; + if (begin_reg_cond->comp_type == CompareRegisterValueType::StaticValue) { + cond_value = GetVmInt(begin_reg_cond->value, begin_reg_cond->bit_width); + } else if (begin_reg_cond->comp_type == CompareRegisterValueType::OtherRegister) { + switch (begin_reg_cond->bit_width) { + case 1: + cond_value = + static_cast<u8>(registers[begin_reg_cond->other_reg_index] & 0xFFul); + break; + case 2: + cond_value = + static_cast<u16>(registers[begin_reg_cond->other_reg_index] & 0xFFFFul); + break; + case 4: + cond_value = + static_cast<u32>(registers[begin_reg_cond->other_reg_index] & 0xFFFFFFFFul); + break; + case 8: + cond_value = static_cast<u64>(registers[begin_reg_cond->other_reg_index] & + 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 cond_address = 0; + switch (begin_reg_cond->comp_type) { + case CompareRegisterValueType::MemoryRelAddr: + cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, + begin_reg_cond->rel_address); + break; + case CompareRegisterValueType::MemoryOfsReg: + cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, + registers[begin_reg_cond->ofs_reg_index]); + break; + case CompareRegisterValueType::RegisterRelAddr: + cond_address = + registers[begin_reg_cond->addr_reg_index] + begin_reg_cond->rel_address; + break; + case CompareRegisterValueType::RegisterOfsReg: + cond_address = registers[begin_reg_cond->addr_reg_index] + + registers[begin_reg_cond->ofs_reg_index]; + break; + default: + break; + } + switch (begin_reg_cond->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(cond_address, &cond_value, begin_reg_cond->bit_width); + break; + } + } + + // Check against condition. + bool cond_met = false; + switch (begin_reg_cond->cond_type) { + case ConditionalComparisonType::GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType::GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType::LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType::LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType::EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType::NE: + cond_met = src_value != cond_value; + break; + } + + // Skip conditional block if condition not met. + if (!cond_met) { + SkipConditionalBlock(); + } + } else if (auto save_restore_reg = + std::get_if<SaveRestoreRegisterOpcode>(&cur_opcode.opcode)) { + // Save or restore a register. + switch (save_restore_reg->op_type) { + case SaveRestoreRegisterOpType::ClearRegs: + registers[save_restore_reg->dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType::ClearSaved: + saved_values[save_restore_reg->dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType::Save: + saved_values[save_restore_reg->dst_index] = registers[save_restore_reg->src_index]; + break; + case SaveRestoreRegisterOpType::Restore: + default: + registers[save_restore_reg->dst_index] = saved_values[save_restore_reg->src_index]; + break; + } + } else if (auto save_restore_regmask = + std::get_if<SaveRestoreRegisterMaskOpcode>(&cur_opcode.opcode)) { + // Save or restore register mask. + u64* src; + u64* dst; + switch (save_restore_regmask->op_type) { + case SaveRestoreRegisterOpType::ClearSaved: + case SaveRestoreRegisterOpType::Save: + src = registers.data(); + dst = saved_values.data(); + break; + case SaveRestoreRegisterOpType::ClearRegs: + case SaveRestoreRegisterOpType::Restore: + default: + src = registers.data(); + dst = saved_values.data(); + break; + } + for (std::size_t i = 0; i < NumRegisters; i++) { + if (save_restore_regmask->should_operate[i]) { + switch (save_restore_regmask->op_type) { + case SaveRestoreRegisterOpType::ClearSaved: + case SaveRestoreRegisterOpType::ClearRegs: + dst[i] = 0ul; + break; + case SaveRestoreRegisterOpType::Save: + case SaveRestoreRegisterOpType::Restore: + default: + dst[i] = src[i]; + break; + } + } + } + } else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) { + // Read value from memory. + u64 log_value = 0; + if (debug_log->val_type == DebugLogValueType::RegisterValue) { + switch (debug_log->bit_width) { + case 1: + log_value = static_cast<u8>(registers[debug_log->val_reg_index] & 0xFFul); + break; + case 2: + log_value = static_cast<u16>(registers[debug_log->val_reg_index] & 0xFFFFul); + break; + case 4: + log_value = + static_cast<u32>(registers[debug_log->val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + log_value = static_cast<u64>(registers[debug_log->val_reg_index] & + 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 val_address = 0; + switch (debug_log->val_type) { + case DebugLogValueType::MemoryRelAddr: + val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, + debug_log->rel_address); + break; + case DebugLogValueType::MemoryOfsReg: + val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, + registers[debug_log->ofs_reg_index]); + break; + case DebugLogValueType::RegisterRelAddr: + val_address = registers[debug_log->addr_reg_index] + debug_log->rel_address; + break; + case DebugLogValueType::RegisterOfsReg: + val_address = + registers[debug_log->addr_reg_index] + registers[debug_log->ofs_reg_index]; + break; + default: + break; + } + switch (debug_log->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(val_address, &log_value, debug_log->bit_width); + break; + } + } + + // Log value. + DebugLog(debug_log->log_id, log_value); + } + } +} + +} // namespace Memory diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/memory/dmnt_cheat_vm.h new file mode 100644 index 000000000..c36212cf1 --- /dev/null +++ b/src/core/memory/dmnt_cheat_vm.h @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Adapted by DarkLordZach for use/interaction with yuzu + * + * Modifications Copyright 2019 yuzu emulator team + * Licensed under GPLv2 or any later version + * Refer to the license.txt file included. + */ + +#pragma once + +#include <variant> +#include <vector> +#include <fmt/printf.h> +#include "common/common_types.h" +#include "core/memory/dmnt_cheat_types.h" + +namespace Memory { + +enum class CheatVmOpcodeType : u32 { + StoreStatic = 0, + BeginConditionalBlock = 1, + EndConditionalBlock = 2, + ControlLoop = 3, + LoadRegisterStatic = 4, + LoadRegisterMemory = 5, + StoreStaticToAddress = 6, + PerformArithmeticStatic = 7, + BeginKeypressConditionalBlock = 8, + + // These are not implemented by Gateway's VM. + PerformArithmeticRegister = 9, + StoreRegisterToAddress = 10, + Reserved11 = 11, + + // This is a meta entry, and not a real opcode. + // This is to facilitate multi-nybble instruction decoding. + ExtendedWidth = 12, + + // Extended width opcodes. + BeginRegisterConditionalBlock = 0xC0, + SaveRestoreRegister = 0xC1, + SaveRestoreRegisterMask = 0xC2, + + // This is a meta entry, and not a real opcode. + // This is to facilitate multi-nybble instruction decoding. + DoubleExtendedWidth = 0xF0, + + // Double-extended width opcodes. + DebugLog = 0xFFF, +}; + +enum class MemoryAccessType : u32 { + MainNso = 0, + Heap = 1, +}; + +enum class ConditionalComparisonType : u32 { + GT = 1, + GE = 2, + LT = 3, + LE = 4, + EQ = 5, + NE = 6, +}; + +enum class RegisterArithmeticType : u32 { + Addition = 0, + Subtraction = 1, + Multiplication = 2, + LeftShift = 3, + RightShift = 4, + + // These are not supported by Gateway's VM. + LogicalAnd = 5, + LogicalOr = 6, + LogicalNot = 7, + LogicalXor = 8, + + None = 9, +}; + +enum class StoreRegisterOffsetType : u32 { + None = 0, + Reg = 1, + Imm = 2, + MemReg = 3, + MemImm = 4, + MemImmReg = 5, +}; + +enum class CompareRegisterValueType : u32 { + MemoryRelAddr = 0, + MemoryOfsReg = 1, + RegisterRelAddr = 2, + RegisterOfsReg = 3, + StaticValue = 4, + OtherRegister = 5, +}; + +enum class SaveRestoreRegisterOpType : u32 { + Restore = 0, + Save = 1, + ClearSaved = 2, + ClearRegs = 3, +}; + +enum class DebugLogValueType : u32 { + MemoryRelAddr = 0, + MemoryOfsReg = 1, + RegisterRelAddr = 2, + RegisterOfsReg = 3, + RegisterValue = 4, +}; + +union VmInt { + u8 bit8; + u16 bit16; + u32 bit32; + u64 bit64; +}; + +struct StoreStaticOpcode { + u32 bit_width{}; + MemoryAccessType mem_type{}; + u32 offset_register{}; + u64 rel_address{}; + VmInt value{}; +}; + +struct BeginConditionalOpcode { + u32 bit_width{}; + MemoryAccessType mem_type{}; + ConditionalComparisonType cond_type{}; + u64 rel_address{}; + VmInt value{}; +}; + +struct EndConditionalOpcode {}; + +struct ControlLoopOpcode { + bool start_loop{}; + u32 reg_index{}; + u32 num_iters{}; +}; + +struct LoadRegisterStaticOpcode { + u32 reg_index{}; + u64 value{}; +}; + +struct LoadRegisterMemoryOpcode { + u32 bit_width{}; + MemoryAccessType mem_type{}; + u32 reg_index{}; + bool load_from_reg{}; + u64 rel_address{}; +}; + +struct StoreStaticToAddressOpcode { + u32 bit_width{}; + u32 reg_index{}; + bool increment_reg{}; + bool add_offset_reg{}; + u32 offset_reg_index{}; + u64 value{}; +}; + +struct PerformArithmeticStaticOpcode { + u32 bit_width{}; + u32 reg_index{}; + RegisterArithmeticType math_type{}; + u32 value{}; +}; + +struct BeginKeypressConditionalOpcode { + u32 key_mask{}; +}; + +struct PerformArithmeticRegisterOpcode { + u32 bit_width{}; + RegisterArithmeticType math_type{}; + u32 dst_reg_index{}; + u32 src_reg_1_index{}; + u32 src_reg_2_index{}; + bool has_immediate{}; + VmInt value{}; +}; + +struct StoreRegisterToAddressOpcode { + u32 bit_width{}; + u32 str_reg_index{}; + u32 addr_reg_index{}; + bool increment_reg{}; + StoreRegisterOffsetType ofs_type{}; + MemoryAccessType mem_type{}; + u32 ofs_reg_index{}; + u64 rel_address{}; +}; + +struct BeginRegisterConditionalOpcode { + u32 bit_width{}; + ConditionalComparisonType cond_type{}; + u32 val_reg_index{}; + CompareRegisterValueType comp_type{}; + MemoryAccessType mem_type{}; + u32 addr_reg_index{}; + u32 other_reg_index{}; + u32 ofs_reg_index{}; + u64 rel_address{}; + VmInt value{}; +}; + +struct SaveRestoreRegisterOpcode { + u32 dst_index{}; + u32 src_index{}; + SaveRestoreRegisterOpType op_type{}; +}; + +struct SaveRestoreRegisterMaskOpcode { + SaveRestoreRegisterOpType op_type{}; + std::array<bool, 0x10> should_operate{}; +}; + +struct DebugLogOpcode { + u32 bit_width{}; + u32 log_id{}; + DebugLogValueType val_type{}; + MemoryAccessType mem_type{}; + u32 addr_reg_index{}; + u32 val_reg_index{}; + u32 ofs_reg_index{}; + u64 rel_address{}; +}; + +struct UnrecognizedInstruction { + CheatVmOpcodeType opcode{}; +}; + +struct CheatVmOpcode { + bool begin_conditional_block{}; + std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode, + LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode, + PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode, + PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode, + BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode, + SaveRestoreRegisterMaskOpcode, DebugLogOpcode, UnrecognizedInstruction> + opcode{}; +}; + +class DmntCheatVm { +public: + /// Helper Type for DmntCheatVm <=> yuzu Interface + class Callbacks { + public: + virtual ~Callbacks(); + + virtual void MemoryRead(VAddr address, void* data, u64 size) = 0; + virtual void MemoryWrite(VAddr address, const void* data, u64 size) = 0; + + virtual u64 HidKeysDown() = 0; + + virtual void DebugLog(u8 id, u64 value) = 0; + virtual void CommandLog(std::string_view data) = 0; + }; + + static constexpr std::size_t MaximumProgramOpcodeCount = 0x400; + static constexpr std::size_t NumRegisters = 0x10; + + explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks); + ~DmntCheatVm(); + + std::size_t GetProgramSize() const { + return this->num_opcodes; + } + + bool LoadProgram(const std::vector<CheatEntry>& cheats); + void Execute(const CheatProcessMetadata& metadata); + +private: + std::unique_ptr<Callbacks> callbacks; + + std::size_t num_opcodes = 0; + std::size_t instruction_ptr = 0; + std::size_t condition_depth = 0; + bool decode_success = false; + std::array<u32, MaximumProgramOpcodeCount> program{}; + std::array<u64, NumRegisters> registers{}; + std::array<u64, NumRegisters> saved_values{}; + std::array<std::size_t, NumRegisters> loop_tops{}; + + bool DecodeNextOpcode(CheatVmOpcode& out); + void SkipConditionalBlock(); + void ResetState(); + + // For implementing the DebugLog opcode. + void DebugLog(u32 log_id, u64 value); + + void LogOpcode(const CheatVmOpcode& opcode); + + static u64 GetVmInt(VmInt value, u32 bit_width); + static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata, + MemoryAccessType mem_type, u64 rel_address); +}; + +}; // namespace Memory diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index 4afd6c8a3..d2c69d1a0 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -4,8 +4,14 @@ #include <algorithm> #include <chrono> +#include <iterator> #include <mutex> +#include <numeric> +#include <sstream> #include <thread> +#include <fmt/chrono.h> +#include <fmt/format.h> +#include "common/file_util.h" #include "common/math_util.h" #include "core/perf_stats.h" #include "core/settings.h" @@ -15,8 +21,31 @@ using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>; using std::chrono::duration_cast; using std::chrono::microseconds; +// Purposefully ignore the first five frames, as there's a significant amount of overhead in +// booting that we shouldn't account for +constexpr std::size_t IgnoreFrames = 5; + namespace Core { +PerfStats::PerfStats(u64 title_id) : title_id(title_id) {} + +PerfStats::~PerfStats() { + if (!Settings::values.record_frame_times || title_id == 0) { + return; + } + + const std::time_t t = std::time(nullptr); + std::ostringstream stream; + std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index, + std::ostream_iterator<double>(stream, "\n")); + const std::string& path = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + // %F Date format expanded is "%Y-%m-%d" + const std::string filename = + fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id); + FileUtil::IOFile file(filename, "w"); + file.WriteString(stream.str()); +} + void PerfStats::BeginSystemFrame() { std::lock_guard lock{object_mutex}; @@ -27,7 +56,12 @@ void PerfStats::EndSystemFrame() { std::lock_guard lock{object_mutex}; auto frame_end = Clock::now(); - accumulated_frametime += frame_end - frame_begin; + const auto frame_time = frame_end - frame_begin; + if (current_index < perf_history.size()) { + perf_history[current_index++] = + std::chrono::duration<double, std::milli>(frame_time).count(); + } + accumulated_frametime += frame_time; system_frames += 1; previous_frame_length = frame_end - previous_frame_end; @@ -40,6 +74,17 @@ void PerfStats::EndGameFrame() { game_frames += 1; } +double PerfStats::GetMeanFrametime() { + std::lock_guard lock{object_mutex}; + + if (current_index <= IgnoreFrames) { + return 0; + } + const double sum = std::accumulate(perf_history.begin() + IgnoreFrames, + perf_history.begin() + current_index, 0); + return sum / (current_index - IgnoreFrames); +} + PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) { std::lock_guard lock{object_mutex}; diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index 222ac1a63..d9a64f072 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -4,7 +4,9 @@ #pragma once +#include <array> #include <chrono> +#include <cstddef> #include <mutex> #include "common/common_types.h" @@ -27,6 +29,10 @@ struct PerfStatsResults { */ class PerfStats { public: + explicit PerfStats(u64 title_id); + + ~PerfStats(); + using Clock = std::chrono::high_resolution_clock; void BeginSystemFrame(); @@ -36,13 +42,26 @@ public: PerfStatsResults GetAndResetStats(std::chrono::microseconds current_system_time_us); /** + * Returns the Arthimetic Mean of all frametime values stored in the performance history. + */ + double GetMeanFrametime(); + + /** * Gets the ratio between walltime and the emulated time of the previous system frame. This is * useful for scaling inputs or outputs moving between the two time domains. */ double GetLastFrameTimeScale(); private: - std::mutex object_mutex; + std::mutex object_mutex{}; + + /// Title ID for the game that is running. 0 if there is no game running yet + u64 title_id{0}; + /// Current index for writing to the perf_history array + std::size_t current_index{0}; + /// Stores an hour of historical frametime data useful for processing and tracking performance + /// regressions with code changes. + std::array<double, 216000> perf_history = {}; /// Point when the cumulative counters were reset Clock::time_point reset_point = Clock::now(); diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp new file mode 100644 index 000000000..9c657929e --- /dev/null +++ b/src/core/reporter.cpp @@ -0,0 +1,391 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <ctime> +#include <fstream> + +#include <fmt/chrono.h> +#include <fmt/format.h> +#include <json.hpp> + +#include "common/file_util.h" +#include "common/hex_util.h" +#include "common/scm_rev.h" +#include "core/arm/arm_interface.h" +#include "core/core.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/process.h" +#include "core/hle/result.h" +#include "core/reporter.h" +#include "core/settings.h" + +namespace { + +std::string GetPath(std::string_view type, u64 title_id, std::string_view timestamp) { + return fmt::format("{}{}/{:016X}_{}.json", FileUtil::GetUserPath(FileUtil::UserPath::LogDir), + type, title_id, timestamp); +} + +std::string GetTimestamp() { + const auto time = std::time(nullptr); + return fmt::format("{:%FT%H-%M-%S}", *std::localtime(&time)); +} + +using namespace nlohmann; + +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)); + file << std::setw(4) << json << std::endl; +} + +json GetYuzuVersionData() { + return { + {"scm_rev", std::string(Common::g_scm_rev)}, + {"scm_branch", std::string(Common::g_scm_branch)}, + {"scm_desc", std::string(Common::g_scm_desc)}, + {"build_name", std::string(Common::g_build_name)}, + {"build_date", std::string(Common::g_build_date)}, + {"build_fullname", std::string(Common::g_build_fullname)}, + {"build_version", std::string(Common::g_build_version)}, + {"shader_cache_version", std::string(Common::g_shader_cache_version)}, + }; +} + +json GetReportCommonData(u64 title_id, ResultCode result, const std::string& timestamp, + std::optional<u128> user_id = {}) { + auto out = json{ + {"title_id", fmt::format("{:016X}", title_id)}, + {"result_raw", fmt::format("{:08X}", result.raw)}, + {"result_module", fmt::format("{:08X}", static_cast<u32>(result.module.Value()))}, + {"result_description", fmt::format("{:08X}", result.description.Value())}, + {"timestamp", timestamp}, + }; + + if (user_id.has_value()) { + out["user_id"] = fmt::format("{:016X}{:016X}", (*user_id)[1], (*user_id)[0]); + } + + return out; +} + +json GetProcessorStateData(const std::string& architecture, u64 entry_point, u64 sp, u64 pc, + u64 pstate, std::array<u64, 31> registers, + std::optional<std::array<u64, 32>> backtrace = {}) { + auto out = json{ + {"entry_point", fmt::format("{:016X}", entry_point)}, + {"sp", fmt::format("{:016X}", sp)}, + {"pc", fmt::format("{:016X}", pc)}, + {"pstate", fmt::format("{:016X}", pstate)}, + {"architecture", architecture}, + }; + + auto registers_out = json::object(); + for (std::size_t i = 0; i < registers.size(); ++i) { + registers_out[fmt::format("X{:02d}", i)] = fmt::format("{:016X}", registers[i]); + } + + out["registers"] = std::move(registers_out); + + if (backtrace.has_value()) { + auto backtrace_out = json::array(); + for (const auto& entry : *backtrace) { + backtrace_out.push_back(fmt::format("{:016X}", entry)); + } + out["backtrace"] = std::move(backtrace_out); + } + + return out; +} + +json GetProcessorStateDataAuto(Core::System& system) { + const auto* process{system.CurrentProcess()}; + const auto& vm_manager{process->VMManager()}; + auto& arm{system.CurrentArmInterface()}; + + Core::ARM_Interface::ThreadContext context{}; + arm.SaveContext(context); + + return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32", + vm_manager.GetCodeRegionBaseAddress(), context.sp, context.pc, + context.pstate, context.cpu_registers); +} + +json GetBacktraceData(Core::System& system) { + auto out = json::array(); + const auto& backtrace{system.CurrentArmInterface().GetBacktrace()}; + for (const auto& entry : backtrace) { + out.push_back({ + {"module", entry.module}, + {"address", fmt::format("{:016X}", entry.address)}, + {"original_address", fmt::format("{:016X}", entry.original_address)}, + {"offset", fmt::format("{:016X}", entry.offset)}, + {"symbol_name", entry.name}, + }); + } + + return out; +} + +json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& system) { + json out; + + out["yuzu_version"] = GetYuzuVersionData(); + out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp); + out["processor_state"] = GetProcessorStateDataAuto(system); + out["backtrace"] = GetBacktraceData(system); + + return out; +} + +template <bool read_value, typename DescriptorType> +json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer) { + auto buffer_out = json::array(); + for (const auto& desc : buffer) { + auto entry = json{ + {"address", fmt::format("{:016X}", desc.Address())}, + {"size", fmt::format("{:016X}", desc.Size())}, + }; + + if constexpr (read_value) { + std::vector<u8> data(desc.Size()); + Memory::ReadBlock(desc.Address(), data.data(), desc.Size()); + entry["data"] = Common::HexToString(data); + } + + buffer_out.push_back(std::move(entry)); + } + + return buffer_out; +} + +json GetHLERequestContextData(Kernel::HLERequestContext& ctx) { + json out; + + auto cmd_buf = json::array(); + for (std::size_t i = 0; i < IPC::COMMAND_BUFFER_LENGTH; ++i) { + cmd_buf.push_back(fmt::format("{:08X}", ctx.CommandBuffer()[i])); + } + + out["command_buffer"] = std::move(cmd_buf); + + out["buffer_descriptor_a"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorA()); + out["buffer_descriptor_b"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorB()); + out["buffer_descriptor_c"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorC()); + out["buffer_descriptor_x"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorX()); + + return out; +} + +} // Anonymous namespace + +namespace Core { + +Reporter::Reporter(System& system) : system(system) {} + +Reporter::~Reporter() = default; + +void Reporter::SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, + u64 sp, u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far, + const std::array<u64, 31>& registers, + const std::array<u64, 32>& backtrace, u32 backtrace_size, + const std::string& arch, u32 unk10) const { + if (!IsReportingEnabled()) { + return; + } + + const auto timestamp = GetTimestamp(); + json out; + + out["yuzu_version"] = GetYuzuVersionData(); + out["report_common"] = GetReportCommonData(title_id, result, timestamp); + + auto proc_out = GetProcessorStateData(arch, entry_point, sp, pc, pstate, registers, backtrace); + proc_out["set_flags"] = fmt::format("{:016X}", set_flags); + proc_out["afsr0"] = fmt::format("{:016X}", afsr0); + proc_out["afsr1"] = fmt::format("{:016X}", afsr1); + proc_out["esr"] = fmt::format("{:016X}", esr); + proc_out["far"] = fmt::format("{:016X}", far); + proc_out["backtrace_size"] = fmt::format("{:08X}", backtrace_size); + proc_out["unknown_10"] = fmt::format("{:08X}", unk10); + + out["processor_state"] = std::move(proc_out); + + SaveToFile(std::move(out), GetPath("crash_report", title_id, timestamp)); +} + +void Reporter::SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2, + std::optional<std::vector<u8>> resolved_buffer) const { + if (!IsReportingEnabled()) { + return; + } + + const auto timestamp = GetTimestamp(); + const auto title_id = system.CurrentProcess()->GetTitleID(); + auto out = GetFullDataAuto(timestamp, title_id, system); + + auto break_out = json{ + {"type", fmt::format("{:08X}", type)}, + {"signal_debugger", fmt::format("{}", signal_debugger)}, + {"info1", fmt::format("{:016X}", info1)}, + {"info2", fmt::format("{:016X}", info2)}, + }; + + if (resolved_buffer.has_value()) { + break_out["debug_buffer"] = Common::HexToString(*resolved_buffer); + } + + out["svc_break"] = std::move(break_out); + + SaveToFile(std::move(out), GetPath("svc_break_report", title_id, timestamp)); +} + +void Reporter::SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id, + const std::string& name, + const std::string& service_name) const { + if (!IsReportingEnabled()) { + return; + } + + const auto timestamp = GetTimestamp(); + const auto title_id = system.CurrentProcess()->GetTitleID(); + auto out = GetFullDataAuto(timestamp, title_id, system); + + auto function_out = GetHLERequestContextData(ctx); + function_out["command_id"] = command_id; + function_out["function_name"] = name; + function_out["service_name"] = service_name; + + out["function"] = std::move(function_out); + + SaveToFile(std::move(out), GetPath("unimpl_func_report", title_id, timestamp)); +} + +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()) { + return; + } + + const auto timestamp = GetTimestamp(); + const auto title_id = system.CurrentProcess()->GetTitleID(); + auto out = GetFullDataAuto(timestamp, title_id, system); + + out["applet_common_args"] = { + {"applet_id", fmt::format("{:02X}", applet_id)}, + {"common_args_version", fmt::format("{:08X}", common_args_version)}, + {"library_version", fmt::format("{:08X}", library_version)}, + {"theme_color", fmt::format("{:08X}", theme_color)}, + {"startup_sound", fmt::format("{}", startup_sound)}, + {"system_tick", fmt::format("{:016X}", system_tick)}, + }; + + auto normal_out = json::array(); + for (const auto& data : normal_channel) { + normal_out.push_back(Common::HexToString(data)); + } + + auto interactive_out = json::array(); + for (const auto& data : interactive_channel) { + interactive_out.push_back(Common::HexToString(data)); + } + + out["applet_normal_data"] = std::move(normal_out); + out["applet_interactive_data"] = std::move(interactive_out); + + SaveToFile(std::move(out), GetPath("unimpl_applet_report", title_id, timestamp)); +} + +void Reporter::SavePlayReport(PlayReportType type, u64 title_id, std::vector<std::vector<u8>> data, + std::optional<u64> process_id, std::optional<u128> user_id) const { + if (!IsReportingEnabled()) { + return; + } + + const auto timestamp = GetTimestamp(); + json out; + + out["yuzu_version"] = GetYuzuVersionData(); + out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp, user_id); + + auto data_out = json::array(); + for (const auto& d : data) { + data_out.push_back(Common::HexToString(d)); + } + + if (process_id.has_value()) { + out["play_report_process_id"] = fmt::format("{:016X}", *process_id); + } + + out["play_report_type"] = fmt::format("{:02}", static_cast<u8>(type)); + out["play_report_data"] = std::move(data_out); + + SaveToFile(std::move(out), GetPath("play_report", title_id, timestamp)); +} + +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()) { + return; + } + + const auto timestamp = GetTimestamp(); + json out; + + out["yuzu_version"] = GetYuzuVersionData(); + out["report_common"] = GetReportCommonData(title_id, result, timestamp); + out["processor_state"] = GetProcessorStateDataAuto(system); + out["backtrace"] = GetBacktraceData(system); + + out["error_custom_text"] = { + {"main", *custom_text_main}, + {"detail", *custom_text_detail}, + }; + + SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp)); +} + +void Reporter::SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, + std::string log_message) const { + if (!IsReportingEnabled()) + return; + + const auto timestamp = GetTimestamp(); + const auto title_id = system.CurrentProcess()->GetTitleID(); + json out; + + out["yuzu_version"] = GetYuzuVersionData(); + out["report_common"] = GetReportCommonData(title_id, RESULT_SUCCESS, timestamp); + + out["log_mode"] = fmt::format("{:08X}", static_cast<u32>(log_mode)); + out["log_message"] = std::move(log_message); + + SaveToFile(std::move(out), GetPath("filesystem_access_report", title_id, timestamp)); +} + +void Reporter::SaveUserReport() const { + if (!IsReportingEnabled()) { + return; + } + + const auto timestamp = GetTimestamp(); + const auto title_id = system.CurrentProcess()->GetTitleID(); + + SaveToFile(GetFullDataAuto(timestamp, title_id, system), + GetPath("user_report", title_id, timestamp)); +} + +bool Reporter::IsReportingEnabled() const { + return Settings::values.reporting_services; +} + +} // namespace Core diff --git a/src/core/reporter.h b/src/core/reporter.h new file mode 100644 index 000000000..f08aa11fb --- /dev/null +++ b/src/core/reporter.h @@ -0,0 +1,73 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <optional> +#include <string> +#include <vector> +#include "common/common_types.h" + +union ResultCode; + +namespace Kernel { +class HLERequestContext; +} // namespace Kernel + +namespace Service::FileSystem { +enum class LogMode : u32; +} + +namespace Core { + +class System; + +class Reporter { +public: + explicit Reporter(System& system); + ~Reporter(); + + void SaveCrashReport(u64 title_id, ResultCode result, u64 set_flags, u64 entry_point, u64 sp, + u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far, + const std::array<u64, 31>& registers, const std::array<u64, 32>& backtrace, + u32 backtrace_size, const std::string& arch, u32 unk10) const; + + void SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2, + std::optional<std::vector<u8>> resolved_buffer = {}) const; + + void SaveUnimplementedFunctionReport(Kernel::HLERequestContext& ctx, u32 command_id, + const std::string& name, + const std::string& service_name) const; + + void 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; + + enum class PlayReportType { + Old, + New, + System, + }; + + void SavePlayReport(PlayReportType type, u64 title_id, std::vector<std::vector<u8>> data, + std::optional<u64> process_id = {}, std::optional<u128> user_id = {}) const; + + void SaveErrorReport(u64 title_id, ResultCode result, + std::optional<std::string> custom_text_main = {}, + std::optional<std::string> custom_text_detail = {}) const; + + void SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, + std::string log_message) const; + + void SaveUserReport() const; + +private: + bool IsReportingEnabled() const; + + System& system; +}; + +} // namespace Core diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 6d32ebea3..7de3fd1e5 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/file_util.h" #include "core/core.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/service/hid/hid.h" @@ -85,7 +86,6 @@ 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_UseMultiCore", Settings::values.use_multi_core); LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor); LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit); @@ -98,8 +98,8 @@ void LogSettings() { LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); LogSetting("Audio_OutputDevice", Settings::values.audio_device_id); LogSetting("DataStorage_UseVirtualSd", Settings::values.use_virtual_sd); - LogSetting("DataStorage_NandDir", Settings::values.nand_dir); - LogSetting("DataStorage_SdmcDir", Settings::values.sdmc_dir); + LogSetting("DataStorage_NandDir", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)); + LogSetting("DataStorage_SdmcDir", FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)); LogSetting("Debugging_UseGdbstub", Settings::values.use_gdbstub); LogSetting("Debugging_GdbstubPort", Settings::values.gdbstub_port); LogSetting("Debugging_ProgramArgs", Settings::values.program_args); diff --git a/src/core/settings.h b/src/core/settings.h index b84390745..47bddfb30 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -346,6 +346,31 @@ struct TouchscreenInput { u32 rotation_angle; }; +enum class NANDTotalSize : u64 { + S29_1GB = 0x747C00000ULL, +}; + +enum class NANDUserSize : u64 { + S26GB = 0x680000000ULL, +}; + +enum class NANDSystemSize : u64 { + S2_5GB = 0xA0000000, +}; + +enum class SDMCSize : u64 { + S1GB = 0x40000000, + S2GB = 0x80000000, + S4GB = 0x100000000ULL, + S8GB = 0x200000000ULL, + S16GB = 0x400000000ULL, + S32GB = 0x800000000ULL, + S64GB = 0x1000000000ULL, + S128GB = 0x2000000000ULL, + S256GB = 0x4000000000ULL, + S1TB = 0x10000000000ULL, +}; + struct Values { // System bool use_docked_mode; @@ -378,13 +403,17 @@ struct Values { std::atomic_bool is_device_reload_pending{true}; // Core - bool use_cpu_jit; bool use_multi_core; // Data Storage bool use_virtual_sd; - std::string nand_dir; - std::string sdmc_dir; + bool gamecard_inserted; + bool gamecard_current_game; + std::string gamecard_path; + NANDTotalSize nand_total_size; + NANDSystemSize nand_system_size; + NANDUserSize nand_user_size; + SDMCSize sdmc_size; // Renderer float resolution_factor; @@ -410,11 +439,14 @@ struct Values { float volume; // Debugging + bool record_frame_times; bool use_gdbstub; u16 gdbstub_port; std::string program_args; bool dump_exefs; bool dump_nso; + bool reporting_services; + bool quest_flag; // WebService bool enable_telemetry; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 90d06830f..793d102d3 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -168,7 +168,6 @@ 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_UseMultiCore", Settings::values.use_multi_core); AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor", diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp new file mode 100644 index 000000000..17f050068 --- /dev/null +++ b/src/core/tools/freezer.cpp @@ -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 diff --git a/src/core/tools/freezer.h b/src/core/tools/freezer.h new file mode 100644 index 000000000..b58de5472 --- /dev/null +++ b/src/core/tools/freezer.h @@ -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 diff --git a/src/core/tracer/citrace.h b/src/core/tracer/citrace.h deleted file mode 100644 index 21fdc127a..000000000 --- a/src/core/tracer/citrace.h +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" - -namespace CiTrace { - -// NOTE: Things are stored in little-endian - -#pragma pack(1) - -struct CTHeader { - static const char* ExpectedMagicWord() { - return "CiTr"; - } - - static u32 ExpectedVersion() { - return 1; - } - - char magic[4]; - u32 version; - u32 header_size; - - struct { - // NOTE: Register range sizes are technically hardware-constants, but the actual limits - // aren't known. Hence we store the presumed limits along the offsets. - // Sizes are given in u32 units. - u32 gpu_registers; - u32 gpu_registers_size; - u32 lcd_registers; - u32 lcd_registers_size; - u32 pica_registers; - u32 pica_registers_size; - u32 default_attributes; - u32 default_attributes_size; - u32 vs_program_binary; - u32 vs_program_binary_size; - u32 vs_swizzle_data; - u32 vs_swizzle_data_size; - u32 vs_float_uniforms; - u32 vs_float_uniforms_size; - u32 gs_program_binary; - u32 gs_program_binary_size; - u32 gs_swizzle_data; - u32 gs_swizzle_data_size; - u32 gs_float_uniforms; - u32 gs_float_uniforms_size; - - // Other things we might want to store here: - // - Initial framebuffer data, maybe even a full copy of FCRAM/VRAM - // - Lookup tables for fragment lighting - // - Lookup tables for procedural textures - } initial_state_offsets; - - u32 stream_offset; - u32 stream_size; -}; - -enum CTStreamElementType : u32 { - FrameMarker = 0xE1, - MemoryLoad = 0xE2, - RegisterWrite = 0xE3, -}; - -struct CTMemoryLoad { - u32 file_offset; - u32 size; - u32 physical_address; - u32 pad; -}; - -struct CTRegisterWrite { - u32 physical_address; - - enum : u32 { - SIZE_8 = 0xD1, - SIZE_16 = 0xD2, - SIZE_32 = 0xD3, - SIZE_64 = 0xD4, - } size; - - // TODO: Make it clearer which bits of this member are used for sizes other than 32 bits - u64 value; -}; - -struct CTStreamElement { - CTStreamElementType type; - - union { - CTMemoryLoad memory_load; - CTRegisterWrite register_write; - }; -}; - -#pragma pack() -} // namespace CiTrace diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp deleted file mode 100644 index 73cacb47f..000000000 --- a/src/core/tracer/recorder.cpp +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <cstring> -#include "common/assert.h" -#include "common/file_util.h" -#include "common/logging/log.h" -#include "core/tracer/recorder.h" - -namespace CiTrace { - -Recorder::Recorder(const InitialState& initial_state) : initial_state(initial_state) {} - -void Recorder::Finish(const std::string& filename) { - // Setup CiTrace header - CTHeader header; - std::memcpy(header.magic, CTHeader::ExpectedMagicWord(), 4); - header.version = CTHeader::ExpectedVersion(); - header.header_size = sizeof(CTHeader); - - // Calculate file offsets - auto& initial = header.initial_state_offsets; - - initial.gpu_registers_size = static_cast<u32>(initial_state.gpu_registers.size()); - initial.lcd_registers_size = static_cast<u32>(initial_state.lcd_registers.size()); - initial.pica_registers_size = static_cast<u32>(initial_state.pica_registers.size()); - initial.default_attributes_size = static_cast<u32>(initial_state.default_attributes.size()); - initial.vs_program_binary_size = static_cast<u32>(initial_state.vs_program_binary.size()); - initial.vs_swizzle_data_size = static_cast<u32>(initial_state.vs_swizzle_data.size()); - initial.vs_float_uniforms_size = static_cast<u32>(initial_state.vs_float_uniforms.size()); - initial.gs_program_binary_size = static_cast<u32>(initial_state.gs_program_binary.size()); - initial.gs_swizzle_data_size = static_cast<u32>(initial_state.gs_swizzle_data.size()); - initial.gs_float_uniforms_size = static_cast<u32>(initial_state.gs_float_uniforms.size()); - header.stream_size = static_cast<u32>(stream.size()); - - initial.gpu_registers = sizeof(header); - initial.lcd_registers = initial.gpu_registers + initial.gpu_registers_size * sizeof(u32); - initial.pica_registers = initial.lcd_registers + initial.lcd_registers_size * sizeof(u32); - ; - initial.default_attributes = initial.pica_registers + initial.pica_registers_size * sizeof(u32); - initial.vs_program_binary = - initial.default_attributes + initial.default_attributes_size * sizeof(u32); - initial.vs_swizzle_data = - initial.vs_program_binary + initial.vs_program_binary_size * sizeof(u32); - initial.vs_float_uniforms = - initial.vs_swizzle_data + initial.vs_swizzle_data_size * sizeof(u32); - initial.gs_program_binary = - initial.vs_float_uniforms + initial.vs_float_uniforms_size * sizeof(u32); - initial.gs_swizzle_data = - initial.gs_program_binary + initial.gs_program_binary_size * sizeof(u32); - initial.gs_float_uniforms = - initial.gs_swizzle_data + initial.gs_swizzle_data_size * sizeof(u32); - header.stream_offset = initial.gs_float_uniforms + initial.gs_float_uniforms_size * sizeof(u32); - - // Iterate through stream elements, update relevant stream element data - for (auto& stream_element : stream) { - switch (stream_element.data.type) { - case MemoryLoad: { - auto& file_offset = memory_regions[stream_element.hash]; - if (!stream_element.uses_existing_data) { - file_offset = header.stream_offset; - } - stream_element.data.memory_load.file_offset = file_offset; - break; - } - - default: - // Other commands don't use any extra data - DEBUG_ASSERT(stream_element.extra_data.size() == 0); - break; - } - header.stream_offset += static_cast<u32>(stream_element.extra_data.size()); - } - - try { - // Open file and write header - FileUtil::IOFile file(filename, "wb"); - std::size_t written = file.WriteObject(header); - if (written != 1 || file.Tell() != initial.gpu_registers) - throw "Failed to write header"; - - // Write initial state - written = - file.WriteArray(initial_state.gpu_registers.data(), initial_state.gpu_registers.size()); - if (written != initial_state.gpu_registers.size() || file.Tell() != initial.lcd_registers) - throw "Failed to write GPU registers"; - - written = - file.WriteArray(initial_state.lcd_registers.data(), initial_state.lcd_registers.size()); - if (written != initial_state.lcd_registers.size() || file.Tell() != initial.pica_registers) - throw "Failed to write LCD registers"; - - written = file.WriteArray(initial_state.pica_registers.data(), - initial_state.pica_registers.size()); - if (written != initial_state.pica_registers.size() || - file.Tell() != initial.default_attributes) - throw "Failed to write Pica registers"; - - written = file.WriteArray(initial_state.default_attributes.data(), - initial_state.default_attributes.size()); - if (written != initial_state.default_attributes.size() || - file.Tell() != initial.vs_program_binary) - throw "Failed to write default vertex attributes"; - - written = file.WriteArray(initial_state.vs_program_binary.data(), - initial_state.vs_program_binary.size()); - if (written != initial_state.vs_program_binary.size() || - file.Tell() != initial.vs_swizzle_data) - throw "Failed to write vertex shader program binary"; - - written = file.WriteArray(initial_state.vs_swizzle_data.data(), - initial_state.vs_swizzle_data.size()); - if (written != initial_state.vs_swizzle_data.size() || - file.Tell() != initial.vs_float_uniforms) - throw "Failed to write vertex shader swizzle data"; - - written = file.WriteArray(initial_state.vs_float_uniforms.data(), - initial_state.vs_float_uniforms.size()); - if (written != initial_state.vs_float_uniforms.size() || - file.Tell() != initial.gs_program_binary) - throw "Failed to write vertex shader float uniforms"; - - written = file.WriteArray(initial_state.gs_program_binary.data(), - initial_state.gs_program_binary.size()); - if (written != initial_state.gs_program_binary.size() || - file.Tell() != initial.gs_swizzle_data) - throw "Failed to write geomtry shader program binary"; - - written = file.WriteArray(initial_state.gs_swizzle_data.data(), - initial_state.gs_swizzle_data.size()); - if (written != initial_state.gs_swizzle_data.size() || - file.Tell() != initial.gs_float_uniforms) - throw "Failed to write geometry shader swizzle data"; - - written = file.WriteArray(initial_state.gs_float_uniforms.data(), - initial_state.gs_float_uniforms.size()); - if (written != initial_state.gs_float_uniforms.size() || - file.Tell() != initial.gs_float_uniforms + sizeof(u32) * initial.gs_float_uniforms_size) - throw "Failed to write geometry shader float uniforms"; - - // Iterate through stream elements, write "extra data" - for (const auto& stream_element : stream) { - if (stream_element.extra_data.size() == 0) - continue; - - written = - file.WriteBytes(stream_element.extra_data.data(), stream_element.extra_data.size()); - if (written != stream_element.extra_data.size()) - throw "Failed to write extra data"; - } - - if (file.Tell() != header.stream_offset) - throw "Unexpected end of extra data"; - - // Write actual stream elements - for (const auto& stream_element : stream) { - if (1 != file.WriteObject(stream_element.data)) - throw "Failed to write stream element"; - } - } catch (const char* str) { - LOG_ERROR(HW_GPU, "Writing CiTrace file failed: {}", str); - } -} - -void Recorder::FrameFinished() { - stream.push_back({{FrameMarker}}); -} - -void Recorder::MemoryAccessed(const u8* data, u32 size, u32 physical_address) { - StreamElement element = {{MemoryLoad}}; - element.data.memory_load.size = size; - element.data.memory_load.physical_address = physical_address; - - // Compute hash over given memory region to check if the contents are already stored internally - boost::crc_32_type result; - result.process_bytes(data, size); - element.hash = result.checksum(); - - element.uses_existing_data = (memory_regions.find(element.hash) != memory_regions.end()); - if (!element.uses_existing_data) { - element.extra_data.resize(size); - memcpy(element.extra_data.data(), data, size); - memory_regions.insert({element.hash, 0}); // file offset will be initialized in Finish() - } - - stream.push_back(element); -} - -template <typename T> -void Recorder::RegisterWritten(u32 physical_address, T value) { - StreamElement element = {{RegisterWrite}}; - element.data.register_write.size = - (sizeof(T) == 1) ? CTRegisterWrite::SIZE_8 - : (sizeof(T) == 2) ? CTRegisterWrite::SIZE_16 - : (sizeof(T) == 4) ? CTRegisterWrite::SIZE_32 - : CTRegisterWrite::SIZE_64; - element.data.register_write.physical_address = physical_address; - element.data.register_write.value = value; - - stream.push_back(element); -} - -template void Recorder::RegisterWritten(u32, u8); -template void Recorder::RegisterWritten(u32, u16); -template void Recorder::RegisterWritten(u32, u32); -template void Recorder::RegisterWritten(u32, u64); -} // namespace CiTrace diff --git a/src/core/tracer/recorder.h b/src/core/tracer/recorder.h deleted file mode 100644 index e1cefd5fe..000000000 --- a/src/core/tracer/recorder.h +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <string> -#include <unordered_map> -#include <vector> -#include <boost/crc.hpp> -#include "common/common_types.h" -#include "core/tracer/citrace.h" - -namespace CiTrace { - -class Recorder { -public: - struct InitialState { - std::vector<u32> gpu_registers; - std::vector<u32> lcd_registers; - std::vector<u32> pica_registers; - std::vector<u32> default_attributes; - std::vector<u32> vs_program_binary; - std::vector<u32> vs_swizzle_data; - std::vector<u32> vs_float_uniforms; - std::vector<u32> gs_program_binary; - std::vector<u32> gs_swizzle_data; - std::vector<u32> gs_float_uniforms; - }; - - /** - * Recorder constructor - * @param initial_state Initial recorder state - */ - explicit Recorder(const InitialState& initial_state); - - /// Finish recording of this Citrace and save it using the given filename. - void Finish(const std::string& filename); - - /// Mark end of a frame - void FrameFinished(); - - /** - * Store a copy of the given memory range in the recording. - * @note Use this whenever the GPU is about to access a particular memory region. - * @note The implementation will make sure to minimize redundant memory updates. - */ - void MemoryAccessed(const u8* data, u32 size, u32 physical_address); - - /** - * Record a register write. - * @note Use this whenever a GPU-related MMIO register has been written to. - */ - template <typename T> - void RegisterWritten(u32 physical_address, T value); - -private: - // Initial state of recording start - InitialState initial_state; - - // Command stream - struct StreamElement { - CTStreamElement data; - - /** - * Extra data to store along "core" data. - * This is e.g. used for data used in MemoryUpdates. - */ - std::vector<u8> extra_data; - - /// Optional CRC hash (e.g. for hashing memory regions) - boost::crc_32_type::value_type hash; - - /// If true, refer to data already written to the output file instead of extra_data - bool uses_existing_data; - }; - - std::vector<StreamElement> stream; - - /** - * Internal cache which maps hashes of memory contents to file offsets at which those memory - * contents are stored. - */ - std::unordered_map<boost::crc_32_type::value_type /*hash*/, u32 /*file_offset*/> memory_regions; -}; - -} // namespace CiTrace diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index 58af41f6e..ac7ae3e52 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp @@ -16,7 +16,8 @@ namespace ArmTests { TestEnvironment::TestEnvironment(bool mutable_memory_) : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)), kernel{Core::System::GetInstance()} { - auto process = Kernel::Process::Create(Core::System::GetInstance(), ""); + auto process = Kernel::Process::Create(Core::System::GetInstance(), "", + Kernel::Process::ProcessType::Userland); page_table = &process->VMManager().page_table; std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr); diff --git a/src/tests/core/core_timing.cpp b/src/tests/core/core_timing.cpp index 340d6a272..f8be8fd19 100644 --- a/src/tests/core/core_timing.cpp +++ b/src/tests/core/core_timing.cpp @@ -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()); diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 2d4caa08d..e2f85c5f1 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,8 +1,12 @@ add_library(video_core STATIC + buffer_cache/buffer_block.h + buffer_cache/buffer_cache.h + buffer_cache/map_interval.h dma_pusher.cpp dma_pusher.h debug_utils/debug_utils.cpp debug_utils/debug_utils.h + engines/const_buffer_info.h engines/engine_upload.cpp engines/engine_upload.h engines/fermi_2d.cpp @@ -40,12 +44,10 @@ add_library(video_core STATIC renderer_opengl/gl_buffer_cache.h renderer_opengl/gl_device.cpp renderer_opengl/gl_device.h - renderer_opengl/gl_global_cache.cpp - renderer_opengl/gl_global_cache.h + renderer_opengl/gl_framebuffer_cache.cpp + renderer_opengl/gl_framebuffer_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 @@ -66,6 +68,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 @@ -87,6 +91,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 @@ -97,8 +102,11 @@ add_library(video_core STATIC shader/decode/integer_set.cpp shader/decode/half_set.cpp shader/decode/video.cpp + shader/decode/warp.cpp shader/decode/xmad.cpp shader/decode/other.cpp + shader/control_flow.cpp + shader/control_flow.h shader/decode.cpp shader/node_helper.cpp shader/node_helper.h @@ -108,6 +116,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 @@ -115,8 +130,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 ) diff --git a/src/video_core/buffer_cache/buffer_block.h b/src/video_core/buffer_cache/buffer_block.h new file mode 100644 index 000000000..4b9193182 --- /dev/null +++ b/src/video_core/buffer_cache/buffer_block.h @@ -0,0 +1,76 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <unordered_set> +#include <utility> + +#include "common/alignment.h" +#include "common/common_types.h" +#include "video_core/gpu.h" + +namespace VideoCommon { + +class BufferBlock { +public: + bool Overlaps(const CacheAddr start, const CacheAddr end) const { + return (cache_addr < end) && (cache_addr_end > start); + } + + bool IsInside(const CacheAddr other_start, const CacheAddr other_end) const { + return cache_addr <= other_start && other_end <= cache_addr_end; + } + + u8* GetWritableHostPtr() const { + return FromCacheAddr(cache_addr); + } + + u8* GetWritableHostPtr(std::size_t offset) const { + return FromCacheAddr(cache_addr + offset); + } + + std::size_t GetOffset(const CacheAddr in_addr) { + return static_cast<std::size_t>(in_addr - cache_addr); + } + + CacheAddr GetCacheAddr() const { + return cache_addr; + } + + CacheAddr GetCacheAddrEnd() const { + return cache_addr_end; + } + + void SetCacheAddr(const CacheAddr new_addr) { + cache_addr = new_addr; + cache_addr_end = new_addr + size; + } + + std::size_t GetSize() const { + return size; + } + + void SetEpoch(u64 new_epoch) { + epoch = new_epoch; + } + + u64 GetEpoch() { + return epoch; + } + +protected: + explicit BufferBlock(CacheAddr cache_addr, const std::size_t size) : size{size} { + SetCacheAddr(cache_addr); + } + ~BufferBlock() = default; + +private: + CacheAddr cache_addr{}; + CacheAddr cache_addr_end{}; + std::size_t size{}; + u64 epoch{}; +}; + +} // namespace VideoCommon diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h new file mode 100644 index 000000000..2442ddfd6 --- /dev/null +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -0,0 +1,447 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <mutex> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + +#include "common/alignment.h" +#include "common/common_types.h" +#include "core/core.h" +#include "video_core/buffer_cache/buffer_block.h" +#include "video_core/buffer_cache/map_interval.h" +#include "video_core/memory_manager.h" +#include "video_core/rasterizer_interface.h" + +namespace VideoCommon { + +using MapInterval = std::shared_ptr<MapIntervalBase>; + +template <typename TBuffer, typename TBufferType, typename StreamBuffer> +class BufferCache { +public: + using BufferInfo = std::pair<const TBufferType*, u64>; + + BufferInfo UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4, + bool is_written = false) { + std::lock_guard lock{mutex}; + + auto& memory_manager = system.GPU().MemoryManager(); + const auto host_ptr = memory_manager.GetPointer(gpu_addr); + if (!host_ptr) { + return {GetEmptyBuffer(size), 0}; + } + const auto cache_addr = ToCacheAddr(host_ptr); + + // Cache management is a big overhead, so only cache entries with a given size. + // TODO: Figure out which size is the best for given games. + constexpr std::size_t max_stream_size = 0x800; + if (size < max_stream_size) { + if (!is_written && !IsRegionWritten(cache_addr, cache_addr + size - 1)) { + return StreamBufferUpload(host_ptr, size, alignment); + } + } + + auto block = GetBlock(cache_addr, size); + auto map = MapAddress(block, gpu_addr, cache_addr, size); + if (is_written) { + map->MarkAsModified(true, GetModifiedTicks()); + if (!map->IsWritten()) { + map->MarkAsWritten(true); + MarkRegionAsWritten(map->GetStart(), map->GetEnd() - 1); + } + } else { + if (map->IsWritten()) { + WriteBarrier(); + } + } + + const u64 offset = static_cast<u64>(block->GetOffset(cache_addr)); + + return {ToHandle(block), offset}; + } + + /// Uploads from a host memory. Returns the OpenGL buffer where it's located and its offset. + BufferInfo UploadHostMemory(const void* raw_pointer, std::size_t size, + std::size_t alignment = 4) { + std::lock_guard lock{mutex}; + return StreamBufferUpload(raw_pointer, size, alignment); + } + + void Map(std::size_t max_size) { + std::lock_guard lock{mutex}; + + std::tie(buffer_ptr, buffer_offset_base, invalidated) = stream_buffer->Map(max_size, 4); + buffer_offset = buffer_offset_base; + } + + /// Finishes the upload stream, returns true on bindings invalidation. + bool Unmap() { + std::lock_guard lock{mutex}; + + stream_buffer->Unmap(buffer_offset - buffer_offset_base); + return std::exchange(invalidated, false); + } + + void TickFrame() { + ++epoch; + while (!pending_destruction.empty()) { + if (pending_destruction.front()->GetEpoch() + 1 > epoch) { + break; + } + pending_destruction.pop_front(); + } + } + + /// Write any cached resources overlapping the specified region back to memory + void FlushRegion(CacheAddr addr, std::size_t size) { + std::lock_guard lock{mutex}; + + std::vector<MapInterval> objects = GetMapsInRange(addr, size); + std::sort(objects.begin(), objects.end(), [](const MapInterval& a, const MapInterval& b) { + return a->GetModificationTick() < b->GetModificationTick(); + }); + for (auto& object : objects) { + if (object->IsModified() && object->IsRegistered()) { + FlushMap(object); + } + } + } + + /// Mark the specified region as being invalidated + void InvalidateRegion(CacheAddr addr, u64 size) { + std::lock_guard lock{mutex}; + + std::vector<MapInterval> objects = GetMapsInRange(addr, size); + for (auto& object : objects) { + if (object->IsRegistered()) { + Unregister(object); + } + } + } + + virtual const TBufferType* GetEmptyBuffer(std::size_t size) = 0; + +protected: + explicit BufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system, + std::unique_ptr<StreamBuffer> stream_buffer) + : rasterizer{rasterizer}, system{system}, stream_buffer{std::move(stream_buffer)}, + stream_buffer_handle{this->stream_buffer->GetHandle()} {} + + ~BufferCache() = default; + + virtual const TBufferType* ToHandle(const TBuffer& storage) = 0; + + virtual void WriteBarrier() = 0; + + virtual TBuffer CreateBlock(CacheAddr cache_addr, std::size_t size) = 0; + + virtual void UploadBlockData(const TBuffer& buffer, std::size_t offset, std::size_t size, + const u8* data) = 0; + + virtual void DownloadBlockData(const TBuffer& buffer, std::size_t offset, std::size_t size, + u8* data) = 0; + + virtual void CopyBlock(const TBuffer& src, const TBuffer& dst, std::size_t src_offset, + std::size_t dst_offset, std::size_t size) = 0; + + /// Register an object into the cache + void Register(const MapInterval& new_map, bool inherit_written = false) { + const CacheAddr cache_ptr = new_map->GetStart(); + const std::optional<VAddr> cpu_addr = + system.GPU().MemoryManager().GpuToCpuAddress(new_map->GetGpuAddress()); + if (!cache_ptr || !cpu_addr) { + LOG_CRITICAL(HW_GPU, "Failed to register buffer with unmapped gpu_address 0x{:016x}", + new_map->GetGpuAddress()); + return; + } + const std::size_t size = new_map->GetEnd() - new_map->GetStart(); + new_map->SetCpuAddress(*cpu_addr); + new_map->MarkAsRegistered(true); + const IntervalType interval{new_map->GetStart(), new_map->GetEnd()}; + mapped_addresses.insert({interval, new_map}); + rasterizer.UpdatePagesCachedCount(*cpu_addr, size, 1); + if (inherit_written) { + MarkRegionAsWritten(new_map->GetStart(), new_map->GetEnd() - 1); + new_map->MarkAsWritten(true); + } + } + + /// Unregisters an object from the cache + void Unregister(MapInterval& map) { + const std::size_t size = map->GetEnd() - map->GetStart(); + rasterizer.UpdatePagesCachedCount(map->GetCpuAddress(), size, -1); + map->MarkAsRegistered(false); + if (map->IsWritten()) { + UnmarkRegionAsWritten(map->GetStart(), map->GetEnd() - 1); + } + const IntervalType delete_interval{map->GetStart(), map->GetEnd()}; + mapped_addresses.erase(delete_interval); + } + +private: + MapInterval CreateMap(const CacheAddr start, const CacheAddr end, const GPUVAddr gpu_addr) { + return std::make_shared<MapIntervalBase>(start, end, gpu_addr); + } + + MapInterval MapAddress(const TBuffer& block, const GPUVAddr gpu_addr, + const CacheAddr cache_addr, const std::size_t size) { + + std::vector<MapInterval> overlaps = GetMapsInRange(cache_addr, size); + if (overlaps.empty()) { + const CacheAddr cache_addr_end = cache_addr + size; + MapInterval new_map = CreateMap(cache_addr, cache_addr_end, gpu_addr); + u8* host_ptr = FromCacheAddr(cache_addr); + UploadBlockData(block, block->GetOffset(cache_addr), size, host_ptr); + Register(new_map); + return new_map; + } + + const CacheAddr cache_addr_end = cache_addr + size; + if (overlaps.size() == 1) { + MapInterval& current_map = overlaps[0]; + if (current_map->IsInside(cache_addr, cache_addr_end)) { + return current_map; + } + } + CacheAddr new_start = cache_addr; + CacheAddr new_end = cache_addr_end; + bool write_inheritance = false; + bool modified_inheritance = false; + // Calculate new buffer parameters + for (auto& overlap : overlaps) { + new_start = std::min(overlap->GetStart(), new_start); + new_end = std::max(overlap->GetEnd(), new_end); + write_inheritance |= overlap->IsWritten(); + modified_inheritance |= overlap->IsModified(); + } + GPUVAddr new_gpu_addr = gpu_addr + new_start - cache_addr; + for (auto& overlap : overlaps) { + Unregister(overlap); + } + UpdateBlock(block, new_start, new_end, overlaps); + MapInterval new_map = CreateMap(new_start, new_end, new_gpu_addr); + if (modified_inheritance) { + new_map->MarkAsModified(true, GetModifiedTicks()); + } + Register(new_map, write_inheritance); + return new_map; + } + + void UpdateBlock(const TBuffer& block, CacheAddr start, CacheAddr end, + std::vector<MapInterval>& overlaps) { + const IntervalType base_interval{start, end}; + IntervalSet interval_set{}; + interval_set.add(base_interval); + for (auto& overlap : overlaps) { + const IntervalType subtract{overlap->GetStart(), overlap->GetEnd()}; + interval_set.subtract(subtract); + } + for (auto& interval : interval_set) { + std::size_t size = interval.upper() - interval.lower(); + if (size > 0) { + u8* host_ptr = FromCacheAddr(interval.lower()); + UploadBlockData(block, block->GetOffset(interval.lower()), size, host_ptr); + } + } + } + + std::vector<MapInterval> GetMapsInRange(CacheAddr addr, std::size_t size) { + if (size == 0) { + return {}; + } + + std::vector<MapInterval> objects{}; + const IntervalType interval{addr, addr + size}; + for (auto& pair : boost::make_iterator_range(mapped_addresses.equal_range(interval))) { + objects.push_back(pair.second); + } + + return objects; + } + + /// Returns a ticks counter used for tracking when cached objects were last modified + u64 GetModifiedTicks() { + return ++modified_ticks; + } + + void FlushMap(MapInterval map) { + std::size_t size = map->GetEnd() - map->GetStart(); + TBuffer block = blocks[map->GetStart() >> block_page_bits]; + u8* host_ptr = FromCacheAddr(map->GetStart()); + DownloadBlockData(block, block->GetOffset(map->GetStart()), size, host_ptr); + map->MarkAsModified(false, 0); + } + + BufferInfo StreamBufferUpload(const void* raw_pointer, std::size_t size, + std::size_t alignment) { + AlignBuffer(alignment); + const std::size_t uploaded_offset = buffer_offset; + std::memcpy(buffer_ptr, raw_pointer, size); + + buffer_ptr += size; + buffer_offset += size; + return {&stream_buffer_handle, uploaded_offset}; + } + + void AlignBuffer(std::size_t alignment) { + // Align the offset, not the mapped pointer + const std::size_t offset_aligned = Common::AlignUp(buffer_offset, alignment); + buffer_ptr += offset_aligned - buffer_offset; + buffer_offset = offset_aligned; + } + + TBuffer EnlargeBlock(TBuffer buffer) { + const std::size_t old_size = buffer->GetSize(); + const std::size_t new_size = old_size + block_page_size; + const CacheAddr cache_addr = buffer->GetCacheAddr(); + TBuffer new_buffer = CreateBlock(cache_addr, new_size); + CopyBlock(buffer, new_buffer, 0, 0, old_size); + buffer->SetEpoch(epoch); + pending_destruction.push_back(buffer); + const CacheAddr cache_addr_end = cache_addr + new_size - 1; + u64 page_start = cache_addr >> block_page_bits; + const u64 page_end = cache_addr_end >> block_page_bits; + while (page_start <= page_end) { + blocks[page_start] = new_buffer; + ++page_start; + } + return new_buffer; + } + + TBuffer MergeBlocks(TBuffer first, TBuffer second) { + const std::size_t size_1 = first->GetSize(); + const std::size_t size_2 = second->GetSize(); + const CacheAddr first_addr = first->GetCacheAddr(); + const CacheAddr second_addr = second->GetCacheAddr(); + const CacheAddr new_addr = std::min(first_addr, second_addr); + const std::size_t new_size = size_1 + size_2; + TBuffer new_buffer = CreateBlock(new_addr, new_size); + CopyBlock(first, new_buffer, 0, new_buffer->GetOffset(first_addr), size_1); + CopyBlock(second, new_buffer, 0, new_buffer->GetOffset(second_addr), size_2); + first->SetEpoch(epoch); + second->SetEpoch(epoch); + pending_destruction.push_back(first); + pending_destruction.push_back(second); + const CacheAddr cache_addr_end = new_addr + new_size - 1; + u64 page_start = new_addr >> block_page_bits; + const u64 page_end = cache_addr_end >> block_page_bits; + while (page_start <= page_end) { + blocks[page_start] = new_buffer; + ++page_start; + } + return new_buffer; + } + + TBuffer GetBlock(const CacheAddr cache_addr, const std::size_t size) { + TBuffer found{}; + const CacheAddr cache_addr_end = cache_addr + size - 1; + u64 page_start = cache_addr >> block_page_bits; + const u64 page_end = cache_addr_end >> block_page_bits; + while (page_start <= page_end) { + auto it = blocks.find(page_start); + if (it == blocks.end()) { + if (found) { + found = EnlargeBlock(found); + } else { + const CacheAddr start_addr = (page_start << block_page_bits); + found = CreateBlock(start_addr, block_page_size); + blocks[page_start] = found; + } + } else { + if (found) { + if (found == it->second) { + ++page_start; + continue; + } + found = MergeBlocks(found, it->second); + } else { + found = it->second; + } + } + ++page_start; + } + return found; + } + + void MarkRegionAsWritten(const CacheAddr start, const CacheAddr end) { + u64 page_start = start >> write_page_bit; + const u64 page_end = end >> write_page_bit; + while (page_start <= page_end) { + auto it = written_pages.find(page_start); + if (it != written_pages.end()) { + it->second = it->second + 1; + } else { + written_pages[page_start] = 1; + } + page_start++; + } + } + + void UnmarkRegionAsWritten(const CacheAddr start, const CacheAddr end) { + u64 page_start = start >> write_page_bit; + const u64 page_end = end >> write_page_bit; + while (page_start <= page_end) { + auto it = written_pages.find(page_start); + if (it != written_pages.end()) { + if (it->second > 1) { + it->second = it->second - 1; + } else { + written_pages.erase(it); + } + } + page_start++; + } + } + + bool IsRegionWritten(const CacheAddr start, const CacheAddr end) const { + u64 page_start = start >> write_page_bit; + const u64 page_end = end >> write_page_bit; + while (page_start <= page_end) { + if (written_pages.count(page_start) > 0) { + return true; + } + page_start++; + } + return false; + } + + VideoCore::RasterizerInterface& rasterizer; + Core::System& system; + std::unique_ptr<StreamBuffer> stream_buffer; + + TBufferType stream_buffer_handle{}; + + bool invalidated = false; + + u8* buffer_ptr = nullptr; + u64 buffer_offset = 0; + u64 buffer_offset_base = 0; + + using IntervalSet = boost::icl::interval_set<CacheAddr>; + using IntervalCache = boost::icl::interval_map<CacheAddr, MapInterval>; + using IntervalType = typename IntervalCache::interval_type; + IntervalCache mapped_addresses{}; + + static constexpr u64 write_page_bit{11}; + std::unordered_map<u64, u32> written_pages{}; + + static constexpr u64 block_page_bits{21}; + static constexpr u64 block_page_size{1 << block_page_bits}; + std::unordered_map<u64, TBuffer> blocks{}; + + std::list<TBuffer> pending_destruction{}; + u64 epoch{}; + u64 modified_ticks{}; + + std::recursive_mutex mutex; +}; + +} // namespace VideoCommon diff --git a/src/video_core/buffer_cache/map_interval.h b/src/video_core/buffer_cache/map_interval.h new file mode 100644 index 000000000..3a104d5cd --- /dev/null +++ b/src/video_core/buffer_cache/map_interval.h @@ -0,0 +1,89 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "video_core/gpu.h" + +namespace VideoCommon { + +class MapIntervalBase { +public: + MapIntervalBase(const CacheAddr start, const CacheAddr end, const GPUVAddr gpu_addr) + : start{start}, end{end}, gpu_addr{gpu_addr} {} + + void SetCpuAddress(VAddr new_cpu_addr) { + cpu_addr = new_cpu_addr; + } + + VAddr GetCpuAddress() const { + return cpu_addr; + } + + GPUVAddr GetGpuAddress() const { + return gpu_addr; + } + + bool IsInside(const CacheAddr other_start, const CacheAddr other_end) const { + return (start <= other_start && other_end <= end); + } + + bool operator==(const MapIntervalBase& rhs) const { + return std::tie(start, end) == std::tie(rhs.start, rhs.end); + } + + bool operator!=(const MapIntervalBase& rhs) const { + return !operator==(rhs); + } + + void MarkAsRegistered(const bool registered) { + is_registered = registered; + } + + bool IsRegistered() const { + return is_registered; + } + + CacheAddr GetStart() const { + return start; + } + + CacheAddr GetEnd() const { + return end; + } + + void MarkAsModified(const bool is_modified_, const u64 tick) { + is_modified = is_modified_; + ticks = tick; + } + + bool IsModified() const { + return is_modified; + } + + u64 GetModificationTick() const { + return ticks; + } + + void MarkAsWritten(const bool is_written_) { + is_written = is_written_; + } + + bool IsWritten() const { + return is_written; + } + +private: + CacheAddr start; + CacheAddr end; + GPUVAddr gpu_addr; + VAddr cpu_addr{}; + bool is_written{}; + bool is_modified{}; + bool is_registered{}; + u64 ticks{}; +}; + +} // namespace VideoCommon diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 3175579cc..0094fd715 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -22,7 +22,7 @@ void DmaPusher::DispatchCalls() { MICROPROFILE_SCOPE(DispatchCalls); // On entering GPU code, assume all memory may be touched by the ARM core. - gpu.Maxwell3D().dirty_flags.OnMemoryWrite(); + gpu.Maxwell3D().dirty.OnMemoryWrite(); dma_pushbuffer_subindex = 0; @@ -31,6 +31,7 @@ void DmaPusher::DispatchCalls() { break; } } + gpu.FlushCommands(); } bool DmaPusher::Step() { diff --git a/src/video_core/engines/const_buffer_info.h b/src/video_core/engines/const_buffer_info.h new file mode 100644 index 000000000..d8f672462 --- /dev/null +++ b/src/video_core/engines/const_buffer_info.h @@ -0,0 +1,17 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace Tegra::Engines { + +struct ConstBufferInfo { + GPUVAddr address; + u32 size; + bool enabled; +}; + +} // namespace Tegra::Engines diff --git a/src/video_core/engines/engine_upload.cpp b/src/video_core/engines/engine_upload.cpp index 082a40cd9..d44ad0cd8 100644 --- a/src/video_core/engines/engine_upload.cpp +++ b/src/video_core/engines/engine_upload.cpp @@ -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, diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h index ef4f5839a..462da419e 100644 --- a/src/video_core/engines/engine_upload.h +++ b/src/video_core/engines/engine_upload.h @@ -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; }; diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index 55966eef1..7ff44f06d 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -4,15 +4,13 @@ #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" namespace Tegra::Engines { -Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager) - : rasterizer{rasterizer}, memory_manager{memory_manager} {} +Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {} void Fermi2D::CallMethod(const GPU::MethodCall& method_call) { ASSERT_MSG(method_call.method < Regs::NUM_REGS, @@ -31,25 +29,35 @@ void Fermi2D::CallMethod(const GPU::MethodCall& method_call) { } void Fermi2D::HandleSurfaceCopy() { - LOG_WARNING(HW_GPU, "Requested a surface copy with operation {}", - static_cast<u32>(regs.operation)); + LOG_DEBUG(HW_GPU, "Requested a surface copy with operation {}", + 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(); } } diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h index 45f59a4d9..0901cf2fa 100644 --- a/src/video_core/engines/fermi_2d.h +++ b/src/video_core/engines/fermi_2d.h @@ -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 { @@ -32,12 +33,32 @@ namespace Tegra::Engines { class Fermi2D final { public: - explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager); + explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer); ~Fermi2D() = default; /// 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,9 +136,15 @@ 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; /// Performs the copy from the source surface to the destination surface as configured in the /// registers. diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp index 7404a8163..63d449135 100644 --- a/src/video_core/engines/kepler_compute.cpp +++ b/src/video_core/engines/kepler_compute.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <bitset> #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" @@ -37,7 +38,7 @@ void KeplerCompute::CallMethod(const GPU::MethodCall& method_call) { const bool is_last_call = method_call.IsLastCall(); upload_state.ProcessData(method_call.argument, is_last_call); if (is_last_call) { - system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite(); + system.GPU().Maxwell3D().dirty.OnMemoryWrite(); } break; } @@ -49,14 +50,67 @@ void KeplerCompute::CallMethod(const GPU::MethodCall& method_call) { } } -void KeplerCompute::ProcessLaunch() { +Tegra::Texture::FullTextureInfo KeplerCompute::GetTexture(std::size_t offset) const { + const std::bitset<8> cbuf_mask = launch_description.const_buffer_enable_mask.Value(); + ASSERT(cbuf_mask[regs.tex_cb_index]); + + const auto& texinfo = launch_description.const_buffer_config[regs.tex_cb_index]; + ASSERT(texinfo.Address() != 0); + + const GPUVAddr address = texinfo.Address() + offset * sizeof(Texture::TextureHandle); + ASSERT(address < texinfo.Address() + texinfo.size); + + const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(address)}; + return GetTextureInfo(tex_handle, offset); +} +Texture::FullTextureInfo KeplerCompute::GetTextureInfo(const Texture::TextureHandle tex_handle, + std::size_t offset) const { + return Texture::FullTextureInfo{static_cast<u32>(offset), GetTICEntry(tex_handle.tic_id), + GetTSCEntry(tex_handle.tsc_id)}; +} + +u32 KeplerCompute::AccessConstBuffer32(u64 const_buffer, u64 offset) const { + const auto& buffer = launch_description.const_buffer_config[const_buffer]; + u32 result; + std::memcpy(&result, memory_manager.GetPointer(buffer.Address() + offset), sizeof(u32)); + return result; +} + +void KeplerCompute::ProcessLaunch() { const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address(); memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description, LaunchParams::NUM_LAUNCH_PARAMETERS * sizeof(u32)); - const GPUVAddr code_loc = regs.code_loc.Address() + launch_description.program_start; - LOG_WARNING(HW_GPU, "Compute Kernel Execute at Address 0x{:016x}, STUBBED", code_loc); + const GPUVAddr code_addr = regs.code_loc.Address() + launch_description.program_start; + LOG_TRACE(HW_GPU, "Compute invocation launched at address 0x{:016x}", code_addr); + + rasterizer.DispatchCompute(code_addr); +} + +Texture::TICEntry KeplerCompute::GetTICEntry(u32 tic_index) const { + const GPUVAddr tic_address_gpu{regs.tic.Address() + tic_index * sizeof(Texture::TICEntry)}; + + Texture::TICEntry tic_entry; + memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry)); + + 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); + + return tic_entry; +} + +Texture::TSCEntry KeplerCompute::GetTSCEntry(u32 tsc_index) const { + const GPUVAddr tsc_address_gpu{regs.tsc.Address() + tsc_index * sizeof(Texture::TSCEntry)}; + + Texture::TSCEntry tsc_entry; + memory_manager.ReadBlockUnsafe(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry)); + return tsc_entry; } } // namespace Tegra::Engines diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h index 6a3309a2c..90cf650d2 100644 --- a/src/video_core/engines/kepler_compute.h +++ b/src/video_core/engines/kepler_compute.h @@ -12,6 +12,7 @@ #include "common/common_types.h" #include "video_core/engines/engine_upload.h" #include "video_core/gpu.h" +#include "video_core/textures/texture.h" namespace Core { class System; @@ -111,7 +112,7 @@ public: INSERT_PADDING_WORDS(0x3FE); - u32 texture_const_buffer_index; + u32 tex_cb_index; INSERT_PADDING_WORDS(0x374); }; @@ -149,7 +150,7 @@ public: union { BitField<0, 8, u32> const_buffer_enable_mask; BitField<29, 2, u32> cache_layout; - } memory_config; + }; INSERT_PADDING_WORDS(0x8); @@ -194,6 +195,14 @@ public: /// Write the value to the register identified by method. void CallMethod(const GPU::MethodCall& method_call); + Tegra::Texture::FullTextureInfo GetTexture(std::size_t offset) const; + + /// Given a Texture Handle, returns the TSC and TIC entries. + Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle, + std::size_t offset) const; + + u32 AccessConstBuffer32(u64 const_buffer, u64 offset) const; + private: Core::System& system; VideoCore::RasterizerInterface& rasterizer; @@ -201,6 +210,12 @@ private: Upload::State upload_state; void ProcessLaunch(); + + /// Retrieves information about a specific TIC entry from the TIC buffer. + Texture::TICEntry GetTICEntry(u32 tic_index) const; + + /// Retrieves information about a specific TSC entry from the TSC buffer. + Texture::TSCEntry GetTSCEntry(u32 tsc_index) const; }; #define ASSERT_REG_POSITION(field_name, position) \ @@ -218,12 +233,12 @@ ASSERT_REG_POSITION(launch, 0xAF); ASSERT_REG_POSITION(tsc, 0x557); ASSERT_REG_POSITION(tic, 0x55D); ASSERT_REG_POSITION(code_loc, 0x582); -ASSERT_REG_POSITION(texture_const_buffer_index, 0x982); +ASSERT_REG_POSITION(tex_cb_index, 0x982); ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8); ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC); ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11); ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12); -ASSERT_LAUNCH_PARAM_POSITION(memory_config, 0x14); +ASSERT_LAUNCH_PARAM_POSITION(const_buffer_enable_mask, 0x14); ASSERT_LAUNCH_PARAM_POSITION(const_buffer_config, 0x1D); #undef ASSERT_REG_POSITION diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp index 0561f676c..fa4a7c5c1 100644 --- a/src/video_core/engines/kepler_memory.cpp +++ b/src/video_core/engines/kepler_memory.cpp @@ -15,7 +15,7 @@ namespace Tegra::Engines { KeplerMemory::KeplerMemory(Core::System& system, MemoryManager& memory_manager) - : system{system}, memory_manager{memory_manager}, upload_state{memory_manager, regs.upload} {} + : system{system}, upload_state{memory_manager, regs.upload} {} KeplerMemory::~KeplerMemory() = default; @@ -34,7 +34,7 @@ void KeplerMemory::CallMethod(const GPU::MethodCall& method_call) { const bool is_last_call = method_call.IsLastCall(); upload_state.ProcessData(method_call.argument, is_last_call); if (is_last_call) { - system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite(); + system.GPU().Maxwell3D().dirty.OnMemoryWrite(); } break; } diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h index f3bc675a9..e0e25c321 100644 --- a/src/video_core/engines/kepler_memory.h +++ b/src/video_core/engines/kepler_memory.h @@ -65,7 +65,6 @@ public: private: Core::System& system; - MemoryManager& memory_manager; Upload::State upload_state; }; diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 39968d403..b318aedb8 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -22,6 +22,7 @@ Maxwell3D::Maxwell3D(Core::System& system, VideoCore::RasterizerInterface& raste MemoryManager& memory_manager) : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager}, macro_interpreter{*this}, upload_state{memory_manager, regs.upload} { + InitDirtySettings(); InitializeRegisterDefaults(); } @@ -69,6 +70,10 @@ void Maxwell3D::InitializeRegisterDefaults() { regs.stencil_back_func_mask = 0xFFFFFFFF; regs.stencil_back_mask = 0xFFFFFFFF; + regs.depth_test_func = Regs::ComparisonOp::Always; + regs.cull.front_face = Regs::Cull::FrontFace::CounterClockWise; + regs.cull.cull_face = Regs::Cull::CullFace::Back; + // TODO(Rodrigo): Most games do not set a point size. I think this is a case of a // register carrying a default value. Assume it's OpenGL's default (1). regs.point_size = 1.0f; @@ -84,23 +89,180 @@ void Maxwell3D::InitializeRegisterDefaults() { // Commercial games seem to assume this value is enabled and nouveau sets this value manually. regs.rt_separate_frag_data = 1; + + // Some games (like Super Mario Odyssey) assume that SRGB is enabled. + regs.framebuffer_srgb = 1; + mme_inline[MAXWELL3D_REG_INDEX(draw.vertex_end_gl)] = true; + mme_inline[MAXWELL3D_REG_INDEX(draw.vertex_begin_gl)] = true; + mme_inline[MAXWELL3D_REG_INDEX(vertex_buffer.count)] = true; + mme_inline[MAXWELL3D_REG_INDEX(index_array.count)] = true; } -void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { +#define DIRTY_REGS_POS(field_name) (offsetof(Maxwell3D::DirtyRegs, field_name)) + +void Maxwell3D::InitDirtySettings() { + const auto set_block = [this](const u32 start, const u32 range, const u8 position) { + const auto start_itr = dirty_pointers.begin() + start; + const auto end_itr = start_itr + range; + std::fill(start_itr, end_itr, position); + }; + dirty.regs.fill(true); + + // Init Render Targets + constexpr u32 registers_per_rt = sizeof(regs.rt[0]) / sizeof(u32); + constexpr u32 rt_start_reg = MAXWELL3D_REG_INDEX(rt); + constexpr u32 rt_end_reg = rt_start_reg + registers_per_rt * 8; + u32 rt_dirty_reg = DIRTY_REGS_POS(render_target); + for (u32 rt_reg = rt_start_reg; rt_reg < rt_end_reg; rt_reg += registers_per_rt) { + set_block(rt_reg, registers_per_rt, rt_dirty_reg); + rt_dirty_reg++; + } + constexpr u32 depth_buffer_flag = DIRTY_REGS_POS(depth_buffer); + dirty_pointers[MAXWELL3D_REG_INDEX(zeta_enable)] = depth_buffer_flag; + dirty_pointers[MAXWELL3D_REG_INDEX(zeta_width)] = depth_buffer_flag; + dirty_pointers[MAXWELL3D_REG_INDEX(zeta_height)] = depth_buffer_flag; + constexpr u32 registers_in_zeta = sizeof(regs.zeta) / sizeof(u32); + constexpr u32 zeta_reg = MAXWELL3D_REG_INDEX(zeta); + set_block(zeta_reg, registers_in_zeta, depth_buffer_flag); + + // Init Vertex Arrays + constexpr u32 vertex_array_start = MAXWELL3D_REG_INDEX(vertex_array); + constexpr u32 vertex_array_size = sizeof(regs.vertex_array[0]) / sizeof(u32); + constexpr u32 vertex_array_end = vertex_array_start + vertex_array_size * Regs::NumVertexArrays; + u32 va_reg = DIRTY_REGS_POS(vertex_array); + u32 vi_reg = DIRTY_REGS_POS(vertex_instance); + for (u32 vertex_reg = vertex_array_start; vertex_reg < vertex_array_end; + vertex_reg += vertex_array_size) { + set_block(vertex_reg, 3, va_reg); + // The divisor concerns vertex array instances + dirty_pointers[vertex_reg + 3] = vi_reg; + va_reg++; + vi_reg++; + } + constexpr u32 vertex_limit_start = MAXWELL3D_REG_INDEX(vertex_array_limit); + constexpr u32 vertex_limit_size = sizeof(regs.vertex_array_limit[0]) / sizeof(u32); + constexpr u32 vertex_limit_end = vertex_limit_start + vertex_limit_size * Regs::NumVertexArrays; + va_reg = DIRTY_REGS_POS(vertex_array); + for (u32 vertex_reg = vertex_limit_start; vertex_reg < vertex_limit_end; + vertex_reg += vertex_limit_size) { + set_block(vertex_reg, vertex_limit_size, va_reg); + va_reg++; + } + constexpr u32 vertex_instance_start = MAXWELL3D_REG_INDEX(instanced_arrays); + constexpr u32 vertex_instance_size = + sizeof(regs.instanced_arrays.is_instanced[0]) / sizeof(u32); + constexpr u32 vertex_instance_end = + vertex_instance_start + vertex_instance_size * Regs::NumVertexArrays; + vi_reg = DIRTY_REGS_POS(vertex_instance); + for (u32 vertex_reg = vertex_instance_start; vertex_reg < vertex_instance_end; + vertex_reg += vertex_instance_size) { + set_block(vertex_reg, vertex_instance_size, vi_reg); + vi_reg++; + } + set_block(MAXWELL3D_REG_INDEX(vertex_attrib_format), regs.vertex_attrib_format.size(), + DIRTY_REGS_POS(vertex_attrib_format)); + + // Init Shaders + constexpr u32 shader_registers_count = + sizeof(regs.shader_config[0]) * Regs::MaxShaderProgram / sizeof(u32); + set_block(MAXWELL3D_REG_INDEX(shader_config[0]), shader_registers_count, + DIRTY_REGS_POS(shaders)); + + // State + + // Viewport + constexpr u32 viewport_dirty_reg = DIRTY_REGS_POS(viewport); + constexpr u32 viewport_start = MAXWELL3D_REG_INDEX(viewports); + constexpr u32 viewport_size = sizeof(regs.viewports) / sizeof(u32); + set_block(viewport_start, viewport_size, viewport_dirty_reg); + constexpr u32 view_volume_start = MAXWELL3D_REG_INDEX(view_volume_clip_control); + constexpr u32 view_volume_size = sizeof(regs.view_volume_clip_control) / sizeof(u32); + set_block(view_volume_start, view_volume_size, viewport_dirty_reg); + + // Viewport transformation + constexpr u32 viewport_trans_start = MAXWELL3D_REG_INDEX(viewport_transform); + constexpr u32 viewport_trans_size = sizeof(regs.viewport_transform) / sizeof(u32); + set_block(viewport_trans_start, viewport_trans_size, DIRTY_REGS_POS(viewport_transform)); + + // Cullmode + constexpr u32 cull_mode_start = MAXWELL3D_REG_INDEX(cull); + constexpr u32 cull_mode_size = sizeof(regs.cull) / sizeof(u32); + set_block(cull_mode_start, cull_mode_size, DIRTY_REGS_POS(cull_mode)); + + // Screen y control + dirty_pointers[MAXWELL3D_REG_INDEX(screen_y_control)] = DIRTY_REGS_POS(screen_y_control); + + // Primitive Restart + constexpr u32 primitive_restart_start = MAXWELL3D_REG_INDEX(primitive_restart); + constexpr u32 primitive_restart_size = sizeof(regs.primitive_restart) / sizeof(u32); + set_block(primitive_restart_start, primitive_restart_size, DIRTY_REGS_POS(primitive_restart)); + + // Depth Test + constexpr u32 depth_test_dirty_reg = DIRTY_REGS_POS(depth_test); + dirty_pointers[MAXWELL3D_REG_INDEX(depth_test_enable)] = depth_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(depth_write_enabled)] = depth_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(depth_test_func)] = depth_test_dirty_reg; + + // Stencil Test + constexpr u32 stencil_test_dirty_reg = DIRTY_REGS_POS(stencil_test); + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_enable)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_front_func_func)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_front_func_ref)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_front_func_mask)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_front_op_fail)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_front_op_zfail)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_front_op_zpass)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_front_mask)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_two_side_enable)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_back_func_func)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_back_func_ref)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_back_func_mask)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_back_op_fail)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_back_op_zfail)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_back_op_zpass)] = stencil_test_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(stencil_back_mask)] = stencil_test_dirty_reg; + + // Color Mask + constexpr u32 color_mask_dirty_reg = DIRTY_REGS_POS(color_mask); + dirty_pointers[MAXWELL3D_REG_INDEX(color_mask_common)] = color_mask_dirty_reg; + set_block(MAXWELL3D_REG_INDEX(color_mask), sizeof(regs.color_mask) / sizeof(u32), + color_mask_dirty_reg); + // Blend State + constexpr u32 blend_state_dirty_reg = DIRTY_REGS_POS(blend_state); + set_block(MAXWELL3D_REG_INDEX(blend_color), sizeof(regs.blend_color) / sizeof(u32), + blend_state_dirty_reg); + dirty_pointers[MAXWELL3D_REG_INDEX(independent_blend_enable)] = blend_state_dirty_reg; + set_block(MAXWELL3D_REG_INDEX(blend), sizeof(regs.blend) / sizeof(u32), blend_state_dirty_reg); + set_block(MAXWELL3D_REG_INDEX(independent_blend), sizeof(regs.independent_blend) / sizeof(u32), + blend_state_dirty_reg); + + // Scissor State + constexpr u32 scissor_test_dirty_reg = DIRTY_REGS_POS(scissor_test); + set_block(MAXWELL3D_REG_INDEX(scissor_test), sizeof(regs.scissor_test) / sizeof(u32), + scissor_test_dirty_reg); + + // Polygon Offset + constexpr u32 polygon_offset_dirty_reg = DIRTY_REGS_POS(polygon_offset); + dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_fill_enable)] = polygon_offset_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_line_enable)] = polygon_offset_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_point_enable)] = polygon_offset_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_units)] = polygon_offset_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_factor)] = polygon_offset_dirty_reg; + dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_clamp)] = polygon_offset_dirty_reg; +} + +void Maxwell3D::CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters) { // Reset the current macro. executing_macro = 0; // Lookup the macro offset - const u32 entry{(method - MacroRegistersStart) >> 1}; - const auto& search{macro_offsets.find(entry)}; - if (search == macro_offsets.end()) { - LOG_CRITICAL(HW_GPU, "macro not found for method 0x{:X}!", method); - UNREACHABLE(); - return; - } + const u32 entry = ((method - MacroRegistersStart) >> 1) % macro_positions.size(); // Execute the current macro. - macro_interpreter.Execute(search->second, std::move(parameters)); + macro_interpreter.Execute(macro_positions[entry], num_parameters, parameters); + if (mme_draw.current_mode != MMEDrawMode::Undefined) { + FlushMMEInlineDraw(); + } } void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { @@ -108,6 +270,14 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { const u32 method = method_call.method; + if (method == cb_data_state.current) { + regs.reg_array[method] = method_call.argument; + ProcessCBData(method_call.argument); + return; + } else if (cb_data_state.current != null_cb_data) { + FinishCBData(); + } + // It is an error to write to a register other than the current macro's ARG register before it // has finished execution. if (executing_macro != 0) { @@ -129,7 +299,8 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { // Call the macro when there are no more parameters in the command buffer if (method_call.IsLastCall()) { - CallMacroMethod(executing_macro, std::move(macro_params)); + CallMacroMethod(executing_macro, macro_params.size(), macro_params.data()); + macro_params.clear(); } return; } @@ -143,49 +314,19 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { if (regs.reg_array[method] != method_call.argument) { regs.reg_array[method] = method_call.argument; - // Color buffers - constexpr u32 first_rt_reg = MAXWELL3D_REG_INDEX(rt); - constexpr u32 registers_per_rt = sizeof(regs.rt[0]) / sizeof(u32); - if (method >= first_rt_reg && - method < first_rt_reg + registers_per_rt * Regs::NumRenderTargets) { - const std::size_t rt_index = (method - first_rt_reg) / registers_per_rt; - dirty_flags.color_buffer.set(rt_index); - } - - // Zeta buffer - constexpr u32 registers_in_zeta = sizeof(regs.zeta) / sizeof(u32); - if (method == MAXWELL3D_REG_INDEX(zeta_enable) || - method == MAXWELL3D_REG_INDEX(zeta_width) || - method == MAXWELL3D_REG_INDEX(zeta_height) || - (method >= MAXWELL3D_REG_INDEX(zeta) && - method < MAXWELL3D_REG_INDEX(zeta) + registers_in_zeta)) { - dirty_flags.zeta_buffer = true; - } - - // Shader - constexpr u32 shader_registers_count = - sizeof(regs.shader_config[0]) * Regs::MaxShaderProgram / sizeof(u32); - if (method >= MAXWELL3D_REG_INDEX(shader_config[0]) && - method < MAXWELL3D_REG_INDEX(shader_config[0]) + shader_registers_count) { - dirty_flags.shaders = true; - } - - // Vertex format - if (method >= MAXWELL3D_REG_INDEX(vertex_attrib_format) && - method < MAXWELL3D_REG_INDEX(vertex_attrib_format) + regs.vertex_attrib_format.size()) { - dirty_flags.vertex_attrib_format = true; - } - - // Vertex buffer - if (method >= MAXWELL3D_REG_INDEX(vertex_array) && - method < MAXWELL3D_REG_INDEX(vertex_array) + 4 * Regs::NumVertexArrays) { - dirty_flags.vertex_array.set((method - MAXWELL3D_REG_INDEX(vertex_array)) >> 2); - } else if (method >= MAXWELL3D_REG_INDEX(vertex_array_limit) && - method < MAXWELL3D_REG_INDEX(vertex_array_limit) + 2 * Regs::NumVertexArrays) { - dirty_flags.vertex_array.set((method - MAXWELL3D_REG_INDEX(vertex_array_limit)) >> 1); - } else if (method >= MAXWELL3D_REG_INDEX(instanced_arrays) && - method < MAXWELL3D_REG_INDEX(instanced_arrays) + Regs::NumVertexArrays) { - dirty_flags.vertex_array.set(method - MAXWELL3D_REG_INDEX(instanced_arrays)); + const std::size_t dirty_reg = dirty_pointers[method]; + if (dirty_reg) { + dirty.regs[dirty_reg] = true; + if (dirty_reg >= DIRTY_REGS_POS(vertex_array) && + dirty_reg < DIRTY_REGS_POS(vertex_array_buffers)) { + dirty.vertex_array_buffers = true; + } else if (dirty_reg >= DIRTY_REGS_POS(vertex_instance) && + dirty_reg < DIRTY_REGS_POS(vertex_instances)) { + dirty.vertex_instances = true; + } else if (dirty_reg >= DIRTY_REGS_POS(render_target) && + dirty_reg < DIRTY_REGS_POS(render_settings)) { + dirty.render_settings = true; + } } } @@ -198,6 +339,10 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { ProcessMacroBind(method_call.argument); break; } + case MAXWELL3D_REG_INDEX(firmware[4]): { + ProcessFirmwareCall4(); + break; + } case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]): case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]): case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]): @@ -214,7 +359,7 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { case MAXWELL3D_REG_INDEX(const_buffer.cb_data[13]): case MAXWELL3D_REG_INDEX(const_buffer.cb_data[14]): case MAXWELL3D_REG_INDEX(const_buffer.cb_data[15]): { - ProcessCBData(method_call.argument); + StartCBData(method); break; } case MAXWELL3D_REG_INDEX(cb_bind[0].raw_config): { @@ -249,6 +394,10 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { ProcessQueryGet(); break; } + case MAXWELL3D_REG_INDEX(condition.mode): { + ProcessQueryCondition(); + break; + } case MAXWELL3D_REG_INDEX(sync_info): { ProcessSyncPoint(); break; @@ -261,7 +410,7 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { const bool is_last_call = method_call.IsLastCall(); upload_state.ProcessData(method_call.argument, is_last_call); if (is_last_call) { - dirty_flags.OnMemoryWrite(); + dirty.OnMemoryWrite(); } break; } @@ -274,6 +423,97 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { } } +void Maxwell3D::StepInstance(const MMEDrawMode expected_mode, const u32 count) { + if (mme_draw.current_mode == MMEDrawMode::Undefined) { + if (mme_draw.gl_begin_consume) { + mme_draw.current_mode = expected_mode; + mme_draw.current_count = count; + mme_draw.instance_count = 1; + mme_draw.gl_begin_consume = false; + mme_draw.gl_end_count = 0; + } + return; + } else { + if (mme_draw.current_mode == expected_mode && count == mme_draw.current_count && + mme_draw.instance_mode && mme_draw.gl_begin_consume) { + mme_draw.instance_count++; + mme_draw.gl_begin_consume = false; + return; + } else { + FlushMMEInlineDraw(); + } + } + // Tail call in case it needs to retry. + StepInstance(expected_mode, count); +} + +void Maxwell3D::CallMethodFromMME(const GPU::MethodCall& method_call) { + const u32 method = method_call.method; + if (mme_inline[method]) { + regs.reg_array[method] = method_call.argument; + if (method == MAXWELL3D_REG_INDEX(vertex_buffer.count) || + method == MAXWELL3D_REG_INDEX(index_array.count)) { + const MMEDrawMode expected_mode = method == MAXWELL3D_REG_INDEX(vertex_buffer.count) + ? MMEDrawMode::Array + : MMEDrawMode::Indexed; + StepInstance(expected_mode, method_call.argument); + } else if (method == MAXWELL3D_REG_INDEX(draw.vertex_begin_gl)) { + mme_draw.instance_mode = + (regs.draw.instance_next != 0) || (regs.draw.instance_cont != 0); + mme_draw.gl_begin_consume = true; + } else { + mme_draw.gl_end_count++; + } + } else { + if (mme_draw.current_mode != MMEDrawMode::Undefined) { + FlushMMEInlineDraw(); + } + CallMethod(method_call); + } +} + +void Maxwell3D::FlushMMEInlineDraw() { + LOG_DEBUG(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()), + regs.vertex_buffer.count); + ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?"); + ASSERT(mme_draw.instance_count == mme_draw.gl_end_count); + + auto debug_context = system.GetGPUDebugContext(); + + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr); + } + + // Both instance configuration registers can not be set at the same time. + ASSERT_MSG(!regs.draw.instance_next || !regs.draw.instance_cont, + "Illegal combination of instancing parameters"); + + const bool is_indexed = mme_draw.current_mode == MMEDrawMode::Indexed; + if (ShouldExecute()) { + rasterizer.DrawMultiBatch(is_indexed); + } + + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); + } + + // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if + // the game is trying to draw indexed or direct mode. This needs to be verified on HW still - + // it's possible that it is incorrect and that there is some other register used to specify the + // drawing mode. + if (is_indexed) { + regs.index_array.count = 0; + } else { + regs.vertex_buffer.count = 0; + } + mme_draw.current_mode = MMEDrawMode::Undefined; + mme_draw.current_count = 0; + mme_draw.instance_count = 0; + mme_draw.instance_mode = false; + mme_draw.gl_begin_consume = false; + mme_draw.gl_end_count = 0; +} + void Maxwell3D::ProcessMacroUpload(u32 data) { ASSERT_MSG(regs.macros.upload_address < macro_memory.size(), "upload_address exceeded macro_memory size!"); @@ -281,7 +521,15 @@ void Maxwell3D::ProcessMacroUpload(u32 data) { } void Maxwell3D::ProcessMacroBind(u32 data) { - macro_offsets[regs.macros.entry] = data; + macro_positions[regs.macros.entry++] = data; +} + +void Maxwell3D::ProcessFirmwareCall4() { + LOG_WARNING(HW_GPU, "(STUBBED) called"); + + // Firmware call 4 is a blob that changes some registers depending on its parameters. + // These registers don't affect emulation and so are stubbed by setting 0xd00 to 1. + regs.reg_array[0xd00] = 1; } void Maxwell3D::ProcessQueryGet() { @@ -302,6 +550,7 @@ void Maxwell3D::ProcessQueryGet() { result = regs.query.query_sequence; break; default: + result = 1; UNIMPLEMENTED_MSG("Unimplemented query select type {}", static_cast<u32>(regs.query.query_get.select.Value())); } @@ -333,7 +582,6 @@ void Maxwell3D::ProcessQueryGet() { query_result.timestamp = system.CoreTiming().GetTicks(); memory_manager.WriteBlock(sequence_address, &query_result, sizeof(query_result)); } - dirty_flags.OnMemoryWrite(); break; } default: @@ -342,16 +590,56 @@ void Maxwell3D::ProcessQueryGet() { } } +void Maxwell3D::ProcessQueryCondition() { + const GPUVAddr condition_address{regs.condition.Address()}; + switch (regs.condition.mode) { + case Regs::ConditionMode::Always: { + execute_on = true; + break; + } + case Regs::ConditionMode::Never: { + execute_on = false; + break; + } + case Regs::ConditionMode::ResNonZero: { + Regs::QueryCompare cmp; + memory_manager.ReadBlockUnsafe(condition_address, &cmp, sizeof(cmp)); + execute_on = cmp.initial_sequence != 0U && cmp.initial_mode != 0U; + break; + } + case Regs::ConditionMode::Equal: { + Regs::QueryCompare cmp; + memory_manager.ReadBlockUnsafe(condition_address, &cmp, sizeof(cmp)); + execute_on = + cmp.initial_sequence == cmp.current_sequence && cmp.initial_mode == cmp.current_mode; + break; + } + case Regs::ConditionMode::NotEqual: { + Regs::QueryCompare cmp; + memory_manager.ReadBlockUnsafe(condition_address, &cmp, sizeof(cmp)); + execute_on = + cmp.initial_sequence != cmp.current_sequence || cmp.initial_mode != cmp.current_mode; + break; + } + default: { + UNIMPLEMENTED_MSG("Uninplemented Condition Mode!"); + execute_on = true; + break; + } + } +} + void Maxwell3D::ProcessSyncPoint() { const u32 sync_point = regs.sync_info.sync_point.Value(); const u32 increment = regs.sync_info.increment.Value(); - const u32 cache_flush = regs.sync_info.unknown.Value(); - LOG_DEBUG(HW_GPU, "Syncpoint set {}, increment: {}, unk: {}", sync_point, increment, - cache_flush); + [[maybe_unused]] const u32 cache_flush = regs.sync_info.unknown.Value(); + if (increment) { + system.GPU().IncrementSyncPoint(sync_point); + } } void Maxwell3D::DrawArrays() { - LOG_DEBUG(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()), + LOG_TRACE(HW_GPU, "called, topology={}, count={}", static_cast<u32>(regs.draw.topology.Value()), regs.vertex_buffer.count); ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?"); @@ -374,7 +662,9 @@ void Maxwell3D::DrawArrays() { } const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count}; - rasterizer.AccelerateDrawBatch(is_indexed); + if (ShouldExecute()) { + rasterizer.DrawBatch(is_indexed); + } if (debug_context) { debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); @@ -396,34 +686,48 @@ void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) { auto& shader = state.shader_stages[static_cast<std::size_t>(stage)]; auto& bind_data = regs.cb_bind[static_cast<std::size_t>(stage)]; - auto& buffer = shader.const_buffers[bind_data.index]; - ASSERT(bind_data.index < Regs::MaxConstBuffers); + auto& buffer = shader.const_buffers[bind_data.index]; buffer.enabled = bind_data.valid.Value() != 0; - buffer.index = bind_data.index; buffer.address = regs.const_buffer.BufferAddress(); buffer.size = regs.const_buffer.cb_size; } void Maxwell3D::ProcessCBData(u32 value) { + const u32 id = cb_data_state.id; + cb_data_state.buffer[id][cb_data_state.counter] = value; + // Increment the current buffer position. + regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4; + cb_data_state.counter++; +} + +void Maxwell3D::StartCBData(u32 method) { + constexpr u32 first_cb_data = MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]); + cb_data_state.start_pos = regs.const_buffer.cb_pos; + cb_data_state.id = method - first_cb_data; + cb_data_state.current = method; + cb_data_state.counter = 0; + ProcessCBData(regs.const_buffer.cb_data[cb_data_state.id]); +} + +void Maxwell3D::FinishCBData() { // Write the input value to the current const buffer at the current position. const GPUVAddr buffer_address = regs.const_buffer.BufferAddress(); ASSERT(buffer_address != 0); // Don't allow writing past the end of the buffer. - ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size); - - const GPUVAddr address{buffer_address + regs.const_buffer.cb_pos}; + ASSERT(regs.const_buffer.cb_pos <= regs.const_buffer.cb_size); - u8* ptr{memory_manager.GetPointer(address)}; - rasterizer.InvalidateRegion(ToCacheAddr(ptr), sizeof(u32)); - memory_manager.Write<u32>(address, value); + const GPUVAddr address{buffer_address + cb_data_state.start_pos}; + const std::size_t size = regs.const_buffer.cb_pos - cb_data_state.start_pos; - dirty_flags.OnMemoryWrite(); + const u32 id = cb_data_state.id; + memory_manager.WriteBlock(address, cb_data_state.buffer[id].data(), size); + dirty.OnMemoryWrite(); - // Increment the current buffer position. - regs.const_buffer.cb_pos = regs.const_buffer.cb_pos + 4; + cb_data_state.id = null_cb_data; + cb_data_state.current = null_cb_data; } Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { @@ -432,14 +736,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(); + [[maybe_unused]] const auto r_type{tic_entry.r_type.Value()}; + [[maybe_unused]] const auto g_type{tic_entry.g_type.Value()}; + [[maybe_unused]] const auto b_type{tic_entry.b_type.Value()}; + [[maybe_unused]] 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); diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index f342c78e6..4c97759ed 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -15,6 +15,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/math_util.h" +#include "video_core/engines/const_buffer_info.h" #include "video_core/engines/engine_upload.h" #include "video_core/gpu.h" #include "video_core/macro_interpreter.h" @@ -61,11 +62,13 @@ public: static constexpr std::size_t NumVertexAttributes = 32; static constexpr std::size_t NumVaryings = 31; static constexpr std::size_t NumTextureSamplers = 32; + static constexpr std::size_t NumImages = 8; // TODO(Rodrigo): Investigate this number static constexpr std::size_t NumClipDistances = 8; static constexpr std::size_t MaxShaderProgram = 6; static constexpr std::size_t MaxShaderStage = 5; // Maximum number of const buffers per shader stage. static constexpr std::size_t MaxConstBuffers = 18; + static constexpr std::size_t MaxConstBufferSize = 0x10000; enum class QueryMode : u32 { Write = 0, @@ -88,6 +91,20 @@ public: enum class QuerySelect : u32 { Zero = 0, + TimeElapsed = 2, + TransformFeedbackPrimitivesGenerated = 11, + PrimitivesGenerated = 18, + SamplesPassed = 21, + TransformFeedbackUnknown = 26, + }; + + struct QueryCompare { + u32 initial_sequence; + u32 initial_mode; + u32 unknown1; + u32 unknown2; + u32 current_sequence; + u32 current_mode; }; enum class QuerySyncCondition : u32 { @@ -95,6 +112,14 @@ public: GreaterThan = 1, }; + enum class ConditionMode : u32 { + Never = 0, + Always = 1, + ResNonZero = 2, + Equal = 3, + NotEqual = 4, + }; + enum class ShaderProgram : u32 { VertexA = 0, VertexB = 1, @@ -786,8 +811,9 @@ public: INSERT_PADDING_WORDS(0x21); u32 vb_element_base; + u32 vb_base_instance; - INSERT_PADDING_WORDS(0x36); + INSERT_PADDING_WORDS(0x35); union { BitField<0, 1, u32> c0; @@ -813,7 +839,18 @@ public: BitField<4, 1, u32> alpha_to_one; } multisample_control; - INSERT_PADDING_WORDS(0x7); + INSERT_PADDING_WORDS(0x4); + + struct { + u32 address_high; + u32 address_low; + ConditionMode mode; + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | + address_low); + } + } condition; struct { u32 tsc_address_high; @@ -1053,7 +1090,9 @@ public: INSERT_PADDING_WORDS(14); } shader_config[MaxShaderProgram]; - INSERT_PADDING_WORDS(0x80); + INSERT_PADDING_WORDS(0x60); + + u32 firmware[0x20]; struct { u32 cb_size; @@ -1112,13 +1151,6 @@ public: static_assert(std::is_trivially_copyable_v<Regs>, "Maxwell3D Regs must be trivially copyable"); struct State { - struct ConstBufferInfo { - GPUVAddr address; - u32 index; - u32 size; - bool enabled; - }; - struct ShaderStageInfo { std::array<ConstBufferInfo, Regs::MaxConstBuffers> const_buffers; }; @@ -1129,23 +1161,77 @@ public: State state{}; - struct DirtyFlags { - std::bitset<8> color_buffer{0xFF}; - std::bitset<32> vertex_array{0xFFFFFFFF}; + struct DirtyRegs { + static constexpr std::size_t NUM_REGS = 256; + union { + struct { + bool null_dirty; + + // Vertex Attributes + bool vertex_attrib_format; + + // Vertex Arrays + std::array<bool, 32> vertex_array; + + bool vertex_array_buffers; + + // Vertex Instances + std::array<bool, 32> vertex_instance; + + bool vertex_instances; + + // Render Targets + std::array<bool, 8> render_target; + bool depth_buffer; + + bool render_settings; - bool vertex_attrib_format = true; - bool zeta_buffer = true; - bool shaders = true; + // Shaders + bool shaders; + + // Rasterizer State + bool viewport; + bool clip_coefficient; + bool cull_mode; + bool primitive_restart; + bool depth_test; + bool stencil_test; + bool blend_state; + bool scissor_test; + bool transform_feedback; + bool color_mask; + bool polygon_offset; + + // Complementary + bool viewport_transform; + bool screen_y_control; + + bool memory_general; + }; + std::array<bool, NUM_REGS> regs; + }; + + void ResetVertexArrays() { + vertex_array.fill(true); + vertex_array_buffers = true; + } + + void ResetRenderTargets() { + depth_buffer = true; + render_target.fill(true); + render_settings = true; + } void OnMemoryWrite() { - zeta_buffer = true; shaders = true; - color_buffer.set(); - vertex_array.set(); + memory_general = true; + ResetRenderTargets(); + ResetVertexArrays(); } - }; - DirtyFlags dirty_flags; + } dirty{}; + + std::array<u8, Regs::NUM_REGS> dirty_pointers{}; /// Reads a register value located at the input method address u32 GetRegisterValue(u32 method) const; @@ -1153,6 +1239,11 @@ public: /// Write the value to the register identified by method. void CallMethod(const GPU::MethodCall& method_call); + /// Write the value to the register identified by method. + void CallMethodFromMME(const GPU::MethodCall& method_call); + + void FlushMMEInlineDraw(); + /// Given a Texture Handle, returns the TSC and TIC entries. Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle, std::size_t offset) const; @@ -1174,6 +1265,25 @@ public: return macro_memory; } + bool ShouldExecute() const { + return execute_on; + } + + enum class MMEDrawMode : u32 { + Undefined, + Array, + Indexed, + }; + + struct MMEDrawState { + MMEDrawMode current_mode{MMEDrawMode::Undefined}; + u32 current_count{}; + u32 instance_count{}; + bool instance_mode{}; + bool gl_begin_consume{}; + u32 gl_end_count{}; + } mme_draw; + private: void InitializeRegisterDefaults(); @@ -1184,7 +1294,9 @@ private: MemoryManager& memory_manager; /// Start offsets of each macro in macro_memory - std::unordered_map<u32, u32> macro_offsets; + std::array<u32, 0x80> macro_positions = {}; + + std::array<bool, Regs::NUM_REGS> mme_inline{}; /// Memory for macro code MacroMemory macro_memory; @@ -1197,20 +1309,34 @@ private: /// Interpreter for the macro codes uploaded to the GPU. MacroInterpreter macro_interpreter; + static constexpr u32 null_cb_data = 0xFFFFFFFF; + struct { + std::array<std::array<u32, 0x4000>, 16> buffer; + u32 current{null_cb_data}; + u32 id{null_cb_data}; + u32 start_pos{}; + u32 counter{}; + } cb_data_state; + Upload::State upload_state; + bool execute_on{true}; + /// Retrieves information about a specific TIC entry from the TIC buffer. Texture::TICEntry GetTICEntry(u32 tic_index) const; /// Retrieves information about a specific TSC entry from the TSC buffer. Texture::TSCEntry GetTSCEntry(u32 tsc_index) const; + void InitDirtySettings(); + /** * Call a macro on this engine. * @param method Method to call + * @param num_parameters Number of arguments * @param parameters Arguments to the method call */ - void CallMacroMethod(u32 method, std::vector<u32> parameters); + void CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters); /// Handles writes to the macro uploading register. void ProcessMacroUpload(u32 data); @@ -1218,23 +1344,34 @@ private: /// Handles writes to the macro bind register. void ProcessMacroBind(u32 data); + /// Handles firmware blob 4 + void ProcessFirmwareCall4(); + /// Handles a write to the CLEAR_BUFFERS register. void ProcessClearBuffers(); /// Handles a write to the QUERY_GET register. void ProcessQueryGet(); + // Handles Conditional Rendering + void ProcessQueryCondition(); + /// Handles writes to syncing register. void ProcessSyncPoint(); /// Handles a write to the CB_DATA[i] register. + void StartCBData(u32 method); void ProcessCBData(u32 value); + void FinishCBData(); /// Handles a write to the CB_BIND register. void ProcessCBBind(Regs::ShaderStage stage); /// Handles a write to the VERTEX_END_GL register, triggering a draw. void DrawArrays(); + + // Handles a instance drawcall from MME + void StepInstance(MMEDrawMode expected_mode, u32 count); }; #define ASSERT_REG_POSITION(field_name, position) \ @@ -1291,10 +1428,12 @@ ASSERT_REG_POSITION(stencil_front_mask, 0x4E7); ASSERT_REG_POSITION(frag_color_clamp, 0x4EA); ASSERT_REG_POSITION(screen_y_control, 0x4EB); ASSERT_REG_POSITION(vb_element_base, 0x50D); +ASSERT_REG_POSITION(vb_base_instance, 0x50E); ASSERT_REG_POSITION(clip_distance_enabled, 0x544); ASSERT_REG_POSITION(point_size, 0x546); ASSERT_REG_POSITION(zeta_enable, 0x54E); ASSERT_REG_POSITION(multisample_control, 0x54F); +ASSERT_REG_POSITION(condition, 0x554); ASSERT_REG_POSITION(tsc, 0x557); ASSERT_REG_POSITION(polygon_offset_factor, 0x55b); ASSERT_REG_POSITION(tic, 0x55D); @@ -1324,6 +1463,7 @@ ASSERT_REG_POSITION(vertex_array[0], 0x700); ASSERT_REG_POSITION(independent_blend, 0x780); ASSERT_REG_POSITION(vertex_array_limit[0], 0x7C0); ASSERT_REG_POSITION(shader_config[0], 0x800); +ASSERT_REG_POSITION(firmware, 0x8C0); ASSERT_REG_POSITION(const_buffer, 0x8E0); ASSERT_REG_POSITION(cb_bind[0], 0x904); ASSERT_REG_POSITION(tex_cb_index, 0x982); diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 3a5dfef0c..ad8453c5f 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -5,18 +5,17 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" +#include "core/settings.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_dma.h" #include "video_core/memory_manager.h" -#include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" #include "video_core/textures/decoders.h" namespace Tegra::Engines { -MaxwellDMA::MaxwellDMA(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - MemoryManager& memory_manager) - : system{system}, rasterizer{rasterizer}, memory_manager{memory_manager} {} +MaxwellDMA::MaxwellDMA(Core::System& system, MemoryManager& memory_manager) + : system{system}, memory_manager{memory_manager} {} void MaxwellDMA::CallMethod(const GPU::MethodCall& method_call) { ASSERT_MSG(method_call.method < Regs::NUM_REGS, @@ -38,7 +37,7 @@ void MaxwellDMA::CallMethod(const GPU::MethodCall& method_call) { } void MaxwellDMA::HandleCopy() { - LOG_WARNING(HW_GPU, "Requested a DMA copy"); + LOG_TRACE(HW_GPU, "Requested a DMA copy"); const GPUVAddr source = regs.src_address.Address(); const GPUVAddr dest = regs.dst_address.Address(); @@ -58,7 +57,7 @@ void MaxwellDMA::HandleCopy() { } // All copies here update the main memory, so mark all rasterizer states as invalid. - system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite(); + system.GPU().Maxwell3D().dirty.OnMemoryWrite(); if (regs.exec.is_dst_linear && regs.exec.is_src_linear) { // When the enable_2d bit is disabled, the copy is performed as if we were copying a 1D @@ -84,13 +83,17 @@ void MaxwellDMA::HandleCopy() { ASSERT(regs.exec.enable_2d == 1); if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) { - ASSERT(regs.src_params.size_z == 1); + ASSERT(regs.src_params.BlockDepth() == 0); // If the input is tiled and the output is linear, deswizzle the input and copy it over. - const u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x; + const u32 bytes_per_pixel = regs.dst_pitch / regs.x_count; const std::size_t src_size = Texture::CalculateSize( - true, src_bytes_per_pixel, regs.src_params.size_x, regs.src_params.size_y, + true, bytes_per_pixel, regs.src_params.size_x, regs.src_params.size_y, regs.src_params.size_z, regs.src_params.BlockHeight(), regs.src_params.BlockDepth()); + const std::size_t src_layer_size = Texture::CalculateSize( + true, bytes_per_pixel, regs.src_params.size_x, regs.src_params.size_y, 1, + regs.src_params.BlockHeight(), regs.src_params.BlockDepth()); + const std::size_t dst_size = regs.dst_pitch * regs.y_count; if (read_buffer.size() < src_size) { @@ -104,23 +107,23 @@ void MaxwellDMA::HandleCopy() { memory_manager.ReadBlock(source, read_buffer.data(), src_size); memory_manager.ReadBlock(dest, write_buffer.data(), dst_size); - Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch, - regs.src_params.size_x, src_bytes_per_pixel, read_buffer.data(), - write_buffer.data(), regs.src_params.BlockHeight(), - regs.src_params.pos_x, regs.src_params.pos_y); + Texture::UnswizzleSubrect( + regs.x_count, regs.y_count, regs.dst_pitch, regs.src_params.size_x, bytes_per_pixel, + read_buffer.data() + src_layer_size * regs.src_params.pos_z, write_buffer.data(), + regs.src_params.BlockHeight(), regs.src_params.pos_x, regs.src_params.pos_y); 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; + const u32 bytes_per_pixel = regs.src_pitch / regs.x_count; const std::size_t dst_size = Texture::CalculateSize( - true, src_bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y, + true, bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y, regs.dst_params.size_z, regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth()); const std::size_t dst_layer_size = Texture::CalculateSize( - true, src_bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y, 1, + true, bytes_per_pixel, regs.dst_params.size_x, regs.dst_params.size_y, 1, regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth()); const std::size_t src_size = regs.src_pitch * regs.y_count; @@ -133,14 +136,19 @@ void MaxwellDMA::HandleCopy() { write_buffer.resize(dst_size); } - memory_manager.ReadBlock(source, read_buffer.data(), src_size); - memory_manager.ReadBlock(dest, write_buffer.data(), dst_size); + if (Settings::values.use_accurate_gpu_emulation) { + memory_manager.ReadBlock(source, read_buffer.data(), src_size); + memory_manager.ReadBlock(dest, write_buffer.data(), dst_size); + } else { + memory_manager.ReadBlockUnsafe(source, read_buffer.data(), src_size); + memory_manager.ReadBlockUnsafe(dest, write_buffer.data(), dst_size); + } // If the input is linear and the output is tiled, swizzle the input and copy it over. - Texture::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x, - src_bytes_per_pixel, - write_buffer.data() + dst_layer_size * regs.dst_params.pos_z, - read_buffer.data(), regs.dst_params.BlockHeight()); + Texture::SwizzleSubrect( + regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x, bytes_per_pixel, + write_buffer.data() + dst_layer_size * regs.dst_params.pos_z, read_buffer.data(), + regs.dst_params.BlockHeight(), regs.dst_params.pos_x, regs.dst_params.pos_y); memory_manager.WriteBlock(dest, write_buffer.data(), dst_size); } diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index e5942f671..93808a9bb 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -20,10 +20,6 @@ namespace Tegra { class MemoryManager; } -namespace VideoCore { -class RasterizerInterface; -} - namespace Tegra::Engines { /** @@ -33,8 +29,7 @@ namespace Tegra::Engines { class MaxwellDMA final { public: - explicit MaxwellDMA(Core::System& system, VideoCore::RasterizerInterface& rasterizer, - MemoryManager& memory_manager); + explicit MaxwellDMA(Core::System& system, MemoryManager& memory_manager); ~MaxwellDMA() = default; /// Write the value to the register identified by method. @@ -59,11 +54,11 @@ public: }; u32 BlockHeight() const { - return 1 << block_height; + return block_height.Value(); } u32 BlockDepth() const { - return 1 << block_depth; + return block_depth.Value(); } }; @@ -180,8 +175,6 @@ public: private: Core::System& system; - VideoCore::RasterizerInterface& rasterizer; - MemoryManager& memory_manager; std::vector<u8> read_buffer; diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index ffb3ec3e0..7a6355ce2 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -4,6 +4,7 @@ #pragma once +#include <array> #include <bitset> #include <optional> #include <tuple> @@ -77,7 +78,7 @@ union Attribute { constexpr explicit Attribute(u64 value) : value(value) {} enum class Index : u64 { - PointSize = 6, + LayerViewportPointSize = 6, Position = 7, Attribute_0 = 8, Attribute_31 = 39, @@ -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, @@ -508,6 +538,41 @@ enum class PhysicalAttributeDirection : u64 { Output = 1, }; +enum class VoteOperation : u64 { + All = 0, // allThreadsNV + Any = 1, // anyThreadNV + Eq = 2, // allThreadsEqualNV +}; + +enum class ImageAtomicOperationType : u64 { + U32 = 0, + S32 = 1, + U64 = 2, + F32 = 3, + S64 = 5, + SD32 = 6, + SD64 = 7, +}; + +enum class ImageAtomicOperation : u64 { + Add = 0, + Min = 1, + Max = 2, + Inc = 3, + Dec = 4, + And = 5, + Or = 6, + Xor = 7, + Exch = 8, +}; + +enum class ShuffleOperation : u64 { + Idx = 0, // shuffleNV + Up = 1, // shuffleUpNV + Down = 2, // shuffleDownNV + Bfly = 3, // shuffleXorNV +}; + union Instruction { Instruction& operator=(const Instruction& instr) { value = instr.value; @@ -530,6 +595,27 @@ union Instruction { BitField<48, 16, u64> opcode; union { + BitField<8, 5, ConditionCode> cc; + BitField<13, 1, u64> trigger; + } nop; + + union { + BitField<48, 2, VoteOperation> operation; + BitField<45, 3, u64> dest_pred; + BitField<39, 3, u64> value; + BitField<42, 1, u64> negate_value; + } vote; + + union { + BitField<30, 2, ShuffleOperation> operation; + BitField<48, 3, u64> pred48; + BitField<28, 1, u64> is_index_imm; + BitField<29, 1, u64> is_mask_imm; + BitField<20, 5, u64> index_imm; + BitField<34, 13, u64> mask_imm; + } shfl; + + union { BitField<8, 8, Register> gpr; BitField<20, 24, s64> offset; } gmem; @@ -627,6 +713,10 @@ union Instruction { } shift; union { + BitField<39, 1, u64> wrap; + } shr; + + union { BitField<39, 5, u64> shift_amount; BitField<48, 1, u64> negate_b; BitField<49, 1, u64> negate_a; @@ -811,7 +901,7 @@ union Instruction { } ld_l; union { - BitField<44, 2, LmemStoreCacheManagement> cache_management; + BitField<44, 2, StoreCacheManagement> cache_management; } st_l; union { @@ -838,6 +928,7 @@ union Instruction { union { BitField<0, 3, u64> pred0; BitField<3, 3, u64> pred3; + BitField<6, 1, u64> neg_b; BitField<7, 1, u64> abs_a; BitField<39, 3, u64> pred39; BitField<42, 1, u64> neg_pred; @@ -859,6 +950,11 @@ union Instruction { } isetp; union { + BitField<48, 1, u64> is_signed; + BitField<49, 3, PredCondition> cond; + } icmp; + + union { BitField<0, 3, u64> pred0; BitField<3, 3, u64> pred3; BitField<12, 3, u64> pred12; @@ -901,8 +997,6 @@ union Instruction { } csetp; union { - BitField<35, 4, PredCondition> cond; - BitField<49, 1, u64> h_and; BitField<6, 1, u64> ftz; BitField<45, 2, PredOperation> op; BitField<3, 3, u64> pred3; @@ -910,9 +1004,21 @@ union Instruction { BitField<43, 1, u64> negate_a; BitField<44, 1, u64> abs_a; BitField<47, 2, HalfType> type_a; - BitField<31, 1, u64> negate_b; - BitField<30, 1, u64> abs_b; - BitField<28, 2, HalfType> type_b; + union { + BitField<35, 4, PredCondition> cond; + BitField<49, 1, u64> h_and; + BitField<31, 1, u64> negate_b; + BitField<30, 1, u64> abs_b; + BitField<28, 2, HalfType> type_b; + } reg; + union { + BitField<56, 1, u64> negate_b; + BitField<54, 1, u64> abs_b; + } cbuf; + union { + BitField<49, 4, PredCondition> cond; + BitField<53, 1, u64> h_and; + } cbuf_and_imm; BitField<42, 1, u64> neg_pred; BitField<39, 3, u64> pred39; } hsetp2; @@ -961,7 +1067,6 @@ union Instruction { } iset; union { - BitField<41, 2, u64> selector; // i2i and i2f only BitField<45, 1, u64> negate_a; BitField<49, 1, u64> abs_a; BitField<10, 2, Register::Size> src_size; @@ -978,8 +1083,6 @@ union Instruction { } f2i; union { - BitField<8, 2, Register::Size> src_size; - BitField<10, 2, Register::Size> dst_size; BitField<39, 4, u64> rounding; // H0, H1 extract for F16 missing BitField<41, 1, u64> selector; // Guessed as some games set it, TODO: reverse this value @@ -989,6 +1092,13 @@ union Instruction { } } f2f; + union { + BitField<41, 2, u64> selector; + } int_src; + + union { + BitField<41, 1, u64> selector; + } float_src; } conversion; union { @@ -1232,8 +1342,23 @@ 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; + BitField<59, 1, u64> fp32_flag; TextureType GetTextureType() const { // The TLDS instruction has a weird encoding for the texture type. @@ -1281,6 +1406,43 @@ 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; + } + } suldst; + + union { + BitField<28, 1, u64> is_ba; + BitField<51, 3, ImageAtomicOperationType> operation_type; + BitField<33, 3, ImageType> image_type; + BitField<29, 4, ImageAtomicOperation> operation; + BitField<49, 2, OutOfBoundsStore> out_of_bounds_store; + } suatom_d; + + union { BitField<20, 24, u64> target; BitField<5, 1, u64> constant_buffer; @@ -1295,6 +1457,20 @@ union Instruction { } bra; union { + BitField<20, 24, u64> target; + BitField<5, 1, u64> constant_buffer; + + s32 GetBranchExtend() const { + // Sign extend the branch target offset + u32 mask = 1U << (24 - 1); + u32 value = static_cast<u32>(target); + // The branch offset is relative to the next instruction and is stored in bytes, so + // divide it by the size of an instruction and add 1 to it. + return static_cast<s32>((value ^ mask) - mask) / sizeof(Instruction) + 1; + } + } brx; + + union { BitField<39, 1, u64> emit; // EmitVertex BitField<40, 1, u64> cut; // EndPrimitive } out; @@ -1371,6 +1547,7 @@ union Instruction { Attribute attribute; Sampler sampler; + Image image; u64 value; }; @@ -1385,11 +1562,14 @@ public: SYNC, BRK, DEPBAR, + VOTE, + SHFL, BFE_C, BFE_R, BFE_IMM, BFI_IMM_R, BRA, + BRX, PBK, LD_A, LD_L, @@ -1408,12 +1588,17 @@ 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 + SULD, // Surface Load + SUATOM, // Surface Atomic Operation EXIT, + NOP, IPA, OUT_R, // Emit vertex/primitive ISBERD, @@ -1456,7 +1641,9 @@ public: HFMA2_RC, HFMA2_RR, HFMA2_IMM_R, + HSETP2_C, HSETP2_R, + HSETP2_IMM, HSET2_R, POPC_C, POPC_R, @@ -1464,6 +1651,10 @@ public: SEL_C, SEL_R, SEL_IMM, + ICMP_RC, + ICMP_R, + ICMP_CR, + ICMP_IMM, MUFU, // Multi-Function Operator RRO_C, // Range Reduction Operator RRO_R, @@ -1541,8 +1732,10 @@ public: Hfma2, Flow, Synch, + Warp, Memory, Texture, + Image, FloatSet, FloatSetPredicate, IntegerSet, @@ -1661,10 +1854,13 @@ private: INST("111000101001----", Id::SSY, Type::Flow, "SSY"), INST("111000101010----", Id::PBK, Type::Flow, "PBK"), INST("111000100100----", Id::BRA, Type::Flow, "BRA"), + INST("111000100101----", Id::BRX, Type::Flow, "BRX"), INST("1111000011111---", Id::SYNC, Type::Flow, "SYNC"), INST("111000110100---", Id::BRK, Type::Flow, "BRK"), INST("111000110000----", Id::EXIT, Type::Flow, "EXIT"), INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"), + INST("0101000011011---", Id::VOTE, Type::Warp, "VOTE"), + INST("1110111100010---", Id::SHFL, Type::Warp, "SHFL"), INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"), INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"), INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"), @@ -1682,11 +1878,16 @@ 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("1101101---------", Id::TLDS, Type::Texture, "TLDS"), + INST("11011100--11----", Id::TLD, Type::Texture, "TLD"), + INST("1101-01---------", 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("11101011000-----", Id::SULD, Type::Image, "SULD"), + INST("1110101000------", Id::SUATOM, Type::Image, "SUATOM_D"), + INST("0101000010110---", Id::NOP, Type::Trivial, "NOP"), INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"), INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"), @@ -1720,6 +1921,10 @@ private: INST("0100110010100---", Id::SEL_C, Type::ArithmeticInteger, "SEL_C"), INST("0101110010100---", Id::SEL_R, Type::ArithmeticInteger, "SEL_R"), INST("0011100-10100---", Id::SEL_IMM, Type::ArithmeticInteger, "SEL_IMM"), + INST("010100110100----", Id::ICMP_RC, Type::ArithmeticInteger, "ICMP_RC"), + INST("010110110100----", Id::ICMP_R, Type::ArithmeticInteger, "ICMP_R"), + INST("010010110100----", Id::ICMP_CR, Type::ArithmeticInteger, "ICMP_CR"), + INST("0011011-0100----", Id::ICMP_IMM, Type::ArithmeticInteger, "ICMP_IMM"), INST("0101101111011---", Id::LEA_R2, Type::ArithmeticInteger, "LEA_R2"), INST("0101101111010---", Id::LEA_R1, Type::ArithmeticInteger, "LEA_R1"), INST("001101101101----", Id::LEA_IMM, Type::ArithmeticInteger, "LEA_IMM"), @@ -1735,7 +1940,9 @@ private: INST("01100---1-------", Id::HFMA2_RC, Type::Hfma2, "HFMA2_RC"), INST("0101110100000---", Id::HFMA2_RR, Type::Hfma2, "HFMA2_RR"), INST("01110---0-------", Id::HFMA2_IMM_R, Type::Hfma2, "HFMA2_R_IMM"), - INST("0101110100100---", Id::HSETP2_R, Type::HalfSetPredicate, "HSETP_R"), + INST("0111111-1-------", Id::HSETP2_C, Type::HalfSetPredicate, "HSETP2_C"), + INST("0101110100100---", Id::HSETP2_R, Type::HalfSetPredicate, "HSETP2_R"), + INST("0111111-0-------", Id::HSETP2_IMM, Type::HalfSetPredicate, "HSETP2_IMM"), INST("0101110100011---", Id::HSET2_R, Type::HalfSet, "HSET2_R"), INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"), INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"), diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 52706505b..76cfe8107 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -17,26 +17,15 @@ namespace Tegra { -u32 FramebufferConfig::BytesPerPixel(PixelFormat format) { - switch (format) { - case PixelFormat::ABGR8: - case PixelFormat::BGRA8: - return 4; - default: - return 4; - } - - UNREACHABLE(); -} - -GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer) : renderer{renderer} { +GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer, bool is_async) + : system{system}, renderer{renderer}, is_async{is_async} { auto& rasterizer{renderer.Rasterizer()}; - memory_manager = std::make_unique<Tegra::MemoryManager>(rasterizer); + memory_manager = std::make_unique<Tegra::MemoryManager>(system, rasterizer); dma_pusher = std::make_unique<Tegra::DmaPusher>(*this); maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager); - fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager); + fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer); kepler_compute = std::make_unique<Engines::KeplerCompute>(system, rasterizer, *memory_manager); - maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, rasterizer, *memory_manager); + maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager); kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager); } @@ -50,6 +39,14 @@ const Engines::Maxwell3D& GPU::Maxwell3D() const { return *maxwell_3d; } +Engines::KeplerCompute& GPU::KeplerCompute() { + return *kepler_compute; +} + +const Engines::KeplerCompute& GPU::KeplerCompute() const { + return *kepler_compute; +} + MemoryManager& GPU::MemoryManager() { return *memory_manager; } @@ -66,6 +63,55 @@ const DmaPusher& GPU::DmaPusher() const { return *dma_pusher; } +void GPU::IncrementSyncPoint(const u32 syncpoint_id) { + syncpoints[syncpoint_id]++; + std::lock_guard lock{sync_mutex}; + if (!syncpt_interrupts[syncpoint_id].empty()) { + u32 value = syncpoints[syncpoint_id].load(); + auto it = syncpt_interrupts[syncpoint_id].begin(); + while (it != syncpt_interrupts[syncpoint_id].end()) { + if (value >= *it) { + TriggerCpuInterrupt(syncpoint_id, *it); + it = syncpt_interrupts[syncpoint_id].erase(it); + continue; + } + it++; + } + } +} + +u32 GPU::GetSyncpointValue(const u32 syncpoint_id) const { + return syncpoints[syncpoint_id].load(); +} + +void GPU::RegisterSyncptInterrupt(const u32 syncpoint_id, const u32 value) { + auto& interrupt = syncpt_interrupts[syncpoint_id]; + bool contains = std::any_of(interrupt.begin(), interrupt.end(), + [value](u32 in_value) { return in_value == value; }); + if (contains) { + return; + } + syncpt_interrupts[syncpoint_id].emplace_back(value); +} + +bool GPU::CancelSyncptInterrupt(const u32 syncpoint_id, const u32 value) { + std::lock_guard lock{sync_mutex}; + auto& interrupt = syncpt_interrupts[syncpoint_id]; + const auto iter = + std::find_if(interrupt.begin(), interrupt.end(), + [value](u32 interrupt_value) { return value == interrupt_value; }); + + if (iter == interrupt.end()) { + return false; + } + interrupt.erase(iter); + return true; +} + +void GPU::FlushCommands() { + renderer.Rasterizer().FlushCommands(); +} + u32 RenderTargetBytesPerPixel(RenderTargetFormat format) { ASSERT(format != RenderTargetFormat::NONE); @@ -76,6 +122,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) { case RenderTargetFormat::RGBA16_UINT: case RenderTargetFormat::RGBA16_UNORM: case RenderTargetFormat::RGBA16_FLOAT: + case RenderTargetFormat::RGBX16_FLOAT: case RenderTargetFormat::RG32_FLOAT: case RenderTargetFormat::RG32_UINT: return 8; @@ -143,12 +190,12 @@ enum class BufferMethods { NotifyIntr = 0x8, WrcacheFlush = 0x9, Unk28 = 0xA, - Unk2c = 0xB, + UnkCacheFlush = 0xB, RefCnt = 0x14, SemaphoreAcquire = 0x1A, SemaphoreRelease = 0x1B, - Unk70 = 0x1C, - Unk74 = 0x1D, + FenceValue = 0x1C, + FenceAction = 0x1D, Unk78 = 0x1E, Unk7c = 0x1F, Yield = 0x20, @@ -194,6 +241,10 @@ void GPU::CallPullerMethod(const MethodCall& method_call) { case BufferMethods::SemaphoreAddressLow: case BufferMethods::SemaphoreSequence: case BufferMethods::RefCnt: + case BufferMethods::UnkCacheFlush: + case BufferMethods::WrcacheFlush: + case BufferMethods::FenceValue: + case BufferMethods::FenceAction: break; case BufferMethods::SemaphoreTrigger: { ProcessSemaphoreTriggerMethod(); @@ -204,21 +255,11 @@ void GPU::CallPullerMethod(const MethodCall& method_call) { LOG_ERROR(HW_GPU, "Special puller engine method NotifyIntr not implemented"); break; } - case BufferMethods::WrcacheFlush: { - // TODO(Kmather73): Research and implement this method. - LOG_ERROR(HW_GPU, "Special puller engine method WrcacheFlush not implemented"); - break; - } case BufferMethods::Unk28: { // TODO(Kmather73): Research and implement this method. LOG_ERROR(HW_GPU, "Special puller engine method Unk28 not implemented"); break; } - case BufferMethods::Unk2c: { - // TODO(Kmather73): Research and implement this method. - LOG_ERROR(HW_GPU, "Special puller engine method Unk2c not implemented"); - break; - } case BufferMethods::SemaphoreAcquire: { ProcessSemaphoreAcquire(); break; diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index fe6628923..29fa8e95b 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -5,8 +5,12 @@ #pragma once #include <array> +#include <atomic> +#include <list> #include <memory> +#include <mutex> #include "common/common_types.h" +#include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvflinger/buffer_queue.h" #include "video_core/dma_pusher.h" @@ -15,6 +19,10 @@ inline CacheAddr ToCacheAddr(const void* host_ptr) { return reinterpret_cast<CacheAddr>(host_ptr); } +inline u8* FromCacheAddr(CacheAddr cache_addr) { + return reinterpret_cast<u8*>(cache_addr); +} + namespace Core { class System; } @@ -34,6 +42,7 @@ enum class RenderTargetFormat : u32 { RGBA16_FLOAT = 0xCA, RG32_FLOAT = 0xCB, RG32_UINT = 0xCD, + RGBX16_FLOAT = 0xCE, BGRA8_UNORM = 0xCF, BGRA8_SRGB = 0xD0, RGB10_A2_UNORM = 0xD1, @@ -87,14 +96,10 @@ class DebugContext; struct FramebufferConfig { enum class PixelFormat : u32 { ABGR8 = 1, + RGB565 = 4, BGRA8 = 5, }; - /** - * Returns the number of bytes per pixel. - */ - static u32 BytesPerPixel(PixelFormat format); - VAddr address; u32 offset; u32 width; @@ -127,7 +132,7 @@ class MemoryManager; class GPU { public: - explicit GPU(Core::System& system, VideoCore::RendererBase& renderer); + explicit GPU(Core::System& system, VideoCore::RendererBase& renderer, bool is_async); virtual ~GPU(); @@ -149,12 +154,20 @@ public: /// Calls a GPU method. void CallMethod(const MethodCall& method_call); + void FlushCommands(); + /// Returns a reference to the Maxwell3D GPU engine. Engines::Maxwell3D& Maxwell3D(); /// Returns a const reference to the Maxwell3D GPU engine. const Engines::Maxwell3D& Maxwell3D() const; + /// Returns a reference to the KeplerCompute GPU engine. + Engines::KeplerCompute& KeplerCompute(); + + /// Returns a reference to the KeplerCompute GPU engine. + const Engines::KeplerCompute& KeplerCompute() const; + /// Returns a reference to the GPU memory manager. Tegra::MemoryManager& MemoryManager(); @@ -164,6 +177,22 @@ public: /// Returns a reference to the GPU DMA pusher. Tegra::DmaPusher& DmaPusher(); + void IncrementSyncPoint(u32 syncpoint_id); + + u32 GetSyncpointValue(u32 syncpoint_id) const; + + void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value); + + bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value); + + std::unique_lock<std::mutex> LockSync() { + return std::unique_lock{sync_mutex}; + } + + bool IsAsync() const { + return is_async; + } + /// Returns a const reference to the GPU DMA pusher. const Tegra::DmaPusher& DmaPusher() const; @@ -194,7 +223,12 @@ public: u32 semaphore_acquire; u32 semaphore_release; - INSERT_PADDING_WORDS(0xE4); + u32 fence_value; + union { + BitField<4, 4, u32> operation; + BitField<8, 8, u32> id; + } fence_action; + INSERT_PADDING_WORDS(0xE2); // Puller state u32 acquire_mode; @@ -216,8 +250,7 @@ public: virtual void PushGPUEntries(Tegra::CommandList&& entries) = 0; /// Swap buffers (render frame) - virtual void SwapBuffers( - std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0; + virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory virtual void FlushRegion(CacheAddr addr, u64 size) = 0; @@ -228,6 +261,9 @@ public: /// Notify rasterizer that any caches of the specified region should be flushed and invalidated virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0; +protected: + virtual void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const = 0; + private: void ProcessBindMethod(const MethodCall& method_call); void ProcessSemaphoreTriggerMethod(); @@ -245,6 +281,7 @@ private: protected: std::unique_ptr<Tegra::DmaPusher> dma_pusher; + Core::System& system; VideoCore::RendererBase& renderer; private: @@ -262,6 +299,14 @@ private: std::unique_ptr<Engines::MaxwellDMA> maxwell_dma; /// Inline memory engine std::unique_ptr<Engines::KeplerMemory> kepler_memory; + + std::array<std::atomic<u32>, Service::Nvidia::MaxSyncPoints> syncpoints{}; + + std::array<std::list<u32>, Service::Nvidia::MaxSyncPoints> syncpt_interrupts; + + std::mutex sync_mutex; + + const bool is_async; }; #define ASSERT_REG_POSITION(field_name, position) \ @@ -274,6 +319,8 @@ ASSERT_REG_POSITION(semaphore_trigger, 0x7); ASSERT_REG_POSITION(reference_count, 0x14); ASSERT_REG_POSITION(semaphore_acquire, 0x1A); ASSERT_REG_POSITION(semaphore_release, 0x1B); +ASSERT_REG_POSITION(fence_value, 0x1C); +ASSERT_REG_POSITION(fence_action, 0x1D); ASSERT_REG_POSITION(acquire_mode, 0x100); ASSERT_REG_POSITION(acquire_source, 0x101); diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp index d4e2553a9..f2a3a390e 100644 --- a/src/video_core/gpu_asynch.cpp +++ b/src/video_core/gpu_asynch.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/core.h" +#include "core/hardware_interrupt_manager.h" #include "video_core/gpu_asynch.h" #include "video_core/gpu_thread.h" #include "video_core/renderer_base.h" @@ -9,7 +11,7 @@ namespace VideoCommon { GPUAsynch::GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer) - : GPU(system, renderer), gpu_thread{system} {} + : GPU(system, renderer, true), gpu_thread{system} {} GPUAsynch::~GPUAsynch() = default; @@ -21,9 +23,8 @@ void GPUAsynch::PushGPUEntries(Tegra::CommandList&& entries) { gpu_thread.SubmitList(std::move(entries)); } -void GPUAsynch::SwapBuffers( - std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { - gpu_thread.SwapBuffers(std::move(framebuffer)); +void GPUAsynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { + gpu_thread.SwapBuffers(framebuffer); } void GPUAsynch::FlushRegion(CacheAddr addr, u64 size) { @@ -38,4 +39,9 @@ void GPUAsynch::FlushAndInvalidateRegion(CacheAddr addr, u64 size) { gpu_thread.FlushAndInvalidateRegion(addr, size); } +void GPUAsynch::TriggerCpuInterrupt(const u32 syncpoint_id, const u32 value) const { + auto& interrupt_manager = system.InterruptManager(); + interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value); +} + } // namespace VideoCommon diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h index 30be74cba..a12f9bac4 100644 --- a/src/video_core/gpu_asynch.h +++ b/src/video_core/gpu_asynch.h @@ -14,19 +14,21 @@ class RendererBase; namespace VideoCommon { /// Implementation of GPU interface that runs the GPU asynchronously -class GPUAsynch : public Tegra::GPU { +class GPUAsynch final : public Tegra::GPU { public: explicit GPUAsynch(Core::System& system, VideoCore::RendererBase& renderer); ~GPUAsynch() override; void Start() override; void PushGPUEntries(Tegra::CommandList&& entries) override; - void SwapBuffers( - std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override; + void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; void FlushRegion(CacheAddr addr, u64 size) override; void InvalidateRegion(CacheAddr addr, u64 size) override; void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; +protected: + void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const override; + private: GPUThread::ThreadManager gpu_thread; }; diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp index 45e43b1dc..d48221077 100644 --- a/src/video_core/gpu_synch.cpp +++ b/src/video_core/gpu_synch.cpp @@ -8,7 +8,7 @@ namespace VideoCommon { GPUSynch::GPUSynch(Core::System& system, VideoCore::RendererBase& renderer) - : GPU(system, renderer) {} + : GPU(system, renderer, false) {} GPUSynch::~GPUSynch() = default; @@ -19,9 +19,8 @@ void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) { dma_pusher->DispatchCalls(); } -void GPUSynch::SwapBuffers( - std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { - renderer.SwapBuffers(std::move(framebuffer)); +void GPUSynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { + renderer.SwapBuffers(framebuffer); } void GPUSynch::FlushRegion(CacheAddr addr, u64 size) { diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h index 3031fcf72..5eb1c461c 100644 --- a/src/video_core/gpu_synch.h +++ b/src/video_core/gpu_synch.h @@ -13,18 +13,21 @@ class RendererBase; namespace VideoCommon { /// Implementation of GPU interface that runs the GPU synchronously -class GPUSynch : public Tegra::GPU { +class GPUSynch final : public Tegra::GPU { public: explicit GPUSynch(Core::System& system, VideoCore::RendererBase& renderer); ~GPUSynch() override; void Start() override; void PushGPUEntries(Tegra::CommandList&& entries) override; - void SwapBuffers( - std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override; + void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; void FlushRegion(CacheAddr addr, u64 size) override; void InvalidateRegion(CacheAddr addr, u64 size) override; void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; + +protected: + void TriggerCpuInterrupt([[maybe_unused]] u32 syncpoint_id, + [[maybe_unused]] u32 value) const override {} }; } // namespace VideoCommon diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 3f0939ec9..5f039e4fd 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -21,7 +21,8 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p MicroProfileOnThreadCreate("GpuThread"); // Wait for first GPU command before acquiring the window context - state.WaitForCommands(); + while (state.queue.Empty()) + ; // If emulation was stopped during disk shader loading, abort before trying to acquire context if (!state.is_running) { @@ -32,14 +33,13 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p CommandDataContainer next; while (state.is_running) { - state.WaitForCommands(); while (!state.queue.Empty()) { state.queue.Pop(next); if (const auto submit_list = std::get_if<SubmitListCommand>(&next.data)) { dma_pusher.Push(std::move(submit_list->entries)); dma_pusher.DispatchCalls(); } else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) { - renderer.SwapBuffers(std::move(data->framebuffer)); + renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr); } else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) { renderer.Rasterizer().FlushRegion(data->addr, data->size); } else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) { @@ -49,8 +49,7 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p } else { UNREACHABLE(); } - state.signaled_fence = next.fence; - state.TrySynchronize(); + state.signaled_fence.store(next.fence); } } } @@ -79,9 +78,9 @@ void ThreadManager::SubmitList(Tegra::CommandList&& entries) { system.CoreTiming().ScheduleEvent(synchronization_ticks, synchronization_event, fence); } -void ThreadManager::SwapBuffers( - std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { - PushCommand(SwapBuffersCommand(std::move(framebuffer))); +void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { + PushCommand(SwapBuffersCommand(framebuffer ? *framebuffer + : std::optional<const Tegra::FramebufferConfig>{})); } void ThreadManager::FlushRegion(CacheAddr addr, u64 size) { @@ -89,12 +88,7 @@ void ThreadManager::FlushRegion(CacheAddr addr, u64 size) { } void ThreadManager::InvalidateRegion(CacheAddr addr, u64 size) { - if (state.queue.Empty()) { - // It's quicker to invalidate a single region on the CPU if the queue is already empty - system.Renderer().Rasterizer().InvalidateRegion(addr, size); - } else { - PushCommand(InvalidateRegionCommand(addr, size)); - } + system.Renderer().Rasterizer().InvalidateRegion(addr, size); } void ThreadManager::FlushAndInvalidateRegion(CacheAddr addr, u64 size) { @@ -105,22 +99,13 @@ void ThreadManager::FlushAndInvalidateRegion(CacheAddr addr, u64 size) { u64 ThreadManager::PushCommand(CommandData&& command_data) { const u64 fence{++state.last_fence}; state.queue.Push(CommandDataContainer(std::move(command_data), fence)); - state.SignalCommands(); return fence; } MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192)); void SynchState::WaitForSynchronization(u64 fence) { - if (signaled_fence >= fence) { - return; - } - - // Wait for the GPU to be idle (all commands to be executed) - { - MICROPROFILE_SCOPE(GPU_wait); - std::unique_lock lock{synchronization_mutex}; - synchronization_condition.wait(lock, [this, fence] { return signaled_fence >= fence; }); - } + while (signaled_fence.load() < fence) + ; } } // namespace VideoCommon::GPUThread diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h index 05a168a72..3ae0ec9f3 100644 --- a/src/video_core/gpu_thread.h +++ b/src/video_core/gpu_thread.h @@ -88,41 +88,9 @@ struct CommandDataContainer { /// Struct used to synchronize the GPU thread struct SynchState final { std::atomic_bool is_running{true}; - std::atomic_int queued_frame_count{}; - std::mutex synchronization_mutex; - std::mutex commands_mutex; - std::condition_variable commands_condition; - std::condition_variable synchronization_condition; - - /// Returns true if the gap in GPU commands is small enough that we can consider the CPU and GPU - /// synchronized. This is entirely empirical. - bool IsSynchronized() const { - constexpr std::size_t max_queue_gap{5}; - return queue.Size() <= max_queue_gap; - } - - void TrySynchronize() { - if (IsSynchronized()) { - std::lock_guard lock{synchronization_mutex}; - synchronization_condition.notify_one(); - } - } void WaitForSynchronization(u64 fence); - void SignalCommands() { - if (queue.Empty()) { - return; - } - - commands_condition.notify_one(); - } - - void WaitForCommands() { - std::unique_lock lock{commands_mutex}; - commands_condition.wait(lock, [this] { return !queue.Empty(); }); - } - using CommandQueue = Common::SPSCQueue<CommandDataContainer>; CommandQueue queue; u64 last_fence{}; @@ -142,8 +110,7 @@ public: void SubmitList(Tegra::CommandList&& entries); /// Swap buffers (render frame) - void SwapBuffers( - std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer); + void SwapBuffers(const Tegra::FramebufferConfig* framebuffer); /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory void FlushRegion(CacheAddr addr, u64 size); diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp index c766ed692..dbaeac6db 100644 --- a/src/video_core/macro_interpreter.cpp +++ b/src/video_core/macro_interpreter.cpp @@ -4,17 +4,28 @@ #include "common/assert.h" #include "common/logging/log.h" +#include "common/microprofile.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/macro_interpreter.h" +MICROPROFILE_DEFINE(MacroInterp, "GPU", "Execute macro interpreter", MP_RGB(128, 128, 192)); + namespace Tegra { MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {} -void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) { +void MacroInterpreter::Execute(u32 offset, std::size_t num_parameters, const u32* parameters) { + MICROPROFILE_SCOPE(MacroInterp); Reset(); + registers[1] = parameters[0]; - this->parameters = std::move(parameters); + + if (num_parameters > parameters_capacity) { + parameters_capacity = num_parameters; + this->parameters = std::make_unique<u32[]>(num_parameters); + } + std::memcpy(this->parameters.get(), parameters, num_parameters * sizeof(u32)); + this->num_parameters = num_parameters; // Execute the code until we hit an exit condition. bool keep_executing = true; @@ -23,7 +34,7 @@ void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) { } // Assert the the macro used all the input parameters - ASSERT(next_parameter_index == this->parameters.size()); + ASSERT(next_parameter_index == num_parameters); } void MacroInterpreter::Reset() { @@ -31,7 +42,7 @@ void MacroInterpreter::Reset() { pc = 0; delayed_pc = {}; method_address.raw = 0; - parameters.clear(); + num_parameters = 0; // The next parameter index starts at 1, because $r1 already has the value of the first // parameter. next_parameter_index = 1; @@ -120,9 +131,7 @@ bool MacroInterpreter::Step(u32 offset, bool is_delay_slot) { // An instruction with the Exit flag will not actually // cause an exit if it's executed inside a delay slot. - // TODO(Blinkhawk): Reversed to always exit. The behavior explained above requires further - // testing on the MME code. - if (opcode.is_exit) { + if (opcode.is_exit && !is_delay_slot) { // Exit has a delay slot, execute the next instruction Step(offset, true); return false; @@ -225,7 +234,8 @@ void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 res } u32 MacroInterpreter::FetchParameter() { - return parameters.at(next_parameter_index++); + ASSERT(next_parameter_index < num_parameters); + return parameters[next_parameter_index++]; } u32 MacroInterpreter::GetRegister(u32 register_id) const { @@ -247,7 +257,7 @@ void MacroInterpreter::SetMethodAddress(u32 address) { } void MacroInterpreter::Send(u32 value) { - maxwell3d.CallMethod({method_address.address, value}); + maxwell3d.CallMethodFromMME({method_address.address, value}); // Increment the method address by the method increment. method_address.address.Assign(method_address.address.Value() + method_address.increment.Value()); diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h index cde360288..76b6a895b 100644 --- a/src/video_core/macro_interpreter.h +++ b/src/video_core/macro_interpreter.h @@ -25,7 +25,7 @@ public: * @param offset Offset to start execution at. * @param parameters The parameters of the macro. */ - void Execute(u32 offset, std::vector<u32> parameters); + void Execute(u32 offset, std::size_t num_parameters, const u32* parameters); private: enum class Operation : u32 { @@ -162,10 +162,12 @@ private: MethodAddress method_address = {}; /// Input parameters of the current macro. - std::vector<u32> parameters; + std::unique_ptr<u32[]> parameters; + std::size_t num_parameters = 0; + std::size_t parameters_capacity = 0; /// Index of the next parameter that will be fetched by the 'parm' instruction. u32 next_parameter_index = 0; - bool carry_flag{}; + bool carry_flag = false; }; } // namespace Tegra diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 5d8d126c1..bffae940c 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -5,13 +5,17 @@ #include "common/alignment.h" #include "common/assert.h" #include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/vm_manager.h" #include "core/memory.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" namespace Tegra { -MemoryManager::MemoryManager(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} { +MemoryManager::MemoryManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer) + : rasterizer{rasterizer}, system{system} { std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr); std::fill(page_table.attributes.begin(), page_table.attributes.end(), Common::PageType::Unmapped); @@ -49,6 +53,11 @@ GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) { const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)}; MapBackingMemory(gpu_addr, Memory::GetPointer(cpu_addr), aligned_size, cpu_addr); + ASSERT(system.CurrentProcess() + ->VMManager() + .SetMemoryAttribute(cpu_addr, size, Kernel::MemoryAttribute::DeviceMapped, + Kernel::MemoryAttribute::DeviceMapped) + .IsSuccess()); return gpu_addr; } @@ -59,7 +68,11 @@ GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) const u64 aligned_size{Common::AlignUp(size, page_size)}; MapBackingMemory(gpu_addr, Memory::GetPointer(cpu_addr), aligned_size, cpu_addr); - + ASSERT(system.CurrentProcess() + ->VMManager() + .SetMemoryAttribute(cpu_addr, size, Kernel::MemoryAttribute::DeviceMapped, + Kernel::MemoryAttribute::DeviceMapped) + .IsSuccess()); return gpu_addr; } @@ -68,9 +81,16 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) { const u64 aligned_size{Common::AlignUp(size, page_size)}; const CacheAddr cache_addr{ToCacheAddr(GetPointer(gpu_addr))}; + const auto cpu_addr = GpuToCpuAddress(gpu_addr); + ASSERT(cpu_addr); rasterizer.FlushAndInvalidateRegion(cache_addr, aligned_size); UnmapRange(gpu_addr, aligned_size); + ASSERT(system.CurrentProcess() + ->VMManager() + .SetMemoryAttribute(cpu_addr.value(), size, Kernel::MemoryAttribute::DeviceMapped, + Kernel::MemoryAttribute::None) + .IsSuccess()); return gpu_addr; } @@ -202,11 +222,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 { diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index 113f9d8f3..aea010087 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -14,6 +14,10 @@ namespace VideoCore { class RasterizerInterface; } +namespace Core { +class System; +} + namespace Tegra { /** @@ -47,7 +51,7 @@ struct VirtualMemoryArea { class MemoryManager final { public: - explicit MemoryManager(VideoCore::RasterizerInterface& rasterizer); + explicit MemoryManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer); ~MemoryManager(); GPUVAddr AllocateSpace(u64 size, u64 align); @@ -163,8 +167,8 @@ private: static constexpr u64 page_size{1 << page_bits}; static constexpr u64 page_mask{page_size - 1}; - /// Address space in bits, this is fairly arbitrary but sufficiently large. - static constexpr u32 address_space_width{39}; + /// Address space in bits, according to Tegra X1 TRM + static constexpr u32 address_space_width{40}; /// Start address for mapping, this is fairly arbitrary but must be non-zero. static constexpr GPUVAddr address_space_base{0x100000}; /// End of address space, based on address space in bits. @@ -173,6 +177,8 @@ private: Common::PageTable page_table{page_bits}; VMAMap vma_map; VideoCore::RasterizerInterface& rasterizer; + + Core::System& system; }; } // namespace Tegra diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp index 3e91cbc83..ab71870ab 100644 --- a/src/video_core/morton.cpp +++ b/src/video_core/morton.cpp @@ -25,8 +25,8 @@ static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual // pixel values. - const u32 tile_size_x{GetDefaultBlockWidth(format)}; - const u32 tile_size_y{GetDefaultBlockHeight(format)}; + constexpr u32 tile_size_x{GetDefaultBlockWidth(format)}; + constexpr u32 tile_size_y{GetDefaultBlockHeight(format)}; if constexpr (morton_to_linear) { Tegra::Texture::UnswizzleTexture(buffer, addr, tile_size_x, tile_size_y, bytes_per_pixel, @@ -83,6 +83,7 @@ static constexpr ConversionArray morton_to_linear_fns = { MortonCopy<true, PixelFormat::RG8U>, MortonCopy<true, PixelFormat::RG8S>, MortonCopy<true, PixelFormat::RG32UI>, + MortonCopy<true, PixelFormat::RGBX16F>, MortonCopy<true, PixelFormat::R32UI>, MortonCopy<true, PixelFormat::ASTC_2D_8X8>, MortonCopy<true, PixelFormat::ASTC_2D_8X5>, @@ -151,6 +152,7 @@ static constexpr ConversionArray linear_to_morton_fns = { MortonCopy<false, PixelFormat::RG8U>, MortonCopy<false, PixelFormat::RG8S>, MortonCopy<false, PixelFormat::RG32UI>, + MortonCopy<false, PixelFormat::RGBX16F>, MortonCopy<false, PixelFormat::R32UI>, nullptr, nullptr, @@ -186,99 +188,6 @@ static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFor return morton_to_linear_fns[static_cast<std::size_t>(format)]; } -static u32 MortonInterleave128(u32 x, u32 y) { - // 128x128 Z-Order coordinate from 2D coordinates - static constexpr u32 xlut[] = { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, - 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, - 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, - 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, - 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, - 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, - 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, - 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, - 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, - 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, - 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, - 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, - 0x0008, 0x0009, 0x000a, 0x000b, 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, - 0x004b, 0x0800, 0x0801, 0x0802, 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, - 0x0842, 0x0843, 0x0848, 0x0849, 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, - 0x1009, 0x100a, 0x100b, 0x1040, 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, - 0x1800, 0x1801, 0x1802, 0x1803, 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, - 0x1843, 0x1848, 0x1849, 0x184a, 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, - 0x200a, 0x200b, 0x2040, 0x2041, 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, - 0x2801, 0x2802, 0x2803, 0x2808, 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, - 0x2848, 0x2849, 0x284a, 0x284b, 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, - 0x300b, 0x3040, 0x3041, 0x3042, 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, - 0x3802, 0x3803, 0x3808, 0x3809, 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, - 0x3849, 0x384a, 0x384b, 0x0000, 0x0001, 0x0002, 0x0003, 0x0008, 0x0009, 0x000a, 0x000b, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0048, 0x0049, 0x004a, 0x004b, 0x0800, 0x0801, 0x0802, - 0x0803, 0x0808, 0x0809, 0x080a, 0x080b, 0x0840, 0x0841, 0x0842, 0x0843, 0x0848, 0x0849, - 0x084a, 0x084b, 0x1000, 0x1001, 0x1002, 0x1003, 0x1008, 0x1009, 0x100a, 0x100b, 0x1040, - 0x1041, 0x1042, 0x1043, 0x1048, 0x1049, 0x104a, 0x104b, 0x1800, 0x1801, 0x1802, 0x1803, - 0x1808, 0x1809, 0x180a, 0x180b, 0x1840, 0x1841, 0x1842, 0x1843, 0x1848, 0x1849, 0x184a, - 0x184b, 0x2000, 0x2001, 0x2002, 0x2003, 0x2008, 0x2009, 0x200a, 0x200b, 0x2040, 0x2041, - 0x2042, 0x2043, 0x2048, 0x2049, 0x204a, 0x204b, 0x2800, 0x2801, 0x2802, 0x2803, 0x2808, - 0x2809, 0x280a, 0x280b, 0x2840, 0x2841, 0x2842, 0x2843, 0x2848, 0x2849, 0x284a, 0x284b, - 0x3000, 0x3001, 0x3002, 0x3003, 0x3008, 0x3009, 0x300a, 0x300b, 0x3040, 0x3041, 0x3042, - 0x3043, 0x3048, 0x3049, 0x304a, 0x304b, 0x3800, 0x3801, 0x3802, 0x3803, 0x3808, 0x3809, - 0x380a, 0x380b, 0x3840, 0x3841, 0x3842, 0x3843, 0x3848, 0x3849, 0x384a, 0x384b, - }; - static constexpr u32 ylut[] = { - 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, - 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, - 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, - 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, - 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, - 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, - 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, - 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, - 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, - 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, - 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, - 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, - 0x0020, 0x0024, 0x0030, 0x0034, 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, - 0x00b4, 0x0100, 0x0104, 0x0110, 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, - 0x0190, 0x0194, 0x01a0, 0x01a4, 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, - 0x0224, 0x0230, 0x0234, 0x0280, 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, - 0x0300, 0x0304, 0x0310, 0x0314, 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, - 0x0394, 0x03a0, 0x03a4, 0x03b0, 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, - 0x0430, 0x0434, 0x0480, 0x0484, 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, - 0x0504, 0x0510, 0x0514, 0x0520, 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, - 0x05a0, 0x05a4, 0x05b0, 0x05b4, 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, - 0x0634, 0x0680, 0x0684, 0x0690, 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, - 0x0710, 0x0714, 0x0720, 0x0724, 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, - 0x07a4, 0x07b0, 0x07b4, 0x0000, 0x0004, 0x0010, 0x0014, 0x0020, 0x0024, 0x0030, 0x0034, - 0x0080, 0x0084, 0x0090, 0x0094, 0x00a0, 0x00a4, 0x00b0, 0x00b4, 0x0100, 0x0104, 0x0110, - 0x0114, 0x0120, 0x0124, 0x0130, 0x0134, 0x0180, 0x0184, 0x0190, 0x0194, 0x01a0, 0x01a4, - 0x01b0, 0x01b4, 0x0200, 0x0204, 0x0210, 0x0214, 0x0220, 0x0224, 0x0230, 0x0234, 0x0280, - 0x0284, 0x0290, 0x0294, 0x02a0, 0x02a4, 0x02b0, 0x02b4, 0x0300, 0x0304, 0x0310, 0x0314, - 0x0320, 0x0324, 0x0330, 0x0334, 0x0380, 0x0384, 0x0390, 0x0394, 0x03a0, 0x03a4, 0x03b0, - 0x03b4, 0x0400, 0x0404, 0x0410, 0x0414, 0x0420, 0x0424, 0x0430, 0x0434, 0x0480, 0x0484, - 0x0490, 0x0494, 0x04a0, 0x04a4, 0x04b0, 0x04b4, 0x0500, 0x0504, 0x0510, 0x0514, 0x0520, - 0x0524, 0x0530, 0x0534, 0x0580, 0x0584, 0x0590, 0x0594, 0x05a0, 0x05a4, 0x05b0, 0x05b4, - 0x0600, 0x0604, 0x0610, 0x0614, 0x0620, 0x0624, 0x0630, 0x0634, 0x0680, 0x0684, 0x0690, - 0x0694, 0x06a0, 0x06a4, 0x06b0, 0x06b4, 0x0700, 0x0704, 0x0710, 0x0714, 0x0720, 0x0724, - 0x0730, 0x0734, 0x0780, 0x0784, 0x0790, 0x0794, 0x07a0, 0x07a4, 0x07b0, 0x07b4, - }; - return xlut[x % 128] + ylut[y % 128]; -} - -static u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) { - // Calculates the offset of the position of the pixel in Morton order - // Framebuffer images are split into 128x128 tiles. - - constexpr u32 block_height = 128; - const u32 coarse_x = x & ~127; - - const u32 i = MortonInterleave128(x, y); - - const u32 offset = coarse_x * block_height; - - return (i + offset) * bytes_per_pixel; -} - void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, u8* buffer, u8* addr) { @@ -286,23 +195,4 @@ void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stri tile_width_spacing, buffer, addr); } -void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel, - u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data) { - const bool morton_to_linear = mode == MortonSwizzleMode::MortonToLinear; - u8* data_ptrs[2]; - for (u32 y = 0; y < height; ++y) { - for (u32 x = 0; x < width; ++x) { - const u32 coarse_y = y & ~127; - const u32 morton_offset = - GetMortonOffset128(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; - const u32 linear_pixel_index = (x + y * width) * linear_bytes_per_pixel; - - data_ptrs[morton_to_linear ? 1 : 0] = morton_data + morton_offset; - data_ptrs[morton_to_linear ? 0 : 1] = &linear_data[linear_pixel_index]; - - std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); - } - } -} - } // namespace VideoCore diff --git a/src/video_core/morton.h b/src/video_core/morton.h index ee5b45555..b714a7e3f 100644 --- a/src/video_core/morton.h +++ b/src/video_core/morton.h @@ -15,7 +15,4 @@ void MortonSwizzle(MortonSwizzleMode mode, VideoCore::Surface::PixelFormat forma u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing, u8* buffer, u8* addr); -void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel, - u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data); - } // namespace VideoCore diff --git a/src/video_core/rasterizer_cache.h b/src/video_core/rasterizer_cache.h index 0c4ea1494..6de1597a2 100644 --- a/src/video_core/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache.h @@ -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; }; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index d7b86df38..5b0eca9e2 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -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 { @@ -25,11 +29,17 @@ public: virtual ~RasterizerInterface() {} /// Draw the current batch of vertex arrays - virtual void DrawArrays() = 0; + virtual bool DrawBatch(bool is_indexed) = 0; + + /// Draw the current batch of multiple instances of vertex arrays + virtual bool DrawMultiBatch(bool is_indexed) = 0; /// Clear the current framebuffer virtual void Clear() = 0; + /// Dispatches a compute shader invocation + virtual void DispatchCompute(GPUVAddr code_addr) = 0; + /// Notify rasterizer that all caches should be flushed to Switch memory virtual void FlushAll() = 0; @@ -43,11 +53,16 @@ public: /// and invalidated virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0; + /// Notify the rasterizer to send all written commands to the host GPU. + virtual void FlushCommands() = 0; + + /// Notify rasterizer that a frame is about to finish + virtual void TickFrame() = 0; + /// 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; } @@ -57,10 +72,6 @@ public: return false; } - virtual bool AccelerateDrawBatch(bool is_indexed) { - return false; - } - /// Increase/decrease the number of object in pages touching the specified region virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {} diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 1d54c3723..af1bebc4f 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -36,8 +36,7 @@ public: virtual ~RendererBase(); /// Swap buffers (render frame) - virtual void SwapBuffers( - std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0; + virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; /// Initialize the renderer virtual bool Init() = 0; diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 48b86f3bd..f8a807c84 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -2,101 +2,71 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cstring> #include <memory> -#include "common/alignment.h" -#include "core/core.h" -#include "video_core/memory_manager.h" +#include <glad/glad.h> + +#include "common/assert.h" +#include "common/microprofile.h" +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_opengl/gl_buffer_cache.h" #include "video_core/renderer_opengl/gl_rasterizer.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" namespace OpenGL { -CachedBufferEntry::CachedBufferEntry(VAddr cpu_addr, std::size_t size, GLintptr offset, - std::size_t alignment, u8* host_ptr) - : RasterizerCacheObject{host_ptr}, cpu_addr{cpu_addr}, size{size}, offset{offset}, - alignment{alignment} {} - -OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, std::size_t size) - : RasterizerCache{rasterizer}, stream_buffer(size, true) {} - -GLintptr OGLBufferCache::UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment, - bool cache) { - auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager(); - - // Cache management is a big overhead, so only cache entries with a given size. - // TODO: Figure out which size is the best for given games. - cache &= size >= 2048; - - const auto& host_ptr{memory_manager.GetPointer(gpu_addr)}; - if (cache) { - auto entry = TryGet(host_ptr); - if (entry) { - if (entry->GetSize() >= size && entry->GetAlignment() == alignment) { - return entry->GetOffset(); - } - Unregister(entry); - } - } - - AlignBuffer(alignment); - const GLintptr uploaded_offset = buffer_offset; - - if (!host_ptr) { - return uploaded_offset; - } - - std::memcpy(buffer_ptr, host_ptr, size); - buffer_ptr += size; - buffer_offset += size; - - if (cache) { - auto entry = std::make_shared<CachedBufferEntry>( - *memory_manager.GpuToCpuAddress(gpu_addr), size, uploaded_offset, alignment, host_ptr); - Register(entry); - } - - return uploaded_offset; +MICROPROFILE_DEFINE(OpenGL_Buffer_Download, "OpenGL", "Buffer Download", MP_RGB(192, 192, 128)); + +CachedBufferBlock::CachedBufferBlock(CacheAddr cache_addr, const std::size_t size) + : VideoCommon::BufferBlock{cache_addr, size} { + gl_buffer.Create(); + glNamedBufferData(gl_buffer.handle, static_cast<GLsizeiptr>(size), nullptr, GL_DYNAMIC_DRAW); } -GLintptr OGLBufferCache::UploadHostMemory(const void* raw_pointer, std::size_t size, - std::size_t alignment) { - AlignBuffer(alignment); - std::memcpy(buffer_ptr, raw_pointer, size); - const GLintptr uploaded_offset = buffer_offset; +CachedBufferBlock::~CachedBufferBlock() = default; + +OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system, + std::size_t stream_size) + : VideoCommon::BufferCache<Buffer, GLuint, OGLStreamBuffer>{ + rasterizer, system, std::make_unique<OGLStreamBuffer>(stream_size, true)} {} + +OGLBufferCache::~OGLBufferCache() = default; - buffer_ptr += size; - buffer_offset += size; - return uploaded_offset; +Buffer OGLBufferCache::CreateBlock(CacheAddr cache_addr, std::size_t size) { + return std::make_shared<CachedBufferBlock>(cache_addr, size); } -bool OGLBufferCache::Map(std::size_t max_size) { - bool invalidate; - std::tie(buffer_ptr, buffer_offset_base, invalidate) = - stream_buffer.Map(static_cast<GLsizeiptr>(max_size), 4); - buffer_offset = buffer_offset_base; +void OGLBufferCache::WriteBarrier() { + glMemoryBarrier(GL_ALL_BARRIER_BITS); +} + +const GLuint* OGLBufferCache::ToHandle(const Buffer& buffer) { + return buffer->GetHandle(); +} - if (invalidate) { - InvalidateAll(); - } - return invalidate; +const GLuint* OGLBufferCache::GetEmptyBuffer(std::size_t) { + static const GLuint null_buffer = 0; + return &null_buffer; } -void OGLBufferCache::Unmap() { - stream_buffer.Unmap(buffer_offset - buffer_offset_base); +void OGLBufferCache::UploadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size, + const u8* data) { + glNamedBufferSubData(*buffer->GetHandle(), static_cast<GLintptr>(offset), + static_cast<GLsizeiptr>(size), data); } -GLuint OGLBufferCache::GetHandle() const { - return stream_buffer.GetHandle(); +void OGLBufferCache::DownloadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size, + u8* data) { + MICROPROFILE_SCOPE(OpenGL_Buffer_Download); + glGetNamedBufferSubData(*buffer->GetHandle(), static_cast<GLintptr>(offset), + static_cast<GLsizeiptr>(size), data); } -void OGLBufferCache::AlignBuffer(std::size_t alignment) { - // Align the offset, not the mapped pointer - const GLintptr offset_aligned = - static_cast<GLintptr>(Common::AlignUp(static_cast<std::size_t>(buffer_offset), alignment)); - buffer_ptr += offset_aligned - buffer_offset; - buffer_offset = offset_aligned; +void OGLBufferCache::CopyBlock(const Buffer& src, const Buffer& dst, std::size_t src_offset, + std::size_t dst_offset, std::size_t size) { + glCopyNamedBufferSubData(*src->GetHandle(), *dst->GetHandle(), + static_cast<GLintptr>(src_offset), static_cast<GLintptr>(dst_offset), + static_cast<GLsizeiptr>(size)); } } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index f2347581b..022e7bfa9 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -4,80 +4,63 @@ #pragma once -#include <cstddef> #include <memory> -#include <tuple> #include "common/common_types.h" +#include "video_core/buffer_cache/buffer_cache.h" #include "video_core/rasterizer_cache.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_stream_buffer.h" +namespace Core { +class System; +} + namespace OpenGL { +class OGLStreamBuffer; class RasterizerOpenGL; -class CachedBufferEntry final : public RasterizerCacheObject { -public: - explicit CachedBufferEntry(VAddr cpu_addr, std::size_t size, GLintptr offset, - std::size_t alignment, u8* host_ptr); - - VAddr GetCpuAddr() const override { - return cpu_addr; - } +class CachedBufferBlock; - std::size_t GetSizeInBytes() const override { - return size; - } - - std::size_t GetSize() const { - return size; - } +using Buffer = std::shared_ptr<CachedBufferBlock>; - GLintptr GetOffset() const { - return offset; - } +class CachedBufferBlock : public VideoCommon::BufferBlock { +public: + explicit CachedBufferBlock(CacheAddr cache_addr, const std::size_t size); + ~CachedBufferBlock(); - std::size_t GetAlignment() const { - return alignment; + const GLuint* GetHandle() const { + return &gl_buffer.handle; } private: - VAddr cpu_addr{}; - std::size_t size{}; - GLintptr offset{}; - std::size_t alignment{}; + OGLBuffer gl_buffer{}; }; -class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> { +class OGLBufferCache final : public VideoCommon::BufferCache<Buffer, GLuint, OGLStreamBuffer> { public: - explicit OGLBufferCache(RasterizerOpenGL& rasterizer, std::size_t size); - - /// Uploads data from a guest GPU address. Returns host's buffer offset where it's been - /// allocated. - GLintptr UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4, - bool cache = true); + explicit OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system, + std::size_t stream_size); + ~OGLBufferCache(); - /// Uploads from a host memory. Returns host's buffer offset where it's been allocated. - GLintptr UploadHostMemory(const void* raw_pointer, std::size_t size, std::size_t alignment = 4); + const GLuint* GetEmptyBuffer(std::size_t) override; - bool Map(std::size_t max_size); - void Unmap(); +protected: + Buffer CreateBlock(CacheAddr cache_addr, std::size_t size) override; - GLuint GetHandle() const; + void WriteBarrier() override; -protected: - void AlignBuffer(std::size_t alignment); + const GLuint* ToHandle(const Buffer& buffer) override; - // We do not have to flush this cache as things in it are never modified by us. - void FlushObjectInner(const std::shared_ptr<CachedBufferEntry>& object) override {} + void UploadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size, + const u8* data) override; -private: - OGLStreamBuffer stream_buffer; + void DownloadBlockData(const Buffer& buffer, std::size_t offset, std::size_t size, + u8* data) override; - u8* buffer_ptr = nullptr; - GLintptr buffer_offset = 0; - GLintptr buffer_offset_base = 0; + void CopyBlock(const Buffer& src, const Buffer& dst, std::size_t src_offset, + std::size_t dst_offset, std::size_t size) override; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 65a88b06c..64de7e425 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -2,8 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <array> #include <cstddef> +#include <vector> #include <glad/glad.h> #include "common/logging/log.h" @@ -14,51 +16,84 @@ namespace OpenGL { namespace { + template <typename T> T GetInteger(GLenum pname) { GLint temporary; glGetIntegerv(pname, &temporary); return static_cast<T>(temporary); } + +bool TestProgram(const GLchar* glsl) { + const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl)}; + GLint link_status; + glGetProgramiv(shader, GL_LINK_STATUS, &link_status); + glDeleteProgram(shader); + return link_status == GL_TRUE; +} + +std::vector<std::string_view> GetExtensions() { + GLint num_extensions; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); + std::vector<std::string_view> extensions; + extensions.reserve(num_extensions); + for (GLint index = 0; index < num_extensions; ++index) { + extensions.push_back( + reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, static_cast<GLuint>(index)))); + } + return extensions; +} + +bool HasExtension(const std::vector<std::string_view>& images, std::string_view extension) { + return std::find(images.begin(), images.end(), extension) != images.end(); +} + } // Anonymous namespace Device::Device() { + const std::vector extensions = GetExtensions(); + uniform_buffer_alignment = GetInteger<std::size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT); + shader_storage_alignment = GetInteger<std::size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT); max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS); max_varyings = GetInteger<u32>(GL_MAX_VARYING_VECTORS); + has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group && + GLAD_GL_NV_shader_thread_shuffle; + has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array; + has_image_load_formatted = HasExtension(extensions, "GL_EXT_shader_image_load_formatted"); has_variable_aoffi = TestVariableAoffi(); has_component_indexing_bug = TestComponentIndexingBug(); + has_precise_bug = TestPreciseBug(); + + LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi); + LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug); + LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug); } Device::Device(std::nullptr_t) { uniform_buffer_alignment = 0; max_vertex_attributes = 16; max_varyings = 15; + has_warp_intrinsics = true; + has_vertex_viewport_layer = true; + has_image_load_formatted = true; has_variable_aoffi = true; has_component_indexing_bug = false; + has_precise_bug = false; } bool Device::TestVariableAoffi() { - const GLchar* AOFFI_TEST = R"(#version 430 core + return TestProgram(R"(#version 430 core // This is a unit test, please ignore me on apitrace bug reports. uniform sampler2D tex; uniform ivec2 variable_offset; +out vec4 output_attribute; void main() { - gl_Position = textureOffset(tex, vec2(0), variable_offset); -} -)"; - const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &AOFFI_TEST)}; - GLint link_status{}; - glGetProgramiv(shader, GL_LINK_STATUS, &link_status); - glDeleteProgram(shader); - - const bool supported{link_status == GL_TRUE}; - LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", supported); - return supported; + output_attribute = textureOffset(tex, vec2(0), variable_offset); +})"); } bool Device::TestComponentIndexingBug() { - constexpr char log_message[] = "Renderer_ComponentIndexingBug: {}"; const GLchar* COMPONENT_TEST = R"(#version 430 core layout (std430, binding = 0) buffer OutputBuffer { uint output_value; @@ -98,12 +133,21 @@ void main() { GLuint result; glGetNamedBufferSubData(ssbo.handle, 0, sizeof(result), &result); if (result != values.at(index)) { - LOG_INFO(Render_OpenGL, log_message, true); return true; } } - LOG_INFO(Render_OpenGL, log_message, false); return false; } +bool Device::TestPreciseBug() { + return !TestProgram(R"(#version 430 core +in vec3 coords; +out float out_value; +uniform sampler2DShadow tex; +void main() { + precise float tmp_value = vec4(texture(tex, coords)).x; + out_value = tmp_value; +})"); +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h index 8c8c93760..bb273c3d6 100644 --- a/src/video_core/renderer_opengl/gl_device.h +++ b/src/video_core/renderer_opengl/gl_device.h @@ -18,6 +18,10 @@ public: return uniform_buffer_alignment; } + std::size_t GetShaderStorageBufferAlignment() const { + return shader_storage_alignment; + } + u32 GetMaxVertexAttributes() const { return max_vertex_attributes; } @@ -26,6 +30,18 @@ public: return max_varyings; } + bool HasWarpIntrinsics() const { + return has_warp_intrinsics; + } + + bool HasVertexViewportLayer() const { + return has_vertex_viewport_layer; + } + + bool HasImageLoadFormatted() const { + return has_image_load_formatted; + } + bool HasVariableAoffi() const { return has_variable_aoffi; } @@ -34,15 +50,25 @@ public: return has_component_indexing_bug; } + bool HasPreciseBug() const { + return has_precise_bug; + } + private: static bool TestVariableAoffi(); static bool TestComponentIndexingBug(); + static bool TestPreciseBug(); std::size_t uniform_buffer_alignment{}; + std::size_t shader_storage_alignment{}; u32 max_vertex_attributes{}; u32 max_varyings{}; + bool has_warp_intrinsics{}; + bool has_vertex_viewport_layer{}; + bool has_image_load_formatted{}; bool has_variable_aoffi{}; bool has_component_indexing_bug{}; + bool has_precise_bug{}; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp b/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp new file mode 100644 index 000000000..a5d69d78d --- /dev/null +++ b/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp @@ -0,0 +1,70 @@ +// 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(); + + 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); + } + } + if (key.colors_count) { + glDrawBuffers(key.colors_count, key.color_attachments.data()); + } else { + glDrawBuffer(GL_NONE); + } + + 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(stencil_enable, colors_count, color_attachments, colors, zeta) == + std::tie(rhs.stencil_enable, rhs.colors_count, rhs.color_attachments, rhs.colors, + rhs.zeta); +} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_framebuffer_cache.h b/src/video_core/renderer_opengl/gl_framebuffer_cache.h new file mode 100644 index 000000000..424344c48 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_framebuffer_cache.h @@ -0,0 +1,67 @@ +// 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 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 diff --git a/src/video_core/renderer_opengl/gl_global_cache.cpp b/src/video_core/renderer_opengl/gl_global_cache.cpp deleted file mode 100644 index ea4a593af..000000000 --- a/src/video_core/renderer_opengl/gl_global_cache.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <glad/glad.h> - -#include "common/logging/log.h" -#include "core/core.h" -#include "video_core/memory_manager.h" -#include "video_core/renderer_opengl/gl_global_cache.h" -#include "video_core/renderer_opengl/gl_rasterizer.h" -#include "video_core/renderer_opengl/gl_shader_decompiler.h" -#include "video_core/renderer_opengl/utils.h" - -namespace OpenGL { - -CachedGlobalRegion::CachedGlobalRegion(VAddr cpu_addr, u8* host_ptr, u32 size, u32 max_size) - : RasterizerCacheObject{host_ptr}, cpu_addr{cpu_addr}, host_ptr{host_ptr}, size{size}, - max_size{max_size} { - buffer.Create(); - LabelGLObject(GL_BUFFER, buffer.handle, cpu_addr, "GlobalMemory"); -} - -CachedGlobalRegion::~CachedGlobalRegion() = default; - -void CachedGlobalRegion::Reload(u32 size_) { - size = size_; - if (size > max_size) { - size = max_size; - LOG_CRITICAL(HW_GPU, "Global region size {} exceeded the supported size {}!", size_, - max_size); - } - glNamedBufferData(buffer.handle, size, host_ptr, GL_STREAM_DRAW); -} - -void CachedGlobalRegion::Flush() { - LOG_DEBUG(Render_OpenGL, "Flushing {} bytes to CPU memory address 0x{:16}", size, cpu_addr); - glGetNamedBufferSubData(buffer.handle, 0, static_cast<GLsizeiptr>(size), host_ptr); -} - -GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const { - const auto search{reserve.find(addr)}; - if (search == reserve.end()) { - return {}; - } - return search->second; -} - -GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(GPUVAddr addr, u8* host_ptr, - u32 size) { - GlobalRegion region{TryGetReservedGlobalRegion(ToCacheAddr(host_ptr), size)}; - if (!region) { - // No reserved surface available, create a new one and reserve it - auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; - const auto cpu_addr{memory_manager.GpuToCpuAddress(addr)}; - ASSERT(cpu_addr); - - region = std::make_shared<CachedGlobalRegion>(*cpu_addr, host_ptr, size, max_ssbo_size); - ReserveGlobalRegion(region); - } - region->Reload(size); - return region; -} - -void GlobalRegionCacheOpenGL::ReserveGlobalRegion(GlobalRegion region) { - reserve.insert_or_assign(region->GetCacheAddr(), std::move(region)); -} - -GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer) - : RasterizerCache{rasterizer} { - GLint max_ssbo_size_; - glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &max_ssbo_size_); - max_ssbo_size = static_cast<u32>(max_ssbo_size_); -} - -GlobalRegion GlobalRegionCacheOpenGL::GetGlobalRegion( - const GLShader::GlobalMemoryEntry& global_region, - Tegra::Engines::Maxwell3D::Regs::ShaderStage stage) { - - auto& gpu{Core::System::GetInstance().GPU()}; - auto& memory_manager{gpu.MemoryManager()}; - const auto cbufs{gpu.Maxwell3D().state.shader_stages[static_cast<std::size_t>(stage)]}; - const auto addr{cbufs.const_buffers[global_region.GetCbufIndex()].address + - global_region.GetCbufOffset()}; - const auto actual_addr{memory_manager.Read<u64>(addr)}; - const auto size{memory_manager.Read<u32>(addr + 8)}; - - // Look up global region in the cache based on address - const auto& host_ptr{memory_manager.GetPointer(actual_addr)}; - GlobalRegion region{TryGet(host_ptr)}; - - if (!region) { - // No global region found - create a new one - region = GetUncachedGlobalRegion(actual_addr, host_ptr, size); - Register(region); - } - - return region; -} - -} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_global_cache.h b/src/video_core/renderer_opengl/gl_global_cache.h deleted file mode 100644 index 2d467a240..000000000 --- a/src/video_core/renderer_opengl/gl_global_cache.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <unordered_map> - -#include <glad/glad.h> - -#include "common/assert.h" -#include "common/common_types.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/rasterizer_cache.h" -#include "video_core/renderer_opengl/gl_resource_manager.h" - -namespace OpenGL { - -namespace GLShader { -class GlobalMemoryEntry; -} - -class RasterizerOpenGL; -class CachedGlobalRegion; -using GlobalRegion = std::shared_ptr<CachedGlobalRegion>; - -class CachedGlobalRegion final : public RasterizerCacheObject { -public: - explicit CachedGlobalRegion(VAddr cpu_addr, u8* host_ptr, u32 size, u32 max_size); - ~CachedGlobalRegion(); - - VAddr GetCpuAddr() const override { - return cpu_addr; - } - - std::size_t GetSizeInBytes() const override { - return size; - } - - /// Gets the GL program handle for the buffer - GLuint GetBufferHandle() const { - return buffer.handle; - } - - /// Reloads the global region from guest memory - void Reload(u32 size_); - - void Flush(); - -private: - VAddr cpu_addr{}; - u8* host_ptr{}; - u32 size{}; - u32 max_size{}; - - OGLBuffer buffer; -}; - -class GlobalRegionCacheOpenGL final : public RasterizerCache<GlobalRegion> { -public: - explicit GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer); - - /// Gets the current specified shader stage program - GlobalRegion GetGlobalRegion(const GLShader::GlobalMemoryEntry& descriptor, - Tegra::Engines::Maxwell3D::Regs::ShaderStage stage); - -protected: - void FlushObjectInner(const GlobalRegion& object) override { - object->Flush(); - } - -private: - GlobalRegion TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const; - GlobalRegion GetUncachedGlobalRegion(GPUVAddr addr, u8* host_ptr, u32 size); - void ReserveGlobalRegion(GlobalRegion region); - - std::unordered_map<CacheAddr, GlobalRegion> reserve; - u32 max_ssbo_size{}; -}; - -} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index ca410287a..6a17bed72 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -4,6 +4,7 @@ #include <algorithm> #include <array> +#include <bitset> #include <memory> #include <string> #include <string_view> @@ -19,7 +20,9 @@ #include "core/core.h" #include "core/hle/kernel/process.h" #include "core/settings.h" +#include "video_core/engines/kepler_compute.h" #include "video_core/engines/maxwell_3d.h" +#include "video_core/memory_manager.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_cache.h" #include "video_core/renderer_opengl/gl_shader_gen.h" @@ -29,8 +32,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)); @@ -44,70 +49,31 @@ MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192)); MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); MICROPROFILE_DEFINE(OpenGL_PrimitiveAssembly, "OpenGL", "Prim Asmbl", MP_RGB(255, 100, 100)); -struct DrawParameters { - GLenum primitive_mode; - GLsizei count; - GLint current_instance; - bool use_indexed; - - GLint vertex_first; - - GLenum index_format; - GLint base_vertex; - GLintptr index_buffer_offset; - - void DispatchDraw() const { - if (use_indexed) { - const auto index_buffer_ptr = reinterpret_cast<const void*>(index_buffer_offset); - if (current_instance > 0) { - glDrawElementsInstancedBaseVertexBaseInstance(primitive_mode, count, index_format, - index_buffer_ptr, 1, base_vertex, - current_instance); - } else { - glDrawElementsBaseVertex(primitive_mode, count, index_format, index_buffer_ptr, - base_vertex); - } - } else { - if (current_instance > 0) { - glDrawArraysInstancedBaseInstance(primitive_mode, vertex_first, count, 1, - current_instance); - } else { - glDrawArrays(primitive_mode, vertex_first, count); - } - } +static std::size_t GetConstBufferSize(const Tegra::Engines::ConstBufferInfo& buffer, + const GLShader::ConstBufferEntry& entry) { + if (!entry.IsIndirect()) { + return entry.GetSize(); } -}; - -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); + if (buffer.size > Maxwell::MaxConstBufferSize) { + LOG_WARNING(Render_OpenGL, "Indirect constbuffer size {} exceeds maximum {}", buffer.size, + Maxwell::MaxConstBufferSize); + return Maxwell::MaxConstBufferSize; } - bool operator<(const FramebufferCacheKey& rhs) const { - return Tie() < rhs.Tie(); - } -}; + return buffer.size; +} RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window, ScreenInfo& info) - : res_cache{*this}, shader_cache{*this, system, emu_window, device}, - global_cache{*this}, system{system}, screen_info{info}, - buffer_cache(*this, STREAM_BUFFER_SIZE) { + : texture_cache{system, *this, device}, shader_cache{*this, system, emu_window, device}, + system{system}, screen_info{info}, buffer_cache{*this, system, STREAM_BUFFER_SIZE} { OpenGLState::ApplyDefaultState(); shader_program_manager = std::make_unique<GLShader::ProgramManager>(); state.draw.shader_program = 0; state.Apply(); + clear_framebuffer.Create(); LOG_DEBUG(Render_OpenGL, "Sync fixed function OpenGL state here"); CheckExtensions(); @@ -121,21 +87,16 @@ 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() { auto& gpu = system.GPU().Maxwell3D(); const auto& regs = gpu.regs; - if (!gpu.dirty_flags.vertex_attrib_format) { + if (!gpu.dirty.vertex_attrib_format) { return state.draw.vertex_array; } - gpu.dirty_flags.vertex_attrib_format = false; + gpu.dirty.vertex_attrib_format = false; MICROPROFILE_SCOPE(OpenGL_VAO); @@ -152,8 +113,6 @@ GLuint RasterizerOpenGL::SetupVertexFormat() { state.draw.vertex_array = vao; state.ApplyVertexArrayState(); - glVertexArrayElementBuffer(vao, buffer_cache.GetHandle()); - // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. // Enables the first 16 vertex attributes always, as we don't know which ones are actually // used until shader time. Note, Tegra technically supports 32, but we're capping this to 16 @@ -191,7 +150,7 @@ GLuint RasterizerOpenGL::SetupVertexFormat() { } // Rebinding the VAO invalidates the vertex buffer bindings. - gpu.dirty_flags.vertex_array.set(); + gpu.dirty.ResetVertexArrays(); state.draw.vertex_array = vao_entry.handle; return vao_entry.handle; @@ -199,17 +158,20 @@ GLuint RasterizerOpenGL::SetupVertexFormat() { void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) { auto& gpu = system.GPU().Maxwell3D(); - const auto& regs = gpu.regs; - - if (gpu.dirty_flags.vertex_array.none()) + if (!gpu.dirty.vertex_array_buffers) return; + gpu.dirty.vertex_array_buffers = false; + + const auto& regs = gpu.regs; MICROPROFILE_SCOPE(OpenGL_VB); // Upload all guest vertex arrays sequentially to our buffer for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { - if (!gpu.dirty_flags.vertex_array[index]) + if (!gpu.dirty.vertex_array[index]) continue; + gpu.dirty.vertex_array[index] = false; + gpu.dirty.vertex_instance[index] = false; const auto& vertex_array = regs.vertex_array[index]; if (!vertex_array.IsEnabled()) @@ -220,11 +182,11 @@ void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) { ASSERT(end > start); const u64 size = end - start + 1; - const GLintptr vertex_buffer_offset = buffer_cache.UploadMemory(start, size); + const auto [vertex_buffer, vertex_buffer_offset] = buffer_cache.UploadMemory(start, size); // Bind the vertex array to the buffer at the current offset. - glVertexArrayVertexBuffer(vao, index, buffer_cache.GetHandle(), vertex_buffer_offset, - vertex_array.stride); + vertex_array_pushbuffer.SetVertexBuffer(index, vertex_buffer, vertex_buffer_offset, + vertex_array.stride); if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) { // Enable vertex buffer instancing with the specified divisor. @@ -234,33 +196,44 @@ void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) { glVertexArrayBindingDivisor(vao, index, 0); } } - - gpu.dirty_flags.vertex_array.reset(); } -DrawParameters RasterizerOpenGL::SetupDraw() { - const auto& gpu = system.GPU().Maxwell3D(); +void RasterizerOpenGL::SetupVertexInstances(GLuint vao) { + auto& gpu = system.GPU().Maxwell3D(); + + if (!gpu.dirty.vertex_instances) + return; + gpu.dirty.vertex_instances = false; + const auto& regs = gpu.regs; - const bool is_indexed = accelerate_draw == AccelDraw::Indexed; + // Upload all guest vertex arrays sequentially to our buffer + for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { + if (!gpu.dirty.vertex_instance[index]) + continue; - DrawParameters params{}; - params.current_instance = gpu.state.current_instance; + gpu.dirty.vertex_instance[index] = false; - params.use_indexed = is_indexed; - params.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology); + if (regs.instanced_arrays.IsInstancingEnabled(index) && + regs.vertex_array[index].divisor != 0) { + // Enable vertex buffer instancing with the specified divisor. + glVertexArrayBindingDivisor(vao, index, regs.vertex_array[index].divisor); + } else { + // Disable the vertex buffer instancing. + glVertexArrayBindingDivisor(vao, index, 0); + } + } +} - if (is_indexed) { - MICROPROFILE_SCOPE(OpenGL_Index); - params.index_format = MaxwellToGL::IndexFormat(regs.index_array.format); - params.count = regs.index_array.count; - params.index_buffer_offset = - buffer_cache.UploadMemory(regs.index_array.IndexStart(), CalculateIndexBufferSize()); - params.base_vertex = static_cast<GLint>(regs.vb_element_base); - } else { - params.count = regs.vertex_buffer.count; - params.vertex_first = regs.vertex_buffer.first; +GLintptr RasterizerOpenGL::SetupIndexBuffer() { + if (accelerate_draw != AccelDraw::Indexed) { + return 0; } - return params; + MICROPROFILE_SCOPE(OpenGL_Index); + const auto& regs = system.GPU().Maxwell3D().regs; + const std::size_t size = CalculateIndexBufferSize(); + const auto [buffer, offset] = buffer_cache.UploadMemory(regs.index_array.IndexStart(), size); + vertex_array_pushbuffer.SetIndexBuffer(buffer); + return offset; } void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { @@ -270,10 +243,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { BaseBindings base_bindings; std::array<bool, Maxwell::NumClipDistances> clip_distances{}; - // Prepare packed bindings - bind_ubo_pushbuffer.Setup(base_bindings.cbuf); - bind_ssbo_pushbuffer.Setup(base_bindings.gmem); - for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { const auto& shader_config = gpu.regs.shader_config[index]; const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)}; @@ -294,16 +263,21 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { GLShader::MaxwellUniformData ubo{}; ubo.SetFromRegs(gpu, stage); - const GLintptr offset = + const auto [buffer, offset] = buffer_cache.UploadHostMemory(&ubo, sizeof(ubo), device.GetUniformBufferAlignment()); // Bind the emulation info buffer - bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), offset, - static_cast<GLsizeiptr>(sizeof(ubo))); + bind_ubo_pushbuffer.Push(buffer, offset, 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); + SetupDrawGlobalMemory(stage_enum, shader); + const auto texture_buffer_usage{SetupDrawTextures(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 +295,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { shader_config.enable.Value(), shader_config.offset); } - const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage); - SetupConstBuffers(stage_enum, shader, program_handle, base_bindings); - SetupGlobalRegions(stage_enum, shader, program_handle, base_bindings); - SetupTextures(stage_enum, shader, program_handle, 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 @@ -343,50 +312,9 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { base_bindings = next_bindings; } - bind_ubo_pushbuffer.Bind(); - bind_ssbo_pushbuffer.Bind(); - SyncClipEnabled(clip_distances); - 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); - } + gpu.dirty.shaders = false; } std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const { @@ -414,12 +342,6 @@ std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const { static_cast<std::size_t>(regs.index_array.FormatSizeInBytes()); } -bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) { - accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays; - DrawArrays(); - return true; -} - template <typename Map, typename Interval> static constexpr auto RangeFromInterval(Map& map, const Interval& interval) { return boost::make_iterator_range(map.equal_range(interval)); @@ -460,107 +382,117 @@ void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading, shader_cache.LoadDiskCache(stop_loading, callback); } -std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers( - OpenGLState& current_state, bool using_color_fb, bool using_depth_fb, bool preserve_contents, - std::optional<std::size_t> single_color_target) { +void RasterizerOpenGL::ConfigureFramebuffers() { MICROPROFILE_SCOPE(OpenGL_Framebuffer); auto& gpu = system.GPU().Maxwell3D(); - const auto& regs = gpu.regs; - - const FramebufferConfigState fb_config_state{using_color_fb, using_depth_fb, preserve_contents, - single_color_target}; - if (fb_config_state == current_framebuffer_config_state && - gpu.dirty_flags.color_buffer.none() && !gpu.dirty_flags.zeta_buffer) { - // Only skip if the previous ConfigureFramebuffers call was from the same kind (multiple or - // single color targets). This is done because the guest registers may not change but the - // host framebuffer may contain different attachments - return current_depth_stencil_usage; + if (!gpu.dirty.render_settings) { + return; } - current_framebuffer_config_state = fb_config_state; + gpu.dirty.render_settings = false; - Surface depth_surface; - if (using_depth_fb) { - depth_surface = res_cache.GetDepthBufferSurface(preserve_contents); - } + texture_cache.GuardRenderTargets(true); + + View depth_surface = texture_cache.GetDepthBufferSurface(true); + const auto& regs = gpu.regs; + state.framebuffer_srgb.enabled = regs.framebuffer_srgb != 0; UNIMPLEMENTED_IF(regs.rt_separate_frag_data == 0); // Bind the framebuffer surfaces - current_state.framebuffer_srgb.enabled = regs.framebuffer_srgb != 0; - FramebufferCacheKey fbkey; + for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { + View color_surface{texture_cache.GetColorBufferSurface(index, true)}; - 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); - - 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); - // 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; - } - - 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; - } 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); - - 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); - // 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/ - state.framebuffer_srgb.enabled |= - color_surface->GetSurfaceParams().srgb_conversion; - } - - fbkey.color_attachments[index] = - GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index); - fbkey.colors[index] = - color_surface != nullptr ? color_surface->Texture().handle : 0; - } - fbkey.is_single_buffer = false; - fbkey.colors_count = regs.rt_control.count; + 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. + texture_cache.MarkColorBufferInUse(index); } - } else { - // No color attachments are enabled - leave them as zero - fbkey.is_single_buffer = true; + + fbkey.color_attachments[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index); + fbkey.colors[index] = std::move(color_surface); } + fbkey.colors_count = regs.rt_control.count; 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.stencil_enable = regs.stencil_enable && - depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil; + fbkey.stencil_enable = depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil; + fbkey.zeta = std::move(depth_surface); } - SetupCachedFramebuffer(fbkey, current_state); - SyncViewport(current_state); + texture_cache.GuardRenderTargets(false); - return current_depth_stencil_usage = {static_cast<bool>(depth_surface), fbkey.stencil_enable}; + state.draw.draw_framebuffer = framebuffer_cache.GetFramebuffer(fbkey); + SyncViewport(state); +} + +void RasterizerOpenGL::ConfigureClearFramebuffer(OpenGLState& current_state, bool using_color_fb, + bool using_depth_fb, bool using_stencil_fb) { + auto& gpu = system.GPU().Maxwell3D(); + const auto& regs = gpu.regs; + + texture_cache.GuardRenderTargets(true); + View color_surface{}; + if (using_color_fb) { + color_surface = texture_cache.GetColorBufferSurface(regs.clear_buffers.RT, false); + } + View depth_surface{}; + if (using_depth_fb || using_stencil_fb) { + depth_surface = texture_cache.GetDepthBufferSurface(false); + } + texture_cache.GuardRenderTargets(false); + + current_state.draw.draw_framebuffer = clear_framebuffer.handle; + current_state.ApplyFramebufferState(); + + if (color_surface) { + color_surface->Attach(GL_COLOR_ATTACHMENT0, GL_DRAW_FRAMEBUFFER); + } else { + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + } + + if (depth_surface) { + const auto& params = depth_surface->GetSurfaceParams(); + switch (params.type) { + case VideoCore::Surface::SurfaceType::Depth: + depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + break; + case VideoCore::Surface::SurfaceType::DepthStencil: + depth_surface->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_DRAW_FRAMEBUFFER); + break; + default: + UNIMPLEMENTED(); + } + } else { + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + } } void RasterizerOpenGL::Clear() { - const auto& regs = system.GPU().Maxwell3D().regs; + const auto& maxwell3d = system.GPU().Maxwell3D(); + + if (!maxwell3d.ShouldExecute()) { + return; + } + + const auto& regs = maxwell3d.regs; bool use_color{}; bool use_depth{}; bool use_stencil{}; - OpenGLState clear_state; + OpenGLState prev_state{OpenGLState::GetCurState()}; + SCOPE_EXIT({ + prev_state.AllDirty(); + prev_state.Apply(); + }); + + OpenGLState clear_state{OpenGLState::GetCurState()}; + clear_state.SetDefaultViewports(); if (regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B || regs.clear_buffers.A) { use_color = true; @@ -580,11 +512,13 @@ void RasterizerOpenGL::Clear() { // true. clear_state.depth.test_enabled = true; clear_state.depth.test_func = GL_ALWAYS; + clear_state.depth.write_mask = GL_TRUE; } if (regs.clear_buffers.S) { ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!"); use_stencil = true; clear_state.stencil.test_enabled = true; + if (regs.clear_flags.stencil) { // Stencil affects the clear so fill it with the used masks clear_state.stencil.front.test_func = GL_ALWAYS; @@ -616,8 +550,9 @@ void RasterizerOpenGL::Clear() { return; } - const auto [clear_depth, clear_stencil] = ConfigureFramebuffers( - clear_state, use_color, use_depth || use_stencil, false, regs.clear_buffers.RT.Value()); + ConfigureClearFramebuffer(clear_state, use_color, use_depth, use_stencil); + + SyncViewport(clear_state); if (regs.clear_flags.scissor) { SyncScissorTest(clear_state); } @@ -626,33 +561,25 @@ void RasterizerOpenGL::Clear() { clear_state.EmulateViewportWithScissor(); } - clear_state.ApplyColorMask(); - clear_state.ApplyDepth(); - clear_state.ApplyStencilTest(); - clear_state.ApplyViewport(); + clear_state.AllDirty(); + clear_state.Apply(); if (use_color) { - glClearBufferfv(GL_COLOR, regs.clear_buffers.RT, regs.clear_color); + glClearBufferfv(GL_COLOR, 0, regs.clear_color); } - if (clear_depth && clear_stencil) { + if (use_depth && use_stencil) { glClearBufferfi(GL_DEPTH_STENCIL, 0, regs.clear_depth, regs.clear_stencil); - } else if (clear_depth) { + } else if (use_depth) { glClearBufferfv(GL_DEPTH, 0, ®s.clear_depth); - } else if (clear_stencil) { + } else if (use_stencil) { glClearBufferiv(GL_STENCIL, 0, ®s.clear_stencil); } } -void RasterizerOpenGL::DrawArrays() { - if (accelerate_draw == AccelDraw::Disabled) - return; - - MICROPROFILE_SCOPE(OpenGL_Drawing); +void RasterizerOpenGL::DrawPrelude() { auto& gpu = system.GPU().Maxwell3D(); - const auto& regs = gpu.regs; - ConfigureFramebuffers(state); SyncColorMask(); SyncFragmentColorClampState(); SyncMultiSampleState(); @@ -684,31 +611,196 @@ void RasterizerOpenGL::DrawArrays() { Maxwell::MaxShaderStage; // Add space for at least 18 constant buffers - buffer_size += - Maxwell::MaxConstBuffers * (MaxConstbufferSize + device.GetUniformBufferAlignment()); + buffer_size += Maxwell::MaxConstBuffers * + (Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment()); - const bool invalidate = buffer_cache.Map(buffer_size); - if (invalidate) { - // As all cached buffers are invalidated, we need to recheck their state. - gpu.dirty_flags.vertex_array.set(); - } + // Prepare the vertex array. + buffer_cache.Map(buffer_size); + // Prepare vertex array format. const GLuint vao = SetupVertexFormat(); + vertex_array_pushbuffer.Setup(vao); + + // Upload vertex and index data. SetupVertexBuffer(vao); + SetupVertexInstances(vao); + index_buffer_offset = SetupIndexBuffer(); - DrawParameters params = SetupDraw(); - SetupShaders(params.primitive_mode); + // Prepare packed bindings. + bind_ubo_pushbuffer.Setup(0); + bind_ssbo_pushbuffer.Setup(0); - buffer_cache.Unmap(); + // Setup shaders and their used resources. + texture_cache.GuardSamplers(true); + const auto primitive_mode = MaxwellToGL::PrimitiveTopology(gpu.regs.draw.topology); + SetupShaders(primitive_mode); + texture_cache.GuardSamplers(false); + + ConfigureFramebuffers(); + + // Signal the buffer cache that we are not going to upload more things. + const bool invalidate = buffer_cache.Unmap(); + + // Now that we are no longer uploading data, we can safely bind the buffers to OpenGL. + vertex_array_pushbuffer.Bind(); + bind_ubo_pushbuffer.Bind(); + bind_ssbo_pushbuffer.Bind(); + + if (invalidate) { + // As all cached buffers are invalidated, we need to recheck their state. + gpu.dirty.ResetVertexArrays(); + } shader_program_manager->ApplyTo(state); state.Apply(); - res_cache.SignalPreDrawCall(); - params.DispatchDraw(); - res_cache.SignalPostDrawCall(); + if (texture_cache.TextureBarrier()) { + glTextureBarrier(); + } +} + +struct DrawParams { + bool is_indexed{}; + bool is_instanced{}; + GLenum primitive_mode{}; + GLint count{}; + GLint base_vertex{}; + // Indexed settings + GLenum index_format{}; + GLintptr index_buffer_offset{}; + + // Instanced setting + GLint num_instances{}; + GLint base_instance{}; + + void DispatchDraw() { + if (is_indexed) { + const auto index_buffer_ptr = reinterpret_cast<const void*>(index_buffer_offset); + if (is_instanced) { + glDrawElementsInstancedBaseVertexBaseInstance(primitive_mode, count, index_format, + index_buffer_ptr, num_instances, + base_vertex, base_instance); + } else { + glDrawElementsBaseVertex(primitive_mode, count, index_format, index_buffer_ptr, + base_vertex); + } + } else { + if (is_instanced) { + glDrawArraysInstancedBaseInstance(primitive_mode, base_vertex, count, num_instances, + base_instance); + } else { + glDrawArrays(primitive_mode, base_vertex, count); + } + } + } +}; + +bool RasterizerOpenGL::DrawBatch(bool is_indexed) { + accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays; + + MICROPROFILE_SCOPE(OpenGL_Drawing); + + DrawPrelude(); + + auto& maxwell3d = system.GPU().Maxwell3D(); + const auto& regs = maxwell3d.regs; + const auto current_instance = maxwell3d.state.current_instance; + DrawParams draw_call{}; + draw_call.is_indexed = is_indexed; + draw_call.num_instances = static_cast<GLint>(1); + draw_call.base_instance = static_cast<GLint>(current_instance); + draw_call.is_instanced = current_instance > 0; + draw_call.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology); + if (draw_call.is_indexed) { + draw_call.count = static_cast<GLint>(regs.index_array.count); + draw_call.base_vertex = static_cast<GLint>(regs.vb_element_base); + draw_call.index_format = MaxwellToGL::IndexFormat(regs.index_array.format); + draw_call.index_buffer_offset = index_buffer_offset; + } else { + draw_call.count = static_cast<GLint>(regs.vertex_buffer.count); + draw_call.base_vertex = static_cast<GLint>(regs.vertex_buffer.first); + } + draw_call.DispatchDraw(); + + maxwell3d.dirty.memory_general = false; accelerate_draw = AccelDraw::Disabled; + return true; +} + +bool RasterizerOpenGL::DrawMultiBatch(bool is_indexed) { + accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays; + + MICROPROFILE_SCOPE(OpenGL_Drawing); + + DrawPrelude(); + + auto& maxwell3d = system.GPU().Maxwell3D(); + const auto& regs = maxwell3d.regs; + const auto& draw_setup = maxwell3d.mme_draw; + DrawParams draw_call{}; + draw_call.is_indexed = is_indexed; + draw_call.num_instances = static_cast<GLint>(draw_setup.instance_count); + draw_call.base_instance = static_cast<GLint>(regs.vb_base_instance); + draw_call.is_instanced = draw_setup.instance_count > 1; + draw_call.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology); + if (draw_call.is_indexed) { + draw_call.count = static_cast<GLint>(regs.index_array.count); + draw_call.base_vertex = static_cast<GLint>(regs.vb_element_base); + draw_call.index_format = MaxwellToGL::IndexFormat(regs.index_array.format); + draw_call.index_buffer_offset = index_buffer_offset; + } else { + draw_call.count = static_cast<GLint>(regs.vertex_buffer.count); + draw_call.base_vertex = static_cast<GLint>(regs.vertex_buffer.first); + } + draw_call.DispatchDraw(); + + maxwell3d.dirty.memory_general = false; + accelerate_draw = AccelDraw::Disabled; + return true; +} + +void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { + if (!GLAD_GL_ARB_compute_variable_group_size) { + LOG_ERROR(Render_OpenGL, "Compute is currently not supported on this device due to the " + "lack of GL_ARB_compute_variable_group_size"); + return; + } + + auto kernel = shader_cache.GetComputeKernel(code_addr); + ProgramVariant variant; + variant.texture_buffer_usage = SetupComputeTextures(kernel); + SetupComputeImages(kernel); + + const auto [program, next_bindings] = kernel->GetProgramHandle(variant); + state.draw.shader_program = program; + state.draw.program_pipeline = 0; + + const std::size_t buffer_size = + Tegra::Engines::KeplerCompute::NumConstBuffers * + (Maxwell::MaxConstBufferSize + device.GetUniformBufferAlignment()); + buffer_cache.Map(buffer_size); + + bind_ubo_pushbuffer.Setup(0); + bind_ssbo_pushbuffer.Setup(0); + + SetupComputeConstBuffers(kernel); + SetupComputeGlobalMemory(kernel); + + buffer_cache.Unmap(); + + bind_ubo_pushbuffer.Bind(); + bind_ssbo_pushbuffer.Bind(); + + state.ApplyTextures(); + state.ApplyImages(); + state.ApplyShaderProgram(); + state.ApplyProgramPipeline(); + + const auto& launch_desc = system.GPU().KeplerCompute().launch_description; + glDispatchComputeGroupSizeARB(launch_desc.grid_dim_x, launch_desc.grid_dim_y, + launch_desc.grid_dim_z, launch_desc.block_dim_x, + launch_desc.block_dim_y, launch_desc.block_dim_z); } void RasterizerOpenGL::FlushAll() {} @@ -718,8 +810,8 @@ void RasterizerOpenGL::FlushRegion(CacheAddr addr, u64 size) { if (!addr || !size) { return; } - res_cache.FlushRegion(addr, size); - global_cache.FlushRegion(addr, size); + texture_cache.FlushRegion(addr, size); + buffer_cache.FlushRegion(addr, size); } void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) { @@ -727,23 +819,31 @@ 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); } +void RasterizerOpenGL::FlushCommands() { + glFlush(); +} + +void RasterizerOpenGL::TickFrame() { + buffer_cache.TickFrame(); +} + 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 +855,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,109 +872,204 @@ 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(); + screen_info.display_srgb = surface->GetSurfaceParams().srgb_conversion; return true; } -void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, - const Shader& shader, GLuint program_handle, - BaseBindings base_bindings) { +void RasterizerOpenGL::SetupDrawConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, + const Shader& shader) { MICROPROFILE_SCOPE(OpenGL_UBO); - const auto& gpu = system.GPU(); - const auto& maxwell3d = gpu.Maxwell3D(); - const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<std::size_t>(stage)]; - const auto& entries = shader->GetShaderEntries().const_buffers; - - // Upload only the enabled buffers from the 16 constbuffers of each shader stage - for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { - const auto& used_buffer = entries[bindpoint]; - const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()]; - - if (!buffer.enabled) { - // Set values to zero to unbind buffers - bind_ubo_pushbuffer.Push(0, 0, 0); - continue; - } - - std::size_t size = 0; + const auto& stages = system.GPU().Maxwell3D().state.shader_stages; + const auto& shader_stage = stages[static_cast<std::size_t>(stage)]; + for (const auto& entry : shader->GetShaderEntries().const_buffers) { + const auto& buffer = shader_stage.const_buffers[entry.GetIndex()]; + SetupConstBuffer(buffer, entry); + } +} - if (used_buffer.IsIndirect()) { - // Buffer is accessed indirectly, so upload the entire thing - size = buffer.size; +void RasterizerOpenGL::SetupComputeConstBuffers(const Shader& kernel) { + MICROPROFILE_SCOPE(OpenGL_UBO); + const auto& launch_desc = system.GPU().KeplerCompute().launch_description; + for (const auto& entry : kernel->GetShaderEntries().const_buffers) { + const auto& config = launch_desc.const_buffer_config[entry.GetIndex()]; + const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value(); + Tegra::Engines::ConstBufferInfo buffer; + buffer.address = config.Address(); + buffer.size = config.size; + buffer.enabled = mask[entry.GetIndex()]; + SetupConstBuffer(buffer, entry); + } +} - if (size > MaxConstbufferSize) { - LOG_WARNING(Render_OpenGL, "Indirect constbuffer size {} exceeds maximum {}", size, - MaxConstbufferSize); - size = MaxConstbufferSize; - } - } else { - // Buffer is accessed directly, upload just what we use - size = used_buffer.GetSize(); - } +void RasterizerOpenGL::SetupConstBuffer(const Tegra::Engines::ConstBufferInfo& buffer, + const GLShader::ConstBufferEntry& entry) { + if (!buffer.enabled) { + // Set values to zero to unbind buffers + bind_ubo_pushbuffer.Push(buffer_cache.GetEmptyBuffer(sizeof(float)), 0, sizeof(float)); + return; + } - // Align the actual size so it ends up being a multiple of vec4 to meet the OpenGL std140 - // UBO alignment requirements. - size = Common::AlignUp(size, sizeof(GLvec4)); - ASSERT_MSG(size <= MaxConstbufferSize, "Constbuffer too big"); + // Align the actual size so it ends up being a multiple of vec4 to meet the OpenGL std140 + // UBO alignment requirements. + const std::size_t size = Common::AlignUp(GetConstBufferSize(buffer, entry), sizeof(GLvec4)); - const GLintptr const_buffer_offset = - buffer_cache.UploadMemory(buffer.address, size, device.GetUniformBufferAlignment()); + const auto alignment = device.GetUniformBufferAlignment(); + const auto [cbuf, offset] = buffer_cache.UploadMemory(buffer.address, size, alignment); + bind_ubo_pushbuffer.Push(cbuf, offset, size); +} - bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), const_buffer_offset, size); +void RasterizerOpenGL::SetupDrawGlobalMemory(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, + const Shader& shader) { + auto& gpu{system.GPU()}; + auto& memory_manager{gpu.MemoryManager()}; + const auto cbufs{gpu.Maxwell3D().state.shader_stages[static_cast<std::size_t>(stage)]}; + for (const auto& entry : shader->GetShaderEntries().global_memory_entries) { + const auto addr{cbufs.const_buffers[entry.GetCbufIndex()].address + entry.GetCbufOffset()}; + const auto gpu_addr{memory_manager.Read<u64>(addr)}; + const auto size{memory_manager.Read<u32>(addr + 8)}; + SetupGlobalMemory(entry, gpu_addr, size); } } -void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, - const Shader& shader, GLenum primitive_mode, - BaseBindings base_bindings) { - const auto& entries = shader->GetShaderEntries().global_memory_entries; - for (std::size_t bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { - const auto& entry{entries[bindpoint]}; - const auto& region{global_cache.GetGlobalRegion(entry, stage)}; - if (entry.IsWritten()) { - region->MarkAsModified(true, global_cache); - } - bind_ssbo_pushbuffer.Push(region->GetBufferHandle(), 0, - static_cast<GLsizeiptr>(region->GetSizeInBytes())); +void RasterizerOpenGL::SetupComputeGlobalMemory(const Shader& kernel) { + auto& gpu{system.GPU()}; + auto& memory_manager{gpu.MemoryManager()}; + const auto cbufs{gpu.KeplerCompute().launch_description.const_buffer_config}; + for (const auto& entry : kernel->GetShaderEntries().global_memory_entries) { + const auto addr{cbufs[entry.GetCbufIndex()].Address() + entry.GetCbufOffset()}; + const auto gpu_addr{memory_manager.Read<u64>(addr)}; + const auto size{memory_manager.Read<u32>(addr + 8)}; + SetupGlobalMemory(entry, gpu_addr, size); } } -void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader, - GLuint program_handle, BaseBindings base_bindings) { +void RasterizerOpenGL::SetupGlobalMemory(const GLShader::GlobalMemoryEntry& entry, + GPUVAddr gpu_addr, std::size_t size) { + const auto alignment{device.GetShaderStorageBufferAlignment()}; + const auto [ssbo, buffer_offset] = + buffer_cache.UploadMemory(gpu_addr, size, alignment, entry.IsWritten()); + bind_ssbo_pushbuffer.Push(ssbo, buffer_offset, static_cast<GLsizeiptr>(size)); +} + +TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stage, + const Shader& shader, + BaseBindings base_bindings) { MICROPROFILE_SCOPE(OpenGL_Texture); const auto& gpu = system.GPU(); const auto& maxwell3d = gpu.Maxwell3D(); const auto& entries = shader->GetShaderEntries().samplers; - ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.texture_units), + ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.textures), "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; - if (entry.IsBindless()) { + const auto texture = [&]() { + if (!entry.IsBindless()) { + return maxwell3d.GetStageTexture(stage, entry.GetOffset()); + } const auto cbuf = entry.GetBindlessCBuf(); Tegra::Texture::TextureHandle tex_handle; tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second); - texture = maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset()); - } else { - texture = maxwell3d.GetStageTexture(stage, entry.GetOffset()); + return maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset()); + }(); + + if (SetupTexture(base_bindings.sampler + bindpoint, texture, entry)) { + texture_buffer_usage.set(bindpoint); } - const u32 current_bindpoint = base_bindings.sampler + bindpoint; + } - state.texture_units[current_bindpoint].sampler = sampler_cache.GetSampler(texture.tsc); + return texture_buffer_usage; +} - 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, - texture.tic.w_source); - } else { - // Can occur when texture addr is null or its memory is unmapped/invalid - state.texture_units[current_bindpoint].texture = 0; +TextureBufferUsage RasterizerOpenGL::SetupComputeTextures(const Shader& kernel) { + MICROPROFILE_SCOPE(OpenGL_Texture); + const auto& compute = system.GPU().KeplerCompute(); + const auto& entries = kernel->GetShaderEntries().samplers; + + ASSERT_MSG(entries.size() <= std::size(state.textures), + "Exceeded the number of active textures."); + + TextureBufferUsage texture_buffer_usage{0}; + + for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { + const auto& entry = entries[bindpoint]; + const auto texture = [&]() { + if (!entry.IsBindless()) { + return compute.GetTexture(entry.GetOffset()); + } + const auto cbuf = entry.GetBindlessCBuf(); + Tegra::Texture::TextureHandle tex_handle; + tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second); + return compute.GetTextureInfo(tex_handle, entry.GetOffset()); + }(); + + if (SetupTexture(bindpoint, texture, entry)) { + texture_buffer_usage.set(bindpoint); } } + + return texture_buffer_usage; +} + +bool RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, + const GLShader::SamplerEntry& entry) { + state.samplers[binding] = sampler_cache.GetSampler(texture.tsc); + + const auto view = texture_cache.GetTextureSurface(texture.tic, entry); + if (!view) { + // Can occur when texture addr is null or its memory is unmapped/invalid + state.textures[binding] = 0; + return false; + } + state.textures[binding] = view->GetTexture(); + + if (view->GetSurfaceParams().IsBuffer()) { + return true; + } + + // 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); + return false; +} + +void RasterizerOpenGL::SetupComputeImages(const Shader& shader) { + const auto& compute = system.GPU().KeplerCompute(); + const auto& entries = shader->GetShaderEntries().images; + for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { + const auto& entry = entries[bindpoint]; + const auto tic = [&]() { + if (!entry.IsBindless()) { + return compute.GetTexture(entry.GetOffset()).tic; + } + const auto cbuf = entry.GetBindlessCBuf(); + Tegra::Texture::TextureHandle tex_handle; + tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second); + return compute.GetTextureInfo(tex_handle, entry.GetOffset()).tic; + }(); + SetupImage(bindpoint, tic, entry); + } +} + +void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, + const GLShader::ImageEntry& entry) { + const auto view = texture_cache.GetImageSurface(tic, entry); + if (!view) { + state.images[binding] = 0; + return; + } + if (!tic.IsBuffer()) { + view->ApplySwizzle(tic.x_source, tic.y_source, tic.z_source, tic.w_source); + } + if (entry.IsWritten()) { + view->MarkAsModified(texture_cache.Tick()); + } + state.images[binding] = view->GetTexture(); } void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { @@ -917,10 +1113,11 @@ void RasterizerOpenGL::SyncClipCoef() { } void RasterizerOpenGL::SyncCullMode() { - const auto& regs = system.GPU().Maxwell3D().regs; + auto& maxwell3d = system.GPU().Maxwell3D(); - state.cull.enabled = regs.cull.enabled != 0; + const auto& regs = maxwell3d.regs; + state.cull.enabled = regs.cull.enabled != 0; if (state.cull.enabled) { state.cull.front_face = MaxwellToGL::FrontFace(regs.cull.front_face); state.cull.mode = MaxwellToGL::CullFace(regs.cull.cull_face); @@ -953,15 +1150,23 @@ void RasterizerOpenGL::SyncDepthTestState() { state.depth.test_enabled = regs.depth_test_enable != 0; state.depth.write_mask = regs.depth_write_enabled ? GL_TRUE : GL_FALSE; - if (!state.depth.test_enabled) + if (!state.depth.test_enabled) { return; + } state.depth.test_func = MaxwellToGL::ComparisonOp(regs.depth_test_func); } void RasterizerOpenGL::SyncStencilTestState() { - const auto& regs = system.GPU().Maxwell3D().regs; + auto& maxwell3d = system.GPU().Maxwell3D(); + if (!maxwell3d.dirty.stencil_test) { + return; + } + maxwell3d.dirty.stencil_test = false; + + const auto& regs = maxwell3d.regs; state.stencil.test_enabled = regs.stencil_enable != 0; + state.MarkDirtyStencilState(); if (!regs.stencil_enable) { return; @@ -994,7 +1199,12 @@ void RasterizerOpenGL::SyncStencilTestState() { } void RasterizerOpenGL::SyncColorMask() { - const auto& regs = system.GPU().Maxwell3D().regs; + auto& maxwell3d = system.GPU().Maxwell3D(); + if (!maxwell3d.dirty.color_mask) { + return; + } + const auto& regs = maxwell3d.regs; + const std::size_t count = regs.independent_blend_enable ? Tegra::Engines::Maxwell3D::Regs::NumRenderTargets : 1; for (std::size_t i = 0; i < count; i++) { @@ -1005,6 +1215,9 @@ void RasterizerOpenGL::SyncColorMask() { dest.blue_enabled = (source.B == 0) ? GL_FALSE : GL_TRUE; dest.alpha_enabled = (source.A == 0) ? GL_FALSE : GL_TRUE; } + + state.MarkDirtyColorMask(); + maxwell3d.dirty.color_mask = false; } void RasterizerOpenGL::SyncMultiSampleState() { @@ -1019,7 +1232,11 @@ void RasterizerOpenGL::SyncFragmentColorClampState() { } void RasterizerOpenGL::SyncBlendState() { - const auto& regs = system.GPU().Maxwell3D().regs; + auto& maxwell3d = system.GPU().Maxwell3D(); + if (!maxwell3d.dirty.blend_state) { + return; + } + const auto& regs = maxwell3d.regs; state.blend_color.red = regs.blend_color.r; state.blend_color.green = regs.blend_color.g; @@ -1042,6 +1259,8 @@ void RasterizerOpenGL::SyncBlendState() { for (std::size_t i = 1; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { state.blend[i].enabled = false; } + maxwell3d.dirty.blend_state = false; + state.MarkDirtyBlendState(); return; } @@ -1058,6 +1277,9 @@ void RasterizerOpenGL::SyncBlendState() { blend.src_a_func = MaxwellToGL::BlendFunc(src.factor_source_a); blend.dst_a_func = MaxwellToGL::BlendFunc(src.factor_dest_a); } + + state.MarkDirtyBlendState(); + maxwell3d.dirty.blend_state = false; } void RasterizerOpenGL::SyncLogicOpState() { @@ -1109,13 +1331,21 @@ void RasterizerOpenGL::SyncPointState() { } void RasterizerOpenGL::SyncPolygonOffset() { - const auto& regs = system.GPU().Maxwell3D().regs; + auto& maxwell3d = system.GPU().Maxwell3D(); + if (!maxwell3d.dirty.polygon_offset) { + return; + } + const auto& regs = maxwell3d.regs; + state.polygon_offset.fill_enable = regs.polygon_offset_fill_enable != 0; state.polygon_offset.line_enable = regs.polygon_offset_line_enable != 0; state.polygon_offset.point_enable = regs.polygon_offset_point_enable != 0; state.polygon_offset.units = regs.polygon_offset_units; state.polygon_offset.factor = regs.polygon_offset_factor; state.polygon_offset.clamp = regs.polygon_offset_clamp; + + state.MarkDirtyPolygonOffset(); + maxwell3d.dirty.polygon_offset = false; } void RasterizerOpenGL::SyncAlphaTest() { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 2817f65c9..9c10ebda3 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -17,19 +17,22 @@ #include <glad/glad.h> #include "common/common_types.h" +#include "video_core/engines/const_buffer_info.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/rasterizer_cache.h" #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_global_cache.h" -#include "video_core/renderer_opengl/gl_rasterizer_cache.h" +#include "video_core/renderer_opengl/gl_framebuffer_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" +#include "video_core/textures/texture.h" namespace Core { class System; @@ -39,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: @@ -51,71 +57,75 @@ public: ScreenInfo& info); ~RasterizerOpenGL() override; - void DrawArrays() override; + bool DrawBatch(bool is_indexed) override; + bool DrawMultiBatch(bool is_indexed) override; void Clear() override; + void DispatchCompute(GPUVAddr code_addr) override; void FlushAll() override; void FlushRegion(CacheAddr addr, u64 size) override; void InvalidateRegion(CacheAddr addr, u64 size) override; void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; + void FlushCommands() override; + void TickFrame() 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; void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) override; void LoadDiskResources(const std::atomic_bool& stop_loading, const VideoCore::DiskResourceLoadCallback& callback) override; - /// Maximum supported size that a constbuffer can have in bytes. - static constexpr std::size_t MaxConstbufferSize = 0x10000; - static_assert(MaxConstbufferSize % sizeof(GLvec4) == 0, - "The maximum size of a constbuffer must be a multiple of the size of GLvec4"); - private: - struct FramebufferConfigState { - bool using_color_fb{}; - bool using_depth_fb{}; - bool preserve_contents{}; - std::optional<std::size_t> single_color_target; - - bool operator==(const FramebufferConfigState& rhs) const { - return std::tie(using_color_fb, using_depth_fb, preserve_contents, - single_color_target) == std::tie(rhs.using_color_fb, rhs.using_depth_fb, - rhs.preserve_contents, - rhs.single_color_target); - } - bool operator!=(const FramebufferConfigState& rhs) const { - return !operator==(rhs); - } - }; - - /** - * Configures the color and depth framebuffer states. - * @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. - * @param single_color_target Specifies if a single color buffer target should be used. - * @returns If depth (first) or stencil (second) are being stored in the bound zeta texture - * (requires using_depth_fb to be true) - */ - std::pair<bool, bool> ConfigureFramebuffers( - OpenGLState& current_state, bool use_color_fb = true, bool using_depth_fb = true, - bool preserve_contents = true, std::optional<std::size_t> single_color_target = {}); + /// Configures the color and depth framebuffer states. + void ConfigureFramebuffers(); + + void ConfigureClearFramebuffer(OpenGLState& current_state, bool using_color_fb, + bool using_depth_fb, bool using_stencil_fb); /// Configures the current constbuffers to use for the draw command. - void SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader, - GLuint program_handle, BaseBindings base_bindings); + void SetupDrawConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, + const Shader& shader); + + /// Configures the current constbuffers to use for the kernel invocation. + void SetupComputeConstBuffers(const Shader& kernel); + + /// Configures a constant buffer. + void SetupConstBuffer(const Tegra::Engines::ConstBufferInfo& buffer, + const GLShader::ConstBufferEntry& entry); /// Configures the current global memory entries to use for the draw command. - void SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, - const Shader& shader, GLenum primitive_mode, - BaseBindings base_bindings); + void SetupDrawGlobalMemory(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, + const Shader& shader); + + /// Configures the current global memory entries to use for the kernel invocation. + void SetupComputeGlobalMemory(const Shader& kernel); + + /// Configures a constant buffer. + void SetupGlobalMemory(const GLShader::GlobalMemoryEntry& entry, GPUVAddr gpu_addr, + std::size_t size); + + /// Syncs all the state, shaders, render targets and textures setting before a draw call. + void DrawPrelude(); - /// Configures the current textures to use for the draw command. - void SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader, - GLuint program_handle, BaseBindings base_bindings); + /// Configures the current textures to use for the draw command. Returns shaders texture buffer + /// usage. + TextureBufferUsage SetupDrawTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, + const Shader& shader, BaseBindings base_bindings); + + /// Configures the textures used in a compute shader. Returns texture buffer usage. + TextureBufferUsage SetupComputeTextures(const Shader& kernel); + + /// Configures a texture. Returns true when the texture is a texture buffer. + bool SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, + const GLShader::SamplerEntry& entry); + + /// Configures images in a compute shader. + void SetupComputeImages(const Shader& shader); + + /// Configures an image. + void SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, + const GLShader::ImageEntry& entry); /// Syncs the viewport and depth range to match the guest state void SyncViewport(OpenGLState& current_state); @@ -176,10 +186,10 @@ 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; @@ -190,13 +200,10 @@ private: OGLVertexArray> vertex_array_cache; - std::map<FramebufferCacheKey, OGLFramebuffer> framebuffer_cache; - FramebufferConfigState current_framebuffer_config_state; - std::pair<bool, bool> current_depth_stencil_usage{}; - static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024; OGLBufferCache buffer_cache; + VertexArrayPushBuffer vertex_array_pushbuffer; BindBuffersRangePushBuffer bind_ubo_pushbuffer{GL_UNIFORM_BUFFER}; BindBuffersRangePushBuffer bind_ssbo_pushbuffer{GL_SHADER_STORAGE_BUFFER}; @@ -208,16 +215,19 @@ private: GLuint SetupVertexFormat(); void SetupVertexBuffer(GLuint vao); + void SetupVertexInstances(GLuint vao); - DrawParameters SetupDraw(); + GLintptr SetupIndexBuffer(); - void SetupShaders(GLenum primitive_mode); + GLintptr index_buffer_offset; - void SetupCachedFramebuffer(const FramebufferCacheKey& fbkey, OpenGLState& current_state); + void SetupShaders(GLenum primitive_mode); enum class AccelDraw { Disabled, Arrays, Indexed }; AccelDraw accelerate_draw = AccelDraw::Disabled; + OGLFramebuffer clear_framebuffer; + using CachedPageMap = boost::icl::interval_map<u64, int>; CachedPageMap cached_pages; }; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp deleted file mode 100644 index a7681902e..000000000 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ /dev/null @@ -1,1362 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <optional> -#include <glad/glad.h> - -#include "common/alignment.h" -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/microprofile.h" -#include "common/scope_exit.h" -#include "core/core.h" -#include "core/hle/kernel/process.h" -#include "core/settings.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/memory_manager.h" -#include "video_core/morton.h" -#include "video_core/renderer_opengl/gl_rasterizer.h" -#include "video_core/renderer_opengl/gl_rasterizer_cache.h" -#include "video_core/renderer_opengl/utils.h" -#include "video_core/surface.h" -#include "video_core/textures/convert.h" -#include "video_core/textures/decoders.h" - -namespace OpenGL { - -using VideoCore::MortonSwizzle; -using VideoCore::MortonSwizzleMode; -using VideoCore::Surface::ComponentTypeFromDepthFormat; -using VideoCore::Surface::ComponentTypeFromRenderTarget; -using VideoCore::Surface::ComponentTypeFromTexture; -using VideoCore::Surface::PixelFormatFromDepthFormat; -using VideoCore::Surface::PixelFormatFromRenderTargetFormat; -using VideoCore::Surface::PixelFormatFromTextureFormat; -using VideoCore::Surface::SurfaceTargetFromTextureType; - -struct FormatTuple { - GLint internal_format; - GLenum format; - GLenum type; - ComponentType component_type; - bool compressed; -}; - -static void ApplyTextureDefaults(GLuint texture, u32 max_mip_level) { - glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, max_mip_level - 1); - if (max_mip_level == 1) { - glTextureParameterf(texture, GL_TEXTURE_LOD_BIAS, 1000.0); - } -} - -void SurfaceParams::InitCacheParameters(GPUVAddr gpu_addr_) { - auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; - - gpu_addr = gpu_addr_; - host_ptr = memory_manager.GetPointer(gpu_addr_); - size_in_bytes = SizeInBytesRaw(); - - if (IsPixelFormatASTC(pixel_format)) { - // ASTC is uncompressed in software, in emulated as RGBA8 - size_in_bytes_gl = width * height * depth * 4; - } else { - size_in_bytes_gl = SizeInBytesGL(); - } -} - -std::size_t SurfaceParams::InnerMipmapMemorySize(u32 mip_level, bool force_gl, bool layer_only, - bool uncompressed) const { - const u32 tile_x{GetDefaultBlockWidth(pixel_format)}; - const u32 tile_y{GetDefaultBlockHeight(pixel_format)}; - const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)}; - u32 m_depth = (layer_only ? 1U : depth); - u32 m_width = MipWidth(mip_level); - u32 m_height = MipHeight(mip_level); - m_width = uncompressed ? m_width : std::max(1U, (m_width + tile_x - 1) / tile_x); - m_height = uncompressed ? m_height : std::max(1U, (m_height + tile_y - 1) / tile_y); - m_depth = std::max(1U, m_depth >> mip_level); - u32 m_block_height = MipBlockHeight(mip_level); - u32 m_block_depth = MipBlockDepth(mip_level); - return Tegra::Texture::CalculateSize(force_gl ? false : is_tiled, bytes_per_pixel, m_width, - m_height, m_depth, m_block_height, m_block_depth); -} - -std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only, - bool uncompressed) const { - std::size_t block_size_bytes = Tegra::Texture::GetGOBSize() * block_height * block_depth; - std::size_t size = 0; - for (u32 i = 0; i < max_mip_level; i++) { - size += InnerMipmapMemorySize(i, force_gl, layer_only, uncompressed); - } - if (!force_gl && is_tiled) { - size = Common::AlignUp(size, block_size_bytes); - } - return size; -} - -/*static*/ SurfaceParams SurfaceParams::CreateForTexture( - const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry) { - SurfaceParams params{}; - params.is_tiled = config.tic.IsTiled(); - params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, - params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, - params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, - params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; - params.srgb_conversion = config.tic.IsSrgbConversionEnabled(); - params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), - params.srgb_conversion); - - if (config.tsc.depth_compare_enabled) { - // Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled, - // then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also - // causes GetFormatType to properly return 'Depth' below). - if (GetFormatType(params.pixel_format) == SurfaceType::ColorTexture) { - switch (params.pixel_format) { - case PixelFormat::R16S: - case PixelFormat::R16U: - case PixelFormat::R16F: - params.pixel_format = PixelFormat::Z16; - break; - case PixelFormat::R32F: - params.pixel_format = PixelFormat::Z32F; - break; - default: - LOG_WARNING(HW_GPU, "Color texture format being used with depth compare: {}", - static_cast<u32>(params.pixel_format)); - break; - } - } - } - - params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); - params.type = GetFormatType(params.pixel_format); - UNIMPLEMENTED_IF(params.type == SurfaceType::ColorTexture && config.tsc.depth_compare_enabled); - - params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format)); - params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format)); - if (!params.is_tiled) { - params.pitch = config.tic.Pitch(); - } - params.unaligned_height = config.tic.Height(); - params.target = SurfaceTargetFromTextureType(config.tic.texture_type); - params.identity = SurfaceClass::Uploaded; - - switch (params.target) { - case SurfaceTarget::Texture1D: - case SurfaceTarget::Texture2D: - params.depth = 1; - break; - case SurfaceTarget::TextureCubemap: - params.depth = config.tic.Depth() * 6; - break; - case SurfaceTarget::Texture3D: - params.depth = config.tic.Depth(); - break; - case SurfaceTarget::Texture2DArray: - params.depth = config.tic.Depth(); - if (!entry.IsArray()) { - // TODO(bunnei): We have seen games re-use a Texture2D as Texture2DArray with depth of - // one, but sample the texture in the shader as if it were not an array texture. This - // probably is valid on hardware, but we still need to write a test to confirm this. In - // emulation, the workaround here is to continue to treat this as a Texture2D. An - // example game that does this is Super Mario Odyssey (in Cloud Kingdom). - ASSERT(params.depth == 1); - params.target = SurfaceTarget::Texture2D; - } - break; - case SurfaceTarget::TextureCubeArray: - params.depth = config.tic.Depth() * 6; - if (!entry.IsArray()) { - ASSERT(params.depth == 6); - params.target = SurfaceTarget::TextureCubemap; - } - break; - default: - LOG_CRITICAL(HW_GPU, "Unknown depth for target={}", static_cast<u32>(params.target)); - UNREACHABLE(); - params.depth = 1; - break; - } - - params.is_layered = SurfaceTargetIsLayered(params.target); - params.is_array = SurfaceTargetIsArray(params.target); - params.max_mip_level = config.tic.max_mip_level + 1; - params.rt = {}; - - params.InitCacheParameters(config.tic.Address()); - - return params; -} - -/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(std::size_t index) { - const auto& config{Core::System::GetInstance().GPU().Maxwell3D().regs.rt[index]}; - SurfaceParams params{}; - - params.is_tiled = - config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; - params.block_width = 1 << config.memory_layout.block_width; - params.block_height = 1 << config.memory_layout.block_height; - params.block_depth = 1 << config.memory_layout.block_depth; - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); - params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB || - config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; - params.component_type = ComponentTypeFromRenderTarget(config.format); - params.type = GetFormatType(params.pixel_format); - if (params.is_tiled) { - params.width = config.width; - } else { - params.pitch = config.width; - const u32 bpp = params.GetFormatBpp() / 8; - params.width = params.pitch / bpp; - } - params.height = config.height; - params.unaligned_height = config.height; - params.target = SurfaceTarget::Texture2D; - params.identity = SurfaceClass::RenderTarget; - params.depth = 1; - params.max_mip_level = 1; - params.is_layered = false; - - // Render target specific parameters, not used for caching - params.rt.index = static_cast<u32>(index); - params.rt.array_mode = config.array_mode; - params.rt.layer_stride = config.layer_stride; - params.rt.volume = config.volume; - params.rt.base_layer = config.base_layer; - - params.InitCacheParameters(config.Address()); - - return params; -} - -/*static*/ SurfaceParams 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) { - SurfaceParams params{}; - - params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; - params.block_width = 1 << std::min(block_width, 5U); - params.block_height = 1 << std::min(block_height, 5U); - params.block_depth = 1 << std::min(block_depth, 5U); - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromDepthFormat(format); - params.component_type = ComponentTypeFromDepthFormat(format); - params.type = GetFormatType(params.pixel_format); - params.srgb_conversion = false; - params.width = zeta_width; - params.height = zeta_height; - params.unaligned_height = zeta_height; - params.target = SurfaceTarget::Texture2D; - params.identity = SurfaceClass::DepthBuffer; - params.depth = 1; - params.max_mip_level = 1; - params.is_layered = false; - params.rt = {}; - - params.InitCacheParameters(zeta_address); - - return params; -} - -/*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface( - const Tegra::Engines::Fermi2D::Regs::Surface& config) { - SurfaceParams params{}; - - params.is_tiled = !config.linear; - params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0, - params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0, - params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0, - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); - params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB || - config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; - params.component_type = ComponentTypeFromRenderTarget(config.format); - params.type = GetFormatType(params.pixel_format); - params.width = config.width; - params.pitch = config.pitch; - params.height = config.height; - params.unaligned_height = config.height; - params.target = SurfaceTarget::Texture2D; - params.identity = SurfaceClass::Copy; - params.depth = 1; - params.max_mip_level = 1; - params.rt = {}; - - params.InitCacheParameters(config.Address()); - - return params; -} - -static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format_tuples = {{ - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U - {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S - {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI - {GL_RGB8, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U - {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm, - false}, // A2B10G10R10U - {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5U - {GL_R8, GL_RED, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // R8U - {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // R8UI - {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, ComponentType::Float, false}, // RGBA16F - {GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RGBA16U - {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RGBA16UI - {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float, - false}, // R11FG11FB10F - {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI - {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT1 - {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT23 - {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT45 - {GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // DXN1 - {GL_COMPRESSED_RG_RGTC2, GL_RG, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXN2UNORM - {GL_COMPRESSED_SIGNED_RG_RGTC2, GL_RG, GL_INT, ComponentType::SNorm, true}, // DXN2SNORM - {GL_COMPRESSED_RGBA_BPTC_UNORM, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // BC7U - {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float, - true}, // BC6H_UF16 - {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float, - true}, // BC6H_SF16 - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4 - {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8 - {GL_RGBA32F, GL_RGBA, GL_FLOAT, ComponentType::Float, false}, // RGBA32F - {GL_RG32F, GL_RG, GL_FLOAT, ComponentType::Float, false}, // RG32F - {GL_R32F, GL_RED, GL_FLOAT, ComponentType::Float, false}, // R32F - {GL_R16F, GL_RED, GL_HALF_FLOAT, ComponentType::Float, false}, // R16F - {GL_R16, GL_RED, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // R16U - {GL_R16_SNORM, GL_RED, GL_SHORT, ComponentType::SNorm, false}, // R16S - {GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // R16UI - {GL_R16I, GL_RED_INTEGER, GL_SHORT, ComponentType::SInt, false}, // R16I - {GL_RG16, GL_RG, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RG16 - {GL_RG16F, GL_RG, GL_HALF_FLOAT, ComponentType::Float, false}, // RG16F - {GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RG16UI - {GL_RG16I, GL_RG_INTEGER, GL_SHORT, ComponentType::SInt, false}, // RG16I - {GL_RG16_SNORM, GL_RG, GL_SHORT, ComponentType::SNorm, false}, // RG16S - {GL_RGB32F, GL_RGB, GL_FLOAT, ComponentType::Float, false}, // RGB32F - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, - false}, // RGBA8_SRGB - {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U - {GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S - {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI - {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8 - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5 - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4 - {GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8 - // Compressed sRGB formats - {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT1_SRGB - {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT23_SRGB - {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT45_SRGB - {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // BC7U_SRGB - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4_SRGB - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8_SRGB - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5_SRGB - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4_SRGB - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5 - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5_SRGB - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X8 - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X8_SRGB - - // Depth formats - {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F - {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, ComponentType::UNorm, - false}, // Z16 - - // DepthStencil formats - {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm, - false}, // Z24S8 - {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm, - false}, // S8Z24 - {GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, - ComponentType::Float, false}, // Z32FS8 -}}; - -static GLenum SurfaceTargetToGL(SurfaceTarget target) { - switch (target) { - case SurfaceTarget::Texture1D: - return GL_TEXTURE_1D; - case SurfaceTarget::Texture2D: - return GL_TEXTURE_2D; - case SurfaceTarget::Texture3D: - return GL_TEXTURE_3D; - case SurfaceTarget::Texture1DArray: - return GL_TEXTURE_1D_ARRAY; - case SurfaceTarget::Texture2DArray: - return GL_TEXTURE_2D_ARRAY; - case SurfaceTarget::TextureCubemap: - return GL_TEXTURE_CUBE_MAP; - case SurfaceTarget::TextureCubeArray: - return GL_TEXTURE_CUBE_MAP_ARRAY; - } - LOG_CRITICAL(Render_OpenGL, "Unimplemented texture target={}", static_cast<u32>(target)); - UNREACHABLE(); - return {}; -} - -static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) { - ASSERT(static_cast<std::size_t>(pixel_format) < tex_format_tuples.size()); - auto& format = tex_format_tuples[static_cast<unsigned int>(pixel_format)]; - ASSERT(component_type == format.component_type); - - return format; -} - -/// Returns the discrepant array target -constexpr GLenum GetArrayDiscrepantTarget(SurfaceTarget target) { - switch (target) { - case SurfaceTarget::Texture1D: - return GL_TEXTURE_1D_ARRAY; - case SurfaceTarget::Texture2D: - return GL_TEXTURE_2D_ARRAY; - case SurfaceTarget::Texture3D: - return GL_NONE; - case SurfaceTarget::Texture1DArray: - return GL_TEXTURE_1D; - case SurfaceTarget::Texture2DArray: - return GL_TEXTURE_2D; - case SurfaceTarget::TextureCubemap: - return GL_TEXTURE_CUBE_MAP_ARRAY; - case SurfaceTarget::TextureCubeArray: - return GL_TEXTURE_CUBE_MAP; - } - return GL_NONE; -} - -Common::Rectangle<u32> SurfaceParams::GetRect(u32 mip_level) const { - u32 actual_height{std::max(1U, unaligned_height >> mip_level)}; - if (IsPixelFormatASTC(pixel_format)) { - // ASTC formats must stop at the ATSC block size boundary - actual_height = Common::AlignDown(actual_height, GetASTCBlockSize(pixel_format).second); - } - return {0, actual_height, MipWidth(mip_level), 0}; -} - -void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params, - std::vector<u8>& gl_buffer, u32 mip_level) { - u32 depth = params.MipDepth(mip_level); - if (params.target == SurfaceTarget::Texture2D) { - // TODO(Blinkhawk): Eliminate this condition once all texture types are implemented. - depth = 1U; - } - if (params.is_layered) { - u64 offset = params.GetMipmapLevelOffset(mip_level); - u64 offset_gl = 0; - const u64 layer_size = params.LayerMemorySize(); - const u64 gl_size = params.LayerSizeGL(mip_level); - for (u32 i = 0; i < params.depth; i++) { - MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level), - params.MipBlockHeight(mip_level), params.MipHeight(mip_level), - params.MipBlockDepth(mip_level), 1, params.tile_width_spacing, - gl_buffer.data() + offset_gl, params.host_ptr + offset); - offset += layer_size; - offset_gl += gl_size; - } - } else { - const u64 offset = params.GetMipmapLevelOffset(mip_level); - MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level), - params.MipBlockHeight(mip_level), params.MipHeight(mip_level), - params.MipBlockDepth(mip_level), depth, params.tile_width_spacing, - gl_buffer.data(), params.host_ptr + offset); - } -} - -void RasterizerCacheOpenGL::FastCopySurface(const Surface& src_surface, - const Surface& dst_surface) { - const auto& src_params{src_surface->GetSurfaceParams()}; - const auto& dst_params{dst_surface->GetSurfaceParams()}; - - const u32 width{std::min(src_params.width, dst_params.width)}; - const u32 height{std::min(src_params.height, dst_params.height)}; - - glCopyImageSubData(src_surface->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, 0, - 0, dst_surface->Texture().handle, SurfaceTargetToGL(dst_params.target), 0, 0, - 0, 0, width, height, 1); - - dst_surface->MarkAsModified(true, *this); -} - -MICROPROFILE_DEFINE(OpenGL_CopySurface, "OpenGL", "CopySurface", MP_RGB(128, 192, 64)); -void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surface& dst_surface, - const GLuint copy_pbo_handle, const GLenum src_attachment, - const GLenum dst_attachment, - const std::size_t cubemap_face) { - MICROPROFILE_SCOPE(OpenGL_CopySurface); - ASSERT_MSG(dst_attachment == 0, "Unimplemented"); - - const auto& src_params{src_surface->GetSurfaceParams()}; - const auto& dst_params{dst_surface->GetSurfaceParams()}; - - const auto source_format = GetFormatTuple(src_params.pixel_format, src_params.component_type); - const auto dest_format = GetFormatTuple(dst_params.pixel_format, dst_params.component_type); - - const std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes); - - glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle); - glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_COPY); - if (source_format.compressed) { - glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment, - static_cast<GLsizei>(src_params.size_in_bytes), nullptr); - } else { - glGetTextureImage(src_surface->Texture().handle, src_attachment, source_format.format, - source_format.type, static_cast<GLsizei>(src_params.size_in_bytes), - nullptr); - } - // If the new texture is bigger than the previous one, we need to fill in the rest with data - // from the CPU. - if (src_params.size_in_bytes < dst_params.size_in_bytes) { - // Upload the rest of the memory. - if (dst_params.is_tiled) { - // TODO(Subv): We might have to de-tile the subtexture and re-tile it with the rest - // of the data in this case. Games like Super Mario Odyssey seem to hit this case - // when drawing, it re-uses the memory of a previous texture as a bigger framebuffer - // but it doesn't clear it beforehand, the texture is already full of zeros. - LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during " - "reinterpretation but the texture is tiled."); - } - const std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes; - auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; - glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size, - memory_manager.GetPointer(dst_params.gpu_addr + src_params.size_in_bytes)); - } - - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - - const GLsizei width{static_cast<GLsizei>( - std::min(src_params.GetRect().GetWidth(), dst_params.GetRect().GetWidth()))}; - const GLsizei height{static_cast<GLsizei>( - std::min(src_params.GetRect().GetHeight(), dst_params.GetRect().GetHeight()))}; - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, copy_pbo_handle); - if (dest_format.compressed) { - LOG_CRITICAL(HW_GPU, "Compressed copy is unimplemented!"); - UNREACHABLE(); - } else { - switch (dst_params.target) { - case SurfaceTarget::Texture1D: - glTextureSubImage1D(dst_surface->Texture().handle, 0, 0, width, dest_format.format, - dest_format.type, nullptr); - break; - case SurfaceTarget::Texture2D: - glTextureSubImage2D(dst_surface->Texture().handle, 0, 0, 0, width, height, - dest_format.format, dest_format.type, nullptr); - break; - case SurfaceTarget::Texture3D: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glTextureSubImage3D(dst_surface->Texture().handle, 0, 0, 0, 0, width, height, - static_cast<GLsizei>(dst_params.depth), dest_format.format, - dest_format.type, nullptr); - break; - case SurfaceTarget::TextureCubemap: - glTextureSubImage3D(dst_surface->Texture().handle, 0, 0, 0, - static_cast<GLint>(cubemap_face), width, height, 1, - dest_format.format, dest_format.type, nullptr); - break; - default: - LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", - static_cast<u32>(dst_params.target)); - UNREACHABLE(); - } - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - } - - dst_surface->MarkAsModified(true, *this); -} - -CachedSurface::CachedSurface(const SurfaceParams& params) - : RasterizerCacheObject{params.host_ptr}, params{params}, - gl_target{SurfaceTargetToGL(params.target)}, cached_size_in_bytes{params.size_in_bytes} { - - const auto optional_cpu_addr{ - Core::System::GetInstance().GPU().MemoryManager().GpuToCpuAddress(params.gpu_addr)}; - ASSERT_MSG(optional_cpu_addr, "optional_cpu_addr is invalid"); - cpu_addr = *optional_cpu_addr; - - texture.Create(gl_target); - - // TODO(Rodrigo): Using params.GetRect() returns a different size than using its Mip*(0) - // alternatives. This signals a bug on those functions. - const auto width = static_cast<GLsizei>(params.MipWidth(0)); - const auto height = static_cast<GLsizei>(params.MipHeight(0)); - memory_size = params.MemorySize(); - reinterpreted = false; - - const auto& format_tuple = GetFormatTuple(params.pixel_format, params.component_type); - gl_internal_format = format_tuple.internal_format; - - switch (params.target) { - case SurfaceTarget::Texture1D: - glTextureStorage1D(texture.handle, params.max_mip_level, format_tuple.internal_format, - width); - break; - case SurfaceTarget::Texture2D: - case SurfaceTarget::TextureCubemap: - glTextureStorage2D(texture.handle, params.max_mip_level, format_tuple.internal_format, - width, height); - break; - case SurfaceTarget::Texture3D: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glTextureStorage3D(texture.handle, params.max_mip_level, format_tuple.internal_format, - width, height, params.depth); - break; - default: - LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", - static_cast<u32>(params.target)); - UNREACHABLE(); - glTextureStorage2D(texture.handle, params.max_mip_level, format_tuple.internal_format, - width, height); - } - - ApplyTextureDefaults(texture.handle, params.max_mip_level); - - OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.gpu_addr, params.IdentityString()); -} - -MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64)); -void CachedSurface::LoadGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem) { - MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); - auto& gl_buffer = res_cache_tmp_mem.gl_buffer; - if (gl_buffer.size() < params.max_mip_level) - gl_buffer.resize(params.max_mip_level); - for (u32 i = 0; i < params.max_mip_level; i++) - gl_buffer[i].resize(params.GetMipmapSizeGL(i)); - if (params.is_tiled) { - ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}", - params.block_width, static_cast<u32>(params.target)); - for (u32 i = 0; i < params.max_mip_level; i++) - SwizzleFunc(MortonSwizzleMode::MortonToLinear, params, gl_buffer[i], i); - } else { - const u32 bpp = params.GetFormatBpp() / 8; - const u32 copy_size = (params.width * bpp + GetDefaultBlockWidth(params.pixel_format) - 1) / - GetDefaultBlockWidth(params.pixel_format); - if (params.pitch == copy_size) { - std::memcpy(gl_buffer[0].data(), params.host_ptr, params.size_in_bytes_gl); - } else { - const u32 height = (params.height + GetDefaultBlockHeight(params.pixel_format) - 1) / - GetDefaultBlockHeight(params.pixel_format); - const u8* start{params.host_ptr}; - u8* write_to = gl_buffer[0].data(); - for (u32 h = height; h > 0; h--) { - std::memcpy(write_to, start, copy_size); - start += params.pitch; - write_to += copy_size; - } - } - } - for (u32 i = 0; i < params.max_mip_level; i++) { - const u32 width = params.MipWidth(i); - const u32 height = params.MipHeight(i); - const u32 depth = params.MipDepth(i); - if (VideoCore::Surface::IsPixelFormatASTC(params.pixel_format)) { - // Reserve size for RGBA8 conversion - constexpr std::size_t rgba_bpp = 4; - gl_buffer[i].resize(std::max(gl_buffer[i].size(), width * height * depth * rgba_bpp)); - } - Tegra::Texture::ConvertFromGuestToHost(gl_buffer[i].data(), params.pixel_format, width, - height, depth, true, true); - } -} - -MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); -void CachedSurface::FlushGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem) { - MICROPROFILE_SCOPE(OpenGL_SurfaceFlush); - - ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented"); - - auto& gl_buffer = res_cache_tmp_mem.gl_buffer; - // OpenGL temporary buffer needs to be big enough to store raw texture size - gl_buffer[0].resize(GetSizeInBytes()); - - const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); - const u32 align = std::clamp(params.RowAlign(0), 1U, 8U); - glPixelStorei(GL_PACK_ALIGNMENT, align); - glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width)); - ASSERT(!tuple.compressed); - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - glGetTextureImage(texture.handle, 0, tuple.format, tuple.type, - static_cast<GLsizei>(gl_buffer[0].size()), gl_buffer[0].data()); - glPixelStorei(GL_PACK_ROW_LENGTH, 0); - Tegra::Texture::ConvertFromHostToGuest(gl_buffer[0].data(), params.pixel_format, params.width, - params.height, params.depth, true, true); - if (params.is_tiled) { - ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}", - params.block_width, static_cast<u32>(params.target)); - - SwizzleFunc(MortonSwizzleMode::LinearToMorton, params, gl_buffer[0], 0); - } else { - const u32 bpp = params.GetFormatBpp() / 8; - const u32 copy_size = params.width * bpp; - if (params.pitch == copy_size) { - std::memcpy(params.host_ptr, gl_buffer[0].data(), GetSizeInBytes()); - } else { - u8* start{params.host_ptr}; - const u8* read_to = gl_buffer[0].data(); - for (u32 h = params.height; h > 0; h--) { - std::memcpy(start, read_to, copy_size); - start += params.pitch; - read_to += copy_size; - } - } - } -} - -void CachedSurface::UploadGLMipmapTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, u32 mip_map, - GLuint read_fb_handle, GLuint draw_fb_handle) { - const auto& rect{params.GetRect(mip_map)}; - - auto& gl_buffer = res_cache_tmp_mem.gl_buffer; - - // Load data from memory to the surface - const auto x0 = static_cast<GLint>(rect.left); - const auto y0 = static_cast<GLint>(rect.bottom); - auto buffer_offset = - static_cast<std::size_t>(static_cast<std::size_t>(y0) * params.MipWidth(mip_map) + - static_cast<std::size_t>(x0)) * - GetBytesPerPixel(params.pixel_format); - - const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); - - const u32 align = std::clamp(params.RowAlign(mip_map), 1U, 8U); - glPixelStorei(GL_UNPACK_ALIGNMENT, align); - glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.MipWidth(mip_map))); - - const auto image_size = static_cast<GLsizei>(params.GetMipmapSizeGL(mip_map, false)); - if (tuple.compressed) { - switch (params.target) { - case SurfaceTarget::Texture2D: - glCompressedTextureSubImage2D( - texture.handle, mip_map, 0, 0, static_cast<GLsizei>(params.MipWidth(mip_map)), - static_cast<GLsizei>(params.MipHeight(mip_map)), tuple.internal_format, image_size, - &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::Texture3D: - glCompressedTextureSubImage3D( - texture.handle, mip_map, 0, 0, 0, static_cast<GLsizei>(params.MipWidth(mip_map)), - static_cast<GLsizei>(params.MipHeight(mip_map)), - static_cast<GLsizei>(params.MipDepth(mip_map)), tuple.internal_format, image_size, - &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glCompressedTextureSubImage3D( - texture.handle, mip_map, 0, 0, 0, static_cast<GLsizei>(params.MipWidth(mip_map)), - static_cast<GLsizei>(params.MipHeight(mip_map)), static_cast<GLsizei>(params.depth), - tuple.internal_format, image_size, &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::TextureCubemap: { - const auto layer_size = static_cast<GLsizei>(params.LayerSizeGL(mip_map)); - for (std::size_t face = 0; face < params.depth; ++face) { - glCompressedTextureSubImage3D( - texture.handle, mip_map, 0, 0, static_cast<GLint>(face), - static_cast<GLsizei>(params.MipWidth(mip_map)), - static_cast<GLsizei>(params.MipHeight(mip_map)), 1, tuple.internal_format, - layer_size, &gl_buffer[mip_map][buffer_offset]); - buffer_offset += layer_size; - } - break; - } - default: - LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", - static_cast<u32>(params.target)); - UNREACHABLE(); - glCompressedTextureSubImage2D( - texture.handle, mip_map, 0, 0, static_cast<GLsizei>(params.MipWidth(mip_map)), - static_cast<GLsizei>(params.MipHeight(mip_map)), tuple.internal_format, - static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[mip_map][buffer_offset]); - } - } else { - switch (params.target) { - case SurfaceTarget::Texture1D: - glTextureSubImage1D(texture.handle, mip_map, x0, static_cast<GLsizei>(rect.GetWidth()), - tuple.format, tuple.type, &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::Texture2D: - glTextureSubImage2D(texture.handle, mip_map, x0, y0, - static_cast<GLsizei>(rect.GetWidth()), - static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type, - &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::Texture3D: - glTextureSubImage3D(texture.handle, mip_map, x0, y0, 0, - static_cast<GLsizei>(rect.GetWidth()), - static_cast<GLsizei>(rect.GetHeight()), params.MipDepth(mip_map), - tuple.format, tuple.type, &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glTextureSubImage3D(texture.handle, mip_map, x0, y0, 0, - static_cast<GLsizei>(rect.GetWidth()), - static_cast<GLsizei>(rect.GetHeight()), params.depth, tuple.format, - tuple.type, &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::TextureCubemap: { - for (std::size_t face = 0; face < params.depth; ++face) { - glTextureSubImage3D(texture.handle, mip_map, x0, y0, static_cast<GLint>(face), - static_cast<GLsizei>(rect.GetWidth()), - static_cast<GLsizei>(rect.GetHeight()), 1, tuple.format, - tuple.type, &gl_buffer[mip_map][buffer_offset]); - buffer_offset += params.LayerSizeGL(mip_map); - } - break; - } - default: - LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", - static_cast<u32>(params.target)); - UNREACHABLE(); - glTextureSubImage2D(texture.handle, mip_map, x0, y0, - static_cast<GLsizei>(rect.GetWidth()), - static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type, - &gl_buffer[mip_map][buffer_offset]); - } - } - - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); -} - -void CachedSurface::EnsureTextureDiscrepantView() { - if (discrepant_view.handle != 0) - return; - - const GLenum target{GetArrayDiscrepantTarget(params.target)}; - ASSERT(target != GL_NONE); - - const GLuint num_layers{target == GL_TEXTURE_CUBE_MAP_ARRAY ? 6u : 1u}; - constexpr GLuint min_layer = 0; - constexpr GLuint min_level = 0; - - glGenTextures(1, &discrepant_view.handle); - glTextureView(discrepant_view.handle, target, texture.handle, gl_internal_format, min_level, - params.max_mip_level, min_layer, num_layers); - ApplyTextureDefaults(discrepant_view.handle, params.max_mip_level); - glTextureParameteriv(discrepant_view.handle, GL_TEXTURE_SWIZZLE_RGBA, - reinterpret_cast<const GLint*>(swizzle.data())); -} - -MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64)); -void CachedSurface::UploadGLTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, - GLuint read_fb_handle, GLuint draw_fb_handle) { - MICROPROFILE_SCOPE(OpenGL_TextureUL); - - for (u32 i = 0; i < params.max_mip_level; i++) - UploadGLMipmapTexture(res_cache_tmp_mem, i, read_fb_handle, draw_fb_handle); -} - -void CachedSurface::UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x, - Tegra::Texture::SwizzleSource swizzle_y, - Tegra::Texture::SwizzleSource swizzle_z, - Tegra::Texture::SwizzleSource swizzle_w) { - const GLenum new_x = MaxwellToGL::SwizzleSource(swizzle_x); - const GLenum new_y = MaxwellToGL::SwizzleSource(swizzle_y); - const GLenum new_z = MaxwellToGL::SwizzleSource(swizzle_z); - const GLenum new_w = MaxwellToGL::SwizzleSource(swizzle_w); - if (swizzle[0] == new_x && swizzle[1] == new_y && swizzle[2] == new_z && swizzle[3] == new_w) { - return; - } - swizzle = {new_x, new_y, new_z, new_w}; - const auto swizzle_data = reinterpret_cast<const GLint*>(swizzle.data()); - glTextureParameteriv(texture.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data); - if (discrepant_view.handle != 0) { - glTextureParameteriv(discrepant_view.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data); - } -} - -RasterizerCacheOpenGL::RasterizerCacheOpenGL(RasterizerOpenGL& rasterizer) - : RasterizerCache{rasterizer} { - read_framebuffer.Create(); - draw_framebuffer.Create(); - copy_pbo.Create(); -} - -Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextureInfo& config, - const GLShader::SamplerEntry& entry) { - return GetSurface(SurfaceParams::CreateForTexture(config, entry)); -} - -Surface RasterizerCacheOpenGL::GetDepthBufferSurface(bool preserve_contents) { - auto& gpu{Core::System::GetInstance().GPU().Maxwell3D()}; - const auto& regs{gpu.regs}; - - if (!gpu.dirty_flags.zeta_buffer) { - return last_depth_buffer; - } - gpu.dirty_flags.zeta_buffer = false; - - if (!regs.zeta.Address() || !regs.zeta_enable) { - return last_depth_buffer = {}; - } - - SurfaceParams depth_params{SurfaceParams::CreateForDepthBuffer( - regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format, - regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, - regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; - - return last_depth_buffer = GetSurface(depth_params, preserve_contents); -} - -Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool preserve_contents) { - auto& gpu{Core::System::GetInstance().GPU().Maxwell3D()}; - const auto& regs{gpu.regs}; - - if (!gpu.dirty_flags.color_buffer[index]) { - return current_color_buffers[index]; - } - gpu.dirty_flags.color_buffer.reset(index); - - ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); - - if (index >= regs.rt_control.count) { - return current_color_buffers[index] = {}; - } - - if (regs.rt[index].Address() == 0 || regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { - return current_color_buffers[index] = {}; - } - - const SurfaceParams color_params{SurfaceParams::CreateForFramebuffer(index)}; - - return current_color_buffers[index] = GetSurface(color_params, preserve_contents); -} - -void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) { - surface->LoadGLBuffer(temporal_memory); - surface->UploadGLTexture(temporal_memory, read_framebuffer.handle, draw_framebuffer.handle); - surface->MarkAsModified(false, *this); - surface->MarkForReload(false); -} - -Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) { - if (!params.IsValid()) { - return {}; - } - - // Look up surface in the cache based on address - Surface surface{TryGet(params.host_ptr)}; - if (surface) { - if (surface->GetSurfaceParams().IsCompatibleSurface(params)) { - // Use the cached surface as-is unless it's not synced with memory - if (surface->MustReload()) - LoadSurface(surface); - return surface; - } else if (preserve_contents) { - // If surface parameters changed and we care about keeping the previous data, recreate - // the surface from the old one - Surface new_surface{RecreateSurface(surface, params)}; - Unregister(surface); - Register(new_surface); - if (new_surface->IsUploaded()) { - RegisterReinterpretSurface(new_surface); - } - return new_surface; - } else { - // Delete the old surface before creating a new one to prevent collisions. - Unregister(surface); - } - } - - // No cached surface found - get a new one - surface = GetUncachedSurface(params); - Register(surface); - - // Only load surface from memory if we care about the contents - if (preserve_contents) { - LoadSurface(surface); - } - - return surface; -} - -Surface RasterizerCacheOpenGL::GetUncachedSurface(const SurfaceParams& params) { - Surface surface{TryGetReservedSurface(params)}; - if (!surface) { - // No reserved surface available, create a new one and reserve it - surface = std::make_shared<CachedSurface>(params); - ReserveSurface(surface); - } - return surface; -} - -void RasterizerCacheOpenGL::FastLayeredCopySurface(const Surface& src_surface, - const Surface& dst_surface) { - const auto& init_params{src_surface->GetSurfaceParams()}; - const auto& dst_params{dst_surface->GetSurfaceParams()}; - auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; - GPUVAddr address{init_params.gpu_addr}; - const std::size_t layer_size{dst_params.LayerMemorySize()}; - for (u32 layer = 0; layer < dst_params.depth; layer++) { - for (u32 mipmap = 0; mipmap < dst_params.max_mip_level; mipmap++) { - const GPUVAddr sub_address{address + dst_params.GetMipmapLevelOffset(mipmap)}; - const Surface& copy{TryGet(memory_manager.GetPointer(sub_address))}; - if (!copy) { - continue; - } - const auto& src_params{copy->GetSurfaceParams()}; - const u32 width{std::min(src_params.width, dst_params.MipWidth(mipmap))}; - const u32 height{std::min(src_params.height, dst_params.MipHeight(mipmap))}; - - glCopyImageSubData(copy->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, - 0, 0, dst_surface->Texture().handle, - SurfaceTargetToGL(dst_params.target), mipmap, 0, 0, layer, width, - height, 1); - } - address += layer_size; - } - - dst_surface->MarkAsModified(true, *this); -} - -static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface, - const Common::Rectangle<u32>& src_rect, - const Common::Rectangle<u32>& dst_rect, GLuint read_fb_handle, - GLuint draw_fb_handle, GLenum src_attachment = 0, GLenum dst_attachment = 0, - std::size_t cubemap_face = 0) { - - const auto& src_params{src_surface->GetSurfaceParams()}; - const auto& dst_params{dst_surface->GetSurfaceParams()}; - - OpenGLState prev_state{OpenGLState::GetCurState()}; - SCOPE_EXIT({ prev_state.Apply(); }); - - OpenGLState state; - state.draw.read_framebuffer = read_fb_handle; - state.draw.draw_framebuffer = draw_fb_handle; - state.Apply(); - - u32 buffers{}; - - if (src_params.type == SurfaceType::ColorTexture) { - switch (src_params.target) { - case SurfaceTarget::Texture2D: - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, src_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - case SurfaceTarget::TextureCubemap: - glFramebufferTexture2D( - GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), - src_surface->Texture().handle, 0); - glFramebufferTexture2D( - GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), 0, 0); - break; - case SurfaceTarget::Texture2DArray: - glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - src_surface->Texture().handle, 0, 0); - glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 0, 0, 0); - break; - case SurfaceTarget::Texture3D: - glFramebufferTexture3D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - SurfaceTargetToGL(src_params.target), - src_surface->Texture().handle, 0, 0); - glFramebufferTexture3D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - SurfaceTargetToGL(src_params.target), 0, 0, 0); - break; - default: - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, src_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - } - - switch (dst_params.target) { - case SurfaceTarget::Texture2D: - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, dst_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - case SurfaceTarget::TextureCubemap: - glFramebufferTexture2D( - GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), - dst_surface->Texture().handle, 0); - glFramebufferTexture2D( - GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), 0, 0); - break; - case SurfaceTarget::Texture2DArray: - glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - dst_surface->Texture().handle, 0, 0); - glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 0, 0, 0); - break; - - case SurfaceTarget::Texture3D: - glFramebufferTexture3D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - SurfaceTargetToGL(dst_params.target), - dst_surface->Texture().handle, 0, 0); - glFramebufferTexture3D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - SurfaceTargetToGL(dst_params.target), 0, 0, 0); - break; - default: - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, dst_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - } - - buffers = GL_COLOR_BUFFER_BIT; - } else if (src_params.type == SurfaceType::Depth) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - src_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - dst_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - - buffers = GL_DEPTH_BUFFER_BIT; - } else if (src_params.type == SurfaceType::DepthStencil) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - src_surface->Texture().handle, 0); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - dst_surface->Texture().handle, 0); - - buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; - } - - glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom, dst_rect.left, - dst_rect.top, dst_rect.right, dst_rect.bottom, buffers, - buffers == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST); - - return true; -} - -void RasterizerCacheOpenGL::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) { - - const auto& src_params = SurfaceParams::CreateForFermiCopySurface(src_config); - const auto& dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config); - - ASSERT(src_params.pixel_format == dst_params.pixel_format); - ASSERT(src_params.block_height == dst_params.block_height); - ASSERT(src_params.is_tiled == dst_params.is_tiled); - ASSERT(src_params.depth == dst_params.depth); - ASSERT(src_params.target == dst_params.target); - ASSERT(src_params.rt.index == dst_params.rt.index); - - auto src_surface = GetSurface(src_params, true); - auto dst_surface = GetSurface(dst_params, true); - - BlitSurface(src_surface, dst_surface, src_rect, dst_rect, read_framebuffer.handle, - draw_framebuffer.handle); - - dst_surface->MarkAsModified(true, *this); -} - -void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface, - const Surface& dst_surface) { - const auto& src_params{src_surface->GetSurfaceParams()}; - const auto& dst_params{dst_surface->GetSurfaceParams()}; - - // Flush enough memory for both the source and destination surface - FlushRegion(ToCacheAddr(src_params.host_ptr), - std::max(src_params.MemorySize(), dst_params.MemorySize())); - - LoadSurface(dst_surface); -} - -Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, - const SurfaceParams& new_params) { - // Verify surface is compatible for blitting - auto old_params{old_surface->GetSurfaceParams()}; - - // Get a new surface with the new parameters, and blit the previous surface to it - Surface new_surface{GetUncachedSurface(new_params)}; - - // With use_accurate_gpu_emulation enabled, do an accurate surface copy - if (Settings::values.use_accurate_gpu_emulation) { - AccurateCopySurface(old_surface, new_surface); - return new_surface; - } - - const bool old_compressed = - GetFormatTuple(old_params.pixel_format, old_params.component_type).compressed; - const bool new_compressed = - GetFormatTuple(new_params.pixel_format, new_params.component_type).compressed; - const bool compatible_formats = - GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format) && - !(old_compressed || new_compressed); - // For compatible surfaces, we can just do fast glCopyImageSubData based copy - if (old_params.target == new_params.target && old_params.depth == new_params.depth && - old_params.depth == 1 && compatible_formats) { - FastCopySurface(old_surface, new_surface); - return new_surface; - } - - switch (new_params.target) { - case SurfaceTarget::Texture2D: - CopySurface(old_surface, new_surface, copy_pbo.handle); - break; - case SurfaceTarget::Texture3D: - AccurateCopySurface(old_surface, new_surface); - break; - case SurfaceTarget::TextureCubemap: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - if (compatible_formats) - FastLayeredCopySurface(old_surface, new_surface); - else { - AccurateCopySurface(old_surface, new_surface); - } - break; - default: - LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", - static_cast<u32>(new_params.target)); - UNREACHABLE(); - } - - return new_surface; -} - -Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(const u8* host_ptr) const { - return TryGet(host_ptr); -} - -void RasterizerCacheOpenGL::ReserveSurface(const Surface& surface) { - const auto& surface_reserve_key{SurfaceReserveKey::Create(surface->GetSurfaceParams())}; - surface_reserve[surface_reserve_key] = surface; -} - -Surface RasterizerCacheOpenGL::TryGetReservedSurface(const SurfaceParams& params) { - const auto& surface_reserve_key{SurfaceReserveKey::Create(params)}; - auto search{surface_reserve.find(surface_reserve_key)}; - if (search != surface_reserve.end()) { - return search->second; - } - return {}; -} - -static std::optional<u32> TryFindBestMipMap(std::size_t memory, const SurfaceParams params, - u32 height) { - for (u32 i = 0; i < params.max_mip_level; i++) { - if (memory == params.GetMipmapSingleSize(i) && params.MipHeight(i) == height) { - return {i}; - } - } - return {}; -} - -static std::optional<u32> TryFindBestLayer(GPUVAddr addr, const SurfaceParams params, u32 mipmap) { - const std::size_t size{params.LayerMemorySize()}; - GPUVAddr start{params.gpu_addr + params.GetMipmapLevelOffset(mipmap)}; - for (u32 i = 0; i < params.depth; i++) { - if (start == addr) { - return {i}; - } - start += size; - } - return {}; -} - -static bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surface render_surface, - const Surface blitted_surface) { - const auto& dst_params = blitted_surface->GetSurfaceParams(); - const auto& src_params = render_surface->GetSurfaceParams(); - const std::size_t src_memory_size = src_params.size_in_bytes; - const std::optional<u32> level = - TryFindBestMipMap(src_memory_size, dst_params, src_params.height); - if (level.has_value()) { - if (src_params.width == dst_params.MipWidthGobAligned(*level) && - src_params.height == dst_params.MipHeight(*level) && - src_params.block_height >= dst_params.MipBlockHeight(*level)) { - const std::optional<u32> slot = - TryFindBestLayer(render_surface->GetSurfaceParams().gpu_addr, dst_params, *level); - if (slot.has_value()) { - glCopyImageSubData(render_surface->Texture().handle, - SurfaceTargetToGL(src_params.target), 0, 0, 0, 0, - blitted_surface->Texture().handle, - SurfaceTargetToGL(dst_params.target), *level, 0, 0, *slot, - dst_params.MipWidth(*level), dst_params.MipHeight(*level), 1); - blitted_surface->MarkAsModified(true, cache); - return true; - } - } - } - return false; -} - -static bool IsReinterpretInvalid(const Surface render_surface, const Surface blitted_surface) { - const VAddr bound1 = blitted_surface->GetCpuAddr() + blitted_surface->GetMemorySize(); - const VAddr bound2 = render_surface->GetCpuAddr() + render_surface->GetMemorySize(); - if (bound2 > bound1) - return true; - const auto& dst_params = blitted_surface->GetSurfaceParams(); - const auto& src_params = render_surface->GetSurfaceParams(); - return (dst_params.component_type != src_params.component_type); -} - -static bool IsReinterpretInvalidSecond(const Surface render_surface, - const Surface blitted_surface) { - const auto& dst_params = blitted_surface->GetSurfaceParams(); - const auto& src_params = render_surface->GetSurfaceParams(); - return (dst_params.height > src_params.height && dst_params.width > src_params.width); -} - -bool RasterizerCacheOpenGL::PartialReinterpretSurface(Surface triggering_surface, - Surface intersect) { - if (IsReinterpretInvalid(triggering_surface, intersect)) { - Unregister(intersect); - return false; - } - if (!LayerFitReinterpretSurface(*this, triggering_surface, intersect)) { - if (IsReinterpretInvalidSecond(triggering_surface, intersect)) { - Unregister(intersect); - return false; - } - FlushObject(intersect); - FlushObject(triggering_surface); - intersect->MarkForReload(true); - } - return true; -} - -void RasterizerCacheOpenGL::SignalPreDrawCall() { - if (texception && GLAD_GL_ARB_texture_barrier) { - glTextureBarrier(); - } - texception = false; -} - -void RasterizerCacheOpenGL::SignalPostDrawCall() { - for (u32 i = 0; i < Maxwell::NumRenderTargets; i++) { - if (current_color_buffers[i] != nullptr) { - Surface intersect = - CollideOnReinterpretedSurface(current_color_buffers[i]->GetCacheAddr()); - if (intersect != nullptr) { - PartialReinterpretSurface(current_color_buffers[i], intersect); - texception = true; - } - } - } -} - -} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h deleted file mode 100644 index 6263ef3e7..000000000 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ /dev/null @@ -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 diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index bfe666a73..5c96c1d46 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp @@ -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; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index fbb93ee49..3a85a1d4c 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -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; }; diff --git a/src/video_core/renderer_opengl/gl_sampler_cache.h b/src/video_core/renderer_opengl/gl_sampler_cache.h index defbc2d81..34ee37f00 100644 --- a/src/video_core/renderer_opengl/gl_sampler_cache.h +++ b/src/video_core/renderer_opengl/gl_sampler_cache.h @@ -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 diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index ac8a9e6b7..42ca3b1bd 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -23,13 +23,13 @@ namespace OpenGL { using VideoCommon::Shader::ProgramCode; -// One UBO is always reserved for emulation values -constexpr u32 RESERVED_UBOS = 1; +// One UBO is always reserved for emulation values on staged shaders +constexpr u32 STAGE_RESERVED_UBOS = 1; struct UnspecializedShader { std::string code; GLShader::ShaderEntries entries; - Maxwell::ShaderProgram program_type; + ProgramType program_type; }; namespace { @@ -55,15 +55,17 @@ ProgramCode GetShaderCode(Tegra::MemoryManager& memory_manager, const GPUVAddr g } /// Gets the shader type from a Maxwell program type -constexpr GLenum GetShaderType(Maxwell::ShaderProgram program_type) { +constexpr GLenum GetShaderType(ProgramType program_type) { switch (program_type) { - case Maxwell::ShaderProgram::VertexA: - case Maxwell::ShaderProgram::VertexB: + case ProgramType::VertexA: + case ProgramType::VertexB: return GL_VERTEX_SHADER; - case Maxwell::ShaderProgram::Geometry: + case ProgramType::Geometry: return GL_GEOMETRY_SHADER; - case Maxwell::ShaderProgram::Fragment: + case ProgramType::Fragment: return GL_FRAGMENT_SHADER; + case ProgramType::Compute: + return GL_COMPUTE_SHADER; default: return GL_NONE; } @@ -100,18 +102,44 @@ constexpr std::tuple<const char*, const char*, u32> GetPrimitiveDescription(GLen } } +ProgramType GetProgramType(Maxwell::ShaderProgram program) { + switch (program) { + case Maxwell::ShaderProgram::VertexA: + return ProgramType::VertexA; + case Maxwell::ShaderProgram::VertexB: + return ProgramType::VertexB; + case Maxwell::ShaderProgram::TesselationControl: + return ProgramType::TessellationControl; + case Maxwell::ShaderProgram::TesselationEval: + return ProgramType::TessellationEval; + case Maxwell::ShaderProgram::Geometry: + return ProgramType::Geometry; + case Maxwell::ShaderProgram::Fragment: + return ProgramType::Fragment; + } + UNREACHABLE(); + return {}; +} + /// Calculates the size of a program stream std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) { constexpr std::size_t start_offset = 10; + // This is the encoded version of BRA that jumps to itself. All Nvidia + // shaders end with one. + constexpr u64 self_jumping_branch = 0xE2400FFFFF07000FULL; + constexpr u64 mask = 0xFFFFFFFFFF7FFFFFULL; std::size_t offset = start_offset; std::size_t size = start_offset * sizeof(u64); while (offset < program.size()) { const u64 instruction = program[offset]; if (!IsSchedInstruction(offset, start_offset)) { - if (instruction == 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++; @@ -121,11 +149,13 @@ std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) { } /// Hashes one (or two) program streams -u64 GetUniqueIdentifier(Maxwell::ShaderProgram program_type, const ProgramCode& code, - const ProgramCode& code_b) { - u64 unique_identifier = - Common::CityHash64(reinterpret_cast<const char*>(code.data()), CalculateProgramSize(code)); - if (program_type != Maxwell::ShaderProgram::VertexA) { +u64 GetUniqueIdentifier(ProgramType program_type, const ProgramCode& code, + const ProgramCode& code_b, std::size_t size_a = 0, std::size_t size_b = 0) { + if (size_a == 0) { + size_a = CalculateProgramSize(code); + } + u64 unique_identifier = Common::CityHash64(reinterpret_cast<const char*>(code.data()), size_a); + if (program_type != ProgramType::VertexA) { return unique_identifier; } // VertexA programs include two programs @@ -133,46 +163,70 @@ u64 GetUniqueIdentifier(Maxwell::ShaderProgram program_type, const ProgramCode& std::size_t seed = 0; boost::hash_combine(seed, unique_identifier); - const u64 identifier_b = Common::CityHash64(reinterpret_cast<const char*>(code_b.data()), - CalculateProgramSize(code_b)); + if (size_b == 0) { + size_b = CalculateProgramSize(code_b); + } + const u64 identifier_b = + Common::CityHash64(reinterpret_cast<const char*>(code_b.data()), size_b); boost::hash_combine(seed, identifier_b); return static_cast<u64>(seed); } /// Creates an unspecialized program from code streams -GLShader::ProgramResult CreateProgram(const Device& device, Maxwell::ShaderProgram program_type, +GLShader::ProgramResult CreateProgram(const Device& device, ProgramType program_type, ProgramCode program_code, ProgramCode program_code_b) { GLShader::ShaderSetup setup(program_code); - if (program_type == Maxwell::ShaderProgram::VertexA) { + setup.program.size_a = CalculateProgramSize(program_code); + setup.program.size_b = 0; + if (program_type == ProgramType::VertexA) { // VertexB is always enabled, so when VertexA is enabled, we have two vertex shaders. // Conventional HW does not support this, so we combine VertexA and VertexB into one // stage here. setup.SetProgramB(program_code_b); + setup.program.size_b = CalculateProgramSize(program_code_b); } - setup.program.unique_identifier = - GetUniqueIdentifier(program_type, program_code, program_code_b); + setup.program.unique_identifier = GetUniqueIdentifier( + program_type, program_code, program_code_b, setup.program.size_a, setup.program.size_b); switch (program_type) { - case Maxwell::ShaderProgram::VertexA: - case Maxwell::ShaderProgram::VertexB: + case ProgramType::VertexA: + case ProgramType::VertexB: return GLShader::GenerateVertexShader(device, setup); - case Maxwell::ShaderProgram::Geometry: + case ProgramType::Geometry: return GLShader::GenerateGeometryShader(device, setup); - case Maxwell::ShaderProgram::Fragment: + case ProgramType::Fragment: return GLShader::GenerateFragmentShader(device, setup); + case ProgramType::Compute: + return GLShader::GenerateComputeShader(device, setup); default: - LOG_CRITICAL(HW_GPU, "Unimplemented program_type={}", static_cast<u32>(program_type)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented program_type={}", static_cast<u32>(program_type)); return {}; } } CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEntries& entries, - Maxwell::ShaderProgram program_type, BaseBindings base_bindings, - GLenum primitive_mode, bool hint_retrievable = false) { - 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++); + ProgramType 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 = R"(#version 430 core +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shader_viewport_layer_array : enable +#extension GL_EXT_shader_image_load_formatted : enable +#extension GL_NV_gpu_shader5 : enable +#extension GL_NV_shader_thread_group : enable +#extension GL_NV_shader_thread_shuffle : enable +)"; + if (program_type == ProgramType::Compute) { + source += "#extension GL_ARB_compute_variable_group_size : require\n"; + } + source += '\n'; + + if (program_type != ProgramType::Compute) { + source += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++); + } for (const auto& cbuf : entries.const_buffers) { source += @@ -186,15 +240,34 @@ 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\n", i); + } + if (texture_buffer_usage.any()) { + source += '\n'; + } - if (program_type == Maxwell::ShaderProgram::Geometry) { + if (program_type == ProgramType::Geometry) { const auto [glsl_topology, debug_name, max_vertices] = GetPrimitiveDescription(primitive_mode); - source += "layout (" + std::string(glsl_topology) + ") in;\n"; + source += "layout (" + std::string(glsl_topology) + ") in;\n\n"; source += "#define MAX_VERTEX_INPUT " + std::to_string(max_vertices) + '\n'; } + if (program_type == ProgramType::Compute) { + source += "layout (local_size_variable) in;\n"; + } + source += '\n'; source += code; OGLShader shader; @@ -221,131 +294,97 @@ 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, ProgramType program_type, + GLShader::ProgramResult result) + : RasterizerCacheObject{params.host_ptr}, cpu_addr{params.cpu_addr}, + unique_identifier{params.unique_identifier}, program_type{program_type}, + disk_cache{params.disk_cache}, precompiled_programs{params.precompiled_programs}, + entries{result.second}, code{std::move(result.first)}, shader_length{entries.shader_length} {} + +Shader CachedShader::CreateStageFromMemory(const ShaderParameters& params, + Maxwell::ShaderProgram program_type, + ProgramCode&& program_code, + ProgramCode&& program_code_b) { + const auto code_size{CalculateProgramSize(program_code)}; + const auto code_size_b{CalculateProgramSize(program_code_b)}; + auto result{ + CreateProgram(params.device, GetProgramType(program_type), program_code, program_code_b)}; + if (result.first.empty()) { // TODO(Rodrigo): Unimplemented shader stages hit here, avoid using these for now - return; + return {}; } - code = program_result.first; - entries = program_result.second; - shader_length = entries.shader_length; + params.disk_cache.SaveRaw(ShaderDiskCacheRaw( + params.unique_identifier, GetProgramType(program_type), + static_cast<u32>(code_size / sizeof(u64)), static_cast<u32>(code_size_b / sizeof(u64)), + std::move(program_code), std::move(program_code_b))); - 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, GetProgramType(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, GetProgramType(program_type), std::move(result))); } -std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive_mode, - BaseBindings base_bindings) { - GLuint handle{}; - if (program_type == Maxwell::ShaderProgram::Geometry) { - handle = GetGeometryShader(primitive_mode, base_bindings); - } else { - const auto [entry, is_cache_miss] = programs.try_emplace(base_bindings); - auto& program = entry->second; - if (is_cache_miss) { - program = TryLoadProgram(primitive_mode, base_bindings); - if (!program) { - program = - SpecializeShader(code, entries, program_type, base_bindings, primitive_mode); - disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings)); - } +Shader CachedShader::CreateKernelFromMemory(const ShaderParameters& params, ProgramCode&& code) { + auto result{CreateProgram(params.device, ProgramType::Compute, code, {})}; - LabelGLObject(GL_PROGRAM, program->handle, cpu_addr); - } + const auto code_size{CalculateProgramSize(code)}; + params.disk_cache.SaveRaw(ShaderDiskCacheRaw(params.unique_identifier, ProgramType::Compute, + static_cast<u32>(code_size / sizeof(u64)), 0, + std::move(code), {})); - handle = program->handle; - } - - 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()); + return std::shared_ptr<CachedShader>( + new CachedShader(params, ProgramType::Compute, std::move(result))); +} - return {handle, base_bindings}; +Shader CachedShader::CreateKernelFromCache(const ShaderParameters& params, + GLShader::ProgramResult result) { + return std::shared_ptr<CachedShader>( + new CachedShader(params, ProgramType::Compute, std::move(result))); } -GLuint CachedShader::GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings) { - const auto [entry, is_cache_miss] = geometry_programs.try_emplace(base_bindings); - auto& programs = entry->second; +std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVariant& variant) { + const auto [entry, is_cache_miss] = programs.try_emplace(variant); + auto& program = entry->second; + if (is_cache_miss) { + program = TryLoadProgram(variant); + if (!program) { + program = SpecializeShader(code, entries, program_type, variant); + disk_cache.SaveUsage(GetUsage(variant)); + } - switch (primitive_mode) { - case GL_POINTS: - return LazyGeometryProgram(programs.points, base_bindings, primitive_mode); - case GL_LINES: - case GL_LINE_STRIP: - return LazyGeometryProgram(programs.lines, base_bindings, primitive_mode); - case GL_LINES_ADJACENCY: - case GL_LINE_STRIP_ADJACENCY: - return LazyGeometryProgram(programs.lines_adjacency, base_bindings, primitive_mode); - case GL_TRIANGLES: - case GL_TRIANGLE_STRIP: - case GL_TRIANGLE_FAN: - return LazyGeometryProgram(programs.triangles, base_bindings, primitive_mode); - case GL_TRIANGLES_ADJACENCY: - case GL_TRIANGLE_STRIP_ADJACENCY: - return LazyGeometryProgram(programs.triangles_adjacency, base_bindings, primitive_mode); - default: - UNREACHABLE_MSG("Unknown primitive mode."); - return LazyGeometryProgram(programs.points, base_bindings, primitive_mode); + LabelGLObject(GL_PROGRAM, program->handle, cpu_addr); } -} -GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings, - GLenum primitive_mode) { - if (target_program) { - return target_program->handle; + auto base_bindings = variant.base_bindings; + base_bindings.cbuf += static_cast<u32>(entries.const_buffers.size()); + if (program_type != ProgramType::Compute) { + base_bindings.cbuf += STAGE_RESERVED_UBOS; } - const auto [glsl_name, debug_name, vertices] = GetPrimitiveDescription(primitive_mode); - target_program = TryLoadProgram(primitive_mode, base_bindings); - if (!target_program) { - target_program = - SpecializeShader(code, entries, program_type, base_bindings, primitive_mode); - disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings)); - } - - LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name); + base_bindings.gmem += static_cast<u32>(entries.global_memory_entries.size()); + base_bindings.sampler += static_cast<u32>(entries.samplers.size()); - return target_program->handle; -}; + return {program->handle, base_bindings}; +} -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 +450,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); @@ -547,7 +585,7 @@ std::unordered_map<u64, UnspecializedShader> ShaderCacheOpenGL::GenerateUnspecia } Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { - if (!system.GPU().Maxwell3D().dirty_flags.shaders) { + if (!system.GPU().Maxwell3D().dirty.shaders) { return last_shaders[static_cast<std::size_t>(program)]; } @@ -564,28 +602,55 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { // No shader found - create a new one ProgramCode program_code{GetShaderCode(memory_manager, program_addr, host_ptr)}; ProgramCode program_code_b; - if (program == Maxwell::ShaderProgram::VertexA) { + const bool is_program_a{program == Maxwell::ShaderProgram::VertexA}; + if (is_program_a) { const GPUVAddr program_addr_b{GetShaderAddress(system, Maxwell::ShaderProgram::VertexB)}; program_code_b = GetShaderCode(memory_manager, program_addr_b, memory_manager.GetPointer(program_addr_b)); } - 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(GetProgramType(program), program_code, program_code_b); + const auto cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)}; + const ShaderParameters params{disk_cache, precompiled_programs, device, cpu_addr, + host_ptr, unique_identifier}; + const auto 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); return last_shaders[static_cast<std::size_t>(program)] = shader; } +Shader ShaderCacheOpenGL::GetComputeKernel(GPUVAddr code_addr) { + auto& memory_manager{system.GPU().MemoryManager()}; + const auto host_ptr{memory_manager.GetPointer(code_addr)}; + auto kernel = TryGet(host_ptr); + if (kernel) { + return kernel; + } + + // No kernel found - create a new one + auto code{GetShaderCode(memory_manager, code_addr, host_ptr)}; + const auto unique_identifier{GetUniqueIdentifier(ProgramType::Compute, code, {})}; + const auto cpu_addr{*memory_manager.GpuToCpuAddress(code_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()) { + kernel = CachedShader::CreateKernelFromMemory(params, std::move(code)); + } else { + kernel = CachedShader::CreateKernelFromCache(params, found->second); + } + + Register(kernel); + return kernel; +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 09bd0761d..de195cc5d 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -6,6 +6,7 @@ #include <array> #include <atomic> +#include <bitset> #include <memory> #include <set> #include <tuple> @@ -41,17 +42,29 @@ 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); + + static Shader CreateStageFromCache(const ShaderParameters& params, + Maxwell::ShaderProgram program_type, + GLShader::ProgramResult result); + + static Shader CreateKernelFromMemory(const ShaderParameters& params, ProgramCode&& code); - 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 CreateKernelFromCache(const ShaderParameters& params, + GLShader::ProgramResult result); VAddr GetCpuAddr() const override { return cpu_addr; @@ -67,49 +80,27 @@ 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: - // 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. - struct GeometryPrograms { - CachedProgram points; - CachedProgram lines; - CachedProgram lines_adjacency; - CachedProgram triangles; - CachedProgram triangles_adjacency; - }; + explicit CachedShader(const ShaderParameters& params, ProgramType program_type, + GLShader::ProgramResult result); - GLuint GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings); + CachedProgram TryLoadProgram(const ProgramVariant& variant) const; - /// Generates a geometry shader or returns one that already exists. - GLuint LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings, - GLenum primitive_mode); + ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const; - CachedProgram TryLoadProgram(GLenum primitive_mode, BaseBindings base_bindings) const; - - ShaderDiskCacheUsage GetUsage(GLenum primitive_mode, BaseBindings base_bindings) const; - - u8* host_ptr{}; VAddr cpu_addr{}; u64 unique_identifier{}; - Maxwell::ShaderProgram program_type{}; + ProgramType program_type{}; 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<u32, GLuint> cbuf_resource_cache; - std::unordered_map<u32, GLuint> gmem_resource_cache; - std::unordered_map<u32, GLint> uniform_cache; + std::unordered_map<ProgramVariant, CachedProgram> programs; }; class ShaderCacheOpenGL final : public RasterizerCache<Shader> { @@ -124,6 +115,9 @@ public: /// Gets the current specified shader stage program Shader GetStageProgram(Maxwell::ShaderProgram program); + /// Gets a compute kernel in the passed address + Shader GetComputeKernel(GPUVAddr code_addr); + protected: // We do not have to flush this cache as things in it are never modified by us. void FlushObjectInner(const Shader& object) override {} diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 739477cc9..8fa9e6534 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -14,10 +14,12 @@ #include "common/alignment.h" #include "common/assert.h" #include "common/common_types.h" +#include "common/logging/log.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_opengl/gl_device.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" +#include "video_core/shader/node.h" #include "video_core/shader/shader_ir.h" namespace OpenGL::GLShader { @@ -36,19 +38,18 @@ using namespace std::string_literals; using namespace VideoCommon::Shader; using Maxwell = Tegra::Engines::Maxwell3D::Regs; -using ShaderStage = Tegra::Engines::Maxwell3D::Regs::ShaderStage; using Operation = const OperationNode&; -enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat }; +enum class Type { Void, Bool, Bool2, Float, Int, Uint, HalfFloat }; struct TextureAoffi {}; using TextureArgument = std::pair<Type, Node>; using TextureIR = std::variant<TextureAoffi, TextureArgument>; constexpr u32 MAX_CONSTBUFFER_ELEMENTS = - static_cast<u32>(RasterizerOpenGL::MaxConstbufferSize) / (4 * sizeof(float)); + static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float)); -class ShaderWriter { +class ShaderWriter final { public: void AddExpression(std::string_view text) { DEBUG_ASSERT(scope >= 0); @@ -93,9 +94,177 @@ private: u32 temporary_index = 1; }; +class Expression final { +public: + Expression(std::string code, Type type) : code{std::move(code)}, type{type} { + ASSERT(type != Type::Void); + } + Expression() : type{Type::Void} {} + + Type GetType() const { + return type; + } + + std::string GetCode() const { + return code; + } + + void CheckVoid() const { + ASSERT(type == Type::Void); + } + + std::string As(Type type) const { + switch (type) { + case Type::Bool: + return AsBool(); + case Type::Bool2: + return AsBool2(); + case Type::Float: + return AsFloat(); + case Type::Int: + return AsInt(); + case Type::Uint: + return AsUint(); + case Type::HalfFloat: + return AsHalfFloat(); + default: + UNREACHABLE_MSG("Invalid type"); + return code; + } + } + + std::string AsBool() const { + switch (type) { + case Type::Bool: + return code; + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + + std::string AsBool2() const { + switch (type) { + case Type::Bool2: + return code; + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + + std::string AsFloat() const { + switch (type) { + case Type::Float: + return code; + case Type::Uint: + return fmt::format("utof({})", code); + case Type::Int: + return fmt::format("itof({})", code); + case Type::HalfFloat: + return fmt::format("utof(packHalf2x16({}))", code); + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + + std::string AsInt() const { + switch (type) { + case Type::Float: + return fmt::format("ftoi({})", code); + case Type::Uint: + return fmt::format("int({})", code); + case Type::Int: + return code; + case Type::HalfFloat: + return fmt::format("int(packHalf2x16({}))", code); + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + + std::string AsUint() const { + switch (type) { + case Type::Float: + return fmt::format("ftou({})", code); + case Type::Uint: + return code; + case Type::Int: + return fmt::format("uint({})", code); + case Type::HalfFloat: + return fmt::format("packHalf2x16({})", code); + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + + std::string AsHalfFloat() const { + switch (type) { + case Type::Float: + return fmt::format("unpackHalf2x16(ftou({}))", code); + case Type::Uint: + return fmt::format("unpackHalf2x16({})", code); + case Type::Int: + return fmt::format("unpackHalf2x16(int({}))", code); + case Type::HalfFloat: + return code; + default: + UNREACHABLE_MSG("Incompatible types"); + return code; + } + } + +private: + std::string code; + Type type{}; +}; + +constexpr const char* GetTypeString(Type type) { + switch (type) { + case Type::Bool: + return "bool"; + case Type::Bool2: + return "bvec2"; + case Type::Float: + return "float"; + case Type::Int: + return "int"; + case Type::Uint: + return "uint"; + case Type::HalfFloat: + return "vec2"; + default: + UNREACHABLE_MSG("Invalid type"); + return "<invalid type>"; + } +} + +constexpr const char* GetImageTypeDeclaration(Tegra::Shader::ImageType image_type) { + switch (image_type) { + case Tegra::Shader::ImageType::Texture1D: + return "1D"; + case Tegra::Shader::ImageType::TextureBuffer: + return "Buffer"; + case Tegra::Shader::ImageType::Texture1DArray: + return "1DArray"; + case Tegra::Shader::ImageType::Texture2D: + return "2D"; + case Tegra::Shader::ImageType::Texture2DArray: + return "2DArray"; + case Tegra::Shader::ImageType::Texture3D: + return "3D"; + default: + UNREACHABLE(); + return "1D"; + } +} + /// Generates code to use for a swizzle operation. constexpr const char* GetSwizzle(u32 element) { - constexpr std::array<const char*, 4> swizzle = {".x", ".y", ".z", ".w"}; + constexpr std::array swizzle = {".x", ".y", ".z", ".w"}; return swizzle.at(element); } @@ -134,8 +303,8 @@ constexpr bool IsGenericAttribute(Attribute::Index index) { return index >= Attribute::Index::Attribute_0 && index <= Attribute::Index::Attribute_31; } -constexpr Attribute::Index ToGenericAttribute(u32 value) { - return static_cast<Attribute::Index>(value + static_cast<u32>(Attribute::Index::Attribute_0)); +constexpr Attribute::Index ToGenericAttribute(u64 value) { + return static_cast<Attribute::Index>(value + static_cast<u64>(Attribute::Index::Attribute_0)); } u32 GetGenericAttributeIndex(Attribute::Index index) { @@ -143,9 +312,31 @@ u32 GetGenericAttributeIndex(Attribute::Index index) { return static_cast<u32>(index) - static_cast<u32>(Attribute::Index::Attribute_0); } +constexpr const char* GetFlowStackPrefix(MetaStackClass stack) { + switch (stack) { + case MetaStackClass::Ssy: + return "ssy"; + case MetaStackClass::Pbk: + return "pbk"; + } + return {}; +} + +std::string FlowStackName(MetaStackClass stack) { + return fmt::format("{}_flow_stack", GetFlowStackPrefix(stack)); +} + +std::string FlowStackTopName(MetaStackClass stack) { + return fmt::format("{}_flow_stack_top", GetFlowStackPrefix(stack)); +} + +constexpr bool IsVertexShader(ProgramType stage) { + return stage == ProgramType::VertexA || stage == ProgramType::VertexB; +} + class GLSLDecompiler final { public: - explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ShaderStage stage, + explicit GLSLDecompiler(const Device& device, const ShaderIR& ir, ProgramType stage, std::string suffix) : device{device}, ir{ir}, stage{stage}, suffix{suffix}, header{ir.GetHeader()} {} @@ -155,6 +346,7 @@ public: DeclareRegisters(); DeclarePredicates(); DeclareLocalMemory(); + DeclareSharedMemory(); DeclareInternalFlags(); DeclareInputAttributes(); DeclareOutputAttributes(); @@ -162,19 +354,24 @@ public: DeclareGlobalMemory(); DeclareSamplers(); DeclarePhysicalAttributeReader(); + DeclareImages(); code.AddLine("void execute_{}() {{", suffix); ++code.scope; // VM's program counter const auto first_address = ir.GetBasicBlocks().begin()->first; - code.AddLine("uint jmp_to = {}u;", first_address); + code.AddLine("uint jmp_to = {}U;", first_address); // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems // unlikely that shaders will use 20 nested SSYs and PBKs. - constexpr u32 FLOW_STACK_SIZE = 20; - code.AddLine("uint flow_stack[{}];", FLOW_STACK_SIZE); - code.AddLine("uint flow_stack_top = 0u;"); + if (!ir.IsFlowStackDisabled()) { + constexpr u32 FLOW_STACK_SIZE = 20; + for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) { + code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE); + code.AddLine("uint {} = 0U;", FlowStackTopName(stack)); + } + } code.AddLine("while (true) {{"); ++code.scope; @@ -183,7 +380,7 @@ public: for (const auto& pair : ir.GetBasicBlocks()) { const auto [address, bb] = pair; - code.AddLine("case 0x{:x}u: {{", address); + code.AddLine("case 0x{:X}U: {{", address); ++code.scope; VisitBlock(bb); @@ -214,8 +411,10 @@ public: for (const auto& sampler : ir.GetSamplers()) { entries.samplers.emplace_back(sampler); } - for (const auto& gmem_pair : ir.GetGlobalMemory()) { - const auto& [base, usage] = gmem_pair; + for (const auto& [offset, image] : ir.GetImages()) { + entries.images.emplace_back(image); + } + for (const auto& [base, usage] : ir.GetGlobalMemory()) { entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset, usage.is_read, usage.is_written); } @@ -225,19 +424,15 @@ public: } private: - using OperationDecompilerFn = std::string (GLSLDecompiler::*)(Operation); - using OperationDecompilersArray = - std::array<OperationDecompilerFn, static_cast<std::size_t>(OperationCode::Amount)>; - void DeclareVertex() { - if (stage != ShaderStage::Vertex) + if (!IsVertexShader(stage)) return; DeclareVertexRedeclarations(); } void DeclareGeometry() { - if (stage != ShaderStage::Geometry) { + if (stage != ProgramType::Geometry) { return; } @@ -256,22 +451,43 @@ private: } void DeclareVertexRedeclarations() { - bool clip_distances_declared = false; - code.AddLine("out gl_PerVertex {{"); ++code.scope; code.AddLine("vec4 gl_Position;"); - for (const auto o : ir.GetOutputAttributes()) { - if (o == Attribute::Index::PointSize) - code.AddLine("float gl_PointSize;"); - if (!clip_distances_declared && (o == Attribute::Index::ClipDistances0123 || - o == Attribute::Index::ClipDistances4567)) { + for (const auto attribute : ir.GetOutputAttributes()) { + if (attribute == Attribute::Index::ClipDistances0123 || + attribute == Attribute::Index::ClipDistances4567) { code.AddLine("float gl_ClipDistance[];"); - clip_distances_declared = true; + break; } } + if (!IsVertexShader(stage) || device.HasVertexViewportLayer()) { + if (ir.UsesLayer()) { + code.AddLine("int gl_Layer;"); + } + if (ir.UsesViewportIndex()) { + code.AddLine("int gl_ViewportIndex;"); + } + } else if ((ir.UsesLayer() || ir.UsesViewportIndex()) && IsVertexShader(stage) && + !device.HasVertexViewportLayer()) { + LOG_ERROR( + Render_OpenGL, + "GL_ARB_shader_viewport_layer_array is not available and its required by a shader"); + } + + if (ir.UsesPointSize()) { + code.AddLine("float gl_PointSize;"); + } + + if (ir.UsesInstanceId()) { + code.AddLine("int gl_InstanceID;"); + } + + if (ir.UsesVertexId()) { + code.AddLine("int gl_VertexID;"); + } --code.scope; code.AddLine("}};"); @@ -281,7 +497,7 @@ private: void DeclareRegisters() { const auto& registers = ir.GetRegisters(); for (const u32 gpr : registers) { - code.AddLine("float {} = 0;", GetRegister(gpr)); + code.AddLine("float {} = 0.0f;", GetRegister(gpr)); } if (!registers.empty()) { code.AddNewLine(); @@ -299,11 +515,23 @@ private: } void DeclareLocalMemory() { - if (const u64 local_memory_size = header.GetLocalMemorySize(); local_memory_size > 0) { - const auto element_count = Common::AlignUp(local_memory_size, 4) / 4; - code.AddLine("float {}[{}];", GetLocalMemory(), element_count); - code.AddNewLine(); + // TODO(Rodrigo): Unstub kernel local memory size and pass it from a register at + // specialization time. + const u64 local_memory_size = + stage == ProgramType::Compute ? 0x400 : header.GetLocalMemorySize(); + if (local_memory_size == 0) { + return; + } + const auto element_count = Common::AlignUp(local_memory_size, 4) / 4; + code.AddLine("uint {}[{}];", GetLocalMemory(), element_count); + code.AddNewLine(); + } + + void DeclareSharedMemory() { + if (stage != ProgramType::Compute) { + return; } + code.AddLine("shared uint {}[];", GetSharedMemory()); } void DeclareInternalFlags() { @@ -325,8 +553,6 @@ private: return "noperspective "; default: case AttributeUse::Unused: - UNREACHABLE_MSG("Unused attribute being fetched"); - return {}; UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute)); return {}; } @@ -357,12 +583,12 @@ private: const u32 location{GetGenericAttributeIndex(index)}; std::string name{GetInputAttribute(index)}; - if (stage == ShaderStage::Geometry) { + if (stage == ProgramType::Geometry) { name = "gs_" + name + "[]"; } std::string suffix; - if (stage == ShaderStage::Fragment) { + if (stage == ProgramType::Fragment) { const auto input_mode{header.ps.GetAttributeUse(location)}; if (skip_unused && input_mode == AttributeUse::Unused) { return; @@ -374,7 +600,7 @@ private: } void DeclareOutputAttributes() { - if (ir.HasPhysicalAttributes() && stage != ShaderStage::Fragment) { + if (ir.HasPhysicalAttributes() && stage != ProgramType::Fragment) { for (u32 i = 0; i < GetNumPhysicalVaryings(); ++i) { DeclareOutputAttribute(ToGenericAttribute(i)); } @@ -403,7 +629,7 @@ private: const auto [index, size] = entry; code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index, GetConstBufferBlock(index)); - code.AddLine(" vec4 {}[MAX_CONSTBUFFER_ELEMENTS];", GetConstBuffer(index)); + code.AddLine(" uvec4 {}[{}];", GetConstBuffer(index), MAX_CONSTBUFFER_ELEMENTS); code.AddLine("}};"); code.AddNewLine(); } @@ -424,7 +650,7 @@ private: code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{", base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base)); - code.AddLine(" float {}[];", GetGlobalMemory(base)); + code.AddLine(" uint {}[];", GetGlobalMemory(base)); code.AddLine("}};"); code.AddNewLine(); } @@ -433,9 +659,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"; @@ -455,8 +685,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(); @@ -467,7 +708,7 @@ private: if (!ir.HasPhysicalAttributes()) { return; } - code.AddLine("float readPhysicalAttribute(uint physical_address) {{"); + code.AddLine("float ReadPhysicalAttribute(uint physical_address) {{"); ++code.scope; code.AddLine("switch (physical_address) {{"); @@ -476,15 +717,16 @@ private: for (u32 index = 0; index < num_attributes; ++index) { const auto attribute{ToGenericAttribute(index)}; for (u32 element = 0; element < 4; ++element) { - constexpr u32 generic_base{0x80}; - constexpr u32 generic_stride{16}; - constexpr u32 element_stride{4}; + constexpr u32 generic_base = 0x80; + constexpr u32 generic_stride = 16; + constexpr u32 element_stride = 4; const u32 address{generic_base + index * generic_stride + element * element_stride}; - const bool declared{stage != ShaderStage::Fragment || - header.ps.GetAttributeUse(index) != AttributeUse::Unused}; - const std::string value{declared ? ReadAttribute(attribute, element) : "0"}; - code.AddLine("case 0x{:x}: return {};", address, value); + const bool declared = stage != ProgramType::Fragment || + header.ps.GetAttributeUse(index) != AttributeUse::Unused; + const std::string value = + declared ? ReadAttribute(attribute, element).AsFloat() : "0.0f"; + code.AddLine("case 0x{:X}U: return {};", address, value); } } @@ -496,15 +738,33 @@ private: code.AddNewLine(); } + void DeclareImages() { + const auto& images{ir.GetImages()}; + for (const auto& [offset, image] : images) { + std::string qualifier = "coherent volatile"; + if (image.IsRead() && !image.IsWritten()) { + qualifier += " readonly"; + } else if (image.IsWritten() && !image.IsRead()) { + qualifier += " writeonly"; + } + + const char* format = image.IsAtomic() ? "r32ui, " : ""; + const char* type_declaration = GetImageTypeDeclaration(image.GetType()); + code.AddLine("layout ({}binding = IMAGE_BINDING_{}) {} uniform uimage{} {};", format, + image.GetIndex(), qualifier, type_declaration, 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()) { - code.AddLine(expr); - } + Visit(node).CheckVoid(); } } - std::string Visit(const Node& node) { + Expression Visit(const Node& node) { if (const auto operation = std::get_if<OperationNode>(&*node)) { const auto operation_index = static_cast<std::size_t>(operation->GetCode()); if (operation_index >= operation_decompilers.size()) { @@ -522,18 +782,18 @@ private: if (const auto gpr = std::get_if<GprNode>(&*node)) { const u32 index = gpr->GetIndex(); if (index == Register::ZeroIndex) { - return "0"; + return {"0U", Type::Uint}; } - return GetRegister(index); + return {GetRegister(index), Type::Float}; } if (const auto immediate = std::get_if<ImmediateNode>(&*node)) { const u32 value = immediate->GetValue(); if (value < 10) { // For eyecandy avoid using hex numbers on single digits - return fmt::format("utof({}u)", immediate->GetValue()); + return {fmt::format("{}U", immediate->GetValue()), Type::Uint}; } - return fmt::format("utof(0x{:x}u)", immediate->GetValue()); + return {fmt::format("0x{:X}U", immediate->GetValue()), Type::Uint}; } if (const auto predicate = std::get_if<PredicateNode>(&*node)) { @@ -548,17 +808,18 @@ private: } }(); if (predicate->IsNegated()) { - return fmt::format("!({})", value); + return {fmt::format("!({})", value), Type::Bool}; } - return value; + return {value, Type::Bool}; } if (const auto abuf = std::get_if<AbufNode>(&*node)) { - UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ShaderStage::Geometry, + UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry, "Physical attributes in geometry shaders are not implemented"); if (abuf->IsPhysicalBuffer()) { - return fmt::format("readPhysicalAttribute(ftou({}))", - Visit(abuf->GetPhysicalAddress())); + return {fmt::format("ReadPhysicalAttribute({})", + Visit(abuf->GetPhysicalAddress()).AsUint()), + Type::Float}; } return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer()); } @@ -569,56 +830,70 @@ private: // Direct access const u32 offset_imm = immediate->GetValue(); ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access"); - return fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), - offset_imm / (4 * 4), (offset_imm / 4) % 4); + return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), + offset_imm / (4 * 4), (offset_imm / 4) % 4), + Type::Uint}; } if (std::holds_alternative<OperationNode>(*offset)) { // Indirect access const std::string final_offset = code.GenerateTemporary(); - code.AddLine("uint {} = ftou({}) >> 2;", final_offset, Visit(offset)); + code.AddLine("uint {} = {} >> 2;", final_offset, Visit(offset).AsUint()); if (!device.HasComponentIndexingBug()) { - return fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()), - final_offset, final_offset); + return {fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()), + final_offset, final_offset), + Type::Uint}; } // AMD's proprietary GLSL compiler emits ill code for variable component access. // To bypass this driver bug generate 4 ifs, one per each component. const std::string pack = code.GenerateTemporary(); - code.AddLine("vec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()), + code.AddLine("uvec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()), final_offset); const std::string result = code.GenerateTemporary(); - code.AddLine("float {};", result); + code.AddLine("uint {};", result); for (u32 swizzle = 0; swizzle < 4; ++swizzle) { code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result, pack, GetSwizzle(swizzle)); } - return result; + return {result, Type::Uint}; } UNREACHABLE_MSG("Unmanaged offset node type"); } if (const auto gmem = std::get_if<GmemNode>(&*node)) { - const std::string real = Visit(gmem->GetRealAddress()); - const std::string base = Visit(gmem->GetBaseAddress()); - const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); - return fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); + const std::string real = Visit(gmem->GetRealAddress()).AsUint(); + const std::string base = Visit(gmem->GetBaseAddress()).AsUint(); + const std::string final_offset = fmt::format("({} - {}) >> 2", real, base); + return {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset), + Type::Uint}; } if (const auto lmem = std::get_if<LmemNode>(&*node)) { - return fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); + if (stage == ProgramType::Compute) { + LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); + } + return { + fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()), + Type::Uint}; + } + + if (const auto smem = std::get_if<SmemNode>(&*node)) { + return { + fmt::format("{}[{} >> 2]", GetSharedMemory(), Visit(smem->GetAddress()).AsUint()), + Type::Uint}; } if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) { - return GetInternalFlag(internal_flag->GetFlag()); + return {GetInternalFlag(internal_flag->GetFlag()), Type::Bool}; } if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { // It's invalid to call conditional on nested nodes, use an operation instead - code.AddLine("if ({}) {{", Visit(conditional->GetCondition())); + code.AddLine("if ({}) {{", Visit(conditional->GetCondition()).AsBool()); ++code.scope; VisitBlock(conditional->GetCode()); @@ -629,20 +904,21 @@ private: } if (const auto comment = std::get_if<CommentNode>(&*node)) { - return "// " + comment->GetText(); + code.AddLine("// " + comment->GetText()); + return {}; } UNREACHABLE(); return {}; } - std::string ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) { + Expression ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) { const auto GeometryPass = [&](std::string_view name) { - if (stage == ShaderStage::Geometry && buffer) { + if (stage == ProgramType::Geometry && buffer) { // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games // set an 0x80000000 index for those and the shader fails to build. Find out why // this happens and what's its intent. - return fmt::format("gs_{}[ftou({}) % MAX_VERTEX_INPUT]", name, Visit(buffer)); + return fmt::format("gs_{}[{} % MAX_VERTEX_INPUT]", name, Visit(buffer).AsUint()); } return std::string(name); }; @@ -650,72 +926,79 @@ private: switch (attribute) { case Attribute::Index::Position: switch (stage) { - case ShaderStage::Geometry: - return fmt::format("gl_in[ftou({})].gl_Position{}", Visit(buffer), - GetSwizzle(element)); - case ShaderStage::Fragment: - return element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)); + case ProgramType::Geometry: + return {fmt::format("gl_in[{}].gl_Position{}", Visit(buffer).AsUint(), + GetSwizzle(element)), + Type::Float}; + case ProgramType::Fragment: + return {element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)), + Type::Float}; default: UNREACHABLE(); } case Attribute::Index::PointCoord: switch (element) { case 0: - return "gl_PointCoord.x"; + return {"gl_PointCoord.x", Type::Float}; case 1: - return "gl_PointCoord.y"; + return {"gl_PointCoord.y", Type::Float}; case 2: case 3: - return "0"; + return {"0.0f", Type::Float}; } UNREACHABLE(); - return "0"; + return {"0", Type::Int}; case Attribute::Index::TessCoordInstanceIDVertexID: // TODO(Subv): Find out what the values are for the first two elements when inside a // vertex shader, and what's the value of the fourth element when inside a Tess Eval // shader. - ASSERT(stage == ShaderStage::Vertex); + ASSERT(IsVertexShader(stage)); switch (element) { case 2: // Config pack's first value is instance_id. - return "uintBitsToFloat(config_pack[0])"; + return {"gl_InstanceID", Type::Int}; case 3: - return "uintBitsToFloat(gl_VertexID)"; + return {"gl_VertexID", Type::Int}; } UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element); - return "0"; + return {"0", Type::Int}; case Attribute::Index::FrontFacing: // TODO(Subv): Find out what the values are for the other elements. - ASSERT(stage == ShaderStage::Fragment); + ASSERT(stage == ProgramType::Fragment); switch (element) { case 3: - return "itof(gl_FrontFacing ? -1 : 0)"; + return {"(gl_FrontFacing ? -1 : 0)", Type::Int}; } UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element); - return "0"; + return {"0", Type::Int}; default: if (IsGenericAttribute(attribute)) { - return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element); + return {GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element), + Type::Float}; } break; } UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); - return "0"; + return {"0", Type::Int}; } - std::string ApplyPrecise(Operation operation, const std::string& value) { + Expression ApplyPrecise(Operation operation, std::string value, Type type) { if (!IsPrecise(operation)) { - return value; + return {std::move(value), type}; } - // There's a bug in NVidia's proprietary drivers that makes precise fail on fragment shaders - const std::string precise = stage != ShaderStage::Fragment ? "precise " : ""; + // Old Nvidia drivers have a bug with precise and texture sampling. These are more likely to + // be found in fragment shaders, so we disable precise there. There are vertex shaders that + // also fail to build but nobody seems to care about those. + // Note: Only bugged drivers will skip precise. + const bool disable_precise = device.HasPreciseBug() && stage == ProgramType::Fragment; - const std::string temporary = code.GenerateTemporary(); - code.AddLine("{}float {} = {};", precise, temporary, value); - return temporary; + std::string temporary = code.GenerateTemporary(); + code.AddLine("{}{} {} = {};", disable_precise ? "" : "precise ", GetTypeString(type), + temporary, value); + return {std::move(temporary), type}; } - std::string VisitOperand(Operation operation, std::size_t operand_index) { + Expression VisitOperand(Operation operation, std::size_t operand_index) { const auto& operand = operation[operand_index]; const bool parent_precise = IsPrecise(operation); const bool child_precise = IsPrecise(operand); @@ -724,102 +1007,98 @@ private: return Visit(operand); } - const std::string temporary = code.GenerateTemporary(); - code.AddLine("float {} = {};", temporary, Visit(operand)); - return temporary; - } - - std::string VisitOperand(Operation operation, std::size_t operand_index, Type type) { - return CastOperand(VisitOperand(operation, operand_index), type); + Expression value = Visit(operand); + std::string temporary = code.GenerateTemporary(); + code.AddLine("{} {} = {};", GetTypeString(value.GetType()), temporary, value.GetCode()); + return {std::move(temporary), value.GetType()}; } - std::string CastOperand(const std::string& value, Type type) const { - switch (type) { - case Type::Bool: - case Type::Bool2: - case Type::Float: - return value; - case Type::Int: - return fmt::format("ftoi({})", value); - case Type::Uint: - return fmt::format("ftou({})", value); - case Type::HalfFloat: - return fmt::format("toHalf2({})", value); - } - UNREACHABLE(); - return value; - } - - std::string BitwiseCastResult(const std::string& value, Type type, - bool needs_parenthesis = false) { - switch (type) { - case Type::Bool: - case Type::Bool2: - case Type::Float: - if (needs_parenthesis) { - return fmt::format("({})", value); + std::optional<Expression> GetOutputAttribute(const AbufNode* abuf) { + switch (const auto attribute = abuf->GetIndex()) { + case Attribute::Index::Position: + return {{"gl_Position"s + GetSwizzle(abuf->GetElement()), Type::Float}}; + case Attribute::Index::LayerViewportPointSize: + switch (abuf->GetElement()) { + case 0: + UNIMPLEMENTED(); + return {}; + case 1: + if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { + return {}; + } + return {{"gl_Layer", Type::Int}}; + case 2: + if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { + return {}; + } + return {{"gl_ViewportIndex", Type::Int}}; + case 3: + UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader"); + return {{"gl_PointSize", Type::Float}}; } - return value; - case Type::Int: - return fmt::format("itof({})", value); - case Type::Uint: - return fmt::format("utof({})", value); - case Type::HalfFloat: - return fmt::format("fromHalf2({})", value); + return {}; + case Attribute::Index::ClipDistances0123: + return {{fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), Type::Float}}; + case Attribute::Index::ClipDistances4567: + return {{fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), Type::Float}}; + default: + if (IsGenericAttribute(attribute)) { + return { + {GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), Type::Float}}; + } + UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); + return {}; } - UNREACHABLE(); - return value; } - std::string GenerateUnary(Operation operation, const std::string& func, Type result_type, - Type type_a, bool needs_parenthesis = true) { - const std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0, type_a)); - - return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type, needs_parenthesis)); + Expression GenerateUnary(Operation operation, std::string_view func, Type result_type, + Type type_a) { + std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0).As(type_a)); + return ApplyPrecise(operation, std::move(op_str), result_type); } - std::string GenerateBinaryInfix(Operation operation, const std::string& func, Type result_type, - Type type_a, Type type_b) { - const std::string op_a = VisitOperand(operation, 0, type_a); - const std::string op_b = VisitOperand(operation, 1, type_b); - const std::string op_str = fmt::format("({} {} {})", op_a, func, op_b); + Expression GenerateBinaryInfix(Operation operation, std::string_view func, Type result_type, + Type type_a, Type type_b) { + const std::string op_a = VisitOperand(operation, 0).As(type_a); + const std::string op_b = VisitOperand(operation, 1).As(type_b); + std::string op_str = fmt::format("({} {} {})", op_a, func, op_b); - return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); + return ApplyPrecise(operation, std::move(op_str), result_type); } - std::string GenerateBinaryCall(Operation operation, const std::string& func, Type result_type, - Type type_a, Type type_b) { - const std::string op_a = VisitOperand(operation, 0, type_a); - const std::string op_b = VisitOperand(operation, 1, type_b); - const std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b); + Expression GenerateBinaryCall(Operation operation, std::string_view func, Type result_type, + Type type_a, Type type_b) { + const std::string op_a = VisitOperand(operation, 0).As(type_a); + const std::string op_b = VisitOperand(operation, 1).As(type_b); + std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b); - return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); + return ApplyPrecise(operation, std::move(op_str), result_type); } - std::string GenerateTernary(Operation operation, const std::string& func, Type result_type, - Type type_a, Type type_b, Type type_c) { - const std::string op_a = VisitOperand(operation, 0, type_a); - const std::string op_b = VisitOperand(operation, 1, type_b); - const std::string op_c = VisitOperand(operation, 2, type_c); - const std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c); + Expression GenerateTernary(Operation operation, std::string_view func, Type result_type, + Type type_a, Type type_b, Type type_c) { + const std::string op_a = VisitOperand(operation, 0).As(type_a); + const std::string op_b = VisitOperand(operation, 1).As(type_b); + const std::string op_c = VisitOperand(operation, 2).As(type_c); + std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c); - return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); + return ApplyPrecise(operation, std::move(op_str), result_type); } - std::string GenerateQuaternary(Operation operation, const std::string& func, Type result_type, - Type type_a, Type type_b, Type type_c, Type type_d) { - const std::string op_a = VisitOperand(operation, 0, type_a); - const std::string op_b = VisitOperand(operation, 1, type_b); - const std::string op_c = VisitOperand(operation, 2, type_c); - const std::string op_d = VisitOperand(operation, 3, type_d); - const std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d); + Expression GenerateQuaternary(Operation operation, const std::string& func, Type result_type, + Type type_a, Type type_b, Type type_c, Type type_d) { + const std::string op_a = VisitOperand(operation, 0).As(type_a); + const std::string op_b = VisitOperand(operation, 1).As(type_b); + const std::string op_c = VisitOperand(operation, 2).As(type_c); + const std::string op_d = VisitOperand(operation, 3).As(type_d); + std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d); - return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); + return ApplyPrecise(operation, std::move(op_str), result_type); } std::string GenerateTexture(Operation operation, const std::string& function_suffix, const std::vector<TextureIR>& extras) { - constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"}; + constexpr std::array coord_constructors = {"float", "vec2", "vec3", "vec4"}; const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); @@ -836,17 +1115,17 @@ private: expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1); expr += '('; for (std::size_t i = 0; i < count; ++i) { - expr += Visit(operation[i]); + expr += Visit(operation[i]).AsFloat(); const std::size_t next = i + 1; if (next < count) expr += ", "; } if (has_array) { - expr += ", float(ftoi(" + Visit(meta->array) + "))"; + expr += ", float(" + Visit(meta->array).AsInt() + ')'; } if (has_shadow) { - expr += ", " + Visit(meta->depth_compare); + expr += ", " + Visit(meta->depth_compare).AsFloat(); } expr += ')'; @@ -877,11 +1156,11 @@ private: // required to be constant) expr += std::to_string(static_cast<s32>(immediate->GetValue())); } else { - expr += fmt::format("ftoi({})", Visit(operand)); + expr += Visit(operand).AsInt(); } break; case Type::Float: - expr += Visit(operand); + expr += Visit(operand).AsFloat(); break; default: { const auto type_int = static_cast<u32>(type); @@ -897,7 +1176,7 @@ private: if (aoffi.empty()) { return {}; } - constexpr std::array<const char*, 3> coord_constructors = {"int", "ivec2", "ivec3"}; + constexpr std::array coord_constructors = {"int", "ivec2", "ivec3"}; std::string expr = ", "; expr += coord_constructors.at(aoffi.size() - 1); expr += '('; @@ -910,7 +1189,7 @@ private: expr += std::to_string(static_cast<s32>(immediate->GetValue())); } else if (device.HasVariableAoffi()) { // Avoid using variable AOFFI on unsupported devices. - expr += fmt::format("ftoi({})", Visit(operand)); + expr += Visit(operand).AsInt(); } else { // Insert 0 on devices not supporting variable AOFFI. expr += '0'; @@ -924,318 +1203,355 @@ private: return expr; } - std::string Assign(Operation operation) { + std::string BuildIntegerCoordinates(Operation operation) { + constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; + const std::size_t coords_count{operation.GetOperandsCount()}; + std::string expr = constructors.at(coords_count - 1); + for (std::size_t i = 0; i < coords_count; ++i) { + expr += VisitOperand(operation, i).AsInt(); + if (i + 1 < coords_count) { + expr += ", "; + } + } + expr += ')'; + return expr; + } + + std::string BuildImageValues(Operation operation) { + constexpr std::array constructors{"uint", "uvec2", "uvec3", "uvec4"}; + const auto meta{std::get<MetaImage>(operation.GetMeta())}; + + const std::size_t values_count{meta.values.size()}; + std::string expr = fmt::format("{}(", constructors.at(values_count - 1)); + for (std::size_t i = 0; i < values_count; ++i) { + expr += Visit(meta.values.at(i)).AsUint(); + if (i + 1 < values_count) { + expr += ", "; + } + } + expr += ')'; + return expr; + } + + Expression Assign(Operation operation) { const Node& dest = operation[0]; const Node& src = operation[1]; - std::string target; + Expression target; if (const auto gpr = std::get_if<GprNode>(&*dest)) { if (gpr->GetIndex() == Register::ZeroIndex) { // Writing to Register::ZeroIndex is a no op return {}; } - target = GetRegister(gpr->GetIndex()); + target = {GetRegister(gpr->GetIndex()), Type::Float}; } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) { UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer()); - - target = [&]() -> std::string { - switch (const auto attribute = abuf->GetIndex(); abuf->GetIndex()) { - case Attribute::Index::Position: - return "gl_Position"s + GetSwizzle(abuf->GetElement()); - case Attribute::Index::PointSize: - return "gl_PointSize"; - case Attribute::Index::ClipDistances0123: - return fmt::format("gl_ClipDistance[{}]", abuf->GetElement()); - case Attribute::Index::ClipDistances4567: - return fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4); - default: - if (IsGenericAttribute(attribute)) { - return GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()); - } - UNIMPLEMENTED_MSG("Unhandled output attribute: {}", - static_cast<u32>(attribute)); - return "0"; - } - }(); + auto output = GetOutputAttribute(abuf); + if (!output) { + return {}; + } + target = std::move(*output); } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) { - target = fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); + if (stage == ProgramType::Compute) { + LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); + } + target = { + fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()), + Type::Uint}; + } else if (const auto smem = std::get_if<SmemNode>(&*dest)) { + ASSERT(stage == ProgramType::Compute); + target = { + fmt::format("{}[{} >> 2]", GetSharedMemory(), Visit(smem->GetAddress()).AsUint()), + Type::Uint}; } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { - const std::string real = Visit(gmem->GetRealAddress()); - const std::string base = Visit(gmem->GetBaseAddress()); - const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); - target = fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); + const std::string real = Visit(gmem->GetRealAddress()).AsUint(); + const std::string base = Visit(gmem->GetBaseAddress()).AsUint(); + const std::string final_offset = fmt::format("({} - {}) >> 2", real, base); + target = {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset), + Type::Uint}; } else { UNREACHABLE_MSG("Assign called without a proper target"); } - code.AddLine("{} = {};", target, Visit(src)); + code.AddLine("{} = {};", target.GetCode(), Visit(src).As(target.GetType())); return {}; } template <Type type> - std::string Add(Operation operation) { + Expression Add(Operation operation) { return GenerateBinaryInfix(operation, "+", type, type, type); } template <Type type> - std::string Mul(Operation operation) { + Expression Mul(Operation operation) { return GenerateBinaryInfix(operation, "*", type, type, type); } template <Type type> - std::string Div(Operation operation) { + Expression Div(Operation operation) { return GenerateBinaryInfix(operation, "/", type, type, type); } template <Type type> - std::string Fma(Operation operation) { + Expression Fma(Operation operation) { return GenerateTernary(operation, "fma", type, type, type, type); } template <Type type> - std::string Negate(Operation operation) { - return GenerateUnary(operation, "-", type, type, true); + Expression Negate(Operation operation) { + return GenerateUnary(operation, "-", type, type); } template <Type type> - std::string Absolute(Operation operation) { - return GenerateUnary(operation, "abs", type, type, false); + Expression Absolute(Operation operation) { + return GenerateUnary(operation, "abs", type, type); } - std::string FClamp(Operation operation) { + Expression FClamp(Operation operation) { return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float, Type::Float); } + Expression FCastHalf0(Operation operation) { + return {fmt::format("({})[0]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; + } + + Expression FCastHalf1(Operation operation) { + return {fmt::format("({})[1]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; + } + template <Type type> - std::string Min(Operation operation) { + Expression Min(Operation operation) { return GenerateBinaryCall(operation, "min", type, type, type); } template <Type type> - std::string Max(Operation operation) { + Expression Max(Operation operation) { return GenerateBinaryCall(operation, "max", type, type, type); } - std::string Select(Operation operation) { - const std::string condition = Visit(operation[0]); - const std::string true_case = Visit(operation[1]); - const std::string false_case = Visit(operation[2]); - const std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case); + Expression Select(Operation operation) { + const std::string condition = Visit(operation[0]).AsBool(); + const std::string true_case = Visit(operation[1]).AsUint(); + const std::string false_case = Visit(operation[2]).AsUint(); + std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case); - return ApplyPrecise(operation, op_str); + return ApplyPrecise(operation, std::move(op_str), Type::Uint); } - std::string FCos(Operation operation) { - return GenerateUnary(operation, "cos", Type::Float, Type::Float, false); + Expression FCos(Operation operation) { + return GenerateUnary(operation, "cos", Type::Float, Type::Float); } - std::string FSin(Operation operation) { - return GenerateUnary(operation, "sin", Type::Float, Type::Float, false); + Expression FSin(Operation operation) { + return GenerateUnary(operation, "sin", Type::Float, Type::Float); } - std::string FExp2(Operation operation) { - return GenerateUnary(operation, "exp2", Type::Float, Type::Float, false); + Expression FExp2(Operation operation) { + return GenerateUnary(operation, "exp2", Type::Float, Type::Float); } - std::string FLog2(Operation operation) { - return GenerateUnary(operation, "log2", Type::Float, Type::Float, false); + Expression FLog2(Operation operation) { + return GenerateUnary(operation, "log2", Type::Float, Type::Float); } - std::string FInverseSqrt(Operation operation) { - return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float, false); + Expression FInverseSqrt(Operation operation) { + return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float); } - std::string FSqrt(Operation operation) { - return GenerateUnary(operation, "sqrt", Type::Float, Type::Float, false); + Expression FSqrt(Operation operation) { + return GenerateUnary(operation, "sqrt", Type::Float, Type::Float); } - std::string FRoundEven(Operation operation) { - return GenerateUnary(operation, "roundEven", Type::Float, Type::Float, false); + Expression FRoundEven(Operation operation) { + return GenerateUnary(operation, "roundEven", Type::Float, Type::Float); } - std::string FFloor(Operation operation) { - return GenerateUnary(operation, "floor", Type::Float, Type::Float, false); + Expression FFloor(Operation operation) { + return GenerateUnary(operation, "floor", Type::Float, Type::Float); } - std::string FCeil(Operation operation) { - return GenerateUnary(operation, "ceil", Type::Float, Type::Float, false); + Expression FCeil(Operation operation) { + return GenerateUnary(operation, "ceil", Type::Float, Type::Float); } - std::string FTrunc(Operation operation) { - return GenerateUnary(operation, "trunc", Type::Float, Type::Float, false); + Expression FTrunc(Operation operation) { + return GenerateUnary(operation, "trunc", Type::Float, Type::Float); } template <Type type> - std::string FCastInteger(Operation operation) { - return GenerateUnary(operation, "float", Type::Float, type, false); + Expression FCastInteger(Operation operation) { + return GenerateUnary(operation, "float", Type::Float, type); } - std::string ICastFloat(Operation operation) { - return GenerateUnary(operation, "int", Type::Int, Type::Float, false); + Expression ICastFloat(Operation operation) { + return GenerateUnary(operation, "int", Type::Int, Type::Float); } - std::string ICastUnsigned(Operation operation) { - return GenerateUnary(operation, "int", Type::Int, Type::Uint, false); + Expression ICastUnsigned(Operation operation) { + return GenerateUnary(operation, "int", Type::Int, Type::Uint); } template <Type type> - std::string LogicalShiftLeft(Operation operation) { + Expression LogicalShiftLeft(Operation operation) { return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint); } - std::string ILogicalShiftRight(Operation operation) { - const std::string op_a = VisitOperand(operation, 0, Type::Uint); - const std::string op_b = VisitOperand(operation, 1, Type::Uint); - const std::string op_str = fmt::format("int({} >> {})", op_a, op_b); + Expression ILogicalShiftRight(Operation operation) { + const std::string op_a = VisitOperand(operation, 0).AsUint(); + const std::string op_b = VisitOperand(operation, 1).AsUint(); + std::string op_str = fmt::format("int({} >> {})", op_a, op_b); - return ApplyPrecise(operation, BitwiseCastResult(op_str, Type::Int)); + return ApplyPrecise(operation, std::move(op_str), Type::Int); } - std::string IArithmeticShiftRight(Operation operation) { + Expression IArithmeticShiftRight(Operation operation) { return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint); } template <Type type> - std::string BitwiseAnd(Operation operation) { + Expression BitwiseAnd(Operation operation) { return GenerateBinaryInfix(operation, "&", type, type, type); } template <Type type> - std::string BitwiseOr(Operation operation) { + Expression BitwiseOr(Operation operation) { return GenerateBinaryInfix(operation, "|", type, type, type); } template <Type type> - std::string BitwiseXor(Operation operation) { + Expression BitwiseXor(Operation operation) { return GenerateBinaryInfix(operation, "^", type, type, type); } template <Type type> - std::string BitwiseNot(Operation operation) { - return GenerateUnary(operation, "~", type, type, false); + Expression BitwiseNot(Operation operation) { + return GenerateUnary(operation, "~", type, type); } - std::string UCastFloat(Operation operation) { - return GenerateUnary(operation, "uint", Type::Uint, Type::Float, false); + Expression UCastFloat(Operation operation) { + return GenerateUnary(operation, "uint", Type::Uint, Type::Float); } - std::string UCastSigned(Operation operation) { - return GenerateUnary(operation, "uint", Type::Uint, Type::Int, false); + Expression UCastSigned(Operation operation) { + return GenerateUnary(operation, "uint", Type::Uint, Type::Int); } - std::string UShiftRight(Operation operation) { + Expression UShiftRight(Operation operation) { return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint); } template <Type type> - std::string BitfieldInsert(Operation operation) { + Expression BitfieldInsert(Operation operation) { return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int, Type::Int); } template <Type type> - std::string BitfieldExtract(Operation operation) { + Expression BitfieldExtract(Operation operation) { return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int); } template <Type type> - std::string BitCount(Operation operation) { - return GenerateUnary(operation, "bitCount", type, type, false); + Expression BitCount(Operation operation) { + return GenerateUnary(operation, "bitCount", type, type); } - std::string HNegate(Operation operation) { + Expression HNegate(Operation operation) { const auto GetNegate = [&](std::size_t index) { - return VisitOperand(operation, index, Type::Bool) + " ? -1 : 1"; + return VisitOperand(operation, index).AsBool() + " ? -1 : 1"; }; - const std::string value = - fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0, Type::HalfFloat), - GetNegate(1), GetNegate(2)); - return BitwiseCastResult(value, Type::HalfFloat); - } - - std::string HClamp(Operation operation) { - const std::string value = VisitOperand(operation, 0, Type::HalfFloat); - const std::string min = VisitOperand(operation, 1, Type::Float); - const std::string max = VisitOperand(operation, 2, Type::Float); - const std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max); - - return ApplyPrecise(operation, BitwiseCastResult(clamped, Type::HalfFloat)); - } - - std::string HUnpack(Operation operation) { - const std::string operand{VisitOperand(operation, 0, Type::HalfFloat)}; - const auto value = [&]() -> std::string { - switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) { - case Tegra::Shader::HalfType::H0_H1: - return operand; - case Tegra::Shader::HalfType::F32: - return fmt::format("vec2(fromHalf2({}))", operand); - case Tegra::Shader::HalfType::H0_H0: - return fmt::format("vec2({}[0])", operand); - case Tegra::Shader::HalfType::H1_H1: - return fmt::format("vec2({}[1])", operand); - } - UNREACHABLE(); - return "0"; - }(); - return fmt::format("fromHalf2({})", value); + return {fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0).AsHalfFloat(), + GetNegate(1), GetNegate(2)), + Type::HalfFloat}; + } + + Expression HClamp(Operation operation) { + const std::string value = VisitOperand(operation, 0).AsHalfFloat(); + const std::string min = VisitOperand(operation, 1).AsFloat(); + const std::string max = VisitOperand(operation, 2).AsFloat(); + std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max); + + return ApplyPrecise(operation, std::move(clamped), Type::HalfFloat); } - std::string HMergeF32(Operation operation) { - return fmt::format("float(toHalf2({})[0])", Visit(operation[0])); + Expression HCastFloat(Operation operation) { + return {fmt::format("vec2({})", VisitOperand(operation, 0).AsFloat()), Type::HalfFloat}; } - std::string HMergeH0(Operation operation) { - return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[1]), - Visit(operation[0])); + Expression HUnpack(Operation operation) { + Expression operand = VisitOperand(operation, 0); + switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) { + case Tegra::Shader::HalfType::H0_H1: + return operand; + case Tegra::Shader::HalfType::F32: + return {fmt::format("vec2({})", operand.AsFloat()), Type::HalfFloat}; + case Tegra::Shader::HalfType::H0_H0: + return {fmt::format("vec2({}[0])", operand.AsHalfFloat()), Type::HalfFloat}; + case Tegra::Shader::HalfType::H1_H1: + return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat}; + } + UNREACHABLE(); + return {"0", Type::Int}; } - std::string HMergeH1(Operation operation) { - return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[0]), - Visit(operation[1])); + Expression HMergeF32(Operation operation) { + return {fmt::format("float({}[0])", VisitOperand(operation, 0).AsHalfFloat()), Type::Float}; } - std::string HPack2(Operation operation) { - return fmt::format("utof(packHalf2x16(vec2({}, {})))", Visit(operation[0]), - Visit(operation[1])); + Expression HMergeH0(Operation operation) { + std::string dest = VisitOperand(operation, 0).AsUint(); + std::string src = VisitOperand(operation, 1).AsUint(); + return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", src, dest), Type::Uint}; + } + + Expression HMergeH1(Operation operation) { + std::string dest = VisitOperand(operation, 0).AsUint(); + std::string src = VisitOperand(operation, 1).AsUint(); + return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", dest, src), Type::Uint}; + } + + Expression HPack2(Operation operation) { + return {fmt::format("vec2({}, {})", VisitOperand(operation, 0).AsFloat(), + VisitOperand(operation, 1).AsFloat()), + Type::HalfFloat}; } template <Type type> - std::string LogicalLessThan(Operation operation) { + Expression LogicalLessThan(Operation operation) { return GenerateBinaryInfix(operation, "<", Type::Bool, type, type); } template <Type type> - std::string LogicalEqual(Operation operation) { + Expression LogicalEqual(Operation operation) { return GenerateBinaryInfix(operation, "==", Type::Bool, type, type); } template <Type type> - std::string LogicalLessEqual(Operation operation) { + Expression LogicalLessEqual(Operation operation) { return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type); } template <Type type> - std::string LogicalGreaterThan(Operation operation) { + Expression LogicalGreaterThan(Operation operation) { return GenerateBinaryInfix(operation, ">", Type::Bool, type, type); } template <Type type> - std::string LogicalNotEqual(Operation operation) { + Expression LogicalNotEqual(Operation operation) { return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type); } template <Type type> - std::string LogicalGreaterEqual(Operation operation) { + Expression LogicalGreaterEqual(Operation operation) { return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type); } - std::string LogicalFIsNan(Operation operation) { - return GenerateUnary(operation, "isnan", Type::Bool, Type::Float, false); + Expression LogicalFIsNan(Operation operation) { + return GenerateUnary(operation, "isnan", Type::Bool, Type::Float); } - std::string LogicalAssign(Operation operation) { + Expression LogicalAssign(Operation operation) { const Node& dest = operation[0]; const Node& src = operation[1]; @@ -1256,82 +1572,80 @@ private: target = GetInternalFlag(flag->GetFlag()); } - code.AddLine("{} = {};", target, Visit(src)); + code.AddLine("{} = {};", target, Visit(src).AsBool()); return {}; } - std::string LogicalAnd(Operation operation) { + Expression LogicalAnd(Operation operation) { return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool); } - std::string LogicalOr(Operation operation) { + Expression LogicalOr(Operation operation) { return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool); } - std::string LogicalXor(Operation operation) { + Expression LogicalXor(Operation operation) { return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool); } - std::string LogicalNegate(Operation operation) { - return GenerateUnary(operation, "!", Type::Bool, Type::Bool, false); + Expression LogicalNegate(Operation operation) { + return GenerateUnary(operation, "!", Type::Bool, Type::Bool); } - std::string LogicalPick2(Operation operation) { - const std::string pair = VisitOperand(operation, 0, Type::Bool2); - return fmt::format("{}[{}]", pair, VisitOperand(operation, 1, Type::Uint)); + Expression LogicalPick2(Operation operation) { + return {fmt::format("{}[{}]", VisitOperand(operation, 0).AsBool2(), + VisitOperand(operation, 1).AsUint()), + Type::Bool}; } - std::string LogicalAll2(Operation operation) { + Expression LogicalAnd2(Operation operation) { return GenerateUnary(operation, "all", Type::Bool, Type::Bool2); } - std::string LogicalAny2(Operation operation) { - return GenerateUnary(operation, "any", Type::Bool, Type::Bool2); - } - template <bool with_nan> - std::string GenerateHalfComparison(Operation operation, const std::string& compare_op) { - const std::string comparison{GenerateBinaryCall(operation, compare_op, Type::Bool2, - Type::HalfFloat, Type::HalfFloat)}; + Expression GenerateHalfComparison(Operation operation, std::string_view compare_op) { + Expression comparison = GenerateBinaryCall(operation, compare_op, Type::Bool2, + Type::HalfFloat, Type::HalfFloat); if constexpr (!with_nan) { return comparison; } - return fmt::format("halfFloatNanComparison({}, {}, {})", comparison, - VisitOperand(operation, 0, Type::HalfFloat), - VisitOperand(operation, 1, Type::HalfFloat)); + return {fmt::format("HalfFloatNanComparison({}, {}, {})", comparison.AsBool2(), + VisitOperand(operation, 0).AsHalfFloat(), + VisitOperand(operation, 1).AsHalfFloat()), + Type::Bool2}; } template <bool with_nan> - std::string Logical2HLessThan(Operation operation) { + Expression Logical2HLessThan(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "lessThan"); } template <bool with_nan> - std::string Logical2HEqual(Operation operation) { + Expression Logical2HEqual(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "equal"); } template <bool with_nan> - std::string Logical2HLessEqual(Operation operation) { + Expression Logical2HLessEqual(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "lessThanEqual"); } template <bool with_nan> - std::string Logical2HGreaterThan(Operation operation) { + Expression Logical2HGreaterThan(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "greaterThan"); } template <bool with_nan> - std::string Logical2HNotEqual(Operation operation) { + Expression Logical2HNotEqual(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "notEqual"); } template <bool with_nan> - std::string Logical2HGreaterEqual(Operation operation) { + Expression Logical2HGreaterEqual(Operation operation) { return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual"); } - std::string Texture(Operation operation) { + Expression Texture(Operation operation) { const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); @@ -1340,10 +1654,10 @@ private: if (meta->sampler.IsShadow()) { expr = "vec4(" + expr + ')'; } - return expr + GetSwizzle(meta->element); + return {expr + GetSwizzle(meta->element), Type::Float}; } - std::string TextureLod(Operation operation) { + Expression TextureLod(Operation operation) { const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); @@ -1352,54 +1666,54 @@ private: if (meta->sampler.IsShadow()) { expr = "vec4(" + expr + ')'; } - return expr + GetSwizzle(meta->element); + return {expr + GetSwizzle(meta->element), Type::Float}; } - std::string TextureGather(Operation operation) { + Expression TextureGather(Operation operation) { const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int; - return GenerateTexture(operation, "Gather", - {TextureArgument{type, meta->component}, TextureAoffi{}}) + - GetSwizzle(meta->element); + return {GenerateTexture(operation, "Gather", + {TextureArgument{type, meta->component}, TextureAoffi{}}) + + GetSwizzle(meta->element), + Type::Float}; } - std::string TextureQueryDimensions(Operation operation) { + Expression TextureQueryDimensions(Operation operation) { const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); const std::string sampler = GetSampler(meta->sampler); - const std::string lod = VisitOperand(operation, 0, Type::Int); + const std::string lod = VisitOperand(operation, 0).AsInt(); switch (meta->element) { case 0: case 1: - return fmt::format("itof(int(textureSize({}, {}){}))", sampler, lod, - GetSwizzle(meta->element)); - case 2: - return "0"; + return {fmt::format("textureSize({}, {}){}", sampler, lod, GetSwizzle(meta->element)), + Type::Int}; case 3: - return fmt::format("itof(textureQueryLevels({}))", sampler); + return {fmt::format("textureQueryLevels({})", sampler), Type::Int}; } UNREACHABLE(); - return "0"; + return {"0", Type::Int}; } - std::string TextureQueryLod(Operation operation) { + Expression TextureQueryLod(Operation operation) { const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); if (meta->element < 2) { - return fmt::format("itof(int(({} * vec2(256)){}))", - GenerateTexture(operation, "QueryLod", {}), - GetSwizzle(meta->element)); + return {fmt::format("int(({} * vec2(256)){})", + GenerateTexture(operation, "QueryLod", {}), + GetSwizzle(meta->element)), + Type::Int}; } - return "0"; + return {"0", Type::Int}; } - std::string TexelFetch(Operation operation) { - constexpr std::array<const char*, 4> constructors = {"int", "ivec2", "ivec3", "ivec4"}; + Expression TexelFetch(Operation operation) { + constexpr std::array constructors = {"int", "ivec2", "ivec3", "ivec4"}; const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); ASSERT(meta); UNIMPLEMENTED_IF(meta->sampler.IsArray()); @@ -1412,57 +1726,113 @@ private: expr += constructors.at(operation.GetOperandsCount() - 1); expr += '('; for (std::size_t i = 0; i < count; ++i) { - expr += VisitOperand(operation, i, Type::Int); + expr += VisitOperand(operation, i).AsInt(); const std::size_t next = i + 1; if (next == count) expr += ')'; 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 += Visit(meta->lod).AsInt(); } expr += ')'; + expr += GetSwizzle(meta->element); + + expr_buffer += ')'; + expr_buffer += GetSwizzle(meta->element); - return expr + 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, Type::Float}; } - std::string Branch(Operation operation) { + Expression ImageLoad(Operation operation) { + if (!device.HasImageLoadFormatted()) { + LOG_ERROR(Render_OpenGL, + "Device lacks GL_EXT_shader_image_load_formatted, stubbing image load"); + return {"0", Type::Int}; + } + + const auto meta{std::get<MetaImage>(operation.GetMeta())}; + return {fmt::format("imageLoad({}, {}){}", GetImage(meta.image), + BuildIntegerCoordinates(operation), GetSwizzle(meta.element)), + Type::Uint}; + } + + Expression ImageStore(Operation operation) { + const auto meta{std::get<MetaImage>(operation.GetMeta())}; + code.AddLine("imageStore({}, {}, {});", GetImage(meta.image), + BuildIntegerCoordinates(operation), BuildImageValues(operation)); + return {}; + } + + template <const std::string_view& opname> + Expression AtomicImage(Operation operation) { + const auto meta{std::get<MetaImage>(operation.GetMeta())}; + ASSERT(meta.values.size() == 1); + + return {fmt::format("imageAtomic{}({}, {}, {})", opname, GetImage(meta.image), + BuildIntegerCoordinates(operation), Visit(meta.values[0]).AsUint()), + Type::Uint}; + } + + Expression Branch(Operation operation) { const auto target = std::get_if<ImmediateNode>(&*operation[0]); UNIMPLEMENTED_IF(!target); - code.AddLine("jmp_to = 0x{:x}u;", target->GetValue()); + code.AddLine("jmp_to = 0x{:X}U;", target->GetValue()); + code.AddLine("break;"); + return {}; + } + + Expression BranchIndirect(Operation operation) { + const std::string op_a = VisitOperand(operation, 0).AsUint(); + + code.AddLine("jmp_to = {};", op_a); code.AddLine("break;"); return {}; } - std::string PushFlowStack(Operation operation) { + Expression PushFlowStack(Operation operation) { + const auto stack = std::get<MetaStackClass>(operation.GetMeta()); const auto target = std::get_if<ImmediateNode>(&*operation[0]); UNIMPLEMENTED_IF(!target); - code.AddLine("flow_stack[flow_stack_top++] = 0x{:x}u;", target->GetValue()); + code.AddLine("{}[{}++] = 0x{:X}U;", FlowStackName(stack), FlowStackTopName(stack), + target->GetValue()); return {}; } - std::string PopFlowStack(Operation operation) { - code.AddLine("jmp_to = flow_stack[--flow_stack_top];"); + Expression PopFlowStack(Operation operation) { + const auto stack = std::get<MetaStackClass>(operation.GetMeta()); + code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack)); code.AddLine("break;"); return {}; } - std::string Exit(Operation operation) { - if (stage != ShaderStage::Fragment) { + Expression Exit(Operation operation) { + if (stage != ProgramType::Fragment) { code.AddLine("return;"); return {}; } const auto& used_registers = ir.GetRegisters(); - const auto SafeGetRegister = [&](u32 reg) -> std::string { + const auto SafeGetRegister = [&](u32 reg) -> Expression { // TODO(Rodrigo): Replace with contains once C++20 releases if (used_registers.find(reg) != used_registers.end()) { - return GetRegister(reg); + return {GetRegister(reg), Type::Float}; } - return "0.0f"; + return {"0.0f", Type::Float}; }; UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); @@ -1475,7 +1845,7 @@ private: for (u32 component = 0; component < 4; ++component) { if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { code.AddLine("FragColor{}[{}] = {};", render_target, component, - SafeGetRegister(current_reg)); + SafeGetRegister(current_reg).AsFloat()); ++current_reg; } } @@ -1484,14 +1854,14 @@ private: if (header.ps.omap.depth) { // The depth output is always 2 registers after the last color output, and current_reg // already contains one past the last color register. - code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1)); + code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat()); } code.AddLine("return;"); return {}; } - std::string Discard(Operation operation) { + Expression Discard(Operation operation) { // Enclose "discard" in a conditional, so that GLSL compilation does not complain // about unexecuted instructions that may follow this. code.AddLine("if (true) {{"); @@ -1502,8 +1872,8 @@ private: return {}; } - std::string EmitVertex(Operation operation) { - ASSERT_MSG(stage == ShaderStage::Geometry, + Expression EmitVertex(Operation operation) { + ASSERT_MSG(stage == ProgramType::Geometry, "EmitVertex is expected to be used in a geometry shader."); // If a geometry shader is attached, it will always flip (it's the last stage before @@ -1513,30 +1883,115 @@ private: return {}; } - std::string EndPrimitive(Operation operation) { - ASSERT_MSG(stage == ShaderStage::Geometry, + Expression EndPrimitive(Operation operation) { + ASSERT_MSG(stage == ProgramType::Geometry, "EndPrimitive is expected to be used in a geometry shader."); code.AddLine("EndPrimitive();"); return {}; } - std::string YNegate(Operation operation) { + Expression YNegate(Operation operation) { // Config pack's third value is Y_NEGATE's state. - return "uintBitsToFloat(config_pack[2])"; + return {"config_pack[2]", Type::Uint}; } template <u32 element> - std::string LocalInvocationId(Operation) { - return "utof(gl_LocalInvocationID"s + GetSwizzle(element) + ')'; + Expression LocalInvocationId(Operation) { + return {"gl_LocalInvocationID"s + GetSwizzle(element), Type::Uint}; } template <u32 element> - std::string WorkGroupId(Operation) { - return "utof(gl_WorkGroupID"s + GetSwizzle(element) + ')'; + Expression WorkGroupId(Operation) { + return {"gl_WorkGroupID"s + GetSwizzle(element), Type::Uint}; } - static constexpr OperationDecompilersArray operation_decompilers = { + Expression BallotThread(Operation operation) { + const std::string value = VisitOperand(operation, 0).AsBool(); + if (!device.HasWarpIntrinsics()) { + LOG_ERROR(Render_OpenGL, "Nvidia vote intrinsics are required by this shader"); + // Stub on non-Nvidia devices by simulating all threads voting the same as the active + // one. + return {fmt::format("({} ? 0xFFFFFFFFU : 0U)", value), Type::Uint}; + } + return {fmt::format("ballotThreadNV({})", value), Type::Uint}; + } + + Expression Vote(Operation operation, const char* func) { + const std::string value = VisitOperand(operation, 0).AsBool(); + if (!device.HasWarpIntrinsics()) { + LOG_ERROR(Render_OpenGL, "Nvidia vote intrinsics are required by this shader"); + // Stub with a warp size of one. + return {value, Type::Bool}; + } + return {fmt::format("{}({})", func, value), Type::Bool}; + } + + Expression VoteAll(Operation operation) { + return Vote(operation, "allThreadsNV"); + } + + Expression VoteAny(Operation operation) { + return Vote(operation, "anyThreadNV"); + } + + Expression VoteEqual(Operation operation) { + if (!device.HasWarpIntrinsics()) { + LOG_ERROR(Render_OpenGL, "Nvidia vote intrinsics are required by this shader"); + // We must return true here since a stub for a theoretical warp size of 1. + // This will always return an equal result across all votes. + return {"true", Type::Bool}; + } + return Vote(operation, "allThreadsEqualNV"); + } + + template <const std::string_view& func> + Expression Shuffle(Operation operation) { + const std::string value = VisitOperand(operation, 0).AsFloat(); + if (!device.HasWarpIntrinsics()) { + LOG_ERROR(Render_OpenGL, "Nvidia shuffle intrinsics are required by this shader"); + // On a "single-thread" device we are either on the same thread or out of bounds. Both + // cases return the passed value. + return {value, Type::Float}; + } + + const std::string index = VisitOperand(operation, 1).AsUint(); + const std::string width = VisitOperand(operation, 2).AsUint(); + return {fmt::format("{}({}, {}, {})", func, value, index, width), Type::Float}; + } + + template <const std::string_view& func> + Expression InRangeShuffle(Operation operation) { + const std::string index = VisitOperand(operation, 0).AsUint(); + const std::string width = VisitOperand(operation, 1).AsUint(); + if (!device.HasWarpIntrinsics()) { + // On a "single-thread" device we are only in bounds when the requested index is 0. + return {fmt::format("({} == 0U)", index), Type::Bool}; + } + + const std::string in_range = code.GenerateTemporary(); + code.AddLine("bool {};", in_range); + code.AddLine("{}(0U, {}, {}, {});", func, index, width, in_range); + return {in_range, Type::Bool}; + } + + struct Func final { + Func() = delete; + ~Func() = delete; + + static constexpr std::string_view Add = "Add"; + static constexpr std::string_view And = "And"; + static constexpr std::string_view Or = "Or"; + static constexpr std::string_view Xor = "Xor"; + static constexpr std::string_view Exchange = "Exchange"; + + static constexpr std::string_view ShuffleIndexed = "shuffleNV"; + static constexpr std::string_view ShuffleUp = "shuffleUpNV"; + static constexpr std::string_view ShuffleDown = "shuffleDownNV"; + static constexpr std::string_view ShuffleButterfly = "shuffleXorNV"; + }; + + static constexpr std::array operation_decompilers = { &GLSLDecompiler::Assign, &GLSLDecompiler::Select, @@ -1548,6 +2003,8 @@ private: &GLSLDecompiler::Negate<Type::Float>, &GLSLDecompiler::Absolute<Type::Float>, &GLSLDecompiler::FClamp, + &GLSLDecompiler::FCastHalf0, + &GLSLDecompiler::FCastHalf1, &GLSLDecompiler::Min<Type::Float>, &GLSLDecompiler::Max<Type::Float>, &GLSLDecompiler::FCos, @@ -1608,6 +2065,7 @@ private: &GLSLDecompiler::Absolute<Type::HalfFloat>, &GLSLDecompiler::HNegate, &GLSLDecompiler::HClamp, + &GLSLDecompiler::HCastFloat, &GLSLDecompiler::HUnpack, &GLSLDecompiler::HMergeF32, &GLSLDecompiler::HMergeH0, @@ -1620,8 +2078,7 @@ private: &GLSLDecompiler::LogicalXor, &GLSLDecompiler::LogicalNegate, &GLSLDecompiler::LogicalPick2, - &GLSLDecompiler::LogicalAll2, - &GLSLDecompiler::LogicalAny2, + &GLSLDecompiler::LogicalAnd2, &GLSLDecompiler::LogicalLessThan<Type::Float>, &GLSLDecompiler::LogicalEqual<Type::Float>, @@ -1665,7 +2122,17 @@ private: &GLSLDecompiler::TextureQueryLod, &GLSLDecompiler::TexelFetch, + &GLSLDecompiler::ImageLoad, + &GLSLDecompiler::ImageStore, + + &GLSLDecompiler::AtomicImage<Func::Add>, + &GLSLDecompiler::AtomicImage<Func::And>, + &GLSLDecompiler::AtomicImage<Func::Or>, + &GLSLDecompiler::AtomicImage<Func::Xor>, + &GLSLDecompiler::AtomicImage<Func::Exchange>, + &GLSLDecompiler::Branch, + &GLSLDecompiler::BranchIndirect, &GLSLDecompiler::PushFlowStack, &GLSLDecompiler::PopFlowStack, &GLSLDecompiler::Exit, @@ -1681,7 +2148,23 @@ private: &GLSLDecompiler::WorkGroupId<0>, &GLSLDecompiler::WorkGroupId<1>, &GLSLDecompiler::WorkGroupId<2>, + + &GLSLDecompiler::BallotThread, + &GLSLDecompiler::VoteAll, + &GLSLDecompiler::VoteAny, + &GLSLDecompiler::VoteEqual, + + &GLSLDecompiler::Shuffle<Func::ShuffleIndexed>, + &GLSLDecompiler::Shuffle<Func::ShuffleUp>, + &GLSLDecompiler::Shuffle<Func::ShuffleDown>, + &GLSLDecompiler::Shuffle<Func::ShuffleButterfly>, + + &GLSLDecompiler::InRangeShuffle<Func::ShuffleIndexed>, + &GLSLDecompiler::InRangeShuffle<Func::ShuffleUp>, + &GLSLDecompiler::InRangeShuffle<Func::ShuffleDown>, + &GLSLDecompiler::InRangeShuffle<Func::ShuffleButterfly>, }; + static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount)); std::string GetRegister(u32 index) const { return GetDeclarationWithSuffix(index, "gpr"); @@ -1720,9 +2203,13 @@ private: return "lmem_" + suffix; } + std::string GetSharedMemory() const { + return fmt::format("smem_{}", suffix); + } + std::string GetInternalFlag(InternalFlag flag) const { - constexpr std::array<const char*, 4> InternalFlagNames = {"zero_flag", "sign_flag", - "carry_flag", "overflow_flag"}; + constexpr std::array InternalFlagNames = {"zero_flag", "sign_flag", "carry_flag", + "overflow_flag"}; const auto index = static_cast<u32>(flag); ASSERT(index < static_cast<u32>(InternalFlag::Amount)); @@ -1733,12 +2220,20 @@ 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); } u32 GetNumPhysicalInputAttributes() const { - return stage == ShaderStage::Vertex ? GetNumPhysicalAttributes() : GetNumPhysicalVaryings(); + return IsVertexShader(stage) ? GetNumPhysicalAttributes() : GetNumPhysicalVaryings(); } u32 GetNumPhysicalAttributes() const { @@ -1751,7 +2246,7 @@ private: const Device& device; const ShaderIR& ir; - const ShaderStage stage; + const ProgramType stage; const std::string suffix; const Header header; @@ -1762,27 +2257,19 @@ private: std::string GetCommonDeclarations() { return fmt::format( - "#define MAX_CONSTBUFFER_ELEMENTS {}\n" "#define ftoi floatBitsToInt\n" "#define ftou floatBitsToUint\n" "#define itof intBitsToFloat\n" "#define utof uintBitsToFloat\n\n" - "float fromHalf2(vec2 pair) {{\n" - " return utof(packHalf2x16(pair));\n" - "}}\n\n" - "vec2 toHalf2(float value) {{\n" - " return unpackHalf2x16(ftou(value));\n" - "}}\n\n" - "bvec2 halfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n" + "bvec2 HalfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n" " bvec2 is_nan1 = isnan(pair1);\n" " bvec2 is_nan2 = isnan(pair2);\n" " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || " "is_nan2.y);\n" - "}}\n", - MAX_CONSTBUFFER_ELEMENTS); + "}}\n\n"); } -ProgramResult Decompile(const Device& device, const ShaderIR& ir, Maxwell::ShaderStage stage, +ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, const std::string& suffix) { GLSLDecompiler decompiler(device, ir, stage, suffix); decompiler.Decompile(); diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h index c1569e737..e538dc001 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.h +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -12,14 +12,26 @@ #include "video_core/engines/maxwell_3d.h" #include "video_core/shader/shader_ir.h" -namespace OpenGL { -class Device; -} - namespace VideoCommon::Shader { class ShaderIR; } +namespace OpenGL { + +class Device; + +enum class ProgramType : u32 { + VertexA = 0, + VertexB = 1, + TessellationControl = 2, + TessellationEval = 3, + Geometry = 4, + Fragment = 5, + Compute = 6 +}; + +} // namespace OpenGL + namespace OpenGL::GLShader { struct ShaderEntries; @@ -27,6 +39,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 +87,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{}; @@ -82,6 +96,6 @@ struct ShaderEntries { std::string GetCommonDeclarations(); ProgramResult Decompile(const Device& device, const VideoCommon::Shader::ShaderIR& ir, - Maxwell::ShaderStage stage, const std::string& suffix); + ProgramType stage, const std::string& suffix); } // namespace OpenGL::GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp index ee4a45ca2..6a7012b54 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -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 { @@ -51,7 +51,7 @@ ShaderCacheVersionHash GetShaderCacheVersionHash() { } // namespace -ShaderDiskCacheRaw::ShaderDiskCacheRaw(u64 unique_identifier, Maxwell::ShaderProgram program_type, +ShaderDiskCacheRaw::ShaderDiskCacheRaw(u64 unique_identifier, ProgramType program_type, u32 program_code_size, u32 program_code_size_b, ProgramCode program_code, ProgramCode program_code_b) : unique_identifier{unique_identifier}, program_type{program_type}, @@ -332,11 +332,34 @@ 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{}; + u8 is_written{}; + u8 is_read{}; + u8 is_atomic{}; + if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) || + !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) || + !LoadObjectFromPrecompiled(is_written) || !LoadObjectFromPrecompiled(is_read) || + !LoadObjectFromPrecompiled(is_atomic)) { + return {}; + } + entry.entries.images.emplace_back( + static_cast<std::size_t>(offset), static_cast<std::size_t>(index), + static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0, is_written != 0, + is_read != 0, is_atomic != 0); + } + u32 global_memory_count{}; if (!LoadObjectFromPrecompiled(global_memory_count)) { return {}; } - for (u32 i = 0; i < global_memory_count; ++i) { u32 cbuf_index{}; u32 cbuf_offset{}; @@ -360,7 +383,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 +422,21 @@ 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)) || + !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0)) || + !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) || + !SaveObjectToPrecompiled(static_cast<u8>(image.IsAtomic() ? 1 : 0))) { + return false; + } + } + if (!SaveObjectToPrecompiled(static_cast<u32>(entries.global_memory_entries.size()))) { return false; } diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h index ecd72ba58..cc8bbd61e 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h @@ -4,6 +4,7 @@ #pragma once +#include <bitset> #include <optional> #include <string> #include <tuple> @@ -17,7 +18,6 @@ #include "common/assert.h" #include "common/common_types.h" #include "core/file_sys/vfs_vector.h" -#include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_opengl/gl_shader_gen.h" namespace Core { @@ -30,22 +30,23 @@ class IOFile; namespace OpenGL { -using ProgramCode = std::vector<u64>; -using Maxwell = Tegra::Engines::Maxwell3D::Regs; - struct ShaderDiskCacheUsage; struct ShaderDiskCacheDump; +using ProgramCode = std::vector<u64>; using ShaderDumpsMap = std::unordered_map<ShaderDiskCacheUsage, ShaderDiskCacheDump>; +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 +54,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 +91,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 +111,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); } }; @@ -95,7 +122,7 @@ namespace OpenGL { /// Describes a shader how it's used by the guest GPU class ShaderDiskCacheRaw { public: - explicit ShaderDiskCacheRaw(u64 unique_identifier, Maxwell::ShaderProgram program_type, + explicit ShaderDiskCacheRaw(u64 unique_identifier, ProgramType program_type, u32 program_code_size, u32 program_code_size_b, ProgramCode program_code, ProgramCode program_code_b); ShaderDiskCacheRaw(); @@ -110,30 +137,13 @@ public: } bool HasProgramA() const { - return program_type == Maxwell::ShaderProgram::VertexA; + return program_type == ProgramType::VertexA; } - Maxwell::ShaderProgram GetProgramType() const { + ProgramType GetProgramType() const { return program_type; } - Maxwell::ShaderStage GetProgramStage() const { - switch (program_type) { - case Maxwell::ShaderProgram::VertexA: - case Maxwell::ShaderProgram::VertexB: - return Maxwell::ShaderStage::Vertex; - case Maxwell::ShaderProgram::TesselationControl: - return Maxwell::ShaderStage::TesselationControl; - case Maxwell::ShaderProgram::TesselationEval: - return Maxwell::ShaderStage::TesselationEval; - case Maxwell::ShaderProgram::Geometry: - return Maxwell::ShaderStage::Geometry; - case Maxwell::ShaderProgram::Fragment: - return Maxwell::ShaderStage::Fragment; - } - UNREACHABLE(); - } - const ProgramCode& GetProgramCode() const { return program_code; } @@ -144,7 +154,7 @@ public: private: u64 unique_identifier{}; - Maxwell::ShaderProgram program_type{}; + ProgramType program_type{}; u32 program_code_size{}; u32 program_code_size_b{}; @@ -275,26 +285,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{}; }; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 9148629ec..3a8d9e1da 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -14,7 +14,8 @@ using Tegra::Engines::Maxwell3D; using VideoCommon::Shader::ProgramCode; using VideoCommon::Shader::ShaderIR; -static constexpr u32 PROGRAM_OFFSET{10}; +static constexpr u32 PROGRAM_OFFSET = 10; +static constexpr u32 COMPUTE_OFFSET = 0; ProgramResult GenerateVertexShader(const Device& device, const ShaderSetup& setup) { const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); @@ -29,17 +30,15 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform vs_config { }; )"; - const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET); - ProgramResult program = - Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Vertex, "vertex"); + const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a); + const auto stage = setup.IsDualProgram() ? ProgramType::VertexA : ProgramType::VertexB; + ProgramResult program = Decompile(device, program_ir, stage, "vertex"); out += program.first; if (setup.IsDualProgram()) { - const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET); - ProgramResult program_b = - Decompile(device, program_ir_b, Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b"); - + const ShaderIR program_ir_b(setup.program.code_b, PROGRAM_OFFSET, setup.program.size_b); + ProgramResult program_b = Decompile(device, program_ir_b, ProgramType::VertexB, "vertex_b"); out += program_b.first; } @@ -80,9 +79,9 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform gs_config { }; )"; - const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET); - ProgramResult program = - Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Geometry, "geometry"); + + const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a); + ProgramResult program = Decompile(device, program_ir, ProgramType::Geometry, "geometry"); out += program.first; out += R"( @@ -115,10 +114,8 @@ layout (std140, binding = EMULATION_UBO_BINDING) uniform fs_config { }; )"; - const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET); - ProgramResult program = - Decompile(device, program_ir, Maxwell3D::Regs::ShaderStage::Fragment, "fragment"); - + const ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET, setup.program.size_a); + ProgramResult program = Decompile(device, program_ir, ProgramType::Fragment, "fragment"); out += program.first; out += R"( @@ -130,4 +127,22 @@ void main() { return {std::move(out), std::move(program.second)}; } +ProgramResult GenerateComputeShader(const Device& device, const ShaderSetup& setup) { + const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); + + std::string out = "// Shader Unique Id: CS" + id + "\n\n"; + out += GetCommonDeclarations(); + + const ShaderIR program_ir(setup.program.code, COMPUTE_OFFSET, setup.program.size_a); + ProgramResult program = Decompile(device, program_ir, ProgramType::Compute, "compute"); + out += program.first; + + out += R"( +void main() { + execute_compute(); +} +)"; + return {std::move(out), std::move(program.second)}; +} + } // namespace OpenGL::GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 0536c8a03..3833e88ab 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -27,6 +27,8 @@ struct ShaderSetup { ProgramCode code; ProgramCode code_b; // Used for dual vertex shaders u64 unique_identifier; + std::size_t size_a; + std::size_t size_b; } program; /// Used in scenarios where we have a dual vertex shaders @@ -52,4 +54,7 @@ ProgramResult GenerateGeometryShader(const Device& device, const ShaderSetup& se /// Generates the GLSL fragment shader program source code for the given FS program ProgramResult GenerateFragmentShader(const Device& device, const ShaderSetup& setup); +/// Generates the GLSL compute shader program source code for the given CS program +ProgramResult GenerateComputeShader(const Device& device, const ShaderSetup& setup); + } // namespace OpenGL::GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp index 5f3fe067e..9e74eda0d 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.cpp +++ b/src/video_core/renderer_opengl/gl_shader_util.cpp @@ -10,21 +10,25 @@ namespace OpenGL::GLShader { -GLuint LoadShader(const char* source, GLenum type) { - const char* debug_type; +namespace { +const char* GetStageDebugName(GLenum type) { switch (type) { case GL_VERTEX_SHADER: - debug_type = "vertex"; - break; + return "vertex"; case GL_GEOMETRY_SHADER: - debug_type = "geometry"; - break; + return "geometry"; case GL_FRAGMENT_SHADER: - debug_type = "fragment"; - break; - default: - UNREACHABLE(); + return "fragment"; + case GL_COMPUTE_SHADER: + return "compute"; } + UNIMPLEMENTED(); + return "unknown"; +} +} // Anonymous namespace + +GLuint LoadShader(const char* source, GLenum type) { + const char* debug_type = GetStageDebugName(type); const GLuint shader_id = glCreateShader(type); glShaderSource(shader_id, 1, &source, nullptr); LOG_DEBUG(Render_OpenGL, "Compiling {} shader...", debug_type); diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index d86e137ac..bf86b5a0b 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -6,14 +6,16 @@ #include <glad/glad.h> #include "common/assert.h" #include "common/logging/log.h" +#include "common/microprofile.h" #include "video_core/renderer_opengl/gl_state.h" +MICROPROFILE_DEFINE(OpenGL_State, "OpenGL", "State Change", MP_RGB(192, 128, 128)); + namespace OpenGL { using Maxwell = Tegra::Engines::Maxwell3D::Regs; OpenGLState OpenGLState::cur_state; -bool OpenGLState::s_rgb_used; namespace { @@ -31,6 +33,25 @@ bool UpdateTie(T1 current_value, const T2 new_value) { return changed; } +template <typename T> +std::optional<std::pair<GLuint, GLsizei>> UpdateArray(T& current_values, const T& new_values) { + std::optional<std::size_t> first; + std::size_t last; + for (std::size_t i = 0; i < std::size(current_values); ++i) { + if (!UpdateValue(current_values[i], new_values[i])) { + continue; + } + if (!first) { + first = i; + } + last = i; + } + if (!first) { + return std::nullopt; + } + return std::make_pair(static_cast<GLuint>(*first), static_cast<GLsizei>(last - *first + 1)); +} + void Enable(GLenum cap, bool enable) { if (enable) { glEnable(cap); @@ -131,10 +152,6 @@ OpenGLState::OpenGLState() { logic_op.enabled = false; logic_op.operation = GL_COPY; - for (auto& texture_unit : texture_units) { - texture_unit.Reset(); - } - draw.read_framebuffer = 0; draw.draw_framebuffer = 0; draw.vertex_array = 0; @@ -162,6 +179,25 @@ OpenGLState::OpenGLState() { alpha_test.ref = 0.0f; } +void OpenGLState::SetDefaultViewports() { + for (auto& item : viewports) { + item.x = 0; + item.y = 0; + item.width = 0; + item.height = 0; + item.depth_range_near = 0.0f; + item.depth_range_far = 1.0f; + item.scissor.enabled = false; + item.scissor.x = 0; + item.scissor.y = 0; + item.scissor.width = 0; + item.scissor.height = 0; + } + + depth_clamp.far_plane = false; + depth_clamp.near_plane = false; +} + void OpenGLState::ApplyDefaultState() { glEnable(GL_BLEND); glDisable(GL_FRAMEBUFFER_SRGB); @@ -245,8 +281,6 @@ void OpenGLState::ApplySRgb() const { return; cur_state.framebuffer_srgb.enabled = framebuffer_srgb.enabled; if (framebuffer_srgb.enabled) { - // Track if sRGB is used - s_rgb_used = true; glEnable(GL_FRAMEBUFFER_SRGB); } else { glDisable(GL_FRAMEBUFFER_SRGB); @@ -474,56 +508,25 @@ void OpenGLState::ApplyAlphaTest() const { } void OpenGLState::ApplyTextures() const { - bool has_delta{}; - std::size_t first{}; - std::size_t last{}; - std::array<GLuint, Maxwell::NumTextureSamplers> textures; - - for (std::size_t i = 0; i < std::size(texture_units); ++i) { - const auto& texture_unit = texture_units[i]; - auto& cur_state_texture_unit = cur_state.texture_units[i]; - textures[i] = texture_unit.texture; - if (cur_state_texture_unit.texture == textures[i]) { - continue; - } - cur_state_texture_unit.texture = textures[i]; - if (!has_delta) { - first = i; - has_delta = true; - } - last = i; - } - if (has_delta) { - glBindTextures(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1), - textures.data() + first); + if (const auto update = UpdateArray(cur_state.textures, textures)) { + glBindTextures(update->first, update->second, textures.data() + update->first); } } void OpenGLState::ApplySamplers() const { - bool has_delta{}; - std::size_t first{}; - std::size_t last{}; - std::array<GLuint, Maxwell::NumTextureSamplers> samplers; - - for (std::size_t i = 0; i < std::size(samplers); ++i) { - samplers[i] = texture_units[i].sampler; - if (cur_state.texture_units[i].sampler == texture_units[i].sampler) { - continue; - } - cur_state.texture_units[i].sampler = texture_units[i].sampler; - if (!has_delta) { - first = i; - has_delta = true; - } - last = i; + if (const auto update = UpdateArray(cur_state.samplers, samplers)) { + glBindSamplers(update->first, update->second, samplers.data() + update->first); } - if (has_delta) { - glBindSamplers(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1), - samplers.data() + first); +} + +void OpenGLState::ApplyImages() const { + if (const auto update = UpdateArray(cur_state.images, images)) { + glBindImageTextures(update->first, update->second, images.data() + update->first); } } -void OpenGLState::Apply() const { +void OpenGLState::Apply() { + MICROPROFILE_SCOPE(OpenGL_State); ApplyFramebufferState(); ApplyVertexArrayState(); ApplyShaderProgram(); @@ -532,19 +535,32 @@ void OpenGLState::Apply() const { ApplyPointSize(); ApplyFragmentColorClamp(); ApplyMultisample(); + if (dirty.color_mask) { + ApplyColorMask(); + dirty.color_mask = false; + } ApplyDepthClamp(); - ApplyColorMask(); ApplyViewport(); - ApplyStencilTest(); + if (dirty.stencil_state) { + ApplyStencilTest(); + dirty.stencil_state = false; + } ApplySRgb(); ApplyCulling(); ApplyDepth(); ApplyPrimitiveRestart(); - ApplyBlending(); + if (dirty.blend_state) { + ApplyBlending(); + dirty.blend_state = false; + } ApplyLogicOp(); ApplyTextures(); ApplySamplers(); - ApplyPolygonOffset(); + ApplyImages(); + if (dirty.polygon_offset) { + ApplyPolygonOffset(); + dirty.polygon_offset = false; + } ApplyAlphaTest(); } @@ -571,18 +587,18 @@ void OpenGLState::EmulateViewportWithScissor() { } OpenGLState& OpenGLState::UnbindTexture(GLuint handle) { - for (auto& unit : texture_units) { - if (unit.texture == handle) { - unit.Unbind(); + for (auto& texture : textures) { + if (texture == handle) { + texture = 0; } } return *this; } OpenGLState& OpenGLState::ResetSampler(GLuint handle) { - for (auto& unit : texture_units) { - if (unit.sampler == handle) { - unit.sampler = 0; + for (auto& sampler : samplers) { + if (sampler == handle) { + sampler = 0; } } return *this; diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index b0140495d..c358d3b38 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -118,21 +118,9 @@ public: GLenum operation; } logic_op; - // 3 texture units - one for each that is used in PICA fragment shader emulation - struct TextureUnit { - GLuint texture; // GL_TEXTURE_BINDING_2D - GLuint sampler; // GL_SAMPLER_BINDING - - void Unbind() { - texture = 0; - } - - void Reset() { - Unbind(); - sampler = 0; - } - }; - std::array<TextureUnit, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_units; + std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> textures{}; + std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> samplers{}; + std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumImages> images{}; struct { GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING @@ -187,16 +175,9 @@ public: return cur_state; } - static bool GetsRGBUsed() { - return s_rgb_used; - } - - static void ClearsRGBUsed() { - s_rgb_used = false; - } - + void SetDefaultViewports(); /// Apply this state as the current OpenGL state - void Apply() const; + void Apply(); void ApplyFramebufferState() const; void ApplyVertexArrayState() const; @@ -219,6 +200,7 @@ public: void ApplyLogicOp() const; void ApplyTextures() const; void ApplySamplers() const; + void ApplyImages() const; void ApplyDepthClamp() const; void ApplyPolygonOffset() const; void ApplyAlphaTest() const; @@ -237,11 +219,39 @@ public: /// Viewport does not affects glClearBuffer so emulate viewport using scissor test void EmulateViewportWithScissor(); + void MarkDirtyBlendState() { + dirty.blend_state = true; + } + + void MarkDirtyStencilState() { + dirty.stencil_state = true; + } + + void MarkDirtyPolygonOffset() { + dirty.polygon_offset = true; + } + + void MarkDirtyColorMask() { + dirty.color_mask = true; + } + + void AllDirty() { + dirty.blend_state = true; + dirty.stencil_state = true; + dirty.polygon_offset = true; + dirty.color_mask = true; + } + private: static OpenGLState cur_state; - // Workaround for sRGB problems caused by QT not supporting srgb output - static bool s_rgb_used; + struct { + bool blend_state; + bool stencil_state; + bool viewport_state; + bool polygon_offset; + bool color_mask; + } dirty{}; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp index d0b14b3f6..35ba334e4 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp +++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp @@ -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 = diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h index 3d18ecb4d..f8383cbd4 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.h +++ b/src/video_core/renderer_opengl/gl_stream_buffer.h @@ -13,7 +13,8 @@ namespace OpenGL { class OGLStreamBuffer : private NonCopyable { public: - explicit OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent = false); + explicit OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent = false, + bool use_persistent = true); ~OGLStreamBuffer(); GLuint GetHandle() const; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp new file mode 100644 index 000000000..173b76c4e --- /dev/null +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -0,0 +1,625 @@ +// 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/bit_util.h" +#include "common/common_types.h" +#include "common/microprofile.h" +#include "common/scope_exit.h" +#include "core/core.h" +#include "video_core/morton.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" +#include "video_core/renderer_opengl/utils.h" +#include "video_core/texture_cache/surface_base.h" +#include "video_core/texture_cache/texture_cache.h" +#include "video_core/textures/convert.h" +#include "video_core/textures/texture.h" + +namespace OpenGL { + +using Tegra::Texture::SwizzleSource; +using VideoCore::MortonSwizzleMode; + +using VideoCore::Surface::ComponentType; +using VideoCore::Surface::PixelFormat; +using VideoCore::Surface::SurfaceCompression; +using VideoCore::Surface::SurfaceTarget; +using VideoCore::Surface::SurfaceType; + +MICROPROFILE_DEFINE(OpenGL_Texture_Upload, "OpenGL", "Texture Upload", MP_RGB(128, 192, 128)); +MICROPROFILE_DEFINE(OpenGL_Texture_Download, "OpenGL", "Texture Download", MP_RGB(128, 192, 128)); +MICROPROFILE_DEFINE(OpenGL_Texture_Buffer_Copy, "OpenGL", "Texture Buffer Copy", + MP_RGB(128, 192, 128)); + +namespace { + +struct FormatTuple { + GLint internal_format; + GLenum format; + GLenum type; + ComponentType component_type; + bool compressed; +}; + +constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format_tuples = {{ + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U + {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S + {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI + {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U + {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm, + false}, // A2B10G10R10U + {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5U + {GL_R8, GL_RED, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // R8U + {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // R8UI + {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, ComponentType::Float, false}, // RGBA16F + {GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RGBA16U + {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RGBA16UI + {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float, + false}, // R11FG11FB10F + {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI + {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT1 + {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT23 + {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT45 + {GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // DXN1 + {GL_COMPRESSED_RG_RGTC2, GL_RG, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXN2UNORM + {GL_COMPRESSED_SIGNED_RG_RGTC2, GL_RG, GL_INT, ComponentType::SNorm, true}, // DXN2SNORM + {GL_COMPRESSED_RGBA_BPTC_UNORM, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // BC7U + {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float, + true}, // BC6H_UF16 + {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float, + true}, // BC6H_SF16 + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4 + {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8 + {GL_RGBA32F, GL_RGBA, GL_FLOAT, ComponentType::Float, false}, // RGBA32F + {GL_RG32F, GL_RG, GL_FLOAT, ComponentType::Float, false}, // RG32F + {GL_R32F, GL_RED, GL_FLOAT, ComponentType::Float, false}, // R32F + {GL_R16F, GL_RED, GL_HALF_FLOAT, ComponentType::Float, false}, // R16F + {GL_R16, GL_RED, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // R16U + {GL_R16_SNORM, GL_RED, GL_SHORT, ComponentType::SNorm, false}, // R16S + {GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // R16UI + {GL_R16I, GL_RED_INTEGER, GL_SHORT, ComponentType::SInt, false}, // R16I + {GL_RG16, GL_RG, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RG16 + {GL_RG16F, GL_RG, GL_HALF_FLOAT, ComponentType::Float, false}, // RG16F + {GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RG16UI + {GL_RG16I, GL_RG_INTEGER, GL_SHORT, ComponentType::SInt, false}, // RG16I + {GL_RG16_SNORM, GL_RG, GL_SHORT, ComponentType::SNorm, false}, // RG16S + {GL_RGB32F, GL_RGB, GL_FLOAT, ComponentType::Float, false}, // RGB32F + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, + false}, // RGBA8_SRGB + {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U + {GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S + {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI + {GL_RGB16F, GL_RGBA16, GL_HALF_FLOAT, ComponentType::Float, false}, // RGBX16F + {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8 + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5 + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4 + {GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8 + // Compressed sRGB formats + {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT1_SRGB + {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT23_SRGB + {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT45_SRGB + {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // BC7U_SRGB + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4_SRGB + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8_SRGB + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5_SRGB + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4_SRGB + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5 + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5_SRGB + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X8 + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X8_SRGB + + // Depth formats + {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F + {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, ComponentType::UNorm, + false}, // Z16 + + // DepthStencil formats + {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm, + false}, // Z24S8 + {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm, + false}, // S8Z24 + {GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, + ComponentType::Float, false}, // Z32FS8 +}}; + +const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) { + ASSERT(static_cast<std::size_t>(pixel_format) < tex_format_tuples.size()); + const auto& format{tex_format_tuples[static_cast<std::size_t>(pixel_format)]}; + return format; +} + +GLenum GetTextureTarget(const SurfaceTarget& target) { + switch (target) { + case SurfaceTarget::TextureBuffer: + return GL_TEXTURE_BUFFER; + case SurfaceTarget::Texture1D: + return GL_TEXTURE_1D; + case SurfaceTarget::Texture2D: + return GL_TEXTURE_2D; + case SurfaceTarget::Texture3D: + return GL_TEXTURE_3D; + case SurfaceTarget::Texture1DArray: + return GL_TEXTURE_1D_ARRAY; + case SurfaceTarget::Texture2DArray: + return GL_TEXTURE_2D_ARRAY; + case SurfaceTarget::TextureCubemap: + return GL_TEXTURE_CUBE_MAP; + case SurfaceTarget::TextureCubeArray: + return GL_TEXTURE_CUBE_MAP_ARRAY; + } + UNREACHABLE(); + return {}; +} + +GLint GetSwizzleSource(SwizzleSource source) { + switch (source) { + case SwizzleSource::Zero: + return GL_ZERO; + case SwizzleSource::R: + return GL_RED; + case SwizzleSource::G: + return GL_GREEN; + case SwizzleSource::B: + return GL_BLUE; + case SwizzleSource::A: + return GL_ALPHA; + case SwizzleSource::OneInt: + case SwizzleSource::OneFloat: + return GL_ONE; + } + UNREACHABLE(); + return GL_NONE; +} + +void ApplyTextureDefaults(const SurfaceParams& params, GLuint texture) { + if (params.IsBuffer()) { + return; + } + glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, params.num_levels - 1); + if (params.num_levels == 1) { + glTextureParameterf(texture, GL_TEXTURE_LOD_BIAS, 1000.0f); + } +} + +OGLTexture CreateTexture(const SurfaceParams& params, GLenum target, GLenum internal_format, + OGLBuffer& texture_buffer) { + OGLTexture texture; + texture.Create(target); + + switch (params.target) { + case SurfaceTarget::Texture1D: + glTextureStorage1D(texture.handle, params.emulated_levels, internal_format, params.width); + break; + case SurfaceTarget::TextureBuffer: + texture_buffer.Create(); + glNamedBufferStorage(texture_buffer.handle, params.width * params.GetBytesPerPixel(), + nullptr, GL_DYNAMIC_STORAGE_BIT); + glTextureBuffer(texture.handle, internal_format, texture_buffer.handle); + break; + case SurfaceTarget::Texture2D: + case SurfaceTarget::TextureCubemap: + glTextureStorage2D(texture.handle, params.emulated_levels, internal_format, params.width, + params.height); + break; + case SurfaceTarget::Texture3D: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubeArray: + glTextureStorage3D(texture.handle, params.emulated_levels, internal_format, params.width, + params.height, params.depth); + break; + default: + UNREACHABLE(); + } + + ApplyTextureDefaults(params, texture.handle); + + return texture; +} + +} // Anonymous namespace + +CachedSurface::CachedSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) + : VideoCommon::SurfaceBase<View>(gpu_addr, params) { + const auto& tuple{GetFormatTuple(params.pixel_format, params.component_type)}; + internal_format = tuple.internal_format; + format = tuple.format; + type = tuple.type; + is_compressed = tuple.compressed; + target = GetTextureTarget(params.target); + texture = CreateTexture(params, target, internal_format, texture_buffer); + DecorateSurfaceName(); + main_view = CreateViewInner( + ViewParams(params.target, 0, params.is_layered ? params.depth : 1, 0, params.num_levels), + true); +} + +CachedSurface::~CachedSurface() = default; + +void CachedSurface::DownloadTexture(std::vector<u8>& staging_buffer) { + MICROPROFILE_SCOPE(OpenGL_Texture_Download); + + SCOPE_EXIT({ glPixelStorei(GL_PACK_ROW_LENGTH, 0); }); + + for (u32 level = 0; level < params.emulated_levels; ++level) { + glPixelStorei(GL_PACK_ALIGNMENT, std::min(8U, params.GetRowAlignment(level))); + glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.GetMipWidth(level))); + const std::size_t mip_offset = params.GetHostMipmapLevelOffset(level); + if (is_compressed) { + glGetCompressedTextureImage(texture.handle, level, + static_cast<GLsizei>(params.GetHostMipmapSize(level)), + staging_buffer.data() + mip_offset); + } else { + glGetTextureImage(texture.handle, level, format, type, + static_cast<GLsizei>(params.GetHostMipmapSize(level)), + staging_buffer.data() + mip_offset); + } + } +} + +void CachedSurface::UploadTexture(const std::vector<u8>& staging_buffer) { + MICROPROFILE_SCOPE(OpenGL_Texture_Upload); + SCOPE_EXIT({ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); }); + for (u32 level = 0; level < params.emulated_levels; ++level) { + UploadTextureMipmap(level, staging_buffer); + } +} + +void CachedSurface::UploadTextureMipmap(u32 level, const std::vector<u8>& staging_buffer) { + glPixelStorei(GL_UNPACK_ALIGNMENT, std::min(8U, params.GetRowAlignment(level))); + glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.GetMipWidth(level))); + + auto compression_type = params.GetCompressionType(); + + const std::size_t mip_offset = compression_type == SurfaceCompression::Converted + ? params.GetConvertedMipmapOffset(level) + : params.GetHostMipmapLevelOffset(level); + const u8* buffer{staging_buffer.data() + mip_offset}; + if (is_compressed) { + const auto image_size{static_cast<GLsizei>(params.GetHostMipmapSize(level))}; + switch (params.target) { + case SurfaceTarget::Texture2D: + glCompressedTextureSubImage2D(texture.handle, level, 0, 0, + static_cast<GLsizei>(params.GetMipWidth(level)), + static_cast<GLsizei>(params.GetMipHeight(level)), + internal_format, image_size, buffer); + break; + case SurfaceTarget::Texture3D: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubeArray: + glCompressedTextureSubImage3D(texture.handle, level, 0, 0, 0, + static_cast<GLsizei>(params.GetMipWidth(level)), + static_cast<GLsizei>(params.GetMipHeight(level)), + static_cast<GLsizei>(params.GetMipDepth(level)), + internal_format, image_size, buffer); + break; + case SurfaceTarget::TextureCubemap: { + const std::size_t layer_size{params.GetHostLayerSize(level)}; + for (std::size_t face = 0; face < params.depth; ++face) { + glCompressedTextureSubImage3D(texture.handle, level, 0, 0, static_cast<GLint>(face), + static_cast<GLsizei>(params.GetMipWidth(level)), + static_cast<GLsizei>(params.GetMipHeight(level)), 1, + internal_format, static_cast<GLsizei>(layer_size), + buffer); + buffer += layer_size; + } + break; + } + default: + UNREACHABLE(); + } + } else { + switch (params.target) { + case SurfaceTarget::Texture1D: + glTextureSubImage1D(texture.handle, level, 0, params.GetMipWidth(level), format, type, + buffer); + break; + case SurfaceTarget::TextureBuffer: + ASSERT(level == 0); + glNamedBufferSubData(texture_buffer.handle, 0, + params.GetMipWidth(level) * params.GetBytesPerPixel(), buffer); + break; + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2D: + glTextureSubImage2D(texture.handle, level, 0, 0, params.GetMipWidth(level), + params.GetMipHeight(level), format, type, buffer); + break; + case SurfaceTarget::Texture3D: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubeArray: + glTextureSubImage3D( + texture.handle, level, 0, 0, 0, static_cast<GLsizei>(params.GetMipWidth(level)), + static_cast<GLsizei>(params.GetMipHeight(level)), + static_cast<GLsizei>(params.GetMipDepth(level)), format, type, buffer); + break; + case SurfaceTarget::TextureCubemap: + for (std::size_t face = 0; face < params.depth; ++face) { + glTextureSubImage3D(texture.handle, level, 0, 0, static_cast<GLint>(face), + params.GetMipWidth(level), params.GetMipHeight(level), 1, + format, type, buffer); + buffer += params.GetHostLayerSize(level); + } + break; + default: + UNREACHABLE(); + } + } +} + +void CachedSurface::DecorateSurfaceName() { + LabelGLObject(GL_TEXTURE, texture.handle, GetGpuAddr(), params.TargetName()); +} + +void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, std::string prefix) { + LabelGLObject(GL_TEXTURE, texture_view.handle, gpu_addr, prefix); +} + +View CachedSurface::CreateView(const ViewParams& view_key) { + return CreateViewInner(view_key, false); +} + +View CachedSurface::CreateViewInner(const ViewParams& view_key, const bool is_proxy) { + auto view = std::make_shared<CachedSurfaceView>(*this, view_key, is_proxy); + views[view_key] = view; + if (!is_proxy) + view->DecorateViewName(gpu_addr, params.TargetName() + "V:" + std::to_string(view_count++)); + return view; +} + +CachedSurfaceView::CachedSurfaceView(CachedSurface& surface, const ViewParams& params, + const bool is_proxy) + : VideoCommon::ViewBase(params), surface{surface}, is_proxy{is_proxy} { + target = GetTextureTarget(params.target); + if (!is_proxy) { + texture_view = CreateTextureView(); + } + swizzle = EncodeSwizzle(SwizzleSource::R, SwizzleSource::G, SwizzleSource::B, SwizzleSource::A); +} + +CachedSurfaceView::~CachedSurfaceView() = default; + +void CachedSurfaceView::Attach(GLenum attachment, GLenum target) const { + ASSERT(params.num_layers == 1 && params.num_levels == 1); + + const auto& owner_params = surface.GetSurfaceParams(); + + switch (owner_params.target) { + case SurfaceTarget::Texture1D: + glFramebufferTexture1D(target, attachment, surface.GetTarget(), surface.GetTexture(), + params.base_level); + break; + case SurfaceTarget::Texture2D: + glFramebufferTexture2D(target, attachment, surface.GetTarget(), surface.GetTexture(), + params.base_level); + break; + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubemap: + case SurfaceTarget::TextureCubeArray: + glFramebufferTextureLayer(target, attachment, surface.GetTexture(), params.base_level, + params.base_layer); + break; + default: + UNIMPLEMENTED(); + } +} + +void CachedSurfaceView::ApplySwizzle(SwizzleSource x_source, SwizzleSource y_source, + SwizzleSource z_source, SwizzleSource w_source) { + u32 new_swizzle = EncodeSwizzle(x_source, y_source, z_source, w_source); + if (new_swizzle == swizzle) + return; + swizzle = new_swizzle; + const std::array<GLint, 4> gl_swizzle = {GetSwizzleSource(x_source), GetSwizzleSource(y_source), + GetSwizzleSource(z_source), + GetSwizzleSource(w_source)}; + const GLuint handle = GetTexture(); + glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data()); +} + +OGLTextureView CachedSurfaceView::CreateTextureView() const { + const auto& owner_params = surface.GetSurfaceParams(); + OGLTextureView texture_view; + texture_view.Create(); + + const GLuint handle{texture_view.handle}; + const FormatTuple& tuple{ + GetFormatTuple(owner_params.pixel_format, owner_params.component_type)}; + + glTextureView(handle, target, surface.texture.handle, tuple.internal_format, params.base_level, + params.num_levels, params.base_layer, params.num_layers); + + ApplyTextureDefaults(owner_params, handle); + + return texture_view; +} + +TextureCacheOpenGL::TextureCacheOpenGL(Core::System& system, + VideoCore::RasterizerInterface& rasterizer, + const Device& device) + : TextureCacheBase{system, rasterizer} { + src_framebuffer.Create(); + dst_framebuffer.Create(); +} + +TextureCacheOpenGL::~TextureCacheOpenGL() = default; + +Surface TextureCacheOpenGL::CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) { + return std::make_shared<CachedSurface>(gpu_addr, params); +} + +void TextureCacheOpenGL::ImageCopy(Surface& src_surface, Surface& dst_surface, + const VideoCommon::CopyParams& copy_params) { + const auto& src_params = src_surface->GetSurfaceParams(); + const auto& dst_params = dst_surface->GetSurfaceParams(); + if (src_params.type != dst_params.type) { + // A fallback is needed + return; + } + const auto src_handle = src_surface->GetTexture(); + const auto src_target = src_surface->GetTarget(); + const auto dst_handle = dst_surface->GetTexture(); + const auto dst_target = dst_surface->GetTarget(); + glCopyImageSubData(src_handle, src_target, copy_params.source_level, copy_params.source_x, + copy_params.source_y, copy_params.source_z, dst_handle, dst_target, + copy_params.dest_level, copy_params.dest_x, copy_params.dest_y, + copy_params.dest_z, copy_params.width, copy_params.height, + copy_params.depth); +} + +void TextureCacheOpenGL::ImageBlit(View& src_view, View& dst_view, + const Tegra::Engines::Fermi2D::Config& copy_config) { + const auto& src_params{src_view->GetSurfaceParams()}; + const auto& dst_params{dst_view->GetSurfaceParams()}; + + OpenGLState prev_state{OpenGLState::GetCurState()}; + SCOPE_EXIT({ + prev_state.AllDirty(); + prev_state.Apply(); + }); + + OpenGLState state; + state.draw.read_framebuffer = src_framebuffer.handle; + state.draw.draw_framebuffer = dst_framebuffer.handle; + state.AllDirty(); + state.Apply(); + + u32 buffers{}; + + UNIMPLEMENTED_IF(src_params.target == SurfaceTarget::Texture3D); + UNIMPLEMENTED_IF(dst_params.target == SurfaceTarget::Texture3D); + + if (src_params.type == SurfaceType::ColorTexture) { + src_view->Attach(GL_COLOR_ATTACHMENT0, GL_READ_FRAMEBUFFER); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + + dst_view->Attach(GL_COLOR_ATTACHMENT0, GL_DRAW_FRAMEBUFFER); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + + buffers = GL_COLOR_BUFFER_BIT; + } else if (src_params.type == SurfaceType::Depth) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + src_view->Attach(GL_DEPTH_ATTACHMENT, GL_READ_FRAMEBUFFER); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + dst_view->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + buffers = GL_DEPTH_BUFFER_BIT; + } else if (src_params.type == SurfaceType::DepthStencil) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + src_view->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_READ_FRAMEBUFFER); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + dst_view->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_DRAW_FRAMEBUFFER); + + buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + } + + const Common::Rectangle<u32>& src_rect = copy_config.src_rect; + const Common::Rectangle<u32>& dst_rect = copy_config.dst_rect; + const bool is_linear = copy_config.filter == Tegra::Engines::Fermi2D::Filter::Linear; + + glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom, dst_rect.left, + dst_rect.top, dst_rect.right, dst_rect.bottom, buffers, + is_linear && (buffers == GL_COLOR_BUFFER_BIT) ? GL_LINEAR : GL_NEAREST); +} + +void TextureCacheOpenGL::BufferCopy(Surface& src_surface, Surface& dst_surface) { + MICROPROFILE_SCOPE(OpenGL_Texture_Buffer_Copy); + const auto& src_params = src_surface->GetSurfaceParams(); + const auto& dst_params = dst_surface->GetSurfaceParams(); + UNIMPLEMENTED_IF(src_params.num_levels > 1 || dst_params.num_levels > 1); + + const auto source_format = GetFormatTuple(src_params.pixel_format, src_params.component_type); + const auto dest_format = GetFormatTuple(dst_params.pixel_format, dst_params.component_type); + + const std::size_t source_size = src_surface->GetHostSizeInBytes(); + const std::size_t dest_size = dst_surface->GetHostSizeInBytes(); + + const std::size_t buffer_size = std::max(source_size, dest_size); + + GLuint copy_pbo_handle = FetchPBO(buffer_size); + + glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle); + + if (source_format.compressed) { + glGetCompressedTextureImage(src_surface->GetTexture(), 0, static_cast<GLsizei>(source_size), + nullptr); + } else { + glGetTextureImage(src_surface->GetTexture(), 0, source_format.format, source_format.type, + static_cast<GLsizei>(source_size), nullptr); + } + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, copy_pbo_handle); + + const GLsizei width = static_cast<GLsizei>(dst_params.width); + const GLsizei height = static_cast<GLsizei>(dst_params.height); + const GLsizei depth = static_cast<GLsizei>(dst_params.depth); + if (dest_format.compressed) { + LOG_CRITICAL(HW_GPU, "Compressed buffer copy is unimplemented!"); + UNREACHABLE(); + } else { + switch (dst_params.target) { + case SurfaceTarget::Texture1D: + glTextureSubImage1D(dst_surface->GetTexture(), 0, 0, width, dest_format.format, + dest_format.type, nullptr); + break; + case SurfaceTarget::Texture2D: + glTextureSubImage2D(dst_surface->GetTexture(), 0, 0, 0, width, height, + dest_format.format, dest_format.type, nullptr); + break; + case SurfaceTarget::Texture3D: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubeArray: + glTextureSubImage3D(dst_surface->GetTexture(), 0, 0, 0, 0, width, height, depth, + dest_format.format, dest_format.type, nullptr); + break; + case SurfaceTarget::TextureCubemap: + glTextureSubImage3D(dst_surface->GetTexture(), 0, 0, 0, 0, width, height, depth, + dest_format.format, dest_format.type, nullptr); + break; + default: + LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", + static_cast<u32>(dst_params.target)); + UNREACHABLE(); + } + } + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + glTextureBarrier(); +} + +GLuint TextureCacheOpenGL::FetchPBO(std::size_t buffer_size) { + ASSERT_OR_EXECUTE(buffer_size > 0, { return 0; }); + const u32 l2 = Common::Log2Ceil64(static_cast<u64>(buffer_size)); + OGLBuffer& cp = copy_pbo_cache[l2]; + if (cp.handle == 0) { + const std::size_t ceil_size = 1ULL << l2; + cp.Create(); + cp.MakeStreamCopy(ceil_size); + } + return cp.handle; +} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h new file mode 100644 index 000000000..8e13ab38b --- /dev/null +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -0,0 +1,147 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <functional> +#include <memory> +#include <unordered_map> +#include <utility> +#include <vector> + +#include <glad/glad.h> + +#include "common/common_types.h" +#include "video_core/engines/shader_bytecode.h" +#include "video_core/renderer_opengl/gl_device.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/texture_cache/texture_cache.h" + +namespace OpenGL { + +using VideoCommon::SurfaceParams; +using VideoCommon::ViewParams; + +class CachedSurfaceView; +class CachedSurface; +class TextureCacheOpenGL; + +using Surface = std::shared_ptr<CachedSurface>; +using View = std::shared_ptr<CachedSurfaceView>; +using TextureCacheBase = VideoCommon::TextureCache<Surface, View>; + +class CachedSurface final : public VideoCommon::SurfaceBase<View> { + friend CachedSurfaceView; + +public: + explicit CachedSurface(GPUVAddr gpu_addr, const SurfaceParams& params); + ~CachedSurface(); + + void UploadTexture(const std::vector<u8>& staging_buffer) override; + void DownloadTexture(std::vector<u8>& staging_buffer) override; + + GLenum GetTarget() const { + return target; + } + + GLuint GetTexture() const { + return texture.handle; + } + +protected: + void DecorateSurfaceName() override; + + View CreateView(const ViewParams& view_key) override; + View CreateViewInner(const ViewParams& view_key, bool is_proxy); + +private: + void UploadTextureMipmap(u32 level, const std::vector<u8>& staging_buffer); + + GLenum internal_format{}; + GLenum format{}; + GLenum type{}; + bool is_compressed{}; + GLenum target{}; + u32 view_count{}; + + OGLTexture texture; + OGLBuffer texture_buffer; +}; + +class CachedSurfaceView final : public VideoCommon::ViewBase { +public: + explicit CachedSurfaceView(CachedSurface& surface, const ViewParams& params, bool is_proxy); + ~CachedSurfaceView(); + + /// Attaches this texture view to the current bound GL_DRAW_FRAMEBUFFER + void Attach(GLenum attachment, GLenum target) const; + + void ApplySwizzle(Tegra::Texture::SwizzleSource x_source, + Tegra::Texture::SwizzleSource y_source, + Tegra::Texture::SwizzleSource z_source, + Tegra::Texture::SwizzleSource w_source); + + void DecorateViewName(GPUVAddr gpu_addr, std::string prefix); + + void MarkAsModified(u64 tick) { + surface.MarkAsModified(true, tick); + } + + GLuint GetTexture() const { + if (is_proxy) { + return surface.GetTexture(); + } + return texture_view.handle; + } + + const SurfaceParams& GetSurfaceParams() const { + return surface.GetSurfaceParams(); + } + +private: + u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, + Tegra::Texture::SwizzleSource y_source, + Tegra::Texture::SwizzleSource z_source, + Tegra::Texture::SwizzleSource w_source) const { + return (static_cast<u32>(x_source) << 24) | (static_cast<u32>(y_source) << 16) | + (static_cast<u32>(z_source) << 8) | static_cast<u32>(w_source); + } + + OGLTextureView CreateTextureView() const; + + CachedSurface& surface; + GLenum target{}; + + OGLTextureView texture_view; + u32 swizzle{}; + bool is_proxy{}; +}; + +class TextureCacheOpenGL final : public TextureCacheBase { +public: + explicit TextureCacheOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer, + const Device& device); + ~TextureCacheOpenGL(); + +protected: + Surface CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) override; + + void ImageCopy(Surface& src_surface, Surface& dst_surface, + const VideoCommon::CopyParams& copy_params) override; + + void ImageBlit(View& src_view, View& dst_view, + const Tegra::Engines::Fermi2D::Config& copy_config) override; + + void BufferCopy(Surface& src_surface, Surface& dst_surface) override; + +private: + GLuint FetchPBO(std::size_t buffer_size); + + OGLFramebuffer src_framebuffer; + OGLFramebuffer dst_framebuffer; + std::unordered_map<u32, OGLBuffer> copy_pbo_cache; +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index ea77dd211..9ed738171 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -145,7 +145,7 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode, case Tegra::Texture::TextureMipmapFilter::None: return GL_LINEAR; case Tegra::Texture::TextureMipmapFilter::Nearest: - return GL_NEAREST_MIPMAP_LINEAR; + return GL_LINEAR_MIPMAP_NEAREST; case Tegra::Texture::TextureMipmapFilter::Linear: return GL_LINEAR_MIPMAP_LINEAR; } @@ -157,7 +157,7 @@ inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode, case Tegra::Texture::TextureMipmapFilter::Nearest: return GL_NEAREST_MIPMAP_NEAREST; case Tegra::Texture::TextureMipmapFilter::Linear: - return GL_LINEAR_MIPMAP_NEAREST; + return GL_NEAREST_MIPMAP_LINEAR; } } } diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 3451d321d..1e6ef66ab 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -18,7 +18,6 @@ #include "core/perf_stats.h" #include "core/settings.h" #include "core/telemetry_session.h" -#include "core/tracer/recorder.h" #include "video_core/morton.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/renderer_opengl.h" @@ -102,21 +101,19 @@ RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::Syst RendererOpenGL::~RendererOpenGL() = default; -/// Swap buffers (render frame) -void RendererOpenGL::SwapBuffers( - std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) { - +void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { system.GetPerfStats().EndSystemFrame(); // Maintain the rasterizer's state as a priority OpenGLState prev_state = OpenGLState::GetCurState(); + state.AllDirty(); state.Apply(); if (framebuffer) { // If framebuffer is provided, reload it from memory to a texture - if (screen_info.texture.width != (GLsizei)framebuffer->get().width || - screen_info.texture.height != (GLsizei)framebuffer->get().height || - screen_info.texture.pixel_format != framebuffer->get().pixel_format) { + if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) || + screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) || + screen_info.texture.pixel_format != framebuffer->pixel_format) { // Reallocate texture if the framebuffer size has changed. // This is expected to not happen very often and hence should not be a // performance problem. @@ -131,6 +128,8 @@ void RendererOpenGL::SwapBuffers( DrawScreen(render_window.GetFramebufferLayout()); + rasterizer->TickFrame(); + render_window.SwapBuffers(); } @@ -140,6 +139,7 @@ void RendererOpenGL::SwapBuffers( system.GetPerfStats().BeginSystemFrame(); // Restore the rasterizer state + prev_state.AllDirty(); prev_state.Apply(); } @@ -147,43 +147,43 @@ void RendererOpenGL::SwapBuffers( * Loads framebuffer from emulated memory into the active OpenGL texture. */ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { - const u32 bytes_per_pixel{Tegra::FramebufferConfig::BytesPerPixel(framebuffer.pixel_format)}; - const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel}; - const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset}; - // Framebuffer orientation handling framebuffer_transform_flags = framebuffer.transform_flags; framebuffer_crop_rect = framebuffer.crop_rect; - // Ensure no bad interactions with GL_UNPACK_ALIGNMENT, which by default - // only allows rows to have a memory alignement of 4. - ASSERT(framebuffer.stride % 4 == 0); - - if (!rasterizer->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride)) { - // Reset the screen info's display texture to its own permanent texture - screen_info.display_texture = screen_info.texture.resource.handle; - - rasterizer->FlushRegion(ToCacheAddr(Memory::GetPointer(framebuffer_addr)), size_in_bytes); - - constexpr u32 linear_bpp = 4; - VideoCore::MortonCopyPixels128(VideoCore::MortonSwizzleMode::MortonToLinear, - framebuffer.width, framebuffer.height, bytes_per_pixel, - linear_bpp, Memory::GetPointer(framebuffer_addr), - gl_framebuffer_data.data()); - - glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride)); + const VAddr framebuffer_addr{framebuffer.address + framebuffer.offset}; + if (rasterizer->AccelerateDisplay(framebuffer, framebuffer_addr, framebuffer.stride)) { + return; + } - // Update existing texture - // TODO: Test what happens on hardware when you change the framebuffer dimensions so that - // they differ from the LCD resolution. - // TODO: Applications could theoretically crash yuzu here by specifying too large - // framebuffer sizes. We should make sure that this cannot happen. - glTextureSubImage2D(screen_info.texture.resource.handle, 0, 0, 0, framebuffer.width, - framebuffer.height, screen_info.texture.gl_format, - screen_info.texture.gl_type, gl_framebuffer_data.data()); + // Reset the screen info's display texture to its own permanent texture + screen_info.display_texture = screen_info.texture.resource.handle; - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - } + const auto pixel_format{ + VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)}; + const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)}; + const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel}; + const auto host_ptr{Memory::GetPointer(framebuffer_addr)}; + rasterizer->FlushRegion(ToCacheAddr(host_ptr), size_in_bytes); + + // TODO(Rodrigo): Read this from HLE + constexpr u32 block_height_log2 = 4; + VideoCore::MortonSwizzle(VideoCore::MortonSwizzleMode::MortonToLinear, pixel_format, + framebuffer.stride, block_height_log2, framebuffer.height, 0, 1, 1, + gl_framebuffer_data.data(), host_ptr); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride)); + + // Update existing texture + // TODO: Test what happens on hardware when you change the framebuffer dimensions so that + // they differ from the LCD resolution. + // TODO: Applications could theoretically crash yuzu here by specifying too large + // framebuffer sizes. We should make sure that this cannot happen. + glTextureSubImage2D(screen_info.texture.resource.handle, 0, 0, 0, framebuffer.width, + framebuffer.height, screen_info.texture.gl_format, + screen_info.texture.gl_type, gl_framebuffer_data.data()); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } /** @@ -206,6 +206,7 @@ void RendererOpenGL::InitOpenGLObjects() { // Link shaders and get variable locations shader.CreateFromSource(vertex_shader, nullptr, fragment_shader); state.draw.shader_program = shader.handle; + state.AllDirty(); state.Apply(); uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix"); uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture"); @@ -263,8 +264,6 @@ void RendererOpenGL::CreateRasterizer() { if (rasterizer) { return; } - // Initialize sRGB Usage - OpenGLState::ClearsRGBUsed(); rasterizer = std::make_unique<RasterizerOpenGL>(system, emu_window, screen_info); } @@ -274,22 +273,29 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, texture.height = framebuffer.height; texture.pixel_format = framebuffer.pixel_format; + const auto pixel_format{ + VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)}; + const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)}; + gl_framebuffer_data.resize(texture.width * texture.height * bytes_per_pixel); + GLint internal_format; switch (framebuffer.pixel_format) { case Tegra::FramebufferConfig::PixelFormat::ABGR8: internal_format = GL_RGBA8; texture.gl_format = GL_RGBA; texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; - gl_framebuffer_data.resize(texture.width * texture.height * 4); + break; + case Tegra::FramebufferConfig::PixelFormat::RGB565: + internal_format = GL_RGB565; + texture.gl_format = GL_RGB; + texture.gl_type = GL_UNSIGNED_SHORT_5_6_5; break; default: internal_format = GL_RGBA8; texture.gl_format = GL_RGBA; texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; - gl_framebuffer_data.resize(texture.width * texture.height * 4); - LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer pixel format: {}", - static_cast<u32>(framebuffer.pixel_format)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}", + static_cast<u32>(framebuffer.pixel_format)); } texture.resource.Release(); @@ -335,19 +341,17 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v), }}; - state.texture_units[0].texture = screen_info.display_texture; - // Workaround brigthness problems in SMO by enabling sRGB in the final output - // if it has been used in the frame. Needed because of this bug in QT: QTBUG-50987 - state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed(); + state.textures[0] = screen_info.display_texture; + state.framebuffer_srgb.enabled = screen_info.display_srgb; + state.AllDirty(); state.Apply(); glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices), vertices.data()); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // Restore default state state.framebuffer_srgb.enabled = false; - state.texture_units[0].texture = 0; + state.textures[0] = 0; + state.AllDirty(); state.Apply(); - // Clear sRGB state for the next frame - OpenGLState::ClearsRGBUsed(); } /** @@ -389,6 +393,7 @@ void RendererOpenGL::CaptureScreenshot() { GLuint old_read_fb = state.draw.read_framebuffer; GLuint old_draw_fb = state.draw.draw_framebuffer; state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle; + state.AllDirty(); state.Apply(); Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout}; @@ -396,8 +401,8 @@ void RendererOpenGL::CaptureScreenshot() { GLuint renderbuffer; glGenRenderbuffers(1, &renderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); - glRenderbufferStorage(GL_RENDERBUFFER, state.GetsRGBUsed() ? GL_SRGB8 : GL_RGB8, layout.width, - layout.height); + glRenderbufferStorage(GL_RENDERBUFFER, screen_info.display_srgb ? GL_SRGB8 : GL_RGB8, + layout.width, layout.height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); DrawScreen(layout); @@ -408,6 +413,7 @@ void RendererOpenGL::CaptureScreenshot() { screenshot_framebuffer.Release(); state.draw.read_framebuffer = old_read_fb; state.draw.draw_framebuffer = old_draw_fb; + state.AllDirty(); state.Apply(); glDeleteRenderbuffers(1, &renderbuffer); @@ -472,7 +478,6 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum } } -/// Initialize the renderer bool RendererOpenGL::Init() { Core::Frontend::ScopeAcquireWindowContext acquire_context{render_window}; diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 4aebf2321..cf26628ca 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -38,19 +38,19 @@ struct TextureInfo { /// Structure used for storing information about the display target for the Switch screen struct ScreenInfo { - GLuint display_texture; + GLuint display_texture{}; + bool display_srgb{}; const Common::Rectangle<float> display_texcoords{0.0f, 0.0f, 1.0f, 1.0f}; TextureInfo texture; }; -class RendererOpenGL : public VideoCore::RendererBase { +class RendererOpenGL final : public VideoCore::RendererBase { public: explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system); ~RendererOpenGL() override; /// Swap buffers (render frame) - void SwapBuffers( - std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override; + void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; /// Initialize the renderer bool Init() override; diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp index f23fc9f9d..c504a2c1a 100644 --- a/src/video_core/renderer_opengl/utils.cpp +++ b/src/video_core/renderer_opengl/utils.cpp @@ -5,35 +5,75 @@ #include <string> #include <fmt/format.h> #include <glad/glad.h> + #include "common/assert.h" #include "common/common_types.h" +#include "common/scope_exit.h" #include "video_core/renderer_opengl/utils.h" namespace OpenGL { +VertexArrayPushBuffer::VertexArrayPushBuffer() = default; + +VertexArrayPushBuffer::~VertexArrayPushBuffer() = default; + +void VertexArrayPushBuffer::Setup(GLuint vao_) { + vao = vao_; + index_buffer = nullptr; + vertex_buffers.clear(); +} + +void VertexArrayPushBuffer::SetIndexBuffer(const GLuint* buffer) { + index_buffer = buffer; +} + +void VertexArrayPushBuffer::SetVertexBuffer(GLuint binding_index, const GLuint* buffer, + GLintptr offset, GLsizei stride) { + vertex_buffers.push_back(Entry{binding_index, buffer, offset, stride}); +} + +void VertexArrayPushBuffer::Bind() { + if (index_buffer) { + glVertexArrayElementBuffer(vao, *index_buffer); + } + + // TODO(Rodrigo): Find a way to ARB_multi_bind this + for (const auto& entry : vertex_buffers) { + glVertexArrayVertexBuffer(vao, entry.binding_index, *entry.buffer, entry.offset, + entry.stride); + } +} + BindBuffersRangePushBuffer::BindBuffersRangePushBuffer(GLenum target) : target{target} {} BindBuffersRangePushBuffer::~BindBuffersRangePushBuffer() = default; void BindBuffersRangePushBuffer::Setup(GLuint first_) { first = first_; - buffers.clear(); + buffer_pointers.clear(); offsets.clear(); sizes.clear(); } -void BindBuffersRangePushBuffer::Push(GLuint buffer, GLintptr offset, GLsizeiptr size) { - buffers.push_back(buffer); +void BindBuffersRangePushBuffer::Push(const GLuint* buffer, GLintptr offset, GLsizeiptr size) { + buffer_pointers.push_back(buffer); offsets.push_back(offset); sizes.push_back(size); } -void BindBuffersRangePushBuffer::Bind() const { - const std::size_t count{buffers.size()}; +void BindBuffersRangePushBuffer::Bind() { + // Ensure sizes are valid. + const std::size_t count{buffer_pointers.size()}; DEBUG_ASSERT(count == offsets.size() && count == sizes.size()); if (count == 0) { return; } + + // Dereference buffers. + buffers.resize(count); + std::transform(buffer_pointers.begin(), buffer_pointers.end(), buffers.begin(), + [](const GLuint* pointer) { return *pointer; }); + glBindBuffersRange(target, first, static_cast<GLsizei>(count), buffers.data(), offsets.data(), sizes.data()); } @@ -63,4 +103,4 @@ void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_vie glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str())); } -} // namespace OpenGL
\ No newline at end of file +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h index b3e9fc499..6c2b45546 100644 --- a/src/video_core/renderer_opengl/utils.h +++ b/src/video_core/renderer_opengl/utils.h @@ -11,20 +11,49 @@ namespace OpenGL { -class BindBuffersRangePushBuffer { +class VertexArrayPushBuffer final { public: - BindBuffersRangePushBuffer(GLenum target); + explicit VertexArrayPushBuffer(); + ~VertexArrayPushBuffer(); + + void Setup(GLuint vao_); + + void SetIndexBuffer(const GLuint* buffer); + + void SetVertexBuffer(GLuint binding_index, const GLuint* buffer, GLintptr offset, + GLsizei stride); + + void Bind(); + +private: + struct Entry { + GLuint binding_index{}; + const GLuint* buffer{}; + GLintptr offset{}; + GLsizei stride{}; + }; + + GLuint vao{}; + const GLuint* index_buffer{}; + std::vector<Entry> vertex_buffers; +}; + +class BindBuffersRangePushBuffer final { +public: + explicit BindBuffersRangePushBuffer(GLenum target); ~BindBuffersRangePushBuffer(); void Setup(GLuint first_); - void Push(GLuint buffer, GLintptr offset, GLsizeiptr size); + void Push(const GLuint* buffer, GLintptr offset, GLsizeiptr size); - void Bind() const; + void Bind(); private: - GLenum target; - GLuint first; + GLenum target{}; + GLuint first{}; + std::vector<const GLuint*> buffer_pointers; + std::vector<GLuint> buffers; std::vector<GLintptr> offsets; std::vector<GLsizeiptr> sizes; @@ -32,4 +61,4 @@ private: void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info = {}); -} // namespace OpenGL
\ No newline at end of file +} // namespace OpenGL diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 0bbbf6851..3c5acda3e 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -143,6 +143,7 @@ static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG8U {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG8S {vk::Format::eUndefined, ComponentType::Invalid, false}, // RG32UI + {vk::Format::eUndefined, ComponentType::Invalid, false}, // RGBX16F {vk::Format::eUndefined, ComponentType::Invalid, false}, // R32UI {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X8 {vk::Format::eUndefined, ComponentType::Invalid, false}, // ASTC_2D_8X5 diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 02a9f5ecb..d2e9f4031 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -109,8 +109,8 @@ void VKBufferCache::Reserve(std::size_t max_size) { } } -VKExecutionContext VKBufferCache::Send(VKExecutionContext exctx) { - return stream_buffer->Send(exctx, buffer_offset - buffer_offset_base); +void VKBufferCache::Send() { + stream_buffer->Send(buffer_offset - buffer_offset_base); } void VKBufferCache::AlignBuffer(std::size_t alignment) { diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index 3edf460df..49f13bcdc 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -77,7 +77,7 @@ public: void Reserve(std::size_t max_size); /// Ensures that the set data is sent to the device. - [[nodiscard]] VKExecutionContext Send(VKExecutionContext exctx); + void Send(); /// Returns the buffer cache handle. vk::Buffer GetBuffer() const { diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp index 3b966ddc3..897cbb4e8 100644 --- a/src/video_core/renderer_vulkan/vk_device.cpp +++ b/src/video_core/renderer_vulkan/vk_device.cpp @@ -2,9 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <map> +#include <bitset> #include <optional> #include <set> +#include <string_view> #include <vector> #include "common/assert.h" #include "video_core/renderer_vulkan/declarations.h" @@ -12,13 +13,32 @@ namespace Vulkan { +namespace { + +template <typename T> +void SetNext(void**& next, T& data) { + *next = &data; + next = &data.pNext; +} + +template <typename T> +T GetFeatures(vk::PhysicalDevice physical, vk::DispatchLoaderDynamic dldi) { + vk::PhysicalDeviceFeatures2 features; + T extension_features; + features.pNext = &extension_features; + physical.getFeatures2(&features, dldi); + return extension_features; +} + +} // Anonymous namespace + namespace Alternatives { -constexpr std::array<vk::Format, 3> Depth24UnormS8Uint = { - vk::Format::eD32SfloatS8Uint, vk::Format::eD16UnormS8Uint, {}}; -constexpr std::array<vk::Format, 3> Depth16UnormS8Uint = { - vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint, {}}; -constexpr std::array<vk::Format, 2> Astc = {vk::Format::eA8B8G8R8UnormPack32, {}}; +constexpr std::array Depth24UnormS8Uint = {vk::Format::eD32SfloatS8Uint, + vk::Format::eD16UnormS8Uint, vk::Format{}}; +constexpr std::array Depth16UnormS8Uint = {vk::Format::eD24UnormS8Uint, + vk::Format::eD32SfloatS8Uint, vk::Format{}}; +constexpr std::array Astc = {vk::Format::eA8B8G8R8UnormPack32, vk::Format{}}; } // namespace Alternatives @@ -58,16 +78,53 @@ VKDevice::VKDevice(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice phy VKDevice::~VKDevice() = default; bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instance) { - vk::PhysicalDeviceFeatures device_features; - device_features.vertexPipelineStoresAndAtomics = true; - device_features.independentBlend = true; - device_features.textureCompressionASTC_LDR = is_optimal_astc_supported; - const auto queue_cis = GetDeviceQueueCreateInfos(); - const std::vector<const char*> extensions = LoadExtensions(dldi); - const vk::DeviceCreateInfo device_ci({}, static_cast<u32>(queue_cis.size()), queue_cis.data(), - 0, nullptr, static_cast<u32>(extensions.size()), - extensions.data(), &device_features); + const std::vector extensions = LoadExtensions(dldi); + + vk::PhysicalDeviceFeatures2 features2; + void** next = &features2.pNext; + auto& features = features2.features; + features.vertexPipelineStoresAndAtomics = true; + features.independentBlend = true; + features.depthClamp = true; + features.samplerAnisotropy = true; + features.largePoints = true; + features.textureCompressionASTC_LDR = is_optimal_astc_supported; + + vk::PhysicalDeviceVertexAttributeDivisorFeaturesEXT vertex_divisor; + vertex_divisor.vertexAttributeInstanceRateDivisor = true; + vertex_divisor.vertexAttributeInstanceRateZeroDivisor = true; + SetNext(next, vertex_divisor); + + vk::PhysicalDeviceFloat16Int8FeaturesKHR float16_int8; + if (is_float16_supported) { + float16_int8.shaderFloat16 = true; + SetNext(next, float16_int8); + } else { + LOG_INFO(Render_Vulkan, "Device doesn't support float16 natively"); + } + + vk::PhysicalDeviceUniformBufferStandardLayoutFeaturesKHR std430_layout; + if (khr_uniform_buffer_standard_layout) { + std430_layout.uniformBufferStandardLayout = true; + SetNext(next, std430_layout); + } else { + LOG_INFO(Render_Vulkan, "Device doesn't support packed UBOs"); + } + + vk::PhysicalDeviceIndexTypeUint8FeaturesEXT index_type_uint8; + if (ext_index_type_uint8) { + index_type_uint8.indexTypeUint8 = true; + SetNext(next, index_type_uint8); + } else { + LOG_INFO(Render_Vulkan, "Device doesn't support uint8 indexes"); + } + + vk::DeviceCreateInfo device_ci({}, static_cast<u32>(queue_cis.size()), queue_cis.data(), 0, + nullptr, static_cast<u32>(extensions.size()), extensions.data(), + nullptr); + device_ci.pNext = &features2; + vk::Device dummy_logical; if (physical.createDevice(&device_ci, nullptr, &dummy_logical, dldi) != vk::Result::eSuccess) { LOG_CRITICAL(Render_Vulkan, "Logical device failed to be created!"); @@ -78,6 +135,17 @@ bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instan logical = UniqueDevice( dummy_logical, vk::ObjectDestroy<vk::NoParent, vk::DispatchLoaderDynamic>(nullptr, dld)); + if (khr_driver_properties) { + vk::PhysicalDeviceDriverPropertiesKHR driver; + vk::PhysicalDeviceProperties2 properties; + properties.pNext = &driver; + physical.getProperties2(&properties, dld); + driver_id = driver.driverID; + LOG_INFO(Render_Vulkan, "Driver: {} {}", driver.driverName, driver.driverInfo); + } else { + LOG_INFO(Render_Vulkan, "Driver: Unknown"); + } + graphics_queue = logical->getQueue(graphics_family, 0, dld); present_queue = logical->getQueue(present_family, 0, dld); return true; @@ -92,20 +160,19 @@ vk::Format VKDevice::GetSupportedFormat(vk::Format wanted_format, // The wanted format is not supported by hardware, search for alternatives const vk::Format* alternatives = GetFormatAlternatives(wanted_format); if (alternatives == nullptr) { - LOG_CRITICAL(Render_Vulkan, - "Format={} with usage={} and type={} has no defined alternatives and host " - "hardware does not support it", - vk::to_string(wanted_format), vk::to_string(wanted_usage), - static_cast<u32>(format_type)); - UNREACHABLE(); + UNREACHABLE_MSG("Format={} with usage={} and type={} has no defined alternatives and host " + "hardware does not support it", + vk::to_string(wanted_format), vk::to_string(wanted_usage), + static_cast<u32>(format_type)); return wanted_format; } std::size_t i = 0; for (vk::Format alternative = alternatives[0]; alternative != vk::Format{}; alternative = alternatives[++i]) { - if (!IsFormatSupported(alternative, wanted_usage, format_type)) + if (!IsFormatSupported(alternative, wanted_usage, format_type)) { continue; + } LOG_WARNING(Render_Vulkan, "Emulating format={} with alternative format={} with usage={} and type={}", static_cast<u32>(wanted_format), static_cast<u32>(alternative), @@ -114,12 +181,10 @@ vk::Format VKDevice::GetSupportedFormat(vk::Format wanted_format, } // No alternatives found, panic - LOG_CRITICAL(Render_Vulkan, - "Format={} with usage={} and type={} is not supported by the host hardware and " - "doesn't support any of the alternatives", - static_cast<u32>(wanted_format), static_cast<u32>(wanted_usage), - static_cast<u32>(format_type)); - UNREACHABLE(); + UNREACHABLE_MSG("Format={} with usage={} and type={} is not supported by the host hardware and " + "doesn't support any of the alternatives", + static_cast<u32>(wanted_format), static_cast<u32>(wanted_usage), + static_cast<u32>(format_type)); return wanted_format; } @@ -132,7 +197,7 @@ bool VKDevice::IsOptimalAstcSupported(const vk::PhysicalDeviceFeatures& features vk::FormatFeatureFlagBits::eSampledImage | vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eBlitDst | vk::FormatFeatureFlagBits::eTransferSrc | vk::FormatFeatureFlagBits::eTransferDst}; - constexpr std::array<vk::Format, 9> astc_formats = { + constexpr std::array astc_formats = { vk::Format::eAstc4x4UnormBlock, vk::Format::eAstc4x4SrgbBlock, vk::Format::eAstc8x8SrgbBlock, vk::Format::eAstc8x6SrgbBlock, vk::Format::eAstc5x4SrgbBlock, vk::Format::eAstc5x5UnormBlock, @@ -151,76 +216,120 @@ bool VKDevice::IsFormatSupported(vk::Format wanted_format, vk::FormatFeatureFlag FormatType format_type) const { const auto it = format_properties.find(wanted_format); if (it == format_properties.end()) { - LOG_CRITICAL(Render_Vulkan, "Unimplemented format query={}", vk::to_string(wanted_format)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented format query={}", vk::to_string(wanted_format)); return true; } - const vk::FormatFeatureFlags supported_usage = GetFormatFeatures(it->second, format_type); + const auto supported_usage = GetFormatFeatures(it->second, format_type); return (supported_usage & wanted_usage) == wanted_usage; } bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical, vk::SurfaceKHR surface) { - bool has_swapchain{}; + LOG_INFO(Render_Vulkan, "{}", physical.getProperties(dldi).deviceName); + bool is_suitable = true; + + constexpr std::array required_extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME}; + std::bitset<required_extensions.size()> available_extensions{}; + for (const auto& prop : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) { - has_swapchain |= prop.extensionName == std::string(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + for (std::size_t i = 0; i < required_extensions.size(); ++i) { + if (available_extensions[i]) { + continue; + } + available_extensions[i] = + required_extensions[i] == std::string_view{prop.extensionName}; + } } - if (!has_swapchain) { - // The device doesn't support creating swapchains. - return false; + if (!available_extensions.all()) { + for (std::size_t i = 0; i < required_extensions.size(); ++i) { + if (available_extensions[i]) { + continue; + } + LOG_INFO(Render_Vulkan, "Missing required extension: {}", required_extensions[i]); + is_suitable = false; + } } bool has_graphics{}, has_present{}; const auto queue_family_properties = physical.getQueueFamilyProperties(dldi); for (u32 i = 0; i < static_cast<u32>(queue_family_properties.size()); ++i) { const auto& family = queue_family_properties[i]; - if (family.queueCount == 0) + if (family.queueCount == 0) { continue; - + } has_graphics |= (family.queueFlags & vk::QueueFlagBits::eGraphics) != static_cast<vk::QueueFlagBits>(0); has_present |= physical.getSurfaceSupportKHR(i, surface, dldi) != 0; } if (!has_graphics || !has_present) { - // The device doesn't have a graphics and present queue. - return false; + LOG_INFO(Render_Vulkan, "Device lacks a graphics and present queue"); + is_suitable = false; } // TODO(Rodrigo): Check if the device matches all requeriments. const auto properties{physical.getProperties(dldi)}; - const auto limits{properties.limits}; - if (limits.maxUniformBufferRange < 65536) { - return false; + const auto& limits{properties.limits}; + + constexpr u32 required_ubo_size = 65536; + if (limits.maxUniformBufferRange < required_ubo_size) { + LOG_INFO(Render_Vulkan, "Device UBO size {} is too small, {} is required)", + limits.maxUniformBufferRange, required_ubo_size); + is_suitable = false; } - const vk::PhysicalDeviceFeatures features{physical.getFeatures(dldi)}; - if (!features.vertexPipelineStoresAndAtomics || !features.independentBlend) { - return false; + const auto features{physical.getFeatures(dldi)}; + const std::array feature_report = { + std::make_pair(features.vertexPipelineStoresAndAtomics, "vertexPipelineStoresAndAtomics"), + std::make_pair(features.independentBlend, "independentBlend"), + std::make_pair(features.depthClamp, "depthClamp"), + std::make_pair(features.samplerAnisotropy, "samplerAnisotropy"), + std::make_pair(features.largePoints, "largePoints"), + }; + for (const auto& [supported, name] : feature_report) { + if (supported) { + continue; + } + LOG_INFO(Render_Vulkan, "Missing required feature: {}", name); + is_suitable = false; } - // Device is suitable. - return true; + return is_suitable; } std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynamic& dldi) { std::vector<const char*> extensions; - extensions.reserve(2); + extensions.reserve(7); extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + extensions.push_back(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME); const auto Test = [&](const vk::ExtensionProperties& extension, std::optional<std::reference_wrapper<bool>> status, const char* name, - u32 revision) { - if (extension.extensionName != std::string(name)) { + bool push) { + if (extension.extensionName != std::string_view(name)) { return; } - extensions.push_back(name); + if (push) { + extensions.push_back(name); + } if (status) { status->get() = true; } }; + bool khr_shader_float16_int8{}; for (const auto& extension : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) { - Test(extension, ext_scalar_block_layout, VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME, 1); + Test(extension, khr_uniform_buffer_standard_layout, + VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, true); + Test(extension, ext_index_type_uint8, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, true); + Test(extension, khr_driver_properties, VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, true); + Test(extension, khr_shader_float16_int8, VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, false); + } + + if (khr_shader_float16_int8) { + is_float16_supported = + GetFeatures<vk::PhysicalDeviceFloat16Int8FeaturesKHR>(physical, dldi).shaderFloat16; + extensions.push_back(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME); } return extensions; @@ -250,9 +359,10 @@ void VKDevice::SetupFamilies(const vk::DispatchLoaderDynamic& dldi, vk::SurfaceK } void VKDevice::SetupProperties(const vk::DispatchLoaderDynamic& dldi) { - const vk::PhysicalDeviceProperties props = physical.getProperties(dldi); + const auto props = physical.getProperties(dldi); device_type = props.deviceType; uniform_buffer_alignment = static_cast<u64>(props.limits.minUniformBufferOffsetAlignment); + storage_buffer_alignment = static_cast<u64>(props.limits.minStorageBufferOffsetAlignment); max_storage_buffer_range = static_cast<u64>(props.limits.maxStorageBufferRange); } @@ -273,42 +383,53 @@ std::vector<vk::DeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() con return queue_cis; } -std::map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperties( +std::unordered_map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperties( const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical) { - static constexpr std::array formats{vk::Format::eA8B8G8R8UnormPack32, - vk::Format::eB5G6R5UnormPack16, - vk::Format::eA2B10G10R10UnormPack32, - vk::Format::eR32G32B32A32Sfloat, - vk::Format::eR16G16Unorm, - vk::Format::eR16G16Snorm, - vk::Format::eR8G8B8A8Srgb, - vk::Format::eR8Unorm, - vk::Format::eB10G11R11UfloatPack32, - vk::Format::eR32Sfloat, - vk::Format::eR16Sfloat, - vk::Format::eR16G16B16A16Sfloat, - vk::Format::eD32Sfloat, - vk::Format::eD16Unorm, - vk::Format::eD16UnormS8Uint, - vk::Format::eD24UnormS8Uint, - vk::Format::eD32SfloatS8Uint, - vk::Format::eBc1RgbaUnormBlock, - vk::Format::eBc2UnormBlock, - vk::Format::eBc3UnormBlock, - vk::Format::eBc4UnormBlock, - vk::Format::eBc5UnormBlock, - vk::Format::eBc5SnormBlock, - vk::Format::eBc7UnormBlock, - vk::Format::eAstc4x4UnormBlock, - vk::Format::eAstc4x4SrgbBlock, - vk::Format::eAstc8x8SrgbBlock, - vk::Format::eAstc8x6SrgbBlock, - vk::Format::eAstc5x4SrgbBlock, - vk::Format::eAstc5x5UnormBlock, - vk::Format::eAstc5x5SrgbBlock, - vk::Format::eAstc10x8UnormBlock, - vk::Format::eAstc10x8SrgbBlock}; - std::map<vk::Format, vk::FormatProperties> format_properties; + constexpr std::array formats{vk::Format::eA8B8G8R8UnormPack32, + vk::Format::eA8B8G8R8SnormPack32, + vk::Format::eA8B8G8R8SrgbPack32, + vk::Format::eB5G6R5UnormPack16, + vk::Format::eA2B10G10R10UnormPack32, + vk::Format::eR32G32B32A32Sfloat, + vk::Format::eR16G16B16A16Uint, + vk::Format::eR16G16Unorm, + vk::Format::eR16G16Snorm, + vk::Format::eR16G16Sfloat, + vk::Format::eR16Unorm, + vk::Format::eR8G8B8A8Srgb, + vk::Format::eR8G8Unorm, + vk::Format::eR8G8Snorm, + vk::Format::eR8Unorm, + vk::Format::eB10G11R11UfloatPack32, + vk::Format::eR32Sfloat, + vk::Format::eR16Sfloat, + vk::Format::eR16G16B16A16Sfloat, + vk::Format::eB8G8R8A8Unorm, + vk::Format::eD32Sfloat, + vk::Format::eD16Unorm, + vk::Format::eD16UnormS8Uint, + vk::Format::eD24UnormS8Uint, + vk::Format::eD32SfloatS8Uint, + vk::Format::eBc1RgbaUnormBlock, + vk::Format::eBc2UnormBlock, + vk::Format::eBc3UnormBlock, + vk::Format::eBc4UnormBlock, + vk::Format::eBc5UnormBlock, + vk::Format::eBc5SnormBlock, + vk::Format::eBc7UnormBlock, + vk::Format::eBc1RgbaSrgbBlock, + vk::Format::eBc3SrgbBlock, + vk::Format::eBc7SrgbBlock, + vk::Format::eAstc4x4UnormBlock, + vk::Format::eAstc4x4SrgbBlock, + vk::Format::eAstc8x8SrgbBlock, + vk::Format::eAstc8x6SrgbBlock, + vk::Format::eAstc5x4SrgbBlock, + vk::Format::eAstc5x5UnormBlock, + vk::Format::eAstc5x5SrgbBlock, + vk::Format::eAstc10x8UnormBlock, + vk::Format::eAstc10x8SrgbBlock}; + std::unordered_map<vk::Format, vk::FormatProperties> format_properties; for (const auto format : formats) { format_properties.emplace(format, physical.getFormatProperties(format, dldi)); } diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h index 537825d8b..010d4c3d6 100644 --- a/src/video_core/renderer_vulkan/vk_device.h +++ b/src/video_core/renderer_vulkan/vk_device.h @@ -4,7 +4,7 @@ #pragma once -#include <map> +#include <unordered_map> #include <vector> #include "common/common_types.h" #include "video_core/renderer_vulkan/declarations.h" @@ -69,16 +69,26 @@ public: return present_family; } - /// Returns if the device is integrated with the host CPU. + /// Returns true if the device is integrated with the host CPU. bool IsIntegrated() const { return device_type == vk::PhysicalDeviceType::eIntegratedGpu; } + /// Returns the driver ID. + vk::DriverIdKHR GetDriverID() const { + return driver_id; + } + /// Returns uniform buffer alignment requeriment. u64 GetUniformBufferAlignment() const { return uniform_buffer_alignment; } + /// Returns storage alignment requeriment. + u64 GetStorageBufferAlignment() const { + return storage_buffer_alignment; + } + /// Returns the maximum range for storage buffers. u64 GetMaxStorageBufferRange() const { return max_storage_buffer_range; @@ -89,9 +99,19 @@ public: return is_optimal_astc_supported; } + /// Returns true if the device supports float16 natively + bool IsFloat16Supported() const { + return is_float16_supported; + } + /// Returns true if the device supports VK_EXT_scalar_block_layout. - bool IsExtScalarBlockLayoutSupported() const { - return ext_scalar_block_layout; + bool IsKhrUniformBufferStandardLayoutSupported() const { + return khr_uniform_buffer_standard_layout; + } + + /// Returns true if the device supports VK_EXT_index_type_uint8. + bool IsExtIndexTypeUint8Supported() const { + return ext_index_type_uint8; } /// Checks if the physical device is suitable. @@ -123,22 +143,28 @@ private: FormatType format_type) const; /// Returns the device properties for Vulkan formats. - static std::map<vk::Format, vk::FormatProperties> GetFormatProperties( + static std::unordered_map<vk::Format, vk::FormatProperties> GetFormatProperties( const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical); - const vk::PhysicalDevice physical; ///< Physical device. - vk::DispatchLoaderDynamic dld; ///< Device function pointers. - UniqueDevice logical; ///< Logical device. - vk::Queue graphics_queue; ///< Main graphics queue. - vk::Queue present_queue; ///< Main present queue. - u32 graphics_family{}; ///< Main graphics queue family index. - u32 present_family{}; ///< Main present queue family index. - vk::PhysicalDeviceType device_type; ///< Physical device type. - u64 uniform_buffer_alignment{}; ///< Uniform buffer alignment requeriment. - u64 max_storage_buffer_range{}; ///< Max storage buffer size. - bool is_optimal_astc_supported{}; ///< Support for native ASTC. - bool ext_scalar_block_layout{}; ///< Support for VK_EXT_scalar_block_layout. - std::map<vk::Format, vk::FormatProperties> format_properties; ///< Format properties dictionary. + const vk::PhysicalDevice physical; ///< Physical device. + vk::DispatchLoaderDynamic dld; ///< Device function pointers. + UniqueDevice logical; ///< Logical device. + vk::Queue graphics_queue; ///< Main graphics queue. + vk::Queue present_queue; ///< Main present queue. + u32 graphics_family{}; ///< Main graphics queue family index. + u32 present_family{}; ///< Main present queue family index. + vk::PhysicalDeviceType device_type; ///< Physical device type. + vk::DriverIdKHR driver_id{}; ///< Driver ID. + u64 uniform_buffer_alignment{}; ///< Uniform buffer alignment requeriment. + u64 storage_buffer_alignment{}; ///< Storage buffer alignment requeriment. + u64 max_storage_buffer_range{}; ///< Max storage buffer size. + bool is_optimal_astc_supported{}; ///< Support for native ASTC. + bool is_float16_supported{}; ///< Support for float16 arithmetics. + bool khr_uniform_buffer_standard_layout{}; ///< Support for std430 on UBOs. + bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8. + bool khr_driver_properties{}; ///< Support for VK_KHR_driver_properties. + std::unordered_map<vk::Format, vk::FormatProperties> + format_properties; ///< Format properties dictionary. }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.h b/src/video_core/renderer_vulkan/vk_sampler_cache.h index 771b05c73..1f73b716b 100644 --- a/src/video_core/renderer_vulkan/vk_sampler_cache.h +++ b/src/video_core/renderer_vulkan/vk_sampler_cache.h @@ -4,9 +4,6 @@ #pragma once -#include <unordered_map> - -#include "common/common_types.h" #include "video_core/renderer_vulkan/declarations.h" #include "video_core/sampler_cache.h" #include "video_core/textures/texture.h" @@ -21,9 +18,9 @@ public: ~VKSamplerCache(); protected: - UniqueSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const; + UniqueSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc) const override; - vk::Sampler ToSamplerType(const UniqueSampler& sampler) const; + vk::Sampler ToSamplerType(const UniqueSampler& sampler) const override; private: const VKDevice& device; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index f1fea1871..0f8116458 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -19,23 +19,19 @@ VKScheduler::VKScheduler(const VKDevice& device, VKResourceManager& resource_man VKScheduler::~VKScheduler() = default; -VKExecutionContext VKScheduler::GetExecutionContext() const { - return VKExecutionContext(current_fence, current_cmdbuf); -} - -VKExecutionContext VKScheduler::Flush(vk::Semaphore semaphore) { +void VKScheduler::Flush(bool release_fence, vk::Semaphore semaphore) { SubmitExecution(semaphore); - current_fence->Release(); + if (release_fence) + current_fence->Release(); AllocateNewContext(); - return GetExecutionContext(); } -VKExecutionContext VKScheduler::Finish(vk::Semaphore semaphore) { +void VKScheduler::Finish(bool release_fence, vk::Semaphore semaphore) { SubmitExecution(semaphore); current_fence->Wait(); - current_fence->Release(); + if (release_fence) + current_fence->Release(); AllocateNewContext(); - return GetExecutionContext(); } void VKScheduler::SubmitExecution(vk::Semaphore semaphore) { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index cfaf5376f..0e5b49c7f 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -10,10 +10,43 @@ namespace Vulkan { class VKDevice; -class VKExecutionContext; class VKFence; class VKResourceManager; +class VKFenceView { +public: + VKFenceView() = default; + VKFenceView(VKFence* const& fence) : fence{fence} {} + + VKFence* operator->() const noexcept { + return fence; + } + + operator VKFence&() const noexcept { + return *fence; + } + +private: + VKFence* const& fence; +}; + +class VKCommandBufferView { +public: + VKCommandBufferView() = default; + VKCommandBufferView(const vk::CommandBuffer& cmdbuf) : cmdbuf{cmdbuf} {} + + const vk::CommandBuffer* operator->() const noexcept { + return &cmdbuf; + } + + operator vk::CommandBuffer() const noexcept { + return cmdbuf; + } + +private: + const vk::CommandBuffer& cmdbuf; +}; + /// The scheduler abstracts command buffer and fence management with an interface that's able to do /// OpenGL-like operations on Vulkan command buffers. class VKScheduler { @@ -21,16 +54,21 @@ public: explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager); ~VKScheduler(); - /// Gets the current execution context. - [[nodiscard]] VKExecutionContext GetExecutionContext() const; + /// Gets a reference to the current fence. + VKFenceView GetFence() const { + return current_fence; + } + + /// Gets a reference to the current command buffer. + VKCommandBufferView GetCommandBuffer() const { + return current_cmdbuf; + } - /// Sends the current execution context to the GPU. It invalidates the current execution context - /// and returns a new one. - VKExecutionContext Flush(vk::Semaphore semaphore = nullptr); + /// Sends the current execution context to the GPU. + void Flush(bool release_fence = true, vk::Semaphore semaphore = nullptr); - /// Sends the current execution context to the GPU and waits for it to complete. It invalidates - /// the current execution context and returns a new one. - VKExecutionContext Finish(vk::Semaphore semaphore = nullptr); + /// Sends the current execution context to the GPU and waits for it to complete. + void Finish(bool release_fence = true, vk::Semaphore semaphore = nullptr); private: void SubmitExecution(vk::Semaphore semaphore); @@ -44,26 +82,4 @@ private: VKFence* next_fence = nullptr; }; -class VKExecutionContext { - friend class VKScheduler; - -public: - VKExecutionContext() = default; - - VKFence& GetFence() const { - return *fence; - } - - vk::CommandBuffer GetCommandBuffer() const { - return cmdbuf; - } - -private: - explicit VKExecutionContext(VKFence* fence, vk::CommandBuffer cmdbuf) - : fence{fence}, cmdbuf{cmdbuf} {} - - VKFence* fence{}; - vk::CommandBuffer cmdbuf; -}; - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index 547883425..77fc58f25 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -19,6 +19,7 @@ #include "video_core/engines/shader_header.h" #include "video_core/renderer_vulkan/vk_device.h" #include "video_core/renderer_vulkan/vk_shader_decompiler.h" +#include "video_core/shader/node.h" #include "video_core/shader/shader_ir.h" namespace Vulkan::VKShader { @@ -132,20 +133,16 @@ public: branch_labels.push_back(label); } - // TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely - // that shaders will use 20 nested SSYs and PBKs. - constexpr u32 FLOW_STACK_SIZE = 20; - const Id flow_stack_type = TypeArray(t_uint, Constant(t_uint, FLOW_STACK_SIZE)); jmp_to = Emit(OpVariable(TypePointer(spv::StorageClass::Function, t_uint), spv::StorageClass::Function, Constant(t_uint, first_address))); - flow_stack = Emit(OpVariable(TypePointer(spv::StorageClass::Function, flow_stack_type), - spv::StorageClass::Function, ConstantNull(flow_stack_type))); - flow_stack_top = - Emit(OpVariable(t_func_uint, spv::StorageClass::Function, Constant(t_uint, 0))); + std::tie(ssy_flow_stack, ssy_flow_stack_top) = CreateFlowStack(); + std::tie(pbk_flow_stack, pbk_flow_stack_top) = CreateFlowStack(); Name(jmp_to, "jmp_to"); - Name(flow_stack, "flow_stack"); - Name(flow_stack_top, "flow_stack_top"); + Name(ssy_flow_stack, "ssy_flow_stack"); + Name(ssy_flow_stack_top, "ssy_flow_stack_top"); + Name(pbk_flow_stack, "pbk_flow_stack"); + Name(pbk_flow_stack_top, "pbk_flow_stack_top"); Emit(OpBranch(loop_label)); Emit(loop_label); @@ -209,10 +206,6 @@ public: } private: - using OperationDecompilerFn = Id (SPIRVDecompiler::*)(Operation); - using OperationDecompilersArray = - std::array<OperationDecompilerFn, static_cast<std::size_t>(OperationCode::Amount)>; - static constexpr auto INTERNAL_FLAGS_COUNT = static_cast<std::size_t>(InternalFlag::Amount); void AllocateBindings() { @@ -378,8 +371,8 @@ private: u32 binding = const_buffers_base_binding; for (const auto& entry : ir.GetConstantBuffers()) { const auto [index, size] = entry; - const Id type = - device.IsExtScalarBlockLayoutSupported() ? t_cbuf_scalar_ubo : t_cbuf_std140_ubo; + const Id type = device.IsKhrUniformBufferStandardLayoutSupported() ? t_cbuf_scalar_ubo + : t_cbuf_std140_ubo; const Id id = OpVariable(type, spv::StorageClass::Uniform); AddGlobalVariable(Name(id, fmt::format("cbuf_{}", index))); @@ -434,20 +427,17 @@ private: instance_index = DeclareBuiltIn(spv::BuiltIn::InstanceIndex, spv::StorageClass::Input, t_in_uint, "instance_index"); - bool is_point_size_declared = false; bool is_clip_distances_declared = false; for (const auto index : ir.GetOutputAttributes()) { - if (index == Attribute::Index::PointSize) { - is_point_size_declared = true; - } else if (index == Attribute::Index::ClipDistances0123 || - index == Attribute::Index::ClipDistances4567) { + if (index == Attribute::Index::ClipDistances0123 || + index == Attribute::Index::ClipDistances4567) { is_clip_distances_declared = true; } } std::vector<Id> members; members.push_back(t_float4); - if (is_point_size_declared) { + if (ir.UsesPointSize()) { members.push_back(t_float); } if (is_clip_distances_declared) { @@ -470,7 +460,7 @@ private: position_index = MemberDecorateBuiltIn(spv::BuiltIn::Position, "position", true); point_size_index = - MemberDecorateBuiltIn(spv::BuiltIn::PointSize, "point_size", is_point_size_declared); + MemberDecorateBuiltIn(spv::BuiltIn::PointSize, "point_size", ir.UsesPointSize()); clip_distances_index = MemberDecorateBuiltIn(spv::BuiltIn::ClipDistance, "clip_distances", is_clip_distances_declared); @@ -576,7 +566,7 @@ private: const Id buffer_id = constant_buffers.at(cbuf->GetIndex()); Id pointer{}; - if (device.IsExtScalarBlockLayoutSupported()) { + if (device.IsKhrUniformBufferStandardLayoutSupported()) { const Id buffer_offset = Emit(OpShiftRightLogical( t_uint, BitcastTo<Type::Uint>(Visit(offset)), Constant(t_uint, 2u))); pointer = Emit( @@ -716,7 +706,8 @@ private: case Attribute::Index::Position: return AccessElement(t_out_float, per_vertex, position_index, abuf->GetElement()); - case Attribute::Index::PointSize: + case Attribute::Index::LayerViewportPointSize: + UNIMPLEMENTED_IF(abuf->GetElement() != 3); return AccessElement(t_out_float, per_vertex, point_size_index); case Attribute::Index::ClipDistances0123: return AccessElement(t_out_float, per_vertex, clip_distances_index, @@ -745,6 +736,16 @@ private: return {}; } + Id FCastHalf0(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id FCastHalf1(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + Id HNegate(Operation operation) { UNIMPLEMENTED(); return {}; @@ -755,6 +756,11 @@ private: return {}; } + Id HCastFloat(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + Id HUnpack(Operation operation) { UNIMPLEMENTED(); return {}; @@ -810,12 +816,7 @@ private: return {}; } - Id LogicalAll2(Operation operation) { - UNIMPLEMENTED(); - return {}; - } - - Id LogicalAny2(Operation operation) { + Id LogicalAnd2(Operation operation) { UNIMPLEMENTED(); return {}; } @@ -939,6 +940,41 @@ private: return {}; } + Id ImageLoad(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id ImageStore(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id AtomicImageAdd(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id AtomicImageAnd(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id AtomicImageOr(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id AtomicImageXor(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + + Id AtomicImageExchange(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + Id Branch(Operation operation) { const auto target = std::get_if<ImmediateNode>(&*operation[0]); UNIMPLEMENTED_IF(!target); @@ -948,10 +984,19 @@ private: return {}; } + Id BranchIndirect(Operation operation) { + const Id op_a = VisitOperand<Type::Uint>(operation, 0); + + Emit(OpStore(jmp_to, op_a)); + BranchingOp([&]() { Emit(OpBranch(continue_label)); }); + return {}; + } + Id PushFlowStack(Operation operation) { const auto target = std::get_if<ImmediateNode>(&*operation[0]); ASSERT(target); + const auto [flow_stack, flow_stack_top] = GetFlowStack(operation); const Id current = Emit(OpLoad(t_uint, flow_stack_top)); const Id next = Emit(OpIAdd(t_uint, current, Constant(t_uint, 1))); const Id access = Emit(OpAccessChain(t_func_uint, flow_stack, current)); @@ -962,6 +1007,7 @@ private: } Id PopFlowStack(Operation operation) { + const auto [flow_stack, flow_stack_top] = GetFlowStack(operation); const Id current = Emit(OpLoad(t_uint, flow_stack_top)); const Id previous = Emit(OpISub(t_uint, current, Constant(t_uint, 1))); const Id access = Emit(OpAccessChain(t_func_uint, flow_stack, previous)); @@ -1057,6 +1103,66 @@ private: return {}; } + Id BallotThread(Operation) { + UNIMPLEMENTED(); + return {}; + } + + Id VoteAll(Operation) { + UNIMPLEMENTED(); + return {}; + } + + Id VoteAny(Operation) { + UNIMPLEMENTED(); + return {}; + } + + Id VoteEqual(Operation) { + UNIMPLEMENTED(); + return {}; + } + + Id ShuffleIndexed(Operation) { + UNIMPLEMENTED(); + return {}; + } + + Id ShuffleUp(Operation) { + UNIMPLEMENTED(); + return {}; + } + + Id ShuffleDown(Operation) { + UNIMPLEMENTED(); + return {}; + } + + Id ShuffleButterfly(Operation) { + UNIMPLEMENTED(); + return {}; + } + + Id InRangeShuffleIndexed(Operation) { + UNIMPLEMENTED(); + return {}; + } + + Id InRangeShuffleUp(Operation) { + UNIMPLEMENTED(); + return {}; + } + + Id InRangeShuffleDown(Operation) { + UNIMPLEMENTED(); + return {}; + } + + Id InRangeShuffleButterfly(Operation) { + UNIMPLEMENTED(); + return {}; + } + Id DeclareBuiltIn(spv::BuiltIn builtin, spv::StorageClass storage, Id type, const std::string& name) { const Id id = OpVariable(type, storage); @@ -1172,7 +1278,32 @@ private: Emit(skip_label); } - static constexpr OperationDecompilersArray operation_decompilers = { + std::tuple<Id, Id> CreateFlowStack() { + // TODO(Rodrigo): Figure out the actual depth of the flow stack, for now it seems unlikely + // that shaders will use 20 nested SSYs and PBKs. + constexpr u32 FLOW_STACK_SIZE = 20; + constexpr auto storage_class = spv::StorageClass::Function; + + const Id flow_stack_type = TypeArray(t_uint, Constant(t_uint, FLOW_STACK_SIZE)); + const Id stack = Emit(OpVariable(TypePointer(storage_class, flow_stack_type), storage_class, + ConstantNull(flow_stack_type))); + const Id top = Emit(OpVariable(t_func_uint, storage_class, Constant(t_uint, 0))); + return std::tie(stack, top); + } + + std::pair<Id, Id> GetFlowStack(Operation operation) { + const auto stack_class = std::get<MetaStackClass>(operation.GetMeta()); + switch (stack_class) { + case MetaStackClass::Ssy: + return {ssy_flow_stack, ssy_flow_stack_top}; + case MetaStackClass::Pbk: + return {pbk_flow_stack, pbk_flow_stack_top}; + } + UNREACHABLE(); + return {}; + } + + static constexpr std::array operation_decompilers = { &SPIRVDecompiler::Assign, &SPIRVDecompiler::Ternary<&Module::OpSelect, Type::Float, Type::Bool, Type::Float, @@ -1185,6 +1316,8 @@ private: &SPIRVDecompiler::Unary<&Module::OpFNegate, Type::Float>, &SPIRVDecompiler::Unary<&Module::OpFAbs, Type::Float>, &SPIRVDecompiler::Ternary<&Module::OpFClamp, Type::Float>, + &SPIRVDecompiler::FCastHalf0, + &SPIRVDecompiler::FCastHalf1, &SPIRVDecompiler::Binary<&Module::OpFMin, Type::Float>, &SPIRVDecompiler::Binary<&Module::OpFMax, Type::Float>, &SPIRVDecompiler::Unary<&Module::OpCos, Type::Float>, @@ -1245,6 +1378,7 @@ private: &SPIRVDecompiler::Unary<&Module::OpFAbs, Type::HalfFloat>, &SPIRVDecompiler::HNegate, &SPIRVDecompiler::HClamp, + &SPIRVDecompiler::HCastFloat, &SPIRVDecompiler::HUnpack, &SPIRVDecompiler::HMergeF32, &SPIRVDecompiler::HMergeH0, @@ -1257,8 +1391,7 @@ private: &SPIRVDecompiler::Binary<&Module::OpLogicalNotEqual, Type::Bool>, &SPIRVDecompiler::Unary<&Module::OpLogicalNot, Type::Bool>, &SPIRVDecompiler::LogicalPick2, - &SPIRVDecompiler::LogicalAll2, - &SPIRVDecompiler::LogicalAny2, + &SPIRVDecompiler::LogicalAnd2, &SPIRVDecompiler::Binary<&Module::OpFOrdLessThan, Type::Bool, Type::Float>, &SPIRVDecompiler::Binary<&Module::OpFOrdEqual, Type::Bool, Type::Float>, @@ -1303,7 +1436,16 @@ private: &SPIRVDecompiler::TextureQueryLod, &SPIRVDecompiler::TexelFetch, + &SPIRVDecompiler::ImageLoad, + &SPIRVDecompiler::ImageStore, + &SPIRVDecompiler::AtomicImageAdd, + &SPIRVDecompiler::AtomicImageAnd, + &SPIRVDecompiler::AtomicImageOr, + &SPIRVDecompiler::AtomicImageXor, + &SPIRVDecompiler::AtomicImageExchange, + &SPIRVDecompiler::Branch, + &SPIRVDecompiler::BranchIndirect, &SPIRVDecompiler::PushFlowStack, &SPIRVDecompiler::PopFlowStack, &SPIRVDecompiler::Exit, @@ -1319,7 +1461,23 @@ private: &SPIRVDecompiler::WorkGroupId<0>, &SPIRVDecompiler::WorkGroupId<1>, &SPIRVDecompiler::WorkGroupId<2>, + + &SPIRVDecompiler::BallotThread, + &SPIRVDecompiler::VoteAll, + &SPIRVDecompiler::VoteAny, + &SPIRVDecompiler::VoteEqual, + + &SPIRVDecompiler::ShuffleIndexed, + &SPIRVDecompiler::ShuffleUp, + &SPIRVDecompiler::ShuffleDown, + &SPIRVDecompiler::ShuffleButterfly, + + &SPIRVDecompiler::InRangeShuffleIndexed, + &SPIRVDecompiler::InRangeShuffleUp, + &SPIRVDecompiler::InRangeShuffleDown, + &SPIRVDecompiler::InRangeShuffleButterfly, }; + static_assert(operation_decompilers.size() == static_cast<std::size_t>(OperationCode::Amount)); const VKDevice& device; const ShaderIR& ir; @@ -1414,8 +1572,10 @@ private: Id execute_function{}; Id jmp_to{}; - Id flow_stack_top{}; - Id flow_stack{}; + Id ssy_flow_stack_top{}; + Id pbk_flow_stack_top{}; + Id ssy_flow_stack{}; + Id pbk_flow_stack{}; Id continue_label{}; std::map<u32, Id> labels; }; diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp index 58ffa42f2..62f1427f5 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp @@ -46,12 +46,12 @@ std::tuple<u8*, u64, bool> VKStreamBuffer::Reserve(u64 size) { return {mapped_pointer + offset, offset, invalidation_mark.has_value()}; } -VKExecutionContext VKStreamBuffer::Send(VKExecutionContext exctx, u64 size) { +void VKStreamBuffer::Send(u64 size) { ASSERT_MSG(size <= mapped_size, "Reserved size is too small"); if (invalidation_mark) { // TODO(Rodrigo): Find a better way to invalidate than waiting for all watches to finish. - exctx = scheduler.Flush(); + scheduler.Flush(); std::for_each(watches.begin(), watches.begin() + *invalidation_mark, [&](auto& resource) { resource->Wait(); }); invalidation_mark = std::nullopt; @@ -62,11 +62,9 @@ VKExecutionContext VKStreamBuffer::Send(VKExecutionContext exctx, u64 size) { ReserveWatches(WATCHES_RESERVE_CHUNK); } // Add a watch for this allocation. - watches[used_watches++]->Watch(exctx.GetFence()); + watches[used_watches++]->Watch(scheduler.GetFence()); offset += size; - - return exctx; } void VKStreamBuffer::CreateBuffers(VKMemoryManager& memory_manager, vk::BufferUsageFlags usage) { diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.h b/src/video_core/renderer_vulkan/vk_stream_buffer.h index 69d036ccd..842e54162 100644 --- a/src/video_core/renderer_vulkan/vk_stream_buffer.h +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.h @@ -37,7 +37,7 @@ public: std::tuple<u8*, u64, bool> Reserve(u64 size); /// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy. - [[nodiscard]] VKExecutionContext Send(VKExecutionContext exctx, u64 size); + void Send(u64 size); vk::Buffer GetBuffer() const { return *buffer; diff --git a/src/video_core/shader/control_flow.cpp b/src/video_core/shader/control_flow.cpp new file mode 100644 index 000000000..ec3a76690 --- /dev/null +++ b/src/video_core/shader/control_flow.cpp @@ -0,0 +1,481 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <list> +#include <map> +#include <stack> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include "common/assert.h" +#include "common/common_types.h" +#include "video_core/shader/control_flow.h" +#include "video_core/shader/shader_ir.h" + +namespace VideoCommon::Shader { +namespace { +using Tegra::Shader::Instruction; +using Tegra::Shader::OpCode; + +constexpr s32 unassigned_branch = -2; + +struct Query { + u32 address{}; + std::stack<u32> ssy_stack{}; + std::stack<u32> pbk_stack{}; +}; + +struct BlockStack { + BlockStack() = default; + explicit BlockStack(const Query& q) : ssy_stack{q.ssy_stack}, pbk_stack{q.pbk_stack} {} + std::stack<u32> ssy_stack{}; + std::stack<u32> pbk_stack{}; +}; + +struct BlockBranchInfo { + Condition condition{}; + s32 address{exit_branch}; + bool kill{}; + bool is_sync{}; + bool is_brk{}; + bool ignore{}; +}; + +struct BlockInfo { + u32 start{}; + u32 end{}; + bool visited{}; + BlockBranchInfo branch{}; + + bool IsInside(const u32 address) const { + return start <= address && address <= end; + } +}; + +struct CFGRebuildState { + explicit CFGRebuildState(const ProgramCode& program_code, const std::size_t program_size, + const u32 start) + : start{start}, program_code{program_code}, program_size{program_size} {} + + u32 start{}; + std::vector<BlockInfo> block_info{}; + std::list<u32> inspect_queries{}; + std::list<Query> queries{}; + std::unordered_map<u32, u32> registered{}; + std::unordered_set<u32> labels{}; + std::map<u32, u32> ssy_labels{}; + std::map<u32, u32> pbk_labels{}; + std::unordered_map<u32, BlockStack> stacks{}; + const ProgramCode& program_code; + const std::size_t program_size; +}; + +enum class BlockCollision : u32 { None, Found, Inside }; + +std::pair<BlockCollision, u32> TryGetBlock(CFGRebuildState& state, u32 address) { + const auto& blocks = state.block_info; + for (u32 index = 0; index < blocks.size(); index++) { + if (blocks[index].start == address) { + return {BlockCollision::Found, index}; + } + if (blocks[index].IsInside(address)) { + return {BlockCollision::Inside, index}; + } + } + return {BlockCollision::None, 0xFFFFFFFF}; +} + +struct ParseInfo { + BlockBranchInfo branch_info{}; + u32 end_address{}; +}; + +BlockInfo& CreateBlockInfo(CFGRebuildState& state, u32 start, u32 end) { + auto& it = state.block_info.emplace_back(); + it.start = start; + it.end = end; + const u32 index = static_cast<u32>(state.block_info.size() - 1); + state.registered.insert({start, index}); + return it; +} + +Pred GetPredicate(u32 index, bool negated) { + return static_cast<Pred>(index + (negated ? 8 : 0)); +} + +/** + * Returns whether the instruction at the specified offset is a 'sched' instruction. + * Sched instructions always appear before a sequence of 3 instructions. + */ +constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) { + constexpr u32 SchedPeriod = 4; + u32 absolute_offset = offset - main_offset; + + return (absolute_offset % SchedPeriod) == 0; +} + +enum class ParseResult : u32 { + ControlCaught, + BlockEnd, + AbnormalFlow, +}; + +std::pair<ParseResult, ParseInfo> ParseCode(CFGRebuildState& state, u32 address) { + u32 offset = static_cast<u32>(address); + const u32 end_address = static_cast<u32>(state.program_size / sizeof(Instruction)); + ParseInfo parse_info{}; + + const auto insert_label = [](CFGRebuildState& state, u32 address) { + const auto pair = state.labels.emplace(address); + if (pair.second) { + state.inspect_queries.push_back(address); + } + }; + + while (true) { + if (offset >= end_address) { + // ASSERT_OR_EXECUTE can't be used, as it ignores the break + ASSERT_MSG(false, "Shader passed the current limit!"); + parse_info.branch_info.address = exit_branch; + parse_info.branch_info.ignore = false; + break; + } + if (state.registered.count(offset) != 0) { + parse_info.branch_info.address = offset; + parse_info.branch_info.ignore = true; + break; + } + if (IsSchedInstruction(offset, state.start)) { + offset++; + continue; + } + const Instruction instr = {state.program_code[offset]}; + const auto opcode = OpCode::Decode(instr); + if (!opcode || opcode->get().GetType() != OpCode::Type::Flow) { + offset++; + continue; + } + + switch (opcode->get().GetId()) { + case OpCode::Id::EXIT: { + const auto pred_index = static_cast<u32>(instr.pred.pred_index); + parse_info.branch_info.condition.predicate = + GetPredicate(pred_index, instr.negate_pred != 0); + if (parse_info.branch_info.condition.predicate == Pred::NeverExecute) { + offset++; + continue; + } + const ConditionCode cc = instr.flow_condition_code; + parse_info.branch_info.condition.cc = cc; + if (cc == ConditionCode::F) { + offset++; + continue; + } + parse_info.branch_info.address = exit_branch; + parse_info.branch_info.kill = false; + parse_info.branch_info.is_sync = false; + parse_info.branch_info.is_brk = false; + parse_info.branch_info.ignore = false; + parse_info.end_address = offset; + + return {ParseResult::ControlCaught, parse_info}; + } + case OpCode::Id::BRA: { + if (instr.bra.constant_buffer != 0) { + return {ParseResult::AbnormalFlow, parse_info}; + } + const auto pred_index = static_cast<u32>(instr.pred.pred_index); + parse_info.branch_info.condition.predicate = + GetPredicate(pred_index, instr.negate_pred != 0); + if (parse_info.branch_info.condition.predicate == Pred::NeverExecute) { + offset++; + continue; + } + const ConditionCode cc = instr.flow_condition_code; + parse_info.branch_info.condition.cc = cc; + if (cc == ConditionCode::F) { + offset++; + continue; + } + const u32 branch_offset = offset + instr.bra.GetBranchTarget(); + if (branch_offset == 0) { + parse_info.branch_info.address = exit_branch; + } else { + parse_info.branch_info.address = branch_offset; + } + insert_label(state, branch_offset); + parse_info.branch_info.kill = false; + parse_info.branch_info.is_sync = false; + parse_info.branch_info.is_brk = false; + parse_info.branch_info.ignore = false; + parse_info.end_address = offset; + + return {ParseResult::ControlCaught, parse_info}; + } + case OpCode::Id::SYNC: { + const auto pred_index = static_cast<u32>(instr.pred.pred_index); + parse_info.branch_info.condition.predicate = + GetPredicate(pred_index, instr.negate_pred != 0); + if (parse_info.branch_info.condition.predicate == Pred::NeverExecute) { + offset++; + continue; + } + const ConditionCode cc = instr.flow_condition_code; + parse_info.branch_info.condition.cc = cc; + if (cc == ConditionCode::F) { + offset++; + continue; + } + parse_info.branch_info.address = unassigned_branch; + parse_info.branch_info.kill = false; + parse_info.branch_info.is_sync = true; + parse_info.branch_info.is_brk = false; + parse_info.branch_info.ignore = false; + parse_info.end_address = offset; + + return {ParseResult::ControlCaught, parse_info}; + } + case OpCode::Id::BRK: { + const auto pred_index = static_cast<u32>(instr.pred.pred_index); + parse_info.branch_info.condition.predicate = + GetPredicate(pred_index, instr.negate_pred != 0); + if (parse_info.branch_info.condition.predicate == Pred::NeverExecute) { + offset++; + continue; + } + const ConditionCode cc = instr.flow_condition_code; + parse_info.branch_info.condition.cc = cc; + if (cc == ConditionCode::F) { + offset++; + continue; + } + parse_info.branch_info.address = unassigned_branch; + parse_info.branch_info.kill = false; + parse_info.branch_info.is_sync = false; + parse_info.branch_info.is_brk = true; + parse_info.branch_info.ignore = false; + parse_info.end_address = offset; + + return {ParseResult::ControlCaught, parse_info}; + } + case OpCode::Id::KIL: { + const auto pred_index = static_cast<u32>(instr.pred.pred_index); + parse_info.branch_info.condition.predicate = + GetPredicate(pred_index, instr.negate_pred != 0); + if (parse_info.branch_info.condition.predicate == Pred::NeverExecute) { + offset++; + continue; + } + const ConditionCode cc = instr.flow_condition_code; + parse_info.branch_info.condition.cc = cc; + if (cc == ConditionCode::F) { + offset++; + continue; + } + parse_info.branch_info.address = exit_branch; + parse_info.branch_info.kill = true; + parse_info.branch_info.is_sync = false; + parse_info.branch_info.is_brk = false; + parse_info.branch_info.ignore = false; + parse_info.end_address = offset; + + return {ParseResult::ControlCaught, parse_info}; + } + case OpCode::Id::SSY: { + const u32 target = offset + instr.bra.GetBranchTarget(); + insert_label(state, target); + state.ssy_labels.emplace(offset, target); + break; + } + case OpCode::Id::PBK: { + const u32 target = offset + instr.bra.GetBranchTarget(); + insert_label(state, target); + state.pbk_labels.emplace(offset, target); + break; + } + case OpCode::Id::BRX: { + return {ParseResult::AbnormalFlow, parse_info}; + } + default: + break; + } + + offset++; + } + parse_info.branch_info.kill = false; + parse_info.branch_info.is_sync = false; + parse_info.branch_info.is_brk = false; + parse_info.end_address = offset - 1; + return {ParseResult::BlockEnd, parse_info}; +} + +bool TryInspectAddress(CFGRebuildState& state) { + if (state.inspect_queries.empty()) { + return false; + } + + const u32 address = state.inspect_queries.front(); + state.inspect_queries.pop_front(); + const auto [result, block_index] = TryGetBlock(state, address); + switch (result) { + case BlockCollision::Found: { + return true; + } + case BlockCollision::Inside: { + // This case is the tricky one: + // We need to Split the block in 2 sepparate blocks + const u32 end = state.block_info[block_index].end; + BlockInfo& new_block = CreateBlockInfo(state, address, end); + BlockInfo& current_block = state.block_info[block_index]; + current_block.end = address - 1; + new_block.branch = current_block.branch; + BlockBranchInfo forward_branch{}; + forward_branch.address = address; + forward_branch.ignore = true; + current_block.branch = forward_branch; + return true; + } + default: + break; + } + const auto [parse_result, parse_info] = ParseCode(state, address); + if (parse_result == ParseResult::AbnormalFlow) { + // if it's AbnormalFlow, we end it as false, ending the CFG reconstruction + return false; + } + + BlockInfo& block_info = CreateBlockInfo(state, address, parse_info.end_address); + block_info.branch = parse_info.branch_info; + if (parse_info.branch_info.condition.IsUnconditional()) { + return true; + } + + const u32 fallthrough_address = parse_info.end_address + 1; + state.inspect_queries.push_front(fallthrough_address); + return true; +} + +bool TryQuery(CFGRebuildState& state) { + const auto gather_labels = [](std::stack<u32>& cc, std::map<u32, u32>& labels, + BlockInfo& block) { + auto gather_start = labels.lower_bound(block.start); + const auto gather_end = labels.upper_bound(block.end); + while (gather_start != gather_end) { + cc.push(gather_start->second); + ++gather_start; + } + }; + if (state.queries.empty()) { + return false; + } + + Query& q = state.queries.front(); + const u32 block_index = state.registered[q.address]; + BlockInfo& block = state.block_info[block_index]; + // If the block is visited, check if the stacks match, else gather the ssy/pbk + // labels into the current stack and look if the branch at the end of the block + // consumes a label. Schedule new queries accordingly + if (block.visited) { + BlockStack& stack = state.stacks[q.address]; + const bool all_okay = (stack.ssy_stack.empty() || q.ssy_stack == stack.ssy_stack) && + (stack.pbk_stack.empty() || q.pbk_stack == stack.pbk_stack); + state.queries.pop_front(); + return all_okay; + } + block.visited = true; + state.stacks.insert_or_assign(q.address, BlockStack{q}); + + Query q2(q); + state.queries.pop_front(); + gather_labels(q2.ssy_stack, state.ssy_labels, block); + gather_labels(q2.pbk_stack, state.pbk_labels, block); + if (!block.branch.condition.IsUnconditional()) { + q2.address = block.end + 1; + state.queries.push_back(q2); + } + + Query conditional_query{q2}; + if (block.branch.is_sync) { + if (block.branch.address == unassigned_branch) { + block.branch.address = conditional_query.ssy_stack.top(); + } + conditional_query.ssy_stack.pop(); + } + if (block.branch.is_brk) { + if (block.branch.address == unassigned_branch) { + block.branch.address = conditional_query.pbk_stack.top(); + } + conditional_query.pbk_stack.pop(); + } + conditional_query.address = block.branch.address; + state.queries.push_back(std::move(conditional_query)); + return true; +} +} // Anonymous namespace + +std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, + std::size_t program_size, u32 start_address) { + CFGRebuildState state{program_code, program_size, start_address}; + + // Inspect Code and generate blocks + state.labels.clear(); + state.labels.emplace(start_address); + state.inspect_queries.push_back(state.start); + while (!state.inspect_queries.empty()) { + if (!TryInspectAddress(state)) { + return {}; + } + } + + // Decompile Stacks + state.queries.push_back(Query{state.start, {}, {}}); + bool decompiled = true; + while (!state.queries.empty()) { + if (!TryQuery(state)) { + decompiled = false; + break; + } + } + + // Sort and organize results + std::sort(state.block_info.begin(), state.block_info.end(), + [](const BlockInfo& a, const BlockInfo& b) { return a.start < b.start; }); + ShaderCharacteristics result_out{}; + result_out.decompilable = decompiled; + result_out.start = start_address; + result_out.end = start_address; + for (const auto& block : state.block_info) { + ShaderBlock new_block{}; + new_block.start = block.start; + new_block.end = block.end; + new_block.ignore_branch = block.branch.ignore; + if (!new_block.ignore_branch) { + new_block.branch.cond = block.branch.condition; + new_block.branch.kills = block.branch.kill; + new_block.branch.address = block.branch.address; + } + result_out.end = std::max(result_out.end, block.end); + result_out.blocks.push_back(new_block); + } + if (result_out.decompilable) { + result_out.labels = std::move(state.labels); + return {std::move(result_out)}; + } + + // If it's not decompilable, merge the unlabelled blocks together + auto back = result_out.blocks.begin(); + auto next = std::next(back); + while (next != result_out.blocks.end()) { + if (state.labels.count(next->start) == 0 && next->start == back->end + 1) { + back->end = next->end; + next = result_out.blocks.erase(next); + continue; + } + back = next; + ++next; + } + return {std::move(result_out)}; +} +} // namespace VideoCommon::Shader diff --git a/src/video_core/shader/control_flow.h b/src/video_core/shader/control_flow.h new file mode 100644 index 000000000..b0a5e4f8c --- /dev/null +++ b/src/video_core/shader/control_flow.h @@ -0,0 +1,79 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <list> +#include <optional> +#include <unordered_set> + +#include "video_core/engines/shader_bytecode.h" +#include "video_core/shader/shader_ir.h" + +namespace VideoCommon::Shader { + +using Tegra::Shader::ConditionCode; +using Tegra::Shader::Pred; + +constexpr s32 exit_branch = -1; + +struct Condition { + Pred predicate{Pred::UnusedIndex}; + ConditionCode cc{ConditionCode::T}; + + bool IsUnconditional() const { + return predicate == Pred::UnusedIndex && cc == ConditionCode::T; + } + + bool operator==(const Condition& other) const { + return std::tie(predicate, cc) == std::tie(other.predicate, other.cc); + } + + bool operator!=(const Condition& other) const { + return !operator==(other); + } +}; + +struct ShaderBlock { + struct Branch { + Condition cond{}; + bool kills{}; + s32 address{}; + + bool operator==(const Branch& b) const { + return std::tie(cond, kills, address) == std::tie(b.cond, b.kills, b.address); + } + + bool operator!=(const Branch& b) const { + return !operator==(b); + } + }; + + u32 start{}; + u32 end{}; + bool ignore_branch{}; + Branch branch{}; + + bool operator==(const ShaderBlock& sb) const { + return std::tie(start, end, ignore_branch, branch) == + std::tie(sb.start, sb.end, sb.ignore_branch, sb.branch); + } + + bool operator!=(const ShaderBlock& sb) const { + return !operator==(sb); + } +}; + +struct ShaderCharacteristics { + std::list<ShaderBlock> blocks{}; + bool decompilable{}; + u32 start{}; + u32 end{}; + std::unordered_set<u32> labels{}; +}; + +std::optional<ShaderCharacteristics> ScanFlow(const ProgramCode& program_code, + std::size_t program_size, u32 start_address); + +} // namespace VideoCommon::Shader diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp index a0554c97e..47a9fd961 100644 --- a/src/video_core/shader/decode.cpp +++ b/src/video_core/shader/decode.cpp @@ -11,6 +11,7 @@ #include "common/common_types.h" #include "video_core/engines/shader_bytecode.h" #include "video_core/engines/shader_header.h" +#include "video_core/shader/control_flow.h" #include "video_core/shader/node_helper.h" #include "video_core/shader/shader_ir.h" @@ -21,20 +22,6 @@ using Tegra::Shader::OpCode; namespace { -/// Merges exit method of two parallel branches. -constexpr ExitMethod ParallelExit(ExitMethod a, ExitMethod b) { - if (a == ExitMethod::Undetermined) { - return b; - } - if (b == ExitMethod::Undetermined) { - return a; - } - if (a == b) { - return a; - } - return ExitMethod::Conditional; -} - /** * Returns whether the instruction at the specified offset is a 'sched' instruction. * Sched instructions always appear before a sequence of 3 instructions. @@ -51,85 +38,104 @@ constexpr bool IsSchedInstruction(u32 offset, u32 main_offset) { void ShaderIR::Decode() { std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header)); - std::set<u32> labels; - const ExitMethod exit_method = Scan(main_offset, MAX_PROGRAM_LENGTH, labels); - if (exit_method != ExitMethod::AlwaysEnd) { - UNREACHABLE_MSG("Program does not always end"); - } - - if (labels.empty()) { - basic_blocks.insert({main_offset, DecodeRange(main_offset, MAX_PROGRAM_LENGTH)}); + disable_flow_stack = false; + const auto info = ScanFlow(program_code, program_size, main_offset); + if (info) { + const auto& shader_info = *info; + coverage_begin = shader_info.start; + coverage_end = shader_info.end; + if (shader_info.decompilable) { + disable_flow_stack = true; + const auto insert_block = [this](NodeBlock& nodes, u32 label) { + if (label == static_cast<u32>(exit_branch)) { + return; + } + basic_blocks.insert({label, nodes}); + }; + const auto& blocks = shader_info.blocks; + NodeBlock current_block; + u32 current_label = static_cast<u32>(exit_branch); + for (auto& block : blocks) { + if (shader_info.labels.count(block.start) != 0) { + insert_block(current_block, current_label); + current_block.clear(); + current_label = block.start; + } + if (!block.ignore_branch) { + DecodeRangeInner(current_block, block.start, block.end); + InsertControlFlow(current_block, block); + } else { + DecodeRangeInner(current_block, block.start, block.end + 1); + } + } + insert_block(current_block, current_label); + return; + } + LOG_WARNING(HW_GPU, "Flow Stack Removing Failed! Falling back to old method"); + // we can't decompile it, fallback to standard method + for (const auto& block : shader_info.blocks) { + basic_blocks.insert({block.start, DecodeRange(block.start, block.end + 1)}); + } return; } + LOG_WARNING(HW_GPU, "Flow Analysis Failed! Falling back to brute force compiling"); + + // Now we need to deal with an undecompilable shader. We need to brute force + // a shader that captures every position. + coverage_begin = main_offset; + const u32 shader_end = static_cast<u32>(program_size / sizeof(u64)); + coverage_end = shader_end; + for (u32 label = main_offset; label < shader_end; label++) { + basic_blocks.insert({label, DecodeRange(label, label + 1)}); + } +} - labels.insert(main_offset); - - for (const u32 label : labels) { - const auto next_it = labels.lower_bound(label + 1); - const u32 next_label = next_it == labels.end() ? MAX_PROGRAM_LENGTH : *next_it; +NodeBlock ShaderIR::DecodeRange(u32 begin, u32 end) { + NodeBlock basic_block; + DecodeRangeInner(basic_block, begin, end); + return basic_block; +} - basic_blocks.insert({label, DecodeRange(label, next_label)}); +void ShaderIR::DecodeRangeInner(NodeBlock& bb, u32 begin, u32 end) { + for (u32 pc = begin; pc < (begin > end ? MAX_PROGRAM_LENGTH : end);) { + pc = DecodeInstr(bb, pc); } } -ExitMethod ShaderIR::Scan(u32 begin, u32 end, std::set<u32>& labels) { - const auto [iter, inserted] = - exit_method_map.emplace(std::make_pair(begin, end), ExitMethod::Undetermined); - ExitMethod& exit_method = iter->second; - if (!inserted) - return exit_method; - - for (u32 offset = begin; offset != end && offset != MAX_PROGRAM_LENGTH; ++offset) { - coverage_begin = std::min(coverage_begin, offset); - coverage_end = std::max(coverage_end, offset + 1); - - const Instruction instr = {program_code[offset]}; - const auto opcode = OpCode::Decode(instr); - if (!opcode) - continue; - switch (opcode->get().GetId()) { - case OpCode::Id::EXIT: { - // The EXIT instruction can be predicated, which means that the shader can conditionally - // end on this instruction. We have to consider the case where the condition is not met - // and check the exit method of that other basic block. - using Tegra::Shader::Pred; - if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) { - return exit_method = ExitMethod::AlwaysEnd; - } else { - const ExitMethod not_met = Scan(offset + 1, end, labels); - return exit_method = ParallelExit(ExitMethod::AlwaysEnd, not_met); - } +void ShaderIR::InsertControlFlow(NodeBlock& bb, const ShaderBlock& block) { + const auto apply_conditions = [&](const Condition& cond, Node n) -> Node { + Node result = n; + if (cond.cc != ConditionCode::T) { + result = Conditional(GetConditionCode(cond.cc), {result}); } - case OpCode::Id::BRA: { - const u32 target = offset + instr.bra.GetBranchTarget(); - labels.insert(target); - const ExitMethod no_jmp = Scan(offset + 1, end, labels); - const ExitMethod jmp = Scan(target, end, labels); - return exit_method = ParallelExit(no_jmp, jmp); - } - case OpCode::Id::SSY: - case OpCode::Id::PBK: { - // The SSY and PBK use a similar encoding as the BRA instruction. - UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, - "Constant buffer branching is not supported"); - const u32 target = offset + instr.bra.GetBranchTarget(); - labels.insert(target); - // Continue scanning for an exit method. - break; + if (cond.predicate != Pred::UnusedIndex) { + u32 pred = static_cast<u32>(cond.predicate); + const bool is_neg = pred > 7; + if (is_neg) { + pred -= 8; + } + result = Conditional(GetPredicate(pred, is_neg), {result}); } - default: - break; + return result; + }; + if (block.branch.address < 0) { + if (block.branch.kills) { + Node n = Operation(OperationCode::Discard); + n = apply_conditions(block.branch.cond, n); + bb.push_back(n); + global_code.push_back(n); + return; } + Node n = Operation(OperationCode::Exit); + n = apply_conditions(block.branch.cond, n); + bb.push_back(n); + global_code.push_back(n); + return; } - return exit_method = ExitMethod::AlwaysReturn; -} - -NodeBlock ShaderIR::DecodeRange(u32 begin, u32 end) { - NodeBlock basic_block; - for (u32 pc = begin; pc < (begin > end ? MAX_PROGRAM_LENGTH : end);) { - pc = DecodeInstr(basic_block, pc); - } - return basic_block; + Node n = Operation(OperationCode::Branch, Immediate(block.branch.address)); + n = apply_conditions(block.branch.cond, n); + bb.push_back(n); + global_code.push_back(n); } u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) { @@ -140,15 +146,18 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; const auto opcode = OpCode::Decode(instr); + const u32 nv_address = ConvertAddressToNvidiaSpace(pc); // Decoding failure if (!opcode) { UNIMPLEMENTED_MSG("Unhandled instruction: {0:x}", instr.value); + bb.push_back(Comment(fmt::format("{:05x} Unimplemented Shader instruction (0x{:016x})", + nv_address, instr.value))); return pc + 1; } - bb.push_back( - Comment(fmt::format("{}: {} (0x{:016x})", pc, opcode->get().GetName(), instr.value))); + bb.push_back(Comment( + fmt::format("{:05x} {} (0x{:016x})", nv_address, opcode->get().GetName(), instr.value))); using Tegra::Shader::Pred; UNIMPLEMENTED_IF_MSG(instr.pred.full_pred == Pred::NeverExecute, @@ -167,8 +176,10 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) { {OpCode::Type::Ffma, &ShaderIR::DecodeFfma}, {OpCode::Type::Hfma2, &ShaderIR::DecodeHfma2}, {OpCode::Type::Conversion, &ShaderIR::DecodeConversion}, + {OpCode::Type::Warp, &ShaderIR::DecodeWarp}, {OpCode::Type::Memory, &ShaderIR::DecodeMemory}, {OpCode::Type::Texture, &ShaderIR::DecodeTexture}, + {OpCode::Type::Image, &ShaderIR::DecodeImage}, {OpCode::Type::FloatSetPredicate, &ShaderIR::DecodeFloatSetPredicate}, {OpCode::Type::IntegerSetPredicate, &ShaderIR::DecodeIntegerSetPredicate}, {OpCode::Type::HalfSetPredicate, &ShaderIR::DecodeHalfSetPredicate}, diff --git a/src/video_core/shader/decode/arithmetic.cpp b/src/video_core/shader/decode/arithmetic.cpp index 87d8fecaa..1473c282a 100644 --- a/src/video_core/shader/decode/arithmetic.cpp +++ b/src/video_core/shader/decode/arithmetic.cpp @@ -42,11 +42,14 @@ u32 ShaderIR::DecodeArithmetic(NodeBlock& bb, u32 pc) { case OpCode::Id::FMUL_R: case OpCode::Id::FMUL_IMM: { // FMUL does not have 'abs' bits and only the second operand has a 'neg' bit. - UNIMPLEMENTED_IF_MSG(instr.fmul.tab5cb8_2 != 0, "FMUL tab5cb8_2({}) is not implemented", - instr.fmul.tab5cb8_2.Value()); - UNIMPLEMENTED_IF_MSG( - instr.fmul.tab5c68_0 != 1, "FMUL tab5cb8_0({}) is not implemented", - instr.fmul.tab5c68_0.Value()); // SMO typical sends 1 here which seems to be the default + if (instr.fmul.tab5cb8_2 != 0) { + LOG_WARNING(HW_GPU, "FMUL tab5cb8_2({}) is not implemented", + instr.fmul.tab5cb8_2.Value()); + } + if (instr.fmul.tab5c68_0 != 1) { + LOG_WARNING(HW_GPU, "FMUL tab5cb8_0({}) is not implemented", + instr.fmul.tab5c68_0.Value()); + } op_b = GetOperandAbsNegFloat(op_b, false, instr.fmul.negate_b); diff --git a/src/video_core/shader/decode/arithmetic_half_immediate.cpp b/src/video_core/shader/decode/arithmetic_half_immediate.cpp index 7bcf38f23..6466fc011 100644 --- a/src/video_core/shader/decode/arithmetic_half_immediate.cpp +++ b/src/video_core/shader/decode/arithmetic_half_immediate.cpp @@ -23,7 +23,9 @@ u32 ShaderIR::DecodeArithmeticHalfImmediate(NodeBlock& bb, u32 pc) { LOG_WARNING(HW_GPU, "{} FTZ not implemented", opcode->get().GetName()); } } else { - UNIMPLEMENTED_IF(instr.alu_half_imm.precision != Tegra::Shader::HalfPrecision::None); + if (instr.alu_half_imm.precision != Tegra::Shader::HalfPrecision::None) { + LOG_WARNING(HW_GPU, "{} FTZ not implemented", opcode->get().GetName()); + } } Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half_imm.type_a); diff --git a/src/video_core/shader/decode/arithmetic_integer.cpp b/src/video_core/shader/decode/arithmetic_integer.cpp index c8c1a7f40..b73f6536e 100644 --- a/src/video_core/shader/decode/arithmetic_integer.cpp +++ b/src/video_core/shader/decode/arithmetic_integer.cpp @@ -138,6 +138,35 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) { SetRegister(bb, instr.gpr0, value); break; } + case OpCode::Id::ICMP_CR: + case OpCode::Id::ICMP_R: + case OpCode::Id::ICMP_RC: + case OpCode::Id::ICMP_IMM: { + const Node zero = Immediate(0); + + const auto [op_b, test] = [&]() -> std::pair<Node, Node> { + switch (opcode->get().GetId()) { + case OpCode::Id::ICMP_CR: + return {GetConstBuffer(instr.cbuf34.index, instr.cbuf34.offset), + GetRegister(instr.gpr39)}; + case OpCode::Id::ICMP_R: + return {GetRegister(instr.gpr20), GetRegister(instr.gpr39)}; + case OpCode::Id::ICMP_RC: + return {GetRegister(instr.gpr39), + GetConstBuffer(instr.cbuf34.index, instr.cbuf34.offset)}; + case OpCode::Id::ICMP_IMM: + return {Immediate(instr.alu.GetSignedImm20_20()), GetRegister(instr.gpr39)}; + default: + UNREACHABLE(); + return {zero, zero}; + } + }(); + const Node op_a = GetRegister(instr.gpr8); + const Node comparison = + GetPredicateComparisonInteger(instr.icmp.cond, instr.icmp.is_signed != 0, test, zero); + SetRegister(bb, instr.gpr0, Operation(OperationCode::Select, comparison, op_a, op_b)); + break; + } case OpCode::Id::LOP_C: case OpCode::Id::LOP_R: case OpCode::Id::LOP_IMM: { diff --git a/src/video_core/shader/decode/conversion.cpp b/src/video_core/shader/decode/conversion.cpp index 4221f0c58..32facd6ba 100644 --- a/src/video_core/shader/decode/conversion.cpp +++ b/src/video_core/shader/decode/conversion.cpp @@ -14,6 +14,12 @@ using Tegra::Shader::Instruction; using Tegra::Shader::OpCode; using Tegra::Shader::Register; +namespace { +constexpr OperationCode GetFloatSelector(u64 selector) { + return selector == 0 ? OperationCode::FCastHalf0 : OperationCode::FCastHalf1; +} +} // Anonymous namespace + u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; const auto opcode = OpCode::Decode(instr); @@ -22,7 +28,7 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { case OpCode::Id::I2I_R: case OpCode::Id::I2I_C: case OpCode::Id::I2I_IMM: { - UNIMPLEMENTED_IF(instr.conversion.selector); + UNIMPLEMENTED_IF(instr.conversion.int_src.selector != 0); UNIMPLEMENTED_IF(instr.conversion.dst_size != Register::Size::Word); UNIMPLEMENTED_IF(instr.alu.saturate_d); @@ -57,8 +63,8 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { case OpCode::Id::I2F_R: case OpCode::Id::I2F_C: case OpCode::Id::I2F_IMM: { - UNIMPLEMENTED_IF(instr.conversion.dst_size != Register::Size::Word); - UNIMPLEMENTED_IF(instr.conversion.selector); + UNIMPLEMENTED_IF(instr.conversion.int_src.selector != 0); + UNIMPLEMENTED_IF(instr.conversion.dst_size == Register::Size::Long); UNIMPLEMENTED_IF_MSG(instr.generates_cc, "Condition codes generation in I2F is not implemented"); @@ -82,14 +88,19 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { value = GetOperandAbsNegFloat(value, false, instr.conversion.negate_a); SetInternalFlagsFromFloat(bb, value, instr.generates_cc); + + if (instr.conversion.dst_size == Register::Size::Short) { + value = Operation(OperationCode::HCastFloat, PRECISE, value); + } + SetRegister(bb, instr.gpr0, value); break; } case OpCode::Id::F2F_R: case OpCode::Id::F2F_C: case OpCode::Id::F2F_IMM: { - UNIMPLEMENTED_IF(instr.conversion.f2f.dst_size != Register::Size::Word); - UNIMPLEMENTED_IF(instr.conversion.f2f.src_size != Register::Size::Word); + UNIMPLEMENTED_IF(instr.conversion.dst_size == Register::Size::Long); + UNIMPLEMENTED_IF(instr.conversion.src_size == Register::Size::Long); UNIMPLEMENTED_IF_MSG(instr.generates_cc, "Condition codes generation in F2F is not implemented"); @@ -107,6 +118,13 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { } }(); + if (instr.conversion.src_size == Register::Size::Short) { + value = Operation(GetFloatSelector(instr.conversion.float_src.selector), NO_PRECISE, + std::move(value)); + } else { + ASSERT(instr.conversion.float_src.selector == 0); + } + value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a); value = [&]() { @@ -124,19 +142,24 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { default: UNIMPLEMENTED_MSG("Unimplemented F2F rounding mode {}", static_cast<u32>(instr.conversion.f2f.rounding.Value())); - return Immediate(0); + return value; } }(); value = GetSaturatedFloat(value, instr.alu.saturate_d); SetInternalFlagsFromFloat(bb, value, instr.generates_cc); + + if (instr.conversion.dst_size == Register::Size::Short) { + value = Operation(OperationCode::HCastFloat, PRECISE, value); + } + SetRegister(bb, instr.gpr0, value); break; } case OpCode::Id::F2I_R: case OpCode::Id::F2I_C: case OpCode::Id::F2I_IMM: { - UNIMPLEMENTED_IF(instr.conversion.src_size != Register::Size::Word); + UNIMPLEMENTED_IF(instr.conversion.src_size == Register::Size::Long); UNIMPLEMENTED_IF_MSG(instr.generates_cc, "Condition codes generation in F2I is not implemented"); Node value = [&]() { @@ -153,6 +176,13 @@ u32 ShaderIR::DecodeConversion(NodeBlock& bb, u32 pc) { } }(); + if (instr.conversion.src_size == Register::Size::Short) { + value = Operation(GetFloatSelector(instr.conversion.float_src.selector), NO_PRECISE, + std::move(value)); + } else { + ASSERT(instr.conversion.float_src.selector == 0); + } + value = GetOperandAbsNegFloat(value, instr.conversion.abs_a, instr.conversion.negate_a); value = [&]() { diff --git a/src/video_core/shader/decode/decode_integer_set.cpp b/src/video_core/shader/decode/decode_integer_set.cpp deleted file mode 100644 index e69de29bb..000000000 --- a/src/video_core/shader/decode/decode_integer_set.cpp +++ /dev/null diff --git a/src/video_core/shader/decode/ffma.cpp b/src/video_core/shader/decode/ffma.cpp index 29be25ca3..ca2f39e8d 100644 --- a/src/video_core/shader/decode/ffma.cpp +++ b/src/video_core/shader/decode/ffma.cpp @@ -18,10 +18,12 @@ u32 ShaderIR::DecodeFfma(NodeBlock& bb, u32 pc) { const auto opcode = OpCode::Decode(instr); UNIMPLEMENTED_IF_MSG(instr.ffma.cc != 0, "FFMA cc not implemented"); - UNIMPLEMENTED_IF_MSG(instr.ffma.tab5980_0 != 1, "FFMA tab5980_0({}) not implemented", - instr.ffma.tab5980_0.Value()); // Seems to be 1 by default based on SMO - UNIMPLEMENTED_IF_MSG(instr.ffma.tab5980_1 != 0, "FFMA tab5980_1({}) not implemented", - instr.ffma.tab5980_1.Value()); + if (instr.ffma.tab5980_0 != 1) { + LOG_WARNING(HW_GPU, "FFMA tab5980_0({}) not implemented", instr.ffma.tab5980_0.Value()); + } + if (instr.ffma.tab5980_1 != 0) { + LOG_WARNING(HW_GPU, "FFMA tab5980_1({}) not implemented", instr.ffma.tab5980_1.Value()); + } const Node op_a = GetRegister(instr.gpr8); diff --git a/src/video_core/shader/decode/float_set.cpp b/src/video_core/shader/decode/float_set.cpp index f5013e44a..5614e8a0d 100644 --- a/src/video_core/shader/decode/float_set.cpp +++ b/src/video_core/shader/decode/float_set.cpp @@ -15,7 +15,6 @@ using Tegra::Shader::OpCode; u32 ShaderIR::DecodeFloatSet(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; - const auto opcode = OpCode::Decode(instr); const Node op_a = GetOperandAbsNegFloat(GetRegister(instr.gpr8), instr.fset.abs_a != 0, instr.fset.neg_a != 0); diff --git a/src/video_core/shader/decode/float_set_predicate.cpp b/src/video_core/shader/decode/float_set_predicate.cpp index 2323052b0..200c2c983 100644 --- a/src/video_core/shader/decode/float_set_predicate.cpp +++ b/src/video_core/shader/decode/float_set_predicate.cpp @@ -16,10 +16,9 @@ using Tegra::Shader::Pred; u32 ShaderIR::DecodeFloatSetPredicate(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; - const auto opcode = OpCode::Decode(instr); - const Node op_a = GetOperandAbsNegFloat(GetRegister(instr.gpr8), instr.fsetp.abs_a != 0, - instr.fsetp.neg_a != 0); + Node op_a = GetOperandAbsNegFloat(GetRegister(instr.gpr8), instr.fsetp.abs_a != 0, + instr.fsetp.neg_a != 0); Node op_b = [&]() { if (instr.is_b_imm) { return GetImmediate19(instr); @@ -29,12 +28,13 @@ u32 ShaderIR::DecodeFloatSetPredicate(NodeBlock& bb, u32 pc) { return GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()); } }(); - op_b = GetOperandAbsNegFloat(op_b, instr.fsetp.abs_b, false); + op_b = GetOperandAbsNegFloat(std::move(op_b), instr.fsetp.abs_b, instr.fsetp.neg_b); // We can't use the constant predicate as destination. ASSERT(instr.fsetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); - const Node predicate = GetPredicateComparisonFloat(instr.fsetp.cond, op_a, op_b); + const Node predicate = + GetPredicateComparisonFloat(instr.fsetp.cond, std::move(op_a), std::move(op_b)); const Node second_pred = GetPredicate(instr.fsetp.pred39, instr.fsetp.neg_pred != 0); const OperationCode combiner = GetPredicateCombiner(instr.fsetp.op); diff --git a/src/video_core/shader/decode/half_set_predicate.cpp b/src/video_core/shader/decode/half_set_predicate.cpp index d59d15bd8..840694527 100644 --- a/src/video_core/shader/decode/half_set_predicate.cpp +++ b/src/video_core/shader/decode/half_set_predicate.cpp @@ -18,43 +18,55 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; const auto opcode = OpCode::Decode(instr); - UNIMPLEMENTED_IF(instr.hsetp2.ftz != 0); + DEBUG_ASSERT(instr.hsetp2.ftz == 0); Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.hsetp2.type_a); op_a = GetOperandAbsNegHalf(op_a, instr.hsetp2.abs_a, instr.hsetp2.negate_a); - Node op_b = [&]() { - switch (opcode->get().GetId()) { - case OpCode::Id::HSETP2_R: - return GetOperandAbsNegHalf(GetRegister(instr.gpr20), instr.hsetp2.abs_a, - instr.hsetp2.negate_b); - default: - UNREACHABLE(); - return Immediate(0); - } - }(); - op_b = UnpackHalfFloat(op_b, instr.hsetp2.type_b); - - // We can't use the constant predicate as destination. - ASSERT(instr.hsetp2.pred3 != static_cast<u64>(Pred::UnusedIndex)); - - const Node second_pred = GetPredicate(instr.hsetp2.pred39, instr.hsetp2.neg_pred != 0); + Tegra::Shader::PredCondition cond{}; + bool h_and{}; + Node op_b{}; + switch (opcode->get().GetId()) { + case OpCode::Id::HSETP2_C: + cond = instr.hsetp2.cbuf_and_imm.cond; + h_and = instr.hsetp2.cbuf_and_imm.h_and; + op_b = GetOperandAbsNegHalf(GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()), + instr.hsetp2.cbuf.abs_b, instr.hsetp2.cbuf.negate_b); + break; + case OpCode::Id::HSETP2_IMM: + cond = instr.hsetp2.cbuf_and_imm.cond; + h_and = instr.hsetp2.cbuf_and_imm.h_and; + op_b = UnpackHalfImmediate(instr, true); + break; + case OpCode::Id::HSETP2_R: + cond = instr.hsetp2.reg.cond; + h_and = instr.hsetp2.reg.h_and; + op_b = + GetOperandAbsNegHalf(UnpackHalfFloat(GetRegister(instr.gpr20), instr.hsetp2.reg.type_b), + instr.hsetp2.reg.abs_b, instr.hsetp2.reg.negate_b); + break; + default: + UNREACHABLE(); + op_b = Immediate(0); + } const OperationCode combiner = GetPredicateCombiner(instr.hsetp2.op); - const OperationCode pair_combiner = - instr.hsetp2.h_and ? OperationCode::LogicalAll2 : OperationCode::LogicalAny2; - - const Node comparison = GetPredicateComparisonHalf(instr.hsetp2.cond, op_a, op_b); - const Node first_pred = Operation(pair_combiner, comparison); + const Node combined_pred = GetPredicate(instr.hsetp2.pred39, instr.hsetp2.neg_pred); - // Set the primary predicate to the result of Predicate OP SecondPredicate - const Node value = Operation(combiner, first_pred, second_pred); - SetPredicate(bb, instr.hsetp2.pred3, value); + const auto Write = [&](u64 dest, Node src) { + SetPredicate(bb, dest, Operation(combiner, std::move(src), combined_pred)); + }; - if (instr.hsetp2.pred0 != static_cast<u64>(Pred::UnusedIndex)) { - // Set the secondary predicate to the result of !Predicate OP SecondPredicate, if enabled - const Node negated_pred = Operation(OperationCode::LogicalNegate, first_pred); - SetPredicate(bb, instr.hsetp2.pred0, Operation(combiner, negated_pred, second_pred)); + const Node comparison = GetPredicateComparisonHalf(cond, op_a, op_b); + const u64 first = instr.hsetp2.pred3; + const u64 second = instr.hsetp2.pred0; + if (h_and) { + Node joined = Operation(OperationCode::LogicalAnd2, comparison); + Write(first, joined); + Write(second, Operation(OperationCode::LogicalNegate, std::move(joined))); + } else { + Write(first, Operation(OperationCode::LogicalPick2, comparison, Immediate(0U))); + Write(second, Operation(OperationCode::LogicalPick2, comparison, Immediate(1U))); } return pc; diff --git a/src/video_core/shader/decode/hfma2.cpp b/src/video_core/shader/decode/hfma2.cpp index c3bcf1ae9..5b44cb79c 100644 --- a/src/video_core/shader/decode/hfma2.cpp +++ b/src/video_core/shader/decode/hfma2.cpp @@ -22,9 +22,9 @@ u32 ShaderIR::DecodeHfma2(NodeBlock& bb, u32 pc) { const auto opcode = OpCode::Decode(instr); if (opcode->get().GetId() == OpCode::Id::HFMA2_RR) { - UNIMPLEMENTED_IF(instr.hfma2.rr.precision != HalfPrecision::None); + DEBUG_ASSERT(instr.hfma2.rr.precision == HalfPrecision::None); } else { - UNIMPLEMENTED_IF(instr.hfma2.precision != HalfPrecision::None); + DEBUG_ASSERT(instr.hfma2.precision == HalfPrecision::None); } constexpr auto identity = HalfType::H0_H1; diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp new file mode 100644 index 000000000..95ec1cdd9 --- /dev/null +++ b/src/video_core/shader/decode/image.cpp @@ -0,0 +1,181 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <vector> +#include <fmt/format.h> + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "video_core/engines/shader_bytecode.h" +#include "video_core/shader/node_helper.h" +#include "video_core/shader/shader_ir.h" + +namespace VideoCommon::Shader { + +using Tegra::Shader::Instruction; +using Tegra::Shader::OpCode; + +namespace { +std::size_t GetImageTypeNumCoordinates(Tegra::Shader::ImageType image_type) { + switch (image_type) { + case Tegra::Shader::ImageType::Texture1D: + case Tegra::Shader::ImageType::TextureBuffer: + return 1; + case Tegra::Shader::ImageType::Texture1DArray: + case Tegra::Shader::ImageType::Texture2D: + return 2; + case Tegra::Shader::ImageType::Texture2DArray: + case Tegra::Shader::ImageType::Texture3D: + return 3; + } + UNREACHABLE(); + return 1; +} +} // Anonymous namespace + +u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { + const Instruction instr = {program_code[pc]}; + const auto opcode = OpCode::Decode(instr); + + const auto GetCoordinates = [this, instr](Tegra::Shader::ImageType image_type) { + std::vector<Node> coords; + const std::size_t num_coords{GetImageTypeNumCoordinates(image_type)}; + coords.reserve(num_coords); + for (std::size_t i = 0; i < num_coords; ++i) { + coords.push_back(GetRegister(instr.gpr8.Value() + i)); + } + return coords; + }; + + switch (opcode->get().GetId()) { + case OpCode::Id::SULD: { + UNIMPLEMENTED_IF(instr.suldst.mode != Tegra::Shader::SurfaceDataMode::P); + UNIMPLEMENTED_IF(instr.suldst.out_of_bounds_store != + Tegra::Shader::OutOfBoundsStore::Ignore); + + const auto type{instr.suldst.image_type}; + auto& image{instr.suldst.is_immediate ? GetImage(instr.image, type) + : GetBindlessImage(instr.gpr39, type)}; + image.MarkRead(); + + u32 indexer = 0; + for (u32 element = 0; element < 4; ++element) { + if (!instr.suldst.IsComponentEnabled(element)) { + continue; + } + MetaImage meta{image, {}, element}; + Node value = Operation(OperationCode::ImageLoad, meta, GetCoordinates(type)); + SetTemporary(bb, indexer++, std::move(value)); + } + for (u32 i = 0; i < indexer; ++i) { + SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i)); + } + break; + } + case OpCode::Id::SUST: { + UNIMPLEMENTED_IF(instr.suldst.mode != Tegra::Shader::SurfaceDataMode::P); + UNIMPLEMENTED_IF(instr.suldst.out_of_bounds_store != + Tegra::Shader::OutOfBoundsStore::Ignore); + UNIMPLEMENTED_IF(instr.suldst.component_mask_selector != 0xf); // Ensure we have RGBA + + std::vector<Node> values; + constexpr std::size_t hardcoded_size{4}; + for (std::size_t i = 0; i < hardcoded_size; ++i) { + values.push_back(GetRegister(instr.gpr0.Value() + i)); + } + + const auto type{instr.suldst.image_type}; + auto& image{instr.suldst.is_immediate ? GetImage(instr.image, type) + : GetBindlessImage(instr.gpr39, type)}; + image.MarkWrite(); + + MetaImage meta{image, std::move(values)}; + bb.push_back(Operation(OperationCode::ImageStore, meta, GetCoordinates(type))); + break; + } + case OpCode::Id::SUATOM: { + UNIMPLEMENTED_IF(instr.suatom_d.is_ba != 0); + + const OperationCode operation_code = [instr] { + switch (instr.suatom_d.operation_type) { + case Tegra::Shader::ImageAtomicOperationType::S32: + case Tegra::Shader::ImageAtomicOperationType::U32: + switch (instr.suatom_d.operation) { + case Tegra::Shader::ImageAtomicOperation::Add: + return OperationCode::AtomicImageAdd; + case Tegra::Shader::ImageAtomicOperation::And: + return OperationCode::AtomicImageAnd; + case Tegra::Shader::ImageAtomicOperation::Or: + return OperationCode::AtomicImageOr; + case Tegra::Shader::ImageAtomicOperation::Xor: + return OperationCode::AtomicImageXor; + case Tegra::Shader::ImageAtomicOperation::Exch: + return OperationCode::AtomicImageExchange; + } + default: + break; + } + UNIMPLEMENTED_MSG("Unimplemented operation={} type={}", + static_cast<u64>(instr.suatom_d.operation.Value()), + static_cast<u64>(instr.suatom_d.operation_type.Value())); + return OperationCode::AtomicImageAdd; + }(); + + Node value = GetRegister(instr.gpr0); + + const auto type = instr.suatom_d.image_type; + auto& image = GetImage(instr.image, type); + image.MarkAtomic(); + + MetaImage meta{image, {std::move(value)}}; + SetRegister(bb, instr.gpr0, Operation(operation_code, meta, GetCoordinates(type))); + break; + } + default: + UNIMPLEMENTED_MSG("Unhandled image instruction: {}", opcode->get().GetName()); + } + + return pc; +} + +Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { + const auto offset{static_cast<std::size_t>(image.index.Value())}; + if (const auto image = TryUseExistingImage(offset, type)) { + return *image; + } + + const std::size_t next_index{used_images.size()}; + return used_images.emplace(offset, Image{offset, next_index, type}).first->second; +} + +Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) { + const Node image_register{GetRegister(reg)}; + const auto [base_image, cbuf_index, cbuf_offset]{ + TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))}; + const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)}; + + if (const auto image = TryUseExistingImage(cbuf_key, type)) { + return *image; + } + + const std::size_t next_index{used_images.size()}; + return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type}) + .first->second; +} + +Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type) { + auto it = used_images.find(offset); + if (it == used_images.end()) { + return nullptr; + } + auto& image = it->second; + ASSERT(image.GetType() == type); + + return ℑ +} + +} // namespace VideoCommon::Shader diff --git a/src/video_core/shader/decode/integer_set.cpp b/src/video_core/shader/decode/integer_set.cpp index 46e3d5905..59809bcd8 100644 --- a/src/video_core/shader/decode/integer_set.cpp +++ b/src/video_core/shader/decode/integer_set.cpp @@ -14,7 +14,6 @@ using Tegra::Shader::OpCode; u32 ShaderIR::DecodeIntegerSet(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; - const auto opcode = OpCode::Decode(instr); const Node op_a = GetRegister(instr.gpr8); const Node op_b = [&]() { diff --git a/src/video_core/shader/decode/integer_set_predicate.cpp b/src/video_core/shader/decode/integer_set_predicate.cpp index dd20775d7..25e48fef8 100644 --- a/src/video_core/shader/decode/integer_set_predicate.cpp +++ b/src/video_core/shader/decode/integer_set_predicate.cpp @@ -16,7 +16,6 @@ using Tegra::Shader::Pred; u32 ShaderIR::DecodeIntegerSetPredicate(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; - const auto opcode = OpCode::Decode(instr); const Node op_a = GetRegister(instr.gpr8); diff --git a/src/video_core/shader/decode/memory.cpp b/src/video_core/shader/decode/memory.cpp index 80fc0ccfc..7923d4d69 100644 --- a/src/video_core/shader/decode/memory.cpp +++ b/src/video_core/shader/decode/memory.cpp @@ -35,7 +35,7 @@ u32 GetUniformTypeElementsCount(Tegra::Shader::UniformType uniform_type) { return 1; } } -} // namespace +} // Anonymous namespace u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; @@ -95,10 +95,10 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { const Node op_b = GetConstBufferIndirect(instr.cbuf36.index, instr.cbuf36.GetOffset() + 4, index); - SetTemporal(bb, 0, op_a); - SetTemporal(bb, 1, op_b); - SetRegister(bb, instr.gpr0, GetTemporal(0)); - SetRegister(bb, instr.gpr0.Value() + 1, GetTemporal(1)); + SetTemporary(bb, 0, op_a); + SetTemporary(bb, 1, op_b); + SetRegister(bb, instr.gpr0, GetTemporary(0)); + SetRegister(bb, instr.gpr0.Value() + 1, GetTemporary(1)); break; } default: @@ -106,16 +106,17 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { } break; } - case OpCode::Id::LD_L: { - LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", - static_cast<u64>(instr.ld_l.unknown.Value())); - - const auto GetLmem = [&](s32 offset) { + case OpCode::Id::LD_L: + LOG_DEBUG(HW_GPU, "LD_L cache management mode: {}", static_cast<u64>(instr.ld_l.unknown)); + [[fallthrough]]; + case OpCode::Id::LD_S: { + const auto GetMemory = [&](s32 offset) { ASSERT(offset % 4 == 0); const Node immediate_offset = Immediate(static_cast<s32>(instr.smem_imm) + offset); const Node address = Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8), immediate_offset); - return GetLocalMemory(address); + return opcode->get().GetId() == OpCode::Id::LD_S ? GetSharedMemory(address) + : GetLocalMemory(address); }; switch (instr.ldst_sl.type.Value()) { @@ -135,14 +136,16 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { return 0; } }(); - for (u32 i = 0; i < count; ++i) - SetTemporal(bb, i, GetLmem(i * 4)); - for (u32 i = 0; i < count; ++i) - SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i)); + for (u32 i = 0; i < count; ++i) { + SetTemporary(bb, i, GetMemory(i * 4)); + } + for (u32 i = 0; i < count; ++i) { + SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i)); + } break; } default: - UNIMPLEMENTED_MSG("LD_L Unhandled type: {}", + UNIMPLEMENTED_MSG("{} Unhandled type: {}", opcode->get().GetName(), static_cast<u32>(instr.ldst_sl.type.Value())); } break; @@ -172,10 +175,10 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { Operation(OperationCode::UAdd, NO_PRECISE, real_address_base, it_offset); const Node gmem = MakeNode<GmemNode>(real_address, base_address, descriptor); - SetTemporal(bb, i, gmem); + SetTemporary(bb, i, gmem); } for (u32 i = 0; i < count; ++i) { - SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i)); + SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i)); } break; } @@ -209,27 +212,34 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { break; } - case OpCode::Id::ST_L: { + case OpCode::Id::ST_L: LOG_DEBUG(HW_GPU, "ST_L cache management mode: {}", static_cast<u64>(instr.st_l.cache_management.Value())); - - const auto GetLmemAddr = [&](s32 offset) { + [[fallthrough]]; + case OpCode::Id::ST_S: { + const auto GetAddress = [&](s32 offset) { ASSERT(offset % 4 == 0); const Node immediate = Immediate(static_cast<s32>(instr.smem_imm) + offset); return Operation(OperationCode::IAdd, NO_PRECISE, GetRegister(instr.gpr8), immediate); }; + const auto set_memory = opcode->get().GetId() == OpCode::Id::ST_L + ? &ShaderIR::SetLocalMemory + : &ShaderIR::SetSharedMemory; + switch (instr.ldst_sl.type.Value()) { case Tegra::Shader::StoreType::Bits128: - SetLocalMemory(bb, GetLmemAddr(12), GetRegister(instr.gpr0.Value() + 3)); - SetLocalMemory(bb, GetLmemAddr(8), GetRegister(instr.gpr0.Value() + 2)); + (this->*set_memory)(bb, GetAddress(12), GetRegister(instr.gpr0.Value() + 3)); + (this->*set_memory)(bb, GetAddress(8), GetRegister(instr.gpr0.Value() + 2)); + [[fallthrough]]; case Tegra::Shader::StoreType::Bits64: - SetLocalMemory(bb, GetLmemAddr(4), GetRegister(instr.gpr0.Value() + 1)); + (this->*set_memory)(bb, GetAddress(4), GetRegister(instr.gpr0.Value() + 1)); + [[fallthrough]]; case Tegra::Shader::StoreType::Bits32: - SetLocalMemory(bb, GetLmemAddr(0), GetRegister(instr.gpr0)); + (this->*set_memory)(bb, GetAddress(0), GetRegister(instr.gpr0)); break; default: - UNIMPLEMENTED_MSG("ST_L Unhandled type: {}", + UNIMPLEMENTED_MSG("{} unhandled type: {}", opcode->get().GetName(), static_cast<u32>(instr.ldst_sl.type.Value())); } break; @@ -253,11 +263,11 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { TrackAndGetGlobalMemory(bb, instr, true); // Encode in temporary registers like this: real_base_address, {registers_to_be_written...} - SetTemporal(bb, 0, real_address_base); + SetTemporary(bb, 0, real_address_base); const u32 count = GetUniformTypeElementsCount(type); for (u32 i = 0; i < count; ++i) { - SetTemporal(bb, i + 1, GetRegister(instr.gpr0.Value() + i)); + SetTemporary(bb, i + 1, GetRegister(instr.gpr0.Value() + i)); } for (u32 i = 0; i < count; ++i) { const Node it_offset = Immediate(i * 4); @@ -265,7 +275,7 @@ u32 ShaderIR::DecodeMemory(NodeBlock& bb, u32 pc) { Operation(OperationCode::UAdd, NO_PRECISE, real_address_base, it_offset); const Node gmem = MakeNode<GmemNode>(real_address, base_address, descriptor); - bb.push_back(Operation(OperationCode::Assign, gmem, GetTemporal(i + 1))); + bb.push_back(Operation(OperationCode::Assign, gmem, GetTemporary(i + 1))); } break; } @@ -297,18 +307,13 @@ std::tuple<Node, Node, GlobalMemoryBase> ShaderIR::TrackAndGetGlobalMemory(NodeB const auto addr_register{GetRegister(instr.gmem.gpr)}; const auto immediate_offset{static_cast<u32>(instr.gmem.offset)}; - const Node base_address{ - TrackCbuf(addr_register, global_code, static_cast<s64>(global_code.size()))}; - const auto cbuf = std::get_if<CbufNode>(&*base_address); - ASSERT(cbuf != nullptr); - const auto cbuf_offset_imm = std::get_if<ImmediateNode>(&*cbuf->GetOffset()); - ASSERT(cbuf_offset_imm != nullptr); - const auto cbuf_offset = cbuf_offset_imm->GetValue(); + const auto [base_address, index, offset] = + TrackCbuf(addr_register, global_code, static_cast<s64>(global_code.size())); + ASSERT(base_address != nullptr); - bb.push_back( - Comment(fmt::format("Base address is c[0x{:x}][0x{:x}]", cbuf->GetIndex(), cbuf_offset))); + bb.push_back(Comment(fmt::format("Base address is c[0x{:x}][0x{:x}]", index, offset))); - const GlobalMemoryBase descriptor{cbuf->GetIndex(), cbuf_offset}; + const GlobalMemoryBase descriptor{index, offset}; const auto& [entry, is_new] = used_global_memory.try_emplace(descriptor); auto& usage = entry->second; if (is_write) { diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp index 6fc07f213..d46e0f823 100644 --- a/src/video_core/shader/decode/other.cpp +++ b/src/video_core/shader/decode/other.cpp @@ -22,6 +22,12 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { const auto opcode = OpCode::Decode(instr); switch (opcode->get().GetId()) { + case OpCode::Id::NOP: { + UNIMPLEMENTED_IF(instr.nop.cc != Tegra::Shader::ConditionCode::T); + UNIMPLEMENTED_IF(instr.nop.trigger != 0); + // With the previous preconditions, this instruction is a no-operation. + break; + } case OpCode::Id::EXIT: { const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "EXIT condition code used: {}", @@ -68,6 +74,13 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { case SystemVariable::InvocationInfo: LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete"); return Immediate(0u); + case SystemVariable::Tid: { + Node value = Immediate(0); + value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdX), 0, 9); + value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdY), 16, 9); + value = BitfieldInsert(value, Operation(OperationCode::LocalInvocationIdZ), 26, 5); + return value; + } case SystemVariable::TidX: return Operation(OperationCode::LocalInvocationIdX); case SystemVariable::TidY: @@ -91,11 +104,46 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { break; } case OpCode::Id::BRA: { - UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, - "BRA with constant buffers are not implemented"); + Node branch; + if (instr.bra.constant_buffer == 0) { + const u32 target = pc + instr.bra.GetBranchTarget(); + branch = Operation(OperationCode::Branch, Immediate(target)); + } else { + const u32 target = pc + 1; + const Node op_a = GetConstBuffer(instr.cbuf36.index, instr.cbuf36.GetOffset()); + const Node convert = SignedOperation(OperationCode::IArithmeticShiftRight, true, + PRECISE, op_a, Immediate(3)); + const Node operand = + Operation(OperationCode::IAdd, PRECISE, convert, Immediate(target)); + branch = Operation(OperationCode::BranchIndirect, operand); + } - const u32 target = pc + instr.bra.GetBranchTarget(); - const Node branch = Operation(OperationCode::Branch, Immediate(target)); + const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; + if (cc != Tegra::Shader::ConditionCode::T) { + bb.push_back(Conditional(GetConditionCode(cc), {branch})); + } else { + bb.push_back(branch); + } + break; + } + case OpCode::Id::BRX: { + Node operand; + if (instr.brx.constant_buffer != 0) { + const s32 target = pc + 1; + const Node index = GetRegister(instr.gpr8); + const Node op_a = + GetConstBufferIndirect(instr.cbuf36.index, instr.cbuf36.GetOffset() + 0, index); + const Node convert = SignedOperation(OperationCode::IArithmeticShiftRight, true, + PRECISE, op_a, Immediate(3)); + operand = Operation(OperationCode::IAdd, PRECISE, convert, Immediate(target)); + } else { + const s32 target = pc + instr.brx.GetBranchExtend(); + const Node op_a = GetRegister(instr.gpr8); + const Node convert = SignedOperation(OperationCode::IArithmeticShiftRight, true, + PRECISE, op_a, Immediate(3)); + operand = Operation(OperationCode::IAdd, PRECISE, convert, Immediate(target)); + } + const Node branch = Operation(OperationCode::BranchIndirect, operand); const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; if (cc != Tegra::Shader::ConditionCode::T) { @@ -109,22 +157,28 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, "Constant buffer flow is not supported"); - // The SSY opcode tells the GPU where to re-converge divergent execution paths, it sets the - // target of the jump that the SYNC instruction will make. The SSY opcode has a similar - // structure to the BRA opcode. + if (disable_flow_stack) { + break; + } + + // The SSY opcode tells the GPU where to re-converge divergent execution paths with SYNC. const u32 target = pc + instr.bra.GetBranchTarget(); - bb.push_back(Operation(OperationCode::PushFlowStack, Immediate(target))); + bb.push_back( + Operation(OperationCode::PushFlowStack, MetaStackClass::Ssy, Immediate(target))); break; } case OpCode::Id::PBK: { UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, "Constant buffer PBK is not supported"); - // PBK pushes to a stack the address where BRK will jump to. This shares stack with SSY but - // using SYNC on a PBK address will kill the shader execution. We don't emulate this because - // it's very unlikely a driver will emit such invalid shader. + if (disable_flow_stack) { + break; + } + + // PBK pushes to a stack the address where BRK will jump to. const u32 target = pc + instr.bra.GetBranchTarget(); - bb.push_back(Operation(OperationCode::PushFlowStack, Immediate(target))); + bb.push_back( + Operation(OperationCode::PushFlowStack, MetaStackClass::Pbk, Immediate(target))); break; } case OpCode::Id::SYNC: { @@ -132,17 +186,24 @@ u32 ShaderIR::DecodeOther(NodeBlock& bb, u32 pc) { UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "SYNC condition code used: {}", static_cast<u32>(cc)); + if (disable_flow_stack) { + break; + } + // The SYNC opcode jumps to the address previously set by the SSY opcode - bb.push_back(Operation(OperationCode::PopFlowStack)); + bb.push_back(Operation(OperationCode::PopFlowStack, MetaStackClass::Ssy)); break; } case OpCode::Id::BRK: { const Tegra::Shader::ConditionCode cc = instr.flow_condition_code; UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ConditionCode::T, "BRK condition code used: {}", static_cast<u32>(cc)); + if (disable_flow_stack) { + break; + } // The BRK opcode jumps to the address previously set by the PBK opcode - bb.push_back(Operation(OperationCode::PopFlowStack)); + bb.push_back(Operation(OperationCode::PopFlowStack, MetaStackClass::Pbk)); break; } case OpCode::Id::IPA: { diff --git a/src/video_core/shader/decode/predicate_set_register.cpp b/src/video_core/shader/decode/predicate_set_register.cpp index febbfeb50..84dbc50fe 100644 --- a/src/video_core/shader/decode/predicate_set_register.cpp +++ b/src/video_core/shader/decode/predicate_set_register.cpp @@ -15,7 +15,6 @@ using Tegra::Shader::OpCode; u32 ShaderIR::DecodePredicateSetRegister(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; - const auto opcode = OpCode::Decode(instr); UNIMPLEMENTED_IF_MSG(instr.generates_cc, "Condition codes generation in PSET is not implemented"); diff --git a/src/video_core/shader/decode/shift.cpp b/src/video_core/shader/decode/shift.cpp index 2ac16eeb0..f6ee68a54 100644 --- a/src/video_core/shader/decode/shift.cpp +++ b/src/video_core/shader/decode/shift.cpp @@ -17,8 +17,8 @@ u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) { const Instruction instr = {program_code[pc]}; const auto opcode = OpCode::Decode(instr); - const Node op_a = GetRegister(instr.gpr8); - const Node op_b = [&]() { + Node op_a = GetRegister(instr.gpr8); + Node op_b = [&]() { if (instr.is_b_imm) { return Immediate(instr.alu.GetSignedImm20_20()); } else if (instr.is_b_gpr) { @@ -32,16 +32,23 @@ u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) { case OpCode::Id::SHR_C: case OpCode::Id::SHR_R: case OpCode::Id::SHR_IMM: { - const Node value = SignedOperation(OperationCode::IArithmeticShiftRight, - instr.shift.is_signed, PRECISE, op_a, op_b); + if (instr.shr.wrap) { + op_b = Operation(OperationCode::UBitwiseAnd, std::move(op_b), Immediate(0x1f)); + } else { + op_b = Operation(OperationCode::IMax, std::move(op_b), Immediate(0)); + op_b = Operation(OperationCode::IMin, std::move(op_b), Immediate(31)); + } + + Node value = SignedOperation(OperationCode::IArithmeticShiftRight, instr.shift.is_signed, + std::move(op_a), std::move(op_b)); SetInternalFlagsFromInteger(bb, value, instr.generates_cc); - SetRegister(bb, instr.gpr0, value); + SetRegister(bb, instr.gpr0, std::move(value)); break; } case OpCode::Id::SHL_C: case OpCode::Id::SHL_R: case OpCode::Id::SHL_IMM: { - const Node value = Operation(OperationCode::ILogicalShiftLeft, PRECISE, op_a, op_b); + const Node value = Operation(OperationCode::ILogicalShiftLeft, op_a, op_b); SetInternalFlagsFromInteger(bb, value, instr.generates_cc); SetRegister(bb, instr.gpr0, value); break; diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp index 4a356dbd4..0b934a069 100644 --- a/src/video_core/shader/decode/texture.cpp +++ b/src/video_core/shader/decode/texture.cpp @@ -181,10 +181,10 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { const Node value = Operation(OperationCode::TextureQueryDimensions, meta, GetRegister(instr.gpr8.Value() + (is_bindless ? 1 : 0))); - SetTemporal(bb, indexer++, value); + SetTemporary(bb, indexer++, value); } for (u32 i = 0; i < indexer; ++i) { - SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i)); + SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i)); } break; } @@ -238,13 +238,25 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { auto params = coords; MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element}; const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params)); - SetTemporal(bb, indexer++, value); + SetTemporary(bb, indexer++, value); } for (u32 i = 0; i < indexer; ++i) { - SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i)); + SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i)); } break; } + case OpCode::Id::TLD: { + UNIMPLEMENTED_IF_MSG(instr.tld.aoffi, "AOFFI is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld.ms, "MS is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld.cl, "CL is not implemented"); + + if (instr.tld.nodep_flag) { + LOG_WARNING(HW_GPU, "TLD.NODEP implementation is incomplete"); + } + + WriteTexInstructionFloat(bb, instr, GetTldCode(instr)); + break; + } case OpCode::Id::TLDS: { const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()}; const bool is_array{instr.tlds.IsArrayTexture()}; @@ -257,7 +269,13 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { LOG_WARNING(HW_GPU, "TLDS.NODEP implementation is incomplete"); } - WriteTexsInstructionFloat(bb, instr, GetTldsCode(instr, texture_type, is_array)); + const Node4 components = GetTldsCode(instr, texture_type, is_array); + + if (instr.tlds.fp32_flag) { + WriteTexsInstructionFloat(bb, instr, components); + } else { + WriteTexsInstructionHalfFloat(bb, instr, components); + } break; } default: @@ -290,13 +308,9 @@ const Sampler& ShaderIR::GetSampler(const Tegra::Shader::Sampler& sampler, Textu const Sampler& ShaderIR::GetBindlessSampler(const Tegra::Shader::Register& reg, TextureType type, bool is_array, bool is_shadow) { const Node sampler_register = GetRegister(reg); - const Node base_sampler = + const auto [base_sampler, cbuf_index, cbuf_offset] = TrackCbuf(sampler_register, global_code, static_cast<s64>(global_code.size())); - const auto cbuf = std::get_if<CbufNode>(&*base_sampler); - const auto cbuf_offset_imm = std::get_if<ImmediateNode>(&*cbuf->GetOffset()); - ASSERT(cbuf_offset_imm != nullptr); - const auto cbuf_offset = cbuf_offset_imm->GetValue(); - const auto cbuf_index = cbuf->GetIndex(); + ASSERT(base_sampler != nullptr); const auto cbuf_key = (static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset); // If this sampler has already been used, return the existing mapping. @@ -322,11 +336,11 @@ void ShaderIR::WriteTexInstructionFloat(NodeBlock& bb, Instruction instr, const // Skip disabled components continue; } - SetTemporal(bb, dest_elem++, components[elem]); + SetTemporary(bb, dest_elem++, components[elem]); } // After writing values in temporals, move them to the real registers for (u32 i = 0; i < dest_elem; ++i) { - SetRegister(bb, instr.gpr0.Value() + i, GetTemporal(i)); + SetRegister(bb, instr.gpr0.Value() + i, GetTemporary(i)); } } @@ -339,17 +353,17 @@ void ShaderIR::WriteTexsInstructionFloat(NodeBlock& bb, Instruction instr, for (u32 component = 0; component < 4; ++component) { if (!instr.texs.IsComponentEnabled(component)) continue; - SetTemporal(bb, dest_elem++, components[component]); + SetTemporary(bb, dest_elem++, components[component]); } for (u32 i = 0; i < dest_elem; ++i) { if (i < 2) { // Write the first two swizzle components to gpr0 and gpr0+1 - SetRegister(bb, instr.gpr0.Value() + i % 2, GetTemporal(i)); + SetRegister(bb, instr.gpr0.Value() + i % 2, GetTemporary(i)); } else { ASSERT(instr.texs.HasTwoDestinations()); // Write the rest of the swizzle components to gpr28 and gpr28+1 - SetRegister(bb, instr.gpr28.Value() + i % 2, GetTemporal(i)); + SetRegister(bb, instr.gpr28.Value() + i % 2, GetTemporary(i)); } } } @@ -377,11 +391,11 @@ void ShaderIR::WriteTexsInstructionHalfFloat(NodeBlock& bb, Instruction instr, return; } - SetTemporal(bb, 0, first_value); - SetTemporal(bb, 1, Operation(OperationCode::HPack2, values[2], values[3])); + SetTemporary(bb, 0, first_value); + SetTemporary(bb, 1, Operation(OperationCode::HPack2, values[2], values[3])); - SetRegister(bb, instr.gpr0, GetTemporal(0)); - SetRegister(bb, instr.gpr28, GetTemporal(1)); + SetRegister(bb, instr.gpr0, GetTemporary(0)); + SetRegister(bb, instr.gpr28, GetTemporary(1)); } Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type, @@ -575,6 +589,39 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de return values; } +Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) { + const auto texture_type{instr.tld.texture_type}; + const bool is_array{instr.tld.is_array}; + const bool lod_enabled{instr.tld.GetTextureProcessMode() == TextureProcessMode::LL}; + const std::size_t coord_count{GetCoordCount(texture_type)}; + + u64 gpr8_cursor{instr.gpr8.Value()}; + const Node array_register{is_array ? GetRegister(gpr8_cursor++) : nullptr}; + + std::vector<Node> coords; + coords.reserve(coord_count); + for (std::size_t i = 0; i < coord_count; ++i) { + coords.push_back(GetRegister(gpr8_cursor++)); + } + + u64 gpr20_cursor{instr.gpr20.Value()}; + // const Node bindless_register{is_bindless ? GetRegister(gpr20_cursor++) : nullptr}; + const Node lod{lod_enabled ? GetRegister(gpr20_cursor++) : Immediate(0u)}; + // const Node aoffi_register{is_aoffi ? GetRegister(gpr20_cursor++) : nullptr}; + // const Node multisample{is_multisample ? GetRegister(gpr20_cursor++) : nullptr}; + + const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false); + + Node4 values; + for (u32 element = 0; element < values.size(); ++element) { + auto coords_copy = coords; + MetaTexture meta{sampler, array_register, {}, {}, {}, lod, {}, element}; + values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy)); + } + + return values; +} + Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) { const std::size_t type_coord_count = GetCoordCount(texture_type); const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL; diff --git a/src/video_core/shader/decode/warp.cpp b/src/video_core/shader/decode/warp.cpp new file mode 100644 index 000000000..a8e481b3c --- /dev/null +++ b/src/video_core/shader/decode/warp.cpp @@ -0,0 +1,102 @@ +// 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/common_types.h" +#include "video_core/engines/shader_bytecode.h" +#include "video_core/shader/node_helper.h" +#include "video_core/shader/shader_ir.h" + +namespace VideoCommon::Shader { + +using Tegra::Shader::Instruction; +using Tegra::Shader::OpCode; +using Tegra::Shader::Pred; +using Tegra::Shader::ShuffleOperation; +using Tegra::Shader::VoteOperation; + +namespace { +OperationCode GetOperationCode(VoteOperation vote_op) { + switch (vote_op) { + case VoteOperation::All: + return OperationCode::VoteAll; + case VoteOperation::Any: + return OperationCode::VoteAny; + case VoteOperation::Eq: + return OperationCode::VoteEqual; + default: + UNREACHABLE_MSG("Invalid vote operation={}", static_cast<u64>(vote_op)); + return OperationCode::VoteAll; + } +} +} // Anonymous namespace + +u32 ShaderIR::DecodeWarp(NodeBlock& bb, u32 pc) { + const Instruction instr = {program_code[pc]}; + const auto opcode = OpCode::Decode(instr); + + switch (opcode->get().GetId()) { + case OpCode::Id::VOTE: { + const Node value = GetPredicate(instr.vote.value, instr.vote.negate_value != 0); + const Node active = Operation(OperationCode::BallotThread, value); + const Node vote = Operation(GetOperationCode(instr.vote.operation), value); + SetRegister(bb, instr.gpr0, active); + SetPredicate(bb, instr.vote.dest_pred, vote); + break; + } + case OpCode::Id::SHFL: { + Node mask = instr.shfl.is_mask_imm ? Immediate(static_cast<u32>(instr.shfl.mask_imm)) + : GetRegister(instr.gpr39); + Node width = [&] { + // Convert the obscure SHFL mask back into GL_NV_shader_thread_shuffle's width. This has + // been done reversing Nvidia's math. It won't work on all cases due to SHFL having + // different parameters that don't properly map to GLSL's interface, but it should work + // for cases emitted by Nvidia's compiler. + if (instr.shfl.operation == ShuffleOperation::Up) { + return Operation( + OperationCode::ILogicalShiftRight, + Operation(OperationCode::IAdd, std::move(mask), Immediate(-0x2000)), + Immediate(8)); + } else { + return Operation(OperationCode::ILogicalShiftRight, + Operation(OperationCode::IAdd, Immediate(0x201F), + Operation(OperationCode::INegate, std::move(mask))), + Immediate(8)); + } + }(); + + const auto [operation, in_range] = [instr]() -> std::pair<OperationCode, OperationCode> { + switch (instr.shfl.operation) { + case ShuffleOperation::Idx: + return {OperationCode::ShuffleIndexed, OperationCode::InRangeShuffleIndexed}; + case ShuffleOperation::Up: + return {OperationCode::ShuffleUp, OperationCode::InRangeShuffleUp}; + case ShuffleOperation::Down: + return {OperationCode::ShuffleDown, OperationCode::InRangeShuffleDown}; + case ShuffleOperation::Bfly: + return {OperationCode::ShuffleButterfly, OperationCode::InRangeShuffleButterfly}; + } + UNREACHABLE_MSG("Invalid SHFL operation: {}", + static_cast<u64>(instr.shfl.operation.Value())); + return {}; + }(); + + // Setting the predicate before the register is intentional to avoid overwriting. + Node index = instr.shfl.is_index_imm ? Immediate(static_cast<u32>(instr.shfl.index_imm)) + : GetRegister(instr.gpr20); + SetPredicate(bb, instr.shfl.pred48, Operation(in_range, index, width)); + SetRegister( + bb, instr.gpr0, + Operation(operation, GetRegister(instr.gpr8), std::move(index), std::move(width))); + break; + } + default: + UNIMPLEMENTED_MSG("Unhandled warp instruction: {}", opcode->get().GetName()); + break; + } + + return pc; +} + +} // namespace VideoCommon::Shader diff --git a/src/video_core/shader/decode/xmad.cpp b/src/video_core/shader/decode/xmad.cpp index 93dee77d1..206961909 100644 --- a/src/video_core/shader/decode/xmad.cpp +++ b/src/video_core/shader/decode/xmad.cpp @@ -73,8 +73,8 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) { if (is_psl) { product = Operation(OperationCode::ILogicalShiftLeft, NO_PRECISE, product, Immediate(16)); } - SetTemporal(bb, 0, product); - product = GetTemporal(0); + SetTemporary(bb, 0, product); + product = GetTemporary(0); const Node original_c = op_c; const Tegra::Shader::XmadMode set_mode = mode; // Workaround to clang compile error @@ -98,13 +98,13 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) { } }(); - SetTemporal(bb, 1, op_c); - op_c = GetTemporal(1); + SetTemporary(bb, 1, op_c); + op_c = GetTemporary(1); // TODO(Rodrigo): Use an appropiate sign for this operation Node sum = Operation(OperationCode::IAdd, product, op_c); - SetTemporal(bb, 2, sum); - sum = GetTemporal(2); + SetTemporary(bb, 2, sum); + sum = GetTemporary(2); if (is_merge) { const Node a = BitfieldExtract(sum, 0, 16); const Node b = diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index c002f90f9..338bab17c 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h @@ -7,6 +7,7 @@ #include <array> #include <cstddef> #include <memory> +#include <optional> #include <string> #include <tuple> #include <utility> @@ -30,6 +31,8 @@ enum class OperationCode { FNegate, /// (MetaArithmetic, float a) -> float FAbsolute, /// (MetaArithmetic, float a) -> float FClamp, /// (MetaArithmetic, float value, float min, float max) -> float + FCastHalf0, /// (MetaArithmetic, f16vec2 a) -> float + FCastHalf1, /// (MetaArithmetic, f16vec2 a) -> float FMin, /// (MetaArithmetic, float a, float b) -> float FMax, /// (MetaArithmetic, float a, float b) -> float FCos, /// (MetaArithmetic, float a) -> float @@ -83,17 +86,18 @@ enum class OperationCode { UBitfieldExtract, /// (MetaArithmetic, uint value, int offset, int offset) -> uint UBitCount, /// (MetaArithmetic, uint) -> uint - HAdd, /// (MetaArithmetic, f16vec2 a, f16vec2 b) -> f16vec2 - HMul, /// (MetaArithmetic, f16vec2 a, f16vec2 b) -> f16vec2 - HFma, /// (MetaArithmetic, f16vec2 a, f16vec2 b, f16vec2 c) -> f16vec2 - HAbsolute, /// (f16vec2 a) -> f16vec2 - HNegate, /// (f16vec2 a, bool first, bool second) -> f16vec2 - HClamp, /// (f16vec2 src, float min, float max) -> f16vec2 - HUnpack, /// (Tegra::Shader::HalfType, T value) -> f16vec2 - HMergeF32, /// (f16vec2 src) -> float - HMergeH0, /// (f16vec2 dest, f16vec2 src) -> f16vec2 - HMergeH1, /// (f16vec2 dest, f16vec2 src) -> f16vec2 - HPack2, /// (float a, float b) -> f16vec2 + HAdd, /// (MetaArithmetic, f16vec2 a, f16vec2 b) -> f16vec2 + HMul, /// (MetaArithmetic, f16vec2 a, f16vec2 b) -> f16vec2 + HFma, /// (MetaArithmetic, f16vec2 a, f16vec2 b, f16vec2 c) -> f16vec2 + HAbsolute, /// (f16vec2 a) -> f16vec2 + HNegate, /// (f16vec2 a, bool first, bool second) -> f16vec2 + HClamp, /// (f16vec2 src, float min, float max) -> f16vec2 + HCastFloat, /// (MetaArithmetic, float a) -> f16vec2 + HUnpack, /// (Tegra::Shader::HalfType, T value) -> f16vec2 + HMergeF32, /// (f16vec2 src) -> float + HMergeH0, /// (f16vec2 dest, f16vec2 src) -> f16vec2 + HMergeH1, /// (f16vec2 dest, f16vec2 src) -> f16vec2 + HPack2, /// (float a, float b) -> f16vec2 LogicalAssign, /// (bool& dst, bool src) -> void LogicalAnd, /// (bool a, bool b) -> bool @@ -101,8 +105,7 @@ enum class OperationCode { LogicalXor, /// (bool a, bool b) -> bool LogicalNegate, /// (bool a) -> bool LogicalPick2, /// (bool2 pair, uint index) -> bool - LogicalAll2, /// (bool2 a) -> bool - LogicalAny2, /// (bool2 a) -> bool + LogicalAnd2, /// (bool2 a) -> bool LogicalFLessThan, /// (float a, float b) -> bool LogicalFEqual, /// (float a, float b) -> bool @@ -146,11 +149,21 @@ enum class OperationCode { TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4 TexelFetch, /// (MetaTexture, int[N], int) -> float4 - Branch, /// (uint branch_target) -> void - PushFlowStack, /// (uint branch_target) -> void - PopFlowStack, /// () -> void - Exit, /// () -> void - Discard, /// () -> void + ImageLoad, /// (MetaImage, int[N] coords) -> void + ImageStore, /// (MetaImage, int[N] coords) -> void + + AtomicImageAdd, /// (MetaImage, int[N] coords) -> void + AtomicImageAnd, /// (MetaImage, int[N] coords) -> void + AtomicImageOr, /// (MetaImage, int[N] coords) -> void + AtomicImageXor, /// (MetaImage, int[N] coords) -> void + AtomicImageExchange, /// (MetaImage, int[N] coords) -> void + + Branch, /// (uint branch_target) -> void + BranchIndirect, /// (uint branch_target) -> void + PushFlowStack, /// (uint branch_target) -> void + PopFlowStack, /// () -> void + Exit, /// () -> void + Discard, /// () -> void EmitVertex, /// () -> void EndPrimitive, /// () -> void @@ -163,6 +176,21 @@ enum class OperationCode { WorkGroupIdY, /// () -> uint WorkGroupIdZ, /// () -> uint + BallotThread, /// (bool) -> uint + VoteAll, /// (bool) -> bool + VoteAny, /// (bool) -> bool + VoteEqual, /// (bool) -> bool + + ShuffleIndexed, /// (uint value, uint index, uint width) -> uint + ShuffleUp, /// (uint value, uint index, uint width) -> uint + ShuffleDown, /// (uint value, uint index, uint width) -> uint + ShuffleButterfly, /// (uint value, uint index, uint width) -> uint + + InRangeShuffleIndexed, /// (uint index, uint width) -> bool + InRangeShuffleUp, /// (uint index, uint width) -> bool + InRangeShuffleDown, /// (uint index, uint width) -> bool + InRangeShuffleButterfly, /// (uint index, uint width) -> bool + Amount, }; @@ -174,6 +202,11 @@ enum class InternalFlag { Amount = 4, }; +enum class MetaStackClass { + Ssy, + Pbk, +}; + class OperationNode; class ConditionalNode; class GprNode; @@ -183,12 +216,13 @@ class PredicateNode; class AbufNode; class CbufNode; class LmemNode; +class SmemNode; class GmemNode; class CommentNode; using NodeData = std::variant<OperationNode, ConditionalNode, GprNode, ImmediateNode, InternalFlagNode, - PredicateNode, AbufNode, CbufNode, LmemNode, GmemNode, CommentNode>; + PredicateNode, AbufNode, CbufNode, LmemNode, SmemNode, GmemNode, CommentNode>; using Node = std::shared_ptr<NodeData>; using Node4 = std::array<Node, 4>; using NodeBlock = std::vector<Node>; @@ -258,6 +292,82 @@ private: bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not. }; +class Image final { +public: + constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type) + : offset{offset}, index{index}, type{type}, is_bindless{false} {} + + constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, + Tegra::Shader::ImageType type) + : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, + is_bindless{true} {} + + constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, + bool is_bindless, bool is_written, bool is_read, bool is_atomic) + : offset{offset}, index{index}, type{type}, is_bindless{is_bindless}, + is_written{is_written}, is_read{is_read}, is_atomic{is_atomic} {} + + void MarkWrite() { + is_written = true; + } + + void MarkRead() { + is_read = true; + } + + void MarkAtomic() { + MarkWrite(); + MarkRead(); + is_atomic = true; + } + + constexpr std::size_t GetOffset() const { + return offset; + } + + constexpr std::size_t GetIndex() const { + return index; + } + + constexpr Tegra::Shader::ImageType GetType() const { + return type; + } + + constexpr bool IsBindless() const { + return is_bindless; + } + + constexpr bool IsWritten() const { + return is_written; + } + + constexpr bool IsRead() const { + return is_read; + } + + constexpr bool IsAtomic() const { + return is_atomic; + } + + constexpr std::pair<u32, u32> GetBindlessCBuf() const { + return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)}; + } + + constexpr bool operator<(const Image& rhs) const { + return std::tie(offset, index, type, is_bindless) < + std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless); + } + +private: + u64 offset{}; + std::size_t index{}; + Tegra::Shader::ImageType type{}; + bool is_bindless{}; + bool is_written{}; + bool is_read{}; + bool is_atomic{}; +}; + struct GlobalMemoryBase { u32 cbuf_index{}; u32 cbuf_offset{}; @@ -284,8 +394,15 @@ struct MetaTexture { u32 element{}; }; +struct MetaImage { + const Image& image; + std::vector<Node> values; + u32 element{}; +}; + /// Parameters that modify an operation but are not part of any particular operand -using Meta = std::variant<MetaArithmetic, MetaTexture, Tegra::Shader::HalfType>; +using Meta = + std::variant<MetaArithmetic, MetaTexture, MetaImage, MetaStackClass, Tegra::Shader::HalfType>; /// Holds any kind of operation that can be done in the IR class OperationNode final { @@ -473,6 +590,19 @@ private: Node address; }; +/// Shared memory node +class SmemNode final { +public: + explicit SmemNode(Node address) : address{std::move(address)} {} + + const Node& GetAddress() const { + return address; + } + +private: + Node address; +}; + /// Global memory node class GmemNode final { public: diff --git a/src/video_core/shader/node_helper.cpp b/src/video_core/shader/node_helper.cpp index 6fccbbba3..b3dcd291c 100644 --- a/src/video_core/shader/node_helper.cpp +++ b/src/video_core/shader/node_helper.cpp @@ -12,7 +12,7 @@ namespace VideoCommon::Shader { Node Conditional(Node condition, std::vector<Node> code) { - return MakeNode<ConditionalNode>(condition, std::move(code)); + return MakeNode<ConditionalNode>(std::move(condition), std::move(code)); } Node Comment(std::string text) { diff --git a/src/video_core/shader/shader_ir.cpp b/src/video_core/shader/shader_ir.cpp index 11b545cca..2c357f310 100644 --- a/src/video_core/shader/shader_ir.cpp +++ b/src/video_core/shader/shader_ir.cpp @@ -22,8 +22,8 @@ using Tegra::Shader::PredCondition; using Tegra::Shader::PredOperation; using Tegra::Shader::Register; -ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset) - : program_code{program_code}, main_offset{main_offset} { +ShaderIR::ShaderIR(const ProgramCode& program_code, u32 main_offset, const std::size_t size) + : program_code{program_code}, main_offset{main_offset}, program_size{size} { Decode(); } @@ -61,8 +61,17 @@ Node ShaderIR::GetConstBufferIndirect(u64 index_, u64 offset_, Node node) { const auto [entry, is_new] = used_cbufs.try_emplace(index); entry->second.MarkAsUsedIndirect(); - const Node final_offset = Operation(OperationCode::UAdd, NO_PRECISE, node, Immediate(offset)); - return MakeNode<CbufNode>(index, final_offset); + Node final_offset = [&] { + // Attempt to inline constant buffer without a variable offset. This is done to allow + // tracking LDC calls. + if (const auto gpr = std::get_if<GprNode>(&*node)) { + if (gpr->GetIndex() == Register::ZeroIndex) { + return Immediate(offset); + } + } + return Operation(OperationCode::UAdd, NO_PRECISE, std::move(node), Immediate(offset)); + }(); + return MakeNode<CbufNode>(index, std::move(final_offset)); } Node ShaderIR::GetPredicate(u64 pred_, bool negated) { @@ -80,7 +89,7 @@ Node ShaderIR::GetPredicate(bool immediate) { Node ShaderIR::GetInputAttribute(Attribute::Index index, u64 element, Node buffer) { used_input_attributes.emplace(index); - return MakeNode<AbufNode>(index, static_cast<u32>(element), buffer); + return MakeNode<AbufNode>(index, static_cast<u32>(element), std::move(buffer)); } Node ShaderIR::GetPhysicalInputAttribute(Tegra::Shader::Register physical_address, Node buffer) { @@ -89,6 +98,34 @@ Node ShaderIR::GetPhysicalInputAttribute(Tegra::Shader::Register physical_addres } Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buffer) { + if (index == Attribute::Index::LayerViewportPointSize) { + switch (element) { + case 0: + UNIMPLEMENTED(); + break; + case 1: + uses_layer = true; + break; + case 2: + uses_viewport_index = true; + break; + case 3: + uses_point_size = true; + break; + } + } + if (index == Attribute::Index::TessCoordInstanceIDVertexID) { + switch (element) { + case 2: + uses_instance_id = true; + break; + case 3: + uses_vertex_id = true; + break; + default: + break; + } + } if (index == Attribute::Index::ClipDistances0123 || index == Attribute::Index::ClipDistances4567) { const auto clip_index = @@ -97,7 +134,7 @@ Node ShaderIR::GetOutputAttribute(Attribute::Index index, u64 element, Node buff } used_output_attributes.insert(index); - return MakeNode<AbufNode>(index, static_cast<u32>(element), buffer); + return MakeNode<AbufNode>(index, static_cast<u32>(element), std::move(buffer)); } Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) { @@ -109,19 +146,23 @@ Node ShaderIR::GetInternalFlag(InternalFlag flag, bool negated) { } Node ShaderIR::GetLocalMemory(Node address) { - return MakeNode<LmemNode>(address); + return MakeNode<LmemNode>(std::move(address)); +} + +Node ShaderIR::GetSharedMemory(Node address) { + return MakeNode<SmemNode>(std::move(address)); } -Node ShaderIR::GetTemporal(u32 id) { +Node ShaderIR::GetTemporary(u32 id) { return GetRegister(Register::ZeroIndex + 1 + id); } Node ShaderIR::GetOperandAbsNegFloat(Node value, bool absolute, bool negate) { if (absolute) { - value = Operation(OperationCode::FAbsolute, NO_PRECISE, value); + value = Operation(OperationCode::FAbsolute, NO_PRECISE, std::move(value)); } if (negate) { - value = Operation(OperationCode::FNegate, NO_PRECISE, value); + value = Operation(OperationCode::FNegate, NO_PRECISE, std::move(value)); } return value; } @@ -130,24 +171,26 @@ Node ShaderIR::GetSaturatedFloat(Node value, bool saturate) { if (!saturate) { return value; } - const Node positive_zero = Immediate(std::copysignf(0, 1)); - const Node positive_one = Immediate(1.0f); - return Operation(OperationCode::FClamp, NO_PRECISE, value, positive_zero, positive_one); + + Node positive_zero = Immediate(std::copysignf(0, 1)); + Node positive_one = Immediate(1.0f); + return Operation(OperationCode::FClamp, NO_PRECISE, std::move(value), std::move(positive_zero), + std::move(positive_one)); } -Node ShaderIR::ConvertIntegerSize(Node value, Tegra::Shader::Register::Size size, bool is_signed) { +Node ShaderIR::ConvertIntegerSize(Node value, Register::Size size, bool is_signed) { switch (size) { case Register::Size::Byte: - value = SignedOperation(OperationCode::ILogicalShiftLeft, is_signed, NO_PRECISE, value, - Immediate(24)); - value = SignedOperation(OperationCode::IArithmeticShiftRight, is_signed, NO_PRECISE, value, - Immediate(24)); + value = SignedOperation(OperationCode::ILogicalShiftLeft, is_signed, NO_PRECISE, + std::move(value), Immediate(24)); + value = SignedOperation(OperationCode::IArithmeticShiftRight, is_signed, NO_PRECISE, + std::move(value), Immediate(24)); return value; case Register::Size::Short: - value = SignedOperation(OperationCode::ILogicalShiftLeft, is_signed, NO_PRECISE, value, - Immediate(16)); - value = SignedOperation(OperationCode::IArithmeticShiftRight, is_signed, NO_PRECISE, value, - Immediate(16)); + value = SignedOperation(OperationCode::ILogicalShiftLeft, is_signed, NO_PRECISE, + std::move(value), Immediate(16)); + value = SignedOperation(OperationCode::IArithmeticShiftRight, is_signed, NO_PRECISE, + std::move(value), Immediate(16)); case Register::Size::Word: // Default - do nothing return value; @@ -163,27 +206,29 @@ Node ShaderIR::GetOperandAbsNegInteger(Node value, bool absolute, bool negate, b return value; } if (absolute) { - value = Operation(OperationCode::IAbsolute, NO_PRECISE, value); + value = Operation(OperationCode::IAbsolute, NO_PRECISE, std::move(value)); } if (negate) { - value = Operation(OperationCode::INegate, NO_PRECISE, value); + value = Operation(OperationCode::INegate, NO_PRECISE, std::move(value)); } return value; } Node ShaderIR::UnpackHalfImmediate(Instruction instr, bool has_negation) { - const Node value = Immediate(instr.half_imm.PackImmediates()); + Node value = Immediate(instr.half_imm.PackImmediates()); if (!has_negation) { return value; } - const Node first_negate = GetPredicate(instr.half_imm.first_negate != 0); - const Node second_negate = GetPredicate(instr.half_imm.second_negate != 0); - return Operation(OperationCode::HNegate, NO_PRECISE, value, first_negate, second_negate); + Node first_negate = GetPredicate(instr.half_imm.first_negate != 0); + Node second_negate = GetPredicate(instr.half_imm.second_negate != 0); + + return Operation(OperationCode::HNegate, NO_PRECISE, std::move(value), std::move(first_negate), + std::move(second_negate)); } Node ShaderIR::UnpackHalfFloat(Node value, Tegra::Shader::HalfType type) { - return Operation(OperationCode::HUnpack, type, value); + return Operation(OperationCode::HUnpack, type, std::move(value)); } Node ShaderIR::HalfMerge(Node dest, Node src, Tegra::Shader::HalfMerge merge) { @@ -191,11 +236,11 @@ Node ShaderIR::HalfMerge(Node dest, Node src, Tegra::Shader::HalfMerge merge) { case Tegra::Shader::HalfMerge::H0_H1: return src; case Tegra::Shader::HalfMerge::F32: - return Operation(OperationCode::HMergeF32, src); + return Operation(OperationCode::HMergeF32, std::move(src)); case Tegra::Shader::HalfMerge::Mrg_H0: - return Operation(OperationCode::HMergeH0, dest, src); + return Operation(OperationCode::HMergeH0, std::move(dest), std::move(src)); case Tegra::Shader::HalfMerge::Mrg_H1: - return Operation(OperationCode::HMergeH1, dest, src); + return Operation(OperationCode::HMergeH1, std::move(dest), std::move(src)); } UNREACHABLE(); return src; @@ -203,10 +248,10 @@ Node ShaderIR::HalfMerge(Node dest, Node src, Tegra::Shader::HalfMerge merge) { Node ShaderIR::GetOperandAbsNegHalf(Node value, bool absolute, bool negate) { if (absolute) { - value = Operation(OperationCode::HAbsolute, NO_PRECISE, value); + value = Operation(OperationCode::HAbsolute, NO_PRECISE, std::move(value)); } if (negate) { - value = Operation(OperationCode::HNegate, NO_PRECISE, value, GetPredicate(true), + value = Operation(OperationCode::HNegate, NO_PRECISE, std::move(value), GetPredicate(true), GetPredicate(true)); } return value; @@ -216,9 +261,11 @@ Node ShaderIR::GetSaturatedHalfFloat(Node value, bool saturate) { if (!saturate) { return value; } - const Node positive_zero = Immediate(std::copysignf(0, 1)); - const Node positive_one = Immediate(1.0f); - return Operation(OperationCode::HClamp, NO_PRECISE, value, positive_zero, positive_one); + + Node positive_zero = Immediate(std::copysignf(0, 1)); + Node positive_one = Immediate(1.0f); + return Operation(OperationCode::HClamp, NO_PRECISE, std::move(value), std::move(positive_zero), + std::move(positive_one)); } Node ShaderIR::GetPredicateComparisonFloat(PredCondition condition, Node op_a, Node op_b) { @@ -246,7 +293,6 @@ Node ShaderIR::GetPredicateComparisonFloat(PredCondition condition, Node op_a, N condition == PredCondition::LessEqualWithNan || condition == PredCondition::GreaterThanWithNan || condition == PredCondition::GreaterEqualWithNan) { - predicate = Operation(OperationCode::LogicalOr, predicate, Operation(OperationCode::LogicalFIsNan, op_a)); predicate = Operation(OperationCode::LogicalOr, predicate, @@ -275,7 +321,8 @@ Node ShaderIR::GetPredicateComparisonInteger(PredCondition condition, bool is_si UNIMPLEMENTED_IF_MSG(comparison == PredicateComparisonTable.end(), "Unknown predicate comparison operation"); - Node predicate = SignedOperation(comparison->second, is_signed, NO_PRECISE, op_a, op_b); + Node predicate = SignedOperation(comparison->second, is_signed, NO_PRECISE, std::move(op_a), + std::move(op_b)); UNIMPLEMENTED_IF_MSG(condition == PredCondition::LessThanWithNan || condition == PredCondition::NotEqualWithNan || @@ -305,9 +352,7 @@ Node ShaderIR::GetPredicateComparisonHalf(Tegra::Shader::PredCondition condition UNIMPLEMENTED_IF_MSG(comparison == PredicateComparisonTable.end(), "Unknown predicate comparison operation"); - const Node predicate = Operation(comparison->second, NO_PRECISE, op_a, op_b); - - return predicate; + return Operation(comparison->second, NO_PRECISE, std::move(op_a), std::move(op_b)); } OperationCode ShaderIR::GetPredicateCombiner(PredOperation operation) { @@ -333,31 +378,37 @@ Node ShaderIR::GetConditionCode(Tegra::Shader::ConditionCode cc) { } void ShaderIR::SetRegister(NodeBlock& bb, Register dest, Node src) { - bb.push_back(Operation(OperationCode::Assign, GetRegister(dest), src)); + bb.push_back(Operation(OperationCode::Assign, GetRegister(dest), std::move(src))); } void ShaderIR::SetPredicate(NodeBlock& bb, u64 dest, Node src) { - bb.push_back(Operation(OperationCode::LogicalAssign, GetPredicate(dest), src)); + bb.push_back(Operation(OperationCode::LogicalAssign, GetPredicate(dest), std::move(src))); } void ShaderIR::SetInternalFlag(NodeBlock& bb, InternalFlag flag, Node value) { - bb.push_back(Operation(OperationCode::LogicalAssign, GetInternalFlag(flag), value)); + bb.push_back(Operation(OperationCode::LogicalAssign, GetInternalFlag(flag), std::move(value))); } void ShaderIR::SetLocalMemory(NodeBlock& bb, Node address, Node value) { - bb.push_back(Operation(OperationCode::Assign, GetLocalMemory(address), value)); + bb.push_back( + Operation(OperationCode::Assign, GetLocalMemory(std::move(address)), std::move(value))); } -void ShaderIR::SetTemporal(NodeBlock& bb, u32 id, Node value) { - SetRegister(bb, Register::ZeroIndex + 1 + id, value); +void ShaderIR::SetSharedMemory(NodeBlock& bb, Node address, Node value) { + bb.push_back( + Operation(OperationCode::Assign, GetSharedMemory(std::move(address)), std::move(value))); +} + +void ShaderIR::SetTemporary(NodeBlock& bb, u32 id, Node value) { + SetRegister(bb, Register::ZeroIndex + 1 + id, std::move(value)); } void ShaderIR::SetInternalFlagsFromFloat(NodeBlock& bb, Node value, bool sets_cc) { if (!sets_cc) { return; } - const Node zerop = Operation(OperationCode::LogicalFEqual, value, Immediate(0.0f)); - SetInternalFlag(bb, InternalFlag::Zero, zerop); + Node zerop = Operation(OperationCode::LogicalFEqual, std::move(value), Immediate(0.0f)); + SetInternalFlag(bb, InternalFlag::Zero, std::move(zerop)); LOG_WARNING(HW_GPU, "Condition codes implementation is incomplete"); } @@ -365,13 +416,18 @@ void ShaderIR::SetInternalFlagsFromInteger(NodeBlock& bb, Node value, bool sets_ if (!sets_cc) { return; } - const Node zerop = Operation(OperationCode::LogicalIEqual, value, Immediate(0)); - SetInternalFlag(bb, InternalFlag::Zero, zerop); + Node zerop = Operation(OperationCode::LogicalIEqual, std::move(value), Immediate(0)); + SetInternalFlag(bb, InternalFlag::Zero, std::move(zerop)); LOG_WARNING(HW_GPU, "Condition codes implementation is incomplete"); } Node ShaderIR::BitfieldExtract(Node value, u32 offset, u32 bits) { - return Operation(OperationCode::UBitfieldExtract, NO_PRECISE, value, Immediate(offset), + return Operation(OperationCode::UBitfieldExtract, NO_PRECISE, std::move(value), + Immediate(offset), Immediate(bits)); +} + +Node ShaderIR::BitfieldInsert(Node base, Node insert, u32 offset, u32 bits) { + return Operation(OperationCode::UBitfieldInsert, NO_PRECISE, base, insert, Immediate(offset), Immediate(bits)); } diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index edcf2288e..6f666ee30 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -5,13 +5,10 @@ #pragma once #include <array> -#include <cstring> #include <map> #include <optional> #include <set> -#include <string> #include <tuple> -#include <variant> #include <vector> #include "common/common_types.h" @@ -22,18 +19,12 @@ namespace VideoCommon::Shader { +struct ShaderBlock; + using ProgramCode = std::vector<u64>; constexpr u32 MAX_PROGRAM_LENGTH = 0x1000; -/// Describes the behaviour of code path of a given entry point and a return point. -enum class ExitMethod { - Undetermined, ///< Internal value. Only occur when analyzing JMP loop. - AlwaysReturn, ///< All code paths reach the return point. - Conditional, ///< Code path reaches the return point or an END instruction conditionally. - AlwaysEnd, ///< All code paths reach a END instruction. -}; - class ConstBuffer { public: explicit ConstBuffer(u32 max_offset, bool is_indirect) @@ -73,7 +64,7 @@ struct GlobalMemoryUsage { class ShaderIR final { public: - explicit ShaderIR(const ProgramCode& program_code, u32 main_offset); + explicit ShaderIR(const ProgramCode& program_code, u32 main_offset, std::size_t size); ~ShaderIR(); const std::map<u32, NodeBlock>& GetBasicBlocks() const { @@ -104,6 +95,10 @@ public: return used_samplers; } + const std::map<u64, Image>& GetImages() const { + return used_images; + } + const std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances>& GetClipDistances() const { return used_clip_distances; @@ -117,6 +112,26 @@ public: return static_cast<std::size_t>(coverage_end * sizeof(u64)); } + bool UsesLayer() const { + return uses_layer; + } + + bool UsesViewportIndex() const { + return uses_viewport_index; + } + + bool UsesPointSize() const { + return uses_point_size; + } + + bool UsesInstanceId() const { + return uses_instance_id; + } + + bool UsesVertexId() const { + return uses_vertex_id; + } + bool HasPhysicalAttributes() const { return uses_physical_attributes; } @@ -125,12 +140,20 @@ public: return header; } + bool IsFlowStackDisabled() const { + return disable_flow_stack; + } + + u32 ConvertAddressToNvidiaSpace(const u32 address) const { + return (address - main_offset) * sizeof(Tegra::Shader::Instruction); + } + private: void Decode(); - ExitMethod Scan(u32 begin, u32 end, std::set<u32>& labels); - NodeBlock DecodeRange(u32 begin, u32 end); + void DecodeRangeInner(NodeBlock& bb, u32 begin, u32 end); + void InsertControlFlow(NodeBlock& bb, const ShaderBlock& block); /** * Decodes a single instruction from Tegra to IR. @@ -152,8 +175,10 @@ private: u32 DecodeFfma(NodeBlock& bb, u32 pc); u32 DecodeHfma2(NodeBlock& bb, u32 pc); u32 DecodeConversion(NodeBlock& bb, u32 pc); + u32 DecodeWarp(NodeBlock& bb, u32 pc); u32 DecodeMemory(NodeBlock& bb, u32 pc); u32 DecodeTexture(NodeBlock& bb, u32 pc); + u32 DecodeImage(NodeBlock& bb, u32 pc); u32 DecodeFloatSetPredicate(NodeBlock& bb, u32 pc); u32 DecodeIntegerSetPredicate(NodeBlock& bb, u32 pc); u32 DecodeHalfSetPredicate(NodeBlock& bb, u32 pc); @@ -191,8 +216,10 @@ private: Node GetInternalFlag(InternalFlag flag, bool negated = false); /// Generates a node representing a local memory address Node GetLocalMemory(Node address); - /// Generates a temporal, internally it uses a post-RZ register - Node GetTemporal(u32 id); + /// Generates a node representing a shared memory address + Node GetSharedMemory(Node address); + /// Generates a temporary, internally it uses a post-RZ register + Node GetTemporary(u32 id); /// Sets a register. src value must be a number-evaluated node. void SetRegister(NodeBlock& bb, Tegra::Shader::Register dest, Node src); @@ -200,10 +227,12 @@ private: void SetPredicate(NodeBlock& bb, u64 dest, Node src); /// Sets an internal flag. src value must be a bool-evaluated node void SetInternalFlag(NodeBlock& bb, InternalFlag flag, Node value); - /// Sets a local memory address. address and value must be a number-evaluated node + /// Sets a local memory address with a value. void SetLocalMemory(NodeBlock& bb, Node address, Node value); - /// Sets a temporal. Internally it uses a post-RZ register - void SetTemporal(NodeBlock& bb, u32 id, Node value); + /// Sets a shared memory address with a value. + void SetSharedMemory(NodeBlock& bb, Node address, Node value); + /// Sets a temporary. Internally it uses a post-RZ register + void SetTemporary(NodeBlock& bb, u32 id, Node value); /// Sets internal flags from a float void SetInternalFlagsFromFloat(NodeBlock& bb, Node value, bool sets_cc = true); @@ -254,9 +283,21 @@ private: Tegra::Shader::TextureType type, bool is_array, bool is_shadow); + /// Accesses an image. + Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); + + /// Access a bindless image sampler. + Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); + + /// Tries to access an existing image, updating it's state as needed + Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type); + /// Extracts a sequence of bits from a node Node BitfieldExtract(Node value, u32 offset, u32 bits); + /// Inserts a sequence of bits from a node + Node BitfieldInsert(Node base, Node insert, u32 offset, u32 bits); + void WriteTexInstructionFloat(NodeBlock& bb, Tegra::Shader::Instruction instr, const Node4& components); @@ -277,6 +318,8 @@ private: Node4 GetTld4Code(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, bool depth_compare, bool is_array, bool is_aoffi); + Node4 GetTldCode(Tegra::Shader::Instruction instr); + Node4 GetTldsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, bool is_array); @@ -301,7 +344,7 @@ private: void WriteLop3Instruction(NodeBlock& bb, Tegra::Shader::Register dest, Node op_a, Node op_b, Node op_c, Node imm_lut, bool sets_cc); - Node TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) const; + std::tuple<Node, u32, u32> TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) const; std::optional<u32> TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) const; @@ -313,10 +356,11 @@ private: const ProgramCode& program_code; const u32 main_offset; + const std::size_t program_size; + bool disable_flow_stack{}; u32 coverage_begin{}; u32 coverage_end{}; - std::map<std::pair<u32, u32>, ExitMethod> exit_method_map; std::map<u32, NodeBlock> basic_blocks; NodeBlock global_code; @@ -327,9 +371,15 @@ private: std::set<Tegra::Shader::Attribute::Index> used_output_attributes; std::map<u32, ConstBuffer> used_cbufs; std::set<Sampler> used_samplers; + std::map<u64, Image> used_images; std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{}; std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory; + bool uses_layer{}; + bool uses_viewport_index{}; + bool uses_point_size{}; bool uses_physical_attributes{}; // Shader uses AL2P or physical attribute read/writes + bool uses_instance_id{}; + bool uses_vertex_id{}; Tegra::Shader::Header header; }; diff --git a/src/video_core/shader/track.cpp b/src/video_core/shader/track.cpp index fc957d980..55f5949e4 100644 --- a/src/video_core/shader/track.cpp +++ b/src/video_core/shader/track.cpp @@ -15,56 +15,63 @@ namespace { std::pair<Node, s64> FindOperation(const NodeBlock& code, s64 cursor, OperationCode operation_code) { for (; cursor >= 0; --cursor) { - const Node node = code.at(cursor); + Node node = code.at(cursor); + if (const auto operation = std::get_if<OperationNode>(&*node)) { if (operation->GetCode() == operation_code) { - return {node, cursor}; + return {std::move(node), cursor}; } } + if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { const auto& conditional_code = conditional->GetCode(); - const auto [found, internal_cursor] = FindOperation( + auto [found, internal_cursor] = FindOperation( conditional_code, static_cast<s64>(conditional_code.size() - 1), operation_code); if (found) { - return {found, cursor}; + return {std::move(found), cursor}; } } } return {}; } -} // namespace +} // Anonymous namespace -Node ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, s64 cursor) const { +std::tuple<Node, u32, u32> ShaderIR::TrackCbuf(Node tracked, const NodeBlock& code, + s64 cursor) const { if (const auto cbuf = std::get_if<CbufNode>(&*tracked)) { - // Cbuf found, but it has to be immediate - return std::holds_alternative<ImmediateNode>(*cbuf->GetOffset()) ? tracked : nullptr; + // Constant buffer found, test if it's an immediate + const auto offset = cbuf->GetOffset(); + if (const auto immediate = std::get_if<ImmediateNode>(&*offset)) { + return {tracked, cbuf->GetIndex(), immediate->GetValue()}; + } + return {}; } if (const auto gpr = std::get_if<GprNode>(&*tracked)) { if (gpr->GetIndex() == Tegra::Shader::Register::ZeroIndex) { - return nullptr; + return {}; } // Reduce the cursor in one to avoid infinite loops when the instruction sets the same // register that it uses as operand const auto [source, new_cursor] = TrackRegister(gpr, code, cursor - 1); if (!source) { - return nullptr; + return {}; } return TrackCbuf(source, code, new_cursor); } if (const auto operation = std::get_if<OperationNode>(&*tracked)) { - for (std::size_t i = 0; i < operation->GetOperandsCount(); ++i) { - if (const auto found = TrackCbuf((*operation)[i], code, cursor)) { - // Cbuf found in operand + for (std::size_t i = operation->GetOperandsCount(); i > 0; --i) { + if (auto found = TrackCbuf((*operation)[i - 1], code, cursor); std::get<0>(found)) { + // Cbuf found in operand. return found; } } - return nullptr; + return {}; } if (const auto conditional = std::get_if<ConditionalNode>(&*tracked)) { const auto& conditional_code = conditional->GetCode(); return TrackCbuf(tracked, conditional_code, static_cast<s64>(conditional_code.size())); } - return nullptr; + return {}; } std::optional<u32> ShaderIR::TrackImmediate(Node tracked, const NodeBlock& code, s64 cursor) const { diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 6384fa8d2..250afc6d6 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -12,6 +12,8 @@ SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_t switch (texture_type) { case Tegra::Texture::TextureType::Texture1D: return SurfaceTarget::Texture1D; + case Tegra::Texture::TextureType::Texture1DBuffer: + return SurfaceTarget::TextureBuffer; case Tegra::Texture::TextureType::Texture2D: case Tegra::Texture::TextureType::Texture2DNoMipmap: return SurfaceTarget::Texture2D; @@ -35,6 +37,7 @@ SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_t bool SurfaceTargetIsLayered(SurfaceTarget target) { switch (target) { case SurfaceTarget::Texture1D: + case SurfaceTarget::TextureBuffer: case SurfaceTarget::Texture2D: case SurfaceTarget::Texture3D: return false; @@ -53,6 +56,7 @@ bool SurfaceTargetIsLayered(SurfaceTarget target) { bool SurfaceTargetIsArray(SurfaceTarget target) { switch (target) { case SurfaceTarget::Texture1D: + case SurfaceTarget::TextureBuffer: case SurfaceTarget::Texture2D: case SurfaceTarget::Texture3D: case SurfaceTarget::TextureCubemap: @@ -155,6 +159,8 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) return PixelFormat::R32UI; case Tegra::RenderTargetFormat::RG32_UINT: return PixelFormat::RG32UI; + case Tegra::RenderTargetFormat::RGBX16_FLOAT: + return PixelFormat::RGBX16F; default: LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); UNREACHABLE(); @@ -304,8 +310,8 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format, return PixelFormat::Z32F; case Tegra::Texture::TextureFormat::Z16: return PixelFormat::Z16; - case Tegra::Texture::TextureFormat::Z24S8: - return PixelFormat::Z24S8; + case Tegra::Texture::TextureFormat::S8Z24: + return PixelFormat::S8Z24; case Tegra::Texture::TextureFormat::ZF32_X24S8: return PixelFormat::Z32FS8; case Tegra::Texture::TextureFormat::DXT1: @@ -411,6 +417,7 @@ ComponentType ComponentTypeFromRenderTarget(Tegra::RenderTargetFormat format) { case Tegra::RenderTargetFormat::RG8_SNORM: return ComponentType::SNorm; case Tegra::RenderTargetFormat::RGBA16_FLOAT: + case Tegra::RenderTargetFormat::RGBX16_FLOAT: case Tegra::RenderTargetFormat::R11G11B10_FLOAT: case Tegra::RenderTargetFormat::RGBA32_FLOAT: case Tegra::RenderTargetFormat::RG32_FLOAT: @@ -441,11 +448,12 @@ PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat switch (format) { case Tegra::FramebufferConfig::PixelFormat::ABGR8: return PixelFormat::ABGR8U; + case Tegra::FramebufferConfig::PixelFormat::RGB565: + return PixelFormat::B5G6R5U; case Tegra::FramebufferConfig::PixelFormat::BGRA8: return PixelFormat::BGRA8; default: - LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format)); return PixelFormat::ABGR8U; } } @@ -508,6 +516,26 @@ bool IsPixelFormatASTC(PixelFormat format) { } } +bool IsPixelFormatSRGB(PixelFormat format) { + switch (format) { + case PixelFormat::RGBA8_SRGB: + case PixelFormat::BGRA8_SRGB: + case PixelFormat::DXT1_SRGB: + case PixelFormat::DXT23_SRGB: + case PixelFormat::DXT45_SRGB: + case PixelFormat::BC7U_SRGB: + case PixelFormat::ASTC_2D_4X4_SRGB: + case PixelFormat::ASTC_2D_8X8_SRGB: + case PixelFormat::ASTC_2D_8X5_SRGB: + case PixelFormat::ASTC_2D_5X4_SRGB: + case PixelFormat::ASTC_2D_5X5_SRGB: + case PixelFormat::ASTC_2D_10X8_SRGB: + return true; + default: + return false; + } +} + std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) { return {GetDefaultBlockWidth(format), GetDefaultBlockHeight(format)}; } diff --git a/src/video_core/surface.h b/src/video_core/surface.h index b783e4b27..1e1c432a5 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -57,36 +57,37 @@ enum class PixelFormat { RG8U = 39, RG8S = 40, RG32UI = 41, - R32UI = 42, - ASTC_2D_8X8 = 43, - ASTC_2D_8X5 = 44, - ASTC_2D_5X4 = 45, - BGRA8_SRGB = 46, - DXT1_SRGB = 47, - DXT23_SRGB = 48, - DXT45_SRGB = 49, - BC7U_SRGB = 50, - ASTC_2D_4X4_SRGB = 51, - ASTC_2D_8X8_SRGB = 52, - ASTC_2D_8X5_SRGB = 53, - ASTC_2D_5X4_SRGB = 54, - ASTC_2D_5X5 = 55, - ASTC_2D_5X5_SRGB = 56, - ASTC_2D_10X8 = 57, - ASTC_2D_10X8_SRGB = 58, + RGBX16F = 42, + R32UI = 43, + ASTC_2D_8X8 = 44, + ASTC_2D_8X5 = 45, + ASTC_2D_5X4 = 46, + BGRA8_SRGB = 47, + DXT1_SRGB = 48, + DXT23_SRGB = 49, + DXT45_SRGB = 50, + BC7U_SRGB = 51, + ASTC_2D_4X4_SRGB = 52, + ASTC_2D_8X8_SRGB = 53, + ASTC_2D_8X5_SRGB = 54, + ASTC_2D_5X4_SRGB = 55, + ASTC_2D_5X5 = 56, + ASTC_2D_5X5_SRGB = 57, + ASTC_2D_10X8 = 58, + ASTC_2D_10X8_SRGB = 59, MaxColorFormat, // Depth formats - Z32F = 59, - Z16 = 60, + Z32F = 60, + Z16 = 61, MaxDepthFormat, // DepthStencil formats - Z24S8 = 61, - S8Z24 = 62, - Z32FS8 = 63, + Z24S8 = 62, + S8Z24 = 63, + Z32FS8 = 64, MaxDepthStencilFormat, @@ -114,6 +115,7 @@ enum class SurfaceType { enum class SurfaceTarget { Texture1D, + TextureBuffer, Texture2D, Texture3D, Texture1DArray, @@ -122,71 +124,72 @@ enum class SurfaceTarget { TextureCubeArray, }; -constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{ - 1, // ABGR8U - 1, // ABGR8S - 1, // ABGR8UI - 1, // B5G6R5U - 1, // A2B10G10R10U - 1, // A1B5G5R5U - 1, // R8U - 1, // R8UI - 1, // RGBA16F - 1, // RGBA16U - 1, // RGBA16UI - 1, // R11FG11FB10F - 1, // RGBA32UI - 4, // DXT1 - 4, // DXT23 - 4, // DXT45 - 4, // DXN1 - 4, // DXN2UNORM - 4, // DXN2SNORM - 4, // BC7U - 4, // BC6H_UF16 - 4, // BC6H_SF16 - 4, // ASTC_2D_4X4 - 1, // BGRA8 - 1, // RGBA32F - 1, // RG32F - 1, // R32F - 1, // R16F - 1, // R16U - 1, // R16S - 1, // R16UI - 1, // R16I - 1, // RG16 - 1, // RG16F - 1, // RG16UI - 1, // RG16I - 1, // RG16S - 1, // RGB32F - 1, // RGBA8_SRGB - 1, // RG8U - 1, // RG8S - 1, // RG32UI - 1, // R32UI - 4, // ASTC_2D_8X8 - 4, // ASTC_2D_8X5 - 4, // ASTC_2D_5X4 - 1, // BGRA8_SRGB - 4, // DXT1_SRGB - 4, // DXT23_SRGB - 4, // DXT45_SRGB - 4, // BC7U_SRGB - 4, // ASTC_2D_4X4_SRGB - 4, // ASTC_2D_8X8_SRGB - 4, // ASTC_2D_8X5_SRGB - 4, // ASTC_2D_5X4_SRGB - 4, // ASTC_2D_5X5 - 4, // ASTC_2D_5X5_SRGB - 4, // ASTC_2D_10X8 - 4, // ASTC_2D_10X8_SRGB - 1, // Z32F - 1, // Z16 - 1, // Z24S8 - 1, // S8Z24 - 1, // Z32FS8 +constexpr std::array<u32, MaxPixelFormat> compression_factor_shift_table = {{ + 0, // ABGR8U + 0, // ABGR8S + 0, // ABGR8UI + 0, // B5G6R5U + 0, // A2B10G10R10U + 0, // A1B5G5R5U + 0, // R8U + 0, // R8UI + 0, // RGBA16F + 0, // RGBA16U + 0, // RGBA16UI + 0, // R11FG11FB10F + 0, // RGBA32UI + 2, // DXT1 + 2, // DXT23 + 2, // DXT45 + 2, // DXN1 + 2, // DXN2UNORM + 2, // DXN2SNORM + 2, // BC7U + 2, // BC6H_UF16 + 2, // BC6H_SF16 + 2, // ASTC_2D_4X4 + 0, // BGRA8 + 0, // RGBA32F + 0, // RG32F + 0, // R32F + 0, // R16F + 0, // R16U + 0, // R16S + 0, // R16UI + 0, // R16I + 0, // RG16 + 0, // RG16F + 0, // RG16UI + 0, // RG16I + 0, // RG16S + 0, // RGB32F + 0, // RGBA8_SRGB + 0, // RG8U + 0, // RG8S + 0, // RG32UI + 0, // RGBX16F + 0, // R32UI + 2, // ASTC_2D_8X8 + 2, // ASTC_2D_8X5 + 2, // ASTC_2D_5X4 + 0, // BGRA8_SRGB + 2, // DXT1_SRGB + 2, // DXT23_SRGB + 2, // DXT45_SRGB + 2, // BC7U_SRGB + 2, // ASTC_2D_4X4_SRGB + 2, // ASTC_2D_8X8_SRGB + 2, // ASTC_2D_8X5_SRGB + 2, // ASTC_2D_5X4_SRGB + 2, // ASTC_2D_5X5 + 2, // ASTC_2D_5X5_SRGB + 2, // ASTC_2D_10X8 + 2, // ASTC_2D_10X8_SRGB + 0, // Z32F + 0, // Z16 + 0, // Z24S8 + 0, // S8Z24 + 0, // Z32FS8 }}; /** @@ -195,12 +198,14 @@ constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{ * compressed image. This is used for maintaining proper surface sizes for compressed * texture formats. */ -static constexpr u32 GetCompressionFactor(PixelFormat format) { - if (format == PixelFormat::Invalid) - return 0; +inline constexpr u32 GetCompressionFactorShift(PixelFormat format) { + DEBUG_ASSERT(format != PixelFormat::Invalid); + DEBUG_ASSERT(static_cast<std::size_t>(format) < compression_factor_shift_table.size()); + return compression_factor_shift_table[static_cast<std::size_t>(format)]; +} - ASSERT(static_cast<std::size_t>(format) < compression_factor_table.size()); - return compression_factor_table[static_cast<std::size_t>(format)]; +inline constexpr u32 GetCompressionFactor(PixelFormat format) { + return 1U << GetCompressionFactorShift(format); } constexpr std::array<u32, MaxPixelFormat> block_width_table = {{ @@ -246,6 +251,7 @@ constexpr std::array<u32, MaxPixelFormat> block_width_table = {{ 1, // RG8U 1, // RG8S 1, // RG32UI + 1, // RGBX16F 1, // R32UI 8, // ASTC_2D_8X8 8, // ASTC_2D_8X5 @@ -321,6 +327,7 @@ constexpr std::array<u32, MaxPixelFormat> block_height_table = {{ 1, // RG8U 1, // RG8S 1, // RG32UI + 1, // RGBX16F 1, // R32UI 8, // ASTC_2D_8X8 5, // ASTC_2D_8X5 @@ -396,6 +403,7 @@ constexpr std::array<u32, MaxPixelFormat> bpp_table = {{ 16, // RG8U 16, // RG8S 64, // RG32UI + 64, // RGBX16F 32, // R32UI 128, // ASTC_2D_8X8 128, // ASTC_2D_8X5 @@ -436,6 +444,89 @@ static constexpr u32 GetBytesPerPixel(PixelFormat pixel_format) { return GetFormatBpp(pixel_format) / CHAR_BIT; } +enum class SurfaceCompression { + None, // Not compressed + Compressed, // Texture is compressed + Converted, // Texture is converted before upload or after download + Rearranged, // Texture is swizzled before upload or after download +}; + +constexpr std::array<SurfaceCompression, MaxPixelFormat> compression_type_table = {{ + SurfaceCompression::None, // ABGR8U + SurfaceCompression::None, // ABGR8S + SurfaceCompression::None, // ABGR8UI + SurfaceCompression::None, // B5G6R5U + SurfaceCompression::None, // A2B10G10R10U + SurfaceCompression::None, // A1B5G5R5U + SurfaceCompression::None, // R8U + SurfaceCompression::None, // R8UI + SurfaceCompression::None, // RGBA16F + SurfaceCompression::None, // RGBA16U + SurfaceCompression::None, // RGBA16UI + SurfaceCompression::None, // R11FG11FB10F + SurfaceCompression::None, // RGBA32UI + SurfaceCompression::Compressed, // DXT1 + SurfaceCompression::Compressed, // DXT23 + SurfaceCompression::Compressed, // DXT45 + SurfaceCompression::Compressed, // DXN1 + SurfaceCompression::Compressed, // DXN2UNORM + SurfaceCompression::Compressed, // DXN2SNORM + SurfaceCompression::Compressed, // BC7U + SurfaceCompression::Compressed, // BC6H_UF16 + SurfaceCompression::Compressed, // BC6H_SF16 + SurfaceCompression::Converted, // ASTC_2D_4X4 + SurfaceCompression::None, // BGRA8 + SurfaceCompression::None, // RGBA32F + SurfaceCompression::None, // RG32F + SurfaceCompression::None, // R32F + SurfaceCompression::None, // R16F + SurfaceCompression::None, // R16U + SurfaceCompression::None, // R16S + SurfaceCompression::None, // R16UI + SurfaceCompression::None, // R16I + SurfaceCompression::None, // RG16 + SurfaceCompression::None, // RG16F + SurfaceCompression::None, // RG16UI + SurfaceCompression::None, // RG16I + SurfaceCompression::None, // RG16S + SurfaceCompression::None, // RGB32F + SurfaceCompression::None, // RGBA8_SRGB + SurfaceCompression::None, // RG8U + SurfaceCompression::None, // RG8S + SurfaceCompression::None, // RG32UI + SurfaceCompression::None, // RGBX16F + SurfaceCompression::None, // R32UI + SurfaceCompression::Converted, // ASTC_2D_8X8 + SurfaceCompression::Converted, // ASTC_2D_8X5 + SurfaceCompression::Converted, // ASTC_2D_5X4 + SurfaceCompression::None, // BGRA8_SRGB + SurfaceCompression::Compressed, // DXT1_SRGB + SurfaceCompression::Compressed, // DXT23_SRGB + SurfaceCompression::Compressed, // DXT45_SRGB + SurfaceCompression::Compressed, // BC7U_SRGB + SurfaceCompression::Converted, // ASTC_2D_4X4_SRGB + SurfaceCompression::Converted, // ASTC_2D_8X8_SRGB + SurfaceCompression::Converted, // ASTC_2D_8X5_SRGB + SurfaceCompression::Converted, // ASTC_2D_5X4_SRGB + SurfaceCompression::Converted, // ASTC_2D_5X5 + SurfaceCompression::Converted, // ASTC_2D_5X5_SRGB + SurfaceCompression::Converted, // ASTC_2D_10X8 + SurfaceCompression::Converted, // ASTC_2D_10X8_SRGB + SurfaceCompression::None, // Z32F + SurfaceCompression::None, // Z16 + SurfaceCompression::None, // Z24S8 + SurfaceCompression::Rearranged, // S8Z24 + SurfaceCompression::None, // Z32FS8 +}}; + +constexpr SurfaceCompression GetFormatCompressionType(PixelFormat format) { + if (format == PixelFormat::Invalid) { + return SurfaceCompression::None; + } + DEBUG_ASSERT(static_cast<std::size_t>(format) < compression_type_table.size()); + return compression_type_table[static_cast<std::size_t>(format)]; +} + SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type); bool SurfaceTargetIsLayered(SurfaceTarget target); @@ -462,6 +553,8 @@ SurfaceType GetFormatType(PixelFormat pixel_format); bool IsPixelFormatASTC(PixelFormat format); +bool IsPixelFormatSRGB(PixelFormat format); + std::pair<u32, u32> GetASTCBlockSize(PixelFormat format); /// Returns true if the specified PixelFormat is a BCn format, e.g. DXT or DXN diff --git a/src/video_core/texture_cache.cpp b/src/video_core/texture_cache.cpp deleted file mode 100644 index e96eba7cc..000000000 --- a/src/video_core/texture_cache.cpp +++ /dev/null @@ -1,386 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/alignment.h" -#include "common/assert.h" -#include "common/cityhash.h" -#include "common/common_types.h" -#include "core/core.h" -#include "video_core/surface.h" -#include "video_core/texture_cache.h" -#include "video_core/textures/decoders.h" -#include "video_core/textures/texture.h" - -namespace VideoCommon { - -using VideoCore::Surface::SurfaceTarget; - -using VideoCore::Surface::ComponentTypeFromDepthFormat; -using VideoCore::Surface::ComponentTypeFromRenderTarget; -using VideoCore::Surface::ComponentTypeFromTexture; -using VideoCore::Surface::PixelFormatFromDepthFormat; -using VideoCore::Surface::PixelFormatFromRenderTargetFormat; -using VideoCore::Surface::PixelFormatFromTextureFormat; -using VideoCore::Surface::SurfaceTargetFromTextureType; - -constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { - return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); -} - -SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, - const Tegra::Texture::FullTextureInfo& config) { - SurfaceParams params; - params.is_tiled = config.tic.IsTiled(); - params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, - params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, - params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, - params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; - params.pixel_format = - PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), false); - params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); - params.type = GetFormatType(params.pixel_format); - params.target = SurfaceTargetFromTextureType(config.tic.texture_type); - params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format)); - params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format)); - params.depth = config.tic.Depth(); - if (params.target == SurfaceTarget::TextureCubemap || - params.target == SurfaceTarget::TextureCubeArray) { - params.depth *= 6; - } - params.pitch = params.is_tiled ? 0 : config.tic.Pitch(); - params.unaligned_height = config.tic.Height(); - params.num_levels = config.tic.max_mip_level + 1; - - params.CalculateCachedValues(); - return params; -} - -SurfaceParams SurfaceParams::CreateForDepthBuffer( - Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, - u32 block_width, u32 block_height, u32 block_depth, - Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { - SurfaceParams params; - params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; - params.block_width = 1 << std::min(block_width, 5U); - params.block_height = 1 << std::min(block_height, 5U); - params.block_depth = 1 << std::min(block_depth, 5U); - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromDepthFormat(format); - params.component_type = ComponentTypeFromDepthFormat(format); - params.type = GetFormatType(params.pixel_format); - params.width = zeta_width; - params.height = zeta_height; - params.unaligned_height = zeta_height; - params.target = SurfaceTarget::Texture2D; - params.depth = 1; - params.num_levels = 1; - - params.CalculateCachedValues(); - return params; -} - -SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) { - const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; - SurfaceParams params; - params.is_tiled = - config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; - params.block_width = 1 << config.memory_layout.block_width; - params.block_height = 1 << config.memory_layout.block_height; - params.block_depth = 1 << config.memory_layout.block_depth; - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); - params.component_type = ComponentTypeFromRenderTarget(config.format); - params.type = GetFormatType(params.pixel_format); - if (params.is_tiled) { - params.width = config.width; - } else { - const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT; - params.pitch = config.width; - params.width = params.pitch / bpp; - } - params.height = config.height; - params.depth = 1; - params.unaligned_height = config.height; - params.target = SurfaceTarget::Texture2D; - params.num_levels = 1; - - params.CalculateCachedValues(); - return params; -} - -SurfaceParams SurfaceParams::CreateForFermiCopySurface( - const Tegra::Engines::Fermi2D::Regs::Surface& config) { - SurfaceParams params{}; - params.is_tiled = !config.linear; - params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0, - params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0, - params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0, - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); - params.component_type = ComponentTypeFromRenderTarget(config.format); - params.type = GetFormatType(params.pixel_format); - params.width = config.width; - params.height = config.height; - params.unaligned_height = config.height; - // TODO(Rodrigo): Try to guess the surface target from depth and layer parameters - params.target = SurfaceTarget::Texture2D; - params.depth = 1; - params.num_levels = 1; - - params.CalculateCachedValues(); - return params; -} - -u32 SurfaceParams::GetMipWidth(u32 level) const { - return std::max(1U, width >> level); -} - -u32 SurfaceParams::GetMipHeight(u32 level) const { - return std::max(1U, height >> level); -} - -u32 SurfaceParams::GetMipDepth(u32 level) const { - return IsLayered() ? depth : std::max(1U, depth >> level); -} - -bool SurfaceParams::IsLayered() const { - switch (target) { - case SurfaceTarget::Texture1DArray: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - case SurfaceTarget::TextureCubemap: - return true; - default: - return false; - } -} - -u32 SurfaceParams::GetMipBlockHeight(u32 level) const { - // Auto block resizing algorithm from: - // https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c - if (level == 0) { - return block_height; - } - const u32 height{GetMipHeight(level)}; - const u32 default_block_height{GetDefaultBlockHeight(pixel_format)}; - const u32 blocks_in_y{(height + default_block_height - 1) / default_block_height}; - u32 block_height = 16; - while (block_height > 1 && blocks_in_y <= block_height * 4) { - block_height >>= 1; - } - return block_height; -} - -u32 SurfaceParams::GetMipBlockDepth(u32 level) const { - if (level == 0) - return block_depth; - if (target != SurfaceTarget::Texture3D) - return 1; - - const u32 depth{GetMipDepth(level)}; - u32 block_depth = 32; - while (block_depth > 1 && depth * 2 <= block_depth) { - block_depth >>= 1; - } - if (block_depth == 32 && GetMipBlockHeight(level) >= 4) { - return 16; - } - return block_depth; -} - -std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const { - std::size_t offset = 0; - for (u32 i = 0; i < level; i++) { - offset += GetInnerMipmapMemorySize(i, false, IsLayered(), false); - } - return offset; -} - -std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const { - std::size_t offset = 0; - for (u32 i = 0; i < level; i++) { - offset += GetInnerMipmapMemorySize(i, true, false, false); - } - return offset; -} - -std::size_t SurfaceParams::GetGuestLayerSize() const { - return GetInnerMemorySize(false, true, false); -} - -std::size_t SurfaceParams::GetHostLayerSize(u32 level) const { - return GetInnerMipmapMemorySize(level, true, IsLayered(), false); -} - -bool SurfaceParams::IsFamiliar(const SurfaceParams& view_params) const { - if (std::tie(is_tiled, tile_width_spacing, pixel_format, component_type, type) != - std::tie(view_params.is_tiled, view_params.tile_width_spacing, view_params.pixel_format, - view_params.component_type, view_params.type)) { - return false; - } - - const SurfaceTarget view_target{view_params.target}; - if (view_target == target) { - return true; - } - - switch (target) { - case SurfaceTarget::Texture1D: - case SurfaceTarget::Texture2D: - case SurfaceTarget::Texture3D: - return false; - case SurfaceTarget::Texture1DArray: - return view_target == SurfaceTarget::Texture1D; - case SurfaceTarget::Texture2DArray: - return view_target == SurfaceTarget::Texture2D; - case SurfaceTarget::TextureCubemap: - return view_target == SurfaceTarget::Texture2D || - view_target == SurfaceTarget::Texture2DArray; - case SurfaceTarget::TextureCubeArray: - return view_target == SurfaceTarget::Texture2D || - view_target == SurfaceTarget::Texture2DArray || - view_target == SurfaceTarget::TextureCubemap; - default: - UNIMPLEMENTED_MSG("Unimplemented texture family={}", static_cast<u32>(target)); - return false; - } -} - -bool SurfaceParams::IsPixelFormatZeta() const { - return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat && - pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat; -} - -void SurfaceParams::CalculateCachedValues() { - guest_size_in_bytes = GetInnerMemorySize(false, false, false); - - // ASTC is uncompressed in software, in emulated as RGBA8 - if (IsPixelFormatASTC(pixel_format)) { - host_size_in_bytes = width * height * depth * 4; - } else { - host_size_in_bytes = GetInnerMemorySize(true, false, false); - } - - switch (target) { - case SurfaceTarget::Texture1D: - case SurfaceTarget::Texture2D: - case SurfaceTarget::Texture3D: - num_layers = 1; - break; - case SurfaceTarget::Texture1DArray: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubemap: - case SurfaceTarget::TextureCubeArray: - num_layers = depth; - break; - default: - UNREACHABLE(); - } -} - -std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, - bool uncompressed) const { - const bool tiled{as_host_size ? false : is_tiled}; - const u32 tile_x{GetDefaultBlockWidth(pixel_format)}; - const u32 tile_y{GetDefaultBlockHeight(pixel_format)}; - const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), tile_x)}; - const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), tile_y)}; - const u32 depth{layer_only ? 1U : GetMipDepth(level)}; - return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(pixel_format), width, height, - depth, GetMipBlockHeight(level), GetMipBlockDepth(level)); -} - -std::size_t SurfaceParams::GetInnerMemorySize(bool as_host_size, bool layer_only, - bool uncompressed) const { - std::size_t size = 0; - for (u32 level = 0; level < num_levels; ++level) { - size += GetInnerMipmapMemorySize(level, as_host_size, layer_only, uncompressed); - } - if (!as_host_size && is_tiled) { - size = Common::AlignUp(size, Tegra::Texture::GetGOBSize() * block_height * block_depth); - } - return size; -} - -std::map<u64, std::pair<u32, u32>> SurfaceParams::CreateViewOffsetMap() const { - std::map<u64, std::pair<u32, u32>> view_offset_map; - switch (target) { - case SurfaceTarget::Texture1D: - case SurfaceTarget::Texture2D: - case SurfaceTarget::Texture3D: { - constexpr u32 layer = 0; - for (u32 level = 0; level < num_levels; ++level) { - const std::size_t offset{GetGuestMipmapLevelOffset(level)}; - view_offset_map.insert({offset, {layer, level}}); - } - break; - } - case SurfaceTarget::Texture1DArray: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubemap: - case SurfaceTarget::TextureCubeArray: { - const std::size_t layer_size{GetGuestLayerSize()}; - for (u32 level = 0; level < num_levels; ++level) { - const std::size_t level_offset{GetGuestMipmapLevelOffset(level)}; - for (u32 layer = 0; layer < num_layers; ++layer) { - const auto layer_offset{static_cast<std::size_t>(layer_size * layer)}; - const std::size_t offset{level_offset + layer_offset}; - view_offset_map.insert({offset, {layer, level}}); - } - } - break; - } - default: - UNIMPLEMENTED_MSG("Unimplemented surface target {}", static_cast<u32>(target)); - } - return view_offset_map; -} - -bool SurfaceParams::IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const { - return IsDimensionValid(view_params, level) && IsDepthValid(view_params, level) && - IsInBounds(view_params, layer, level); -} - -bool SurfaceParams::IsDimensionValid(const SurfaceParams& view_params, u32 level) const { - return view_params.width == GetMipWidth(level) && view_params.height == GetMipHeight(level); -} - -bool SurfaceParams::IsDepthValid(const SurfaceParams& view_params, u32 level) const { - if (view_params.target != SurfaceTarget::Texture3D) { - return true; - } - return view_params.depth == GetMipDepth(level); -} - -bool SurfaceParams::IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const { - return layer + view_params.num_layers <= num_layers && - level + view_params.num_levels <= num_levels; -} - -std::size_t HasheableSurfaceParams::Hash() const { - return static_cast<std::size_t>( - Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); -} - -bool HasheableSurfaceParams::operator==(const HasheableSurfaceParams& rhs) const { - return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width, - height, depth, pitch, unaligned_height, num_levels, pixel_format, - component_type, type, target) == - std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth, - rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch, - rhs.unaligned_height, rhs.num_levels, rhs.pixel_format, rhs.component_type, - rhs.type, rhs.target); -} - -std::size_t ViewKey::Hash() const { - return static_cast<std::size_t>( - Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); -} - -bool ViewKey::operator==(const ViewKey& rhs) const { - return std::tie(base_layer, num_layers, base_level, num_levels) == - std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels); -} - -} // namespace VideoCommon diff --git a/src/video_core/texture_cache.h b/src/video_core/texture_cache.h deleted file mode 100644 index 041551691..000000000 --- a/src/video_core/texture_cache.h +++ /dev/null @@ -1,586 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <list> -#include <memory> -#include <set> -#include <tuple> -#include <type_traits> -#include <unordered_map> - -#include <boost/icl/interval_map.hpp> -#include <boost/range/iterator_range.hpp> - -#include "common/assert.h" -#include "common/common_types.h" -#include "core/memory.h" -#include "video_core/engines/fermi_2d.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/gpu.h" -#include "video_core/rasterizer_interface.h" -#include "video_core/surface.h" - -namespace Core { -class System; -} - -namespace Tegra::Texture { -struct FullTextureInfo; -} - -namespace VideoCore { -class RasterizerInterface; -} - -namespace VideoCommon { - -class HasheableSurfaceParams { -public: - std::size_t Hash() const; - - bool operator==(const HasheableSurfaceParams& rhs) const; - -protected: - // Avoid creation outside of a managed environment. - HasheableSurfaceParams() = default; - - bool is_tiled; - u32 block_width; - u32 block_height; - u32 block_depth; - u32 tile_width_spacing; - u32 width; - u32 height; - u32 depth; - u32 pitch; - u32 unaligned_height; - u32 num_levels; - VideoCore::Surface::PixelFormat pixel_format; - VideoCore::Surface::ComponentType component_type; - VideoCore::Surface::SurfaceType type; - VideoCore::Surface::SurfaceTarget target; -}; - -class SurfaceParams final : public HasheableSurfaceParams { -public: - /// Creates SurfaceCachedParams from a texture configuration. - static SurfaceParams CreateForTexture(Core::System& system, - const Tegra::Texture::FullTextureInfo& config); - - /// Creates SurfaceCachedParams for a depth buffer configuration. - static SurfaceParams CreateForDepthBuffer( - Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, - u32 block_width, u32 block_height, u32 block_depth, - Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); - - /// Creates SurfaceCachedParams from a framebuffer configuration. - static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index); - - /// Creates SurfaceCachedParams from a Fermi2D surface configuration. - static SurfaceParams CreateForFermiCopySurface( - const Tegra::Engines::Fermi2D::Regs::Surface& config); - - bool IsTiled() const { - return is_tiled; - } - - u32 GetBlockWidth() const { - return block_width; - } - - u32 GetTileWidthSpacing() const { - return tile_width_spacing; - } - - u32 GetWidth() const { - return width; - } - - u32 GetHeight() const { - return height; - } - - u32 GetDepth() const { - return depth; - } - - u32 GetPitch() const { - return pitch; - } - - u32 GetNumLevels() const { - return num_levels; - } - - VideoCore::Surface::PixelFormat GetPixelFormat() const { - return pixel_format; - } - - VideoCore::Surface::ComponentType GetComponentType() const { - return component_type; - } - - VideoCore::Surface::SurfaceTarget GetTarget() const { - return target; - } - - VideoCore::Surface::SurfaceType GetType() const { - return type; - } - - std::size_t GetGuestSizeInBytes() const { - return guest_size_in_bytes; - } - - std::size_t GetHostSizeInBytes() const { - return host_size_in_bytes; - } - - u32 GetNumLayers() const { - return num_layers; - } - - /// Returns the width of a given mipmap level. - u32 GetMipWidth(u32 level) const; - - /// Returns the height of a given mipmap level. - u32 GetMipHeight(u32 level) const; - - /// Returns the depth of a given mipmap level. - u32 GetMipDepth(u32 level) const; - - /// Returns true if these parameters are from a layered surface. - bool IsLayered() const; - - /// Returns the block height of a given mipmap level. - u32 GetMipBlockHeight(u32 level) const; - - /// Returns the block depth of a given mipmap level. - u32 GetMipBlockDepth(u32 level) const; - - /// Returns the offset in bytes in guest memory of a given mipmap level. - std::size_t GetGuestMipmapLevelOffset(u32 level) const; - - /// Returns the offset in bytes in host memory (linear) of a given mipmap level. - std::size_t GetHostMipmapLevelOffset(u32 level) const; - - /// Returns the size of a layer in bytes in guest memory. - std::size_t GetGuestLayerSize() const; - - /// Returns the size of a layer in bytes in host memory for a given mipmap level. - std::size_t GetHostLayerSize(u32 level) const; - - /// Returns true if another surface can be familiar with this. This is a loosely defined term - /// that reflects the possibility of these two surface parameters potentially being part of a - /// bigger superset. - bool IsFamiliar(const SurfaceParams& view_params) const; - - /// Returns true if the pixel format is a depth and/or stencil format. - bool IsPixelFormatZeta() const; - - /// Creates a map that redirects an address difference to a layer and mipmap level. - std::map<u64, std::pair<u32, u32>> CreateViewOffsetMap() const; - - /// Returns true if the passed surface view parameters is equal or a valid subset of this. - bool IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const; - -private: - /// Calculates values that can be deduced from HasheableSurfaceParams. - void CalculateCachedValues(); - - /// Returns the size of a given mipmap level. - std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, - bool uncompressed) const; - - /// Returns the size of all mipmap levels and aligns as needed. - std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const; - - /// Returns true if the passed view width and height match the size of this params in a given - /// mipmap level. - bool IsDimensionValid(const SurfaceParams& view_params, u32 level) const; - - /// Returns true if the passed view depth match the size of this params in a given mipmap level. - bool IsDepthValid(const SurfaceParams& view_params, u32 level) const; - - /// Returns true if the passed view layers and mipmap levels are in bounds. - bool IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const; - - std::size_t guest_size_in_bytes; - std::size_t host_size_in_bytes; - u32 num_layers; -}; - -struct ViewKey { - std::size_t Hash() const; - - bool operator==(const ViewKey& rhs) const; - - u32 base_layer{}; - u32 num_layers{}; - u32 base_level{}; - u32 num_levels{}; -}; - -} // namespace VideoCommon - -namespace std { - -template <> -struct hash<VideoCommon::SurfaceParams> { - std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept { - return k.Hash(); - } -}; - -template <> -struct hash<VideoCommon::ViewKey> { - std::size_t operator()(const VideoCommon::ViewKey& k) const noexcept { - return k.Hash(); - } -}; - -} // namespace std - -namespace VideoCommon { - -template <typename TView, typename TExecutionContext> -class SurfaceBase { - static_assert(std::is_trivially_copyable_v<TExecutionContext>); - -public: - virtual void LoadBuffer() = 0; - - virtual TExecutionContext FlushBuffer(TExecutionContext exctx) = 0; - - virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0; - - TView* TryGetView(VAddr view_addr, const SurfaceParams& view_params) { - if (view_addr < cpu_addr || !params.IsFamiliar(view_params)) { - // It can't be a view if it's in a prior address. - return {}; - } - - const auto relative_offset{static_cast<u64>(view_addr - cpu_addr)}; - const auto it{view_offset_map.find(relative_offset)}; - if (it == view_offset_map.end()) { - // Couldn't find an aligned view. - return {}; - } - const auto [layer, level] = it->second; - - if (!params.IsViewValid(view_params, layer, level)) { - return {}; - } - - return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels()); - } - - VAddr GetCpuAddr() const { - ASSERT(is_registered); - return cpu_addr; - } - - u8* GetHostPtr() const { - ASSERT(is_registered); - return host_ptr; - } - - CacheAddr GetCacheAddr() const { - ASSERT(is_registered); - return cache_addr; - } - - std::size_t GetSizeInBytes() const { - return params.GetGuestSizeInBytes(); - } - - void MarkAsModified(bool is_modified_) { - is_modified = is_modified_; - } - - const SurfaceParams& GetSurfaceParams() const { - return params; - } - - TView* GetView(VAddr view_addr, const SurfaceParams& view_params) { - TView* view{TryGetView(view_addr, view_params)}; - ASSERT(view != nullptr); - return view; - } - - void Register(VAddr cpu_addr_, u8* host_ptr_) { - ASSERT(!is_registered); - is_registered = true; - cpu_addr = cpu_addr_; - host_ptr = host_ptr_; - cache_addr = ToCacheAddr(host_ptr_); - } - - void Register(VAddr cpu_addr_) { - Register(cpu_addr_, Memory::GetPointer(cpu_addr_)); - } - - void Unregister() { - ASSERT(is_registered); - is_registered = false; - } - - bool IsRegistered() const { - return is_registered; - } - -protected: - explicit SurfaceBase(const SurfaceParams& params) - : params{params}, view_offset_map{params.CreateViewOffsetMap()} {} - - ~SurfaceBase() = default; - - virtual std::unique_ptr<TView> CreateView(const ViewKey& view_key) = 0; - - bool IsModified() const { - return is_modified; - } - - const SurfaceParams params; - -private: - TView* GetView(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels) { - const ViewKey key{base_layer, num_layers, base_level, num_levels}; - const auto [entry, is_cache_miss] = views.try_emplace(key); - auto& view{entry->second}; - if (is_cache_miss) { - view = CreateView(key); - } - return view.get(); - } - - const std::map<u64, std::pair<u32, u32>> view_offset_map; - - VAddr cpu_addr{}; - u8* host_ptr{}; - CacheAddr cache_addr{}; - bool is_modified{}; - bool is_registered{}; - std::unordered_map<ViewKey, std::unique_ptr<TView>> views; -}; - -template <typename TSurface, typename TView, typename TExecutionContext> -class TextureCache { - static_assert(std::is_trivially_copyable_v<TExecutionContext>); - using ResultType = std::tuple<TView*, TExecutionContext>; - using IntervalMap = boost::icl::interval_map<CacheAddr, std::set<TSurface*>>; - using IntervalType = typename IntervalMap::interval_type; - -public: - void InvalidateRegion(CacheAddr addr, std::size_t size) { - for (TSurface* surface : GetSurfacesInRegion(addr, size)) { - if (!surface->IsRegistered()) { - // Skip duplicates - continue; - } - Unregister(surface); - } - } - - ResultType GetTextureSurface(TExecutionContext exctx, - const Tegra::Texture::FullTextureInfo& config) { - auto& memory_manager{system.GPU().MemoryManager()}; - const auto cpu_addr{memory_manager.GpuToCpuAddress(config.tic.Address())}; - if (!cpu_addr) { - return {{}, exctx}; - } - const auto params{SurfaceParams::CreateForTexture(system, config)}; - return GetSurfaceView(exctx, *cpu_addr, params, true); - } - - ResultType GetDepthBufferSurface(TExecutionContext exctx, bool preserve_contents) { - const auto& regs{system.GPU().Maxwell3D().regs}; - if (!regs.zeta.Address() || !regs.zeta_enable) { - return {{}, exctx}; - } - - auto& memory_manager{system.GPU().MemoryManager()}; - const auto cpu_addr{memory_manager.GpuToCpuAddress(regs.zeta.Address())}; - if (!cpu_addr) { - return {{}, exctx}; - } - - const auto depth_params{SurfaceParams::CreateForDepthBuffer( - system, regs.zeta_width, regs.zeta_height, regs.zeta.format, - regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, - regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; - return GetSurfaceView(exctx, *cpu_addr, depth_params, preserve_contents); - } - - ResultType GetColorBufferSurface(TExecutionContext exctx, std::size_t index, - bool preserve_contents) { - ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); - - const auto& regs{system.GPU().Maxwell3D().regs}; - if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 || - regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { - return {{}, exctx}; - } - - auto& memory_manager{system.GPU().MemoryManager()}; - const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; - const auto cpu_addr{memory_manager.GpuToCpuAddress( - config.Address() + config.base_layer * config.layer_stride * sizeof(u32))}; - if (!cpu_addr) { - return {{}, exctx}; - } - - return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index), - preserve_contents); - } - - ResultType GetFermiSurface(TExecutionContext exctx, - const Tegra::Engines::Fermi2D::Regs::Surface& config) { - const auto cpu_addr{system.GPU().MemoryManager().GpuToCpuAddress(config.Address())}; - ASSERT(cpu_addr); - return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFermiCopySurface(config), - true); - } - - TSurface* TryFindFramebufferSurface(const u8* host_ptr) const { - const auto it{registered_surfaces.find(ToCacheAddr(host_ptr))}; - return it != registered_surfaces.end() ? *it->second.begin() : nullptr; - } - -protected: - TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer) - : system{system}, rasterizer{rasterizer} {} - - ~TextureCache() = default; - - virtual ResultType TryFastGetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, - const SurfaceParams& params, bool preserve_contents, - const std::vector<TSurface*>& overlaps) = 0; - - virtual std::unique_ptr<TSurface> CreateSurface(const SurfaceParams& params) = 0; - - void Register(TSurface* surface, VAddr cpu_addr, u8* host_ptr) { - surface->Register(cpu_addr, host_ptr); - registered_surfaces.add({GetSurfaceInterval(surface), {surface}}); - rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), 1); - } - - void Unregister(TSurface* surface) { - registered_surfaces.subtract({GetSurfaceInterval(surface), {surface}}); - rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), -1); - surface->Unregister(); - } - - TSurface* GetUncachedSurface(const SurfaceParams& params) { - if (TSurface* surface = TryGetReservedSurface(params); surface) - return surface; - // No reserved surface available, create a new one and reserve it - auto new_surface{CreateSurface(params)}; - TSurface* surface{new_surface.get()}; - ReserveSurface(params, std::move(new_surface)); - return surface; - } - - Core::System& system; - -private: - ResultType GetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, const SurfaceParams& params, - bool preserve_contents) { - const auto host_ptr{Memory::GetPointer(cpu_addr)}; - const auto cache_addr{ToCacheAddr(host_ptr)}; - const auto overlaps{GetSurfacesInRegion(cache_addr, params.GetGuestSizeInBytes())}; - if (overlaps.empty()) { - return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); - } - - if (overlaps.size() == 1) { - if (TView* view = overlaps[0]->TryGetView(cpu_addr, params); view) - return {view, exctx}; - } - - TView* fast_view; - std::tie(fast_view, exctx) = - TryFastGetSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents, overlaps); - - for (TSurface* surface : overlaps) { - if (!fast_view) { - // Flush even when we don't care about the contents, to preserve memory not written - // by the new surface. - exctx = surface->FlushBuffer(exctx); - } - Unregister(surface); - } - - if (fast_view) { - return {fast_view, exctx}; - } - - return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); - } - - ResultType LoadSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, - const SurfaceParams& params, bool preserve_contents) { - TSurface* new_surface{GetUncachedSurface(params)}; - Register(new_surface, cpu_addr, host_ptr); - if (preserve_contents) { - exctx = LoadSurface(exctx, new_surface); - } - return {new_surface->GetView(cpu_addr, params), exctx}; - } - - TExecutionContext LoadSurface(TExecutionContext exctx, TSurface* surface) { - surface->LoadBuffer(); - exctx = surface->UploadTexture(exctx); - surface->MarkAsModified(false); - return exctx; - } - - std::vector<TSurface*> GetSurfacesInRegion(CacheAddr cache_addr, std::size_t size) const { - if (size == 0) { - return {}; - } - const IntervalType interval{cache_addr, cache_addr + size}; - - std::vector<TSurface*> surfaces; - for (auto& pair : boost::make_iterator_range(registered_surfaces.equal_range(interval))) { - surfaces.push_back(*pair.second.begin()); - } - return surfaces; - } - - void ReserveSurface(const SurfaceParams& params, std::unique_ptr<TSurface> surface) { - surface_reserve[params].push_back(std::move(surface)); - } - - TSurface* TryGetReservedSurface(const SurfaceParams& params) { - auto search{surface_reserve.find(params)}; - if (search == surface_reserve.end()) { - return {}; - } - for (auto& surface : search->second) { - if (!surface->IsRegistered()) { - return surface.get(); - } - } - return {}; - } - - IntervalType GetSurfaceInterval(TSurface* surface) const { - return IntervalType::right_open(surface->GetCacheAddr(), - surface->GetCacheAddr() + surface->GetSizeInBytes()); - } - - VideoCore::RasterizerInterface& rasterizer; - - IntervalMap registered_surfaces; - - /// 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<SurfaceParams, std::list<std::unique_ptr<TSurface>>> surface_reserve; -}; - -} // namespace VideoCommon diff --git a/src/video_core/texture_cache/copy_params.h b/src/video_core/texture_cache/copy_params.h new file mode 100644 index 000000000..9c21a0649 --- /dev/null +++ b/src/video_core/texture_cache/copy_params.h @@ -0,0 +1,36 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace VideoCommon { + +struct CopyParams { + constexpr CopyParams(u32 source_x, u32 source_y, u32 source_z, u32 dest_x, u32 dest_y, + u32 dest_z, u32 source_level, u32 dest_level, u32 width, u32 height, + u32 depth) + : source_x{source_x}, source_y{source_y}, source_z{source_z}, dest_x{dest_x}, + dest_y{dest_y}, dest_z{dest_z}, source_level{source_level}, + dest_level{dest_level}, width{width}, height{height}, depth{depth} {} + + constexpr CopyParams(u32 width, u32 height, u32 depth, u32 level) + : source_x{}, source_y{}, source_z{}, dest_x{}, dest_y{}, dest_z{}, source_level{level}, + dest_level{level}, width{width}, height{height}, depth{depth} {} + + u32 source_x; + u32 source_y; + u32 source_z; + u32 dest_x; + u32 dest_y; + u32 dest_z; + u32 source_level; + u32 dest_level; + u32 width; + u32 height; + u32 depth; +}; + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_base.cpp b/src/video_core/texture_cache/surface_base.cpp new file mode 100644 index 000000000..683c49207 --- /dev/null +++ b/src/video_core/texture_cache/surface_base.cpp @@ -0,0 +1,302 @@ +// 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/common_types.h" +#include "common/microprofile.h" +#include "video_core/memory_manager.h" +#include "video_core/texture_cache/surface_base.h" +#include "video_core/texture_cache/surface_params.h" +#include "video_core/textures/convert.h" + +namespace VideoCommon { + +MICROPROFILE_DEFINE(GPU_Load_Texture, "GPU", "Texture Load", MP_RGB(128, 192, 128)); +MICROPROFILE_DEFINE(GPU_Flush_Texture, "GPU", "Texture Flush", MP_RGB(128, 192, 128)); + +using Tegra::Texture::ConvertFromGuestToHost; +using VideoCore::MortonSwizzleMode; +using VideoCore::Surface::SurfaceCompression; + +StagingCache::StagingCache() = default; + +StagingCache::~StagingCache() = default; + +SurfaceBaseImpl::SurfaceBaseImpl(GPUVAddr gpu_addr, const SurfaceParams& params) + : params{params}, host_memory_size{params.GetHostSizeInBytes()}, gpu_addr{gpu_addr}, + mipmap_sizes(params.num_levels), mipmap_offsets(params.num_levels) { + std::size_t offset = 0; + for (u32 level = 0; level < params.num_levels; ++level) { + const std::size_t mipmap_size{params.GetGuestMipmapSize(level)}; + mipmap_sizes[level] = mipmap_size; + mipmap_offsets[level] = offset; + offset += mipmap_size; + } + layer_size = offset; + if (params.is_layered) { + if (params.is_tiled) { + layer_size = + SurfaceParams::AlignLayered(layer_size, params.block_height, params.block_depth); + } + guest_memory_size = layer_size * params.depth; + } else { + guest_memory_size = layer_size; + } +} + +MatchTopologyResult SurfaceBaseImpl::MatchesTopology(const SurfaceParams& rhs) const { + const u32 src_bpp{params.GetBytesPerPixel()}; + const u32 dst_bpp{rhs.GetBytesPerPixel()}; + const bool ib1 = params.IsBuffer(); + const bool ib2 = rhs.IsBuffer(); + if (std::tie(src_bpp, params.is_tiled, ib1) == std::tie(dst_bpp, rhs.is_tiled, ib2)) { + const bool cb1 = params.IsCompressed(); + const bool cb2 = rhs.IsCompressed(); + if (cb1 == cb2) { + return MatchTopologyResult::FullMatch; + } + return MatchTopologyResult::CompressUnmatch; + } + return MatchTopologyResult::None; +} + +MatchStructureResult SurfaceBaseImpl::MatchesStructure(const SurfaceParams& rhs) const { + // Buffer surface Check + if (params.IsBuffer()) { + const std::size_t wd1 = params.width * params.GetBytesPerPixel(); + const std::size_t wd2 = rhs.width * rhs.GetBytesPerPixel(); + if (wd1 == wd2) { + return MatchStructureResult::FullMatch; + } + return MatchStructureResult::None; + } + + // Linear Surface check + if (!params.is_tiled) { + if (std::tie(params.height, params.pitch) == std::tie(rhs.height, rhs.pitch)) { + if (params.width == rhs.width) { + return MatchStructureResult::FullMatch; + } else { + return MatchStructureResult::SemiMatch; + } + } + return MatchStructureResult::None; + } + + // Tiled Surface check + if (std::tie(params.depth, params.block_width, params.block_height, params.block_depth, + params.tile_width_spacing, params.num_levels) == + std::tie(rhs.depth, rhs.block_width, rhs.block_height, rhs.block_depth, + rhs.tile_width_spacing, rhs.num_levels)) { + if (std::tie(params.width, params.height) == std::tie(rhs.width, rhs.height)) { + return MatchStructureResult::FullMatch; + } + const u32 ws = SurfaceParams::ConvertWidth(rhs.GetBlockAlignedWidth(), params.pixel_format, + rhs.pixel_format); + const u32 hs = + SurfaceParams::ConvertHeight(rhs.height, params.pixel_format, rhs.pixel_format); + const u32 w1 = params.GetBlockAlignedWidth(); + if (std::tie(w1, params.height) == std::tie(ws, hs)) { + return MatchStructureResult::SemiMatch; + } + } + return MatchStructureResult::None; +} + +std::optional<std::pair<u32, u32>> SurfaceBaseImpl::GetLayerMipmap( + const GPUVAddr candidate_gpu_addr) const { + if (gpu_addr == candidate_gpu_addr) { + return {{0, 0}}; + } + if (candidate_gpu_addr < gpu_addr) { + return {}; + } + const auto relative_address{static_cast<GPUVAddr>(candidate_gpu_addr - gpu_addr)}; + const auto layer{static_cast<u32>(relative_address / layer_size)}; + const GPUVAddr mipmap_address = relative_address - layer_size * layer; + const auto mipmap_it = + Common::BinaryFind(mipmap_offsets.begin(), mipmap_offsets.end(), mipmap_address); + if (mipmap_it == mipmap_offsets.end()) { + return {}; + } + const auto level{static_cast<u32>(std::distance(mipmap_offsets.begin(), mipmap_it))}; + return std::make_pair(layer, level); +} + +std::vector<CopyParams> SurfaceBaseImpl::BreakDownLayered(const SurfaceParams& in_params) const { + const u32 layers{params.depth}; + const u32 mipmaps{params.num_levels}; + std::vector<CopyParams> result; + result.reserve(static_cast<std::size_t>(layers) * static_cast<std::size_t>(mipmaps)); + + for (u32 layer = 0; layer < layers; layer++) { + for (u32 level = 0; level < mipmaps; level++) { + const u32 width = SurfaceParams::IntersectWidth(params, in_params, level, level); + const u32 height = SurfaceParams::IntersectHeight(params, in_params, level, level); + result.emplace_back(width, height, layer, level); + } + } + return result; +} + +std::vector<CopyParams> SurfaceBaseImpl::BreakDownNonLayered(const SurfaceParams& in_params) const { + const u32 mipmaps{params.num_levels}; + std::vector<CopyParams> result; + result.reserve(mipmaps); + + for (u32 level = 0; level < mipmaps; level++) { + const u32 width = SurfaceParams::IntersectWidth(params, in_params, level, level); + const u32 height = SurfaceParams::IntersectHeight(params, in_params, level, level); + const u32 depth{std::min(params.GetMipDepth(level), in_params.GetMipDepth(level))}; + result.emplace_back(width, height, depth, level); + } + return result; +} + +void SurfaceBaseImpl::SwizzleFunc(MortonSwizzleMode mode, u8* memory, const SurfaceParams& params, + u8* buffer, u32 level) { + const u32 width{params.GetMipWidth(level)}; + const u32 height{params.GetMipHeight(level)}; + const u32 block_height{params.GetMipBlockHeight(level)}; + const u32 block_depth{params.GetMipBlockDepth(level)}; + + std::size_t guest_offset{mipmap_offsets[level]}; + if (params.is_layered) { + std::size_t host_offset{0}; + const std::size_t guest_stride = layer_size; + const std::size_t host_stride = params.GetHostLayerSize(level); + for (u32 layer = 0; layer < params.depth; ++layer) { + MortonSwizzle(mode, params.pixel_format, width, block_height, height, block_depth, 1, + params.tile_width_spacing, buffer + host_offset, memory + guest_offset); + guest_offset += guest_stride; + host_offset += host_stride; + } + } else { + MortonSwizzle(mode, params.pixel_format, width, block_height, height, block_depth, + params.GetMipDepth(level), params.tile_width_spacing, buffer, + memory + guest_offset); + } +} + +void SurfaceBaseImpl::LoadBuffer(Tegra::MemoryManager& memory_manager, + StagingCache& staging_cache) { + MICROPROFILE_SCOPE(GPU_Load_Texture); + auto& staging_buffer = staging_cache.GetBuffer(0); + u8* host_ptr; + is_continuous = memory_manager.IsBlockContinuous(gpu_addr, guest_memory_size); + + // Handle continuouty + if (is_continuous) { + // Use physical memory directly + host_ptr = memory_manager.GetPointer(gpu_addr); + if (!host_ptr) { + return; + } + } else { + // Use an extra temporal buffer + auto& tmp_buffer = staging_cache.GetBuffer(1); + tmp_buffer.resize(guest_memory_size); + host_ptr = tmp_buffer.data(); + memory_manager.ReadBlockUnsafe(gpu_addr, host_ptr, guest_memory_size); + } + + if (params.is_tiled) { + ASSERT_MSG(params.block_width == 0, "Block width is defined as {} on texture target {}", + params.block_width, static_cast<u32>(params.target)); + for (u32 level = 0; level < params.num_levels; ++level) { + const std::size_t host_offset{params.GetHostMipmapLevelOffset(level)}; + SwizzleFunc(MortonSwizzleMode::MortonToLinear, host_ptr, params, + staging_buffer.data() + host_offset, level); + } + } else { + ASSERT_MSG(params.num_levels == 1, "Linear mipmap loading is not implemented"); + const u32 bpp{params.GetBytesPerPixel()}; + const u32 block_width{params.GetDefaultBlockWidth()}; + const u32 block_height{params.GetDefaultBlockHeight()}; + const u32 width{(params.width + block_width - 1) / block_width}; + const u32 height{(params.height + block_height - 1) / block_height}; + const u32 copy_size{width * bpp}; + if (params.pitch == copy_size) { + std::memcpy(staging_buffer.data(), host_ptr, params.GetHostSizeInBytes()); + } else { + const u8* start{host_ptr}; + u8* write_to{staging_buffer.data()}; + for (u32 h = height; h > 0; --h) { + std::memcpy(write_to, start, copy_size); + start += params.pitch; + write_to += copy_size; + } + } + } + + auto compression_type = params.GetCompressionType(); + if (compression_type == SurfaceCompression::None || + compression_type == SurfaceCompression::Compressed) + return; + + for (u32 level_up = params.num_levels; level_up > 0; --level_up) { + const u32 level = level_up - 1; + const std::size_t in_host_offset{params.GetHostMipmapLevelOffset(level)}; + const std::size_t out_host_offset = compression_type == SurfaceCompression::Rearranged + ? in_host_offset + : params.GetConvertedMipmapOffset(level); + u8* in_buffer = staging_buffer.data() + in_host_offset; + u8* out_buffer = staging_buffer.data() + out_host_offset; + ConvertFromGuestToHost(in_buffer, out_buffer, params.pixel_format, + params.GetMipWidth(level), params.GetMipHeight(level), + params.GetMipDepth(level), true, true); + } +} + +void SurfaceBaseImpl::FlushBuffer(Tegra::MemoryManager& memory_manager, + StagingCache& staging_cache) { + MICROPROFILE_SCOPE(GPU_Flush_Texture); + auto& staging_buffer = staging_cache.GetBuffer(0); + u8* host_ptr; + + // Handle continuouty + if (is_continuous) { + // Use physical memory directly + host_ptr = memory_manager.GetPointer(gpu_addr); + if (!host_ptr) { + return; + } + } else { + // Use an extra temporal buffer + auto& tmp_buffer = staging_cache.GetBuffer(1); + tmp_buffer.resize(guest_memory_size); + host_ptr = tmp_buffer.data(); + } + + if (params.is_tiled) { + ASSERT_MSG(params.block_width == 0, "Block width is defined as {}", params.block_width); + for (u32 level = 0; level < params.num_levels; ++level) { + const std::size_t host_offset{params.GetHostMipmapLevelOffset(level)}; + SwizzleFunc(MortonSwizzleMode::LinearToMorton, host_ptr, params, + staging_buffer.data() + host_offset, level); + } + } else { + ASSERT(params.target == SurfaceTarget::Texture2D); + ASSERT(params.num_levels == 1); + + const u32 bpp{params.GetBytesPerPixel()}; + const u32 copy_size{params.width * bpp}; + if (params.pitch == copy_size) { + std::memcpy(host_ptr, staging_buffer.data(), guest_memory_size); + } else { + u8* start{host_ptr}; + const u8* read_to{staging_buffer.data()}; + for (u32 h = params.height; h > 0; --h) { + std::memcpy(start, read_to, copy_size); + start += params.pitch; + read_to += copy_size; + } + } + } + if (!is_continuous) { + memory_manager.WriteBlockUnsafe(gpu_addr, host_ptr, guest_memory_size); + } +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_base.h b/src/video_core/texture_cache/surface_base.h new file mode 100644 index 000000000..5e497e49f --- /dev/null +++ b/src/video_core/texture_cache/surface_base.h @@ -0,0 +1,325 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <unordered_map> +#include <vector> + +#include "common/assert.h" +#include "common/binary_find.h" +#include "common/common_types.h" +#include "video_core/gpu.h" +#include "video_core/morton.h" +#include "video_core/texture_cache/copy_params.h" +#include "video_core/texture_cache/surface_params.h" +#include "video_core/texture_cache/surface_view.h" + +namespace Tegra { +class MemoryManager; +} + +namespace VideoCommon { + +using VideoCore::MortonSwizzleMode; +using VideoCore::Surface::SurfaceTarget; + +enum class MatchStructureResult : u32 { + FullMatch = 0, + SemiMatch = 1, + None = 2, +}; + +enum class MatchTopologyResult : u32 { + FullMatch = 0, + CompressUnmatch = 1, + None = 2, +}; + +class StagingCache { +public: + explicit StagingCache(); + ~StagingCache(); + + std::vector<u8>& GetBuffer(std::size_t index) { + return staging_buffer[index]; + } + + const std::vector<u8>& GetBuffer(std::size_t index) const { + return staging_buffer[index]; + } + + void SetSize(std::size_t size) { + staging_buffer.resize(size); + } + +private: + std::vector<std::vector<u8>> staging_buffer; +}; + +class SurfaceBaseImpl { +public: + void LoadBuffer(Tegra::MemoryManager& memory_manager, StagingCache& staging_cache); + + void FlushBuffer(Tegra::MemoryManager& memory_manager, StagingCache& staging_cache); + + GPUVAddr GetGpuAddr() const { + return gpu_addr; + } + + bool Overlaps(const CacheAddr start, const CacheAddr end) const { + return (cache_addr < end) && (cache_addr_end > start); + } + + bool IsInside(const GPUVAddr other_start, const GPUVAddr other_end) { + const GPUVAddr gpu_addr_end = gpu_addr + guest_memory_size; + return (gpu_addr <= other_start && other_end <= gpu_addr_end); + } + + // Use only when recycling a surface + void SetGpuAddr(const GPUVAddr new_addr) { + gpu_addr = new_addr; + } + + VAddr GetCpuAddr() const { + return cpu_addr; + } + + void SetCpuAddr(const VAddr new_addr) { + cpu_addr = new_addr; + } + + CacheAddr GetCacheAddr() const { + return cache_addr; + } + + CacheAddr GetCacheAddrEnd() const { + return cache_addr_end; + } + + void SetCacheAddr(const CacheAddr new_addr) { + cache_addr = new_addr; + cache_addr_end = new_addr + guest_memory_size; + } + + const SurfaceParams& GetSurfaceParams() const { + return params; + } + + std::size_t GetSizeInBytes() const { + return guest_memory_size; + } + + std::size_t GetHostSizeInBytes() const { + return host_memory_size; + } + + std::size_t GetMipmapSize(const u32 level) const { + return mipmap_sizes[level]; + } + + void MarkAsContinuous(const bool is_continuous) { + this->is_continuous = is_continuous; + } + + bool IsContinuous() const { + return is_continuous; + } + + bool IsLinear() const { + return !params.is_tiled; + } + + bool MatchFormat(VideoCore::Surface::PixelFormat pixel_format) const { + return params.pixel_format == pixel_format; + } + + VideoCore::Surface::PixelFormat GetFormat() const { + return params.pixel_format; + } + + bool MatchTarget(VideoCore::Surface::SurfaceTarget target) const { + return params.target == target; + } + + MatchTopologyResult MatchesTopology(const SurfaceParams& rhs) const; + + MatchStructureResult MatchesStructure(const SurfaceParams& rhs) const; + + bool MatchesSubTexture(const SurfaceParams& rhs, const GPUVAddr other_gpu_addr) const { + return std::tie(gpu_addr, params.target, params.num_levels) == + std::tie(other_gpu_addr, rhs.target, rhs.num_levels) && + params.target == SurfaceTarget::Texture2D && params.num_levels == 1; + } + + std::optional<std::pair<u32, u32>> GetLayerMipmap(const GPUVAddr candidate_gpu_addr) const; + + std::vector<CopyParams> BreakDown(const SurfaceParams& in_params) const { + return params.is_layered ? BreakDownLayered(in_params) : BreakDownNonLayered(in_params); + } + +protected: + explicit SurfaceBaseImpl(GPUVAddr gpu_addr, const SurfaceParams& params); + ~SurfaceBaseImpl() = default; + + virtual void DecorateSurfaceName() = 0; + + const SurfaceParams params; + std::size_t layer_size; + std::size_t guest_memory_size; + const std::size_t host_memory_size; + GPUVAddr gpu_addr{}; + CacheAddr cache_addr{}; + CacheAddr cache_addr_end{}; + VAddr cpu_addr{}; + bool is_continuous{}; + + std::vector<std::size_t> mipmap_sizes; + std::vector<std::size_t> mipmap_offsets; + +private: + void SwizzleFunc(MortonSwizzleMode mode, u8* memory, const SurfaceParams& params, u8* buffer, + u32 level); + + std::vector<CopyParams> BreakDownLayered(const SurfaceParams& in_params) const; + + std::vector<CopyParams> BreakDownNonLayered(const SurfaceParams& in_params) const; +}; + +template <typename TView> +class SurfaceBase : public SurfaceBaseImpl { +public: + virtual void UploadTexture(const std::vector<u8>& staging_buffer) = 0; + + virtual void DownloadTexture(std::vector<u8>& staging_buffer) = 0; + + void MarkAsModified(bool is_modified_, u64 tick) { + is_modified = is_modified_ || is_target; + modification_tick = tick; + } + + void MarkAsRenderTarget(bool is_target_, u32 index_) { + is_target = is_target_; + index = index_; + } + + void MarkAsPicked(bool is_picked_) { + is_picked = is_picked_; + } + + bool IsModified() const { + return is_modified; + } + + bool IsProtected() const { + // Only 3D Slices are to be protected + return is_target && params.block_depth > 0; + } + + bool IsRenderTarget() const { + return is_target; + } + + u32 GetRenderTarget() const { + return index; + } + + bool IsRegistered() const { + return is_registered; + } + + bool IsPicked() const { + return is_picked; + } + + void MarkAsRegistered(bool is_reg) { + is_registered = is_reg; + } + + u64 GetModificationTick() const { + return modification_tick; + } + + TView EmplaceOverview(const SurfaceParams& overview_params) { + const u32 num_layers{(params.is_layered && !overview_params.is_layered) ? 1 : params.depth}; + return GetView(ViewParams(overview_params.target, 0, num_layers, 0, params.num_levels)); + } + + std::optional<TView> EmplaceIrregularView(const SurfaceParams& view_params, + const GPUVAddr view_addr, + const std::size_t candidate_size, const u32 mipmap, + const u32 layer) { + const auto layer_mipmap{GetLayerMipmap(view_addr + candidate_size)}; + if (!layer_mipmap) { + return {}; + } + const u32 end_layer{layer_mipmap->first}; + const u32 end_mipmap{layer_mipmap->second}; + if (layer != end_layer) { + if (mipmap == 0 && end_mipmap == 0) { + return GetView(ViewParams(view_params.target, layer, end_layer - layer + 1, 0, 1)); + } + return {}; + } else { + return GetView( + ViewParams(view_params.target, layer, 1, mipmap, end_mipmap - mipmap + 1)); + } + } + + std::optional<TView> EmplaceView(const SurfaceParams& view_params, const GPUVAddr view_addr, + const std::size_t candidate_size) { + if (params.target == SurfaceTarget::Texture3D || + (params.num_levels == 1 && !params.is_layered) || + view_params.target == SurfaceTarget::Texture3D) { + return {}; + } + const auto layer_mipmap{GetLayerMipmap(view_addr)}; + if (!layer_mipmap) { + return {}; + } + const u32 layer{layer_mipmap->first}; + const u32 mipmap{layer_mipmap->second}; + if (GetMipmapSize(mipmap) != candidate_size) { + return EmplaceIrregularView(view_params, view_addr, candidate_size, mipmap, layer); + } + return GetView(ViewParams(view_params.target, layer, 1, mipmap, 1)); + } + + TView GetMainView() const { + return main_view; + } + +protected: + explicit SurfaceBase(const GPUVAddr gpu_addr, const SurfaceParams& params) + : SurfaceBaseImpl(gpu_addr, params) {} + + ~SurfaceBase() = default; + + virtual TView CreateView(const ViewParams& view_key) = 0; + + TView main_view; + std::unordered_map<ViewParams, TView> views; + +private: + TView GetView(const ViewParams& key) { + const auto [entry, is_cache_miss] = views.try_emplace(key); + auto& view{entry->second}; + if (is_cache_miss) { + view = CreateView(key); + } + return view; + } + + static constexpr u32 NO_RT = 0xFFFFFFFF; + + bool is_modified{}; + bool is_target{}; + bool is_registered{}; + bool is_picked{}; + u32 index{NO_RT}; + u64 modification_tick{}; +}; + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp new file mode 100644 index 000000000..1e4d3fb79 --- /dev/null +++ b/src/video_core/texture_cache/surface_params.cpp @@ -0,0 +1,389 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <map> + +#include "common/alignment.h" +#include "common/bit_util.h" +#include "core/core.h" +#include "video_core/engines/shader_bytecode.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/surface_params.h" + +namespace VideoCommon { + +using VideoCore::Surface::ComponentTypeFromDepthFormat; +using VideoCore::Surface::ComponentTypeFromRenderTarget; +using VideoCore::Surface::ComponentTypeFromTexture; +using VideoCore::Surface::PixelFormat; +using VideoCore::Surface::PixelFormatFromDepthFormat; +using VideoCore::Surface::PixelFormatFromRenderTargetFormat; +using VideoCore::Surface::PixelFormatFromTextureFormat; +using VideoCore::Surface::SurfaceTarget; +using VideoCore::Surface::SurfaceTargetFromTextureType; +using VideoCore::Surface::SurfaceType; + +namespace { + +SurfaceTarget TextureTypeToSurfaceTarget(Tegra::Shader::TextureType type, bool is_array) { + switch (type) { + case Tegra::Shader::TextureType::Texture1D: + return is_array ? SurfaceTarget::Texture1DArray : SurfaceTarget::Texture1D; + case Tegra::Shader::TextureType::Texture2D: + return is_array ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D; + case Tegra::Shader::TextureType::Texture3D: + ASSERT(!is_array); + return SurfaceTarget::Texture3D; + case Tegra::Shader::TextureType::TextureCube: + return is_array ? SurfaceTarget::TextureCubeArray : SurfaceTarget::TextureCubemap; + default: + UNREACHABLE(); + return SurfaceTarget::Texture2D; + } +} + +SurfaceTarget ImageTypeToSurfaceTarget(Tegra::Shader::ImageType type) { + switch (type) { + case Tegra::Shader::ImageType::Texture1D: + return SurfaceTarget::Texture1D; + case Tegra::Shader::ImageType::TextureBuffer: + return SurfaceTarget::TextureBuffer; + case Tegra::Shader::ImageType::Texture1DArray: + return SurfaceTarget::Texture1DArray; + case Tegra::Shader::ImageType::Texture2D: + return SurfaceTarget::Texture2D; + case Tegra::Shader::ImageType::Texture2DArray: + return SurfaceTarget::Texture2DArray; + case Tegra::Shader::ImageType::Texture3D: + return SurfaceTarget::Texture3D; + default: + UNREACHABLE(); + return SurfaceTarget::Texture2D; + } +} + +constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { + return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); +} + +} // Anonymous namespace + +SurfaceParams SurfaceParams::CreateForTexture(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Sampler& entry) { + SurfaceParams params; + params.is_tiled = tic.IsTiled(); + params.srgb_conversion = tic.IsSrgbConversionEnabled(); + params.block_width = params.is_tiled ? tic.BlockWidth() : 0, + params.block_height = params.is_tiled ? tic.BlockHeight() : 0, + params.block_depth = params.is_tiled ? tic.BlockDepth() : 0, + params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1; + params.pixel_format = + PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion); + params.type = GetFormatType(params.pixel_format); + if (entry.IsShadow() && params.type == SurfaceType::ColorTexture) { + switch (params.pixel_format) { + case PixelFormat::R16U: + case PixelFormat::R16F: { + params.pixel_format = PixelFormat::Z16; + break; + } + case PixelFormat::R32F: { + params.pixel_format = PixelFormat::Z32F; + break; + } + default: { + UNIMPLEMENTED_MSG("Unimplemented shadow convert format: {}", + static_cast<u32>(params.pixel_format)); + } + } + params.type = GetFormatType(params.pixel_format); + } + params.component_type = ComponentTypeFromTexture(tic.r_type.Value()); + params.type = GetFormatType(params.pixel_format); + // TODO: on 1DBuffer we should use the tic info. + if (tic.IsBuffer()) { + params.target = SurfaceTarget::TextureBuffer; + params.width = tic.Width(); + params.pitch = params.width * params.GetBytesPerPixel(); + params.height = 1; + params.depth = 1; + params.num_levels = 1; + params.emulated_levels = 1; + params.is_layered = false; + } else { + params.target = TextureTypeToSurfaceTarget(entry.GetType(), entry.IsArray()); + params.width = tic.Width(); + params.height = tic.Height(); + params.depth = tic.Depth(); + params.pitch = params.is_tiled ? 0 : tic.Pitch(); + if (params.target == SurfaceTarget::TextureCubemap || + params.target == SurfaceTarget::TextureCubeArray) { + params.depth *= 6; + } + params.num_levels = tic.max_mip_level + 1; + params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); + params.is_layered = params.IsLayered(); + } + return params; +} + +SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Image& entry) { + SurfaceParams params; + params.is_tiled = tic.IsTiled(); + params.srgb_conversion = tic.IsSrgbConversionEnabled(); + params.block_width = params.is_tiled ? tic.BlockWidth() : 0, + params.block_height = params.is_tiled ? tic.BlockHeight() : 0, + params.block_depth = params.is_tiled ? tic.BlockDepth() : 0, + params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1; + params.pixel_format = + PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion); + params.type = GetFormatType(params.pixel_format); + params.component_type = ComponentTypeFromTexture(tic.r_type.Value()); + params.type = GetFormatType(params.pixel_format); + params.target = ImageTypeToSurfaceTarget(entry.GetType()); + // TODO: on 1DBuffer we should use the tic info. + if (tic.IsBuffer()) { + params.target = SurfaceTarget::TextureBuffer; + params.width = tic.Width(); + params.pitch = params.width * params.GetBytesPerPixel(); + params.height = 1; + params.depth = 1; + params.num_levels = 1; + params.emulated_levels = 1; + params.is_layered = false; + } else { + params.width = tic.Width(); + params.height = tic.Height(); + params.depth = tic.Depth(); + params.pitch = params.is_tiled ? 0 : tic.Pitch(); + if (params.target == SurfaceTarget::TextureCubemap || + params.target == SurfaceTarget::TextureCubeArray) { + params.depth *= 6; + } + params.num_levels = tic.max_mip_level + 1; + params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); + params.is_layered = params.IsLayered(); + } + return params; +} + +SurfaceParams SurfaceParams::CreateForDepthBuffer( + Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, + u32 block_width, u32 block_height, u32 block_depth, + Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { + SurfaceParams params; + params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; + params.srgb_conversion = false; + params.block_width = std::min(block_width, 5U); + params.block_height = std::min(block_height, 5U); + params.block_depth = std::min(block_depth, 5U); + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromDepthFormat(format); + params.component_type = ComponentTypeFromDepthFormat(format); + params.type = GetFormatType(params.pixel_format); + params.width = zeta_width; + params.height = zeta_height; + params.target = SurfaceTarget::Texture2D; + params.depth = 1; + params.pitch = 0; + params.num_levels = 1; + params.emulated_levels = 1; + params.is_layered = false; + return params; +} + +SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) { + const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; + SurfaceParams params; + params.is_tiled = + config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; + params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB || + config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; + params.block_width = config.memory_layout.block_width; + params.block_height = config.memory_layout.block_height; + params.block_depth = config.memory_layout.block_depth; + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); + params.component_type = ComponentTypeFromRenderTarget(config.format); + params.type = GetFormatType(params.pixel_format); + if (params.is_tiled) { + params.pitch = 0; + params.width = config.width; + } else { + const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT; + params.pitch = config.width; + params.width = params.pitch / bpp; + } + params.height = config.height; + params.depth = 1; + params.target = SurfaceTarget::Texture2D; + params.num_levels = 1; + params.emulated_levels = 1; + params.is_layered = false; + return params; +} + +SurfaceParams SurfaceParams::CreateForFermiCopySurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config) { + SurfaceParams params{}; + params.is_tiled = !config.linear; + params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB || + config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; + params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 5U) : 0, + params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 5U) : 0, + params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 5U) : 0, + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); + params.component_type = ComponentTypeFromRenderTarget(config.format); + params.type = GetFormatType(params.pixel_format); + params.width = config.width; + params.height = config.height; + params.pitch = config.pitch; + // TODO(Rodrigo): Try to guess the surface target from depth and layer parameters + params.target = SurfaceTarget::Texture2D; + params.depth = 1; + params.num_levels = 1; + params.emulated_levels = 1; + params.is_layered = params.IsLayered(); + return params; +} + +bool SurfaceParams::IsLayered() const { + switch (target) { + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubemap: + case SurfaceTarget::TextureCubeArray: + return true; + default: + return false; + } +} + +// Auto block resizing algorithm from: +// https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c +u32 SurfaceParams::GetMipBlockHeight(u32 level) const { + if (level == 0) { + return this->block_height; + } + + const u32 height_new{GetMipHeight(level)}; + const u32 default_block_height{GetDefaultBlockHeight()}; + const u32 blocks_in_y{(height_new + default_block_height - 1) / default_block_height}; + const u32 block_height_new = Common::Log2Ceil32(blocks_in_y); + return std::clamp(block_height_new, 3U, 7U) - 3U; +} + +u32 SurfaceParams::GetMipBlockDepth(u32 level) const { + if (level == 0) { + return this->block_depth; + } + if (is_layered) { + return 0; + } + + const u32 depth_new{GetMipDepth(level)}; + const u32 block_depth_new = Common::Log2Ceil32(depth_new); + if (block_depth_new > 4) { + return 5 - (GetMipBlockHeight(level) >= 2); + } + return block_depth_new; +} + +std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const { + std::size_t offset = 0; + for (u32 i = 0; i < level; i++) { + offset += GetInnerMipmapMemorySize(i, false, false); + } + return offset; +} + +std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const { + std::size_t offset = 0; + for (u32 i = 0; i < level; i++) { + offset += GetInnerMipmapMemorySize(i, true, false) * GetNumLayers(); + } + return offset; +} + +std::size_t SurfaceParams::GetConvertedMipmapOffset(u32 level) const { + std::size_t offset = 0; + for (u32 i = 0; i < level; i++) { + offset += GetConvertedMipmapSize(i); + } + return offset; +} + +std::size_t SurfaceParams::GetConvertedMipmapSize(u32 level) const { + constexpr std::size_t rgba8_bpp = 4ULL; + const std::size_t width_t = GetMipWidth(level); + const std::size_t height_t = GetMipHeight(level); + const std::size_t depth_t = is_layered ? depth : GetMipDepth(level); + return width_t * height_t * depth_t * rgba8_bpp; +} + +std::size_t SurfaceParams::GetLayerSize(bool as_host_size, bool uncompressed) const { + std::size_t size = 0; + for (u32 level = 0; level < num_levels; ++level) { + size += GetInnerMipmapMemorySize(level, as_host_size, uncompressed); + } + if (is_tiled && is_layered) { + return Common::AlignBits(size, + Tegra::Texture::GetGOBSizeShift() + block_height + block_depth); + } + return size; +} + +std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, + bool uncompressed) const { + const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), GetDefaultBlockWidth())}; + const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), GetDefaultBlockHeight())}; + const u32 depth{is_layered ? 1U : GetMipDepth(level)}; + if (is_tiled) { + return Tegra::Texture::CalculateSize(!as_host_size, GetBytesPerPixel(), width, height, + depth, GetMipBlockHeight(level), + GetMipBlockDepth(level)); + } else if (as_host_size || IsBuffer()) { + return GetBytesPerPixel() * width * height * depth; + } else { + // Linear Texture Case + return pitch * height * depth; + } +} + +bool SurfaceParams::operator==(const SurfaceParams& rhs) const { + return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width, + height, depth, pitch, num_levels, pixel_format, component_type, type, target) == + std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth, + rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch, + rhs.num_levels, rhs.pixel_format, rhs.component_type, rhs.type, rhs.target); +} + +std::string SurfaceParams::TargetName() const { + switch (target) { + case SurfaceTarget::Texture1D: + return "1D"; + case SurfaceTarget::TextureBuffer: + return "TexBuffer"; + 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"; + case SurfaceTarget::TextureCubeArray: + return "CubeArray"; + default: + LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target)); + UNREACHABLE(); + return fmt::format("TUK({})", static_cast<u32>(target)); + } +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h new file mode 100644 index 000000000..c58e7f8a4 --- /dev/null +++ b/src/video_core/texture_cache/surface_params.h @@ -0,0 +1,286 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/alignment.h" +#include "common/bit_util.h" +#include "common/cityhash.h" +#include "common/common_types.h" +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/shader/shader_ir.h" +#include "video_core/surface.h" +#include "video_core/textures/decoders.h" + +namespace VideoCommon { + +using VideoCore::Surface::SurfaceCompression; + +class SurfaceParams { +public: + /// Creates SurfaceCachedParams from a texture configuration. + static SurfaceParams CreateForTexture(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Sampler& entry); + + /// Creates SurfaceCachedParams from an image configuration. + static SurfaceParams CreateForImage(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Image& entry); + + /// Creates SurfaceCachedParams for a depth buffer configuration. + static SurfaceParams CreateForDepthBuffer( + Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, + u32 block_width, u32 block_height, u32 block_depth, + Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); + + /// Creates SurfaceCachedParams from a framebuffer configuration. + static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index); + + /// Creates SurfaceCachedParams from a Fermi2D surface configuration. + static SurfaceParams CreateForFermiCopySurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config); + + std::size_t Hash() const { + return static_cast<std::size_t>( + Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); + } + + bool operator==(const SurfaceParams& rhs) const; + + bool operator!=(const SurfaceParams& rhs) const { + return !operator==(rhs); + } + + std::size_t GetGuestSizeInBytes() const { + return GetInnerMemorySize(false, false, false); + } + + std::size_t GetHostSizeInBytes() const { + std::size_t host_size_in_bytes; + if (GetCompressionType() == SurfaceCompression::Converted) { + // ASTC is uncompressed in software, in emulated as RGBA8 + host_size_in_bytes = 0; + for (u32 level = 0; level < num_levels; ++level) { + host_size_in_bytes += GetConvertedMipmapSize(level); + } + } else { + host_size_in_bytes = GetInnerMemorySize(true, false, false); + } + return host_size_in_bytes; + } + + u32 GetBlockAlignedWidth() const { + return Common::AlignUp(width, 64 / GetBytesPerPixel()); + } + + /// Returns the width of a given mipmap level. + u32 GetMipWidth(u32 level) const { + return std::max(1U, width >> level); + } + + /// Returns the height of a given mipmap level. + u32 GetMipHeight(u32 level) const { + return std::max(1U, height >> level); + } + + /// Returns the depth of a given mipmap level. + u32 GetMipDepth(u32 level) const { + return is_layered ? depth : std::max(1U, depth >> level); + } + + /// Returns the block height of a given mipmap level. + u32 GetMipBlockHeight(u32 level) const; + + /// Returns the block depth of a given mipmap level. + u32 GetMipBlockDepth(u32 level) const; + + /// Returns the best possible row/pitch alignment for the surface. + u32 GetRowAlignment(u32 level) const { + const u32 bpp = + GetCompressionType() == SurfaceCompression::Converted ? 4 : GetBytesPerPixel(); + return 1U << Common::CountTrailingZeroes32(GetMipWidth(level) * bpp); + } + + /// Returns the offset in bytes in guest memory of a given mipmap level. + std::size_t GetGuestMipmapLevelOffset(u32 level) const; + + /// Returns the offset in bytes in host memory (linear) of a given mipmap level. + std::size_t GetHostMipmapLevelOffset(u32 level) const; + + /// Returns the offset in bytes in host memory (linear) of a given mipmap level + /// for a texture that is converted in host gpu. + std::size_t GetConvertedMipmapOffset(u32 level) const; + + /// Returns the size in bytes in guest memory of a given mipmap level. + std::size_t GetGuestMipmapSize(u32 level) const { + return GetInnerMipmapMemorySize(level, false, false); + } + + /// Returns the size in bytes in host memory (linear) of a given mipmap level. + std::size_t GetHostMipmapSize(u32 level) const { + return GetInnerMipmapMemorySize(level, true, false) * GetNumLayers(); + } + + std::size_t GetConvertedMipmapSize(u32 level) const; + + /// Returns the size of a layer in bytes in guest memory. + std::size_t GetGuestLayerSize() const { + return GetLayerSize(false, false); + } + + /// Returns the size of a layer in bytes in host memory for a given mipmap level. + std::size_t GetHostLayerSize(u32 level) const { + ASSERT(target != VideoCore::Surface::SurfaceTarget::Texture3D); + return GetInnerMipmapMemorySize(level, true, false); + } + + /// Returns the max possible mipmap that the texture can have in host gpu + u32 MaxPossibleMipmap() const { + const u32 max_mipmap_w = Common::Log2Ceil32(width) + 1U; + const u32 max_mipmap_h = Common::Log2Ceil32(height) + 1U; + const u32 max_mipmap = std::max(max_mipmap_w, max_mipmap_h); + if (target != VideoCore::Surface::SurfaceTarget::Texture3D) + return max_mipmap; + return std::max(max_mipmap, Common::Log2Ceil32(depth) + 1U); + } + + /// Returns if the guest surface is a compressed surface. + bool IsCompressed() const { + return GetDefaultBlockHeight() > 1 || GetDefaultBlockWidth() > 1; + } + + /// Returns the default block width. + u32 GetDefaultBlockWidth() const { + return VideoCore::Surface::GetDefaultBlockWidth(pixel_format); + } + + /// Returns the default block height. + u32 GetDefaultBlockHeight() const { + return VideoCore::Surface::GetDefaultBlockHeight(pixel_format); + } + + /// Returns the bits per pixel. + u32 GetBitsPerPixel() const { + return VideoCore::Surface::GetFormatBpp(pixel_format); + } + + /// Returns the bytes per pixel. + u32 GetBytesPerPixel() const { + return VideoCore::Surface::GetBytesPerPixel(pixel_format); + } + + /// Returns true if the pixel format is a depth and/or stencil format. + bool IsPixelFormatZeta() const { + return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat && + pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat; + } + + /// Returns how the compression should be handled for this texture. + SurfaceCompression GetCompressionType() const { + return VideoCore::Surface::GetFormatCompressionType(pixel_format); + } + + /// Returns is the surface is a TextureBuffer type of surface. + bool IsBuffer() const { + return target == VideoCore::Surface::SurfaceTarget::TextureBuffer; + } + + /// Returns the debug name of the texture for use in graphic debuggers. + std::string TargetName() const; + + // Helper used for out of class size calculations + static std::size_t AlignLayered(const std::size_t out_size, const u32 block_height, + const u32 block_depth) { + return Common::AlignBits(out_size, + Tegra::Texture::GetGOBSizeShift() + block_height + block_depth); + } + + /// Converts a width from a type of surface into another. This helps represent the + /// equivalent value between compressed/non-compressed textures. + static u32 ConvertWidth(u32 width, VideoCore::Surface::PixelFormat pixel_format_from, + VideoCore::Surface::PixelFormat pixel_format_to) { + const u32 bw1 = VideoCore::Surface::GetDefaultBlockWidth(pixel_format_from); + const u32 bw2 = VideoCore::Surface::GetDefaultBlockWidth(pixel_format_to); + return (width * bw2 + bw1 - 1) / bw1; + } + + /// Converts a height from a type of surface into another. This helps represent the + /// equivalent value between compressed/non-compressed textures. + static u32 ConvertHeight(u32 height, VideoCore::Surface::PixelFormat pixel_format_from, + VideoCore::Surface::PixelFormat pixel_format_to) { + const u32 bh1 = VideoCore::Surface::GetDefaultBlockHeight(pixel_format_from); + const u32 bh2 = VideoCore::Surface::GetDefaultBlockHeight(pixel_format_to); + return (height * bh2 + bh1 - 1) / bh1; + } + + // Finds the maximun possible width between 2 2D layers of different formats + static u32 IntersectWidth(const SurfaceParams& src_params, const SurfaceParams& dst_params, + const u32 src_level, const u32 dst_level) { + const u32 bw1 = src_params.GetDefaultBlockWidth(); + const u32 bw2 = dst_params.GetDefaultBlockWidth(); + const u32 t_src_width = (src_params.GetMipWidth(src_level) * bw2 + bw1 - 1) / bw1; + const u32 t_dst_width = (dst_params.GetMipWidth(dst_level) * bw1 + bw2 - 1) / bw2; + return std::min(t_src_width, t_dst_width); + } + + // Finds the maximun possible height between 2 2D layers of different formats + static u32 IntersectHeight(const SurfaceParams& src_params, const SurfaceParams& dst_params, + const u32 src_level, const u32 dst_level) { + const u32 bh1 = src_params.GetDefaultBlockHeight(); + const u32 bh2 = dst_params.GetDefaultBlockHeight(); + const u32 t_src_height = (src_params.GetMipHeight(src_level) * bh2 + bh1 - 1) / bh1; + const u32 t_dst_height = (dst_params.GetMipHeight(dst_level) * bh1 + bh2 - 1) / bh2; + return std::min(t_src_height, t_dst_height); + } + + bool is_tiled; + bool srgb_conversion; + bool is_layered; + u32 block_width; + u32 block_height; + u32 block_depth; + u32 tile_width_spacing; + u32 width; + u32 height; + u32 depth; + u32 pitch; + u32 num_levels; + u32 emulated_levels; + VideoCore::Surface::PixelFormat pixel_format; + VideoCore::Surface::ComponentType component_type; + VideoCore::Surface::SurfaceType type; + VideoCore::Surface::SurfaceTarget target; + +private: + /// Returns the size of a given mipmap level inside a layer. + std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool uncompressed) const; + + /// Returns the size of all mipmap levels and aligns as needed. + std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const { + return GetLayerSize(as_host_size, uncompressed) * (layer_only ? 1U : depth); + } + + /// Returns the size of a layer + std::size_t GetLayerSize(bool as_host_size, bool uncompressed) const; + + std::size_t GetNumLayers() const { + return is_layered ? depth : 1; + } + + /// Returns true if these parameters are from a layered surface. + bool IsLayered() const; +}; + +} // namespace VideoCommon + +namespace std { + +template <> +struct hash<VideoCommon::SurfaceParams> { + std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std diff --git a/src/video_core/texture_cache/surface_view.cpp b/src/video_core/texture_cache/surface_view.cpp new file mode 100644 index 000000000..57a1f5803 --- /dev/null +++ b/src/video_core/texture_cache/surface_view.cpp @@ -0,0 +1,23 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <tuple> + +#include "common/common_types.h" +#include "video_core/texture_cache/surface_view.h" + +namespace VideoCommon { + +std::size_t ViewParams::Hash() const { + return static_cast<std::size_t>(base_layer) ^ (static_cast<std::size_t>(num_layers) << 16) ^ + (static_cast<std::size_t>(base_level) << 24) ^ + (static_cast<std::size_t>(num_levels) << 32) ^ (static_cast<std::size_t>(target) << 36); +} + +bool ViewParams::operator==(const ViewParams& rhs) const { + return std::tie(base_layer, num_layers, base_level, num_levels, target) == + std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels, rhs.target); +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_view.h b/src/video_core/texture_cache/surface_view.h new file mode 100644 index 000000000..b17fd11a9 --- /dev/null +++ b/src/video_core/texture_cache/surface_view.h @@ -0,0 +1,67 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> + +#include "common/common_types.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/surface_params.h" + +namespace VideoCommon { + +struct ViewParams { + constexpr explicit ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer, + u32 num_layers, u32 base_level, u32 num_levels) + : target{target}, base_layer{base_layer}, num_layers{num_layers}, base_level{base_level}, + num_levels{num_levels} {} + + std::size_t Hash() const; + + bool operator==(const ViewParams& rhs) const; + + bool IsLayered() const { + switch (target) { + case VideoCore::Surface::SurfaceTarget::Texture1DArray: + case VideoCore::Surface::SurfaceTarget::Texture2DArray: + case VideoCore::Surface::SurfaceTarget::TextureCubemap: + case VideoCore::Surface::SurfaceTarget::TextureCubeArray: + return true; + default: + return false; + } + } + + VideoCore::Surface::SurfaceTarget target{}; + u32 base_layer{}; + u32 num_layers{}; + u32 base_level{}; + u32 num_levels{}; +}; + +class ViewBase { +public: + constexpr explicit ViewBase(const ViewParams& params) : params{params} {} + + constexpr const ViewParams& GetViewParams() const { + return params; + } + +protected: + ViewParams params; +}; + +} // namespace VideoCommon + +namespace std { + +template <> +struct hash<VideoCommon::ViewParams> { + std::size_t operator()(const VideoCommon::ViewParams& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h new file mode 100644 index 000000000..877c6635d --- /dev/null +++ b/src/video_core/texture_cache/texture_cache.h @@ -0,0 +1,835 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <array> +#include <memory> +#include <mutex> +#include <set> +#include <tuple> +#include <unordered_map> +#include <vector> + +#include <boost/icl/interval_map.hpp> +#include <boost/range/iterator_range.hpp> + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/math_util.h" +#include "core/core.h" +#include "core/memory.h" +#include "core/settings.h" +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/copy_params.h" +#include "video_core/texture_cache/surface_base.h" +#include "video_core/texture_cache/surface_params.h" +#include "video_core/texture_cache/surface_view.h" + +namespace Tegra::Texture { +struct FullTextureInfo; +} + +namespace VideoCore { +class RasterizerInterface; +} + +namespace VideoCommon { + +using VideoCore::Surface::PixelFormat; + +using VideoCore::Surface::SurfaceTarget; +using RenderTargetConfig = Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig; + +template <typename TSurface, typename TView> +class TextureCache { + using IntervalMap = boost::icl::interval_map<CacheAddr, std::set<TSurface>>; + using IntervalType = typename IntervalMap::interval_type; + +public: + void InvalidateRegion(CacheAddr addr, std::size_t size) { + std::lock_guard lock{mutex}; + + for (const auto& surface : GetSurfacesInRegion(addr, size)) { + Unregister(surface); + } + } + + /*** + * `Guard` guarantees that rendertargets don't unregister themselves if the + * collide. Protection is currently only done on 3D slices. + ***/ + void GuardRenderTargets(bool new_guard) { + guard_render_targets = new_guard; + } + + void GuardSamplers(bool new_guard) { + guard_samplers = new_guard; + } + + void FlushRegion(CacheAddr addr, std::size_t size) { + std::lock_guard lock{mutex}; + + auto surfaces = GetSurfacesInRegion(addr, size); + if (surfaces.empty()) { + return; + } + std::sort(surfaces.begin(), surfaces.end(), [](const TSurface& a, const TSurface& b) { + return a->GetModificationTick() < b->GetModificationTick(); + }); + for (const auto& surface : surfaces) { + FlushSurface(surface); + } + } + + TView GetTextureSurface(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Sampler& entry) { + std::lock_guard lock{mutex}; + const auto gpu_addr{tic.Address()}; + if (!gpu_addr) { + return {}; + } + const auto params{SurfaceParams::CreateForTexture(tic, entry)}; + const auto [surface, view] = GetSurface(gpu_addr, params, true, false); + if (guard_samplers) { + sampled_textures.push_back(surface); + } + return view; + } + + TView GetImageSurface(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Image& entry) { + std::lock_guard lock{mutex}; + const auto gpu_addr{tic.Address()}; + if (!gpu_addr) { + return {}; + } + const auto params{SurfaceParams::CreateForImage(tic, entry)}; + const auto [surface, view] = GetSurface(gpu_addr, params, true, false); + if (guard_samplers) { + sampled_textures.push_back(surface); + } + return view; + } + + bool TextureBarrier() { + const bool any_rt = + std::any_of(sampled_textures.begin(), sampled_textures.end(), + [](const auto& surface) { return surface->IsRenderTarget(); }); + sampled_textures.clear(); + return any_rt; + } + + TView GetDepthBufferSurface(bool preserve_contents) { + std::lock_guard lock{mutex}; + auto& maxwell3d = system.GPU().Maxwell3D(); + + if (!maxwell3d.dirty.depth_buffer) { + return depth_buffer.view; + } + maxwell3d.dirty.depth_buffer = false; + + const auto& regs{maxwell3d.regs}; + const auto gpu_addr{regs.zeta.Address()}; + if (!gpu_addr || !regs.zeta_enable) { + SetEmptyDepthBuffer(); + return {}; + } + const auto depth_params{SurfaceParams::CreateForDepthBuffer( + system, regs.zeta_width, regs.zeta_height, regs.zeta.format, + regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, + regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; + auto surface_view = GetSurface(gpu_addr, depth_params, preserve_contents, true); + if (depth_buffer.target) + depth_buffer.target->MarkAsRenderTarget(false, NO_RT); + depth_buffer.target = surface_view.first; + depth_buffer.view = surface_view.second; + if (depth_buffer.target) + depth_buffer.target->MarkAsRenderTarget(true, DEPTH_RT); + return surface_view.second; + } + + TView GetColorBufferSurface(std::size_t index, bool preserve_contents) { + std::lock_guard lock{mutex}; + ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); + auto& maxwell3d = system.GPU().Maxwell3D(); + if (!maxwell3d.dirty.render_target[index]) { + return render_targets[index].view; + } + maxwell3d.dirty.render_target[index] = false; + + const auto& regs{maxwell3d.regs}; + if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 || + regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { + SetEmptyColorBuffer(index); + return {}; + } + + const auto& config{regs.rt[index]}; + const auto gpu_addr{config.Address()}; + if (!gpu_addr) { + SetEmptyColorBuffer(index); + return {}; + } + + auto surface_view = GetSurface(gpu_addr, SurfaceParams::CreateForFramebuffer(system, index), + preserve_contents, true); + if (render_targets[index].target) + render_targets[index].target->MarkAsRenderTarget(false, NO_RT); + render_targets[index].target = surface_view.first; + render_targets[index].view = surface_view.second; + if (render_targets[index].target) + render_targets[index].target->MarkAsRenderTarget(true, static_cast<u32>(index)); + return surface_view.second; + } + + void MarkColorBufferInUse(std::size_t index) { + if (auto& render_target = render_targets[index].target) { + render_target->MarkAsModified(true, Tick()); + } + } + + void MarkDepthBufferInUse() { + if (depth_buffer.target) { + depth_buffer.target->MarkAsModified(true, Tick()); + } + } + + void SetEmptyDepthBuffer() { + if (depth_buffer.target == nullptr) { + return; + } + depth_buffer.target->MarkAsRenderTarget(false, NO_RT); + depth_buffer.target = nullptr; + depth_buffer.view = nullptr; + } + + void SetEmptyColorBuffer(std::size_t index) { + if (render_targets[index].target == nullptr) { + return; + } + render_targets[index].target->MarkAsRenderTarget(false, NO_RT); + render_targets[index].target = nullptr; + render_targets[index].view = nullptr; + } + + void DoFermiCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src_config, + const Tegra::Engines::Fermi2D::Regs::Surface& dst_config, + const Tegra::Engines::Fermi2D::Config& copy_config) { + std::lock_guard lock{mutex}; + std::pair<TSurface, TView> dst_surface = GetFermiSurface(dst_config); + std::pair<TSurface, TView> src_surface = GetFermiSurface(src_config); + ImageBlit(src_surface.second, dst_surface.second, copy_config); + dst_surface.first->MarkAsModified(true, Tick()); + } + + TSurface TryFindFramebufferSurface(const u8* host_ptr) { + const CacheAddr cache_addr = ToCacheAddr(host_ptr); + if (!cache_addr) { + return nullptr; + } + const CacheAddr page = cache_addr >> registry_page_bits; + std::vector<TSurface>& list = registry[page]; + for (auto& surface : list) { + if (surface->GetCacheAddr() == cache_addr) { + return surface; + } + } + return nullptr; + } + + u64 Tick() { + return ++ticks; + } + +protected: + TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer) + : system{system}, rasterizer{rasterizer} { + for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { + SetEmptyColorBuffer(i); + } + + SetEmptyDepthBuffer(); + staging_cache.SetSize(2); + + const auto make_siblings = [this](PixelFormat a, PixelFormat b) { + siblings_table[static_cast<std::size_t>(a)] = b; + siblings_table[static_cast<std::size_t>(b)] = a; + }; + std::fill(siblings_table.begin(), siblings_table.end(), PixelFormat::Invalid); + make_siblings(PixelFormat::Z16, PixelFormat::R16U); + make_siblings(PixelFormat::Z32F, PixelFormat::R32F); + make_siblings(PixelFormat::Z32FS8, PixelFormat::RG32F); + + sampled_textures.reserve(64); + } + + ~TextureCache() = default; + + virtual TSurface CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) = 0; + + virtual void ImageCopy(TSurface& src_surface, TSurface& dst_surface, + const CopyParams& copy_params) = 0; + + virtual void ImageBlit(TView& src_view, TView& dst_view, + const Tegra::Engines::Fermi2D::Config& copy_config) = 0; + + // Depending on the backend, a buffer copy can be slow as it means deoptimizing the texture + // and reading it from a sepparate buffer. + virtual void BufferCopy(TSurface& src_surface, TSurface& dst_surface) = 0; + + void ManageRenderTargetUnregister(TSurface& surface) { + auto& maxwell3d = system.GPU().Maxwell3D(); + const u32 index = surface->GetRenderTarget(); + if (index == DEPTH_RT) { + maxwell3d.dirty.depth_buffer = true; + } else { + maxwell3d.dirty.render_target[index] = true; + } + maxwell3d.dirty.render_settings = true; + } + + void Register(TSurface surface) { + const GPUVAddr gpu_addr = surface->GetGpuAddr(); + const CacheAddr cache_ptr = ToCacheAddr(system.GPU().MemoryManager().GetPointer(gpu_addr)); + const std::size_t size = surface->GetSizeInBytes(); + const std::optional<VAddr> cpu_addr = + system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); + if (!cache_ptr || !cpu_addr) { + LOG_CRITICAL(HW_GPU, "Failed to register surface with unmapped gpu_address 0x{:016x}", + gpu_addr); + return; + } + const bool continuous = system.GPU().MemoryManager().IsBlockContinuous(gpu_addr, size); + surface->MarkAsContinuous(continuous); + surface->SetCacheAddr(cache_ptr); + surface->SetCpuAddr(*cpu_addr); + RegisterInnerCache(surface); + surface->MarkAsRegistered(true); + rasterizer.UpdatePagesCachedCount(*cpu_addr, size, 1); + } + + void Unregister(TSurface surface) { + if (guard_render_targets && surface->IsProtected()) { + return; + } + if (!guard_render_targets && surface->IsRenderTarget()) { + ManageRenderTargetUnregister(surface); + } + const std::size_t size = surface->GetSizeInBytes(); + const VAddr cpu_addr = surface->GetCpuAddr(); + rasterizer.UpdatePagesCachedCount(cpu_addr, size, -1); + UnregisterInnerCache(surface); + surface->MarkAsRegistered(false); + ReserveSurface(surface->GetSurfaceParams(), surface); + } + + TSurface GetUncachedSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) { + if (const auto surface = TryGetReservedSurface(params); surface) { + surface->SetGpuAddr(gpu_addr); + return surface; + } + // No reserved surface available, create a new one and reserve it + auto new_surface{CreateSurface(gpu_addr, params)}; + return new_surface; + } + + std::pair<TSurface, TView> GetFermiSurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config) { + SurfaceParams params = SurfaceParams::CreateForFermiCopySurface(config); + const GPUVAddr gpu_addr = config.Address(); + return GetSurface(gpu_addr, params, true, false); + } + + Core::System& system; + +private: + enum class RecycleStrategy : u32 { + Ignore = 0, + Flush = 1, + BufferCopy = 3, + }; + + /** + * `PickStrategy` takes care of selecting a proper strategy to deal with a texture recycle. + * @param overlaps, the overlapping surfaces registered in the cache. + * @param params, the paremeters on the new surface. + * @param gpu_addr, the starting address of the new surface. + * @param untopological, tells the recycler that the texture has no way to match the overlaps + * due to topological reasons. + **/ + RecycleStrategy PickStrategy(std::vector<TSurface>& overlaps, const SurfaceParams& params, + const GPUVAddr gpu_addr, const MatchTopologyResult untopological) { + if (Settings::values.use_accurate_gpu_emulation) { + return RecycleStrategy::Flush; + } + // 3D Textures decision + if (params.block_depth > 1 || params.target == SurfaceTarget::Texture3D) { + return RecycleStrategy::Flush; + } + for (auto s : overlaps) { + const auto& s_params = s->GetSurfaceParams(); + if (s_params.block_depth > 1 || s_params.target == SurfaceTarget::Texture3D) { + return RecycleStrategy::Flush; + } + } + // Untopological decision + if (untopological == MatchTopologyResult::CompressUnmatch) { + return RecycleStrategy::Flush; + } + if (untopological == MatchTopologyResult::FullMatch && !params.is_tiled) { + return RecycleStrategy::Flush; + } + return RecycleStrategy::Ignore; + } + + /** + * `RecycleSurface` es a method we use to decide what to do with textures we can't resolve in + *the cache It has 2 implemented strategies: Ignore and Flush. Ignore just unregisters all the + *overlaps and loads the new texture. Flush, flushes all the overlaps into memory and loads the + *new surface from that data. + * @param overlaps, the overlapping surfaces registered in the cache. + * @param params, the paremeters on the new surface. + * @param gpu_addr, the starting address of the new surface. + * @param preserve_contents, tells if the new surface should be loaded from meory or left blank + * @param untopological, tells the recycler that the texture has no way to match the overlaps + * due to topological reasons. + **/ + std::pair<TSurface, TView> RecycleSurface(std::vector<TSurface>& overlaps, + const SurfaceParams& params, const GPUVAddr gpu_addr, + const bool preserve_contents, + const MatchTopologyResult untopological) { + const bool do_load = preserve_contents && Settings::values.use_accurate_gpu_emulation; + for (auto& surface : overlaps) { + Unregister(surface); + } + switch (PickStrategy(overlaps, params, gpu_addr, untopological)) { + case RecycleStrategy::Ignore: { + return InitializeSurface(gpu_addr, params, do_load); + } + case RecycleStrategy::Flush: { + std::sort(overlaps.begin(), overlaps.end(), + [](const TSurface& a, const TSurface& b) -> bool { + return a->GetModificationTick() < b->GetModificationTick(); + }); + for (auto& surface : overlaps) { + FlushSurface(surface); + } + return InitializeSurface(gpu_addr, params, preserve_contents); + } + case RecycleStrategy::BufferCopy: { + auto new_surface = GetUncachedSurface(gpu_addr, params); + BufferCopy(overlaps[0], new_surface); + return {new_surface, new_surface->GetMainView()}; + } + default: { + UNIMPLEMENTED_MSG("Unimplemented Texture Cache Recycling Strategy!"); + return InitializeSurface(gpu_addr, params, do_load); + } + } + } + + /** + * `RebuildSurface` this method takes a single surface and recreates into another that + * may differ in format, target or width alingment. + * @param current_surface, the registered surface in the cache which we want to convert. + * @param params, the new surface params which we'll use to recreate the surface. + **/ + std::pair<TSurface, TView> RebuildSurface(TSurface current_surface, const SurfaceParams& params, + bool is_render) { + const auto gpu_addr = current_surface->GetGpuAddr(); + const auto& cr_params = current_surface->GetSurfaceParams(); + TSurface new_surface; + if (cr_params.pixel_format != params.pixel_format && !is_render && + GetSiblingFormat(cr_params.pixel_format) == params.pixel_format) { + SurfaceParams new_params = params; + new_params.pixel_format = cr_params.pixel_format; + new_params.component_type = cr_params.component_type; + new_params.type = cr_params.type; + new_surface = GetUncachedSurface(gpu_addr, new_params); + } else { + new_surface = GetUncachedSurface(gpu_addr, params); + } + const auto& final_params = new_surface->GetSurfaceParams(); + if (cr_params.type != final_params.type || + (cr_params.component_type != final_params.component_type)) { + BufferCopy(current_surface, new_surface); + } else { + std::vector<CopyParams> bricks = current_surface->BreakDown(final_params); + for (auto& brick : bricks) { + ImageCopy(current_surface, new_surface, brick); + } + } + Unregister(current_surface); + Register(new_surface); + new_surface->MarkAsModified(current_surface->IsModified(), Tick()); + return {new_surface, new_surface->GetMainView()}; + } + + /** + * `ManageStructuralMatch` this method takes a single surface and checks with the new surface's + * params if it's an exact match, we return the main view of the registered surface. If it's + * formats don't match, we rebuild the surface. We call this last method a `Mirage`. If formats + * match but the targets don't, we create an overview View of the registered surface. + * @param current_surface, the registered surface in the cache which we want to convert. + * @param params, the new surface params which we want to check. + **/ + std::pair<TSurface, TView> ManageStructuralMatch(TSurface current_surface, + const SurfaceParams& params, bool is_render) { + const bool is_mirage = !current_surface->MatchFormat(params.pixel_format); + const bool matches_target = current_surface->MatchTarget(params.target); + const auto match_check = [&]() -> std::pair<TSurface, TView> { + if (matches_target) { + return {current_surface, current_surface->GetMainView()}; + } + return {current_surface, current_surface->EmplaceOverview(params)}; + }; + if (!is_mirage) { + return match_check(); + } + if (!is_render && GetSiblingFormat(current_surface->GetFormat()) == params.pixel_format) { + return match_check(); + } + return RebuildSurface(current_surface, params, is_render); + } + + /** + * `TryReconstructSurface` unlike `RebuildSurface` where we know the registered surface + * matches the candidate in some way, we got no guarantess here. We try to see if the overlaps + * are sublayers/mipmaps of the new surface, if they all match we end up recreating a surface + * for them, else we return nothing. + * @param overlaps, the overlapping surfaces registered in the cache. + * @param params, the paremeters on the new surface. + * @param gpu_addr, the starting address of the new surface. + **/ + std::optional<std::pair<TSurface, TView>> TryReconstructSurface(std::vector<TSurface>& overlaps, + const SurfaceParams& params, + const GPUVAddr gpu_addr) { + if (params.target == SurfaceTarget::Texture3D) { + return {}; + } + bool modified = false; + TSurface new_surface = GetUncachedSurface(gpu_addr, params); + u32 passed_tests = 0; + for (auto& surface : overlaps) { + const SurfaceParams& src_params = surface->GetSurfaceParams(); + if (src_params.is_layered || src_params.num_levels > 1) { + // We send this cases to recycle as they are more complex to handle + return {}; + } + const std::size_t candidate_size = surface->GetSizeInBytes(); + auto mipmap_layer{new_surface->GetLayerMipmap(surface->GetGpuAddr())}; + if (!mipmap_layer) { + continue; + } + const auto [layer, mipmap] = *mipmap_layer; + if (new_surface->GetMipmapSize(mipmap) != candidate_size) { + continue; + } + modified |= surface->IsModified(); + // Now we got all the data set up + const u32 width = SurfaceParams::IntersectWidth(src_params, params, 0, mipmap); + const u32 height = SurfaceParams::IntersectHeight(src_params, params, 0, mipmap); + const CopyParams copy_params(0, 0, 0, 0, 0, layer, 0, mipmap, width, height, 1); + passed_tests++; + ImageCopy(surface, new_surface, copy_params); + } + if (passed_tests == 0) { + return {}; + // In Accurate GPU all tests should pass, else we recycle + } else if (Settings::values.use_accurate_gpu_emulation && passed_tests != overlaps.size()) { + return {}; + } + for (auto surface : overlaps) { + Unregister(surface); + } + new_surface->MarkAsModified(modified, Tick()); + Register(new_surface); + return {{new_surface, new_surface->GetMainView()}}; + } + + /** + * `GetSurface` gets the starting address and parameters of a candidate surface and tries + * to find a matching surface within the cache. This is done in 3 big steps. The first is to + * check the 1st Level Cache in order to find an exact match, if we fail, we move to step 2. + * Step 2 is checking if there are any overlaps at all, if none, we just load the texture from + * memory else we move to step 3. Step 3 consists on figuring the relationship between the + * candidate texture and the overlaps. We divide the scenarios depending if there's 1 or many + * overlaps. If there's many, we just try to reconstruct a new surface out of them based on the + * candidate's parameters, if we fail, we recycle. When there's only 1 overlap then we have to + * check if the candidate is a view (layer/mipmap) of the overlap or if the registered surface + * is a mipmap/layer of the candidate. In this last case we reconstruct a new surface. + * @param gpu_addr, the starting address of the candidate surface. + * @param params, the paremeters on the candidate surface. + * @param preserve_contents, tells if the new surface should be loaded from meory or left blank. + **/ + std::pair<TSurface, TView> GetSurface(const GPUVAddr gpu_addr, const SurfaceParams& params, + bool preserve_contents, bool is_render) { + const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)}; + const auto cache_addr{ToCacheAddr(host_ptr)}; + + // Step 0: guarantee a valid surface + if (!cache_addr) { + // Return a null surface if it's invalid + SurfaceParams new_params = params; + new_params.width = 1; + new_params.height = 1; + new_params.depth = 1; + new_params.block_height = 0; + new_params.block_depth = 0; + return InitializeSurface(gpu_addr, new_params, false); + } + + // Step 1 + // Check Level 1 Cache for a fast structural match. If candidate surface + // matches at certain level we are pretty much done. + if (const auto iter = l1_cache.find(cache_addr); iter != l1_cache.end()) { + TSurface& current_surface = iter->second; + const auto topological_result = current_surface->MatchesTopology(params); + if (topological_result != MatchTopologyResult::FullMatch) { + std::vector<TSurface> overlaps{current_surface}; + return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, + topological_result); + } + const auto struct_result = current_surface->MatchesStructure(params); + if (struct_result != MatchStructureResult::None && + (params.target != SurfaceTarget::Texture3D || + current_surface->MatchTarget(params.target))) { + if (struct_result == MatchStructureResult::FullMatch) { + return ManageStructuralMatch(current_surface, params, is_render); + } else { + return RebuildSurface(current_surface, params, is_render); + } + } + } + + // Step 2 + // Obtain all possible overlaps in the memory region + const std::size_t candidate_size = params.GetGuestSizeInBytes(); + auto overlaps{GetSurfacesInRegion(cache_addr, candidate_size)}; + + // If none are found, we are done. we just load the surface and create it. + if (overlaps.empty()) { + return InitializeSurface(gpu_addr, params, preserve_contents); + } + + // Step 3 + // Now we need to figure the relationship between the texture and its overlaps + // we do a topological test to ensure we can find some relationship. If it fails + // inmediatly recycle the texture + for (const auto& surface : overlaps) { + const auto topological_result = surface->MatchesTopology(params); + if (topological_result != MatchTopologyResult::FullMatch) { + return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, + topological_result); + } + } + + // Split cases between 1 overlap or many. + if (overlaps.size() == 1) { + TSurface current_surface = overlaps[0]; + // First check if the surface is within the overlap. If not, it means + // two things either the candidate surface is a supertexture of the overlap + // or they don't match in any known way. + if (!current_surface->IsInside(gpu_addr, gpu_addr + candidate_size)) { + if (current_surface->GetGpuAddr() == gpu_addr) { + std::optional<std::pair<TSurface, TView>> view = + TryReconstructSurface(overlaps, params, gpu_addr); + if (view) { + return *view; + } + } + return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, + MatchTopologyResult::FullMatch); + } + // Now we check if the candidate is a mipmap/layer of the overlap + std::optional<TView> view = + current_surface->EmplaceView(params, gpu_addr, candidate_size); + if (view) { + const bool is_mirage = !current_surface->MatchFormat(params.pixel_format); + if (is_mirage) { + // On a mirage view, we need to recreate the surface under this new view + // and then obtain a view again. + SurfaceParams new_params = current_surface->GetSurfaceParams(); + const u32 wh = SurfaceParams::ConvertWidth( + new_params.width, new_params.pixel_format, params.pixel_format); + const u32 hh = SurfaceParams::ConvertHeight( + new_params.height, new_params.pixel_format, params.pixel_format); + new_params.width = wh; + new_params.height = hh; + new_params.pixel_format = params.pixel_format; + std::pair<TSurface, TView> pair = + RebuildSurface(current_surface, new_params, is_render); + std::optional<TView> mirage_view = + pair.first->EmplaceView(params, gpu_addr, candidate_size); + if (mirage_view) + return {pair.first, *mirage_view}; + return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, + MatchTopologyResult::FullMatch); + } + return {current_surface, *view}; + } + } else { + // If there are many overlaps, odds are they are subtextures of the candidate + // surface. We try to construct a new surface based on the candidate parameters, + // using the overlaps. If a single overlap fails, this will fail. + std::optional<std::pair<TSurface, TView>> view = + TryReconstructSurface(overlaps, params, gpu_addr); + if (view) { + return *view; + } + } + // We failed all the tests, recycle the overlaps into a new texture. + return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, + MatchTopologyResult::FullMatch); + } + + std::pair<TSurface, TView> InitializeSurface(GPUVAddr gpu_addr, const SurfaceParams& params, + bool preserve_contents) { + auto new_surface{GetUncachedSurface(gpu_addr, params)}; + Register(new_surface); + if (preserve_contents) { + LoadSurface(new_surface); + } + return {new_surface, new_surface->GetMainView()}; + } + + void LoadSurface(const TSurface& surface) { + staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes()); + surface->LoadBuffer(system.GPU().MemoryManager(), staging_cache); + surface->UploadTexture(staging_cache.GetBuffer(0)); + surface->MarkAsModified(false, Tick()); + } + + void FlushSurface(const TSurface& surface) { + if (!surface->IsModified()) { + return; + } + staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes()); + surface->DownloadTexture(staging_cache.GetBuffer(0)); + surface->FlushBuffer(system.GPU().MemoryManager(), staging_cache); + surface->MarkAsModified(false, Tick()); + } + + void RegisterInnerCache(TSurface& surface) { + const CacheAddr cache_addr = surface->GetCacheAddr(); + CacheAddr start = cache_addr >> registry_page_bits; + const CacheAddr end = (surface->GetCacheAddrEnd() - 1) >> registry_page_bits; + l1_cache[cache_addr] = surface; + while (start <= end) { + registry[start].push_back(surface); + start++; + } + } + + void UnregisterInnerCache(TSurface& surface) { + const CacheAddr cache_addr = surface->GetCacheAddr(); + CacheAddr start = cache_addr >> registry_page_bits; + const CacheAddr end = (surface->GetCacheAddrEnd() - 1) >> registry_page_bits; + l1_cache.erase(cache_addr); + while (start <= end) { + auto& reg{registry[start]}; + reg.erase(std::find(reg.begin(), reg.end(), surface)); + start++; + } + } + + std::vector<TSurface> GetSurfacesInRegion(const CacheAddr cache_addr, const std::size_t size) { + if (size == 0) { + return {}; + } + const CacheAddr cache_addr_end = cache_addr + size; + CacheAddr start = cache_addr >> registry_page_bits; + const CacheAddr end = (cache_addr_end - 1) >> registry_page_bits; + std::vector<TSurface> surfaces; + while (start <= end) { + std::vector<TSurface>& list = registry[start]; + for (auto& surface : list) { + if (!surface->IsPicked() && surface->Overlaps(cache_addr, cache_addr_end)) { + surface->MarkAsPicked(true); + surfaces.push_back(surface); + } + } + start++; + } + for (auto& surface : surfaces) { + surface->MarkAsPicked(false); + } + return surfaces; + } + + void ReserveSurface(const SurfaceParams& params, TSurface surface) { + surface_reserve[params].push_back(std::move(surface)); + } + + TSurface TryGetReservedSurface(const SurfaceParams& params) { + auto search{surface_reserve.find(params)}; + if (search == surface_reserve.end()) { + return {}; + } + for (auto& surface : search->second) { + if (!surface->IsRegistered()) { + return surface; + } + } + return {}; + } + + constexpr PixelFormat GetSiblingFormat(PixelFormat format) const { + return siblings_table[static_cast<std::size_t>(format)]; + } + + struct FramebufferTargetInfo { + TSurface target; + TView view; + }; + + VideoCore::RasterizerInterface& rasterizer; + + u64 ticks{}; + + // Guards the cache for protection conflicts. + bool guard_render_targets{}; + bool guard_samplers{}; + + // The siblings table is for formats that can inter exchange with one another + // without causing issues. This is only valid when a conflict occurs on a non + // rendering use. + std::array<PixelFormat, static_cast<std::size_t>(PixelFormat::Max)> siblings_table; + + // The internal Cache is different for the Texture Cache. It's based on buckets + // of 1MB. This fits better for the purpose of this cache as textures are normaly + // large in size. + static constexpr u64 registry_page_bits{20}; + static constexpr u64 registry_page_size{1 << registry_page_bits}; + std::unordered_map<CacheAddr, std::vector<TSurface>> registry; + + static constexpr u32 DEPTH_RT = 8; + static constexpr u32 NO_RT = 0xFFFFFFFF; + + // The L1 Cache is used for fast texture lookup before checking the overlaps + // This avoids calculating size and other stuffs. + std::unordered_map<CacheAddr, TSurface> l1_cache; + + /// 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<SurfaceParams, std::vector<TSurface>> surface_reserve; + std::array<FramebufferTargetInfo, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> + render_targets; + FramebufferTargetInfo depth_buffer; + + std::vector<TSurface> sampled_textures; + + StagingCache staging_cache; + std::recursive_mutex mutex; +}; + +} // namespace VideoCommon diff --git a/src/video_core/textures/convert.cpp b/src/video_core/textures/convert.cpp index 82050bd51..f3efa7eb0 100644 --- a/src/video_core/textures/convert.cpp +++ b/src/video_core/textures/convert.cpp @@ -62,19 +62,19 @@ static void ConvertZ24S8ToS8Z24(u8* data, u32 width, u32 height) { SwapS8Z24ToZ24S8<true>(data, width, height); } -void ConvertFromGuestToHost(u8* data, PixelFormat pixel_format, u32 width, u32 height, u32 depth, - bool convert_astc, bool convert_s8z24) { +void ConvertFromGuestToHost(u8* in_data, u8* out_data, PixelFormat pixel_format, u32 width, + u32 height, u32 depth, bool convert_astc, bool convert_s8z24) { if (convert_astc && IsPixelFormatASTC(pixel_format)) { // Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC. u32 block_width{}; u32 block_height{}; std::tie(block_width, block_height) = GetASTCBlockSize(pixel_format); - const std::vector<u8> rgba8_data = - Tegra::Texture::ASTC::Decompress(data, width, height, depth, block_width, block_height); - std::copy(rgba8_data.begin(), rgba8_data.end(), data); + const std::vector<u8> rgba8_data = Tegra::Texture::ASTC::Decompress( + in_data, width, height, depth, block_width, block_height); + std::copy(rgba8_data.begin(), rgba8_data.end(), out_data); } else if (convert_s8z24 && pixel_format == PixelFormat::S8Z24) { - Tegra::Texture::ConvertS8Z24ToZ24S8(data, width, height); + Tegra::Texture::ConvertS8Z24ToZ24S8(in_data, width, height); } } @@ -90,4 +90,4 @@ void ConvertFromHostToGuest(u8* data, PixelFormat pixel_format, u32 width, u32 h } } -} // namespace Tegra::Texture
\ No newline at end of file +} // namespace Tegra::Texture diff --git a/src/video_core/textures/convert.h b/src/video_core/textures/convert.h index 12542e71c..d5d6c77bb 100644 --- a/src/video_core/textures/convert.h +++ b/src/video_core/textures/convert.h @@ -12,10 +12,11 @@ enum class PixelFormat; namespace Tegra::Texture { -void ConvertFromGuestToHost(u8* data, VideoCore::Surface::PixelFormat pixel_format, u32 width, - u32 height, u32 depth, bool convert_astc, bool convert_s8z24); +void ConvertFromGuestToHost(u8* in_data, u8* out_data, VideoCore::Surface::PixelFormat pixel_format, + u32 width, u32 height, u32 depth, bool convert_astc, + bool convert_s8z24); void ConvertFromHostToGuest(u8* data, VideoCore::Surface::PixelFormat pixel_format, u32 width, u32 height, u32 depth, bool convert_astc, bool convert_s8z24); -} // namespace Tegra::Texture
\ No newline at end of file +} // namespace Tegra::Texture diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 217805386..7df5f1452 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -36,10 +36,16 @@ struct alignas(64) SwizzleTable { std::array<std::array<u16, M>, N> values{}; }; -constexpr u32 gob_size_x = 64; -constexpr u32 gob_size_y = 8; -constexpr u32 gob_size_z = 1; -constexpr u32 gob_size = gob_size_x * gob_size_y * gob_size_z; +constexpr u32 gob_size_x_shift = 6; +constexpr u32 gob_size_y_shift = 3; +constexpr u32 gob_size_z_shift = 0; +constexpr u32 gob_size_shift = gob_size_x_shift + gob_size_y_shift + gob_size_z_shift; + +constexpr u32 gob_size_x = 1U << gob_size_x_shift; +constexpr u32 gob_size_y = 1U << gob_size_y_shift; +constexpr u32 gob_size_z = 1U << gob_size_z_shift; +constexpr u32 gob_size = 1U << gob_size_shift; + constexpr u32 fast_swizzle_align = 16; constexpr auto legacy_swizzle_table = SwizzleTable<gob_size_y, gob_size_x, gob_size_z>(); @@ -171,14 +177,16 @@ void SwizzledData(u8* const swizzled_data, u8* const unswizzled_data, const bool void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel, u32 out_bytes_per_pixel, u8* const swizzled_data, u8* const unswizzled_data, bool unswizzle, u32 block_height, u32 block_depth, u32 width_spacing) { + const u32 block_height_size{1U << block_height}; + const u32 block_depth_size{1U << block_depth}; if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % fast_swizzle_align == 0) { SwizzledData<true>(swizzled_data, unswizzled_data, unswizzle, width, height, depth, - bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth, - width_spacing); + bytes_per_pixel, out_bytes_per_pixel, block_height_size, + block_depth_size, width_spacing); } else { SwizzledData<false>(swizzled_data, unswizzled_data, unswizzle, width, height, depth, - bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth, - width_spacing); + bytes_per_pixel, out_bytes_per_pixel, block_height_size, + block_depth_size, width_spacing); } } @@ -248,18 +256,22 @@ std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, } void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, - u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height) { + u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, + u32 block_height_bit, u32 offset_x, u32 offset_y) { + const u32 block_height = 1U << block_height_bit; const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + (gob_size_x - 1)) / gob_size_x}; for (u32 line = 0; line < subrect_height; ++line) { + const u32 dst_y = line + offset_y; const u32 gob_address_y = - (line / (gob_size_y * block_height)) * gob_size * block_height * image_width_in_gobs + - ((line % (gob_size_y * block_height)) / gob_size_y) * gob_size; - const auto& table = legacy_swizzle_table[line % gob_size_y]; + (dst_y / (gob_size_y * block_height)) * gob_size * block_height * image_width_in_gobs + + ((dst_y % (gob_size_y * block_height)) / gob_size_y) * gob_size; + const auto& table = legacy_swizzle_table[dst_y % gob_size_y]; for (u32 x = 0; x < subrect_width; ++x) { + const u32 dst_x = x + offset_x; const u32 gob_address = - gob_address_y + (x * bytes_per_pixel / gob_size_x) * gob_size * block_height; - const u32 swizzled_offset = gob_address + table[(x * bytes_per_pixel) % gob_size_x]; + gob_address_y + (dst_x * bytes_per_pixel / gob_size_x) * gob_size * block_height; + const u32 swizzled_offset = gob_address + table[(dst_x * bytes_per_pixel) % gob_size_x]; u8* source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel; u8* dest_addr = swizzled_data + swizzled_offset; @@ -269,8 +281,9 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 } void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width, - u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height, - u32 offset_x, u32 offset_y) { + u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, + u32 block_height_bit, u32 offset_x, u32 offset_y) { + const u32 block_height = 1U << block_height_bit; for (u32 line = 0; line < subrect_height; ++line) { const u32 y2 = line + offset_y; const u32 gob_address_y = (y2 / (gob_size_y * block_height)) * gob_size * block_height + @@ -289,8 +302,9 @@ void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 } void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 dst_y, - const u32 block_height, const std::size_t copy_size, const u8* source_data, + const u32 block_height_bit, const std::size_t copy_size, const u8* source_data, u8* swizzle_data) { + const u32 block_height = 1U << block_height_bit; const u32 image_width_in_gobs{(width + gob_size_x - 1) / gob_size_x}; std::size_t count = 0; for (std::size_t y = dst_y; y < height && count < copy_size; ++y) { @@ -356,9 +370,9 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth) { if (tiled) { - const u32 aligned_width = Common::AlignUp(width * bytes_per_pixel, gob_size_x); - const u32 aligned_height = Common::AlignUp(height, gob_size_y * block_height); - const u32 aligned_depth = Common::AlignUp(depth, gob_size_z * block_depth); + const u32 aligned_width = Common::AlignBits(width * bytes_per_pixel, gob_size_x_shift); + const u32 aligned_height = Common::AlignBits(height, gob_size_y_shift + block_height); + const u32 aligned_depth = Common::AlignBits(depth, gob_size_z_shift + block_depth); return aligned_width * aligned_height * aligned_depth; } else { return width * height * depth * bytes_per_pixel; diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index e072d8401..f1e3952bc 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -12,8 +12,8 @@ namespace Tegra::Texture { // GOBSize constant. Calculated by 64 bytes in x multiplied by 8 y coords, represents // an small rect of (64/bytes_per_pixel)X8. -inline std::size_t GetGOBSize() { - return 512; +inline std::size_t GetGOBSizeShift() { + return 9; } /// Unswizzles a swizzled texture without changing its format. @@ -44,7 +44,8 @@ std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height /// Copies an untiled subrectangle into a tiled surface. void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, - u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height); + u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height, + u32 offset_x, u32 offset_y); /// Copies a tiled subrectangle into a linear surface. void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width, diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 219bfd559..e36bc2c04 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -52,9 +52,9 @@ enum class TextureFormat : u32 { DXT45 = 0x26, DXN1 = 0x27, DXN2 = 0x28, - Z24S8 = 0x29, + S8Z24 = 0x29, X8Z24 = 0x2a, - S8Z24 = 0x2b, + Z24S8 = 0x2b, X4V4Z24__COV4R4V = 0x2c, X4V4Z24__COV8R8V = 0x2d, V8Z24__COV4R12V = 0x2e, @@ -172,12 +172,16 @@ struct TICEntry { BitField<26, 1, u32> use_header_opt_control; BitField<27, 1, u32> depth_texture; BitField<28, 4, u32> max_mip_level; + + BitField<0, 16, u32> buffer_high_width_minus_one; }; union { BitField<0, 16, u32> width_minus_1; BitField<22, 1, u32> srgb_conversion; BitField<23, 4, TextureType> texture_type; BitField<29, 3, u32> border_size; + + BitField<0, 16, u32> buffer_low_width_minus_one; }; union { BitField<0, 16, u32> height_minus_1; @@ -206,7 +210,10 @@ struct TICEntry { } u32 Width() const { - return width_minus_1 + 1; + if (header_version != TICHeaderVersion::OneDBuffer) { + return width_minus_1 + 1; + } + return ((buffer_high_width_minus_one << 16) | buffer_low_width_minus_one) + 1; } u32 Height() const { @@ -219,20 +226,17 @@ struct TICEntry { u32 BlockWidth() const { ASSERT(IsTiled()); - // The block height is stored in log2 format. - return 1 << block_width; + return block_width; } u32 BlockHeight() const { ASSERT(IsTiled()); - // The block height is stored in log2 format. - return 1 << block_height; + return block_height; } u32 BlockDepth() const { ASSERT(IsTiled()); - // The block height is stored in log2 format. - return 1 << block_depth; + return block_depth; } bool IsTiled() const { @@ -240,6 +244,15 @@ struct TICEntry { header_version == TICHeaderVersion::BlockLinearColorKey; } + bool IsLineal() const { + return header_version == TICHeaderVersion::Pitch || + header_version == TICHeaderVersion::PitchColorKey; + } + + bool IsBuffer() const { + return header_version == TICHeaderVersion::OneDBuffer; + } + bool IsSrgbConversionEnabled() const { return srgb_conversion != 0; } diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 3dc0e47d0..dc6fa07fc 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -1,5 +1,6 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) @@ -7,6 +8,7 @@ add_executable(yuzu Info.plist about_dialog.cpp about_dialog.h + aboutdialog.ui applets/error.cpp applets/error.h applets/profile_select.cpp @@ -17,42 +19,62 @@ add_executable(yuzu applets/web_browser.h bootmanager.cpp bootmanager.h + compatdb.ui compatibility_list.cpp compatibility_list.h configuration/config.cpp configuration/config.h + configuration/configure.ui configuration/configure_audio.cpp configuration/configure_audio.h + configuration/configure_audio.ui configuration/configure_debug.cpp configuration/configure_debug.h + configuration/configure_debug.ui configuration/configure_dialog.cpp configuration/configure_dialog.h + configuration/configure_filesystem.cpp + configuration/configure_filesystem.h + configuration/configure_filesystem.ui configuration/configure_gamelist.cpp configuration/configure_gamelist.h + configuration/configure_gamelist.ui configuration/configure_general.cpp configuration/configure_general.h + configuration/configure_general.ui configuration/configure_graphics.cpp configuration/configure_graphics.h + configuration/configure_graphics.ui configuration/configure_hotkeys.cpp configuration/configure_hotkeys.h + configuration/configure_hotkeys.ui configuration/configure_input.cpp configuration/configure_input.h + configuration/configure_input.ui configuration/configure_input_player.cpp configuration/configure_input_player.h + configuration/configure_input_player.ui configuration/configure_input_simple.cpp configuration/configure_input_simple.h + configuration/configure_input_simple.ui configuration/configure_mouse_advanced.cpp configuration/configure_mouse_advanced.h + configuration/configure_mouse_advanced.ui + configuration/configure_per_general.cpp + configuration/configure_per_general.h + configuration/configure_per_general.ui configuration/configure_profile_manager.cpp configuration/configure_profile_manager.h + configuration/configure_profile_manager.ui configuration/configure_system.cpp configuration/configure_system.h - configuration/configure_per_general.cpp - configuration/configure_per_general.h + configuration/configure_system.ui configuration/configure_touchscreen_advanced.cpp configuration/configure_touchscreen_advanced.h + configuration/configure_touchscreen_advanced.ui configuration/configure_web.cpp configuration/configure_web.h + configuration/configure_web.ui debugger/graphics/graphics_breakpoint_observer.cpp debugger/graphics/graphics_breakpoint_observer.h debugger/graphics/graphics_breakpoints.cpp @@ -72,12 +94,14 @@ add_executable(yuzu game_list_worker.h loading_screen.cpp loading_screen.h + loading_screen.ui hotkeys.cpp hotkeys.h main.cpp main.h - ui_settings.cpp - ui_settings.h + main.ui + uisettings.cpp + uisettings.h util/limitable_input_dialog.cpp util/limitable_input_dialog.h util/sequence_dialog/sequence_dialog.cpp @@ -89,44 +113,18 @@ add_executable(yuzu yuzu.rc ) -set(UIS - aboutdialog.ui - configuration/configure.ui - configuration/configure_audio.ui - configuration/configure_debug.ui - configuration/configure_gamelist.ui - configuration/configure_general.ui - configuration/configure_graphics.ui - configuration/configure_hotkeys.ui - configuration/configure_input.ui - configuration/configure_input_player.ui - configuration/configure_input_simple.ui - configuration/configure_mouse_advanced.ui - configuration/configure_per_general.ui - configuration/configure_profile_manager.ui - configuration/configure_system.ui - configuration/configure_touchscreen_advanced.ui - configuration/configure_web.ui - compatdb.ui - loading_screen.ui - main.ui -) - file(GLOB COMPAT_LIST ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*) file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*) -qt5_wrap_ui(UI_HDRS ${UIS}) target_sources(yuzu PRIVATE ${COMPAT_LIST} ${ICONS} ${THEMES} - ${UI_HDRS} - ${UIS} ) if (APPLE) diff --git a/src/yuzu/applets/web_browser.cpp b/src/yuzu/applets/web_browser.cpp index ac80b2fa2..33f1c385d 100644 --- a/src/yuzu/applets/web_browser.cpp +++ b/src/yuzu/applets/web_browser.cpp @@ -87,8 +87,8 @@ QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { QtWebBrowser::~QtWebBrowser() = default; -void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, - std::function<void()> finished_callback) { +void QtWebBrowser::OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback, + std::function<void()> finished_callback) { this->unpack_romfs_callback = std::move(unpack_romfs_callback); this->finished_callback = std::move(finished_callback); diff --git a/src/yuzu/applets/web_browser.h b/src/yuzu/applets/web_browser.h index 1a3d67353..b38437e46 100644 --- a/src/yuzu/applets/web_browser.h +++ b/src/yuzu/applets/web_browser.h @@ -37,8 +37,8 @@ public: explicit QtWebBrowser(GMainWindow& main_window); ~QtWebBrowser() 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; signals: void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 10e5c5c38..92d9fb161 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -11,7 +11,7 @@ #include "core/hle/service/hid/controllers/npad.h" #include "input_common/main.h" #include "yuzu/configuration/config.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" Config::Config() { // TODO: Don't hardcode the path; let the frontend decide where to put the config files. @@ -436,7 +436,6 @@ void Config::ReadControlValues() { void Config::ReadCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); - Settings::values.use_cpu_jit = ReadSetting(QStringLiteral("use_cpu_jit"), true).toBool(); Settings::values.use_multi_core = ReadSetting(QStringLiteral("use_multi_core"), false).toBool(); qt_config->endGroup(); @@ -460,6 +459,49 @@ void Config::ReadDataStorageValues() { QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))) .toString() .toStdString()); + FileUtil::GetUserPath( + FileUtil::UserPath::LoadDir, + qt_config + ->value(QStringLiteral("load_directory"), + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))) + .toString() + .toStdString()); + FileUtil::GetUserPath( + FileUtil::UserPath::DumpDir, + qt_config + ->value(QStringLiteral("dump_directory"), + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))) + .toString() + .toStdString()); + FileUtil::GetUserPath( + FileUtil::UserPath::CacheDir, + qt_config + ->value(QStringLiteral("cache_directory"), + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))) + .toString() + .toStdString()); + Settings::values.gamecard_inserted = + ReadSetting(QStringLiteral("gamecard_inserted"), false).toBool(); + Settings::values.gamecard_current_game = + ReadSetting(QStringLiteral("gamecard_current_game"), false).toBool(); + Settings::values.gamecard_path = + ReadSetting(QStringLiteral("gamecard_path"), QStringLiteral("")).toString().toStdString(); + Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>( + ReadSetting(QStringLiteral("nand_total_size"), + QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB))) + .toULongLong()); + Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>( + ReadSetting(QStringLiteral("nand_user_size"), + QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB))) + .toULongLong()); + Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>( + ReadSetting(QStringLiteral("nand_system_size"), + QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB))) + .toULongLong()); + Settings::values.sdmc_size = static_cast<Settings::SDMCSize>( + ReadSetting(QStringLiteral("sdmc_size"), + QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB))) + .toULongLong()); qt_config->endGroup(); } @@ -467,12 +509,18 @@ void Config::ReadDataStorageValues() { void Config::ReadDebuggingValues() { qt_config->beginGroup(QStringLiteral("Debugging")); + // Intentionally not using the QT default setting as this is intended to be changed in the ini + Settings::values.record_frame_times = + qt_config->value(QStringLiteral("record_frame_times"), false).toBool(); Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool(); Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt(); Settings::values.program_args = ReadSetting(QStringLiteral("program_args"), QStringLiteral("")).toString().toStdString(); Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool(); Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool(); + Settings::values.reporting_services = + ReadSetting(QStringLiteral("reporting_services"), false).toBool(); + Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool(); qt_config->endGroup(); } @@ -514,10 +562,38 @@ void Config::ReadPathValues() { UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString(); UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString(); - UISettings::values.game_directory_path = + UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString(); + UISettings::values.game_dir_deprecated = ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); - UISettings::values.game_directory_deepscan = + UISettings::values.game_dir_deprecated_deepscan = ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool(); + const int gamedirs_size = qt_config->beginReadArray(QStringLiteral("gamedirs")); + for (int i = 0; i < gamedirs_size; ++i) { + qt_config->setArrayIndex(i); + UISettings::GameDir game_dir; + game_dir.path = ReadSetting(QStringLiteral("path")).toString(); + game_dir.deep_scan = ReadSetting(QStringLiteral("deep_scan"), false).toBool(); + game_dir.expanded = ReadSetting(QStringLiteral("expanded"), true).toBool(); + UISettings::values.game_dirs.append(game_dir); + } + qt_config->endArray(); + // create NAND and SD card directories if empty, these are not removable through the UI, + // also carries over old game list settings if present + if (UISettings::values.game_dirs.isEmpty()) { + UISettings::GameDir game_dir; + game_dir.path = QStringLiteral("SDMC"); + game_dir.expanded = true; + UISettings::values.game_dirs.append(game_dir); + game_dir.path = QStringLiteral("UserNAND"); + UISettings::values.game_dirs.append(game_dir); + game_dir.path = QStringLiteral("SysNAND"); + UISettings::values.game_dirs.append(game_dir); + if (UISettings::values.game_dir_deprecated != QStringLiteral(".")) { + game_dir.path = UISettings::values.game_dir_deprecated; + game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan; + UISettings::values.game_dirs.append(game_dir); + } + } UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); qt_config->endGroup(); @@ -691,7 +767,7 @@ void Config::ReadValues() { ReadDataStorageValues(); ReadSystemValues(); ReadMiscellaneousValues(); - ReadDebugValues(); + ReadDebuggingValues(); ReadWebServiceValues(); ReadDisabledAddOnValues(); ReadUIValues(); @@ -827,7 +903,6 @@ void Config::SaveControlValues() { void Config::SaveCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); - WriteSetting(QStringLiteral("use_cpu_jit"), Settings::values.use_cpu_jit, true); WriteSetting(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false); qt_config->endGroup(); @@ -843,19 +918,47 @@ void Config::SaveDataStorageValues() { WriteSetting(QStringLiteral("sdmc_directory"), QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)), QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); - + WriteSetting(QStringLiteral("load_directory"), + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir)), + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))); + WriteSetting(QStringLiteral("dump_directory"), + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir)), + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))); + WriteSetting(QStringLiteral("cache_directory"), + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir)), + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))); + WriteSetting(QStringLiteral("gamecard_inserted"), Settings::values.gamecard_inserted, false); + WriteSetting(QStringLiteral("gamecard_current_game"), Settings::values.gamecard_current_game, + false); + WriteSetting(QStringLiteral("gamecard_path"), + QString::fromStdString(Settings::values.gamecard_path), QStringLiteral("")); + WriteSetting(QStringLiteral("nand_total_size"), + QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_total_size)), + QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDTotalSize::S29_1GB))); + WriteSetting(QStringLiteral("nand_user_size"), + QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_user_size)), + QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDUserSize::S26GB))); + WriteSetting(QStringLiteral("nand_system_size"), + QVariant::fromValue<u64>(static_cast<u64>(Settings::values.nand_system_size)), + QVariant::fromValue<u64>(static_cast<u64>(Settings::NANDSystemSize::S2_5GB))); + WriteSetting(QStringLiteral("sdmc_size"), + QVariant::fromValue<u64>(static_cast<u64>(Settings::values.sdmc_size)), + QVariant::fromValue<u64>(static_cast<u64>(Settings::SDMCSize::S16GB))); qt_config->endGroup(); } void Config::SaveDebuggingValues() { qt_config->beginGroup(QStringLiteral("Debugging")); + // Intentionally not using the QT default setting as this is intended to be changed in the ini + qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times); WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false); WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689); WriteSetting(QStringLiteral("program_args"), QString::fromStdString(Settings::values.program_args), QStringLiteral("")); WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false); WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false); + WriteSetting(QStringLiteral("quest_flag"), Settings::values.quest_flag, false); qt_config->endGroup(); } @@ -896,10 +999,15 @@ void Config::SavePathValues() { WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); - WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path, - QStringLiteral(".")); - WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan, - false); + qt_config->beginWriteArray(QStringLiteral("gamedirs")); + for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { + qt_config->setArrayIndex(i); + const auto& game_dir = UISettings::values.game_dirs[i]; + WriteSetting(QStringLiteral("path"), game_dir.path); + WriteSetting(QStringLiteral("deep_scan"), game_dir.deep_scan, false); + WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true); + } + qt_config->endArray(); WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 267717bc9..49fadd0ef 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -63,6 +63,11 @@ <string>Profiles</string> </attribute> </widget> + <widget class="ConfigureFilesystem" name="filesystemTab"> + <attribute name="title"> + <string>Filesystem</string> + </attribute> + </widget> <widget class="ConfigureInputSimple" name="inputTab"> <attribute name="title"> <string>Input</string> @@ -126,6 +131,12 @@ <container>1</container> </customwidget> <customwidget> + <class>ConfigureFilesystem</class> + <extends>QWidget</extends> + <header>configuration/configure_filesystem.h</header> + <container>1</container> + </customwidget> + <customwidget> <class>ConfigureAudio</class> <extends>QWidget</extends> <header>configuration/configure_audio.h</header> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index efc2bedfd..90c1f9459 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -12,13 +12,13 @@ #include "ui_configure_debug.h" #include "yuzu/configuration/configure_debug.h" #include "yuzu/debugger/console.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) { ui->setupUi(this); SetConfiguration(); - connect(ui->open_log_button, &QPushButton::pressed, []() { + connect(ui->open_log_button, &QPushButton::clicked, []() { QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir)); QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }); @@ -34,8 +34,8 @@ void ConfigureDebug::SetConfiguration() { ui->toggle_console->setChecked(UISettings::values.show_console); ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); - ui->dump_exefs->setChecked(Settings::values.dump_exefs); - ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso); + ui->reporting_services->setChecked(Settings::values.reporting_services); + ui->quest_flag->setChecked(Settings::values.quest_flag); } void ConfigureDebug::ApplyConfiguration() { @@ -44,8 +44,8 @@ void ConfigureDebug::ApplyConfiguration() { UISettings::values.show_console = ui->toggle_console->isChecked(); Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); - Settings::values.dump_exefs = ui->dump_exefs->isChecked(); - Settings::values.dump_nso = ui->dump_decompressed_nso->isChecked(); + Settings::values.reporting_services = ui->reporting_services->isChecked(); + Settings::values.quest_flag = ui->quest_flag->isChecked(); Debugger::ToggleConsole(); Log::Filter filter; filter.ParseFilterString(Settings::values.log_filter); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 5ca9ce0e6..ce49569bb 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>400</width> - <height>357</height> + <height>474</height> </rect> </property> <property name="windowTitle"> @@ -103,6 +103,44 @@ </item> </layout> </item> + <item> + <widget class="QCheckBox" name="reporting_services"> + <property name="text"> + <string>Enable Verbose Reporting Services</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string>This will be reset automatically when yuzu closes.</string> + </property> + <property name="indent"> + <number>20</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_5"> + <property name="title"> + <string>Advanced</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="quest_flag"> + <property name="text"> + <string>Kiosk (Quest) Mode</string> + </property> + </widget> + </item> </layout> </widget> </item> @@ -130,39 +168,13 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_4"> - <property name="title"> - <string>Dump</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_6"> - <item> - <widget class="QCheckBox" name="dump_decompressed_nso"> - <property name="whatsThis"> - <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string> - </property> - <property name="text"> - <string>Dump Decompressed NSOs</string> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="dump_exefs"> - <property name="whatsThis"> - <string>When checked, any game that yuzu loads will have its ExeFS dumped to the yuzu/dump directory.</string> - </property> - <property name="text"> - <string>Dump ExeFS</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index e636964e3..7c875ae87 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -37,6 +37,7 @@ void ConfigureDialog::ApplyConfiguration() { ui->gameListTab->ApplyConfiguration(); ui->systemTab->ApplyConfiguration(); ui->profileManagerTab->ApplyConfiguration(); + ui->filesystemTab->applyConfiguration(); ui->inputTab->ApplyConfiguration(); ui->hotkeysTab->ApplyConfiguration(registry); ui->graphicsTab->ApplyConfiguration(); @@ -68,12 +69,14 @@ void ConfigureDialog::RetranslateUI() { ui->tabWidget->setCurrentIndex(old_index); } +Q_DECLARE_METATYPE(QList<QWidget*>); + void ConfigureDialog::PopulateSelectionList() { - const std::array<std::pair<QString, QStringList>, 4> items{ - {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, - {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}}, - {tr("Graphics"), {tr("Graphics")}}, - {tr("Controls"), {tr("Input"), tr("Hotkeys")}}}, + const std::array<std::pair<QString, QList<QWidget*>>, 4> items{ + {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}}, + {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->filesystemTab, ui->audioTab}}, + {tr("Graphics"), {ui->graphicsTab}}, + {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}}, }; [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList); @@ -81,7 +84,7 @@ void ConfigureDialog::PopulateSelectionList() { ui->selectorList->clear(); for (const auto& entry : items) { auto* const item = new QListWidgetItem(entry.first); - item->setData(Qt::UserRole, entry.second); + item->setData(Qt::UserRole, QVariant::fromValue(entry.second)); ui->selectorList->addItem(item); } @@ -93,24 +96,27 @@ void ConfigureDialog::UpdateVisibleTabs() { return; } - const std::map<QString, QWidget*> widgets = { - {tr("General"), ui->generalTab}, - {tr("System"), ui->systemTab}, - {tr("Profiles"), ui->profileManagerTab}, - {tr("Input"), ui->inputTab}, - {tr("Hotkeys"), ui->hotkeysTab}, - {tr("Graphics"), ui->graphicsTab}, - {tr("Audio"), ui->audioTab}, - {tr("Debug"), ui->debugTab}, - {tr("Web"), ui->webTab}, - {tr("Game List"), ui->gameListTab}, + const std::map<QWidget*, QString> widgets = { + {ui->generalTab, tr("General")}, + {ui->systemTab, tr("System")}, + {ui->profileManagerTab, tr("Profiles")}, + {ui->inputTab, tr("Input")}, + {ui->hotkeysTab, tr("Hotkeys")}, + {ui->graphicsTab, tr("Graphics")}, + {ui->audioTab, tr("Audio")}, + {ui->debugTab, tr("Debug")}, + {ui->webTab, tr("Web")}, + {ui->gameListTab, tr("Game List")}, + {ui->filesystemTab, tr("Filesystem")}, }; [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); ui->tabWidget->clear(); - const QStringList tabs = items[0]->data(Qt::UserRole).toStringList(); - for (const auto& tab : tabs) { - ui->tabWidget->addTab(widgets.find(tab)->second, tab); + + const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole)); + + for (const auto tab : tabs) { + ui->tabWidget->addTab(tab, widgets.at(tab)); } } diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp new file mode 100644 index 000000000..29f540eb7 --- /dev/null +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -0,0 +1,177 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QFileDialog> +#include <QMessageBox> +#include "common/common_paths.h" +#include "common/file_util.h" +#include "core/settings.h" +#include "ui_configure_filesystem.h" +#include "yuzu/configuration/configure_filesystem.h" +#include "yuzu/uisettings.h" + +namespace { + +template <typename T> +void SetComboBoxFromData(QComboBox* combo_box, T data) { + const auto index = combo_box->findData(QVariant::fromValue(static_cast<u64>(data))); + if (index >= combo_box->count() || index < 0) + return; + + combo_box->setCurrentIndex(index); +} + +} // Anonymous namespace + +ConfigureFilesystem::ConfigureFilesystem(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::ConfigureFilesystem>()) { + ui->setupUi(this); + this->setConfiguration(); + + connect(ui->nand_directory_button, &QToolButton::pressed, this, + [this] { SetDirectory(DirectoryTarget::NAND, ui->nand_directory_edit); }); + connect(ui->sdmc_directory_button, &QToolButton::pressed, this, + [this] { SetDirectory(DirectoryTarget::SD, ui->sdmc_directory_edit); }); + connect(ui->gamecard_path_button, &QToolButton::pressed, this, + [this] { SetDirectory(DirectoryTarget::Gamecard, ui->gamecard_path_edit); }); + connect(ui->dump_path_button, &QToolButton::pressed, this, + [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); }); + connect(ui->load_path_button, &QToolButton::pressed, this, + [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); }); + connect(ui->cache_directory_button, &QToolButton::pressed, this, + [this] { SetDirectory(DirectoryTarget::Cache, ui->cache_directory_edit); }); + + connect(ui->reset_game_list_cache, &QPushButton::pressed, this, + &ConfigureFilesystem::ResetMetadata); + + connect(ui->gamecard_inserted, &QCheckBox::stateChanged, this, + &ConfigureFilesystem::UpdateEnabledControls); + connect(ui->gamecard_current_game, &QCheckBox::stateChanged, this, + &ConfigureFilesystem::UpdateEnabledControls); +} + +ConfigureFilesystem::~ConfigureFilesystem() = default; + +void ConfigureFilesystem::setConfiguration() { + ui->nand_directory_edit->setText( + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); + ui->sdmc_directory_edit->setText( + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); + ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path)); + ui->dump_path_edit->setText( + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))); + ui->load_path_edit->setText( + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))); + ui->cache_directory_edit->setText( + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))); + + ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted); + ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game); + ui->dump_exefs->setChecked(Settings::values.dump_exefs); + ui->dump_nso->setChecked(Settings::values.dump_nso); + + ui->cache_game_list->setChecked(UISettings::values.cache_game_list); + + SetComboBoxFromData(ui->nand_size, Settings::values.nand_total_size); + SetComboBoxFromData(ui->usrnand_size, Settings::values.nand_user_size); + SetComboBoxFromData(ui->sysnand_size, Settings::values.nand_system_size); + SetComboBoxFromData(ui->sdmc_size, Settings::values.sdmc_size); + + UpdateEnabledControls(); +} + +void ConfigureFilesystem::applyConfiguration() { + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir, + ui->nand_directory_edit->text().toStdString()); + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, + ui->sdmc_directory_edit->text().toStdString()); + FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, ui->dump_path_edit->text().toStdString()); + FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, ui->load_path_edit->text().toStdString()); + FileUtil::GetUserPath(FileUtil::UserPath::CacheDir, + ui->cache_directory_edit->text().toStdString()); + Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString(); + + Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); + Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked(); + Settings::values.dump_exefs = ui->dump_exefs->isChecked(); + Settings::values.dump_nso = ui->dump_nso->isChecked(); + + UISettings::values.cache_game_list = ui->cache_game_list->isChecked(); + + Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>( + ui->nand_size->itemData(ui->nand_size->currentIndex()).toULongLong()); + Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>( + ui->nand_size->itemData(ui->sysnand_size->currentIndex()).toULongLong()); + Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>( + ui->nand_size->itemData(ui->usrnand_size->currentIndex()).toULongLong()); + Settings::values.sdmc_size = static_cast<Settings::SDMCSize>( + ui->nand_size->itemData(ui->sdmc_size->currentIndex()).toULongLong()); +} + +void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) { + QString caption; + + switch (target) { + case DirectoryTarget::NAND: + caption = tr("Select Emulated NAND Directory..."); + break; + case DirectoryTarget::SD: + caption = tr("Select Emulated SD Directory..."); + break; + case DirectoryTarget::Gamecard: + caption = tr("Select Gamecard Path..."); + break; + case DirectoryTarget::Dump: + caption = tr("Select Dump Directory..."); + break; + case DirectoryTarget::Load: + caption = tr("Select Mod Load Directory..."); + break; + case DirectoryTarget::Cache: + caption = tr("Select Cache Directory..."); + break; + } + + QString str; + if (target == DirectoryTarget::Gamecard) { + str = QFileDialog::getOpenFileName(this, caption, QFileInfo(edit->text()).dir().path(), + QStringLiteral("NX Gamecard;*.xci")); + } else { + str = QFileDialog::getExistingDirectory(this, caption, edit->text()); + } + + if (str.isEmpty()) + return; + + edit->setText(str); +} + +void ConfigureFilesystem::ResetMetadata() { + if (!FileUtil::Exists(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + + "game_list")) { + QMessageBox::information(this, tr("Reset Metadata Cache"), + tr("The metadata cache is already empty.")); + } else if (FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + + DIR_SEP + "game_list")) { + QMessageBox::information(this, tr("Reset Metadata Cache"), + tr("The operation completed successfully.")); + UISettings::values.is_game_list_reload_pending.exchange(true); + } else { + QMessageBox::warning( + this, tr("Reset Metadata Cache"), + tr("The metadata cache couldn't be deleted. It might be in use or non-existent.")); + } +} + +void ConfigureFilesystem::UpdateEnabledControls() { + ui->gamecard_current_game->setEnabled(ui->gamecard_inserted->isChecked()); + ui->gamecard_path_edit->setEnabled(ui->gamecard_inserted->isChecked() && + !ui->gamecard_current_game->isChecked()); + ui->gamecard_path_button->setEnabled(ui->gamecard_inserted->isChecked() && + !ui->gamecard_current_game->isChecked()); +} + +void ConfigureFilesystem::retranslateUi() { + ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_filesystem.h b/src/yuzu/configuration/configure_filesystem.h new file mode 100644 index 000000000..a79303760 --- /dev/null +++ b/src/yuzu/configuration/configure_filesystem.h @@ -0,0 +1,43 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QWidget> + +class QLineEdit; + +namespace Ui { +class ConfigureFilesystem; +} + +class ConfigureFilesystem : public QWidget { + Q_OBJECT + +public: + explicit ConfigureFilesystem(QWidget* parent = nullptr); + ~ConfigureFilesystem() override; + + void applyConfiguration(); + void retranslateUi(); + +private: + void setConfiguration(); + + enum class DirectoryTarget { + NAND, + SD, + Gamecard, + Dump, + Load, + Cache, + }; + + void SetDirectory(DirectoryTarget target, QLineEdit* edit); + void ResetMetadata(); + void UpdateEnabledControls(); + + std::unique_ptr<Ui::ConfigureFilesystem> ui; +}; diff --git a/src/yuzu/configuration/configure_filesystem.ui b/src/yuzu/configuration/configure_filesystem.ui new file mode 100644 index 000000000..58cd07f52 --- /dev/null +++ b/src/yuzu/configuration/configure_filesystem.ui @@ -0,0 +1,395 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureFilesystem</class> + <widget class="QWidget" name="ConfigureFilesystem"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>453</width> + <height>561</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Storage Directories</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>NAND</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QToolButton" name="nand_directory_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLineEdit" name="nand_directory_edit"/> + </item> + <item row="1" column="2"> + <widget class="QLineEdit" name="sdmc_directory_edit"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>SD Card</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QToolButton" name="sdmc_directory_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Maximum</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>60</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Gamecard</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="2" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Path</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLineEdit" name="gamecard_path_edit"/> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="gamecard_inserted"> + <property name="text"> + <string>Inserted</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="gamecard_current_game"> + <property name="text"> + <string>Current Game</string> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QToolButton" name="gamecard_path_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Storage Sizes</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="3" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>SD Card</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>System NAND</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="sysnand_size"> + <item> + <property name="text"> + <string>2.5 GB</string> + </property> + </item> + </widget> + </item> + <item row="3" column="1"> + <widget class="QComboBox" name="sdmc_size"> + <property name="currentText"> + <string>32 GB</string> + </property> + <item> + <property name="text"> + <string>1 GB</string> + </property> + </item> + <item> + <property name="text"> + <string>2 GB</string> + </property> + </item> + <item> + <property name="text"> + <string>4 GB</string> + </property> + </item> + <item> + <property name="text"> + <string>8 GB</string> + </property> + </item> + <item> + <property name="text"> + <string>16 GB</string> + </property> + </item> + <item> + <property name="text"> + <string>32 GB</string> + </property> + </item> + <item> + <property name="text"> + <string>64 GB</string> + </property> + </item> + <item> + <property name="text"> + <string>128 GB</string> + </property> + </item> + <item> + <property name="text"> + <string>256 GB</string> + </property> + </item> + <item> + <property name="text"> + <string>1 TB</string> + </property> + </item> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="usrnand_size"> + <item> + <property name="text"> + <string>26 GB</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>User NAND</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>NAND</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="nand_size"> + <item> + <property name="text"> + <string>29.1 GB</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="title"> + <string>Patch Manager</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="1" column="2"> + <widget class="QLineEdit" name="load_path_edit"/> + </item> + <item row="0" column="2"> + <widget class="QLineEdit" name="dump_path_edit"/> + </item> + <item row="0" column="3"> + <widget class="QToolButton" name="dump_path_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QToolButton" name="load_path_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="4"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QCheckBox" name="dump_nso"> + <property name="text"> + <string>Dump Decompressed NSOs</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="dump_exefs"> + <property name="text"> + <string>Dump ExeFS</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Mod Load Root</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Dump Root</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_5"> + <property name="title"> + <string>Caching</string> + </property> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="0" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Cache Directory</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="2"> + <widget class="QLineEdit" name="cache_directory_edit"/> + </item> + <item row="0" column="3"> + <widget class="QToolButton" name="cache_directory_button"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="4"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QCheckBox" name="cache_game_list"> + <property name="text"> + <string>Cache Game List Metadata</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="reset_game_list_cache"> + <property name="text"> + <string>Reset Metadata Cache</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp index d1724ba89..daedbc33e 100644 --- a/src/yuzu/configuration/configure_gamelist.cpp +++ b/src/yuzu/configuration/configure_gamelist.cpp @@ -9,7 +9,7 @@ #include "core/settings.h" #include "ui_configure_gamelist.h" #include "yuzu/configuration/configure_gamelist.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" namespace { constexpr std::array default_icon_sizes{ diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 06d368dfc..98bc9b391 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -2,11 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QCheckBox> +#include <QSpinBox> #include "core/core.h" #include "core/settings.h" #include "ui_configure_general.h" #include "yuzu/configuration/configure_general.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" ConfigureGeneral::ConfigureGeneral(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGeneral) { @@ -20,30 +22,29 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) SetConfiguration(); - connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this, - [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); - - ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled); } ConfigureGeneral::~ConfigureGeneral() = default; void ConfigureGeneral::SetConfiguration() { - ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); - ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); + + ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); + ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); + ui->frame_limit->setValue(Settings::values.frame_limit); } void ConfigureGeneral::ApplyConfiguration() { - UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); UISettings::values.theme = ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); - Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); + Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); + Settings::values.frame_limit = ui->frame_limit->value(); } void ConfigureGeneral::changeEvent(QEvent* event) { diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 1a5721fe7..0bb91d64b 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -25,11 +25,31 @@ <item> <layout class="QVBoxLayout" name="GeneralVerticalLayout"> <item> - <widget class="QCheckBox" name="toggle_deepscan"> - <property name="text"> - <string>Search sub-directories for games</string> - </property> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QCheckBox" name="toggle_frame_limit"> + <property name="text"> + <string>Limit Speed Percent</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="frame_limit"> + <property name="suffix"> + <string>%</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>9999</number> + </property> + <property name="value"> + <number>100</number> + </property> + </widget> + </item> + </layout> </item> <item> <widget class="QCheckBox" name="toggle_check_exit"> @@ -51,26 +71,6 @@ </widget> </item> <item> - <widget class="QGroupBox" name="PerformanceGroupBox"> - <property name="title"> - <string>Performance</string> - </property> - <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> - <item> - <layout class="QVBoxLayout" name="PerformanceVerticalLayout"> - <item> - <widget class="QCheckBox" name="use_cpu_jit"> - <property name="text"> - <string>Enable CPU JIT</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item> <widget class="QGroupBox" name="theme_group_box"> <property name="title"> <string>Theme</string> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 2b17b250c..2c9e322c9 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -55,7 +55,6 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) SetConfiguration(); - connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled); connect(ui->bg_button, &QPushButton::clicked, this, [this] { const QColor new_bg_color = QColorDialog::getColor(bg_color); if (!new_bg_color.isValid()) { @@ -72,9 +71,6 @@ void ConfigureGraphics::SetConfiguration() { ui->resolution_factor_combobox->setCurrentIndex( static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); - ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); - ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); - ui->frame_limit->setValue(Settings::values.frame_limit); ui->use_disk_shader_cache->setEnabled(runtime_lock); ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); @@ -89,8 +85,6 @@ void ConfigureGraphics::SetConfiguration() { void ConfigureGraphics::ApplyConfiguration() { Settings::values.resolution_factor = ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); - Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); - Settings::values.frame_limit = ui->frame_limit->value(); Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); Settings::values.use_asynchronous_gpu_emulation = diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 15ab18ecd..0309ee300 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -23,33 +23,6 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QCheckBox" name="toggle_frame_limit"> - <property name="text"> - <string>Limit Speed Percent</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="frame_limit"> - <property name="suffix"> - <string>%</string> - </property> - <property name="minimum"> - <number>1</number> - </property> - <property name="maximum"> - <number>9999</number> - </property> - <property name="value"> - <number>100</number> - </property> - </widget> - </item> - </layout> - </item> - <item> <widget class="QCheckBox" name="use_disk_shader_cache"> <property name="text"> <string>Use disk shader cache</string> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 4dd775aab..f2977719c 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -79,7 +79,7 @@ ConfigureInput::ConfigureInput(QWidget* parent) LoadConfiguration(); UpdateUIEnabled(); - connect(ui->restore_defaults_button, &QPushButton::pressed, this, + connect(ui->restore_defaults_button, &QPushButton::clicked, this, &ConfigureInput::RestoreDefaults); for (auto* enabled : players_controller) { @@ -96,20 +96,20 @@ ConfigureInput::ConfigureInput(QWidget* parent) &ConfigureInput::UpdateUIEnabled); for (std::size_t i = 0; i < players_configure.size(); ++i) { - connect(players_configure[i], &QPushButton::pressed, this, + connect(players_configure[i], &QPushButton::clicked, this, [this, i] { CallConfigureDialog<ConfigureInputPlayer>(*this, i, false); }); } - connect(ui->handheld_configure, &QPushButton::pressed, this, + connect(ui->handheld_configure, &QPushButton::clicked, this, [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 8, false); }); - connect(ui->debug_configure, &QPushButton::pressed, this, + connect(ui->debug_configure, &QPushButton::clicked, this, [this] { CallConfigureDialog<ConfigureInputPlayer>(*this, 9, true); }); - connect(ui->mouse_advanced, &QPushButton::pressed, this, + connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallConfigureDialog<ConfigureMouseAdvanced>(*this); }); - connect(ui->touchscreen_advanced, &QPushButton::pressed, this, + connect(ui->touchscreen_advanced, &QPushButton::clicked, this, [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); }); } @@ -182,6 +182,8 @@ void ConfigureInput::UpdateUIEnabled() { players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0); } + ui->handheld_connected->setChecked(ui->handheld_connected->isChecked() && + !ui->use_docked_mode->isChecked()); ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked()); ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() && !ui->use_docked_mode->isChecked()); diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index 8378451c8..efffd8487 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -11,7 +11,7 @@ </rect> </property> <property name="windowTitle"> - <string>ConfigureInput</string> + <string>Custom Input Settings</string> </property> <layout class="QVBoxLayout" name="verticalLayout_5"> <item> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 916baccc1..a968cfb5d 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -244,7 +244,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } button->setContextMenuPolicy(Qt::CustomContextMenu); - connect(button, &QPushButton::released, [=] { + connect(button, &QPushButton::clicked, [=] { HandleClick( button_map[button_id], [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, @@ -273,7 +273,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } analog_button->setContextMenuPolicy(Qt::CustomContextMenu); - connect(analog_button, &QPushButton::released, [=]() { + connect(analog_button, &QPushButton::clicked, [=]() { HandleClick(analog_map_buttons[analog_id][sub_button_id], [=](const Common::ParamPackage& params) { SetAnalogButton(params, analogs_param[analog_id], @@ -300,19 +300,22 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i menu_location)); }); } - connect(analog_map_stick[analog_id], &QPushButton::released, [=] { - QMessageBox::information(this, tr("Information"), - tr("After pressing OK, first move your joystick horizontally, " - "and then vertically.")); - HandleClick( - analog_map_stick[analog_id], - [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, - InputCommon::Polling::DeviceType::Analog); + connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] { + if (QMessageBox::information( + this, tr("Information"), + tr("After pressing OK, first move your joystick horizontally, " + "and then vertically."), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { + HandleClick( + analog_map_stick[analog_id], + [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, + InputCommon::Polling::DeviceType::Analog); + } }); } - connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); - connect(ui->buttonRestoreDefaults, &QPushButton::released, [this] { RestoreDefaults(); }); + connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); + connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); timeout_timer->setSingleShot(true); connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp index 864803ea3..ab3a11d30 100644 --- a/src/yuzu/configuration/configure_input_simple.cpp +++ b/src/yuzu/configuration/configure_input_simple.cpp @@ -9,7 +9,7 @@ #include "yuzu/configuration/configure_input.h" #include "yuzu/configuration/configure_input_player.h" #include "yuzu/configuration/configure_input_simple.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" namespace { @@ -101,7 +101,7 @@ ConfigureInputSimple::ConfigureInputSimple(QWidget* parent) connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ConfigureInputSimple::OnSelectProfile); - connect(ui->profile_configure, &QPushButton::pressed, this, &ConfigureInputSimple::OnConfigure); + connect(ui->profile_configure, &QPushButton::clicked, this, &ConfigureInputSimple::OnConfigure); LoadConfiguration(); } diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp index b7305e653..0a4abe34f 100644 --- a/src/yuzu/configuration/configure_mouse_advanced.cpp +++ b/src/yuzu/configuration/configure_mouse_advanced.cpp @@ -83,7 +83,7 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent) } button->setContextMenuPolicy(Qt::CustomContextMenu); - connect(button, &QPushButton::released, [=] { + connect(button, &QPushButton::clicked, [=] { HandleClick( button_map[button_id], [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, @@ -104,8 +104,8 @@ ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent) }); } - connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); - connect(ui->buttonRestoreDefaults, &QPushButton::released, [this] { RestoreDefaults(); }); + connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); + connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); timeout_timer->setSingleShot(true); connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp index 90336e235..d7f259f12 100644 --- a/src/yuzu/configuration/configure_per_general.cpp +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -23,7 +23,7 @@ #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input.h" #include "yuzu/configuration/configure_per_general.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" #include "yuzu/util/util.h" ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index c90f4cdd8..f53423440 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -108,10 +108,10 @@ ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent) connect(tree_view, &QTreeView::clicked, this, &ConfigureProfileManager::SelectUser); - connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureProfileManager::AddUser); - connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureProfileManager::RenameUser); - connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureProfileManager::DeleteUser); - connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureProfileManager::SetUserImage); + connect(ui->pm_add, &QPushButton::clicked, this, &ConfigureProfileManager::AddUser); + connect(ui->pm_rename, &QPushButton::clicked, this, &ConfigureProfileManager::RenameUser); + connect(ui->pm_remove, &QPushButton::clicked, this, &ConfigureProfileManager::DeleteUser); + connect(ui->pm_set_image, &QPushButton::clicked, this, &ConfigureProfileManager::SetUserImage); scene = new QGraphicsScene; ui->current_user_icon->setScene(scene); diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp index 8ced28c75..7d7cc00b7 100644 --- a/src/yuzu/configuration/configure_touchscreen_advanced.cpp +++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp @@ -11,7 +11,7 @@ ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent) : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchscreenAdvanced>()) { ui->setupUi(this); - connect(ui->restore_defaults_button, &QPushButton::pressed, this, + connect(ui->restore_defaults_button, &QPushButton::clicked, this, &ConfigureTouchscreenAdvanced::RestoreDefaults); LoadConfiguration(); diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index 5a70ef168..336b062b3 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp @@ -9,7 +9,7 @@ #include "core/telemetry_session.h" #include "ui_configure_web.h" #include "yuzu/configuration/configure_web.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" ConfigureWeb::ConfigureWeb(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) { diff --git a/src/yuzu/debugger/console.cpp b/src/yuzu/debugger/console.cpp index 320898f6a..207ff4d58 100644 --- a/src/yuzu/debugger/console.cpp +++ b/src/yuzu/debugger/console.cpp @@ -10,7 +10,7 @@ #include "common/logging/backend.h" #include "yuzu/debugger/console.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" namespace Debugger { void ToggleConsole() { diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp index 9d87a41eb..ea0079353 100644 --- a/src/yuzu/discord_impl.cpp +++ b/src/yuzu/discord_impl.cpp @@ -9,7 +9,7 @@ #include "core/core.h" #include "core/loader/loader.h" #include "yuzu/discord_impl.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" namespace DiscordRPC { diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 83d675773..d5fab2f1f 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -23,7 +23,7 @@ #include "yuzu/game_list_p.h" #include "yuzu/game_list_worker.h" #include "yuzu/main.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {} @@ -34,7 +34,6 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve return QObject::eventFilter(obj, event); QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); - int rowCount = gamelist->tree_view->model()->rowCount(); QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); // If the searchfield's text hasn't changed special function keys get checked @@ -56,19 +55,9 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve // If there is only one result launch this game case Qt::Key_Return: case Qt::Key_Enter: { - QStandardItemModel* item_model = new QStandardItemModel(gamelist->tree_view); - QModelIndex root_index = item_model->invisibleRootItem()->index(); - QStandardItem* child_file; - QString file_path; - int resultCount = 0; - for (int i = 0; i < rowCount; ++i) { - if (!gamelist->tree_view->isRowHidden(i, root_index)) { - ++resultCount; - child_file = gamelist->item_model->item(i, 0); - file_path = child_file->data(GameListItemPath::FullPathRole).toString(); - } - } - if (resultCount == 1) { + if (gamelist->search_field->visible == 1) { + QString file_path = gamelist->getLastFilterResultItem(); + // To avoid loading error dialog loops while confirming them using enter // Also users usually want to run a different game after closing one gamelist->search_field->edit_filter->clear(); @@ -88,9 +77,31 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve } void GameListSearchField::setFilterResult(int visible, int total) { + this->visible = visible; + this->total = total; + label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); } +QString GameList::getLastFilterResultItem() const { + QStandardItem* folder; + QStandardItem* child; + QString file_path; + const int folder_count = item_model->rowCount(); + for (int i = 0; i < folder_count; ++i) { + folder = item_model->item(i, 0); + const QModelIndex folder_index = folder->index(); + const int children_count = folder->rowCount(); + for (int j = 0; j < children_count; ++j) { + if (!tree_view->isRowHidden(j, folder_index)) { + child = folder->child(j, 0); + file_path = child->data(GameListItemPath::FullPathRole).toString(); + } + } + } + return file_path; +} + void GameListSearchField::clear() { edit_filter->clear(); } @@ -147,45 +158,120 @@ static bool ContainsAllWords(const QString& haystack, const QString& userinput) [&haystack](const QString& s) { return haystack.contains(s); }); } +// Syncs the expanded state of Game Directories with settings to persist across sessions +void GameList::onItemExpanded(const QModelIndex& item) { + const auto type = item.data(GameListItem::TypeRole).value<GameListItemType>(); + if (type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir || + type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir) + item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded = + tree_view->isExpanded(item); +} + // Event in order to filter the gamelist after editing the searchfield void GameList::onTextChanged(const QString& new_text) { - const int row_count = tree_view->model()->rowCount(); - const QString edit_filter_text = new_text.toLower(); - const QModelIndex root_index = item_model->invisibleRootItem()->index(); + const int folder_count = tree_view->model()->rowCount(); + QString edit_filter_text = new_text.toLower(); + QStandardItem* folder; + QStandardItem* child; + int children_total = 0; + QModelIndex root_index = item_model->invisibleRootItem()->index(); // If the searchfield is empty every item is visible // Otherwise the filter gets applied if (edit_filter_text.isEmpty()) { - for (int i = 0; i < row_count; ++i) { - tree_view->setRowHidden(i, root_index, false); + for (int i = 0; i < folder_count; ++i) { + folder = item_model->item(i, 0); + const QModelIndex folder_index = folder->index(); + const int children_count = folder->rowCount(); + for (int j = 0; j < children_count; ++j) { + ++children_total; + tree_view->setRowHidden(j, folder_index, false); + } } - search_field->setFilterResult(row_count, row_count); + search_field->setFilterResult(children_total, children_total); } else { int result_count = 0; - for (int i = 0; i < row_count; ++i) { - const QStandardItem* child_file = item_model->item(i, 0); - const QString file_path = - child_file->data(GameListItemPath::FullPathRole).toString().toLower(); - const QString file_title = - child_file->data(GameListItemPath::TitleRole).toString().toLower(); - const QString file_program_id = - child_file->data(GameListItemPath::ProgramIdRole).toString().toLower(); - - // Only items which filename in combination with its title contains all words - // that are in the searchfield will be visible in the gamelist - // The search is case insensitive because of toLower() - // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent - // multiple conversions of edit_filter_text for each game in the gamelist - const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + - QLatin1Char{' '} + file_title; - if (ContainsAllWords(file_name, edit_filter_text) || - (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { - tree_view->setRowHidden(i, root_index, false); - ++result_count; - } else { - tree_view->setRowHidden(i, root_index, true); + for (int i = 0; i < folder_count; ++i) { + folder = item_model->item(i, 0); + const QModelIndex folder_index = folder->index(); + const int children_count = folder->rowCount(); + for (int j = 0; j < children_count; ++j) { + ++children_total; + const QStandardItem* child = folder->child(j, 0); + const QString file_path = + child->data(GameListItemPath::FullPathRole).toString().toLower(); + const QString file_title = + child->data(GameListItemPath::TitleRole).toString().toLower(); + const QString file_program_id = + child->data(GameListItemPath::ProgramIdRole).toString().toLower(); + + // Only items which filename in combination with its title contains all words + // that are in the searchfield will be visible in the gamelist + // The search is case insensitive because of toLower() + // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent + // multiple conversions of edit_filter_text for each game in the gamelist + const QString file_name = + file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + + file_title; + if (ContainsAllWords(file_name, edit_filter_text) || + (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { + tree_view->setRowHidden(j, folder_index, false); + ++result_count; + } else { + tree_view->setRowHidden(j, folder_index, true); + } + search_field->setFilterResult(result_count, children_total); } - search_field->setFilterResult(result_count, row_count); + } + } +} + +void GameList::onUpdateThemedIcons() { + for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) { + QStandardItem* child = item_model->invisibleRootItem()->child(i); + + const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64); + switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) { + case GameListItemType::SdmcDir: + child->setData( + QIcon::fromTheme(QStringLiteral("sd_card")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + break; + case GameListItemType::UserNandDir: + child->setData( + QIcon::fromTheme(QStringLiteral("chip")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + break; + case GameListItemType::SysNandDir: + child->setData( + QIcon::fromTheme(QStringLiteral("chip")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + break; + case GameListItemType::CustomDir: { + const UISettings::GameDir* game_dir = + child->data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); + const QString icon_name = QFileInfo::exists(game_dir->path) + ? QStringLiteral("folder") + : QStringLiteral("bad_folder"); + child->setData( + QIcon::fromTheme(icon_name).pixmap(icon_size).scaled( + icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + break; + } + case GameListItemType::AddDir: + child->setData( + QIcon::fromTheme(QStringLiteral("plus")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + break; } } } @@ -214,7 +300,6 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); tree_view->setSortingEnabled(true); tree_view->setEditTriggers(QHeaderView::NoEditTriggers); - tree_view->setUniformRowHeights(true); tree_view->setContextMenuPolicy(Qt::CustomContextMenu); tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); @@ -230,12 +315,16 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide item_model->setHeaderData(COLUMN_FILE_TYPE - 1, Qt::Horizontal, tr("File type")); item_model->setHeaderData(COLUMN_SIZE - 1, Qt::Horizontal, tr("Size")); } + item_model->setSortRole(GameListItemPath::TitleRole); + connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::onUpdateThemedIcons); connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); + connect(tree_view, &QTreeView::expanded, this, &GameList::onItemExpanded); + connect(tree_view, &QTreeView::collapsed, this, &GameList::onItemExpanded); - // We must register all custom types with the Qt Automoc system so that we are able to use it - // with signals/slots. In this case, QList falls under the umbrells of custom types. + // We must register all custom types with the Qt Automoc system so that we are able to use + // it with signals/slots. In this case, QList falls under the umbrells of custom types. qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); layout->setContentsMargins(0, 0, 0, 0); @@ -263,38 +352,68 @@ void GameList::clearFilter() { search_field->clear(); } -void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { +void GameList::AddDirEntry(GameListDir* entry_items) { item_model->invisibleRootItem()->appendRow(entry_items); + tree_view->setExpanded( + entry_items->index(), + entry_items->data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded); } -void GameList::ValidateEntry(const QModelIndex& item) { - // We don't care about the individual QStandardItem that was selected, but its row. - const int row = item_model->itemFromIndex(item)->row(); - const QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); - const QString file_path = child_file->data(GameListItemPath::FullPathRole).toString(); - - if (file_path.isEmpty()) - return; - - if (!QFileInfo::exists(file_path)) - return; +void GameList::AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent) { + parent->appendRow(entry_items); +} - const QFileInfo file_info{file_path}; - if (file_info.isDir()) { - const QDir dir{file_path}; - const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files); - if (matching_main.size() == 1) { - emit GameChosen(dir.path() + QDir::separator() + matching_main[0]); +void GameList::ValidateEntry(const QModelIndex& item) { + const auto selected = item.sibling(item.row(), 0); + + switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) { + case GameListItemType::Game: { + const QString file_path = selected.data(GameListItemPath::FullPathRole).toString(); + if (file_path.isEmpty()) + return; + const QFileInfo file_info(file_path); + if (!file_info.exists()) + return; + + if (file_info.isDir()) { + const QDir dir{file_path}; + const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files); + if (matching_main.size() == 1) { + emit GameChosen(dir.path() + QDir::separator() + matching_main[0]); + } + return; } - return; + + // Users usually want to run a different game after closing one + search_field->clear(); + emit GameChosen(file_path); + break; + } + case GameListItemType::AddDir: + emit AddDirectory(); + break; } +} - // Users usually want to run a diffrent game after closing one - search_field->clear(); - emit GameChosen(file_path); +bool GameList::isEmpty() const { + for (int i = 0; i < item_model->rowCount(); i++) { + const QStandardItem* child = item_model->invisibleRootItem()->child(i); + const auto type = static_cast<GameListItemType>(child->type()); + if (!child->hasChildren() && + (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir || + type == GameListItemType::SysNandDir)) { + item_model->invisibleRootItem()->removeRow(child->row()); + i--; + }; + } + return !item_model->invisibleRootItem()->hasChildren(); } void GameList::DonePopulating(QStringList watch_list) { + emit ShowList(!isEmpty()); + + item_model->invisibleRootItem()->appendRow(new GameListAddDir()); + // Clear out the old directories to watch for changes and add the new ones auto watch_dirs = watcher->directories(); if (!watch_dirs.isEmpty()) { @@ -311,9 +430,13 @@ void GameList::DonePopulating(QStringList watch_list) { QCoreApplication::processEvents(); } tree_view->setEnabled(true); - int rowCount = tree_view->model()->rowCount(); - search_field->setFilterResult(rowCount, rowCount); - if (rowCount > 0) { + const int folder_count = tree_view->model()->rowCount(); + int children_total = 0; + for (int i = 0; i < folder_count; ++i) { + children_total += item_model->item(i, 0)->rowCount(); + } + search_field->setFilterResult(children_total, children_total); + if (children_total > 0) { search_field->setFocus(); } } @@ -323,12 +446,27 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { if (!item.isValid()) return; - int row = item_model->itemFromIndex(item)->row(); - QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); - u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); - std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString(); - + const auto selected = item.sibling(item.row(), 0); QMenu context_menu; + switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) { + case GameListItemType::Game: + AddGamePopup(context_menu, selected.data(GameListItemPath::ProgramIdRole).toULongLong(), + selected.data(GameListItemPath::FullPathRole).toString().toStdString()); + break; + case GameListItemType::CustomDir: + AddPermDirPopup(context_menu, selected); + AddCustomDirPopup(context_menu, selected); + break; + case GameListItemType::SdmcDir: + case GameListItemType::UserNandDir: + case GameListItemType::SysNandDir: + AddPermDirPopup(context_menu, selected); + break; + } + context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); +} + +void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) { QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); QAction* open_transferable_shader_cache = @@ -344,19 +482,86 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); - connect(open_save_location, &QAction::triggered, - [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); - connect(open_lfs_location, &QAction::triggered, - [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); }); + connect(open_save_location, &QAction::triggered, [this, program_id]() { + emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); + }); + connect(open_lfs_location, &QAction::triggered, [this, program_id]() { + emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); + }); connect(open_transferable_shader_cache, &QAction::triggered, - [&]() { emit OpenTransferableShaderCacheRequested(program_id); }); - connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); }); - connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); - connect(navigate_to_gamedb_entry, &QAction::triggered, - [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); - connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); }); + [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); }); + connect(dump_romfs, &QAction::triggered, + [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); }); + connect(copy_tid, &QAction::triggered, + [this, program_id]() { emit CopyTIDRequested(program_id); }); + connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { + emit NavigateToGamedbEntryRequested(program_id, compatibility_list); + }); + connect(properties, &QAction::triggered, + [this, path]() { emit OpenPerGameGeneralRequested(path); }); +}; - context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); +void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) { + UISettings::GameDir& game_dir = + *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); + + QAction* deep_scan = context_menu.addAction(tr("Scan Subfolders")); + QAction* delete_dir = context_menu.addAction(tr("Remove Game Directory")); + + deep_scan->setCheckable(true); + deep_scan->setChecked(game_dir.deep_scan); + + connect(deep_scan, &QAction::triggered, [this, &game_dir] { + game_dir.deep_scan = !game_dir.deep_scan; + PopulateAsync(UISettings::values.game_dirs); + }); + connect(delete_dir, &QAction::triggered, [this, &game_dir, selected] { + UISettings::values.game_dirs.removeOne(game_dir); + item_model->invisibleRootItem()->removeRow(selected.row()); + }); +} + +void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) { + UISettings::GameDir& game_dir = + *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>(); + + QAction* move_up = context_menu.addAction(tr(u8"\U000025b2 Move Up")); + QAction* move_down = context_menu.addAction(tr(u8"\U000025bc Move Down ")); + QAction* open_directory_location = context_menu.addAction(tr("Open Directory Location")); + + const int row = selected.row(); + + move_up->setEnabled(row > 0); + move_down->setEnabled(row < item_model->rowCount() - 2); + + connect(move_up, &QAction::triggered, [this, selected, row, &game_dir] { + // find the indices of the items in settings and swap them + std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)], + UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf( + *selected.sibling(row - 1, 0) + .data(GameListDir::GameDirRole) + .value<UISettings::GameDir*>())]); + // move the treeview items + QList<QStandardItem*> item = item_model->takeRow(row); + item_model->invisibleRootItem()->insertRow(row - 1, item); + tree_view->setExpanded(selected, game_dir.expanded); + }); + + connect(move_down, &QAction::triggered, [this, selected, row, &game_dir] { + // find the indices of the items in settings and swap them + std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)], + UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf( + *selected.sibling(row + 1, 0) + .data(GameListDir::GameDirRole) + .value<UISettings::GameDir*>())]); + // move the treeview items + const QList<QStandardItem*> item = item_model->takeRow(row); + item_model->invisibleRootItem()->insertRow(row + 1, item); + tree_view->setExpanded(selected, game_dir.expanded); + }); + + connect(open_directory_location, &QAction::triggered, + [this, game_dir] { emit OpenDirectory(game_dir.path); }); } void GameList::LoadCompatibilityList() { @@ -403,14 +608,7 @@ void GameList::LoadCompatibilityList() { } } -void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { - const QFileInfo dir_info{dir_path}; - if (!dir_info.exists() || !dir_info.isDir()) { - LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toStdString()); - search_field->setFilterResult(0, 0); - return; - } - +void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { tree_view->setEnabled(false); // Update the columns in case UISettings has changed @@ -433,17 +631,19 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { // Delete any rows that might already exist if we're repopulating item_model->removeRows(0, item_model->rowCount()); + search_field->clear(); emit ShouldCancelWorker(); - GameListWorker* worker = - new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list); + GameListWorker* worker = new GameListWorker(vfs, provider, game_dirs, compatibility_list); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); + connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, + Qt::QueuedConnection); connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, Qt::QueuedConnection); - // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to cancel - // without delay. + // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to + // cancel without delay. connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel, Qt::DirectConnection); @@ -468,14 +668,43 @@ void GameList::LoadInterfaceLayout() { const QStringList GameList::supported_file_extensions = { QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"), - QStringLiteral("xci"), QStringLiteral("nsp"), -}; + QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; void GameList::RefreshGameDirectory() { - if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) { + if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) { LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); - search_field->clear(); - PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + PopulateAsync(UISettings::values.game_dirs); } } + +GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} { + connect(parent, &GMainWindow::UpdateThemedIcons, this, + &GameListPlaceholder::onUpdateThemedIcons); + + layout = new QVBoxLayout; + image = new QLabel; + text = new QLabel; + layout->setAlignment(Qt::AlignCenter); + image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); + + text->setText(tr("Double-click to add a new folder to the game list")); + QFont font = text->font(); + font.setPointSize(20); + text->setFont(font); + text->setAlignment(Qt::AlignHCenter); + image->setAlignment(Qt::AlignHCenter); + + layout->addWidget(image); + layout->addWidget(text); + setLayout(layout); +} + +GameListPlaceholder::~GameListPlaceholder() = default; + +void GameListPlaceholder::onUpdateThemedIcons() { + image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); +} + +void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) { + emit GameListPlaceholder::AddDirectory(); +} diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index f8f8bd6c5..878d94413 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -8,6 +8,7 @@ #include <QHBoxLayout> #include <QLabel> #include <QLineEdit> +#include <QList> #include <QModelIndex> #include <QSettings> #include <QStandardItem> @@ -16,13 +17,16 @@ #include <QToolButton> #include <QTreeView> #include <QVBoxLayout> +#include <QVector> #include <QWidget> #include "common/common_types.h" +#include "uisettings.h" #include "yuzu/compatibility_list.h" class GameListWorker; class GameListSearchField; +class GameListDir; class GMainWindow; namespace FileSys { @@ -52,12 +56,14 @@ public: FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr); ~GameList() override; + QString getLastFilterResultItem() const; void clearFilter(); void setFilterFocus(); void setFilterVisible(bool visibility); + bool isEmpty() const; void LoadCompatibilityList(); - void PopulateAsync(const QString& dir_path, bool deep_scan); + void PopulateAsync(QVector<UISettings::GameDir>& game_dirs); void SaveInterfaceLayout(); void LoadInterfaceLayout(); @@ -74,19 +80,29 @@ signals: void NavigateToGamedbEntryRequested(u64 program_id, const CompatibilityList& compatibility_list); void OpenPerGameGeneralRequested(const std::string& file); + void OpenDirectory(const QString& directory); + void AddDirectory(); + void ShowList(bool show); private slots: + void onItemExpanded(const QModelIndex& item); void onTextChanged(const QString& new_text); void onFilterCloseClicked(); + void onUpdateThemedIcons(); private: - void AddEntry(const QList<QStandardItem*>& entry_items); + void AddDirEntry(GameListDir* entry_items); + void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent); void ValidateEntry(const QModelIndex& item); void DonePopulating(QStringList watch_list); - void PopupContextMenu(const QPoint& menu_location); void RefreshGameDirectory(); + void PopupContextMenu(const QPoint& menu_location); + void AddGamePopup(QMenu& context_menu, u64 program_id, std::string path); + void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); + void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); + std::shared_ptr<FileSys::VfsFilesystem> vfs; FileSys::ManualContentProvider* provider; GameListSearchField* search_field; @@ -102,3 +118,24 @@ private: }; Q_DECLARE_METATYPE(GameListOpenTarget); + +class GameListPlaceholder : public QWidget { + Q_OBJECT +public: + explicit GameListPlaceholder(GMainWindow* parent = nullptr); + ~GameListPlaceholder(); + +signals: + void AddDirectory(); + +private slots: + void onUpdateThemedIcons(); + +protected: + void mouseDoubleClickEvent(QMouseEvent* event) override; + +private: + QVBoxLayout* layout = nullptr; + QLabel* image = nullptr; + QLabel* text = nullptr; +}; diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 0b458ef48..a8d888fee 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -10,6 +10,7 @@ #include <utility> #include <QCoreApplication> +#include <QFileInfo> #include <QImage> #include <QObject> #include <QStandardItem> @@ -19,9 +20,20 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "common/string_util.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" #include "yuzu/util/util.h" +enum class GameListItemType { + Game = QStandardItem::UserType + 1, + CustomDir = QStandardItem::UserType + 2, + SdmcDir = QStandardItem::UserType + 3, + UserNandDir = QStandardItem::UserType + 4, + SysNandDir = QStandardItem::UserType + 5, + AddDir = QStandardItem::UserType + 6 +}; + +Q_DECLARE_METATYPE(GameListItemType); + /** * Gets the default icon (for games without valid title metadata) * @param size The desired width and height of the default icon. @@ -36,8 +48,13 @@ static QPixmap GetDefaultIcon(u32 size) { class GameListItem : public QStandardItem { public: + // used to access type from item index + static const int TypeRole = Qt::UserRole + 1; + static const int SortRole = Qt::UserRole + 2; GameListItem() = default; - explicit GameListItem(const QString& string) : QStandardItem(string) {} + GameListItem(const QString& string) : QStandardItem(string) { + setData(string, SortRole); + } }; /** @@ -48,14 +65,15 @@ public: */ class GameListItemPath : public GameListItem { public: - static const int FullPathRole = Qt::UserRole + 1; - static const int TitleRole = Qt::UserRole + 2; - static const int ProgramIdRole = Qt::UserRole + 3; - static const int FileTypeRole = Qt::UserRole + 4; + static const int TitleRole = SortRole; + static const int FullPathRole = SortRole + 1; + static const int ProgramIdRole = SortRole + 2; + static const int FileTypeRole = SortRole + 3; GameListItemPath() = default; GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data, const QString& game_name, const QString& game_type, u64 program_id) { + setData(type(), TypeRole); setData(game_path, FullPathRole); setData(game_name, TitleRole); setData(qulonglong(program_id), ProgramIdRole); @@ -72,6 +90,10 @@ public: setData(picture, Qt::DecorationRole); } + int type() const override { + return static_cast<int>(GameListItemType::Game); + } + QVariant data(int role) const override { if (role == Qt::DisplayRole) { std::string filename; @@ -103,9 +125,11 @@ public: class GameListItemCompat : public GameListItem { Q_DECLARE_TR_FUNCTIONS(GameListItemCompat) public: - static const int CompatNumberRole = Qt::UserRole + 1; + static const int CompatNumberRole = SortRole; GameListItemCompat() = default; explicit GameListItemCompat(const QString& compatibility) { + setData(type(), TypeRole); + struct CompatStatus { QString color; const char* text; @@ -135,6 +159,10 @@ public: setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); } + int type() const override { + return static_cast<int>(GameListItemType::Game); + } + bool operator<(const QStandardItem& other) const override { return data(CompatNumberRole) < other.data(CompatNumberRole); } @@ -146,12 +174,12 @@ public: * human-readable string representation will be displayed to the user. */ class GameListItemSize : public GameListItem { - public: - static const int SizeRole = Qt::UserRole + 1; + static const int SizeRole = SortRole; GameListItemSize() = default; explicit GameListItemSize(const qulonglong size_bytes) { + setData(type(), TypeRole); setData(size_bytes, SizeRole); } @@ -167,6 +195,10 @@ public: } } + int type() const override { + return static_cast<int>(GameListItemType::Game); + } + /** * This operator is, in practice, only used by the TreeView sorting systems. * Override it so that it will correctly sort by numerical value instead of by string @@ -177,6 +209,82 @@ public: } }; +class GameListDir : public GameListItem { +public: + static const int GameDirRole = Qt::UserRole + 2; + + explicit GameListDir(UISettings::GameDir& directory, + GameListItemType dir_type = GameListItemType::CustomDir) + : dir_type{dir_type} { + setData(type(), TypeRole); + + UISettings::GameDir* game_dir = &directory; + setData(QVariant::fromValue(game_dir), GameDirRole); + + const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64); + switch (dir_type) { + case GameListItemType::SdmcDir: + setData( + QIcon::fromTheme(QStringLiteral("sd_card")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + setData(QObject::tr("Installed SD Titles"), Qt::DisplayRole); + break; + case GameListItemType::UserNandDir: + setData( + QIcon::fromTheme(QStringLiteral("chip")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + setData(QObject::tr("Installed NAND Titles"), Qt::DisplayRole); + break; + case GameListItemType::SysNandDir: + setData( + QIcon::fromTheme(QStringLiteral("chip")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + setData(QObject::tr("System Titles"), Qt::DisplayRole); + break; + case GameListItemType::CustomDir: + const QString icon_name = QFileInfo::exists(game_dir->path) + ? QStringLiteral("folder") + : QStringLiteral("bad_folder"); + setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled( + icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + setData(game_dir->path, Qt::DisplayRole); + break; + }; + }; + + int type() const override { + return static_cast<int>(dir_type); + } + +private: + GameListItemType dir_type; +}; + +class GameListAddDir : public GameListItem { +public: + explicit GameListAddDir() { + setData(type(), TypeRole); + + const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64); + setData(QIcon::fromTheme(QStringLiteral("plus")) + .pixmap(icon_size) + .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + Qt::DecorationRole); + setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole); + } + + int type() const override { + return static_cast<int>(GameListItemType::AddDir); + } +}; + class GameList; class QHBoxLayout; class QTreeView; @@ -208,6 +316,9 @@ private: // EventFilter in order to process systemkeys while editing the searchfield bool eventFilter(QObject* obj, QEvent* event) override; }; + int visible; + int total; + QHBoxLayout* layout_filter = nullptr; QTreeView* tree_view = nullptr; QLabel* label_filter = nullptr; diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 4f30e9147..fd21a9761 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -29,7 +29,7 @@ #include "yuzu/game_list.h" #include "yuzu/game_list_p.h" #include "yuzu/game_list_worker.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" namespace { @@ -223,21 +223,37 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri } // Anonymous namespace GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, - FileSys::ManualContentProvider* provider, QString dir_path, - bool deep_scan, const CompatibilityList& compatibility_list) - : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan), + FileSys::ManualContentProvider* provider, + QVector<UISettings::GameDir>& game_dirs, + const CompatibilityList& compatibility_list) + : vfs(std::move(vfs)), provider(provider), game_dirs(game_dirs), compatibility_list(compatibility_list) {} GameListWorker::~GameListWorker() = default; -void GameListWorker::AddTitlesToGameList() { - const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>( - Core::System::GetInstance().GetContentProvider()); - const auto installed_games = cache.ListEntriesFilterOrigin( - std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program); +void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { + using namespace FileSys; + + const auto& cache = + dynamic_cast<ContentProviderUnion&>(Core::System::GetInstance().GetContentProvider()); + + std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> installed_games; + installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application, + ContentRecordType::Program); + + if (parent_dir->type() == static_cast<int>(GameListItemType::SdmcDir)) { + installed_games = cache.ListEntriesFilterOrigin( + ContentProviderUnionSlot::SDMC, TitleType::Application, ContentRecordType::Program); + } else if (parent_dir->type() == static_cast<int>(GameListItemType::UserNandDir)) { + installed_games = cache.ListEntriesFilterOrigin( + ContentProviderUnionSlot::UserNAND, TitleType::Application, ContentRecordType::Program); + } else if (parent_dir->type() == static_cast<int>(GameListItemType::SysNandDir)) { + installed_games = cache.ListEntriesFilterOrigin( + ContentProviderUnionSlot::SysNAND, TitleType::Application, ContentRecordType::Program); + } for (const auto& [slot, game] : installed_games) { - if (slot == FileSys::ContentProviderUnionSlot::FrontendManual) + if (slot == ContentProviderUnionSlot::FrontendManual) continue; const auto file = cache.GetEntryUnparsed(game.title_id, game.type); @@ -250,21 +266,22 @@ void GameListWorker::AddTitlesToGameList() { u64 program_id = 0; loader->ReadProgramId(program_id); - const FileSys::PatchManager patch{program_id}; - const auto control = cache.GetEntry(game.title_id, FileSys::ContentRecordType::Control); + const PatchManager patch{program_id}; + const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control); if (control != nullptr) GetMetadataFromControlNCA(patch, *control, icon, name); emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, - compatibility_list, patch)); + compatibility_list, patch), + parent_dir); } } void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, - unsigned int recursion) { - const auto callback = [this, target, recursion](u64* num_entries_out, - const std::string& directory, - const std::string& virtual_name) -> bool { + unsigned int recursion, GameListDir* parent_dir) { + const auto callback = [this, target, recursion, + parent_dir](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { if (stop_processing) { // Breaks the callback loop. return false; @@ -317,11 +334,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa const FileSys::PatchManager patch{program_id}; emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, - compatibility_list, patch)); + compatibility_list, patch), + parent_dir); } } else if (is_dir && recursion > 0) { watch_list.append(QString::fromStdString(physical_name)); - ScanFileSystem(target, physical_name, recursion - 1); + ScanFileSystem(target, physical_name, recursion - 1, parent_dir); } return true; @@ -332,12 +350,32 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa void GameListWorker::run() { stop_processing = false; - watch_list.append(dir_path); - provider->ClearAllEntries(); - ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(), - deep_scan ? 256 : 0); - AddTitlesToGameList(); - ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0); + + for (UISettings::GameDir& game_dir : game_dirs) { + if (game_dir.path == QStringLiteral("SDMC")) { + auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); + emit DirEntryReady({game_list_dir}); + AddTitlesToGameList(game_list_dir); + } else if (game_dir.path == QStringLiteral("UserNAND")) { + auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); + emit DirEntryReady({game_list_dir}); + AddTitlesToGameList(game_list_dir); + } else if (game_dir.path == QStringLiteral("SysNAND")) { + auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); + emit DirEntryReady({game_list_dir}); + AddTitlesToGameList(game_list_dir); + } else { + watch_list.append(game_dir.path); + auto* const game_list_dir = new GameListDir(game_dir); + emit DirEntryReady({game_list_dir}); + provider->ClearAllEntries(); + ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2, + game_list_dir); + ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), + game_dir.deep_scan ? 256 : 0, game_list_dir); + } + }; + emit Finished(watch_list); } diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 7c3074af9..6e52fca89 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -14,6 +14,7 @@ #include <QObject> #include <QRunnable> #include <QString> +#include <QVector> #include "common/common_types.h" #include "yuzu/compatibility_list.h" @@ -33,9 +34,10 @@ class GameListWorker : public QObject, public QRunnable { Q_OBJECT public: - GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, - FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan, - const CompatibilityList& compatibility_list); + explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, + FileSys::ManualContentProvider* provider, + QVector<UISettings::GameDir>& game_dirs, + const CompatibilityList& compatibility_list); ~GameListWorker() override; /// Starts the processing of directory tree information. @@ -48,31 +50,33 @@ signals: /** * The `EntryReady` signal is emitted once an entry has been prepared and is ready * to be added to the game list. - * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. + * @param entry_items a list with `QStandardItem`s that make up the columns of the new + * entry. */ - void EntryReady(QList<QStandardItem*> entry_items); + void DirEntryReady(GameListDir* entry_items); + void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir); /** - * After the worker has traversed the game directory looking for entries, this signal is emitted - * with a list of folders that should be watched for changes as well. + * After the worker has traversed the game directory looking for entries, this signal is + * emitted with a list of folders that should be watched for changes as well. */ void Finished(QStringList watch_list); private: - void AddTitlesToGameList(); + void AddTitlesToGameList(GameListDir* parent_dir); enum class ScanTarget { FillManualContentProvider, PopulateGameList, }; - void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0); + void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion, + GameListDir* parent_dir); std::shared_ptr<FileSys::VfsFilesystem> vfs; FileSys::ManualContentProvider* provider; QStringList watch_list; - QString dir_path; - bool deep_scan; const CompatibilityList& compatibility_list; + QVector<UISettings::GameDir>& game_dirs; std::atomic_bool stop_processing; }; diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp index 4582e7f21..d4e97fa16 100644 --- a/src/yuzu/hotkeys.cpp +++ b/src/yuzu/hotkeys.cpp @@ -7,7 +7,7 @@ #include <QTreeWidgetItem> #include <QtGlobal> #include "yuzu/hotkeys.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" HotkeyRegistry::HotkeyRegistry() = default; HotkeyRegistry::~HotkeyRegistry() = default; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index cd32623b4..2d82df739 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -6,6 +6,9 @@ #include <clocale> #include <memory> #include <thread> +#ifdef __APPLE__ +#include <unistd.h> // for chdir +#endif // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. #include "applets/error.h" @@ -19,6 +22,8 @@ #include "core/frontend/applets/general_frontend.h" #include "core/frontend/scope_acquire_window_context.h" #include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/am/applet_ae.h" +#include "core/hle/service/am/applet_oe.h" #include "core/hle/service/am/applets/applets.h" #include "core/hle/service/hid/controllers/npad.h" #include "core/hle/service/hid/hid.h" @@ -51,6 +56,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include <QProgressDialog> #include <QShortcut> #include <QStatusBar> +#include <QSysInfo> #include <QtConcurrent/QtConcurrent> #include <fmt/format.h> @@ -63,6 +69,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "common/microprofile.h" #include "common/scm_rev.h" #include "common/scope_exit.h" +#ifdef ARCHITECTURE_x86_64 +#include "common/x64/cpu_detect.h" +#endif #include "common/telemetry.h" #include "core/core.h" #include "core/crypto/key_manager.h" @@ -76,6 +85,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/file_sys/submission_package.h" #include "core/frontend/applets/software_keyboard.h" #include "core/hle/kernel/process.h" +#include "core/hle/service/am/am.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/nfp/nfp.h" #include "core/hle/service/sm/sm.h" @@ -100,7 +110,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/hotkeys.h" #include "yuzu/loading_screen.h" #include "yuzu/main.h" -#include "yuzu/ui_settings.h" +#include "yuzu/uisettings.h" #ifdef USE_DISCORD_PRESENCE #include "yuzu/discord_impl.h" @@ -119,6 +129,7 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #endif #ifdef _WIN32 +#include <windows.h> extern "C" { // tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable // graphics @@ -201,6 +212,10 @@ GMainWindow::GMainWindow() LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); +#ifdef ARCHITECTURE_x86_64 + LOG_INFO(Frontend, "Host CPU: {}", Common::GetCPUCaps().cpu_string); +#endif + LOG_INFO(Frontend, "Host OS: {}", QSysInfo::prettyProductName().toStdString()); UpdateWindowTitle(); show(); @@ -209,14 +224,13 @@ GMainWindow::GMainWindow() std::make_unique<FileSys::ContentProviderUnion>()); Core::System::GetInstance().RegisterContentProvider( FileSys::ContentProviderUnionSlot::FrontendManual, provider.get()); - Service::FileSystem::CreateFactories(*vfs); + Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs); // Gen keys if necessary OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); game_list->LoadCompatibilityList(); - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + game_list->PopulateAsync(UISettings::values.game_dirs); // Show one-time "callout" messages to the user ShowTelemetryCallout(); @@ -426,6 +440,10 @@ void GMainWindow::InitializeWidgets() { game_list = new GameList(vfs, provider.get(), this); ui.horizontalLayout->addWidget(game_list); + game_list_placeholder = new GameListPlaceholder(this); + ui.horizontalLayout->addWidget(game_list_placeholder); + game_list_placeholder->setVisible(false); + loading_screen = new LoadingScreen(this); loading_screen->hide(); ui.horizontalLayout->addWidget(loading_screen); @@ -659,6 +677,7 @@ void GMainWindow::RestoreUIState() { void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); + connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory); connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, &GMainWindow::OnTransferableShaderCacheOpenFile); @@ -666,6 +685,11 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, &GMainWindow::OnGameListNavigateToGamedbEntry); + connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory); + connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this, + &GMainWindow::OnGameListAddDirectory); + connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList); + connect(game_list, &GameList::OpenPerGameGeneralRequested, this, &GMainWindow::OnGameListOpenPerGameProperties); @@ -683,8 +707,6 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); connect(ui.action_Install_File_NAND, &QAction::triggered, this, &GMainWindow::OnMenuInstallToNAND); - connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, - &GMainWindow::OnMenuSelectGameListRoot); connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, @@ -747,9 +769,24 @@ void GMainWindow::OnDisplayTitleBars(bool show) { } } +void GMainWindow::PreventOSSleep() { +#ifdef _WIN32 + SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED); +#endif +} + +void GMainWindow::AllowOSSleep() { +#ifdef _WIN32 + SetThreadExecutionState(ES_CONTINUOUS); +#endif +} + QStringList GMainWindow::GetUnsupportedGLExtensions() { QStringList unsupported_ext; + if (!GLAD_GL_ARB_buffer_storage) { + unsupported_ext.append(QStringLiteral("ARB_buffer_storage")); + } if (!GLAD_GL_ARB_direct_state_access) { unsupported_ext.append(QStringLiteral("ARB_direct_state_access")); } @@ -814,11 +851,13 @@ bool GMainWindow::LoadROM(const QString& filename) { system.SetGPUDebugContext(debug_context); system.SetAppletFrontendSet({ - std::make_unique<QtErrorDisplay>(*this), - nullptr, - std::make_unique<QtProfileSelector>(*this), - std::make_unique<QtSoftwareKeyboard>(*this), - std::make_unique<QtWebBrowser>(*this), + nullptr, // Parental Controls + std::make_unique<QtErrorDisplay>(*this), // + nullptr, // Photo Viewer + std::make_unique<QtProfileSelector>(*this), // + std::make_unique<QtSoftwareKeyboard>(*this), // + std::make_unique<QtWebBrowser>(*this), // + nullptr, // E-Commerce }); const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; @@ -932,14 +971,15 @@ void GMainWindow::BootGame(const QString& filename) { // Update the GUI if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); + game_list_placeholder->hide(); } status_bar_update_timer.start(2000); + const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + std::string title_name; const auto res = Core::System::GetInstance().GetGameName(title_name); if (res != Loader::ResultStatus::Success) { - const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); - const auto [nacp, icon_file] = FileSys::PatchManager(title_id).GetControlMetadata(); if (nacp != nullptr) title_name = nacp->GetApplicationName(); @@ -947,7 +987,7 @@ void GMainWindow::BootGame(const QString& filename) { if (title_name.empty()) title_name = FileUtil::GetFilename(filename.toStdString()); } - + LOG_INFO(Frontend, "Booting game: {:016X} | {}", title_id, title_name); UpdateWindowTitle(QString::fromStdString(title_name)); loading_screen->Prepare(Core::System::GetInstance().GetAppLoader()); @@ -961,6 +1001,8 @@ void GMainWindow::BootGame(const QString& filename) { } void GMainWindow::ShutdownGame() { + AllowOSSleep(); + discord_rpc->Pause(); emu_thread->RequestStop(); @@ -987,7 +1029,10 @@ void GMainWindow::ShutdownGame() { render_window->hide(); loading_screen->hide(); loading_screen->Clear(); - game_list->show(); + if (game_list->isEmpty()) + game_list_placeholder->show(); + else + game_list->show(); game_list->setFilterFocus(); UpdateWindowTitle(); @@ -1278,6 +1323,47 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); } +void GMainWindow::OnGameListOpenDirectory(const QString& directory) { + QString path; + if (directory == QStringLiteral("SDMC")) { + path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + + "Nintendo/Contents/registered"); + } else if (directory == QStringLiteral("UserNAND")) { + path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "user/Contents/registered"); + } else if (directory == QStringLiteral("SysNAND")) { + path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "system/Contents/registered"); + } else { + path = directory; + } + if (!QFileInfo::exists(path)) { + QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!")); + return; + } + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); +} + +void GMainWindow::OnGameListAddDirectory() { + const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); + if (dir_path.isEmpty()) + return; + UISettings::GameDir game_dir{dir_path, false, true}; + if (!UISettings::values.game_dirs.contains(game_dir)) { + UISettings::values.game_dirs.append(game_dir); + game_list->PopulateAsync(UISettings::values.game_dirs); + } else { + LOG_WARNING(Frontend, "Selected directory is already in the game list"); + } +} + +void GMainWindow::OnGameListShowList(bool show) { + if (emulation_running && ui.action_Single_Window_Mode->isChecked()) + return; + game_list->setVisible(show); + game_list_placeholder->setVisible(!show); +}; + void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { u64 title_id{}; const auto v_file = Core::GetGameFileFromPath(vfs, file); @@ -1296,8 +1382,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); if (reload) { - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + game_list->PopulateAsync(UISettings::values.game_dirs); } config->Save(); @@ -1387,8 +1472,7 @@ void GMainWindow::OnMenuInstallToNAND() { const auto success = [this]() { QMessageBox::information(this, tr("Successfully Installed"), tr("The file was successfully installed.")); - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + game_list->PopulateAsync(UISettings::values.game_dirs); FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list"); }; @@ -1426,15 +1510,19 @@ void GMainWindow::OnMenuInstallToNAND() { failed(); return; } - const auto res = - Service::FileSystem::GetUserNANDContents()->InstallEntry(*nsp, false, qt_raw_copy); + const auto res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->InstallEntry(*nsp, false, qt_raw_copy); if (res == FileSys::InstallResult::Success) { success(); } else { if (res == FileSys::InstallResult::ErrorAlreadyExists) { if (overwrite()) { - const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( - *nsp, true, qt_raw_copy); + const auto res2 = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->InstallEntry(*nsp, true, qt_raw_copy); if (res2 == FileSys::InstallResult::Success) { success(); } else { @@ -1488,19 +1576,28 @@ void GMainWindow::OnMenuInstallToNAND() { FileSys::InstallResult res; if (index >= static_cast<size_t>(FileSys::TitleType::Application)) { - res = Service::FileSystem::GetUserNANDContents()->InstallEntry( - *nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); + res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false, + qt_raw_copy); } else { - res = Service::FileSystem::GetSystemNANDContents()->InstallEntry( - *nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); + res = Core::System::GetInstance() + .GetFileSystemController() + .GetSystemNANDContents() + ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false, + qt_raw_copy); } if (res == FileSys::InstallResult::Success) { success(); } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { if (overwrite()) { - const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( - *nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy); + const auto res2 = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), + true, qt_raw_copy); if (res2 == FileSys::InstallResult::Success) { success(); } else { @@ -1513,14 +1610,6 @@ void GMainWindow::OnMenuInstallToNAND() { } } -void GMainWindow::OnMenuSelectGameListRoot() { - QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); - if (!dir_path.isEmpty()) { - UISettings::values.game_directory_path = dir_path; - game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan); - } -} - void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { const auto res = QMessageBox::information( this, tr("Changing Emulated Directory"), @@ -1538,9 +1627,8 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) FileUtil::GetUserPath(target == EmulatedDirectoryTarget::SDMC ? FileUtil::UserPath::SDMCDir : FileUtil::UserPath::NANDDir, dir_path.toStdString()); - Service::FileSystem::CreateFactories(*vfs); - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs); + game_list->PopulateAsync(UISettings::values.game_dirs); } } @@ -1562,6 +1650,8 @@ void GMainWindow::OnMenuRecentFile() { } void GMainWindow::OnStartGame() { + PreventOSSleep(); + emu_thread->SetRunning(true); qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>( @@ -1587,15 +1677,27 @@ void GMainWindow::OnStartGame() { } void GMainWindow::OnPauseGame() { + Core::System& system{Core::System::GetInstance()}; + if (system.GetExitLock() && !ConfirmForceLockedExit()) { + return; + } + emu_thread->SetRunning(false); ui.action_Start->setEnabled(true); ui.action_Pause->setEnabled(false); ui.action_Stop->setEnabled(true); ui.action_Capture_Screenshot->setEnabled(false); + + AllowOSSleep(); } void GMainWindow::OnStopGame() { + Core::System& system{Core::System::GetInstance()}; + if (system.GetExitLock() && !ConfirmForceLockedExit()) { + return; + } + ShutdownGame(); } @@ -1700,11 +1802,11 @@ void GMainWindow::OnConfigure() { if (UISettings::values.enable_discord_presence != old_discord_presence) { SetDiscordEnabled(UISettings::values.enable_discord_presence); } + emit UpdateThemedIcons(); const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); if (reload) { - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + game_list->PopulateAsync(UISettings::values.game_dirs); } config->Save(); @@ -1838,13 +1940,14 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det "data, or other bugs."); switch (result) { case Core::System::ResultStatus::ErrorSystemFiles: { - QString message = tr("yuzu was unable to locate a Switch system archive"); - if (!details.empty()) { - message.append(tr(": %1. ").arg(QString::fromStdString(details))); + QString message; + if (details.empty()) { + message = + tr("yuzu was unable to locate a Switch system archive. %1").arg(common_message); } else { - message.append(tr(". ")); + message = tr("yuzu was unable to locate a Switch system archive: %1. %2") + .arg(QString::fromStdString(details), common_message); } - message.append(common_message); answer = QMessageBox::question(this, tr("System Archive Not Found"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); @@ -1853,8 +1956,8 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det } case Core::System::ResultStatus::ErrorSharedFont: { - QString message = tr("yuzu was unable to locate the Switch shared fonts. "); - message.append(common_message); + const QString message = + tr("yuzu was unable to locate the Switch shared fonts. %1").arg(common_message); answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); status_message = tr("Shared Font Missing"); @@ -1919,7 +2022,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { const auto function = [this, &keys, &pdm] { keys.PopulateFromPartitionData(pdm); - Service::FileSystem::CreateFactories(*vfs); + Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs); keys.DeriveETicket(pdm); }; @@ -1944,7 +2047,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { errors + tr("<br><br>You can get all of these and dump all of your games easily by " "following <a href='https://yuzu-emu.org/help/quickstart/'>the " - "quickstart guide</a>. Alternatively, you can use another method of dumping" + "quickstart guide</a>. Alternatively, you can use another method of dumping " "to obtain all of your keys.")); } @@ -1964,11 +2067,10 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { prog.close(); } - Service::FileSystem::CreateFactories(*vfs); + Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs); if (behavior == ReinitializeKeyBehavior::Warning) { - game_list->PopulateAsync(UISettings::values.game_directory_path, - UISettings::values.game_directory_deepscan); + game_list->PopulateAsync(UISettings::values.game_dirs); } } @@ -2093,13 +2195,41 @@ bool GMainWindow::ConfirmChangeGame() { if (emu_thread == nullptr) return true; - auto answer = QMessageBox::question( + const auto answer = QMessageBox::question( this, tr("yuzu"), tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); return answer != QMessageBox::No; } +bool GMainWindow::ConfirmForceLockedExit() { + if (emu_thread == nullptr) + return true; + + const auto answer = + QMessageBox::question(this, tr("yuzu"), + tr("The currently running application has requested yuzu to not " + "exit.\n\nWould you like to bypass this and exit anyway?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + return answer != QMessageBox::No; +} + +void GMainWindow::RequestGameExit() { + auto& sm{Core::System::GetInstance().ServiceManager()}; + auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); + auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); + bool has_signalled = false; + + if (applet_oe != nullptr) { + applet_oe->GetMessageQueue()->RequestExit(); + has_signalled = true; + } + + if (applet_ae != nullptr && !has_signalled) { + applet_ae->GetMessageQueue()->RequestExit(); + } +} + void GMainWindow::filterBarSetChecked(bool state) { ui.action_Show_Filter_Bar->setChecked(state); emit(OnToggleFilterBar()); @@ -2133,7 +2263,6 @@ void GMainWindow::UpdateUITheme() { } QIcon::setThemeSearchPaths(theme_paths); - emit UpdateThemedIcons(); } void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { @@ -2162,6 +2291,14 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); QCoreApplication::setApplicationName(QStringLiteral("yuzu")); +#ifdef __APPLE__ + // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/". + // But since we require the working directory to be the executable path for the location of the + // user folder in the Qt Frontend, we need to cd into that working directory + const std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; + chdir(bin_path.c_str()); +#endif + // Enables the core to make the qt created contexts current on std::threads QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); QApplication app(argc, argv); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 1137bbc7a..e942d1248 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -30,6 +30,7 @@ class ProfilerWidget; class QLabel; class WaitTreeWidget; enum class GameListOpenTarget; +class GameListPlaceholder; namespace Core::Frontend { struct SoftwareKeyboardParameters; @@ -130,6 +131,9 @@ private: void ConnectWidgetEvents(); void ConnectMenuEvents(); + void PreventOSSleep(); + void AllowOSSleep(); + QStringList GetUnsupportedGLExtensions(); bool LoadROM(const QString& filename); void BootGame(const QString& filename); @@ -168,6 +172,8 @@ private: */ bool ConfirmClose(); bool ConfirmChangeGame(); + bool ConfirmForceLockedExit(); + void RequestGameExit(); void closeEvent(QCloseEvent* event) override; private slots: @@ -183,12 +189,13 @@ private slots: void OnGameListCopyTID(u64 program_id); void OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list); + void OnGameListOpenDirectory(const QString& directory); + void OnGameListAddDirectory(); + void OnGameListShowList(bool show); void OnGameListOpenPerGameProperties(const std::string& file); void OnMenuLoadFile(); void OnMenuLoadFolder(); void OnMenuInstallToNAND(); - /// Called whenever a user selects the "File->Select Game List Root" menu item - void OnMenuSelectGameListRoot(); /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); void OnMenuRecentFile(); @@ -220,6 +227,8 @@ private: GameList* game_list; LoadingScreen* loading_screen; + GameListPlaceholder* game_list_placeholder; + // Status bar elements QLabel* message_label = nullptr; QLabel* emu_speed_label = nullptr; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index ffcabb495..a1ce3c0c3 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -62,7 +62,6 @@ <addaction name="action_Load_File"/> <addaction name="action_Load_Folder"/> <addaction name="separator"/> - <addaction name="action_Select_Game_List_Root"/> <addaction name="menu_recent_files"/> <addaction name="separator"/> <addaction name="action_Select_NAND_Directory"/> diff --git a/src/yuzu/ui_settings.cpp b/src/yuzu/uisettings.cpp index 4bdc302e0..7f7d247a3 100644 --- a/src/yuzu/ui_settings.cpp +++ b/src/yuzu/uisettings.cpp @@ -2,7 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "ui_settings.h" +#include "yuzu/uisettings.h" namespace UISettings { diff --git a/src/yuzu/ui_settings.h b/src/yuzu/uisettings.h index a62cd6911..c57290006 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/uisettings.h @@ -8,8 +8,10 @@ #include <atomic> #include <vector> #include <QByteArray> +#include <QMetaType> #include <QString> #include <QStringList> +#include <QVector> #include "common/common_types.h" namespace UISettings { @@ -25,6 +27,18 @@ struct Shortcut { using Themes = std::array<std::pair<const char*, const char*>, 2>; extern const Themes themes; +struct GameDir { + QString path; + bool deep_scan; + bool expanded; + bool operator==(const GameDir& rhs) const { + return path == rhs.path; + }; + bool operator!=(const GameDir& rhs) const { + return !operator==(rhs); + }; +}; + struct Values { QByteArray geometry; QByteArray state; @@ -55,8 +69,9 @@ struct Values { QString roms_path; QString symbols_path; QString screenshot_path; - QString game_directory_path; - bool game_directory_deepscan; + QString game_dir_deprecated; + bool game_dir_deprecated_deepscan; + QVector<UISettings::GameDir> game_dirs; QStringList recent_files; QString theme; @@ -84,3 +99,5 @@ struct Values { extern Values values; } // namespace UISettings + +Q_DECLARE_METATYPE(UISettings::GameDir*); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index f3817bb87..d82438502 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -316,6 +316,29 @@ void Config::ReadValues() { FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, sdl2_config->Get("Data Storage", "sdmc_directory", FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); + FileUtil::GetUserPath(FileUtil::UserPath::LoadDir, + sdl2_config->Get("Data Storage", "load_directory", + FileUtil::GetUserPath(FileUtil::UserPath::LoadDir))); + FileUtil::GetUserPath(FileUtil::UserPath::DumpDir, + sdl2_config->Get("Data Storage", "dump_directory", + FileUtil::GetUserPath(FileUtil::UserPath::DumpDir))); + FileUtil::GetUserPath(FileUtil::UserPath::CacheDir, + sdl2_config->Get("Data Storage", "cache_directory", + FileUtil::GetUserPath(FileUtil::UserPath::CacheDir))); + Settings::values.gamecard_inserted = + sdl2_config->GetBoolean("Data Storage", "gamecard_inserted", false); + Settings::values.gamecard_current_game = + sdl2_config->GetBoolean("Data Storage", "gamecard_current_game", false); + Settings::values.gamecard_path = sdl2_config->Get("Data Storage", "gamecard_path", ""); + Settings::values.nand_total_size = static_cast<Settings::NANDTotalSize>(sdl2_config->GetInteger( + "Data Storage", "nand_total_size", static_cast<long>(Settings::NANDTotalSize::S29_1GB))); + Settings::values.nand_user_size = static_cast<Settings::NANDUserSize>(sdl2_config->GetInteger( + "Data Storage", "nand_user_size", static_cast<long>(Settings::NANDUserSize::S26GB))); + Settings::values.nand_system_size = static_cast<Settings::NANDSystemSize>( + sdl2_config->GetInteger("Data Storage", "nand_system_size", + static_cast<long>(Settings::NANDSystemSize::S2_5GB))); + Settings::values.sdmc_size = static_cast<Settings::SDMCSize>(sdl2_config->GetInteger( + "Data Storage", "sdmc_size", static_cast<long>(Settings::SDMCSize::S16GB))); // System Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); @@ -340,7 +363,6 @@ void Config::ReadValues() { } // Core - Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); // Renderer @@ -375,12 +397,17 @@ void Config::ReadValues() { Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); // Debugging + Settings::values.record_frame_times = + sdl2_config->GetBoolean("Debugging", "record_frame_times", false); Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); Settings::values.gdbstub_port = static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", ""); Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false); Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); + Settings::values.reporting_services = + sdl2_config->GetBoolean("Debugging", "reporting_services", false); + Settings::values.quest_flag = sdl2_config->GetBoolean("Debugging", "quest_flag", false); const auto title_list = sdl2_config->Get("AddOns", "title_ids", ""); std::stringstream ss(title_list); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 6538af098..a6171c3ed 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -76,10 +76,6 @@ motion_device= touch_device= [Core] -# Whether to use the Just-In-Time (JIT) compiler for CPU emulation -# 0: Interpreter (slow), 1 (default): JIT (fast) -use_cpu_jit = - # Whether to use multi-core for CPU emulation # 0 (default): Disabled, 1: Enabled use_multi_core= @@ -177,6 +173,20 @@ volume = # 1 (default): Yes, 0: No use_virtual_sd = +# Whether or not to enable gamecard emulation +# 1: Yes, 0 (default): No +gamecard_inserted = + +# Whether or not the gamecard should be emulated as the current game +# If 'gamecard_inserted' is 0 this setting is irrelevant +# 1: Yes, 0 (default): No +gamecard_current_game = + +# Path to an XCI file to use as the gamecard +# If 'gamecard_inserted' is 0 this setting is irrelevant +# If 'gamecard_current_game' is 1 this setting is irrelevant +gamecard_path = + [System] # Whether the system is docked # 1: Yes, 0 (default): No @@ -217,6 +227,8 @@ region_value = log_filter = *:Trace [Debugging] +# Record frame time data, can be found in the log directory. Boolean value +record_frame_times = # Port for listening to GDB connections. use_gdbstub=false gdbstub_port=24689 @@ -224,6 +236,9 @@ gdbstub_port=24689 dump_exefs=false # Determines whether or not yuzu will dump all NSOs it attempts to load while loading them dump_nso=false +# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode +# false: Retail/Normal Mode (default), true: Kiosk Mode +quest_flag = [WebService] # Whether or not to enable telemetry diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp index e2d3df180..f91b071bf 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp @@ -52,6 +52,10 @@ private: bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { std::vector<std::string> unsupported_ext; + if (!GLAD_GL_ARB_buffer_storage) + unsupported_ext.push_back("ARB_buffer_storage"); + if (!GLAD_GL_ARB_direct_state_access) + unsupported_ext.push_back("ARB_direct_state_access"); if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev"); if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 129d8ca73..bac05b959 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -184,7 +184,7 @@ int main(int argc, char** argv) { Core::System& system{Core::System::GetInstance()}; system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); - Service::FileSystem::CreateFactories(*system.GetFilesystem()); + system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); SCOPE_EXIT({ system.Shutdown(); }); diff --git a/src/yuzu_tester/CMakeLists.txt b/src/yuzu_tester/CMakeLists.txt new file mode 100644 index 000000000..06c2ee011 --- /dev/null +++ b/src/yuzu_tester/CMakeLists.txt @@ -0,0 +1,34 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) + +add_executable(yuzu-tester + config.cpp + config.h + default_ini.h + emu_window/emu_window_sdl2_hide.cpp + emu_window/emu_window_sdl2_hide.h + resource.h + service/yuzutest.cpp + service/yuzutest.h + yuzu.cpp + yuzu.rc +) + +create_target_directory_groups(yuzu-tester) + +target_link_libraries(yuzu-tester PRIVATE common core input_common) +target_link_libraries(yuzu-tester PRIVATE inih glad) +if (MSVC) + target_link_libraries(yuzu-tester PRIVATE getopt) +endif() +target_link_libraries(yuzu-tester PRIVATE ${PLATFORM_LIBRARIES} SDL2 Threads::Threads) + +if(UNIX AND NOT APPLE) + install(TARGETS yuzu-tester RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") +endif() + +if (MSVC) + include(CopyYuzuSDLDeps) + include(CopyYuzuUnicornDeps) + copy_yuzu_SDL_deps(yuzu-tester) + copy_yuzu_unicorn_deps(yuzu-tester) +endif() diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp new file mode 100644 index 000000000..9a11dc6c3 --- /dev/null +++ b/src/yuzu_tester/config.cpp @@ -0,0 +1,183 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <sstream> +#include <SDL.h> +#include <inih/cpp/INIReader.h> +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/param_package.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "yuzu_tester/config.h" +#include "yuzu_tester/default_ini.h" + +Config::Config() { + // TODO: Don't hardcode the path; let the frontend decide where to put the config files. + sdl2_config_loc = + FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-tester-config.ini"; + sdl2_config = std::make_unique<INIReader>(sdl2_config_loc); + + Reload(); +} + +Config::~Config() = default; + +bool Config::LoadINI(const std::string& default_contents, bool retry) { + const char* location = this->sdl2_config_loc.c_str(); + if (sdl2_config->ParseError() < 0) { + if (retry) { + LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); + FileUtil::CreateFullPath(location); + FileUtil::WriteStringToFile(true, default_contents, location); + sdl2_config = std::make_unique<INIReader>(location); // Reopen file + + return LoadINI(default_contents, false); + } + LOG_ERROR(Config, "Failed."); + return false; + } + LOG_INFO(Config, "Successfully loaded {}", location); + return true; +} + +void Config::ReadValues() { + // Controls + for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + Settings::values.players[p].buttons[i] = ""; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + Settings::values.players[p].analogs[i] = ""; + } + } + + Settings::values.mouse_enabled = false; + for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { + Settings::values.mouse_buttons[i] = ""; + } + + Settings::values.motion_device = ""; + + Settings::values.keyboard_enabled = false; + + Settings::values.debug_pad_enabled = false; + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + Settings::values.debug_pad_buttons[i] = ""; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + Settings::values.debug_pad_analogs[i] = ""; + } + + Settings::values.touchscreen.enabled = ""; + Settings::values.touchscreen.device = ""; + Settings::values.touchscreen.finger = 0; + Settings::values.touchscreen.rotation_angle = 0; + Settings::values.touchscreen.diameter_x = 15; + Settings::values.touchscreen.diameter_y = 15; + + // Data Storage + Settings::values.use_virtual_sd = + sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir, + sdl2_config->Get("Data Storage", "nand_directory", + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, + sdl2_config->Get("Data Storage", "sdmc_directory", + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); + + // System + Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); + const auto size = sdl2_config->GetInteger("System", "users_size", 0); + + Settings::values.current_user = std::clamp<int>( + sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1); + + const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false); + if (rng_seed_enabled) { + Settings::values.rng_seed = sdl2_config->GetInteger("System", "rng_seed", 0); + } else { + Settings::values.rng_seed = std::nullopt; + } + + const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false); + if (custom_rtc_enabled) { + Settings::values.custom_rtc = + std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)); + } else { + Settings::values.custom_rtc = std::nullopt; + } + + // Core + Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); + + // Renderer + Settings::values.resolution_factor = + static_cast<float>(sdl2_config->GetReal("Renderer", "resolution_factor", 1.0)); + Settings::values.use_frame_limit = false; + Settings::values.frame_limit = 100; + Settings::values.use_disk_shader_cache = + sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false); + Settings::values.use_accurate_gpu_emulation = + sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false); + Settings::values.use_asynchronous_gpu_emulation = + sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false); + + Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)); + Settings::values.bg_green = + static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0)); + Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0)); + + // Audio + Settings::values.sink_id = "null"; + Settings::values.enable_audio_stretching = false; + Settings::values.audio_device_id = "auto"; + Settings::values.volume = 0; + + Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1); + + // Miscellaneous + Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); + Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); + + // Debugging + Settings::values.use_gdbstub = false; + Settings::values.program_args = ""; + Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false); + Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); + + const auto title_list = sdl2_config->Get("AddOns", "title_ids", ""); + std::stringstream ss(title_list); + std::string line; + while (std::getline(ss, line, '|')) { + const auto title_id = std::stoul(line, nullptr, 16); + const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); + + std::stringstream inner_ss(disabled_list); + std::string inner_line; + std::vector<std::string> out; + while (std::getline(inner_ss, inner_line, '|')) { + out.push_back(inner_line); + } + + Settings::values.disabled_addons.insert_or_assign(title_id, out); + } + + // Web Service + Settings::values.enable_telemetry = + sdl2_config->GetBoolean("WebService", "enable_telemetry", true); + Settings::values.web_api_url = + sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org"); + Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", ""); + Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", ""); +} + +void Config::Reload() { + LoadINI(DefaultINI::sdl2_config_file); + ReadValues(); +} diff --git a/src/yuzu_tester/config.h b/src/yuzu_tester/config.h new file mode 100644 index 000000000..3b68e5bc9 --- /dev/null +++ b/src/yuzu_tester/config.h @@ -0,0 +1,24 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <string> + +class INIReader; + +class Config { + std::unique_ptr<INIReader> sdl2_config; + std::string sdl2_config_loc; + + bool LoadINI(const std::string& default_contents = "", bool retry = true); + void ReadValues(); + +public: + Config(); + ~Config(); + + void Reload(); +}; diff --git a/src/yuzu_tester/default_ini.h b/src/yuzu_tester/default_ini.h new file mode 100644 index 000000000..9a3e86d68 --- /dev/null +++ b/src/yuzu_tester/default_ini.h @@ -0,0 +1,146 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace DefaultINI { + +const char* sdl2_config_file = R"( +[Core] +# Whether to use multi-core for CPU emulation +# 0 (default): Disabled, 1: Enabled +use_multi_core= + +[Renderer] +# Whether to use software or hardware rendering. +# 0: Software, 1 (default): Hardware +use_hw_renderer = + +# Whether to use the Just-In-Time (JIT) compiler for shader emulation +# 0: Interpreter (slow), 1 (default): JIT (fast) +use_shader_jit = + +# Resolution scale factor +# 0: Auto (scales resolution to window size), 1: Native Switch screen resolution, Otherwise a scale +# factor for the Switch resolution +resolution_factor = + +# Whether to enable V-Sync (caps the framerate at 60FPS) or not. +# 0 (default): Off, 1: On +use_vsync = + +# Whether to use disk based shader cache +# 0 (default): Off, 1 : On +use_disk_shader_cache = + +# Whether to use accurate GPU emulation +# 0 (default): Off (fast), 1 : On (slow) +use_accurate_gpu_emulation = + +# Whether to use asynchronous GPU emulation +# 0 : Off (slow), 1 (default): On (fast) +use_asynchronous_gpu_emulation = + +# The clear color for the renderer. What shows up on the sides of the bottom screen. +# Must be in range of 0.0-1.0. Defaults to 1.0 for all. +bg_red = +bg_blue = +bg_green = + +[Layout] +# Layout for the screen inside the render window. +# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen +layout_option = + +# Toggle custom layout (using the settings below) on or off. +# 0 (default): Off, 1: On +custom_layout = + +# Screen placement when using Custom layout option +# 0x, 0y is the top left corner of the render window. +custom_top_left = +custom_top_top = +custom_top_right = +custom_top_bottom = +custom_bottom_left = +custom_bottom_top = +custom_bottom_right = +custom_bottom_bottom = + +# Swaps the prominent screen with the other screen. +# For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen. +# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent +swap_screen = + +[Data Storage] +# Whether to create a virtual SD card. +# 1 (default): Yes, 0: No +use_virtual_sd = + +[System] +# Whether the system is docked +# 1: Yes, 0 (default): No +use_docked_mode = + +# Allow the use of NFC in games +# 1 (default): Yes, 0 : No +enable_nfc = + +# Sets the seed for the RNG generator built into the switch +# rng_seed will be ignored and randomly generated if rng_seed_enabled is false +rng_seed_enabled = +rng_seed = + +# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service +# This will auto-increment, with the time set being the time the game is started +# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used +custom_rtc_enabled = +custom_rtc = + +# Sets the account username, max length is 32 characters +# yuzu (default) +username = yuzu + +# Sets the systems language index +# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese, +# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French, +# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese +language_index = + +# The system region that yuzu will use during emulation +# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan +region_value = + +[Miscellaneous] +# A filter which removes logs below a certain logging level. +# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical +log_filter = *:Trace + +[Debugging] +# Arguments to be passed to argv/argc in the emulated program. It is preferable to use the testing service datastring +program_args= +# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them +dump_exefs=false +# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them +dump_nso=false + +[WebService] +# Whether or not to enable telemetry +# 0: No, 1 (default): Yes +enable_telemetry = +# URL for Web API +web_api_url = https://api.yuzu-emu.org +# Username and token for yuzu Web Service +# See https://profile.yuzu-emu.org/ for more info +yuzu_username = +yuzu_token = + +[AddOns] +# Used to disable add-ons +# List of title IDs of games that will have add-ons disabled (separated by '|'): +title_ids = +# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|') +# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey +)"; +} diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp new file mode 100644 index 000000000..e7fe8decf --- /dev/null +++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp @@ -0,0 +1,122 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cstdlib> +#include <string> +#define SDL_MAIN_HANDLED +#include <SDL.h> +#include <fmt/format.h> +#include <glad/glad.h> +#include "common/logging/log.h" +#include "common/scm_rev.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "yuzu_tester/emu_window/emu_window_sdl2_hide.h" + +bool EmuWindow_SDL2_Hide::SupportsRequiredGLExtensions() { + std::vector<std::string> unsupported_ext; + + if (!GLAD_GL_ARB_direct_state_access) + unsupported_ext.push_back("ARB_direct_state_access"); + if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) + unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev"); + if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) + unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge"); + if (!GLAD_GL_ARB_multi_bind) + unsupported_ext.push_back("ARB_multi_bind"); + + // Extensions required to support some texture formats. + if (!GLAD_GL_EXT_texture_compression_s3tc) + unsupported_ext.push_back("EXT_texture_compression_s3tc"); + if (!GLAD_GL_ARB_texture_compression_rgtc) + unsupported_ext.push_back("ARB_texture_compression_rgtc"); + if (!GLAD_GL_ARB_depth_buffer_float) + unsupported_ext.push_back("ARB_depth_buffer_float"); + + for (const std::string& ext : unsupported_ext) + LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext); + + return unsupported_ext.empty(); +} + +EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() { + // Initialize the window + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); + exit(1); + } + + InputCommon::Init(); + + SDL_SetMainReady(); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); + + std::string window_title = fmt::format("yuzu-tester {} | {}-{}", Common::g_build_fullname, + Common::g_scm_branch, Common::g_scm_desc); + render_window = SDL_CreateWindow(window_title.c_str(), + SDL_WINDOWPOS_UNDEFINED, // x position + SDL_WINDOWPOS_UNDEFINED, // y position + Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | + SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); + + if (render_window == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError()); + exit(1); + } + + gl_context = SDL_GL_CreateContext(render_window); + + if (gl_context == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError()); + exit(1); + } + + if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) { + LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError()); + exit(1); + } + + if (!SupportsRequiredGLExtensions()) { + LOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting..."); + exit(1); + } + + SDL_PumpEvents(); + SDL_GL_SetSwapInterval(false); + LOG_INFO(Frontend, "yuzu-tester Version: {} | {}-{}", Common::g_build_fullname, + Common::g_scm_branch, Common::g_scm_desc); + Settings::LogSettings(); + + DoneCurrent(); +} + +EmuWindow_SDL2_Hide::~EmuWindow_SDL2_Hide() { + InputCommon::Shutdown(); + SDL_GL_DeleteContext(gl_context); + SDL_Quit(); +} + +void EmuWindow_SDL2_Hide::SwapBuffers() { + SDL_GL_SwapWindow(render_window); +} + +void EmuWindow_SDL2_Hide::PollEvents() {} + +void EmuWindow_SDL2_Hide::MakeCurrent() { + SDL_GL_MakeCurrent(render_window, gl_context); +} + +void EmuWindow_SDL2_Hide::DoneCurrent() { + SDL_GL_MakeCurrent(render_window, nullptr); +} diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h new file mode 100644 index 000000000..1a8953c75 --- /dev/null +++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h @@ -0,0 +1,41 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/frontend/emu_window.h" + +struct SDL_Window; + +class EmuWindow_SDL2_Hide : public Core::Frontend::EmuWindow { +public: + explicit EmuWindow_SDL2_Hide(); + ~EmuWindow_SDL2_Hide(); + + /// Swap buffers to display the next frame + void SwapBuffers() override; + + /// Polls window events + void PollEvents() override; + + /// Makes the graphics context current for the caller thread + void MakeCurrent() override; + + /// Releases the GL context from the caller thread + void DoneCurrent() override; + + /// Whether the window is still open, and a close request hasn't yet been sent + bool IsOpen() const; + +private: + /// Whether the GPU and driver supports the OpenGL extension required + bool SupportsRequiredGLExtensions(); + + /// Internal SDL2 render window + SDL_Window* render_window; + + using SDL_GLContext = void*; + /// The OpenGL context associated with the window + SDL_GLContext gl_context; +}; diff --git a/src/yuzu_tester/resource.h b/src/yuzu_tester/resource.h new file mode 100644 index 000000000..df8e459e4 --- /dev/null +++ b/src/yuzu_tester/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by pcafe.rc +// +#define IDI_ICON3 103 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 105 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/yuzu_tester/service/yuzutest.cpp b/src/yuzu_tester/service/yuzutest.cpp new file mode 100644 index 000000000..85d3f436b --- /dev/null +++ b/src/yuzu_tester/service/yuzutest.cpp @@ -0,0 +1,112 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include "common/string_util.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/service.h" +#include "core/hle/service/sm/sm.h" +#include "yuzu_tester/service/yuzutest.h" + +namespace Service::Yuzu { + +constexpr u64 SERVICE_VERSION = 0x00000002; + +class YuzuTest final : public ServiceFramework<YuzuTest> { +public: + explicit YuzuTest(std::string data, + std::function<void(std::vector<TestResult>)> finish_callback) + : ServiceFramework{"yuzutest"}, data(std::move(data)), + finish_callback(std::move(finish_callback)) { + static const FunctionInfo functions[] = { + {0, &YuzuTest::Initialize, "Initialize"}, + {1, &YuzuTest::GetServiceVersion, "GetServiceVersion"}, + {2, &YuzuTest::GetData, "GetData"}, + {10, &YuzuTest::StartIndividual, "StartIndividual"}, + {20, &YuzuTest::FinishIndividual, "FinishIndividual"}, + {100, &YuzuTest::ExitProgram, "ExitProgram"}, + }; + + RegisterHandlers(functions); + } + +private: + void Initialize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Frontend, "called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetServiceVersion(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Frontend, "called"); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.Push(SERVICE_VERSION); + } + + void GetData(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Frontend, "called"); + const auto size = ctx.GetWriteBufferSize(); + const auto write_size = std::min(size, data.size()); + ctx.WriteBuffer(data.data(), write_size); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(write_size); + } + + void StartIndividual(Kernel::HLERequestContext& ctx) { + const auto name_raw = ctx.ReadBuffer(); + + const auto name = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(name_raw.data()), name_raw.size()); + + LOG_DEBUG(Frontend, "called, name={}", name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void FinishIndividual(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto code = rp.PopRaw<u32>(); + + const auto result_data_raw = ctx.ReadBuffer(); + const auto test_name_raw = ctx.ReadBuffer(1); + + const auto data = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(result_data_raw.data()), result_data_raw.size()); + const auto test_name = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(test_name_raw.data()), test_name_raw.size()); + + LOG_INFO(Frontend, "called, result_code={:08X}, data={}, name={}", code, data, test_name); + + results.push_back({code, data, test_name}); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void ExitProgram(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Frontend, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + finish_callback(std::move(results)); + } + + std::string data; + + std::vector<TestResult> results; + std::function<void(std::vector<TestResult>)> finish_callback; +}; + +void InstallInterfaces(SM::ServiceManager& sm, std::string data, + std::function<void(std::vector<TestResult>)> finish_callback) { + std::make_shared<YuzuTest>(data, finish_callback)->InstallAsService(sm); +} + +} // namespace Service::Yuzu diff --git a/src/yuzu_tester/service/yuzutest.h b/src/yuzu_tester/service/yuzutest.h new file mode 100644 index 000000000..eca129c8c --- /dev/null +++ b/src/yuzu_tester/service/yuzutest.h @@ -0,0 +1,25 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <string> + +namespace Service::SM { +class ServiceManager; +} + +namespace Service::Yuzu { + +struct TestResult { + u32 code; + std::string data; + std::string name; +}; + +void InstallInterfaces(SM::ServiceManager& sm, std::string data, + std::function<void(std::vector<TestResult>)> finish_callback); + +} // namespace Service::Yuzu diff --git a/src/yuzu_tester/yuzu.cpp b/src/yuzu_tester/yuzu.cpp new file mode 100644 index 000000000..94ad50cb3 --- /dev/null +++ b/src/yuzu_tester/yuzu.cpp @@ -0,0 +1,268 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <iostream> +#include <memory> +#include <string> +#include <thread> + +#include <fmt/ostream.h> + +#include "common/common_paths.h" +#include "common/detached_tasks.h" +#include "common/file_util.h" +#include "common/logging/backend.h" +#include "common/logging/filter.h" +#include "common/logging/log.h" +#include "common/microprofile.h" +#include "common/scm_rev.h" +#include "common/scope_exit.h" +#include "common/string_util.h" +#include "common/telemetry.h" +#include "core/core.h" +#include "core/crypto/key_manager.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/vfs_real.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" +#include "core/settings.h" +#include "core/telemetry_session.h" +#include "video_core/renderer_base.h" +#include "yuzu_tester/config.h" +#include "yuzu_tester/emu_window/emu_window_sdl2_hide.h" +#include "yuzu_tester/service/yuzutest.h" + +#ifdef _WIN32 +// windows.h needs to be included before shellapi.h +#include <windows.h> + +#include <shellapi.h> +#endif + +#undef _UNICODE +#include <getopt.h> +#ifndef _MSC_VER +#include <unistd.h> +#endif + +#ifdef _WIN32 +extern "C" { +// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable +// graphics +__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; +__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} +#endif + +static void PrintHelp(const char* argv0) { + std::cout << "Usage: " << argv0 + << " [options] <filename>\n" + "-h, --help Display this help and exit\n" + "-v, --version Output version information and exit\n" + "-d, --datastring Pass following string as data to test service command #2\n" + "-l, --log Log to console in addition to file (will log to file only " + "by default)\n"; +} + +static void PrintVersion() { + std::cout << "yuzu [Test Utility] " << Common::g_scm_branch << " " << Common::g_scm_desc + << std::endl; +} + +static void InitializeLogging(bool console) { + Log::Filter log_filter(Log::Level::Debug); + log_filter.ParseFilterString(Settings::values.log_filter); + Log::SetGlobalFilter(log_filter); + + if (console) + Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); + + const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + FileUtil::CreateFullPath(log_dir); + Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); +#ifdef _WIN32 + Log::AddBackend(std::make_unique<Log::DebuggerBackend>()); +#endif +} + +/// Application entry point +int main(int argc, char** argv) { + Common::DetachedTasks detached_tasks; + Config config; + + int option_index = 0; + +#ifdef _WIN32 + int argc_w; + auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); + + if (argv_w == nullptr) { + std::cout << "Failed to get command line arguments" << std::endl; + return -1; + } +#endif + std::string filepath; + + static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {"datastring", optional_argument, 0, 'd'}, + {"log", no_argument, 0, 'l'}, + {0, 0, 0, 0}, + }; + + bool console_log = false; + std::string datastring; + + while (optind < argc) { + int arg = getopt_long(argc, argv, "hvdl::", long_options, &option_index); + if (arg != -1) { + switch (static_cast<char>(arg)) { + case 'h': + PrintHelp(argv[0]); + return 0; + case 'v': + PrintVersion(); + return 0; + case 'd': + datastring = argv[optind]; + ++optind; + break; + case 'l': + console_log = true; + break; + } + } else { +#ifdef _WIN32 + filepath = Common::UTF16ToUTF8(argv_w[optind]); +#else + filepath = argv[optind]; +#endif + optind++; + } + } + + InitializeLogging(console_log); + +#ifdef _WIN32 + LocalFree(argv_w); +#endif + + MicroProfileOnThreadCreate("EmuThread"); + SCOPE_EXIT({ MicroProfileShutdown(); }); + + if (filepath.empty()) { + LOG_CRITICAL(Frontend, "Failed to load application: No application specified"); + std::cout << "Failed to load application: No application specified" << std::endl; + PrintHelp(argv[0]); + return -1; + } + + Settings::values.use_gdbstub = false; + Settings::Apply(); + + std::unique_ptr<EmuWindow_SDL2_Hide> emu_window{std::make_unique<EmuWindow_SDL2_Hide>()}; + + if (!Settings::values.use_multi_core) { + // Single core mode must acquire OpenGL context for entire emulation session + emu_window->MakeCurrent(); + } + + bool finished = false; + int return_value = 0; + const auto callback = [&finished, + &return_value](std::vector<Service::Yuzu::TestResult> results) { + finished = true; + return_value = 0; + + // Find the minimum length needed to fully enclose all test names (and the header field) in + // the fmt::format column by first finding the maximum size of any test name and comparing + // that to 9, the string length of 'Test Name' + const auto needed_length_name = + std::max<u64>(std::max_element(results.begin(), results.end(), + [](const auto& lhs, const auto& rhs) { + return lhs.name.size() < rhs.name.size(); + }) + ->name.size(), + 9ull); + + std::size_t passed = 0; + std::size_t failed = 0; + + std::cout << fmt::format("Result [Res Code] | {:<{}} | Extra Data", "Test Name", + needed_length_name) + << std::endl; + + for (const auto& res : results) { + const auto main_res = res.code == 0 ? "PASSED" : "FAILED"; + if (res.code == 0) + ++passed; + else + ++failed; + std::cout << fmt::format("{} [{:08X}] | {:<{}} | {}", main_res, res.code, res.name, + needed_length_name, res.data) + << std::endl; + } + + std::cout << std::endl + << fmt::format("{:4d} Passed | {:4d} Failed | {:4d} Total | {:2.2f} Passed Ratio", + passed, failed, passed + failed, + static_cast<float>(passed) / (passed + failed)) + << std::endl + << (failed == 0 ? "PASSED" : "FAILED") << std::endl; + + if (failed > 0) + return_value = -1; + }; + + Core::System& system{Core::System::GetInstance()}; + system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); + system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); + system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); + + SCOPE_EXIT({ system.Shutdown(); }); + + const Core::System::ResultStatus load_result{system.Load(*emu_window, filepath)}; + + switch (load_result) { + case Core::System::ResultStatus::ErrorGetLoader: + LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath); + return -1; + case Core::System::ResultStatus::ErrorLoader: + LOG_CRITICAL(Frontend, "Failed to load ROM!"); + return -1; + case Core::System::ResultStatus::ErrorNotInitialized: + LOG_CRITICAL(Frontend, "CPUCore not initialized"); + return -1; + case Core::System::ResultStatus::ErrorVideoCore: + LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!"); + return -1; + case Core::System::ResultStatus::Success: + break; // Expected case + default: + if (static_cast<u32>(load_result) > + static_cast<u32>(Core::System::ResultStatus::ErrorLoader)) { + const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader); + const u16 error_id = static_cast<u16>(load_result) - loader_id; + LOG_CRITICAL(Frontend, + "While attempting to load the ROM requested, an error occured. Please " + "refer to the yuzu wiki for more information or the yuzu discord for " + "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", + loader_id, error_id, static_cast<Loader::ResultStatus>(error_id)); + } + } + + Service::Yuzu::InstallInterfaces(system.ServiceManager(), datastring, callback); + + system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDLHideTester"); + + system.Renderer().Rasterizer().LoadDiskResources(); + + while (!finished) { + system.RunLoop(); + } + + detached_tasks.WaitForAllTasks(); + return return_value; +} diff --git a/src/yuzu_tester/yuzu.rc b/src/yuzu_tester/yuzu.rc new file mode 100644 index 000000000..7de8ef3d9 --- /dev/null +++ b/src/yuzu_tester/yuzu.rc @@ -0,0 +1,17 @@ +#include "winresrc.h" +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +YUZU_ICON ICON "../../dist/yuzu.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// RT_MANIFEST +// + +1 RT_MANIFEST "../../dist/yuzu.manifest" |