A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
VRStereoWidgetComponent.cpp
Go to the documentation of this file.
1// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
2
5#include "VRBaseCharacter.h"
6#include "TextureResource.h"
7#include "Engine/Texture.h"
8#include "IStereoLayers.h"
9#include "IHeadMountedDisplay.h"
10#include "IXRTrackingSystem.h"
11#include "PrimitiveViewRelevance.h"
12#include "PrimitiveSceneProxy.h"
13#include "UObject/ConstructorHelpers.h"
14#include "EngineGlobals.h"
15#include "MaterialShared.h"
16#include "Materials/MaterialInstanceDynamic.h"
17#include "Engine/Engine.h"
18//#include "Widgets/SWindow.h"
19#include "Engine/TextureRenderTarget2D.h"
20#include "Framework/Application/SlateApplication.h"
21#include "Kismet/KismetSystemLibrary.h"
22//#include "Input/HittestGrid.h"
23//#include "SceneManagement.h"
24#include "DynamicMeshBuilder.h"
25//#include "PhysicsEngine/BoxElem.h"
26#include "PhysicsEngine/BodySetup.h"
27#include "Slate/SGameLayerManager.h"
28#include "Slate/SWorldWidgetScreenLayer.h"
29#include "Widgets/SViewport.h"
30#include "Widgets/SViewport.h"
31
32// CVars
34{
36 FAutoConsoleVariableRef CVarForceNoStereoWithVRWidgets(
37 TEXT("vr.ForceNoStereoWithVRWidgets"),
39 TEXT("When set to 0, will render stereo layer widgets as stereo by default.\n")
40 TEXT("When set to 1, will not allow stereo widget components to use stereo layers, will instead fall back to default widget rendering.\n")
41 TEXT("When set to 2, will render stereo layer widgets as both stereo and in game.\n")
42 TEXT("0: Default, 1: Force no stereo, 2: Render both at once"),
43 ECVF_Default);
44}
45
46UVRStereoWidgetRenderComponent::UVRStereoWidgetRenderComponent(const FObjectInitializer& ObjectInitializer)
47 : Super(ObjectInitializer)
48{
49 Widget = nullptr;
50 WidgetRenderScale = 1.0f;
51 WidgetRenderGamma = 1.0f;
52 bUseGammaCorrection = false;
53 WidgetRenderer = nullptr;
54 RenderTarget = nullptr;
55 bDrawAtDesiredSize = true;
56 RenderTargetClearColor = FLinearColor::Black;
57 bDrawWithoutStereo = false;
58 DrawRate = 60.0f;
59 DrawCounter = 0.0f;
60 bLiveTexture = true;
61}
62
63void UVRStereoWidgetRenderComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
64{
65
66 IStereoLayers* StereoLayers;
67 if (!GetVisibleFlag() || (!bDrawWithoutStereo && (!GEngine->StereoRenderingDevice.IsValid() || (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) == nullptr)))
68 {
69 }
70 else
71 {
72 DrawCounter += DeltaTime;
73
74 if (DrawRate > 0.0f && DrawCounter >= (1.0f / DrawRate))
75 {
76 if (!IsRunningDedicatedServer())
77 {
78 RenderWidget(DeltaTime);
79 }
80
81 if (!bLiveTexture)
82 {
83 MarkStereoLayerDirty();
84 }
85
86 DrawCounter = 0.0f;
87 }
88 }
89
91 {
92 // Skip the stereo comps setup, we are just drawing to the texture
93 Super::Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
94 }
95 else
96 {
97 Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
98 }
99}
100
102{
103 Super::BeginPlay();
104
105 if (WidgetClass.Get() != nullptr)
106 {
107 InitWidget();
108
109 IStereoLayers* StereoLayers;
110 if (!GetVisibleFlag() || (!bDrawWithoutStereo && (!GEngine->StereoRenderingDevice.IsValid() || (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) == nullptr)))
111 {
112 }
113 else
114 {
115 // Initial render
116 RenderWidget(0.0f);
117 }
118 }
119}
120
121void UVRStereoWidgetRenderComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
122{
123 Super::EndPlay(EndPlayReason);
124
126}
127
129{
130
131#if !UE_SERVER
132 FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this);
133#endif
134 if (Widget)
135 {
136 Widget = nullptr;
137 }
138
139 if (SlateWidget)
140 {
141 SlateWidget = nullptr;
142 }
143
144 if (WidgetRenderer)
145 {
146 BeginCleanup(WidgetRenderer);
147 WidgetRenderer = nullptr;
148 }
149
150 Texture = nullptr;
151
152 if (SlateWindow.IsValid())
153 {
154 if (FSlateApplication::IsInitialized())
155 {
156 FSlateApplication::Get().UnregisterVirtualWindow(SlateWindow.ToSharedRef());
157 }
158
159 SlateWindow.Reset();
160 }
161}
162
163void UVRStereoWidgetRenderComponent::DestroyComponent(bool bPromoteChildren/*= false*/)
164{
165 Super::DestroyComponent(bPromoteChildren);
166
168}
169
170void UVRStereoWidgetRenderComponent::SetWidgetAndInit(TSubclassOf<UUserWidget> NewWidgetClass)
171{
172 WidgetClass = NewWidgetClass;
173 InitWidget();
174}
175
177{
178 // If the InLevel is null, it's a signal that the entire world is about to disappear, so
179 // go ahead and remove this widget from the viewport, it could be holding onto too many
180 // dangerous actor references that won't carry over into the next world.
181 if (InLevel == nullptr && InWorld == GetWorld())
182 {
184 }
185}
186
188{
189 if (IsTemplate())
190 return;
191
192#if !UE_SERVER
193 FWorldDelegates::LevelRemovedFromWorld.AddUObject(this, &ThisClass::OnLevelRemovedFromWorld);
194#endif
195 if (IsRunningDedicatedServer())
196 return;
197
198 if (Widget && Widget->GetClass() == WidgetClass)
199 return;
200
201 if (Widget != nullptr)
202 {
203 Widget->MarkPendingKill();
204 Widget = nullptr;
205 }
206
207 if (SlateWidget)
208 {
209 SlateWidget = nullptr;
210 }
211
212 // Don't do any work if Slate is not initialized
213 if (FSlateApplication::IsInitialized())
214 {
215 UWorld* World = GetWorld();
216
217 if (WidgetClass && Widget == nullptr && World && !World->bIsTearingDown)
218 {
219 Widget = CreateWidget(GetWorld(), WidgetClass);
220 Widget->SetRenderScale(FVector2D(1.0f, 1.0f));
221 }
222
223#if WITH_EDITOR
224 if (Widget && !World->IsGameWorld())// && !bEditTimeUsable)
225 {
226 if (!GEnableVREditorHacks)
227 {
228 // Prevent native ticking of editor component previews
229 Widget->SetDesignerFlags(EWidgetDesignFlags::Designing);
230 }
231 }
232#endif
233
234 if (Widget)
235 {
236 SlateWidget = Widget->TakeWidget();
237 }
238
239 // Create the SlateWindow if it doesn't exists
240 if (!SlateWindow.IsValid())
241 {
242 FVector2D DrawSize = this->GetQuadSize();
243 SlateWindow = SNew(SVirtualWindow).Size(DrawSize);
244 SlateWindow->SetIsFocusable(false);
245 SlateWindow->SetVisibility(EVisibility::Visible);
246 SlateWindow->SetContentScale(FVector2D(1.0f, 1.0f));
247
248 if (Widget && !Widget->IsDesignTime())
249 {
250 if (UWorld* LocalWorld = GetWorld())
251 {
252 if (LocalWorld->IsGameWorld())
253 {
254 UGameInstance* GameInstance = LocalWorld->GetGameInstance();
255 check(GameInstance);
256
257 UGameViewportClient* GameViewportClient = GameInstance->GetGameViewportClient();
258 if (GameViewportClient)
259 {
260 SlateWindow->AssignParentWidget(GameViewportClient->GetGameViewportWidget());
261 }
262 }
263 }
264 }
265
266 }
267
268 if (SlateWindow)
269 {
270 TSharedRef<SWidget> MyWidget = SlateWidget ? SlateWidget.ToSharedRef() : Widget->TakeWidget();
271 SlateWindow->SetContent(MyWidget);
272 }
273 }
274}
275
277{
278 if (!Widget)
279 return;
280
281 if (WidgetRenderer == nullptr)
282 {
283 WidgetRenderer = new FWidgetRenderer(bUseGammaCorrection);
284 check(WidgetRenderer);
285 }
286
287 FVector2D DrawSize = this->GetQuadSize();
288 FVector2D TextureSize = DrawSize;
289
290 const int32 MaxAllowedDrawSize = GetMax2DTextureDimension();
291 if (DrawSize.X <= 0 || DrawSize.Y <= 0 || DrawSize.X > MaxAllowedDrawSize || DrawSize.Y > MaxAllowedDrawSize)
292 {
293 return;
294 }
295
296 TSharedRef<SWidget> MyWidget = SlateWidget ? SlateWidget.ToSharedRef() : Widget->TakeWidget();
297
299 {
300 SlateWindow->SlatePrepass(WidgetRenderScale);
301
302 FVector2D DesiredSize = SlateWindow->GetDesiredSize();
303 DesiredSize.X = FMath::RoundToInt(DesiredSize.X);
304 DesiredSize.Y = FMath::RoundToInt(DesiredSize.Y);
305
306 if (!DesiredSize.IsNearlyZero())
307 {
308 TextureSize = DesiredSize;// .IntPoint();
309
310 WidgetRenderer->SetIsPrepassNeeded(false);
311
312 if (SlateWindow->GetSizeInScreen() != DesiredSize)
313 {
314 SlateWindow->Resize(TextureSize);
315 }
316 }
317 else
318 {
319 WidgetRenderer->SetIsPrepassNeeded(true);
320 }
321 }
322 else
323 {
324 WidgetRenderer->SetIsPrepassNeeded(true);
325 }
326
327 if (RenderTarget == nullptr)
328 {
329 const EPixelFormat requestedFormat = FSlateApplication::Get().GetRenderer()->GetSlateRecommendedColorFormat();
330 RenderTarget = NewObject<UTextureRenderTarget2D>();
331 check(RenderTarget);
332 RenderTarget->AddToRoot();
334 RenderTarget->TargetGamma = WidgetRenderGamma;
335 RenderTarget->InitCustomFormat(TextureSize.X, TextureSize.Y, requestedFormat /*PF_B8G8R8A8*/, false);
336 MarkStereoLayerDirty();
337 }
338 else if (RenderTarget->Resource->GetSizeX() != TextureSize.X || RenderTarget->Resource->GetSizeY() != TextureSize.Y)
339 {
340 const EPixelFormat requestedFormat = FSlateApplication::Get().GetRenderer()->GetSlateRecommendedColorFormat();
341 RenderTarget->InitCustomFormat(TextureSize.X, TextureSize.Y, requestedFormat /*PF_B8G8R8A8*/, false);
342 RenderTarget->UpdateResourceImmediate();
343 MarkStereoLayerDirty();
344 }
345
346 WidgetRenderer->DrawWidget(RenderTarget, MyWidget, WidgetRenderScale, TextureSize, DeltaTime);//DeltaTime);
347
348 if (Texture != RenderTarget)
349 {
350 Texture = RenderTarget;
351 }
352}
353
354 //=============================================================================
355UVRStereoWidgetComponent::UVRStereoWidgetComponent(const FObjectInitializer& ObjectInitializer)
356 : Super(ObjectInitializer)
357// , bLiveTexture(false)
358 , bSupportsDepth(false)
359 , bNoAlphaChannel(false)
360 //, Texture(nullptr)
361 //, LeftTexture(nullptr)
362 , bQuadPreserveTextureRatio(false)
363 //, StereoLayerQuadSize(FVector2D(500.0f, 500.0f))
364 , UVRect(FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f)))
365 //, CylinderRadius(100)
366 //, CylinderOverlayArc(100)EWidgetGeometryMode
367 //, CylinderHeight(50)
368 //, StereoLayerType(SLT_TrackerLocked)
369 //, StereoLayerShape(SLSH_QuadLayer)
370 , Priority(0)
371 , bIsDirty(true)
372 , bTextureNeedsUpdate(false)
373 , LayerId(0)
374 , LastTransform(FTransform::Identity)
375 , bLastVisible(false)
376{
377 bShouldCreateProxy = true;
379 // Replace quad size with DrawSize instead
380 //StereoLayerQuadSize = DrawSize;
381
382 PrimaryComponentTick.TickGroup = TG_DuringPhysics;
383
384 bIsDirty = true;
385 bDirtyRenderTarget = false;
387 bDrawWithoutStereo = false;
388 bDelayForRenderThread = false;
389 bIsSleeping = false;
390 //Texture = nullptr;
391}
392
393//=============================================================================
397
399{
400 IStereoLayers* StereoLayers;
401 if (LayerId && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr)
402 {
403 StereoLayers->DestroyLayer(LayerId);
404 LayerId = 0;
405 }
406
407 Super::BeginDestroy();
408}
409
410
412{
413 IStereoLayers* StereoLayers;
414 if (LayerId && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr)
415 {
416 StereoLayers->DestroyLayer(LayerId);
417 LayerId = 0;
418 }
419
420 Super::OnUnregister();
421}
422
424{
425 Super::DrawWidgetToRenderTarget(DeltaTime);
426
427 bDirtyRenderTarget = true;
428}
429
430void UVRStereoWidgetComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
431{
432
433 // Precaching what the widget uses for draw time here as it gets modified in the super tick
434 bool bWidgetDrew = ShouldDrawWidget();
435
436 Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
437
438
439 //bool bIsCurVis = IsWidgetVisible();
440
441 bool bIsVisible = IsVisible() && IsWidgetVisible() && !bIsSleeping;// && ((GetWorld()->TimeSince(GetLastRenderTime()) <= 0.5f));
442
443 // If we are set to not use stereo layers or we don't have a valid stereo layer device
444 if (
448 !GEngine->StereoRenderingDevice.IsValid() ||
449 (GEngine->StereoRenderingDevice->GetStereoLayers() == nullptr)
450 )
451 {
453 {
454 bShouldCreateProxy = true;
455 //MarkRenderStateDirty(); // Recreate
456
457 if (LayerId)
458 {
459 if (GEngine->StereoRenderingDevice.IsValid())
460 {
461 IStereoLayers* StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers();
462 if (StereoLayers)
463 StereoLayers->DestroyLayer(LayerId);
464 }
465 LayerId = 0;
466 }
467 }
468
469 return;
470 }
471 else if (bRenderBothStereoAndWorld || StereoWidgetCvars::ForceNoStereoWithVRWidgets == 2) // Forcing both modes at once
472 {
474 {
475 bShouldCreateProxy = true;
476 MarkRenderStateDirty(); // Recreate
477 }
478 }
479 else // Stereo only
480 {
482 {
483 bShouldCreateProxy = false;
484 MarkRenderStateDirty(); // Recreate
485 }
486 }
487
488#if !UE_SERVER
489
490 // Same check that the widget runs prior to ticking
491 if (IsRunningDedicatedServer() || !GetSlateWindow() || GetSlateWindow()->GetContent() == SNullWidget::NullWidget)
492 {
493 return;
494 }
495
496 IStereoLayers* StereoLayers;
497 if (!UVRExpansionFunctionLibrary::IsInVREditorPreviewOrGame() || !GEngine->StereoRenderingDevice.IsValid() || !RenderTarget)
498 {
499 return;
500 }
501
502 StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers();
503
504 if (StereoLayers == nullptr)
505 return;
506
507 FTransform Transform = LastTransform;
508 // Never true until epic fixes back end code
509 if (false)//StereoLayerType == SLT_WorldLocked)
510 {
511 Transform = GetComponentTransform();
512 }
513 else if (Space == EWidgetSpace::Screen)
514 {
515 Transform = GetRelativeTransform();
516 }
517 else if(bIsVisible) // World locked here now
518 {
519
521 {
522 // Its incorrect......even in 4.17
523 Transform = FTransform(FRotator(0.f,-180.f, 0.f)) * GetComponentTransform();
524 //Transform.ConcatenateRotation(FRotator(0.0f, -180.0f, 0.0f).Quaternion());
525 }
526 else
527 {
528 // Fix this when stereo world locked works again
529 // Thanks to mitch for the temp work around idea
530
531 APlayerController* PC = nullptr;
532 if (UWorld * CurWorld = GetWorld())
533 {
534 const ULocalPlayer* FirstPlayer = GEngine->GetFirstGamePlayer(CurWorld);
535 PC = FirstPlayer ? FirstPlayer->GetPlayerController(CurWorld) : nullptr;
536 }
537
538 if (PC)
539 {
540 APawn * mpawn = PC->GetPawnOrSpectator();
541 //bTextureNeedsUpdate = true;
542 if (mpawn)
543 {
544
545 // Offset the transform by the widget pivot.
546 float DeltaY = (Pivot.X - 0.5f) * DrawSize.X;
547 float DeltaZ = (Pivot.Y - 0.5f) * DrawSize.Y;
548 FTransform OffsetTransform = FTransform(FVector(0.f, DeltaY, DeltaZ));
549 OffsetTransform = OffsetTransform * GetComponentTransform();
550
551 // Set transform to this relative transform
552
553 bool bHandledTransform = false;
554 if (AVRBaseCharacter* BaseVRChar = Cast<AVRBaseCharacter>(mpawn))
555 {
556 if (USceneComponent* CameraParent = BaseVRChar->VRReplicatedCamera->GetAttachParent())
557 {
558 Transform = OffsetTransform.GetRelativeTransform(CameraParent->GetComponentTransform());
559 Transform = FTransform(FRotator(0.f, -180.f, 0.f)) * Transform;
560 bHandledTransform = true;
561 }
562 }
563 else if (UCameraComponent* Camera = mpawn->FindComponentByClass<UCameraComponent>())
564 {
565 // Look for generic camera comp and use its attach parent
566 if (USceneComponent* CameraParent = Camera->GetAttachParent())
567 {
568 Transform = OffsetTransform.GetRelativeTransform(CameraParent->GetComponentTransform());
569 Transform = FTransform(FRotator(0.f, -180.f, 0.f)) * Transform;
570 bHandledTransform = true;
571 }
572 }
573
574 if(!bHandledTransform) // Just use the pawn as we don't know the heirarchy
575 {
576 Transform = OffsetTransform.GetRelativeTransform(mpawn->GetTransform());
577 Transform = FTransform(FRotator(0.f, -180.f, 0.f)) * Transform;
578 }
579
580 // OpenVR y+ Up, +x Right, -z Going away
581 // UE4 z+ up, +y right, +x forward
582
583 //Transform.ConcatenateRotation(FRotator(0.0f, -180.0f, 0.0f).Quaternion());
584 // I might need to inverse X axis here to get it facing the correct way, we'll see
585
586 //Transform = mpawn->GetActorTransform().GetRelativeTransform(GetComponentTransform());
587 }
588 }
589 else
590 {
591 // No PC, destroy the layer and enable drawing it normally.
592 bShouldCreateProxy = true;
593
594 if (LayerId)
595 {
596 StereoLayers->DestroyLayer(LayerId);
597 LayerId = 0;
598 }
599 return;
600 }
601 //
602 //Transform = GetRelativeTransform();
603 }
604 }
605
606 // If the transform changed dirty the layer and push the new transform
607
608 if (!bIsDirty)
609 {
610 if (bLastVisible != bIsVisible)
611 {
612 bIsDirty = true;
613 }
614 else if (bDirtyRenderTarget || FMemory::Memcmp(&LastTransform, &Transform, sizeof(Transform)) != 0)
615 {
616 bIsDirty = true;
617 }
618 }
619
620 bool bCurrVisible = bIsVisible;
621 if (!RenderTarget || !RenderTarget->Resource)
622 {
623 bCurrVisible = false;
624 }
625
626 if (bIsDirty)
627 {
628 // OpenXR doesn't take the transforms scale component into account for the stereo layer, so we need to scale the buffer instead
629 bool bScaleBuffer = false;
630 static FName SystemName(TEXT("OpenXR"));
631 if (GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName))
632 {
633 bScaleBuffer = true;
634 }
635
636 IStereoLayers::FLayerDesc LayerDsec;
637 LayerDsec.Priority = Priority;
638 LayerDsec.QuadSize = FVector2D(DrawSize);
639 LayerDsec.UVRect = UVRect;
640
641 if (bDelayForRenderThread && !LastTransform.Equals(FTransform::Identity))
642 {
643 LayerDsec.Transform = LastTransform;
644 if (bScaleBuffer)
645 {
646 LayerDsec.QuadSize = FVector2D(DrawSize) * FVector2D(LastTransform.GetScale3D());
647 }
648 }
649 else
650 {
651 LayerDsec.Transform = Transform;
652 if (bScaleBuffer)
653 {
654 LayerDsec.QuadSize = FVector2D(DrawSize) * FVector2D(Transform.GetScale3D());
655 }
656 }
657
658 if (RenderTarget)
659 {
660 LayerDsec.Texture = RenderTarget->Resource->TextureRHI;
661 LayerDsec.Flags |= (RenderTarget->GetMaterialType() == MCT_TextureExternal) ? IStereoLayers::LAYER_FLAG_TEX_EXTERNAL : 0;
662 }
663 // Forget the left texture implementation
664 //if (LeftTexture)
665 //{
666 // LayerDsec.LeftTexture = LeftTexture->Resource->TextureRHI;
667 //}
668
669 LayerDsec.Flags |= IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE;// (/*bLiveTexture*/true) ? IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE : 0;
670 LayerDsec.Flags |= (bNoAlphaChannel) ? IStereoLayers::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL : 0;
671 LayerDsec.Flags |= (bQuadPreserveTextureRatio) ? IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO : 0;
672 LayerDsec.Flags |= (bSupportsDepth) ? IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH : 0;
673 LayerDsec.Flags |= (!bCurrVisible) ? IStereoLayers::LAYER_FLAG_HIDDEN : 0;
674
675 // Fix this later when WorldLocked is no longer wrong.
676 switch (Space)
677 {
678 case EWidgetSpace::World:
679 {
681 LayerDsec.PositionType = IStereoLayers::WorldLocked;
682 else
683 LayerDsec.PositionType = IStereoLayers::TrackerLocked;
684
685 //LayerDsec.Flags |= IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH;
686 }break;
687
688 case EWidgetSpace::Screen:
689 default:
690 {
691 LayerDsec.PositionType = IStereoLayers::FaceLocked;
692 }break;
693 }
694
695 switch (GeometryMode)
696 {
697 case EWidgetGeometryMode::Cylinder:
698 {
699 UStereoLayerShapeCylinder* Cylinder = Cast<UStereoLayerShapeCylinder>(Shape);
700
701 if (!Cylinder)
702 {
703 if (Shape)
704 {
705 Shape->MarkPendingKill();
706 }
707
708 Cylinder = NewObject<UStereoLayerShapeCylinder>(this, NAME_None, RF_Public);
709 Shape = Cylinder;
710 }
711
712 if (Cylinder)
713 {
714 const float ArcAngleRadians = FMath::DegreesToRadians(CylinderArcAngle);
715 const float Radius = GetDrawSize().X / ArcAngleRadians;
716
717 Cylinder->Height = GetDrawSize().Y;//CylinderHeight_DEPRECATED;
718 Cylinder->OverlayArc = CylinderArcAngle;// CylinderOverlayArc_DEPRECATED;
719 Cylinder->Radius = Radius;// CylinderRadius_DEPRECATED;
720 }
721 break;
722
723 //LayerDsec.ShapeType = IStereoLayers::CylinderLayer;
724
725 }break;
726 case EWidgetGeometryMode::Plane:
727 default:
728 {
729 UStereoLayerShapeQuad* Quad = Cast<UStereoLayerShapeQuad>(Shape);
730
731 if (!Quad)
732 {
733 if (Shape)
734 {
735 Shape->MarkPendingKill();
736 }
737 Shape = NewObject<UStereoLayerShapeQuad>(this, NAME_None, RF_Public);
738 }
739 //LayerDsec.ShapeType = IStereoLayers::QuadLayer;
740 }break;
741 }
742
743 if(Shape)
744 Shape->ApplyShape(LayerDsec);
745
746 if (LayerId)
747 {
748 StereoLayers->SetLayerDesc(LayerId, LayerDsec);
749 }
750 else
751 {
752 LayerId = StereoLayers->CreateLayer(LayerDsec);
753 }
754
755 }
756
757 LastTransform = Transform;
758 bLastVisible = bCurrVisible;
759 bIsDirty = false;
760 bDirtyRenderTarget = false;
761#endif
762}
763
764
766{
767 if (Priority == InPriority)
768 {
769 return;
770 }
771
772 Priority = InPriority;
773 bIsDirty = true;
774}
775
776void UVRStereoWidgetComponent::UpdateRenderTarget(FIntPoint DesiredRenderTargetSize)
777{
778 Super::UpdateRenderTarget(DesiredRenderTargetSize);
779}
780
783{
784public:
785 SIZE_T GetTypeHash() const override
786 {
787 static size_t UniquePointer;
788 return reinterpret_cast<size_t>(&UniquePointer);
789 }
791 FStereoWidget3DSceneProxy(UVRStereoWidgetComponent* InComponent, ISlate3DRenderer& InRenderer)
792 : FPrimitiveSceneProxy(InComponent)
793 , Pivot(InComponent->GetPivot())
794 , Renderer(InRenderer)
795 , RenderTarget(InComponent->GetRenderTarget())
796 , MaterialInstance(InComponent->GetMaterialInstance())
797 , BodySetup(InComponent->GetBodySetup())
798 , BlendMode(InComponent->GetBlendMode())
799 , GeometryMode(InComponent->GetGeometryMode())
800 , ArcAngle(FMath::DegreesToRadians(InComponent->GetCylinderArcAngle()))
801 {
802 bWillEverBeLit = false;
804 MaterialRelevance = MaterialInstance->GetRelevance(GetScene().GetFeatureLevel());
805 }
806
807 // FPrimitiveSceneProxy interface.
808 virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
809 {
811 return;
812
813#if WITH_EDITOR
814 const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe;
815
816 auto WireframeMaterialInstance = new FColoredMaterialRenderProxy(
817 GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr,
818 FLinearColor(0, 0.5f, 1.f)
819 );
820
821 Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance);
822
823 FMaterialRenderProxy* ParentMaterialProxy = nullptr;
824 if (bWireframe)
825 {
826 ParentMaterialProxy = WireframeMaterialInstance;
827 }
828 else
829 {
830 ParentMaterialProxy = MaterialInstance->GetRenderProxy();
831 }
832#else
833 FMaterialRenderProxy* ParentMaterialProxy = MaterialInstance->GetRenderProxy();
834#endif
835
836 //FSpriteTextureOverrideRenderProxy* TextureOverrideMaterialProxy = new FSpriteTextureOverrideRenderProxy(ParentMaterialProxy,
837
838 const FMatrix& ViewportLocalToWorld = GetLocalToWorld();
839
840 FMatrix PreviousLocalToWorld;
841
842 if (!GetScene().GetPreviousLocalToWorld(GetPrimitiveSceneInfo(), PreviousLocalToWorld))
843 {
844 PreviousLocalToWorld = GetLocalToWorld();
845 }
846
847 if (RenderTarget)//false)//RenderTarget)
848 {
849 FTextureResource* TextureResource = RenderTarget->Resource;
850 if (TextureResource)
851 {
852 if (GeometryMode == EWidgetGeometryMode::Plane)
853 {
854 float U = -RenderTarget->SizeX * Pivot.X;
855 float V = -RenderTarget->SizeY * Pivot.Y;
856 float UL = RenderTarget->SizeX * (1.0f - Pivot.X);
857 float VL = RenderTarget->SizeY * (1.0f - Pivot.Y);
858
859 int32 VertexIndices[4];
860
861 for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
862 {
863 FDynamicMeshBuilder MeshBuilder(Views[ViewIndex]->GetFeatureLevel());
864
865 if (VisibilityMap & (1 << ViewIndex))
866 {
867 VertexIndices[0] = MeshBuilder.AddVertex(-FVector(0, U, V), FVector2D(0, 0), FVector(0, -1, 0), FVector(0, 0, -1), FVector(1, 0, 0), FColor::White);
868 VertexIndices[1] = MeshBuilder.AddVertex(-FVector(0, U, VL), FVector2D(0, 1), FVector(0, -1, 0), FVector(0, 0, -1), FVector(1, 0, 0), FColor::White);
869 VertexIndices[2] = MeshBuilder.AddVertex(-FVector(0, UL, VL), FVector2D(1, 1), FVector(0, -1, 0), FVector(0, 0, -1), FVector(1, 0, 0), FColor::White);
870 VertexIndices[3] = MeshBuilder.AddVertex(-FVector(0, UL, V), FVector2D(1, 0), FVector(0, -1, 0), FVector(0, 0, -1), FVector(1, 0, 0), FColor::White);
871
872 MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[1], VertexIndices[2]);
873 MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[2], VertexIndices[3]);
874
875 FDynamicMeshBuilderSettings Settings;
876 Settings.bDisableBackfaceCulling = false;
877 Settings.bReceivesDecals = true;
878 Settings.bUseSelectionOutline = true;
879 MeshBuilder.GetMesh(ViewportLocalToWorld, PreviousLocalToWorld, ParentMaterialProxy, SDPG_World, Settings, nullptr, ViewIndex, Collector, FHitProxyId());
880 }
881 }
882 }
883 else
884 {
885 ensure(GeometryMode == EWidgetGeometryMode::Cylinder);
886
887 const int32 NumSegments = FMath::Lerp(4, 32, ArcAngle / PI);
888
889
890 const float Radius = RenderTarget->SizeX / ArcAngle;
891 const float Apothem = Radius * FMath::Cos(0.5f*ArcAngle);
892 const float ChordLength = 2.0f * Radius * FMath::Sin(0.5f*ArcAngle);
893
894 const float PivotOffsetX = ChordLength * (0.5 - Pivot.X);
895 const float V = -RenderTarget->SizeY * Pivot.Y;
896 const float VL = RenderTarget->SizeY * (1.0f - Pivot.Y);
897
898 int32 VertexIndices[4];
899
900 for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
901 {
902 FDynamicMeshBuilder MeshBuilder(Views[ViewIndex]->GetFeatureLevel());
903
904 if (VisibilityMap & (1 << ViewIndex))
905 {
906 const float RadiansPerStep = ArcAngle / NumSegments;
907
908 FVector LastTangentX;
909 FVector LastTangentY;
910 FVector LastTangentZ;
911
912 for (int32 Segment = 0; Segment < NumSegments; Segment++)
913 {
914 const float Angle = -ArcAngle / 2 + Segment * RadiansPerStep;
915 const float NextAngle = Angle + RadiansPerStep;
916
917 // Polar to Cartesian
918 const float X0 = Radius * FMath::Cos(Angle) - Apothem;
919 const float Y0 = Radius * FMath::Sin(Angle);
920 const float X1 = Radius * FMath::Cos(NextAngle) - Apothem;
921 const float Y1 = Radius * FMath::Sin(NextAngle);
922
923 const float U0 = static_cast<float>(Segment) / NumSegments;
924 const float U1 = static_cast<float>(Segment + 1) / NumSegments;
925
926 const FVector Vertex0 = -FVector(X0, PivotOffsetX + Y0, V);
927 const FVector Vertex1 = -FVector(X0, PivotOffsetX + Y0, VL);
928 const FVector Vertex2 = -FVector(X1, PivotOffsetX + Y1, VL);
929 const FVector Vertex3 = -FVector(X1, PivotOffsetX + Y1, V);
930
931 FVector TangentX = Vertex3 - Vertex0;
932 TangentX.Normalize();
933 FVector TangentY = Vertex1 - Vertex0;
934 TangentY.Normalize();
935 FVector TangentZ = FVector::CrossProduct(TangentX, TangentY);
936
937 if (Segment == 0)
938 {
939 LastTangentX = TangentX;
940 LastTangentY = TangentY;
941 LastTangentZ = TangentZ;
942 }
943
944 VertexIndices[0] = MeshBuilder.AddVertex(Vertex0, FVector2D(U0, 0), LastTangentX, LastTangentY, LastTangentZ, FColor::White);
945 VertexIndices[1] = MeshBuilder.AddVertex(Vertex1, FVector2D(U0, 1), LastTangentX, LastTangentY, LastTangentZ, FColor::White);
946 VertexIndices[2] = MeshBuilder.AddVertex(Vertex2, FVector2D(U1, 1), TangentX, TangentY, TangentZ, FColor::White);
947 VertexIndices[3] = MeshBuilder.AddVertex(Vertex3, FVector2D(U1, 0), TangentX, TangentY, TangentZ, FColor::White);
948
949 MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[1], VertexIndices[2]);
950 MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[2], VertexIndices[3]);
951
952 LastTangentX = TangentX;
953 LastTangentY = TangentY;
954 LastTangentZ = TangentZ;
955 }
956
957 FDynamicMeshBuilderSettings Settings;
958 Settings.bDisableBackfaceCulling = false;
959 Settings.bReceivesDecals = true;
960 Settings.bUseSelectionOutline = true;
961 MeshBuilder.GetMesh(ViewportLocalToWorld, PreviousLocalToWorld, ParentMaterialProxy, SDPG_World, Settings, nullptr, ViewIndex, Collector, FHitProxyId());
962 }
963 }
964 }
965 }
966 }
967
968#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
969 for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
970 {
971 if (VisibilityMap & (1 << ViewIndex))
972 {
973 RenderCollision(BodySetup, Collector, ViewIndex, ViewFamily.EngineShowFlags, GetBounds(), IsSelected());
974 RenderBounds(Collector.GetPDI(ViewIndex), ViewFamily.EngineShowFlags, GetBounds(), IsSelected());
975 }
976 }
977#endif
978 }
979
980 void RenderCollision(UBodySetup* InBodySetup, FMeshElementCollector& Collector, int32 ViewIndex, const FEngineShowFlags& EngineShowFlags, const FBoxSphereBounds& InBounds, bool bRenderInEditor) const
981 {
982 if (InBodySetup)
983 {
984 bool bDrawCollision = EngineShowFlags.Collision && IsCollisionEnabled();
985
986 if (bDrawCollision && AllowDebugViewmodes())
987 {
988 // Draw simple collision as wireframe if 'show collision', collision is enabled, and we are not using the complex as the simple
989 const bool bDrawSimpleWireframeCollision = InBodySetup->CollisionTraceFlag != ECollisionTraceFlag::CTF_UseComplexAsSimple;
990
991 if (FMath::Abs(GetLocalToWorld().Determinant()) < SMALL_NUMBER)
992 {
993 // Catch this here or otherwise GeomTransform below will assert
994 // This spams so commented out
995 //UE_LOG(LogStaticMesh, Log, TEXT("Zero scaling not supported (%s)"), *StaticMesh->GetPathName());
996 }
997 else
998 {
999 const bool bDrawSolid = !bDrawSimpleWireframeCollision;
1000 const bool bProxyIsSelected = IsSelected();
1001
1002 if (bDrawSolid)
1003 {
1004 // Make a material for drawing solid collision stuff
1005 auto SolidMaterialInstance = new FColoredMaterialRenderProxy(
1006 GEngine->ShadedLevelColorationUnlitMaterial->GetRenderProxy(),
1007 GetWireframeColor()
1008 );
1009
1010 Collector.RegisterOneFrameMaterialProxy(SolidMaterialInstance);
1011
1012 FTransform GeomTransform(GetLocalToWorld());
1013 InBodySetup->AggGeom.GetAggGeom(GeomTransform, GetWireframeColor().ToFColor(true), SolidMaterialInstance, false, true, DrawsVelocity(), ViewIndex, Collector);
1014 }
1015 // wireframe
1016 else
1017 {
1018 FColor CollisionColor = FColor(157, 149, 223, 255);
1019 FTransform GeomTransform(GetLocalToWorld());
1020 InBodySetup->AggGeom.GetAggGeom(GeomTransform, GetSelectionColor(CollisionColor, bProxyIsSelected, IsHovered()).ToFColor(true), nullptr, false, false, DrawsVelocity(), ViewIndex, Collector);
1021 }
1022 }
1023 }
1024 }
1025 }
1026
1027 virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
1028 {
1029 bool bVisible = true;
1030
1031 FPrimitiveViewRelevance Result;
1032
1033 MaterialRelevance.SetPrimitiveViewRelevance(Result);
1034
1035 Result.bDrawRelevance = IsShown(View) && bVisible && View->Family->EngineShowFlags.WidgetComponents;
1036 Result.bDynamicRelevance = true;
1037 Result.bShadowRelevance = IsShadowCast(View);
1038 Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow;
1039 Result.bEditorPrimitiveRelevance = false;
1040 Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass;
1041
1042 return Result;
1043 }
1044
1045 virtual void GetLightRelevance(const FLightSceneProxy* LightSceneProxy, bool& bDynamic, bool& bRelevant, bool& bLightMapped, bool& bShadowMapped) const override
1046 {
1047 bDynamic = false;
1048 bRelevant = false;
1049 bLightMapped = false;
1050 bShadowMapped = false;
1051 }
1052
1053 virtual void OnTransformChanged() override
1054 {
1055 Origin = GetLocalToWorld().GetOrigin();
1056 }
1057
1058 virtual bool CanBeOccluded() const override
1059 {
1060 return !MaterialRelevance.bDisableDepthTest;
1061 }
1062
1063 virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); }
1064
1065 uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); }
1066
1067private:
1068 FVector Origin;
1069 FVector2D Pivot;
1070 ISlate3DRenderer& Renderer;
1071 UTextureRenderTarget2D* RenderTarget;
1072 UMaterialInstanceDynamic* MaterialInstance;
1073 FMaterialRelevance MaterialRelevance;
1074 UBodySetup* BodySetup;
1075 EWidgetBlendMode BlendMode;
1076 EWidgetGeometryMode GeometryMode;
1079};
1080
1081
1083{
1084 if (Space == EWidgetSpace::Screen)
1085 {
1086 return nullptr;
1087 }
1088
1089 if (WidgetRenderer && GetSlateWindow() && GetSlateWindow()->GetContent() != SNullWidget::NullWidget)
1090 {
1091 RequestRedraw();
1092 LastWidgetRenderTime = 0;
1093
1094 return new FStereoWidget3DSceneProxy(this, *WidgetRenderer->GetSlateRenderer());
1095 }
1096
1097 return nullptr;
1098}
1099
1101{
1102public:
1104 : FActorComponentInstanceData(SourceComponent)
1105 , RenderTarget(SourceComponent->GetRenderTarget())
1106 {}
1107
1108 virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override
1109 {
1110 FActorComponentInstanceData::ApplyToComponent(Component, CacheApplyPhase);
1111 CastChecked<UVRStereoWidgetComponent>(Component)->ApplyVRComponentInstanceData(this);
1112 }
1113
1114 /*virtual void AddReferencedObjects(FReferenceCollector& Collector) override
1115 {
1116 FActorComponentInstanceData::AddReferencedObjects(Collector);
1117
1118 UClass* WidgetUClass = *WidgetClass;
1119 Collector.AddReferencedObject(WidgetUClass);
1120 Collector.AddReferencedObject(RenderTarget);
1121 }*/
1122
1123public:
1124 UTextureRenderTarget2D* RenderTarget;
1125};
1126
1127TStructOnScope<FActorComponentInstanceData> UVRStereoWidgetComponent::GetComponentInstanceData() const
1128{
1129 return MakeStructOnScope<FActorComponentInstanceData, FVRStereoWidgetComponentInstanceData>(this);
1130}
1131
1133{
1134 check(WidgetInstanceData);
1135
1136 // Note: ApplyComponentInstanceData is called while the component is registered so the rendering thread is already using this component
1137 // That means all component state that is modified here must be mirrored on the scene proxy, which will be recreated to receive the changes later due to MarkRenderStateDirty.
1138
1139 if (GetWidgetClass() != WidgetClass)
1140 {
1141 return;
1142 }
1143
1144 RenderTarget = WidgetInstanceData->RenderTarget;
1145
1146 // Also set the texture
1147 //Texture = RenderTarget;
1148 // Not needed anymore, just using the render target directly now
1149
1150 if (MaterialInstance && RenderTarget)
1151 {
1152 MaterialInstance->SetTextureParameterValue("SlateUI", RenderTarget);
1153 }
1154
1155 MarkRenderStateDirty();
1156}
virtual bool CanBeOccluded() const override
SIZE_T GetTypeHash() const override
virtual void GetLightRelevance(const FLightSceneProxy *LightSceneProxy, bool &bDynamic, bool &bRelevant, bool &bLightMapped, bool &bShadowMapped) const override
UTextureRenderTarget2D * RenderTarget
virtual void GetDynamicMeshElements(const TArray< const FSceneView * > &Views, const FSceneViewFamily &ViewFamily, uint32 VisibilityMap, FMeshElementCollector &Collector) const override
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView *View) const override
void RenderCollision(UBodySetup *InBodySetup, FMeshElementCollector &Collector, int32 ViewIndex, const FEngineShowFlags &EngineShowFlags, const FBoxSphereBounds &InBounds, bool bRenderInEditor) const
virtual void OnTransformChanged() override
UMaterialInstanceDynamic * MaterialInstance
FStereoWidget3DSceneProxy(UVRStereoWidgetComponent *InComponent, ISlate3DRenderer &InRenderer)
virtual uint32 GetMemoryFootprint(void) const override
virtual void ApplyToComponent(UActorComponent *Component, const ECacheApplyPhase CacheApplyPhase) override
FVRStereoWidgetComponentInstanceData(const UVRStereoWidgetComponent *SourceComponent)
static bool IsInVREditorPreviewOrGame()
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true",...
UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin))
bool bUseEpicsWorldLockedStereo
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer")
bool bIsSleeping
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer")
virtual TStructOnScope< FActorComponentInstanceData > GetComponentInstanceData() const override
bool bDelayForRenderThread
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer")
void SetPriority(int32 InPriority)
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
virtual void DrawWidgetToRenderTarget(float DeltaTime) override
bool bDrawWithoutStereo
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer")
void ApplyVRComponentInstanceData(class FVRStereoWidgetComponentInstanceData *WidgetInstanceData)
UStereoLayerShape * Shape
UPROPERTY(EditAnywhere, BlueprintReadOnly, NoClear, Instanced, Category = "StereoLayer",...
virtual FPrimitiveSceneProxy * CreateSceneProxy() override
int32 Priority
UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer | Cylinder Overlay Propert...
bool bRenderBothStereoAndWorld
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer")
uint32 bQuadPreserveTextureRatio
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer")
uint32 bSupportsDepth
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer")
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override
UVRStereoWidgetComponent(const FObjectInitializer &ObjectInitializer)
uint32 bNoAlphaChannel
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer")
virtual void UpdateRenderTarget(FIntPoint DesiredRenderTargetSize) override
bool bDrawWithoutStereo
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true...
void SetWidgetAndInit(TSubclassOf< UUserWidget > NewWidgetClass)
UFUNCTION(BlueprintCallable, Category = "WidgetSettings")
UUserWidget * Widget
UPROPERTY(Transient, DuplicateTransient)
UTextureRenderTarget2D * RenderTarget
UPROPERTY(BlueprintReadOnly, Transient, DuplicateTransient, Category = "WidgetSettings")
void OnLevelRemovedFromWorld(ULevel *InLevel, UWorld *InWorld)
TSharedPtr< class SVirtualWindow > SlateWindow
bool bUseGammaCorrection
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true...
TSubclassOf< UUserWidget > WidgetClass
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true...
float WidgetRenderGamma
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true...
float WidgetRenderScale
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true...
float DrawRate
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true...
bool bDrawAtDesiredSize
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true...
virtual void DestroyComponent(bool bPromoteChildren) override
FLinearColor RenderTargetClearColor
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true...
UVRStereoWidgetRenderComponent(const FObjectInitializer &ObjectInitializer)
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override
FAutoConsoleVariableRef CVarForceNoStereoWithVRWidgets(TEXT("vr.ForceNoStereoWithVRWidgets"), ForceNoStereoWithVRWidgets, TEXT("0: Default, 1: Force no stereo, 2: Render both at once"), ECVF_Default)
static int32 ForceNoStereoWithVRWidgets