diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalCommon.h b/src/Cafe/HW/Latte/Renderer/Metal/MetalCommon.h index ba9ebc36..952fd1de 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/MetalCommon.h +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalCommon.h @@ -67,6 +67,18 @@ inline NS::String* ToNSString(const std::string& str) return ToNSString(str.c_str()); } +// Cast from const char* to NS::URL* +inline NS::URL* ToNSURL(const char* str) +{ + return NS::URL::fileURLWithPath(ToNSString(str)); +} + +// Cast from std::string to NS::URL* +inline NS::URL* ToNSURL(const std::string& str) +{ + return ToNSURL(str.c_str()); +} + inline NS::String* GetLabel(const std::string& label, const void* identifier) { return ToNSString(label + " (" + std::to_string(reinterpret_cast(identifier)) + ")"); diff --git a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp index 72b756e1..19a4b55d 100644 --- a/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Metal/MetalRenderer.cpp @@ -20,6 +20,9 @@ #include "Cemu/Logging/CemuLogging.h" #include "Cafe/HW/Latte/Core/FetchShader.h" #include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Common/precompiled.h" +#include "HW/Latte/Renderer/Metal/MetalCommon.h" +#include "Metal/MTLCaptureManager.hpp" #include "config/CemuConfig.h" #include "gui/guiWrapper.h" @@ -2179,6 +2182,38 @@ void MetalRenderer::StartCapture() auto desc = MTL::CaptureDescriptor::alloc()->init(); desc->setCaptureObject(m_device); + // Check if a debugger with support for GPU capture is attached + if (captureManager->supportsDestination(MTL::CaptureDestinationDeveloperTools)) + { + desc->setDestination(MTL::CaptureDestinationDeveloperTools); + } + else + { + if (GetConfig().gpu_capture_dir.GetValue().empty()) + { + cemuLog_log(LogType::Force, "No GPU capture directory specified, cannot do a GPU capture"); + return; + } + + // Check if the GPU trace document destination is available + if (!captureManager->supportsDestination(MTL::CaptureDestinationGPUTraceDocument)) + { + cemuLog_log(LogType::Force, "GPU trace document destination is not available, cannot do a GPU capture"); + return; + } + + // Get current date and time as a string + auto now = std::chrono::system_clock::now(); + std::time_t now_time = std::chrono::system_clock::to_time_t(now); + std::ostringstream oss; + oss << std::put_time(std::localtime(&now_time), "%Y-%m-%d_%H-%M-%S"); + std::string now_str = oss.str(); + + std::string capturePath = fmt::format("{}/cemu_{}.gputrace", GetConfig().gpu_capture_dir.GetValue(), now_str); + desc->setDestination(MTL::CaptureDestinationGPUTraceDocument); + desc->setOutputURL(ToNSURL(capturePath)); + } + NS::Error* error = nullptr; captureManager->startCapture(desc, &error); if (error) diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 00e56d6d..cbea09cb 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -336,6 +336,7 @@ void CemuConfig::Load(XMLConfigParser& parser) crash_dump = debug.get("CrashDumpUnix", crash_dump); #endif gdb_port = debug.get("GDBPort", 1337); + gpu_capture_dir = debug.get("GPUCaptureDir", ""); // input auto input = parser.get("Input"); @@ -537,6 +538,7 @@ void CemuConfig::Save(XMLConfigParser& parser) debug.set("CrashDumpUnix", crash_dump.GetValue()); #endif debug.set("GDBPort", gdb_port); + debug.set("GPUCaptureDir", gpu_capture_dir.GetValue()); // input auto input = config.set("Input"); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index b3ff1999..08a7c994 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -526,6 +526,7 @@ struct CemuConfig // debug ConfigValueBounds crash_dump{ CrashDump::Disabled }; ConfigValue gdb_port{ 1337 }; + ConfigValue gpu_capture_dir{}; void Load(XMLConfigParser& parser); void Save(XMLConfigParser& parser); diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index 8663dbff..68cd93ed 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -892,6 +893,21 @@ wxPanel* GeneralSettings2::AddDebugPage(wxNotebook* notebook) debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5); } + { + auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0); + debug_row->SetFlexibleDirection(wxBOTH); + debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + debug_row->Add(new wxStaticText(panel, wxID_ANY, _("GPU capture save directory"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_gpu_capture_dir = new wxTextCtrl(panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_DONTWRAP); + m_gpu_capture_dir->SetMinSize(wxSize(150, -1)); + m_gpu_capture_dir->SetToolTip(_("Cemu will save the GPU captures done by selecting Debug -> GPU capture in the menu bar in this directory. If a debugger with support for GPU captures (like Xcode) is attached, the capture will be opened in that debugger instead.")); + + debug_row->Add(m_gpu_capture_dir, 0, wxALL | wxEXPAND, 5); + debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5); + } + panel->SetSizerAndFit(debug_panel_sizer); return panel; @@ -1101,6 +1117,7 @@ void GeneralSettings2::StoreConfig() // debug config.crash_dump = (CrashDump)m_crash_dump->GetSelection(); config.gdb_port = m_gdb_port->GetValue(); + config.gpu_capture_dir = m_gpu_capture_dir->GetValue().utf8_string(); g_config.Save(); } @@ -1794,6 +1811,7 @@ void GeneralSettings2::ApplyConfig() // debug m_crash_dump->SetSelection((int)config.crash_dump.GetValue()); m_gdb_port->SetValue(config.gdb_port.GetValue()); + m_gpu_capture_dir->SetValue(wxHelper::FromUtf8(config.gpu_capture_dir.GetValue())); } void GeneralSettings2::OnAudioAPISelected(wxCommandEvent& event) diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h index 83ede03b..2551b2bd 100644 --- a/src/gui/GeneralSettings2.h +++ b/src/gui/GeneralSettings2.h @@ -78,6 +78,7 @@ private: // Debug wxChoice* m_crash_dump; wxSpinCtrl* m_gdb_port; + wxTextCtrl* m_gpu_capture_dir; void OnAccountCreate(wxCommandEvent& event); void OnAccountDelete(wxCommandEvent& event);