resource management and logging improvements

cosmonaut 2023-10-04 14:45:17 -07:00
parent 1d27a9e4a4
commit e616b0fa62
10 changed files with 144 additions and 64 deletions

View File

@ -70,7 +70,7 @@ namespace MoonWorks
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0) if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0)
{ {
System.Console.WriteLine("Failed to initialize SDL!"); Logger.LogError("Failed to initialize SDL!");
return; return;
} }
@ -235,6 +235,8 @@ namespace MoonWorks
Draw(alpha); Draw(alpha);
accumulatedDrawTime -= FramerateCapTimeSpan; accumulatedDrawTime -= FramerateCapTimeSpan;
GraphicsDevice.FlushEmergencyDisposalQueue();
} }
} }
@ -338,14 +340,14 @@ namespace MoonWorks
var index = evt.cdevice.which; var index = evt.cdevice.which;
if (SDL.SDL_IsGameController(index) == SDL.SDL_bool.SDL_TRUE) if (SDL.SDL_IsGameController(index) == SDL.SDL_bool.SDL_TRUE)
{ {
System.Console.WriteLine($"New controller detected!"); Logger.LogInfo("New controller detected!");
Inputs.AddGamepad(index); Inputs.AddGamepad(index);
} }
} }
private void HandleControllerRemoved(SDL.SDL_Event evt) private void HandleControllerRemoved(SDL.SDL_Event evt)
{ {
System.Console.WriteLine($"Controller removal detected!"); Logger.LogInfo("Controller removal detected!");
Inputs.RemoveGamepad(evt.cdevice.which); Inputs.RemoveGamepad(evt.cdevice.which);
} }

View File

