ANGLE+DXGI experiment, window/GL init rewrite (#1982)

This commit is contained in:
Pieter-Jan Briers
2021-08-30 01:35:07 +02:00
committed by GitHub
parent 051d47f4ff
commit cd3a7ef91e
31 changed files with 2155 additions and 659 deletions

View File

@@ -5552,6 +5552,11 @@ namespace OpenToolkit.GraphicsLibraryFramework
return glfwGetX11Window(window);
}
public static unsafe IntPtr GetX11Display(Window* window)
{
return glfwGetX11Display(window);
}
public static unsafe IntPtr GetWin32Window(Window* window)
{
return glfwGetWin32Window(window);

View File

@@ -407,6 +407,9 @@ namespace OpenToolkit.GraphicsLibraryFramework
[DllImport(LibraryName)]
public static extern uint glfwGetX11Window(Window* window);
[DllImport(LibraryName)]
public static extern IntPtr glfwGetX11Display(Window* window);
[DllImport(LibraryName)]
public static extern IntPtr glfwGetWin32Window(Window* window);
}

View File

@@ -2,7 +2,8 @@ namespace Robust.Client.Graphics.Clyde
{
internal sealed partial class Clyde
{
private static readonly (string, uint)[] BaseShaderAttribLocations = {
private static readonly (string, uint)[] BaseShaderAttribLocations =
{
("aPos", 0),
("tCoord", 1)
};
@@ -30,14 +31,21 @@ namespace Robust.Client.Graphics.Clyde
// To be clear: You shouldn't change this. This just helps with understanding where Primitive Restart is being used.
private const ushort PrimitiveRestartIndex = ushort.MaxValue;
private enum Renderer : short
private enum Renderer : sbyte
{
// Default: Try all supported renderers (not necessarily the renderers shown here)
Default = default,
OpenGL33 = 1,
OpenGL31 = 2,
OpenGLES2 = 3,
// Auto: Try all supported renderers (not necessarily the renderers shown here)
Auto = default,
OpenGL = 1,
Explode = -1,
}
private enum RendererOpenGLVersion : byte
{
Auto = default,
GL33 = 1,
GL31 = 2,
GLES3 = 3,
GLES2 = 4,
}
}
}

View File

@@ -102,17 +102,10 @@ namespace Robust.Client.Graphics.Clyde
private void SendWindowResized(WindowReg reg, Vector2i oldSize)
{
if (reg.IsMainWindow)
{
UpdateMainWindowLoadedRtSize();
GL.Viewport(0, 0, reg.FramebufferSize.X, reg.FramebufferSize.Y);
CheckGlError();
}
else
{
reg.RenderTexture!.Dispose();
CreateWindowRenderTexture(reg);
}
var loaded = RtToLoaded(reg.RenderTarget);
loaded.Size = reg.FramebufferSize;
_glContext!.WindowResized(reg, oldSize);
var eventArgs = new WindowResizedEventArgs(
oldSize,

View File

@@ -0,0 +1,77 @@
using System;
using Robust.Shared;
using Robust.Shared.Log;
namespace Robust.Client.Graphics.Clyde
{
internal sealed partial class Clyde
{
private GLContextBase? _glContext;
// Current OpenGL version we managed to initialize with.
private RendererOpenGLVersion _openGLVersion;
private void InitGLContextManager()
{
// Advanced GL contexts currently disabled due to lack of testing etc.
/*
if (OperatingSystem.IsWindows() && _cfg.GetCVar(CVars.DisplayAngle))
{
if (_cfg.GetCVar(CVars.DisplayAngleCustomSwapChain))
{
_sawmillOgl.Debug("Trying custom swap chain ANGLE.");
var ctxAngle = new GLContextAngle(this);
if (ctxAngle.TryInitialize())
{
_sawmillOgl.Debug("Successfully initialized custom ANGLE");
_glContext = ctxAngle;
return;
}
}
if (_cfg.GetCVar(CVars.DisplayEgl))
{
_sawmillOgl.Debug("Trying EGL");
var ctxEgl = new GLContextEgl(this);
ctxEgl.InitializePublic();
_glContext = ctxEgl;
return;
}
}
if (OperatingSystem.IsLinux() && _cfg.GetCVar(CVars.DisplayEgl))
{
_sawmillOgl.Debug("Trying EGL");
var ctxEgl = new GLContextEgl(this);
ctxEgl.InitializePublic();
_glContext = ctxEgl;
return;
}
*/
_glContext = new GLContextWindow(this);
}
private struct GLContextSpec
{
public int Major;
public int Minor;
public GLContextProfile Profile;
public GLContextCreationApi CreationApi;
}
private enum GLContextProfile
{
Compatibility,
Core,
Es
}
private enum GLContextCreationApi
{
Native,
Egl,
}
}
}

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Log;
namespace Robust.Client.Graphics.Clyde
{
@@ -72,6 +71,12 @@ namespace Robust.Client.Graphics.Clyde
CheckGLCap(ref _hasGLMapBufferRange, "map_buffer_range", (3, 0));
CheckGLCap(ref _hasGLPixelBufferObjects, "pixel_buffer_object", (2, 1));
CheckGLCap(ref _hasGLStandardDerivatives, "standard_derivatives", (2, 1));
_hasGLSrgb = true;
_hasGLReadFramebuffer = true;
_hasGLPrimitiveRestart = true;
_hasGLUniformBuffers = true;
_hasGLFloatFramebuffers = true;
}
else
{
@@ -94,15 +99,14 @@ namespace Robust.Client.Graphics.Clyde
CheckGLCap(ref _hasGLMapBufferRange, "map_buffer_range", (3, 0));
CheckGLCap(ref _hasGLPixelBufferObjects, "pixel_buffer_object", (3, 0));
CheckGLCap(ref _hasGLStandardDerivatives, "standard_derivatives", (3, 0), "GL_OES_standard_derivatives");
}
// TODO: Enable these on ES 3.0
_hasGLSrgb = !_isGLES;
_hasGLReadFramebuffer = !_isGLES;
_hasGLPrimitiveRestart = !_isGLES;
_hasGLUniformBuffers = !_isGLES;
// This is 3.2 or extensions
_hasGLFloatFramebuffers = !_isGLES;
_hasGLSrgb = major >= 3;
CheckGLCap(ref _hasGLReadFramebuffer, "read_framebuffer", (3, 0));
CheckGLCap(ref _hasGLPrimitiveRestart, "primitive_restart", (3, 1));
CheckGLCap(ref _hasGLUniformBuffers, "uniform_buffers", (3, 0));
CheckGLCap(ref _hasGLFloatFramebuffers, "float_framebuffers", (3, 2), "GL_EXT_color_buffer_float");
}
_sawmillOgl.Debug($" GLES: {_isGLES}");
@@ -141,7 +145,11 @@ namespace Robust.Client.Graphics.Clyde
"map_buffer_range",
"pixel_buffer_object",
"map_buffer_oes",
"standard_derivatives"
"standard_derivatives",
"read_framebuffer",
"primitive_restart",
"uniform_buffers",
"float_framebuffers"
};
foreach (var cvar in cvars)

View File

@@ -2,6 +2,7 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.Map;
@@ -26,12 +27,12 @@ namespace Robust.Client.Graphics.Clyde
=
new();
public unsafe void Render()
public void Render()
{
CheckTransferringScreenshots();
var allMinimized = true;
foreach (var windowReg in _windowing!.AllWindows)
foreach (var windowReg in _windows)
{
if (!windowReg.IsMinimized)
{
@@ -45,9 +46,8 @@ namespace Robust.Client.Graphics.Clyde
{
ClearFramebuffer(Color.Black);
// We have to keep running swapbuffers here
// or else the user's PC will turn into a heater!!
SwapMainBuffers();
// Sleep to avoid turning the computer into a heater.
Thread.Sleep(16);
return;
}
@@ -63,7 +63,7 @@ namespace Robust.Client.Graphics.Clyde
ClearFramebuffer(Color.Black);
// Update shared UBOs.
_updateUniformConstants(_windowing.MainWindow!.FramebufferSize);
_updateUniformConstants(_mainWindow!.FramebufferSize);
{
CalcScreenMatrices(ScreenSize, out var proj, out var view);
@@ -75,7 +75,7 @@ namespace Robust.Client.Graphics.Clyde
{
DrawSplash(_renderHandle);
FlushRenderQueue();
SwapMainBuffers();
SwapAllBuffers();
return;
}
@@ -93,10 +93,8 @@ namespace Robust.Client.Graphics.Clyde
TakeScreenshot(ScreenshotType.Final);
BlitSecondaryWindows();
// And finally, swap those buffers!
SwapMainBuffers();
SwapAllBuffers();
}
private void RenderOverlays(Viewport vp, OverlaySpace space, in Box2 worldBox)

View File

@@ -2,10 +2,8 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp.PixelFormats;
using ES20 = OpenToolkit.Graphics.ES20;
namespace Robust.Client.Graphics.Clyde
@@ -276,7 +274,7 @@ namespace Robust.Client.Graphics.Clyde
private void LoadGLProc<T>(string name, out T field) where T : Delegate
{
var proc = _windowing!.GraphicsBindingContext.GetProcAddress(name);
var proc = _glBindingsContext.GetProcAddress(name);
if (proc == IntPtr.Zero || proc == new IntPtr(1) || proc == new IntPtr(2))
{
throw new InvalidOperationException($"Unable to load GL function '{name}'!");

View File

@@ -136,7 +136,7 @@ namespace Robust.Client.Graphics.Clyde
// Switch rendering to pseudo-world space.
{
CalcWorldProjMatrix(_clyde._currentRenderTarget.Size, out var proj);
_clyde.CalcWorldProjMatrix(_clyde._currentRenderTarget.Size, out var proj);
var ofsX = position.X - _clyde.ScreenSize.X / 2f;
var ofsY = position.Y - _clyde.ScreenSize.Y / 2f;

View File

@@ -3,9 +3,9 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.Log;
using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable once IdentifierTypo
@@ -24,9 +24,6 @@ namespace Robust.Client.Graphics.Clyde
private readonly ConcurrentQueue<ClydeHandle> _renderTargetDisposeQueue
= new();
// Initialized in Clyde's constructor
private readonly RenderMainWindow _mainMainWindowRenderMainTarget;
// This is always kept up-to-date, except in CreateRenderTarget (because it restores the old value)
// It is used for SRGB emulation.
// It, like _mainWindowRenderTarget, is initialized in Clyde's constructor
@@ -248,8 +245,7 @@ namespace Robust.Client.Graphics.Clyde
// NOTE: It's critically important that this be the "focal point" of all framebuffer bindings.
if (rt.IsWindow)
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
CheckGlError();
_glContext!.BindWindowRenderTarget(rt.WindowId);
}
else
{
@@ -267,18 +263,16 @@ namespace Robust.Client.Graphics.Clyde
}
}
private void UpdateMainWindowLoadedRtSize()
{
var loadedRt = RtToLoaded(_mainMainWindowRenderMainTarget);
loadedRt.Size = _windowing!.MainWindow!.FramebufferSize;
}
private sealed class LoadedRenderTarget
{
public bool IsWindow;
public WindowId WindowId;
public Vector2i Size;
public bool IsSrgb;
public bool FlipY;
public RTCF ColorFormat;
// Remaining properties only apply if the render target is NOT a window.
@@ -386,11 +380,11 @@ namespace Robust.Client.Graphics.Clyde
}
}
private sealed class RenderMainWindow : RenderTargetBase
private sealed class RenderWindow : RenderTargetBase
{
public override Vector2i Size => Clyde._windowing!.MainWindow!.FramebufferSize;
public override Vector2i Size => Clyde._renderTargets[Handle].Size;
public RenderMainWindow(Clyde clyde, ClydeHandle handle) : base(clyde, handle)
public RenderWindow(Clyde clyde, ClydeHandle handle) : base(clyde, handle)
{
}
}

View File

@@ -6,8 +6,6 @@ using System.Runtime.InteropServices;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Color = Robust.Shared.Maths.Color;
@@ -107,7 +105,7 @@ namespace Robust.Client.Graphics.Clyde
UniformConstantsUBO.Reallocate(constants);
}
private static void CalcScreenMatrices(in Vector2i screenSize, out Matrix3 proj, out Matrix3 view)
private void CalcScreenMatrices(in Vector2i screenSize, out Matrix3 proj, out Matrix3 view)
{
proj = Matrix3.Identity;
proj.R0C0 = 2f / screenSize.X;
@@ -115,10 +113,16 @@ namespace Robust.Client.Graphics.Clyde
proj.R0C2 = -1;
proj.R1C2 = 1;
if (_currentRenderTarget.FlipY)
{
proj.R1C1 *= -1;
proj.R1C2 *= -1;
}
view = Matrix3.Identity;
}
private static void CalcWorldMatrices(in Vector2i screenSize, in Vector2 renderScale, IEye eye,
private void CalcWorldMatrices(in Vector2i screenSize, in Vector2 renderScale, IEye eye,
out Matrix3 proj, out Matrix3 view)
{
eye.GetViewMatrix(out view, renderScale);
@@ -127,11 +131,17 @@ namespace Robust.Client.Graphics.Clyde
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CalcWorldProjMatrix(in Vector2i screenSize, out Matrix3 proj)
private void CalcWorldProjMatrix(in Vector2i screenSize, out Matrix3 proj)
{
proj = Matrix3.Identity;
proj.R0C0 = EyeManager.PixelsPerMeter * 2f / screenSize.X;
proj.R1C1 = EyeManager.PixelsPerMeter * 2f / screenSize.Y;
if (_currentRenderTarget.FlipY)
{
proj.R1C1 *= -1;
proj.R1C2 *= -1;
}
}
private void SetProjViewBuffer(in Matrix3 proj, in Matrix3 view)
@@ -354,7 +364,15 @@ namespace Robust.Client.Graphics.Clyde
// Don't forget to flip it, these coordinates have bottom left as origin.
// TODO: Broken when rendering to non-screen render targets.
GL.Scissor(box.Left, _currentRenderTarget.Size.Y - box.Bottom, box.Width, box.Height);
if (_currentRenderTarget.FlipY)
{
GL.Scissor(box.Left, box.Top, box.Width, box.Height);
}
else
{
GL.Scissor(box.Left, _currentRenderTarget.Size.Y - box.Bottom, box.Width, box.Height);
}
CheckGlError();
}
else if (oldIsScissoring)
@@ -828,9 +846,11 @@ namespace Robust.Client.Graphics.Clyde
_lightingReady = false;
_currentMatrixModel = Matrix3.Identity;
SetScissorFull(null);
BindRenderTargetFull(_mainMainWindowRenderMainTarget);
BindRenderTargetFull(_mainWindow!.RenderTarget);
_batchMetaData = null;
_queuedShader = _defaultShader.Handle;
GL.Viewport(0, 0, _mainWindow!.FramebufferSize.X, _mainWindow!.FramebufferSize.Y);
}
private void ResetBlendFunc()
@@ -842,98 +862,6 @@ namespace Robust.Client.Graphics.Clyde
BlendingFactorDest.OneMinusSrcAlpha);
}
private void BlitSecondaryWindows()
{
// Only got main window.
if (_windowing!.AllWindows.Count == 1)
return;
if (!_hasGLFenceSync && _cfg.GetCVar(CVars.DisplayForceSyncWindows))
{
GL.Finish();
}
if (EffectiveThreadWindowBlit)
{
foreach (var window in _windowing.AllWindows)
{
if (window.IsMainWindow)
continue;
window.BlitDoneEvent!.Reset();
window.BlitStartEvent!.Set();
window.BlitDoneEvent.Wait();
}
}
else
{
foreach (var window in _windowing.AllWindows)
{
if (window.IsMainWindow)
continue;
_windowing.GLMakeContextCurrent(window);
BlitThreadDoSecondaryWindowBlit(window);
}
_windowing.GLMakeContextCurrent(_windowing.MainWindow!);
}
}
private void BlitThreadDoSecondaryWindowBlit(WindowReg window)
{
var rt = window.RenderTexture!;
if (_hasGLFenceSync)
{
// 0xFFFFFFFFFFFFFFFFUL is GL_TIMEOUT_IGNORED
var sync = rt!.LastGLSync;
GL.WaitSync(sync, WaitSyncFlags.None, unchecked((long) 0xFFFFFFFFFFFFFFFFUL));
CheckGlError();
}
GL.Viewport(0, 0, window.FramebufferSize.X, window.FramebufferSize.Y);
CheckGlError();
SetTexture(TextureUnit.Texture0, window.RenderTexture!.Texture);
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
CheckGlError();
window.BlitDoneEvent?.Set();
_windowing!.WindowSwapBuffers(window);
}
private void BlitThreadInit(WindowReg reg)
{
_windowing!.GLMakeContextCurrent(reg);
_windowing.GLSwapInterval(0);
if (!_isGLES)
GL.Enable(EnableCap.FramebufferSrgb);
var vao = GL.GenVertexArray();
GL.BindVertexArray(vao);
GL.BindBuffer(BufferTarget.ArrayBuffer, WindowVBO.ObjectHandle);
// Vertex Coords
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 0);
GL.EnableVertexAttribArray(0);
// Texture Coords.
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 2 * sizeof(float));
GL.EnableVertexAttribArray(1);
var program = _compileProgram(_winBlitShaderVert, _winBlitShaderFrag, new (string, uint)[]
{
("aPos", 0),
("tCoord", 1),
}, includeLib: false);
GL.UseProgram(program.Handle);
var loc = GL.GetUniformLocation(program.Handle, "tex");
SetTexture(TextureUnit.Texture0, reg.RenderTexture!.Texture);
GL.Uniform1(loc, 0);
}
private void FenceRenderTarget(RenderTargetBase rt)
{
if (!_hasGLFenceSync || !rt.MakeGLFence)

View File

@@ -150,12 +150,20 @@ namespace Robust.Client.Graphics.Clyde
if (_isGLES)
{
// GLES2 uses a different GLSL versioning scheme to desktop GL.
versionHeader = "#version 100\n#define HAS_VARYING_ATTRIBUTE\n";
if (_hasGLStandardDerivatives)
if (_openGLVersion == RendererOpenGLVersion.GLES3)
{
versionHeader += "#extension GL_OES_standard_derivatives : enable\n";
versionHeader = "#version 300 es\n";
}
else
{
// GLES2 uses a different GLSL versioning scheme to desktop GL.
versionHeader = "#version 100\n#define HAS_VARYING_ATTRIBUTE\n";
if (_hasGLStandardDerivatives)
{
versionHeader += "#extension GL_OES_standard_derivatives : enable\n";
}
}
}
if (_hasGLStandardDerivatives)

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Robust.Client.Input;
using Robust.Client.UserInterface;
@@ -17,10 +17,14 @@ using FrameEventArgs = Robust.Shared.Timing.FrameEventArgs;
namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
internal partial class Clyde
{
private readonly List<WindowReg> _windows = new();
private readonly List<WindowHandle> _windowHandles = new();
private readonly List<MonitorHandle> _monitorHandles = new();
private readonly Dictionary<int, MonitorHandle> _monitorHandles = new();
private int _primaryMonitorId;
private WindowReg? _mainWindow;
private IWindowingImpl? _windowing;
private Renderer _chosenRenderer;
@@ -47,18 +51,18 @@ namespace Robust.Client.Graphics.Clyde
// NOTE: in engine we pretend the framebuffer size is the screen size..
// For practical reasons like UI rendering.
public IClydeWindow MainWindow => _windowing?.MainWindow?.Handle ??
public IClydeWindow MainWindow => _mainWindow?.Handle ??
throw new InvalidOperationException("Windowing is not initialized");
public Vector2i ScreenSize => _windowing?.MainWindow?.FramebufferSize ??
public Vector2i ScreenSize => _mainWindow?.FramebufferSize ??
throw new InvalidOperationException("Windowing is not initialized");
public bool IsFocused => _windowing?.MainWindow?.IsFocused ??
public bool IsFocused => _mainWindow?.IsFocused ??
throw new InvalidOperationException("Windowing is not initialized");
public IEnumerable<IClydeWindow> AllWindows => _windowHandles;
public Vector2 DefaultWindowScale => _windowing?.MainWindow?.WindowScale ??
public Vector2 DefaultWindowScale => _mainWindow?.WindowScale ??
throw new InvalidOperationException("Windowing is not initialized");
public ScreenCoordinates MouseScreenPosition
@@ -82,7 +86,7 @@ namespace Robust.Client.Graphics.Clyde
public uint? GetX11WindowId()
{
return _windowing?.WindowGetX11Id(_windowing.MainWindow!) ?? null;
return _windowing?.WindowGetX11Id(_mainWindow!) ?? null;
}
private bool InitWindowing()
@@ -98,38 +102,110 @@ namespace Robust.Client.Graphics.Clyde
return _windowing.Init();
}
private bool TryInitMainWindow(GLContextSpec? glSpec, [NotNullWhen(false)] out string? error)
{
DebugTools.AssertNotNull(_glContext);
var width = _cfg.GetCVar(CVars.DisplayWidth);
var height = _cfg.GetCVar(CVars.DisplayHeight);
var prevWidth = width;
var prevHeight = height;
IClydeMonitor? monitor = null;
var fullscreen = false;
if (_windowMode == WindowMode.Fullscreen)
{
monitor = _monitorHandles[_primaryMonitorId];
width = monitor.Size.X;
height = monitor.Size.Y;
fullscreen = true;
}
var parameters = new WindowCreateParameters
{
Width = width,
Height = height,
Monitor = monitor,
Fullscreen = fullscreen
};
var (reg, err) = SharedWindowCreate(glSpec, parameters, null, isMain: true);
if (reg == null)
{
error = err!;
return false;
}
DebugTools.Assert(reg.Id == WindowId.Main);
if (fullscreen)
{
reg.PrevWindowSize = (prevWidth, prevHeight);
reg.PrevWindowPos = (50, 50);
}
error = null;
return true;
}
private unsafe bool InitMainWindowAndRenderer()
{
DebugTools.AssertNotNull(_windowing);
DebugTools.AssertNotNull(_glContext);
_chosenRenderer = (Renderer) _cfg.GetCVar(CVars.DisplayRenderer);
_chosenRenderer = Renderer.OpenGL;
_openGLVersion = (RendererOpenGLVersion) _cfg.GetCVar(CVars.DisplayOpenGLVersion);
var renderers = _chosenRenderer == Renderer.Default
? stackalloc Renderer[]
RendererOpenGLVersion[] versions;
if (_glContext!.GlesOnly || _cfg.GetCVar(CVars.DisplayCompat))
{
versions = new[]
{
Renderer.OpenGL33,
Renderer.OpenGL31,
Renderer.OpenGLES2
}
: stackalloc Renderer[] {_chosenRenderer};
RendererOpenGLVersion.GLES3,
RendererOpenGLVersion.GLES2
};
}
else
{
versions = new[]
{
RendererOpenGLVersion.GL33,
RendererOpenGLVersion.GL31,
RendererOpenGLVersion.GLES3,
RendererOpenGLVersion.GLES2
};
}
if (_openGLVersion != RendererOpenGLVersion.Auto)
{
if (Array.IndexOf(versions, _openGLVersion) != -1)
versions = new[] {_openGLVersion};
else
Logger.ErrorS("clyde.win", $"Requested OpenGL version {_openGLVersion} not supported.");
}
var succeeded = false;
string? lastError = null;
foreach (var renderer in renderers)
foreach (var version in versions)
{
if (!_windowing!.TryInitMainWindow(renderer, out lastError))
var glSpec = _glContext!.SpecWithOpenGLVersion(version);
if (!TryInitMainWindow(glSpec, out lastError))
{
Logger.DebugS("clyde.win", $"{renderer} unsupported: {lastError}");
Logger.DebugS("clyde.win", $"OpenGL {version} unsupported: {lastError}");
continue;
}
// We should have a main window now.
DebugTools.AssertNotNull(_windowing.MainWindow);
DebugTools.AssertNotNull(_mainWindow);
succeeded = true;
_chosenRenderer = renderer;
_isGLES = _chosenRenderer == Renderer.OpenGLES2;
_isCore = _chosenRenderer == Renderer.OpenGL33;
_openGLVersion = version;
_isGLES = _openGLVersion is RendererOpenGLVersion.GLES2 or RendererOpenGLVersion.GLES3;
_isCore = _openGLVersion is RendererOpenGLVersion.GL33;
break;
}
@@ -157,12 +233,8 @@ namespace Robust.Client.Graphics.Clyde
return false;
}
_windowing!.GLInitMainContext(_isGLES);
UpdateMainWindowLoadedRtSize();
_windowing.GLMakeContextCurrent(_windowing.MainWindow!);
InitOpenGL();
return true;
}
@@ -194,31 +266,88 @@ namespace Robust.Client.Graphics.Clyde
public void SetWindowTitle(string title)
{
DebugTools.AssertNotNull(_windowing);
DebugTools.AssertNotNull(_mainWindow);
_windowing!.WindowSetTitle(_windowing.MainWindow!, title);
_windowing!.WindowSetTitle(_mainWindow!, title);
}
public void SetWindowMonitor(IClydeMonitor monitor)
{
DebugTools.AssertNotNull(_windowing);
DebugTools.AssertNotNull(_mainWindow);
var window = _windowing!.MainWindow!;
_windowing.WindowSetMonitor(window, monitor);
_windowing!.WindowSetMonitor(_mainWindow!, monitor);
}
public void RequestWindowAttention()
{
DebugTools.AssertNotNull(_windowing);
DebugTools.AssertNotNull(_mainWindow);
_windowing!.WindowRequestAttention(_windowing.MainWindow!);
_windowing!.WindowRequestAttention(_mainWindow!);
}
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
{
DebugTools.AssertNotNull(_windowing);
DebugTools.AssertNotNull(_glContext);
DebugTools.AssertNotNull(_mainWindow);
return _windowing!.WindowCreate(parameters);
var glSpec = _glContext!.GetNewWindowSpec();
_glContext.BeforeSharedWindowCreateUnbind();
var (reg, error) = SharedWindowCreate(
glSpec,
parameters,
glSpec == null ? null : _mainWindow,
isMain: false);
// Rebinding is handed by WindowCreated in the GL context.
if (error != null)
throw new Exception(error);
return reg!.Handle;
}
private (WindowReg?, string? error) SharedWindowCreate(
GLContextSpec? glSpec,
WindowCreateParameters parameters,
WindowReg? share,
bool isMain)
{
WindowReg? owner = null;
if (parameters.Owner != null)
owner = ((WindowHandle)parameters.Owner).Reg;
var (reg, error) = _windowing!.WindowCreate(glSpec, parameters, share, owner);
if (reg != null)
{
// Window init succeeded, do setup.
reg.IsMainWindow = isMain;
if (isMain)
_mainWindow = reg;
_windows.Add(reg);
_windowHandles.Add(reg.Handle);
var rtId = AllocRid();
_renderTargets.Add(rtId, new LoadedRenderTarget
{
Size = reg.FramebufferSize,
IsWindow = true,
WindowId = reg.Id
});
reg.RenderTarget = new RenderWindow(this, rtId);
_glContext!.WindowCreated(reg);
}
// Pass through result whether successful or not, caller handles it.
return (reg, error);
}
private void DoDestroyWindow(WindowReg reg)
@@ -226,8 +355,17 @@ namespace Robust.Client.Graphics.Clyde
if (reg.IsMainWindow)
throw new InvalidOperationException("Cannot destroy main window.");
reg.BlitDoneEvent?.Set();
if (reg.IsDisposed)
return;
reg.IsDisposed = true;
_glContext!.WindowDestroyed(reg);
_windowing!.WindowDestroy(reg);
_windows.Remove(reg);
_windowHandles.Remove(reg.Handle);
var destroyed = new WindowDestroyedEventArgs(reg.Handle);
DestroyWindow?.Invoke(destroyed);
reg.Closed?.Invoke(destroyed);
@@ -239,28 +377,15 @@ namespace Robust.Client.Graphics.Clyde
DispatchEvents();
}
private void SwapMainBuffers()
private void SwapAllBuffers()
{
_windowing?.WindowSwapBuffers(_windowing.MainWindow!);
_glContext?.SwapAllBuffers();
}
private void VSyncChanged(bool newValue)
{
_vSync = newValue;
_windowing?.UpdateVSync();
}
private void CreateWindowRenderTexture(WindowReg reg)
{
reg.RenderTexture?.Dispose();
reg.RenderTexture = CreateRenderTarget(reg.FramebufferSize, new RenderTargetFormatParameters
{
ColorFormat = RenderTargetColorFormat.Rgba8Srgb,
HasDepthStencil = true
});
// Necessary to correctly sync multi-context blitting.
reg.RenderTexture.MakeGLFence = true;
_glContext?.UpdateVSync();
}
private void WindowModeChanged(int mode)
@@ -271,17 +396,17 @@ namespace Robust.Client.Graphics.Clyde
Task<string> IClipboardManager.GetText()
{
return _windowing?.ClipboardGetText() ?? Task.FromResult("");
return _windowing?.ClipboardGetText(_mainWindow!) ?? Task.FromResult("");
}
void IClipboardManager.SetText(string text)
{
_windowing?.ClipboardSetText(text);
_windowing?.ClipboardSetText(_mainWindow!, text);
}
public IEnumerable<IClydeMonitor> EnumerateMonitors()
{
return _monitorHandles;
return _monitorHandles.Values;
}
public ICursor GetStandardCursor(StandardCursorShape shape)
@@ -302,7 +427,7 @@ namespace Robust.Client.Graphics.Clyde
{
DebugTools.AssertNotNull(_windowing);
_windowing!.CursorSet(_windowing.MainWindow!, cursor);
_windowing!.CursorSet(_mainWindow!, cursor);
}
@@ -313,68 +438,6 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.WindowSetVisible(reg, visible);
}
private void InitWindowBlitThread(WindowReg reg)
{
if (EffectiveThreadWindowBlit)
{
reg.BlitStartEvent = new ManualResetEventSlim();
reg.BlitDoneEvent = new ManualResetEventSlim();
reg.BlitThread = new Thread(() => BlitThread(reg))
{
Name = $"WinBlitThread ID:{reg.Id}",
IsBackground = true
};
// System.Console.WriteLine("A");
reg.BlitThread.Start();
// Wait for thread to finish init.
reg.BlitDoneEvent.Wait();
}
else
{
// Binds GL context.
BlitThreadInit(reg);
_windowing!.GLMakeContextCurrent(_windowing.MainWindow!);
}
}
private void BlitThread(WindowReg reg)
{
BlitThreadInit(reg);
reg.BlitDoneEvent!.Set();
try
{
while (true)
{
reg.BlitStartEvent!.Wait();
if (reg.IsDisposed)
{
BlitThreadCleanup(reg);
return;
}
reg.BlitStartEvent!.Reset();
// Do channel blit.
BlitThreadDoSecondaryWindowBlit(reg);
}
}
catch (AggregateException e)
{
// ok channel closed, we exit.
e.Handle(ec => ec is ChannelClosedException);
}
}
private static void BlitThreadCleanup(WindowReg reg)
{
reg.BlitDoneEvent!.Dispose();
reg.BlitStartEvent!.Dispose();
}
private abstract class WindowReg
{
public bool IsDisposed;
@@ -396,14 +459,9 @@ namespace Robust.Client.Graphics.Clyde
public bool DisposeOnClose;
// Used EXCLUSIVELY to run the two rendering commands to blit to the window.
public Thread? BlitThread;
public ManualResetEventSlim? BlitStartEvent;
public ManualResetEventSlim? BlitDoneEvent;
public bool IsMainWindow;
public WindowHandle Handle = default!;
public RenderTexture? RenderTexture;
public RenderWindow RenderTarget = default!;
public Action<WindowRequestClosedEventArgs>? RequestClosed;
public Action<WindowDestroyedEventArgs>? Closed;
}
@@ -434,18 +492,7 @@ namespace Robust.Client.Graphics.Clyde
public Vector2i Size => Reg.FramebufferSize;
public IRenderTarget RenderTarget
{
get
{
if (Reg.IsMainWindow)
{
return _clyde._mainMainWindowRenderMainTarget;
}
return Reg.RenderTexture!;
}
}
public IRenderTarget RenderTarget => Reg.RenderTarget;
public string Title
{

View File

@@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Threading;
using OpenToolkit;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.Map;
using Robust.Client.ResourceManagement;
@@ -65,21 +66,12 @@ namespace Robust.Client.Graphics.Clyde
private ISawmill _sawmillOgl = default!;
private IBindingsContext _glBindingsContext = default!;
public Clyde()
{
// Init main window render target.
var windowRid = AllocRid();
var window = new RenderMainWindow(this, windowRid);
var loadedData = new LoadedRenderTarget
{
IsWindow = true,
IsSrgb = true
};
_renderTargets.Add(windowRid, loadedData);
_mainMainWindowRenderMainTarget = window;
_currentRenderTarget = RtToLoaded(window);
_currentBoundRenderTarget = _currentRenderTarget;
_currentBoundRenderTarget = default!;
_currentRenderTarget = default!;
}
public bool InitializePreWindowing()
@@ -101,6 +93,8 @@ namespace Robust.Client.Graphics.Clyde
public bool InitializePostWindowing()
{
_gameThread = Thread.CurrentThread;
InitGLContextManager();
if (!InitMainWindowAndRenderer())
return false;
@@ -153,8 +147,22 @@ namespace Robust.Client.Graphics.Clyde
RegisterBlockCVars();
}
private void GLInitBindings(bool gles)
{
_glBindingsContext = _glContext!.BindingsContext;
GL.LoadBindings(_glBindingsContext);
if (gles)
{
// On GLES we use some OES and KHR functions so make sure to initialize them.
OpenToolkit.Graphics.ES20.GL.LoadBindings(_glBindingsContext);
}
}
private void InitOpenGL()
{
GLInitBindings(_isGLES);
var vendor = GL.GetString(StringName.Vendor);
var renderer = GL.GetString(StringName.Renderer);
var version = GL.GetString(StringName.Version);
@@ -183,7 +191,7 @@ namespace Robust.Client.Graphics.Clyde
DebugInfo = new ClydeDebugInfo(glVersion, renderer, vendor, version, overrideVersion != null);
GL.Enable(EnableCap.Blend);
if (_hasGLSrgb)
if (_hasGLSrgb && !_isGLES)
{
GL.Enable(EnableCap.FramebufferSrgb);
CheckGlError();
@@ -325,7 +333,7 @@ namespace Robust.Client.Graphics.Clyde
screenBufferHandle = new GLHandle(GL.GenTexture());
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
ApplySampleParameters(TextureSampleParameters.Default);
ScreenBufferTexture = GenTexture(screenBufferHandle, _windowing!.MainWindow!.FramebufferSize, true, null, TexturePixelType.Rgba32);
ScreenBufferTexture = GenTexture(screenBufferHandle, _mainWindow!.FramebufferSize, true, null, TexturePixelType.Rgba32);
}
private GLHandle MakeQuadVao()
@@ -468,7 +476,7 @@ namespace Robust.Client.Graphics.Clyde
[Conditional("DEBUG")]
private void PushDebugGroupMaybe(string group)
{
if (!_hasGLKhrDebug)
if (!_hasGLKhrDebug || true)
{
return;
}
@@ -486,7 +494,7 @@ namespace Robust.Client.Graphics.Clyde
[Conditional("DEBUG")]
private void PopDebugGroupMaybe()
{
if (!_hasGLKhrDebug)
if (!_hasGLKhrDebug || true)
{
return;
}
@@ -503,6 +511,7 @@ namespace Robust.Client.Graphics.Clyde
public void Shutdown()
{
_glContext?.Shutdown();
ShutdownWindowing();
_shutdownAudio();
}

View File

@@ -0,0 +1,139 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace Robust.Client.Graphics.Clyde
{
/// <summary>
/// Minimal ANGLE EGL API P/Invokes.
/// </summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "IdentifierTypo")]
internal static unsafe class Egl
{
// ANGLE exports all the functions we need directly on the dll. No need to do eglGetProcAddress for EGL itself.
// Still need it for OpenGL functions, however.
private const string LibraryName = "libEGL.dll";
public const int EGL_FALSE = 0;
public const int EGL_TRUE = 1;
public const int EGL_NONE = 0x3038;
public const int EGL_VENDOR = 0x3053;
public const int EGL_VERSION = 0x3054;
public const int EGL_EXTENSIONS = 0x3055;
public const int EGL_CONFIG_ID = 0x3028;
public const int EGL_COLOR_BUFFER_TYPE = 0x303F;
public const int EGL_SAMPLES = 0x3031;
public const int EGL_SAMPLE_BUFFERS = 0x3032;
public const int EGL_CONFIG_CAVEAT = 0x3027;
public const int EGL_CONFORMANT = 0x3042;
public const int EGL_NATIVE_VISUAL_ID = 0x302E;
public const int EGL_SURFACE_TYPE = 0x3033;
public const int EGL_ALPHA_SIZE = 0x3021;
public const int EGL_BLUE_SIZE = 0x3022;
public const int EGL_GREEN_SIZE = 0x3023;
public const int EGL_RED_SIZE = 0x3024;
public const int EGL_DEPTH_SIZE = 0x3025;
public const int EGL_STENCIL_SIZE = 0x3026;
public const int EGL_WINDOW_BIT = 0x0004;
public const int EGL_OPENGL_ES_API = 0x30A0;
public const int EGL_RENDERABLE_TYPE = 0x3040;
public const int EGL_OPENGL_ES3_BIT = 0x00000040;
public const int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
public const int EGL_TEXTURE_FORMAT = 0x3080;
public const int EGL_TEXTURE_RGBA = 0x305E;
public const int EGL_TEXTURE_TARGET = 0x3081;
public const int EGL_TEXTURE_2D = 0x305F;
public const int EGL_GL_COLORSPACE = 0x309D;
public const int EGL_GL_COLORSPACE_SRGB = 0x3089;
public const int EGL_PLATFORM_ANGLE_ANGLE = 0x3202;
public const nint EGL_NO_CONTEXT = 0;
public const nint EGL_NO_DEVICE_EXT = 0;
public const nint EGL_NO_SURFACE = 0;
public const int EGL_D3D_TEXTURE_ANGLE = 0x33A3;
public const int EGL_D3D11_DEVICE_ANGLE = 0x33A1;
public const int EGL_PLATFORM_DEVICE_EXT = 0x313F;
public const int EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE = 0x33A7;
[DllImport(LibraryName)]
public static extern int eglInitialize(void* display, int* major, int* minor);
[DllImport(LibraryName)]
public static extern int eglTerminate(void* display);
[DllImport(LibraryName)]
public static extern void* eglGetDisplay(void* display);
[DllImport(LibraryName)]
public static extern void* eglGetPlatformDisplayEXT(int platform, void* native_display, nint* attrib_list);
[DllImport(LibraryName)]
public static extern int eglBindAPI(int api);
[DllImport(LibraryName)]
public static extern void* eglCreateDeviceANGLE(int device_type, void* native_device, nint* attrib_list);
[DllImport(LibraryName)]
public static extern int eglReleaseDeviceANGLE(void* device);
[DllImport(LibraryName)]
public static extern void* eglGetProcAddress(byte* procname);
[DllImport(LibraryName)]
public static extern int eglChooseConfig(
void* display,
int* attrib_list,
void** configs,
int config_size,
int* num_config);
[DllImport(LibraryName)]
public static extern int eglGetConfigAttrib(void* display, void* config, int attribute, int* value);
[DllImport(LibraryName)]
public static extern void* eglCreateContext(void* display, void* config, void* share_context, int* attrib_list);
[DllImport(LibraryName)]
public static extern int eglGetError();
[DllImport(LibraryName)]
public static extern byte* eglQueryString(void* display, int name);
[DllImport(LibraryName)]
public static extern void* eglCreatePbufferFromClientBuffer(
void* display,
int buftype,
void* buffer,
void* config,
int* attrib_list);
[DllImport(LibraryName)]
public static extern void* eglCreateWindowSurface(
void* display,
void* config,
void* native_window,
int* attrib_list);
[DllImport(LibraryName)]
public static extern int eglMakeCurrent(
void* display,
void* draw,
void* read,
void* context);
[DllImport(LibraryName)]
public static extern void* eglGetCurrentContext();
[DllImport(LibraryName)]
public static extern int eglSwapBuffers(void* display, void* surface);
[DllImport(LibraryName)]
public static extern int eglSwapInterval(void* display, int interval);
[DllImport(LibraryName)]
public static extern int eglDestroySurface(void* display, void* surface);
}
}

View File

@@ -0,0 +1,508 @@
/*
// Commented out because I can't be bothered to figure out trimming for TerraFX.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using TerraFX.Interop;
using static Robust.Client.Graphics.Clyde.Egl;
using static TerraFX.Interop.D3D_DRIVER_TYPE;
using static TerraFX.Interop.D3D_FEATURE_LEVEL;
using static TerraFX.Interop.DXGI_FORMAT;
using static TerraFX.Interop.DXGI_MEMORY_SEGMENT_GROUP;
using static TerraFX.Interop.DXGI_SWAP_EFFECT;
using static TerraFX.Interop.Windows;
namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
/// <summary>
/// Explicit ANGLE GL context with manual DXGI/D3D device and swap chain management.
/// </summary>
private sealed unsafe class GLContextAngle : GLContextBase
{
// Thanks to mpv's implementation of context_angle for inspiration/hints.
// https://github.com/mpv-player/mpv/blob/f8e62d3d82dd0a3d06f9a557d756f0ad78118cc7/video/out/opengl/context_angle.c
// NOTE: This class only handles GLES3/D3D11.
// For anything lower we just let ANGLE fall back and do the work 100%.
private IDXGIFactory1* _factory;
private IDXGIAdapter1* _adapter;
private ID3D11Device* _device;
private ID3D11DeviceContext* _deviceContext;
private void* _eglDevice;
private void* _eglDisplay;
private void* _eglContext;
private void* _eglConfig;
private uint _swapInterval;
private readonly Dictionary<WindowId, WindowData> _windowData = new();
public override bool GlesOnly => true;
public GLContextAngle(Clyde clyde) : base(clyde)
{
}
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
{
// Do not initialize GL context on the window directly, we use ANGLE.
return null;
}
public override void UpdateVSync()
{
_swapInterval = (uint) (Clyde._vSync ? 1 : 0);
}
public override void WindowCreated(WindowReg reg)
{
var data = new WindowData
{
Reg = reg
};
_windowData[reg.Id] = data;
IDXGIFactory2* factory2;
var iid = IID_IDXGIFactory2;
if (FAILED(_factory->QueryInterface(&iid, (void**) &factory2)))
factory2 = null;
var hWnd = Clyde._windowing!.WindowGetWin32Window(reg)!.Value;
// todo: exception management.
if (factory2 != null && !Clyde._cfg.GetCVar(CVars.DisplayAngleDxgi1))
{
CreateSwapChain2(factory2, hWnd, data);
}
else
{
CreateSwapChain1(data);
}
var rt = Clyde.RtToLoaded(reg.RenderTarget);
rt.FlipY = true;
if (factory2 != null)
factory2->Release();
if (reg.IsMainWindow)
{
UpdateVSync();
eglMakeCurrent(_eglDisplay, data.EglBackbuffer, data.EglBackbuffer, _eglContext);
}
}
private void DestroyBackbuffer(WindowData data)
{
if (data.EglBackbuffer != null)
{
eglMakeCurrent(_eglDisplay, null, null, null);
eglDestroySurface(_eglDisplay, data.EglBackbuffer);
data.EglBackbuffer = null;
}
data.Backbuffer->Release();
data.Backbuffer = null;
}
private void SetupBackbuffer(WindowData data)
{
DebugTools.Assert(data.Backbuffer == null, "Backbuffer must have been released!");
DebugTools.Assert(data.EglBackbuffer == null, "EGL Backbuffer must have been released!");
fixed (ID3D11Texture2D** texPtr = &data.Backbuffer)
{
var iid = IID_ID3D11Texture2D;
ThrowIfFailed("GetBuffer", data.SwapChain->GetBuffer(0, &iid, (void**) texPtr));
}
var attributes = stackalloc int[]
{
EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,
EGL_NONE
};
data.EglBackbuffer = eglCreatePbufferFromClientBuffer(
_eglDisplay,
EGL_D3D_TEXTURE_ANGLE,
data.Backbuffer,
_eglConfig,
attributes);
}
private void CreateSwapChain2(IDXGIFactory2* factory2, nint hWnd, WindowData data)
{
var desc = new DXGI_SWAP_CHAIN_DESC1
{
Width = (uint) data.Reg.FramebufferSize.X,
Height = (uint) data.Reg.FramebufferSize.Y,
Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
SampleDesc =
{
Count = 1
},
BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT,
BufferCount = 2,
SwapEffect = DXGI_SWAP_EFFECT_DISCARD
};
IDXGISwapChain1* swapChain1;
ThrowIfFailed("CreateSwapChainForHwnd", factory2->CreateSwapChainForHwnd(
(IUnknown*) _device,
hWnd,
&desc,
null,
null,
&swapChain1
));
fixed (IDXGISwapChain** swapPtr = &data.SwapChain)
{
var iid = IID_IDXGISwapChain;
ThrowIfFailed("QueryInterface", swapChain1->QueryInterface(&iid, (void**) swapPtr));
}
swapChain1->Release();
SetupBackbuffer(data);
}
private void CreateSwapChain1(WindowData data)
{
throw new NotImplementedException();
}
public override void WindowDestroyed(WindowReg reg)
{
throw new NotImplementedException();
}
public bool TryInitialize()
{
try
{
TryInitializeCore();
}
catch (Exception e)
{
Logger.ErrorS("clyde.ogl.angle", $"Failed to initialize custom ANGLE: {e}");
Shutdown();
return false;
}
return true;
}
private void TryInitializeCore()
{
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(null, EGL_EXTENSIONS));
Logger.DebugS("clyde.ogl.angle", $"EGL client extensions: {extensions}!");
CreateD3D11Device();
CreateEglContext();
}
private void CreateEglContext()
{
_eglDevice = eglCreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE, _device, null);
if (_eglDevice == (void*) EGL_NO_DEVICE_EXT)
throw new Exception("eglCreateDeviceANGLE failed.");
_eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, _eglDevice, null);
if (_eglDisplay == null)
throw new Exception("eglGetPlatformDisplayEXT failed.");
int major;
int minor;
if (eglInitialize(_eglDisplay, &major, &minor) == EGL_FALSE)
throw new Exception("eglInitialize failed.");
var vendor = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VENDOR));
var version = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VERSION));
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_EXTENSIONS));
Logger.DebugS("clyde.ogl.angle", "EGL initialized!");
Logger.DebugS("clyde.ogl.angle", $"EGL vendor: {vendor}!");
Logger.DebugS("clyde.ogl.angle", $"EGL version: {version}!");
Logger.DebugS("clyde.ogl.angle", $"EGL extensions: {extensions}!");
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE)
throw new Exception("eglBindAPI failed.");
var attribs = stackalloc int[]
{
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_STENCIL_SIZE, 8,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_NONE
};
var numConfigs = 0;
if (eglChooseConfig(_eglDisplay, attribs, null, 0, &numConfigs) == EGL_FALSE)
throw new Exception("eglChooseConfig failed.");
var configs = stackalloc void*[numConfigs];
if (eglChooseConfig(_eglDisplay, attribs, configs, numConfigs, &numConfigs) == EGL_FALSE)
throw new Exception("eglChooseConfig failed.");
if (numConfigs == 0)
throw new Exception("No compatible EGL configurations returned!");
Logger.DebugS("clyde.ogl.angle", $"{numConfigs} EGL configs possible!");
for (var i = 0; i < numConfigs; i++)
{
Logger.DebugS("clyde.ogl.angle", DumpEglConfig(_eglDisplay, configs[i]));
}
_eglConfig = configs[0];
var createAttribs = stackalloc int[]
{
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
};
_eglContext = eglCreateContext(_eglDisplay, _eglConfig, null, createAttribs);
if (_eglContext == (void*) EGL_NO_CONTEXT)
throw new Exception("eglCreateContext failed!");
Logger.DebugS("clyde.ogl.angle", "EGL context created!");
}
private void CreateD3D11Device()
{
IDXGIDevice1* dxgiDevice = null;
try
{
var iid = IID_IDXGIFactory1;
fixed (IDXGIFactory1** ptr = &_factory)
{
ThrowIfFailed(nameof(CreateDXGIFactory1), CreateDXGIFactory1(&iid, (void**) ptr));
}
// Try to find the correct adapter if specified.
var adapterName = Clyde._cfg.GetCVar(CVars.DisplayAdapter);
if (adapterName != "")
{
_adapter = TryFindAdapterWithName(adapterName);
if (_adapter == null)
{
Logger.WarningS("clyde.ogl.angle",
$"Unable to find display adapter with requested name: {adapterName}");
}
Logger.DebugS("clyde.ogl.angle", $"Found display adapter with name: {adapterName}");
}
var featureLevels = stackalloc D3D_FEATURE_LEVEL[]
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0
};
fixed (ID3D11Device** device = &_device)
{
ThrowIfFailed("D3D11CreateDevice", D3D11CreateDevice(
(IDXGIAdapter*) _adapter,
_adapter == null ? D3D_DRIVER_TYPE_HARDWARE : D3D_DRIVER_TYPE_UNKNOWN,
IntPtr.Zero,
0,
featureLevels,
1,
D3D11_SDK_VERSION,
device,
null,
null
));
}
// Get adapter from the device.
iid = IID_IDXGIDevice1;
ThrowIfFailed("QueryInterface", _device->QueryInterface(&iid, (void**) &dxgiDevice));
fixed (IDXGIAdapter1** ptrAdapter = &_adapter)
{
iid = IID_IDXGIAdapter1;
ThrowIfFailed("GetParent", dxgiDevice->GetParent(&iid, (void**) ptrAdapter));
}
Logger.DebugS("clyde.ogl.angle", "Successfully created D3D11 device!");
}
finally
{
if (dxgiDevice != null)
dxgiDevice->Release();
}
}
public override void Shutdown()
{
// Shut down ANGLE.
if (_eglDisplay != null)
eglTerminate(_eglDisplay);
if (_eglDevice != null)
eglReleaseDeviceANGLE(_eglDevice);
// Shut down D3D11/DXGI
if (_factory != null)
_factory->Release();
if (_adapter != null)
_adapter->Release();
if (_device != null)
_device->Release();
}
public override void SwapAllBuffers()
{
foreach (var data in _windowData.Values)
{
data.SwapChain->Present(_swapInterval, 0);
}
}
public override void WindowResized(WindowReg reg, Vector2i oldSize)
{
var data = _windowData[reg.Id];
DestroyBackbuffer(data);
ThrowIfFailed("ResizeBuffers", data.SwapChain->ResizeBuffers(
2,
(uint) reg.FramebufferSize.X, (uint) reg.FramebufferSize.Y,
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
0));
SetupBackbuffer(data);
if (reg.IsMainWindow)
eglMakeCurrent(_eglDisplay, data.EglBackbuffer, data.EglBackbuffer, _eglContext);
}
private IDXGIAdapter1* TryFindAdapterWithName(string name)
{
uint idx = 0;
while (true)
{
IDXGIAdapter1* adapter;
var hr = _factory->EnumAdapters1(idx++, &adapter);
if (hr == DXGI_ERROR_NOT_FOUND)
break;
ThrowIfFailed("EnumAdapters1", hr);
DXGI_ADAPTER_DESC1 desc;
ThrowIfFailed("GetDesc1", adapter->GetDesc1(&desc));
var descName = new ReadOnlySpan<char>(desc.Description, 128);
if (descName.StartsWith(name))
return adapter;
adapter->Release();
}
return null;
}
public override void* GetProcAddress(string name)
{
Span<byte> buf = stackalloc byte[128];
var len = Encoding.UTF8.GetBytes(name, buf);
buf[len] = 0;
fixed (byte* ptr = &buf[0])
{
return eglGetProcAddress(ptr);
}
}
public override void BindWindowRenderTarget(WindowId rtWindowId)
{
var data = _windowData[rtWindowId];
var result = eglMakeCurrent(_eglDisplay, data.EglBackbuffer, data.EglBackbuffer, _eglContext);
if (result == EGL_FALSE)
throw new Exception("eglMakeCurrent failed.");
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
Clyde.CheckGlError();
}
private static void ThrowIfFailed(string methodName, HRESULT hr)
{
if (FAILED(hr))
{
Marshal.ThrowExceptionForHR(hr);
}
}
private static string DumpEglConfig(void* display, void* config)
{
var sb = new StringBuilder();
sb.Append($"cfg: {Get(EGL_CONFIG_ID):00} | ");
sb.AppendFormat(
"R/G/B/A/D/S: {0}/{1}/{2}/{3}/{4:00}/{5} | ",
Get(EGL_RED_SIZE), Get(EGL_GREEN_SIZE), Get(EGL_BLUE_SIZE), Get(EGL_ALPHA_SIZE),
Get(EGL_DEPTH_SIZE), Get(EGL_STENCIL_SIZE));
// COLOR_BUFFER_TYPE
sb.Append($"CBT: {Get(EGL_COLOR_BUFFER_TYPE)} | ");
sb.Append($"CC: {Get(EGL_CONFIG_CAVEAT)} | ");
sb.Append($"CONF: {Get(EGL_CONFORMANT)} | ");
sb.Append($"NAT: {Get(EGL_NATIVE_VISUAL_ID)} | ");
sb.Append($"SAMPLES: {Get(EGL_SAMPLES)} | ");
sb.Append($"SAMPLE_BUFFERS: {Get(EGL_SAMPLE_BUFFERS)} | ");
sb.Append($"ORIENTATION: {Get(EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE)}");
return sb.ToString();
int Get(int attrib)
{
int value;
if (eglGetConfigAttrib(display, config, attrib, &value) == EGL_FALSE)
throw new Exception("eglGetConfigAttrib failed!");
return value;
}
}
private sealed class WindowData
{
public WindowReg Reg = default!;
public IDXGISwapChain* SwapChain;
public ID3D11Texture2D* Backbuffer;
public void* EglBackbuffer;
}
}
}
}
*/

View File

@@ -0,0 +1,115 @@
using System;
using OpenToolkit;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
/// <summary>
/// Manages OpenGL contexts for the windowing system.
/// </summary>
private abstract class GLContextBase
{
protected readonly Clyde Clyde;
public IBindingsContext BindingsContext { get; }
public GLContextBase(Clyde clyde)
{
Clyde = clyde;
BindingsContext = new BindingsContextImpl(this);
}
public GLContextSpec? GetNewWindowSpec()
{
return SpecWithOpenGLVersion(Clyde._openGLVersion);
}
public abstract GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version);
public abstract void UpdateVSync();
public abstract void WindowCreated(WindowReg reg);
public abstract void WindowDestroyed(WindowReg reg);
public abstract void Shutdown();
public abstract bool GlesOnly { get; }
protected static GLContextSpec GetVersionSpec(RendererOpenGLVersion version)
{
var spec = new GLContextSpec();
switch (version)
{
case RendererOpenGLVersion.GL33:
spec.Major = 3;
spec.Minor = 3;
spec.Profile = GLContextProfile.Core;
spec.CreationApi = GLContextCreationApi.Native;
break;
case RendererOpenGLVersion.GL31:
spec.Major = 3;
spec.Minor = 1;
spec.Profile = GLContextProfile.Compatibility;
spec.CreationApi = GLContextCreationApi.Native;
break;
case RendererOpenGLVersion.GLES3:
spec.Major = 3;
spec.Minor = 0;
spec.Profile = GLContextProfile.Es;
// Initializing ES on Windows EGL so that we can use ANGLE.
spec.CreationApi = OperatingSystem.IsWindows()
? GLContextCreationApi.Egl
: GLContextCreationApi.Native;
break;
case RendererOpenGLVersion.GLES2:
spec.Major = 2;
spec.Minor = 0;
spec.Profile = GLContextProfile.Es;
// Initializing ES on Windows EGL so that we can use ANGLE.
spec.CreationApi = OperatingSystem.IsWindows()
? GLContextCreationApi.Egl
: GLContextCreationApi.Native;
break;
default:
throw new ArgumentOutOfRangeException();
}
return spec;
}
public abstract void SwapAllBuffers();
public abstract void WindowResized(WindowReg reg, Vector2i oldSize);
public abstract unsafe void* GetProcAddress(string name);
public abstract void BindWindowRenderTarget(WindowId rtWindowId);
public virtual void BeforeSharedWindowCreateUnbind()
{
}
private sealed class BindingsContextImpl : IBindingsContext
{
private readonly GLContextBase _context;
public BindingsContextImpl(GLContextBase context)
{
_context = context;
}
public unsafe IntPtr GetProcAddress(string procName)
{
return (nint)_context.GetProcAddress(procName);
}
}
}
}
}

View File

@@ -0,0 +1,293 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using OpenToolkit;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using static Robust.Client.Graphics.Clyde.Egl;
namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
/// <summary>
/// Context manager that uses EGL directly so that we get better control over multi-window management.
/// </summary>
private sealed unsafe class GLContextEgl : GLContextBase
{
// TODO: Currently this class uses ANGLE and Windows-specific initialization code.
// It could be made more general purpose later if anybody ever gets adventurous with like, Wayland.
private readonly Dictionary<WindowId, WindowData> _windowData = new();
private void* _eglDisplay;
private void* _eglContext;
private void* _eglConfig;
public GLContextEgl(Clyde clyde) : base(clyde)
{
}
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
{
return null;
}
public override void UpdateVSync()
{
var interval = Clyde._vSync ? 1 : 0;
eglSwapInterval(_eglDisplay, interval);
}
public void InitializePublic()
{
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(null, EGL_EXTENSIONS));
Logger.DebugS("clyde.ogl.egl", $"EGL client extensions: {extensions}!");
}
public override void WindowCreated(WindowReg reg)
{
var data = new WindowData
{
Reg = reg
};
_windowData[reg.Id] = data;
if (reg.IsMainWindow)
Initialize(data);
var attribs = stackalloc int[]
{
EGL_GL_COLORSPACE, EGL_GL_COLORSPACE_SRGB,
EGL_NONE
};
if (OperatingSystem.IsWindows())
{
// Set up window surface.
var hWNd = Clyde._windowing!.WindowGetWin32Window(reg)!.Value;
data.EglSurface = eglCreateWindowSurface(_eglDisplay, _eglConfig, (void*) hWNd, attribs);
if (data.EglSurface == (void*) EGL_NO_SURFACE)
throw new Exception("eglCreateWindowSurface failed.");
}
else if (OperatingSystem.IsLinux())
{
var window = Clyde._windowing!.WindowGetX11Id(reg)!.Value;
data.EglSurface = eglCreateWindowSurface(_eglDisplay, _eglConfig, (void*) window, attribs);
if (data.EglSurface == (void*) EGL_NO_SURFACE)
throw new Exception("eglCreateWindowSurface failed.");
}
else
{
throw new NotSupportedException("EGL is not currently supported outside Windows ANGLE or X11 Linux");
}
if (reg.IsMainWindow)
{
var result = eglMakeCurrent(_eglDisplay, data.EglSurface, data.EglSurface, _eglContext);
if (result == EGL_FALSE)
throw new Exception("eglMakeCurrent failed.");
}
}
public override void WindowDestroyed(WindowReg reg)
{
var data = _windowData[reg.Id];
eglDestroySurface(_eglDisplay, data.EglSurface);
}
private void Initialize(WindowData mainWindow)
{
if (OperatingSystem.IsWindows())
{
// Setting up ANGLE without manually selecting a D3D11 device requires a windows DC.
mainWindow.DC = GetDC(Clyde._windowing!.WindowGetWin32Window(mainWindow.Reg)!.Value);
_eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, (void*) mainWindow.DC, null);
if (_eglDisplay == null)
throw new Exception("eglGetPlatformDisplayEXT failed.");
}
else if (OperatingSystem.IsLinux())
{
var xDisplay = Clyde._windowing!.WindowGetX11Display(mainWindow.Reg)!.Value;
_eglDisplay = eglGetDisplay((void*) xDisplay);
if (mainWindow.EglSurface == (void*) EGL_NO_SURFACE)
throw new Exception("eglCreateWindowSurface failed.");
}
else
{
throw new NotSupportedException("EGL is not currently supported outside Windows ANGLE or X11 Linux");
}
int major;
int minor;
if (eglInitialize(_eglDisplay, &major, &minor) == EGL_FALSE)
throw new Exception("eglInitialize failed.");
var vendor = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VENDOR));
var version = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VERSION));
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_EXTENSIONS));
Logger.DebugS("clyde.ogl.egl", "EGL initialized!");
Logger.DebugS("clyde.ogl.egl", $"EGL vendor: {vendor}!");
Logger.DebugS("clyde.ogl.egl", $"EGL version: {version}!");
Logger.DebugS("clyde.ogl.egl", $"EGL extensions: {extensions}!");
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE)
throw new Exception("eglBindAPI failed.");
var attribs = stackalloc int[]
{
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_STENCIL_SIZE, 8,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_NONE
};
var numConfigs = 0;
if (eglChooseConfig(_eglDisplay, attribs, null, 0, &numConfigs) == EGL_FALSE)
throw new Exception("eglChooseConfig failed.");
var configs = stackalloc void*[numConfigs];
if (eglChooseConfig(_eglDisplay, attribs, configs, numConfigs, &numConfigs) == EGL_FALSE)
throw new Exception("eglChooseConfig failed.");
if (numConfigs == 0)
throw new Exception("No compatible EGL configurations returned!");
Logger.DebugS("clyde.ogl.egl", $"{numConfigs} EGL configs possible!");
for (var i = 0; i < numConfigs; i++)
{
Logger.DebugS("clyde.ogl.egl", DumpEglConfig(_eglDisplay, configs[i]));
}
_eglConfig = configs[0];
var createAttribs = stackalloc int[]
{
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
};
_eglContext = eglCreateContext(_eglDisplay, _eglConfig, null, createAttribs);
if (_eglContext == (void*) EGL_NO_CONTEXT)
throw new Exception("eglCreateContext failed!");
Logger.DebugS("clyde.ogl.egl", "EGL context created!");
}
public override void Shutdown()
{
if (_eglDisplay != null)
{
eglMakeCurrent(_eglDisplay, null, null, null);
eglTerminate(_eglDisplay);
}
}
public override bool GlesOnly => true;
public override void SwapAllBuffers()
{
foreach (var data in _windowData.Values)
{
eglSwapBuffers(_eglDisplay, data.EglSurface);
}
}
public override void WindowResized(WindowReg reg, Vector2i oldSize)
{
// Nada..?
}
public override void* GetProcAddress(string name)
{
Span<byte> buf = stackalloc byte[128];
var len = Encoding.UTF8.GetBytes(name, buf);
buf[len] = 0;
fixed (byte* ptr = &buf[0])
{
return eglGetProcAddress(ptr);
}
}
public override void BindWindowRenderTarget(WindowId rtWindowId)
{
var data = _windowData[rtWindowId];
var result = eglMakeCurrent(_eglDisplay, data.EglSurface, data.EglSurface, _eglContext);
if (result == EGL_FALSE)
throw new Exception("eglMakeCurrent failed.");
}
private static string DumpEglConfig(void* display, void* config)
{
var sb = new StringBuilder();
sb.Append($"cfg: {Get(EGL_CONFIG_ID):00} | ");
sb.AppendFormat(
"R/G/B/A/D/S: {0}/{1}/{2}/{3}/{4:00}/{5} | ",
Get(EGL_RED_SIZE), Get(EGL_GREEN_SIZE), Get(EGL_BLUE_SIZE), Get(EGL_ALPHA_SIZE),
Get(EGL_DEPTH_SIZE), Get(EGL_STENCIL_SIZE));
// COLOR_BUFFER_TYPE
sb.Append($"CBT: {Get(EGL_COLOR_BUFFER_TYPE)} | ");
sb.Append($"CC: {Get(EGL_CONFIG_CAVEAT)} | ");
sb.Append($"CONF: {Get(EGL_CONFORMANT)} | ");
sb.Append($"NAT: {Get(EGL_NATIVE_VISUAL_ID)} | ");
sb.Append($"SAMPLES: {Get(EGL_SAMPLES)} | ");
sb.Append($"SAMPLE_BUFFERS: {Get(EGL_SAMPLE_BUFFERS)}");
return sb.ToString();
int Get(int attrib)
{
int value;
if (eglGetConfigAttrib(display, config, attrib, &value) == EGL_FALSE)
throw new Exception("eglGetConfigAttrib failed!");
return value;
}
}
private sealed class WindowData
{
public WindowReg Reg = default!;
// ReSharper disable once InconsistentNaming
// Windows DC for this window.
// Only used for main window.
public nint DC;
public void* EglSurface;
}
[DllImport("user32.dll")]
private static extern nint GetDC(nint hWnd);
private sealed class EglBindingsContext : IBindingsContext
{
public IntPtr GetProcAddress(string procName)
{
Span<byte> buf = stackalloc byte[128];
buf.Clear();
Encoding.UTF8.GetBytes(procName, buf);
fixed (byte* b = &buf.GetPinnableReference())
{
return (nint) eglGetProcAddress(b);
}
}
}
}
}
}

