// 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" 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()); // SCOPED_SUSPEND_RENDERING_THREAD(true); // 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() ); // SCOPED_SUSPEND_RENDERING_THREAD(true); 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; bRevertToSharedContextAfterDrawingViewport = false; EOpenGLCurrentContext CurrentContext = PlatformOpenGLCurrentContext( PlatformDevice ); if( CurrentContext != CONTEXT_Rendering ) { check(CurrentContext == CONTEXT_Shared); check(!bIsRenderingContextAcquired || !GUseThreadedRendering); bRevertToSharedContextAfterDrawingViewport = true; PlatformRenderingContextSetup(PlatformDevice); } // 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); uint32 IdleStart = FPlatformTime::Cycles(); check(DrawingViewport.GetReference() == Viewport); FOpenGLTexture* BackBuffer = Viewport->GetBackBuffer(); FOpenGLContextState& ContextState = GetContextStateForCurrentContext(); if (ContextState.bScissorEnabled) { ContextState.bScissorEnabled = false; glDisable(GL_SCISSOR_TEST); } bool bNeedFinishFrame = PlatformBlitToViewport(PlatformDevice, *Viewport, BackBuffer->GetSizeX(), BackBuffer->GetSizeY(), bPresent, bLockToVsync ); // Always consider the Framebuffer in the rendering context dirty after the blit RenderingContextState.Framebuffer = -1; DrawingViewport = NULL; // Don't wait on the GPU when using SLI, let the driver determine how many frames behind the GPU should be allowed to get if (GNumAlternateFrameRenderingGroups == 1) { 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; } } if (bRevertToSharedContextAfterDrawingViewport) { PlatformSharedContextSetup(PlatformDevice); bRevertToSharedContextAfterDrawingViewport = false; } uint32 ThisCycles = FPlatformTime::Cycles() - IdleStart; if (IsInRHIThread()) { GWorkingRHIThreadStallTime += ThisCycles; } else if (IsInActualRenderingThread()) { GRenderThreadIdle[ERenderThreadIdleTypes::WaitingForGPUPresent] += ThisCycles; GRenderThreadNumIdle[ERenderThreadIdleTypes::WaitingForGPUPresent]++; } // TODO: find better location to poll this, or create programs on separate thread. Gil had a prototype of this. FOpenGLProgramBinaryCache::CheckPendingGLProgramCreateRequests(); FTextureEvictionLRU::Get().TickEviction(); } FTexture2DRHIRef 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); check(PlatformOpenGLCurrentContext(OpenGLRHI->PlatformDevice) == CONTEXT_Shared); OpenGLContext = PlatformCreateOpenGLContext(OpenGLRHI->PlatformDevice, InWindowHandle); Resize(InSizeX, InSizeY, bInIsFullscreen); check(PlatformOpenGLCurrentContext(OpenGLRHI->PlatformDevice) == CONTEXT_Shared); ENQUEUE_RENDER_COMMAND(CreateFrameSyncEvent)([this](FRHICommandListImmediate& RHICmdList) { RunOnGLRenderContextThread([this]() { FrameSyncEvent = MakeUnique(); }); }); } FOpenGLViewport::~FOpenGLViewport() { check(IsInRenderingThread() || IsInRHIThread()); if (bIsFullscreen) { PlatformRestoreDesktopDisplayMode(); } // Release back buffer, before OpenGL context becomes invalid, making it impossible BackBuffer.SafeRelease(); check(!IsValidRef(BackBuffer)); RunOnGLRenderContextThread([&]() { FrameSyncEvent = nullptr; PlatformDestroyOpenGLContext(OpenGLRHI->PlatformDevice, OpenGLContext); }, true); 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); } RHICmdList.EnqueueLambda([=](FRHICommandListImmediate&) { PlatformResizeGLContext(OpenGLRHI->PlatformDevice, OpenGLContext, InSizeX, InSizeY, bInIsFullscreen, bWasFullscreen, BackBuffer->Target, BackBuffer->GetResource()); }); }); } void* FOpenGLViewport::GetNativeWindow(void** AddParam) const { return PlatformGetWindow(OpenGLContext, AddParam); }