@ -1,11 +1,11 @@
using System.Collections.Generic; using System.Collections.Concurrent;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics
{ {
internal class FencePool internal class FencePool
{ {
private GraphicsDevice GraphicsDevice; private GraphicsDevice GraphicsDevice;
private Queue<Fence> Fences = new Queue<Fence>(); private ConcurrentQueue<Fence> Fences = new ConcurrentQueue<Fence>();
public FencePool(GraphicsDevice graphicsDevice) public FencePool(GraphicsDevice graphicsDevice)
{ {
@ -14,12 +14,14 @@ namespace MoonWorks.Graphics
public Fence Obtain() public Fence Obtain()
{ {
if (Fences.Count == 0) if (Fences.TryDequeue(out var fence))
{
return fence;
}
else
{ {
return new Fence(GraphicsDevice); return new Fence(GraphicsDevice);
} }
return Fences.Dequeue();
} }
public void Return(Fence fence) public void Return(Fence fence)

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using RefreshCS; using RefreshCS;
@ -87,6 +88,12 @@ namespace MoonWorks.Graphics
/// <returns>True if successfully claimed.</returns> /// <returns>True if successfully claimed.</returns>
public bool ClaimWindow(Window window, PresentMode presentMode) public bool ClaimWindow(Window window, PresentMode presentMode)
{ {
if (window.Claimed)
{
Logger.LogError("Window already claimed!");
return false;
}
var success = Conversions.ByteToBool( var success = Conversions.ByteToBool(
Refresh.Refresh_ClaimWindow( Refresh.Refresh_ClaimWindow(
Handle, Handle,
@ -101,7 +108,7 @@ namespace MoonWorks.Graphics
window.SwapchainFormat = GetSwapchainFormat(window); window.SwapchainFormat = GetSwapchainFormat(window);
if (window.SwapchainTexture == null) if (window.SwapchainTexture == null)
{ {
window.SwapchainTexture = new Texture(this, IntPtr.Zero, window.SwapchainFormat, 0, 0); window.SwapchainTexture = new Texture(this);
} }
} }
@ -112,12 +119,20 @@ namespace MoonWorks.Graphics
/// Unclaims a window, making it unavailable for presenting and freeing associated resources. /// Unclaims a window, making it unavailable for presenting and freeing associated resources.
/// </summary> /// </summary>
public void UnclaimWindow(Window window) public void UnclaimWindow(Window window)
{
if (window.Claimed)
{ {
Refresh.Refresh_UnclaimWindow( Refresh.Refresh_UnclaimWindow(
Handle, Handle,
window.Handle window.Handle
); );
window.Claimed = false; window.Claimed = false;
// The swapchain texture doesn't actually have a permanent texture reference, so we zero the handle before disposing.
window.SwapchainTexture.Handle = IntPtr.Zero;
window.SwapchainTexture.Dispose();
window.SwapchainTexture = null;
}
} }
/// <summary> /// <summary>
@ -129,6 +144,7 @@ namespace MoonWorks.Graphics
{ {
if (!window.Claimed) if (!window.Claimed)
{ {
Logger.LogError("Cannot set present mode on unclaimed window!");
return; return;
} }
@ -338,6 +354,21 @@ namespace MoonWorks.Graphics
} }
} }
ConcurrentQueue<GraphicsResourceDisposalHandle> emergencyDisposalQueue = new ConcurrentQueue<GraphicsResourceDisposalHandle>();
internal void RegisterForEmergencyDisposal(GraphicsResourceDisposalHandle handle)
{
emergencyDisposalQueue.Enqueue(handle);
}
internal void FlushEmergencyDisposalQueue()
{
while (emergencyDisposalQueue.TryDequeue(out var handle))
{
handle.Dispose(this);
}
}
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (!IsDisposed) if (!IsDisposed)
@ -359,6 +390,8 @@ namespace MoonWorks.Graphics
Refresh.Refresh_DestroyDevice(Handle); Refresh.Refresh_DestroyDevice(Handle);
} }
FlushEmergencyDisposalQueue();
IsDisposed = true; IsDisposed = true;
} }
} }

View File

@ -16,23 +16,31 @@ namespace MoonWorks.Graphics
{ {
Device = device; Device = device;
if (trackResource)
{
weakReference = new WeakReference<GraphicsResource>(this); weakReference = new WeakReference<GraphicsResource>(this);
Device.AddResourceReference(weakReference); Device.AddResourceReference(weakReference);
} }
internal GraphicsResourceDisposalHandle CreateDisposalHandle()
{
return new GraphicsResourceDisposalHandle
{
QueueDestroyAction = QueueDestroyFunction,
ResourceHandle = Handle
};
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (!IsDisposed) if (!IsDisposed)
{ {
if (weakReference != null) if (Handle != IntPtr.Zero)
{ {
QueueDestroyFunction(Device.Handle, Handle); QueueDestroyFunction(Device.Handle, Handle);
Device.RemoveResourceReference(weakReference); Device.RemoveResourceReference(weakReference);
weakReference.SetTarget(null); weakReference.SetTarget(null);
weakReference = null; weakReference = null;
Handle = IntPtr.Zero;
} }
IsDisposed = true; IsDisposed = true;
@ -41,8 +49,19 @@ namespace MoonWorks.Graphics
~GraphicsResource() ~GraphicsResource()
{ {
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method #if DEBUG
Dispose(disposing: false); if (!IsDisposed && Device != null && !Device.IsDisposed)
{
// If you see this log message, you leaked a graphics resource without disposing it!
// This means your game may eventually run out of native memory for mysterious reasons.
Logger.LogWarn($"A resource of type {GetType().Name} was not Disposed.");
}
#endif
// While we only log in debug builds, in both debug and release builds we want to free
// any native resources associated with this object at the earliest opportunity.
// This will at least prevent you from running out of memory rapidly.
Device.RegisterForEmergencyDisposal(CreateDisposalHandle());
} }
public void Dispose() public void Dispose()

View File

@ -0,0 +1,26 @@
using System;
namespace MoonWorks.Graphics
{
// This allows us to defer native disposal calls from the finalizer thread.
internal struct GraphicsResourceDisposalHandle
{
internal Action<IntPtr, IntPtr> QueueDestroyAction;
internal IntPtr ResourceHandle;
public void Dispose(GraphicsDevice device)
{
if (device == null)
{
throw new ArgumentNullException(nameof(device));
}
if (QueueDestroyAction == null)
{
return;
}
QueueDestroyAction(device.Handle, ResourceHandle);
}
}
}

View File

@ -12,9 +12,9 @@ namespace MoonWorks.Graphics
/// </summary> /// </summary>
public class Fence : GraphicsResource public class Fence : GraphicsResource
{ {
protected override Action<nint, nint> QueueDestroyFunction => Release; protected override Action<nint, nint> QueueDestroyFunction => Refresh.Refresh_ReleaseFence;
internal Fence(GraphicsDevice device) : base(device, true) internal Fence(GraphicsDevice device) : base(device)
{ {
} }
@ -22,14 +22,5 @@ namespace MoonWorks.Graphics
{ {
Handle = handle; Handle = handle;
} }
private void Release(nint deviceHandle, nint fenceHandle)
{
if (fenceHandle != IntPtr.Zero)
{
// This will only be called if the client forgot to release a handle. Oh no!
Refresh.Refresh_ReleaseFence(deviceHandle, fenceHandle);
}
}
} }
} }

View File

@ -303,18 +303,14 @@ namespace MoonWorks.Graphics
// Used by AcquireSwapchainTexture. // Used by AcquireSwapchainTexture.
// Should not be tracked, because swapchain textures are managed by Vulkan. // Should not be tracked, because swapchain textures are managed by Vulkan.
internal Texture( internal Texture(
GraphicsDevice device, GraphicsDevice device
IntPtr handle, ) : base(device)
TextureFormat format,
uint width,
uint height
) : base(device, false)
{ {
Handle = handle; Handle = IntPtr.Zero;
Format = format; Format = TextureFormat.R8G8B8A8;
Width = width; Width = 0;
Height = height; Height = 0;
Depth = 1; Depth = 1;
IsCube = false; IsCube = false;
LevelCount = 1; LevelCount = 1;

View File

@ -119,19 +119,19 @@ namespace MoonWorks.Input
var openResult = SDL.SDL_GameControllerOpen(index); var openResult = SDL.SDL_GameControllerOpen(index);
if (openResult == 0) if (openResult == 0)
{ {
System.Console.WriteLine($"Error opening gamepad!"); Logger.LogError("Error opening gamepad!");
System.Console.WriteLine(SDL.SDL_GetError()); Logger.LogError(SDL.SDL_GetError());
} }
else else
{ {
Gamepads[slot].Register(openResult); Gamepads[slot].Register(openResult);
System.Console.WriteLine($"Gamepad added to slot {slot}!"); Logger.LogInfo($"Gamepad added to slot {slot}!");
} }
return; return;
} }
} }
System.Console.WriteLine("Too many gamepads already!"); Logger.LogInfo("Too many gamepads already!");
} }
internal void RemoveGamepad(int joystickInstanceID) internal void RemoveGamepad(int joystickInstanceID)
@ -142,7 +142,7 @@ namespace MoonWorks.Input
{ {
SDL.SDL_GameControllerClose(Gamepads[slot].Handle); SDL.SDL_GameControllerClose(Gamepads[slot].Handle);
Gamepads[slot].Unregister(); Gamepads[slot].Unregister();
System.Console.WriteLine($"Removing gamepad from slot {slot}!"); Logger.LogInfo($"Removing gamepad from slot {slot}!");
return; return;
} }
} }