View File

@@ -0,0 +1,303 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Channels;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
/// <summary>
/// GL Context(s) provided by the windowing system (GLFW, SDL2...)
/// </summary>
private sealed class GLContextWindow : GLContextBase
{
private readonly Dictionary<WindowId, WindowData> _windowData = new();
public override bool GlesOnly => false;
public GLContextWindow(Clyde clyde) : base(clyde)
{
}
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
{
return GetVersionSpec(version);
}
public override void UpdateVSync()
{
if (Clyde._mainWindow == null)
return;
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow);
Clyde._windowing.GLSwapInterval(Clyde._vSync ? 1 : 0);
}
public override void WindowCreated(WindowReg reg)
{
reg.RenderTarget.MakeGLFence = true;
var data = new WindowData
{
Reg = reg
};
_windowData[reg.Id] = data;
if (reg.IsMainWindow)
{
UpdateVSync();
}
else
{
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow);
CreateWindowRenderTexture(data);
InitWindowBlitThread(data);
}
}
public override void WindowDestroyed(WindowReg reg)
{
var data = _windowData[reg.Id];
data.BlitDoneEvent?.Set();
}
public override void Shutdown()
{
// Nada, window system shutdown handles it.
}
public override void SwapAllBuffers()
{
BlitSecondaryWindows();
Clyde._windowing!.WindowSwapBuffers(Clyde._mainWindow!);
}
public override void WindowResized(WindowReg reg, Vector2i oldSize)
{
if (reg.IsMainWindow)
return;
// Recreate render texture for the window.
var data = _windowData[reg.Id];
data.RenderTexture!.Dispose();
CreateWindowRenderTexture(data);
}
public override unsafe void* GetProcAddress(string name)
{
return Clyde._windowing!.GLGetProcAddress(name);
}
public override void BindWindowRenderTarget(WindowId rtWindowId)
{
var data = _windowData[rtWindowId];
if (data.Reg.IsMainWindow)
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
Clyde.CheckGlError();
}
else
{
var loaded = Clyde.RtToLoaded(data.RenderTexture!);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, loaded.FramebufferHandle.Handle);
}
}
public override void BeforeSharedWindowCreateUnbind()
{
Clyde._windowing!.GLMakeContextCurrent(null);
}
private void BlitSecondaryWindows()
{
// Only got main window.
if (Clyde._windows.Count == 1)
return;
if (!Clyde._hasGLFenceSync && Clyde._cfg.GetCVar(CVars.DisplayForceSyncWindows))
{
GL.Finish();
}
if (Clyde.EffectiveThreadWindowBlit)
{
foreach (var window in _windowData.Values)
{
if (window.Reg.IsMainWindow)
continue;
window.BlitDoneEvent!.Reset();
window.BlitStartEvent!.Set();
window.BlitDoneEvent.Wait();
}
}
else
{
foreach (var window in _windowData.Values)
{
if (window.Reg.IsMainWindow)
continue;
Clyde._windowing!.GLMakeContextCurrent(window.Reg);
BlitThreadDoSecondaryWindowBlit(window);
}
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow!);
}
}
private void BlitThreadDoSecondaryWindowBlit(WindowData window)
{
var rt = window.RenderTexture!;
if (Clyde._hasGLFenceSync)
{
// 0xFFFFFFFFFFFFFFFFUL is GL_TIMEOUT_IGNORED
var sync = rt.LastGLSync;
GL.WaitSync(sync, WaitSyncFlags.None, unchecked((long) 0xFFFFFFFFFFFFFFFFUL));
Clyde.CheckGlError();
}
GL.Viewport(0, 0, window.Reg.FramebufferSize.X, window.Reg.FramebufferSize.Y);
Clyde.CheckGlError();
Clyde.SetTexture(TextureUnit.Texture0, window.RenderTexture!.Texture);
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
Clyde.CheckGlError();
window.BlitDoneEvent?.Set();
Clyde._windowing!.WindowSwapBuffers(window.Reg);
}
private void BlitThreadInit(WindowData reg)
{
Clyde._windowing!.GLMakeContextCurrent(reg.Reg);
Clyde._windowing.GLSwapInterval(0);
if (!Clyde._isGLES)
GL.Enable(EnableCap.FramebufferSrgb);
var vao = GL.GenVertexArray();
GL.BindVertexArray(vao);
GL.BindBuffer(BufferTarget.ArrayBuffer, Clyde.WindowVBO.ObjectHandle);
// Vertex Coords
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 0);
GL.EnableVertexAttribArray(0);
// Texture Coords.
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, Vertex2D.SizeOf, 2 * sizeof(float));
GL.EnableVertexAttribArray(1);
var program = Clyde._compileProgram(
Clyde._winBlitShaderVert,
Clyde._winBlitShaderFrag,
new (string, uint)[]
{
("aPos", 0),
("tCoord", 1),
},
includeLib: false);
GL.UseProgram(program.Handle);
var loc = GL.GetUniformLocation(program.Handle, "tex");
Clyde.SetTexture(TextureUnit.Texture0, reg.RenderTexture!.Texture);
GL.Uniform1(loc, 0);
}
private void InitWindowBlitThread(WindowData reg)
{
if (Clyde.EffectiveThreadWindowBlit)
{
reg.BlitStartEvent = new ManualResetEventSlim();
reg.BlitDoneEvent = new ManualResetEventSlim();
reg.BlitThread = new Thread(() => BlitThread(reg))
{
Name = $"WinBlitThread ID:{reg.Reg.Id}",
IsBackground = true
};
// System.Console.WriteLine("A");
reg.BlitThread.Start();
// Wait for thread to finish init.
reg.BlitDoneEvent.Wait();
}
else
{
// Binds GL context.
BlitThreadInit(reg);
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow!);
}
}
private void BlitThread(WindowData reg)
{
BlitThreadInit(reg);
reg.BlitDoneEvent!.Set();
try
{
while (true)
{
reg.BlitStartEvent!.Wait();
if (reg.Reg.IsDisposed)
{
BlitThreadCleanup(reg);
return;
}
reg.BlitStartEvent!.Reset();
// Do channel blit.
BlitThreadDoSecondaryWindowBlit(reg);
}
}
catch (AggregateException e)
{
// ok channel closed, we exit.
e.Handle(ec => ec is ChannelClosedException);
}
}
private static void BlitThreadCleanup(WindowData reg)
{
reg.BlitDoneEvent!.Dispose();
reg.BlitStartEvent!.Dispose();
}
private void CreateWindowRenderTexture(WindowData reg)
{
reg.RenderTexture?.Dispose();
reg.RenderTexture = Clyde.CreateRenderTarget(reg.Reg.FramebufferSize, new RenderTargetFormatParameters
{
ColorFormat = RenderTargetColorFormat.Rgba8Srgb,
HasDepthStencil = true
});
// Necessary to correctly sync multi-context blitting.
reg.RenderTexture.MakeGLFence = true;
}
private sealed class WindowData
{
public WindowReg Reg = default!;
public RenderTexture? RenderTexture;
// Used EXCLUSIVELY to run the two rendering commands to blit to the window.
public Thread? BlitThread;
public ManualResetEventSlim? BlitStartEvent;
public ManualResetEventSlim? BlitDoneEvent;
}
}
}
}

