Skip to content

Commit c300042

Browse files
authored
Merge pull request #6 from TimmHess/dev/plugin
Dev/plugin
2 parents 1de5bb4 + bfc065e commit c300042

27 files changed

+1110
-1
lines changed
-38.3 KB
Binary file not shown.

CaptureToDisk/Content/TestMap.umap

-1.55 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"BuildId": "5660361",
3+
"Modules":
4+
{
5+
"CameraCaptureToDisk": "UE4Editor-CameraCaptureToDisk-0036.dll"
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"FileVersion": 3,
3+
"Version": 1,
4+
"VersionName": "1.0",
5+
"FriendlyName": "CameraCaptureToDisk",
6+
"Description": "",
7+
"Category": "ImageCaptureToDisk",
8+
"CreatedBy": "TimmHess",
9+
"CreatedByURL": "",
10+
"DocsURL": "",
11+
"MarketplaceURL": "",
12+
"SupportURL": "",
13+
"CanContainContent": true,
14+
"IsBetaVersion": true,
15+
"Installed": false,
16+
"Modules": [
17+
{
18+
"Name": "CameraCaptureToDisk",
19+
"Type": "Runtime",
20+
"LoadingPhase": "Default",
21+
"WhitelistPlatforms": [
22+
"Win64"
23+
]
24+
}
25+
]
26+
}
Binary file not shown.
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
2+
3+
using UnrealBuildTool;
4+
5+
public class CameraCaptureToDisk : ModuleRules
6+
{
7+
public CameraCaptureToDisk(ReadOnlyTargetRules Target) : base(Target)
8+
{
9+
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
10+
11+
PublicIncludePaths.AddRange(
12+
new string[] {
13+
// ... add public include paths required here ...
14+
}
15+
);
16+
17+
18+
PrivateIncludePaths.AddRange(
19+
new string[] {
20+
// ... add other private include paths required here ...
21+
}
22+
);
23+
24+
25+
PublicDependencyModuleNames.AddRange(
26+
new string[]
27+
{
28+
"Core",
29+
"InputCore",
30+
"ImageWrapper",
31+
"RenderCore",
32+
"Renderer",
33+
"RHI"
34+
// ... add other public dependencies that you statically link with here ...
35+
}
36+
);
37+
38+
39+
PrivateDependencyModuleNames.AddRange(
40+
new string[]
41+
{
42+
"CoreUObject",
43+
"Engine",
44+
"Slate",
45+
"SlateCore",
46+
// ... add private dependencies that you statically link with here ...
47+
}
48+
);
49+
50+
51+
DynamicallyLoadedModuleNames.AddRange(
52+
new string[]
53+
{
54+
// ... add any modules that your module loads dynamically here ...
55+
}
56+
);
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
// Fill out your copyright notice in the Description page of Project Settings.
2+
3+
4+
#include "CameraCaptureManager.h"
5+
6+
//#include "Engine.h"
7+
#include "Runtime/Engine/Classes/Engine/Engine.h"
8+
9+
#include "Engine/SceneCapture2D.h"
10+
#include "Components/SceneCaptureComponent2D.h"
11+
#include "Engine/TextureRenderTarget2D.h"
12+
#include "Kismet/GameplayStatics.h"
13+
#include "ShowFlags.h"
14+
15+
#include "Materials/Material.h"
16+
17+
#include "RHICommandList.h"
18+
19+
#include "ImageWrapper/Public/IImageWrapper.h"
20+
#include "ImageWrapper/Public/IImageWrapperModule.h"
21+
22+
#include "ImageUtils.h"
23+
24+
#include "Modules/ModuleManager.h"
25+
26+
#include "Misc/FileHelper.h"
27+
28+
// Sets default values
29+
ACameraCaptureManager::ACameraCaptureManager()
30+
{
31+
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
32+
PrimaryActorTick.bCanEverTick = true;
33+
34+
}
35+
36+
// Called when the game starts or when spawned
37+
void ACameraCaptureManager::BeginPlay()
38+
{
39+
Super::BeginPlay();
40+
41+
if(CaptureComponent){ // nullptr check
42+
SetupCaptureComponent();
43+
} else{
44+
UE_LOG(LogTemp, Error, TEXT("No CaptureComponent set!"));
45+
}
46+
47+
}
48+
49+
// Called every frame
50+
void ACameraCaptureManager::Tick(float DeltaTime)
51+
{
52+
Super::Tick(DeltaTime);
53+
54+
// Read pixels once RenderFence is completed
55+
if(!RenderRequestQueue.IsEmpty()){
56+
// Peek the next RenderRequest from queue
57+
FRenderRequestStruct* nextRenderRequest = nullptr;
58+
RenderRequestQueue.Peek(nextRenderRequest);
59+
60+
//int32 frameWidht = 640;
61+
//int32 frameHeight = 480;
62+
63+
if(nextRenderRequest){ //nullptr check
64+
if(nextRenderRequest->RenderFence.IsFenceComplete()){ // Check if rendering is done, indicated by RenderFence
65+
66+
// Load the image wrapper module
67+
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
68+
69+
// Decide storing of data, either jpeg or png
70+
FString fileName = "";
71+
if(UsePNG){
72+
//Generate image name
73+
fileName = FPaths::ProjectSavedDir() + SubDirectoryName + "/img" + "_" + ToStringWithLeadingZeros(ImgCounter, NumDigits);
74+
fileName += ".png"; // Add file ending
75+
76+
// Prepare data to be written to disk
77+
static TSharedPtr<IImageWrapper> imageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); //EImageFormat::PNG //EImageFormat::JPEG
78+
imageWrapper->SetRaw(nextRenderRequest->Image.GetData(), nextRenderRequest->Image.GetAllocatedSize(), FrameWidth, FrameHeight, ERGBFormat::BGRA, 8);
79+
const TArray<uint8>& ImgData = imageWrapper->GetCompressed(5);
80+
RunAsyncImageSaveTask(ImgData, fileName);
81+
} else{
82+
// Generate image name
83+
fileName = FPaths::ProjectSavedDir() + SubDirectoryName + "/img" + "_" + ToStringWithLeadingZeros(ImgCounter, NumDigits);
84+
fileName += ".jpeg"; // Add file ending
85+
86+
// Prepare data to be written to disk
87+
static TSharedPtr<IImageWrapper> imageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG); //EImageFormat::PNG //EImageFormat::JPEG
88+
imageWrapper->SetRaw(nextRenderRequest->Image.GetData(), nextRenderRequest->Image.GetAllocatedSize(), FrameWidth, FrameHeight, ERGBFormat::BGRA, 8);
89+
const TArray<uint8>& ImgData = imageWrapper->GetCompressed(0);
90+
RunAsyncImageSaveTask(ImgData, fileName);
91+
}
92+
93+
if(VerboseLogging && !fileName.IsEmpty()){
94+
UE_LOG(LogTemp, Warning, TEXT("%f"), *fileName);
95+
}
96+
97+
ImgCounter += 1;
98+
99+
// Delete the first element from RenderQueue
100+
RenderRequestQueue.Pop();
101+
delete nextRenderRequest;
102+
103+
UE_LOG(LogTemp, Log, TEXT("Done..."));
104+
}
105+
}
106+
}
107+
108+
}
109+
110+
void ACameraCaptureManager::SetupCaptureComponent(){
111+
if(!IsValid(CaptureComponent)){
112+
UE_LOG(LogTemp, Error, TEXT("SetupCaptureComponent: CaptureComponent is not valid!"));
113+
return;
114+
}
115+
116+
// Create RenderTargets
117+
UTextureRenderTarget2D* renderTarget2D = NewObject<UTextureRenderTarget2D>();
118+
119+
// Set FrameWidth and FrameHeight
120+
renderTarget2D->TargetGamma = GEngine->GetDisplayGamma(); //1.2f; // for Vulkan //GEngine->GetDisplayGamma(); // for DX11/12
121+
122+
// Setup the RenderTarget capture format
123+
renderTarget2D->InitAutoFormat(256, 256); // some random format, got crashing otherwise
124+
//int32 frameWidht = 640;
125+
//int32 frameHeight = 480;
126+
renderTarget2D->InitCustomFormat(FrameWidth, FrameHeight, PF_B8G8R8A8, true); // PF_B8G8R8A8 disables HDR which will boost storing to disk due to less image information
127+
renderTarget2D->RenderTargetFormat = ETextureRenderTargetFormat::RTF_RGBA8;
128+
renderTarget2D->bGPUSharedFlag = true; // demand buffer on GPU
129+
130+
// Assign RenderTarget
131+
CaptureComponent->GetCaptureComponent2D()->TextureTarget = renderTarget2D;
132+
133+
// Set Camera Properties
134+
CaptureComponent->GetCaptureComponent2D()->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
135+
CaptureComponent->GetCaptureComponent2D()->ShowFlags.SetTemporalAA(true);
136+
// lookup more showflags in the UE4 documentation..
137+
138+
// Assign PostProcess Material if assigned
139+
if(PostProcessMaterial){ // check nullptr
140+
CaptureComponent->GetCaptureComponent2D()->AddOrUpdateBlendable(PostProcessMaterial);
141+
} else {
142+
UE_LOG(LogTemp, Log, TEXT("No PostProcessMaterial is assigend"));
143+
}
144+
145+
}
146+
147+
void ACameraCaptureManager::CaptureNonBlocking(){
148+
if(!IsValid(CaptureComponent)){
149+
UE_LOG(LogTemp, Error, TEXT("CaptureColorNonBlocking: CaptureComponent was not valid!"));
150+
return;
151+
}
152+
153+
CaptureComponent->GetCaptureComponent2D()->TextureTarget->TargetGamma = GEngine->GetDisplayGamma();
154+
155+
// Get RenderConterxt
156+
FTextureRenderTargetResource* renderTargetResource = CaptureComponent->GetCaptureComponent2D()->TextureTarget->GameThread_GetRenderTargetResource();
157+
158+
struct FReadSurfaceContext{
159+
FRenderTarget* SrcRenderTarget;
160+
TArray<FColor>* OutData;
161+
FIntRect Rect;
162+
FReadSurfaceDataFlags Flags;
163+
};
164+
165+
// Init new RenderRequest
166+
FRenderRequestStruct* renderRequest = new FRenderRequestStruct();
167+
168+
// Setup GPU command
169+
FReadSurfaceContext readSurfaceContext = {
170+
renderTargetResource,
171+
&(renderRequest->Image),
172+
FIntRect(0,0,renderTargetResource->GetSizeXY().X, renderTargetResource->GetSizeXY().Y),
173+
FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX)
174+
};
175+
176+
// Send command to GPU
177+
/* Up to version 4.22 use this
178+
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
179+
SceneDrawCompletion,//ReadSurfaceCommand,
180+
FReadSurfaceContext, Context, readSurfaceContext,
181+
{
182+
RHICmdList.ReadSurfaceData(
183+
Context.SrcRenderTarget->GetRenderTargetTexture(),
184+
Context.Rect,
185+
*Context.OutData,
186+
Context.Flags
187+
);
188+
});
189+
*/
190+
// Above 4.22 use this
191+
ENQUEUE_RENDER_COMMAND(SceneDrawCompletion)(
192+
[readSurfaceContext](FRHICommandListImmediate& RHICmdList){
193+
RHICmdList.ReadSurfaceData(
194+
readSurfaceContext.SrcRenderTarget->GetRenderTargetTexture(),
195+
readSurfaceContext.Rect,
196+
*readSurfaceContext.OutData,
197+
readSurfaceContext.Flags
198+
);
199+
});
200+
201+
// Notifiy new task in RenderQueue
202+
RenderRequestQueue.Enqueue(renderRequest);
203+
204+
// Set RenderCommandFence
205+
renderRequest->RenderFence.BeginFence();
206+
}
207+
208+
209+
/*
210+
void ACameraCaptureManager::SpawnSegmentationCaptureComponent(ASceneCapture2D* ColorCapture){
211+
if(!IsValid(ColorCapture)){
212+
UE_LOG(LogTemp, Error, TEXT("CaptureColorNonBlocking: CaptureComponent was not valid!"));
213+
return;
214+
}
215+
216+
// Spawning a new SceneCaptureComponent
217+
ASceneCapture2D* newSegmentationCapture = (ASceneCapture2D*) GetWorld()->SpawnActor<ASceneCapture2D>(ASceneCapture2D::StaticClass());
218+
if(!newSegmentationCapture){ // nullptr check
219+
UE_LOG(LogTemp, Error, TEXT("Failed to spawn SegmentationComponent"));
220+
return;
221+
}
222+
// Register new CaptureComponent to game
223+
newSegmentationCapture->GetCaptureComponent2D()->RegisterComponent();
224+
// Attach SegmentationCaptureComponent to match ColorCaptureComponent
225+
newSegmentationCapture->AttachToActor(ColorCapture, FAttachmentTransformRules::SnapToTargetNotIncludingScale);
226+
227+
// Get values from "parent" ColorCaptureComponent
228+
newSegmentationCapture->GetCaptureComponent2D()->FOVAngle = ColorCapture->GetCaptureComponent2D()->FOVAngle;
229+
230+
// Set pointer to new segmentation capture component
231+
SegmentationCapture = newSegmentationCapture;
232+
233+
UE_LOG(LogTemp, Warning, TEXT("Done..."));
234+
}
235+
*/
236+
/*
237+
void ACameraCaptureManager::SetupSegmentationCaptureComponent(ASceneCapture2D* ColorCapture){
238+
if(!IsValid(ColorCapture)){
239+
UE_LOG(LogTemp, Error, TEXT("CaptureColorNonBlocking: CaptureComponent was not valid!"));
240+
return;
241+
}
242+
243+
// Spawn SegmentaitonCaptureComponents
244+
SpawnSegmentationCaptureComponent(ColorCapture);
245+
246+
// Setup SegmentationCaptureComponent
247+
SetupColorCaptureComponent(SegmentationCapture);
248+
249+
// Assign PostProcess Material
250+
if(PostProcessMaterial){ // check nullptr
251+
SegmentationCapture->GetCaptureComponent2D()->AddOrUpdateBlendable(PostProcessMaterial);
252+
} else {
253+
UE_LOG(LogTemp, Error, TEXT("PostProcessMaterial was nullptr!"));
254+
}
255+
}
256+
*/
257+
258+
FString ACameraCaptureManager::ToStringWithLeadingZeros(int32 Integer, int32 MaxDigits){
259+
FString result = FString::FromInt(Integer);
260+
int32 stringSize = result.Len();
261+
int32 stringDelta = MaxDigits - stringSize;
262+
if(stringDelta < 0){
263+
UE_LOG(LogTemp, Error, TEXT("MaxDigits of ImageCounter Overflow!"));
264+
return result;
265+
}
266+
//FIXME: Smarter function for this..
267+
FString leadingZeros = "";
268+
for(size_t i=0;i<stringDelta;i++){
269+
leadingZeros += "0";
270+
}
271+
result = leadingZeros + result;
272+
273+
return result;
274+
}
275+
276+
277+
278+
279+
280+
void ACameraCaptureManager::RunAsyncImageSaveTask(TArray<uint8> Image, FString ImageName){
281+
(new FAutoDeleteAsyncTask<AsyncSaveImageToDiskTask>(Image, ImageName))->StartBackgroundTask();
282+
}
283+
284+
285+
286+
/*
287+
*******************************************************************
288+
*/
289+
290+
AsyncSaveImageToDiskTask::AsyncSaveImageToDiskTask(TArray<uint8> Image, FString ImageName){
291+
ImageCopy = Image;
292+
FileName = ImageName;
293+
}
294+
295+
AsyncSaveImageToDiskTask::~AsyncSaveImageToDiskTask(){
296+
//UE_LOG(LogTemp, Warning, TEXT("AsyncTaskDone"));
297+
}
298+
299+
void AsyncSaveImageToDiskTask::DoWork(){
300+
FFileHelper::SaveArrayToFile(ImageCopy, *FileName);
301+
UE_LOG(LogTemp, Log, TEXT("Stored Image: %s"), *FileName);
302+
}

0 commit comments

Comments
 (0)