View File

@ -5,9 +5,9 @@ namespace MoonWorks
{ {
public static class Logger public static class Logger
{ {
public static Action<string> LogInfo; public static Action<string> LogInfo = LogInfoDefault;
public static Action<string> LogWarn; public static Action<string> LogWarn = LogWarnDefault;
public static Action<string> LogError; public static Action<string> LogError = LogErrorDefault;
private static RefreshCS.Refresh.Refresh_LogFunc LogInfoFunc = RefreshLogInfo; private static RefreshCS.Refresh.Refresh_LogFunc LogInfoFunc = RefreshLogInfo;
private static RefreshCS.Refresh.Refresh_LogFunc LogWarnFunc = RefreshLogWarn; private static RefreshCS.Refresh.Refresh_LogFunc LogWarnFunc = RefreshLogWarn;
@ -15,19 +15,6 @@ namespace MoonWorks
internal static void Initialize() internal static void Initialize()
{ {
if (Logger.LogInfo == null)
{
Logger.LogInfo = Console.WriteLine;
}
if (Logger.LogWarn == null)
{
Logger.LogWarn = Console.WriteLine;
}
if (Logger.LogError == null)
{
Logger.LogError = Console.WriteLine;
}
Refresh.Refresh_HookLogFunctions( Refresh.Refresh_HookLogFunctions(
LogInfoFunc, LogInfoFunc,
LogWarnFunc, LogWarnFunc,
@ -35,6 +22,30 @@ namespace MoonWorks
); );
} }
private static void LogInfoDefault(string str)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("INFO: ");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(str);
}
private static void LogWarnDefault(string str)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("WARN: ");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(str);
}
private static void LogErrorDefault(string str)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Write("ERROR: ");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(str);
}
private static void RefreshLogInfo(IntPtr msg) private static void RefreshLogInfo(IntPtr msg)
{ {
LogInfo(UTF8_ToManaged(msg)); LogInfo(UTF8_ToManaged(msg));

View File

@ -16,7 +16,7 @@ namespace MoonWorks
public ScreenMode ScreenMode { get; private set; } public ScreenMode ScreenMode { get; private set; }
public uint Width { get; private set; } public uint Width { get; private set; }
public uint Height { get; private set; } public uint Height { get; private set; }
internal Texture SwapchainTexture { get; set; } = null; internal Texture SwapchainTexture { get; set; }
public bool Claimed { get; internal set; } public bool Claimed { get; internal set; }
public MoonWorks.Graphics.TextureFormat SwapchainFormat { get; internal set; } public MoonWorks.Graphics.TextureFormat SwapchainFormat { get; internal set; }