View File

@@ -67,14 +67,14 @@ highp vec4 zToSrgb(highp vec4 sRGB)
#ifdef HAS_UNIFORM_BUFFERS
layout (std140) uniform projectionViewMatrices
{
mat3 projectionMatrix;
mat3 viewMatrix;
highp mat3 projectionMatrix;
highp mat3 viewMatrix;
};
layout (std140) uniform uniformConstants
{
vec2 SCREEN_PIXEL_SIZE;
float TIME;
highp vec2 SCREEN_PIXEL_SIZE;
highp float TIME;
};
#else
uniform highp mat3 projectionMatrix;

View File

@@ -0,0 +1,61 @@
/*
using System;
using System.Runtime.InteropServices;
using Robust.Shared.Console;
using Robust.Shared.Utility;
using TerraFX.Interop;
using static TerraFX.Interop.D3D_DRIVER_TYPE;
using static TerraFX.Interop.D3D_FEATURE_LEVEL;
using static TerraFX.Interop.DXGI_MEMORY_SEGMENT_GROUP;
using static TerraFX.Interop.DXGI_SWAP_EFFECT;
using static TerraFX.Interop.Windows;
namespace Robust.Client.Graphics.Clyde
{
public sealed class VramCommand : IConsoleCommand
{
public string Command => "vram";
public string Description => "Checks vram";
public string Help => "Usage: vram";
public unsafe void Execute(IConsoleShell shell, string argStr, string[] args)
{
IDXGIFactory1* dxgiFactory;
var iid = IID_IDXGIFactory1;
ThrowIfFailed(nameof(CreateDXGIFactory1), CreateDXGIFactory1(&iid, (void**) &dxgiFactory));
uint idx = 0;
IDXGIAdapter* adapter;
while (dxgiFactory->EnumAdapters(idx, &adapter) != DXGI_ERROR_NOT_FOUND)
{
DXGI_ADAPTER_DESC2 desc;
IDXGIAdapter3* adapter3;
iid = IID_IDXGIAdapter3;
adapter->QueryInterface(&iid, (void**) &adapter3);
ThrowIfFailed("GetDesc", adapter3->GetDesc2(&desc));
var descString = new ReadOnlySpan<char>(desc.Description, 128).TrimEnd('\0');
shell.WriteLine(descString.ToString());
DXGI_QUERY_VIDEO_MEMORY_INFO memInfo;
adapter3->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &memInfo);
shell.WriteLine($"Usage (local): {ByteHelpers.FormatBytes((long) memInfo.CurrentUsage)}");
adapter3->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL, &memInfo);
shell.WriteLine($"Usage (non local): {ByteHelpers.FormatBytes((long) memInfo.CurrentUsage)}");
idx += 1;
}
}
private static void ThrowIfFailed(string methodName, HRESULT hr)
{
if (FAILED(hr))
{
Marshal.ThrowExceptionForHR(hr);
}
}
}
}
*/

