You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Please note that file comments had no purpose in nearly all cases and just added visual clutter. The two files that had meaningful file comments had their comments moved into the corresponding classes. There are still hundreds of file comments left in other files that will be removed over time. Also cleaned up some random stuff along the way: - relative paths to public headers within the same module are no longer necessary (automatically discovered by UBT now) - header guards are deprecated, use #pragma once instead (all compilers support it now) - space between multiple template brackets is no longer required (all compilers support >> now) - NULL to nullptr, OVERRIDE to override - spelling errors, whitespace, line breaks [CL 2104067 by Max Preussner in Main branch]
364 lines
9.1 KiB
C++
364 lines
9.1 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "CrashTrackerPrivatePCH.h"
|
|
|
|
|
|
DEFINE_LOG_CATEGORY(LogCrashTracker);
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
Stat declarations.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("Total Time"),STAT_TotalTime,STATGROUP_CrashTracker);
|
|
DECLARE_CYCLE_STAT(TEXT("Send Frame To Compressor"),STAT_SendFrameToCompressor,STATGROUP_CrashTracker);
|
|
DECLARE_CYCLE_STAT(TEXT("Wait For Previous Frame To Compress"),STAT_WaitForPreviousFrameToCompress,STATGROUP_CrashTracker);
|
|
|
|
namespace CrashTrackerConstants
|
|
{
|
|
static const int32 MaxVideoFrameCompressedSize = 400 * 1024; // 400 KB
|
|
|
|
static const int32 JPEGQuality = 85; // high quality
|
|
|
|
static const int32 VideoFramesToCapture = 200;
|
|
static const float CaptureFrequency = 0.1f; // 10 fps
|
|
|
|
static const float SecondsToKeyPressFade = 2.f;
|
|
static const int32 MaxKeyPressesInBuffer = 15;
|
|
}
|
|
|
|
|
|
FCompressedDataFrame::FCompressedDataFrame(int InBufferSize)
|
|
: Data(NULL)
|
|
, BufferSize(InBufferSize)
|
|
, ActualSize(0)
|
|
{
|
|
Data = new uint8[BufferSize];
|
|
}
|
|
|
|
|
|
FCompressedDataFrame::~FCompressedDataFrame()
|
|
{
|
|
delete [] Data;
|
|
}
|
|
|
|
|
|
FAsyncImageCompress::FAsyncImageCompress(IImageWrapperPtr InImageWrapper, class FCompressedDataFrame* InDataFrame)
|
|
: ImageWrapper(InImageWrapper)
|
|
, DataFrame(InDataFrame)
|
|
{}
|
|
|
|
|
|
void FAsyncImageCompress::DoWork()
|
|
{
|
|
TArray<uint8> CompressedData = ImageWrapper->GetCompressed(CrashTrackerConstants::JPEGQuality);
|
|
int32 CompressedSize = CompressedData.Num();
|
|
|
|
if (CompressedSize <= CrashTrackerConstants::MaxVideoFrameCompressedSize)
|
|
{
|
|
DataFrame->ActualSize = CompressedSize;
|
|
|
|
FMemory::Memcpy(DataFrame->Data, CompressedData.GetTypedData(), CompressedSize);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCrashTracker, Warning, TEXT("Crash Tracker Size of %i bytes was too large. Frame Dropped."), CompressedSize);
|
|
DataFrame->ActualSize = 0;
|
|
}
|
|
}
|
|
|
|
|
|
const TCHAR* FAsyncImageCompress::Name()
|
|
{
|
|
return TEXT("FAsyncImageCompress");
|
|
}
|
|
|
|
|
|
FString FCrashVideoCapture::CaptureVideoPath;
|
|
|
|
|
|
FCrashVideoCapture::FCrashVideoCapture()
|
|
: CurrentBufferIndex(0)
|
|
, Width(0)
|
|
, Height(0)
|
|
, CaptureSlateRenderer(NULL)
|
|
, CurrentAccumSeconds(0.f)
|
|
, CurrentFrameCaptureIndex(0)
|
|
, bIsRunning(false)
|
|
, KeypressBuffer()
|
|
, CompressedFrames()
|
|
, AsyncTask(NULL)
|
|
, CVarCrashTrackerIsRunning(
|
|
TEXT("CrashTracker.IsRunning"),
|
|
bIsRunning,
|
|
TEXT("Whether to toggle the running of crash video capturing in the background."))
|
|
{
|
|
Buffer[0] = Buffer[1] = NULL;
|
|
}
|
|
|
|
|
|
FCrashVideoCapture::~FCrashVideoCapture()
|
|
{
|
|
if (bIsRunning)
|
|
{
|
|
EndCapture(/*bSaveCapture=*/false);
|
|
}
|
|
}
|
|
|
|
|
|
void FCrashVideoCapture::BeginCapture(FSlateRenderer* SlateRenderer)
|
|
{
|
|
IFileManager::Get().Delete(*CaptureVideoPath);
|
|
|
|
CaptureSlateRenderer = SlateRenderer;
|
|
CurrentAccumSeconds = 0.f;
|
|
CurrentFrameCaptureIndex = 0;
|
|
|
|
KeypressBuffer.Empty();
|
|
|
|
Buffer[0] = Buffer[1] = NULL;
|
|
CurrentBufferIndex = 0;
|
|
|
|
CompressedFrames.Empty(CrashTrackerConstants::VideoFramesToCapture);
|
|
for (int32 i = 0; i < CrashTrackerConstants::VideoFramesToCapture; ++i)
|
|
{
|
|
CompressedFrames.Add(new FCompressedDataFrame(CrashTrackerConstants::MaxVideoFrameCompressedSize));
|
|
}
|
|
|
|
bIsRunning = true;
|
|
}
|
|
|
|
|
|
void FCrashVideoCapture::EndCapture(bool bSaveCapture)
|
|
{
|
|
UE_LOG(LogCrashTracker, Log, TEXT("Ending crash video capture."));
|
|
|
|
if (bSaveCapture && bIsRunning)
|
|
{
|
|
// ensure we capture the last frame
|
|
if (IsInGameThread() && IsRenderingThreadHealthy())
|
|
{
|
|
Update(CrashTrackerConstants::CaptureFrequency);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCrashTracker, Log, TEXT("Crashed outside of game thread; not drawing final frame"));
|
|
}
|
|
CleanupFinalFrame();
|
|
SaveFinalCapture( CaptureVideoPath );
|
|
}
|
|
else
|
|
{
|
|
CleanupFinalFrame();
|
|
}
|
|
|
|
for (int32 i = 0; i < 2; ++i)
|
|
{
|
|
if (Buffer[i])
|
|
{
|
|
Buffer[i] = NULL;
|
|
}
|
|
}
|
|
KeypressBuffer.Empty();
|
|
for (int32 i = 0; i < CompressedFrames.Num(); ++i)
|
|
{
|
|
delete CompressedFrames[i];
|
|
}
|
|
CompressedFrames.Empty();
|
|
|
|
bIsRunning = false;
|
|
}
|
|
|
|
|
|
void FCrashVideoCapture::CleanupFinalFrame()
|
|
{
|
|
SyncWorkerThread();
|
|
|
|
if (IsInGameThread() && IsRenderingThreadHealthy())
|
|
{
|
|
UE_LOG(LogCrashTracker, Log, TEXT("Attempting final rendering command flush..."));
|
|
|
|
FlushRenderingCommands();
|
|
|
|
UE_LOG(LogCrashTracker, Log, TEXT("Finished final rendering command flush"));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCrashTracker, Log, TEXT("Crashed outside of game thread; not flushing render commands"));
|
|
}
|
|
}
|
|
|
|
|
|
void FCrashVideoCapture::SetCrashTrackingEnabled(bool bEnabled)
|
|
{
|
|
bIsRunning = bEnabled;
|
|
}
|
|
|
|
|
|
void FCrashVideoCapture::SaveFinalCapture( const FString& CapturePath )
|
|
{
|
|
int32 FinalFrameIndex = CurrentFrameCaptureIndex == 0 ? CrashTrackerConstants::VideoFramesToCapture - 1 : CurrentFrameCaptureIndex - 1;
|
|
|
|
FCompressedDataFrame* FinalFrame = NULL;
|
|
TArray<FCompressedDataFrame*> OrderedDataFrames;
|
|
for (int32 i = CurrentFrameCaptureIndex; i != FinalFrameIndex; i = (i + 1) % CrashTrackerConstants::VideoFramesToCapture)
|
|
{
|
|
FCompressedDataFrame* DataFrame = CompressedFrames[i];
|
|
|
|
if (DataFrame->ActualSize > 0)
|
|
{
|
|
OrderedDataFrames.Add(DataFrame);
|
|
FinalFrame = DataFrame;
|
|
}
|
|
}
|
|
|
|
// duplicate the final frame at the end to fill 1 second so the end viewer
|
|
// can see what happened exactly at the very end
|
|
// this may not be useful with a crash on a non-game thread, because the final frame can't be drawn
|
|
if (FinalFrame)
|
|
{
|
|
int32 ExtraFramesAdded = FMath::TruncToInt(1.f / CrashTrackerConstants::CaptureFrequency);
|
|
for (int32 i = 0; i < ExtraFramesAdded; ++i)
|
|
{
|
|
OrderedDataFrames.Add(FinalFrame);
|
|
}
|
|
}
|
|
|
|
DumpOutMJPEGAVI(OrderedDataFrames, CapturePath, Width, Height, FMath::TruncToInt(1.f / CrashTrackerConstants::CaptureFrequency));
|
|
|
|
UE_LOG(LogCrashTracker, Log, TEXT("Finished dumping out crash capture video. %i Frames at %ix%i"), OrderedDataFrames.Num(), Width, Height);
|
|
}
|
|
|
|
|
|
void FCrashVideoCapture::SyncWorkerThread()
|
|
{
|
|
FScopeLock SyncWorkerThreadLock(&SyncWorkerThreadSection);
|
|
|
|
if (AsyncTask)
|
|
{
|
|
AsyncTask->EnsureCompletion(/*bDoWorkOnThisThreadIfNotStarted=*/true);
|
|
delete AsyncTask;
|
|
AsyncTask = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void FCrashVideoCapture::Update(float DeltaSeconds)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_TotalTime);
|
|
|
|
if (bIsRunning)
|
|
{
|
|
// Update the key press buffer times
|
|
for (int32 i = 0; i < KeypressBuffer.Num(); ++i)
|
|
{
|
|
KeypressBuffer[i].Value -= DeltaSeconds;
|
|
}
|
|
|
|
// check to see if we need to render a new frame
|
|
bool bShouldUpdate = false;
|
|
CurrentAccumSeconds += DeltaSeconds;
|
|
if (CurrentAccumSeconds > CrashTrackerConstants::CaptureFrequency)
|
|
{
|
|
CurrentAccumSeconds -= FMath::TruncToFloat(CurrentAccumSeconds / CrashTrackerConstants::CaptureFrequency) * CrashTrackerConstants::CaptureFrequency;
|
|
bShouldUpdate = true;
|
|
}
|
|
|
|
if (bShouldUpdate)
|
|
{
|
|
CleanupKeyPressBuffer(DeltaSeconds);
|
|
|
|
TArray<FString> StrippedKeyPressBuffer;
|
|
for (int32 i = 0; i < KeypressBuffer.Num(); ++i)
|
|
{
|
|
StrippedKeyPressBuffer.Add(KeypressBuffer[i].Key);
|
|
}
|
|
|
|
if (Buffer[CurrentBufferIndex] && Width > 0 && Height > 0)
|
|
{
|
|
IImageWrapperPtr ImageWrapper;
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_SendFrameToCompressor);
|
|
|
|
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>( FName("ImageWrapper") );
|
|
ImageWrapper = ImageWrapperModule.CreateImageWrapper( EImageFormat::JPEG );
|
|
ImageWrapper->SetRaw(Buffer[CurrentBufferIndex], Width * Height * sizeof(FColor), Width, Height, ERGBFormat::RGBA, 8);
|
|
}
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_WaitForPreviousFrameToCompress);
|
|
|
|
SyncWorkerThread();
|
|
|
|
AsyncTask = new FAsyncTask<class FAsyncImageCompress>(ImageWrapper, CompressedFrames[CurrentFrameCaptureIndex]);
|
|
AsyncTask->StartBackgroundTask();
|
|
}
|
|
|
|
CaptureSlateRenderer->UnmapCrashTrackerBuffer();
|
|
}
|
|
|
|
++CurrentFrameCaptureIndex;
|
|
if (CurrentFrameCaptureIndex >= CrashTrackerConstants::VideoFramesToCapture)
|
|
{
|
|
CurrentFrameCaptureIndex = 0;
|
|
}
|
|
|
|
CaptureSlateRenderer->CopyWindowsToDrawBuffer(StrippedKeyPressBuffer);
|
|
CaptureSlateRenderer->MapCrashTrackerBuffer(&Buffer[CurrentBufferIndex], &Width, &Height);
|
|
|
|
CurrentBufferIndex = (CurrentBufferIndex + 1) % 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool FCrashVideoCapture::IsCapturing() const
|
|
{
|
|
return bIsRunning != 0;
|
|
}
|
|
|
|
|
|
void FCrashVideoCapture::InvalidateFrame()
|
|
{
|
|
Buffer[0] = Buffer[1] = NULL;
|
|
}
|
|
|
|
|
|
void FCrashVideoCapture::BufferKeyPress(const FString& KeyPress)
|
|
{
|
|
KeypressBuffer.Add(TKeyValuePair<FString, float>(KeyPress, CrashTrackerConstants::SecondsToKeyPressFade));
|
|
}
|
|
|
|
|
|
void FCrashVideoCapture::CleanupKeyPressBuffer(float DeltaSeconds)
|
|
{
|
|
TArray< TKeyValuePair<FString, float> > NewKeyPressBuffer;
|
|
int32 StartKeyPressIndex = FMath::Max(0, KeypressBuffer.Num() - CrashTrackerConstants::MaxKeyPressesInBuffer);
|
|
for (int32 i = StartKeyPressIndex; i < KeypressBuffer.Num(); ++i)
|
|
{
|
|
if (KeypressBuffer[i].Value > 0) {NewKeyPressBuffer.Add(KeypressBuffer[i]);}
|
|
}
|
|
KeypressBuffer = NewKeyPressBuffer;
|
|
}
|
|
|
|
|
|
bool FCrashVideoCapture::SaveCaptureNow( const FString& CapturePath )
|
|
{
|
|
bool bSavedOk = false;
|
|
UE_LOG(LogCrashTracker, Log, TEXT("Forcing save of crash video capture to %s.") , *CapturePath );
|
|
|
|
if (bIsRunning)
|
|
{
|
|
SyncWorkerThread();
|
|
|
|
SaveFinalCapture( CapturePath );
|
|
|
|
bSavedOk = true;
|
|
}
|
|
return bSavedOk;
|
|
}
|
|
|
|
|
|
|
|
|