Compare commits

...

60 Commits

Author SHA1 Message Date
cosmonaut 4180ecb129 update WellspringCS to point to github 2024-06-06 17:00:10 -07:00
cosmonaut 535abec45a make the fullscreen shader not render upside down 2024-06-06 16:19:14 -07:00
cosmonaut 60d2642119 Fix projection matrices 2024-06-06 11:47:23 -07:00
cosmonaut 8d31a398ab ImageUtils returns byte pointers 2024-06-06 11:14:10 -07:00
cosmonaut baa4df5e18 Refresh API updates 2024-06-06 10:51:46 -07:00
cosmonaut 6d95203816 fix render pass validation 2024-06-05 22:39:42 -07:00
cosmonaut ad025e1777 refactor built-in shader pipelines 2024-06-05 22:01:12 -07:00
cosmonaut 7386952974 some tweaks 2024-06-05 14:19:17 -07:00
cosmonaut 35684df005 renaming some SDL references 2024-06-05 12:37:02 -07:00
cosmonaut baf51d2ee9 using Refresh again 2024-06-05 12:34:24 -07:00
cosmonaut 3cfb43438c refactor CopyPass 2024-06-04 19:35:17 -07:00
cosmonaut 97dee2a170 refactor ComputePass 2024-06-04 17:24:25 -07:00
cosmonaut fe6734d6db refactor GraphicsDevice 2024-06-04 16:32:41 -07:00
cosmonaut eab282cdca refactor RenderPass structure 2024-06-04 16:04:19 -07:00
cosmonaut 4e8653fb1f struct and enum conversions 2024-06-04 12:19:41 -07:00
cosmonaut 8b35b6b0b7 starting graphics layer restructuring 2024-06-04 10:20:07 -07:00
cosmonaut cc7cae9d6b DrawIndexedPrimitives calls DrawInstancedPrimitives 2024-03-13 09:44:59 -07:00
cosmonaut 39dc2fb7f4 VideoPlayer only uses one VideoAV1Stream 2024-03-13 09:40:44 -07:00
cosmonaut b4021156cf restructure video threading implementation 2024-03-13 09:40:44 -07:00
cosmonaut 871b32d742 some more threading changes 2024-03-13 09:40:44 -07:00
cosmonaut 0fbe241848 adjust VideoPlayer to unload AV1 streams on Unload 2024-03-13 09:40:44 -07:00
cosmonaut 1b40b5e7d3 potential fix for render before decode finish 2024-03-13 09:40:44 -07:00
cosmonaut ee8331a96b misc fixes 2024-03-12 16:52:10 -07:00
cosmonaut 8813a0139d expose GraphicsDevice.DebugMode 2024-03-11 16:28:00 -07:00
cosmonaut 217ae96888 Debug Naming API 2024-03-11 16:11:12 -07:00
cosmonaut 23b6499479 add WriteOptions.Unsafe 2024-03-11 10:18:41 -07:00
cosmonaut 929d8f5cc9 bump Refresh to 2 in dll.config 2024-03-08 15:56:31 -08:00
cosmonaut 69d2c9cc31 TransferUsage change 2024-03-08 15:29:44 -08:00
cosmonaut 9195e445b2 update comments on compute bindings 2024-03-07 14:29:13 -08:00
cosmonaut f30d8f0197 rename Discard to Cycle 2024-03-07 14:24:42 -08:00
cosmonaut bde31fbe07 change graphics backend selection behavior 2024-03-07 10:34:30 -08:00
cosmonaut a762a80c4f adjust comment on internal Texture constructor 2024-03-06 16:18:07 -08:00
cosmonaut 099c07aa39 Window.SetPosition 2024-03-05 23:19:07 -08:00
cosmonaut cba6ca59d3 fix bounds check error message in TransferBuffer 2024-03-05 22:51:06 -08:00
cosmonaut a004488f81 cleanup ImageUtils 2024-03-05 22:50:53 -08:00
cosmonaut 33ed8b2364 streamline Font loading 2024-03-05 22:50:45 -08:00
cosmonaut 3c832550d0 modify readback API 2024-03-05 17:47:37 -08:00
cosmonaut c84752f38c D3D11 support 2024-03-05 16:17:29 -08:00
cosmonaut 019afa91f5 fix stencil API 2024-03-02 23:10:44 -08:00
cosmonaut 00adec189c update bindings and WriteOption API 2024-03-01 15:03:14 -08:00
cosmonaut 0e723514df TextureSlice and TextureRegion API 2024-02-29 23:53:11 -08:00
cosmonaut e0f4c19dc2 another refresh2 update 2024-02-28 20:07:19 -08:00
cosmonaut 178a5ea3cf some tweaks to ResourceUploader 2024-02-27 00:42:53 -08:00
cosmonaut 50b8cb11c9 add SetBufferData to ResourceUploader 2024-02-23 16:00:29 -08:00
cosmonaut d83501437d rearrange methods in CommandBuffer 2024-02-23 15:53:49 -08:00
cosmonaut fe520dc9cc add element-wise buffer upload 2024-02-23 15:40:01 -08:00
cosmonaut b29341eca3 VideoPlayer fixes 2024-02-23 15:28:34 -08:00
cosmonaut 22bcd2e471 add SetTextureData to ResourceUploader 2024-02-23 14:53:35 -08:00
cosmonaut fe31e23ccc rename ResourceInitializer to ResourceUploader 2024-02-23 14:32:23 -08:00
cosmonaut 848b1c706c add UploadAndWait method to ResourceInitializer 2024-02-23 14:23:43 -08:00
cosmonaut a207f404b9 add texture slice setters to ResourceInitializer 2024-02-23 14:04:57 -08:00
cosmonaut 31c79d3179 built-in Blit operation 2024-02-23 12:39:59 -08:00
cosmonaut 8229e5dd33 CreateTextureFromDDS + respect buffer alignment 2024-02-23 11:59:56 -08:00
cosmonaut 1eae01c95c add DDS parser to ImageUtils 2024-02-23 11:59:33 -08:00
cosmonaut b80527d793 rename CpuBuffer.cs to TransferBuffer.cs 2024-02-23 10:57:34 -08:00
cosmonaut ecfcb666a8 remove byte pointer interface from TransferBuffer 2024-02-23 10:56:03 -08:00
cosmonaut ad97aed60f compute pass + additional validation 2024-02-23 10:43:39 -08:00
cosmonaut 0a5ec9e82d rename cpuBuffer to transferBuffer in Font 2024-02-23 10:43:22 -08:00
cosmonaut 8648eef5d1 rename CpuBuffer to TransferBuffer 2024-02-23 09:56:00 -08:00
cosmonaut 39496c37ea Refresh 2 changes 2024-02-23 00:06:04 -08:00
64 changed files with 5164 additions and 4644 deletions

12
.gitmodules vendored
View File

@ -4,12 +4,12 @@
[submodule "lib/FAudio"] [submodule "lib/FAudio"]
path = lib/FAudio path = lib/FAudio
url = https://github.com/FNA-XNA/FAudio.git url = https://github.com/FNA-XNA/FAudio.git
[submodule "lib/RefreshCS"]
path = lib/RefreshCS
url = https://gitea.moonside.games/MoonsideGames/RefreshCS.git
[submodule "lib/WellspringCS"]
path = lib/WellspringCS
url = https://gitea.moonside.games/MoonsideGames/WellspringCS.git
[submodule "lib/dav1dfile"] [submodule "lib/dav1dfile"]
path = lib/dav1dfile path = lib/dav1dfile
url = https://github.com/MoonsideGames/dav1dfile.git url = https://github.com/MoonsideGames/dav1dfile.git
[submodule "lib/RefreshCS"]
path = lib/RefreshCS
url = https://github.com/MoonsideGames/RefreshCS.git
[submodule "lib/WellspringCS"]
path = lib/WellspringCS
url = https://github.com/MoonsideGames/WellspringCS.git

View File

@ -3,7 +3,6 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>11</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
@ -12,7 +11,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="lib\FAudio\csharp\FAudio.cs" /> <Compile Include="lib\FAudio\csharp\FAudio.cs" />
<Compile Include="lib\RefreshCS\src\Refresh.cs" /> <Compile Include="lib\RefreshCS\RefreshCS.cs" />
<Compile Include="lib\SDL2-CS\src\SDL2.cs" /> <Compile Include="lib\SDL2-CS\src\SDL2.cs" />
<Compile Include="lib\WellspringCS\WellspringCS.cs" /> <Compile Include="lib\WellspringCS\WellspringCS.cs" />
<Compile Include="lib\dav1dfile\csharp\dav1dfile.cs" /> <Compile Include="lib\dav1dfile\csharp\dav1dfile.cs" />
@ -26,17 +25,17 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_fullscreen.vert.refresh"> <EmbeddedResource Include="src\Graphics\StockShaders\Binary\fullscreen.vert.spv">
<LogicalName>MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh</LogicalName> <LogicalName>MoonWorks.Graphics.StockShaders.Fullscreen.vert.spv</LogicalName>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_yuv2rgba.frag.refresh"> <EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_yuv2rgba.frag.spv">
<LogicalName>MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh</LogicalName> <LogicalName>MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.spv</LogicalName>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_transform.vert.refresh"> <EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_transform.vert.spv">
<LogicalName>MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh</LogicalName> <LogicalName>MoonWorks.Graphics.StockShaders.TextTransform.vert.spv</LogicalName>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_msdf.frag.refresh"> <EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_msdf.frag.spv">
<LogicalName>MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh</LogicalName> <LogicalName>MoonWorks.Graphics.StockShaders.TextMSDF.frag.spv</LogicalName>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -5,8 +5,8 @@
<dllmap dll="SDL2" os="linux,freebsd,netbsd" target="libSDL2-2.0.so.0"/> <dllmap dll="SDL2" os="linux,freebsd,netbsd" target="libSDL2-2.0.so.0"/>
<dllmap dll="Refresh" os="windows" target="Refresh.dll"/> <dllmap dll="Refresh" os="windows" target="Refresh.dll"/>
<dllmap dll="Refresh" os="osx" target="libRefresh.1.dylib"/> <dllmap dll="Refresh" os="osx" target="libRefresh.2.dylib"/>
<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.1"/> <dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.2"/>
<dllmap dll="FAudio" os="windows" target="FAudio.dll"/> <dllmap dll="FAudio" os="windows" target="FAudio.dll"/>
<dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/> <dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/>

@ -1 +1 @@
Subproject commit b5325e6d0329eeb35b074091a569a5f679852d28 Subproject commit 5d09b036cbd0a1402857776d8f39be87234b450a

View File

@ -21,7 +21,7 @@ namespace MoonWorks
{ VertexElementFormat.NormalizedShort4, (uint) Marshal.SizeOf<NormalizedShort4>() }, { VertexElementFormat.NormalizedShort4, (uint) Marshal.SizeOf<NormalizedShort4>() },
{ VertexElementFormat.Short2, (uint) Marshal.SizeOf<Short2>() }, { VertexElementFormat.Short2, (uint) Marshal.SizeOf<Short2>() },
{ VertexElementFormat.Short4, (uint) Marshal.SizeOf<Short4>() }, { VertexElementFormat.Short4, (uint) Marshal.SizeOf<Short4>() },
{ VertexElementFormat.UInt, (uint) Marshal.SizeOf<uint>() }, { VertexElementFormat.Uint, (uint) Marshal.SizeOf<uint>() },
{ VertexElementFormat.Vector2, (uint) Marshal.SizeOf<Math.Float.Vector2>() }, { VertexElementFormat.Vector2, (uint) Marshal.SizeOf<Math.Float.Vector2>() },
{ VertexElementFormat.Vector3, (uint) Marshal.SizeOf<Math.Float.Vector3>() }, { VertexElementFormat.Vector3, (uint) Marshal.SizeOf<Math.Float.Vector3>() },
{ VertexElementFormat.Vector4, (uint) Marshal.SizeOf<Math.Float.Vector4>() } { VertexElementFormat.Vector4, (uint) Marshal.SizeOf<Math.Float.Vector4>() }
@ -37,6 +37,16 @@ namespace MoonWorks
return b != 0; return b != 0;
} }
public static int BoolToInt(bool b)
{
return b ? 1 : 0;
}
public static bool IntToBool(int b)
{
return b != 0;
}
public static uint VertexElementFormatSize(VertexElementFormat format) public static uint VertexElementFormatSize(VertexElementFormat format)
{ {
return Sizes[format]; return Sizes[format];

View File

@ -48,15 +48,16 @@ namespace MoonWorks
/// </summary> /// </summary>
/// <param name="windowCreateInfo">The parameters that will be used to create the MainWindow.</param> /// <param name="windowCreateInfo">The parameters that will be used to create the MainWindow.</param>
/// <param name="frameLimiterSettings">The frame limiter settings.</param> /// <param name="frameLimiterSettings">The frame limiter settings.</param>
/// <param name="preferredBackends">Bitflags of which GPU backends to attempt to initialize.</param>
/// <param name="targetTimestep">How often Game.Update will run in terms of ticks per second.</param> /// <param name="targetTimestep">How often Game.Update will run in terms of ticks per second.</param>
/// <param name="debugMode">If true, enables extra debug checks. Should be turned off for release builds.</param> /// <param name="debugMode">If true, enables extra debug checks. Should be turned off for release builds.</param>
public Game( public Game(
WindowCreateInfo windowCreateInfo, WindowCreateInfo windowCreateInfo,
FrameLimiterSettings frameLimiterSettings, FrameLimiterSettings frameLimiterSettings,
BackendFlags preferredBackends,
int targetTimestep = 60, int targetTimestep = 60,
bool debugMode = false bool debugMode = false
) ) {
{
Logger.LogInfo("Initializing frame limiter..."); Logger.LogInfo("Initializing frame limiter...");
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep); Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
gameTimer = Stopwatch.StartNew(); gameTimer = Stopwatch.StartNew();
@ -75,21 +76,25 @@ namespace MoonWorks
return; return;
} }
Logger.Initialize();
Logger.LogInfo("Initializing input..."); Logger.LogInfo("Initializing input...");
Inputs = new Inputs(); Inputs = new Inputs();
Logger.LogInfo("Initializing graphics device..."); Logger.LogInfo("Initializing graphics device...");
GraphicsDevice = new GraphicsDevice( GraphicsDevice = new GraphicsDevice(
Backend.Vulkan, preferredBackends,
debugMode debugMode
); );
Logger.LogInfo("Initializing main window..."); SDL.SDL_WindowFlags windowFlags = 0;
MainWindow = new Window(windowCreateInfo, GraphicsDevice.WindowFlags | SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN); if ((preferredBackends & BackendFlags.Vulkan) != 0)
{
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN;
}
if (!GraphicsDevice.ClaimWindow(MainWindow, windowCreateInfo.PresentMode)) Logger.LogInfo("Initializing main window...");
MainWindow = new Window(windowCreateInfo, windowFlags | SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN);
if (!GraphicsDevice.ClaimWindow(MainWindow, windowCreateInfo.SwapchainComposition, windowCreateInfo.PresentMode))
{ {
throw new System.SystemException("Could not claim window!"); throw new System.SystemException("Could not claim window!");
} }

View File

@ -1,17 +0,0 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// A buffer-offset pair to be used when binding vertex buffers.
/// </summary>
public struct BufferBinding
{
public Buffer Buffer;
public ulong Offset;
public BufferBinding(Buffer buffer, ulong offset)
{
Buffer = buffer;
Offset = offset;
}
}
}

View File

@ -1,17 +0,0 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// A texture-sampler pair to be used when binding samplers.
/// </summary>
public struct TextureSamplerBinding
{
public Texture Texture;
public Sampler Sampler;
public TextureSamplerBinding(Texture texture, Sampler sampler)
{
Texture = texture;
Sampler = sampler;
}
}
}

File diff suppressed because it is too large Load Diff

212
src/Graphics/ComputePass.cs Normal file
View File

@ -0,0 +1,212 @@
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics;
public class ComputePass
{
public nint Handle { get; private set; }
internal void SetHandle(nint handle)
{
Handle = handle;
}
#if DEBUG
internal bool active;
ComputePipeline currentComputePipeline;
#endif
/// <summary>
/// Binds a compute pipeline so that compute work may be dispatched.
/// </summary>
/// <param name="computePipeline">The compute pipeline to bind.</param>
public void BindComputePipeline(
ComputePipeline computePipeline
) {
#if DEBUG
AssertComputePassActive();
// TODO: validate formats?
#endif
Refresh.Refresh_BindComputePipeline(
Handle,
computePipeline.Handle
);
#if DEBUG
currentComputePipeline = computePipeline;
#endif
}
/// <summary>
/// Binds a texture to be used in the compute shader.
/// This texture must have been created with the ComputeShaderRead usage flag.
/// </summary>
public unsafe void BindStorageTexture(
in TextureSlice textureSlice,
uint slot = 0
) {
#if DEBUG
AssertComputePassActive();
AssertComputePipelineBound();
AssertTextureNonNull(textureSlice.Texture);
AssertTextureHasComputeStorageReadFlag(textureSlice.Texture);
#endif
var refreshTextureSlice = textureSlice.ToRefresh();
Refresh.Refresh_BindComputeStorageTextures(
Handle,
slot,
&refreshTextureSlice,
1
);
}
/// <summary>
/// Binds a buffer to be used in the compute shader.
/// This buffer must have been created with the ComputeShaderRead usage flag.
/// </summary>
public unsafe void BindStorageBuffer(
GpuBuffer buffer,
uint slot = 0
) {
#if DEBUG
AssertComputePassActive();
AssertComputePipelineBound();
AssertBufferNonNull(buffer);
AssertBufferHasComputeStorageReadFlag(buffer);
#endif
var bufferHandle = buffer.Handle;
Refresh.Refresh_BindComputeStorageBuffers(
Handle,
slot,
&bufferHandle,
1
);
}
/// <summary>
/// Pushes compute shader uniform data.
/// Subsequent draw calls will use this uniform data.
/// </summary>
public unsafe void PushUniformData(
void* uniformsPtr,
uint size,
uint slot = 0
) {
#if DEBUG
AssertComputePassActive();
AssertComputePipelineBound();
if (slot >= currentComputePipeline.ResourceInfo.UniformBufferCount)
{
throw new System.ArgumentException($"Slot {slot} given, but {currentComputePipeline.ResourceInfo.UniformBufferCount} uniform buffers are used on the shader!");
}
#endif
Refresh.Refresh_PushComputeUniformData(
Handle,
slot,
(nint) uniformsPtr,
size
);
}
/// <summary>
/// Pushes compute shader uniform data.
/// Subsequent draw calls will use this uniform data.
/// </summary>
public unsafe void PushUniformData<T>(
in T uniforms,
uint slot = 0
) where T : unmanaged
{
fixed (T* uniformsPtr = &uniforms)
{
PushUniformData(uniformsPtr, (uint) Marshal.SizeOf<T>(), slot);
}
}
/// <summary>
/// Dispatches compute work.
/// </summary>
public void Dispatch(
uint groupCountX,
uint groupCountY,
uint groupCountZ
) {
#if DEBUG
AssertComputePassActive();
AssertComputePipelineBound();
if (groupCountX < 1 || groupCountY < 1 || groupCountZ < 1)
{
throw new System.ArgumentException("All dimensions for the compute work groups must be >= 1!");
}
#endif
Refresh.Refresh_DispatchCompute(
Handle,
groupCountX,
groupCountY,
groupCountZ
);
}
#if DEBUG
private void AssertComputePassActive(string message = "Render pass is not active!")
{
if (!active)
{
throw new System.InvalidOperationException(message);
}
}
private void AssertComputePipelineBound(string message = "No compute pipeline is bound!")
{
if (currentComputePipeline == null)
{
throw new System.InvalidOperationException(message);
}
}
private void AssertTextureNonNull(in TextureSlice textureSlice)
{
if (textureSlice.Texture == null || textureSlice.Texture.Handle == nint.Zero)
{
throw new System.NullReferenceException("Texture must not be null!");
}
}
private void AssertTextureHasComputeStorageReadFlag(Texture texture)
{
if ((texture.UsageFlags & TextureUsageFlags.ComputeStorageRead) == 0)
{
throw new System.ArgumentException("The bound Texture's UsageFlags must include TextureUsageFlags.ComputeStorageRead!");
}
}
private void AssertBufferNonNull(GpuBuffer buffer)
{
if (buffer == null || buffer.Handle == nint.Zero)
{
throw new System.NullReferenceException("Buffer must not be null!");
}
}
private void AssertBufferHasComputeStorageReadFlag(GpuBuffer buffer)
{
if ((buffer.UsageFlags & BufferUsageFlags.ComputeStorageRead) == 0)
{
throw new System.ArgumentException("The bound Buffer's UsageFlags must include BufferUsageFlag.ComputeStorageRead!");
}
}
#endif
}

View File

@ -0,0 +1,25 @@
using System.Collections.Concurrent;
namespace MoonWorks.Graphics;
internal class ComputePassPool
{
private ConcurrentQueue<ComputePass> ComputePasses = new ConcurrentQueue<ComputePass>();
public ComputePass Obtain()
{
if (ComputePasses.TryDequeue(out var computePass))
{
return computePass;
}
else
{
return new ComputePass();
}
}
public void Return(ComputePass computePass)
{
ComputePasses.Enqueue(computePass);
}
}

238
src/Graphics/CopyPass.cs Normal file
View File

@ -0,0 +1,238 @@
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics;
public class CopyPass
{
public nint Handle { get; private set; }
internal void SetHandle(nint handle)
{
Handle = handle;
}
/// <summary>
/// Uploads data from a TransferBuffer to a TextureSlice.
/// This copy occurs on the GPU timeline.
///
/// Overwriting the contents of the TransferBuffer before the command buffer
/// has finished execution will cause undefined behavior.
///
/// You MAY assume that the copy has finished for subsequent commands.
/// </summary>
/// <param name="cycle">If true, cycles the texture if the given slice is bound.</param>
public void UploadToTexture(
TransferBuffer transferBuffer,
in TextureRegion textureRegion,
in BufferImageCopy copyParams,
bool cycle
) {
#if DEBUG
AssertBufferBoundsCheck(transferBuffer.Size, copyParams.BufferOffset, textureRegion.Size);
#endif
Refresh.Refresh_UploadToTexture(
Handle,
transferBuffer.Handle,
textureRegion.ToRefresh(),
copyParams.ToRefresh(),
Conversions.BoolToInt(cycle)
);
}
/// <summary>
/// Uploads the contents of an entire buffer to a 2D texture with no mips.
/// </summary>
public void UploadToTexture(
TransferBuffer transferBuffer,
Texture texture,
bool cycle
) {
UploadToTexture(
transferBuffer,
new TextureRegion(texture),
new BufferImageCopy(0, 0, 0),
cycle
);
}
/// <summary>
/// Uploads data from a TransferBuffer to a GpuBuffer.
/// This copy occurs on the GPU timeline.
///
/// Overwriting the contents of the TransferBuffer before the command buffer
/// has finished execution will cause undefined behavior.
///
/// You MAY assume that the copy has finished for subsequent commands.
/// </summary>
/// <param name="cycle">If true, cycles the buffer if it is bound.</param>
public void UploadToBuffer(
TransferBuffer transferBuffer,
GpuBuffer buffer,
in BufferCopy copyParams,
bool cycle
) {
#if DEBUG
AssertBufferBoundsCheck(transferBuffer.Size, copyParams.SrcOffset, copyParams.Size);
AssertBufferBoundsCheck(buffer.Size, copyParams.DstOffset, copyParams.Size);
#endif
Refresh.Refresh_UploadToBuffer(
Handle,
transferBuffer.Handle,
buffer.Handle,
copyParams.ToRefresh(),
Conversions.BoolToInt(cycle)
);
}
/// <summary>
/// Copies the entire contents of a TransferBuffer to a GpuBuffer.
/// </summary>
public void UploadToBuffer(
TransferBuffer transferBuffer,
GpuBuffer buffer,
bool cycle
) {
UploadToBuffer(
transferBuffer,
buffer,
new BufferCopy(0, 0, transferBuffer.Size),
cycle
);
}
/// <summary>
/// Copies data element-wise into from a TransferBuffer to a GpuBuffer.
/// </summary>
public void UploadToBuffer<T>(
TransferBuffer transferBuffer,
GpuBuffer buffer,
uint sourceStartElement,
uint destinationStartElement,
uint numElements,
bool cycle
) where T : unmanaged
{
var elementSize = Marshal.SizeOf<T>();
var dataLengthInBytes = (uint) (elementSize * numElements);
var srcOffsetInBytes = (uint) (elementSize * sourceStartElement);
var dstOffsetInBytes = (uint) (elementSize * destinationStartElement);
UploadToBuffer(
transferBuffer,
buffer,
new BufferCopy(srcOffsetInBytes, dstOffsetInBytes, dataLengthInBytes),
cycle
);
}
/// <summary>
/// Copies the contents of a TextureRegion to another TextureRegion.
/// The regions must have the same dimensions.
/// This copy occurs on the GPU timeline.
///
/// You MAY assume that the copy has finished in subsequent commands.
/// </summary>
public void CopyTextureToTexture(
in TextureRegion source,
in TextureRegion destination,
bool cycle
) {
#if DEBUG
AssertTextureBoundsCheck(destination.Size, source.Size);
if (source.Width != destination.Width || source.Height != destination.Height || source.Depth != destination.Depth)
{
throw new System.InvalidOperationException("Texture copy must have the same dimensions!");
}
#endif
Refresh.Refresh_CopyTextureToTexture(
Handle,
source.ToRefresh(),
destination.ToRefresh(),
Conversions.BoolToInt(cycle)
);
}
/// <summary>
/// Copies data from a GpuBuffer to another GpuBuffer.
/// This copy occurs on the GPU timeline.
///
/// You MAY assume that the copy has finished in subsequent commands.
/// </summary>
public void CopyBufferToBuffer(
GpuBuffer source,
GpuBuffer destination,
in BufferCopy copyParams,
bool cycle
) {
#if DEBUG
AssertBufferBoundsCheck(source.Size, copyParams.SrcOffset, copyParams.Size);
AssertBufferBoundsCheck(destination.Size, copyParams.DstOffset, copyParams.Size);
#endif
Refresh.Refresh_CopyBufferToBuffer(
Handle,
source.Handle,
destination.Handle,
copyParams.ToRefresh(),
Conversions.BoolToInt(cycle)
);
}
public void DownloadFromBuffer(
GpuBuffer buffer,
TransferBuffer transferBuffer,
in BufferCopy copyParams
) {
#if DEBUG
AssertBufferBoundsCheck(buffer.Size, copyParams.SrcOffset, copyParams.Size);
AssertBufferBoundsCheck(transferBuffer.Size, copyParams.DstOffset, copyParams.Size);
#endif
Refresh.Refresh_DownloadFromBuffer(
Handle,
buffer.Handle,
transferBuffer.Handle,
copyParams.ToRefresh()
);
}
public void DownloadFromTexture(
in TextureRegion textureRegion,
TransferBuffer transferBuffer,
in BufferImageCopy copyParams
) {
#if DEBUG
AssertBufferBoundsCheck(transferBuffer.Size, copyParams.BufferOffset, textureRegion.Size);
#endif
Refresh.Refresh_DownloadFromTexture(
Handle,
textureRegion.ToRefresh(),
transferBuffer.Handle,
copyParams.ToRefresh()
);
}
#if DEBUG
private void AssertBufferBoundsCheck(uint bufferLengthInBytes, uint offsetInBytes, uint copyLengthInBytes)
{
if (copyLengthInBytes > bufferLengthInBytes + offsetInBytes)
{
throw new System.InvalidOperationException($"SetBufferData overflow! buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}");
}
}
private void AssertTextureBoundsCheck(uint textureSizeInBytes, uint dataLengthInBytes)
{
if (dataLengthInBytes > textureSizeInBytes)
{
throw new System.InvalidOperationException($"SetTextureData overflow! texture size {textureSizeInBytes}, data size {dataLengthInBytes}");
}
}
#endif
}

View File

@ -0,0 +1,25 @@
using System.Collections.Concurrent;
namespace MoonWorks.Graphics;
internal class CopyPassPool
{
private ConcurrentQueue<CopyPass> CopyPasses = new ConcurrentQueue<CopyPass>();
public CopyPass Obtain()
{
if (CopyPasses.TryDequeue(out var copyPass))
{
return copyPass;
}
else
{
return new CopyPass();
}
}
public void Return(CopyPass copyPass)
{
CopyPasses.Enqueue(copyPass);
}
}

View File

@ -47,7 +47,13 @@ namespace MoonWorks.Graphics.Font
out float distanceRange out float distanceRange
); );
var texture = Texture.FromImageFile(graphicsDevice, commandBuffer, Path.ChangeExtension(fontPath, ".png")); var imagePath = Path.ChangeExtension(fontPath, ".png");
ImageUtils.ImageInfoFromFile(imagePath, out var width, out var height, out var sizeInBytes);
var uploader = new ResourceUploader(graphicsDevice);
var texture = uploader.CreateTexture2DFromCompressed(imagePath);
uploader.Upload();
uploader.Dispose();
NativeMemory.Free(fontFileByteBuffer); NativeMemory.Free(fontFileByteBuffer);
NativeMemory.Free(atlasFileByteBuffer); NativeMemory.Free(atlasFileByteBuffer);

View File

@ -13,10 +13,12 @@ namespace MoonWorks.Graphics.Font
private GraphicsDevice GraphicsDevice { get; } private GraphicsDevice GraphicsDevice { get; }
public IntPtr Handle { get; } public IntPtr Handle { get; }
public Buffer VertexBuffer { get; protected set; } = null; public GpuBuffer VertexBuffer { get; protected set; } = null;
public Buffer IndexBuffer { get; protected set; } = null; public GpuBuffer IndexBuffer { get; protected set; } = null;
public uint PrimitiveCount { get; protected set; } public uint PrimitiveCount { get; protected set; }
private TransferBuffer TransferBuffer;
public Font CurrentFont { get; private set; } public Font CurrentFont { get; private set; }
private byte* StringBytes; private byte* StringBytes;
@ -30,8 +32,10 @@ namespace MoonWorks.Graphics.Font
StringBytesLength = 128; StringBytesLength = 128;
StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength); StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength);
VertexBuffer = Buffer.Create<Vertex>(GraphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT); VertexBuffer = GpuBuffer.Create<Vertex>(GraphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT);
IndexBuffer = Buffer.Create<uint>(GraphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT); IndexBuffer = GpuBuffer.Create<uint>(GraphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT);
TransferBuffer = TransferBuffer.Create<byte>(GraphicsDevice, TransferUsage.Buffer, TransferBufferMapFlags.Write, VertexBuffer.Size + IndexBuffer.Size);
} }
// Call this to initialize or reset the batch. // Call this to initialize or reset the batch.
@ -93,42 +97,60 @@ namespace MoonWorks.Graphics.Font
out uint indexDataLengthInBytes out uint indexDataLengthInBytes
); );
var vertexSpan = new Span<byte>((void*) vertexDataPointer, (int) vertexDataLengthInBytes);
var indexSpan = new Span<byte>((void*) indexDataPointer, (int) indexDataLengthInBytes);
var newTransferBufferNeeded = false;
if (VertexBuffer.Size < vertexDataLengthInBytes) if (VertexBuffer.Size < vertexDataLengthInBytes)
{ {
VertexBuffer.Dispose(); VertexBuffer.Dispose();
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes); VertexBuffer = new GpuBuffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
newTransferBufferNeeded = true;
} }
if (IndexBuffer.Size < indexDataLengthInBytes) if (IndexBuffer.Size < indexDataLengthInBytes)
{ {
IndexBuffer.Dispose(); IndexBuffer.Dispose();
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes); IndexBuffer = new GpuBuffer(GraphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes);
newTransferBufferNeeded = true;
}
if (newTransferBufferNeeded)
{
TransferBuffer.Dispose();
TransferBuffer = new TransferBuffer(GraphicsDevice, TransferUsage.Buffer, TransferBufferMapFlags.Write, VertexBuffer.Size + IndexBuffer.Size);
} }
if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0) if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
{ {
commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes); TransferBuffer.SetData(vertexSpan, true);
commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes); TransferBuffer.SetData(indexSpan, (uint) vertexSpan.Length, false);
var copyPass = commandBuffer.BeginCopyPass();
copyPass.UploadToBuffer(TransferBuffer, VertexBuffer, new BufferCopy(0, 0, (uint) vertexSpan.Length), true);
copyPass.UploadToBuffer(TransferBuffer, IndexBuffer, new BufferCopy((uint) vertexSpan.Length, 0, (uint) indexSpan.Length), true);
commandBuffer.EndCopyPass(copyPass);
} }
PrimitiveCount = vertexCount / 2; PrimitiveCount = vertexCount / 2;
} }
// Call this AFTER binding your text pipeline! // Call this AFTER binding your text pipeline!
public void Render(CommandBuffer commandBuffer, Math.Float.Matrix4x4 transformMatrix) public void Render(RenderPass renderPass, Math.Float.Matrix4x4 transformMatrix)
{ {
commandBuffer.BindFragmentSamplers(new TextureSamplerBinding( renderPass.BindFragmentSampler(new TextureSamplerBinding(
CurrentFont.Texture, CurrentFont.Texture,
GraphicsDevice.LinearSampler GraphicsDevice.LinearSampler
)); ));
commandBuffer.BindVertexBuffers(VertexBuffer); renderPass.BindVertexBuffer(VertexBuffer);
commandBuffer.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo); renderPass.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo);
commandBuffer.DrawIndexedPrimitives( renderPass.PushVertexUniformData(transformMatrix);
renderPass.PushFragmentUniformData(CurrentFont.DistanceRange);
renderPass.DrawIndexedPrimitives(
0, 0,
0, 0,
PrimitiveCount, PrimitiveCount
commandBuffer.PushVertexShaderUniforms(transformMatrix),
commandBuffer.PushFragmentShaderUniforms(CurrentFont.DistanceRange)
); );
} }

View File

@ -4,7 +4,6 @@ using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using MoonWorks.Video; using MoonWorks.Video;
using RefreshCS; using RefreshCS;
using WellspringCS;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics
{ {
@ -14,17 +13,17 @@ 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; }
private uint windowFlags;
public SDL2.SDL.SDL_WindowFlags WindowFlags => (SDL2.SDL.SDL_WindowFlags) windowFlags;
// Built-in video pipeline // Built-in video pipeline
internal GraphicsPipeline VideoPipeline { get; } internal GraphicsPipeline VideoPipeline { 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
@ -34,73 +33,137 @@ namespace MoonWorks.Graphics
public bool IsDisposed { get; private set; } public bool IsDisposed { get; private set; }
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>(); private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
private FencePool FencePool;
private CommandBufferPool CommandBufferPool; private CommandBufferPool CommandBufferPool;
private FencePool FencePool;
internal RenderPassPool RenderPassPool = new RenderPassPool();
internal ComputePassPool ComputePassPool = new ComputePassPool();
internal CopyPassPool CopyPassPool = new CopyPassPool();
internal GraphicsDevice( internal unsafe GraphicsDevice(
Backend preferredBackend, BackendFlags preferredBackends,
bool debugMode bool debugMode
) { ) {
Backend = (Backend) Refresh.Refresh_SelectBackend((Refresh.Backend) preferredBackend, out windowFlags); if (preferredBackends == BackendFlags.Invalid)
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 = Refresh.Refresh_CreateDevice(
(Refresh.BackendFlags) preferredBackends,
Conversions.BoolToByte(debugMode) Conversions.BoolToByte(debugMode)
); );
DebugMode = debugMode;
// TODO: check for CreateDevice fail // TODO: check for CreateDevice fail
Backend = (BackendFlags) Refresh.Refresh_GetBackend(Handle);
// Check for replacement stock shaders // Check for replacement stock shaders
string basePath = System.AppContext.BaseDirectory; string basePath = System.AppContext.BaseDirectory;
string videoVertPath = Path.Combine(basePath, "video_fullscreen.vert.refresh"); string fullscreenVertPath = Path.Combine(basePath, "fullscreen.vert.refresh");
string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh");
string textVertPath = Path.Combine(basePath, "text_transform.vert.refresh"); string textVertPath = Path.Combine(basePath, "text_transform.vert.refresh");
string textFragPath = Path.Combine(basePath, "text_msdf.frag.refresh"); string textFragPath = Path.Combine(basePath, "text_msdf.frag.refresh");
ShaderModule videoVertShader; string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh");
ShaderModule videoFragShader;
ShaderModule textVertShader; Shader fullscreenVertShader;
ShaderModule textFragShader;
if (File.Exists(videoVertPath) && File.Exists(videoFragPath)) Shader textVertShader;
Shader textFragShader;
Shader videoFragShader;
if (File.Exists(fullscreenVertPath))
{ {
videoVertShader = new ShaderModule(this, videoVertPath); fullscreenVertShader = new Shader(
videoFragShader = new ShaderModule(this, videoFragPath); 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.spv");
fullscreenVertShader = new Shader(
this,
vertStream,
"main",
ShaderStage.Vertex,
ShaderFormat.SPIRV
);
}
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh"); if (File.Exists(videoFragPath))
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh"); {
videoFragShader = new Shader(
videoVertShader = new ShaderModule(this, vertStream); this,
videoFragShader = new ShaderModule(this, fragStream); videoFragPath,
"main",
ShaderStage.Fragment,
ShaderFormat.SECRET
);
}
else
{
// use defaults
var assembly = typeof(GraphicsDevice).Assembly;
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.spv");
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
{ {
// use defaults // use defaults
var assembly = typeof(GraphicsDevice).Assembly; var assembly = typeof(GraphicsDevice).Assembly;
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh"); using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextTransform.vert.spv");
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh"); using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextMSDF.frag.spv");
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
);
} }
VideoPipeline = new GraphicsPipeline( VideoPipeline = new GraphicsPipeline(
@ -114,16 +177,12 @@ namespace MoonWorks.Graphics
) )
), ),
DepthStencilState = DepthStencilState.Disable, DepthStencilState = DepthStencilState.Disable,
VertexShaderInfo = GraphicsShaderInfo.Create( VertexShader = fullscreenVertShader,
videoVertShader, FragmentShader = videoFragShader,
"main", FragmentShaderResourceInfo = new GraphicsPipelineResourceInfo
0 {
), SamplerCount = 3
FragmentShaderInfo = 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,
@ -131,8 +190,15 @@ namespace MoonWorks.Graphics
} }
); );
TextVertexShaderInfo = GraphicsShaderInfo.Create<Math.Float.Matrix4x4>(textVertShader, "main", 0); TextVertexShader = textVertShader;
TextFragmentShaderInfo = GraphicsShaderInfo.Create<float>(textFragShader, "main", 1); TextVertexShaderInfo = new GraphicsPipelineResourceInfo();
TextFragmentShader = textFragShader;
TextFragmentShaderInfo = new GraphicsPipelineResourceInfo
{
SamplerCount = 1
};
TextVertexInputState = VertexInputState.CreateSingleBinding<Font.Vertex>(); TextVertexInputState = VertexInputState.CreateSingleBinding<Font.Vertex>();
PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp); PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp);
@ -145,20 +211,25 @@ 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( Refresh.Refresh_ClaimWindow(
Handle, Handle,
window.Handle, window.Handle,
(Refresh.SwapchainComposition) swapchainComposition,
(Refresh.PresentMode) presentMode (Refresh.PresentMode) presentMode
) )
); );
@ -166,7 +237,9 @@ namespace MoonWorks.Graphics
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);
@ -201,17 +274,21 @@ 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( Refresh.Refresh_SetSwapchainParameters(
Handle, Handle,
window.Handle, window.Handle,
(Refresh.SwapchainComposition) swapchainComposition,
(Refresh.PresentMode) presentMode (Refresh.PresentMode) presentMode
); );
} }
@ -244,7 +321,6 @@ namespace MoonWorks.Graphics
#endif #endif
Refresh.Refresh_Submit( Refresh.Refresh_Submit(
Handle,
commandBuffer.Handle commandBuffer.Handle
); );
@ -262,7 +338,6 @@ namespace MoonWorks.Graphics
public Fence SubmitAndAcquireFence(CommandBuffer commandBuffer) public Fence SubmitAndAcquireFence(CommandBuffer commandBuffer)
{ {
var fenceHandle = Refresh.Refresh_SubmitAndAcquireFence( var fenceHandle = Refresh.Refresh_SubmitAndAcquireFence(
Handle,
commandBuffer.Handle commandBuffer.Handle
); );
@ -283,16 +358,15 @@ namespace MoonWorks.Graphics
/// <summary> /// <summary>
/// Waits for the given fence to become signaled. /// Waits for the given fence to become signaled.
/// </summary> /// </summary>
public unsafe void WaitForFences(Fence fence) public unsafe void WaitForFence(Fence fence)
{ {
var handlePtr = stackalloc nint[1]; var fenceHandle = fence.Handle;
handlePtr[0] = fence.Handle;
Refresh.Refresh_WaitForFences( Refresh.Refresh_WaitForFences(
Handle, Handle,
1, 1,
1, &fenceHandle,
(nint) handlePtr 1
); );
} }
@ -300,76 +374,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( public unsafe void WaitForFences(Span<Fence> fences, bool waitAll)
Fence fenceOne,
Fence fenceTwo,
bool waitAll
) {
var handlePtr = stackalloc nint[2];
handlePtr[0] = fenceOne.Handle;
handlePtr[1] = fenceTwo.Handle;
Refresh.Refresh_WaitForFences(
Handle,
Conversions.BoolToByte(waitAll),
2,
(nint) handlePtr
);
}
/// <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
) {
var handlePtr = stackalloc nint[3];
handlePtr[0] = fenceOne.Handle;
handlePtr[1] = fenceTwo.Handle;
handlePtr[2] = fenceThree.Handle;
Refresh.Refresh_WaitForFences(
Handle,
Conversions.BoolToByte(waitAll),
3,
(nint) handlePtr
);
}
/// <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
) {
var handlePtr = stackalloc nint[4];
handlePtr[0] = fenceOne.Handle;
handlePtr[1] = fenceTwo.Handle;
handlePtr[2] = fenceThree.Handle;
handlePtr[3] = fenceFour.Handle;
Refresh.Refresh_WaitForFences(
Handle,
Conversions.BoolToByte(waitAll),
4,
(nint) handlePtr
);
}
/// <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)
{ {
var handlePtr = stackalloc nint[fences.Length]; var handlePtr = stackalloc nint[fences.Length];
@ -380,9 +385,9 @@ namespace MoonWorks.Graphics
Refresh.Refresh_WaitForFences( Refresh.Refresh_WaitForFences(
Handle, Handle,
Conversions.BoolToByte(waitAll), Conversions.BoolToInt(waitAll),
4, handlePtr,
(nint) handlePtr (uint) fences.Length
); );
} }
@ -414,7 +419,12 @@ namespace MoonWorks.Graphics
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) Refresh.Refresh_GetSwapchainTextureFormat(Handle, window.Handle);
} }
internal void AddResourceReference(GCHandle resourceReference) internal void AddResourceReference(GCHandle resourceReference)

456
src/Graphics/ImageUtils.cs Normal file
View File

