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)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue