GS: Improve capture robustness

Automatically restart capture on renderer or hardware reset.
This commit is contained in:
Stenzek 2023-07-06 23:06:02 +10:00 committed by Connor McLaughlin
parent 6bf07086a0
commit 0e78f3f3bc
5 changed files with 108 additions and 9 deletions

View File

@ -227,6 +227,16 @@ bool GSreopen(bool recreate_device, bool recreate_renderer, const Pcsx2Config::G
if (GSConfig.UserHacks_ReadTCOnClose) if (GSConfig.UserHacks_ReadTCOnClose)
g_gs_renderer->ReadbackTextureCache(); g_gs_renderer->ReadbackTextureCache();
std::string capture_filename;
GSVector2i capture_size;
if (GSCapture::IsCapturing() && (recreate_renderer || recreate_device))
{
capture_filename = GSCapture::GetNextCaptureFileName();
capture_size = GSCapture::GetSize();
Console.Warning(fmt::format("Restarting video capture to {}.", capture_filename));
g_gs_renderer->EndCapture();
}
u8* basemem = g_gs_renderer->GetRegsMem(); u8* basemem = g_gs_renderer->GetRegsMem();
const u32 gamecrc = g_gs_renderer->GetGameCRC(); const u32 gamecrc = g_gs_renderer->GetGameCRC();
@ -302,6 +312,9 @@ bool GSreopen(bool recreate_device, bool recreate_renderer, const Pcsx2Config::G
g_gs_renderer->SetGameCRC(gamecrc); g_gs_renderer->SetGameCRC(gamecrc);
} }
if (!capture_filename.empty())
g_gs_renderer->BeginCapture(std::move(capture_filename), capture_size);
return true; return true;
} }
@ -411,6 +424,12 @@ int GSfreeze(FreezeAction mode, freezeData* data)
// local memory just before it's overwritten), we have to manually wipe // local memory just before it's overwritten), we have to manually wipe
// out the current textures. // out the current textures.
g_gs_device->ClearCurrent(); g_gs_device->ClearCurrent();
// Dump audio frames in video capture if it's been started, otherwise we get
// a buildup of audio frames from the CPU thread.
if (GSCapture::IsCapturing())
GSCapture::Flush();
return g_gs_renderer->Defrost(data); return g_gs_renderer->Defrost(data);
} }
} }

View File

