// Fill out your copyright notice in the Description page of Project Settings.
#include "FPSCpp.h"
#include "VRGameViewportClient.h"
#include "BufferVisualizationData.h"
#include "EngineModule.h"
#include "EngineStats.h"
#include "SceneViewExtension.h"
#include "AudioDevice.h"
#include "IHeadMountedDisplay.h"
#include "Engine/Console.h"
#include "ContentStreaming.h"
#include "FXSystem.h"
#include "SubtitleManager.h"
#include "HitProxies.h"
DECLARE_CYCLE_STAT(TEXT("UI Drawing Time"), STAT_UIDrawingTime, STATGROUP_UI);
/** Util to find named canvas in transient package, and create if not found */
static UCanvas* GetCanvasByName(FName CanvasName)
{
// Cache to avoid FString/FName conversions/compares
static TMap<FName, UCanvas*> CanvasMap;
UCanvas** FoundCanvas = CanvasMap.Find(CanvasName);
if (!FoundCanvas)
{
UCanvas* CanvasObject = FindObject<UCanvas>(GetTransientPackage(), *CanvasName.ToString());
if (!CanvasObject)
{
CanvasObject = NewObject<UCanvas>(GetTransientPackage(), CanvasName);
CanvasObject->AddToRoot();
}
CanvasMap.Add(CanvasName, CanvasObject);
return CanvasObject;
}
return *FoundCanvas;
}
struct HGameWidgetAxis : public HHitProxy
{
DECLARE_HIT_PROXY(FPSCPP_API);
EAxisList::Type Axis;
uint32 bDisabled : 1;
HGameWidgetAxis(EAxisList::Type InAxis, bool InbDisabled = false) :
HHitProxy(HPP_UI),
Axis(InAxis),
bDisabled(InbDisabled) {}
virtual EMouseCursor::Type GetMouseCursor() override
{
if (bDisabled)
{
return EMouseCursor::SlashedCircle;
}
return EMouseCursor::CardinalCross;
}
/**
* Method that specifies whether the hit proxy *always* allows translucent primitives to be associated with it or not,
* regardless of any other engine/editor setting. For example, if translucent selection was disabled, any hit proxies
* returning true would still allow translucent selection. In this specific case, true is always returned because geometry
* mode hit proxies always need to be selectable or geometry mode will not function correctly.
*
* @return true if translucent primitives are always allowed with this hit proxy; false otherwise
*/
virtual bool AlwaysAllowsTranslucentPrimitives() const override
{
return true;
}
};
IMPLEMENT_HIT_PROXY(HGameWidgetAxis, HHitProxy);
/**
* Draw debug info on a game scene view.
*/
class FGameGizmoViewDrawer : public FViewElementDrawer
{
public:
/**
* Draws debug info using the given draw interface.
*/
virtual void Draw(const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
UMaterial* AxisMaterialBase = GEngine->ArrowMaterial;
UMaterialInstanceDynamic* AxisMaterialX = UMaterialInstanceDynamic::Create(AxisMaterialBase, NULL);
FTransform Org = FTransform::Identity;
Org.SetLocation(FVector(-351, -99, 236));
//DrawWireSphere(PDI, Org, FColor(255, 130, 0), 100, 32, SDPG_Foreground);
//DrawSphere(PDI, Org.GetLocation(), FVector(100, 100, 100), 32, 32, AxisMaterialX->GetRenderProxy(false), SDPG_Foreground);
PDI->SetHitProxy(new HGameWidgetAxis(EAxisList::Z, false));
DrawCylinder(PDI, Org.ToMatrixWithScale(), FVector(0, 0, 30), FVector(1, 0, 0), FVector(0, 1, 0), FVector(0, 0, 1), 30, 100, 16, AxisMaterialX->GetRenderProxy(false), SDPG_Foreground);
PDI->SetHitProxy(nullptr);
}
};
void UVRGameViewportClient::Draw(FViewport* InViewport, FCanvas* SceneCanvas)
{
//Valid SceneCanvas is required. Make this explicit.
check(SceneCanvas);
OnBeginDraw().Broadcast();
FCanvas* DebugCanvas = InViewport->GetDebugCanvas();
// Create a temporary canvas if there isn't already one.
static FName CanvasObjectName(TEXT("CanvasObject"));
UCanvas* CanvasObject = GetCanvasByName(CanvasObjectName);
CanvasObject->Canvas = SceneCanvas;
// Create temp debug canvas object
static FName DebugCanvasObjectName(TEXT("DebugCanvasObject"));
UCanvas* DebugCanvasObject = GetCanvasByName(DebugCanvasObjectName);
DebugCanvasObject->Canvas = DebugCanvas;
DebugCanvasObject->Init(InViewport->GetSizeXY().X, InViewport->GetSizeXY().Y, NULL);
const bool bStereoRendering = GEngine->HMDDevice.IsValid() && GEngine->IsStereoscopic3D(InViewport);
if (DebugCanvas)
{
DebugCanvas->SetScaledToRenderTarget(bStereoRendering);
DebugCanvas->SetStereoRendering(bStereoRendering);
}
if (SceneCanvas)
{
SceneCanvas->SetScaledToRenderTarget(bStereoRendering);
SceneCanvas->SetStereoRendering(bStereoRendering);
}
bool bUIDisableWorldRendering = false;
FGameGizmoViewDrawer GameViewDrawer;
// create the view family for rendering the world scene to the viewport's render target
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
InViewport,
GetWorld()->Scene,
EngineShowFlags)
.SetRealtimeUpdate(true));
// Allow HMD to modify the view later, just before rendering
if (GEngine->HMDDevice.IsValid() && GEngine->IsStereoscopic3D(InViewport))
{
auto HmdViewExt = GEngine->HMDDevice->GetViewExtension();
if (HmdViewExt.IsValid())
{
ViewFamily.ViewExtensions.Add(HmdViewExt);
HmdViewExt->SetupViewFamily(ViewFamily);
}
}
if (bStereoRendering)
{
// Allow HMD to modify screen settings
GEngine->HMDDevice->UpdateScreenSettings(Viewport);
}
ESplitScreenType::Type SplitScreenConfig = GetCurrentSplitscreenConfiguration();
EngineShowFlagOverride(ESFIM_Game, (EViewModeIndex)ViewModeIndex, ViewFamily.EngineShowFlags, NAME_None, SplitScreenConfig != ESplitScreenType::None);
if (ViewFamily.EngineShowFlags.VisualizeBuffer && AllowDebugViewmodes())
{
// Process the buffer visualization console command
FName NewBufferVisualizationMode = NAME_None;
static IConsoleVariable* ICVar = IConsoleManager::Get().FindConsoleVariable(FBufferVisualizationData::GetVisualizationTargetConsoleCommandName());
if (ICVar)
{
static const FName OverviewName = TEXT("Overview");
FString ModeNameString = ICVar->GetString();
FName ModeName = *ModeNameString;
if (ModeNameString.IsEmpty() || ModeName == OverviewName || ModeName == NAME_None)
{
NewBufferVisualizationMode = NAME_None;
}
else
{
if (GetBufferVisualizationData().GetMaterial(ModeName) == NULL)
{
// Mode is out of range, so display a message to the user, and reset the mode back to the previous valid one
UE_LOG(LogConsoleResponse, Warning, TEXT("Buffer visualization mode '%s' does not exist"), *ModeNameString);
//NewBufferVisualizationMode = CurrentBufferVisualizationMode;
// todo: cvars are user settings, here the cvar state is used to avoid log spam and to auto correct for the user (likely not what the user wants)
ICVar->Set(*NewBufferVisualizationMode.GetPlainNameString(), ECVF_SetByCode);
}
else
{
NewBufferVisualizationMode = ModeName;
}
}
}
//if (NewBufferVisualizationMode != CurrentBufferVisualizationMode)
{
//CurrentBufferVisualizationMode = NewBufferVisualizationMode;
}
}
TMap<ULocalPlayer*, FSceneView*> PlayerViewMap;
FAudioDevice* AudioDevice = GetWorld()->GetAudioDevice();
bool bReverbSettingsFound = false;
FReverbSettings ReverbSettings;
class AAudioVolume* AudioVolume = nullptr;
for (FLocalPlayerIterator Iterator(GEngine, GetWorld()); Iterator; ++Iterator)
{
ULocalPlayer* LocalPlayer = *Iterator;
if (LocalPlayer)
{
APlayerController* PlayerController = LocalPlayer->PlayerController;
const bool bEnableStereo = GEngine->IsStereoscopic3D(InViewport);
int32 NumViews = bEnableStereo ? 2 : 1;
for (int i = 0; i < NumViews; ++i)
{
// Calculate the player's view information.
FVector ViewLocation;
FRotator ViewRotation;
EStereoscopicPass PassType = !bEnableStereo ? eSSP_FULL : ((i == 0) ? eSSP_LEFT_EYE : eSSP_RIGHT_EYE);
FSceneView* View = LocalPlayer->CalcSceneView(&ViewFamily, ViewLocation, ViewRotation, InViewport, &GameViewDrawer, PassType);
if (View)
{
if (View->Family->EngineShowFlags.Wireframe)
{
// Wireframe color is emissive-only, and mesh-modifying materials do not use material substitution, hence...
View->DiffuseOverrideParameter = FVector4(0.f, 0.f, 0.f, 0.f);
View->SpecularOverrideParameter = FVector4(0.f, 0.f, 0.f, 0.f);
}
else if (View->Family->EngineShowFlags.OverrideDiffuseAndSpecular)
{
View->DiffuseOverrideParameter = FVector4(GEngine->LightingOnlyBrightness.R, GEngine->LightingOnlyBrightness.G, GEngine->LightingOnlyBrightness.B, 0.0f);
View->SpecularOverrideParameter = FVector4(.1f, .1f, .1f, 0.0f);
}
else if (View->Family->EngineShowFlags.ReflectionOverride)
{
View->DiffuseOverrideParameter = FVector4(0.f, 0.f, 0.f, 0.f);
View->SpecularOverrideParameter = FVector4(1, 1, 1, 0.0f);
View->NormalOverrideParameter = FVector4(0, 0, 1, 0.0f);
View->RoughnessOverrideParameter = FVector2D(0.0f, 0.0f);
}
if (!View->Family->EngineShowFlags.Diffuse)
{
View->DiffuseOverrideParameter = FVector4(0.f, 0.f, 0.f, 0.f);
}
if (!View->Family->EngineShowFlags.Specular)
{
View->SpecularOverrideParameter = FVector4(0.f, 0.f, 0.f, 0.f);
}
View->CurrentBufferVisualizationMode = NAME_None;
View->CameraConstrainedViewRect = View->UnscaledViewRect;
// If this is the primary drawing pass, update things that depend on the view location
if (i == 0)
{
// Save the location of the view.
LocalPlayer->LastViewLocation = ViewLocation;
PlayerViewMap.Add(LocalPlayer, View);
// Update the listener.
if (AudioDevice != NULL && PlayerController != NULL)
{
bool bUpdateListenerPosition = true;
// If the main audio device is used for multiple PIE viewport clients, we only
// want to update the main audio device listener position if it is in focus
if (GEngine)
{
FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager();
// If there is more than one world referencing the main audio device
if (AudioDeviceManager->GetNumMainAudioDeviceWorlds() > 1)
{
uint32 MainAudioDeviceHandle = GEngine->GetAudioDeviceHandle();
if (AudioDevice->DeviceHandle == MainAudioDeviceHandle /*&& !bHasAudioFocus*/)
{
bUpdateListenerPosition = false;
}
}
}
if (bUpdateListenerPosition)
{
FVector Location;
FVector ProjFront;
FVector ProjRight;
PlayerController->GetAudioListenerPosition(/*out*/ Location, /*out*/ ProjFront, /*out*/ ProjRight);
FTransform ListenerTransform(FRotationMatrix::MakeFromXY(ProjFront, ProjRight));
ListenerTransform.SetTranslation(Location);
ListenerTransform.NormalizeRotation();
bReverbSettingsFound = true;
FReverbSettings PlayerReverbSettings;
FInteriorSettings PlayerInteriorSettings;
class AAudioVolume* PlayerAudioVolume = GetWorld()->GetAudioSettings(Location, &PlayerReverbSettings, &PlayerInteriorSettings);
if (AudioVolume == nullptr || (PlayerAudioVolume != nullptr && PlayerAudioVolume->Priority > AudioVolume->Priority))
{
AudioVolume = PlayerAudioVolume;
ReverbSettings = PlayerReverbSettings;
}
uint32 ViewportIndex = PlayerViewMap.Num() - 1;
AudioDevice->SetListener(ViewportIndex, ListenerTransform, (View->bCameraCut ? 0.f : GetWorld()->GetDeltaSeconds()), PlayerAudioVolume, PlayerInteriorSettings);
}
}
}
// Add view information for resource streaming.
IStreamingManager::Get().AddViewInformation(View->ViewMatrices.ViewOrigin, View->ViewRect.Width(), View->ViewRect.Width() * View->ViewMatrices.ProjMatrix.M[0][0]);
GetWorld()->ViewLocationsRenderedLastFrame.Add(View->ViewMatrices.ViewOrigin);
}
}
}
}
if (bReverbSettingsFound)
{
AudioDevice->SetReverbSettings(AudioVolume, ReverbSettings);
}
// Update level streaming.
GetWorld()->UpdateLevelStreaming();
// Draw the player views.
if (!bDisableWorldRendering && !bUIDisableWorldRendering && PlayerViewMap.Num() > 0) //-V560
{
GetRendererModule().BeginRenderingViewFamily(SceneCanvas, &ViewFamily);
}
// Clear areas of the rendertarget (backbuffer) that aren't drawn over by the views.
{
// Find largest rectangle bounded by all rendered views.
uint32 MinX = InViewport->GetSizeXY().X, MinY = InViewport->GetSizeXY().Y, MaxX = 0, MaxY = 0;
uint32 TotalArea = 0;
for (int32 ViewIndex = 0; ViewIndex < ViewFamily.Views.Num(); ++ViewIndex)
{
const FSceneView* View = ViewFamily.Views[ViewIndex];
FIntRect UpscaledViewRect = View->UnscaledViewRect;
MinX = FMath::Min<uint32>(UpscaledViewRect.Min.X, MinX);
MinY = FMath::Min<uint32>(UpscaledViewRect.Min.Y, MinY);
MaxX = FMath::Max<uint32>(UpscaledViewRect.Max.X, MaxX);
MaxY = FMath::Max<uint32>(UpscaledViewRect.Max.Y, MaxY);
TotalArea += FMath::TruncToInt(UpscaledViewRect.Width()) * FMath::TruncToInt(UpscaledViewRect.Height());
}
// To draw black borders around the rendered image (prevents artifacts from post processing passes that read outside of the image e.g. PostProcessAA)
{
int32 BlackBorders = 0; //FMath::Clamp(CVarSetBlackBordersEnabled.GetValueOnGameThread(), 0, 10);
if (ViewFamily.Views.Num() == 1 && BlackBorders)
{
MinX += BlackBorders;
MinY += BlackBorders;
MaxX -= BlackBorders;
MaxY -= BlackBorders;
TotalArea = (MaxX - MinX) * (MaxY - MinY);
}
}
// If the views don't cover the entire bounding rectangle, clear the entire buffer.
if (ViewFamily.Views.Num() == 0 || TotalArea != (MaxX - MinX)*(MaxY - MinY) || bDisableWorldRendering)
{
SceneCanvas->DrawTile(0, 0, InViewport->GetSizeXY().X, InViewport->GetSizeXY().Y, 0.0f, 0.0f, 1.0f, 1.f, FLinearColor::Black, NULL, false);
}
else
{
// clear left
if (MinX > 0)
{
SceneCanvas->DrawTile(0, 0, MinX, InViewport->GetSizeXY().Y, 0.0f, 0.0f, 1.0f, 1.f, FLinearColor::Black, NULL, false);
}
// clear right
if (MaxX < (uint32)InViewport->GetSizeXY().X)
{
SceneCanvas->DrawTile(MaxX, 0, InViewport->GetSizeXY().X, InViewport->GetSizeXY().Y, 0.0f, 0.0f, 1.0f, 1.f, FLinearColor::Black, NULL, false);
}
// clear top
if (MinY > 0)
{
SceneCanvas->DrawTile(MinX, 0, MaxX, MinY, 0.0f, 0.0f, 1.0f, 1.f, FLinearColor::Black, NULL, false);
}
// clear bottom
if (MaxY < (uint32)InViewport->GetSizeXY().Y)
{
SceneCanvas->DrawTile(MinX, MaxY, MaxX, InViewport->GetSizeXY().Y, 0.0f, 0.0f, 1.0f, 1.f, FLinearColor::Black, NULL, false);
}
}
}
// Remove temporary debug lines.
if (GetWorld()->LineBatcher != NULL)
{
GetWorld()->LineBatcher->Flush();
}
if (GetWorld()->ForegroundLineBatcher != NULL)
{
GetWorld()->ForegroundLineBatcher->Flush();
}
// Draw FX debug information.
if (GetWorld()->FXSystem)
{
GetWorld()->FXSystem->DrawDebug(SceneCanvas);
}
// Render the UI.
{
SCOPE_CYCLE_COUNTER(STAT_UIDrawingTime);
// render HUD
bool bDisplayedSubtitles = false;
for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
APlayerController* PlayerController = *Iterator;
if (PlayerController)
{
ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(PlayerController->Player);
if (LocalPlayer)
{
FSceneView* View = PlayerViewMap.FindRef(LocalPlayer);
if (View != NULL)
{
// rendering to directly to viewport target
FVector CanvasOrigin(FMath::TruncToFloat(View->UnscaledViewRect.Min.X), FMath::TruncToInt(View->UnscaledViewRect.Min.Y), 0.f);
CanvasObject->Init(View->UnscaledViewRect.Width(), View->UnscaledViewRect.Height(), View);
// Set the canvas transform for the player's view rectangle.
SceneCanvas->PushAbsoluteTransform(FTranslationMatrix(CanvasOrigin));
CanvasObject->ApplySafeZoneTransform();
// Render the player's HUD.
if (PlayerController->MyHUD)
{
//SCOPE_CYCLE_COUNTER(STAT_HudTime);
DebugCanvasObject->SceneView = View;
PlayerController->MyHUD->SetCanvas(CanvasObject, DebugCanvasObject);
PlayerController->MyHUD->PostRender();
// Put these pointers back as if a blueprint breakpoint hits during HUD PostRender they can
// have been changed
CanvasObject->Canvas = SceneCanvas;
DebugCanvasObject->Canvas = DebugCanvas;
// A side effect of PostRender is that the playercontroller could be destroyed
if (!PlayerController->IsPendingKill())
{
PlayerController->MyHUD->SetCanvas(NULL, NULL);
}
}
if (DebugCanvas != NULL)
{
DebugCanvas->PushAbsoluteTransform(FTranslationMatrix(CanvasOrigin));
UDebugDrawService::Draw(ViewFamily.EngineShowFlags, InViewport, View, DebugCanvas);
DebugCanvas->PopTransform();
}
CanvasObject->PopSafeZoneTransform();
SceneCanvas->PopTransform();
// draw subtitles
if (!bDisplayedSubtitles)
{
FVector2D MinPos(0.f, 0.f);
FVector2D MaxPos(1.f, 1.f);
GetSubtitleRegion(MinPos, MaxPos);
const uint32 SizeX = SceneCanvas->GetRenderTarget()->GetSizeXY().X;
const uint32 SizeY = SceneCanvas->GetRenderTarget()->GetSizeXY().Y;
FIntRect SubtitleRegion(FMath::TruncToInt(SizeX * MinPos.X), FMath::TruncToInt(SizeY * MinPos.Y), FMath::TruncToInt(SizeX * MaxPos.X), FMath::TruncToInt(SizeY * MaxPos.Y));
FSubtitleManager::GetSubtitleManager()->DisplaySubtitles(SceneCanvas, SubtitleRegion, GetWorld()->GetAudioTimeSeconds());
bDisplayedSubtitles = true;
}
}
}
}
}
//ensure canvas has been flushed before rendering UI
SceneCanvas->Flush_GameThread();
if (DebugCanvas != NULL)
{
DebugCanvas->Flush_GameThread();
}
OnDrawn().Broadcast();
// Allow the viewport to render additional stuff
PostRender(DebugCanvasObject);
// Render the console.
if (ViewportConsole)
{
ViewportConsole->PostRender_Console(DebugCanvasObject);
}
}
// Grab the player camera location and orientation so we can pass that along to the stats drawing code.
FVector PlayerCameraLocation = FVector::ZeroVector;
FRotator PlayerCameraRotation = FRotator::ZeroRotator;
{
for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator)
{
(*Iterator)->GetPlayerViewPoint(PlayerCameraLocation, PlayerCameraRotation);
}
}
DrawStatsHUD(GetWorld(), InViewport, DebugCanvas, DebugCanvasObject, DebugProperties, PlayerCameraLocation, PlayerCameraRotation);
if (GEngine->IsStereoscopic3D(InViewport))
{
#if !UE_BUILD_SHIPPING
if (GEngine->HMDDevice.IsValid())
{
GEngine->HMDDevice->DrawDebug(DebugCanvasObject);
}
#endif
}
OnEndDraw().Broadcast();
}
Full c++ source codes.