View File

@@ -19,9 +19,7 @@ namespace Robust.Client.Graphics.Clyde
}
catch (Exception e)
{
_sawmill.Error(
"clyde.win",
$"Caught exception in windowing event ({ev.GetType()}):\n{e}");
_sawmill.Error($"Caught exception in windowing event ({ev.GetType()}):\n{e}");
}
if (single)

View File

@@ -21,7 +21,6 @@ namespace Robust.Client.Graphics.Clyde
// Can't use ClydeHandle because it's 64 bit.
private int _nextMonitorId = 1;
private int _primaryMonitorId;
private readonly Dictionary<int, GlfwMonitorReg> _monitors = new();
private void InitMonitors()
@@ -35,7 +34,7 @@ namespace Robust.Client.Graphics.Clyde
var primaryMonitor = GLFW.GetPrimaryMonitor();
var up = GLFW.GetMonitorUserPointer(primaryMonitor);
_primaryMonitorId = (int) up;
_clyde._primaryMonitorId = (int) up;
ProcessEvents();
}
@@ -86,7 +85,7 @@ namespace Robust.Client.Graphics.Clyde
ev.CurrentMode.RefreshRate,
ev.AllModes);
_clyde._monitorHandles.Add(impl);
_clyde._monitorHandles.Add(ev.Id, impl);
_monitors[ev.Id] = new GlfwMonitorReg
{
Id = ev.Id,
@@ -116,7 +115,7 @@ namespace Robust.Client.Graphics.Clyde
{
var reg = _monitors[ev.Id];
_monitors.Remove(ev.Id);
_clyde._monitorHandles.Remove(reg.Handle);
_clyde._monitorHandles.Remove(ev.Id);
}
private sealed class GlfwMonitorReg : MonitorReg

View File

@@ -204,7 +204,7 @@ namespace Robust.Client.Graphics.Clyde
) : CmdBase;
private sealed record CmdWinCreate(
Renderer Renderer,
GLContextSpec? GLSpec,
WindowCreateParameters Parameters,
nint ShareWindow,
nint OwnerWindow,

View File

@@ -1,15 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using OpenToolkit;
using OpenToolkit.Graphics.OpenGL4;
using OpenToolkit.GraphicsLibraryFramework;
using Robust.Client.Input;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -21,219 +15,11 @@ namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
private unsafe sealed partial class GlfwWindowingImpl
private sealed unsafe partial class GlfwWindowingImpl
{
private readonly List<GlfwWindowReg> _windows = new();
public IReadOnlyList<WindowReg> AllWindows => _windows;
public IBindingsContext GraphicsBindingContext => _mainGraphicsContext;
public WindowReg? MainWindow => _mainWindow;
private GlfwWindowReg? _mainWindow;
private GlfwBindingsContext _mainGraphicsContext = default!;
private int _nextWindowId = 1;
private static bool _eglLoaded;
public WindowHandle WindowCreate(WindowCreateParameters parameters)
{
DebugTools.AssertNotNull(_mainWindow);
GLFW.MakeContextCurrent(null);
Window* ownerPtr = null;
if (parameters.Owner != null)
{
var ownerReg = (GlfwWindowReg) ((WindowHandle)parameters.Owner).Reg;
ownerPtr = ownerReg.GlfwWindow;
}
var task = SharedWindowCreate(
_clyde._chosenRenderer,
parameters,
_mainWindow!.GlfwWindow,
ownerPtr);
// Block the main thread (to avoid stuff like texture uploads being problematic).
WaitWindowCreate(task);
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
var (reg, error) = task.Result;
if (reg == null)
{
var (desc, errCode) = error!.Value;
throw new GlfwException($"{errCode}: {desc}");
}
_clyde.CreateWindowRenderTexture(reg);
_clyde.InitWindowBlitThread(reg);
GLFW.MakeContextCurrent(_mainWindow.GlfwWindow);
return reg.Handle;
}
public bool TryInitMainWindow(Renderer renderer, [NotNullWhen(false)] out string? error)
{
var width = _cfg.GetCVar(CVars.DisplayWidth);
var height = _cfg.GetCVar(CVars.DisplayHeight);
var prevWidth = width;
var prevHeight = height;
IClydeMonitor? monitor = null;
var fullscreen = false;
if (_clyde._windowMode == WindowMode.Fullscreen)
{
monitor = _monitors[_primaryMonitorId].Handle;
width = monitor.Size.X;
height = monitor.Size.Y;
fullscreen = true;
}
var parameters = new WindowCreateParameters
{
Width = width,
Height = height,
Monitor = monitor,
Fullscreen = fullscreen
};
var windowTask = SharedWindowCreate(renderer, parameters, null, null);
WaitWindowCreate(windowTask);
var (reg, err) = windowTask.Result;
if (reg == null)
{
var (desc, code) = err!.Value;
error = $"[{code}] {desc}";
return false;
}
DebugTools.Assert(reg.Id == WindowId.Main);
_mainWindow = reg;
reg.IsMainWindow = true;
if (fullscreen)
{
reg.PrevWindowSize = (prevWidth, prevHeight);
reg.PrevWindowPos = (50, 50);
}
UpdateVSync();
error = null;
return true;
}
private void WaitWindowCreate(Task<GlfwWindowCreateResult> windowTask)
{
while (!windowTask.IsCompleted)
{
// Keep processing events until the window task gives either an error or success.
WaitEvents();
ProcessEvents(single: true);
}
}
private Task<GlfwWindowCreateResult> SharedWindowCreate(
Renderer renderer,
WindowCreateParameters parameters,
Window* share, Window* owner)
{
//
// IF YOU'RE WONDERING WHY THIS IS TASK-BASED:
// I originally wanted this to be async so we could avoid blocking the main thread
// while the OS takes its stupid 100~ms just to initialize a fucking GL context.
// This doesn't *work* because
// we have to release the GL context while the shared context is being created.
// (at least on WGL, I didn't test other platforms and I don't care to.)
// Not worth it to avoid a main thread blockage by allowing Clyde to temporarily release the GL context,
// because rendering would be locked up *anyways*.
//
// Basically what I'm saying is that everything about OpenGL is a fucking mistake
// and I should get on either Veldrid or Vulkan some time.
// Probably Veldrid tbh.
//
// Yes we ping-pong this TCS through the window thread and back, deal with it.
var tcs = new TaskCompletionSource<GlfwWindowCreateResult>();
SendCmd(new CmdWinCreate(
renderer,
parameters,
(nint) share,
// We have to pass the owner window in the Cmd here instead of reading it off parameters,
// in case the owner window is closed between sending and handling of this cmd.
(nint) owner,
tcs));
return tcs.Task;
}
private void FinishWindowCreate(EventWindowCreate ev)
{
var (res, tcs) = ev;
var reg = res.Reg;
if (reg != null)
{
_windows.Add(reg);
_clyde._windowHandles.Add(reg.Handle);
}
tcs.TrySetResult(res);
}
private void WinThreadWinCreate(CmdWinCreate cmd)
{
var (renderer, parameters, share, owner, tcs) = cmd;
var window = CreateGlfwWindowForRenderer(renderer, parameters, (Window*) share, (Window*) owner);
if (window == null)
{
var err = GLFW.GetError(out var desc);
SendEvent(new EventWindowCreate(new GlfwWindowCreateResult(null, (desc, err)), tcs));
return;
}
// We can't invoke the TCS directly from the windowing thread because:
// * it'd hit the synchronization context,
// which would make (blocking) main window init more annoying.
// * it'd not be synchronized to other incoming window events correctly which might be icky.
// So we send the TCS back to the game thread
// which processes events in the correct order and has better control of stuff during init.
var reg = WinThreadSetupWindow(window);
reg.Owner = parameters.Owner;
SendEvent(new EventWindowCreate(new GlfwWindowCreateResult(reg, null), tcs));
}
private void WinThreadWinDestroy(CmdWinDestroy cmd)
{
var window = (Window*) cmd.Window;
if (OperatingSystem.IsWindows() && cmd.hadOwner)
{
// On Windows, closing the child window causes the owner to be minimized, apparently.
// Clear owner on close to avoid this.
var hWnd = (void*) GLFW.GetWin32Window(window);
DebugTools.Assert(hWnd != null);
Win32.SetWindowLongPtrW(
hWnd,
Win32.GWLP_HWNDPARENT,
0);
}
GLFW.DestroyWindow(window);
}
public void WindowSetTitle(WindowReg window, string title)
{
CheckWindowDisposed(window);
@@ -341,18 +127,9 @@ namespace Robust.Client.Graphics.Clyde
GLFW.SwapBuffers(reg.GlfwWindow);
}
public void UpdateVSync()
{
if (_mainWindow == null)
return;
GLFW.MakeContextCurrent(_mainWindow!.GlfwWindow);
GLFW.SwapInterval(_clyde._vSync ? 1 : 0);
}
public void UpdateMainWindowMode()
{
if (_mainWindow == null)
/*if (_mainWindow == null)
{
return;
}
@@ -374,14 +151,14 @@ namespace Robust.Client.Graphics.Clyde
_mainWindow.PrevWindowSize.X, _mainWindow.PrevWindowSize.Y,
0
));
}
}*/
}
private void WinThreadWinSetFullscreen(CmdWinSetFullscreen cmd)
{
var ptr = (Window*) cmd.Window;
GLFW.GetWindowSize(ptr, out var w, out var h);
GLFW.GetWindowPos(ptr, out var x, out var y);
//GLFW.GetWindowSize(ptr, out var w, out var h);
//GLFW.GetWindowPos(ptr, out var x, out var y);
var monitor = MonitorForWindow(ptr);
var mode = GLFW.GetVideoMode(monitor);
@@ -430,6 +207,21 @@ namespace Robust.Client.Graphics.Clyde
}
}
public nint? WindowGetX11Display(WindowReg window)
{
CheckWindowDisposed(window);
var reg = (GlfwWindowReg) window;
try
{
return GLFW.GetX11Display(reg.GlfwWindow);
}
catch (EntryPointNotFoundException)
{
return null;
}
}
public nint? WindowGetWin32Window(WindowReg window)
{
if (!OperatingSystem.IsWindows())
@@ -446,67 +238,190 @@ namespace Robust.Client.Graphics.Clyde
}
}
public (WindowReg?, string? error) WindowCreate(
GLContextSpec? spec,
WindowCreateParameters parameters,
WindowReg? share,
WindowReg? owner)
{
Window* sharePtr = null;
if (share is GlfwWindowReg glfwReg)
sharePtr = glfwReg.GlfwWindow;
Window* ownerPtr = null;
if (owner is GlfwWindowReg glfwOwnerReg)
ownerPtr = glfwOwnerReg.GlfwWindow;
var task = SharedWindowCreate(
spec,
parameters,
sharePtr,
ownerPtr);
// Block the main thread (to avoid stuff like texture uploads being problematic).
WaitWindowCreate(task);
var (reg, errorResult) = task.Result;
if (reg != null)
return (reg, null);
var (desc, errCode) = errorResult!.Value;
return (null, $"[{errCode}]: {desc}");
}
public void WindowDestroy(WindowReg window)
{
var reg = (GlfwWindowReg) window;
if (reg.IsDisposed)
return;
reg.IsDisposed = true;
SendCmd(new CmdWinDestroy((nint) reg.GlfwWindow, window.Owner != null));
}
_windows.Remove(reg);
_clyde._windowHandles.Remove(reg.Handle);
private void WaitWindowCreate(Task<GlfwWindowCreateResult> windowTask)
{
while (!windowTask.IsCompleted)
{
// Keep processing events until the window task gives either an error or success.
WaitEvents();
ProcessEvents(single: true);
}
}
private Task<GlfwWindowCreateResult> SharedWindowCreate(
GLContextSpec? glSpec,
WindowCreateParameters parameters,
Window* share, Window* owner)
{
//
// IF YOU'RE WONDERING WHY THIS IS TASK-BASED:
// I originally wanted this to be async so we could avoid blocking the main thread
// while the OS takes its stupid 100~ms just to initialize a fucking GL context.
// This doesn't *work* because
// we have to release the GL context while the shared context is being created.
// (at least on WGL, I didn't test other platforms and I don't care to.)
// Not worth it to avoid a main thread blockage by allowing Clyde to temporarily release the GL context,
// because rendering would be locked up *anyways*.
//
// Basically what I'm saying is that everything about OpenGL is a fucking mistake
// and I should get on either Veldrid or Vulkan some time.
// Probably Veldrid tbh.
//
// Yes we ping-pong this TCS through the window thread and back, deal with it.
var tcs = new TaskCompletionSource<GlfwWindowCreateResult>();
SendCmd(new CmdWinCreate(
glSpec,
parameters,
(nint) share,
(nint) owner,
tcs));
return tcs.Task;
}
private static void FinishWindowCreate(EventWindowCreate ev)
{
var (res, tcs) = ev;
tcs.TrySetResult(res);
}
private void WinThreadWinCreate(CmdWinCreate cmd)
{
var (glSpec, parameters, share, owner, tcs) = cmd;
var window = CreateGlfwWindowForRenderer(glSpec, parameters, (Window*) share, (Window*) owner);
if (window == null)
{
var err = GLFW.GetError(out var desc);
SendEvent(new EventWindowCreate(new GlfwWindowCreateResult(null, (desc, err)), tcs));
return;
}
// We can't invoke the TCS directly from the windowing thread because:
// * it'd hit the synchronization context,
// which would make (blocking) main window init more annoying.
// * it'd not be synchronized to other incoming window events correctly which might be icky.
// So we send the TCS back to the game thread
// which processes events in the correct order and has better control of stuff during init.
var reg = WinThreadSetupWindow(window);
SendEvent(new EventWindowCreate(new GlfwWindowCreateResult(reg, null), tcs));
}
private static void WinThreadWinDestroy(CmdWinDestroy cmd)
{
var window = (Window*) cmd.Window;
if (OperatingSystem.IsWindows() && cmd.hadOwner)
{
// On Windows, closing the child window causes the owner to be minimized, apparently.
// Clear owner on close to avoid this.
var hWnd = (void*) GLFW.GetWin32Window(window);
DebugTools.Assert(hWnd != null);
Win32.SetWindowLongPtrW(
hWnd,
Win32.GWLP_HWNDPARENT,
0);
}
GLFW.DestroyWindow((Window*) cmd.Window);
}
private Window* CreateGlfwWindowForRenderer(
Renderer r,
GLContextSpec? spec,
WindowCreateParameters parameters,
Window* contextShare,
Window* ownerWindow)
{
#if DEBUG
GLFW.WindowHint(WindowHintBool.OpenGLDebugContext, true);
#endif
GLFW.WindowHint(WindowHintString.X11ClassName, "SS14");
GLFW.WindowHint(WindowHintString.X11InstanceName, "SS14");
GLFW.WindowHint(WindowHintBool.ScaleToMonitor, true);
if (r == Renderer.OpenGL33)
if (spec == null)
{
GLFW.WindowHint(WindowHintInt.ContextVersionMajor, 3);
GLFW.WindowHint(WindowHintInt.ContextVersionMinor, 3);
GLFW.WindowHint(WindowHintBool.OpenGLForwardCompat, true);
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlApi);
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi, ContextApi.NativeContextApi);
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Core);
GLFW.WindowHint(WindowHintBool.SrgbCapable, true);
// No OpenGL context requested.
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.NoApi);
}
else if (r == Renderer.OpenGL31)
else
{
GLFW.WindowHint(WindowHintInt.ContextVersionMajor, 3);
GLFW.WindowHint(WindowHintInt.ContextVersionMinor, 1);
GLFW.WindowHint(WindowHintBool.OpenGLForwardCompat, false);
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlApi);
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi, ContextApi.NativeContextApi);
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
GLFW.WindowHint(WindowHintBool.SrgbCapable, true);
}
else if (r == Renderer.OpenGLES2)
{
GLFW.WindowHint(WindowHintInt.ContextVersionMajor, 2);
GLFW.WindowHint(WindowHintInt.ContextVersionMinor, 0);
GLFW.WindowHint(WindowHintBool.OpenGLForwardCompat, true);
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlEsApi);
// GLES2 is initialized through EGL to allow ANGLE usage.
// (It may be an idea to make this a configuration cvar)
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi, ContextApi.EglContextApi);
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
GLFW.WindowHint(WindowHintBool.SrgbCapable, false);
var s = spec.Value;
if (!_eglLoaded && OperatingSystem.IsWindows())
#if DEBUG
GLFW.WindowHint(WindowHintBool.OpenGLDebugContext, true);
#endif
GLFW.WindowHint(WindowHintInt.ContextVersionMajor, s.Major);
GLFW.WindowHint(WindowHintInt.ContextVersionMinor, s.Minor);
GLFW.WindowHint(WindowHintBool.OpenGLForwardCompat, s.Profile != GLContextProfile.Compatibility);
GLFW.WindowHint(WindowHintBool.SrgbCapable, true);
GLFW.WindowHint(WindowHintBool.ScaleToMonitor, true);
switch (s.Profile)
{
case GLContextProfile.Compatibility:
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlApi);
break;
case GLContextProfile.Core:
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Core);
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlApi);
break;
case GLContextProfile.Es:
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlEsApi);
break;
}
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi,
s.CreationApi == GLContextCreationApi.Egl
? ContextApi.EglContextApi
: ContextApi.NativeContextApi);
#if !FULL_RELEASE
if (s.CreationApi == GLContextCreationApi.Egl && !_eglLoaded && OperatingSystem.IsWindows())
{
// On non-published builds (so, development), GLFW can't find libEGL.dll
// because it'll be in runtimes/<rid>/native/ instead of next to the actual executable.
@@ -519,9 +434,9 @@ namespace Robust.Client.Graphics.Clyde
_eglLoaded = true;
}
#endif
}
Monitor* monitor = null;
if (parameters.Monitor != null &&
_winThreadMonitors.TryGetValue(parameters.Monitor.Id, out var monitorReg))
@@ -654,9 +569,10 @@ namespace Robust.Client.Graphics.Clyde
private WindowReg? FindWindow(Window* window)
{
foreach (var windowReg in _windows)
foreach (var windowReg in _clyde._windows)
{
if (windowReg.GlfwWindow == window)
var glfwReg = (GlfwWindowReg) windowReg;
if (glfwReg.GlfwWindow == window)
{
return windowReg;
}
@@ -665,37 +581,26 @@ namespace Robust.Client.Graphics.Clyde
return null;
}
public int KeyGetScanCode(Keyboard.Key key)
{
return GLFW.GetKeyScancode(ConvertGlfwKeyReverse(key));
}
public string KeyGetNameScanCode(int scanCode)
{
return GLFW.GetKeyName(Keys.Unknown, scanCode);
}
public Task<string> ClipboardGetText()
public Task<string> ClipboardGetText(WindowReg mainWindow)
{
var tcs = new TaskCompletionSource<string>();
SendCmd(new CmdGetClipboard((nint) _mainWindow!.GlfwWindow, tcs));
SendCmd(new CmdGetClipboard((nint) ((GlfwWindowReg) mainWindow).GlfwWindow, tcs));
return tcs.Task;
}
private void WinThreadGetClipboard(CmdGetClipboard cmd)
private static void WinThreadGetClipboard(CmdGetClipboard cmd)
{
var clipboard = GLFW.GetClipboardString((Window*) cmd.Window);
// Don't have to care about synchronization I don't think so just fire this immediately.
cmd.Tcs.TrySetResult(clipboard);
}
public void ClipboardSetText(string text)
public void ClipboardSetText(WindowReg mainWindow, string text)
{
SendCmd(new CmdSetClipboard((nint) _mainWindow!.GlfwWindow, text));
SendCmd(new CmdSetClipboard((nint) ((GlfwWindowReg) mainWindow).GlfwWindow, text));
}
private void WinThreadSetClipboard(CmdSetClipboard cmd)
private static void WinThreadSetClipboard(CmdSetClipboard cmd)
{
GLFW.SetClipboardString((Window*) cmd.Window, cmd.Text);
}
@@ -730,25 +635,20 @@ namespace Robust.Client.Graphics.Clyde
}
}
public void GLInitMainContext(bool gles)
public void GLMakeContextCurrent(WindowReg? window)
{
_mainGraphicsContext = new GlfwBindingsContext();
GL.LoadBindings(_mainGraphicsContext);
if (gles)
if (window != null)
{
// On GLES we use some OES and KHR functions so make sure to initialize them.
OpenToolkit.Graphics.ES20.GL.LoadBindings(_mainGraphicsContext);
CheckWindowDisposed(window);
var reg = (GlfwWindowReg)window;
GLFW.MakeContextCurrent(reg.GlfwWindow);
}
else
{
GLFW.MakeContextCurrent(null);
}
}
public void GLMakeContextCurrent(WindowReg window)
{
CheckWindowDisposed(window);
var reg = (GlfwWindowReg) window;
GLFW.MakeContextCurrent(reg.GlfwWindow);
}
public void GLSwapInterval(int interval)
@@ -756,6 +656,11 @@ namespace Robust.Client.Graphics.Clyde
GLFW.SwapInterval(interval);
}
public void* GLGetProcAddress(string procName)
{
return (void*) GLFW.GetProcAddress(procName);
}
private void CheckWindowDisposed(WindowReg reg)
{
if (reg.IsDisposed)
@@ -769,14 +674,6 @@ namespace Robust.Client.Graphics.Clyde
// Kept around to avoid it being GCd.
public CursorImpl? Cursor;
}
private class GlfwBindingsContext : IBindingsContext
{
public IntPtr GetProcAddress(string procName)
{
return GLFW.GetProcAddress(procName);
}
}
}
}
}

