From 07c0b1b9a264feb1de542ac07222a01b642c3521 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 29 Sep 2022 22:22:50 +0000 Subject: [PATCH] Window API revision + Framerate limiter (#27) - add `Backend` enum - `GraphicsDevice` can now take a preferred backend at creation - add `GraphicsDevice.ClaimWindow` - add `GraphicsDevice.UnclaimWindow` - add `GraphicsDevice.SetPresentMode` - add `Window.Claimed` - add `Window.SwapchainFormat` - fix certain odd behaviors in multi-window scenarios - rename `FramerateSettings` to `FrameLimiterSettings` - add `Game.SetFrameLimiter` Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonWorks/pulls/27 --- lib/RefreshCS | 2 +- src/FrameLimiterSettings.cs | 22 ++++++++++++++ src/FramerateSettings.cs | 14 --------- src/Game.cs | 55 +++++++++++++++++++++------------- src/Graphics/CommandBuffer.cs | 11 ++++--- src/Graphics/GraphicsDevice.cs | 53 ++++++++++++++++++++++++++------ src/Graphics/RefreshEnums.cs | 8 +++++ src/Window.cs | 28 ++++++++++++----- 8 files changed, 136 insertions(+), 57 deletions(-) create mode 100644 src/FrameLimiterSettings.cs delete mode 100644 src/FramerateSettings.cs diff --git a/lib/RefreshCS b/lib/RefreshCS index 356f8e9..330896a 160000 --- a/lib/RefreshCS +++ b/lib/RefreshCS @@ -1 +1 @@ -Subproject commit 356f8e9ec2a6118b75e32d2a2ed7dbf4297aba78 +Subproject commit 330896a7be6db93b17b3b47734e449817bb30b7a diff --git a/src/FrameLimiterSettings.cs b/src/FrameLimiterSettings.cs new file mode 100644 index 0000000..bcf7eb2 --- /dev/null +++ b/src/FrameLimiterSettings.cs @@ -0,0 +1,22 @@ +namespace MoonWorks +{ + public enum FrameLimiterMode + { + Uncapped, + Capped + } + + public struct FrameLimiterSettings + { + public FrameLimiterMode Mode; + public int Cap; + + public FrameLimiterSettings( + FrameLimiterMode mode, + int cap + ) { + Mode = mode; + Cap = cap; + } + } +} diff --git a/src/FramerateSettings.cs b/src/FramerateSettings.cs deleted file mode 100644 index 8ebc580..0000000 --- a/src/FramerateSettings.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MoonWorks -{ - public enum FramerateMode - { - Uncapped, - Capped - } - - public struct FramerateSettings - { - public FramerateMode Mode; - public int Cap; - } -} diff --git a/src/Game.cs b/src/Game.cs index 12a453a..2a37582 100644 --- a/src/Game.cs +++ b/src/Game.cs @@ -29,23 +29,16 @@ namespace MoonWorks private bool FramerateCapped = false; private TimeSpan FramerateCapTimeSpan = TimeSpan.Zero; - public Window Window { get; } public GraphicsDevice GraphicsDevice { get; } public AudioDevice AudioDevice { get; } public Inputs Inputs { get; } - private Dictionary moonWorksToRefreshPresentMode = new Dictionary - { - { PresentMode.Immediate, RefreshCS.Refresh.PresentMode.Immediate }, - { PresentMode.Mailbox, RefreshCS.Refresh.PresentMode.Mailbox }, - { PresentMode.FIFO, RefreshCS.Refresh.PresentMode.FIFO }, - { PresentMode.FIFORelaxed, RefreshCS.Refresh.PresentMode.FIFORelaxed } - }; + public Window MainWindow { get; } public Game( WindowCreateInfo windowCreateInfo, PresentMode presentMode, - FramerateSettings framerateSettings, + FrameLimiterSettings frameLimiterSettings, int targetTimestep = 60, bool debugMode = false ) @@ -53,12 +46,7 @@ namespace MoonWorks Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep); gameTimer = Stopwatch.StartNew(); - FramerateCapped = framerateSettings.Mode == FramerateMode.Capped; - - if (FramerateCapped) - { - FramerateCapTimeSpan = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / framerateSettings.Cap); - } + SetFrameLimiter(frameLimiterSettings); for (int i = 0; i < previousSleepTimes.Length; i += 1) { @@ -75,14 +63,18 @@ namespace MoonWorks Inputs = new Inputs(); - Window = new Window(windowCreateInfo); - GraphicsDevice = new GraphicsDevice( - Window.Handle, - moonWorksToRefreshPresentMode[presentMode], + Backend.Vulkan, debugMode ); + MainWindow = new Window(windowCreateInfo, GraphicsDevice.WindowFlags); + + if (!GraphicsDevice.ClaimWindow(MainWindow, presentMode)) + { + throw new System.SystemException("Could not claim window!"); + } + AudioDevice = new AudioDevice(); } @@ -96,12 +88,26 @@ namespace MoonWorks Destroy(); AudioDevice.Dispose(); + MainWindow.Dispose(); GraphicsDevice.Dispose(); - Window.Dispose(); SDL.SDL_Quit(); } + public void SetFrameLimiter(FrameLimiterSettings settings) + { + FramerateCapped = settings.Mode == FrameLimiterMode.Capped; + + if (FramerateCapped) + { + FramerateCapTimeSpan = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / settings.Cap); + } + else + { + FramerateCapTimeSpan = TimeSpan.Zero; + } + } + protected abstract void Update(TimeSpan delta); protected abstract void Draw(double alpha); protected virtual void Destroy() {} @@ -224,7 +230,14 @@ namespace MoonWorks { if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED) { - Window.SizeChanged((uint) evt.window.data1, (uint) evt.window.data2); + var window = Window.Lookup(evt.window.windowID); + window.SizeChanged((uint) evt.window.data1, (uint) evt.window.data2); + } + else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE) + { + var window = Window.Lookup(evt.window.windowID); + GraphicsDevice.UnclaimWindow(window); + window.Dispose(); } } diff --git a/src/Graphics/CommandBuffer.cs b/src/Graphics/CommandBuffer.cs index ac01e28..1918940 100644 --- a/src/Graphics/CommandBuffer.cs +++ b/src/Graphics/CommandBuffer.cs @@ -13,15 +13,18 @@ namespace MoonWorks.Graphics public IntPtr Handle { get; } // some state for debug validation - GraphicsPipeline currentGraphicsPipeline = null; - ComputePipeline currentComputePipeline = null; - bool renderPassActive = false; + GraphicsPipeline currentGraphicsPipeline; + ComputePipeline currentComputePipeline; + bool renderPassActive; // called from RefreshDevice internal CommandBuffer(GraphicsDevice device, IntPtr handle) { Device = device; Handle = handle; + currentGraphicsPipeline = null; + currentComputePipeline = null; + renderPassActive = false; } // FIXME: we can probably use the NativeMemory functions to not have to generate arrays here @@ -815,7 +818,7 @@ namespace MoonWorks.Graphics return new Texture( Device, texturePtr, - Device.GetSwapchainFormat(window), + window.SwapchainFormat, width, height ); diff --git a/src/Graphics/GraphicsDevice.cs b/src/Graphics/GraphicsDevice.cs index c111c9e..0f9fcbb 100644 --- a/src/Graphics/GraphicsDevice.cs +++ b/src/Graphics/GraphicsDevice.cs @@ -8,6 +8,10 @@ namespace MoonWorks.Graphics public class GraphicsDevice : IDisposable { public IntPtr Handle { get; } + public Backend Backend { get; } + + private uint windowFlags; + public SDL2.SDL.SDL_WindowFlags WindowFlags => (SDL2.SDL.SDL_WindowFlags) windowFlags; // Built-in video pipeline private ShaderModule VideoVertexShader { get; } @@ -19,19 +23,13 @@ namespace MoonWorks.Graphics private readonly List> resources = new List>(); public GraphicsDevice( - IntPtr deviceWindowHandle, - Refresh.PresentMode presentMode, + Backend preferredBackend, bool debugMode ) { - var presentationParameters = new Refresh.PresentationParameters - { - deviceWindowHandle = deviceWindowHandle, - presentMode = presentMode - }; + Backend = (Backend) Refresh.Refresh_SelectBackend((Refresh.Backend) preferredBackend, out windowFlags); Handle = Refresh.Refresh_CreateDevice( - presentationParameters, Conversions.BoolToByte(debugMode) ); @@ -56,6 +54,43 @@ namespace MoonWorks.Graphics ); } + public bool ClaimWindow(Window window, PresentMode presentMode) + { + var success = Conversions.ByteToBool( + Refresh.Refresh_ClaimWindow( + Handle, + window.Handle, + (Refresh.PresentMode) presentMode + ) + ); + + if (success) + { + window.Claimed = true; + window.SwapchainFormat = GetSwapchainFormat(window); + } + + return success; + } + + public void UnclaimWindow(Window window) + { + Refresh.Refresh_UnclaimWindow( + Handle, + window.Handle + ); + window.Claimed = false; + } + + public void SetPresentMode(Window window, PresentMode presentMode) + { + Refresh.Refresh_SetSwapchainPresentMode( + Handle, + window.Handle, + (Refresh.PresentMode) presentMode + ); + } + public CommandBuffer AcquireCommandBuffer() { return new CommandBuffer(this, Refresh.Refresh_AcquireCommandBuffer(Handle, 0)); @@ -82,7 +117,7 @@ namespace MoonWorks.Graphics Refresh.Refresh_Wait(Handle); } - public TextureFormat GetSwapchainFormat(Window window) + private TextureFormat GetSwapchainFormat(Window window) { return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle); } diff --git a/src/Graphics/RefreshEnums.cs b/src/Graphics/RefreshEnums.cs index 1587dd8..1362a90 100644 --- a/src/Graphics/RefreshEnums.cs +++ b/src/Graphics/RefreshEnums.cs @@ -280,4 +280,12 @@ namespace MoonWorks.Graphics FloatOpaqueWhite, IntOpaqueWhite } + + public enum Backend + { + DontCare, + Vulkan, + PS5, + Invalid + } } diff --git a/src/Window.cs b/src/Window.cs index b81cb02..e1f1cdb 100644 --- a/src/Window.cs +++ b/src/Window.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using SDL2; namespace MoonWorks @@ -10,29 +11,32 @@ namespace MoonWorks public uint Width { get; private set; } public uint Height { get; private set; } + public bool Claimed { get; internal set; } + public MoonWorks.Graphics.TextureFormat SwapchainFormat { get; internal set; } + private bool IsDisposed; - public Window(WindowCreateInfo windowCreateInfo) - { - var windowFlags = SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN; + private static Dictionary idLookup = new Dictionary(); + public Window(WindowCreateInfo windowCreateInfo, SDL.SDL_WindowFlags flags) + { if (windowCreateInfo.ScreenMode == ScreenMode.Fullscreen) { - windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN; + flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN; } else if (windowCreateInfo.ScreenMode == ScreenMode.BorderlessWindow) { - windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP; + flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP; } if (windowCreateInfo.SystemResizable) { - windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE; + flags |= SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE; } if (windowCreateInfo.StartMaximized) { - windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_MAXIMIZED; + flags |= SDL.SDL_WindowFlags.SDL_WINDOW_MAXIMIZED; } ScreenMode = windowCreateInfo.ScreenMode; @@ -43,11 +47,13 @@ namespace MoonWorks SDL.SDL_WINDOWPOS_UNDEFINED, (int) windowCreateInfo.WindowWidth, (int) windowCreateInfo.WindowHeight, - windowFlags + flags ); Width = windowCreateInfo.WindowWidth; Height = windowCreateInfo.WindowHeight; + + idLookup.Add(SDL.SDL_GetWindowID(Handle), this); } public void ChangeScreenMode(ScreenMode screenMode) @@ -81,6 +87,11 @@ namespace MoonWorks Height = height; } + internal static Window Lookup(uint windowID) + { + return idLookup.ContainsKey(windowID) ? idLookup[windowID] : null; + } + internal void SizeChanged(uint width, uint height) { Width = width; @@ -96,6 +107,7 @@ namespace MoonWorks // dispose managed state (managed objects) } + idLookup.Remove(SDL.SDL_GetWindowID(Handle)); SDL.SDL_DestroyWindow(Handle); IsDisposed = true;