Compare commits

...

26 Commits

Author SHA1 Message Date
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
cosmonaut bb7e45b9a3 expose Params on ReverbEffect 2024-02-09 13:53:33 -08:00
cosmonaut 4cedf768f7 fix AttackHoldRelease timing 2024-02-03 00:40:29 -08:00
cosmonaut d986b3013f fix TextBatch index buffers being created as vertex buffers 2024-01-27 17:34:26 -08:00
cosmonaut 42e3ac91af Vertex instance input shortcut (#53)
Reviewed-on: #53
2024-01-27 03:44:19 +00:00
cosmonaut 0df2944ccf CommandBuffer is now a pooled class + more state validation 2024-01-18 12:27:34 -08:00
cosmonaut e50fb472b1 Debug mode sample count and depth assertions 2024-01-15 23:16:29 -08:00
cosmonaut df3f38a67b Debug mode bounds checks for buffer and texture upload 2024-01-15 22:19:59 -08:00
Evan Hemsley eaa9266521 remove Marshal call from KeyboardButton.CheckPressed 2023-12-28 18:54:21 -08:00
29 changed files with 2580 additions and 1426 deletions

View File

@ -26,8 +26,8 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_fullscreen.vert.refresh">
<LogicalName>MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh</LogicalName>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\fullscreen.vert.refresh">
<LogicalName>MoonWorks.Graphics.StockShaders.Fullscreen.vert.refresh</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_yuv2rgba.frag.refresh">
<LogicalName>MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh</LogicalName>
@ -38,5 +38,8 @@
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_msdf.frag.refresh">
<LogicalName>MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\blit.frag.refresh">
<LogicalName>MoonWorks.Graphics.StockShaders.Blit.frag.refresh</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Project>

@ -1 +1 @@
Subproject commit b5325e6d0329eeb35b074091a569a5f679852d28
Subproject commit 2e346d84016fb7b26dbe7c5b6c485109571c30f4

View File

@ -35,6 +35,8 @@ namespace MoonWorks.Audio
RoomSize = FAudio.FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE
};
public FAudio.FAudioFXReverbParameters Params { get; private set; }
public ReverbEffect(AudioDevice audioDevice, uint processingStage) : base(audioDevice, 1, audioDevice.DeviceDetails.OutputFormat.Format.nSamplesPerSec, processingStage)
{
/* Init reverb */
@ -64,6 +66,8 @@ namespace MoonWorks.Audio
public void SetParams(in FAudio.FAudioFXReverbParameters reverbParams)
{
Params = reverbParams;
fixed (FAudio.FAudioFXReverbParameters* reverbParamsPtr = &reverbParams)
{
FAudio.FAudioVoice_SetEffectParameters(

View File

@ -5,10 +5,10 @@
/// </summary>
public struct BufferBinding
{
public Buffer Buffer;
public GpuBuffer Buffer;
public ulong Offset;
public BufferBinding(Buffer buffer, ulong offset)
public BufferBinding(GpuBuffer buffer, ulong offset)
{
Buffer = buffer;
Offset = offset;

View File

@ -0,0 +1,17 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// A texture-level pair to be used when binding compute textures.
/// </summary>
public struct TextureLevelBinding
{
public Texture Texture;
public uint MipLevel;
public TextureLevelBinding(Texture texture, uint mipLevel = 0)
{
Texture = texture;
MipLevel = mipLevel;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Concurrent;
namespace MoonWorks.Graphics
{
internal class CommandBufferPool
{
private GraphicsDevice GraphicsDevice;
private ConcurrentQueue<CommandBuffer> CommandBuffers = new ConcurrentQueue<CommandBuffer>();
public CommandBufferPool(GraphicsDevice graphicsDevice)
{
GraphicsDevice = graphicsDevice;
}
public CommandBuffer Obtain()
{
if (CommandBuffers.TryDequeue(out var commandBuffer))
{
return commandBuffer;
}
else
{
return new CommandBuffer(GraphicsDevice);
}
}
public void Return(CommandBuffer commandBuffer)
{
commandBuffer.Handle = IntPtr.Zero;
CommandBuffers.Enqueue(commandBuffer);
}
}
}

View File

@ -47,8 +47,26 @@ namespace MoonWorks.Graphics.Font
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 texture = Texture.CreateTexture2D(graphicsDevice, width, height, TextureFormat.R8G8B8A8, TextureUsageFlags.Sampler);
var transferBuffer = new TransferBuffer(graphicsDevice, sizeInBytes);
ImageUtils.DecodeIntoTransferBuffer(
imagePath,
transferBuffer,
0,
SetDataOptions.Overwrite
);
commandBuffer.BeginCopyPass();
commandBuffer.UploadToTexture(
transferBuffer,
texture
);
commandBuffer.EndCopyPass();
transferBuffer.Dispose();
NativeMemory.Free(fontFileByteBuffer);
NativeMemory.Free(atlasFileByteBuffer);

View File

@ -13,10 +13,12 @@ namespace MoonWorks.Graphics.Font
private GraphicsDevice GraphicsDevice { get; }
public IntPtr Handle { get; }
public Buffer VertexBuffer { get; protected set; } = null;
public Buffer IndexBuffer { get; protected set; } = null;
public GpuBuffer VertexBuffer { get; protected set; } = null;
public GpuBuffer IndexBuffer { get; protected set; } = null;
public uint PrimitiveCount { get; protected set; }
private TransferBuffer TransferBuffer;
public Font CurrentFont { get; private set; }
private byte* StringBytes;
@ -30,8 +32,10 @@ namespace MoonWorks.Graphics.Font
StringBytesLength = 128;
StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength);
VertexBuffer = Buffer.Create<Vertex>(GraphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT);
IndexBuffer = Buffer.Create<uint>(GraphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT);
VertexBuffer = GpuBuffer.Create<Vertex>(GraphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT);
IndexBuffer = GpuBuffer.Create<uint>(GraphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT);
TransferBuffer = TransferBuffer.Create<byte>(GraphicsDevice, VertexBuffer.Size + IndexBuffer.Size);
}
// Call this to initialize or reset the batch.
@ -93,22 +97,38 @@ namespace MoonWorks.Graphics.Font
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)
{
VertexBuffer.Dispose();
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
VertexBuffer = new GpuBuffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
newTransferBufferNeeded = true;
}
if (IndexBuffer.Size < indexDataLengthInBytes)
{
IndexBuffer.Dispose();
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
IndexBuffer = new GpuBuffer(GraphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes);
newTransferBufferNeeded = true;
}
if (newTransferBufferNeeded)
{
TransferBuffer.Dispose();
TransferBuffer = new TransferBuffer(GraphicsDevice, VertexBuffer.Size + IndexBuffer.Size);
}
if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
{
commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes);
commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes);
TransferBuffer.SetData(vertexSpan, SetDataOptions.Discard);
TransferBuffer.SetData(indexSpan, (uint) vertexSpan.Length, SetDataOptions.Overwrite);
commandBuffer.UploadToBuffer(TransferBuffer, VertexBuffer, new BufferCopy(0, 0, (uint) vertexSpan.Length));
commandBuffer.UploadToBuffer(TransferBuffer, IndexBuffer, new BufferCopy((uint) vertexSpan.Length, 0, (uint) indexSpan.Length));
}
PrimitiveCount = vertexCount / 2;
@ -123,12 +143,12 @@ namespace MoonWorks.Graphics.Font
));
commandBuffer.BindVertexBuffers(VertexBuffer);
commandBuffer.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo);
commandBuffer.PushVertexShaderUniforms(transformMatrix);
commandBuffer.PushFragmentShaderUniforms(CurrentFont.DistanceRange);
commandBuffer.DrawIndexedPrimitives(
0,
0,
PrimitiveCount,
commandBuffer.PushVertexShaderUniforms(transformMatrix),
commandBuffer.PushFragmentShaderUniforms(CurrentFont.DistanceRange)
PrimitiveCount
);
}

