Compare commits
41 Commits
main
...
what_if_no
Author | SHA1 | Date |
---|---|---|
cosmonaut | 5a97bd3f33 | |
cosmonaut | 45b085a236 | |
cosmonaut | 679ad2463c | |
cosmonaut | 9e4e44bb52 | |
cosmonaut | 8813a0139d | |
cosmonaut | 217ae96888 | |
cosmonaut | 23b6499479 | |
cosmonaut | 929d8f5cc9 | |
cosmonaut | 69d2c9cc31 | |
cosmonaut | 9195e445b2 | |
cosmonaut | f30d8f0197 | |
cosmonaut | bde31fbe07 | |
cosmonaut | a762a80c4f | |
cosmonaut | 099c07aa39 | |
cosmonaut | cba6ca59d3 | |
cosmonaut | a004488f81 | |
cosmonaut | 33ed8b2364 | |
cosmonaut | 3c832550d0 | |
cosmonaut | c84752f38c | |
cosmonaut | 019afa91f5 | |
cosmonaut | 00adec189c | |
cosmonaut | 0e723514df | |
cosmonaut | e0f4c19dc2 | |
cosmonaut | 178a5ea3cf | |
cosmonaut | 50b8cb11c9 | |
cosmonaut | d83501437d | |
cosmonaut | fe520dc9cc | |
cosmonaut | b29341eca3 | |
cosmonaut | 22bcd2e471 | |
cosmonaut | fe31e23ccc | |
cosmonaut | 848b1c706c | |
cosmonaut | a207f404b9 | |
cosmonaut | 31c79d3179 | |
cosmonaut | 8229e5dd33 | |
cosmonaut | 1eae01c95c | |
cosmonaut | b80527d793 | |
cosmonaut | ecfcb666a8 | |
cosmonaut | ad97aed60f | |
cosmonaut | 0a5ec9e82d | |
cosmonaut | 8648eef5d1 | |
cosmonaut | 39496c37ea |
|
@ -3,7 +3,6 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>11</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
@ -26,8 +25,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 +37,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>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<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="osx" target="libRefresh.1.dylib"/>
|
||||
<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.1"/>
|
||||
<dllmap dll="Refresh" os="osx" target="libRefresh.2.dylib"/>
|
||||
<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.2"/>
|
||||
|
||||
<dllmap dll="FAudio" os="windows" target="FAudio.dll"/>
|
||||
<dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/>
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b5325e6d0329eeb35b074091a569a5f679852d28
|
||||
Subproject commit 995a54fa2df82946441c9ec6446d7cd12236f8f7
|
|
@ -53,10 +53,10 @@ namespace MoonWorks
|
|||
public Game(
|
||||
WindowCreateInfo windowCreateInfo,
|
||||
FrameLimiterSettings frameLimiterSettings,
|
||||
Span<Backend> preferredBackends,
|
||||
int targetTimestep = 60,
|
||||
bool debugMode = false
|
||||
)
|
||||
{
|
||||
) {
|
||||
Logger.LogInfo("Initializing frame limiter...");
|
||||
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
|
||||
gameTimer = Stopwatch.StartNew();
|
||||
|
@ -82,7 +82,7 @@ namespace MoonWorks
|
|||
|
||||
Logger.LogInfo("Initializing graphics device...");
|
||||
GraphicsDevice = new GraphicsDevice(
|
||||
Backend.Vulkan,
|
||||
preferredBackends,
|
||||
debugMode
|
||||
);
|
||||
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
namespace MoonWorks.Graphics
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// A buffer-offset pair to be used when binding vertex buffers.
|
||||
/// A buffer-offset pair to be used when binding vertex or index buffers.
|
||||
/// </summary>
|
||||
public struct BufferBinding
|
||||
{
|
||||
public Buffer Buffer;
|
||||
public ulong Offset;
|
||||
|
||||
public BufferBinding(Buffer buffer, ulong offset)
|
||||
public readonly record struct BufferBinding(
|
||||
GpuBuffer Buffer,
|
||||
uint Offset
|
||||
) {
|
||||
public Refresh.BufferBinding ToRefresh()
|
||||
{
|
||||
Buffer = buffer;
|
||||
Offset = offset;
|
||||
return new Refresh.BufferBinding
|
||||
{
|
||||
gpuBuffer = Buffer.Handle,
|
||||
offset = Offset
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Binding specification to be used when binding buffers for compute shaders.
|
||||
/// </summary>
|
||||
/// <param name="GpuBuffer">The GpuBuffer to bind.</param>
|
||||
/// <param name="WriteOption">
|
||||
/// Specifies data dependency behavior when this buffer is written to in the shader. <br/>
|
||||
///
|
||||
/// Cycle:
|
||||
/// If this buffer has been used in commands that have not finished,
|
||||
/// the implementation may choose to prevent a dependency on those commands
|
||||
/// at the cost of increased memory usage.
|
||||
/// You may NOT assume that any of the previous data is retained.
|
||||
/// This may prevent stalls when frequently updating a resource. <br />
|
||||
///
|
||||
/// SafeOverwrite:
|
||||
/// Overwrites the data safely using a GPU memory barrier.
|
||||
/// </param>
|
||||
public readonly record struct ComputeBufferBinding(
|
||||
GpuBuffer GpuBuffer,
|
||||
WriteOptions WriteOption
|
||||
) {
|
||||
public Refresh.ComputeBufferBinding ToRefresh()
|
||||
{
|
||||
return new Refresh.ComputeBufferBinding
|
||||
{
|
||||
gpuBuffer = GpuBuffer.Handle,
|
||||
writeOption = (Refresh.WriteOptions) WriteOption
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Binding specification used for binding texture slices for compute shaders.
|
||||
/// </summary>
|
||||
/// <param name="TextureSlice">The TextureSlice to bind.</param>
|
||||
/// <param name="WriteOption">
|
||||
/// Specifies data dependency behavior when this texture is written to in the shader. <br/>
|
||||
///
|
||||
/// Cycle:
|
||||
/// If this buffer has been used in commands that have not finished,
|
||||
/// the implementation may choose to prevent a dependency on those commands
|
||||
/// at the cost of increased memory usage.
|
||||
/// You may NOT assume that any of the previous data is retained.
|
||||
/// This may prevent stalls when frequently updating a resource. <br />
|
||||
///
|
||||
/// SafeOverwrite:
|
||||
/// Overwrites the data safely using a GPU memory barrier.
|
||||
/// </param>
|
||||
public readonly record struct ComputeTextureBinding(
|
||||
TextureSlice TextureSlice,
|
||||
WriteOptions WriteOption
|
||||
) {
|
||||
public Refresh.ComputeTextureBinding ToRefresh()
|
||||
{
|
||||
return new Refresh.ComputeTextureBinding
|
||||
{
|
||||
textureSlice = TextureSlice.ToRefreshTextureSlice(),
|
||||
writeOption = (Refresh.WriteOptions) WriteOption
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,21 @@
|
|||
namespace MoonWorks.Graphics
|
||||
using RefreshCS;
|
||||
|
||||
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)
|
||||
public readonly record struct TextureSamplerBinding(
|
||||
Texture Texture,
|
||||
Sampler Sampler
|
||||
) {
|
||||
public Refresh.TextureSamplerBinding ToRefresh()
|
||||
{
|
||||
Texture = texture;
|
||||
Sampler = sampler;
|
||||
return new Refresh.TextureSamplerBinding
|
||||
{
|
||||
texture = Texture.Handle,
|
||||
sampler = Sampler.Handle
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -47,7 +47,13 @@ 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 uploader = new ResourceUploader(graphicsDevice);
|
||||
var texture = uploader.CreateTexture2DFromCompressed(imagePath);
|
||||
uploader.Upload();
|
||||
uploader.Dispose();
|
||||
|
||||
NativeMemory.Free(fontFileByteBuffer);
|
||||
NativeMemory.Free(atlasFileByteBuffer);
|
||||
|
|
|
@ -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, TransferUsage.Buffer, 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.Index, vertexDataLengthInBytes);
|
||||
IndexBuffer = new GpuBuffer(GraphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes);
|
||||
newTransferBufferNeeded = true;
|
||||
}
|
||||
|
||||
if (newTransferBufferNeeded)
|
||||
{
|
||||
TransferBuffer.Dispose();
|
||||
TransferBuffer = new TransferBuffer(GraphicsDevice, TransferUsage.Buffer, VertexBuffer.Size + IndexBuffer.Size);
|
||||
}
|
||||
|
||||
if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
|
||||
{
|
||||
commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes);
|
||||
commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes);
|
||||
TransferBuffer.SetData(vertexSpan, TransferOptions.Cycle);
|
||||
TransferBuffer.SetData(indexSpan, (uint) vertexSpan.Length, TransferOptions.Unsafe);
|
||||
|
||||
commandBuffer.UploadToBuffer(TransferBuffer, VertexBuffer, new BufferCopy(0, 0, (uint) vertexSpan.Length), WriteOptions.Cycle);
|
||||
commandBuffer.UploadToBuffer(TransferBuffer, IndexBuffer, new BufferCopy((uint) vertexSpan.Length, 0, (uint) indexSpan.Length), WriteOptions.Cycle);
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace MoonWorks.Graphics
|
|||
{
|
||||
public IntPtr Handle { get; }
|
||||
public Backend Backend { get; }
|
||||
public bool DebugMode { get; }
|
||||
|
||||
private uint windowFlags;
|
||||
public SDL2.SDL.SDL_WindowFlags WindowFlags => (SDL2.SDL.SDL_WindowFlags) windowFlags;
|
||||
|
@ -22,6 +23,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; }
|
||||
|
@ -37,11 +41,17 @@ namespace MoonWorks.Graphics
|
|||
private FencePool FencePool;
|
||||
private CommandBufferPool CommandBufferPool;
|
||||
|
||||
internal GraphicsDevice(
|
||||
Backend preferredBackend,
|
||||
internal unsafe GraphicsDevice(
|
||||
Span<Backend> preferredBackends,
|
||||
bool debugMode
|
||||
) {
|
||||
Backend = (Backend) Refresh.Refresh_SelectBackend((Refresh.Backend) preferredBackend, out windowFlags);
|
||||
var backends = stackalloc Refresh.Backend[preferredBackends.Length];
|
||||
for (var i = 0; i < preferredBackends.Length; i += 1)
|
||||
{
|
||||
backends[i] = (Refresh.Backend) preferredBackends[i];
|
||||
}
|
||||
|
||||
Backend = (Backend) Refresh.Refresh_SelectBackend(backends, (uint) preferredBackends.Length, out windowFlags);
|
||||
|
||||
if (Backend == Backend.Invalid)
|
||||
{
|
||||
|
@ -52,37 +62,49 @@ namespace MoonWorks.Graphics
|
|||
Conversions.BoolToByte(debugMode)
|
||||
);
|
||||
|
||||
DebugMode = debugMode;
|
||||
// TODO: check for CreateDevice fail
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -103,6 +125,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
|
||||
|
@ -115,7 +150,7 @@ namespace MoonWorks.Graphics
|
|||
),
|
||||
DepthStencilState = DepthStencilState.Disable,
|
||||
VertexShaderInfo = GraphicsShaderInfo.Create(
|
||||
videoVertShader,
|
||||
fullscreenVertShader,
|
||||
"main",
|
||||
0
|
||||
),
|
||||
|
@ -131,6 +166,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>();
|
||||
|
@ -412,6 +475,118 @@ namespace MoonWorks.Graphics
|
|||
FencePool.Return(fence);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ⚠️⚠️⚠️ <br/>
|
||||
/// Downloads data from a Texture to a TransferBuffer.
|
||||
/// This copy occurs immediately on the CPU timeline.<br/>
|
||||
///
|
||||
/// If you modify this texture in a command buffer and then call this function without calling
|
||||
/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/>
|
||||
///
|
||||
/// This method forces a sync point and is generally a bad thing to do.
|
||||
/// Only use it if you have exhausted all other options.<br/>
|
||||
///
|
||||
/// Remember: friends don't let friends readback.<br/>
|
||||
/// ⚠️⚠️⚠️
|
||||
/// </summary>
|
||||
public void DownloadFromTexture(
|
||||
in TextureRegion textureRegion,
|
||||
TransferBuffer transferBuffer,
|
||||
in BufferImageCopy copyParams,
|
||||
TransferOptions transferOption
|
||||
) {
|
||||
Refresh.Refresh_DownloadFromTexture(
|
||||
Handle,
|
||||
textureRegion.ToRefreshTextureRegion(),
|
||||
transferBuffer.Handle,
|
||||
copyParams.ToRefresh(),
|
||||
(Refresh.TransferOptions) transferOption
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ⚠️⚠️⚠️ <br/>
|
||||
/// Downloads all data from a 2D texture with no mips to a TransferBuffer.
|
||||
/// This copy occurs immediately on the CPU timeline.<br/>
|
||||
///
|
||||
/// If you modify this texture in a command buffer and then call this function without calling
|
||||
/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/>
|
||||
///
|
||||
/// This method forces a sync point and is generally a bad thing to do.
|
||||
/// Only use it if you have exhausted all other options.<br/>
|
||||
///
|
||||
/// Remember: friends don't let friends readback.<br/>
|
||||
/// ⚠️⚠️⚠️
|
||||
/// </summary>
|
||||
public void DownloadFromTexture(
|
||||
Texture texture,
|
||||
TransferBuffer transferBuffer,
|
||||
TransferOptions transferOption
|
||||
) {
|
||||
DownloadFromTexture(
|
||||
new TextureRegion(texture),
|
||||
transferBuffer,
|
||||
new BufferImageCopy(0, 0, 0),
|
||||
transferOption
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ⚠️⚠️⚠️ <br/>
|
||||
/// Downloads data from a GpuBuffer to a TransferBuffer.
|
||||
/// This copy occurs immediately on the CPU timeline.<br/>
|
||||
///
|
||||
/// If you modify this GpuBuffer in a command buffer and then call this function without calling
|
||||
/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/>
|
||||
///
|
||||
/// This method forces a sync point and is generally a bad thing to do.
|
||||
/// Only use it if you have exhausted all other options.<br/>
|
||||
///
|
||||
/// Remember: friends don't let friends readback.<br/>
|
||||
/// ⚠️⚠️⚠️
|
||||
/// </summary>
|
||||
public void DownloadFromBuffer(
|
||||
GpuBuffer gpuBuffer,
|
||||
TransferBuffer transferBuffer,
|
||||
in BufferCopy copyParams,
|
||||
TransferOptions transferOption
|
||||
) {
|
||||
Refresh.Refresh_DownloadFromBuffer(
|
||||
Handle,
|
||||
gpuBuffer.Handle,
|
||||
transferBuffer.Handle,
|
||||
copyParams.ToRefresh(),
|
||||
(Refresh.TransferOptions) transferOption
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ⚠️⚠️⚠️ <br/>
|
||||
/// Downloads all data in a GpuBuffer to a TransferBuffer.
|
||||
/// This copy occurs immediately on the CPU timeline.<br/>
|
||||
///
|
||||
/// If you modify this GpuBuffer in a command buffer and then call this function without calling
|
||||
/// SubmitAndAcquireFence and WaitForFences first, the results will not be what you expect.<br/>
|
||||
///
|
||||
/// This method forces a sync point and is generally a bad thing to do.
|
||||
/// Only use it if you have exhausted all other options.<br/>
|
||||
///
|
||||
/// Remember: friends don't let friends readback.<br/>
|
||||
/// ⚠️⚠️⚠️
|
||||
/// </summary>
|
||||
public void DownloadFromBuffer(
|
||||
GpuBuffer gpuBuffer,
|
||||
TransferBuffer transferBuffer,
|
||||
TransferOptions option
|
||||
) {
|
||||
DownloadFromBuffer(
|
||||
gpuBuffer,
|
||||
transferBuffer,
|
||||
new BufferCopy(0, 0, gpuBuffer.Size),
|
||||
option
|
||||
);
|
||||
}
|
||||
|
||||
private TextureFormat GetSwapchainFormat(Window window)
|
||||
{
|
||||
return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle);
|
||||
|
|
|
@ -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 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>
|
||||
/// 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -297,10 +297,29 @@ namespace MoonWorks.Graphics
|
|||
IntOpaqueWhite
|
||||
}
|
||||
|
||||
public enum TransferUsage
|
||||
{
|
||||
Buffer,
|
||||
Texture
|
||||
}
|
||||
|
||||
public enum TransferOptions
|
||||
{
|
||||
Cycle,
|
||||
Unsafe
|
||||
}
|
||||
|
||||
public enum WriteOptions
|
||||
{
|
||||
Cycle,
|
||||
Unsafe,
|
||||
Safe
|
||||
}
|
||||
|
||||
public enum Backend
|
||||
{
|
||||
DontCare,
|
||||
Vulkan,
|
||||
D3D11,
|
||||
PS5,
|
||||
Invalid
|
||||
}
|
||||
|
|
|
@ -154,11 +154,7 @@ namespace MoonWorks.Graphics
|
|||
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
|
||||
|
@ -166,61 +162,97 @@ namespace MoonWorks.Graphics
|
|||
failOp = (Refresh.StencilOp) FailOp,
|
||||
passOp = (Refresh.StencilOp) PassOp,
|
||||
depthFailOp = (Refresh.StencilOp) DepthFailOp,
|
||||
compareOp = (Refresh.CompareOp) CompareOp,
|
||||
compareMask = CompareMask,
|
||||
writeMask = WriteMask,
|
||||
reference = Reference
|
||||
compareOp = (Refresh.CompareOp) CompareOp
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
/// <summary>
|
||||
/// Determines how a color texture will be read/written in a render pass.
|
||||
/// </summary>
|
||||
public struct ColorAttachmentInfo
|
||||
{
|
||||
public Texture Texture;
|
||||
public uint Depth;
|
||||
public uint Layer;
|
||||
public uint Level;
|
||||
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>
|
||||
/// Specifies data dependency behavior. This option is ignored if LoadOp is Load. <br/>
|
||||
///
|
||||
/// Cycle:
|
||||
/// If this texture slice has been used in commands that have not completed,
|
||||
/// the implementation may prevent a dependency on those commands
|
||||
/// at the cost of increased memory usage.
|
||||
/// You may NOT assume that any of the previous texture (not slice!) data is retained.
|
||||
/// This may prevent stalls when frequently reusing a texture slice in rendering. <br/>
|
||||
///
|
||||
/// SafeOverwrite:
|
||||
/// Overwrites the data safely using a GPU memory barrier.
|
||||
/// </summary>
|
||||
public WriteOptions WriteOption;
|
||||
|
||||
public ColorAttachmentInfo(
|
||||
Texture texture,
|
||||
TextureSlice textureSlice,
|
||||
WriteOptions writeOption,
|
||||
Color clearColor,
|
||||
StoreOp storeOp = StoreOp.Store
|
||||
) {
|
||||
Texture = texture;
|
||||
Depth = 0;
|
||||
Layer = 0;
|
||||
Level = 0;
|
||||
TextureSlice = textureSlice;
|
||||
ClearColor = clearColor;
|
||||
LoadOp = LoadOp.Clear;
|
||||
StoreOp = storeOp;
|
||||
WriteOption = writeOption;
|
||||
}
|
||||
|
||||
public ColorAttachmentInfo(
|
||||
Texture texture,
|
||||
TextureSlice textureSlice,
|
||||
WriteOptions writeOption,
|
||||
LoadOp loadOp = LoadOp.DontCare,
|
||||
StoreOp storeOp = StoreOp.Store
|
||||
) {
|
||||
Texture = texture;
|
||||
Depth = 0;
|
||||
Layer = 0;
|
||||
Level = 0;
|
||||
TextureSlice = textureSlice;
|
||||
ClearColor = Color.White;
|
||||
LoadOp = loadOp;
|
||||
StoreOp = storeOp;
|
||||
WriteOption = writeOption;
|
||||
}
|
||||
|
||||
public Refresh.ColorAttachmentInfo ToRefresh()
|
||||
{
|
||||
return new Refresh.ColorAttachmentInfo
|
||||
{
|
||||
texture = Texture.Handle,
|
||||
depth = Depth,
|
||||
layer = Layer,
|
||||
level = Level,
|
||||
textureSlice = TextureSlice.ToRefreshTextureSlice(),
|
||||
clearColor = new Refresh.Vec4
|
||||
{
|
||||
x = ClearColor.R / 255f,
|
||||
|
@ -229,92 +261,142 @@ namespace MoonWorks.Graphics
|
|||
w = ClearColor.A / 255f
|
||||
},
|
||||
loadOp = (Refresh.LoadOp) LoadOp,
|
||||
storeOp = (Refresh.StoreOp) StoreOp
|
||||
storeOp = (Refresh.StoreOp) StoreOp,
|
||||
writeOption = (Refresh.WriteOptions) WriteOption
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
/// <summary>
|
||||
/// Determines how a depth/stencil texture will be read/written in a render pass.
|
||||
/// </summary>
|
||||
public struct DepthStencilAttachmentInfo
|
||||
{
|
||||
public Texture Texture;
|
||||
public uint Depth;
|
||||
public uint Layer;
|
||||
public uint Level;
|
||||
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>
|
||||
/// Specifies data dependency behavior. This option is ignored if LoadOp or StencilLoadOp is Load. <br/>
|
||||
///
|
||||
/// Cycle:
|
||||
/// If this texture slice has been used in commands that have not completed,
|
||||
/// the implementation may prevent a dependency on those commands
|
||||
/// at the cost of increased memory usage.
|
||||
/// You may NOT assume that any of the previous texture (not slice!) data is retained.
|
||||
/// This may prevent stalls when frequently reusing a texture slice in rendering. <br/>
|
||||
///
|
||||
/// SafeOverwrite:
|
||||
/// Overwrites the data safely using a GPU memory barrier.
|
||||
/// </summary>
|
||||
public WriteOptions WriteOption;
|
||||
|
||||
public DepthStencilAttachmentInfo(
|
||||
Texture texture,
|
||||
TextureSlice textureSlice,
|
||||
WriteOptions writeOption,
|
||||
DepthStencilValue clearValue,
|
||||
StoreOp depthStoreOp = StoreOp.Store,
|
||||
StoreOp stencilStoreOp = StoreOp.Store
|
||||
)
|
||||
{
|
||||
Texture = texture;
|
||||
Depth = 0;
|
||||
Layer = 0;
|
||||
Level = 0;
|
||||
StoreOp depthStoreOp = StoreOp.DontCare,
|
||||
StoreOp stencilStoreOp = StoreOp.DontCare
|
||||
){
|
||||
TextureSlice = textureSlice;
|
||||
DepthStencilClearValue = clearValue;
|
||||
LoadOp = LoadOp.Clear;
|
||||
StoreOp = depthStoreOp;
|
||||
StencilLoadOp = LoadOp.Clear;
|
||||
StencilStoreOp = stencilStoreOp;
|
||||
WriteOption = writeOption;
|
||||
}
|
||||
|
||||
public DepthStencilAttachmentInfo(
|
||||
Texture texture,
|
||||
TextureSlice textureSlice,
|
||||
WriteOptions writeOption,
|
||||
LoadOp loadOp = LoadOp.DontCare,
|
||||
StoreOp storeOp = StoreOp.Store,
|
||||
StoreOp storeOp = StoreOp.DontCare,
|
||||
LoadOp stencilLoadOp = LoadOp.DontCare,
|
||||
StoreOp stencilStoreOp = StoreOp.Store
|
||||
StoreOp stencilStoreOp = StoreOp.DontCare
|
||||
) {
|
||||
Texture = texture;
|
||||
Depth = 0;
|
||||
Layer = 0;
|
||||
Level = 0;
|
||||
TextureSlice = textureSlice;
|
||||
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;
|
||||
WriteOption = writeOption;
|
||||
}
|
||||
|
||||
public Refresh.DepthStencilAttachmentInfo ToRefresh()
|
||||
{
|
||||
return new Refresh.DepthStencilAttachmentInfo
|
||||
{
|
||||
texture = Texture.Handle,
|
||||
depth = Depth,
|
||||
layer = Layer,
|
||||
level = Level,
|
||||
textureSlice = TextureSlice.ToRefreshTextureSlice(),
|
||||
depthStencilClearValue = DepthStencilClearValue.ToRefresh(),
|
||||
loadOp = (Refresh.LoadOp) LoadOp,
|
||||
storeOp = (Refresh.StoreOp) StoreOp,
|
||||
stencilLoadOp = (Refresh.LoadOp) StencilLoadOp,
|
||||
stencilStoreOp = (Refresh.StoreOp) StencilStoreOp
|
||||
stencilStoreOp = (Refresh.StoreOp) StencilStoreOp,
|
||||
writeOption = (Refresh.WriteOptions) WriteOption
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -354,4 +436,55 @@ 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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, WriteOptions)> BufferUploads = new List<(GpuBuffer, BufferCopy, WriteOptions)>();
|
||||
List<(TextureRegion, uint, WriteOptions)> TextureUploads = new List<(TextureRegion, uint, WriteOptions)>();
|
||||
|
||||
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, WriteOptions.Unsafe);
|
||||
|
||||
return gpuBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares upload of data into a GpuBuffer.
|
||||
/// </summary>
|
||||
public void SetBufferData<T>(GpuBuffer buffer, uint bufferOffsetInElements, Span<T> data, WriteOptions option) 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, option));
|
||||
}
|
||||
|
||||
// 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, WriteOptions.Unsafe);
|
||||
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, WriteOptions.Unsafe);
|
||||
|
||||
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, WriteOptions.Unsafe);
|
||||
|
||||
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, WriteOptions option) 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.TexelSize(textureRegion.TextureSlice.Texture.Format));
|
||||
}
|
||||
|
||||
TextureUploads.Add((textureRegion, resourceOffset, option));
|
||||
}
|
||||
|
||||
// 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 (BufferUploads.Count > 0)
|
||||
{
|
||||
if (BufferTransferBuffer == null || BufferTransferBuffer.Size < bufferDataSize)
|
||||
{
|
||||
BufferTransferBuffer?.Dispose();
|
||||
BufferTransferBuffer = new TransferBuffer(Device, TransferUsage.Buffer, bufferDataSize);
|
||||
}
|
||||
|
||||
var dataSpan = new Span<byte>(bufferData, (int) bufferDataSize);
|
||||
BufferTransferBuffer.SetData(dataSpan, TransferOptions.Cycle);
|
||||
}
|
||||
|
||||
|
||||
if (TextureUploads.Count > 0)
|
||||
{
|
||||
if (TextureTransferBuffer == null || TextureTransferBuffer.Size < textureDataSize)
|
||||
{
|
||||
TextureTransferBuffer?.Dispose();
|
||||
TextureTransferBuffer = new TransferBuffer(Device, TransferUsage.Texture, textureDataSize);
|
||||
}
|
||||
|
||||
var dataSpan = new Span<byte>(textureData, (int) textureDataSize);
|
||||
TextureTransferBuffer.SetData(dataSpan, TransferOptions.Cycle);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecordUploadCommands(CommandBuffer commandBuffer)
|
||||
{
|
||||
commandBuffer.BeginCopyPass();
|
||||
|
||||
foreach (var (gpuBuffer, bufferCopyParams, option) in BufferUploads)
|
||||
{
|
||||
commandBuffer.UploadToBuffer(
|
||||
BufferTransferBuffer,
|
||||
gpuBuffer,
|
||||
bufferCopyParams,
|
||||
option
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var (textureRegion, offset, option) in TextureUploads)
|
||||
{
|
||||
commandBuffer.UploadToTexture(
|
||||
TextureTransferBuffer,
|
||||
textureRegion,
|
||||
new BufferImageCopy(
|
||||
offset,
|
||||
0,
|
||||
0
|
||||
),
|
||||
option
|
||||
);
|
||||
}
|
||||
|
||||
commandBuffer.EndCopyPass();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
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; }
|
||||
|
||||
private string name;
|
||||
public string Name
|
||||
{
|
||||
get => name;
|
||||
|
||||
set
|
||||
{
|
||||
if (Device.DebugMode)
|
||||
{
|
||||
Refresh.Refresh_SetGpuBufferName(
|
||||
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_CreateGpuBuffer(
|
||||
device.Handle,
|
||||
(Refresh.BufferUsageFlags) usageFlags,
|
||||
sizeInBytes
|
||||
);
|
||||
Size = sizeInBytes;
|
||||
name = "";
|
||||
}
|
||||
|
||||
public static implicit operator BufferBinding(GpuBuffer b)
|
||||
{
|
||||
return new BufferBinding(b, 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -62,11 +62,14 @@ namespace MoonWorks.Graphics
|
|||
refreshGraphicsPipelineCreateInfo.blendConstants[3] = blendConstants.A;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.backStencilState = depthStencilState.BackStencilState.ToRefresh();
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.frontStencilState = depthStencilState.FrontStencilState.ToRefresh();
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.compareMask = depthStencilState.CompareMask;
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.writeMask = depthStencilState.WriteMask;
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.reference = depthStencilState.Reference;
|
||||
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);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
@ -15,170 +14,35 @@ namespace MoonWorks.Graphics
|
|||
public uint Depth { get; }
|
||||
public TextureFormat Format { get; internal set; }
|
||||
public bool IsCube { get; }
|
||||
public uint LayerCount { get; }
|
||||
public uint LevelCount { get; }
|
||||
public SampleCount SampleCount { get; }
|
||||
public TextureUsageFlags UsageFlags { get; }
|
||||
public uint Size { get; }
|
||||
|
||||
private string name;
|
||||
public string Name
|
||||
{
|
||||
get => name;
|
||||
|
||||
set
|
||||
{
|
||||
if (Device.DebugMode)
|
||||
{
|
||||
Refresh.Refresh_SetTextureName(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 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>
|
||||
|
@ -203,6 +67,7 @@ namespace MoonWorks.Graphics
|
|||
Height = height,
|
||||
Depth = 1,
|
||||
IsCube = false,
|
||||
LayerCount = 1,
|
||||
LevelCount = levelCount,
|
||||
SampleCount = sampleCount,
|
||||
Format = format,
|
||||
|
@ -213,15 +78,43 @@ namespace MoonWorks.Graphics
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 3D texture.
|
||||
/// Creates a 2D texture array.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="width">The width 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="usageFlags">Specifies how the texture will be used.</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(
|
||||
GraphicsDevice device,
|
||||
uint width,
|
||||
|
@ -237,6 +130,7 @@ namespace MoonWorks.Graphics
|
|||
Height = height,
|
||||
Depth = depth,
|
||||
IsCube = false,
|
||||
LayerCount = 1,
|
||||
LevelCount = levelCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
|
@ -266,6 +160,7 @@ namespace MoonWorks.Graphics
|
|||
Height = size,
|
||||
Depth = 1,
|
||||
IsCube = true,
|
||||
LayerCount = 6,
|
||||
LevelCount = levelCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
|
@ -294,16 +189,15 @@ namespace MoonWorks.Graphics
|
|||
Height = textureCreateInfo.Height;
|
||||
Depth = textureCreateInfo.Depth;
|
||||
IsCube = textureCreateInfo.IsCube;
|
||||
LayerCount = textureCreateInfo.LayerCount;
|
||||
LevelCount = textureCreateInfo.LevelCount;
|
||||
SampleCount = textureCreateInfo.SampleCount;
|
||||
UsageFlags = textureCreateInfo.UsageFlags;
|
||||
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
|
||||
name = "";
|
||||
}
|
||||
|
||||
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
|
||||
|
||||
// Used by AcquireSwapchainTexture.
|
||||
// Should not be tracked, because swapchain textures are managed by Vulkan.
|
||||
// Used by Window. Swapchain texture handles are managed by the driver backend.
|
||||
internal Texture(
|
||||
GraphicsDevice device,
|
||||
TextureFormat format
|
||||
|
@ -322,333 +216,6 @@ namespace MoonWorks.Graphics
|
|||
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)
|
||||
{
|
||||
switch (format)
|
||||
|
@ -697,6 +264,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)
|
||||
{
|
||||
switch (format)
|
||||
|
@ -739,5 +321,8 @@ namespace MoonWorks.Graphics
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
|
||||
public static implicit operator TextureRegion(Texture t) => new TextureRegion(t);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
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,
|
||||
TransferUsage usage,
|
||||
uint elementCount
|
||||
) where T : unmanaged
|
||||
{
|
||||
return new TransferBuffer(
|
||||
device,
|
||||
usage,
|
||||
(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,
|
||||
uint sizeInBytes
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateTransferBuffer(
|
||||
device.Handle,
|
||||
(Refresh.TransferUsage) usage,
|
||||
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,
|
||||
TransferOptions 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_SetTransferData(
|
||||
Device.Handle,
|
||||
(nint) dataPtr,
|
||||
Handle,
|
||||
new Refresh.BufferCopy
|
||||
{
|
||||
srcOffset = 0,
|
||||
dstOffset = bufferOffsetInBytes,
|
||||
size = dataLengthInBytes
|
||||
},
|
||||
(Refresh.TransferOptions) 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,
|
||||
TransferOptions 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_GetTransferData(
|
||||
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($"Data overflow! Transfer buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -11,15 +11,30 @@
|
|||
public bool DepthTestEnable;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the stencil operation for back-facing primitives.
|
||||
/// Describes the back-face stencil operation.
|
||||
/// </summary>
|
||||
public StencilOpState BackStencilState;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the stencil operation for front-facing primitives.
|
||||
/// Describes the front-face stencil operation.
|
||||
/// </summary>
|
||||
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>
|
||||
/// The comparison operator used in the depth test.
|
||||
/// </summary>
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace MoonWorks.Graphics
|
|||
public uint Height;
|
||||
public uint Depth;
|
||||
public bool IsCube;
|
||||
public uint LayerCount;
|
||||
public uint LevelCount;
|
||||
public SampleCount SampleCount;
|
||||
public TextureFormat Format;
|
||||
|
@ -24,6 +25,7 @@ namespace MoonWorks.Graphics
|
|||
height = Height,
|
||||
depth = Depth,
|
||||
isCube = Conversions.BoolToByte(IsCube),
|
||||
layerCount = LayerCount,
|
||||
levelCount = LevelCount,
|
||||
sampleCount = (Refresh.SampleCount) SampleCount,
|
||||
format = (Refresh.TextureFormat) Format,
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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);
|
||||
}
|
|
@ -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 ToRefreshTextureRegion()
|
||||
{
|
||||
return new Refresh.TextureRegion
|
||||
{
|
||||
textureSlice = TextureSlice.ToRefreshTextureSlice(),
|
||||
x = X,
|
||||
y = Y,
|
||||
z = Z,
|
||||
w = Width,
|
||||
h = Height,
|
||||
d = Depth
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,55 +3,31 @@
|
|||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// A texture slice specifies a subregion of a texture.
|
||||
/// Many operations can use texture slices in place of textures for the sake of convenience.
|
||||
/// A texture slice specifies a subresource of a texture.
|
||||
/// </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 Layer;
|
||||
|
||||
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)
|
||||
{
|
||||
Texture = texture;
|
||||
Rectangle = new Rect
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
W = (int) texture.Width,
|
||||
H = (int) texture.Height
|
||||
};
|
||||
Depth = 0;
|
||||
MipLevel = 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;
|
||||
}
|
||||
|
||||
public Refresh.TextureSlice ToRefreshTextureSlice()
|
||||
{
|
||||
Refresh.TextureSlice textureSlice = new Refresh.TextureSlice
|
||||
return new Refresh.TextureSlice
|
||||
{
|
||||
texture = Texture.Handle,
|
||||
rectangle = Rectangle.ToRefresh(),
|
||||
depth = Depth,
|
||||
layer = Layer,
|
||||
level = Level
|
||||
mipLevel = MipLevel,
|
||||
layer = Layer
|
||||
};
|
||||
|
||||
return textureSlice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace MoonWorks.Video
|
|||
public IntPtr Handle => handle;
|
||||
IntPtr handle;
|
||||
|
||||
public bool Loaded => handle != IntPtr.Zero;
|
||||
public bool Ended => Dav1dfile.df_eos(Handle) == 1;
|
||||
|
||||
public IntPtr yDataHandle;
|
||||
|
@ -20,21 +21,41 @@ namespace MoonWorks.Video
|
|||
|
||||
public bool FrameDataUpdated { get; set; }
|
||||
|
||||
private VideoAV1 Parent;
|
||||
|
||||
public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device)
|
||||
{
|
||||
if (Dav1dfile.df_fopen(video.Filename, out handle) == 0)
|
||||
{
|
||||
throw new Exception("Failed to open video file!");
|
||||
}
|
||||
handle = IntPtr.Zero;
|
||||
Parent = video;
|
||||
}
|
||||
|
||||
Reset();
|
||||
public void Load()
|
||||
{
|
||||
if (!Loaded)
|
||||
{
|
||||
if (Dav1dfile.df_fopen(Parent.Filename, out handle) == 0)
|
||||
{
|
||||
throw new Exception("Failed to load video file!");
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
if (Loaded)
|
||||
{
|
||||
Dav1dfile.df_close(handle);
|
||||
handle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
Dav1dfile.df_reset(Handle);
|
||||
Dav1dfile.df_reset(handle);
|
||||
ReadNextFrame();
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +67,7 @@ namespace MoonWorks.Video
|
|||
if (!Ended)
|
||||
{
|
||||
if (Dav1dfile.df_readvideo(
|
||||
Handle,
|
||||
handle,
|
||||
1,
|
||||
out var yDataHandle,
|
||||
out var uDataHandle,
|
||||
|
@ -74,7 +95,7 @@ namespace MoonWorks.Video
|
|||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
Dav1dfile.df_close(Handle);
|
||||
Unload();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using MoonWorks.Graphics;
|
||||
|
||||
namespace MoonWorks.Video
|
||||
|
@ -18,16 +17,13 @@ namespace MoonWorks.Video
|
|||
private VideoAV1 Video = null;
|
||||
private VideoAV1Stream CurrentStream = null;
|
||||
|
||||
private Task ReadNextFrameTask;
|
||||
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 +32,6 @@ namespace MoonWorks.Video
|
|||
|
||||
public VideoPlayer(GraphicsDevice device) : base(device)
|
||||
{
|
||||
GraphicsDevice = device;
|
||||
|
||||
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
|
||||
|
||||
timer = new Stopwatch();
|
||||
|
@ -55,46 +49,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;
|
||||
|
@ -155,7 +149,7 @@ namespace MoonWorks.Video
|
|||
lastTimestamp = 0;
|
||||
timeElapsed = 0;
|
||||
|
||||
InitializeDav1dStream();
|
||||
ResetDav1dStream();
|
||||
|
||||
State = VideoState.Stopped;
|
||||
}
|
||||
|
@ -165,9 +159,17 @@ namespace MoonWorks.Video
|
|||
/// </summary>
|
||||
public void Unload()
|
||||
{
|
||||
Stop();
|
||||
ResetStreamATask?.Wait();
|
||||
ResetStreamBTask?.Wait();
|
||||
timer.Stop();
|
||||
timer.Reset();
|
||||
|
||||
lastTimestamp = 0;
|
||||
timeElapsed = 0;
|
||||
|
||||
State = VideoState.Stopped;
|
||||
|
||||
Video.StreamA.Unload();
|
||||
Video.StreamB.Unload();
|
||||
|
||||
Video = null;
|
||||
}
|
||||
|
||||
|
@ -194,8 +196,7 @@ namespace MoonWorks.Video
|
|||
}
|
||||
|
||||
currentFrame = thisFrame;
|
||||
ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame);
|
||||
ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
CurrentStream.ReadNextFrame();
|
||||
}
|
||||
|
||||
if (CurrentStream.Ended)
|
||||
|
@ -203,17 +204,7 @@ namespace MoonWorks.Video
|
|||
timer.Stop();
|
||||
timer.Reset();
|
||||
|
||||
var task = Task.Run(CurrentStream.Reset);
|
||||
task.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
|
||||
if (CurrentStream == Video.StreamA)
|
||||
{
|
||||
ResetStreamATask = task;
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetStreamBTask = task;
|
||||
}
|
||||
CurrentStream.Reset();
|
||||
|
||||
if (Loop)
|
||||
{
|
||||
|
@ -233,37 +224,76 @@ 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, TransferUsage.Texture, (uint) (ySpan.Length + uSpan.Length + vSpan.Length));
|
||||
}
|
||||
TransferBuffer.SetData(ySpan, 0, TransferOptions.Cycle);
|
||||
TransferBuffer.SetData(uSpan, (uint) ySpan.Length, TransferOptions.Unsafe);
|
||||
TransferBuffer.SetData(vSpan, (uint) (ySpan.Length + uSpan.Length), TransferOptions.Unsafe);
|
||||
|
||||
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
|
||||
},
|
||||
WriteOptions.Cycle
|
||||
);
|
||||
|
||||
commandBuffer.UploadToTexture(
|
||||
TransferBuffer,
|
||||
uTexture,
|
||||
new BufferImageCopy{
|
||||
BufferOffset = (uint) ySpan.Length,
|
||||
BufferStride = CurrentStream.uvStride,
|
||||
BufferImageHeight = uTexture.Height
|
||||
},
|
||||
WriteOptions.Cycle
|
||||
);
|
||||
|
||||
commandBuffer.UploadToTexture(
|
||||
TransferBuffer,
|
||||
vTexture,
|
||||
new BufferImageCopy
|
||||
{
|
||||
BufferOffset = (uint) (ySpan.Length + uSpan.Length),
|
||||
BufferStride = CurrentStream.uvStride,
|
||||
BufferImageHeight = vTexture.Height
|
||||
},
|
||||
WriteOptions.Cycle
|
||||
);
|
||||
|
||||
commandBuffer.EndCopyPass();
|
||||
|
||||
commandBuffer.BeginRenderPass(
|
||||
new ColorAttachmentInfo(RenderTexture, Color.Black)
|
||||
new ColorAttachmentInfo(RenderTexture, WriteOptions.Cycle, 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,23 +321,20 @@ namespace MoonWorks.Video
|
|||
|
||||
private void InitializeDav1dStream()
|
||||
{
|
||||
ReadNextFrameTask?.Wait();
|
||||
|
||||
ResetStreamATask = Task.Run(Video.StreamA.Reset);
|
||||
ResetStreamATask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
ResetStreamBTask = Task.Run(Video.StreamB.Reset);
|
||||
ResetStreamBTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
Video.StreamA.Load();
|
||||
Video.StreamB.Load();
|
||||
|
||||
CurrentStream = Video.StreamA;
|
||||
currentFrame = -1;
|
||||
}
|
||||
|
||||
private static void HandleTaskException(Task task)
|
||||
private void ResetDav1dStream()
|
||||
{
|
||||
if (task.Exception.InnerException is not TaskCanceledException)
|
||||
{
|
||||
throw task.Exception;
|
||||
}
|
||||
Video.StreamA.Reset();
|
||||
Video.StreamB.Reset();
|
||||
|
||||
CurrentStream = Video.StreamA;
|
||||
currentFrame = -1;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
|
|
@ -21,6 +21,15 @@ namespace MoonWorks
|
|||
public bool Claimed { 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);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
private static Dictionary<uint, Window> idLookup = new Dictionary<uint, Window>();
|
||||
|
@ -113,6 +122,14 @@ namespace MoonWorks
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the window position.
|
||||
/// </summary>
|
||||
public void SetPosition(int x, int y)
|
||||
{
|
||||
SDL.SDL_SetWindowPosition(Handle, x, y);
|
||||
}
|
||||
|
||||
internal static Window Lookup(uint windowID)
|
||||
{
|
||||
return idLookup.ContainsKey(windowID) ? idLookup[windowID] : null;
|
||||
|
|
Loading…
Reference in New Issue