mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
The way SDL handles window coordinates passes through the native platform API's behavior instead of trying to make a consistent API, so the way sizes are handled on macOS is different.
704 lines
25 KiB
C#
704 lines
25 KiB
C#
using System;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading.Tasks;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Maths;
|
|
using SDL3;
|
|
using TerraFX.Interop.Windows;
|
|
using TerraFX.Interop.Xlib;
|
|
#if WINDOWS
|
|
using BOOL = TerraFX.Interop.Windows.BOOL;
|
|
using Windows = TerraFX.Interop.Windows.Windows;
|
|
#endif
|
|
using GLAttr = SDL3.SDL.SDL_GLAttr;
|
|
using X11Window = TerraFX.Interop.Xlib.Window;
|
|
|
|
namespace Robust.Client.Graphics.Clyde;
|
|
|
|
internal partial class Clyde
|
|
{
|
|
private sealed partial class Sdl3WindowingImpl
|
|
{
|
|
private int _nextWindowId = 1;
|
|
private bool _progressUnavailable;
|
|
|
|
public (WindowReg?, string? error) WindowCreate(
|
|
GLContextSpec? spec,
|
|
WindowCreateParameters parameters,
|
|
WindowReg? share,
|
|
WindowReg? owner)
|
|
{
|
|
nint shareWindow = 0;
|
|
nint shareContext = 0;
|
|
if (share is Sdl3WindowReg shareReg)
|
|
{
|
|
shareWindow = shareReg.Sdl3Window;
|
|
shareContext = shareReg.GlContext;
|
|
}
|
|
|
|
nint ownerPtr = 0;
|
|
if (owner is Sdl3WindowReg ownerReg)
|
|
ownerPtr = ownerReg.Sdl3Window;
|
|
|
|
var task = SharedWindowCreate(spec, parameters, shareWindow, shareContext, ownerPtr);
|
|
|
|
// Block the main thread (to avoid stuff like texture uploads being problematic).
|
|
WaitWindowCreate(task);
|
|
|
|
#pragma warning disable RA0004
|
|
// Block above ensured task is done, this is safe.
|
|
var result = task.Result;
|
|
#pragma warning restore RA0004
|
|
if (result.Reg != null)
|
|
{
|
|
result.Reg.Owner = result.Reg.Handle;
|
|
}
|
|
|
|
return (result.Reg, result.Error);
|
|
}
|
|
|
|
private void WaitWindowCreate(Task<Sdl3WindowCreateResult> windowTask)
|
|
{
|
|
while (!windowTask.IsCompleted)
|
|
{
|
|
// Keep processing events until the window task gives either an error or success.
|
|
WaitEvents();
|
|
ProcessEvents(single: true);
|
|
}
|
|
}
|
|
|
|
private Task<Sdl3WindowCreateResult> SharedWindowCreate(
|
|
GLContextSpec? glSpec,
|
|
WindowCreateParameters parameters,
|
|
nint shareWindow,
|
|
nint shareContext,
|
|
nint 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<Sdl3WindowCreateResult>();
|
|
SendCmd(new CmdWinCreate
|
|
{
|
|
GLSpec = glSpec,
|
|
Parameters = parameters,
|
|
ShareWindow = shareWindow,
|
|
ShareContext = shareContext,
|
|
OwnerWindow = owner,
|
|
Tcs = tcs
|
|
});
|
|
return tcs.Task;
|
|
}
|
|
|
|
private static void FinishWindowCreate(EventWindowCreate ev)
|
|
{
|
|
ev.Tcs.TrySetResult(ev.Result);
|
|
}
|
|
|
|
private void WinThreadWinCreate(CmdWinCreate cmd)
|
|
{
|
|
var (window, context) = CreateSdl3WindowForRenderer(
|
|
cmd.GLSpec,
|
|
cmd.Parameters,
|
|
cmd.ShareWindow,
|
|
cmd.ShareContext,
|
|
cmd.OwnerWindow);
|
|
|
|
if (window == 0)
|
|
{
|
|
var err = SDL.SDL_GetError();
|
|
|
|
SendEvent(new EventWindowCreate
|
|
{
|
|
Result = new Sdl3WindowCreateResult { Error = err },
|
|
Tcs = cmd.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, context);
|
|
|
|
SendEvent(new EventWindowCreate
|
|
{
|
|
Result = new Sdl3WindowCreateResult { Reg = reg },
|
|
Tcs = cmd.Tcs
|
|
});
|
|
}
|
|
|
|
private void WinThreadWinDestroy(CmdWinDestroy cmd)
|
|
{
|
|
SDL.SDL_DestroyWindow(cmd.Window);
|
|
#if MACOS
|
|
SendEvent(new EventWindowDestroyed());
|
|
#endif
|
|
}
|
|
|
|
private (nint window, nint context) CreateSdl3WindowForRenderer(
|
|
GLContextSpec? spec,
|
|
WindowCreateParameters parameters,
|
|
nint shareWindow,
|
|
nint shareContext,
|
|
nint ownerWindow)
|
|
{
|
|
var createProps = SDL.SDL_CreateProperties();
|
|
SDL.SDL_SetBooleanProperty(createProps, SDL.SDL_PROP_WINDOW_CREATE_HIDDEN_BOOLEAN, true);
|
|
SDL.SDL_SetBooleanProperty(createProps, SDL.SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, true);
|
|
SDL.SDL_SetBooleanProperty(createProps, SDL.SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
|
|
|
|
if (spec is { } s)
|
|
{
|
|
SDL.SDL_SetBooleanProperty(createProps, SDL.SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
|
|
|
|
SDL.SDL_GL_SetAttribute(GLAttr.SDL_GL_RED_SIZE, 8);
|
|
SDL.SDL_GL_SetAttribute(GLAttr.SDL_GL_GREEN_SIZE, 8);
|
|
SDL.SDL_GL_SetAttribute(GLAttr.SDL_GL_BLUE_SIZE, 8);
|
|
SDL.SDL_GL_SetAttribute(GLAttr.SDL_GL_ALPHA_SIZE, 8);
|
|
SDL.SDL_GL_SetAttribute(GLAttr.SDL_GL_STENCIL_SIZE, 8);
|
|
SDL.SDL_GL_SetAttribute(
|
|
GLAttr.SDL_GL_FRAMEBUFFER_SRGB_CAPABLE,
|
|
s.Profile == GLContextProfile.Es ? 0 : 1);
|
|
int ctxFlags = 0;
|
|
#if DEBUG
|
|
ctxFlags |= SDL.SDL_GL_CONTEXT_DEBUG_FLAG;
|
|
#endif
|
|
if (s.Profile == GLContextProfile.Core)
|
|
ctxFlags |= SDL.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG;
|
|
|
|
SDL.SDL_GL_SetAttribute(GLAttr.SDL_GL_CONTEXT_FLAGS, (int)ctxFlags);
|
|
|
|
if (shareContext != 0)
|
|
{
|
|
SDL.SDL_GL_MakeCurrent(shareWindow, shareContext);
|
|
SDL.SDL_GL_SetAttribute(GLAttr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
|
|
}
|
|
else
|
|
{
|
|
SDL.SDL_GL_SetAttribute(GLAttr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0);
|
|
}
|
|
|
|
var profile = s.Profile switch
|
|
{
|
|
GLContextProfile.Compatibility => SDL.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY,
|
|
GLContextProfile.Core => SDL.SDL_GL_CONTEXT_PROFILE_CORE,
|
|
GLContextProfile.Es => SDL.SDL_GL_CONTEXT_PROFILE_ES,
|
|
_ => SDL.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY,
|
|
};
|
|
|
|
SDL.SDL_GL_SetAttribute(GLAttr.SDL_GL_CONTEXT_PROFILE_MASK, profile);
|
|
SDL.SDL_SetHint(SDL.SDL_HINT_OPENGL_ES_DRIVER, s.CreationApi == GLContextCreationApi.Egl ? "1" : "0");
|
|
|
|
SDL.SDL_GL_SetAttribute(GLAttr.SDL_GL_CONTEXT_MAJOR_VERSION, s.Major);
|
|
SDL.SDL_GL_SetAttribute(GLAttr.SDL_GL_CONTEXT_MINOR_VERSION, s.Minor);
|
|
|
|
if (s.CreationApi == GLContextCreationApi.Egl)
|
|
WsiShared.EnsureEglAvailable();
|
|
}
|
|
|
|
if (parameters.Fullscreen)
|
|
SDL.SDL_SetBooleanProperty(createProps, SDL.SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, true);
|
|
|
|
if ((parameters.Styles & OSWindowStyles.NoTitleBar) != 0)
|
|
SDL.SDL_SetBooleanProperty(createProps, SDL.SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, true);
|
|
|
|
if (ownerWindow != 0)
|
|
{
|
|
SDL.SDL_SetPointerProperty(createProps, SDL.SDL_PROP_WINDOW_CREATE_PARENT_POINTER, ownerWindow);
|
|
|
|
if (parameters.StartupLocation == WindowStartupLocation.CenterOwner)
|
|
{
|
|
SDL.SDL_GetWindowSize(ownerWindow, out var parentW, out var parentH);
|
|
SDL.SDL_GetWindowPosition(ownerWindow, out var parentX, out var parentY);
|
|
|
|
SDL.SDL_SetNumberProperty(
|
|
createProps,
|
|
SDL.SDL_PROP_WINDOW_CREATE_X_NUMBER,
|
|
parentX + (parentW - parameters.Width) / 2);
|
|
SDL.SDL_SetNumberProperty(
|
|
createProps,
|
|
SDL.SDL_PROP_WINDOW_CREATE_Y_NUMBER,
|
|
parentY + (parentH - parameters.Height) / 2);
|
|
}
|
|
}
|
|
|
|
SDL.SDL_SetNumberProperty(createProps, SDL.SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, parameters.Width);
|
|
SDL.SDL_SetNumberProperty(createProps, SDL.SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, parameters.Height);
|
|
SDL.SDL_SetStringProperty(createProps, SDL.SDL_PROP_WINDOW_CREATE_TITLE_STRING, parameters.Title);
|
|
|
|
// ---> CREATE <---
|
|
var window = SDL.SDL_CreateWindowWithProperties(createProps);
|
|
|
|
SDL.SDL_DestroyProperties(createProps);
|
|
|
|
if (window == 0)
|
|
return default;
|
|
|
|
nint glContext = SDL.SDL_GL_CreateContext(window);
|
|
if (glContext == 0)
|
|
{
|
|
SDL.SDL_DestroyWindow(window);
|
|
return default;
|
|
}
|
|
|
|
if ((parameters.Styles & OSWindowStyles.NoTitleOptions) != 0)
|
|
{
|
|
var props = SDL.SDL_GetWindowProperties(window);
|
|
switch (_videoDriver)
|
|
{
|
|
case SdlVideoDriver.Windows:
|
|
{
|
|
var hWnd = SDL.SDL_GetPointerProperty(
|
|
props,
|
|
SDL.SDL_PROP_WINDOW_WIN32_HWND_POINTER,
|
|
0);
|
|
WsiShared.SetWindowStyleNoTitleOptionsWindows((HWND)hWnd);
|
|
break;
|
|
}
|
|
case SdlVideoDriver.X11:
|
|
unsafe
|
|
{
|
|
var x11Display = (Display*)SDL.SDL_GetPointerProperty(
|
|
props,
|
|
SDL.SDL_PROP_WINDOW_X11_DISPLAY_POINTER,
|
|
0);
|
|
var x11Window = (X11Window)SDL.SDL_GetNumberProperty(
|
|
props,
|
|
SDL.SDL_PROP_WINDOW_X11_WINDOW_NUMBER,
|
|
0);
|
|
WsiShared.SetWindowStyleNoTitleOptionsX11(x11Display, x11Window);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
_sawmill.Warning("OSWindowStyles.NoTitleOptions not implemented on this video driver");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// TODO: Monitors, window maximize.
|
|
|
|
// Make sure window thread doesn't keep hold of the GL context.
|
|
SDL.SDL_GL_MakeCurrent(IntPtr.Zero, IntPtr.Zero);
|
|
|
|
if (parameters.Visible)
|
|
SDL.SDL_ShowWindow(window);
|
|
|
|
return (window, glContext);
|
|
}
|
|
|
|
private Sdl3WindowReg WinThreadSetupWindow(nint window, nint context)
|
|
{
|
|
var reg = new Sdl3WindowReg
|
|
{
|
|
Sdl3Window = window,
|
|
GlContext = context,
|
|
WindowId = SDL.SDL_GetWindowID(window),
|
|
Id = new WindowId(_nextWindowId++)
|
|
};
|
|
var handle = new WindowHandle(_clyde, reg);
|
|
reg.Handle = handle;
|
|
|
|
var windowProps = SDL.SDL_GetWindowProperties(window);
|
|
switch (_videoDriver)
|
|
{
|
|
case SdlVideoDriver.Windows:
|
|
reg.WindowsHwnd = SDL.SDL_GetPointerProperty(
|
|
windowProps,
|
|
SDL.SDL_PROP_WINDOW_WIN32_HWND_POINTER,
|
|
0);
|
|
break;
|
|
case SdlVideoDriver.X11:
|
|
reg.X11Display = SDL.SDL_GetPointerProperty(
|
|
windowProps,
|
|
SDL.SDL_PROP_WINDOW_X11_DISPLAY_POINTER,
|
|
0);
|
|
reg.X11Id = (uint)SDL.SDL_GetNumberProperty(windowProps, SDL.SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
|
|
break;
|
|
}
|
|
|
|
AssignWindowIconToWindow(window);
|
|
|
|
SDL.SDL_GetWindowSizeInPixels(window, out var fbW, out var fbH);
|
|
reg.FramebufferSize = (fbW, fbH);
|
|
|
|
var scale = SDL.SDL_GetWindowDisplayScale(window);
|
|
reg.WindowScale = new Vector2(scale, scale);
|
|
|
|
SDL.SDL_GetWindowSize(window, out var w, out var h);
|
|
reg.PrevWindowSize = reg.WindowSize = (w, h);
|
|
|
|
SDL.SDL_GetWindowPosition(window, out var x, out var y);
|
|
reg.PrevWindowPos = reg.WindowPos = (x, y);
|
|
|
|
reg.PixelRatio = reg.FramebufferSize / (Vector2)reg.WindowSize;
|
|
|
|
return reg;
|
|
}
|
|
|
|
public void WindowDestroy(WindowReg window)
|
|
{
|
|
var reg = (Sdl3WindowReg)window;
|
|
SendCmd(new CmdWinDestroy
|
|
{
|
|
Window = reg.Sdl3Window,
|
|
HadOwner = window.Owner != null
|
|
});
|
|
}
|
|
|
|
public void UpdateMainWindowMode()
|
|
{
|
|
if (_clyde._mainWindow == null)
|
|
return;
|
|
|
|
var win = (Sdl3WindowReg)_clyde._mainWindow;
|
|
|
|
if (_clyde._windowMode == WindowMode.Fullscreen)
|
|
{
|
|
win.PrevWindowSize = win.WindowSize;
|
|
win.PrevWindowPos = win.WindowPos;
|
|
|
|
SendCmd(new CmdWinWinSetFullscreen
|
|
{
|
|
Window = win.Sdl3Window,
|
|
});
|
|
}
|
|
else
|
|
{
|
|
SendCmd(new CmdWinSetWindowed
|
|
{
|
|
Window = win.Sdl3Window,
|
|
Width = win.PrevWindowSize.X,
|
|
Height = win.PrevWindowSize.Y,
|
|
PosX = win.PrevWindowPos.X,
|
|
PosY = win.PrevWindowPos.Y
|
|
});
|
|
}
|
|
}
|
|
|
|
private static void WinThreadWinSetFullscreen(CmdWinWinSetFullscreen cmd)
|
|
{
|
|
SDL.SDL_SetWindowFullscreen(cmd.Window, true);
|
|
}
|
|
|
|
private static void WinThreadWinSetWindowed(CmdWinSetWindowed cmd)
|
|
{
|
|
SDL.SDL_SetWindowFullscreen(cmd.Window, false);
|
|
SDL.SDL_SetWindowSize(cmd.Window, cmd.Width, cmd.Height);
|
|
SDL.SDL_SetWindowPosition(cmd.Window, cmd.PosX, cmd.PosY);
|
|
}
|
|
|
|
public void WindowSetTitle(WindowReg window, string title)
|
|
{
|
|
SendCmd(new CmdWinSetTitle
|
|
{
|
|
Window = WinPtr(window),
|
|
Title = title,
|
|
});
|
|
}
|
|
|
|
private static void WinThreadWinSetTitle(CmdWinSetTitle cmd)
|
|
{
|
|
SDL.SDL_SetWindowTitle(cmd.Window, cmd.Title);
|
|
}
|
|
|
|
public void WindowSetMonitor(WindowReg window, IClydeMonitor monitor)
|
|
{
|
|
// API isn't really used and kinda wack, don't feel like figuring it out for SDL3 yet.
|
|
_sawmill.Warning("WindowSetMonitor not implemented on SDL3");
|
|
}
|
|
|
|
public void WindowSetSize(WindowReg window, Vector2i size)
|
|
{
|
|
SendCmd(new CmdWinSetSize { Window = WinPtr(window), W = size.X, H = size.Y });
|
|
}
|
|
|
|
public void WindowSetVisible(WindowReg window, bool visible)
|
|
{
|
|
window.IsVisible = visible;
|
|
SendCmd(new CmdWinSetVisible { Window = WinPtr(window), Visible = visible });
|
|
}
|
|
|
|
private static void WinThreadWinSetSize(CmdWinSetSize cmd)
|
|
{
|
|
var density = SDL.SDL_GetWindowPixelDensity(cmd.Window);
|
|
SDL.SDL_SetWindowSize(cmd.Window, (int)(cmd.W / density), (int)(cmd.H / density));
|
|
}
|
|
|
|
private static void WinThreadWinSetVisible(CmdWinSetVisible cmd)
|
|
{
|
|
if (cmd.Visible)
|
|
SDL.SDL_ShowWindow(cmd.Window);
|
|
else
|
|
SDL.SDL_HideWindow(cmd.Window);
|
|
}
|
|
|
|
public void WindowRequestAttention(WindowReg window)
|
|
{
|
|
SendCmd(new CmdWinRequestAttention { Window = WinPtr(window) });
|
|
}
|
|
|
|
private void WinThreadWinRequestAttention(CmdWinRequestAttention cmd)
|
|
{
|
|
var res = SDL.SDL_FlashWindow(cmd.Window, SDL.SDL_FlashOperation.SDL_FLASH_UNTIL_FOCUSED);
|
|
if (!res)
|
|
_sawmill.Error("Failed to flash window: {error}", SDL.SDL_GetError());
|
|
}
|
|
|
|
public void WindowSetProgress(WindowReg window, WindowProgressState state, float value)
|
|
{
|
|
SendCmd(new CmdWinSetProgress
|
|
{
|
|
Window = WinPtr(window),
|
|
State = (SDL.SDL_ProgressState)state,
|
|
Value = value
|
|
});
|
|
}
|
|
|
|
private void WinThreadWinSetProgress(CmdWinSetProgress cmd)
|
|
{
|
|
if (_progressUnavailable)
|
|
return;
|
|
|
|
try
|
|
{
|
|
var res = SDL.SDL_SetWindowProgressState(cmd.Window, cmd.State);
|
|
if (!res)
|
|
{
|
|
_sawmill.Error("Failed to set window progress state: {error}", SDL.SDL_GetError());
|
|
return;
|
|
}
|
|
|
|
res = SDL.SDL_SetWindowProgressValue(cmd.Window, cmd.Value);
|
|
if (!res)
|
|
_sawmill.Error("Failed to set window progress value: {error}", SDL.SDL_GetError());
|
|
}
|
|
catch (EntryPointNotFoundException)
|
|
{
|
|
// Allowing it to fail means I don't have to update the launcher immediately :)
|
|
_progressUnavailable = true;
|
|
_sawmill.Debug("SDL3 progress APIs unavailable");
|
|
}
|
|
}
|
|
|
|
public unsafe void WindowSwapBuffers(WindowReg window)
|
|
{
|
|
var reg = (Sdl3WindowReg)window;
|
|
var windowPtr = WinPtr(reg);
|
|
|
|
#if WINDOWS
|
|
// On Windows, SwapBuffers does not correctly sync to the DWM compositor.
|
|
// This means OpenGL vsync is effectively broken by default on Windows.
|
|
// We manually sync via DwmFlush(). GLFW does this automatically, SDL3 does not.
|
|
//
|
|
// Windows DwmFlush logic partly taken from:
|
|
// https://github.com/love2d/love/blob/5175b0d1b599ea4c7b929f6b4282dd379fa116b8/src/modules/window/sdl/Window.cpp#L1018
|
|
// https://github.com/glfw/glfw/blob/d3ede7b6847b66cf30b067214b2b4b126d4c729b/src/wgl_context.c#L321-L340
|
|
// See also: https://github.com/libsdl-org/SDL/issues/5797
|
|
|
|
var dwmFlush = false;
|
|
var swapInterval = 0;
|
|
|
|
if (!reg.Fullscreen && reg.SwapInterval > 0)
|
|
{
|
|
BOOL compositing;
|
|
// 6.2 is Windows 8
|
|
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw
|
|
if (OperatingSystem.IsWindowsVersionAtLeast(6, 2)
|
|
|| Windows.SUCCEEDED(Windows.DwmIsCompositionEnabled(&compositing)) && compositing)
|
|
{
|
|
var curCtx = SDL.SDL_GL_GetCurrentContext();
|
|
var curWin = SDL.SDL_GL_GetCurrentWindow();
|
|
|
|
if (curCtx != reg.GlContext || curWin != reg.Sdl3Window)
|
|
throw new InvalidOperationException("Window context must be current!");
|
|
|
|
SDL.SDL_GL_SetSwapInterval(0);
|
|
dwmFlush = true;
|
|
swapInterval = reg.SwapInterval;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//_sawmill.Debug($"Swapping: {window.Id} @ {_clyde._gameTiming.CurFrame}");
|
|
SDL.SDL_GL_SwapWindow(windowPtr);
|
|
|
|
#if WINDOWS
|
|
if (dwmFlush)
|
|
{
|
|
var i = swapInterval;
|
|
while (i-- > 0)
|
|
{
|
|
Windows.DwmFlush();
|
|
}
|
|
|
|
SDL.SDL_GL_SetSwapInterval(swapInterval);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
public uint? WindowGetX11Id(WindowReg window)
|
|
{
|
|
CheckWindowDisposed(window);
|
|
|
|
if (_videoDriver != SdlVideoDriver.X11)
|
|
return null;
|
|
|
|
var reg = (Sdl3WindowReg)window;
|
|
return reg.X11Id;
|
|
}
|
|
|
|
public nint? WindowGetX11Display(WindowReg window)
|
|
{
|
|
CheckWindowDisposed(window);
|
|
|
|
if (_videoDriver != SdlVideoDriver.X11)
|
|
return null;
|
|
|
|
var reg = (Sdl3WindowReg)window;
|
|
return reg.X11Display;
|
|
}
|
|
|
|
public nint? WindowGetWin32Window(WindowReg window)
|
|
{
|
|
CheckWindowDisposed(window);
|
|
|
|
if (_videoDriver != SdlVideoDriver.Windows)
|
|
return null;
|
|
|
|
var reg = (Sdl3WindowReg)window;
|
|
return reg.WindowsHwnd;
|
|
}
|
|
|
|
public void RunOnWindowThread(Action a)
|
|
{
|
|
SendCmd(new CmdRunAction { Action = a });
|
|
}
|
|
|
|
public void TextInputSetRect(WindowReg reg, UIBox2i rect, int cursor)
|
|
{
|
|
var ratio = ((Sdl3WindowReg)reg).PixelRatio;
|
|
SendCmd(new CmdTextInputSetRect
|
|
{
|
|
Window = WinPtr(reg),
|
|
Rect = new SDL.SDL_Rect
|
|
{
|
|
x = (int)(rect.Left / ratio.X),
|
|
y = (int)(rect.Top / ratio.Y),
|
|
w = (int)(rect.Width / ratio.X),
|
|
h = (int)(rect.Height / ratio.Y)
|
|
},
|
|
Cursor = (int)(cursor / ratio.X)
|
|
});
|
|
}
|
|
|
|
private static void WinThreadSetTextInputRect(CmdTextInputSetRect cmdTextInput)
|
|
{
|
|
var rect = cmdTextInput.Rect;
|
|
SDL.SDL_SetTextInputArea(cmdTextInput.Window, ref rect, cmdTextInput.Cursor);
|
|
}
|
|
|
|
public void TextInputStart(WindowReg reg)
|
|
{
|
|
SendCmd(new CmdTextInputStart { Window = WinPtr(reg) });
|
|
}
|
|
|
|
private static void WinThreadStartTextInput(CmdTextInputStart cmd)
|
|
{
|
|
SDL.SDL_StartTextInput(cmd.Window);
|
|
}
|
|
|
|
public void TextInputStop(WindowReg reg)
|
|
{
|
|
SendCmd(new CmdTextInputStop { Window = WinPtr(reg) });
|
|
}
|
|
|
|
private static void WinThreadStopTextInput(CmdTextInputStop cmd)
|
|
{
|
|
SDL.SDL_StopTextInput(cmd.Window);
|
|
}
|
|
|
|
public void ClipboardSetText(WindowReg mainWindow, string text)
|
|
{
|
|
SendCmd(new CmdSetClipboard { Text = text });
|
|
}
|
|
|
|
private void WinThreadSetClipboard(CmdSetClipboard cmd)
|
|
{
|
|
var res = SDL.SDL_SetClipboardText(cmd.Text);
|
|
if (!res)
|
|
_sawmill.Error("Failed to set clipboard text: {error}", SDL.SDL_GetError());
|
|
}
|
|
|
|
public Task<string> ClipboardGetText(WindowReg mainWindow)
|
|
{
|
|
var tcs = new TaskCompletionSource<string>();
|
|
SendCmd(new CmdGetClipboard { Tcs = tcs });
|
|
return tcs.Task;
|
|
}
|
|
|
|
private static void WinThreadGetClipboard(CmdGetClipboard cmd)
|
|
{
|
|
cmd.Tcs.TrySetResult(SDL.SDL_GetClipboardText());
|
|
}
|
|
|
|
private static void CheckWindowDisposed(WindowReg reg)
|
|
{
|
|
if (reg.IsDisposed)
|
|
throw new ObjectDisposedException("Window disposed");
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private static nint WinPtr(WindowReg reg) => ((Sdl3WindowReg)reg).Sdl3Window;
|
|
|
|
private WindowReg? FindWindow(uint windowId)
|
|
{
|
|
foreach (var windowReg in _clyde._windows)
|
|
{
|
|
var glfwReg = (Sdl3WindowReg)windowReg;
|
|
if (glfwReg.WindowId == windowId)
|
|
return windowReg;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
private sealed class Sdl3WindowReg : WindowReg
|
|
{
|
|
public nint Sdl3Window;
|
|
public uint WindowId;
|
|
public nint GlContext;
|
|
#pragma warning disable CS0649
|
|
public bool Fullscreen;
|
|
#pragma warning restore CS0649
|
|
public int SwapInterval;
|
|
|
|
// Kept around to avoid it being GCd.
|
|
public CursorImpl? Cursor;
|
|
|
|
public nint WindowsHwnd;
|
|
public nint X11Display;
|
|
public uint X11Id;
|
|
}
|
|
}
|
|
}
|