mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
* Move to Central Package Management. Allows us to store NuGet package versions all in one place. Yay! * Update NuGet packages and fix code for changes. Notable: Changes to ILVerify. Npgsql doesn't need hacks for inet anymore, now we need hacks to make the old code work with this new reality. NUnit's analyzers are already complaining and I didn't even update it to 4.x yet. TerraFX changed to GetLastSystemError so error handling had to be changed. Buncha APIs have more NRT annotations. * Remove dotnet-eng NuGet package source. I genuinely don't know what this was for, and Central Package Management starts throwing warnings about it, so YEET. * Fix double loading of assemblies due to ALC shenanigans. Due to how the "sideloading" code for the ModLoader was set up, it would first try to load Microsoft.Extensions.Primitives from next to the content dll. But we already have that library in Robust! Chaos ensues. We now try to forcibly prioritize loading from the default ALC first to avoid this. * Remove Robust.Physics project. Never used. * Remove erroneous NVorbis reference. Should be VorbisPizza and otherwise wasn't used. * Sandbox fixes * Remove unused unit test package references. Castle.Core and NUnit.ConsoleRunner. * Update NUnit to 4.0.1 This requires replacing all the old assertion methods because they removed them 🥲 * Mute CA1416 (platform check) errors TerraFX started annotating APIs with this and I can't be arsed to entertain this analyzer so out it goes. * Fine ya cranky, no more CPM for Robust.Client.Injectors * Changelog * Oh so that's what dotnet-eng was used for. Yeah ok that makes sense. * Central package management for remaining 2 robust projects * Ok that was a bad idea let's just use NUnit 3 on the analyzer test project * Oh right forgot to remove this one * Update to a newer version of RemoteExecutor * Disable RemoteExecutor test https://github.com/dotnet/arcade/issues/8483 Yeah this package is not well maintained and clearly we can't rely on it. * Fix immutable list serialization
581 lines
22 KiB
C#
581 lines
22 KiB
C#
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.DirectX;
|
|
using TerraFX.Interop.Windows;
|
|
using static Robust.Client.Graphics.Clyde.Egl;
|
|
using static TerraFX.Interop.DirectX.D3D_DRIVER_TYPE;
|
|
using static TerraFX.Interop.DirectX.D3D_FEATURE_LEVEL;
|
|
using static TerraFX.Interop.DirectX.DXGI_FORMAT;
|
|
using static TerraFX.Interop.DirectX.DXGI_SWAP_EFFECT;
|
|
using static TerraFX.Interop.Windows.Windows;
|
|
using static TerraFX.Interop.DirectX.DirectX;
|
|
using static TerraFX.Interop.DirectX.D3D11;
|
|
using static TerraFX.Interop.DirectX.DXGI;
|
|
using GL = OpenToolkit.Graphics.OpenGL4.GL;
|
|
|
|
|
|
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 D3D_FEATURE_LEVEL _deviceFl;
|
|
private void* _eglDevice;
|
|
private void* _eglDisplay;
|
|
private void* _eglContext;
|
|
private void* _eglConfig;
|
|
|
|
private bool _es3;
|
|
private uint _swapInterval;
|
|
|
|
private readonly Dictionary<WindowId, WindowData> _windowData = new();
|
|
|
|
public override GLContextSpec[] SpecsToTry => Array.Empty<GLContextSpec>();
|
|
public override bool RequireWindowGL => false;
|
|
public override bool EarlyContextInit => true;
|
|
public override bool HasBrokenWindowSrgb => false;
|
|
|
|
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(GLContextSpec? spec, WindowReg reg)
|
|
{
|
|
var data = new WindowData
|
|
{
|
|
Reg = reg
|
|
};
|
|
_windowData[reg.Id] = data;
|
|
|
|
var hWnd = (HWND) Clyde._windowing!.WindowGetWin32Window(reg)!.Value;
|
|
|
|
// todo: exception management.
|
|
CreateSwapChain1(hWnd, data);
|
|
|
|
_factory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER);
|
|
|
|
var rt = Clyde.RtToLoaded(reg.RenderTarget);
|
|
rt.FlipY = true;
|
|
|
|
if (reg.IsMainWindow)
|
|
{
|
|
UpdateVSync();
|
|
eglMakeCurrent(_eglDisplay, data.EglBackbuffer, data.EglBackbuffer, _eglContext);
|
|
}
|
|
}
|
|
|
|
private void DestroyBackbuffer(WindowData data)
|
|
{
|
|
if (data.EglBackbuffer != null)
|
|
{
|
|
if (data.Reg.IsMainWindow)
|
|
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)
|
|
{
|
|
ThrowIfFailed("GetBuffer", data.SwapChain->GetBuffer(0, __uuidof<ID3D11Texture2D>(), (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 CreateSwapChain1(HWND hWnd, WindowData data)
|
|
{
|
|
var desc = new DXGI_SWAP_CHAIN_DESC
|
|
{
|
|
BufferDesc =
|
|
{
|
|
Width = (uint) data.Reg.FramebufferSize.X,
|
|
Height = (uint) data.Reg.FramebufferSize.Y,
|
|
Format = Clyde._hasGLSrgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM
|
|
},
|
|
SampleDesc =
|
|
{
|
|
Count = 1
|
|
},
|
|
OutputWindow = hWnd,
|
|
BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT,
|
|
BufferCount = 2,
|
|
SwapEffect = DXGI_SWAP_EFFECT_DISCARD,
|
|
Windowed = 1
|
|
};
|
|
|
|
fixed (IDXGISwapChain** swapPtr = &data.SwapChain)
|
|
{
|
|
ThrowIfFailed("CreateSwapChain", _factory->CreateSwapChain(
|
|
(IUnknown*) _device,
|
|
&desc,
|
|
swapPtr
|
|
));
|
|
}
|
|
|
|
SetupBackbuffer(data);
|
|
}
|
|
|
|
public override void WindowDestroyed(WindowReg reg)
|
|
{
|
|
var data = _windowData[reg.Id];
|
|
|
|
DestroyBackbuffer(data);
|
|
data.SwapChain->Release();
|
|
|
|
_windowData.Remove(reg.Id);
|
|
}
|
|
|
|
public bool TryInitialize()
|
|
{
|
|
try
|
|
{
|
|
TryInitializeCore();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.ErrorS("clyde.ogl.angle", $"Failed to initialize custom ANGLE: {e}");
|
|
Shutdown();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void EarlyInit()
|
|
{
|
|
// Early GL context init so that feature detection runs before window creation,
|
|
// and so that we can know _hasGLSrgb in window creation.
|
|
eglMakeCurrent(_eglDisplay, null, null, _eglContext);
|
|
Clyde.InitOpenGL();
|
|
Clyde._earlyGLInit = 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_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];
|
|
|
|
int supportedRenderableTypes;
|
|
eglGetConfigAttrib(_eglDisplay, _eglConfig, EGL_RENDERABLE_TYPE, &supportedRenderableTypes);
|
|
|
|
_es3 = (supportedRenderableTypes & EGL_OPENGL_ES3_BIT) != 0;
|
|
|
|
var createAttribs = stackalloc int[]
|
|
{
|
|
EGL_CONTEXT_CLIENT_VERSION, _es3 ? 3 : 2,
|
|
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!");
|
|
|
|
Clyde._openGLVersion = _es3 ? RendererOpenGLVersion.GLES3 : RendererOpenGLVersion.GLES2;
|
|
}
|
|
|
|
private void CreateD3D11Device()
|
|
{
|
|
IDXGIDevice1* dxgiDevice = null;
|
|
|
|
try
|
|
{
|
|
fixed (IDXGIFactory1** ptr = &_factory)
|
|
{
|
|
ThrowIfFailed(nameof(CreateDXGIFactory1), CreateDXGIFactory1(__uuidof<IDXGIFactory1>(), (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}");
|
|
}
|
|
|
|
#pragma warning disable CA1416
|
|
#pragma warning disable CS0162
|
|
IDXGIFactory6* factory6;
|
|
if (_adapter == null && _factory->QueryInterface(__uuidof<IDXGIFactory6>(), (void**) &factory6) == 0)
|
|
{
|
|
var gpuPref = (DXGI_GPU_PREFERENCE) Clyde._cfg.GetCVar(CVars.DisplayGpuPreference);
|
|
IDXGIAdapter1* adapter;
|
|
for (var adapterIndex = 0u;
|
|
factory6->EnumAdapterByGpuPreference(
|
|
adapterIndex,
|
|
gpuPref,
|
|
__uuidof<IDXGIAdapter1>(),
|
|
(void**)&adapter) != DXGI_ERROR_NOT_FOUND;
|
|
adapterIndex++)
|
|
{
|
|
/*
|
|
DXGI_ADAPTER_DESC1 aDesc;
|
|
ThrowIfFailed("GetDesc1", adapter->GetDesc1(&aDesc));
|
|
|
|
var aDescName = new ReadOnlySpan<char>(aDesc.Description, 128);
|
|
|
|
Logger.DebugS("clyde.ogl.angle", aDescName.ToString());
|
|
|
|
adapter->Release();
|
|
*/
|
|
_adapter = adapter;
|
|
break;
|
|
}
|
|
|
|
factory6->Release();
|
|
}
|
|
#pragma warning restore CS0162
|
|
#pragma warning restore CA1416
|
|
|
|
Span<D3D_FEATURE_LEVEL> featureLevels = stackalloc D3D_FEATURE_LEVEL[]
|
|
{
|
|
// 11_0 can do GLES3
|
|
D3D_FEATURE_LEVEL_11_0,
|
|
// 9_3 can do GLES2
|
|
D3D_FEATURE_LEVEL_9_3,
|
|
// If we get a 9_1 FL we can't do D3D11 based ANGLE,
|
|
// but ANGLE can do it manually via the D3D9 renderer.
|
|
// In this case, abort custom swap chain and let ANGLE handle everything.
|
|
D3D_FEATURE_LEVEL_9_1
|
|
};
|
|
|
|
if (Clyde._cfg.GetCVar(CVars.DisplayAngleForceEs2))
|
|
{
|
|
featureLevels = stackalloc D3D_FEATURE_LEVEL[]
|
|
{
|
|
// Don't allow FL 11_0 so ANGLE is forced to init GLES2.
|
|
D3D_FEATURE_LEVEL_9_3,
|
|
D3D_FEATURE_LEVEL_9_1
|
|
};
|
|
}
|
|
|
|
if (Clyde._cfg.GetCVar(CVars.DisplayAngleForce10_0))
|
|
{
|
|
featureLevels = stackalloc D3D_FEATURE_LEVEL[]
|
|
{
|
|
D3D_FEATURE_LEVEL_10_0,
|
|
};
|
|
}
|
|
|
|
fixed (ID3D11Device** device = &_device)
|
|
fixed (D3D_FEATURE_LEVEL* fl = &featureLevels[0])
|
|
{
|
|
ThrowIfFailed("D3D11CreateDevice", D3D11CreateDevice(
|
|
(IDXGIAdapter*) _adapter,
|
|
_adapter == null ? D3D_DRIVER_TYPE_HARDWARE : D3D_DRIVER_TYPE_UNKNOWN,
|
|
HMODULE.NULL,
|
|
0,
|
|
fl,
|
|
(uint) featureLevels.Length,
|
|
D3D11_SDK_VERSION,
|
|
device,
|
|
null,
|
|
null
|
|
));
|
|
}
|
|
|
|
// Get adapter from the device.
|
|
|
|
ThrowIfFailed("QueryInterface", _device->QueryInterface(__uuidof<IDXGIDevice1>(), (void**) &dxgiDevice));
|
|
|
|
fixed (IDXGIAdapter1** ptrAdapter = &_adapter)
|
|
{
|
|
ThrowIfFailed("GetParent", dxgiDevice->GetParent(__uuidof<IDXGIAdapter1>(), (void**) ptrAdapter));
|
|
}
|
|
|
|
_deviceFl = _device->GetFeatureLevel();
|
|
|
|
DXGI_ADAPTER_DESC1 desc;
|
|
ThrowIfFailed("GetDesc1", _adapter->GetDesc1(&desc));
|
|
|
|
var descName = ((ReadOnlySpan<char>)desc.Description).TrimEnd('\0');
|
|
|
|
Logger.DebugS("clyde.ogl.angle", "Successfully created D3D11 device!");
|
|
Logger.DebugS("clyde.ogl.angle", $"D3D11 Device Adapter: {descName.ToString()}");
|
|
Logger.DebugS("clyde.ogl.angle", $"D3D11 Device FL: {_deviceFl}");
|
|
|
|
if (_deviceFl == D3D_FEATURE_LEVEL_9_1)
|
|
{
|
|
throw new Exception(
|
|
"D3D11 device has too low FL (need at least 9_3). Aborting custom swap chain!");
|
|
}
|
|
}
|
|
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,
|
|
Clyde._hasGLSrgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM,
|
|
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 = ((ReadOnlySpan<char>)desc.Description);
|
|
|
|
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):000} | ");
|
|
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)} | ");
|
|
sb.Append($"RENDERABLE: {Get(EGL_RENDERABLE_TYPE)}");
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|