Files
UnrealEngine/Engine/Source/Runtime/OpenGLDrv/Private/OpenGLViewport.cpp
2025-05-18 13:04:45 +08:00

331 lines
9.4 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
OpenGLViewport.cpp: OpenGL viewport RHI implementation.
=============================================================================*/
#include "CoreMinimal.h"
#include "Stats/Stats.h"
#include "HAL/IConsoleManager.h"
#include "RHI.h"
#include "OpenGLDrv.h"
#include "OpenGLDrvPrivate.h"
static int32 GOpenGLPerFrameErrorCheck = 1;
static FAutoConsoleVariableRef CVarPerFrameGLErrorCheck(
TEXT("r.OpenGL.PerFrameErrorCheck"),
GOpenGLPerFrameErrorCheck,
TEXT("When no other GL debugging is in use, check for GL errors once per frame.\nNot active in shipping builds.\n")
TEXT("0: GL errors not be checked.\n")
TEXT("1: any GL errors will be logged as errors. (default)\n")
TEXT("2: any GL errors will be fatal.\n")
,
ECVF_RenderThreadSafe
);
static void CheckForGLErrors()
{
#if !UE_BUILD_SHIPPING
if (GOpenGLPerFrameErrorCheck && IsOGLDebugOutputEnabled() == false && !ENABLE_VERIFY_GL)
{
int32 Error = PlatformGlGetError();
if (Error != GL_NO_ERROR)
{
switch (GOpenGLPerFrameErrorCheck)
{
case 1:
UE_LOG(LogRHI, Error, TEXT("GL Error encountered during frame %d, glerror=0x%x. Set command line arg -OpenGLDebugLevel=1 for detailed debugging."), GFrameNumber, Error);
break;
default: checkNoEntry(); [[fallthrough]];
case 2:
UE_LOG(LogRHI, Fatal, TEXT("GL Error encountered during frame %d, glerror=0x%x. Set command line arg -OpenGLDebugLevel=1 for detailed debugging."), GFrameNumber, Error);
break;
}
}
}
#endif
}
void FOpenGLDynamicRHI::RHIGetSupportedResolution(uint32 &Width, uint32 &Height)
{
PlatformGetSupportedResolution(Width, Height);
}
bool FOpenGLDynamicRHI::RHIGetAvailableResolutions(FScreenResolutionArray& Resolutions, bool bIgnoreRefreshRate)
{
const bool Result = PlatformGetAvailableResolutions(Resolutions, bIgnoreRefreshRate);
if (Result)
{
Resolutions.Sort([](const FScreenResolutionRHI& L, const FScreenResolutionRHI& R)
{
if (L.Width != R.Width)
{
return L.Width < R.Width;
}
else if (L.Height != R.Height)
{
return L.Height < R.Height;
}
else
{
return L.RefreshRate < R.RefreshRate;
}
});
}
return Result;
}
/*=============================================================================
* The following RHI functions must be called from the main thread.
*=============================================================================*/
FViewportRHIRef FOpenGLDynamicRHI::RHICreateViewport(void* WindowHandle,uint32 SizeX,uint32 SizeY,bool bIsFullscreen,EPixelFormat PreferredPixelFormat)
{
check(IsInGameThread());
// Use a default pixel format if none was specified
PreferredPixelFormat = RHIPreferredPixelFormatHint(PreferredPixelFormat);
return new FOpenGLViewport(this,WindowHandle,SizeX,SizeY,bIsFullscreen,PreferredPixelFormat);
}
void FOpenGLDynamicRHI::RHIResizeViewport(FRHIViewport* ViewportRHI,uint32 SizeX,uint32 SizeY,bool bIsFullscreen)
{
FOpenGLViewport* Viewport = ResourceCast(ViewportRHI);
check( IsInGameThread() );
Viewport->Resize(SizeX,SizeY,bIsFullscreen);
}
EPixelFormat FOpenGLDynamicRHI::RHIPreferredPixelFormatHint(EPixelFormat PreferredPixelFormat)
{
return FOpenGL::PreferredPixelFormatHint(PreferredPixelFormat);
}
void FOpenGLDynamicRHI::RHITick( float DeltaTime )
{
}
/*=============================================================================
* Viewport functions.
*=============================================================================*/
void FOpenGLDynamicRHI::RHIBeginDrawingViewport(FRHIViewport* ViewportRHI, FRHITexture* RenderTarget)
{
VERIFY_GL_SCOPE();
FOpenGLViewport* Viewport = ResourceCast(ViewportRHI);
SCOPE_CYCLE_COUNTER(STAT_OpenGLPresentTime);
check(!DrawingViewport);
DrawingViewport = Viewport;
// Set the render target and viewport.
if (RenderTarget)
{
FRHIRenderTargetView RTV(RenderTarget, ERenderTargetLoadAction::ELoad);
SetRenderTargets(1, &RTV, nullptr);
}
else
{
FRHIRenderTargetView RTV(DrawingViewport->GetBackBuffer(), ERenderTargetLoadAction::ELoad);
SetRenderTargets(1, &RTV, nullptr);
}
if (IsValidRef(CustomPresent))
{
CustomPresent->BeginDrawing();
}
}
void FOpenGLDynamicRHI::RHIEndDrawingViewport(FRHIViewport* ViewportRHI,bool bPresent,bool bLockToVsync)
{
VERIFY_GL_SCOPE();
FOpenGLViewport* Viewport = ResourceCast(ViewportRHI);
SCOPE_CYCLE_COUNTER(STAT_OpenGLPresentTime);
{
FRenderThreadIdleScope IdleScope(ERenderThreadIdleTypes::WaitingForGPUPresent);
check(DrawingViewport.GetReference() == Viewport);
FOpenGLTexture* BackBuffer = Viewport->GetBackBuffer();
if (ContextState.bScissorEnabled)
{
ContextState.bScissorEnabled = false;
glDisable(GL_SCISSOR_TEST);
}
bool bNeedFinishFrame = PlatformBlitToViewport(*this, PlatformDevice,
*Viewport,
BackBuffer->GetSizeX(),
BackBuffer->GetSizeY(),
bPresent,
bLockToVsync
);
// Always consider the Framebuffer in the rendering context dirty after the blit
ContextState.Framebuffer = -1;
DrawingViewport = NULL;
if (bNeedFinishFrame)
{
static const auto CFinishFrameVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.FinishCurrentFrame"));
if (!CFinishFrameVar->GetValueOnRenderThread())
{
// Wait for the GPU to finish rendering the previous frame before finishing this frame.
Viewport->WaitForFrameEventCompletion();
Viewport->IssueFrameEvent();
}
else
{
// Finish current frame immediately to reduce latency
Viewport->IssueFrameEvent();
Viewport->WaitForFrameEventCompletion();
}
}
// If the input latency timer has been triggered, block until the GPU is completely
// finished displaying this frame and calculate the delta time.
if ( GInputLatencyTimer.RenderThreadTrigger )
{
Viewport->WaitForFrameEventCompletion();
uint32 EndTime = FPlatformTime::Cycles();
GInputLatencyTimer.DeltaTime = EndTime - GInputLatencyTimer.StartTime;
GInputLatencyTimer.RenderThreadTrigger = false;
}
}
EndFrameTick();
CheckForGLErrors();
}
FTextureRHIRef FOpenGLDynamicRHI::RHIGetViewportBackBuffer(FRHIViewport* ViewportRHI)
{
FOpenGLViewport* Viewport = ResourceCast(ViewportRHI);
return Viewport->GetBackBuffer();
}
void FOpenGLDynamicRHI::RHIAdvanceFrameForGetViewportBackBuffer(FRHIViewport* Viewport)
{
}
FOpenGLViewport::FOpenGLViewport(FOpenGLDynamicRHI* InOpenGLRHI,void* InWindowHandle,uint32 InSizeX,uint32 InSizeY,bool bInIsFullscreen,EPixelFormat PreferredPixelFormat)
: OpenGLRHI(InOpenGLRHI)
, OpenGLContext(NULL)
, SizeX(0)
, SizeY(0)
, bIsFullscreen(false)
, PixelFormat(PreferredPixelFormat)
, bIsValid(true)
{
check(OpenGLRHI);
// @todo lumin: Add a "PLATFORM_HAS_NO_NATIVE_WINDOW" or something
#if !PLATFORM_ANDROID
check(InWindowHandle);
#endif
check(IsInGameThread());
// flush out old errors.
PlatformGlGetError();
OpenGLRHI->Viewports.Add(this);
OpenGLContext = PlatformCreateOpenGLContext(OpenGLRHI->PlatformDevice, InWindowHandle);
Resize(InSizeX, InSizeY, bInIsFullscreen);
ENQUEUE_RENDER_COMMAND(CreateFrameSyncEvent)([this](FRHICommandListImmediate& RHICmdList)
{
RHICmdList.EnqueueLambda([this](FRHICommandListImmediate&)
{
FrameSyncEvent = MakeUnique<FOpenGLEventQuery>();
});
});
}
FOpenGLViewport::~FOpenGLViewport()
{
VERIFY_GL_SCOPE();
if (bIsFullscreen)
{
PlatformRestoreDesktopDisplayMode();
}
// Release back buffer, before OpenGL context becomes invalid, making it impossible
BackBuffer.SafeRelease();
check(!IsValidRef(BackBuffer));
FrameSyncEvent = nullptr;
PlatformDestroyOpenGLContext(OpenGLRHI->PlatformDevice, OpenGLContext);
OpenGLContext = NULL;
OpenGLRHI->Viewports.Remove(this);
}
void FOpenGLViewport::WaitForFrameEventCompletion()
{
VERIFY_GL_SCOPE();
FrameSyncEvent->WaitForCompletion();
}
void FOpenGLViewport::IssueFrameEvent()
{
VERIFY_GL_SCOPE();
FrameSyncEvent->IssueEvent();
}
void FOpenGLViewport::Resize(uint32 InSizeX,uint32 InSizeY,bool bInIsFullscreen)
{
check(IsInGameThread());
if ((InSizeX == SizeX) && (InSizeY == SizeY) && (bInIsFullscreen == bIsFullscreen))
{
return;
}
SizeX = InSizeX;
SizeY = InSizeY;
bool bWasFullscreen = bIsFullscreen;
bIsFullscreen = bInIsFullscreen;
ENQUEUE_RENDER_COMMAND(ResizeViewport)([this, InSizeX, InSizeY, bInIsFullscreen, bWasFullscreen](FRHICommandListImmediate& RHICmdList)
{
if (IsValidRef(CustomPresent))
{
CustomPresent->OnBackBufferResize();
}
BackBuffer.SafeRelease(); // when the rest of the engine releases it, its framebuffers will be released too (those the engine knows about)
BackBuffer = PlatformCreateBuiltinBackBuffer(OpenGLRHI, InSizeX, InSizeY);
if (!BackBuffer)
{
const FRHITextureCreateDesc Desc =
FRHITextureCreateDesc::Create2D(TEXT("FOpenGLViewport"), InSizeX, InSizeY, PixelFormat)
.SetClearValue(FClearValueBinding::Transparent)
.SetFlags(ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::ResolveTargetable)
.DetermineInititialState();
BackBuffer = new FOpenGLTexture(Desc);
BackBuffer->Initialize(RHICmdList);
}
RHICmdList.EnqueueLambda([this, InSizeX, InSizeY, bInIsFullscreen, bWasFullscreen](FRHICommandListImmediate&)
{
PlatformResizeGLContext(OpenGLRHI->PlatformDevice, OpenGLContext, InSizeX, InSizeY, bInIsFullscreen, bWasFullscreen, BackBuffer->Target, BackBuffer->GetResource());
});
});
}
void* FOpenGLViewport::GetNativeWindow(void** AddParam) const
{
return PlatformGetWindow(OpenGLContext, AddParam);
}