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: #27
pull/28/head
cosmonaut 2022-09-29 22:22:50 +00:00
parent 3ffdf8a929
commit 07c0b1b9a2
8 changed files with 136 additions and 57 deletions

@ -1 +1 @@
Subproject commit 356f8e9ec2a6118b75e32d2a2ed7dbf4297aba78
Subproject commit 330896a7be6db93b17b3b47734e449817bb30b7a

View File

@ -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;
}
}
}

View File

@ -1,14 +0,0 @@
namespace MoonWorks
{
public enum FramerateMode
{
Uncapped,
Capped
}
public struct FramerateSettings
{
public FramerateMode Mode;
public int Cap;
}
}

View File

@ -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<PresentMode, RefreshCS.Refresh.PresentMode> moonWorksToRefreshPresentMode = new Dictionary<PresentMode, RefreshCS.Refresh.PresentMode>
{
{ 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();
}
}

View File

@ -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
);

View File

@ -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<WeakReference<GraphicsResource>> resources = new List<WeakReference<GraphicsResource>>();
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);
}

View File

@ -280,4 +280,12 @@ namespace MoonWorks.Graphics
FloatOpaqueWhite,
IntOpaqueWhite
}
public enum Backend
{
DontCare,
Vulkan,
PS5,
Invalid
}
}

View File

@ -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<uint, Window> idLookup = new Dictionary<uint, Window>();
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;