diff options
38 files changed, 951 insertions, 247 deletions
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index 6f0ff953a..23e5d3f10 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -30,7 +30,7 @@ public: return info; } - VoiceInfo& Info() { + VoiceInfo& GetInfo() { return info; } @@ -51,9 +51,30 @@ private: VoiceInfo info{}; }; +class AudioRenderer::EffectState { +public: + const EffectOutStatus& GetOutStatus() const { + return out_status; + } + + const EffectInStatus& GetInfo() const { + return info; + } + + EffectInStatus& GetInfo() { + return info; + } + + void UpdateState(); + +private: + EffectOutStatus out_status{}; + EffectInStatus info{}; +}; AudioRenderer::AudioRenderer(AudioRendererParameter params, Kernel::SharedPtr<Kernel::Event> buffer_event) - : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) { + : 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(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer", @@ -96,11 +117,29 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ memory_pool_count * sizeof(MemoryPoolInfo)); // Copy VoiceInfo structs - std::size_t offset{sizeof(UpdateDataHeader) + config.behavior_size + config.memory_pools_size + - config.voice_resource_size}; + std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size + + config.memory_pools_size + config.voice_resource_size}; for (auto& voice : voices) { - std::memcpy(&voice.Info(), input_params.data() + offset, sizeof(VoiceInfo)); - offset += sizeof(VoiceInfo); + std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo)); + voice_offset += sizeof(VoiceInfo); + } + + std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size + + config.memory_pools_size + config.voice_resource_size + + config.voices_size}; + for (auto& effect : effects) { + std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus)); + effect_offset += sizeof(EffectInStatus); + } + + // Update memory pool state + std::vector<MemoryPoolEntry> memory_pool(memory_pool_count); + for (std::size_t index = 0; index < memory_pool.size(); ++index) { + if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) { + memory_pool[index].state = MemoryPoolStates::Attached; + } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) { + memory_pool[index].state = MemoryPoolStates::Detached; + } } // Update voices @@ -114,14 +153,8 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ } } - // Update memory pool state - std::vector<MemoryPoolEntry> memory_pool(memory_pool_count); - for (std::size_t index = 0; index < memory_pool.size(); ++index) { - if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) { - memory_pool[index].state = MemoryPoolStates::Attached; - } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) { - memory_pool[index].state = MemoryPoolStates::Detached; - } + for (auto& effect : effects) { + effect.UpdateState(); } // Release previous buffers and queue next ones for playback @@ -144,6 +177,14 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ voice_out_status_offset += sizeof(VoiceOutStatus); } + std::size_t effect_out_status_offset{ + sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + + response_data.voice_resource_size}; + for (const auto& effect : effects) { + std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(), + sizeof(EffectOutStatus)); + effect_out_status_offset += sizeof(EffectOutStatus); + } return output_params; } @@ -244,11 +285,29 @@ void AudioRenderer::VoiceState::RefreshBuffer() { break; } - samples = Interpolate(interp_state, std::move(samples), Info().sample_rate, STREAM_SAMPLE_RATE); + samples = + Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, STREAM_SAMPLE_RATE); is_refresh_pending = false; } +void AudioRenderer::EffectState::UpdateState() { + if (info.is_new) { + out_status.state = EffectStatus::New; + } else { + if (info.type == Effect::Aux) { + ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_info) == 0, + "Aux buffers tried to update"); + ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_info) == 0, + "Aux buffers tried to update"); + ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_base) == 0, + "Aux buffers tried to update"); + ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_base) == 0, + "Aux buffers tried to update"); + } + } +} + static constexpr s16 ClampToS16(s32 value) { return static_cast<s16>(std::clamp(value, -32768, 32767)); } diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index dfef89e1d..046417da3 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h @@ -28,6 +28,16 @@ enum class PlayState : u8 { Paused = 2, }; +enum class Effect : u8 { + None = 0, + Aux = 2, +}; + +enum class EffectStatus : u8 { + None = 0, + New = 1, +}; + struct AudioRendererParameter { u32_le sample_rate; u32_le sample_count; @@ -128,6 +138,43 @@ struct VoiceOutStatus { }; static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size"); +struct AuxInfo { + std::array<u8, 24> input_mix_buffers; + std::array<u8, 24> output_mix_buffers; + u32_le mix_buffer_count; + u32_le sample_rate; // Stored in the aux buffer currently + u32_le sampe_count; + u64_le send_buffer_info; + u64_le send_buffer_base; + + u64_le return_buffer_info; + u64_le return_buffer_base; +}; +static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); + +struct EffectInStatus { + Effect type; + u8 is_new; + u8 is_enabled; + INSERT_PADDING_BYTES(1); + u32_le mix_id; + u64_le buffer_base; + u64_le buffer_sz; + s32_le priority; + INSERT_PADDING_BYTES(4); + union { + std::array<u8, 0xa0> raw; + AuxInfo aux_info; + }; +}; +static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size"); + +struct EffectOutStatus { + EffectStatus state; + INSERT_PADDING_BYTES(0xf); +}; +static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size"); + struct UpdateDataHeader { UpdateDataHeader() {} @@ -173,11 +220,13 @@ public: Stream::State GetStreamState() const; private: + class EffectState; class VoiceState; AudioRendererParameter worker_params; Kernel::SharedPtr<Kernel::Event> buffer_event; std::vector<VoiceState> voices; + std::vector<EffectState> effects; std::unique_ptr<AudioOut> audio_out; AudioCore::StreamPtr stream; }; diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 7e978cf7a..0762321a9 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -129,7 +129,7 @@ public: }; std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const { - auto& current_process = Core::CurrentProcess(); + auto* current_process = Core::CurrentProcess(); auto** const page_table = current_process->VMManager().page_table.pointers.data(); Dynarmic::A64::UserConfig config; diff --git a/src/core/core.cpp b/src/core/core.cpp index b6acfb3e4..e2fb9e038 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -136,7 +136,8 @@ struct System::Impl { if (virtual_filesystem == nullptr) virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>(); - kernel.MakeCurrentProcess(Kernel::Process::Create(kernel, "main")); + auto main_process = Kernel::Process::Create(kernel, "main"); + kernel.MakeCurrentProcess(main_process.get()); cpu_barrier = std::make_shared<CpuBarrier>(); cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size()); @@ -361,11 +362,11 @@ const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(std::size_t core_ind return impl->cpu_cores[core_index]->Scheduler(); } -Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() { +Kernel::Process* System::CurrentProcess() { return impl->kernel.CurrentProcess(); } -const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const { +const Kernel::Process* System::CurrentProcess() const { return impl->kernel.CurrentProcess(); } diff --git a/src/core/core.h b/src/core/core.h index f9a3e97e3..ea4d53914 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -174,11 +174,11 @@ public: /// Gets the scheduler for the CPU core with the specified index const std::shared_ptr<Kernel::Scheduler>& Scheduler(std::size_t core_index); - /// Provides a reference to the current process - Kernel::SharedPtr<Kernel::Process>& CurrentProcess(); + /// Provides a pointer to the current process + Kernel::Process* CurrentProcess(); - /// Provides a constant reference to the current process. - const Kernel::SharedPtr<Kernel::Process>& CurrentProcess() const; + /// Provides a constant pointer to the current process. + const Kernel::Process* CurrentProcess() const; /// Provides a reference to the kernel instance. Kernel::KernelCore& Kernel(); @@ -246,7 +246,7 @@ inline TelemetrySession& Telemetry() { return System::GetInstance().TelemetrySession(); } -inline Kernel::SharedPtr<Kernel::Process>& CurrentProcess() { +inline Kernel::Process* CurrentProcess() { return System::GetInstance().CurrentProcess(); } diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index 0cadbc375..554eae9bc 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -2,9 +2,15 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <cstring> +#include <map> #include <sstream> -#include "common/assert.h" +#include <string> +#include <utility> + #include "common/hex_util.h" +#include "common/logging/log.h" #include "common/swap.h" #include "core/file_sys/ips_layer.h" #include "core/file_sys/vfs_vector.h" @@ -17,22 +23,48 @@ enum class IPSFileType { Error, }; -constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{ - std::pair{"\\a", "\a"}, {"\\b", "\b"}, {"\\f", "\f"}, {"\\n", "\n"}, - {"\\r", "\r"}, {"\\t", "\t"}, {"\\v", "\v"}, {"\\\\", "\\"}, - {"\\\'", "\'"}, {"\\\"", "\""}, {"\\\?", "\?"}, -}; +constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{ + {"\\a", "\a"}, + {"\\b", "\b"}, + {"\\f", "\f"}, + {"\\n", "\n"}, + {"\\r", "\r"}, + {"\\t", "\t"}, + {"\\v", "\v"}, + {"\\\\", "\\"}, + {"\\\'", "\'"}, + {"\\\"", "\""}, + {"\\\?", "\?"}, +}}; static IPSFileType IdentifyMagic(const std::vector<u8>& magic) { - if (magic.size() != 5) + if (magic.size() != 5) { return IPSFileType::Error; - if (magic == std::vector<u8>{'P', 'A', 'T', 'C', 'H'}) + } + + constexpr std::array<u8, 5> patch_magic{{'P', 'A', 'T', 'C', 'H'}}; + if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) { return IPSFileType::IPS; - if (magic == std::vector<u8>{'I', 'P', 'S', '3', '2'}) + } + + constexpr std::array<u8, 5> ips32_magic{{'I', 'P', 'S', '3', '2'}}; + if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) { return IPSFileType::IPS32; + } + return IPSFileType::Error; } +static bool IsEOF(IPSFileType type, const std::vector<u8>& data) { + constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}}; + if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) { + return true; + } + + constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}}; + return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin()); +} + VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { if (in == nullptr || ips == nullptr) return nullptr; @@ -47,8 +79,7 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { u64 offset = 5; // After header while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) { offset += temp.size(); - if (type == IPSFileType::IPS32 && temp == std::vector<u8>{'E', 'E', 'O', 'F'} || - type == IPSFileType::IPS && temp == std::vector<u8>{'E', 'O', 'F'}) { + if (IsEOF(type, temp)) { break; } @@ -76,23 +107,32 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { return nullptr; if (real_offset + rle_size > in_data.size()) - rle_size = in_data.size() - real_offset; + rle_size = static_cast<u16>(in_data.size() - real_offset); std::memset(in_data.data() + real_offset, data.get(), rle_size); } else { // Standard Patch auto read = data_size; if (real_offset + read > in_data.size()) - read = in_data.size() - real_offset; + read = static_cast<u16>(in_data.size() - real_offset); if (ips->Read(in_data.data() + real_offset, read, offset) != data_size) return nullptr; offset += data_size; } } - if (temp != std::vector<u8>{'E', 'E', 'O', 'F'} && temp != std::vector<u8>{'E', 'O', 'F'}) + if (!IsEOF(type, temp)) { return nullptr; - return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory()); + } + + return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), + in->GetContainingDirectory()); } +struct IPSwitchCompiler::IPSwitchPatch { + std::string name; + bool enabled; + std::map<u32, std::vector<u8>> records; +}; + IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) { Parse(); } @@ -225,7 +265,7 @@ void IPSwitchCompiler::Parse() { if (patch_line.length() < 11) break; auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); - offset += offset_shift; + offset += static_cast<unsigned long>(offset_shift); std::vector<u8> replace; // 9 - first char of replacement val @@ -291,7 +331,8 @@ VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const { } } - return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory()); + return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(), + in->GetContainingDirectory()); } } // namespace FileSys diff --git a/src/core/file_sys/ips_layer.h b/src/core/file_sys/ips_layer.h index 57da00da8..450b2f71e 100644 --- a/src/core/file_sys/ips_layer.h +++ b/src/core/file_sys/ips_layer.h @@ -4,8 +4,11 @@ #pragma once +#include <array> #include <memory> +#include <vector> +#include "common/common_types.h" #include "core/file_sys/vfs.h" namespace FileSys { @@ -22,17 +25,13 @@ public: VirtualFile Apply(const VirtualFile& in) const; private: + struct IPSwitchPatch; + void ParseFlag(const std::string& flag); void Parse(); bool valid = false; - struct IPSwitchPatch { - std::string name; - bool enabled; - std::map<u32, std::vector<u8>> records; - }; - VirtualFile patch_text; std::vector<IPSwitchPatch> patches; std::array<u8, 0x20> nso_build_id{}; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index b14d7cb0a..019caebe9 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -345,23 +345,22 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam return out; } -std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { +std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { const auto& installed{Service::FileSystem::GetUnionContents()}; const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control); if (base_control_nca == nullptr) return {}; - return ParseControlNCA(base_control_nca); + return ParseControlNCA(*base_control_nca); } -std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA( - const std::shared_ptr<NCA>& nca) const { - const auto base_romfs = nca->GetRomFS(); +std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const { + const auto base_romfs = nca.GetRomFS(); if (base_romfs == nullptr) return {}; - const auto romfs = PatchRomFS(base_romfs, nca->GetBaseIVFCOffset(), ContentRecordType::Control); + const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); if (romfs == nullptr) return {}; @@ -373,7 +372,7 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA( if (nacp_file == nullptr) nacp_file = extracted->GetFile("Control.nacp"); - const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file); + auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file); VirtualFile icon_file; for (const auto& language : FileSys::LANGUAGE_NAMES) { @@ -382,6 +381,6 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA( break; } - return {nacp, icon_file}; + return {std::move(nacp), icon_file}; } } // namespace FileSys diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index eb6fc4607..7d168837f 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -57,11 +57,10 @@ public: // 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::shared_ptr<NACP>, VirtualFile> GetControlMetadata() const; + std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const; // Version of GetControlMetadata that takes an arbitrary NCA - std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA( - const std::shared_ptr<NCA>& nca) const; + std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const; private: u64 title_id; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 98eb74298..bd680adfe 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -116,7 +116,7 @@ struct KernelCore::Impl { next_thread_id = 1; process_list.clear(); - current_process.reset(); + current_process = nullptr; handle_table.Clear(); resource_limits.fill(nullptr); @@ -207,7 +207,7 @@ struct KernelCore::Impl { // Lists all processes that exist in the current session. std::vector<SharedPtr<Process>> process_list; - SharedPtr<Process> current_process; + Process* current_process = nullptr; Kernel::HandleTable handle_table; std::array<SharedPtr<ResourceLimit>, 4> resource_limits; @@ -266,15 +266,15 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) { impl->process_list.push_back(std::move(process)); } -void KernelCore::MakeCurrentProcess(SharedPtr<Process> process) { - impl->current_process = std::move(process); +void KernelCore::MakeCurrentProcess(Process* process) { + impl->current_process = process; } -SharedPtr<Process>& KernelCore::CurrentProcess() { +Process* KernelCore::CurrentProcess() { return impl->current_process; } -const SharedPtr<Process>& KernelCore::CurrentProcess() const { +const Process* KernelCore::CurrentProcess() const { return impl->current_process; } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index c0771ecf0..41554821f 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -66,13 +66,13 @@ public: void AppendNewProcess(SharedPtr<Process> process); /// Makes the given process the new current process. - void MakeCurrentProcess(SharedPtr<Process> process); + void MakeCurrentProcess(Process* process); - /// Retrieves a reference to the current process. - SharedPtr<Process>& CurrentProcess(); + /// Retrieves a pointer to the current process. + Process* CurrentProcess(); - /// Retrieves a const reference to the current process. - const SharedPtr<Process>& CurrentProcess() const; + /// Retrieves a const pointer to the current process. + const Process* CurrentProcess() const; /// Adds a port to the named port table void AddNamedPort(std::string name, SharedPtr<ClientPort> port); diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index cfd6e1bad..1342c597e 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -9,7 +9,7 @@ #include "common/logging/log.h" #include "core/arm/arm_interface.h" #include "core/core.h" -#include "core/core_timing.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/scheduler.h" @@ -78,16 +78,16 @@ void Scheduler::SwitchContext(Thread* new_thread) { // Cancel any outstanding wakeup events for this thread new_thread->CancelWakeupTimer(); - auto previous_process = Core::CurrentProcess(); + auto* const previous_process = Core::CurrentProcess(); current_thread = new_thread; ready_queue.remove(new_thread->GetPriority(), new_thread); new_thread->SetStatus(ThreadStatus::Running); - const auto thread_owner_process = current_thread->GetOwnerProcess(); + auto* const thread_owner_process = current_thread->GetOwnerProcess(); if (previous_process != thread_owner_process) { - Core::CurrentProcess() = thread_owner_process; + Core::System::GetInstance().Kernel().MakeCurrentProcess(thread_owner_process); SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table); } diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 6c4af7e47..3afcce3fe 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -301,13 +301,28 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) { return Mutex::Release(mutex_addr); } +struct BreakReason { + union { + u64 raw; + BitField<31, 1, u64> dont_kill_application; + }; +}; + /// Break program execution static void Break(u64 reason, u64 info1, u64 info2) { - LOG_CRITICAL( - Debug_Emulated, - "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", - reason, info1, info2); - ASSERT(false); + BreakReason break_reason{reason}; + if (break_reason.dont_kill_application) { + LOG_ERROR( + Debug_Emulated, + "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", + reason, info1, info2); + } else { + LOG_CRITICAL( + Debug_Emulated, + "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", + reason, info1, info2); + ASSERT(false); + } } /// Used to output a message on a debug hardware unit - does nothing on a retail unit @@ -326,7 +341,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id, info_sub_id, handle); - const auto& current_process = Core::CurrentProcess(); + const auto* current_process = Core::CurrentProcess(); const auto& vm_manager = current_process->VMManager(); switch (static_cast<GetInfoType>(info_id)) { @@ -424,7 +439,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) { return ERR_INVALID_HANDLE; } - const auto current_process = Core::CurrentProcess(); + const auto* current_process = Core::CurrentProcess(); if (thread->GetOwnerProcess() != current_process) { return ERR_INVALID_HANDLE; } @@ -516,7 +531,7 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s return ERR_INVALID_HANDLE; } - return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type, + return shared_memory->Map(Core::CurrentProcess(), addr, permissions_type, MemoryPermission::DontCare); } @@ -535,7 +550,7 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 auto& kernel = Core::System::GetInstance().Kernel(); auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle); - return shared_memory->Unmap(Core::CurrentProcess().get(), addr); + return shared_memory->Unmap(Core::CurrentProcess(), addr); } /// Query process memory @@ -573,7 +588,7 @@ static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAdd /// Exits the current process static void ExitProcess() { - auto& current_process = Core::CurrentProcess(); + auto* current_process = Core::CurrentProcess(); LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID()); ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running, @@ -621,7 +636,7 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V auto& kernel = Core::System::GetInstance().Kernel(); CASCADE_RESULT(SharedPtr<Thread> thread, Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top, - Core::CurrentProcess())); + *Core::CurrentProcess())); const auto new_guest_handle = kernel.HandleTable().Create(thread); if (new_guest_handle.Failed()) { return new_guest_handle.Code(); diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 8e514cf9a..33aed8c23 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -194,7 +194,7 @@ static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, VAdd ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name, VAddr entry_point, u32 priority, u64 arg, s32 processor_id, - VAddr stack_top, SharedPtr<Process> owner_process) { + VAddr stack_top, Process& owner_process) { // Check if priority is in ranged. Lowest priority -> highest priority id. if (priority > THREADPRIO_LOWEST) { LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority); @@ -208,7 +208,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name // TODO(yuriks): Other checks, returning 0xD9001BEA - if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) { + if (!Memory::IsValidVirtualAddress(owner_process, entry_point)) { LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point); // TODO (bunnei): Find the correct error code to use here return ResultCode(-1); @@ -232,7 +232,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name thread->wait_handle = 0; thread->name = std::move(name); thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap(); - thread->owner_process = owner_process; + thread->owner_process = &owner_process; thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get(); thread->scheduler->AddThread(thread, priority); thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread); @@ -264,7 +264,7 @@ SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 pri // Initialize new "main" thread const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress(); auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, THREADPROCESSORID_0, - stack_top, &owner_process); + stack_top, owner_process); SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c6ffbd28c..f4d7bd235 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -89,7 +89,7 @@ public: static ResultVal<SharedPtr<Thread>> Create(KernelCore& kernel, std::string name, VAddr entry_point, u32 priority, u64 arg, s32 processor_id, VAddr stack_top, - SharedPtr<Process> owner_process); + Process& owner_process); std::string GetName() const override { return name; @@ -262,11 +262,11 @@ public: return processor_id; } - SharedPtr<Process>& GetOwnerProcess() { + Process* GetOwnerProcess() { return owner_process; } - const SharedPtr<Process>& GetOwnerProcess() const { + const Process* GetOwnerProcess() const { return owner_process; } @@ -386,7 +386,7 @@ private: u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register. /// Process that owns this thread - SharedPtr<Process> owner_process; + Process* owner_process; /// Objects that the thread is waiting on, in the same order as they were /// passed to WaitSynchronization1/N. diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 5534ce01c..13e57848d 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -35,7 +35,7 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file) return; std::tie(nacp_file, icon_file) = - FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(control_nca); + FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca); } AppLoader_NSP::~AppLoader_NSP() = default; diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index b006594a6..db91cd01e 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -49,7 +49,7 @@ private: std::unique_ptr<AppLoader> secondary_loader; FileSys::VirtualFile icon_file; - std::shared_ptr<FileSys::NACP> nacp_file; + std::unique_ptr<FileSys::NACP> nacp_file; u64 title_id; }; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index ee5452eb9..7a619acb4 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -30,7 +30,7 @@ AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) return; std::tie(nacp_file, icon_file) = - FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(control_nca); + FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(*control_nca); } AppLoader_XCI::~AppLoader_XCI() = default; diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 770ed1437..46f8dfc9e 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -49,7 +49,7 @@ private: std::unique_ptr<AppLoader_NCA> nca_loader; FileSys::VirtualFile icon_file; - std::shared_ptr<FileSys::NACP> nacp_file; + std::unique_ptr<FileSys::NACP> nacp_file; }; } // namespace Loader diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index f29fff1e7..7b04792b5 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -2,12 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> + +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/entropy.h> + #include "common/assert.h" #include "common/common_types.h" #include "common/file_util.h" +#include "common/logging/log.h" -#include <mbedtls/ctr_drbg.h> -#include <mbedtls/entropy.h> #include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -28,11 +32,11 @@ static u64 GenerateTelemetryId() { mbedtls_entropy_context entropy; mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_context ctr_drbg; - std::string personalization = "yuzu Telemetry ID"; + constexpr std::array<char, 18> personalization{{"yuzu Telemetry ID"}}; mbedtls_ctr_drbg_init(&ctr_drbg); ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, - reinterpret_cast<const unsigned char*>(personalization.c_str()), + reinterpret_cast<const unsigned char*>(personalization.data()), personalization.size()) == 0); ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id), sizeof(u64)) == 0); diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h index cec271df0..2a4845797 100644 --- a/src/core/telemetry_session.h +++ b/src/core/telemetry_session.h @@ -5,6 +5,7 @@ #pragma once #include <memory> +#include <string> #include "common/telemetry.h" namespace Core { @@ -30,8 +31,6 @@ public: field_collection.AddField(type, name, std::move(value)); } - static void FinalizeAsyncJob(); - private: Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields @@ -53,7 +52,6 @@ u64 RegenerateTelemetryId(); * Verifies the username and token. * @param username yuzu username to use for authentication. * @param token yuzu token to use for authentication. - * @param func A function that gets exectued when the verification is finished * @returns Future with bool indicating whether the verification succeeded */ bool VerifyLogin(const std::string& username, const std::string& token); diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index c0a57e71f..37e15bad0 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp @@ -15,7 +15,8 @@ namespace ArmTests { TestEnvironment::TestEnvironment(bool mutable_memory_) : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) { - Core::CurrentProcess() = Kernel::Process::Create(kernel, ""); + auto process = Kernel::Process::Create(kernel, ""); + kernel.MakeCurrentProcess(process.get()); page_table = &Core::CurrentProcess()->VMManager().page_table; std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr); diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index b1f137b9c..550ab1148 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -314,6 +314,15 @@ enum class TextureMiscMode : u64 { PTP, }; +enum class IsberdMode : u64 { + None = 0, + Patch = 1, + Prim = 2, + Attr = 3, +}; + +enum class IsberdShift : u64 { None = 0, U16 = 1, B32 = 2 }; + enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, @@ -340,6 +349,87 @@ struct IpaMode { } }; +enum class SystemVariable : u64 { + LaneId = 0x00, + VirtCfg = 0x02, + VirtId = 0x03, + Pm0 = 0x04, + Pm1 = 0x05, + Pm2 = 0x06, + Pm3 = 0x07, + Pm4 = 0x08, + Pm5 = 0x09, + Pm6 = 0x0a, + Pm7 = 0x0b, + OrderingTicket = 0x0f, + PrimType = 0x10, + InvocationId = 0x11, + Ydirection = 0x12, + ThreadKill = 0x13, + ShaderType = 0x14, + DirectBeWriteAddressLow = 0x15, + DirectBeWriteAddressHigh = 0x16, + DirectBeWriteEnabled = 0x17, + MachineId0 = 0x18, + MachineId1 = 0x19, + MachineId2 = 0x1a, + MachineId3 = 0x1b, + Affinity = 0x1c, + InvocationInfo = 0x1d, + WscaleFactorXY = 0x1e, + WscaleFactorZ = 0x1f, + Tid = 0x20, + TidX = 0x21, + TidY = 0x22, + TidZ = 0x23, + CtaParam = 0x24, + CtaIdX = 0x25, + CtaIdY = 0x26, + CtaIdZ = 0x27, + NtId = 0x28, + CirQueueIncrMinusOne = 0x29, + Nlatc = 0x2a, + SmSpaVersion = 0x2c, + MultiPassShaderInfo = 0x2d, + LwinHi = 0x2e, + SwinHi = 0x2f, + SwinLo = 0x30, + SwinSz = 0x31, + SmemSz = 0x32, + SmemBanks = 0x33, + LwinLo = 0x34, + LwinSz = 0x35, + LmemLosz = 0x36, + LmemHioff = 0x37, + EqMask = 0x38, + LtMask = 0x39, + LeMask = 0x3a, + GtMask = 0x3b, + GeMask = 0x3c, + RegAlloc = 0x3d, + CtxAddr = 0x3e, // .fmask = F_SM50 + BarrierAlloc = 0x3e, // .fmask = F_SM60 + GlobalErrorStatus = 0x40, + WarpErrorStatus = 0x42, + WarpErrorStatusClear = 0x43, + PmHi0 = 0x48, + PmHi1 = 0x49, + PmHi2 = 0x4a, + PmHi3 = 0x4b, + PmHi4 = 0x4c, + PmHi5 = 0x4d, + PmHi6 = 0x4e, + PmHi7 = 0x4f, + ClockLo = 0x50, + ClockHi = 0x51, + GlobalTimerLo = 0x52, + GlobalTimerHi = 0x53, + HwTaskId = 0x60, + CircularQueueEntryIndex = 0x61, + CircularQueueEntryAddressLow = 0x62, + CircularQueueEntryAddressHigh = 0x63, +}; + union Instruction { Instruction& operator=(const Instruction& instr) { value = instr.value; @@ -915,6 +1005,18 @@ union Instruction { } bra; union { + BitField<39, 1, u64> emit; // EmitVertex + BitField<40, 1, u64> cut; // EndPrimitive + } out; + + union { + BitField<31, 1, u64> skew; + BitField<32, 1, u64> o; + BitField<33, 2, IsberdMode> mode; + BitField<47, 2, IsberdShift> shift; + } isberd; + + union { BitField<20, 16, u64> imm20_16; BitField<36, 1, u64> product_shift_left; BitField<37, 1, u64> merge_37; @@ -936,6 +1038,10 @@ union Instruction { BitField<36, 5, u64> index; } cbuf36; + // Unsure about the size of this one. + // It's always used with a gpr0, so any size should be fine. + BitField<20, 8, SystemVariable> sys20; + BitField<47, 1, u64> generates_cc; BitField<61, 1, u64> is_b_imm; BitField<60, 1, u64> is_b_gpr; @@ -975,6 +1081,8 @@ public: TMML, // Texture Mip Map Level EXIT, IPA, + OUT_R, // Emit vertex/primitive + ISBERD, FFMA_IMM, // Fused Multiply and Add FFMA_CR, FFMA_RC, @@ -1034,6 +1142,7 @@ public: MOV_C, MOV_R, MOV_IMM, + MOV_SYS, MOV32_IMM, SHL_C, SHL_R, @@ -1209,6 +1318,8 @@ private: INST("1101111101011---", Id::TMML, Type::Memory, "TMML"), INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"), INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), + INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"), + INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"), INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"), INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"), INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"), @@ -1255,6 +1366,7 @@ private: INST("0100110010011---", Id::MOV_C, Type::Arithmetic, "MOV_C"), INST("0101110010011---", Id::MOV_R, Type::Arithmetic, "MOV_R"), INST("0011100-10011---", Id::MOV_IMM, Type::Arithmetic, "MOV_IMM"), + INST("1111000011001---", Id::MOV_SYS, Type::Trivial, "MOV_SYS"), INST("000000010000----", Id::MOV32_IMM, Type::ArithmeticImmediate, "MOV32_IMM"), INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"), INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"), diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index daae67121..84582c777 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -255,7 +255,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() { return params; } -void RasterizerOpenGL::SetupShaders() { +void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { MICROPROFILE_SCOPE(OpenGL_Shader); const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); @@ -270,6 +270,11 @@ void RasterizerOpenGL::SetupShaders() { // Skip stages that are not enabled if (!gpu.regs.IsShaderConfigEnabled(index)) { + switch (program) { + case Maxwell::ShaderProgram::Geometry: + shader_program_manager->UseTrivialGeometryShader(); + break; + } continue; } @@ -288,11 +293,18 @@ void RasterizerOpenGL::SetupShaders() { switch (program) { case Maxwell::ShaderProgram::VertexA: case Maxwell::ShaderProgram::VertexB: { - shader_program_manager->UseProgrammableVertexShader(shader->GetProgramHandle()); + shader_program_manager->UseProgrammableVertexShader( + shader->GetProgramHandle(primitive_mode)); + break; + } + case Maxwell::ShaderProgram::Geometry: { + shader_program_manager->UseProgrammableGeometryShader( + shader->GetProgramHandle(primitive_mode)); break; } case Maxwell::ShaderProgram::Fragment: { - shader_program_manager->UseProgrammableFragmentShader(shader->GetProgramHandle()); + shader_program_manager->UseProgrammableFragmentShader( + shader->GetProgramHandle(primitive_mode)); break; } default: @@ -302,12 +314,13 @@ void RasterizerOpenGL::SetupShaders() { } // Configure the const buffers for this shader stage. - current_constbuffer_bindpoint = SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), - shader, current_constbuffer_bindpoint); + current_constbuffer_bindpoint = + SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), shader, primitive_mode, + current_constbuffer_bindpoint); // Configure the textures for this shader stage. current_texture_bindpoint = SetupTextures(static_cast<Maxwell::ShaderStage>(stage), shader, - current_texture_bindpoint); + primitive_mode, current_texture_bindpoint); // When VertexA is enabled, we have dual vertex shaders if (program == Maxwell::ShaderProgram::VertexA) { @@ -317,8 +330,6 @@ void RasterizerOpenGL::SetupShaders() { } state.Apply(); - - shader_program_manager->UseTrivialGeometryShader(); } std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const { @@ -581,7 +592,7 @@ void RasterizerOpenGL::DrawArrays() { SetupVertexArrays(); DrawParameters params = SetupDraw(); - SetupShaders(); + SetupShaders(params.primitive_mode); buffer_cache.Unmap(); @@ -720,7 +731,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr } u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shader, - u32 current_bindpoint) { + GLenum primitive_mode, u32 current_bindpoint) { MICROPROFILE_SCOPE(OpenGL_UBO); const auto& gpu = Core::System::GetInstance().GPU(); const auto& maxwell3d = gpu.Maxwell3D(); @@ -772,7 +783,7 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment)); // Now configure the bindpoint of the buffer inside the shader - glUniformBlockBinding(shader->GetProgramHandle(), + glUniformBlockBinding(shader->GetProgramHandle(primitive_mode), shader->GetProgramResourceIndex(used_buffer), current_bindpoint + bindpoint); @@ -788,7 +799,8 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad return current_bindpoint + static_cast<u32>(entries.size()); } -u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, u32 current_unit) { +u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, + GLenum primitive_mode, u32 current_unit) { MICROPROFILE_SCOPE(OpenGL_Texture); const auto& gpu = Core::System::GetInstance().GPU(); const auto& maxwell3d = gpu.Maxwell3D(); @@ -803,8 +815,8 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, // Bind the uniform to the sampler. - glProgramUniform1i(shader->GetProgramHandle(), shader->GetUniformLocation(entry), - current_bindpoint); + glProgramUniform1i(shader->GetProgramHandle(primitive_mode), + shader->GetUniformLocation(entry), current_bindpoint); const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset()); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 2395e0a7a..b1f7ccc7e 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -120,7 +120,7 @@ private: * @returns The next available bindpoint for use in the next shader stage. */ u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader, - u32 current_bindpoint); + GLenum primitive_mode, u32 current_bindpoint); /* * Configures the current textures to use for the draw command. @@ -130,7 +130,7 @@ private: * @returns The next available bindpoint for use in the next shader stage. */ u32 SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader, - u32 current_unit); + GLenum primitive_mode, u32 current_unit); /// Syncs the viewport to match the guest state void SyncViewport(); @@ -210,7 +210,7 @@ private: DrawParameters SetupDraw(); - void SetupShaders(); + void SetupShaders(GLenum primitive_mode); enum class AccelDraw { Disabled, Arrays, Indexed }; AccelDraw accelerate_draw = AccelDraw::Disabled; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 7cd8f91e4..1a03a677f 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -68,6 +68,10 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type) program_result = GLShader::GenerateVertexShader(setup); gl_type = GL_VERTEX_SHADER; break; + case Maxwell::ShaderProgram::Geometry: + program_result = GLShader::GenerateGeometryShader(setup); + gl_type = GL_GEOMETRY_SHADER; + break; case Maxwell::ShaderProgram::Fragment: program_result = GLShader::GenerateFragmentShader(setup); gl_type = GL_FRAGMENT_SHADER; @@ -80,11 +84,16 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type) entries = program_result.second; - OGLShader shader; - shader.Create(program_result.first.c_str(), gl_type); - program.Create(true, shader.handle); - SetShaderUniformBlockBindings(program.handle); - VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr); + if (program_type != Maxwell::ShaderProgram::Geometry) { + OGLShader shader; + shader.Create(program_result.first.c_str(), gl_type); + program.Create(true, shader.handle); + SetShaderUniformBlockBindings(program.handle); + VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr); + } else { + // Store shader's code to lazily build it on draw + geometry_programs.code = program_result.first; + } } GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) { @@ -110,6 +119,21 @@ GLint CachedShader::GetUniformLocation(const GLShader::SamplerEntry& sampler) { return search->second; } +GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program, + const std::string& glsl_topology, + const std::string& debug_name) { + if (target_program.handle != 0) { + return target_program.handle; + } + const std::string source{geometry_programs.code + "layout (" + glsl_topology + ") in;\n"}; + OGLShader shader; + shader.Create(source.c_str(), GL_GEOMETRY_SHADER); + target_program.Create(true, shader.handle); + SetShaderUniformBlockBindings(target_program.handle); + VideoCore::LabelGLObject(GL_PROGRAM, target_program.handle, addr, debug_name); + return target_program.handle; +}; + Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { const VAddr program_addr{GetShaderAddress(program)}; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 9bafe43a9..7bb287f56 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -7,6 +7,7 @@ #include <map> #include <memory> +#include "common/assert.h" #include "common/common_types.h" #include "video_core/rasterizer_cache.h" #include "video_core/renderer_opengl/gl_resource_manager.h" @@ -38,8 +39,31 @@ public: } /// Gets the GL program handle for the shader - GLuint GetProgramHandle() const { - return program.handle; + GLuint GetProgramHandle(GLenum primitive_mode) { + if (program_type != Maxwell::ShaderProgram::Geometry) { + return program.handle; + } + switch (primitive_mode) { + case GL_POINTS: + return LazyGeometryProgram(geometry_programs.points, "points", "ShaderPoints"); + case GL_LINES: + case GL_LINE_STRIP: + return LazyGeometryProgram(geometry_programs.lines, "lines", "ShaderLines"); + case GL_LINES_ADJACENCY: + case GL_LINE_STRIP_ADJACENCY: + return LazyGeometryProgram(geometry_programs.lines_adjacency, "lines_adjacency", + "ShaderLinesAdjacency"); + case GL_TRIANGLES: + case GL_TRIANGLE_STRIP: + case GL_TRIANGLE_FAN: + return LazyGeometryProgram(geometry_programs.triangles, "triangles", "ShaderTriangles"); + case GL_TRIANGLES_ADJACENCY: + case GL_TRIANGLE_STRIP_ADJACENCY: + return LazyGeometryProgram(geometry_programs.triangles_adjacency, "triangles_adjacency", + "ShaderLines"); + default: + UNREACHABLE_MSG("Unknown primitive mode."); + } } /// Gets the GL program resource location for the specified resource, caching as needed @@ -49,12 +73,30 @@ public: GLint GetUniformLocation(const GLShader::SamplerEntry& sampler); private: + /// Generates a geometry shader or returns one that already exists. + GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology, + const std::string& debug_name); + VAddr addr; Maxwell::ShaderProgram program_type; GLShader::ShaderSetup setup; GLShader::ShaderEntries entries; + + // Non-geometry program. OGLProgram program; + // 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 { + std::string code; + OGLProgram points; + OGLProgram lines; + OGLProgram lines_adjacency; + OGLProgram triangles; + OGLProgram triangles_adjacency; + } geometry_programs; + std::map<u32, GLuint> resource_cache; std::map<u32, GLint> uniform_cache; }; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 7e57de78a..c82a0dcfa 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -7,6 +7,7 @@ #include <string> #include <string_view> +#include <boost/optional.hpp> #include <fmt/format.h> #include "common/assert.h" @@ -29,11 +30,32 @@ using Tegra::Shader::SubOp; constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; constexpr u32 PROGRAM_HEADER_SIZE = sizeof(Tegra::Shader::Header); +enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 }; + +constexpr u32 MAX_GEOMETRY_BUFFERS = 6; +constexpr u32 MAX_ATTRIBUTES = 0x100; // Size in vec4s, this value is untested + class DecompileFail : public std::runtime_error { public: using std::runtime_error::runtime_error; }; +/// Translate topology +static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) { + switch (topology) { + case Tegra::Shader::OutputTopology::PointList: + return "points"; + case Tegra::Shader::OutputTopology::LineStrip: + return "line_strip"; + case Tegra::Shader::OutputTopology::TriangleStrip: + return "triangle_strip"; + default: + LOG_CRITICAL(Render_OpenGL, "Unknown output topology {}", static_cast<u32>(topology)); + UNREACHABLE(); + return "points"; + } +} + /// 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. @@ -253,8 +275,9 @@ enum class InternalFlag : u64 { class GLSLRegisterManager { public: GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations, - const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix) - : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix} { + const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix, + const Tegra::Shader::Header& header) + : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header} { BuildRegisterList(); BuildInputList(); } @@ -358,11 +381,13 @@ public: * @param reg The destination register to use. * @param elem The element to use for the operation. * @param attribute The input attribute to use as the source value. + * @param vertex The register that decides which vertex to read from (used in GS). */ void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute, - const Tegra::Shader::IpaMode& input_mode) { + const Tegra::Shader::IpaMode& input_mode, + boost::optional<Register> vertex = {}) { const std::string dest = GetRegisterAsFloat(reg); - const std::string src = GetInputAttribute(attribute, input_mode) + GetSwizzle(elem); + const std::string src = GetInputAttribute(attribute, input_mode, vertex) + GetSwizzle(elem); shader.AddLine(dest + " = " + src + ';'); } @@ -391,16 +416,29 @@ public: * are stored as floats, so this may require conversion. * @param attribute The destination output attribute. * @param elem The element to use for the operation. - * @param reg The register to use as the source value. + * @param val_reg The register to use as the source value. + * @param buf_reg The register that tells which buffer to write to (used in geometry shaders). */ - void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) { + void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& val_reg, + const Register& buf_reg) { const std::string dest = GetOutputAttribute(attribute); - const std::string src = GetRegisterAsFloat(reg); + const std::string src = GetRegisterAsFloat(val_reg); if (!dest.empty()) { // Can happen with unknown/unimplemented output attributes, in which case we ignore the // instruction for now. - shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';'); + if (stage == Maxwell3D::Regs::ShaderStage::Geometry) { + // TODO(Rodrigo): nouveau sets some attributes after setting emitting a geometry + // shader. These instructions use a dirty register as buffer index. To avoid some + // drivers from complaining for the out of boundary writes, guard them. + const std::string buf_index{"min(" + GetRegisterAsInteger(buf_reg) + ", " + + std::to_string(MAX_GEOMETRY_BUFFERS - 1) + ')'}; + shader.AddLine("amem[" + buf_index + "][" + + std::to_string(static_cast<u32>(attribute)) + ']' + + GetSwizzle(elem) + " = " + src + ';'); + } else { + shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';'); + } } } @@ -441,41 +479,123 @@ public: } } - /// Add declarations for registers + /// Add declarations. void GenerateDeclarations(const std::string& suffix) { + GenerateRegisters(suffix); + GenerateInternalFlags(); + GenerateInputAttrs(); + GenerateOutputAttrs(); + GenerateConstBuffers(); + GenerateSamplers(); + GenerateGeometry(); + } + + /// Returns a list of constant buffer declarations. + std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const { + std::vector<ConstBufferEntry> result; + std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(), + std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); }); + return result; + } + + /// Returns a list of samplers used in the shader. + const std::vector<SamplerEntry>& GetSamplers() const { + return used_samplers; + } + + /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if + /// necessary. + std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, + bool is_array, bool is_shadow) { + const auto offset = static_cast<std::size_t>(sampler.index.Value()); + + // If this sampler has already been used, return the existing mapping. + const auto itr = + std::find_if(used_samplers.begin(), used_samplers.end(), + [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); + + if (itr != used_samplers.end()) { + ASSERT(itr->GetType() == type && itr->IsArray() == is_array && + itr->IsShadow() == is_shadow); + return itr->GetName(); + } + + // Otherwise create a new mapping for this sampler + const std::size_t next_index = used_samplers.size(); + const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow}; + used_samplers.emplace_back(entry); + return entry.GetName(); + } + +private: + /// Generates declarations for registers. + void GenerateRegisters(const std::string& suffix) { for (const auto& reg : regs) { declarations.AddLine(GLSLRegister::GetTypeString() + ' ' + reg.GetPrefixString() + std::to_string(reg.GetIndex()) + '_' + suffix + " = 0;"); } declarations.AddNewLine(); + } + /// Generates declarations for internal flags. + void GenerateInternalFlags() { for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) { const InternalFlag code = static_cast<InternalFlag>(ii); declarations.AddLine("bool " + GetInternalFlag(code) + " = false;"); } declarations.AddNewLine(); + } + + /// Generates declarations for input attributes. + void GenerateInputAttrs() { + if (stage != Maxwell3D::Regs::ShaderStage::Vertex) { + const std::string attr = + stage == Maxwell3D::Regs::ShaderStage::Geometry ? "gs_position[]" : "position"; + declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) + + ") in vec4 " + attr + ';'); + } for (const auto element : declr_input_attribute) { // TODO(bunnei): Use proper number of elements for these u32 idx = static_cast<u32>(element.first) - static_cast<u32>(Attribute::Index::Attribute_0); - declarations.AddLine("layout(location = " + std::to_string(idx) + ")" + - GetInputFlags(element.first) + "in vec4 " + - GetInputAttribute(element.first, element.second) + ';'); + if (stage != Maxwell3D::Regs::ShaderStage::Vertex) { + // If inputs are varyings, add an offset + idx += GENERIC_VARYING_START_LOCATION; + } + + std::string attr{GetInputAttribute(element.first, element.second)}; + if (stage == Maxwell3D::Regs::ShaderStage::Geometry) { + attr = "gs_" + attr + "[]"; + } + declarations.AddLine("layout (location = " + std::to_string(idx) + ") " + + GetInputFlags(element.first) + "in vec4 " + attr + ';'); } + declarations.AddNewLine(); + } + /// Generates declarations for output attributes. + void GenerateOutputAttrs() { + if (stage != Maxwell3D::Regs::ShaderStage::Fragment) { + declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) + + ") out vec4 position;"); + } for (const auto& index : declr_output_attribute) { // TODO(bunnei): Use proper number of elements for these - declarations.AddLine("layout(location = " + - std::to_string(static_cast<u32>(index) - - static_cast<u32>(Attribute::Index::Attribute_0)) + - ") out vec4 " + GetOutputAttribute(index) + ';'); + const u32 idx = static_cast<u32>(index) - + static_cast<u32>(Attribute::Index::Attribute_0) + + GENERIC_VARYING_START_LOCATION; + declarations.AddLine("layout (location = " + std::to_string(idx) + ") out vec4 " + + GetOutputAttribute(index) + ';'); } declarations.AddNewLine(); + } + /// Generates declarations for constant buffers. + void GenerateConstBuffers() { for (const auto& entry : GetConstBuffersDeclarations()) { - declarations.AddLine("layout(std140) uniform " + entry.GetName()); + declarations.AddLine("layout (std140) uniform " + entry.GetName()); declarations.AddLine('{'); declarations.AddLine(" vec4 c" + std::to_string(entry.GetIndex()) + "[MAX_CONSTBUFFER_ELEMENTS];"); @@ -483,7 +603,10 @@ public: declarations.AddNewLine(); } declarations.AddNewLine(); + } + /// Generates declarations for samplers. + void GenerateSamplers() { const auto& samplers = GetSamplers(); for (const auto& sampler : samplers) { declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() + @@ -492,44 +615,42 @@ public: declarations.AddNewLine(); } - /// Returns a list of constant buffer declarations - std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const { - std::vector<ConstBufferEntry> result; - std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(), - std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); }); - return result; - } - - /// Returns a list of samplers used in the shader - const std::vector<SamplerEntry>& GetSamplers() const { - return used_samplers; - } - - /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if - /// necessary. - std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, - bool is_array, bool is_shadow) { - const std::size_t offset = static_cast<std::size_t>(sampler.index.Value()); + /// Generates declarations used for geometry shaders. + void GenerateGeometry() { + if (stage != Maxwell3D::Regs::ShaderStage::Geometry) + return; - // If this sampler has already been used, return the existing mapping. - const auto itr = - std::find_if(used_samplers.begin(), used_samplers.end(), - [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); + declarations.AddLine( + "layout (" + GetTopologyName(header.common3.output_topology) + + ", max_vertices = " + std::to_string(header.common4.max_output_vertices) + ") out;"); + declarations.AddNewLine(); - if (itr != used_samplers.end()) { - ASSERT(itr->GetType() == type && itr->IsArray() == is_array && - itr->IsShadow() == is_shadow); - return itr->GetName(); - } + declarations.AddLine("vec4 amem[" + std::to_string(MAX_GEOMETRY_BUFFERS) + "][" + + std::to_string(MAX_ATTRIBUTES) + "];"); + declarations.AddNewLine(); - // Otherwise create a new mapping for this sampler - const std::size_t next_index = used_samplers.size(); - const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow}; - used_samplers.emplace_back(entry); - return entry.GetName(); + constexpr char buffer[] = "amem[output_buffer]"; + declarations.AddLine("void emit_vertex(uint output_buffer) {"); + ++declarations.scope; + for (const auto element : declr_output_attribute) { + declarations.AddLine(GetOutputAttribute(element) + " = " + buffer + '[' + + std::to_string(static_cast<u32>(element)) + "];"); + } + + declarations.AddLine("position = " + std::string(buffer) + '[' + + std::to_string(static_cast<u32>(Attribute::Index::Position)) + "];"); + + // If a geometry shader is attached, it will always flip (it's the last stage before + // fragment). For more info about flipping, refer to gl_shader_gen.cpp. + declarations.AddLine("position.xy *= viewport_flip.xy;"); + declarations.AddLine("gl_Position = position;"); + declarations.AddLine("position.w = 1.0;"); + declarations.AddLine("EmitVertex();"); + --declarations.scope; + declarations.AddLine('}'); + declarations.AddNewLine(); } -private: /// Generates code representing a temporary (GPR) register. std::string GetRegister(const Register& reg, unsigned elem) { if (reg == Register::ZeroIndex) { @@ -586,11 +707,19 @@ private: /// Generates code representing an input attribute register. std::string GetInputAttribute(Attribute::Index attribute, - const Tegra::Shader::IpaMode& input_mode) { + const Tegra::Shader::IpaMode& input_mode, + boost::optional<Register> vertex = {}) { + auto GeometryPass = [&](const std::string& name) { + if (stage == Maxwell3D::Regs::ShaderStage::Geometry && vertex) { + return "gs_" + name + '[' + GetRegisterAsInteger(vertex.value(), 0, false) + ']'; + } + return name; + }; + switch (attribute) { case Attribute::Index::Position: if (stage != Maxwell3D::Regs::ShaderStage::Fragment) { - return "position"; + return GeometryPass("position"); } else { return "vec4(gl_FragCoord.x, gl_FragCoord.y, gl_FragCoord.z, 1.0)"; } @@ -619,7 +748,7 @@ private: UNREACHABLE(); } } - return "input_attribute_" + std::to_string(index); + return GeometryPass("input_attribute_" + std::to_string(index)); } LOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", static_cast<u32>(attribute)); @@ -672,7 +801,7 @@ private: return out; } - /// Generates code representing an output attribute register. + /// Generates code representing the declaration name of an output attribute register. std::string GetOutputAttribute(Attribute::Index attribute) { switch (attribute) { case Attribute::Index::Position: @@ -708,6 +837,7 @@ private: std::vector<SamplerEntry> used_samplers; const Maxwell3D::Regs::ShaderStage& stage; const std::string& suffix; + const Tegra::Shader::Header& header; }; class GLSLGenerator { @@ -1103,8 +1233,8 @@ private: return offset + 1; } - shader.AddLine("// " + std::to_string(offset) + ": " + opcode->GetName() + " (" + - std::to_string(instr.value) + ')'); + shader.AddLine( + fmt::format("// {}: {} (0x{:016x})", offset, opcode->GetName(), instr.value)); using Tegra::Shader::Pred; ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute, @@ -1826,7 +1956,7 @@ private: const auto LoadNextElement = [&](u32 reg_offset) { regs.SetRegisterToInputAttibute(instr.gpr0.Value() + reg_offset, next_element, static_cast<Attribute::Index>(next_index), - input_mode); + input_mode, instr.gpr39.Value()); // Load the next attribute element into the following register. If the element // to load goes beyond the vec4 size, load the first element of the next @@ -1890,8 +2020,8 @@ private: const auto StoreNextElement = [&](u32 reg_offset) { regs.SetOutputAttributeToRegister(static_cast<Attribute::Index>(next_index), - next_element, - instr.gpr0.Value() + reg_offset); + next_element, instr.gpr0.Value() + reg_offset, + instr.gpr39.Value()); // Load the next attribute element into the following register. If the element // to load goes beyond the vec4 size, load the first element of the next @@ -2299,8 +2429,7 @@ private: ASSERT_MSG(!instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), "NDV is not implemented"); - const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); const bool is_array = instr.tmml.array != 0; auto texture_type = instr.tmml.texture_type.Value(); const std::string sampler = @@ -2311,13 +2440,11 @@ private: std::string coord; switch (texture_type) { case Tegra::Shader::TextureType::Texture1D: { - std::string x = regs.GetRegisterAsFloat(instr.gpr8); coord = "float coords = " + x + ';'; break; } case Tegra::Shader::TextureType::Texture2D: { - std::string x = regs.GetRegisterAsFloat(instr.gpr8); - std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); coord = "vec2 coords = vec2(" + x + ", " + y + ");"; break; } @@ -2327,8 +2454,7 @@ private: UNREACHABLE(); // Fallback to interpreting as a 2D texture for now - std::string x = regs.GetRegisterAsFloat(instr.gpr8); - std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); coord = "vec2 coords = vec2(" + x + ", " + y + ");"; texture_type = Tegra::Shader::TextureType::Texture2D; } @@ -2738,6 +2864,52 @@ private: break; } + case OpCode::Id::OUT_R: { + ASSERT(instr.gpr20.Value() == Register::ZeroIndex); + ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry, + "OUT is expected to be used in a geometry shader."); + + if (instr.out.emit) { + // gpr0 is used to store the next address. Hardware returns a pointer but + // we just return the next index with a cyclic cap. + const std::string current{regs.GetRegisterAsInteger(instr.gpr8, 0, false)}; + const std::string next = "((" + current + " + 1" + ") % " + + std::to_string(MAX_GEOMETRY_BUFFERS) + ')'; + shader.AddLine("emit_vertex(" + current + ");"); + regs.SetRegisterToInteger(instr.gpr0, false, 0, next, 1, 1); + } + if (instr.out.cut) { + shader.AddLine("EndPrimitive();"); + } + + break; + } + case OpCode::Id::MOV_SYS: { + switch (instr.sys20) { + case Tegra::Shader::SystemVariable::InvocationInfo: { + LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete"); + regs.SetRegisterToInteger(instr.gpr0, false, 0, "0u", 1, 1); + break; + } + default: { + LOG_CRITICAL(HW_GPU, "Unhandled system move: {}", + static_cast<u32>(instr.sys20.Value())); + UNREACHABLE(); + } + } + break; + } + case OpCode::Id::ISBERD: { + ASSERT(instr.isberd.o == 0); + ASSERT(instr.isberd.skew == 0); + ASSERT(instr.isberd.shift == Tegra::Shader::IsberdShift::None); + ASSERT(instr.isberd.mode == Tegra::Shader::IsberdMode::None); + ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry, + "ISBERD is expected to be used in a geometry shader."); + LOG_WARNING(HW_GPU, "ISBERD instruction is incomplete"); + regs.SetRegisterToFloat(instr.gpr0, 0, regs.GetRegisterAsFloat(instr.gpr8), 1, 1); + break; + } case OpCode::Id::BRA: { ASSERT_MSG(instr.bra.constant_buffer == 0, "BRA with constant buffers are not implemented"); @@ -2911,7 +3083,7 @@ private: ShaderWriter shader; ShaderWriter declarations; - GLSLRegisterManager regs{shader, declarations, stage, suffix}; + GLSLRegisterManager regs{shader, declarations, stage, suffix, header}; // Declarations std::set<std::string> declr_predicates; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index b0466c18f..1e5eb32df 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -17,7 +17,18 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) { std::string out = "#version 430 core\n"; out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; out += Decompiler::GetCommonDeclarations(); - out += "bool exec_vertex();\n"; + + out += R"( +out gl_PerVertex { + vec4 gl_Position; +}; + +layout(std140) uniform vs_config { + vec4 viewport_flip; + uvec4 instance_id; + uvec4 flip_stage; +}; +)"; if (setup.IsDualProgram()) { out += "bool exec_vertex_b();\n"; @@ -28,18 +39,17 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) { Maxwell3D::Regs::ShaderStage::Vertex, "vertex") .get_value_or({}); - out += R"( - -out gl_PerVertex { - vec4 gl_Position; -}; + out += program.first; -out vec4 position; + if (setup.IsDualProgram()) { + ProgramResult program_b = + Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET, + Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b") + .get_value_or({}); + out += program_b.first; + } -layout (std140) uniform vs_config { - vec4 viewport_flip; - uvec4 instance_id; -}; + out += R"( void main() { position = vec4(0.0, 0.0, 0.0, 0.0); @@ -52,27 +62,52 @@ void main() { out += R"( - // Viewport can be flipped, which is unsupported by glViewport - position.xy *= viewport_flip.xy; + // Check if the flip stage is VertexB + if (flip_stage[0] == 1) { + // Viewport can be flipped, which is unsupported by glViewport + position.xy *= viewport_flip.xy; + } gl_Position = position; // TODO(bunnei): This is likely a hack, position.w should be interpolated as 1.0 // For now, this is here to bring order in lieu of proper emulation - position.w = 1.0; + if (flip_stage[0] == 1) { + position.w = 1.0; + } } )"; - out += program.first; + return {out, program.second}; +} - if (setup.IsDualProgram()) { - ProgramResult program_b = - Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET, - Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b") - .get_value_or({}); - out += program_b.first; - } +ProgramResult GenerateGeometryShader(const ShaderSetup& setup) { + std::string out = "#version 430 core\n"; + out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; + out += Decompiler::GetCommonDeclarations(); + out += "bool exec_geometry();\n"; + + ProgramResult program = + Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET, + Maxwell3D::Regs::ShaderStage::Geometry, "geometry") + .get_value_or({}); + out += R"( +out gl_PerVertex { + vec4 gl_Position; +}; +layout (std140) uniform gs_config { + vec4 viewport_flip; + uvec4 instance_id; + uvec4 flip_stage; +}; + +void main() { + exec_geometry(); +} + +)"; + out += program.first; return {out, program.second}; } @@ -87,7 +122,6 @@ ProgramResult GenerateFragmentShader(const ShaderSetup& setup) { Maxwell3D::Regs::ShaderStage::Fragment, "fragment") .get_value_or({}); out += R"( -in vec4 position; layout(location = 0) out vec4 FragColor0; layout(location = 1) out vec4 FragColor1; layout(location = 2) out vec4 FragColor2; @@ -100,6 +134,7 @@ layout(location = 7) out vec4 FragColor7; layout (std140) uniform fs_config { vec4 viewport_flip; uvec4 instance_id; + uvec4 flip_stage; }; void main() { @@ -110,5 +145,4 @@ void main() { out += program.first; return {out, program.second}; } - -} // namespace OpenGL::GLShader +} // namespace OpenGL::GLShader
\ No newline at end of file diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index e56f39e78..79596087a 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -196,6 +196,12 @@ private: ProgramResult GenerateVertexShader(const ShaderSetup& setup); /** + * Generates the GLSL geometry shader program source code for the given GS program + * @returns String of the shader source code + */ +ProgramResult GenerateGeometryShader(const ShaderSetup& setup); + +/** * Generates the GLSL fragment shader program source code for the given FS program * @returns String of the shader source code */ diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index 022d32a86..010857ec6 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -18,6 +18,14 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh // We only assign the instance to the first component of the vector, the rest is just padding. instance_id[0] = state.current_instance; + + // Assign in which stage the position has to be flipped + // (the last stage before the fragment shader). + if (gpu.regs.shader_config[static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry)].enable) { + flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry); + } else { + flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::VertexB); + } } } // namespace OpenGL::GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index 3de15ba9b..b3a191cf2 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -21,8 +21,9 @@ struct MaxwellUniformData { void SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage); alignas(16) GLvec4 viewport_flip; alignas(16) GLuvec4 instance_id; + alignas(16) GLuvec4 flip_stage; }; -static_assert(sizeof(MaxwellUniformData) == 32, "MaxwellUniformData structure size is incorrect"); +static_assert(sizeof(MaxwellUniformData) == 48, "MaxwellUniformData structure size is incorrect"); static_assert(sizeof(MaxwellUniformData) < 16384, "MaxwellUniformData structure must be less than 16kb as per the OpenGL spec"); @@ -36,6 +37,10 @@ public: vs = program; } + void UseProgrammableGeometryShader(GLuint program) { + gs = program; + } + void UseProgrammableFragmentShader(GLuint program) { fs = program; } diff --git a/src/video_core/utils.h b/src/video_core/utils.h index 681919ae3..237cc1307 100644 --- a/src/video_core/utils.h +++ b/src/video_core/utils.h @@ -169,16 +169,20 @@ static void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, const std::string nice_addr = fmt::format("0x{:016x}", addr); std::string object_label; - switch (identifier) { - case GL_TEXTURE: - object_label = extra_info + "@" + nice_addr; - break; - case GL_PROGRAM: - object_label = "ShaderProgram@" + nice_addr; - break; - default: - object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr); - break; + if (extra_info.empty()) { + switch (identifier) { + case GL_TEXTURE: + object_label = "Texture@" + nice_addr; + break; + case GL_PROGRAM: + object_label = "Shader@" + nice_addr; + break; + default: + object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr); + break; + } + } else { + object_label = extra_info + '@' + nice_addr; } glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str())); } diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 4e4c108ab..e8ab23326 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -110,6 +110,7 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); + setAttribute(Qt::WA_AcceptTouchEvents); InputCommon::Init(); InputCommon::StartJoystickEventHandler(); @@ -190,11 +191,17 @@ QByteArray GRenderWindow::saveGeometry() { return geometry; } -qreal GRenderWindow::windowPixelRatio() { +qreal GRenderWindow::windowPixelRatio() const { // windowHandle() might not be accessible until the window is displayed to screen. return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; } +std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const { + const qreal pixel_ratio = windowPixelRatio(); + return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), + static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; +} + void GRenderWindow::closeEvent(QCloseEvent* event) { emit Closed(); QWidget::closeEvent(event); @@ -209,31 +216,81 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { } void GRenderWindow::mousePressEvent(QMouseEvent* event) { + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; // touch input is handled in TouchBeginEvent + auto pos = event->pos(); if (event->button() == Qt::LeftButton) { - qreal pixelRatio = windowPixelRatio(); - this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio), - static_cast<unsigned>(pos.y() * pixelRatio)); + const auto [x, y] = ScaleTouch(pos); + this->TouchPressed(x, y); } else if (event->button() == Qt::RightButton) { InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); } } void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; // touch input is handled in TouchUpdateEvent + auto pos = event->pos(); - qreal pixelRatio = windowPixelRatio(); - this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u), - std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u)); + const auto [x, y] = ScaleTouch(pos); + this->TouchMoved(x, y); InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); } void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; // touch input is handled in TouchEndEvent + if (event->button() == Qt::LeftButton) this->TouchReleased(); else if (event->button() == Qt::RightButton) InputCommon::GetMotionEmu()->EndTilt(); } +void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { + // TouchBegin always has exactly one touch point, so take the .first() + const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); + this->TouchPressed(x, y); +} + +void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) { + QPointF pos; + int active_points = 0; + + // average all active touch points + for (const auto tp : event->touchPoints()) { + if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) { + active_points++; + pos += tp.pos(); + } + } + + pos /= active_points; + + const auto [x, y] = ScaleTouch(pos); + this->TouchMoved(x, y); +} + +void GRenderWindow::TouchEndEvent() { + this->TouchReleased(); +} + +bool GRenderWindow::event(QEvent* event) { + if (event->type() == QEvent::TouchBegin) { + TouchBeginEvent(static_cast<QTouchEvent*>(event)); + return true; + } else if (event->type() == QEvent::TouchUpdate) { + TouchUpdateEvent(static_cast<QTouchEvent*>(event)); + return true; + } else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) { + TouchEndEvent(); + return true; + } + + return QWidget::event(event); +} + void GRenderWindow::focusOutEvent(QFocusEvent* event) { QWidget::focusOutEvent(event); InputCommon::GetKeyboard()->ReleaseAllKeys(); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index f133bfadf..873985564 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -15,6 +15,7 @@ class QKeyEvent; class QScreen; +class QTouchEvent; class GGLWidgetInternal; class GMainWindow; @@ -119,7 +120,7 @@ public: void restoreGeometry(const QByteArray& geometry); // overridden QByteArray saveGeometry(); // overridden - qreal windowPixelRatio(); + qreal windowPixelRatio() const; void closeEvent(QCloseEvent* event) override; @@ -130,6 +131,8 @@ public: void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; + bool event(QEvent* event) override; + void focusOutEvent(QFocusEvent* event) override; void OnClientAreaResized(unsigned width, unsigned height); @@ -148,6 +151,11 @@ signals: void Closed(); private: + std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const; + void TouchBeginEvent(const QTouchEvent* event); + void TouchUpdateEvent(const QTouchEvent* event); + void TouchEndEvent(); + void OnMinimalClientAreaChangeRequest( const std::pair<unsigned, unsigned>& minimal_size) override; diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index d2b3de683..8f99a1c78 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -27,9 +27,8 @@ #include "yuzu/ui_settings.h" namespace { -void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, - const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon, - std::string& name) { +void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca, + std::vector<u8>& icon, std::string& name) { auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca); if (icon_file != nullptr) icon = icon_file->ReadAllBytes(); @@ -110,7 +109,7 @@ void GameListWorker::AddInstalledTitlesToGameList() { const FileSys::PatchManager patch{program_id}; const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); if (control != nullptr) - GetMetadataFromControlNCA(patch, control, icon, name); + GetMetadataFromControlNCA(patch, *control, icon, name); auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); @@ -197,8 +196,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign res2 == Loader::ResultStatus::Success) { // Use from metadata pool. if (nca_control_map.find(program_id) != nca_control_map.end()) { - const auto nca = nca_control_map[program_id]; - GetMetadataFromControlNCA(patch, nca, icon, name); + const auto& nca = nca_control_map[program_id]; + GetMetadataFromControlNCA(patch, *nca, icon, name); } } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 155095095..a9ad92a80 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -40,6 +40,35 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { } } +std::pair<unsigned, unsigned> EmuWindow_SDL2::TouchToPixelPos(float touch_x, float touch_y) const { + int w, h; + SDL_GetWindowSize(render_window, &w, &h); + + touch_x *= w; + touch_y *= h; + + return {static_cast<unsigned>(std::max(std::round(touch_x), 0.0f)), + static_cast<unsigned>(std::max(std::round(touch_y), 0.0f))}; +} + +void EmuWindow_SDL2::OnFingerDown(float x, float y) { + // TODO(NeatNit): keep track of multitouch using the fingerID and a dictionary of some kind + // This isn't critical because the best we can do when we have that is to average them, like the + // 3DS does + + const auto [px, py] = TouchToPixelPos(x, y); + TouchPressed(px, py); +} + +void EmuWindow_SDL2::OnFingerMotion(float x, float y) { + const auto [px, py] = TouchToPixelPos(x, y); + TouchMoved(px, py); +} + +void EmuWindow_SDL2::OnFingerUp() { + TouchReleased(); +} + void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { if (state == SDL_PRESSED) { InputCommon::GetKeyboard()->PressKey(key); @@ -219,11 +248,26 @@ void EmuWindow_SDL2::PollEvents() { OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state); break; case SDL_MOUSEMOTION: - OnMouseMotion(event.motion.x, event.motion.y); + // ignore if it came from touch + if (event.button.which != SDL_TOUCH_MOUSEID) + OnMouseMotion(event.motion.x, event.motion.y); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: - OnMouseButton(event.button.button, event.button.state, event.button.x, event.button.y); + // ignore if it came from touch + if (event.button.which != SDL_TOUCH_MOUSEID) { + OnMouseButton(event.button.button, event.button.state, event.button.x, + event.button.y); + } + break; + case SDL_FINGERDOWN: + OnFingerDown(event.tfinger.x, event.tfinger.y); + break; + case SDL_FINGERMOTION: + OnFingerMotion(event.tfinger.x, event.tfinger.y); + break; + case SDL_FINGERUP: + OnFingerUp(); break; case SDL_QUIT: is_open = false; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index d34902109..b0d4116cc 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -40,6 +40,18 @@ private: /// Called by PollEvents when a mouse button is pressed or released void OnMouseButton(u32 button, u8 state, s32 x, s32 y); + /// Translates pixel position (0..1) to pixel positions + std::pair<unsigned, unsigned> TouchToPixelPos(float touch_x, float touch_y) const; + + /// Called by PollEvents when a finger starts touching the touchscreen + void OnFingerDown(float x, float y); + + /// Called by PollEvents when a finger moves while touching the touchscreen + void OnFingerMotion(float x, float y); + + /// Called by PollEvents when a finger stops touching the touchscreen + void OnFingerUp(); + /// Called by PollEvents when any event that may cause the window to be resized occurs void OnResize(); |