@ -1329,6 +1329,61 @@ GSVector2i GSCapture::GetSize()
return s_size; return s_size;
} }
std::string GSCapture::GetNextCaptureFileName()
{
std::string ret;
if (!IsCapturing())
return ret;
const std::string_view ext = Path::GetExtension(s_filename);
std::string_view name = Path::GetFileTitle(s_filename);
// Should end with a number.
int partnum = 2;
std::string_view::size_type pos = name.rfind("_part");
if (pos >= 0)
{
std::string_view::size_type cpos = pos + 5;
for (; cpos < name.length(); cpos++)
{
if (name[cpos] < '0' || name[cpos] > '9')
break;
}
if (cpos == name.length())
{
// Has existing part number, so add to it.
partnum = StringUtil::FromChars<int>(name.substr(pos + 5)).value_or(1) + 1;
name = name.substr(0, pos);
}
}
// If we haven't started a new file previously, add "_part2".
ret = Path::BuildRelativePath(s_filename, fmt::format("{}_part{:03d}.{}", name, partnum, ext));
return ret;
}
void GSCapture::Flush()
{
std::unique_lock<std::mutex> lock(s_lock);
if (s_encoding_error)
return;
ProcessAllInFlightFrames(lock);
if (IsCapturingAudio())
{
// Clear any buffered audio frames out, we don't want to delay the CPU thread.
const u32 audio_frames = s_audio_buffer_size.load(std::memory_order_acquire);
if (audio_frames > 0)
Console.Warning("Dropping %u audio frames on for buffer clear.", audio_frames);
s_audio_buffer_read_pos = 0;
s_audio_buffer_write_pos = 0;
s_audio_buffer_size.store(0, std::memory_order_release);
}
}
GSCapture::CodecList GSCapture::GetCodecListForContainer(const char* container, AVMediaType type) GSCapture::CodecList GSCapture::GetCodecListForContainer(const char* container, AVMediaType type)
{ {
CodecList ret; CodecList ret;

View File

@ -40,6 +40,8 @@ namespace GSCapture
bool IsCapturingAudio(); bool IsCapturingAudio();
const Threading::ThreadHandle& GetEncoderThreadHandle(); const Threading::ThreadHandle& GetEncoderThreadHandle();
GSVector2i GetSize(); GSVector2i GetSize();
std::string GetNextCaptureFileName();
void Flush();
using CodecName = std::pair<std::string, std::string>; // shortname,longname using CodecName = std::pair<std::string, std::string>; // shortname,longname
using CodecList = std::vector<CodecName>; using CodecList = std::vector<CodecName>;

View File

@ -71,11 +71,22 @@ GSRenderer::~GSRenderer() = default;
void GSRenderer::Reset(bool hardware_reset) void GSRenderer::Reset(bool hardware_reset)
{ {
// clear the current display texture // Clear the current display texture.
if (hardware_reset) if (hardware_reset)
g_gs_device->ClearCurrent(); g_gs_device->ClearCurrent();
GSState::Reset(hardware_reset); GSState::Reset(hardware_reset);
// Restart video capture if it's been started.
// Otherwise we get a buildup of audio frames from the CPU thread.
if (hardware_reset && GSCapture::IsCapturing())
{
std::string next_filename = GSCapture::GetNextCaptureFileName();
const GSVector2i size = GSCapture::GetSize();
Console.Warning(fmt::format("Restarting video capture to {}.", next_filename));
EndCapture();
BeginCapture(std::move(next_filename), size);
}
} }
void GSRenderer::Destroy() void GSRenderer::Destroy()
@ -545,7 +556,7 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
const bool fb_sprite_frame = (fb_sprite_blits > 0); const bool fb_sprite_frame = (fb_sprite_blits > 0);
bool skip_frame = false; bool skip_frame = false;
if (GSConfig.SkipDuplicateFrames) if (GSConfig.SkipDuplicateFrames && !GSCapture::IsCapturingVideo())
{ {
bool is_unique_frame; bool is_unique_frame;
switch (PerformanceMetrics::GetInternalFPSMethod()) switch (PerformanceMetrics::GetInternalFPSMethod())
@ -740,10 +751,9 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
// capture // capture
if (GSCapture::IsCapturingVideo()) if (GSCapture::IsCapturingVideo())
{ {
const GSVector2i size = GSCapture::GetSize();
if (GSTexture* current = g_gs_device->GetCurrent()) if (GSTexture* current = g_gs_device->GetCurrent())
{ {
const GSVector2i size(GSCapture::GetSize());
// TODO: Maybe avoid this copy in the future? We can use swscale to fix it up on the dumping thread.. // TODO: Maybe avoid this copy in the future? We can use swscale to fix it up on the dumping thread..
if (current->GetSize() != size) if (current->GetSize() != size)
{ {
@ -760,6 +770,17 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
GSCapture::DeliverVideoFrame(current); GSCapture::DeliverVideoFrame(current);
} }
} }
else
{
// Bit janky, but unless we want to make variable frame rate files, we need to deliver *a* frame to
// the video file, so just grab a blank RT.
GSTexture* temp = g_gs_device->CreateRenderTarget(size.x, size.y, GSTexture::Format::Color, true);
if (temp)
{
GSCapture::DeliverVideoFrame(temp);
g_gs_device->Recycle(temp);
}
}
} }
} }
@ -893,9 +914,11 @@ void GSSetDisplayAlignment(GSDisplayAlignment alignment)
s_display_alignment = alignment; s_display_alignment = alignment;
} }
bool GSRenderer::BeginCapture(std::string filename) bool GSRenderer::BeginCapture(std::string filename, const GSVector2i& size)
{ {
const GSVector2i capture_resolution(GSConfig.VideoCaptureAutoResolution ? const GSVector2i capture_resolution = (size.x != 0 && size.y != 0) ?
size :
(GSConfig.VideoCaptureAutoResolution ?
GetInternalResolution() : GetInternalResolution() :
GSVector2i(GSConfig.VideoCaptureWidth, GSConfig.VideoCaptureHeight)); GSVector2i(GSConfig.VideoCaptureWidth, GSConfig.VideoCaptureHeight));

View File

@ -70,7 +70,7 @@ public:
void StopGSDump(); void StopGSDump();
void PresentCurrentFrame(); void PresentCurrentFrame();
bool BeginCapture(std::string filename); bool BeginCapture(std::string filename, const GSVector2i& size = GSVector2i(0, 0));
void EndCapture(); void EndCapture();
}; };