refactor GraphicsDevice

sdl2_gpu
cosmonaut 2024-06-04 16:32:41 -07:00
parent eab282cdca
commit fe6734d6db
2 changed files with 155 additions and 222 deletions

View File

@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using MoonWorks.Video; using MoonWorks.Video;
using RefreshCS; using SDL2;
using WellspringCS; using SDL2_gpuCS;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics
{ {
@ -14,7 +14,7 @@ namespace MoonWorks.Graphics
public class GraphicsDevice : IDisposable public class GraphicsDevice : IDisposable
{ {
public IntPtr Handle { get; } public IntPtr Handle { get; }
public Backend Backend { get; } public BackendFlags Backend { get; }
public bool DebugMode { get; } public bool DebugMode { get; }
private uint windowFlags; private uint windowFlags;
@ -23,12 +23,11 @@ namespace MoonWorks.Graphics
// Built-in video pipeline // Built-in video pipeline
internal GraphicsPipeline VideoPipeline { get; } internal GraphicsPipeline VideoPipeline { get; }
// Built-in blit pipeline
internal GraphicsPipeline BlitPipeline { get; }
// Built-in text shader info // Built-in text shader info
public GraphicsShaderInfo TextVertexShaderInfo { get; } public Shader TextVertexShader;
public GraphicsShaderInfo TextFragmentShaderInfo { get; } public Shader TextFragmentShader;
public GraphicsPipelineResourceInfo TextVertexShaderInfo { get; }
public GraphicsPipelineResourceInfo TextFragmentShaderInfo { get; }
public VertexInputState TextVertexInputState { get; } public VertexInputState TextVertexInputState { get; }
// Built-in samplers // Built-in samplers
@ -43,29 +42,24 @@ namespace MoonWorks.Graphics
private FencePool FencePool; private FencePool FencePool;
internal unsafe GraphicsDevice( internal unsafe GraphicsDevice(
Span<Backend> preferredBackends, BackendFlags preferredBackends,
bool debugMode bool debugMode
) { ) {
var backends = stackalloc Refresh.Backend[preferredBackends.Length]; if (preferredBackends == BackendFlags.Invalid)
for (var i = 0; i < preferredBackends.Length; i += 1)
{
backends[i] = (Refresh.Backend) preferredBackends[i];
}
Backend = (Backend) Refresh.Refresh_SelectBackend(backends, (uint) preferredBackends.Length, out windowFlags);
if (Backend == Backend.Invalid)
{ {
throw new System.Exception("Could not set graphics backend!"); throw new System.Exception("Could not set graphics backend!");
} }
Handle = Refresh.Refresh_CreateDevice( Handle = SDL_Gpu.SDL_GpuCreateDevice(
(SDL_Gpu.BackendFlags) preferredBackends,
Conversions.BoolToByte(debugMode) Conversions.BoolToByte(debugMode)
); );
DebugMode = debugMode; DebugMode = debugMode;
// TODO: check for CreateDevice fail // TODO: check for CreateDevice fail
Backend = (BackendFlags) SDL_Gpu.SDL_GpuGetBackend(Handle);
// Check for replacement stock shaders // Check for replacement stock shaders
string basePath = System.AppContext.BaseDirectory; string basePath = System.AppContext.BaseDirectory;
@ -77,42 +71,79 @@ namespace MoonWorks.Graphics
string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh"); string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh");
string blitFragPath = Path.Combine(basePath, "blit.frag.refresh"); string blitFragPath = Path.Combine(basePath, "blit.frag.refresh");
ShaderModule fullscreenVertShader; Shader fullscreenVertShader;
ShaderModule textVertShader; Shader textVertShader;
ShaderModule textFragShader; Shader textFragShader;
ShaderModule videoFragShader; Shader videoFragShader;
ShaderModule blitFragShader; Shader blitFragShader;
if (File.Exists(fullscreenVertPath)) if (File.Exists(fullscreenVertPath))
{ {
fullscreenVertShader = new ShaderModule(this, fullscreenVertPath); fullscreenVertShader = new Shader(
this,
fullscreenVertPath,
"main",
ShaderStage.Vertex,
ShaderFormat.SECRET
);
} }
else else
{ {
// use defaults // use defaults
var assembly = typeof(GraphicsDevice).Assembly; var assembly = typeof(GraphicsDevice).Assembly;
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.Fullscreen.vert.refresh"); using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.Fullscreen.vert.refresh");
fullscreenVertShader = new ShaderModule(this, vertStream); fullscreenVertShader = new Shader(
this,
vertStream,
"main",
ShaderStage.Vertex,
ShaderFormat.SPIRV
);
} }
if (File.Exists(videoFragPath)) if (File.Exists(videoFragPath))
{ {
videoFragShader = new ShaderModule(this, videoFragPath); videoFragShader = new Shader(
this,
videoFragPath,
"main",
ShaderStage.Fragment,
ShaderFormat.SECRET
);
} }
else else
{ {
// use defaults // use defaults
var assembly = typeof(GraphicsDevice).Assembly; var assembly = typeof(GraphicsDevice).Assembly;
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh"); using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh");
videoFragShader = new ShaderModule(this, fragStream); videoFragShader = new Shader(
this,
fragStream,
"main",
ShaderStage.Fragment,
ShaderFormat.SPIRV
);
} }
if (File.Exists(textVertPath) && File.Exists(textFragPath)) if (File.Exists(textVertPath) && File.Exists(textFragPath))
{ {
textVertShader = new ShaderModule(this, textVertPath); textVertShader = new Shader(
textFragShader = new ShaderModule(this, textFragPath); this,
textVertPath,
"main",
ShaderStage.Vertex,
ShaderFormat.SECRET
);
textFragShader = new Shader(
this,
textFragPath,
"main",
ShaderStage.Fragment,
ShaderFormat.SECRET
);
} }
else else
{ {
@ -122,13 +153,32 @@ namespace MoonWorks.Graphics
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh"); using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh");
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh"); using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh");
textVertShader = new ShaderModule(this, vertStream); textVertShader = new Shader(
textFragShader = new ShaderModule(this, fragStream); this,
vertStream,
"main",
ShaderStage.Fragment,
ShaderFormat.SPIRV
);
textFragShader = new Shader(
this,
fragStream,
"main",
ShaderStage.Fragment,
ShaderFormat.SPIRV
);
} }
if (File.Exists(blitFragPath)) if (File.Exists(blitFragPath))
{ {
blitFragShader = new ShaderModule(this, blitFragPath); blitFragShader = new Shader(
this,
blitFragPath,
"main",
ShaderStage.Fragment,
ShaderFormat.SECRET
);
} }
else else
{ {
@ -136,7 +186,13 @@ namespace MoonWorks.Graphics
var assembly = typeof(GraphicsDevice).Assembly; var assembly = typeof(GraphicsDevice).Assembly;
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.Blit.frag.refresh"); using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.Blit.frag.refresh");
blitFragShader = new ShaderModule(this, fragStream); blitFragShader = new Shader(
this,
fragStream,
"main",
ShaderStage.Fragment,
ShaderFormat.SPIRV
);
} }
VideoPipeline = new GraphicsPipeline( VideoPipeline = new GraphicsPipeline(
@ -150,16 +206,12 @@ namespace MoonWorks.Graphics
) )
), ),
DepthStencilState = DepthStencilState.Disable, DepthStencilState = DepthStencilState.Disable,
VertexShaderResourceInfo = GraphicsShaderInfo.Create( VertexShader = fullscreenVertShader,
fullscreenVertShader, FragmentShader = videoFragShader,
"main", FragmentShaderResourceInfo = new GraphicsPipelineResourceInfo
0 {
), SamplerCount = 3
FragmentShaderResourceInfo = GraphicsShaderInfo.Create( },
videoFragShader,
"main",
3
),
VertexInputState = VertexInputState.Empty, VertexInputState = VertexInputState.Empty,
RasterizerState = RasterizerState.CCW_CullNone, RasterizerState = RasterizerState.CCW_CullNone,
PrimitiveType = PrimitiveType.TriangleList, PrimitiveType = PrimitiveType.TriangleList,
@ -167,36 +219,15 @@ namespace MoonWorks.Graphics
} }
); );
BlitPipeline = new GraphicsPipeline( TextVertexShader = textVertShader;
this, TextVertexShaderInfo = new GraphicsPipelineResourceInfo();
new GraphicsPipelineCreateInfo
{ TextFragmentShader = textFragShader;
AttachmentInfo = new GraphicsPipelineAttachmentInfo( TextFragmentShaderInfo = new GraphicsPipelineResourceInfo
new ColorAttachmentDescription( {
TextureFormat.R8G8B8A8, SamplerCount = 1
ColorAttachmentBlendState.None };
)
),
DepthStencilState = DepthStencilState.Disable,
VertexShaderResourceInfo = GraphicsShaderInfo.Create(
fullscreenVertShader,
"main",
0
),
FragmentShaderResourceInfo = GraphicsShaderInfo.Create(
blitFragShader,
"main",
1
),
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>(); TextVertexInputState = VertexInputState.CreateSingleBinding<Font.Vertex>();
PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp); PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp);
@ -209,28 +240,35 @@ namespace MoonWorks.Graphics
/// <summary> /// <summary>
/// Prepares a window so that frames can be presented to it. /// Prepares a window so that frames can be presented to it.
/// </summary> /// </summary>
/// <param name="swapchainComposition">The desired composition of the swapchain. Ignore this unless you are using HDR or tonemapping.</param>
/// <param name="presentMode">The desired presentation mode for the window. Roughly equivalent to V-Sync.</param> /// <param name="presentMode">The desired presentation mode for the window. Roughly equivalent to V-Sync.</param>
/// <returns>True if successfully claimed.</returns> /// <returns>True if successfully claimed.</returns>
public bool ClaimWindow(Window window, PresentMode presentMode) public bool ClaimWindow(
{ Window window,
SwapchainComposition swapchainComposition,
PresentMode presentMode
) {
if (window.Claimed) if (window.Claimed)
{ {
Logger.LogError("Window already claimed!"); Logger.LogError("Window already claimed!");
return false; return false;
} }
var success = Conversions.ByteToBool( var success = Conversions.IntToBool(
Refresh.Refresh_ClaimWindow( SDL_Gpu.SDL_GpuClaimWindow(
Handle, Handle,
window.Handle, window.Handle,
(Refresh.PresentMode) presentMode (SDL_Gpu.SwapchainComposition) swapchainComposition,
(SDL_Gpu.PresentMode) presentMode
) )
); );
if (success) if (success)
{ {
window.Claimed = true; window.Claimed = true;
window.SwapchainComposition = swapchainComposition;
window.SwapchainFormat = GetSwapchainFormat(window); window.SwapchainFormat = GetSwapchainFormat(window);
if (window.SwapchainTexture == null) if (window.SwapchainTexture == null)
{ {
window.SwapchainTexture = new Texture(this, window.SwapchainFormat); window.SwapchainTexture = new Texture(this, window.SwapchainFormat);
@ -247,7 +285,7 @@ namespace MoonWorks.Graphics
{ {
if (window.Claimed) if (window.Claimed)
{ {
Refresh.Refresh_UnclaimWindow( SDL_Gpu.SDL_GpuUnclaimWindow(
Handle, Handle,
window.Handle window.Handle
); );
@ -265,18 +303,22 @@ namespace MoonWorks.Graphics
/// </summary> /// </summary>
/// <param name="window"></param> /// <param name="window"></param>
/// <param name="presentMode"></param> /// <param name="presentMode"></param>
public void SetPresentMode(Window window, PresentMode presentMode) public void SetSwapchainParameters(
{ Window window,
SwapchainComposition swapchainComposition,
PresentMode presentMode
) {
if (!window.Claimed) if (!window.Claimed)
{ {
Logger.LogError("Cannot set present mode on unclaimed window!"); Logger.LogError("Cannot set present mode on unclaimed window!");
return; return;
} }
Refresh.Refresh_SetSwapchainPresentMode( SDL_Gpu.SDL_GpuSetSwapchainParameters(
Handle, Handle,
window.Handle, window.Handle,
(Refresh.PresentMode) presentMode (SDL_Gpu.SwapchainComposition) swapchainComposition,
(SDL_Gpu.PresentMode) presentMode
); );
} }
@ -288,7 +330,7 @@ namespace MoonWorks.Graphics
public CommandBuffer AcquireCommandBuffer() public CommandBuffer AcquireCommandBuffer()
{ {
var commandBuffer = CommandBufferPool.Obtain(); var commandBuffer = CommandBufferPool.Obtain();
commandBuffer.SetHandle(Refresh.Refresh_AcquireCommandBuffer(Handle)); commandBuffer.SetHandle(SDL_Gpu.SDL_GpuAcquireCommandBuffer(Handle));
#if DEBUG #if DEBUG
commandBuffer.ResetStateTracking(); commandBuffer.ResetStateTracking();
#endif #endif
@ -307,8 +349,7 @@ namespace MoonWorks.Graphics
} }
#endif #endif
Refresh.Refresh_Submit( SDL_Gpu.SDL_GpuSubmit(
Handle,
commandBuffer.Handle commandBuffer.Handle
); );
@ -325,8 +366,7 @@ namespace MoonWorks.Graphics
/// <returns></returns> /// <returns></returns>
public Fence SubmitAndAcquireFence(CommandBuffer commandBuffer) public Fence SubmitAndAcquireFence(CommandBuffer commandBuffer)
{ {
var fenceHandle = Refresh.Refresh_SubmitAndAcquireFence( var fenceHandle = SDL_Gpu.SDL_GpuSubmitAndAcquireFence(
Handle,
commandBuffer.Handle commandBuffer.Handle
); );
@ -341,7 +381,7 @@ namespace MoonWorks.Graphics
/// </summary> /// </summary>
public void Wait() public void Wait()
{ {
Refresh.Refresh_Wait(Handle); SDL_Gpu.SDL_GpuWait(Handle);
} }
/// <summary> /// <summary>
@ -349,14 +389,13 @@ namespace MoonWorks.Graphics
/// </summary> /// </summary>
public unsafe void WaitForFences(Fence fence) public unsafe void WaitForFences(Fence fence)
{ {
var handlePtr = stackalloc nint[1]; var fenceHandle = fence.Handle;
handlePtr[0] = fence.Handle;
Refresh.Refresh_WaitForFences( SDL_Gpu.SDL_GpuWaitForFences(
Handle, Handle,
1, 1,
1, 1,
(nint) handlePtr &fenceHandle
); );
} }
@ -373,11 +412,11 @@ namespace MoonWorks.Graphics
handlePtr[0] = fenceOne.Handle; handlePtr[0] = fenceOne.Handle;
handlePtr[1] = fenceTwo.Handle; handlePtr[1] = fenceTwo.Handle;
Refresh.Refresh_WaitForFences( SDL_Gpu.SDL_GpuWaitForFences(
Handle, Handle,
Conversions.BoolToByte(waitAll), Conversions.BoolToInt(waitAll),
2, 2,
(nint) handlePtr handlePtr
); );
} }
@ -396,11 +435,11 @@ namespace MoonWorks.Graphics
handlePtr[1] = fenceTwo.Handle; handlePtr[1] = fenceTwo.Handle;
handlePtr[2] = fenceThree.Handle; handlePtr[2] = fenceThree.Handle;
Refresh.Refresh_WaitForFences( SDL_Gpu.SDL_GpuWaitForFences(
Handle, Handle,
Conversions.BoolToByte(waitAll), Conversions.BoolToInt(waitAll),
3, 3,
(nint) handlePtr handlePtr
); );
} }
@ -421,11 +460,11 @@ namespace MoonWorks.Graphics
handlePtr[2] = fenceThree.Handle; handlePtr[2] = fenceThree.Handle;
handlePtr[3] = fenceFour.Handle; handlePtr[3] = fenceFour.Handle;
Refresh.Refresh_WaitForFences( SDL_Gpu.SDL_GpuWaitForFences(
Handle, Handle,
Conversions.BoolToByte(waitAll), Conversions.BoolToInt(waitAll),
4, 4,
(nint) handlePtr handlePtr
); );
} }
@ -433,7 +472,7 @@ namespace MoonWorks.Graphics
/// Wait for one or more fences to become signaled. /// Wait for one or more fences to become signaled.
/// </summary> /// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param> /// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
public unsafe void WaitForFences(Fence[] fences, bool waitAll) public unsafe void WaitForFences(Span<Fence> fences, bool waitAll)
{ {
var handlePtr = stackalloc nint[fences.Length]; var handlePtr = stackalloc nint[fences.Length];
@ -442,11 +481,11 @@ namespace MoonWorks.Graphics
handlePtr[i] = fences[i].Handle; handlePtr[i] = fences[i].Handle;
} }
Refresh.Refresh_WaitForFences( SDL_Gpu.SDL_GpuWaitForFences(
Handle, Handle,
Conversions.BoolToByte(waitAll), Conversions.BoolToInt(waitAll),
4, 4,
(nint) handlePtr handlePtr
); );
} }
@ -456,7 +495,7 @@ namespace MoonWorks.Graphics
/// <exception cref="InvalidOperationException">Throws if the fence query indicates that the graphics device has been lost.</exception> /// <exception cref="InvalidOperationException">Throws if the fence query indicates that the graphics device has been lost.</exception>
public bool QueryFence(Fence fence) public bool QueryFence(Fence fence)
{ {
var result = Refresh.Refresh_QueryFence(Handle, fence.Handle); var result = SDL_Gpu.SDL_GpuQueryFence(Handle, fence.Handle);
if (result < 0) if (result < 0)
{ {
@ -471,126 +510,19 @@ namespace MoonWorks.Graphics
/// </summary> /// </summary>
public void ReleaseFence(Fence fence) public void ReleaseFence(Fence fence)
{ {
Refresh.Refresh_ReleaseFence(Handle, fence.Handle); SDL_Gpu.SDL_GpuReleaseFence(Handle, fence.Handle);
fence.Handle = IntPtr.Zero; fence.Handle = IntPtr.Zero;
FencePool.Return(fence); FencePool.Return(fence);
} }
/// <summary>
/// ⚠️⚠️⚠️ <br/>
/// Downloads data from a Texture to a TransferBuffer.
/// This copy occurs immediately on the CPU timeline.<br/>
///
/// If you modify this texture in a command buffer and then call this function without calling
/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/>
///
/// This method forces a sync point and is generally a bad thing to do.
/// Only use it if you have exhausted all other options.<br/>
///
/// Remember: friends don't let friends readback.<br/>
/// ⚠️⚠️⚠️
/// </summary>
public void DownloadFromTexture(
in TextureRegion textureRegion,
TransferBuffer transferBuffer,
in BufferImageCopy copyParams,
TransferOptions transferOption
) {
Refresh.Refresh_DownloadFromTexture(
Handle,
textureRegion.ToRefreshTextureRegion(),
transferBuffer.Handle,
copyParams.ToRefresh(),
(Refresh.TransferOptions) transferOption
);
}
/// <summary>
/// ⚠️⚠️⚠️ <br/>
/// Downloads all data from a 2D texture with no mips to a TransferBuffer.
/// This copy occurs immediately on the CPU timeline.<br/>
///
/// If you modify this texture in a command buffer and then call this function without calling
/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/>
///
/// This method forces a sync point and is generally a bad thing to do.
/// Only use it if you have exhausted all other options.<br/>
///
/// Remember: friends don't let friends readback.<br/>
/// ⚠️⚠️⚠️
/// </summary>
public void DownloadFromTexture(
Texture texture,
TransferBuffer transferBuffer,
TransferOptions transferOption
) {
DownloadFromTexture(
new TextureRegion(texture),
transferBuffer,
new BufferImageCopy(0, 0, 0),
transferOption
);
}
/// <summary>
/// ⚠️⚠️⚠️ <br/>
/// Downloads data from a GpuBuffer to a TransferBuffer.
/// This copy occurs immediately on the CPU timeline.<br/>
///
/// If you modify this GpuBuffer in a command buffer and then call this function without calling
/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/>
///
/// This method forces a sync point and is generally a bad thing to do.
/// Only use it if you have exhausted all other options.<br/>
///
/// Remember: friends don't let friends readback.<br/>
/// ⚠️⚠️⚠️
/// </summary>
public void DownloadFromBuffer(
GpuBuffer gpuBuffer,
TransferBuffer transferBuffer,
in BufferCopy copyParams,
TransferOptions transferOption
) {
Refresh.Refresh_DownloadFromBuffer(
Handle,
gpuBuffer.Handle,
transferBuffer.Handle,
copyParams.ToRefresh(),
(Refresh.TransferOptions) transferOption
);
}
/// <summary>
/// ⚠️⚠️⚠️ <br/>
/// Downloads all data in a GpuBuffer to a TransferBuffer.
/// This copy occurs immediately on the CPU timeline.<br/>
///
/// If you modify this GpuBuffer in a command buffer and then call this function without calling
/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/>
///
/// This method forces a sync point and is generally a bad thing to do.
/// Only use it if you have exhausted all other options.<br/>
///
/// Remember: friends don't let friends readback.<br/>
/// ⚠️⚠️⚠️
/// </summary>
public void DownloadFromBuffer(
GpuBuffer gpuBuffer,
TransferBuffer transferBuffer,
TransferOptions option
) {
DownloadFromBuffer(
gpuBuffer,
transferBuffer,
new BufferCopy(0, 0, gpuBuffer.Size),
option
);
}
private TextureFormat GetSwapchainFormat(Window window) private TextureFormat GetSwapchainFormat(Window window)
{ {
return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle); if (!window.Claimed)
{
throw new System.ArgumentException("Cannot get swapchain format of unclaimed window!");
}
return (TextureFormat) SDL_Gpu.SDL_GpuGetSwapchainTextureFormat(Handle, window.Handle);
} }
internal void AddResourceReference(GCHandle resourceReference) internal void AddResourceReference(GCHandle resourceReference)
@ -638,7 +570,7 @@ namespace MoonWorks.Graphics
} }
} }
Refresh.Refresh_DestroyDevice(Handle); SDL_Gpu.SDL_GpuDestroyDevice(Handle);
IsDisposed = true; IsDisposed = true;
} }

View File

@ -19,6 +19,7 @@ namespace MoonWorks
internal Texture SwapchainTexture { get; set; } internal Texture SwapchainTexture { get; set; }
public bool Claimed { get; internal set; } public bool Claimed { get; internal set; }
public MoonWorks.Graphics.SwapchainComposition SwapchainComposition { get; internal set; }
public MoonWorks.Graphics.TextureFormat SwapchainFormat { get; internal set; } public MoonWorks.Graphics.TextureFormat SwapchainFormat { get; internal set; }
public (int, int) Position public (int, int) Position