View File

@ -22,6 +22,9 @@ namespace MoonWorks.Graphics
// Built-in video pipeline
internal GraphicsPipeline VideoPipeline { get; }
// Built-in blit pipeline
internal GraphicsPipeline BlitPipeline { get; }
// Built-in text shader info
public GraphicsShaderInfo TextVertexShaderInfo { get; }
public GraphicsShaderInfo TextFragmentShaderInfo { get; }
@ -35,6 +38,7 @@ namespace MoonWorks.Graphics
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
private FencePool FencePool;
private CommandBufferPool CommandBufferPool;
internal GraphicsDevice(
Backend preferredBackend,
@ -56,32 +60,43 @@ namespace MoonWorks.Graphics
// Check for replacement stock shaders
string basePath = System.AppContext.BaseDirectory;
string videoVertPath = Path.Combine(basePath, "video_fullscreen.vert.refresh");
string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh");
string fullscreenVertPath = Path.Combine(basePath, "fullscreen.vert.refresh");
string textVertPath = Path.Combine(basePath, "text_transform.vert.refresh");
string textFragPath = Path.Combine(basePath, "text_msdf.frag.refresh");
ShaderModule videoVertShader;
ShaderModule videoFragShader;
string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh");
string blitFragPath = Path.Combine(basePath, "blit.frag.refresh");
ShaderModule fullscreenVertShader;
ShaderModule textVertShader;
ShaderModule textFragShader;
if (File.Exists(videoVertPath) && File.Exists(videoFragPath))
ShaderModule videoFragShader;
ShaderModule blitFragShader;
if (File.Exists(fullscreenVertPath))
{
fullscreenVertShader = new ShaderModule(this, fullscreenVertPath);
}
else
{
// use defaults
var assembly = typeof(GraphicsDevice).Assembly;
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.Fullscreen.vert.refresh");
fullscreenVertShader = new ShaderModule(this, vertStream);
}
if (File.Exists(videoFragPath))
{
videoVertShader = new ShaderModule(this, videoVertPath);
videoFragShader = new ShaderModule(this, videoFragPath);
}
else
{
// use defaults
var assembly = typeof(GraphicsDevice).Assembly;
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh");
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh");
videoVertShader = new ShaderModule(this, vertStream);
videoFragShader = new ShaderModule(this, fragStream);
}
@ -102,6 +117,19 @@ namespace MoonWorks.Graphics
textFragShader = new ShaderModule(this, fragStream);
}
if (File.Exists(blitFragPath))
{
blitFragShader = new ShaderModule(this, blitFragPath);
}
else
{
// use defaults
var assembly = typeof(GraphicsDevice).Assembly;
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.Blit.frag.refresh");
blitFragShader = new ShaderModule(this, fragStream);
}
VideoPipeline = new GraphicsPipeline(
this,
new GraphicsPipelineCreateInfo
@ -114,7 +142,7 @@ namespace MoonWorks.Graphics
),
DepthStencilState = DepthStencilState.Disable,
VertexShaderInfo = GraphicsShaderInfo.Create(
videoVertShader,
fullscreenVertShader,
"main",
0
),
@ -130,6 +158,34 @@ namespace MoonWorks.Graphics
}
);
BlitPipeline = new GraphicsPipeline(
this,
new GraphicsPipelineCreateInfo
{
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
new ColorAttachmentDescription(
TextureFormat.R8G8B8A8,
ColorAttachmentBlendState.None
)
),
DepthStencilState = DepthStencilState.Disable,
VertexShaderInfo = GraphicsShaderInfo.Create(
fullscreenVertShader,
"main",
0
),
FragmentShaderInfo = GraphicsShaderInfo.Create(
blitFragShader,
"main",
1
),
VertexInputState = VertexInputState.Empty,
RasterizerState = RasterizerState.CCW_CullNone,
PrimitiveType = PrimitiveType.TriangleList,
MultisampleState = MultisampleState.None
}
);
TextVertexShaderInfo = GraphicsShaderInfo.Create<Math.Float.Matrix4x4>(textVertShader, "main", 0);
TextFragmentShaderInfo = GraphicsShaderInfo.Create<float>(textFragShader, "main", 1);
TextVertexInputState = VertexInputState.CreateSingleBinding<Font.Vertex>();
@ -138,6 +194,7 @@ namespace MoonWorks.Graphics
LinearSampler = new Sampler(this, SamplerCreateInfo.LinearClamp);
FencePool = new FencePool(this);
CommandBufferPool = new CommandBufferPool(this);
}
/// <summary>
@ -167,7 +224,7 @@ namespace MoonWorks.Graphics
window.SwapchainFormat = GetSwapchainFormat(window);
if (window.SwapchainTexture == null)
{
window.SwapchainTexture = new Texture(this);
window.SwapchainTexture = new Texture(this, window.SwapchainFormat);
}
}
@ -221,7 +278,12 @@ namespace MoonWorks.Graphics
/// <returns></returns>
public CommandBuffer AcquireCommandBuffer()
{
return new CommandBuffer(this, Refresh.Refresh_AcquireCommandBuffer(Handle));
var commandBuffer = CommandBufferPool.Obtain();
commandBuffer.SetHandle(Refresh.Refresh_AcquireCommandBuffer(Handle));
#if DEBUG
commandBuffer.ResetStateTracking();
#endif
return commandBuffer;
}
/// <summary>
@ -229,10 +291,23 @@ namespace MoonWorks.Graphics
/// </summary>
public void Submit(CommandBuffer commandBuffer)
{
#if DEBUG
if (commandBuffer.Submitted)
{
throw new System.InvalidOperationException("Command buffer already submitted!");
}
#endif
Refresh.Refresh_Submit(
Handle,
commandBuffer.Handle
);
CommandBufferPool.Return(commandBuffer);
#if DEBUG
commandBuffer.Submitted = true;
#endif
}
/// <summary>

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