@ -0,0 +1,456 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics
{
public static class ImageUtils
{
/// <summary>
/// Gets pointer to pixel data from compressed image byte data.
///
/// The returned pointer must be freed by calling FreePixelData.
/// </summary>
public static unsafe byte* GetPixelDataFromBytes(
Span<byte> data,
out uint width,
out uint height,
out uint sizeInBytes
) {
fixed (byte* ptr = data)
{
var pixelData =
Refresh.Refresh_Image_Load(
ptr,
data.Length,
out var w,
out var h,
out var len
);
width = (uint) w;
height = (uint) h;
sizeInBytes = (uint) len;
return pixelData;
}
}
/// <summary>
/// Gets pointer to pixel data from a compressed image stream.
///
/// The returned pointer must be freed by calling FreePixelData.
/// </summary>
public static unsafe byte* GetPixelDataFromStream(
Stream stream,
out uint width,
out uint height,
out uint sizeInBytes
) {
var length = stream.Length;
var buffer = NativeMemory.Alloc((nuint) length);
var span = new Span<byte>(buffer, (int) length);
stream.ReadExactly(span);
var pixelData = GetPixelDataFromBytes(span, out width, out height, out sizeInBytes);
NativeMemory.Free(buffer);
return pixelData;
}
/// <summary>
/// Gets pointer to pixel data from a compressed image file.
///
/// The returned pointer must be freed by calling FreePixelData.
/// </summary>
public static unsafe byte* GetPixelDataFromFile(
string path,
out uint width,
out uint height,
out uint sizeInBytes
) {
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
return GetPixelDataFromStream(fileStream, out width, out height, out sizeInBytes);
}
/// <summary>
/// Get metadata from compressed image bytes.
/// </summary>
public static unsafe bool ImageInfoFromBytes(
Span<byte> data,
out uint width,
out uint height,
out uint sizeInBytes
) {
fixed (byte* ptr = data)
{
var result =
Refresh.Refresh_Image_Info(
ptr,
data.Length,
out var w,
out var h,
out var len
);
width = (uint) w;
height = (uint) h;
sizeInBytes = (uint) len;
return Conversions.IntToBool(result);
}
}
/// <summary>
/// Get metadata from a compressed image stream.
/// </summary>
public static unsafe bool ImageInfoFromStream(
Stream stream,
out uint width,
out uint height,
out uint sizeInBytes
) {
var length = stream.Length;
var buffer = NativeMemory.Alloc((nuint) length);
var span = new Span<byte>(buffer, (int) length);
stream.ReadExactly(span);
var result = ImageInfoFromBytes(span, out width, out height, out sizeInBytes);
NativeMemory.Free(buffer);
return result;
}
/// <summary>
/// Get metadata from a compressed image file.
/// </summary>
public static bool ImageInfoFromFile(
string path,
out uint width,
out uint height,
out uint sizeInBytes
) {
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
return ImageInfoFromStream(fileStream, out width, out height, out sizeInBytes);
}
/// <summary>
/// Frees pixel data obtained from GetPixelData methods.
/// </summary>
public unsafe static void FreePixelData(byte* pixels)
{
Refresh.Refresh_Image_Free(pixels);
}
/// <summary>
/// Saves pixel data contained in a TransferBuffer to a PNG file.
/// </summary>
public static unsafe void SavePNG(
string path,
TransferBuffer transferBuffer,
uint bufferOffsetInBytes,
int width,
int height,
bool bgra
) {
var sizeInBytes = width * height * 4;
var pixelsPtr = (byte*) NativeMemory.Alloc((nuint) sizeInBytes);
var pixelsSpan = new Span<byte>(pixelsPtr, sizeInBytes);
transferBuffer.GetData(pixelsSpan, bufferOffsetInBytes);
if (bgra)
{
// if data is bgra, we have to swap the R and B channels
var rgbaPtr = (byte*) NativeMemory.Alloc((nuint) sizeInBytes);
var rgbaSpan = new Span<byte>(rgbaPtr, sizeInBytes);
for (var i = 0; i < sizeInBytes; i += 4)
{
rgbaSpan[i] = pixelsSpan[i + 2];
rgbaSpan[i + 1] = pixelsSpan[i + 1];
rgbaSpan[i + 2] = pixelsSpan[i];
rgbaSpan[i + 3] = pixelsSpan[i + 3];
}
NativeMemory.Free(pixelsPtr);
pixelsPtr = rgbaPtr;
}
Refresh.Refresh_Image_SavePNG(path, pixelsPtr, width, height);
NativeMemory.Free(pixelsPtr);
}
// DDS loading extension, based on MojoDDS
// Taken from https://github.com/FNA-XNA/FNA/blob/1e49f868f595f62bc6385db45949a03186a7cd7f/src/Graphics/Texture.cs#L194
public static void ParseDDS(
BinaryReader reader,
out TextureFormat format,
out int width,
out int height,
out int levels,
out bool isCube
) {
// A whole bunch of magic numbers, yay DDS!
const uint DDS_MAGIC = 0x20534444;
const uint DDS_HEADERSIZE = 124;
const uint DDS_PIXFMTSIZE = 32;
const uint DDSD_HEIGHT = 0x2;
const uint DDSD_WIDTH = 0x4;
const uint DDSD_PITCH = 0x8;
const uint DDSD_LINEARSIZE = 0x80000;
const uint DDSD_REQ = (
DDSD_HEIGHT | DDSD_WIDTH
);
const uint DDSCAPS_MIPMAP = 0x400000;
const uint DDSCAPS_TEXTURE = 0x1000;
const uint DDSCAPS2_CUBEMAP = 0x200;
const uint DDPF_FOURCC = 0x4;
const uint DDPF_RGB = 0x40;
const uint FOURCC_DXT1 = 0x31545844;
const uint FOURCC_DXT3 = 0x33545844;
const uint FOURCC_DXT5 = 0x35545844;
const uint FOURCC_DX10 = 0x30315844;
const uint pitchAndLinear = (
DDSD_PITCH | DDSD_LINEARSIZE
);
// File should start with 'DDS '
if (reader.ReadUInt32() != DDS_MAGIC)
{
throw new NotSupportedException("Not a DDS!");
}
// Texture info
uint size = reader.ReadUInt32();
if (size != DDS_HEADERSIZE)
{
throw new NotSupportedException("Invalid DDS header!");
}
uint flags = reader.ReadUInt32();
if ((flags & DDSD_REQ) != DDSD_REQ)
{
throw new NotSupportedException("Invalid DDS flags!");
}
if ((flags & pitchAndLinear) == pitchAndLinear)
{
throw new NotSupportedException("Invalid DDS flags!");
}
height = reader.ReadInt32();
width = reader.ReadInt32();
reader.ReadUInt32(); // dwPitchOrLinearSize, unused
reader.ReadUInt32(); // dwDepth, unused
levels = reader.ReadInt32();
// "Reserved"
reader.ReadBytes(4 * 11);
// Format info
uint formatSize = reader.ReadUInt32();
if (formatSize != DDS_PIXFMTSIZE)
{
throw new NotSupportedException("Bogus PIXFMTSIZE!");
}
uint formatFlags = reader.ReadUInt32();
uint formatFourCC = reader.ReadUInt32();
uint formatRGBBitCount = reader.ReadUInt32();
uint formatRBitMask = reader.ReadUInt32();
uint formatGBitMask = reader.ReadUInt32();
uint formatBBitMask = reader.ReadUInt32();
uint formatABitMask = reader.ReadUInt32();
// dwCaps "stuff"
uint caps = reader.ReadUInt32();
if ((caps & DDSCAPS_TEXTURE) == 0)
{
throw new NotSupportedException("Not a texture!");
}
isCube = false;
uint caps2 = reader.ReadUInt32();
if (caps2 != 0)
{
if ((caps2 & DDSCAPS2_CUBEMAP) == DDSCAPS2_CUBEMAP)
{
isCube = true;
}
else
{
throw new NotSupportedException("Invalid caps2!");
}
}
reader.ReadUInt32(); // dwCaps3, unused
reader.ReadUInt32(); // dwCaps4, unused
// "Reserved"
reader.ReadUInt32();
// Mipmap sanity check
if ((caps & DDSCAPS_MIPMAP) != DDSCAPS_MIPMAP)
{
levels = 1;
}
// Determine texture format
if ((formatFlags & DDPF_FOURCC) == DDPF_FOURCC)
{
switch (formatFourCC)
{
case 0x71: // D3DFMT_A16B16G16R16F
format = TextureFormat.R16G16B16A16_SFLOAT;
break;
case 0x74: // D3DFMT_A32B32G32R32F
format = TextureFormat.R32G32B32A32_SFLOAT;
break;
case FOURCC_DXT1:
format = TextureFormat.BC1;
break;
case FOURCC_DXT3:
format = TextureFormat.BC2;
break;
case FOURCC_DXT5:
format = TextureFormat.BC3;
break;
case FOURCC_DX10:
// If the fourCC is DX10, there is an extra header with additional format information.
uint dxgiFormat = reader.ReadUInt32();
// These values are taken from the DXGI_FORMAT enum.
switch (dxgiFormat)
{
case 2:
format = TextureFormat.R32G32B32A32_SFLOAT;
break;
case 10:
format = TextureFormat.R16G16B16A16_SFLOAT;
break;
case 71:
format = TextureFormat.BC1;
break;
case 74:
format = TextureFormat.BC2;
break;
case 77:
format = TextureFormat.BC3;
break;
case 98:
format = TextureFormat.BC7;
break;
default:
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
uint resourceDimension = reader.ReadUInt32();
// These values are taken from the D3D10_RESOURCE_DIMENSION enum.
switch (resourceDimension)
{
case 0: // Unknown
case 1: // Buffer
throw new NotSupportedException(
"Unsupported DDS texture format"
);
default:
break;
}
/*
* This flag seemingly only indicates if the texture is a cube map.
* This is already determined above. Cool!
*/
uint miscFlag = reader.ReadUInt32();
/*
* Indicates the number of elements in the texture array.
* We don't support texture arrays so just throw if it's greater than 1.
*/
uint arraySize = reader.ReadUInt32();
if (arraySize > 1)
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
reader.ReadUInt32(); // reserved
break;
default:
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
}
else if ((formatFlags & DDPF_RGB) == DDPF_RGB)
{
if ( formatRGBBitCount != 32 ||
formatRBitMask != 0x00FF0000 ||
formatGBitMask != 0x0000FF00 ||
formatBBitMask != 0x000000FF ||
formatABitMask != 0xFF000000 )
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
format = TextureFormat.B8G8R8A8;
}
else
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
}
public static int CalculateDDSLevelSize(
int width,
int height,
TextureFormat format
) {
if (format == TextureFormat.R8G8B8A8)
{
return (((width * 32) + 7) / 8) * height;
}
else if (format == TextureFormat.R16G16B16A16_SFLOAT)
{
return (((width * 64) + 7) / 8) * height;
}
else if (format == TextureFormat.R32G32B32A32_SFLOAT)
{
return (((width * 128) + 7) / 8) * height;
}
else
{
int blockSize = 16;
if (format == TextureFormat.BC1)
{
blockSize = 8;
}
width = System.Math.Max(width, 1);
height = System.Math.Max(height, 1);
return (
((width + 3) / 4) *
((height + 3) / 4) *
blockSize
);
}
}
}
}

View File

@ -1,307 +0,0 @@
using System;
namespace MoonWorks
{
/// <summary>
/// Presentation mode for a window.
/// </summary>
public enum PresentMode
{
/// <summary>
/// Does not wait for v-blank to update the window. Can cause visible tearing.
/// </summary>
Immediate,
/// <summary>
/// Waits for v-blank and uses a queue to hold present requests.
/// Allows for low latency while preventing tearing.
/// May not be supported on non-Vulkan non-Linux systems or older hardware.
/// </summary>
Mailbox,
/// <summary>
/// Waits for v-blank and adds present requests to a queue.
/// Will probably cause latency.
/// Required to be supported by all compliant hardware.
/// </summary>
FIFO,
/// <summary>
/// Usually waits for v-blank, but if v-blank has passed since last update will update immediately.
/// May cause visible tearing.
/// </summary>
FIFORelaxed
}
}
/* Recreate all the enums in here so we don't need to explicitly
* reference the RefreshCS namespace when using MoonWorks.Graphics
*/
namespace MoonWorks.Graphics
{
public enum PrimitiveType
{
PointList,
LineList,
LineStrip,
TriangleList,
TriangleStrip
}
/// <summary>
/// Describes the operation that a render pass will use when loading a render target.
/// </summary>
public enum LoadOp
{
Load,
Clear,
DontCare
}
/// <summary>
/// Describes the operation that a render pass will use when storing a render target.
/// </summary>
public enum StoreOp
{
Store,
DontCare
}
[Flags]
public enum ClearOptionsFlags : uint
{
Color = 1,
Depth = 2,
Stencil = 4,
DepthStencil = Depth | Stencil,
All = Color | Depth | Stencil
}
public enum IndexElementSize
{
Sixteen,
ThirtyTwo
}
public enum TextureFormat
{
R8G8B8A8,
B8G8R8A8,
R5G6B5,
A1R5G5B5,
B4G4R4A4,
A2R10G10B10,
R16G16,
R16G16B16A16,
R8,
BC1,
BC2,
BC3,
BC7,
R8G8_SNORM,
R8G8B8A8_SNORM,
R16_SFLOAT,
R16G16_SFLOAT,
R16G16B16A16_SFLOAT,
R32_SFLOAT,
R32G32_SFLOAT,
R32G32B32A32_SFLOAT,
R8_UINT,
R8G8_UINT,
R8G8B8A8_UINT,
R16_UINT,
R16G16_UINT,
R16G16B16A16_UINT,
D16,
D32,
D16S8,
D32S8
}
[Flags]
public enum TextureUsageFlags : uint
{
Sampler = 1,
ColorTarget = 2,
DepthStencilTarget = 4,
Compute = 8
}
public enum SampleCount
{
One,
Two,
Four,
Eight
}
public enum CubeMapFace : uint
{
PositiveX,
NegativeX,
PositiveY,
NegativeY,
PositiveZ,
NegativeZ
}
[Flags]
public enum BufferUsageFlags : uint
{
Vertex = 1,
Index = 2,
Compute = 4,
Indirect = 8
}
public enum VertexElementFormat
{
UInt,
Float,
Vector2,
Vector3,
Vector4,
Color,
Byte4,
Short2,
Short4,
NormalizedShort2,
NormalizedShort4,
HalfVector2,
HalfVector4
}
public enum VertexInputRate
{
Vertex,
Instance
}
public enum FillMode
{
Fill,
Line
}
public enum CullMode
{
None,
Front,
Back
}
public enum FrontFace
{
CounterClockwise,
Clockwise
}
public enum CompareOp
{
Never,
Less,
Equal,
LessOrEqual,
Greater,
NotEqual,
GreaterOrEqual,
Always
}
public enum StencilOp
{
Keep,
Zero,
Replace,
IncrementAndClamp,
DecrementAndClamp,
Invert,
IncrementAndWrap,
DecrementAndWrap
}
public enum BlendOp
{
Add,
Subtract,
ReverseSubtract,
Min,
Max
}
public enum BlendFactor
{
Zero,
One,
SourceColor,
OneMinusSourceColor,
DestinationColor,
OneMinusDestinationColor,
SourceAlpha,
OneMinusSourceAlpha,
DestinationAlpha,
OneMinusDestinationAlpha,
ConstantColor,
OneMinusConstantColor,
SourceAlphaSaturate
}
[Flags]
public enum ColorComponentFlags : uint
{
R = 1,
G = 2,
B = 4,
A = 8,
RG = R | G,
RB = R | B,
RA = R | A,
GB = G | B,
GA = G | A,
BA = B | A,
RGB = R | G | B,
RGA = R | G | A,
GBA = G | B | A,
RGBA = R | G | B | A,
None = 0
}
public enum Filter
{
Nearest,
Linear
}
public enum SamplerMipmapMode
{
Nearest,
Linear
}
public enum SamplerAddressMode
{
Repeat,
MirroredRepeat,
ClampToEdge,
ClampToBorder
}
public enum BorderColor
{
FloatTransparentBlack,
IntTransparentBlack,
FloatOpaqueBlack,
IntOpaqueBlack,
FloatOpaqueWhite,
IntOpaqueWhite
}
public enum Backend
{
DontCare,
Vulkan,
PS5,
Invalid
}
}

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
namespace MoonWorks.Graphics; namespace MoonWorks.Graphics;
@ -9,7 +8,7 @@ public abstract class RefreshResource : GraphicsResource
public IntPtr Handle { get => handle; internal set => handle = value; } public IntPtr Handle { get => handle; internal set => handle = value; }
private IntPtr handle; private IntPtr handle;
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; } protected abstract Action<IntPtr, IntPtr> ReleaseFunction { get; }
protected RefreshResource(GraphicsDevice device) : base(device) protected RefreshResource(GraphicsDevice device) : base(device)
{ {
@ -19,11 +18,11 @@ public abstract class RefreshResource : GraphicsResource
{ {
if (!IsDisposed) if (!IsDisposed)
{ {
// Atomically call destroy function in case this is called from the finalizer thread // Atomically call release function in case this is called from the finalizer thread
var toDispose = Interlocked.Exchange(ref handle, IntPtr.Zero); var toDispose = Interlocked.Exchange(ref handle, IntPtr.Zero);
if (toDispose != IntPtr.Zero) if (toDispose != IntPtr.Zero)
{ {
QueueDestroyFunction(Device.Handle, toDispose); ReleaseFunction(Device.Handle, toDispose);
} }
} }
base.Dispose(disposing); base.Dispose(disposing);

View File

@ -1,357 +0,0 @@
using RefreshCS;
using System.Runtime.InteropServices;
/* Recreate some structs in here so we don't need to explicitly
* reference the RefreshCS namespace when using MoonWorks.Graphics
*/
namespace MoonWorks.Graphics
{
[StructLayout(LayoutKind.Sequential)]
public struct DepthStencilValue
{
public float Depth;
public uint Stencil;
public DepthStencilValue(float depth, uint stencil)
{
Depth = depth;
Stencil = stencil;
}
// FIXME: can we do an unsafe cast somehow?
public Refresh.DepthStencilValue ToRefresh()
{
return new Refresh.DepthStencilValue
{
depth = Depth,
stencil = Stencil
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int X;
public int Y;
public int W;
public int H;
public Rect(int x, int y, int w, int h)
{
X = x;
Y = y;
W = w;
H = h;
}
public Rect(int w, int h)
{
X = 0;
Y = 0;
W = w;
H = h;
}
// FIXME: can we do an unsafe cast somehow?
public Refresh.Rect ToRefresh()
{
return new Refresh.Rect
{
x = X,
y = Y,
w = W,
h = H
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Viewport
{
public float X;
public float Y;
public float W;
public float H;
public float MinDepth;
public float MaxDepth;
public Viewport(float w, float h)
{
X = 0;
Y = 0;
W = w;
H = h;
MinDepth = 0;
MaxDepth = 1;
}
public Viewport(float x, float y, float w, float h)
{
X = x;
Y = y;
W = w;
H = h;
MinDepth = 0;
MaxDepth = 1;
}
public Viewport(float x, float y, float w, float h, float minDepth, float maxDepth)
{
X = x;
Y = y;
W = w;
H = h;
MinDepth = minDepth;
MaxDepth = maxDepth;
}
public Refresh.Viewport ToRefresh()
{
return new Refresh.Viewport
{
x = X,
y = Y,
w = W,
h = H,
minDepth = MinDepth,
maxDepth = MaxDepth
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct VertexBinding
{
public uint Binding;
public uint Stride;
public VertexInputRate InputRate;
public static VertexBinding Create<T>(uint binding = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged
{
return new VertexBinding
{
Binding = binding,
InputRate = inputRate,
Stride = (uint) Marshal.SizeOf<T>()
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct VertexAttribute
{
public uint Location;
public uint Binding;
public VertexElementFormat Format;
public uint Offset;
}
[StructLayout(LayoutKind.Sequential)]
public struct StencilOpState
{
public StencilOp FailOp;
public StencilOp PassOp;
public StencilOp DepthFailOp;
public CompareOp CompareOp;
public uint CompareMask;
public uint WriteMask;
public uint Reference;
// FIXME: can we do an explicit cast here?
public Refresh.StencilOpState ToRefresh()
{
return new Refresh.StencilOpState
{
failOp = (Refresh.StencilOp) FailOp,
passOp = (Refresh.StencilOp) PassOp,
depthFailOp = (Refresh.StencilOp) DepthFailOp,
compareOp = (Refresh.CompareOp) CompareOp,
compareMask = CompareMask,
writeMask = WriteMask,
reference = Reference
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct ColorAttachmentInfo
{
public Texture Texture;
public uint Depth;
public uint Layer;
public uint Level;
public Color ClearColor;
public LoadOp LoadOp;
public StoreOp StoreOp;
public ColorAttachmentInfo(
Texture texture,
Color clearColor,
StoreOp storeOp = StoreOp.Store
) {
Texture = texture;
Depth = 0;
Layer = 0;
Level = 0;
ClearColor = clearColor;
LoadOp = LoadOp.Clear;
StoreOp = storeOp;
}
public ColorAttachmentInfo(
Texture texture,
LoadOp loadOp = LoadOp.DontCare,
StoreOp storeOp = StoreOp.Store
) {
Texture = texture;
Depth = 0;
Layer = 0;
Level = 0;
ClearColor = Color.White;
LoadOp = loadOp;
StoreOp = storeOp;
}
public Refresh.ColorAttachmentInfo ToRefresh()
{
return new Refresh.ColorAttachmentInfo
{
texture = Texture.Handle,
depth = Depth,
layer = Layer,
level = Level,
clearColor = new Refresh.Vec4
{
x = ClearColor.R / 255f,
y = ClearColor.G / 255f,
z = ClearColor.B / 255f,
w = ClearColor.A / 255f
},
loadOp = (Refresh.LoadOp) LoadOp,
storeOp = (Refresh.StoreOp) StoreOp
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct DepthStencilAttachmentInfo
{
public Texture Texture;
public uint Depth;
public uint Layer;
public uint Level;
public DepthStencilValue DepthStencilClearValue;
public LoadOp LoadOp;
public StoreOp StoreOp;
public LoadOp StencilLoadOp;
public StoreOp StencilStoreOp;
public DepthStencilAttachmentInfo(
Texture texture,
DepthStencilValue clearValue,
StoreOp depthStoreOp = StoreOp.Store,
StoreOp stencilStoreOp = StoreOp.Store
)
{
Texture = texture;
Depth = 0;
Layer = 0;
Level = 0;
DepthStencilClearValue = clearValue;
LoadOp = LoadOp.Clear;
StoreOp = depthStoreOp;
StencilLoadOp = LoadOp.Clear;
StencilStoreOp = stencilStoreOp;
}
public DepthStencilAttachmentInfo(
Texture texture,
LoadOp loadOp = LoadOp.DontCare,
StoreOp storeOp = StoreOp.Store,
LoadOp stencilLoadOp = LoadOp.DontCare,
StoreOp stencilStoreOp = StoreOp.Store
) {
Texture = texture;
Depth = 0;
Layer = 0;
Level = 0;
DepthStencilClearValue = new DepthStencilValue();
LoadOp = loadOp;
StoreOp = storeOp;
StencilLoadOp = stencilLoadOp;
StencilStoreOp = stencilStoreOp;
}
public DepthStencilAttachmentInfo(
Texture texture,
DepthStencilValue depthStencilValue,
LoadOp loadOp,
StoreOp storeOp,
LoadOp stencilLoadOp,
StoreOp stencilStoreOp
) {
Texture = texture;
Depth = 0;
Layer = 0;
Level = 0;
DepthStencilClearValue = depthStencilValue;
LoadOp = loadOp;
StoreOp = storeOp;
StencilLoadOp = stencilLoadOp;
StencilStoreOp = stencilStoreOp;
}
public Refresh.DepthStencilAttachmentInfo ToRefresh()
{
return new Refresh.DepthStencilAttachmentInfo
{
texture = Texture.Handle,
depth = Depth,
layer = Layer,
level = Level,
depthStencilClearValue = DepthStencilClearValue.ToRefresh(),
loadOp = (Refresh.LoadOp) LoadOp,
storeOp = (Refresh.StoreOp) StoreOp,
stencilLoadOp = (Refresh.LoadOp) StencilLoadOp,
stencilStoreOp = (Refresh.StoreOp) StencilStoreOp
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct ColorAttachmentDescription
{
public TextureFormat Format;
public ColorAttachmentBlendState BlendState;
public ColorAttachmentDescription(
TextureFormat format,
ColorAttachmentBlendState blendState
) {
Format = format;
BlendState = blendState;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct IndirectDrawCommand
{
public uint VertexCount;
public uint InstanceCount;
public uint FirstVertex;
public uint FirstInstance;
public IndirectDrawCommand(
uint vertexCount,
uint instanceCount,
uint firstVertex,
uint firstInstance
) {
VertexCount = vertexCount;
InstanceCount = instanceCount;
FirstVertex = firstVertex;
FirstInstance = firstInstance;
}
}
}

View File

@ -0,0 +1,941 @@
using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics;
// Recreate certain types in here so we can hide the Refresh namespace
public enum PrimitiveType
{
PointList,
LineList,
LineStrip,
TriangleList,
TriangleStrip
}
public enum LoadOp
{
Load,
Clear,
DontCare
}
public enum StoreOp
{
Store,
DontCare
}
public enum IndexElementSize
{
Sixteen,
ThirtyTwo
}
public enum TextureFormat
{
/* Unsigned Normalized Float Color Formats */
R8G8B8A8,
B8G8R8A8,
R5G6B5,
A1R5G5B5,
B4G4R4A4,
A2R10G10B10,
A2B10G10R10,
R16G16,
R16G16B16A16,
R8,
A8,
/* Compressed Unsigned Normalized Float Color Formats */
BC1,
BC2,
BC3,
BC7,
/* Signed Normalized Float Color Formats */
R8G8_SNORM,
R8G8B8A8_SNORM,
/* Signed Float Color Formats */
R16_SFLOAT,
R16G16_SFLOAT,
R16G16B16A16_SFLOAT,
R32_SFLOAT,
R32G32_SFLOAT,
R32G32B32A32_SFLOAT,
/* Unsigned Integer Color Formats */
R8_UINT,
R8G8_UINT,
R8G8B8A8_UINT,
R16_UINT,
R16G16_UINT,
R16G16B16A16_UINT,
/* SRGB Color Formats */
R8G8B8A8_SRGB,
B8G8R8A8_SRGB,
/* Compressed SRGB Color Formats */
BC3_SRGB,
BC7_SRGB,
/* Depth Formats */
D16_UNORM,
D24_UNORM,
D32_SFLOAT,
D24_UNORM_S8_UINT,
D32_SFLOAT_S8_UINT
}
[Flags]
public enum TextureUsageFlags
{
Sampler = 0x1,
ColorTarget = 0x2,
DepthStencil = 0x4,
GraphicsStorage = 0x8,
ComputeStorageRead = 0x20,
ComputeStorageWrite = 0x40
}
public enum TextureType
{
TwoD,
ThreeD,
Cube
}
public enum SampleCount
{
One,
Two,
Four,
Eight
}
public enum CubeMapFace
{
PositiveX,
NegativeX,
PositiveY,
NegativeY,
PositiveZ,
NegativeZ
}
[Flags]
public enum BufferUsageFlags
{
Vertex = 0x1,
Index = 0x2,
Indirect = 0x4,
GraphicsStorage = 0x8,
ComputeStorageRead = 0x20,
ComputeStorageWrite = 0x40
}
[Flags]
public enum TransferBufferMapFlags
{
Read = 0x1,
Write = 0x2
}
public enum ShaderStage
{
Vertex,
Fragment,
Compute
}
public enum ShaderFormat
{
Invalid,
SPIRV,
DXBC,
DXIL,
MSL,
METALLIB,
SECRET
}
public enum VertexElementFormat
{
Uint,
Float,
Vector2,
Vector3,
Vector4,
Color,
Byte4,
Short2,
Short4,
NormalizedShort2,
NormalizedShort4,
HalfVector2,
HalfVector4
}
public enum VertexInputRate
{
Vertex,
Instance
}
public enum FillMode
{
Fill,
Line
}
public enum CullMode
{
None,
Front,
Back
}
public enum FrontFace
{
CounterClockwise,
Clockwise
}
public enum CompareOp
{
Never,
Less,
Equal,
LessOrEqual,
Greater,
NotEqual,
GreaterOrEqual,
Always
}
public enum StencilOp
{
Keep,
Zero,
Replace,
IncrementAndClamp,
DecrementAndClamp,
Invert,
IncrementAndWrap,
DecrementAndWrap
}
public enum BlendOp
{
Add,
Subtract,
ReverseSubtract,
Min,
Max
}
public enum BlendFactor
{
Zero,
One,
SourceColor,
OneMinusSourceColor,
DestinationColor,
OneMinusDestinationColor,
SourceAlpha,
OneMinusSourceAlpha,
DestinationAlpha,
OneMinusDestinationAlpha,
ConstantColor,
OneMinusConstantColor,
SourceAlphaSaturate
}
[Flags]
public enum ColorComponentFlags
{
None = 0x0,
R = 0x1,
G = 0x2,
B = 0x4,
A = 0x8,
RGB = R | G | B,
RGBA = R | G| B | A
}
public enum Filter
{
Nearest,
Linear
}
public enum SamplerMipmapMode
{
Nearest,
Linear
}
public enum SamplerAddressMode
{
Repeat,
MirroredRepeat,
ClampToEdge,
ClampToBorder
}
public enum BorderColor
{
FloatTransparentBlack,
IntTransparentBlack,
FloatOpaqueBlack,
IntOpaqueBlack,
FloatOpaqueWhite,
IntOpaqueWhite
}
public enum TransferUsage
{
Buffer,
Texture
}
public enum PresentMode
{
VSync,
Immediate,
Mailbox
}
public enum SwapchainComposition
{
SDR,
SDRLinear,
HDRExtendedLinear,
HDR10_ST2084
}
[Flags]
public enum BackendFlags
{
Invalid = 0x0,
Vulkan = 0x1,
D3D11 = 0x2,
Metal = 0x4,
All = Vulkan | D3D11 | Metal
}
[StructLayout(LayoutKind.Sequential)]
public struct DepthStencilValue
{
public float Depth;
public uint Stencil;
public DepthStencilValue(float depth, uint stencil)
{
Depth = depth;
Stencil = stencil;
}
// FIXME: can we do an unsafe cast somehow?
public Refresh.DepthStencilValue ToRefresh()
{
return new Refresh.DepthStencilValue
{
Depth = Depth,
Stencil = Stencil
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int X;
public int Y;
public int W;
public int H;
public Rect(int x, int y, int w, int h)
{
X = x;
Y = y;
W = w;
H = h;
}
public Rect(int w, int h)
{
X = 0;
Y = 0;
W = w;
H = h;
}
// FIXME: can we do an unsafe cast somehow?
public Refresh.Rect ToRefresh()
{
return new Refresh.Rect
{
X = X,
Y = Y,
W = W,
H = H
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Viewport
{
public float X;
public float Y;
public float W;
public float H;
public float MinDepth;
public float MaxDepth;
public Viewport(float w, float h)
{
X = 0;
Y = 0;
W = w;
H = h;
MinDepth = 0;
MaxDepth = 1;
}
public Viewport(float x, float y, float w, float h)
{
X = x;
Y = y;
W = w;
H = h;
MinDepth = 0;
MaxDepth = 1;
}
public Viewport(float x, float y, float w, float h, float minDepth, float maxDepth)
{
X = x;
Y = y;
W = w;
H = h;
MinDepth = minDepth;
MaxDepth = maxDepth;
}
public Refresh.Viewport ToRefresh()
{
return new Refresh.Viewport
{
X = X,
Y = Y,
W = W,
H = H,
MinDepth = MinDepth,
MaxDepth = MaxDepth
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct VertexBinding
{
public uint Binding;
public uint Stride;
public VertexInputRate InputRate;
public static VertexBinding Create<T>(uint binding = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged
{
return new VertexBinding
{
Binding = binding,
InputRate = inputRate,
Stride = (uint) Marshal.SizeOf<T>()
};
}
public Refresh.VertexBinding ToRefresh()
{
return new Refresh.VertexBinding
{
Binding = Binding,
Stride = Stride,
InputRate = (Refresh.VertexInputRate) InputRate
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct VertexAttribute
{
public uint Location;
public uint Binding;
public VertexElementFormat Format;
public uint Offset;
public Refresh.VertexAttribute ToRefresh()
{
return new Refresh.VertexAttribute
{
Location = Location,
Binding = Binding,
Format = (Refresh.VertexElementFormat) Format,
Offset = Offset
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct StencilOpState
{
public StencilOp FailOp;
public StencilOp PassOp;
public StencilOp DepthFailOp;
public CompareOp CompareOp;
public Refresh.StencilOpState ToRefresh()
{
return new Refresh.StencilOpState
{
FailOp = (Refresh.StencilOp) FailOp,
PassOp = (Refresh.StencilOp) PassOp,
DepthFailOp = (Refresh.StencilOp) DepthFailOp,
CompareOp = (Refresh.CompareOp) CompareOp
};
}
}
/// <summary>
/// Determines how a color texture will be read/written in a render pass.
/// </summary>
public struct ColorAttachmentInfo
{
public TextureSlice TextureSlice;
/// <summary>
/// If LoadOp is set to Clear, the texture slice will be cleared to this color.
/// </summary>
public Color ClearColor;
/// <summary>
/// Determines what is done with the texture slice memory
/// at the beginning of the render pass. <br/>
///
/// Load:
/// Loads the data currently in the texture slice. <br/>
///
/// Clear:
/// Clears the texture slice to a single color. <br/>
///
/// DontCare:
/// The driver will do whatever it wants with the texture slice data.
/// This is a good option if you know that every single pixel will be written in the render pass.
/// </summary>
public LoadOp LoadOp;
/// <summary>
/// Determines what is done with the texture slice memory
/// at the end of the render pass. <br/>
///
/// Store:
/// Stores the results of the render pass in the texture slice memory. <br/>
///
/// DontCare:
/// The driver will do whatever it wants with the texture slice memory.
/// </summary>
public StoreOp StoreOp;
/// <summary>
/// If true, cycles the texture if it is bound.
/// </summary>
public bool Cycle;
public ColorAttachmentInfo(
TextureSlice textureSlice,
bool cycle,
Color clearColor,
StoreOp storeOp = StoreOp.Store
) {
TextureSlice = textureSlice;
ClearColor = clearColor;
LoadOp = LoadOp.Clear;
StoreOp = storeOp;
Cycle = cycle;
}
public ColorAttachmentInfo(
TextureSlice textureSlice,
bool cycle,
LoadOp loadOp = LoadOp.DontCare,
StoreOp storeOp = StoreOp.Store
) {
TextureSlice = textureSlice;
ClearColor = Color.White;
LoadOp = loadOp;
StoreOp = storeOp;
Cycle = cycle;
}
public Refresh.ColorAttachmentInfo ToRefresh()
{
return new Refresh.ColorAttachmentInfo
{
TextureSlice = TextureSlice.ToRefresh(),
ClearColor = new Refresh.Color
{
R = ClearColor.R / 255f,
G = ClearColor.G / 255f,
B = ClearColor.B / 255f,
A = ClearColor.A / 255f
},
LoadOp = (Refresh.LoadOp) LoadOp,
StoreOp = (Refresh.StoreOp) StoreOp,
Cycle = Conversions.BoolToInt(Cycle)
};
}
}
/// <summary>
/// Determines how a depth/stencil texture will be read/written in a render pass.
/// </summary>
public struct DepthStencilAttachmentInfo
{
public TextureSlice TextureSlice;
/// <summary>
/// If LoadOp is set to Clear, the texture slice depth will be cleared to this depth value. <br/>
/// If StencilLoadOp is set to Clear, the texture slice stencil value will be cleared to this stencil value.
/// </summary>
public DepthStencilValue DepthStencilClearValue;
/// <summary>
/// Determines what is done with the texture slice depth values
/// at the beginning of the render pass. <br/>
///
/// Load:
/// Loads the data currently in the texture slice. <br/>
///
/// Clear:
/// Clears the texture slice to a single depth value. <br/>
///
/// DontCare:
/// The driver will do whatever it wants with the texture slice data.
/// This is a good option if you know that every single pixel will be written in the render pass.
/// </summary>
public LoadOp LoadOp;
/// <summary>
/// Determines what is done with the texture slice depth values
/// at the end of the render pass. <br/>
///
/// Store:
/// Stores the results of the render pass in the texture slice memory. <br/>
///
/// DontCare:
/// The driver will do whatever it wants with the texture slice memory.
/// This is usually a good option for depth textures that don't need to be reused.
/// </summary>
public StoreOp StoreOp;
/// <summary>
/// Determines what is done with the texture slice stencil values
/// at the beginning of the render pass. <br/>
///
/// Load:
/// Loads the data currently in the texture slice. <br/>
///
/// Clear:
/// Clears the texture slice to a single stencil value. <br/>
///
/// DontCare:
/// The driver will do whatever it wants with the texture slice data.
/// This is a good option if you know that every single pixel will be written in the render pass.
/// </summary>
public LoadOp StencilLoadOp;
/// <summary>
/// Determines what is done with the texture slice stencil values
/// at the end of the render pass. <br/>
///
/// Store:
/// Stores the results of the render pass in the texture slice memory. <br/>
///
/// DontCare:
/// The driver will do whatever it wants with the texture slice memory.
/// This is usually a good option for stencil textures that don't need to be reused.
/// </summary>
public StoreOp StencilStoreOp;
/// <summary>
/// If true, cycles the texture if it is bound.
/// </summary>
public bool Cycle;
public DepthStencilAttachmentInfo(
TextureSlice textureSlice,
bool cycle,
DepthStencilValue clearValue,
StoreOp depthStoreOp = StoreOp.DontCare,
StoreOp stencilStoreOp = StoreOp.DontCare
){
TextureSlice = textureSlice;
DepthStencilClearValue = clearValue;
LoadOp = LoadOp.Clear;
StoreOp = depthStoreOp;
StencilLoadOp = LoadOp.Clear;
StencilStoreOp = stencilStoreOp;
Cycle = cycle;
}
public DepthStencilAttachmentInfo(
TextureSlice textureSlice,
bool cycle,
LoadOp loadOp = LoadOp.DontCare,
StoreOp storeOp = StoreOp.DontCare,
LoadOp stencilLoadOp = LoadOp.DontCare,
StoreOp stencilStoreOp = StoreOp.DontCare
) {
TextureSlice = textureSlice;
DepthStencilClearValue = new DepthStencilValue();
LoadOp = loadOp;
StoreOp = storeOp;
StencilLoadOp = stencilLoadOp;
StencilStoreOp = stencilStoreOp;
Cycle = cycle;
}
public DepthStencilAttachmentInfo(
TextureSlice textureSlice,
bool cycle,
DepthStencilValue clearValue,
LoadOp loadOp,
StoreOp storeOp,
LoadOp stencilLoadOp,
StoreOp stencilStoreOp
) {
TextureSlice = textureSlice;
DepthStencilClearValue = clearValue;
LoadOp = loadOp;
StoreOp = storeOp;
StencilLoadOp = stencilLoadOp;
StencilStoreOp = stencilStoreOp;
Cycle = cycle;
}
public Refresh.DepthStencilAttachmentInfo ToRefresh()
{
return new Refresh.DepthStencilAttachmentInfo
{
TextureSlice = TextureSlice.ToRefresh(),
DepthStencilClearValue = DepthStencilClearValue.ToRefresh(),
LoadOp = (Refresh.LoadOp) LoadOp,
StoreOp = (Refresh.StoreOp) StoreOp,
StencilLoadOp = (Refresh.LoadOp) StencilLoadOp,
StencilStoreOp = (Refresh.StoreOp) StencilStoreOp,
Cycle = Conversions.BoolToInt(Cycle)
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct ColorAttachmentDescription
{
public TextureFormat Format;
public ColorAttachmentBlendState BlendState;
public ColorAttachmentDescription(
TextureFormat format,
ColorAttachmentBlendState blendState
) {
Format = format;
BlendState = blendState;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct IndirectDrawCommand
{
public uint VertexCount;
public uint InstanceCount;
public uint FirstVertex;
public uint FirstInstance;
public IndirectDrawCommand(
uint vertexCount,
uint instanceCount,
uint firstVertex,
uint firstInstance
) {
VertexCount = vertexCount;
InstanceCount = instanceCount;
FirstVertex = firstVertex;
FirstInstance = firstInstance;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct BufferCopy
{
public uint SrcOffset;
public uint DstOffset;
public uint Size;
public BufferCopy(
uint srcOffset,
uint dstOffset,
uint size
) {
SrcOffset = srcOffset;
DstOffset = dstOffset;
Size = size;
}
public Refresh.BufferCopy ToRefresh()
{
return new Refresh.BufferCopy
{
SourceOffset = SrcOffset,
DestinationOffset = DstOffset,
Size = Size
};
}
}
/// <summary>
/// Parameters for a copy between buffer and image.
/// </summary>
/// <param name="BufferOffset">The offset into the buffer.</param>
/// <param name="BufferStride">If 0, image data is assumed tightly packed.</param>
/// <param name="BufferImageHeight">If 0, image data is assumed tightly packed.</param>
[StructLayout(LayoutKind.Sequential)]
public readonly record struct BufferImageCopy(
uint BufferOffset,
uint BufferStride,
uint BufferImageHeight
) {
public Refresh.BufferImageCopy ToRefresh()
{
return new Refresh.BufferImageCopy
{
BufferOffset = BufferOffset,
BufferStride = BufferStride,
BufferImageHeight = BufferImageHeight
};
}
}
public readonly record struct GraphicsPipelineResourceInfo(
uint SamplerCount,
uint StorageBufferCount,
uint StorageTextureCount,
uint UniformBufferCount
) {
public Refresh.GraphicsPipelineResourceInfo ToRefresh()
{
return new Refresh.GraphicsPipelineResourceInfo
{
SamplerCount = SamplerCount,
StorageBufferCount = StorageBufferCount,
StorageTextureCount = StorageTextureCount,
UniformBufferCount = UniformBufferCount
};
}
}
public readonly record struct ComputePipelineResourceInfo(
uint ReadOnlyStorageTextureCount,
uint ReadOnlyStorageBufferCount,
uint ReadWriteStorageTextureCount,
uint ReadWriteStorageBufferCount,
uint UniformBufferCount
) {
public Refresh.ComputePipelineResourceInfo ToRefresh()
{
return new Refresh.ComputePipelineResourceInfo
{
ReadOnlyStorageTextureCount = ReadOnlyStorageTextureCount,
ReadOnlyStorageBufferCount = ReadOnlyStorageBufferCount,
ReadWriteStorageTextureCount = ReadWriteStorageTextureCount,
ReadWriteStorageBufferCount = ReadWriteStorageBufferCount,
UniformBufferCount = UniformBufferCount
};
}
}
/// <summary>
/// A buffer-offset pair to be used when binding buffers.
/// </summary>
public readonly record struct BufferBinding(
GpuBuffer Buffer,
uint Offset
) {
public Refresh.BufferBinding ToRefresh()
{
return new Refresh.BufferBinding
{
Buffer = Buffer.Handle,
Offset = Offset
};
}
}
/// <summary>
/// A texture-sampler pair to be used when binding samplers.
/// </summary>
public readonly record struct TextureSamplerBinding(
Texture Texture,
Sampler Sampler
) {
public Refresh.TextureSamplerBinding ToRefresh()
{
return new Refresh.TextureSamplerBinding
{
Texture = Texture.Handle,
Sampler = Sampler.Handle
};
}
}
public readonly record struct StorageBufferReadWriteBinding(
GpuBuffer Buffer,
bool Cycle
) {
public Refresh.StorageBufferReadWriteBinding ToRefresh()
{
return new Refresh.StorageBufferReadWriteBinding
{
Buffer = Buffer.Handle,
Cycle = Conversions.BoolToInt(Cycle)
};
}
}
public readonly record struct StorageTextureReadWriteBinding(
in TextureSlice TextureSlice,
bool Cycle
) {
public Refresh.StorageTextureReadWriteBinding ToRefresh()
{
return new Refresh.StorageTextureReadWriteBinding
{
TextureSlice = TextureSlice.ToRefresh(),
Cycle = Conversions.BoolToInt(Cycle)
};
}
}
/// <summary>
/// All of the information that is used to create a GraphicsPipeline.
/// </summary>
public struct GraphicsPipelineCreateInfo
{
public Shader VertexShader;
public Shader FragmentShader;
public VertexInputState VertexInputState;
public PrimitiveType PrimitiveType;
public RasterizerState RasterizerState;
public MultisampleState MultisampleState;
public DepthStencilState DepthStencilState;
public GraphicsPipelineAttachmentInfo AttachmentInfo;
public GraphicsPipelineResourceInfo VertexShaderResourceInfo;
public GraphicsPipelineResourceInfo FragmentShaderResourceInfo;
public BlendConstants BlendConstants;
}

543
src/Graphics/RenderPass.cs Normal file
View File

@ -0,0 +1,543 @@
using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics;
/// <summary>
/// Render passes are begun in command buffers and are used to apply render state and issue draw calls.
/// Render passes are pooled and should not be referenced after calling EndRenderPass.
/// </summary>
public class RenderPass
{
public nint Handle { get; private set; }
#if DEBUG
internal uint colorAttachmentCount;
internal SampleCount colorAttachmentSampleCount;
internal TextureFormat colorFormatOne;
internal TextureFormat colorFormatTwo;
internal TextureFormat colorFormatThree;
internal TextureFormat colorFormatFour;
internal bool hasDepthStencilAttachment;
internal SampleCount depthStencilAttachmentSampleCount;
internal TextureFormat depthStencilFormat;
GraphicsPipeline currentGraphicsPipeline;
#endif
internal void SetHandle(nint handle)
{
Handle = handle;
}
/// <summary>
/// Binds a graphics pipeline so that rendering work may be performed.
/// </summary>
/// <param name="graphicsPipeline">The graphics pipeline to bind.</param>
public void BindGraphicsPipeline(
GraphicsPipeline graphicsPipeline
) {
#if DEBUG
AssertRenderPassPipelineFormatMatch(graphicsPipeline);
if (colorAttachmentCount > 0)
{
if (graphicsPipeline.SampleCount != colorAttachmentSampleCount)
{
throw new System.InvalidOperationException($"Sample count mismatch! Graphics pipeline sample count: {graphicsPipeline.SampleCount}, Color attachment sample count: {colorAttachmentSampleCount}");
}
}
if (hasDepthStencilAttachment)
{
if (graphicsPipeline.SampleCount != depthStencilAttachmentSampleCount)
{
throw new System.InvalidOperationException($"Sample count mismatch! Graphics pipeline sample count: {graphicsPipeline.SampleCount}, Depth stencil attachment sample count: {depthStencilAttachmentSampleCount}");
}
}
#endif
Refresh.Refresh_BindGraphicsPipeline(
Handle,
graphicsPipeline.Handle
);
#if DEBUG
currentGraphicsPipeline = graphicsPipeline;
#endif
}
/// <summary>
/// Sets the viewport.
/// </summary>
public void SetViewport(in Viewport viewport)
{
Refresh.Refresh_SetViewport(
Handle,
viewport.ToRefresh()
);
}
/// <summary>
/// Sets the scissor area.
/// </summary>
public void SetScissor(in Rect scissor)
{
#if DEBUG
if (scissor.X < 0 || scissor.Y < 0 || scissor.W <= 0 || scissor.H <= 0)
{
throw new System.ArgumentOutOfRangeException("Scissor position cannot be negative and dimensions must be positive!");
}
#endif
Refresh.Refresh_SetScissor(
Handle,
scissor.ToRefresh()
);
}
/// <summary>
/// Binds vertex buffers to be used by subsequent draw calls.
/// </summary>
/// <param name="bufferBinding">Buffer to bind and associated offset.</param>
/// <param name="firstBinding">The index of the first vertex input binding whose state is updated by the command.</param>
public unsafe void BindVertexBuffer(
in BufferBinding bufferBinding,
uint firstBinding = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
#endif
var refreshBufferBinding = bufferBinding.ToRefresh();
Refresh.Refresh_BindVertexBuffers(
Handle,
firstBinding,
&refreshBufferBinding,
1
);
}
/// <summary>
/// Binds an index buffer to be used by subsequent draw calls.
/// </summary>
/// <param name="indexBuffer">The index buffer to bind.</param>
/// <param name="indexElementSize">The size in bytes of the index buffer elements.</param>
/// <param name="offset">The offset index for the buffer.</param>
public void BindIndexBuffer(
BufferBinding bufferBinding,
IndexElementSize indexElementSize
)
{
#if DEBUG
AssertGraphicsPipelineBound();
#endif
Refresh.Refresh_BindIndexBuffer(
Handle,
bufferBinding.ToRefresh(),
(Refresh.IndexElementSize) indexElementSize
);
}
/// <summary>
/// Binds samplers to be used by the vertex shader.
/// </summary>
/// <param name="textureSamplerBindings">The texture-sampler to bind.</param>
public unsafe void BindVertexSampler(
in TextureSamplerBinding textureSamplerBinding,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
AssertTextureSamplerBindingNonNull(textureSamplerBinding);
AssertTextureHasSamplerFlag(textureSamplerBinding.Texture);
#endif
var refreshTextureSamplerBinding = textureSamplerBinding.ToRefresh();
Refresh.Refresh_BindVertexSamplers(
Handle,
slot,
&refreshTextureSamplerBinding,
1
);
}
public unsafe void BindVertexStorageTexture(
in TextureSlice textureSlice,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
AssertTextureNonNull(textureSlice.Texture);
AssertTextureHasGraphicsStorageFlag(textureSlice.Texture);
#endif
var refreshTextureSlice = textureSlice.ToRefresh();
Refresh.Refresh_BindVertexStorageTextures(
Handle,
slot,
&refreshTextureSlice,
1
);
}
public unsafe void BindVertexStorageBuffer(
GpuBuffer buffer,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
AssertBufferNonNull(buffer);
AssertBufferHasGraphicsStorageFlag(buffer);
#endif
var bufferHandle = buffer.Handle;
Refresh.Refresh_BindVertexStorageBuffers(
Handle,
slot,
&bufferHandle,
1
);
}
public unsafe void BindFragmentSampler(
in TextureSamplerBinding textureSamplerBinding,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
AssertTextureSamplerBindingNonNull(textureSamplerBinding);
AssertTextureHasSamplerFlag(textureSamplerBinding.Texture);
#endif
var refreshTextureSamplerBinding = textureSamplerBinding.ToRefresh();
Refresh.Refresh_BindFragmentSamplers(
Handle,
slot,
&refreshTextureSamplerBinding,
1
);
}
public unsafe void BindFragmentStorageTexture(
in TextureSlice textureSlice,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
AssertTextureNonNull(textureSlice.Texture);
AssertTextureHasGraphicsStorageFlag(textureSlice.Texture);
#endif
var refreshTextureSlice = textureSlice.ToRefresh();
Refresh.Refresh_BindFragmentStorageTextures(
Handle,
slot,
&refreshTextureSlice,
1
);
}
public unsafe void BindFragmentStorageBuffer(
GpuBuffer buffer,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
AssertBufferNonNull(buffer);
AssertBufferHasGraphicsStorageFlag(buffer);
#endif
var bufferHandle = buffer.Handle;
Refresh.Refresh_BindFragmentStorageBuffers(
Handle,
slot,
&bufferHandle,
1
);
}
public unsafe void PushVertexUniformData(
void* uniformsPtr,
uint size,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
if (slot >= currentGraphicsPipeline.VertexShaderResourceInfo.UniformBufferCount)
{
throw new System.ArgumentException($"Slot {slot} given, but {currentGraphicsPipeline.VertexShaderResourceInfo.UniformBufferCount} uniform buffers are used on the shader!");
}
#endif
Refresh.Refresh_PushVertexUniformData(
Handle,
slot,
(nint) uniformsPtr,
size
);
}
public unsafe void PushVertexUniformData<T>(
in T uniforms,
uint slot = 0
) where T : unmanaged
{
fixed (T* uniformsPtr = &uniforms)
{
PushVertexUniformData(uniformsPtr, (uint) Marshal.SizeOf<T>(), slot);
}
}
public unsafe void PushFragmentUniformData(
void* uniformsPtr,
uint size,
uint slot = 0
) {
#if DEBUG
AssertGraphicsPipelineBound();
if (slot >= currentGraphicsPipeline.FragmentShaderResourceInfo.UniformBufferCount)
{
throw new System.ArgumentException($"Slot {slot} given, but {currentGraphicsPipeline.FragmentShaderResourceInfo.UniformBufferCount} uniform buffers are used on the shader!");
}
#endif
Refresh.Refresh_PushFragmentUniformData(
Handle,
slot,
(nint) uniformsPtr,
size
);
}
public unsafe void PushFragmentUniformData<T>(
in T uniforms,
uint slot = 0
) where T : unmanaged
{
fixed (T* uniformsPtr = &uniforms)
{
PushFragmentUniformData(uniformsPtr, (uint) Marshal.SizeOf<T>(), slot);
}
}
/// <summary>
/// Draws using a vertex buffer and an index buffer, and an optional instance count.
/// </summary>
/// <param name="baseVertex">The starting index offset for the vertex buffer.</param>
/// <param name="startIndex">The starting index offset for the index buffer.</param>
/// <param name="primitiveCount">The number of primitives to draw.</param>
/// <param name="instanceCount">The number of instances to draw.</param>
public void DrawIndexedPrimitives(
uint baseVertex,
uint startIndex,
uint primitiveCount,
uint instanceCount = 1
) {
#if DEBUG
AssertGraphicsPipelineBound();
#endif
Refresh.Refresh_DrawIndexedPrimitives(
Handle,
baseVertex,
startIndex,
primitiveCount,
instanceCount
);
}
/// <summary>
/// Draws using a vertex buffer and an index buffer.
/// </summary>
/// <param name="baseVertex">The starting index offset for the vertex buffer.</param>
/// <param name="startIndex">The starting index offset for the index buffer.</param>
/// <param name="primitiveCount">The number of primitives to draw.</param>
public void DrawPrimitives(
uint vertexStart,
uint primitiveCount
)
{
#if DEBUG
AssertGraphicsPipelineBound();
#endif
Refresh.Refresh_DrawPrimitives(
Handle,
vertexStart,
primitiveCount
);
}
/// <summary>
/// Similar to DrawPrimitives, but parameters are set from a buffer.
/// The buffer must have the Indirect usage flag set.
/// </summary>
/// <param name="buffer">The draw parameters buffer.</param>
/// <param name="offsetInBytes">The offset to start reading from the draw parameters buffer.</param>
/// <param name="drawCount">The number of draw parameter sets that should be read from the buffer.</param>
/// <param name="stride">The byte stride between sets of draw parameters.</param>
public void DrawPrimitivesIndirect(
GpuBuffer buffer,
uint offsetInBytes,
uint drawCount,
uint stride
) {
#if DEBUG
AssertGraphicsPipelineBound();
#endif
Refresh.Refresh_DrawPrimitivesIndirect(
Handle,
buffer.Handle,
offsetInBytes,
drawCount,
stride
);
}
/// <summary>
/// Similar to DrawIndexedPrimitives, but parameters are set from a buffer.
/// The buffer must have the Indirect usage flag set.
/// </summary>
/// <param name="buffer">The draw parameters buffer.</param>
/// <param name="offsetInBytes">The offset to start reading from the draw parameters buffer.</param>
/// <param name="drawCount">The number of draw parameter sets that should be read from the buffer.</param>
/// <param name="stride">The byte stride between sets of draw parameters.</param>
public void DrawIndexedPrimitivesIndirect(
GpuBuffer buffer,
uint offsetInBytes,
uint drawCount,
uint stride
) {
#if DEBUG
AssertGraphicsPipelineBound();
#endif
Refresh.Refresh_DrawIndexedPrimitivesIndirect(
Handle,
buffer.Handle,
offsetInBytes,
drawCount,
stride
);
}
#if DEBUG
private void AssertRenderPassPipelineFormatMatch(GraphicsPipeline graphicsPipeline)
{
for (var i = 0; i < graphicsPipeline.AttachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
{
TextureFormat format;
if (i == 0)
{
format = colorFormatOne;
}
else if (i == 1)
{
format = colorFormatTwo;
}
else if (i == 2)
{
format = colorFormatThree;
}
else
{
format = colorFormatFour;
}
var pipelineFormat = graphicsPipeline.AttachmentInfo.ColorAttachmentDescriptions[i].Format;
if (pipelineFormat != format)
{
throw new System.InvalidOperationException($"Color texture format mismatch! Pipeline expects {pipelineFormat}, render pass attachment is {format}");
}
}
if (graphicsPipeline.AttachmentInfo.HasDepthStencilAttachment)
{
var pipelineDepthFormat = graphicsPipeline.AttachmentInfo.DepthStencilFormat;
if (!hasDepthStencilAttachment)
{
throw new System.InvalidOperationException("Pipeline expects depth attachment!");
}
if (pipelineDepthFormat != depthStencilFormat)
{
throw new System.InvalidOperationException($"Depth texture format mismatch! Pipeline expects {pipelineDepthFormat}, render pass attachment is {depthStencilFormat}");
}
}
}
private void AssertGraphicsPipelineBound(string message = "No graphics pipeline is bound!")
{
if (currentGraphicsPipeline == null)
{
throw new System.InvalidOperationException(message);
}
}
private void AssertTextureNonNull(in TextureSlice textureSlice)
{
if (textureSlice.Texture == null)
{
throw new NullReferenceException("Texture must not be null!");
}
}
private void AssertTextureSamplerBindingNonNull(in TextureSamplerBinding binding)
{
if (binding.Texture == null || binding.Texture.Handle == IntPtr.Zero)
{
throw new NullReferenceException("Texture binding must not be null!");
}
if (binding.Sampler == null || binding.Sampler.Handle == IntPtr.Zero)
{
throw new NullReferenceException("Sampler binding must not be null!");
}
}
private void AssertTextureHasSamplerFlag(Texture texture)
{
if ((texture.UsageFlags & TextureUsageFlags.Sampler) == 0)
{
throw new System.ArgumentException("The bound Texture's UsageFlags must include TextureUsageFlags.Sampler!");
}
}
private void AssertTextureHasGraphicsStorageFlag(Texture texture)
{
if ((texture.UsageFlags & TextureUsageFlags.GraphicsStorage) == 0)
{
throw new System.ArgumentException("The bound Texture's UsageFlags must include TextureUsageFlags.GraphicsStorage!");
}
}
private void AssertBufferNonNull(GpuBuffer buffer)
{
if (buffer == null || buffer.Handle == IntPtr.Zero)
{
throw new System.NullReferenceException("Buffer must not be null!");
}
}
private void AssertBufferHasGraphicsStorageFlag(GpuBuffer buffer)
{
if ((buffer.UsageFlags & BufferUsageFlags.GraphicsStorage) == 0)
{
throw new System.ArgumentException("The bound Buffer's UsageFlags must include BufferUsageFlag.GraphicsStorage!");
}
}
#endif
}

View File

@ -0,0 +1,25 @@
using System.Collections.Concurrent;
namespace MoonWorks.Graphics;
internal class RenderPassPool
{
private ConcurrentQueue<RenderPass> RenderPasses = new ConcurrentQueue<RenderPass>();
public RenderPass Obtain()
{
if (RenderPasses.TryDequeue(out var renderPass))
{
return renderPass;
}
else
{
return new RenderPass();
}
}
public void Return(RenderPass renderPass)
{
RenderPasses.Enqueue(renderPass);
}
}

View File

@ -0,0 +1,381 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
namespace MoonWorks.Graphics
{
/// <summary>
/// A convenience structure for creating resources and uploading their data.
///
/// Note that Upload or UploadAndWait must be called after the Create methods for the data to actually be uploaded.
///
/// Note that this structure does not magically keep memory usage down -
/// you may want to stagger uploads over multiple submissions to minimize memory usage.
/// </summary>
public unsafe class ResourceUploader : GraphicsResource
{
TransferBuffer BufferTransferBuffer;
TransferBuffer TextureTransferBuffer;
byte* bufferData;
uint bufferDataOffset = 0;
uint bufferDataSize = 1024;
byte* textureData;
uint textureDataOffset = 0;
uint textureDataSize = 1024;
List<(GpuBuffer, BufferCopy, bool)> BufferUploads = new List<(GpuBuffer, BufferCopy, bool)>();
List<(TextureRegion, uint, bool)> TextureUploads = new List<(TextureRegion, uint, bool)>();
public ResourceUploader(GraphicsDevice device) : base(device)
{
bufferData = (byte*) NativeMemory.Alloc(bufferDataSize);
textureData = (byte*) NativeMemory.Alloc(textureDataSize);
}
// Buffers
/// <summary>
/// Creates a GpuBuffer with data to be uploaded.
/// </summary>
public GpuBuffer CreateBuffer<T>(Span<T> data, BufferUsageFlags usageFlags) where T : unmanaged
{
var lengthInBytes = (uint) (Marshal.SizeOf<T>() * data.Length);
var gpuBuffer = new GpuBuffer(Device, usageFlags, lengthInBytes);
SetBufferData(gpuBuffer, 0, data, false);
return gpuBuffer;
}
/// <summary>
/// Prepares upload of data into a GpuBuffer.
/// </summary>
public void SetBufferData<T>(GpuBuffer buffer, uint bufferOffsetInElements, Span<T> data, bool cycle) where T : unmanaged
{
uint elementSize = (uint) Marshal.SizeOf<T>();
uint offsetInBytes = elementSize * bufferOffsetInElements;
uint lengthInBytes = (uint) (elementSize * data.Length);
uint resourceOffset;
fixed (void* spanPtr = data)
{
resourceOffset = CopyBufferData(spanPtr, lengthInBytes);
}
var bufferCopyParams = new BufferCopy(resourceOffset, offsetInBytes, lengthInBytes);
BufferUploads.Add((buffer, bufferCopyParams, cycle));
}
// Textures
public Texture CreateTexture2D<T>(Span<T> pixelData, uint width, uint height) where T : unmanaged
{
var texture = Texture.CreateTexture2D(Device, width, height, TextureFormat.R8G8B8A8, TextureUsageFlags.Sampler);
SetTextureData(texture, pixelData, false);
return texture;
}
/// <summary>
/// Creates a 2D Texture from compressed image data to be uploaded.
/// </summary>
public Texture CreateTexture2DFromCompressed(Span<byte> compressedImageData)
{
ImageUtils.ImageInfoFromBytes(compressedImageData, out var width, out var height, out var _);
var texture = Texture.CreateTexture2D(Device, width, height, TextureFormat.R8G8B8A8, TextureUsageFlags.Sampler);
SetTextureDataFromCompressed(texture, compressedImageData);
return texture;
}
/// <summary>
/// Creates a 2D Texture from a compressed image stream to be uploaded.
/// </summary>
public Texture CreateTexture2DFromCompressed(Stream compressedImageStream)
{
var length = compressedImageStream.Length;
var buffer = NativeMemory.Alloc((nuint) length);
var span = new Span<byte>(buffer, (int) length);
compressedImageStream.ReadExactly(span);
var texture = CreateTexture2DFromCompressed(span);
NativeMemory.Free(buffer);
return texture;
}
/// <summary>
/// Creates a 2D Texture from a compressed image file to be uploaded.
/// </summary>
public Texture CreateTexture2DFromCompressed(string compressedImageFilePath)
{
var fileStream = new FileStream(compressedImageFilePath, FileMode.Open, FileAccess.Read);
return CreateTexture2DFromCompressed(fileStream);
}
/// <summary>
/// Creates a texture from a DDS stream.
/// </summary>
public Texture CreateTextureFromDDS(Stream stream)
{
using var reader = new BinaryReader(stream);
Texture texture;
int faces;
ImageUtils.ParseDDS(reader, out var format, out var width, out var height, out var levels, out var isCube);
if (isCube)
{
texture = Texture.CreateTextureCube(Device, (uint) width, format, TextureUsageFlags.Sampler, (uint) levels);
faces = 6;
}
else
{
texture = Texture.CreateTexture2D(Device, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, (uint) levels);
faces = 1;
}
for (int face = 0; face < faces; face += 1)
{
for (int level = 0; level < levels; level += 1)
{
var levelWidth = width >> level;
var levelHeight = height >> level;
var levelSize = ImageUtils.CalculateDDSLevelSize(levelWidth, levelHeight, format);
var byteBuffer = NativeMemory.Alloc((nuint) levelSize);
var byteSpan = new Span<byte>(byteBuffer, levelSize);
stream.ReadExactly(byteSpan);
var textureRegion = new TextureRegion
{
TextureSlice = new TextureSlice
{
Texture = texture,
Layer = (uint) face,
MipLevel = (uint) level
},
X = 0,
Y = 0,
Z = 0,
Width = (uint) levelWidth,
Height = (uint) levelHeight,
Depth = 1
};
SetTextureData(textureRegion, byteSpan, false);
NativeMemory.Free(byteBuffer);
}
}
return texture;
}
/// <summary>
/// Creates a texture from a DDS file.
/// </summary>
public Texture CreateTextureFromDDS(string path)
{
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
return CreateTextureFromDDS(stream);
}
public void SetTextureDataFromCompressed(TextureRegion textureRegion, Span<byte> compressedImageData)
{
var pixelData = ImageUtils.GetPixelDataFromBytes(compressedImageData, out var _, out var _, out var sizeInBytes);
var pixelSpan = new Span<byte>((void*) pixelData, (int) sizeInBytes);
SetTextureData(textureRegion, pixelSpan, false);
ImageUtils.FreePixelData(pixelData);
}
public void SetTextureDataFromCompressed(TextureRegion textureRegion, Stream compressedImageStream)
{
var length = compressedImageStream.Length;
var buffer = NativeMemory.Alloc((nuint) length);
var span = new Span<byte>(buffer, (int) length);
compressedImageStream.ReadExactly(span);
SetTextureDataFromCompressed(textureRegion, span);
NativeMemory.Free(buffer);
}
public void SetTextureDataFromCompressed(TextureRegion textureRegion, string compressedImageFilePath)
{
var fileStream = new FileStream(compressedImageFilePath, FileMode.Open, FileAccess.Read);
SetTextureDataFromCompressed(textureRegion, fileStream);
}
/// <summary>
/// Prepares upload of pixel data into a TextureSlice.
/// </summary>
public void SetTextureData<T>(TextureRegion textureRegion, Span<T> data, bool cycle) where T : unmanaged
{
var elementSize = Marshal.SizeOf<T>();
var dataLengthInBytes = (uint) (elementSize * data.Length);
uint resourceOffset;
fixed (T* dataPtr = data)
{
resourceOffset = CopyTextureData(dataPtr, dataLengthInBytes, Texture.BytesPerPixel(textureRegion.TextureSlice.Texture.Format));
}
TextureUploads.Add((textureRegion, resourceOffset, cycle));
}
// Upload
/// <summary>
/// Uploads all the data corresponding to the created resources.
/// </summary>
public void Upload()
{
CopyToTransferBuffer();
var commandBuffer = Device.AcquireCommandBuffer();
RecordUploadCommands(commandBuffer);
Device.Submit(commandBuffer);
}
/// <summary>
/// Uploads and then blocks until the upload is finished.
/// This is useful for keeping memory usage down during threaded upload.
/// </summary>
public void UploadAndWait()
{
CopyToTransferBuffer();
var commandBuffer = Device.AcquireCommandBuffer();
RecordUploadCommands(commandBuffer);
var fence = Device.SubmitAndAcquireFence(commandBuffer);
Device.WaitForFence(fence);
Device.ReleaseFence(fence);
}
// Helper methods
private void CopyToTransferBuffer()
{
if (BufferUploads.Count > 0)
{
if (BufferTransferBuffer == null || BufferTransferBuffer.Size < bufferDataSize)
{
BufferTransferBuffer?.Dispose();
BufferTransferBuffer = new TransferBuffer(Device, TransferUsage.Buffer, TransferBufferMapFlags.Write, bufferDataSize);
}
var dataSpan = new Span<byte>(bufferData, (int) bufferDataSize);
BufferTransferBuffer.SetData(dataSpan, true);
}
if (TextureUploads.Count > 0)
{
if (TextureTransferBuffer == null || TextureTransferBuffer.Size < textureDataSize)
{
TextureTransferBuffer?.Dispose();
TextureTransferBuffer = new TransferBuffer(Device, TransferUsage.Texture, TransferBufferMapFlags.Write, textureDataSize);
}
var dataSpan = new Span<byte>(textureData, (int) textureDataSize);
TextureTransferBuffer.SetData(dataSpan, true);
}
}
private void RecordUploadCommands(CommandBuffer commandBuffer)
{
var copyPass = commandBuffer.BeginCopyPass();
foreach (var (gpuBuffer, bufferCopyParams, option) in BufferUploads)
{
copyPass.UploadToBuffer(
BufferTransferBuffer,
gpuBuffer,
bufferCopyParams,
option
);
}
foreach (var (textureRegion, offset, option) in TextureUploads)
{
copyPass.UploadToTexture(
TextureTransferBuffer,
textureRegion,
new BufferImageCopy(
offset,
0,
0
),
option
);
}
commandBuffer.EndCopyPass(copyPass);
BufferUploads.Clear();
TextureUploads.Clear();
bufferDataOffset = 0;
}
private uint CopyBufferData(void* ptr, uint lengthInBytes)
{
if (bufferDataOffset + lengthInBytes >= bufferDataSize)
{
bufferDataSize = bufferDataOffset + lengthInBytes;
bufferData = (byte*) NativeMemory.Realloc(bufferData, bufferDataSize);
}
var resourceOffset = bufferDataOffset;
NativeMemory.Copy(ptr, bufferData + bufferDataOffset, lengthInBytes);
bufferDataOffset += lengthInBytes;
return resourceOffset;
}
private uint CopyTextureData(void* ptr, uint lengthInBytes, uint alignment)
{
textureDataOffset = RoundToAlignment(textureDataOffset, alignment);
if (textureDataOffset + lengthInBytes >= textureDataSize)
{
textureDataSize = textureDataOffset + lengthInBytes;
textureData = (byte*) NativeMemory.Realloc(textureData, textureDataSize);
}
var resourceOffset = textureDataOffset;
NativeMemory.Copy(ptr, textureData + textureDataOffset, lengthInBytes);
textureDataOffset += lengthInBytes;
return resourceOffset;
}
private uint RoundToAlignment(uint value, uint alignment)
{
return alignment * ((value + alignment - 1) / alignment);
}
// Dispose
/// <summary>
/// It is valid to immediately call Dispose after calling Upload.
/// </summary>
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
BufferTransferBuffer?.Dispose();
TextureTransferBuffer?.Dispose();
}
NativeMemory.Free(bufferData);
}
base.Dispose(disposing);
}
}
}

View File

@ -1,141 +0,0 @@
using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// Buffers are generic data containers that can be used by the GPU.
/// </summary>
public class Buffer : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
/// <summary>
/// Size in bytes.
/// </summary>
public uint Size { get; }
/// <summary>
/// Creates a buffer of appropriate size given a type and element count.
/// </summary>
/// <typeparam name="T">The type that the buffer will contain.</typeparam>
/// <param name="device">The GraphicsDevice.</param>
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
/// <param name="elementCount">How many elements of type T the buffer will contain.</param>
/// <returns></returns>
public unsafe static Buffer Create<T>(
GraphicsDevice device,
BufferUsageFlags usageFlags,
uint elementCount
) where T : unmanaged
{
return new Buffer(
device,
usageFlags,
(uint) Marshal.SizeOf<T>() * elementCount
);
}
/// <summary>
/// Creates a buffer.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
/// <param name="sizeInBytes">The length of the array. Cannot be resized.</param>
public Buffer(
GraphicsDevice device,
BufferUsageFlags usageFlags,
uint sizeInBytes
) : base(device)
{
Handle = Refresh.Refresh_CreateBuffer(
device.Handle,
(Refresh.BufferUsageFlags) usageFlags,
sizeInBytes
);
Size = sizeInBytes;
}
/// <summary>
/// Reads data out of a buffer and into a span.
/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
/// </summary>
/// <param name="data">The span that data will be copied to.</param>
/// <param name="dataLengthInBytes">The length of the data to read.</param>
public unsafe void GetData<T>(
Span<T> data,
uint dataLengthInBytes
) where T : unmanaged
{
#if DEBUG
if (dataLengthInBytes > Size)
{
Logger.LogWarn("Requested too many bytes from buffer!");
}
if (dataLengthInBytes > data.Length * Marshal.SizeOf<T>())
{
Logger.LogWarn("Data length is larger than the provided Span!");
}
#endif
fixed (T* ptr = data)
{
Refresh.Refresh_GetBufferData(
Device.Handle,
Handle,
(IntPtr) ptr,
dataLengthInBytes
);
}
}
/// <summary>
/// Reads data out of a buffer and into an array.
/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
/// </summary>
/// <param name="data">The span that data will be copied to.</param>
/// <param name="dataLengthInBytes">The length of the data to read.</param>
public unsafe void GetData<T>(
T[] data,
uint dataLengthInBytes
) where T : unmanaged
{
GetData(new Span<T>(data), dataLengthInBytes);
}
/// <summary>
/// Reads data out of a buffer and into a span.
/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
/// Fills the span with as much data from the buffer as it can.
/// </summary>
/// <param name="data">The span that data will be copied to.</param>
public unsafe void GetData<T>(
Span<T> data
) where T : unmanaged
{
var lengthInBytes = System.Math.Min(data.Length * Marshal.SizeOf<T>(), Size);
GetData(data, (uint) lengthInBytes);
}
/// <summary>
/// Reads data out of a buffer and into an array.
/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
/// Fills the array with as much data from the buffer as it can.
/// </summary>
/// <param name="data">The span that data will be copied to.</param>
public unsafe void GetData<T>(
T[] data
) where T : unmanaged
{
var lengthInBytes = System.Math.Min(data.Length * Marshal.SizeOf<T>(), Size);
GetData(new Span<T>(data), (uint) lengthInBytes);
}
public static implicit operator BufferBinding(Buffer b)
{
return new BufferBinding(b, 0);
}
}
}

View File

@ -8,34 +8,33 @@ namespace MoonWorks.Graphics
/// </summary> /// </summary>
public class ComputePipeline : RefreshResource public class ComputePipeline : RefreshResource
{ {
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline; protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseComputePipeline;
public ComputeShaderInfo ComputeShaderInfo { get; } public ComputePipelineResourceInfo ResourceInfo { get; }
public unsafe ComputePipeline( public unsafe ComputePipeline(
GraphicsDevice device, GraphicsDevice device,
ComputeShaderInfo computeShaderInfo Shader computeShader,
ComputePipelineResourceInfo resourceInfo
) : base(device) ) : base(device)
{ {
var refreshComputeShaderInfo = new Refresh.ComputeShaderInfo var refreshComputePipelineCreateInfo = new Refresh.ComputePipelineCreateInfo
{ {
entryPointName = computeShaderInfo.EntryPointName, ComputeShader = computeShader.Handle,
shaderModule = computeShaderInfo.ShaderModule.Handle, PipelineResourceInfo = resourceInfo.ToRefresh()
uniformBufferSize = computeShaderInfo.UniformBufferSize,
bufferBindingCount = computeShaderInfo.BufferBindingCount,
imageBindingCount = computeShaderInfo.ImageBindingCount
}; };
Handle = Refresh.Refresh_CreateComputePipeline( Handle = Refresh.Refresh_CreateComputePipeline(
device.Handle, device.Handle,
refreshComputeShaderInfo refreshComputePipelineCreateInfo
); );
if (Handle == IntPtr.Zero) if (Handle == IntPtr.Zero)
{ {
throw new Exception("Could not create compute pipeline!"); throw new Exception("Could not create compute pipeline!");
} }
ComputeShaderInfo = computeShaderInfo; ResourceInfo = resourceInfo;
} }
} }
} }

View File

@ -1,8 +1,8 @@
using System; using System;
using RefreshCS; using RefreshCS;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics;
{
/// <summary> /// <summary>
/// Fences allow you to track the status of a submitted command buffer. <br/> /// Fences allow you to track the status of a submitted command buffer. <br/>
/// You should only acquire a Fence if you will need to track the command buffer. <br/> /// You should only acquire a Fence if you will need to track the command buffer. <br/>
@ -12,7 +12,7 @@ namespace MoonWorks.Graphics
/// </summary> /// </summary>
public class Fence : RefreshResource public class Fence : RefreshResource
{ {
protected override Action<nint, nint> QueueDestroyFunction => Refresh.Refresh_ReleaseFence; protected override Action<nint, nint> ReleaseFunction => Refresh.Refresh_ReleaseFence;
internal Fence(GraphicsDevice device) : base(device) internal Fence(GraphicsDevice device) : base(device)
{ {
@ -23,4 +23,3 @@ namespace MoonWorks.Graphics
Handle = handle; Handle = handle;
} }
} }
}

View File

@ -0,0 +1,88 @@
using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics;
/// <summary>
/// GpuBuffers are generic data containers that can be used by the GPU.
/// </summary>
public class GpuBuffer : RefreshResource
{
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseBuffer;
public BufferUsageFlags UsageFlags { get; }
/// <summary>
/// Size in bytes.
/// </summary>
public uint Size { get; }
private string name;
public string Name
{
get => name;
set
{
if (Device.DebugMode)
{
Refresh.Refresh_SetBufferName(
Device.Handle,
Handle,
value
);
}
name = value;
}
}
/// <summary>
/// Creates a buffer of appropriate size given a type and element count.
/// </summary>
/// <typeparam name="T">The type that the buffer will contain.</typeparam>
/// <param name="device">The GraphicsDevice.</param>
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
/// <param name="elementCount">How many elements of type T the buffer will contain.</param>
/// <returns></returns>
public unsafe static GpuBuffer Create<T>(
GraphicsDevice device,
BufferUsageFlags usageFlags,
uint elementCount
) where T : unmanaged
{
return new GpuBuffer(
device,
usageFlags,
(uint) Marshal.SizeOf<T>() * elementCount
);
}
/// <summary>
/// Creates a buffer.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
/// <param name="sizeInBytes">The length of the array. Cannot be resized.</param>
public GpuBuffer(
GraphicsDevice device,
BufferUsageFlags usageFlags,
uint sizeInBytes
) : base(device)
{
Handle = Refresh.Refresh_CreateBuffer(
device.Handle,
(Refresh.BufferUsageFlags) usageFlags,
sizeInBytes
);
UsageFlags = usageFlags;
Size = sizeInBytes;
name = "";
}
public static implicit operator BufferBinding(GpuBuffer b)
{
return new BufferBinding(b, 0);
}
}

View File

@ -10,10 +10,10 @@ namespace MoonWorks.Graphics
/// </summary> /// </summary>
public class GraphicsPipeline : RefreshResource public class GraphicsPipeline : RefreshResource
{ {
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline; protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseGraphicsPipeline;
public GraphicsShaderInfo VertexShaderInfo { get; } public GraphicsPipelineResourceInfo VertexShaderResourceInfo { get; }
public GraphicsShaderInfo FragmentShaderInfo { get; } public GraphicsPipelineResourceInfo FragmentShaderResourceInfo { get; }
public SampleCount SampleCount { get; } public SampleCount SampleCount { get; }
#if DEBUG #if DEBUG
@ -25,84 +25,62 @@ namespace MoonWorks.Graphics
in GraphicsPipelineCreateInfo graphicsPipelineCreateInfo in GraphicsPipelineCreateInfo graphicsPipelineCreateInfo
) : base(device) ) : base(device)
{ {
DepthStencilState depthStencilState = graphicsPipelineCreateInfo.DepthStencilState;
GraphicsShaderInfo vertexShaderInfo = graphicsPipelineCreateInfo.VertexShaderInfo;
GraphicsShaderInfo fragmentShaderInfo = graphicsPipelineCreateInfo.FragmentShaderInfo;
MultisampleState multisampleState = graphicsPipelineCreateInfo.MultisampleState;
RasterizerState rasterizerState = graphicsPipelineCreateInfo.RasterizerState;
PrimitiveType primitiveType = graphicsPipelineCreateInfo.PrimitiveType;
VertexInputState vertexInputState = graphicsPipelineCreateInfo.VertexInputState;
GraphicsPipelineAttachmentInfo attachmentInfo = graphicsPipelineCreateInfo.AttachmentInfo;
BlendConstants blendConstants = graphicsPipelineCreateInfo.BlendConstants;
var vertexAttributesHandle = GCHandle.Alloc(
vertexInputState.VertexAttributes,
GCHandleType.Pinned
);
var vertexBindingsHandle = GCHandle.Alloc(
vertexInputState.VertexBindings,
GCHandleType.Pinned
);
var colorAttachmentDescriptions = stackalloc Refresh.ColorAttachmentDescription[
(int) attachmentInfo.ColorAttachmentDescriptions.Length
];
for (var i = 0; i < attachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
{
colorAttachmentDescriptions[i].format = (Refresh.TextureFormat) attachmentInfo.ColorAttachmentDescriptions[i].Format;
colorAttachmentDescriptions[i].blendState = attachmentInfo.ColorAttachmentDescriptions[i].BlendState.ToRefresh();
}
Refresh.GraphicsPipelineCreateInfo refreshGraphicsPipelineCreateInfo; Refresh.GraphicsPipelineCreateInfo refreshGraphicsPipelineCreateInfo;
refreshGraphicsPipelineCreateInfo.blendConstants[0] = blendConstants.R; var vertexAttributes = (Refresh.VertexAttribute*) NativeMemory.Alloc(
refreshGraphicsPipelineCreateInfo.blendConstants[1] = blendConstants.G; (nuint) (graphicsPipelineCreateInfo.VertexInputState.VertexAttributes.Length * Marshal.SizeOf<Refresh.VertexAttribute>())
refreshGraphicsPipelineCreateInfo.blendConstants[2] = blendConstants.B; );
refreshGraphicsPipelineCreateInfo.blendConstants[3] = blendConstants.A;
refreshGraphicsPipelineCreateInfo.depthStencilState.backStencilState = depthStencilState.BackStencilState.ToRefresh(); for (var i = 0; i < graphicsPipelineCreateInfo.VertexInputState.VertexAttributes.Length; i += 1)
refreshGraphicsPipelineCreateInfo.depthStencilState.compareOp = (Refresh.CompareOp) depthStencilState.CompareOp; {
refreshGraphicsPipelineCreateInfo.depthStencilState.depthBoundsTestEnable = Conversions.BoolToByte(depthStencilState.DepthBoundsTestEnable); vertexAttributes[i] = graphicsPipelineCreateInfo.VertexInputState.VertexAttributes[i].ToRefresh();
refreshGraphicsPipelineCreateInfo.depthStencilState.depthTestEnable = Conversions.BoolToByte(depthStencilState.DepthTestEnable); }
refreshGraphicsPipelineCreateInfo.depthStencilState.depthWriteEnable = Conversions.BoolToByte(depthStencilState.DepthWriteEnable);
refreshGraphicsPipelineCreateInfo.depthStencilState.frontStencilState = depthStencilState.FrontStencilState.ToRefresh();
refreshGraphicsPipelineCreateInfo.depthStencilState.maxDepthBounds = depthStencilState.MaxDepthBounds;
refreshGraphicsPipelineCreateInfo.depthStencilState.minDepthBounds = depthStencilState.MinDepthBounds;
refreshGraphicsPipelineCreateInfo.depthStencilState.stencilTestEnable = Conversions.BoolToByte(depthStencilState.StencilTestEnable);
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.entryPointName = vertexShaderInfo.EntryPointName; var vertexBindings = (Refresh.VertexBinding*) NativeMemory.Alloc(
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.shaderModule = vertexShaderInfo.ShaderModule.Handle; (nuint) (graphicsPipelineCreateInfo.VertexInputState.VertexBindings.Length * Marshal.SizeOf<Refresh.VertexBinding>())
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.uniformBufferSize = vertexShaderInfo.UniformBufferSize; );
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.samplerBindingCount = vertexShaderInfo.SamplerBindingCount;
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.entryPointName = fragmentShaderInfo.EntryPointName; for (var i = 0; i < graphicsPipelineCreateInfo.VertexInputState.VertexBindings.Length; i += 1)
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.shaderModule = fragmentShaderInfo.ShaderModule.Handle; {
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.uniformBufferSize = fragmentShaderInfo.UniformBufferSize; vertexBindings[i] = graphicsPipelineCreateInfo.VertexInputState.VertexBindings[i].ToRefresh();
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.samplerBindingCount = fragmentShaderInfo.SamplerBindingCount; }
refreshGraphicsPipelineCreateInfo.multisampleState.multisampleCount = (Refresh.SampleCount) multisampleState.MultisampleCount; var colorAttachmentDescriptions = stackalloc Refresh.ColorAttachmentDescription[
refreshGraphicsPipelineCreateInfo.multisampleState.sampleMask = multisampleState.SampleMask; graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions.Length
];
refreshGraphicsPipelineCreateInfo.rasterizerState.cullMode = (Refresh.CullMode) rasterizerState.CullMode; for (var i = 0; i < graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasClamp = rasterizerState.DepthBiasClamp; {
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasConstantFactor = rasterizerState.DepthBiasConstantFactor; colorAttachmentDescriptions[i].Format = (Refresh.TextureFormat) graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions[i].Format;
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasEnable = Conversions.BoolToByte(rasterizerState.DepthBiasEnable); colorAttachmentDescriptions[i].BlendState = graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions[i].BlendState.ToRefresh();
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasSlopeFactor = rasterizerState.DepthBiasSlopeFactor; }
refreshGraphicsPipelineCreateInfo.rasterizerState.fillMode = (Refresh.FillMode) rasterizerState.FillMode;
refreshGraphicsPipelineCreateInfo.rasterizerState.frontFace = (Refresh.FrontFace) rasterizerState.FrontFace;
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributes = vertexAttributesHandle.AddrOfPinnedObject(); refreshGraphicsPipelineCreateInfo.VertexShader = graphicsPipelineCreateInfo.VertexShader.Handle;
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributeCount = (uint) vertexInputState.VertexAttributes.Length; refreshGraphicsPipelineCreateInfo.FragmentShader = graphicsPipelineCreateInfo.FragmentShader.Handle;
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindings = vertexBindingsHandle.AddrOfPinnedObject();
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindingCount = (uint) vertexInputState.VertexBindings.Length;
refreshGraphicsPipelineCreateInfo.primitiveType = (Refresh.PrimitiveType) primitiveType; refreshGraphicsPipelineCreateInfo.VertexInputState.VertexAttributes = vertexAttributes;
refreshGraphicsPipelineCreateInfo.VertexInputState.VertexAttributeCount = (uint) graphicsPipelineCreateInfo.VertexInputState.VertexAttributes.Length;
refreshGraphicsPipelineCreateInfo.VertexInputState.VertexBindings = vertexBindings;
refreshGraphicsPipelineCreateInfo.VertexInputState.VertexBindingCount = (uint) graphicsPipelineCreateInfo.VertexInputState.VertexBindings.Length;
refreshGraphicsPipelineCreateInfo.attachmentInfo.colorAttachmentCount = (uint) attachmentInfo.ColorAttachmentDescriptions.Length; refreshGraphicsPipelineCreateInfo.PrimitiveType = (Refresh.PrimitiveType) graphicsPipelineCreateInfo.PrimitiveType;
refreshGraphicsPipelineCreateInfo.attachmentInfo.colorAttachmentDescriptions = (IntPtr) colorAttachmentDescriptions;
refreshGraphicsPipelineCreateInfo.attachmentInfo.depthStencilFormat = (Refresh.TextureFormat) attachmentInfo.DepthStencilFormat; refreshGraphicsPipelineCreateInfo.RasterizerState = graphicsPipelineCreateInfo.RasterizerState.ToRefresh();
refreshGraphicsPipelineCreateInfo.attachmentInfo.hasDepthStencilAttachment = Conversions.BoolToByte(attachmentInfo.HasDepthStencilAttachment); refreshGraphicsPipelineCreateInfo.MultisampleState = graphicsPipelineCreateInfo.MultisampleState.ToRefresh();
refreshGraphicsPipelineCreateInfo.DepthStencilState = graphicsPipelineCreateInfo.DepthStencilState.ToRefresh();
refreshGraphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentCount = (uint) graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions.Length;
refreshGraphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions = colorAttachmentDescriptions;
refreshGraphicsPipelineCreateInfo.AttachmentInfo.DepthStencilFormat = (Refresh.TextureFormat) graphicsPipelineCreateInfo.AttachmentInfo.DepthStencilFormat;
refreshGraphicsPipelineCreateInfo.AttachmentInfo.HasDepthStencilAttachment = Conversions.BoolToInt(graphicsPipelineCreateInfo.AttachmentInfo.HasDepthStencilAttachment);
refreshGraphicsPipelineCreateInfo.VertexResourceInfo = graphicsPipelineCreateInfo.VertexShaderResourceInfo.ToRefresh();
refreshGraphicsPipelineCreateInfo.FragmentResourceInfo = graphicsPipelineCreateInfo.FragmentShaderResourceInfo.ToRefresh();
refreshGraphicsPipelineCreateInfo.BlendConstants[0] = graphicsPipelineCreateInfo.BlendConstants.R;
refreshGraphicsPipelineCreateInfo.BlendConstants[1] = graphicsPipelineCreateInfo.BlendConstants.G;
refreshGraphicsPipelineCreateInfo.BlendConstants[2] = graphicsPipelineCreateInfo.BlendConstants.B;
refreshGraphicsPipelineCreateInfo.BlendConstants[3] = graphicsPipelineCreateInfo.BlendConstants.A;
Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo); Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo);
if (Handle == IntPtr.Zero) if (Handle == IntPtr.Zero)
@ -110,15 +88,15 @@ namespace MoonWorks.Graphics
throw new Exception("Could not create graphics pipeline!"); throw new Exception("Could not create graphics pipeline!");
} }
vertexAttributesHandle.Free(); NativeMemory.Free(vertexAttributes);
vertexBindingsHandle.Free(); NativeMemory.Free(vertexBindings);
VertexShaderInfo = vertexShaderInfo; VertexShaderResourceInfo = graphicsPipelineCreateInfo.VertexShaderResourceInfo;
FragmentShaderInfo = fragmentShaderInfo; FragmentShaderResourceInfo = graphicsPipelineCreateInfo.FragmentShaderResourceInfo;
SampleCount = multisampleState.MultisampleCount; SampleCount = graphicsPipelineCreateInfo.MultisampleState.MultisampleCount;
#if DEBUG #if DEBUG
AttachmentInfo = attachmentInfo; AttachmentInfo = graphicsPipelineCreateInfo.AttachmentInfo;
#endif #endif
} }
} }

View File

@ -1,14 +1,14 @@
using System; using System;
using RefreshCS; using RefreshCS;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics;
{
/// <summary> /// <summary>
/// A sampler specifies how a texture will be sampled in a shader. /// A sampler specifies how a texture will be sampled in a shader.
/// </summary> /// </summary>
public class Sampler : RefreshResource public class Sampler : RefreshResource
{ {
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler; protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseSampler;
public Sampler( public Sampler(
GraphicsDevice device, GraphicsDevice device,
@ -17,8 +17,7 @@ namespace MoonWorks.Graphics
{ {
Handle = Refresh.Refresh_CreateSampler( Handle = Refresh.Refresh_CreateSampler(
device.Handle, device.Handle,
samplerCreateInfo.ToRefreshSamplerStateCreateInfo() samplerCreateInfo.ToRefresh()
); );
} }
} }
}

View File

@ -0,0 +1,65 @@
using RefreshCS;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace MoonWorks.Graphics
{
/// <summary>
/// Shader modules expect input in Refresh bytecode format.
/// </summary>
public class Shader : RefreshResource
{
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseShader;
public unsafe Shader(
GraphicsDevice device,
string filePath,
string entryPointName,
ShaderStage shaderStage,
ShaderFormat shaderFormat
) : base(device)
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
Handle = CreateFromStream(device, stream, entryPointName, shaderStage, shaderFormat);
}
public unsafe Shader(
GraphicsDevice device,
Stream stream,
string entryPointName,
ShaderStage shaderStage,
ShaderFormat shaderFormat
) : base(device)
{
Handle = CreateFromStream(device, stream, entryPointName, shaderStage, shaderFormat);
}
private static unsafe IntPtr CreateFromStream(
GraphicsDevice device,
Stream stream,
string entryPointName,
ShaderStage shaderStage,
ShaderFormat shaderFormat
) {
var bytecodeBuffer = NativeMemory.Alloc((nuint) stream.Length);
var bytecodeSpan = new Span<byte>(bytecodeBuffer, (int) stream.Length);
stream.ReadExactly(bytecodeSpan);
Refresh.ShaderCreateInfo shaderCreateInfo;
shaderCreateInfo.CodeSize = (nuint) stream.Length;
shaderCreateInfo.Code = (byte*) bytecodeBuffer;
shaderCreateInfo.EntryPointName = entryPointName;
shaderCreateInfo.Stage = (Refresh.ShaderStage) shaderStage;
shaderCreateInfo.Format = (Refresh.ShaderFormat) shaderFormat;
var shaderModule = Refresh.Refresh_CreateShader(
device.Handle,
shaderCreateInfo
);
NativeMemory.Free(bytecodeBuffer);
return shaderModule;
}
}
}

View File

@ -1,42 +0,0 @@
using RefreshCS;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace MoonWorks.Graphics
{
/// <summary>
/// Shader modules expect input in Refresh bytecode format.
/// </summary>
public class ShaderModule : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
public unsafe ShaderModule(GraphicsDevice device, string filePath) : base(device)
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
Handle = CreateFromStream(device, stream);
}
public unsafe ShaderModule(GraphicsDevice device, Stream stream) : base(device)
{
Handle = CreateFromStream(device, stream);
}
private static unsafe IntPtr CreateFromStream(GraphicsDevice device, Stream stream)
{
var bytecodeBuffer = NativeMemory.Alloc((nuint) stream.Length);
var bytecodeSpan = new Span<byte>(bytecodeBuffer, (int) stream.Length);
stream.ReadExactly(bytecodeSpan);
Refresh.ShaderModuleCreateInfo shaderModuleCreateInfo;
shaderModuleCreateInfo.codeSize = (nuint) stream.Length;
shaderModuleCreateInfo.byteCode = (nint) bytecodeBuffer;
var shaderModule = Refresh.Refresh_CreateShaderModule(device.Handle, shaderModuleCreateInfo);
NativeMemory.Free(bytecodeBuffer);
return shaderModule;
}
}
}

View File

@ -1,6 +1,4 @@
using System; using System;
using System.IO;
using System.Runtime.InteropServices;
using RefreshCS; using RefreshCS;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics
@ -15,169 +13,34 @@ namespace MoonWorks.Graphics
public uint Depth { get; } public uint Depth { get; }
public TextureFormat Format { get; internal set; } public TextureFormat Format { get; internal set; }
public bool IsCube { get; } public bool IsCube { get; }
public uint LayerCount { get; }
public uint LevelCount { get; } public uint LevelCount { get; }
public SampleCount SampleCount { get; } public SampleCount SampleCount { get; }
public TextureUsageFlags UsageFlags { get; } public TextureUsageFlags UsageFlags { get; }
public uint Size { get; } public uint Size { get; }
// FIXME: this allocates a delegate instance private string name;
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture; public string Name
/// <summary>
/// Creates a 2D Texture using PNG or QOI data from raw byte data.
/// </summary>
public static unsafe Texture FromImageBytes(
GraphicsDevice device,
CommandBuffer commandBuffer,
Span<byte> data
) {
Texture texture;
fixed (byte *dataPtr = data)
{ {
var pixels = Refresh.Refresh_Image_Load((nint) dataPtr, data.Length, out var width, out var height, out var len); get => name;
TextureCreateInfo textureCreateInfo = new TextureCreateInfo(); set
textureCreateInfo.Width = (uint) width;
textureCreateInfo.Height = (uint) height;
textureCreateInfo.Depth = 1;
textureCreateInfo.Format = TextureFormat.R8G8B8A8;
textureCreateInfo.IsCube = false;
textureCreateInfo.LevelCount = 1;
textureCreateInfo.SampleCount = SampleCount.One;
textureCreateInfo.UsageFlags = TextureUsageFlags.Sampler;
texture = new Texture(device, textureCreateInfo);
commandBuffer.SetTextureData(texture, pixels, (uint) len);
Refresh.Refresh_Image_Free(pixels);
}
return texture;
}
/// <summary>
/// Creates a 2D Texture using PNG or QOI data from a stream.
/// </summary>
public static unsafe Texture FromImageStream(
GraphicsDevice device,
CommandBuffer commandBuffer,
Stream stream
) {
var length = stream.Length;
var buffer = NativeMemory.Alloc((nuint) length);
var span = new Span<byte>(buffer, (int) length);
stream.ReadExactly(span);
var texture = FromImageBytes(device, commandBuffer, span);
NativeMemory.Free((void*) buffer);
return texture;
}
/// <summary>
/// Creates a 2D Texture using PNG or QOI data from a file.
/// </summary>
public static Texture FromImageFile(
GraphicsDevice device,
CommandBuffer commandBuffer,
string path
) {
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
return FromImageStream(device, commandBuffer, fileStream);
}
public static unsafe void SetDataFromImageBytes(
CommandBuffer commandBuffer,
TextureSlice textureSlice,
Span<byte> data
) {
fixed (byte* ptr = data)
{ {
var pixels = Refresh.Refresh_Image_Load( if (Device.DebugMode)
(nint) ptr, {
(int) data.Length, Refresh.Refresh_SetTextureName(
out var w, Device.Handle,
out var h, Handle,
out var len value
); );
}
commandBuffer.SetTextureData(textureSlice, pixels, (uint) len); name = value;
Refresh.Refresh_Image_Free(pixels);
} }
} }
/// <summary> // FIXME: this allocates a delegate instance
/// Sets data for a texture slice using PNG or QOI data from a stream. protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseTexture;
/// </summary>
public static unsafe void SetDataFromImageStream(
CommandBuffer commandBuffer,
TextureSlice textureSlice,
Stream stream
) {
var length = stream.Length;
var buffer = NativeMemory.Alloc((nuint) length);
var span = new Span<byte>(buffer, (int) length);
stream.ReadExactly(span);
SetDataFromImageBytes(commandBuffer, textureSlice, span);
NativeMemory.Free((void*) buffer);
}
/// <summary>
/// Sets data for a texture slice using PNG or QOI data from a file.
/// </summary>
public static void SetDataFromImageFile(
CommandBuffer commandBuffer,
TextureSlice textureSlice,
string path
) {
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
SetDataFromImageStream(commandBuffer, textureSlice, fileStream);
}
public unsafe static Texture LoadDDS(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, System.IO.Stream stream)
{
using var reader = new BinaryReader(stream);
Texture texture;
int faces;
ParseDDS(reader, out var format, out var width, out var height, out var levels, out var isCube);
if (isCube)
{
texture = CreateTextureCube(graphicsDevice, (uint) width, format, TextureUsageFlags.Sampler, (uint) levels);
faces = 6;
}
else
{
texture = CreateTexture2D(graphicsDevice, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, (uint) levels);
faces = 1;
}
for (int i = 0; i < faces; i += 1)
{
for (int j = 0; j < levels; j += 1)
{
var levelWidth = width >> j;
var levelHeight = height >> j;
var levelSize = CalculateDDSLevelSize(levelWidth, levelHeight, format);
var byteBuffer = NativeMemory.Alloc((nuint) levelSize);
var byteSpan = new Span<byte>(byteBuffer, levelSize);
stream.ReadExactly(byteSpan);
var textureSlice = new TextureSlice(texture, new Rect(0, 0, levelWidth, levelHeight), 0, (uint) i, (uint) j);
commandBuffer.SetTextureData(textureSlice, (nint) byteBuffer, (uint) levelSize);
NativeMemory.Free(byteBuffer);
}
}
return texture;
}
/// <summary> /// <summary>
/// Creates a 2D texture. /// Creates a 2D texture.
@ -203,6 +66,7 @@ namespace MoonWorks.Graphics
Height = height, Height = height,
Depth = 1, Depth = 1,
IsCube = false, IsCube = false,
LayerCount = 1,
LevelCount = levelCount, LevelCount = levelCount,
SampleCount = sampleCount, SampleCount = sampleCount,
Format = format, Format = format,
@ -213,15 +77,43 @@ namespace MoonWorks.Graphics
} }
/// <summary> /// <summary>
/// Creates a 3D texture. /// Creates a 2D texture array.
/// </summary> /// </summary>
/// <param name="device">An initialized GraphicsDevice.</param> /// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="width">The width of the texture.</param> /// <param name="width">The width of the texture.</param>
/// <param name="height">The height of the texture.</param> /// <param name="height">The height of the texture.</param>
/// <param name="depth">The depth of the texture.</param> /// <param name="layerCount">The layer count of the texture.</param>
/// <param name="format">The format of the texture.</param> /// <param name="format">The format of the texture.</param>
/// <param name="usageFlags">Specifies how the texture will be used.</param> /// <param name="usageFlags">Specifies how the texture will be used.</param>
/// <param name="levelCount">Specifies the number of mip levels.</param> /// <param name="levelCount">Specifies the number of mip levels.</param>
public static Texture CreateTexture2DArray(
GraphicsDevice device,
uint width,
uint height,
uint layerCount,
TextureFormat format,
TextureUsageFlags usageFlags,
uint levelCount = 1
) {
var textureCreateInfo = new TextureCreateInfo
{
Width = width,
Height = height,
Depth = 1,
IsCube = false,
LayerCount = layerCount,
LevelCount = levelCount,
Format = format,
UsageFlags = usageFlags
};
return new Texture(device, textureCreateInfo);
}
/// <summary>
/// Creates a 3D texture.
/// Note that the width, height and depth all form one slice and cannot be subdivided in a texture slice.
/// </summary>
public static Texture CreateTexture3D( public static Texture CreateTexture3D(
GraphicsDevice device, GraphicsDevice device,
uint width, uint width,
@ -237,6 +129,7 @@ namespace MoonWorks.Graphics
Height = height, Height = height,
Depth = depth, Depth = depth,
IsCube = false, IsCube = false,
LayerCount = 1,
LevelCount = levelCount, LevelCount = levelCount,
Format = format, Format = format,
UsageFlags = usageFlags UsageFlags = usageFlags
@ -266,6 +159,7 @@ namespace MoonWorks.Graphics
Height = size, Height = size,
Depth = 1, Depth = 1,
IsCube = true, IsCube = true,
LayerCount = 6,
LevelCount = levelCount, LevelCount = levelCount,
Format = format, Format = format,
UsageFlags = usageFlags UsageFlags = usageFlags
@ -286,7 +180,7 @@ namespace MoonWorks.Graphics
{ {
Handle = Refresh.Refresh_CreateTexture( Handle = Refresh.Refresh_CreateTexture(
device.Handle, device.Handle,
textureCreateInfo.ToRefreshTextureCreateInfo() textureCreateInfo.ToRefresh()
); );
Format = textureCreateInfo.Format; Format = textureCreateInfo.Format;
@ -294,16 +188,15 @@ namespace MoonWorks.Graphics
Height = textureCreateInfo.Height; Height = textureCreateInfo.Height;
Depth = textureCreateInfo.Depth; Depth = textureCreateInfo.Depth;
IsCube = textureCreateInfo.IsCube; IsCube = textureCreateInfo.IsCube;
LayerCount = textureCreateInfo.LayerCount;
LevelCount = textureCreateInfo.LevelCount; LevelCount = textureCreateInfo.LevelCount;
SampleCount = textureCreateInfo.SampleCount; SampleCount = textureCreateInfo.SampleCount;
UsageFlags = textureCreateInfo.UsageFlags; UsageFlags = textureCreateInfo.UsageFlags;
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format); Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
name = "";
} }
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t); // Used by Window. Swapchain texture handles are managed by the driver backend.
// Used by AcquireSwapchainTexture.
// Should not be tracked, because swapchain textures are managed by Vulkan.
internal Texture( internal Texture(
GraphicsDevice device, GraphicsDevice device,
TextureFormat format TextureFormat format
@ -322,333 +215,6 @@ namespace MoonWorks.Graphics
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format); Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
} }
// DDS loading extension, based on MojoDDS
// Taken from https://github.com/FNA-XNA/FNA/blob/1e49f868f595f62bc6385db45949a03186a7cd7f/src/Graphics/Texture.cs#L194
private static void ParseDDS(
BinaryReader reader,
out TextureFormat format,
out int width,
out int height,
out int levels,
out bool isCube
) {
// A whole bunch of magic numbers, yay DDS!
const uint DDS_MAGIC = 0x20534444;
const uint DDS_HEADERSIZE = 124;
const uint DDS_PIXFMTSIZE = 32;
const uint DDSD_HEIGHT = 0x2;
const uint DDSD_WIDTH = 0x4;
const uint DDSD_PITCH = 0x8;
const uint DDSD_LINEARSIZE = 0x80000;
const uint DDSD_REQ = (
DDSD_HEIGHT | DDSD_WIDTH
);
const uint DDSCAPS_MIPMAP = 0x400000;
const uint DDSCAPS_TEXTURE = 0x1000;
const uint DDSCAPS2_CUBEMAP = 0x200;
const uint DDPF_FOURCC = 0x4;
const uint DDPF_RGB = 0x40;
const uint FOURCC_DXT1 = 0x31545844;
const uint FOURCC_DXT3 = 0x33545844;
const uint FOURCC_DXT5 = 0x35545844;
const uint FOURCC_DX10 = 0x30315844;
const uint pitchAndLinear = (
DDSD_PITCH | DDSD_LINEARSIZE
);
// File should start with 'DDS '
if (reader.ReadUInt32() != DDS_MAGIC)
{
throw new NotSupportedException("Not a DDS!");
}
// Texture info
uint size = reader.ReadUInt32();
if (size != DDS_HEADERSIZE)
{
throw new NotSupportedException("Invalid DDS header!");
}
uint flags = reader.ReadUInt32();
if ((flags & DDSD_REQ) != DDSD_REQ)
{
throw new NotSupportedException("Invalid DDS flags!");
}
if ((flags & pitchAndLinear) == pitchAndLinear)
{
throw new NotSupportedException("Invalid DDS flags!");
}
height = reader.ReadInt32();
width = reader.ReadInt32();
reader.ReadUInt32(); // dwPitchOrLinearSize, unused
reader.ReadUInt32(); // dwDepth, unused
levels = reader.ReadInt32();
// "Reserved"
reader.ReadBytes(4 * 11);
// Format info
uint formatSize = reader.ReadUInt32();
if (formatSize != DDS_PIXFMTSIZE)
{
throw new NotSupportedException("Bogus PIXFMTSIZE!");
}
uint formatFlags = reader.ReadUInt32();
uint formatFourCC = reader.ReadUInt32();
uint formatRGBBitCount = reader.ReadUInt32();
uint formatRBitMask = reader.ReadUInt32();
uint formatGBitMask = reader.ReadUInt32();
uint formatBBitMask = reader.ReadUInt32();
uint formatABitMask = reader.ReadUInt32();
// dwCaps "stuff"
uint caps = reader.ReadUInt32();
if ((caps & DDSCAPS_TEXTURE) == 0)
{
throw new NotSupportedException("Not a texture!");
}
isCube = false;
uint caps2 = reader.ReadUInt32();
if (caps2 != 0)
{
if ((caps2 & DDSCAPS2_CUBEMAP) == DDSCAPS2_CUBEMAP)
{
isCube = true;
}
else
{
throw new NotSupportedException("Invalid caps2!");
}
}
reader.ReadUInt32(); // dwCaps3, unused
reader.ReadUInt32(); // dwCaps4, unused
// "Reserved"
reader.ReadUInt32();
// Mipmap sanity check
if ((caps & DDSCAPS_MIPMAP) != DDSCAPS_MIPMAP)
{
levels = 1;
}
// Determine texture format
if ((formatFlags & DDPF_FOURCC) == DDPF_FOURCC)
{
switch (formatFourCC)
{
case 0x71: // D3DFMT_A16B16G16R16F
format = TextureFormat.R16G16B16A16_SFLOAT;
break;
case 0x74: // D3DFMT_A32B32G32R32F
format = TextureFormat.R32G32B32A32_SFLOAT;
break;
case FOURCC_DXT1:
format = TextureFormat.BC1;
break;
case FOURCC_DXT3:
format = TextureFormat.BC2;
break;
case FOURCC_DXT5:
format = TextureFormat.BC3;
break;
case FOURCC_DX10:
// If the fourCC is DX10, there is an extra header with additional format information.
uint dxgiFormat = reader.ReadUInt32();
// These values are taken from the DXGI_FORMAT enum.
switch (dxgiFormat)
{
case 2:
format = TextureFormat.R32G32B32A32_SFLOAT;
break;
case 10:
format = TextureFormat.R16G16B16A16_SFLOAT;
break;
case 71:
format = TextureFormat.BC1;
break;
case 74:
format = TextureFormat.BC2;
break;
case 77:
format = TextureFormat.BC3;
break;
case 98:
format = TextureFormat.BC7;
break;
default:
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
uint resourceDimension = reader.ReadUInt32();
// These values are taken from the D3D10_RESOURCE_DIMENSION enum.
switch (resourceDimension)
{
case 0: // Unknown
case 1: // Buffer
throw new NotSupportedException(
"Unsupported DDS texture format"
);
default:
break;
}
/*
* This flag seemingly only indicates if the texture is a cube map.
* This is already determined above. Cool!
*/
uint miscFlag = reader.ReadUInt32();
/*
* Indicates the number of elements in the texture array.
* We don't support texture arrays so just throw if it's greater than 1.
*/
uint arraySize = reader.ReadUInt32();
if (arraySize > 1)
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
reader.ReadUInt32(); // reserved
break;
default:
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
}
else if ((formatFlags & DDPF_RGB) == DDPF_RGB)
{
if ( formatRGBBitCount != 32 ||
formatRBitMask != 0x00FF0000 ||
formatGBitMask != 0x0000FF00 ||
formatBBitMask != 0x000000FF ||
formatABitMask != 0xFF000000 )
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
format = TextureFormat.B8G8R8A8;
}
else
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
}
private static int CalculateDDSLevelSize(
int width,
int height,
TextureFormat format
) {
if (format == TextureFormat.R8G8B8A8)
{
return (((width * 32) + 7) / 8) * height;
}
else if (format == TextureFormat.R16G16B16A16_SFLOAT)
{
return (((width * 64) + 7) / 8) * height;
}
else if (format == TextureFormat.R32G32B32A32_SFLOAT)
{
return (((width * 128) + 7) / 8) * height;
}
else
{
int blockSize = 16;
if (format == TextureFormat.BC1)
{
blockSize = 8;
}
width = System.Math.Max(width, 1);
height = System.Math.Max(height, 1);
return (
((width + 3) / 4) *
((height + 3) / 4) *
blockSize
);
}
}
/// <summary>
/// Asynchronously saves RGBA or BGRA pixel data to a file in PNG format. <br/>
/// Warning: this is expensive and will block to wait for data download from GPU! <br/>
/// You can avoid blocking by calling this method from a thread.
/// </summary>
public unsafe void SavePNG(string path)
{
#if DEBUG
if (Format != TextureFormat.R8G8B8A8 && Format != TextureFormat.B8G8R8A8)
{
throw new ArgumentException("Texture format must be RGBA or BGRA!", "format");
}
#endif
var buffer = new Buffer(Device, 0, Width * Height * 4); // this creates garbage... oh well
// immediately request the data copy
var commandBuffer = Device.AcquireCommandBuffer();
commandBuffer.CopyTextureToBuffer(this, buffer);
var fence = Device.SubmitAndAcquireFence(commandBuffer);
var byteCount = buffer.Size;
var pixelsPtr = NativeMemory.Alloc((nuint) byteCount);
var pixelsSpan = new Span<byte>(pixelsPtr, (int) byteCount);
Device.WaitForFences(fence); // make sure the data transfer is done...
Device.ReleaseFence(fence); // and then release the fence
buffer.GetData(pixelsSpan);
if (Format == TextureFormat.B8G8R8A8)
{
var rgbaPtr = NativeMemory.Alloc((nuint) byteCount);
var rgbaSpan = new Span<byte>(rgbaPtr, (int) byteCount);
for (var i = 0; i < byteCount; i += 4)
{
rgbaSpan[i] = pixelsSpan[i + 2];
rgbaSpan[i + 1] = pixelsSpan[i + 1];
rgbaSpan[i + 2] = pixelsSpan[i];
rgbaSpan[i + 3] = pixelsSpan[i + 3];
}
Refresh.Refresh_Image_SavePNG(path, (nint) rgbaPtr, (int) Width, (int) Height);
NativeMemory.Free((void*) rgbaPtr);
}
else
{
fixed (byte* ptr = pixelsSpan)
{
Refresh.Refresh_Image_SavePNG(path, (nint) ptr, (int) Width, (int) Height);
}
}
NativeMemory.Free(pixelsPtr);
}
public static uint BytesPerPixel(TextureFormat format) public static uint BytesPerPixel(TextureFormat format)
{ {
switch (format) switch (format)
@ -663,10 +229,8 @@ namespace MoonWorks.Graphics
case TextureFormat.R8G8_SNORM: case TextureFormat.R8G8_SNORM:
case TextureFormat.R8G8_UINT: case TextureFormat.R8G8_UINT:
case TextureFormat.R16_UINT: case TextureFormat.R16_UINT:
case TextureFormat.D16: case TextureFormat.D16_UNORM:
return 2; return 2;
case TextureFormat.D16S8:
return 3;
case TextureFormat.R8G8B8A8: case TextureFormat.R8G8B8A8:
case TextureFormat.B8G8R8A8: case TextureFormat.B8G8R8A8:
case TextureFormat.R32_SFLOAT: case TextureFormat.R32_SFLOAT:
@ -676,9 +240,10 @@ namespace MoonWorks.Graphics
case TextureFormat.A2R10G10B10: case TextureFormat.A2R10G10B10:
case TextureFormat.R8G8B8A8_UINT: case TextureFormat.R8G8B8A8_UINT:
case TextureFormat.R16G16_UINT: case TextureFormat.R16G16_UINT:
case TextureFormat.D32: case TextureFormat.D24_UNORM_S8_UINT:
case TextureFormat.D32_SFLOAT:
return 4; return 4;
case TextureFormat.D32S8: case TextureFormat.D32_SFLOAT_S8_UINT:
return 5; return 5;
case TextureFormat.R16G16B16A16_SFLOAT: case TextureFormat.R16G16B16A16_SFLOAT:
case TextureFormat.R16G16B16A16: case TextureFormat.R16G16B16A16:
@ -697,6 +262,21 @@ namespace MoonWorks.Graphics
} }
} }
public static uint TexelSize(TextureFormat format)
{
switch (format)
{
case TextureFormat.BC2:
case TextureFormat.BC3:
case TextureFormat.BC7:
return 16;
case TextureFormat.BC1:
return 8;
default:
return 1;
}
}
public static uint BlockSizeSquared(TextureFormat format) public static uint BlockSizeSquared(TextureFormat format)
{ {
switch (format) switch (format)
@ -729,15 +309,19 @@ namespace MoonWorks.Graphics
case TextureFormat.R16_UINT: case TextureFormat.R16_UINT:
case TextureFormat.R16G16_UINT: case TextureFormat.R16G16_UINT:
case TextureFormat.R16G16B16A16_UINT: case TextureFormat.R16G16B16A16_UINT:
case TextureFormat.D16: case TextureFormat.D16_UNORM:
case TextureFormat.D32: case TextureFormat.D24_UNORM:
case TextureFormat.D16S8: case TextureFormat.D24_UNORM_S8_UINT:
case TextureFormat.D32S8: case TextureFormat.D32_SFLOAT:
case TextureFormat.D32_SFLOAT_S8_UINT:
return 1; return 1;
default: default:
Logger.LogError("Texture format not recognized!"); Logger.LogError("Texture format not recognized!");
return 0; return 0;
} }
} }
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
public static implicit operator TextureRegion(Texture t) => new TextureRegion(t);
} }
} }

View File

@ -0,0 +1,161 @@
using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics
{
public unsafe class TransferBuffer : RefreshResource
{
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseTransferBuffer;
/// <summary>
/// Size in bytes.
/// </summary>
public uint Size { get; }
/// <summary>
/// Creates a buffer of requested size given a type and element count.
/// </summary>
/// <typeparam name="T">The type that the buffer will contain.</typeparam>
/// <param name="device">The GraphicsDevice.</param>
/// <param name="elementCount">How many elements of type T the buffer will contain.</param>
/// <returns></returns>
public unsafe static TransferBuffer Create<T>(
GraphicsDevice device,
TransferUsage usage,
TransferBufferMapFlags mapFlags,
uint elementCount
) where T : unmanaged
{
return new TransferBuffer(
device,
usage,
mapFlags,
(uint) Marshal.SizeOf<T>() * elementCount
);
}
/// <summary>
/// Creates a TransferBuffer.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="sizeInBytes">The length of the buffer. Cannot be resized.</param>
/// <param name="usage">Whether this will be used to upload buffers or textures.</param>
public TransferBuffer(
GraphicsDevice device,
TransferUsage usage,
TransferBufferMapFlags mapFlags,
uint sizeInBytes
) : base(device)
{
Handle = Refresh.Refresh_CreateTransferBuffer(
device.Handle,
(Refresh.TransferUsage) usage,
(Refresh.TransferBufferMapFlags) mapFlags,
sizeInBytes
);
Size = sizeInBytes;
}
/// <summary>
/// Immediately copies data from a Span to the TransferBuffer.
/// Returns the length of the copy in bytes.
///
/// If cycle is set to true and this TransferBuffer was used in an Upload command,
/// that command will still use the corret data at the cost of increased memory usage.
///
/// If cycle is set to false, the data will be overwritten immediately,
/// which could cause a data race.
/// </summary>
public unsafe uint SetData<T>(
Span<T> data,
uint bufferOffsetInBytes,
bool cycle
) where T : unmanaged
{
var elementSize = Marshal.SizeOf<T>();
var dataLengthInBytes = (uint) (elementSize * data.Length);
#if DEBUG
AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes);
#endif
fixed (T* dataPtr = data)
{
Refresh.Refresh_SetTransferData(
Device.Handle,
(nint) dataPtr,
Handle,
new Refresh.BufferCopy
{
SourceOffset = 0,
DestinationOffset = bufferOffsetInBytes,
Size = dataLengthInBytes
},
Conversions.BoolToInt(cycle)
);
}
return dataLengthInBytes;
}
/// <summary>
/// Immediately copies data from a Span to the TransferBuffer.
/// Returns the length of the copy in bytes.
///
/// If cycle is set to true and this TransferBuffer was used in an Upload command,
/// that command will still use the corret data at the cost of increased memory usage.
///
/// If cycle is set to false, the data will be overwritten immediately,
/// which could cause a data race.
/// </summary>
public unsafe uint SetData<T>(
Span<T> data,
bool cycle
) where T : unmanaged
{
return SetData(data, 0, cycle);
}
/// <summary>
/// Immediately copies data from the TransferBuffer into a Span.
/// </summary>
public unsafe void GetData<T>(
Span<T> data,
uint bufferOffsetInBytes = 0
) where T : unmanaged
{
var elementSize = Marshal.SizeOf<T>();
var dataLengthInBytes = (uint) (elementSize * data.Length);
#if DEBUG
AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes);
#endif
fixed (T* dataPtr = data)
{
Refresh.Refresh_GetTransferData(
Device.Handle,
Handle,
(nint) dataPtr,
new Refresh.BufferCopy
{
SourceOffset = bufferOffsetInBytes,
DestinationOffset = 0,
Size = dataLengthInBytes
}
);
}
}
#if DEBUG
private void AssertBufferBoundsCheck(uint bufferLengthInBytes, uint offsetInBytes, uint copyLengthInBytes)
{
if (copyLengthInBytes > bufferLengthInBytes + offsetInBytes)
{
throw new InvalidOperationException($"Data overflow! Transfer buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}");
}
}
#endif
}
}

View File

@ -110,14 +110,14 @@ namespace MoonWorks.Graphics
{ {
return new Refresh.ColorAttachmentBlendState return new Refresh.ColorAttachmentBlendState
{ {
blendEnable = Conversions.BoolToByte(BlendEnable), BlendEnable = Conversions.BoolToInt(BlendEnable),
alphaBlendOp = (Refresh.BlendOp) AlphaBlendOp, AlphaBlendOp = (Refresh.BlendOp) AlphaBlendOp,
colorBlendOp = (Refresh.BlendOp) ColorBlendOp, ColorBlendOp = (Refresh.BlendOp) ColorBlendOp,
colorWriteMask = (Refresh.ColorComponentFlags) ColorWriteMask, ColorWriteMask = (Refresh.ColorComponentFlags) ColorWriteMask,
destinationAlphaBlendFactor = (Refresh.BlendFactor) DestinationAlphaBlendFactor, DestinationAlphaBlendFactor = (Refresh.BlendFactor) DestinationAlphaBlendFactor,
destinationColorBlendFactor = (Refresh.BlendFactor) DestinationColorBlendFactor, DestinationColorBlendFactor = (Refresh.BlendFactor) DestinationColorBlendFactor,
sourceAlphaBlendFactor = (Refresh.BlendFactor) SourceAlphaBlendFactor, SourceAlphaBlendFactor = (Refresh.BlendFactor) SourceAlphaBlendFactor,
sourceColorBlendFactor = (Refresh.BlendFactor) SourceColorBlendFactor SourceColorBlendFactor = (Refresh.BlendFactor) SourceColorBlendFactor
}; };
} }
} }

View File

@ -1,50 +0,0 @@
using System.Runtime.InteropServices;
namespace MoonWorks.Graphics
{
/// <summary>
/// Information that the compute pipeline needs about a compute shader.
/// </summary>
public struct ComputeShaderInfo
{
public ShaderModule ShaderModule;
public string EntryPointName;
public uint UniformBufferSize;
public uint BufferBindingCount;
public uint ImageBindingCount;
public unsafe static ComputeShaderInfo Create<T>(
ShaderModule shaderModule,
string entryPointName,
uint bufferBindingCount,
uint imageBindingCount
) where T : unmanaged
{
return new ComputeShaderInfo
{
ShaderModule = shaderModule,
EntryPointName = entryPointName,
UniformBufferSize = (uint) Marshal.SizeOf<T>(),
BufferBindingCount = bufferBindingCount,
ImageBindingCount = imageBindingCount
};
}
public static ComputeShaderInfo Create(
ShaderModule shaderModule,
string entryPointName,
uint bufferBindingCount,
uint imageBindingCount
)
{
return new ComputeShaderInfo
{
ShaderModule = shaderModule,
EntryPointName = entryPointName,
UniformBufferSize = 0,
BufferBindingCount = bufferBindingCount,
ImageBindingCount = imageBindingCount
};
}
}
}

View File

@ -1,4 +1,6 @@
namespace MoonWorks.Graphics using RefreshCS;
namespace MoonWorks.Graphics
{ {
/// <summary> /// <summary>
/// Determines how data is written to and read from the depth/stencil buffer. /// Determines how data is written to and read from the depth/stencil buffer.
@ -11,15 +13,30 @@
public bool DepthTestEnable; public bool DepthTestEnable;
/// <summary> /// <summary>
/// Describes the stencil operation for back-facing primitives. /// Describes the back-face stencil operation.
/// </summary> /// </summary>
public StencilOpState BackStencilState; public StencilOpState BackStencilState;
/// <summary> /// <summary>
/// Describes the stencil operation for front-facing primitives. /// Describes the front-face stencil operation.
/// </summary> /// </summary>
public StencilOpState FrontStencilState; public StencilOpState FrontStencilState;
/// <summary>
/// The compare mask for stencil ops.
/// </summary>
public uint CompareMask;
/// <summary>
/// The write mask for stencil ops.
/// </summary>
public uint WriteMask;
/// <summary>
/// The stencil reference value.
/// </summary>
public uint Reference;
/// <summary> /// <summary>
/// The comparison operator used in the depth test. /// The comparison operator used in the depth test.
/// </summary> /// </summary>
@ -75,5 +92,24 @@
DepthBoundsTestEnable = false, DepthBoundsTestEnable = false,
StencilTestEnable = false StencilTestEnable = false
}; };
public Refresh.DepthStencilState ToRefresh()
{
return new Refresh.DepthStencilState
{
DepthTestEnable = Conversions.BoolToInt(DepthTestEnable),
BackStencilState = BackStencilState.ToRefresh(),
FrontStencilState = FrontStencilState.ToRefresh(),
CompareMask = CompareMask,
WriteMask = WriteMask,
Reference = Reference,
CompareOp = (Refresh.CompareOp) CompareOp,
DepthBoundsTestEnable = Conversions.BoolToInt(DepthBoundsTestEnable),
DepthWriteEnable = Conversions.BoolToInt(DepthWriteEnable),
MinDepthBounds = MinDepthBounds,
MaxDepthBounds = MaxDepthBounds,
StencilTestEnable = Conversions.BoolToInt(StencilTestEnable)
};
}
} }
} }

View File

@ -14,7 +14,7 @@ namespace MoonWorks.Graphics
) { ) {
ColorAttachmentDescriptions = colorAttachmentDescriptions; ColorAttachmentDescriptions = colorAttachmentDescriptions;
HasDepthStencilAttachment = false; HasDepthStencilAttachment = false;
DepthStencilFormat = TextureFormat.D16; DepthStencilFormat = TextureFormat.D16_UNORM;
} }
public GraphicsPipelineAttachmentInfo( public GraphicsPipelineAttachmentInfo(

View File

@ -1,18 +0,0 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// All of the information that is used to create a GraphicsPipeline.
/// </summary>
public struct GraphicsPipelineCreateInfo
{
public DepthStencilState DepthStencilState;
public GraphicsShaderInfo VertexShaderInfo;
public GraphicsShaderInfo FragmentShaderInfo;
public MultisampleState MultisampleState;
public RasterizerState RasterizerState;
public PrimitiveType PrimitiveType;
public VertexInputState VertexInputState;
public GraphicsPipelineAttachmentInfo AttachmentInfo;
public BlendConstants BlendConstants;
}
}

View File

@ -1,44 +0,0 @@
using System.Runtime.InteropServices;
namespace MoonWorks.Graphics
{
/// <summary>
/// Information that the pipeline needs about a graphics shader.
/// </summary>
public struct GraphicsShaderInfo
{
public ShaderModule ShaderModule;
public string EntryPointName;
public uint UniformBufferSize;
public uint SamplerBindingCount;
public unsafe static GraphicsShaderInfo Create<T>(
ShaderModule shaderModule,
string entryPointName,
uint samplerBindingCount
) where T : unmanaged
{
return new GraphicsShaderInfo
{
ShaderModule = shaderModule,
EntryPointName = entryPointName,
UniformBufferSize = (uint) Marshal.SizeOf<T>(),
SamplerBindingCount = samplerBindingCount
};
}
public static GraphicsShaderInfo Create(
ShaderModule shaderModule,
string entryPointName,
uint samplerBindingCount
) {
return new GraphicsShaderInfo
{
ShaderModule = shaderModule,
EntryPointName = entryPointName,
UniformBufferSize = 0,
SamplerBindingCount = samplerBindingCount
};
}
}
}

View File

@ -1,4 +1,6 @@
namespace MoonWorks.Graphics using RefreshCS;
namespace MoonWorks.Graphics
{ {
/// <summary> /// <summary>
/// Specifies how many samples should be used in rasterization. /// Specifies how many samples should be used in rasterization.
@ -21,5 +23,14 @@
MultisampleCount = sampleCount; MultisampleCount = sampleCount;
SampleMask = sampleMask; SampleMask = sampleMask;
} }
public Refresh.MultisampleState ToRefresh()
{
return new Refresh.MultisampleState
{
MultisampleCount = (Refresh.SampleCount) MultisampleCount,
SampleMask = SampleMask
};
}
} }
} }

View File

@ -1,5 +1,7 @@
namespace MoonWorks.Graphics using RefreshCS;
{
namespace MoonWorks.Graphics;
/// <summary> /// <summary>
/// Specifies how the rasterizer should be configured for a graphics pipeline. /// Specifies how the rasterizer should be configured for a graphics pipeline.
/// </summary> /// </summary>
@ -103,5 +105,18 @@
FillMode = FillMode.Line, FillMode = FillMode.Line,
DepthBiasEnable = false DepthBiasEnable = false
}; };
public Refresh.RasterizerState ToRefresh()
{
return new Refresh.RasterizerState
{
CullMode = (Refresh.CullMode) CullMode,
DepthBiasClamp = DepthBiasClamp,
DepthBiasConstantFactor = DepthBiasConstantFactor,
DepthBiasEnable = Conversions.BoolToInt(DepthBiasEnable),
DepthBiasSlopeFactor = DepthBiasSlopeFactor,
FillMode = (Refresh.FillMode) FillMode,
FrontFace = (Refresh.FrontFace) FrontFace
};
} }
} }

View File

@ -1,7 +1,7 @@
using RefreshCS; using RefreshCS;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics;
{
/// <summary> /// <summary>
/// All of the information that is used to create a sampler. /// All of the information that is used to create a sampler.
/// </summary> /// </summary>
@ -150,25 +150,24 @@ namespace MoonWorks.Graphics
MaxLod = 1000 MaxLod = 1000
}; };
public Refresh.SamplerStateCreateInfo ToRefreshSamplerStateCreateInfo() public Refresh.SamplerCreateInfo ToRefresh()
{ {
return new Refresh.SamplerStateCreateInfo return new Refresh.SamplerCreateInfo
{ {
minFilter = (Refresh.Filter) MinFilter, MinFilter = (Refresh.Filter) MinFilter,
magFilter = (Refresh.Filter) MagFilter, MagFilter = (Refresh.Filter) MagFilter,
mipmapMode = (Refresh.SamplerMipmapMode) MipmapMode, MipmapMode = (Refresh.SamplerMipmapMode) MipmapMode,
addressModeU = (Refresh.SamplerAddressMode) AddressModeU, AddressModeU = (Refresh.SamplerAddressMode) AddressModeU,
addressModeV = (Refresh.SamplerAddressMode) AddressModeV, AddressModeV = (Refresh.SamplerAddressMode) AddressModeV,
addressModeW = (Refresh.SamplerAddressMode) AddressModeW, AddressModeW = (Refresh.SamplerAddressMode) AddressModeW,
mipLodBias = MipLodBias, MipLodBias = MipLodBias,
anisotropyEnable = Conversions.BoolToByte(AnisotropyEnable), AnisotropyEnable = Conversions.BoolToInt(AnisotropyEnable),
maxAnisotropy = MaxAnisotropy, MaxAnisotropy = MaxAnisotropy,
compareEnable = Conversions.BoolToByte(CompareEnable), CompareEnable = Conversions.BoolToInt(CompareEnable),
compareOp = (Refresh.CompareOp) CompareOp, CompareOp = (Refresh.CompareOp) CompareOp,
minLod = MinLod, MinLod = MinLod,
maxLod = MaxLod, MaxLod = MaxLod,
borderColor = (Refresh.BorderColor) BorderColor BorderColor = (Refresh.BorderColor) BorderColor
}; };
} }
} }
}

View File

@ -11,23 +11,25 @@ namespace MoonWorks.Graphics
public uint Height; public uint Height;
public uint Depth; public uint Depth;
public bool IsCube; public bool IsCube;
public uint LayerCount;
public uint LevelCount; public uint LevelCount;
public SampleCount SampleCount; public SampleCount SampleCount;
public TextureFormat Format; public TextureFormat Format;
public TextureUsageFlags UsageFlags; public TextureUsageFlags UsageFlags;
public Refresh.TextureCreateInfo ToRefreshTextureCreateInfo() public Refresh.TextureCreateInfo ToRefresh()
{ {
return new Refresh.TextureCreateInfo return new Refresh.TextureCreateInfo
{ {
width = Width, Width = Width,
height = Height, Height = Height,
depth = Depth, Depth = Depth,
isCube = Conversions.BoolToByte(IsCube), IsCube = Conversions.BoolToInt(IsCube),
levelCount = LevelCount, LayerCount = LayerCount,
sampleCount = (Refresh.SampleCount) SampleCount, LevelCount = LevelCount,
format = (Refresh.TextureFormat) Format, SampleCount = (Refresh.SampleCount) SampleCount,
usageFlags = (Refresh.TextureUsageFlags) UsageFlags Format = (Refresh.TextureFormat) Format,
UsageFlags = (Refresh.TextureUsageFlags) UsageFlags
}; };
} }
} }

Binary file not shown.

View File

@ -5,5 +5,5 @@ layout(location = 0) out vec2 outTexCoord;
void main() void main()
{ {
outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(outTexCoord * 2.0 - 1.0, 0.0, 1.0); gl_Position = vec4(outTexCoord * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0);
} }

View File

@ -1,13 +1,13 @@
#version 450 #version 450
layout(set = 1, binding = 0) uniform sampler2D msdf;
layout(location = 0) in vec2 inTexCoord; layout(location = 0) in vec2 inTexCoord;
layout(location = 1) in vec4 inColor; layout(location = 1) in vec4 inColor;
layout(location = 0) out vec4 outColor; layout(location = 0) out vec4 outColor;
layout(binding = 0, set = 3) uniform UBO layout(set = 2, binding = 0) uniform sampler2D msdf;
layout(set = 3, binding = 0) uniform UBO
{ {
float pxRange; float pxRange;
} ubo; } ubo;

View File

@ -7,7 +7,7 @@ layout(location = 2) in vec4 inColor;
layout(location = 0) out vec2 outTexCoord; layout(location = 0) out vec2 outTexCoord;
layout(location = 1) out vec4 outColor; layout(location = 1) out vec4 outColor;
layout(binding = 0, set = 2) uniform UBO layout(set = 1, binding = 0) uniform UBO
{ {
mat4 ViewProjection; mat4 ViewProjection;
} ubo; } ubo;

View File

@ -9,9 +9,9 @@ layout(location = 0) in vec2 TexCoord;
layout(location = 0) out vec4 FragColor; layout(location = 0) out vec4 FragColor;
layout(binding = 0, set = 1) uniform sampler2D YSampler; layout(set = 2, binding = 0) uniform sampler2D YSampler;
layout(binding = 1, set = 1) uniform sampler2D USampler; layout(set = 2, binding = 1) uniform sampler2D USampler;
layout(binding = 2, set = 1) uniform sampler2D VSampler; layout(set = 2, binding = 2) uniform sampler2D VSampler;
/* More info about colorspace conversion: /* More info about colorspace conversion:
* http://www.equasys.de/colorconversion.html * http://www.equasys.de/colorconversion.html

View File

@ -0,0 +1,46 @@
using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// A texture region specifies a subregion of a texture.
/// These are used by copy commands.
/// </summary>
public struct TextureRegion
{
public TextureSlice TextureSlice;
public uint X;
public uint Y;
public uint Z;
public uint Width;
public uint Height;
public uint Depth;
public uint Size => (Width * Height * Depth * Texture.BytesPerPixel(TextureSlice.Texture.Format) / Texture.BlockSizeSquared(TextureSlice.Texture.Format)) >> (int) TextureSlice.MipLevel;
public TextureRegion(Texture texture)
{
TextureSlice = new TextureSlice(texture);
X = 0;
Y = 0;
Z = 0;
Width = texture.Width;
Height = texture.Height;
Depth = texture.Depth;
}
public Refresh.TextureRegion ToRefresh()
{
return new Refresh.TextureRegion
{
TextureSlice = TextureSlice.ToRefresh(),
X = X,
Y = Y,
Z = Z,
W = Width,
H = Height,
D = Depth
};
}
}
}

View File

@ -1,57 +1,31 @@
using RefreshCS; using RefreshCS;
namespace MoonWorks.Graphics;
namespace MoonWorks.Graphics
{
/// <summary> /// <summary>
/// A texture slice specifies a subregion of a texture. /// A texture slice specifies a subresource of a texture.
/// Many operations can use texture slices in place of textures for the sake of convenience.
/// </summary> /// </summary>
public struct TextureSlice public struct TextureSlice
{ {
public Texture Texture { get; } public Texture Texture;
public Rect Rectangle { get; } public uint MipLevel;
public uint Depth { get; } public uint Layer;
public uint Layer { get; }
public uint Level { get; }
public uint Size => (uint) (Rectangle.W * Rectangle.H * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format)); public uint Size => (Texture.Width * Texture.Height * Texture.Depth * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format)) >> (int) MipLevel;
public TextureSlice(Texture texture) public TextureSlice(Texture texture)
{ {
Texture = texture; Texture = texture;
Rectangle = new Rect MipLevel = 0;
{
X = 0,
Y = 0,
W = (int) texture.Width,
H = (int) texture.Height
};
Depth = 0;
Layer = 0; Layer = 0;
Level = 0;
} }
public TextureSlice(Texture texture, Rect rectangle, uint depth = 0, uint layer = 0, uint level = 0) public Refresh.TextureSlice ToRefresh()
{ {
Texture = texture; return new Refresh.TextureSlice
Rectangle = rectangle;
Depth = depth;
Layer = layer;
Level = level;
}
public Refresh.TextureSlice ToRefreshTextureSlice()
{ {
Refresh.TextureSlice textureSlice = new Refresh.TextureSlice Texture = Texture.Handle,
{ MipLevel = MipLevel,
texture = Texture.Handle, Layer = Layer
rectangle = Rectangle.ToRefresh(),
depth = Depth,
layer = Layer,
level = Level
}; };
return textureSlice;
}
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System;
using RefreshCS;
namespace MoonWorks namespace MoonWorks
{ {
@ -9,19 +8,6 @@ namespace MoonWorks
public static Action<string> LogWarn = LogWarnDefault; public static Action<string> LogWarn = LogWarnDefault;
public static Action<string> LogError = LogErrorDefault; public static Action<string> LogError = LogErrorDefault;
private static RefreshCS.Refresh.Refresh_LogFunc LogInfoFunc = RefreshLogInfo;
private static RefreshCS.Refresh.Refresh_LogFunc LogWarnFunc = RefreshLogWarn;
private static RefreshCS.Refresh.Refresh_LogFunc LogErrorFunc = RefreshLogError;
internal static void Initialize()
{
Refresh.Refresh_HookLogFunctions(
LogInfoFunc,
LogWarnFunc,
LogErrorFunc
);
}
private static void LogInfoDefault(string str) private static void LogInfoDefault(string str)
{ {
Console.ForegroundColor = ConsoleColor.Green; Console.ForegroundColor = ConsoleColor.Green;

View File

@ -1054,7 +1054,7 @@ namespace MoonWorks.Math.Float
result.M13 = 0.0f; result.M13 = 0.0f;
result.M14 = 0.0f; result.M14 = 0.0f;
result.M21 = 0.0f; result.M21 = 0.0f;
result.M22 = (float) (2.0 / ((double) bottom - (double) top)); result.M22 = (float) (2.0 / ((double) top - (double) bottom));
result.M23 = 0.0f; result.M23 = 0.0f;
result.M24 = 0.0f; result.M24 = 0.0f;
result.M31 = 0.0f; result.M31 = 0.0f;
@ -1062,11 +1062,11 @@ namespace MoonWorks.Math.Float
result.M33 = (float) (1.0 / ((double) zNearPlane - (double) zFarPlane)); result.M33 = (float) (1.0 / ((double) zNearPlane - (double) zFarPlane));
result.M34 = 0.0f; result.M34 = 0.0f;
result.M41 = (float) ( result.M41 = (float) (
-((double) left + (double) right) / ((double) left + (double) right) /
((double) right - (double) left) ((double) left - (double) right)
); );
result.M42 = (float) ( result.M42 = (float) (
-((double) top + (double) bottom) / ((double) top + (double) bottom) /
((double) bottom - (double) top) ((double) bottom - (double) top)
); );
result.M43 = (float) ( result.M43 = (float) (
@ -1126,7 +1126,7 @@ namespace MoonWorks.Math.Float
} }
result.M11 = (2f * nearPlaneDistance) / width; result.M11 = (2f * nearPlaneDistance) / width;
result.M12 = result.M13 = result.M14 = 0f; result.M12 = result.M13 = result.M14 = 0f;
result.M22 = -(2f * nearPlaneDistance) / height; result.M22 = (2f * nearPlaneDistance) / height;
result.M21 = result.M23 = result.M24 = 0f; result.M21 = result.M23 = result.M24 = 0f;
result.M33 = farPlaneDistance / (nearPlaneDistance - farPlaneDistance); result.M33 = farPlaneDistance / (nearPlaneDistance - farPlaneDistance);
result.M31 = result.M32 = 0f; result.M31 = result.M32 = 0f;
@ -1199,7 +1199,7 @@ namespace MoonWorks.Math.Float
float num = 1f / ((float) System.Math.Tan((double) (fieldOfView * 0.5f))); float num = 1f / ((float) System.Math.Tan((double) (fieldOfView * 0.5f)));
result.M11 = num / aspectRatio; result.M11 = num / aspectRatio;
result.M12 = result.M13 = result.M14 = 0; result.M12 = result.M13 = result.M14 = 0;
result.M22 = -num; result.M22 = num;
result.M21 = result.M23 = result.M24 = 0; result.M21 = result.M23 = result.M24 = 0;
result.M31 = result.M32 = 0f; result.M31 = result.M32 = 0f;
result.M33 = farPlaneDistance / (nearPlaneDistance - farPlaneDistance); result.M33 = farPlaneDistance / (nearPlaneDistance - farPlaneDistance);
@ -1277,7 +1277,7 @@ namespace MoonWorks.Math.Float
} }
result.M11 = (2f * nearPlaneDistance) / (right - left); result.M11 = (2f * nearPlaneDistance) / (right - left);
result.M12 = result.M13 = result.M14 = 0; result.M12 = result.M13 = result.M14 = 0;
result.M22 = -(2f * nearPlaneDistance) / (top - bottom); result.M22 = (2f * nearPlaneDistance) / (top - bottom);
result.M21 = result.M23 = result.M24 = 0; result.M21 = result.M23 = result.M24 = 0;
result.M31 = (left + right) / (right - left); result.M31 = (left + right) / (right - left);
result.M32 = (top + bottom) / (top - bottom); result.M32 = (top + bottom) / (top - bottom);

View File

@ -11,10 +11,6 @@ namespace MoonWorks.Video
{ {
public string Filename { get; } public string Filename { get; }
// "double buffering" so we can loop without a stutter
internal VideoAV1Stream StreamA { get; }
internal VideoAV1Stream StreamB { get; }
public int Width => width; public int Width => width;
public int Height => height; public int Height => height;
public double FramesPerSecond { get; set; } public double FramesPerSecond { get; set; }
@ -67,23 +63,6 @@ namespace MoonWorks.Video
FramesPerSecond = framesPerSecond; FramesPerSecond = framesPerSecond;
Filename = filename; Filename = filename;
StreamA = new VideoAV1Stream(device, this);
StreamB = new VideoAV1Stream(device, this);
}
// NOTE: if you call this while a VideoPlayer is playing the stream, your program will explode
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
StreamA.Dispose();
StreamB.Dispose();
}
}
base.Dispose(disposing);
} }
} }
} }

View File

@ -1,13 +1,17 @@
using System; using System;
using System.Collections.Concurrent;
using System.Threading;
using MoonWorks.Graphics; using MoonWorks.Graphics;
namespace MoonWorks.Video namespace MoonWorks.Video
{ {
// Note that all public methods are async.
internal class VideoAV1Stream : GraphicsResource internal class VideoAV1Stream : GraphicsResource
{ {
public IntPtr Handle => handle; public IntPtr Handle => handle;
IntPtr handle; IntPtr handle;
public bool Loaded => handle != IntPtr.Zero;
public bool Ended => Dav1dfile.df_eos(Handle) == 1; public bool Ended => Dav1dfile.df_eos(Handle) == 1;
public IntPtr yDataHandle; public IntPtr yDataHandle;
@ -20,33 +24,89 @@ namespace MoonWorks.Video
public bool FrameDataUpdated { get; set; } public bool FrameDataUpdated { get; set; }
public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device) private BlockingCollection<Action> Actions = new BlockingCollection<Action>();
private bool Running = false;
Thread Thread;
public VideoAV1Stream(GraphicsDevice device) : base(device)
{ {
if (Dav1dfile.df_fopen(video.Filename, out handle) == 0) handle = IntPtr.Zero;
{
throw new Exception("Failed to open video file!"); Running = true;
Thread = new Thread(ThreadMain);
Thread.Start();
} }
Reset(); private void ThreadMain()
{
while (Running)
{
// block until we can take an action, then run it
var action = Actions.Take();
action.Invoke();
}
// shutting down...
while (Actions.TryTake(out var action))
{
action.Invoke();
}
}
public void Load(string filename)
{
Actions.Add(() => LoadHelper(filename));
} }
public void Reset() public void Reset()
{ {
lock (this) Actions.Add(ResetHelper);
{
Dav1dfile.df_reset(Handle);
ReadNextFrame();
}
} }
public void ReadNextFrame() public void ReadNextFrame()
{
Actions.Add(ReadNextFrameHelper);
}
public void Unload()
{
Actions.Add(UnloadHelper);
}
private void LoadHelper(string filename)
{
if (!Loaded)
{
if (Dav1dfile.df_fopen(filename, out handle) == 0)
{
Logger.LogError("Failed to load video file: " + filename);
throw new Exception("Failed to load video file!");
}
Reset();
}
}
private void ResetHelper()
{
if (Loaded)
{
Dav1dfile.df_reset(handle);
ReadNextFrame();
}
}
private void ReadNextFrameHelper()
{
if (Loaded && !Ended)
{ {
lock (this) lock (this)
{
if (!Ended)
{ {
if (Dav1dfile.df_readvideo( if (Dav1dfile.df_readvideo(
Handle, handle,
1, 1,
out var yDataHandle, out var yDataHandle,
out var uDataHandle, out var uDataHandle,
@ -70,11 +130,26 @@ namespace MoonWorks.Video
} }
} }
private void UnloadHelper()
{
if (Loaded)
{
Dav1dfile.df_close(handle);
handle = IntPtr.Zero;
}
}
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (!IsDisposed) if (!IsDisposed)
{ {
Dav1dfile.df_close(Handle); Unload();
Running = false;
if (disposing)
{
Thread.Join();
}
} }
base.Dispose(disposing); base.Dispose(disposing);
} }

View File

@ -16,18 +16,15 @@ namespace MoonWorks.Video
public float PlaybackSpeed { get; set; } = 1; public float PlaybackSpeed { get; set; } = 1;
private VideoAV1 Video = null; private VideoAV1 Video = null;
private VideoAV1Stream CurrentStream = null; private VideoAV1Stream Stream { get; }
private Task ReadNextFrameTask;
private Task ResetStreamATask;
private Task ResetStreamBTask;
private GraphicsDevice GraphicsDevice;
private Texture yTexture = null; private Texture yTexture = null;
private Texture uTexture = null; private Texture uTexture = null;
private Texture vTexture = null; private Texture vTexture = null;
private Sampler LinearSampler; private Sampler LinearSampler;
private TransferBuffer TransferBuffer;
private int currentFrame; private int currentFrame;
private Stopwatch timer; private Stopwatch timer;
@ -36,7 +33,7 @@ namespace MoonWorks.Video
public VideoPlayer(GraphicsDevice device) : base(device) public VideoPlayer(GraphicsDevice device) : base(device)
{ {
GraphicsDevice = device; Stream = new VideoAV1Stream(device);
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp); LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
@ -51,50 +48,50 @@ namespace MoonWorks.Video
{ {
if (Video != video) if (Video != video)
{ {
Stop(); Unload();
if (RenderTexture == null) if (RenderTexture == null)
{ {
RenderTexture = CreateRenderTexture(GraphicsDevice, video.Width, video.Height); RenderTexture = CreateRenderTexture(Device, video.Width, video.Height);
} }
if (yTexture == null) if (yTexture == null)
{ {
yTexture = CreateSubTexture(GraphicsDevice, video.Width, video.Height); yTexture = CreateSubTexture(Device, video.Width, video.Height);
} }
if (uTexture == null) if (uTexture == null)
{ {
uTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight); uTexture = CreateSubTexture(Device, video.UVWidth, video.UVHeight);
} }
if (vTexture == null) if (vTexture == null)
{ {
vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight); vTexture = CreateSubTexture(Device, video.UVWidth, video.UVHeight);
} }
if (video.Width != RenderTexture.Width || video.Height != RenderTexture.Height) if (video.Width != RenderTexture.Width || video.Height != RenderTexture.Height)
{ {
RenderTexture.Dispose(); RenderTexture.Dispose();
RenderTexture = CreateRenderTexture(GraphicsDevice, video.Width, video.Height); RenderTexture = CreateRenderTexture(Device, video.Width, video.Height);
} }
if (video.Width != yTexture.Width || video.Height != yTexture.Height) if (video.Width != yTexture.Width || video.Height != yTexture.Height)
{ {
yTexture.Dispose(); yTexture.Dispose();
yTexture = CreateSubTexture(GraphicsDevice, video.Width, video.Height); yTexture = CreateSubTexture(Device, video.Width, video.Height);
} }
if (video.UVWidth != uTexture.Width || video.UVHeight != uTexture.Height) if (video.UVWidth != uTexture.Width || video.UVHeight != uTexture.Height)
{ {
uTexture.Dispose(); uTexture.Dispose();
uTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight); uTexture = CreateSubTexture(Device, video.UVWidth, video.UVHeight);
} }
if (video.UVWidth != vTexture.Width || video.UVHeight != vTexture.Height) if (video.UVWidth != vTexture.Width || video.UVHeight != vTexture.Height)
{ {
vTexture.Dispose(); vTexture.Dispose();
vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight); vTexture = CreateSubTexture(Device, video.UVWidth, video.UVHeight);
} }
Video = video; Video = video;
@ -155,7 +152,7 @@ namespace MoonWorks.Video
lastTimestamp = 0; lastTimestamp = 0;
timeElapsed = 0; timeElapsed = 0;
InitializeDav1dStream(); ResetDav1dStreams();
State = VideoState.Stopped; State = VideoState.Stopped;
} }
@ -165,9 +162,21 @@ namespace MoonWorks.Video
/// </summary> /// </summary>
public void Unload() public void Unload()
{ {
Stop(); if (Video == null)
ResetStreamATask?.Wait(); {
ResetStreamBTask?.Wait(); return;
}
timer.Stop();
timer.Reset();
lastTimestamp = 0;
timeElapsed = 0;
State = VideoState.Stopped;
Stream.Unload();
Video = null; Video = null;
} }
@ -187,38 +196,26 @@ namespace MoonWorks.Video
int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond))); int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond)));
if (thisFrame > currentFrame) if (thisFrame > currentFrame)
{ {
if (CurrentStream.FrameDataUpdated) if (Stream.FrameDataUpdated)
{ {
UpdateRenderTexture(); UpdateRenderTexture();
CurrentStream.FrameDataUpdated = false; Stream.FrameDataUpdated = false;
} }
currentFrame = thisFrame; currentFrame = thisFrame;
ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame); Stream.ReadNextFrame();
ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
} }
if (CurrentStream.Ended) if (Stream.Ended)
{ {
timer.Stop(); timer.Stop();
timer.Reset(); timer.Reset();
var task = Task.Run(CurrentStream.Reset); Stream.Reset();
task.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
if (CurrentStream == Video.StreamA)
{
ResetStreamATask = task;
}
else
{
ResetStreamBTask = task;
}
if (Loop) if (Loop)
{ {
// Start over on the next stream! // Start over!
CurrentStream = (CurrentStream == Video.StreamA) ? Video.StreamB : Video.StreamA;
currentFrame = -1; currentFrame = -1;
timer.Start(); timer.Start();
} }
@ -231,40 +228,87 @@ namespace MoonWorks.Video
private void UpdateRenderTexture() private void UpdateRenderTexture()
{ {
lock (CurrentStream) uint uOffset;
uint vOffset;
uint yStride;
uint uvStride;
lock (Stream)
{ {
var commandBuffer = GraphicsDevice.AcquireCommandBuffer(); var ySpan = new Span<byte>((void*) Stream.yDataHandle, (int) Stream.yDataLength);
var uSpan = new Span<byte>((void*) Stream.uDataHandle, (int) Stream.uvDataLength);
var vSpan = new Span<byte>((void*) Stream.vDataHandle, (int) Stream.uvDataLength);
commandBuffer.SetTextureDataYUV( if (TransferBuffer == null || TransferBuffer.Size < ySpan.Length + uSpan.Length + vSpan.Length)
yTexture, {
uTexture, TransferBuffer?.Dispose();
vTexture, TransferBuffer = new TransferBuffer(Device, TransferUsage.Texture, TransferBufferMapFlags.Write, (uint) (ySpan.Length + uSpan.Length + vSpan.Length));
CurrentStream.yDataHandle,
CurrentStream.uDataHandle,
CurrentStream.vDataHandle,
CurrentStream.yDataLength,
CurrentStream.uvDataLength,
CurrentStream.yStride,
CurrentStream.uvStride
);
commandBuffer.BeginRenderPass(
new ColorAttachmentInfo(RenderTexture, Color.Black)
);
commandBuffer.BindGraphicsPipeline(GraphicsDevice.VideoPipeline);
commandBuffer.BindFragmentSamplers(
new TextureSamplerBinding(yTexture, LinearSampler),
new TextureSamplerBinding(uTexture, LinearSampler),
new TextureSamplerBinding(vTexture, LinearSampler)
);
commandBuffer.DrawPrimitives(0, 1, 0, 0);
commandBuffer.EndRenderPass();
GraphicsDevice.Submit(commandBuffer);
} }
TransferBuffer.SetData(ySpan, 0, true);
TransferBuffer.SetData(uSpan, (uint) ySpan.Length, false);
TransferBuffer.SetData(vSpan, (uint) (ySpan.Length + uSpan.Length), false);
uOffset = (uint) ySpan.Length;
vOffset = (uint) (ySpan.Length + vSpan.Length);
yStride = Stream.yStride;
uvStride = Stream.uvStride;
}
var commandBuffer = Device.AcquireCommandBuffer();
var copyPass = commandBuffer.BeginCopyPass();
copyPass.UploadToTexture(
TransferBuffer,
yTexture,
new BufferImageCopy
{
BufferOffset = 0,
BufferStride = yStride,
BufferImageHeight = yTexture.Height
},
true
);
copyPass.UploadToTexture(
TransferBuffer,
uTexture,
new BufferImageCopy{
BufferOffset = uOffset,
BufferStride = uvStride,
BufferImageHeight = uTexture.Height
},
true
);
copyPass.UploadToTexture(
TransferBuffer,
vTexture,
new BufferImageCopy
{
BufferOffset = vOffset,
BufferStride = uvStride,
BufferImageHeight = vTexture.Height
},
true
);
commandBuffer.EndCopyPass(copyPass);
var renderPass = commandBuffer.BeginRenderPass(
new ColorAttachmentInfo(RenderTexture, true, Color.Black)
);
renderPass.BindGraphicsPipeline(Device.VideoPipeline);
renderPass.BindFragmentSampler(new TextureSamplerBinding(yTexture, LinearSampler), 0);
renderPass.BindFragmentSampler(new TextureSamplerBinding(uTexture, LinearSampler), 1);
renderPass.BindFragmentSampler(new TextureSamplerBinding(vTexture, LinearSampler), 2);
renderPass.DrawPrimitives(0, 1);
commandBuffer.EndRenderPass(renderPass);
Device.Submit(commandBuffer);
} }
private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height) private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height)
@ -291,23 +335,14 @@ namespace MoonWorks.Video
private void InitializeDav1dStream() private void InitializeDav1dStream()
{ {
ReadNextFrameTask?.Wait(); Stream.Load(Video.Filename);
ResetStreamATask = Task.Run(Video.StreamA.Reset);
ResetStreamATask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
ResetStreamBTask = Task.Run(Video.StreamB.Reset);
ResetStreamBTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
CurrentStream = Video.StreamA;
currentFrame = -1; currentFrame = -1;
} }
private static void HandleTaskException(Task task) private void ResetDav1dStreams()
{ {
if (task.Exception.InnerException is not TaskCanceledException) Stream.Reset();
{ currentFrame = -1;
throw task.Exception;
}
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)

View File

@ -19,8 +19,20 @@ 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
{
get
{
SDL.SDL_GetWindowPosition(Handle, out var x, out var y);
return (x, y);
}
}
public string Title { get; private set;}
private bool IsDisposed; private bool IsDisposed;
private static Dictionary<uint, Window> idLookup = new Dictionary<uint, Window>(); private static Dictionary<uint, Window> idLookup = new Dictionary<uint, Window>();
@ -101,7 +113,7 @@ namespace MoonWorks
/// </summary> /// </summary>
/// <param name="width"></param> /// <param name="width"></param>
/// <param name="height"></param> /// <param name="height"></param>
public void SetWindowSize(uint width, uint height) public void SetSize(uint width, uint height)
{ {
SDL.SDL_SetWindowSize(Handle, (int) width, (int) height); SDL.SDL_SetWindowSize(Handle, (int) width, (int) height);
Width = width; Width = width;
@ -113,6 +125,23 @@ namespace MoonWorks
} }
} }
/// <summary>
/// Sets the window position.
/// </summary>
public void SetPosition(int x, int y)
{
SDL.SDL_SetWindowPosition(Handle, x, y);
}
/// <summary>
/// Sets the window title.
/// </summary>
public void SetTitle(string title)
{
SDL.SDL_SetWindowTitle(Handle, title);
Title = title;
}
internal static Window Lookup(uint windowID) internal static Window Lookup(uint windowID)
{ {
return idLookup.ContainsKey(windowID) ? idLookup[windowID] : null; return idLookup.ContainsKey(windowID) ? idLookup[windowID] : null;

View File

@ -22,9 +22,13 @@
/// </summary> /// </summary>
public ScreenMode ScreenMode; public ScreenMode ScreenMode;
/// <summary> /// <summary>
/// Specifies the swapchain composition. Use SDR unless you know what you're doing.
/// </summary>
public Graphics.SwapchainComposition SwapchainComposition;
/// <summary>
/// Specifies the presentation mode for the window. Roughly equivalent to V-Sync. /// Specifies the presentation mode for the window. Roughly equivalent to V-Sync.
/// </summary> /// </summary>
public PresentMode PresentMode; public Graphics.PresentMode PresentMode;
/// <summary> /// <summary>
/// Whether the window can be resized using the operating system's window dragging feature. /// Whether the window can be resized using the operating system's window dragging feature.
/// </summary> /// </summary>
@ -39,7 +43,8 @@
uint windowWidth, uint windowWidth,
uint windowHeight, uint windowHeight,
ScreenMode screenMode, ScreenMode screenMode,
PresentMode presentMode, Graphics.SwapchainComposition swapchainComposition,
Graphics.PresentMode presentMode,
bool systemResizable = false, bool systemResizable = false,
bool startMaximized = false bool startMaximized = false
) { ) {
@ -47,6 +52,7 @@
WindowWidth = windowWidth; WindowWidth = windowWidth;
WindowHeight = windowHeight; WindowHeight = windowHeight;
ScreenMode = screenMode; ScreenMode = screenMode;
SwapchainComposition = swapchainComposition;
PresentMode = presentMode; PresentMode = presentMode;
SystemResizable = systemResizable; SystemResizable = systemResizable;
StartMaximized = startMaximized; StartMaximized = startMaximized;