View File

@@ -1,7 +1,4 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using OpenToolkit;
using System.Threading.Tasks;
using Robust.Client.Input;
using Robust.Shared.Maths;
using SixLabors.ImageSharp;
@@ -13,47 +10,54 @@ namespace Robust.Client.Graphics.Clyde
{
private interface IWindowingImpl
{
WindowReg? MainWindow { get; }
IReadOnlyList<WindowReg> AllWindows { get; }
IBindingsContext GraphicsBindingContext { get; }
// Lifecycle stuff
bool Init();
bool TryInitMainWindow(Renderer renderer, [NotNullWhen(false)] out string? error);
void Shutdown();
// Window loop
void EnterWindowLoop();
void TerminateWindowLoop();
// Event pump
void ProcessEvents(bool single=false);
void FlushDispose();
// Cursor
ICursor CursorGetStandard(StandardCursorShape shape);
ICursor CursorCreate(Image<Rgba32> image, Vector2i hotSpot);
void CursorSet(WindowReg window, ICursor? cursor);
// Window API.
(WindowReg?, string? error) WindowCreate(
GLContextSpec? spec,
WindowCreateParameters parameters,
WindowReg? share,
WindowReg? owner);
void WindowDestroy(WindowReg reg);
void WindowSetTitle(WindowReg window, string title);
void WindowSetMonitor(WindowReg window, IClydeMonitor monitor);
void WindowSetVisible(WindowReg window, bool visible);
void WindowRequestAttention(WindowReg window);
void WindowSwapBuffers(WindowReg window);
uint? WindowGetX11Id(WindowReg window);
nint? WindowGetX11Display(WindowReg window);
nint? WindowGetWin32Window(WindowReg window);
WindowHandle WindowCreate(WindowCreateParameters parameters);
void WindowDestroy(WindowReg reg);
// Keyboard
string KeyGetName(Keyboard.Key key);
Task<string> ClipboardGetText();
void ClipboardSetText(string text);
// Clipboard
Task<string> ClipboardGetText(WindowReg mainWindow);
void ClipboardSetText(WindowReg mainWindow, string text);
void UpdateVSync();
void UpdateMainWindowMode();
// OpenGL-related stuff.
void GLMakeContextCurrent(WindowReg reg);
// Note: you should probably go through GLContextBase instead, which calls these functions.
void GLMakeContextCurrent(WindowReg? reg);
void GLSwapInterval(int interval);
void GLInitMainContext(bool gles);
unsafe void* GLGetProcAddress(string procName);
}
}
}