@ -0,0 +1,501 @@
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 IntPtr GetPixelDataFromBytes(
Span<byte> data,
out uint width,
out uint height,
out uint sizeInBytes
) {
fixed (byte* ptr = data)
{
var pixelData =
Refresh.Refresh_Image_Load(
(nint) 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 IntPtr 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 IntPtr 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(
(nint) ptr,
data.Length,
out var w,
out var h,
out var len
);
width = (uint) w;
height = (uint) h;
sizeInBytes = (uint) len;
return Conversions.ByteToBool(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 static void FreePixelData(IntPtr pixels)
{
Refresh.Refresh_Image_Free(pixels);
}
/// <summary>
/// Decodes image data into a TransferBuffer to prepare for image upload.
/// </summary>
public static unsafe uint DecodeIntoTransferBuffer(
Span<byte> data,
TransferBuffer transferBuffer,
uint bufferOffsetInBytes,
SetDataOptions option
) {
var pixelData = GetPixelDataFromBytes(data, out var w, out var h, out var sizeInBytes);
var length = transferBuffer.SetData(new Span<byte>((void*) pixelData, (int) sizeInBytes), bufferOffsetInBytes, option);
FreePixelData(pixelData);
return length;
}
/// <summary>
/// Decodes an image stream into a TransferBuffer to prepare for image upload.
/// </summary>
public static unsafe uint DecodeIntoTransferBuffer(
Stream stream,
TransferBuffer transferBuffer,
uint bufferOffsetInBytes,
SetDataOptions option
) {
var pixelData = GetPixelDataFromStream(stream, out var w, out var h, out var sizeInBytes);
var length = transferBuffer.SetData(new Span<byte>((void*) pixelData, (int) sizeInBytes), bufferOffsetInBytes, option);
FreePixelData(pixelData);
return length;
}
/// <summary>
/// Decodes an image file into a TransferBuffer to prepare for image upload.
/// </summary>
public static unsafe uint DecodeIntoTransferBuffer(
string path,
TransferBuffer transferBuffer,
uint bufferOffsetInBytes,
SetDataOptions option
) {
var pixelData = GetPixelDataFromFile(path, out var w, out var h, out var sizeInBytes);
var length = transferBuffer.SetData(new Span<byte>((void*) pixelData, (int) sizeInBytes), bufferOffsetInBytes, option);
FreePixelData(pixelData);
return length;
}
/// <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 = 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 = 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, (nint) 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

@ -297,6 +297,12 @@ namespace MoonWorks.Graphics
IntOpaqueWhite
}
public enum SetDataOptions
{
Discard,
Overwrite
}
public enum Backend
{
DontCare,

View File

@ -127,12 +127,12 @@ namespace MoonWorks.Graphics
public uint Stride;
public VertexInputRate InputRate;
public static VertexBinding Create<T>(uint binding = 0) where T : unmanaged
public static VertexBinding Create<T>(uint binding = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged
{
return new VertexBinding
{
Binding = binding,
InputRate = VertexInputRate.Vertex,
InputRate = inputRate,
Stride = (uint) Marshal.SizeOf<T>()
};
}
@ -354,4 +354,60 @@ namespace MoonWorks.Graphics
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
{
srcOffset = SrcOffset,
dstOffset = DstOffset,
size = Size
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct BufferImageCopy
{
public uint BufferOffset;
public uint BufferStride; // if 0, image assumed to be tightly packed
public uint BufferImageHeight; // if 0, image assumed to be tightly packed
public BufferImageCopy(
uint bufferOffset,
uint bufferStride,
uint bufferImageHeight
) {
BufferOffset = bufferOffset;
BufferStride = bufferStride;
BufferImageHeight = bufferImageHeight;
}
public Refresh.BufferImageCopy ToRefresh()
{
return new Refresh.BufferImageCopy
{
bufferOffset = BufferOffset,
bufferStride = BufferStride,
bufferImageHeight = BufferImageHeight
};
}
}
}

View File

@ -0,0 +1,342 @@
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 TransferBuffer;
byte* data;
uint dataOffset = 0;
uint dataSize = 1024;
List<(GpuBuffer, BufferCopy)> BufferUploads = new List<(GpuBuffer, BufferCopy)>();
List<(TextureSlice, uint)> TextureUploads = new List<(TextureSlice, uint)>();
public ResourceUploader(GraphicsDevice device) : base(device)
{
data = (byte*) NativeMemory.Alloc(dataSize);
}
// 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);
return gpuBuffer;
}
/// <summary>
/// Prepares upload of data into a GpuBuffer.
/// </summary>
public void SetBufferData<T>(GpuBuffer buffer, uint bufferOffsetInElements, Span<T> data) 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 = CopyData(spanPtr, lengthInBytes);
}
var bufferCopyParams = new BufferCopy(resourceOffset, offsetInBytes, lengthInBytes);
BufferUploads.Add((buffer, bufferCopyParams));
}
// Textures
public Texture CreateTexture2D(Span<byte> pixelData, uint width, uint height)
{
var texture = Texture.CreateTexture2D(Device, width, height, TextureFormat.R8G8B8A8, TextureUsageFlags.Sampler);
SetTextureData(texture, pixelData);
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 textureSlice = new TextureSlice
{
Texture = texture,
MipLevel = (uint) level,
BaseLayer = (uint) face,
LayerCount = 1,
X = 0,
Y = 0,
Z = 0,
Width = (uint) levelWidth,
Height = (uint) levelHeight,
Depth = 1
};
SetTextureData(textureSlice, byteSpan);
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(TextureSlice textureSlice, 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(textureSlice, pixelSpan);
ImageUtils.FreePixelData(pixelData);
}
public void SetTextureDataFromCompressed(TextureSlice textureSlice, Stream compressedImageStream)
{
var length = compressedImageStream.Length;
var buffer = NativeMemory.Alloc((nuint) length);
var span = new Span<byte>(buffer, (int) length);
compressedImageStream.ReadExactly(span);
SetTextureDataFromCompressed(textureSlice, span);
NativeMemory.Free(buffer);
}
public void SetTextureDataFromCompressed(TextureSlice textureSlice, string compressedImageFilePath)
{
var fileStream = new FileStream(compressedImageFilePath, FileMode.Open, FileAccess.Read);
SetTextureDataFromCompressed(textureSlice, fileStream);
}
/// <summary>
/// Prepares upload of pixel data into a TextureSlice.
/// </summary>
public void SetTextureData<T>(TextureSlice textureSlice, Span<T> data) where T : unmanaged
{
var elementSize = Marshal.SizeOf<T>();
var dataLengthInBytes = (uint) (elementSize * data.Length);
uint resourceOffset;
fixed (T* dataPtr = data)
{
resourceOffset = CopyDataAligned(dataPtr, dataLengthInBytes, Texture.TexelSize(textureSlice.Texture.Format));
}
TextureUploads.Add((textureSlice, resourceOffset));
}
// 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.WaitForFences(fence);
Device.ReleaseFence(fence);
}
// Helper methods
private void CopyToTransferBuffer()
{
if (TransferBuffer == null || TransferBuffer.Size < dataSize)
{
TransferBuffer?.Dispose();
TransferBuffer = new TransferBuffer(Device, dataSize);
}
var dataSpan = new Span<byte>(data, (int) dataSize);
TransferBuffer.SetData(dataSpan, SetDataOptions.Discard);
}
private void RecordUploadCommands(CommandBuffer commandBuffer)
{
commandBuffer.BeginCopyPass();
foreach (var (gpuBuffer, bufferCopyParams) in BufferUploads)
{
commandBuffer.UploadToBuffer(
TransferBuffer,
gpuBuffer,
bufferCopyParams
);
}
foreach (var (textureSlice, offset) in TextureUploads)
{
commandBuffer.UploadToTexture(
TransferBuffer,
textureSlice,
new BufferImageCopy(
offset,
0,
0
)
);
}
commandBuffer.EndCopyPass();
BufferUploads.Clear();
TextureUploads.Clear();
dataOffset = 0;
}
private uint CopyData(void* ptr, uint lengthInBytes)
{
if (dataOffset + lengthInBytes >= dataSize)
{
dataSize = dataOffset + lengthInBytes;
data = (byte*) NativeMemory.Realloc(data, dataSize);
}
var resourceOffset = dataOffset;
NativeMemory.Copy(ptr, data + dataOffset, lengthInBytes);
dataOffset += lengthInBytes;
return resourceOffset;
}
private uint CopyDataAligned(void* ptr, uint lengthInBytes, uint alignment)
{
dataOffset = RoundToAlignment(dataOffset, alignment);
return CopyData(ptr, lengthInBytes);
}
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)
{
TransferBuffer?.Dispose();
}
NativeMemory.Free(data);
}
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

