MoonWorks/src/Graphics/GraphicsDevice.cs

485 lines
12 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using MoonWorks.Video;
using RefreshCS;
using WellspringCS;
namespace MoonWorks.Graphics
{
2023-09-19 20:19:41 +00:00
/// <summary>
/// GraphicsDevice manages all graphics-related concerns.
/// </summary>
2022-02-23 05:14:32 +00:00
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
internal GraphicsPipeline VideoPipeline { get; }
// Built-in text shader info
public GraphicsShaderInfo TextVertexShaderInfo { get; }
public GraphicsShaderInfo TextFragmentShaderInfo { get; }
public VertexInputState TextVertexInputState { get; }
// Built-in samplers
public Sampler PointSampler { get; }
public Sampler LinearSampler { get; }
2022-02-23 05:14:32 +00:00
public bool IsDisposed { get; private set; }
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
2023-09-19 06:18:21 +00:00
private FencePool FencePool;
private CommandBufferPool CommandBufferPool;
2023-09-19 20:19:41 +00:00
internal GraphicsDevice(
Backend preferredBackend,
2022-02-23 06:16:06 +00:00
bool debugMode
) {
Backend = (Backend) Refresh.Refresh_SelectBackend((Refresh.Backend) preferredBackend, out windowFlags);
2022-02-23 05:14:32 +00:00
if (Backend == Backend.Invalid)
{
throw new System.Exception("Could not set graphics backend!");
}
2022-02-23 05:14:32 +00:00
Handle = Refresh.Refresh_CreateDevice(
Conversions.BoolToByte(debugMode)
);
// TODO: check for CreateDevice fail
// Check for replacement stock shaders
2023-02-08 20:44:36 +00:00
string basePath = System.AppContext.BaseDirectory;
2023-02-03 23:07:32 +00:00
string videoVertPath = Path.Combine(basePath, "video_fullscreen.vert.refresh");
string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh");
string textVertPath = Path.Combine(basePath, "text_transform.vert.refresh");
string textFragPath = Path.Combine(basePath, "text_msdf.frag.refresh");
ShaderModule videoVertShader;
ShaderModule videoFragShader;
ShaderModule textVertShader;
ShaderModule textFragShader;
if (File.Exists(videoVertPath) && File.Exists(videoFragPath))
{
videoVertShader = new ShaderModule(this, videoVertPath);
videoFragShader = new ShaderModule(this, videoFragPath);
}
else
{
// use defaults
var assembly = typeof(GraphicsDevice).Assembly;
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh");
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh");
videoVertShader = new ShaderModule(this, vertStream);
videoFragShader = new ShaderModule(this, fragStream);
}
2023-09-19 06:18:21 +00:00
if (File.Exists(textVertPath) && File.Exists(textFragPath))
{
textVertShader = new ShaderModule(this, textVertPath);
textFragShader = new ShaderModule(this, textFragPath);
}
else
{
// use defaults
var assembly = typeof(GraphicsDevice).Assembly;
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh");
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh");
textVertShader = new ShaderModule(this, vertStream);
textFragShader = new ShaderModule(this, fragStream);
}
VideoPipeline = new GraphicsPipeline(
this,
new GraphicsPipelineCreateInfo
{
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
new ColorAttachmentDescription(
TextureFormat.R8G8B8A8,
ColorAttachmentBlendState.None
)
),
DepthStencilState = DepthStencilState.Disable,
VertexShaderInfo = GraphicsShaderInfo.Create(
videoVertShader,
"main",
0
),
FragmentShaderInfo = GraphicsShaderInfo.Create(
videoFragShader,
"main",
3
),
VertexInputState = VertexInputState.Empty,
RasterizerState = RasterizerState.CCW_CullNone,
PrimitiveType = PrimitiveType.TriangleList,
MultisampleState = MultisampleState.None
}
);
TextVertexShaderInfo = GraphicsShaderInfo.Create<Math.Float.Matrix4x4>(textVertShader, "main", 0);
TextFragmentShaderInfo = GraphicsShaderInfo.Create<float>(textFragShader, "main", 1);
TextVertexInputState = VertexInputState.CreateSingleBinding<Font.Vertex>();
PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp);
LinearSampler = new Sampler(this, SamplerCreateInfo.LinearClamp);
2023-09-19 06:18:21 +00:00
FencePool = new FencePool(this);
CommandBufferPool = new CommandBufferPool(this);
2022-02-23 05:14:32 +00:00
}
2023-09-19 20:19:41 +00:00
/// <summary>
/// Prepares a window so that frames can be presented to it.
/// </summary>
/// <param name="presentMode">The desired presentation mode for the window. Roughly equivalent to V-Sync.</param>
/// <returns>True if successfully claimed.</returns>
public bool ClaimWindow(Window window, PresentMode presentMode)
{
if (window.Claimed)
{
Logger.LogError("Window already claimed!");
return false;
}
var success = Conversions.ByteToBool(
Refresh.Refresh_ClaimWindow(
Handle,
window.Handle,
(Refresh.PresentMode) presentMode
)
);
if (success)
{
window.Claimed = true;
window.SwapchainFormat = GetSwapchainFormat(window);
2022-12-13 08:52:35 +00:00
if (window.SwapchainTexture == null)
{
window.SwapchainTexture = new Texture(this, window.SwapchainFormat);
2022-12-13 08:52:35 +00:00
}
}
return success;
}
2023-09-19 20:19:41 +00:00
/// <summary>
/// Unclaims a window, making it unavailable for presenting and freeing associated resources.
/// </summary>
public void UnclaimWindow(Window window)
{
if (window.Claimed)
{
Refresh.Refresh_UnclaimWindow(
Handle,
window.Handle
);
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;
}
}
2023-09-19 20:19:41 +00:00
/// <summary>
/// Changes the present mode of a claimed window. Does nothing if the window is not claimed.
/// </summary>
/// <param name="window"></param>
/// <param name="presentMode"></param>
public void SetPresentMode(Window window, PresentMode presentMode)
{
2023-09-19 20:19:41 +00:00
if (!window.Claimed)
{
Logger.LogError("Cannot set present mode on unclaimed window!");
2023-09-19 20:19:41 +00:00
return;
}
Refresh.Refresh_SetSwapchainPresentMode(
Handle,
window.Handle,
(Refresh.PresentMode) presentMode
);
}
2023-09-19 20:19:41 +00:00
/// <summary>
/// Acquires a command buffer.
/// This is the start of your rendering process.
/// </summary>
/// <returns></returns>
2022-02-23 05:14:32 +00:00
public CommandBuffer AcquireCommandBuffer()
{
var commandBuffer = CommandBufferPool.Obtain();
commandBuffer.SetHandle(Refresh.Refresh_AcquireCommandBuffer(Handle));
#if DEBUG
commandBuffer.ResetStateTracking();
#endif
return commandBuffer;
2022-02-23 05:14:32 +00:00
}
2023-09-19 06:18:21 +00:00
/// <summary>
/// Submits a command buffer to the GPU for processing.
/// </summary>
public void Submit(CommandBuffer commandBuffer)
{
#if DEBUG
if (commandBuffer.Submitted)
{
throw new System.InvalidOperationException("Command buffer already submitted!");
}
#endif
2023-09-19 06:18:21 +00:00
Refresh.Refresh_Submit(
Handle,
commandBuffer.Handle
);
CommandBufferPool.Return(commandBuffer);
#if DEBUG
commandBuffer.Submitted = true;
#endif
2023-09-19 06:18:21 +00:00
}
/// <summary>
/// Submits a command buffer to the GPU for processing and acquires a fence associated with the submission.
/// </summary>
/// <returns></returns>
public Fence SubmitAndAcquireFence(CommandBuffer commandBuffer)
2022-12-13 08:34:16 +00:00
{
2023-09-19 06:18:21 +00:00
var fenceHandle = Refresh.Refresh_SubmitAndAcquireFence(
Handle,
commandBuffer.Handle
);
2022-12-13 08:34:16 +00:00
2023-09-19 06:18:21 +00:00
var fence = FencePool.Obtain();
fence.SetHandle(fenceHandle);
2022-12-13 08:34:16 +00:00
2023-09-19 06:18:21 +00:00
return fence;
}
/// <summary>
/// Wait for the graphics device to become idle.
/// </summary>
public void Wait()
{
Refresh.Refresh_Wait(Handle);
}
/// <summary>
/// Waits for the given fence to become signaled.
/// </summary>
public unsafe void WaitForFences(Fence fence)
{
var handlePtr = stackalloc nint[1];
handlePtr[0] = fence.Handle;
Refresh.Refresh_WaitForFences(
2022-12-13 08:34:16 +00:00
Handle,
1,
2023-09-19 06:18:21 +00:00
1,
(nint) handlePtr
2022-12-13 08:34:16 +00:00
);
}
2023-09-19 06:18:21 +00:00
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
public unsafe void WaitForFences(
Fence fenceOne,
Fence fenceTwo,
bool waitAll
2022-12-13 08:34:16 +00:00
) {
2023-09-19 06:18:21 +00:00
var handlePtr = stackalloc nint[2];
handlePtr[0] = fenceOne.Handle;
handlePtr[1] = fenceTwo.Handle;
2022-12-13 08:34:16 +00:00
2023-09-19 06:18:21 +00:00
Refresh.Refresh_WaitForFences(
2022-12-13 08:34:16 +00:00
Handle,
2023-09-19 06:18:21 +00:00
Conversions.BoolToByte(waitAll),
2022-12-13 08:34:16 +00:00
2,
2023-09-19 06:18:21 +00:00
(nint) handlePtr
2022-12-13 08:34:16 +00:00
);
}
2023-09-19 06:18:21 +00:00
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
public unsafe void WaitForFences(
Fence fenceOne,
Fence fenceTwo,
Fence fenceThree,
bool waitAll
2022-12-13 08:34:16 +00:00
) {
2023-09-19 06:18:21 +00:00
var handlePtr = stackalloc nint[3];
handlePtr[0] = fenceOne.Handle;
handlePtr[1] = fenceTwo.Handle;
handlePtr[2] = fenceThree.Handle;
2022-12-13 08:34:16 +00:00
2023-09-19 06:18:21 +00:00
Refresh.Refresh_WaitForFences(
2022-12-13 08:34:16 +00:00
Handle,
2023-09-19 06:18:21 +00:00
Conversions.BoolToByte(waitAll),
2022-12-13 08:34:16 +00:00
3,
2023-09-19 06:18:21 +00:00
(nint) handlePtr
2022-12-13 08:34:16 +00:00
);
}
2023-09-19 06:18:21 +00:00
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
public unsafe void WaitForFences(
Fence fenceOne,
Fence fenceTwo,
Fence fenceThree,
Fence fenceFour,
bool waitAll
2022-12-13 08:34:16 +00:00
) {
2023-09-19 06:18:21 +00:00
var handlePtr = stackalloc nint[4];
handlePtr[0] = fenceOne.Handle;
handlePtr[1] = fenceTwo.Handle;
handlePtr[2] = fenceThree.Handle;
handlePtr[3] = fenceFour.Handle;
2022-12-13 08:34:16 +00:00
2023-09-19 06:18:21 +00:00
Refresh.Refresh_WaitForFences(
2022-12-13 08:34:16 +00:00
Handle,
2023-09-19 06:18:21 +00:00
Conversions.BoolToByte(waitAll),
2022-12-13 08:34:16 +00:00
4,
2023-09-19 06:18:21 +00:00
(nint) handlePtr
2022-12-13 08:34:16 +00:00
);
}
2023-09-19 06:18:21 +00:00
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
public unsafe void WaitForFences(Fence[] fences, bool waitAll)
2022-02-23 05:14:32 +00:00
{
2023-09-19 06:18:21 +00:00
var handlePtr = stackalloc nint[fences.Length];
2022-02-23 05:14:32 +00:00
2023-09-19 06:18:21 +00:00
for (var i = 0; i < fences.Length; i += 1)
2022-02-23 05:14:32 +00:00
{
2023-09-19 06:18:21 +00:00
handlePtr[i] = fences[i].Handle;
2022-02-23 05:14:32 +00:00
}
2023-09-19 06:18:21 +00:00
Refresh.Refresh_WaitForFences(
2022-02-23 05:14:32 +00:00
Handle,
2023-09-19 06:18:21 +00:00
Conversions.BoolToByte(waitAll),
4,
(nint) handlePtr
2022-02-23 05:14:32 +00:00
);
}
2023-09-19 06:18:21 +00:00
/// <summary>
/// Returns true if the fence is signaled, indicating that the associated command buffer has finished processing.
/// </summary>
/// <exception cref="InvalidOperationException">Throws if the fence query indicates that the graphics device has been lost.</exception>
public bool QueryFence(Fence fence)
2022-02-23 05:14:32 +00:00
{
2023-09-19 06:18:21 +00:00
var result = Refresh.Refresh_QueryFence(Handle, fence.Handle);
if (result < 0)
{
throw new InvalidOperationException("The graphics device has been lost.");
}
return result != 0;
}
/// <summary>
/// Release reference to an acquired fence, enabling it to be reused.
/// </summary>
public void ReleaseFence(Fence fence)
{
Refresh.Refresh_ReleaseFence(Handle, fence.Handle);
fence.Handle = IntPtr.Zero;
FencePool.Return(fence);
2022-02-23 05:14:32 +00:00
}
private TextureFormat GetSwapchainFormat(Window window)
2022-03-02 07:21:42 +00:00
{
return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle);
}
internal void AddResourceReference(GCHandle resourceReference)
2022-02-23 05:14:32 +00:00
{
lock (resources)
{
resources.Add(resourceReference);
}
}
internal void RemoveResourceReference(GCHandle resourceReference)
2022-02-23 05:14:32 +00:00
{
lock (resources)
{
resources.Remove(resourceReference);
}
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
lock (resources)
{
// Dispose video players first to avoid race condition on threaded decoding
foreach (var resource in resources)
{
if (resource.Target is VideoPlayer player)
{
player.Dispose();
}
}
// Dispose everything else
foreach (var resource in resources)
2022-02-22 22:29:50 +00:00
{
2023-11-21 01:09:22 +00:00
if (resource.Target is IDisposable disposable)
2022-02-23 05:14:32 +00:00
{
disposable.Dispose();
2022-02-23 05:14:32 +00:00
}
2022-02-22 22:29:50 +00:00
}
2022-02-23 05:14:32 +00:00
resources.Clear();
}
}
Refresh.Refresh_DestroyDevice(Handle);
2022-02-23 05:14:32 +00:00
IsDisposed = true;
}
}
~GraphicsDevice()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}