823 lines
22 KiB
C++
823 lines
22 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LinuxOpenGLPlatform.h"
|
|
|
|
#include "Misc/ScopeLock.h"
|
|
#include "OpenGLDrv.h"
|
|
#include <SDL3/SDL.h>
|
|
#include "OpenGLDrvPrivate.h"
|
|
#include "ComponentReregisterContext.h"
|
|
#include "Linux/LinuxPlatformApplicationMisc.h"
|
|
#include "GenericPlatform/GenericPlatformFramePacer.h"
|
|
#include "RHIUtilities.h"
|
|
|
|
typedef SDL_Window* SDL_HWindow;
|
|
typedef SDL_GLContext SDL_HGLContext;
|
|
|
|
/*------------------------------------------------------------------------------
|
|
OpenGL context management.
|
|
------------------------------------------------------------------------------*/
|
|
void Linux_ContextMakeCurrent(SDL_HWindow hWnd, SDL_HGLContext hGLDC)
|
|
{
|
|
GLint Result = SDL_GL_MakeCurrent(hWnd, hGLDC);
|
|
if (Result != 0)
|
|
{
|
|
// this is a warning and not error, since Slate sometimes destroys windows before
|
|
// releasing RHI resources associated with them. This code can result in leaks - proper resolution is tracked as UE-7388
|
|
FString SdlError(UTF8_TO_TCHAR(SDL_GetError()));
|
|
UE_LOG(LogLinux, Warning, TEXT("SDL_GL_MakeCurrent() failed, SDL error: '%s'"), *SdlError);
|
|
}
|
|
}
|
|
|
|
SDL_HGLContext Linux_GetCurrentContext()
|
|
{
|
|
return SDL_GL_GetCurrentContext();
|
|
}
|
|
|
|
/** Platform specific OpenGL context. */
|
|
struct FPlatformOpenGLContext
|
|
{
|
|
SDL_HWindow hWnd;
|
|
SDL_HGLContext hGLContext; // this is a (void*) pointer
|
|
|
|
bool bReleaseWindowOnDestroy;
|
|
int32 SyncInterval;
|
|
GLuint ViewportFramebuffer;
|
|
GLuint VertexArrayObject; // one has to be generated and set for each context (OpenGL 3.2 Core requirements)
|
|
};
|
|
|
|
|
|
class FScopeContext
|
|
{
|
|
public:
|
|
FScopeContext(FPlatformOpenGLContext* Context)
|
|
{
|
|
check(Context);
|
|
hPreWnd = SDL_GL_GetCurrentWindow();
|
|
hPreGLContext = SDL_GL_GetCurrentContext();
|
|
|
|
bSameDCAndContext = (hPreGLContext == Context->hGLContext);
|
|
|
|
if (!bSameDCAndContext)
|
|
{
|
|
if (hPreGLContext)
|
|
{
|
|
glFlush();
|
|
}
|
|
// no need to glFlush() on Windows, it does flush by itself before switching contexts
|
|
|
|
Linux_ContextMakeCurrent(Context->hWnd, Context->hGLContext);
|
|
}
|
|
}
|
|
|
|
~FScopeContext(void)
|
|
{
|
|
if (!bSameDCAndContext)
|
|
{
|
|
glFlush(); // not needed on Windows, it does flush by itself before switching contexts
|
|
if (hPreGLContext)
|
|
{
|
|
Linux_ContextMakeCurrent(hPreWnd, hPreGLContext);
|
|
}
|
|
else
|
|
{
|
|
Linux_ContextMakeCurrent(NULL, NULL);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private:
|
|
SDL_HWindow hPreWnd;
|
|
SDL_HGLContext hPreGLContext; // this is a pointer, (void*)
|
|
bool bSameDCAndContext;
|
|
};
|
|
|
|
/**
|
|
* Create a dummy window used to construct OpenGL contexts.
|
|
*/
|
|
void Linux_PlatformCreateDummyGLWindow(FPlatformOpenGLContext* OutContext)
|
|
{
|
|
static bool bInitializedWindowClass = false;
|
|
|
|
// Create a dummy window.
|
|
SDL_HWindow DummyWindow = SDL_CreateWindow(NULL,
|
|
1, 1,
|
|
SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_HIDDEN | SDL_WINDOW_UTILITY );
|
|
if (DummyWindow == nullptr)
|
|
{
|
|
FString SdlError(UTF8_TO_TCHAR(SDL_GetError()));
|
|
|
|
FText ErrorMessage = FText::Format(NSLOCTEXT("Renderer", "LinuxCannotCreatePlatformWindow", "Cannot create OpenGL-enabled SDL window. SDL error: '{0}'."), FText::FromString(SdlError));
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok,
|
|
*ErrorMessage.ToString(),
|
|
*(NSLOCTEXT("Renderer", "LinuxCannotCreatePlatformWindowTitle", "Cannot create SDL window.").ToString()));
|
|
FPlatformMisc::RequestExit(true, TEXT("Linux_PlatformCreateDummyGLWindow"));
|
|
// unreachable
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
SDL_SetWindowTitle(DummyWindow, "UnrealEditor Dummy GL window");
|
|
}
|
|
|
|
OutContext->hWnd = DummyWindow;
|
|
OutContext->bReleaseWindowOnDestroy = true;
|
|
}
|
|
|
|
/**
|
|
* Enable/Disable debug context from the commandline
|
|
*/
|
|
bool Linux_PlatformOpenGLDebugCtx()
|
|
{
|
|
return IsOGLDebugOutputEnabled();
|
|
}
|
|
|
|
/**
|
|
* Create a core profile OpenGL context.
|
|
*/
|
|
void Linux_PlatformCreateOpenGLContextCore(FPlatformOpenGLContext* OutContext)
|
|
{
|
|
check(OutContext);
|
|
|
|
SDL_HWindow prevWindow = SDL_GL_GetCurrentWindow();
|
|
SDL_HGLContext prevContext = SDL_GL_GetCurrentContext();
|
|
|
|
OutContext->SyncInterval = -1; // invalid value to enforce setup on first buffer swap
|
|
OutContext->ViewportFramebuffer = 0;
|
|
|
|
OutContext->hGLContext = SDL_GL_CreateContext(OutContext->hWnd);
|
|
if (OutContext->hGLContext == nullptr)
|
|
{
|
|
FString SdlError(UTF8_TO_TCHAR(SDL_GetError()));
|
|
|
|
// ignore errors getting version, it will be clear from the logs
|
|
int OpenGLMajorVersion = -1;
|
|
int OpenGLMinorVersion = -1;
|
|
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &OpenGLMajorVersion);
|
|
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &OpenGLMinorVersion);
|
|
|
|
UE_LOG(LogInit, Error, TEXT("Linux_PlatformCreateOpenGLContextCore - Could not create OpenGL %d.%d context, SDL error: '%s'"),
|
|
OpenGLMajorVersion, OpenGLMinorVersion,
|
|
*SdlError
|
|
);
|
|
// unreachable
|
|
return;
|
|
}
|
|
|
|
SDL_GL_MakeCurrent(prevWindow, prevContext);
|
|
}
|
|
|
|
void PlatformReleaseOpenGLContext(FPlatformOpenGLDevice* Device, FPlatformOpenGLContext* Context);
|
|
|
|
/** Platform specific OpenGL device. */
|
|
struct FPlatformOpenGLDevice
|
|
{
|
|
static FPlatformOpenGLDevice* Singleton;
|
|
|
|
FPlatformOpenGLContext RenderingContext;
|
|
|
|
/** Guards against operating on viewport contexts from more than one thread at the same time. */
|
|
FCriticalSection* ContextUsageGuard;
|
|
|
|
FPlatformOpenGLDevice()
|
|
{
|
|
check(!Singleton);
|
|
Singleton = this;
|
|
|
|
extern void InitDebugContext();
|
|
|
|
ContextUsageGuard = new FCriticalSection;
|
|
|
|
verifyf(SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0) == 0, TEXT("SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0) failed: %s\n."), UTF8_TO_TCHAR(SDL_GetError()));
|
|
|
|
Linux_PlatformCreateDummyGLWindow(&RenderingContext);
|
|
Linux_PlatformCreateOpenGLContextCore(&RenderingContext);
|
|
|
|
if (RenderingContext.hGLContext == nullptr)
|
|
{
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok,
|
|
*(NSLOCTEXT("Renderer", "LinuxInsufficientDriversText", "Cannot create OpenGL context. Check that the drivers and hardware support at least OpenGL 4.3 (or re-run with -opengl3)").ToString()),
|
|
*(NSLOCTEXT("Renderer", "LinuxInsufficientDriversTitle", "Insufficient drivers or hardware").ToString()));
|
|
FPlatformMisc::RequestExit(true, TEXT("FPlatformOpenGLDevice()"));
|
|
// unreachable
|
|
return;
|
|
}
|
|
|
|
Linux_ContextMakeCurrent(RenderingContext.hWnd, RenderingContext.hGLContext);
|
|
|
|
InitDebugContext();
|
|
glGenVertexArrays(1, &RenderingContext.VertexArrayObject);
|
|
glBindVertexArray(RenderingContext.VertexArrayObject);
|
|
InitDefaultGLContextState();
|
|
|
|
verifyf(SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1) == 0, TEXT("SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1) failed: %s\n."), UTF8_TO_TCHAR(SDL_GetError()));
|
|
}
|
|
|
|
~FPlatformOpenGLDevice()
|
|
{
|
|
Linux_ContextMakeCurrent(NULL, NULL);
|
|
|
|
PlatformReleaseOpenGLContext(this, &RenderingContext);
|
|
|
|
delete ContextUsageGuard;
|
|
|
|
check(Singleton == this);
|
|
Singleton = nullptr;
|
|
}
|
|
};
|
|
|
|
FPlatformOpenGLDevice* FPlatformOpenGLDevice::Singleton = nullptr;
|
|
|
|
FPlatformOpenGLDevice* PlatformCreateOpenGLDevice()
|
|
{
|
|
return new FPlatformOpenGLDevice;
|
|
}
|
|
|
|
bool PlatformCanEnableGPUCapture()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void PlatformDestroyOpenGLDevice(FPlatformOpenGLDevice* Device)
|
|
{
|
|
delete Device;
|
|
}
|
|
|
|
/**
|
|
* Create an OpenGL context.
|
|
*/
|
|
FPlatformOpenGLContext* PlatformCreateOpenGLContext(FPlatformOpenGLDevice* Device, void* InWindowHandle)
|
|
{
|
|
check(InWindowHandle);
|
|
FPlatformOpenGLContext* Context = new FPlatformOpenGLContext;
|
|
|
|
Context->hWnd = (SDL_HWindow)InWindowHandle;
|
|
Context->bReleaseWindowOnDestroy = false;
|
|
|
|
check(Device->RenderingContext.hGLContext);
|
|
|
|
{
|
|
FScopeContext Scope(&Device->RenderingContext);
|
|
verifyf(SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1) == 0, TEXT("SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1) failed: %s\n."), UTF8_TO_TCHAR(SDL_GetError()));
|
|
Linux_PlatformCreateOpenGLContextCore(Context);
|
|
}
|
|
|
|
check(Context->hGLContext);
|
|
{
|
|
FScopeContext Scope(Context);
|
|
InitDefaultGLContextState();
|
|
}
|
|
|
|
return Context;
|
|
}
|
|
|
|
/**
|
|
* Release an OpenGL context.
|
|
*/
|
|
void PlatformReleaseOpenGLContext(FPlatformOpenGLDevice* Device, FPlatformOpenGLContext* Context)
|
|
{
|
|
check(Context && Context->hGLContext);
|
|
|
|
{
|
|
FScopeLock ScopeLock(Device->ContextUsageGuard);
|
|
{
|
|
FScopeContext ScopeContext(Context);
|
|
|
|
glBindVertexArray(0);
|
|
glDeleteVertexArrays(1, &Context->VertexArrayObject);
|
|
|
|
if (Context->ViewportFramebuffer)
|
|
{
|
|
glDeleteFramebuffers(1, &Context->ViewportFramebuffer); // this can be done from any context shared with ours, as long as it's not nil.
|
|
Context->ViewportFramebuffer = 0;
|
|
}
|
|
}
|
|
|
|
SDL_GL_DestroyContext(Context->hGLContext);
|
|
Context->hGLContext = NULL;
|
|
}
|
|
|
|
check(Context->hWnd);
|
|
|
|
if (Context->bReleaseWindowOnDestroy)
|
|
{
|
|
SDL_DestroyWindow(Context->hWnd);
|
|
}
|
|
|
|
Context->hWnd = NULL;
|
|
}
|
|
|
|
/**
|
|
* Destroy an OpenGL context.
|
|
*/
|
|
void PlatformDestroyOpenGLContext(FPlatformOpenGLDevice* Device, FPlatformOpenGLContext* Context)
|
|
{
|
|
PlatformReleaseOpenGLContext(Device, Context);
|
|
delete Context;
|
|
}
|
|
|
|
void* PlatformGetWindow(FPlatformOpenGLContext* Context, void** AddParam)
|
|
{
|
|
check(Context && Context->hWnd);
|
|
|
|
if (AddParam)
|
|
{
|
|
*AddParam = &Context->hGLContext;
|
|
}
|
|
|
|
return (void*)&Context->hWnd;
|
|
}
|
|
|
|
const TCHAR* PlatformDescribeSyncInterval(int32 SyncInterval)
|
|
{
|
|
switch (SyncInterval)
|
|
{
|
|
case -1:
|
|
return TEXT("Late swap");
|
|
case 0:
|
|
return TEXT("Immediate");
|
|
case 1:
|
|
return TEXT("Synchronized with retrace");
|
|
default: break;
|
|
}
|
|
return TEXT("Unknown");
|
|
}
|
|
|
|
/**
|
|
* Main function for transferring data to on-screen buffers.
|
|
* On Windows it temporarily switches OpenGL context, on Mac only context's output view.
|
|
*/
|
|
bool PlatformBlitToViewport(IRHICommandContext& RHICmdContext,
|
|
FPlatformOpenGLDevice* Device,
|
|
const FOpenGLViewport& Viewport,
|
|
uint32 BackbufferSizeX,
|
|
uint32 BackbufferSizeY,
|
|
bool bPresent,
|
|
bool bLockToVsync)
|
|
{
|
|
FPlatformOpenGLContext* const Context = Viewport.GetGLContext();
|
|
|
|
check(Context && Context->hWnd);
|
|
|
|
FScopeLock ScopeLock(Device->ContextUsageGuard);
|
|
|
|
int32 SyncInterval = (int32)RHIGetSyncInterval();
|
|
|
|
{
|
|
FScopeContext ScopeContext(Context);
|
|
|
|
if (Viewport.GetCustomPresent())
|
|
{
|
|
glDisable(GL_FRAMEBUFFER_SRGB);
|
|
bool bShouldPresent = Viewport.GetCustomPresent()->Present(RHICmdContext, SyncInterval);
|
|
glEnable(GL_FRAMEBUFFER_SRGB);
|
|
if (!bShouldPresent)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
glDrawBuffer(GL_BACK);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, Context->ViewportFramebuffer);
|
|
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
|
glDisable(GL_FRAMEBUFFER_SRGB);
|
|
|
|
int WinW = 0;
|
|
int WinH = 0;
|
|
SDL_GetWindowSizeInPixels(Context->hWnd, &WinW, &WinH);
|
|
GLenum BlitFilter;
|
|
GLint DestX0, DestY0, DestX1, DestY1;
|
|
|
|
if (WinH == 0 || WinW == 0)
|
|
{
|
|
// Nothing to blit
|
|
return false;
|
|
}
|
|
|
|
if ((WinW == BackbufferSizeX) && (WinH == BackbufferSizeY))
|
|
{
|
|
// We match up. We're probably in windowed mode, or an exact
|
|
// match for FULLSCREEN_DESKTOP mode. Use a NEAREST blit and
|
|
// don't clear the window system's framebuffer first.
|
|
BlitFilter = GL_NEAREST;
|
|
DestX0 = 0;
|
|
DestY0 = BackbufferSizeY; // flip vertically.
|
|
DestX1 = BackbufferSizeX;
|
|
DestY1 = 0;
|
|
}
|
|
else
|
|
{
|
|
// we need to scale to match the size of the window system's
|
|
// framebuffer, so scale linearly, and adjust for letterboxing.
|
|
BlitFilter = GL_LINEAR;
|
|
|
|
const uint32 w = BackbufferSizeX;
|
|
const uint32 h = BackbufferSizeY;
|
|
const float WantedAspect = (w > h) ? (((float)w) / ((float)h)) : (((float)h) / ((float)w));
|
|
const float PhysicalAspect = (((float)WinW) / ((float)WinH));
|
|
|
|
bool bMustClear; // have to clear the window framebuffer if letterboxing.
|
|
if (PhysicalAspect == WantedAspect) // Perfect aspect ratio; no letterboxing needed?
|
|
{
|
|
bMustClear = false;
|
|
DestX0 = 0;
|
|
DestY0 = WinH; // flip vertically.
|
|
DestX1 = WinW;
|
|
DestY1 = 0;
|
|
}
|
|
else if (PhysicalAspect > WantedAspect) // view is wider than wanted aspect?
|
|
{
|
|
bMustClear = true;
|
|
const float ScaledW = WinH * WantedAspect;
|
|
const float ScaledX = (((float)WinW) - ScaledW) / 2.0f;
|
|
DestX0 = ScaledX;
|
|
DestY0 = WinH; // flip vertically.
|
|
DestX1 = ScaledX + ScaledW;
|
|
DestY1 = 0;
|
|
}
|
|
else // view is taller than wanted aspect?
|
|
{
|
|
bMustClear = true;
|
|
const float ScaledH = WinW / WantedAspect;
|
|
const float ScaledY = (((float)WinH) - ScaledH) / 2.0f;
|
|
DestX0 = 0;
|
|
DestY0 = ScaledY + ScaledH; // flip vertically.
|
|
DestX1 = WinW;
|
|
DestY1 = ScaledY;
|
|
}
|
|
|
|
// if the Steam Overlay is running, it might write garbage into our
|
|
// letterbox area, so if we have a letterbox, clear the framebuffer.
|
|
if (bMustClear)
|
|
{
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
}
|
|
}
|
|
|
|
// Get it to the window system's framebuffer.
|
|
glBlitFramebuffer(0, 0, BackbufferSizeX, BackbufferSizeY,
|
|
DestX0, DestY0, DestX1, DestY1,
|
|
GL_COLOR_BUFFER_BIT,
|
|
BlitFilter);
|
|
|
|
if (bPresent)
|
|
{
|
|
int32 RealSyncInterval = bLockToVsync ? SyncInterval : 0;
|
|
|
|
if (Context->SyncInterval != RealSyncInterval)
|
|
{
|
|
// 0 for immediate updates
|
|
// 1 for updates synchronized with the vertical retrace
|
|
// -1 for late swap tearing;
|
|
|
|
UE_LOG(LogLinux, Log, TEXT("Setting swap interval to '%s'"), PlatformDescribeSyncInterval(RealSyncInterval));
|
|
int SetSwapResult = SDL_GL_SetSwapInterval(RealSyncInterval);
|
|
// if late tearing is not supported, this needs to be retried with a valid value.
|
|
if (SetSwapResult == -1)
|
|
{
|
|
if (RealSyncInterval == -1)
|
|
{
|
|
// fallback to synchronized
|
|
int FallbackInterval = 1;
|
|
UE_LOG(LogLinux, Log, TEXT("Unable to set desired swap interval, falling back to '%s'"), PlatformDescribeSyncInterval(FallbackInterval));
|
|
SetSwapResult = SDL_GL_SetSwapInterval(FallbackInterval);
|
|
}
|
|
}
|
|
|
|
if (SetSwapResult == -1)
|
|
{
|
|
UE_LOG(LogLinux, Warning, TEXT("Unable to set desired swap interval '%s'"), PlatformDescribeSyncInterval(RealSyncInterval));
|
|
}
|
|
|
|
// even if we failed to set it, update the context value to prevent further attempts
|
|
Context->SyncInterval = RealSyncInterval;
|
|
}
|
|
|
|
SDL_GL_SwapWindow(Context->hWnd);
|
|
|
|
glEnable(GL_FRAMEBUFFER_SRGB);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void PlatformFlushIfNeeded()
|
|
{
|
|
glFinish();
|
|
}
|
|
|
|
void PlatformRenderingContextSetup(FPlatformOpenGLDevice* Device)
|
|
{
|
|
check(Device && Device->RenderingContext.hWnd && Device->RenderingContext.hGLContext);
|
|
|
|
if (Linux_GetCurrentContext())
|
|
{
|
|
glFlush();
|
|
}
|
|
|
|
Linux_ContextMakeCurrent(Device->RenderingContext.hWnd, Device->RenderingContext.hGLContext);
|
|
}
|
|
|
|
void PlatformNULLContextSetup()
|
|
{
|
|
if (Linux_GetCurrentContext())
|
|
{
|
|
glFlush();
|
|
}
|
|
|
|
Linux_ContextMakeCurrent(NULL, NULL);
|
|
}
|
|
|
|
/**
|
|
* Resize the GL context.
|
|
*/
|
|
void PlatformResizeGLContext(FPlatformOpenGLDevice* Device,
|
|
FPlatformOpenGLContext* Context,
|
|
uint32 SizeX, uint32 SizeY,
|
|
bool bFullscreen,
|
|
bool bWasFullscreen,
|
|
GLenum BackBufferTarget,
|
|
GLuint BackBufferResource)
|
|
{
|
|
FScopeLock ScopeLock(Device->ContextUsageGuard);
|
|
{
|
|
FScopeContext ScopeContext(Context);
|
|
|
|
if (Context->ViewportFramebuffer == 0)
|
|
{
|
|
glGenFramebuffers(1, &Context->ViewportFramebuffer);
|
|
}
|
|
glBindFramebuffer(GL_FRAMEBUFFER, Context->ViewportFramebuffer);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, BackBufferTarget, BackBufferResource, 0);
|
|
FOpenGL::CheckFrameBuffer();
|
|
|
|
glViewport(0, 0, SizeX, SizeY);
|
|
|
|
static GLfloat ZeroColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
glClearBufferfv(GL_COLOR, 0, ZeroColor);
|
|
}
|
|
|
|
|
|
if (bFullscreen)
|
|
{
|
|
// Deregister all components
|
|
// this line will fix the geometry missing and color distortion bug on Linux/Nvidia machine
|
|
// it detach all the components and re attach all the components
|
|
FGlobalComponentReregisterContext RecreateComponents;
|
|
}
|
|
else if (bWasFullscreen)
|
|
{
|
|
FGlobalComponentReregisterContext RecreateComponents;
|
|
}
|
|
}
|
|
|
|
void PlatformGetSupportedResolution(uint32& Width, uint32& Height)
|
|
{
|
|
uint32 InitializedMode = false;
|
|
uint32 BestWidth = 0;
|
|
uint32 BestHeight = 0;
|
|
uint32 ModeIndex = 0;
|
|
|
|
SDL_DisplayID DisplayID = SDL_GetPrimaryDisplay();
|
|
int NumModes = 0;
|
|
SDL_DisplayMode** Modes = SDL_GetFullscreenDisplayModes(DisplayID, &NumModes);
|
|
if (Modes)
|
|
{
|
|
for (int i=0; i < NumModes; ++i)
|
|
{
|
|
SDL_DisplayMode* Mode = Modes[i];
|
|
bool IsEqualOrBetterWidth = FMath::Abs((int32)Mode->w - (int32)Width) <= FMath::Abs((int32)BestWidth - (int32)Width);
|
|
bool IsEqualOrBetterHeight = FMath::Abs((int32)Mode->h - (int32)Height) <= FMath::Abs((int32)BestHeight - (int32)Height);
|
|
if (!InitializedMode || (IsEqualOrBetterWidth && IsEqualOrBetterHeight))
|
|
{
|
|
BestWidth = Mode->w;
|
|
BestHeight = Mode->h;
|
|
InitializedMode = true;
|
|
}
|
|
|
|
}
|
|
}
|
|
check(InitializedMode);
|
|
Width = BestWidth;
|
|
Height = BestHeight;
|
|
}
|
|
|
|
|
|
bool PlatformGetAvailableResolutions(FScreenResolutionArray& Resolutions, bool bIgnoreRefreshRate)
|
|
{
|
|
int32 MinAllowableResolutionX = 0;
|
|
int32 MinAllowableResolutionY = 0;
|
|
int32 MaxAllowableResolutionX = 10480;
|
|
int32 MaxAllowableResolutionY = 10480;
|
|
int32 MinAllowableRefreshRate = 0;
|
|
int32 MaxAllowableRefreshRate = 10480;
|
|
|
|
if (MaxAllowableResolutionX == 0)
|
|
{
|
|
MaxAllowableResolutionX = 10480;
|
|
}
|
|
if (MaxAllowableResolutionY == 0)
|
|
{
|
|
MaxAllowableResolutionY = 10480;
|
|
}
|
|
if (MaxAllowableRefreshRate == 0)
|
|
{
|
|
MaxAllowableRefreshRate = 10480;
|
|
}
|
|
|
|
|
|
SDL_DisplayID Display = SDL_GetPrimaryDisplay();
|
|
int NumModes = 0;
|
|
SDL_DisplayMode** Modes = SDL_GetFullscreenDisplayModes(Display, &NumModes);
|
|
if (Modes)
|
|
{
|
|
for (int32 i = 0; i < NumModes; i++)
|
|
{
|
|
SDL_DisplayMode* Mode = Modes[i];
|
|
if (((int32)Mode->w >= MinAllowableResolutionX) &&
|
|
((int32)Mode->w <= MaxAllowableResolutionX) &&
|
|
((int32)Mode->h >= MinAllowableResolutionY) &&
|
|
((int32)Mode->h <= MaxAllowableResolutionY)
|
|
)
|
|
{
|
|
bool bAddIt = true;
|
|
if (bIgnoreRefreshRate == false)
|
|
{
|
|
if (((int32)Mode->refresh_rate < MinAllowableRefreshRate) ||
|
|
((int32)Mode->refresh_rate > MaxAllowableRefreshRate))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// See if it is in the list already
|
|
for (int32 CheckIndex = 0; CheckIndex < Resolutions.Num(); CheckIndex++)
|
|
{
|
|
FScreenResolutionRHI& CheckResolution = Resolutions[CheckIndex];
|
|
if ((CheckResolution.Width == Mode->w) &&
|
|
(CheckResolution.Height == Mode->h))
|
|
{
|
|
// Already in the list...
|
|
// bAddIt = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAddIt)
|
|
{
|
|
// Add the mode to the list
|
|
int32 Temp2Index = Resolutions.AddZeroed();
|
|
FScreenResolutionRHI& ScreenResolution = Resolutions[Temp2Index];
|
|
|
|
ScreenResolution.Width = Mode->w;
|
|
ScreenResolution.Height = Mode->h;
|
|
ScreenResolution.RefreshRate = Mode->refresh_rate;
|
|
}
|
|
}
|
|
}
|
|
SDL_free(Modes);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PlatformRestoreDesktopDisplayMode()
|
|
{
|
|
}
|
|
|
|
|
|
bool PlatformInitOpenGL()
|
|
{
|
|
static bool bInitialized = false;
|
|
static bool bOpenGLSupported = false;
|
|
|
|
if (!FLinuxPlatformApplicationMisc::InitSDL()) // will not initialize more than once
|
|
{
|
|
UE_LOG(LogInit, Error, TEXT("PlatformInitOpenGL() : InitSDL() failed, cannot initialize OpenGL."));
|
|
// unreachable
|
|
return false;
|
|
}
|
|
|
|
#if DO_CHECK
|
|
uint32 InitializedSubsystems = SDL_WasInit(SDL_INIT_VIDEO);
|
|
check(InitializedSubsystems & SDL_INIT_VIDEO);
|
|
#endif // DO_CHECK
|
|
|
|
if (!bInitialized)
|
|
{
|
|
if (SDL_GL_LoadLibrary(NULL))
|
|
{
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok,
|
|
*FString::Printf(TEXT("%s. SDL error: \"%s\""), *(NSLOCTEXT("Renderer", "LinuxCannotLoadLibGLText", "Unable to dynamically load libGL").ToString()), UTF8_TO_TCHAR(SDL_GetError())),
|
|
*(NSLOCTEXT("Renderer", "LinuxInsufficientDriversTitle", "Insufficient drivers or hardware").ToString()));
|
|
FPlatformMisc::RequestExit(true, TEXT("PlatformInitOpenGL"));
|
|
// unreachable
|
|
return false;
|
|
}
|
|
|
|
const int MajorVersion = 4;
|
|
const int MinorVersion = 3;
|
|
|
|
int DebugFlag = 0;
|
|
|
|
if (Linux_PlatformOpenGLDebugCtx())
|
|
{
|
|
DebugFlag = SDL_GL_CONTEXT_DEBUG_FLAG;
|
|
}
|
|
|
|
if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, MajorVersion) != 0)
|
|
{
|
|
UE_LOG(LogLinux, Fatal, TEXT("SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, %d) failed: %s"), MajorVersion, UTF8_TO_TCHAR(SDL_GetError()));
|
|
}
|
|
|
|
if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, MinorVersion) != 0)
|
|
{
|
|
UE_LOG(LogLinux, Fatal, TEXT("SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, %d) failed: %s"), MinorVersion, UTF8_TO_TCHAR(SDL_GetError()));
|
|
}
|
|
|
|
if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, DebugFlag) != 0)
|
|
{
|
|
UE_LOG(LogLinux, Fatal, TEXT("SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, %d) failed: %s"), DebugFlag, UTF8_TO_TCHAR(SDL_GetError()));
|
|
}
|
|
|
|
if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE) != 0)
|
|
{
|
|
UE_LOG(LogLinux, Fatal, TEXT("SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE) failed: %s"), UTF8_TO_TCHAR(SDL_GetError()));
|
|
}
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("quad_buffer_stereo")))
|
|
{
|
|
if (SDL_GL_SetAttribute(SDL_GL_STEREO, 1) != 0)
|
|
{
|
|
UE_LOG(LogLinux, Fatal, TEXT("SDL_GL_SetAttribute(SDL_GL_STEREO, 1) failed: %s"), UTF8_TO_TCHAR(SDL_GetError()));
|
|
}
|
|
}
|
|
|
|
// Create a dummy context to verify opengl support.
|
|
FPlatformOpenGLContext DummyContext;
|
|
Linux_PlatformCreateDummyGLWindow(&DummyContext);
|
|
Linux_PlatformCreateOpenGLContextCore(&DummyContext);
|
|
|
|
if (DummyContext.hGLContext)
|
|
{
|
|
bOpenGLSupported = true;
|
|
Linux_ContextMakeCurrent(DummyContext.hWnd, DummyContext.hGLContext);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogRHI, Error, TEXT("OpenGL %d.%d not supported by driver"), MajorVersion, MinorVersion);
|
|
}
|
|
|
|
if (bOpenGLSupported)
|
|
{
|
|
// Initialize all entry points required by Unreal.
|
|
#define GET_GL_ENTRYPOINTS(Type,Func) GLFuncPointers::Func = reinterpret_cast<Type>(SDL_GL_GetProcAddress(#Func));
|
|
ENUM_GL_ENTRYPOINTS(GET_GL_ENTRYPOINTS);
|
|
ENUM_GL_ENTRYPOINTS_OPTIONAL(GET_GL_ENTRYPOINTS);
|
|
#undef GET_GL_ENTRYPOINTS
|
|
|
|
// Check that all of the entry points have been initialized.
|
|
bool bFoundAllEntryPoints = true;
|
|
#define CHECK_GL_ENTRYPOINTS(Type,Func) if (Func == NULL) { bFoundAllEntryPoints = false; UE_LOG(LogRHI, Fatal, TEXT("Failed to find entry point for %s"), TEXT(#Func)); }
|
|
ENUM_GL_ENTRYPOINTS(CHECK_GL_ENTRYPOINTS);
|
|
#undef CHECK_GL_ENTRYPOINTS
|
|
checkf(bFoundAllEntryPoints, TEXT("Failed to find all OpenGL entry points."));
|
|
}
|
|
|
|
// The dummy context can now be released.
|
|
if (DummyContext.hGLContext)
|
|
{
|
|
Linux_ContextMakeCurrent(NULL, NULL);
|
|
SDL_GL_DestroyContext(DummyContext.hGLContext);
|
|
}
|
|
check(DummyContext.bReleaseWindowOnDestroy);
|
|
SDL_DestroyWindow(DummyContext.hWnd);
|
|
|
|
bInitialized = true;
|
|
}
|
|
|
|
return bOpenGLSupported;
|
|
}
|
|
|
|
int32 PlatformGlGetError()
|
|
{
|
|
return glGetError();
|
|
}
|
|
|
|
bool PlatformOpenGLThreadHasRenderingContext()
|
|
{
|
|
return Linux_GetCurrentContext() == FPlatformOpenGLDevice::Singleton->RenderingContext.hGLContext;
|
|
}
|
|
|
|
// =============================================================
|
|
|
|
FOpenGLTexture* PlatformCreateBuiltinBackBuffer(FOpenGLDynamicRHI* OpenGLRHI, uint32 SizeX, uint32 SizeY)
|
|
{
|
|
return nullptr;
|
|
}
|