@ -0,0 +1,65 @@
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> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGpuBuffer;
/// <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 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_CreateGpuBuffer(
device.Handle,
(Refresh.BufferUsageFlags) usageFlags,
sizeInBytes
);
Size = sizeInBytes;
}
public static implicit operator BufferBinding(GpuBuffer b)
{
return new BufferBinding(b, 0);
}
}
}

View File

@ -61,12 +61,11 @@ namespace MoonWorks.Graphics
refreshGraphicsPipelineCreateInfo.blendConstants[2] = blendConstants.B;
refreshGraphicsPipelineCreateInfo.blendConstants[3] = blendConstants.A;
refreshGraphicsPipelineCreateInfo.depthStencilState.backStencilState = depthStencilState.BackStencilState.ToRefresh();
refreshGraphicsPipelineCreateInfo.depthStencilState.stencilState = depthStencilState.StencilState.ToRefresh();
refreshGraphicsPipelineCreateInfo.depthStencilState.compareOp = (Refresh.CompareOp) depthStencilState.CompareOp;
refreshGraphicsPipelineCreateInfo.depthStencilState.depthBoundsTestEnable = Conversions.BoolToByte(depthStencilState.DepthBoundsTestEnable);
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);

View File

@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics
@ -18,166 +17,11 @@ namespace MoonWorks.Graphics
public uint LevelCount { get; }
public SampleCount SampleCount { get; }
public TextureUsageFlags UsageFlags { get; }
public uint Size { get; }
// FIXME: this allocates a delegate instance
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
/// <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);
TextureCreateInfo textureCreateInfo = new TextureCreateInfo();
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(
(nint) ptr,
(int) data.Length,
out var w,
out var h,
out var len
);
commandBuffer.SetTextureData(textureSlice, pixels, (uint) len);
Refresh.Refresh_Image_Free(pixels);
}
}
/// <summary>
/// Sets data for a texture slice using PNG or QOI data from a stream.
/// </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>
/// Creates a 2D texture.
/// </summary>
@ -296,6 +140,7 @@ namespace MoonWorks.Graphics
LevelCount = textureCreateInfo.LevelCount;
SampleCount = textureCreateInfo.SampleCount;
UsageFlags = textureCreateInfo.UsageFlags;
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
}
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
@ -303,12 +148,13 @@ namespace MoonWorks.Graphics
// Used by AcquireSwapchainTexture.
// Should not be tracked, because swapchain textures are managed by Vulkan.
internal Texture(
GraphicsDevice device
GraphicsDevice device,
TextureFormat format
) : base(device)
{
Handle = IntPtr.Zero;
Format = TextureFormat.R8G8B8A8;
Format = format;
Width = 0;
Height = 0;
Depth = 1;
@ -316,333 +162,113 @@ namespace MoonWorks.Graphics
LevelCount = 1;
SampleCount = SampleCount.One;
UsageFlags = TextureUsageFlags.ColorTarget;
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)
public static uint BytesPerPixel(TextureFormat format)
{
throw new NotSupportedException("Not a DDS!");
}
// Texture info
uint size = reader.ReadUInt32();
if (size != DDS_HEADERSIZE)
switch (format)
{
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;
case TextureFormat.R8:
case TextureFormat.R8_UINT:
return 1;
case TextureFormat.R5G6B5:
case TextureFormat.B4G4R4A4:
case TextureFormat.A1R5G5B5:
case TextureFormat.R16_SFLOAT:
case TextureFormat.R8G8_SNORM:
case TextureFormat.R8G8_UINT:
case TextureFormat.R16_UINT:
case TextureFormat.D16:
return 2;
case TextureFormat.D16S8:
return 3;
case TextureFormat.R8G8B8A8:
case TextureFormat.B8G8R8A8:
case TextureFormat.R32_SFLOAT:
case TextureFormat.R16G16:
case TextureFormat.R16G16_SFLOAT:
case TextureFormat.R8G8B8A8_SNORM:
case TextureFormat.A2R10G10B10:
case TextureFormat.R8G8B8A8_UINT:
case TextureFormat.R16G16_UINT:
case TextureFormat.D32:
return 4;
case TextureFormat.D32S8:
return 5;
case TextureFormat.R16G16B16A16_SFLOAT:
case TextureFormat.R16G16B16A16:
case TextureFormat.R32G32_SFLOAT:
case TextureFormat.R16G16B16A16_UINT:
case TextureFormat.BC1:
return 8;
case TextureFormat.R32G32B32A32_SFLOAT:
case TextureFormat.BC2:
case TextureFormat.BC3:
case TextureFormat.BC7:
return 16;
default:
throw new NotSupportedException(
"Unsupported DDS texture format"
);
Logger.LogError("Texture format not recognized!");
return 0;
}
}
uint resourceDimension = reader.ReadUInt32();
// These values are taken from the D3D10_RESOURCE_DIMENSION enum.
switch (resourceDimension)
public static uint TexelSize(TextureFormat format)
{
case 0: // Unknown
case 1: // Buffer
throw new NotSupportedException(
"Unsupported DDS texture format"
);
switch (format)
{
case TextureFormat.BC2:
case TextureFormat.BC3:
case TextureFormat.BC7:
return 16;
case TextureFormat.BC1:
return 8;
default:
break;
return 1;
}
}
/*
* 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)
public static uint BlockSizeSquared(TextureFormat format)
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
reader.ReadUInt32(); // reserved
break;
switch (format)
{
case TextureFormat.BC1:
case TextureFormat.BC2:
case TextureFormat.BC3:
case TextureFormat.BC7:
return 16;
case TextureFormat.R8G8B8A8:
case TextureFormat.B8G8R8A8:
case TextureFormat.R5G6B5:
case TextureFormat.A1R5G5B5:
case TextureFormat.B4G4R4A4:
case TextureFormat.A2R10G10B10:
case TextureFormat.R16G16:
case TextureFormat.R16G16B16A16:
case TextureFormat.R8:
case TextureFormat.R8G8_SNORM:
case TextureFormat.R8G8B8A8_SNORM:
case TextureFormat.R16_SFLOAT:
case TextureFormat.R16G16_SFLOAT:
case TextureFormat.R16G16B16A16_SFLOAT:
case TextureFormat.R32_SFLOAT:
case TextureFormat.R32G32_SFLOAT:
case TextureFormat.R32G32B32A32_SFLOAT:
case TextureFormat.R8_UINT:
case TextureFormat.R8G8_UINT:
case TextureFormat.R8G8B8A8_UINT:
case TextureFormat.R16_UINT:
case TextureFormat.R16G16_UINT:
case TextureFormat.R16G16B16A16_UINT:
case TextureFormat.D16:
case TextureFormat.D32:
case TextureFormat.D16S8:
case TextureFormat.D32S8:
return 1;
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);
Logger.LogError("Texture format not recognized!");
return 0;
}
}
}
}

View File

@ -0,0 +1,152 @@
using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics
{
public unsafe class TransferBuffer : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTransferBuffer;
/// <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,
uint elementCount
) where T : unmanaged
{
return new TransferBuffer(
device,
(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>
public TransferBuffer(
GraphicsDevice device,
uint sizeInBytes
) : base(device)
{
Handle = Refresh.Refresh_CreateTransferBuffer(
device.Handle,
sizeInBytes
);
Size = sizeInBytes;
}
/// <summary>
/// Immediately copies data from a Span to the TransferBuffer.
/// Returns the length of the copy in bytes.
///
/// If setDataOption is DISCARD and this TransferBuffer was used in an Upload command,
/// that command will still use the correct data at the cost of increased memory usage.
///
/// If setDataOption is OVERWRITE and this TransferBuffer was used in an Upload command,
/// the data will be overwritten immediately, which could cause a data race.
/// </summary>
public unsafe uint SetData<T>(
Span<T> data,
uint bufferOffsetInBytes,
SetDataOptions setDataOption
) 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_SetData(
Device.Handle,
(nint) dataPtr,
Handle,
new Refresh.BufferCopy
{
srcOffset = 0,
dstOffset = bufferOffsetInBytes,
size = dataLengthInBytes
},
(Refresh.SetDataOptions) setDataOption
);
}
return dataLengthInBytes;
}
/// <summary>
/// Immediately copies data from a Span to the TransferBuffer.
/// Returns the length of the copy in bytes.
///
/// If setDataOption is DISCARD and this TransferBuffer was used in an Upload command,
/// that command will still use the correct data at the cost of increased memory usage.
///
/// If setDataOption is OVERWRITE and this TransferBuffer was used in an Upload command,
/// the data will be overwritten immediately, which could cause a data race.
/// </summary>
public unsafe uint SetData<T>(
Span<T> data,
SetDataOptions setDataOption
) where T : unmanaged
{
return SetData(data, 0, setDataOption);
}
/// <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_GetData(
Device.Handle,
Handle,
(nint) dataPtr,
new Refresh.BufferCopy
{
srcOffset = bufferOffsetInBytes,
dstOffset = 0,
size = dataLengthInBytes
}
);
}
}
#if DEBUG
private void AssertBufferBoundsCheck(uint bufferLengthInBytes, uint offsetInBytes, uint copyLengthInBytes)
{
if (copyLengthInBytes > bufferLengthInBytes + offsetInBytes)
{
throw new InvalidOperationException($"SetData overflow! Transfer buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}");
}
}
#endif
}
}

View File

@ -11,14 +11,9 @@
public bool DepthTestEnable;
/// <summary>
/// Describes the stencil operation for back-facing primitives.
/// Describes the stencil operation.
/// </summary>
public StencilOpState BackStencilState;
/// <summary>
/// Describes the stencil operation for front-facing primitives.
/// </summary>
public StencilOpState FrontStencilState;
public StencilOpState StencilState;
/// <summary>
/// The comparison operator used in the depth test.

Binary file not shown.

View File

@ -0,0 +1,12 @@
#version 450
layout (location = 0) in vec2 TexCoord;
layout (location = 0) out vec4 FragColor;
layout (binding = 0, set = 1) uniform sampler2D TexSampler;
void main()
{
FragColor = texture(TexSampler, TexCoord);
}

View File

@ -8,34 +8,31 @@ namespace MoonWorks.Graphics
/// </summary>
public struct TextureSlice
{
public Texture Texture { get; }
public Rect Rectangle { get; }
public uint Depth { get; }
public uint Layer { get; }
public uint Level { get; }
public Texture Texture;
public uint MipLevel;
public uint BaseLayer;
public uint LayerCount;
public uint X;
public uint Y;
public uint Z;
public uint Width;
public uint Height;
public uint Depth;
public uint Size => (Width * Height * Depth * LayerCount * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format)) >> (int) MipLevel;
public TextureSlice(Texture texture)
{
Texture = texture;
Rectangle = new Rect
{
X = 0,
Y = 0,
W = (int) texture.Width,
H = (int) texture.Height
};
Depth = 0;
Layer = 0;
Level = 0;
}
public TextureSlice(Texture texture, Rect rectangle, uint depth = 0, uint layer = 0, uint level = 0)
{
Texture = texture;
Rectangle = rectangle;
Depth = depth;
Layer = layer;
Level = level;
MipLevel = 0;
BaseLayer = 0;
LayerCount = (uint) (texture.IsCube ? 6 : 1);
X = 0;
Y = 0;
Z = 0;
Width = texture.Width;
Height = texture.Height;
Depth = texture.Depth;
}
public Refresh.TextureSlice ToRefreshTextureSlice()
@ -43,10 +40,15 @@ namespace MoonWorks.Graphics
Refresh.TextureSlice textureSlice = new Refresh.TextureSlice
{
texture = Texture.Handle,
rectangle = Rectangle.ToRefresh(),
depth = Depth,
layer = Layer,
level = Level
mipLevel = MipLevel,
baseLayer = BaseLayer,
layerCount = LayerCount,
x = X,
y = Y,
z = Z,
w = Width,
h = Height,
d = Depth
};
return textureSlice;

View File

@ -14,9 +14,9 @@ namespace MoonWorks.Graphics
VertexAttributes = attributes;
}
public static VertexBindingAndAttributes Create<T>(uint bindingIndex) where T : unmanaged, IVertexType
public static VertexBindingAndAttributes Create<T>(uint bindingIndex, uint locationOffset = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged, IVertexType
{
VertexBinding binding = VertexBinding.Create<T>(bindingIndex);
VertexBinding binding = VertexBinding.Create<T>(bindingIndex, inputRate);
VertexAttribute[] attributes = new VertexAttribute[T.Formats.Length];
uint offset = 0;
@ -27,7 +27,7 @@ namespace MoonWorks.Graphics
attributes[i] = new VertexAttribute
{
Binding = bindingIndex,
Location = i,
Location = locationOffset + i,
Format = format,
Offset = offset
};

View File

@ -1,5 +1,3 @@
using System.Runtime.InteropServices;
namespace MoonWorks.Input
{
/// <summary>
@ -16,9 +14,9 @@ namespace MoonWorks.Input
KeyCode = keyCode;
}
internal override bool CheckPressed()
internal unsafe override bool CheckPressed()
{
return Conversions.ByteToBool(Marshal.ReadByte(Parent.State, (int) KeyCode));
return Conversions.ByteToBool(((byte*) Parent.State)[(int) KeyCode]);
}
}
}

View File

@ -68,7 +68,7 @@ namespace MoonWorks.Math
{
return Interp(start, hold, time, attackDuration, Function.Get(attackEasingFunction));
}
else if (time >= attackDuration && time < holdDuration)
else if (time >= attackDuration && time < attackDuration + holdDuration)
{
return hold;
}
@ -93,7 +93,7 @@ namespace MoonWorks.Math
{
return Interp(start, hold, time, attackDuration, Function.Get(attackEasingFunction));
}
else if (time >= attackDuration && time < holdDuration)
else if (time >= attackDuration && time < attackDuration + holdDuration)
{
return hold;
}

View File

@ -22,12 +22,13 @@ namespace MoonWorks.Video
private Task ResetStreamATask;
private Task ResetStreamBTask;
private GraphicsDevice GraphicsDevice;
private Texture yTexture = null;
private Texture uTexture = null;
private Texture vTexture = null;
private Sampler LinearSampler;
private TransferBuffer TransferBuffer;
private int currentFrame;
private Stopwatch timer;
@ -36,8 +37,6 @@ namespace MoonWorks.Video
public VideoPlayer(GraphicsDevice device) : base(device)
{
GraphicsDevice = device;
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
timer = new Stopwatch();
@ -55,46 +54,46 @@ namespace MoonWorks.Video
if (RenderTexture == null)
{
RenderTexture = CreateRenderTexture(GraphicsDevice, video.Width, video.Height);
RenderTexture = CreateRenderTexture(Device, video.Width, video.Height);
}
if (yTexture == null)
{
yTexture = CreateSubTexture(GraphicsDevice, video.Width, video.Height);
yTexture = CreateSubTexture(Device, video.Width, video.Height);
}
if (uTexture == null)
{
uTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
uTexture = CreateSubTexture(Device, video.UVWidth, video.UVHeight);
}
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)
{
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)
{
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)
{
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)
{
vTexture.Dispose();
vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
vTexture = CreateSubTexture(Device, video.UVWidth, video.UVHeight);
}
Video = video;
@ -233,37 +232,73 @@ namespace MoonWorks.Video
{
lock (CurrentStream)
{
var commandBuffer = GraphicsDevice.AcquireCommandBuffer();
var commandBuffer = Device.AcquireCommandBuffer();
commandBuffer.SetTextureDataYUV(
var ySpan = new Span<byte>((void*) CurrentStream.yDataHandle, (int) CurrentStream.yDataLength);
var uSpan = new Span<byte>((void*) CurrentStream.uDataHandle, (int) CurrentStream.uvDataLength);
var vSpan = new Span<byte>((void*) CurrentStream.vDataHandle, (int) CurrentStream.uvDataLength);
if (TransferBuffer == null || TransferBuffer.Size < ySpan.Length + uSpan.Length + vSpan.Length)
{
TransferBuffer?.Dispose();
TransferBuffer = new TransferBuffer(Device, (uint) (ySpan.Length + uSpan.Length + vSpan.Length));
}
TransferBuffer.SetData(ySpan, 0, SetDataOptions.Discard);
TransferBuffer.SetData(uSpan, (uint) ySpan.Length, SetDataOptions.Overwrite);
TransferBuffer.SetData(vSpan, (uint) (ySpan.Length + uSpan.Length), SetDataOptions.Overwrite);
commandBuffer.BeginCopyPass();
commandBuffer.UploadToTexture(
TransferBuffer,
yTexture,
uTexture,
vTexture,
CurrentStream.yDataHandle,
CurrentStream.uDataHandle,
CurrentStream.vDataHandle,
CurrentStream.yDataLength,
CurrentStream.uvDataLength,
CurrentStream.yStride,
CurrentStream.uvStride
new BufferImageCopy
{
BufferOffset = 0,
BufferStride = CurrentStream.yStride,
BufferImageHeight = yTexture.Height
}
);
commandBuffer.UploadToTexture(
TransferBuffer,
uTexture,
new BufferImageCopy{
BufferOffset = (uint) ySpan.Length,
BufferStride = CurrentStream.uvStride,
BufferImageHeight = uTexture.Height
}
);
commandBuffer.UploadToTexture(
TransferBuffer,
vTexture,
new BufferImageCopy
{
BufferOffset = (uint) (ySpan.Length + uSpan.Length),
BufferStride = CurrentStream.uvStride,
BufferImageHeight = vTexture.Height
}
);
commandBuffer.EndCopyPass();
commandBuffer.BeginRenderPass(
new ColorAttachmentInfo(RenderTexture, Color.Black)
);
commandBuffer.BindGraphicsPipeline(GraphicsDevice.VideoPipeline);
commandBuffer.BindGraphicsPipeline(Device.VideoPipeline);
commandBuffer.BindFragmentSamplers(
new TextureSamplerBinding(yTexture, LinearSampler),
new TextureSamplerBinding(uTexture, LinearSampler),
new TextureSamplerBinding(vTexture, LinearSampler)
);
commandBuffer.DrawPrimitives(0, 1, 0, 0);
commandBuffer.DrawPrimitives(0, 1);
commandBuffer.EndRenderPass();
GraphicsDevice.Submit(commandBuffer);
Device.Submit(commandBuffer);
}
}