mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
ANGLE+DXGI experiment, window/GL init rewrite (#1982)
This commit is contained in:
committed by
GitHub
parent
051d47f4ff
commit
cd3a7ef91e
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
77
Robust.Client/Graphics/Clyde/Clyde.GLContext.cs
Normal file
77
Robust.Client/Graphics/Clyde/Clyde.GLContext.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}'!");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
139
Robust.Client/Graphics/Clyde/Egl.cs
Normal file
139
Robust.Client/Graphics/Clyde/Egl.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
508
Robust.Client/Graphics/Clyde/GLContext/GLContextAngle.cs
Normal file
508
Robust.Client/Graphics/Clyde/GLContext/GLContextAngle.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
115
Robust.Client/Graphics/Clyde/GLContext/GLContextBase.cs
Normal file
115
Robust.Client/Graphics/Clyde/GLContext/GLContextBase.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
293
Robust.Client/Graphics/Clyde/GLContext/GLContextEgl.cs
Normal file
293
Robust.Client/Graphics/Clyde/GLContext/GLContextEgl.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
303
Robust.Client/Graphics/Clyde/GLContext/GLContextWindow.cs
Normal file
303
Robust.Client/Graphics/Clyde/GLContext/GLContextWindow.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
61
Robust.Client/Graphics/Clyde/VramCommand.cs
Normal file
61
Robust.Client/Graphics/Clyde/VramCommand.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -204,7 +204,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinCreate(
|
||||
Renderer Renderer,
|
||||
GLContextSpec? GLSpec,
|
||||
WindowCreateParameters Parameters,
|
||||
nint ShareWindow,
|
||||
nint OwnerWindow,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
40
Robust.Client/Utility/ClientDllMap.cs
Normal file
40
Robust.Client/Utility/ClientDllMap.cs
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user