View File

@@ -21,6 +21,7 @@
<PackageReference Include="OpenToolkit.OpenAL" Version="4.0.0-pre9.1" />
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" />
<PackageReference Include="Robust.Natives" Version="0.1.0" />
<!--<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-beta1" />-->
</ItemGroup>
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="3.8.0" />

View File

@@ -13,7 +13,11 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=gameobjects_005Centitysystems/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=graphics_005Cclienteye/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=graphics_005Cclyde_005Caudio/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=graphics_005Cclyde_005Cglcontext/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=graphics_005Cclyde_005Cglobjects/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=graphics_005Cclyde_005Crenderer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=graphics_005Cclyde_005Crenderer_005Cglcontext/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=graphics_005Cclyde_005Cwindowing/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=graphics_005Cdrawing/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=graphics_005Clighting/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=graphics_005Coverlays/@EntryIndexedValue">True</s:Boolean>

View File

@@ -30,11 +30,6 @@ namespace Robust.Client.UserInterface
private bool _kDialogAvailable;
private bool _checkedKDialogAvailable;
static FileDialogManager()
{
DllMapHelper.RegisterSimpleMap(typeof(FileDialogManager).Assembly, "swnfd");
}
public async Task<Stream?> OpenFile(FileDialogFilters? filters = null)
{
var name = await GetOpenFileName(filters);

View File

@@ -0,0 +1,40 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Robust.Client.Utility
{
internal static class ClientDllMap
{
[ModuleInitializer]
internal static void Initialize()
{
if (OperatingSystem.IsWindows())
return;
NativeLibrary.SetDllImportResolver(typeof(ClientDllMap).Assembly, (name, assembly, path) =>
{
if (name == "swnfd.dll")
{
if (OperatingSystem.IsLinux())
return NativeLibrary.Load("libswnfd.so", assembly, path);
if (OperatingSystem.IsMacOS())
return NativeLibrary.Load("libswnfd.dylib", assembly, path);
return IntPtr.Zero;
}
if (name == "libEGL.dll")
{
if (OperatingSystem.IsLinux())
return NativeLibrary.Load("libEGL.so", assembly, path);
return IntPtr.Zero;
}
return IntPtr.Zero;
});
}
}
}

View File

@@ -304,15 +304,75 @@ namespace Robust.Shared
public static readonly CVarDef<float> DisplayUIScale =
CVarDef.Create("display.uiScale", 0f, CVar.ARCHIVE | CVar.CLIENTONLY);
// Clyde related enums are in Clyde.Constants.cs.
/// <summary>
/// Which renderer to use to render the game.
/// </summary>
public static readonly CVarDef<int> DisplayRenderer =
CVarDef.Create("display.renderer", 0, CVar.CLIENTONLY);
/// <summary>
/// Whether to use compatibility mode.
/// </summary>
/// <remarks>
/// This can change certain behaviors like GL version selection to try to avoid driver crashes.
/// </remarks>
public static readonly CVarDef<bool> DisplayCompat =
CVarDef.Create("display.compat", false, CVar.CLIENTONLY);
/// <summary>
/// Which OpenGL version to use for the OpenGL renderer.
/// </summary>
public static readonly CVarDef<int> DisplayOpenGLVersion =
CVarDef.Create("display.opengl_version", 0, CVar.CLIENTONLY);
/// <summary>
/// On Windows, use ANGLE as OpenGL implementation.
/// </summary>
public static readonly CVarDef<bool> DisplayAngle =
CVarDef.Create("display.angle", true, CVar.CLIENTONLY);
/// <summary>
/// Use a custom swap chain when using ANGLE.
/// Should improve performance and fixes main window sRGB handling with ANGLE.
/// </summary>
public static readonly CVarDef<bool> DisplayAngleCustomSwapChain =
CVarDef.Create("display.angle_custom_swap_chain", true, CVar.CLIENTONLY);
/// <summary>
/// Force usage of DXGI 1.1 when using custom swap chain setup.
/// </summary>
public static readonly CVarDef<bool> DisplayAngleDxgi1 =
CVarDef.Create("display.angle_dxgi1", false, CVar.CLIENTONLY);
/// <summary>
/// Try to use the display adapter with this name, if the current renderer supports selecting it.
/// </summary>
public static readonly CVarDef<string> DisplayAdapter =
CVarDef.Create("display.adapter", "", CVar.CLIENTONLY);
/// <summary>
/// Use EGL to create GL context instead of GLFW, if possible.
/// </summary>
/// <remarks>
/// This only tries to use EGL if on a platform like X11 or Windows (w/ ANGLE) where it is possible.
/// </remarks>
public static readonly CVarDef<bool> DisplayEgl =
CVarDef.Create("display.egl", true, CVar.CLIENTONLY);
public static readonly CVarDef<int> DisplayFontDpi =
CVarDef.Create("display.fontdpi", 96, CVar.CLIENTONLY);
/// <summary>
/// Override detected OpenGL version, for testing.
/// </summary>
public static readonly CVarDef<string> DisplayOGLOverrideVersion =
CVarDef.Create("display.ogl_override_version", string.Empty, CVar.CLIENTONLY);
/// <summary>
/// Run <c>glCheckError()</c> after (almost) every GL call.
/// </summary>
public static readonly CVarDef<bool> DisplayOGLCheckErrors =
CVarDef.Create("display.ogl_check_errors", false, CVar.CLIENTONLY);
@@ -327,6 +387,9 @@ namespace Robust.Shared
public static readonly CVarDef<bool> DisplayForceSyncWindows =
CVarDef.Create<bool>("display.force_sync_windows", true, CVar.CLIENTONLY);
/// <summary>
/// Use a separate thread for multi-window blitting.
/// </summary>
public static readonly CVarDef<bool> DisplayThreadWindowBlit =
CVarDef.Create("display.thread_window_blit", true, CVar.CLIENTONLY);