mirror of https://github.com/cemu-project/Cemu.git
Merge pull request #14 from SamoZ256/metal-gpu-capture
GPU capture support
This commit is contained in:
commit
6247b6571f
|
@ -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<uintptr_t>(identifier)) + ")");
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
@ -185,6 +188,9 @@ MetalRenderer::MetalRenderer()
|
|||
m_copyBufferToBufferPipeline = new MetalVoidVertexPipeline(this, utilityLibrary, "vertexCopyBufferToBuffer");
|
||||
|
||||
utilityLibrary->release();
|
||||
|
||||
// HACK: for some reason, this variable ends up being initialized to some garbage data, even though its declared as bool m_captureFrame = false;
|
||||
m_captureFrame = false;
|
||||
}
|
||||
|
||||
MetalRenderer::~MetalRenderer()
|
||||
|
@ -303,6 +309,17 @@ void MetalRenderer::SwapBuffers(bool swapTV, bool swapDRC)
|
|||
|
||||
// Debug
|
||||
m_performanceMonitor.ResetPerFrameData();
|
||||
|
||||
// GPU capture
|
||||
if (m_capturing)
|
||||
{
|
||||
EndCapture();
|
||||
}
|
||||
else if (m_captureFrame)
|
||||
{
|
||||
StartCapture();
|
||||
m_captureFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
void MetalRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool padView) {
|
||||
|
@ -2161,3 +2178,59 @@ void MetalRenderer::EnsureImGuiBackend()
|
|||
//ImGui_ImplMetal_CreateFontsTexture(m_device);
|
||||
}
|
||||
}
|
||||
|
||||
void MetalRenderer::StartCapture()
|
||||
{
|
||||
auto captureManager = MTL::CaptureManager::sharedCaptureManager();
|
||||
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)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Failed to start GPU capture: {}", error->localizedDescription()->utf8String());
|
||||
}
|
||||
|
||||
m_capturing = true;
|
||||
}
|
||||
|
||||
void MetalRenderer::EndCapture()
|
||||
{
|
||||
auto captureManager = MTL::CaptureManager::sharedCaptureManager();
|
||||
captureManager->stopCapture();
|
||||
|
||||
m_capturing = false;
|
||||
}
|
||||
|
|
|
@ -460,6 +460,12 @@ public:
|
|||
m_occlusionQuery.m_lastCommandBuffer = GetAndRetainCurrentCommandBufferIfNotCompleted();
|
||||
}
|
||||
|
||||
// GPU capture
|
||||
void CaptureFrame()
|
||||
{
|
||||
m_captureFrame = true;
|
||||
}
|
||||
|
||||
private:
|
||||
MetalLayerHandle m_mainLayer;
|
||||
MetalLayerHandle m_padLayer;
|
||||
|
@ -533,6 +539,11 @@ private:
|
|||
// State
|
||||
MetalState m_state;
|
||||
|
||||
// GPU capture
|
||||
bool m_captureFrame = false;
|
||||
bool m_capturing = false;
|
||||
|
||||
// Helpers
|
||||
MetalLayerHandle& GetLayer(bool mainWindow)
|
||||
{
|
||||
return (mainWindow ? m_mainLayer : m_padLayer);
|
||||
|
@ -541,4 +552,8 @@ private:
|
|||
void SwapBuffer(bool mainWindow);
|
||||
|
||||
void EnsureImGuiBackend();
|
||||
|
||||
// GPU capture
|
||||
void StartCapture();
|
||||
void EndCapture();
|
||||
};
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -526,6 +526,7 @@ struct CemuConfig
|
|||
// debug
|
||||
ConfigValueBounds<CrashDump> crash_dump{ CrashDump::Disabled };
|
||||
ConfigValue<uint16> gdb_port{ 1337 };
|
||||
ConfigValue<std::string> gpu_capture_dir{};
|
||||
|
||||
void Load(XMLConfigParser& parser);
|
||||
void Save(XMLConfigParser& parser);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <wx/collpane.h>
|
||||
#include <wx/clrpicker.h>
|
||||
#include <wx/cshelp.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/textdlg.h>
|
||||
#include <wx/hyperlink.h>
|
||||
|
||||
|
@ -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. If such debugger is not attached, METAL_CAPTURE_ENABLED must be set to 1 as an environment variable."));
|
||||
|
||||
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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include "Cafe/HW/Latte/Renderer/Metal/MetalRenderer.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
||||
#include "gui/wxgui.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/guiWrapper.h"
|
||||
|
@ -137,6 +139,7 @@ enum
|
|||
MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS,
|
||||
MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY,
|
||||
MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS,
|
||||
MAINFRAME_MENU_ID_DEBUG_GPU_CAPTURE,
|
||||
|
||||
// debug->logging
|
||||
MAINFRAME_MENU_ID_DEBUG_LOGGING0 = 21500,
|
||||
|
@ -212,6 +215,7 @@ EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, MainWindow::OnDebugSetting)
|
|||
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN, MainWindow::OnDebugSetting)
|
||||
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY, MainWindow::OnDebugSetting)
|
||||
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, MainWindow::OnDebugSetting)
|
||||
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_GPU_CAPTURE, MainWindow::OnDebugSetting)
|
||||
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, MainWindow::OnDebugSetting)
|
||||
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_FST, MainWindow::OnDebugSetting)
|
||||
// debug -> View ...
|
||||
|
@ -1007,6 +1011,14 @@ void MainWindow::OnDebugSetting(wxCommandEvent& event)
|
|||
if(!GetConfig().vk_accurate_barriers)
|
||||
wxMessageBox(_("Warning: Disabling the accurate barriers option will lead to flickering graphics but may improve performance. It is highly recommended to leave it turned on."), _("Accurate barriers are off"), wxOK);
|
||||
}
|
||||
else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_GPU_CAPTURE)
|
||||
{
|
||||
cemu_assert_debug(g_renderer->GetType() == RendererAPI::Metal);
|
||||
|
||||
#if ENABLE_METAL
|
||||
static_cast<MetalRenderer*>(g_renderer.get())->CaptureFrame();
|
||||
#endif
|
||||
}
|
||||
else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY)
|
||||
ActiveSettings::EnableAudioOnlyAux(event.IsChecked());
|
||||
else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_DUMP_RAM)
|
||||
|
@ -2254,6 +2266,9 @@ void MainWindow::RecreateMenu()
|
|||
auto accurateBarriers = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, _("&Accurate barriers (Vulkan)"), wxEmptyString);
|
||||
accurateBarriers->Check(GetConfig().vk_accurate_barriers);
|
||||
|
||||
auto gpuCapture = debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_GPU_CAPTURE, _("&GPU capture (Metal)"));
|
||||
gpuCapture->Enable(m_game_launched && g_renderer->GetType() == RendererAPI::Metal);
|
||||
|
||||
debugMenu->AppendSeparator();
|
||||
|
||||
#ifdef CEMU_DEBUG_ASSERT
|
||||
|
|
Loading…
Reference in New Issue