mirror of https://github.com/PCSX2/pcsx2.git
GS: Improve capture robustness
Automatically restart capture on renderer or hardware reset.
This commit is contained in:
parent
6bf07086a0
commit
0e78f3f3bc
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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,11 +914,13 @@ 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 ?
|
||||
GetInternalResolution() :
|
||||
GSVector2i(GSConfig.VideoCaptureWidth, GSConfig.VideoCaptureHeight));
|
||||
const GSVector2i capture_resolution = (size.x != 0 && size.y != 0) ?
|
||||
size :
|
||||
(GSConfig.VideoCaptureAutoResolution ?
|
||||
GetInternalResolution() :
|
||||
GSVector2i(GSConfig.VideoCaptureWidth, GSConfig.VideoCaptureHeight));
|
||||
|
||||
return GSCapture::BeginCapture(GetTvRefreshRate(), capture_resolution,
|
||||
GetCurrentAspectRatioFloat(GetVideoMode() == GSVideoMode::SDTV_480P),
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue