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)
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();
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);
}
if (!capture_filename.empty())
g_gs_renderer->BeginCapture(std::move(capture_filename), capture_size);
return true;
}
@ -411,6 +424,12 @@ int GSfreeze(FreezeAction mode, freezeData* data)
// local memory just before it's overwritten), we have to manually wipe
// out the current textures.
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);
}
}

View File

@ -1329,6 +1329,61 @@ GSVector2i GSCapture::GetSize()
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)
{
CodecList ret;

View File

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

View File

@ -71,11 +71,22 @@ GSRenderer::~GSRenderer() = default;
void GSRenderer::Reset(bool hardware_reset)
{
// clear the current display texture
// Clear the current display texture.
if (hardware_reset)
g_gs_device->ClearCurrent();
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()
@ -545,7 +556,7 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
const bool fb_sprite_frame = (fb_sprite_blits > 0);
bool skip_frame = false;
if (GSConfig.SkipDuplicateFrames)
if (GSConfig.SkipDuplicateFrames && !GSCapture::IsCapturingVideo())
{
bool is_unique_frame;
switch (PerformanceMetrics::GetInternalFPSMethod())
@ -740,10 +751,9 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
// capture
if (GSCapture::IsCapturingVideo())
{
const GSVector2i size = GSCapture::GetSize();
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..
if (current->GetSize() != size)
{
@ -760,6 +770,17 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
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;
}
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() :
GSVector2i(GSConfig.VideoCaptureWidth, GSConfig.VideoCaptureHeight));

View File

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