System: Add video capture feature
This commit is contained in:
parent
5f8082734e
commit
af47eb6956
|
@ -14,6 +14,7 @@
|
|||
#include "util/gpu_device.h"
|
||||
#include "util/image.h"
|
||||
#include "util/imgui_manager.h"
|
||||
#include "util/media_capture.h"
|
||||
#include "util/postprocessing.h"
|
||||
#include "util/shadergen.h"
|
||||
#include "util/state_wrapper.h"
|
||||
|
@ -2116,6 +2117,26 @@ bool GPU::RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const
|
|||
return true;
|
||||
}
|
||||
|
||||
bool GPU::SendDisplayToMediaCapture(MediaCapture* cap)
|
||||
{
|
||||
GPUTexture* target = cap->GetRenderTexture();
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
const bool apply_aspect_ratio =
|
||||
(g_settings.display_screenshot_mode != DisplayScreenshotMode::UncorrectedInternalResolution);
|
||||
const bool postfx = (g_settings.display_screenshot_mode != DisplayScreenshotMode::InternalResolution);
|
||||
GSVector4i display_rect, draw_rect;
|
||||
CalculateDrawRect(target->GetWidth(), target->GetHeight(), !g_settings.debugging.show_vram, apply_aspect_ratio,
|
||||
&display_rect, &draw_rect);
|
||||
if (!RenderDisplay(target, display_rect, draw_rect, postfx))
|
||||
return false;
|
||||
|
||||
// TODO: Check for frame rate change
|
||||
|
||||
return cap->DeliverVideoFrame(target);
|
||||
}
|
||||
|
||||
void GPU::DestroyDeinterlaceTextures()
|
||||
{
|
||||
for (std::unique_ptr<GPUTexture>& tex : m_deinterlace_buffers)
|
||||
|
@ -2676,21 +2697,20 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i displ
|
|||
return true;
|
||||
}
|
||||
|
||||
bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
|
||||
bool show_osd_message)
|
||||
void GPU::CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect,
|
||||
GSVector4i* draw_rect) const
|
||||
{
|
||||
u32 width = g_gpu_device->GetWindowWidth();
|
||||
u32 height = g_gpu_device->GetWindowHeight();
|
||||
GSVector4i display_rect, draw_rect;
|
||||
CalculateDrawRect(width, height, true, !g_settings.debugging.show_vram, &display_rect, &draw_rect);
|
||||
*width = g_gpu_device->GetWindowWidth();
|
||||
*height = g_gpu_device->GetWindowHeight();
|
||||
CalculateDrawRect(*width, *height, true, !g_settings.debugging.show_vram, display_rect, draw_rect);
|
||||
|
||||
const bool internal_resolution = (mode != DisplayScreenshotMode::ScreenResolution || g_settings.debugging.show_vram);
|
||||
if (internal_resolution && m_display_texture_view_width != 0 && m_display_texture_view_height != 0)
|
||||
{
|
||||
if (mode == DisplayScreenshotMode::InternalResolution)
|
||||
{
|
||||
const u32 draw_width = static_cast<u32>(draw_rect.width());
|
||||
const u32 draw_height = static_cast<u32>(draw_rect.height());
|
||||
const u32 draw_width = static_cast<u32>(draw_rect->width());
|
||||
const u32 draw_height = static_cast<u32>(draw_rect->height());
|
||||
|
||||
// If internal res, scale the computed draw rectangle to the internal res.
|
||||
// We re-use the draw rect because it's already been AR corrected.
|
||||
|
@ -2701,42 +2721,52 @@ bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mod
|
|||
{
|
||||
// stretch height, preserve width
|
||||
const float scale = static_cast<float>(m_display_texture_view_width) / static_cast<float>(draw_width);
|
||||
width = m_display_texture_view_width;
|
||||
height = static_cast<u32>(std::round(static_cast<float>(draw_height) * scale));
|
||||
*width = m_display_texture_view_width;
|
||||
*height = static_cast<u32>(std::round(static_cast<float>(draw_height) * scale));
|
||||
}
|
||||
else
|
||||
{
|
||||
// stretch width, preserve height
|
||||
const float scale = static_cast<float>(m_display_texture_view_height) / static_cast<float>(draw_height);
|
||||
width = static_cast<u32>(std::round(static_cast<float>(draw_width) * scale));
|
||||
height = m_display_texture_view_height;
|
||||
*width = static_cast<u32>(std::round(static_cast<float>(draw_width) * scale));
|
||||
*height = m_display_texture_view_height;
|
||||
}
|
||||
|
||||
// DX11 won't go past 16K texture size.
|
||||
const u32 max_texture_size = g_gpu_device->GetMaxTextureSize();
|
||||
if (width > max_texture_size)
|
||||
if (*width > max_texture_size)
|
||||
{
|
||||
height = static_cast<u32>(static_cast<float>(height) /
|
||||
(static_cast<float>(width) / static_cast<float>(max_texture_size)));
|
||||
width = max_texture_size;
|
||||
*height = static_cast<u32>(static_cast<float>(*height) /
|
||||
(static_cast<float>(*width) / static_cast<float>(max_texture_size)));
|
||||
*width = max_texture_size;
|
||||
}
|
||||
if (height > max_texture_size)
|
||||
if (*height > max_texture_size)
|
||||
{
|
||||
height = max_texture_size;
|
||||
width = static_cast<u32>(static_cast<float>(width) /
|
||||
(static_cast<float>(height) / static_cast<float>(max_texture_size)));
|
||||
*height = max_texture_size;
|
||||
*width = static_cast<u32>(static_cast<float>(*width) /
|
||||
(static_cast<float>(*height) / static_cast<float>(max_texture_size)));
|
||||
}
|
||||
}
|
||||
else // if (mode == DisplayScreenshotMode::UncorrectedInternalResolution)
|
||||
{
|
||||
width = m_display_texture_view_width;
|
||||
height = m_display_texture_view_height;
|
||||
*width = m_display_texture_view_width;
|
||||
*height = m_display_texture_view_height;
|
||||
}
|
||||
|
||||
// Remove padding, it's not part of the framebuffer.
|
||||
draw_rect = GSVector4i(0, 0, static_cast<s32>(width), static_cast<s32>(height));
|
||||
display_rect = draw_rect;
|
||||
*draw_rect = GSVector4i(0, 0, static_cast<s32>(*width), static_cast<s32>(*height));
|
||||
*display_rect = *draw_rect;
|
||||
}
|
||||
}
|
||||
|
||||
bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
|
||||
bool show_osd_message)
|
||||
{
|
||||
u32 width, height;
|
||||
GSVector4i display_rect, draw_rect;
|
||||
CalculateScreenshotSize(mode, &width, &height, &display_rect, &draw_rect);
|
||||
|
||||
const bool internal_resolution = (mode != DisplayScreenshotMode::ScreenResolution);
|
||||
if (width == 0 || height == 0)
|
||||
return false;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ class StateWrapper;
|
|||
class GPUDevice;
|
||||
class GPUTexture;
|
||||
class GPUPipeline;
|
||||
class MediaCapture;
|
||||
|
||||
struct Settings;
|
||||
|
||||
|
@ -210,6 +211,10 @@ public:
|
|||
void CalculateDrawRect(s32 window_width, s32 window_height, bool apply_rotation, bool apply_aspect_ratio,
|
||||
GSVector4i* display_rect, GSVector4i* draw_rect) const;
|
||||
|
||||
/// Helper function for computing screenshot bounds.
|
||||
void CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect,
|
||||
GSVector4i* draw_rect) const;
|
||||
|
||||
/// Helper function to save current display texture to PNG.
|
||||
bool WriteDisplayTextureToFile(std::string filename, bool compress_on_thread = false);
|
||||
|
||||
|
@ -225,6 +230,9 @@ public:
|
|||
/// Draws the current display texture, with any post-processing.
|
||||
bool PresentDisplay();
|
||||
|
||||
/// Sends the current frame to media capture.
|
||||
bool SendDisplayToMediaCapture(MediaCapture* cap);
|
||||
|
||||
/// Reads the CLUT from the specified coordinates, accounting for wrap-around.
|
||||
static void ReadCLUT(u16* dest, GPUTexturePaletteReg reg, bool clut_is_8bit);
|
||||
|
||||
|
|
|
@ -358,6 +358,17 @@ DEFINE_HOTKEY("ResetEmulationSpeed", TRANSLATE_NOOP("Hotkeys", "System"),
|
|||
}
|
||||
})
|
||||
|
||||
DEFINE_HOTKEY("ToggleMediaCapture", TRANSLATE_NOOP("Hotkeys", "System"),
|
||||
TRANSLATE_NOOP("Hotkeys", "Toggle Media Capture"), [](s32 pressed) {
|
||||
if (!pressed)
|
||||
{
|
||||
if (System::GetMediaCapture())
|
||||
System::StopMediaCapture();
|
||||
else
|
||||
System::StartMediaCapture();
|
||||
}
|
||||
})
|
||||
|
||||
DEFINE_HOTKEY("ToggleSoftwareRendering", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
||||
TRANSLATE_NOOP("Hotkeys", "Toggle Software Rendering"), [](s32 pressed) {
|
||||
if (!pressed && System::IsValid())
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "util/imgui_fullscreen.h"
|
||||
#include "util/imgui_manager.h"
|
||||
#include "util/input_manager.h"
|
||||
#include "util/media_capture.h"
|
||||
|
||||
#include "common/align.h"
|
||||
#include "common/error.h"
|
||||
|
@ -48,7 +49,9 @@ Log_SetChannel(ImGuiManager);
|
|||
|
||||
namespace ImGuiManager {
|
||||
static void FormatProcessorStat(SmallStringBase& text, double usage, double time);
|
||||
static void DrawPerformanceOverlay();
|
||||
static void DrawPerformanceOverlay(float& position_y, float scale, float margin, float spacing);
|
||||
static void DrawMediaCaptureOverlay(float& position_y, float scale, float margin, float spacing);
|
||||
static void DrawFrameTimeOverlay(float& position_y, float scale, float margin, float spacing);
|
||||
static void DrawEnhancementsOverlay();
|
||||
static void DrawInputsOverlay();
|
||||
} // namespace ImGuiManager
|
||||
|
@ -191,7 +194,13 @@ void ImGuiManager::RenderTextOverlays()
|
|||
const System::State state = System::GetState();
|
||||
if (state != System::State::Shutdown)
|
||||
{
|
||||
DrawPerformanceOverlay();
|
||||
const float scale = ImGuiManager::GetGlobalScale();
|
||||
const float margin = std::ceil(10.0f * scale);
|
||||
const float spacing = std::ceil(5.0f * scale);
|
||||
float position_y = margin;
|
||||
DrawPerformanceOverlay(position_y, scale, margin, spacing);
|
||||
DrawFrameTimeOverlay(position_y, scale, margin, spacing);
|
||||
DrawMediaCaptureOverlay(position_y, scale, margin, spacing);
|
||||
|
||||
if (g_settings.display_show_enhancements && state != System::State::Paused)
|
||||
DrawEnhancementsOverlay();
|
||||
|
@ -212,7 +221,7 @@ void ImGuiManager::FormatProcessorStat(SmallStringBase& text, double usage, doub
|
|||
text.append_format("{:.1f}% ({:.2f}ms)", usage, time);
|
||||
}
|
||||
|
||||
void ImGuiManager::DrawPerformanceOverlay()
|
||||
void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float margin, float spacing)
|
||||
{
|
||||
if (!(g_settings.display_show_fps || g_settings.display_show_speed || g_settings.display_show_gpu_stats ||
|
||||
g_settings.display_show_resolution || g_settings.display_show_cpu_usage ||
|
||||
|
@ -222,14 +231,9 @@ void ImGuiManager::DrawPerformanceOverlay()
|
|||
return;
|
||||
}
|
||||
|
||||
const float scale = ImGuiManager::GetGlobalScale();
|
||||
const float shadow_offset = std::ceil(1.0f * scale);
|
||||
const float margin = std::ceil(10.0f * scale);
|
||||
const float spacing = std::ceil(5.0f * scale);
|
||||
ImFont* fixed_font = ImGuiManager::GetFixedFont();
|
||||
ImFont* standard_font = ImGuiManager::GetStandardFont();
|
||||
float position_y = margin;
|
||||
|
||||
ImDrawList* dl = ImGui::GetBackgroundDrawList();
|
||||
SmallString text;
|
||||
ImVec2 text_size;
|
||||
|
@ -364,6 +368,13 @@ void ImGuiManager::DrawPerformanceOverlay()
|
|||
FormatProcessorStat(text, System::GetSWThreadUsage(), System::GetSWThreadAverageTime());
|
||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
|
||||
if (MediaCapture* cap = System::GetMediaCapture())
|
||||
{
|
||||
text.assign("CAP: ");
|
||||
FormatProcessorStat(text, cap->GetCaptureThreadUsage(), cap->GetCaptureThreadTime());
|
||||
DRAW_LINE(fixed_font, text, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
}
|
||||
|
||||
if (g_settings.display_show_gpu_usage && g_gpu_device->IsGPUTimingEnabled())
|
||||
|
@ -382,67 +393,6 @@ void ImGuiManager::DrawPerformanceOverlay()
|
|||
DRAW_LINE(standard_font, text, IM_COL32(255, 255, 255, 255));
|
||||
}
|
||||
}
|
||||
|
||||
if (g_settings.display_show_frame_times)
|
||||
{
|
||||
const ImVec2 history_size(200.0f * scale, 50.0f * scale);
|
||||
ImGui::SetNextWindowSize(ImVec2(history_size.x, history_size.y));
|
||||
ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - margin - history_size.x, position_y));
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.25f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_PlotLines, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
||||
if (ImGui::Begin("##frame_times", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs))
|
||||
{
|
||||
ImGui::PushFont(fixed_font);
|
||||
|
||||
auto [min, max] = GetMinMax(System::GetFrameTimeHistory());
|
||||
|
||||
// add a little bit of space either side, so we're not constantly resizing
|
||||
if ((max - min) < 4.0f)
|
||||
{
|
||||
min = min - std::fmod(min, 1.0f);
|
||||
max = max - std::fmod(max, 1.0f) + 1.0f;
|
||||
min = std::max(min - 2.0f, 0.0f);
|
||||
max += 2.0f;
|
||||
}
|
||||
|
||||
ImGui::PlotEx(
|
||||
ImGuiPlotType_Lines, "##frame_times",
|
||||
[](void*, int idx) -> float {
|
||||
return System::GetFrameTimeHistory()[((System::GetFrameTimeHistoryPos() + idx) %
|
||||
System::NUM_FRAME_TIME_SAMPLES)];
|
||||
},
|
||||
nullptr, System::NUM_FRAME_TIME_SAMPLES, 0, nullptr, min, max, history_size);
|
||||
|
||||
ImDrawList* win_dl = ImGui::GetCurrentWindow()->DrawList;
|
||||
const ImVec2 wpos(ImGui::GetCurrentWindow()->Pos);
|
||||
|
||||
text.format("{:.1f} ms", max);
|
||||
text_size = fixed_font->CalcTextSizeA(fixed_font->FontSize, FLT_MAX, 0.0f, text.c_str(), text.end_ptr());
|
||||
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing + shadow_offset, wpos.y + shadow_offset),
|
||||
IM_COL32(0, 0, 0, 100), text.c_str(), text.end_ptr());
|
||||
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing, wpos.y), IM_COL32(255, 255, 255, 255),
|
||||
text.c_str(), text.end_ptr());
|
||||
|
||||
text.format("{:.1f} ms", min);
|
||||
text_size = fixed_font->CalcTextSizeA(fixed_font->FontSize, FLT_MAX, 0.0f, text.c_str(), text.end_ptr());
|
||||
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing + shadow_offset,
|
||||
wpos.y + history_size.y - fixed_font->FontSize + shadow_offset),
|
||||
IM_COL32(0, 0, 0, 100), text.c_str(), text.end_ptr());
|
||||
win_dl->AddText(
|
||||
ImVec2(wpos.x + history_size.x - text_size.x - spacing, wpos.y + history_size.y - fixed_font->FontSize),
|
||||
IM_COL32(255, 255, 255, 255), text.c_str(), text.end_ptr());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(5);
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
}
|
||||
else if (g_settings.display_show_status_indicators && state == System::State::Paused &&
|
||||
!FullscreenUI::HasActiveWindow())
|
||||
|
@ -547,6 +497,114 @@ void ImGuiManager::DrawEnhancementsOverlay()
|
|||
IM_COL32(255, 255, 255, 255), text.c_str(), text.end_ptr());
|
||||
}
|
||||
|
||||
void ImGuiManager::DrawMediaCaptureOverlay(float& position_y, float scale, float margin, float spacing)
|
||||
{
|
||||
MediaCapture* const cap = System::GetMediaCapture();
|
||||
if (!cap || FullscreenUI::HasActiveWindow())
|
||||
return;
|
||||
|
||||
const float shadow_offset = std::ceil(scale);
|
||||
ImFont* const standard_font = ImGuiManager::GetStandardFont();
|
||||
ImDrawList* dl = ImGui::GetBackgroundDrawList();
|
||||
|
||||
static constexpr const char* ICON = ICON_FA_VIDEO;
|
||||
const time_t elapsed_time = cap->GetElapsedTime();
|
||||
const TinyString text_msg = TinyString::from_format(" {:02d}:{:02d}:{:02d}", elapsed_time / 3600,
|
||||
(elapsed_time % 3600) / 60, (elapsed_time % 3600) % 60);
|
||||
const ImVec2 icon_size = standard_font->CalcTextSizeA(standard_font->FontSize, std::numeric_limits<float>::max(),
|
||||
-1.0f, ICON, nullptr, nullptr);
|
||||
const ImVec2 text_size = standard_font->CalcTextSizeA(standard_font->FontSize, std::numeric_limits<float>::max(),
|
||||
-1.0f, text_msg.c_str(), text_msg.end_ptr(), nullptr);
|
||||
|
||||
const float box_margin = 2.0f * scale;
|
||||
const ImVec2 box_size = ImVec2(icon_size.x + shadow_offset + text_size.x + box_margin * 2.0f,
|
||||
std::max(icon_size.x, text_size.y) + box_margin * 2.0f);
|
||||
const ImVec2 box_pos = ImVec2(ImGui::GetIO().DisplaySize.x - margin - box_size.x, position_y);
|
||||
dl->AddRectFilled(box_pos, box_pos + box_size, IM_COL32(0, 0, 0, 64), box_margin);
|
||||
|
||||
const ImVec2 text_start = ImVec2(box_pos.x + box_margin, box_pos.y + box_margin);
|
||||
dl->AddText(standard_font, standard_font->FontSize,
|
||||
ImVec2(text_start.x + shadow_offset, text_start.y + shadow_offset), IM_COL32(0, 0, 0, 100), ICON);
|
||||
dl->AddText(standard_font, standard_font->FontSize,
|
||||
ImVec2(text_start.x + icon_size.x + shadow_offset, text_start.y + shadow_offset), IM_COL32(0, 0, 0, 100),
|
||||
text_msg.c_str(), text_msg.end_ptr());
|
||||
dl->AddText(standard_font, standard_font->FontSize, text_start, IM_COL32(255, 0, 0, 255), ICON);
|
||||
dl->AddText(standard_font, standard_font->FontSize, ImVec2(text_start.x + icon_size.x, text_start.y),
|
||||
IM_COL32(255, 255, 255, 255), text_msg.c_str(), text_msg.end_ptr());
|
||||
|
||||
position_y += box_size.y + spacing;
|
||||
}
|
||||
|
||||
void ImGuiManager::DrawFrameTimeOverlay(float& position_y, float scale, float margin, float spacing)
|
||||
{
|
||||
if (!g_settings.display_show_frame_times || System::IsPaused())
|
||||
return;
|
||||
|
||||
const float shadow_offset = std::ceil(1.0f * scale);
|
||||
ImFont* fixed_font = ImGuiManager::GetFixedFont();
|
||||
|
||||
const ImVec2 history_size(200.0f * scale, 50.0f * scale);
|
||||
ImGui::SetNextWindowSize(ImVec2(history_size.x, history_size.y));
|
||||
ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - margin - history_size.x, position_y));
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.25f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_PlotLines, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
||||
if (ImGui::Begin("##frame_times", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs))
|
||||
{
|
||||
ImGui::PushFont(fixed_font);
|
||||
|
||||
auto [min, max] = GetMinMax(System::GetFrameTimeHistory());
|
||||
|
||||
// add a little bit of space either side, so we're not constantly resizing
|
||||
if ((max - min) < 4.0f)
|
||||
{
|
||||
min = min - std::fmod(min, 1.0f);
|
||||
max = max - std::fmod(max, 1.0f) + 1.0f;
|
||||
min = std::max(min - 2.0f, 0.0f);
|
||||
max += 2.0f;
|
||||
}
|
||||
|
||||
ImGui::PlotEx(
|
||||
ImGuiPlotType_Lines, "##frame_times",
|
||||
[](void*, int idx) -> float {
|
||||
return System::GetFrameTimeHistory()[((System::GetFrameTimeHistoryPos() + idx) %
|
||||
System::NUM_FRAME_TIME_SAMPLES)];
|
||||
},
|
||||
nullptr, System::NUM_FRAME_TIME_SAMPLES, 0, nullptr, min, max, history_size);
|
||||
|
||||
ImDrawList* win_dl = ImGui::GetCurrentWindow()->DrawList;
|
||||
const ImVec2 wpos(ImGui::GetCurrentWindow()->Pos);
|
||||
|
||||
TinyString text;
|
||||
text.format("{:.1f} ms", max);
|
||||
ImVec2 text_size = fixed_font->CalcTextSizeA(fixed_font->FontSize, FLT_MAX, 0.0f, text.c_str(), text.end_ptr());
|
||||
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing + shadow_offset, wpos.y + shadow_offset),
|
||||
IM_COL32(0, 0, 0, 100), text.c_str(), text.end_ptr());
|
||||
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing, wpos.y), IM_COL32(255, 255, 255, 255),
|
||||
text.c_str(), text.end_ptr());
|
||||
|
||||
text.format("{:.1f} ms", min);
|
||||
text_size = fixed_font->CalcTextSizeA(fixed_font->FontSize, FLT_MAX, 0.0f, text.c_str(), text.end_ptr());
|
||||
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing + shadow_offset,
|
||||
wpos.y + history_size.y - fixed_font->FontSize + shadow_offset),
|
||||
IM_COL32(0, 0, 0, 100), text.c_str(), text.end_ptr());
|
||||
win_dl->AddText(
|
||||
ImVec2(wpos.x + history_size.x - text_size.x - spacing, wpos.y + history_size.y - fixed_font->FontSize),
|
||||
IM_COL32(255, 255, 255, 255), text.c_str(), text.end_ptr());
|
||||
ImGui::PopFont();
|
||||
}
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar(5);
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
position_y += history_size.y + spacing;
|
||||
}
|
||||
|
||||
void ImGuiManager::DrawInputsOverlay()
|
||||
{
|
||||
const float scale = ImGuiManager::GetGlobalScale();
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "util/gpu_device.h"
|
||||
#include "util/imgui_manager.h"
|
||||
#include "util/input_manager.h"
|
||||
#include "util/media_capture.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/file_system.h"
|
||||
|
@ -82,6 +83,12 @@ float SettingInfo::FloatStepValue() const
|
|||
return step_value ? StringUtil::FromChars<float>(step_value).value_or(fallback_value) : fallback_value;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
const MediaCaptureBackend Settings::DEFAULT_MEDIA_CAPTURE_BACKEND = MediaCaptureBackend::MediaFoundation;
|
||||
#elif !defined(__ANDROID__)
|
||||
const MediaCaptureBackend Settings::DEFAULT_MEDIA_CAPTURE_BACKEND = MediaCaptureBackend::FFMPEG;
|
||||
#endif
|
||||
|
||||
Settings::Settings()
|
||||
{
|
||||
controller_types[0] = DEFAULT_CONTROLLER_1_TYPE;
|
||||
|
@ -405,6 +412,27 @@ void Settings::Load(SettingsInterface& si)
|
|||
achievements_leaderboard_duration =
|
||||
si.GetIntValue("Cheevos", "LeaderboardsDuration", DEFAULT_LEADERBOARD_NOTIFICATION_TIME);
|
||||
|
||||
#ifndef __ANDROID__
|
||||
media_capture_backend =
|
||||
MediaCapture::ParseBackendName(
|
||||
si.GetStringValue("MediaCapture", "Backend", MediaCapture::GetBackendName(DEFAULT_MEDIA_CAPTURE_BACKEND)).c_str())
|
||||
.value_or(DEFAULT_MEDIA_CAPTURE_BACKEND);
|
||||
media_capture_container = si.GetStringValue("MediaCapture", "Container", "mp4");
|
||||
media_capture_video = si.GetBoolValue("MediaCapture", "VideoCapture", true);
|
||||
media_capture_video_width = si.GetUIntValue("MediaCapture", "VideoWidth", 640);
|
||||
media_capture_video_height = si.GetUIntValue("MediaCapture", "VideoHeight", 480);
|
||||
media_capture_video_auto_size = si.GetBoolValue("MediaCapture", "VideoAutoSize", false);
|
||||
media_capture_video_bitrate = si.GetUIntValue("MediaCapture", "VideoBitrate", 6000);
|
||||
media_capture_video_codec = si.GetStringValue("MediaCapture", "VideoCodec");
|
||||
media_capture_video_codec_use_args = si.GetBoolValue("MediaCapture", "VideoCodecUseArgs", false);
|
||||
media_capture_video_codec_args = si.GetStringValue("MediaCapture", "AudioCodecArgs");
|
||||
media_capture_audio = si.GetBoolValue("MediaCapture", "AudioCapture", true);
|
||||
media_capture_audio_bitrate = si.GetUIntValue("MediaCapture", "AudioBitrate", 128);
|
||||
media_capture_audio_codec = si.GetStringValue("MediaCapture", "AudioCodec");
|
||||
media_capture_audio_codec_use_args = si.GetBoolValue("MediaCapture", "AudioCodecUseArgs", false);
|
||||
media_capture_audio_codec_args = si.GetStringValue("MediaCapture", "AudioCodecArgs");
|
||||
#endif
|
||||
|
||||
log_level = ParseLogLevelName(si.GetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL)).c_str())
|
||||
.value_or(DEFAULT_LOG_LEVEL);
|
||||
log_filter = si.GetStringValue("Logging", "LogFilter", "");
|
||||
|
@ -657,6 +685,24 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
|||
si.SetIntValue("Cheevos", "NotificationsDuration", achievements_notification_duration);
|
||||
si.SetIntValue("Cheevos", "LeaderboardsDuration", achievements_leaderboard_duration);
|
||||
|
||||
#ifndef __ANDROID__
|
||||
si.SetStringValue("MediaCapture", "Backend", MediaCapture::GetBackendName(media_capture_backend));
|
||||
si.SetStringValue("MediaCapture", "Container", media_capture_container.c_str());
|
||||
si.SetBoolValue("MediaCapture", "VideoCapture", media_capture_video);
|
||||
si.SetUIntValue("MediaCapture", "VideoWidth", media_capture_video_width);
|
||||
si.SetUIntValue("MediaCapture", "VideoHeight", media_capture_video_height);
|
||||
si.SetBoolValue("MediaCapture", "VideoAutoSize", media_capture_video_auto_size);
|
||||
si.SetUIntValue("MediaCapture", "VideoBitrate", media_capture_video_bitrate);
|
||||
si.SetStringValue("MediaCapture", "VideoCodec", media_capture_video_codec.c_str());
|
||||
si.SetBoolValue("MediaCapture", "VideoCodecUseArgs", media_capture_video_codec_use_args);
|
||||
si.SetStringValue("MediaCapture", "AudioCodecArgs", media_capture_video_codec_args.c_str());
|
||||
si.SetBoolValue("MediaCapture", "AudioCapture", media_capture_audio);
|
||||
si.SetUIntValue("MediaCapture", "AudioBitrate", media_capture_audio_bitrate);
|
||||
si.SetStringValue("MediaCapture", "AudioCodec", media_capture_audio_codec.c_str());
|
||||
si.SetBoolValue("MediaCapture", "AudioCodecUseArgs", media_capture_audio_codec_use_args);
|
||||
si.SetStringValue("MediaCapture", "AudioCodecArgs", media_capture_audio_codec_args.c_str());
|
||||
#endif
|
||||
|
||||
if (!ignore_base)
|
||||
{
|
||||
si.SetStringValue("Logging", "LogLevel", GetLogLevelName(log_level));
|
||||
|
@ -1823,6 +1869,7 @@ std::string EmuFolders::Screenshots;
|
|||
std::string EmuFolders::Shaders;
|
||||
std::string EmuFolders::Textures;
|
||||
std::string EmuFolders::UserResources;
|
||||
std::string EmuFolders::Videos;
|
||||
|
||||
void EmuFolders::SetDefaults()
|
||||
{
|
||||
|
@ -1840,6 +1887,7 @@ void EmuFolders::SetDefaults()
|
|||
Shaders = Path::Combine(DataRoot, "shaders");
|
||||
Textures = Path::Combine(DataRoot, "textures");
|
||||
UserResources = Path::Combine(DataRoot, "resources");
|
||||
Videos = Path::Combine(DataRoot, "videos");
|
||||
}
|
||||
|
||||
static std::string LoadPathFromSettings(SettingsInterface& si, const std::string& root, const char* section,
|
||||
|
@ -1870,6 +1918,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
|
|||
Shaders = LoadPathFromSettings(si, DataRoot, "Folders", "Shaders", "shaders");
|
||||
Textures = LoadPathFromSettings(si, DataRoot, "Folders", "Textures", "textures");
|
||||
UserResources = LoadPathFromSettings(si, DataRoot, "Folders", "UserResources", "resources");
|
||||
Videos = LoadPathFromSettings(si, DataRoot, "Folders", "Videos", "videos");
|
||||
|
||||
DEV_LOG("BIOS Directory: {}", Bios);
|
||||
DEV_LOG("Cache Directory: {}", Cache);
|
||||
|
@ -1886,6 +1935,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
|
|||
DEV_LOG("Shaders Directory: {}", Shaders);
|
||||
DEV_LOG("Textures Directory: {}", Textures);
|
||||
DEV_LOG("User Resources Directory: {}", UserResources);
|
||||
DEV_LOG("Videos Directory: {}", Videos);
|
||||
}
|
||||
|
||||
void EmuFolders::Save(SettingsInterface& si)
|
||||
|
@ -1905,6 +1955,7 @@ void EmuFolders::Save(SettingsInterface& si)
|
|||
si.SetStringValue("Folders", "Shaders", Path::MakeRelative(Shaders, DataRoot).c_str());
|
||||
si.SetStringValue("Folders", "Textures", Path::MakeRelative(Textures, DataRoot).c_str());
|
||||
si.SetStringValue("Folders", "UserResources", Path::MakeRelative(UserResources, DataRoot).c_str());
|
||||
si.SetStringValue("Folders", "Videos", Path::MakeRelative(UserResources, Videos).c_str());
|
||||
}
|
||||
|
||||
void EmuFolders::Update()
|
||||
|
@ -1954,6 +2005,7 @@ bool EmuFolders::EnsureFoldersExist()
|
|||
result;
|
||||
result = FileSystem::EnsureDirectoryExists(Textures.c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(UserResources.c_str(), false) && result;
|
||||
result = FileSystem::EnsureDirectoryExists(Videos.c_str(), false) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <vector>
|
||||
|
||||
enum class RenderAPI : u32;
|
||||
enum class MediaCaptureBackend : u8;
|
||||
|
||||
struct SettingInfo
|
||||
{
|
||||
|
@ -223,6 +224,25 @@ struct Settings
|
|||
s32 achievements_notification_duration = DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME;
|
||||
s32 achievements_leaderboard_duration = DEFAULT_LEADERBOARD_NOTIFICATION_TIME;
|
||||
|
||||
#ifndef __ANDROID__
|
||||
// media capture
|
||||
std::string media_capture_container;
|
||||
std::string media_capture_audio_codec;
|
||||
std::string media_capture_audio_codec_args;
|
||||
std::string media_capture_video_codec;
|
||||
std::string media_capture_video_codec_args;
|
||||
u32 media_capture_video_width = 640;
|
||||
u32 media_capture_video_height = 480;
|
||||
u32 media_capture_video_bitrate = 6000;
|
||||
u32 media_capture_audio_bitrate = 128;
|
||||
MediaCaptureBackend media_capture_backend = DEFAULT_MEDIA_CAPTURE_BACKEND;
|
||||
bool media_capture_video : 1 = true;
|
||||
bool media_capture_video_codec_use_args : 1 = true;
|
||||
bool media_capture_video_auto_size : 1 = false;
|
||||
bool media_capture_audio : 1 = true;
|
||||
bool media_capture_audio_codec_use_args : 1 = true;
|
||||
#endif
|
||||
|
||||
struct DebugSettings
|
||||
{
|
||||
bool show_vram : 1 = false;
|
||||
|
@ -517,6 +537,11 @@ struct Settings
|
|||
|
||||
static constexpr SaveStateCompressionMode DEFAULT_SAVE_STATE_COMPRESSION_MODE = SaveStateCompressionMode::ZstDefault;
|
||||
|
||||
#ifndef __ANDROID__
|
||||
static const MediaCaptureBackend DEFAULT_MEDIA_CAPTURE_BACKEND;
|
||||
static constexpr const char* DEFAULT_MEDIA_CAPTURE_CONTAINER = "mp4";
|
||||
#endif
|
||||
|
||||
// Enable console logging by default on Linux platforms.
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
static constexpr bool DEFAULT_LOG_TO_CONSOLE = true;
|
||||
|
@ -562,6 +587,7 @@ extern std::string Screenshots;
|
|||
extern std::string Shaders;
|
||||
extern std::string Textures;
|
||||
extern std::string UserResources;
|
||||
extern std::string Videos;
|
||||
|
||||
// Assumes that AppRoot and DataRoot have been initialized.
|
||||
void SetDefaults();
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "util/audio_stream.h"
|
||||
#include "util/imgui_manager.h"
|
||||
#include "util/media_capture.h"
|
||||
#include "util/state_wrapper.h"
|
||||
#include "util/wav_writer.h"
|
||||
|
||||
|
@ -482,7 +483,6 @@ void SPU::CPUClockChanged()
|
|||
|
||||
void SPU::Shutdown()
|
||||
{
|
||||
StopDumpingAudio();
|
||||
s_state.tick_event.Deactivate();
|
||||
s_state.transfer_event.Deactivate();
|
||||
s_state.audio_stream.reset();
|
||||
|
@ -1508,11 +1508,8 @@ void SPU::InternalGeneratePendingSamples()
|
|||
s_state.tick_event.InvokeEarly(force_exec);
|
||||
}
|
||||
|
||||
bool SPU::IsDumpingAudio()
|
||||
{
|
||||
return static_cast<bool>(s_state.dump_writer);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// TODO: FIXME
|
||||
bool SPU::StartDumpingAudio(const char* filename)
|
||||
{
|
||||
s_state.dump_writer.reset();
|
||||
|
@ -1562,6 +1559,7 @@ bool SPU::StopDumpingAudio()
|
|||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::array<u8, SPU::RAM_SIZE>& SPU::GetRAM()
|
||||
{
|
||||
|
@ -2435,8 +2433,11 @@ void SPU::Execute(void* param, TickCount ticks, TickCount ticks_late)
|
|||
}
|
||||
}
|
||||
|
||||
if (s_state.dump_writer) [[unlikely]]
|
||||
s_state.dump_writer->WriteFrames(output_frame_start, frames_in_this_batch);
|
||||
if (MediaCapture* cap = System::GetMediaCapture()) [[unlikely]]
|
||||
{
|
||||
if (!cap->DeliverAudioFrames(output_frame_start, frames_in_this_batch))
|
||||
System::StopMediaCapture();
|
||||
}
|
||||
|
||||
output_stream->EndWrite(frames_in_this_batch);
|
||||
remaining_frames -= frames_in_this_batch;
|
||||
|
|
|
@ -38,15 +38,6 @@ void DrawDebugStateWindow();
|
|||
// Executes the SPU, generating any pending samples.
|
||||
void GeneratePendingSamples();
|
||||
|
||||
/// Returns true if currently dumping audio.
|
||||
bool IsDumpingAudio();
|
||||
|
||||
/// Starts dumping audio to file.
|
||||
bool StartDumpingAudio(const char* filename);
|
||||
|
||||
/// Stops dumping audio to file, if started.
|
||||
bool StopDumpingAudio();
|
||||
|
||||
/// Access to SPU RAM.
|
||||
const std::array<u8, RAM_SIZE>& GetRAM();
|
||||
std::array<u8, RAM_SIZE>& GetWritableRAM();
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include "util/ini_settings_interface.h"
|
||||
#include "util/input_manager.h"
|
||||
#include "util/iso_reader.h"
|
||||
#include "util/media_capture.h"
|
||||
#include "util/platform_misc.h"
|
||||
#include "util/postprocessing.h"
|
||||
#include "util/sockets.h"
|
||||
|
@ -78,6 +79,7 @@ Log_SetChannel(System);
|
|||
|
||||
#ifdef _WIN32
|
||||
#include "common/windows_headers.h"
|
||||
#include <Objbase.h>
|
||||
#include <mmsystem.h>
|
||||
#include <objbase.h>
|
||||
#endif
|
||||
|
@ -302,6 +304,7 @@ static Common::Timer s_frame_timer;
|
|||
static Threading::ThreadHandle s_cpu_thread_handle;
|
||||
|
||||
static std::unique_ptr<CheatList> s_cheat_list;
|
||||
static std::unique_ptr<MediaCapture> s_media_capture;
|
||||
|
||||
// temporary save state, created when loading, used to undo load state
|
||||
static std::optional<System::SaveStateBuffer> s_undo_load_state;
|
||||
|
@ -445,6 +448,8 @@ void System::Internal::ProcessShutdown()
|
|||
|
||||
bool System::Internal::CPUThreadInitialize(Error* error)
|
||||
{
|
||||
Threading::SetNameOfCurrentThread("CPU Thread");
|
||||
|
||||
#ifdef _WIN32
|
||||
// On Win32, we have a bunch of things which use COM (e.g. SDL, Cubeb, etc).
|
||||
// We need to initialize COM first, before anything else does, because otherwise they might
|
||||
|
@ -1690,8 +1695,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
|
|||
if (parameters.load_image_to_ram || g_settings.cdrom_load_image_to_ram)
|
||||
CDROM::PrecacheMedia();
|
||||
|
||||
if (parameters.start_audio_dump)
|
||||
StartDumpingAudio();
|
||||
if (parameters.start_media_capture)
|
||||
StartMediaCapture({});
|
||||
|
||||
if (g_settings.start_paused || parameters.override_start_paused.value_or(false))
|
||||
PauseSystem(true);
|
||||
|
@ -1809,6 +1814,9 @@ void System::DestroySystem()
|
|||
if (s_state == State::Shutdown)
|
||||
return;
|
||||
|
||||
if (s_media_capture)
|
||||
StopMediaCapture();
|
||||
|
||||
s_undo_load_state.reset();
|
||||
|
||||
#ifdef ENABLE_GDB_SERVER
|
||||
|
@ -2003,6 +2011,13 @@ void System::FrameDone()
|
|||
SaveRunaheadState();
|
||||
}
|
||||
|
||||
// Kick off media capture early, might take a while.
|
||||
if (s_media_capture && s_media_capture->IsCapturingVideo()) [[unlikely]]
|
||||
{
|
||||
if (!g_gpu->SendDisplayToMediaCapture(s_media_capture.get())) [[unlikely]]
|
||||
StopMediaCapture();
|
||||
}
|
||||
|
||||
Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
|
||||
|
||||
// pre-frame sleep accounting (input lag reduction)
|
||||
|
@ -3134,6 +3149,9 @@ void System::UpdatePerformanceCounters()
|
|||
s_sw_thread_usage = static_cast<float>(static_cast<double>(sw_delta) * pct_divider);
|
||||
s_sw_thread_time = static_cast<float>(static_cast<double>(sw_delta) * time_divider);
|
||||
|
||||
if (s_media_capture)
|
||||
s_media_capture->UpdateCaptureThreadUsage(pct_divider, time_divider);
|
||||
|
||||
s_fps_timer.ResetTo(now_ticks);
|
||||
|
||||
if (g_gpu_device->IsGPUTimingEnabled())
|
||||
|
@ -4896,61 +4914,6 @@ void System::UpdateVolume()
|
|||
SPU::GetOutputStream()->SetOutputVolume(GetAudioOutputVolume());
|
||||
}
|
||||
|
||||
bool System::IsDumpingAudio()
|
||||
{
|
||||
return SPU::IsDumpingAudio();
|
||||
}
|
||||
|
||||
bool System::StartDumpingAudio(const char* filename)
|
||||
{
|
||||
if (System::IsShutdown())
|
||||
return false;
|
||||
|
||||
std::string auto_filename;
|
||||
if (!filename)
|
||||
{
|
||||
const auto& serial = System::GetGameSerial();
|
||||
if (serial.empty())
|
||||
{
|
||||
auto_filename = Path::Combine(
|
||||
EmuFolders::Dumps, fmt::format("audio" FS_OSPATH_SEPARATOR_STR "{}.wav", GetTimestampStringForFileName()));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto_filename = Path::Combine(EmuFolders::Dumps, fmt::format("audio" FS_OSPATH_SEPARATOR_STR "{}_{}.wav", serial,
|
||||
GetTimestampStringForFileName()));
|
||||
}
|
||||
|
||||
filename = auto_filename.c_str();
|
||||
}
|
||||
|
||||
if (SPU::StartDumpingAudio(filename))
|
||||
{
|
||||
Host::AddIconOSDMessage(
|
||||
"audio_dumping", ICON_FA_VOLUME_UP,
|
||||
fmt::format(TRANSLATE_FS("OSDMessage", "Started dumping audio to '{}'."), Path::GetFileName(filename)),
|
||||
Host::OSD_INFO_DURATION);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::AddIconOSDMessage(
|
||||
"audio_dumping", ICON_FA_VOLUME_UP,
|
||||
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to start dumping audio to '{}'."), Path::GetFileName(filename)),
|
||||
Host::OSD_ERROR_DURATION);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void System::StopDumpingAudio()
|
||||
{
|
||||
if (System::IsShutdown() || !SPU::StopDumpingAudio())
|
||||
return;
|
||||
|
||||
Host::AddIconOSDMessage("audio_dumping", ICON_FA_VOLUME_MUTE, TRANSLATE_STR("OSDMessage", "Stopped dumping audio."),
|
||||
Host::OSD_INFO_DURATION);
|
||||
}
|
||||
|
||||
bool System::SaveScreenshot(const char* filename, DisplayScreenshotMode mode, DisplayScreenshotFormat format,
|
||||
u8 quality, bool compress_on_thread)
|
||||
{
|
||||
|
@ -4985,6 +4948,132 @@ bool System::SaveScreenshot(const char* filename, DisplayScreenshotMode mode, Di
|
|||
return g_gpu->RenderScreenshotToFile(filename, mode, quality, compress_on_thread, true);
|
||||
}
|
||||
|
||||
static std::string_view GetCaptureTypeForMessage(bool capture_video, bool capture_audio)
|
||||
{
|
||||
return capture_video ? (capture_audio ? TRANSLATE_SV("System", "capturing audio and video") :
|
||||
TRANSLATE_SV("System", "capturing video")) :
|
||||
TRANSLATE_SV("System", "capturing audio");
|
||||
}
|
||||
|
||||
MediaCapture* System::GetMediaCapture()
|
||||
{
|
||||
return s_media_capture.get();
|
||||
}
|
||||
|
||||
std::string System::GetNewMediaCapturePath(const std::string_view title, const std::string_view container)
|
||||
{
|
||||
const std::string sanitized_name = Path::SanitizeFileName(title);
|
||||
std::string path;
|
||||
if (sanitized_name.empty())
|
||||
{
|
||||
path = Path::Combine(EmuFolders::Videos, fmt::format("{}.{}", GetTimestampStringForFileName(), container));
|
||||
}
|
||||
else
|
||||
{
|
||||
path = Path::Combine(EmuFolders::Videos,
|
||||
fmt::format("{} {}.{}", sanitized_name, GetTimestampStringForFileName(), container));
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
bool System::StartMediaCapture(std::string path, bool capture_video, bool capture_audio)
|
||||
{
|
||||
if (!IsValid())
|
||||
return false;
|
||||
|
||||
if (s_media_capture)
|
||||
StopMediaCapture();
|
||||
|
||||
// Need to work out the size.
|
||||
u32 capture_width = g_settings.media_capture_video_width;
|
||||
u32 capture_height = g_settings.media_capture_video_height;
|
||||
const GPUTexture::Format capture_format =
|
||||
g_gpu_device->HasSurface() ? g_gpu_device->GetWindowFormat() : GPUTexture::Format::RGBA8;
|
||||
const float fps = g_gpu->ComputeVerticalFrequency();
|
||||
if (capture_video)
|
||||
{
|
||||
// TODO: This will be a mess with GPU thread.
|
||||
if (g_settings.media_capture_video_auto_size)
|
||||
{
|
||||
GSVector4i unused_display_rect, unused_draw_rect;
|
||||
g_gpu->CalculateScreenshotSize(DisplayScreenshotMode::InternalResolution, &capture_width, &capture_height,
|
||||
&unused_display_rect, &unused_draw_rect);
|
||||
}
|
||||
|
||||
MediaCapture::AdjustVideoSize(&capture_width, &capture_height);
|
||||
}
|
||||
|
||||
// TODO: Render anamorphic capture instead?
|
||||
constexpr float aspect = 1.0f;
|
||||
|
||||
if (path.empty())
|
||||
path = GetNewMediaCapturePath(GetGameTitle(), g_settings.media_capture_container);
|
||||
|
||||
Error error;
|
||||
s_media_capture = MediaCapture::Create(g_settings.media_capture_backend, &error);
|
||||
if (!s_media_capture ||
|
||||
!s_media_capture->BeginCapture(
|
||||
fps, aspect, capture_width, capture_height, capture_format, SPU::SAMPLE_RATE, std::move(path), capture_video,
|
||||
g_settings.media_capture_video_codec, g_settings.media_capture_video_bitrate,
|
||||
g_settings.media_capture_video_codec_use_args ? std::string_view(g_settings.media_capture_video_codec_args) :
|
||||
std::string_view(),
|
||||
capture_audio, g_settings.media_capture_audio_codec, g_settings.media_capture_audio_bitrate,
|
||||
g_settings.media_capture_audio_codec_use_args ? std::string_view(g_settings.media_capture_audio_codec_args) :
|
||||
std::string_view(),
|
||||
&error))
|
||||
{
|
||||
Host::AddIconOSDMessage(
|
||||
"MediaCapture", ICON_FA_EXCLAMATION_TRIANGLE,
|
||||
fmt::format(TRANSLATE_FS("System", "Failed to create media capture: {0}"), error.GetDescription()),
|
||||
Host::OSD_ERROR_DURATION);
|
||||
s_media_capture.reset();
|
||||
Host::OnMediaCaptureStopped();
|
||||
return false;
|
||||
}
|
||||
|
||||
Host::AddIconOSDMessage(
|
||||
"MediaCapture", ICON_FA_CAMERA,
|
||||
fmt::format(TRANSLATE_FS("System", "Starting {0} to '{1}'."),
|
||||
GetCaptureTypeForMessage(s_media_capture->IsCapturingVideo(), s_media_capture->IsCapturingAudio()),
|
||||
Path::GetFileName(s_media_capture->GetPath())),
|
||||
Host::OSD_INFO_DURATION);
|
||||
|
||||
Host::OnMediaCaptureStarted();
|
||||
return true;
|
||||
}
|
||||
|
||||
void System::StopMediaCapture()
|
||||
{
|
||||
if (!s_media_capture)
|
||||
return;
|
||||
|
||||
const bool was_capturing_audio = s_media_capture->IsCapturingAudio();
|
||||
const bool was_capturing_video = s_media_capture->IsCapturingVideo();
|
||||
|
||||
Error error;
|
||||
if (s_media_capture->EndCapture(&error))
|
||||
{
|
||||
Host::AddIconOSDMessage("MediaCapture", ICON_FA_CAMERA,
|
||||
fmt::format(TRANSLATE_FS("System", "Stopped {0} to '{1}'."),
|
||||
GetCaptureTypeForMessage(was_capturing_video, was_capturing_audio),
|
||||
Path::GetFileName(s_media_capture->GetPath())),
|
||||
Host::OSD_INFO_DURATION);
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::AddIconOSDMessage(
|
||||
"MediaCapture", ICON_FA_EXCLAMATION_TRIANGLE,
|
||||
fmt::format(TRANSLATE_FS("System", "Stopped {0}: {1}."),
|
||||
GetCaptureTypeForMessage(s_media_capture->IsCapturingVideo(), s_media_capture->IsCapturingAudio()),
|
||||
error.GetDescription()),
|
||||
Host::OSD_INFO_DURATION);
|
||||
}
|
||||
s_media_capture.reset();
|
||||
|
||||
Host::OnMediaCaptureStopped();
|
||||
}
|
||||
|
||||
std::string System::GetGameSaveStateFileName(std::string_view serial, s32 slot)
|
||||
{
|
||||
if (slot < 0)
|
||||
|
|
|
@ -27,6 +27,7 @@ struct CheatCode;
|
|||
class CheatList;
|
||||
|
||||
class GPUTexture;
|
||||
class MediaCapture;
|
||||
|
||||
namespace BIOS {
|
||||
struct ImageInfo;
|
||||
|
@ -54,7 +55,7 @@ struct SystemBootParameters
|
|||
bool load_image_to_ram = false;
|
||||
bool force_software_renderer = false;
|
||||
bool disable_achievements_hardcore_mode = false;
|
||||
bool start_audio_dump = false;
|
||||
bool start_media_capture = false;
|
||||
};
|
||||
|
||||
struct SaveStateInfo
|
||||
|
@ -382,20 +383,22 @@ std::string GetGameMemoryCardPath(std::string_view serial, std::string_view path
|
|||
s32 GetAudioOutputVolume();
|
||||
void UpdateVolume();
|
||||
|
||||
/// Returns true if currently dumping audio.
|
||||
bool IsDumpingAudio();
|
||||
|
||||
/// Starts dumping audio to a file. If no file name is provided, one will be generated automatically.
|
||||
bool StartDumpingAudio(const char* filename = nullptr);
|
||||
|
||||
/// Stops dumping audio to file if it has been started.
|
||||
void StopDumpingAudio();
|
||||
|
||||
/// Saves a screenshot to the specified file. If no file name is provided, one will be generated automatically.
|
||||
bool SaveScreenshot(const char* filename = nullptr, DisplayScreenshotMode mode = g_settings.display_screenshot_mode,
|
||||
DisplayScreenshotFormat format = g_settings.display_screenshot_format,
|
||||
u8 quality = g_settings.display_screenshot_quality, bool compress_on_thread = true);
|
||||
|
||||
/// Returns the path that a new media capture would be saved to by default. Safe to call from any thread.
|
||||
std::string GetNewMediaCapturePath(const std::string_view title, const std::string_view container);
|
||||
|
||||
/// Current media capture (if active).
|
||||
MediaCapture* GetMediaCapture();
|
||||
|
||||
/// Media capture (video and/or audio). If no path is provided, one will be generated automatically.
|
||||
bool StartMediaCapture(std::string path = {}, bool capture_video = g_settings.media_capture_video,
|
||||
bool capture_audio = g_settings.media_capture_audio);
|
||||
void StopMediaCapture();
|
||||
|
||||
/// Loads the cheat list for the current game title from the user directory.
|
||||
bool LoadCheatList();
|
||||
|
||||
|
@ -508,6 +511,10 @@ void OnPerformanceCountersUpdated();
|
|||
/// Provided by the host; called when the running executable changes.
|
||||
void OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name);
|
||||
|
||||
/// Called when media capture starts/stops.
|
||||
void OnMediaCaptureStarted();
|
||||
void OnMediaCaptureStopped();
|
||||
|
||||
/// Provided by the host; called once per frame at guest vsync.
|
||||
void PumpMessagesOnCPUThread();
|
||||
|
||||
|
|
|
@ -21,11 +21,14 @@ FolderSettingsWidget::FolderSettingsWidget(SettingsWindow* dialog, QWidget* pare
|
|||
m_ui.coversOpen, m_ui.coversReset, "Folders", "Covers",
|
||||
Path::Combine(EmuFolders::DataRoot, "covers"));
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(
|
||||
sif, m_ui.screenshots, m_ui.screenshotsBrowse, tr("Select Screenshot Directory"), m_ui.screenshotsOpen,
|
||||
m_ui.screenshotsReset, "Folders", "Screenshots", Path::Combine(EmuFolders::DataRoot, "screenshots"));
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(
|
||||
sif, m_ui.saveStates, m_ui.saveStatesBrowse, tr("Select Save State Directory"), m_ui.saveStatesOpen,
|
||||
sif, m_ui.saveStates, m_ui.saveStatesBrowse, tr("Select Save States Directory"), m_ui.saveStatesOpen,
|
||||
m_ui.saveStatesReset, "Folders", "SaveStates", Path::Combine(EmuFolders::DataRoot, "savestates"));
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(
|
||||
sif, m_ui.screenshots, m_ui.screenshotsBrowse, tr("Select Screenshots Directory"), m_ui.screenshotsOpen,
|
||||
m_ui.screenshotsReset, "Folders", "Screenshots", Path::Combine(EmuFolders::DataRoot, "screenshots"));
|
||||
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.videos, m_ui.videosBrowse, tr("Select Videos Directory"),
|
||||
m_ui.videosOpen, m_ui.videosReset, "Folders", "Videos",
|
||||
Path::Combine(EmuFolders::DataRoot, "videos"));
|
||||
}
|
||||
|
||||
FolderSettingsWidget::~FolderSettingsWidget() = default;
|
||||
|
|
|
@ -103,46 +103,6 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Screenshots Directory</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="screenshots"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="screenshotsBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="screenshotsOpen">
|
||||
<property name="text">
|
||||
<string>Open...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="screenshotsReset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Used for screenshots.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
|
@ -183,10 +143,90 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Screenshots Directory</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="screenshotsOpen">
|
||||
<property name="text">
|
||||
<string>Open...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Used for screenshots.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="screenshotsBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="screenshots"/>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="screenshotsReset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Videos Directory</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="videosOpen">
|
||||
<property name="text">
|
||||
<string>Open...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Used for media capture, regardless of whether audio and/or video is enabled.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="videosBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="videos"/>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="videosReset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
@ -198,5 +238,6 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "core/gpu.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#include "util/media_capture.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
static QVariant GetMSAAModeValue(uint multisamples, bool ssaa)
|
||||
|
@ -202,6 +204,33 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
|||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.screenshotQuality, "Display", "ScreenshotQuality",
|
||||
Settings::DEFAULT_DISPLAY_SCREENSHOT_QUALITY);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.mediaCaptureBackend, "MediaCapture", "Backend",
|
||||
&MediaCapture::ParseBackendName, &MediaCapture::GetBackendName,
|
||||
Settings::DEFAULT_MEDIA_CAPTURE_BACKEND);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableVideoCapture, "MediaCapture", "VideoCapture", true);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.videoCaptureWidth, "MediaCapture", "VideoWidth", 640);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.videoCaptureHeight, "MediaCapture", "VideoHeight", 480);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.videoCaptureResolutionAuto, "MediaCapture", "VideoAutoSize",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.videoCaptureBitrate, "MediaCapture", "VideoBitrate", 6000);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableVideoCaptureArguments, "MediaCapture",
|
||||
"VideoCodecUseArgs", false);
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.videoCaptureArguments, "MediaCapture", "AudioCodecArgs");
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableAudioCapture, "MediaCapture", "AudioCapture", true);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.audioCaptureBitrate, "MediaCapture", "AudioBitrate", 128);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableVideoCaptureArguments, "MediaCapture",
|
||||
"VideoCodecUseArgs", false);
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.audioCaptureArguments, "MediaCapture", "AudioCodecArgs");
|
||||
|
||||
connect(m_ui.mediaCaptureBackend, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&GraphicsSettingsWidget::onMediaCaptureBackendChanged);
|
||||
connect(m_ui.enableVideoCapture, &QCheckBox::checkStateChanged, this,
|
||||
&GraphicsSettingsWidget::onMediaCaptureVideoEnabledChanged);
|
||||
connect(m_ui.videoCaptureResolutionAuto, &QCheckBox::checkStateChanged, this,
|
||||
&GraphicsSettingsWidget::onMediaCaptureVideoAutoResolutionChanged);
|
||||
connect(m_ui.enableAudioCapture, &QCheckBox::checkStateChanged, this,
|
||||
&GraphicsSettingsWidget::onMediaCaptureAudioEnabledChanged);
|
||||
|
||||
// Texture Replacements Tab
|
||||
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.vramWriteReplacement, "TextureReplacements",
|
||||
|
@ -241,6 +270,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
|||
onAspectRatioChanged();
|
||||
onDownsampleModeChanged();
|
||||
updateResolutionDependentOptions();
|
||||
onMediaCaptureBackendChanged();
|
||||
onMediaCaptureAudioEnabledChanged();
|
||||
onMediaCaptureVideoEnabledChanged();
|
||||
onEnableAnyTextureReplacementsChanged();
|
||||
onEnableVRAMWriteDumpingChanged();
|
||||
onShowDebugSettingsChanged(QtHost::ShouldShowDebugOptions());
|
||||
|
@ -483,6 +515,40 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
|||
QStringLiteral("%1%").arg(Settings::DEFAULT_DISPLAY_SCREENSHOT_QUALITY),
|
||||
tr("Selects the quality at which screenshots will be compressed. Higher values preserve "
|
||||
"more detail for JPEG, and reduce file size for PNG."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.mediaCaptureBackend, tr("Backend"),
|
||||
QString::fromUtf8(MediaCapture::GetBackendDisplayName(Settings::DEFAULT_MEDIA_CAPTURE_BACKEND)),
|
||||
tr("Selects the framework that is used to encode video/audio."));
|
||||
dialog->registerWidgetHelp(m_ui.captureContainer, tr("Container"), tr("MP4"),
|
||||
tr("Determines the file format used to contain the captured audio/video"));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.videoCaptureCodec, tr("Video Codec"), tr("Default"),
|
||||
tr("Selects which Video Codec to be used for Video Capture. <b>If unsure, leave it on default.<b>"));
|
||||
dialog->registerWidgetHelp(m_ui.videoCaptureBitrate, tr("Video Bitrate"), tr("6000 kbps"),
|
||||
tr("Sets the video bitrate to be used. Larger bitrate generally yields better video "
|
||||
"quality at the cost of larger resulting file size."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.videoCaptureResolutionAuto, tr("Automatic Resolution"), tr("Unchecked"),
|
||||
tr("When checked, the video capture resolution will follows the internal resolution of the running "
|
||||
"game. <b>Be careful when using this setting especially when you are upscaling, as higher internal "
|
||||
"resolutions (above 4x) can cause system slowdown.</b>"));
|
||||
dialog->registerWidgetHelp(m_ui.enableVideoCaptureArguments, tr("Enable Extra Video Arguments"), tr("Unchecked"),
|
||||
tr("Allows you to pass arguments to the selected video codec."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.videoCaptureArguments, tr("Extra Video Arguments"), tr("Empty"),
|
||||
tr("Parameters passed to the selected video codec.<br><b>You must use '=' to separate key from value and ':' to "
|
||||
"separate two pairs from each other.</b><br>For example: \"crf = 21 : preset = veryfast\""));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.audioCaptureCodec, tr("Audio Codec"), tr("Default"),
|
||||
tr("Selects which Audio Codec to be used for Video Capture. <b>If unsure, leave it on default.<b>"));
|
||||
dialog->registerWidgetHelp(m_ui.audioCaptureBitrate, tr("Audio Bitrate"), tr("160 kbps"),
|
||||
tr("Sets the audio bitrate to be used."));
|
||||
dialog->registerWidgetHelp(m_ui.enableAudioCaptureArguments, tr("Enable Extra Audio Arguments"), tr("Unchecked"),
|
||||
tr("Allows you to pass arguments to the selected audio codec."));
|
||||
dialog->registerWidgetHelp(
|
||||
m_ui.audioCaptureArguments, tr("Extra Audio Arguments"), tr("Empty"),
|
||||
tr("Parameters passed to the selected audio codec.<br><b>You must use '=' to separate key from value and ':' to "
|
||||
"separate two pairs from each other.</b><br>For example: \"compression_level = 4 : joint_stereo = 1\""));
|
||||
|
||||
// Texture Replacements Tab
|
||||
|
||||
|
@ -625,6 +691,12 @@ void GraphicsSettingsWidget::setupAdditionalUi()
|
|||
QString::fromUtf8(Settings::GetDisplayScreenshotFormatDisplayName(static_cast<DisplayScreenshotFormat>(i))));
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(MediaCaptureBackend::MaxCount); i++)
|
||||
{
|
||||
m_ui.mediaCaptureBackend->addItem(
|
||||
QString::fromUtf8(MediaCapture::GetBackendDisplayName(static_cast<MediaCaptureBackend>(i))));
|
||||
}
|
||||
|
||||
// Debugging Tab
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(GPUWireframeMode::Count); i++)
|
||||
|
@ -931,6 +1003,110 @@ void GraphicsSettingsWidget::onDownsampleModeChanged()
|
|||
}
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onMediaCaptureBackendChanged()
|
||||
{
|
||||
SettingsInterface* const sif = m_dialog->getSettingsInterface();
|
||||
const MediaCaptureBackend backend =
|
||||
MediaCapture::ParseBackendName(
|
||||
m_dialog
|
||||
->getEffectiveStringValue("MediaCapture", "Backend",
|
||||
MediaCapture::GetBackendName(Settings::DEFAULT_MEDIA_CAPTURE_BACKEND))
|
||||
.c_str())
|
||||
.value_or(Settings::DEFAULT_MEDIA_CAPTURE_BACKEND);
|
||||
|
||||
{
|
||||
m_ui.captureContainer->disconnect();
|
||||
m_ui.captureContainer->clear();
|
||||
|
||||
for (const auto& [name, display_name] : MediaCapture::GetContainerList(backend))
|
||||
{
|
||||
const QString qname = QString::fromStdString(name);
|
||||
m_ui.captureContainer->addItem(tr("%1 (%2)").arg(QString::fromStdString(display_name)).arg(qname), qname);
|
||||
}
|
||||
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.captureContainer, "MediaCapture", "Container", "mp4");
|
||||
connect(m_ui.captureContainer, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&GraphicsSettingsWidget::onMediaCaptureContainerChanged);
|
||||
}
|
||||
|
||||
onMediaCaptureContainerChanged();
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onMediaCaptureContainerChanged()
|
||||
{
|
||||
SettingsInterface* const sif = m_dialog->getSettingsInterface();
|
||||
const MediaCaptureBackend backend =
|
||||
MediaCapture::ParseBackendName(
|
||||
m_dialog
|
||||
->getEffectiveStringValue("MediaCapture", "Backend",
|
||||
MediaCapture::GetBackendName(Settings::DEFAULT_MEDIA_CAPTURE_BACKEND))
|
||||
.c_str())
|
||||
.value_or(Settings::DEFAULT_MEDIA_CAPTURE_BACKEND);
|
||||
const std::string container = m_dialog->getEffectiveStringValue("MediaCapture", "Container", "mp4");
|
||||
|
||||
{
|
||||
m_ui.videoCaptureCodec->disconnect();
|
||||
m_ui.videoCaptureCodec->clear();
|
||||
m_ui.videoCaptureCodec->addItem(tr("Default"), QVariant(QString()));
|
||||
|
||||
for (const auto& [name, display_name] : MediaCapture::GetVideoCodecList(backend, container.c_str()))
|
||||
{
|
||||
const QString qname = QString::fromStdString(name);
|
||||
m_ui.videoCaptureCodec->addItem(tr("%1 (%2)").arg(QString::fromStdString(display_name)).arg(qname), qname);
|
||||
}
|
||||
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.videoCaptureCodec, "MediaCapture", "VideoCodec");
|
||||
}
|
||||
|
||||
{
|
||||
m_ui.audioCaptureCodec->disconnect();
|
||||
m_ui.audioCaptureCodec->clear();
|
||||
m_ui.audioCaptureCodec->addItem(tr("Default"), QVariant(QString()));
|
||||
|
||||
for (const auto& [name, display_name] : MediaCapture::GetAudioCodecList(backend, container.c_str()))
|
||||
{
|
||||
const QString qname = QString::fromStdString(name);
|
||||
m_ui.audioCaptureCodec->addItem(tr("%1 (%2)").arg(QString::fromStdString(display_name)).arg(qname), qname);
|
||||
}
|
||||
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.audioCaptureCodec, "MediaCapture", "AudioCodec");
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onMediaCaptureVideoEnabledChanged()
|
||||
{
|
||||
const bool enabled = m_dialog->getEffectiveBoolValue("MediaCapture", "VideoCapture", true);
|
||||
m_ui.videoCaptureCodecLabel->setEnabled(enabled);
|
||||
m_ui.videoCaptureCodec->setEnabled(enabled);
|
||||
m_ui.videoCaptureBitrateLabel->setEnabled(enabled);
|
||||
m_ui.videoCaptureBitrate->setEnabled(enabled);
|
||||
m_ui.videoCaptureResolutionLabel->setEnabled(enabled);
|
||||
m_ui.videoCaptureResolutionAuto->setEnabled(enabled);
|
||||
m_ui.enableVideoCaptureArguments->setEnabled(enabled);
|
||||
m_ui.videoCaptureArguments->setEnabled(enabled);
|
||||
onMediaCaptureVideoAutoResolutionChanged();
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onMediaCaptureVideoAutoResolutionChanged()
|
||||
{
|
||||
const bool enabled = m_dialog->getEffectiveBoolValue("MediaCapture", "VideoCapture", true);
|
||||
const bool auto_enabled = m_dialog->getEffectiveBoolValue("MediaCapture", "VideoAutoSize", false);
|
||||
m_ui.videoCaptureWidth->setEnabled(enabled && !auto_enabled);
|
||||
m_ui.xLabel->setEnabled(enabled && !auto_enabled);
|
||||
m_ui.videoCaptureHeight->setEnabled(enabled && !auto_enabled);
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onMediaCaptureAudioEnabledChanged()
|
||||
{
|
||||
const bool enabled = m_dialog->getEffectiveBoolValue("MediaCapture", "AudioCapture", true);
|
||||
m_ui.audioCaptureCodecLabel->setEnabled(enabled);
|
||||
m_ui.audioCaptureCodec->setEnabled(enabled);
|
||||
m_ui.audioCaptureBitrateLabel->setEnabled(enabled);
|
||||
m_ui.audioCaptureBitrate->setEnabled(enabled);
|
||||
m_ui.enableAudioCaptureArguments->setEnabled(enabled);
|
||||
m_ui.audioCaptureArguments->setEnabled(enabled);
|
||||
}
|
||||
|
||||
void GraphicsSettingsWidget::onEnableAnyTextureReplacementsChanged()
|
||||
{
|
||||
const bool any_replacements_enabled =
|
||||
|
|
|
@ -32,6 +32,13 @@ private Q_SLOTS:
|
|||
void updateResolutionDependentOptions();
|
||||
void onTrueColorChanged();
|
||||
void onDownsampleModeChanged();
|
||||
|
||||
void onMediaCaptureBackendChanged();
|
||||
void onMediaCaptureContainerChanged();
|
||||
void onMediaCaptureVideoEnabledChanged();
|
||||
void onMediaCaptureVideoAutoResolutionChanged();
|
||||
void onMediaCaptureAudioEnabledChanged();
|
||||
|
||||
void onEnableAnyTextureReplacementsChanged();
|
||||
void onEnableVRAMWriteDumpingChanged();
|
||||
|
||||
|
|
|
@ -817,6 +817,244 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="captureTabGroupBox">
|
||||
<property name="title">
|
||||
<string>Media Capture</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="captureTabGroupBoxLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Backend:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="mediaCaptureBackend"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="captureContainerLabel">
|
||||
<property name="text">
|
||||
<string>Container:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="captureContainer"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="captureOptionLayout" columnstretch="1,1">
|
||||
<property name="horizontalSpacing">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item row="1" column="1">
|
||||
<widget class="QWidget" name="audioCaptureOptions" native="true">
|
||||
<layout class="QFormLayout" name="audioCaptureOptionsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="audioCaptureCodecLabel">
|
||||
<property name="text">
|
||||
<string>Codec:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="audioCaptureCodec"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="audioCaptureBitrateLabel">
|
||||
<property name="text">
|
||||
<string>Bitrate:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="audioCaptureBitrate">
|
||||
<property name="suffix">
|
||||
<string> kbps</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2048</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>128</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="enableAudioCaptureArguments">
|
||||
<property name="text">
|
||||
<string>Extra Arguments</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QLineEdit" name="audioCaptureArguments"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="enableAudioCapture">
|
||||
<property name="text">
|
||||
<string>Capture Audio</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QWidget" name="videoCaptureOptions" native="true">
|
||||
<layout class="QFormLayout" name="videoCaptureOptionsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="videoCaptureCodecLabel">
|
||||
<property name="text">
|
||||
<string>Codec:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="videoCaptureCodec"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="videoCaptureBitrateLabel">
|
||||
<property name="text">
|
||||
<string>Bitrate:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="videoCaptureBitrate">
|
||||
<property name="suffix">
|
||||
<string extracomment="Unit that will appear next to a number. Alter the space or whatever is needed before the text depending on your language."> kbps</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>6000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="videoCaptureResolutionLabel">
|
||||
<property name="text">
|
||||
<string>Resolution:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="videoCaptureSizeLayout" stretch="1,0,1,0">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="videoCaptureWidth">
|
||||
<property name="minimum">
|
||||
<number>320</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>32768</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>640</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="xLabel">
|
||||
<property name="text">
|
||||
<string>x</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="videoCaptureHeight">
|
||||
<property name="minimum">
|
||||
<number>240</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>32768</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>480</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="videoCaptureResolutionAuto">
|
||||
<property name="text">
|
||||
<string>Auto</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QLineEdit" name="videoCaptureArguments"/>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="enableVideoCaptureArguments">
|
||||
<property name="text">
|
||||
<string>Extra Arguments</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="enableVideoCapture">
|
||||
<property name="text">
|
||||
<string>Capture Video</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_7">
|
||||
<property name="orientation">
|
||||
|
|
|
@ -623,6 +623,18 @@ void MainWindow::onRunningGameChanged(const QString& filename, const QString& ga
|
|||
updateWindowTitle();
|
||||
}
|
||||
|
||||
void MainWindow::onMediaCaptureStarted()
|
||||
{
|
||||
QSignalBlocker sb(m_ui.actionMediaCapture);
|
||||
m_ui.actionMediaCapture->setChecked(true);
|
||||
}
|
||||
|
||||
void MainWindow::onMediaCaptureStopped()
|
||||
{
|
||||
QSignalBlocker sb(m_ui.actionMediaCapture);
|
||||
m_ui.actionMediaCapture->setChecked(false);
|
||||
}
|
||||
|
||||
void MainWindow::onApplicationStateChanged(Qt::ApplicationState state)
|
||||
{
|
||||
if (!s_system_valid)
|
||||
|
@ -1122,7 +1134,7 @@ const GameList::Entry* MainWindow::resolveDiscSetEntry(const GameList::Entry* en
|
|||
std::shared_ptr<SystemBootParameters> MainWindow::getSystemBootParameters(std::string file)
|
||||
{
|
||||
std::shared_ptr<SystemBootParameters> ret = std::make_shared<SystemBootParameters>(std::move(file));
|
||||
ret->start_audio_dump = m_ui.actionDumpAudio->isChecked();
|
||||
ret->start_media_capture = m_ui.actionMediaCapture->isChecked();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -2103,6 +2115,7 @@ void MainWindow::connectSignals()
|
|||
connect(m_ui.actionMemoryCardEditor, &QAction::triggered, this, &MainWindow::onToolsMemoryCardEditorTriggered);
|
||||
connect(m_ui.actionMemoryScanner, &QAction::triggered, this, &MainWindow::onToolsMemoryScannerTriggered);
|
||||
connect(m_ui.actionCoverDownloader, &QAction::triggered, this, &MainWindow::onToolsCoverDownloaderTriggered);
|
||||
connect(m_ui.actionMediaCapture, &QAction::toggled, this, &MainWindow::onToolsMediaCaptureToggled);
|
||||
connect(m_ui.actionCPUDebugger, &QAction::triggered, this, &MainWindow::openCPUDebugger);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableGDBServer, "Debug", "EnableGDBServer", false);
|
||||
connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
|
||||
|
@ -2137,6 +2150,8 @@ void MainWindow::connectSignals()
|
|||
connect(g_emu_thread, &EmuThread::systemPaused, this, &MainWindow::onSystemPaused);
|
||||
connect(g_emu_thread, &EmuThread::systemResumed, this, &MainWindow::onSystemResumed);
|
||||
connect(g_emu_thread, &EmuThread::runningGameChanged, this, &MainWindow::onRunningGameChanged);
|
||||
connect(g_emu_thread, &EmuThread::mediaCaptureStarted, this, &MainWindow::onMediaCaptureStarted);
|
||||
connect(g_emu_thread, &EmuThread::mediaCaptureStopped, this, &MainWindow::onMediaCaptureStopped);
|
||||
connect(g_emu_thread, &EmuThread::mouseModeRequested, this, &MainWindow::onMouseModeRequested);
|
||||
connect(g_emu_thread, &EmuThread::fullscreenUIStateChange, this, &MainWindow::onFullscreenUIStateChange);
|
||||
connect(g_emu_thread, &EmuThread::achievementsLoginRequested, this, &MainWindow::onAchievementsLoginRequested);
|
||||
|
@ -2162,12 +2177,6 @@ void MainWindow::connectSignals()
|
|||
"DumpCPUToVRAMCopies", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionDebugDumpVRAMtoCPUCopies, "Debug",
|
||||
"DumpVRAMToCPUCopies", false);
|
||||
connect(m_ui.actionDumpAudio, &QAction::toggled, [](bool checked) {
|
||||
if (checked)
|
||||
g_emu_thread->startDumpingAudio();
|
||||
else
|
||||
g_emu_thread->stopDumpingAudio();
|
||||
});
|
||||
connect(m_ui.actionDumpRAM, &QAction::triggered, [this]() {
|
||||
const QString filename = QDir::toNativeSeparators(
|
||||
QFileDialog::getSaveFileName(this, tr("Destination File"), QString(), tr("Binary Files (*.bin)")));
|
||||
|
@ -3034,6 +3043,41 @@ void MainWindow::onToolsCoverDownloaderTriggered()
|
|||
dlg.exec();
|
||||
}
|
||||
|
||||
void MainWindow::onToolsMediaCaptureToggled(bool checked)
|
||||
{
|
||||
if (!QtHost::IsSystemValid())
|
||||
{
|
||||
// leave it for later, we'll fill in the boot params
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checked)
|
||||
{
|
||||
Host::RunOnCPUThread(&System::StopMediaCapture);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string container =
|
||||
Host::GetStringSettingValue("MediaCapture", "Container", Settings::DEFAULT_MEDIA_CAPTURE_CONTAINER);
|
||||
const QString qcontainer = QString::fromStdString(container);
|
||||
const QString filter(tr("%1 Files (*.%2)").arg(qcontainer.toUpper()).arg(qcontainer));
|
||||
|
||||
QString path =
|
||||
QString::fromStdString(System::GetNewMediaCapturePath(QtHost::GetCurrentGameTitle().toStdString(), container));
|
||||
path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Video Capture"), path, filter));
|
||||
if (path.isEmpty())
|
||||
{
|
||||
// uncheck it again
|
||||
const QSignalBlocker sb(m_ui.actionMediaCapture);
|
||||
m_ui.actionMediaCapture->setChecked(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Host::RunOnCPUThread([path = path.toStdString()]() {
|
||||
System::StartMediaCapture(path, g_settings.media_capture_video, g_settings.media_capture_audio);
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::onToolsMemoryScannerTriggered()
|
||||
{
|
||||
if (Achievements::IsHardcoreModeActive())
|
||||
|
|
|
@ -140,6 +140,8 @@ private Q_SLOTS:
|
|||
void onSystemPaused();
|
||||
void onSystemResumed();
|
||||
void onRunningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
|
||||
void onMediaCaptureStarted();
|
||||
void onMediaCaptureStopped();
|
||||
void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
|
||||
void onAchievementsChallengeModeChanged(bool enabled);
|
||||
void onApplicationStateChanged(Qt::ApplicationState state);
|
||||
|
@ -174,6 +176,7 @@ private Q_SLOTS:
|
|||
void onToolsMemoryCardEditorTriggered();
|
||||
void onToolsMemoryScannerTriggered();
|
||||
void onToolsCoverDownloaderTriggered();
|
||||
void onToolsMediaCaptureToggled(bool checked);
|
||||
void onToolsOpenDataDirectoryTriggered();
|
||||
void onSettingsTriggeredFromToolbar();
|
||||
|
||||
|
|
|
@ -188,7 +188,6 @@
|
|||
<addaction name="separator"/>
|
||||
<addaction name="actionDebugDumpCPUtoVRAMCopies"/>
|
||||
<addaction name="actionDebugDumpVRAMtoCPUCopies"/>
|
||||
<addaction name="actionDumpAudio"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionDebugShowVRAM"/>
|
||||
<addaction name="actionDebugShowGPUState"/>
|
||||
|
@ -234,6 +233,8 @@
|
|||
<addaction name="separator"/>
|
||||
<addaction name="actionMemoryScanner"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionMediaCapture"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionOpenDataDirectory"/>
|
||||
</widget>
|
||||
<addaction name="menuSystem"/>
|
||||
|
@ -663,14 +664,6 @@
|
|||
<string>Disable All Enhancements</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDumpAudio">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Dump Audio</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDumpRAM">
|
||||
<property name="text">
|
||||
<string>Dump RAM...</string>
|
||||
|
@ -945,6 +938,14 @@
|
|||
<string>Show Game Icons (List View)</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMediaCapture">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Media Ca&pture</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/duckstation-qt.qrc"/>
|
||||
|
|
|
@ -1481,7 +1481,7 @@ void EmuThread::startDumpingAudio()
|
|||
return;
|
||||
}
|
||||
|
||||
System::StartDumpingAudio();
|
||||
//System::StartDumpingAudio();
|
||||
}
|
||||
|
||||
void EmuThread::stopDumpingAudio()
|
||||
|
@ -1492,7 +1492,7 @@ void EmuThread::stopDumpingAudio()
|
|||
return;
|
||||
}
|
||||
|
||||
System::StopDumpingAudio();
|
||||
//System::StopDumpingAudio();
|
||||
}
|
||||
|
||||
void EmuThread::singleStepCPU()
|
||||
|
@ -2065,6 +2065,16 @@ void Host::OnGameChanged(const std::string& disc_path, const std::string& game_s
|
|||
QString::fromStdString(game_name));
|
||||
}
|
||||
|
||||
void Host::OnMediaCaptureStarted()
|
||||
{
|
||||
emit g_emu_thread->mediaCaptureStarted();
|
||||
}
|
||||
|
||||
void Host::OnMediaCaptureStopped()
|
||||
{
|
||||
emit g_emu_thread->mediaCaptureStopped();
|
||||
}
|
||||
|
||||
void Host::SetMouseMode(bool relative, bool hide_cursor)
|
||||
{
|
||||
emit g_emu_thread->mouseModeRequested(relative, hide_cursor);
|
||||
|
|
|
@ -148,6 +148,8 @@ Q_SIGNALS:
|
|||
void achievementsRefreshed(quint32 id, const QString& game_info_string);
|
||||
void achievementsChallengeModeChanged(bool enabled);
|
||||
void cheatEnabled(quint32 index, bool enabled);
|
||||
void mediaCaptureStarted();
|
||||
void mediaCaptureStopped();
|
||||
|
||||
/// Big Picture UI requests.
|
||||
void onCoverDownloaderOpenRequested();
|
||||
|
|
|
@ -283,6 +283,16 @@ void Host::OnGameChanged(const std::string& disc_path, const std::string& game_s
|
|||
INFO_LOG("Game Name: {}", game_name);
|
||||
}
|
||||
|
||||
void Host::OnMediaCaptureStarted()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
void Host::OnMediaCaptureStopped()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
void Host::PumpMessagesOnCPUThread()
|
||||
{
|
||||
s_frames_to_run--;
|
||||
|
|
|
@ -44,6 +44,8 @@ add_library(util
|
|||
input_source.h
|
||||
iso_reader.cpp
|
||||
iso_reader.h
|
||||
media_capture.cpp
|
||||
media_capture.h
|
||||
page_fault_handler.cpp
|
||||
page_fault_handler.h
|
||||
platform_misc.h
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,75 @@
|
|||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gpu_texture.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
class Error;
|
||||
class GPUTexture;
|
||||
|
||||
enum class MediaCaptureBackend : u8
|
||||
{
|
||||
#ifdef _WIN32
|
||||
MediaFoundation,
|
||||
#endif
|
||||
#ifndef __ANDROID__
|
||||
FFMPEG,
|
||||
#endif
|
||||
MaxCount,
|
||||
};
|
||||
|
||||
class MediaCapture
|
||||
{
|
||||
public:
|
||||
virtual ~MediaCapture();
|
||||
|
||||
using ContainerName = std::pair<std::string, std::string>; // configname,longname
|
||||
using ContainerList = std::vector<ContainerName>;
|
||||
using CodecName = std::pair<std::string, std::string>; // configname,longname
|
||||
using CodecList = std::vector<CodecName>;
|
||||
|
||||
static std::optional<MediaCaptureBackend> ParseBackendName(const char* str);
|
||||
static const char* GetBackendName(MediaCaptureBackend backend);
|
||||
static const char* GetBackendDisplayName(MediaCaptureBackend backend);
|
||||
|
||||
static ContainerList GetContainerList(MediaCaptureBackend backend);
|
||||
static CodecList GetVideoCodecList(MediaCaptureBackend backend, const char* container);
|
||||
static CodecList GetAudioCodecList(MediaCaptureBackend backend, const char* container);
|
||||
|
||||
static void AdjustVideoSize(u32* width, u32* height);
|
||||
|
||||
static std::unique_ptr<MediaCapture> Create(MediaCaptureBackend backend, Error* error);
|
||||
|
||||
virtual bool BeginCapture(float fps, float aspect, u32 width, u32 height, GPUTexture::Format texture_format,
|
||||
u32 sample_rate, std::string path, bool capture_video, std::string_view video_codec,
|
||||
u32 video_bitrate, std::string_view video_codec_args, bool capture_audio,
|
||||
std::string_view audio_codec, u32 audio_bitrate, std::string_view audio_codec_args,
|
||||
Error* error) = 0;
|
||||
virtual bool EndCapture(Error* error) = 0;
|
||||
|
||||
// TODO: make non-virtual?
|
||||
virtual const std::string& GetPath() const = 0;
|
||||
virtual bool IsCapturingAudio() const = 0;
|
||||
virtual bool IsCapturingVideo() const = 0;
|
||||
virtual u32 GetVideoWidth() const = 0;
|
||||
virtual u32 GetVideoHeight() const = 0;
|
||||
|
||||
/// Returns the elapsed time in seconds.
|
||||
virtual time_t GetElapsedTime() const = 0;
|
||||
|
||||
virtual float GetCaptureThreadUsage() const = 0;
|
||||
virtual float GetCaptureThreadTime() const = 0;
|
||||
virtual void UpdateCaptureThreadUsage(double pct_divider, double time_divider) = 0;
|
||||
|
||||
virtual GPUTexture* GetRenderTexture() = 0;
|
||||
virtual bool DeliverVideoFrame(GPUTexture* stex) = 0;
|
||||
virtual bool DeliverAudioFrames(const s16* frames, u32 num_frames) = 0;
|
||||
virtual void Flush() = 0;
|
||||
};
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib</AdditionalDependencies>
|
||||
<AdditionalDependencies>%(AdditionalDependencies);d3d11.lib;d3d12.lib;d3dcompiler.lib;dxgi.lib;Dwmapi.lib;winhttp.lib;Mfplat.lib;Mfreadwrite.lib</AdditionalDependencies>
|
||||
<AdditionalDependencies Condition="'$(Platform)'!='ARM64'">%(AdditionalDependencies);opengl32.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<ClInclude Include="input_manager.h" />
|
||||
<ClInclude Include="input_source.h" />
|
||||
<ClInclude Include="iso_reader.h" />
|
||||
<ClInclude Include="media_capture.h" />
|
||||
<ClInclude Include="metal_device.h">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
|
@ -146,6 +147,7 @@
|
|||
<ClCompile Include="input_source.cpp" />
|
||||
<ClCompile Include="iso_reader.cpp" />
|
||||
<ClCompile Include="cd_subchannel_replacement.cpp" />
|
||||
<ClCompile Include="media_capture.cpp" />
|
||||
<ClCompile Include="opengl_context.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
<ClInclude Include="opengl_context_wgl.h" />
|
||||
<ClInclude Include="image.h" />
|
||||
<ClInclude Include="sockets.h" />
|
||||
<ClInclude Include="media_capture.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="state_wrapper.cpp" />
|
||||
|
@ -150,6 +151,7 @@
|
|||
<ClCompile Include="image.cpp" />
|
||||
<ClCompile Include="sdl_audio_stream.cpp" />
|
||||
<ClCompile Include="sockets.cpp" />
|
||||
<ClCompile Include="media_capture.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="metal_shaders.metal" />
|
||||
|
|
Loading…
Reference in New Issue