MSDF font rendering + improved resource tracking #52
|
@ -24,4 +24,19 @@
|
|||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_fullscreen.vert.refresh">
|
||||
<LogicalName>MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_yuv2rgba.frag.refresh">
|
||||
<LogicalName>MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_transform.vert.refresh">
|
||||
<LogicalName>MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_msdf.frag.refresh">
|
||||
<LogicalName>MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
<dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/>
|
||||
|
||||
<dllmap dll="Wellspring" os="windows" target="Wellspring.dll"/>
|
||||
<dllmap dll="Wellspring" os="osx" target="libWellspring.0.dylib"/>
|
||||
<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.0"/>
|
||||
<dllmap dll="Wellspring" os="osx" target="libWellspring.1.dylib"/>
|
||||
<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.1"/>
|
||||
|
||||
<dllmap dll="dav1dfile" os="windows" target="dav1dfile.dll"/>
|
||||
<dllmap dll="dav1dfile" os="osx" target="libdav1dfile.1.dylib"/>
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit f8872bae59e394b0f8a35224bb39ab8fd041af97
|
||||
Subproject commit 074f2afc833b221906bb2468735041ce78f2cb89
|
|
@ -1 +1 @@
|
|||
Subproject commit 3dcd69ff85db80eea51481edd323b42c05993e1a
|
||||
Subproject commit 5065e2cd4662dbe023b77a45ef967f975170dfff
|
|
@ -25,7 +25,7 @@ namespace MoonWorks.Audio
|
|||
public float DopplerScale = 1f;
|
||||
public float SpeedOfSound = 343.5f;
|
||||
|
||||
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
|
||||
private readonly HashSet<GCHandle> resourceHandles = new HashSet<GCHandle>();
|
||||
private readonly HashSet<UpdatingSourceVoice> updatingSourceVoices = new HashSet<UpdatingSourceVoice>();
|
||||
|
||||
private AudioTweenManager AudioTweenManager;
|
||||
|
@ -123,7 +123,6 @@ namespace MoonWorks.Audio
|
|||
AudioTweenManager = new AudioTweenManager();
|
||||
VoicePool = new SourceVoicePool(this);
|
||||
|
||||
Logger.LogInfo("Setting up audio thread...");
|
||||
WakeSignal = new AutoResetEvent(true);
|
||||
|
||||
Thread = new Thread(ThreadMain);
|
||||
|
@ -265,7 +264,7 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
resources.Add(resourceReference);
|
||||
resourceHandles.Add(resourceReference);
|
||||
|
||||
if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
|
||||
{
|
||||
|
@ -278,7 +277,12 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
resources.Remove(resourceReference);
|
||||
resourceHandles.Remove(resourceReference);
|
||||
|
||||
if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
|
||||
{
|
||||
updatingSourceVoices.Remove(updatableVoice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,28 +296,42 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
Thread.Join();
|
||||
|
||||
// dispose all voices first
|
||||
foreach (var resource in resources)
|
||||
// dispose all source voices first
|
||||
foreach (var handle in resourceHandles)
|
||||
{
|
||||
if (resource.Target is Voice voice)
|
||||
if (handle.Target is SourceVoice voice)
|
||||
{
|
||||
voice.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// destroy all other audio resources
|
||||
foreach (var resource in resources)
|
||||
// dispose all submix voices except the faux mastering voice
|
||||
foreach (var handle in resourceHandles)
|
||||
{
|
||||
if (resource.Target is IDisposable disposable)
|
||||
if (handle.Target is SubmixVoice voice && voice != fauxMasteringVoice)
|
||||
{
|
||||
disposable.Dispose();
|
||||
voice.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
resources.Clear();
|
||||
// dispose the faux mastering voice
|
||||
fauxMasteringVoice.Dispose();
|
||||
|
||||
// dispose the true mastering voice
|
||||
FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice);
|
||||
|
||||
// destroy all other audio resources
|
||||
foreach (var handle in resourceHandles)
|
||||
{
|
||||
if (handle.Target is AudioResource resource)
|
||||
{
|
||||
resource.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
resourceHandles.Clear();
|
||||
}
|
||||
|
||||
FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice);
|
||||
FAudio.FAudio_Release(Handle);
|
||||
|
||||
IsDisposed = true;
|
||||
|
|
|
@ -150,13 +150,16 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
Stop();
|
||||
|
||||
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
||||
lock (StateLock)
|
||||
{
|
||||
if (buffers[i] != IntPtr.Zero)
|
||||
Stop();
|
||||
|
||||
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
||||
{
|
||||
NativeMemory.Free((void*) buffers[i]);
|
||||
if (buffers[i] != IntPtr.Zero)
|
||||
{
|
||||
NativeMemory.Free((void*) buffers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
src/Game.cs
15
src/Game.cs
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using SDL2;
|
||||
using SDL2;
|
||||
using MoonWorks.Audio;
|
||||
using MoonWorks.Graphics;
|
||||
using MoonWorks.Input;
|
||||
|
@ -58,6 +57,7 @@ namespace MoonWorks
|
|||
bool debugMode = false
|
||||
)
|
||||
{
|
||||
Logger.LogInfo("Initializing frame limiter...");
|
||||
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
|
||||
gameTimer = Stopwatch.StartNew();
|
||||
|
||||
|
@ -68,6 +68,7 @@ namespace MoonWorks
|
|||
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
|
||||
}
|
||||
|
||||
Logger.LogInfo("Initializing SDL...");
|
||||
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0)
|
||||
{
|
||||
Logger.LogError("Failed to initialize SDL!");
|
||||
|
@ -76,13 +77,16 @@ namespace MoonWorks
|
|||
|
||||
Logger.Initialize();
|
||||
|
||||
Logger.LogInfo("Initializing input...");
|
||||
Inputs = new Inputs();
|
||||
|
||||
Logger.LogInfo("Initializing graphics device...");
|
||||
GraphicsDevice = new GraphicsDevice(
|
||||
Backend.Vulkan,
|
||||
debugMode
|
||||
);
|
||||
|
||||
Logger.LogInfo("Initializing main window...");
|
||||
MainWindow = new Window(windowCreateInfo, GraphicsDevice.WindowFlags | SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN);
|
||||
|
||||
if (!GraphicsDevice.ClaimWindow(MainWindow, windowCreateInfo.PresentMode))
|
||||
|
@ -90,6 +94,7 @@ namespace MoonWorks
|
|||
throw new System.SystemException("Could not claim window!");
|
||||
}
|
||||
|
||||
Logger.LogInfo("Initializing audio thread...");
|
||||
AudioDevice = new AudioDevice();
|
||||
}
|
||||
|
||||
|
@ -110,9 +115,6 @@ namespace MoonWorks
|
|||
Logger.LogInfo("Cleaning up game...");
|
||||
Destroy();
|
||||
|
||||
Logger.LogInfo("Closing audio thread...");
|
||||
AudioDevice.Dispose();
|
||||
|
||||
Logger.LogInfo("Unclaiming window...");
|
||||
GraphicsDevice.UnclaimWindow(MainWindow);
|
||||
|
||||
|
@ -122,6 +124,9 @@ namespace MoonWorks
|
|||
Logger.LogInfo("Disposing graphics device...");
|
||||
GraphicsDevice.Dispose();
|
||||
|
||||
Logger.LogInfo("Closing audio thread...");
|
||||
AudioDevice.Dispose();
|
||||
|
||||
SDL.SDL_Quit();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,45 +5,117 @@ using WellspringCS;
|
|||
|
||||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
public class Font : IDisposable
|
||||
{
|
||||
public IntPtr Handle { get; }
|
||||
public unsafe class Font : GraphicsResource
|
||||
{
|
||||
public Texture Texture { get; }
|
||||
public float PixelsPerEm { get; }
|
||||
public float DistanceRange { get; }
|
||||
|
||||
private bool IsDisposed;
|
||||
internal IntPtr Handle { get; }
|
||||
|
||||
public unsafe Font(string path)
|
||||
{
|
||||
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
var fileByteBuffer = NativeMemory.Alloc((nuint) fileStream.Length);
|
||||
var fileByteSpan = new Span<byte>(fileByteBuffer, (int) fileStream.Length);
|
||||
fileStream.ReadExactly(fileByteSpan);
|
||||
fileStream.Close();
|
||||
private byte* StringBytes;
|
||||
private int StringBytesLength;
|
||||
|
||||
Handle = Wellspring.Wellspring_CreateFont((IntPtr) fileByteBuffer, (uint) fileByteSpan.Length);
|
||||
/// <summary>
|
||||
/// Loads a TTF or OTF font from a path for use in MSDF rendering.
|
||||
/// Note that there must be an msdf-atlas-gen JSON and image file alongside.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public unsafe static Font Load(
|
||||
GraphicsDevice graphicsDevice,
|
||||
CommandBuffer commandBuffer,
|
||||
string fontPath
|
||||
) {
|
||||
var fontFileStream = new FileStream(fontPath, FileMode.Open, FileAccess.Read);
|
||||
var fontFileByteBuffer = NativeMemory.Alloc((nuint) fontFileStream.Length);
|
||||
var fontFileByteSpan = new Span<byte>(fontFileByteBuffer, (int) fontFileStream.Length);
|
||||
fontFileStream.ReadExactly(fontFileByteSpan);
|
||||
fontFileStream.Close();
|
||||
|
||||
NativeMemory.Free(fileByteBuffer);
|
||||
}
|
||||
var atlasFileStream = new FileStream(Path.ChangeExtension(fontPath, ".json"), FileMode.Open, FileAccess.Read);
|
||||
var atlasFileByteBuffer = NativeMemory.Alloc((nuint) atlasFileStream.Length);
|
||||
var atlasFileByteSpan = new Span<byte>(atlasFileByteBuffer, (int) atlasFileStream.Length);
|
||||
atlasFileStream.ReadExactly(atlasFileByteSpan);
|
||||
atlasFileStream.Close();
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
var handle = Wellspring.Wellspring_CreateFont(
|
||||
(IntPtr) fontFileByteBuffer,
|
||||
(uint) fontFileByteSpan.Length,
|
||||
(IntPtr) atlasFileByteBuffer,
|
||||
(uint) atlasFileByteSpan.Length,
|
||||
out float pixelsPerEm,
|
||||
out float distanceRange
|
||||
);
|
||||
|
||||
var texture = Texture.FromImageFile(graphicsDevice, commandBuffer, Path.ChangeExtension(fontPath, ".png"));
|
||||
|
||||
NativeMemory.Free(fontFileByteBuffer);
|
||||
NativeMemory.Free(atlasFileByteBuffer);
|
||||
|
||||
return new Font(graphicsDevice, handle, texture, pixelsPerEm, distanceRange);
|
||||
}
|
||||
|
||||
private Font(GraphicsDevice device, IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange) : base(device)
|
||||
{
|
||||
Handle = handle;
|
||||
Texture = texture;
|
||||
PixelsPerEm = pixelsPerEm;
|
||||
DistanceRange = distanceRange;
|
||||
|
||||
StringBytesLength = 32;
|
||||
StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength);
|
||||
}
|
||||
|
||||
public unsafe bool TextBounds(
|
||||
string text,
|
||||
int pixelSize,
|
||||
HorizontalAlignment horizontalAlignment,
|
||||
VerticalAlignment verticalAlignment,
|
||||
out Wellspring.Rectangle rectangle
|
||||
) {
|
||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
|
||||
|
||||
if (StringBytesLength < byteCount)
|
||||
{
|
||||
StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount);
|
||||
}
|
||||
|
||||
fixed (char* chars = text)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount);
|
||||
|
||||
var result = Wellspring.Wellspring_TextBounds(
|
||||
Handle,
|
||||
pixelSize,
|
||||
(Wellspring.HorizontalAlignment) horizontalAlignment,
|
||||
(Wellspring.VerticalAlignment) verticalAlignment,
|
||||
(IntPtr) StringBytes,
|
||||
(uint) byteCount,
|
||||
out rectangle
|
||||
);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
Logger.LogWarn("Could not decode string: " + text);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Texture.Dispose();
|
||||
}
|
||||
|
||||
Wellspring.Wellspring_DestroyFont(Handle);
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Font()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using WellspringCS;
|
||||
|
||||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
public class Packer : IDisposable
|
||||
{
|
||||
public IntPtr Handle { get; }
|
||||
public Texture Texture { get; }
|
||||
|
||||
public Font Font { get; }
|
||||
|
||||
private byte[] StringBytes;
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
public unsafe Packer(GraphicsDevice graphicsDevice, Font font, float fontSize, uint textureWidth, uint textureHeight, uint padding = 1)
|
||||
{
|
||||
Font = font;
|
||||
Handle = Wellspring.Wellspring_CreatePacker(Font.Handle, fontSize, textureWidth, textureHeight, 0, padding);
|
||||
Texture = Texture.CreateTexture2D(graphicsDevice, textureWidth, textureHeight, TextureFormat.R8, TextureUsageFlags.Sampler);
|
||||
StringBytes = new byte[128];
|
||||
}
|
||||
|
||||
public unsafe bool PackFontRanges(params FontRange[] fontRanges)
|
||||
{
|
||||
fixed (FontRange *pFontRanges = &fontRanges[0])
|
||||
{
|
||||
var nativeSize = fontRanges.Length * Marshal.SizeOf<Wellspring.FontRange>();
|
||||
var result = Wellspring.Wellspring_PackFontRanges(Handle, (IntPtr) pFontRanges, (uint) fontRanges.Length);
|
||||
return result > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetTextureData(CommandBuffer commandBuffer)
|
||||
{
|
||||
var pixelDataPointer = Wellspring.Wellspring_GetPixelDataPointer(Handle);
|
||||
commandBuffer.SetTextureData(Texture, pixelDataPointer, Texture.Width * Texture.Height);
|
||||
}
|
||||
|
||||
public unsafe void TextBounds(
|
||||
string text,
|
||||
float x,
|
||||
float y,
|
||||
HorizontalAlignment horizontalAlignment,
|
||||
VerticalAlignment verticalAlignment,
|
||||
out Wellspring.Rectangle rectangle
|
||||
) {
|
||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
|
||||
|
||||
if (StringBytes.Length < byteCount)
|
||||
{
|
||||
System.Array.Resize(ref StringBytes, byteCount);
|
||||
}
|
||||
|
||||
fixed (char* chars = text)
|
||||
fixed (byte* bytes = StringBytes)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, bytes, byteCount);
|
||||
Wellspring.Wellspring_TextBounds(
|
||||
Handle,
|
||||
x,
|
||||
y,
|
||||
(Wellspring.HorizontalAlignment) horizontalAlignment,
|
||||
(Wellspring.VerticalAlignment) verticalAlignment,
|
||||
(IntPtr) bytes,
|
||||
(uint) byteCount,
|
||||
out rectangle
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Texture.Dispose();
|
||||
}
|
||||
|
||||
Wellspring.Wellspring_DestroyPacker(Handle);
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Packer()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,19 +4,17 @@ using MoonWorks.Math.Float;
|
|||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct FontRange
|
||||
{
|
||||
public uint FirstCodepoint;
|
||||
public uint NumChars;
|
||||
public byte OversampleH;
|
||||
public byte OversampleV;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vertex
|
||||
public struct Vertex : IVertexType
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Vector2 TexCoord;
|
||||
public Color Color;
|
||||
|
||||
public static VertexElementFormat[] Formats { get; } = new VertexElementFormat[]
|
||||
{
|
||||
VertexElementFormat.Vector3,
|
||||
VertexElementFormat.Vector2,
|
||||
VertexElementFormat.Color
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,75 +1,87 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using WellspringCS;
|
||||
|
||||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
public class TextBatch
|
||||
public unsafe class TextBatch : GraphicsResource
|
||||
{
|
||||
public const int INITIAL_CHAR_COUNT = 64;
|
||||
public const int INITIAL_VERTEX_COUNT = INITIAL_CHAR_COUNT * 4;
|
||||
public const int INITIAL_INDEX_COUNT = INITIAL_CHAR_COUNT * 6;
|
||||
|
||||
private GraphicsDevice GraphicsDevice { get; }
|
||||
public IntPtr Handle { get; }
|
||||
|
||||
public Buffer VertexBuffer { get; protected set; } = null;
|
||||
public Buffer IndexBuffer { get; protected set; } = null;
|
||||
public Texture Texture { get; protected set; }
|
||||
public uint PrimitiveCount { get; protected set; }
|
||||
|
||||
private byte[] StringBytes;
|
||||
public Font CurrentFont { get; private set; }
|
||||
|
||||
public TextBatch(GraphicsDevice graphicsDevice)
|
||||
private byte* StringBytes;
|
||||
private int StringBytesLength;
|
||||
|
||||
public TextBatch(GraphicsDevice device) : base(device)
|
||||
{
|
||||
GraphicsDevice = graphicsDevice;
|
||||
GraphicsDevice = device;
|
||||
Handle = Wellspring.Wellspring_CreateTextBatch();
|
||||
StringBytes = new byte[128];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public void Start(Packer packer)
|
||||
// Call this to initialize or reset the batch.
|
||||
public void Start(Font font)
|
||||
{
|
||||
Wellspring.Wellspring_StartTextBatch(Handle, packer.Handle);
|
||||
Texture = packer.Texture;
|
||||
Wellspring.Wellspring_StartTextBatch(Handle, font.Handle);
|
||||
CurrentFont = font;
|
||||
PrimitiveCount = 0;
|
||||
}
|
||||
|
||||
public unsafe void Draw(
|
||||
// Add text with size and color to the batch
|
||||
public unsafe bool Add(
|
||||
string text,
|
||||
float x,
|
||||
float y,
|
||||
float depth,
|
||||
int pixelSize,
|
||||
Color color,
|
||||
HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
|
||||
) {
|
||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
|
||||
|
||||
if (StringBytes.Length < byteCount)
|
||||
if (StringBytesLength < byteCount)
|
||||
{
|
||||
System.Array.Resize(ref StringBytes, byteCount);
|
||||
StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount);
|
||||
}
|
||||
|
||||
fixed (char* chars = text)
|
||||
fixed (byte* bytes = StringBytes)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, bytes, byteCount);
|
||||
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount);
|
||||
|
||||
var result = Wellspring.Wellspring_Draw(
|
||||
var result = Wellspring.Wellspring_AddToTextBatch(
|
||||
Handle,
|
||||
x,
|
||||
y,
|
||||
depth,
|
||||
pixelSize,
|
||||
new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
|
||||
(Wellspring.HorizontalAlignment) horizontalAlignment,
|
||||
(Wellspring.VerticalAlignment) verticalAlignment,
|
||||
(IntPtr) bytes,
|
||||
(IntPtr) StringBytes,
|
||||
(uint) byteCount
|
||||
);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
throw new System.ArgumentException("Could not decode string!");
|
||||
Logger.LogWarn("Could not decode string: " + text);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Call this after you have made all the Draw calls you want.
|
||||
// Call this after you have made all the Add calls you want, but before beginning a render pass.
|
||||
public unsafe void UploadBufferData(CommandBuffer commandBuffer)
|
||||
{
|
||||
Wellspring.Wellspring_GetBufferData(
|
||||
|
@ -81,24 +93,16 @@ namespace MoonWorks.Graphics.Font
|
|||
out uint indexDataLengthInBytes
|
||||
);
|
||||
|
||||
if (VertexBuffer == null)
|
||||
{
|
||||
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
||||
}
|
||||
else if (VertexBuffer.Size < vertexDataLengthInBytes)
|
||||
if (VertexBuffer.Size < vertexDataLengthInBytes)
|
||||
{
|
||||
VertexBuffer.Dispose();
|
||||
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
||||
}
|
||||
|
||||
if (IndexBuffer == null)
|
||||
{
|
||||
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes);
|
||||
}
|
||||
else if (IndexBuffer.Size < indexDataLengthInBytes)
|
||||
if (IndexBuffer.Size < indexDataLengthInBytes)
|
||||
{
|
||||
IndexBuffer.Dispose();
|
||||
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes);
|
||||
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
||||
}
|
||||
|
||||
if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
|
||||
|
@ -107,7 +111,41 @@ namespace MoonWorks.Graphics.Font
|
|||
commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes);
|
||||
}
|
||||
|
||||
PrimitiveCount = vertexCount / 2; // FIXME: is this jank?
|
||||
PrimitiveCount = vertexCount / 2;
|
||||
}
|
||||
|
||||
// Call this AFTER binding your text pipeline!
|
||||
public void Render(CommandBuffer commandBuffer, Math.Float.Matrix4x4 transformMatrix)
|
||||
{
|
||||
commandBuffer.BindFragmentSamplers(new TextureSamplerBinding(
|
||||
CurrentFont.Texture,
|
||||
GraphicsDevice.LinearSampler
|
||||
));
|
||||
commandBuffer.BindVertexBuffers(VertexBuffer);
|
||||
commandBuffer.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo);
|
||||
commandBuffer.DrawIndexedPrimitives(
|
||||
0,
|
||||
0,
|
||||
PrimitiveCount,
|
||||
commandBuffer.PushVertexShaderUniforms(transformMatrix),
|
||||
commandBuffer.PushFragmentShaderUniforms(CurrentFont.DistanceRange)
|
||||
);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
VertexBuffer.Dispose();
|
||||
IndexBuffer.Dispose();
|
||||
}
|
||||
|
||||
NativeMemory.Free(StringBytes);
|
||||
Wellspring.Wellspring_DestroyTextBatch(Handle);
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Video;
|
||||
using RefreshCS;
|
||||
using WellspringCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
|
@ -21,6 +22,15 @@ namespace MoonWorks.Graphics
|
|||
// Built-in video pipeline
|
||||
internal GraphicsPipeline VideoPipeline { get; }
|
||||
|
||||
// Built-in text shader info
|
||||
public GraphicsShaderInfo TextVertexShaderInfo { get; }
|
||||
public GraphicsShaderInfo TextFragmentShaderInfo { get; }
|
||||
public VertexInputState TextVertexInputState { get; }
|
||||
|
||||
// Built-in samplers
|
||||
public Sampler PointSampler { get; }
|
||||
public Sampler LinearSampler { get; }
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
|
||||
|
@ -41,43 +51,91 @@ namespace MoonWorks.Graphics
|
|||
Conversions.BoolToByte(debugMode)
|
||||
);
|
||||
|
||||
// Check for optional video shaders
|
||||
// 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 textVertPath = Path.Combine(basePath, "text_transform.vert.refresh");
|
||||
string textFragPath = Path.Combine(basePath, "text_msdf.frag.refresh");
|
||||
|
||||
ShaderModule videoVertShader;
|
||||
ShaderModule videoFragShader;
|
||||
|
||||
ShaderModule textVertShader;
|
||||
ShaderModule textFragShader;
|
||||
|
||||
if (File.Exists(videoVertPath) && File.Exists(videoFragPath))
|
||||
{
|
||||
ShaderModule videoVertShader = new ShaderModule(this, videoVertPath);
|
||||
ShaderModule videoFragShader = new ShaderModule(this, videoFragPath);
|
||||
|
||||
VideoPipeline = new GraphicsPipeline(
|
||||
this,
|
||||
new GraphicsPipelineCreateInfo
|
||||
{
|
||||
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
|
||||
new ColorAttachmentDescription(
|
||||
TextureFormat.R8G8B8A8,
|
||||
ColorAttachmentBlendState.None
|
||||
)
|
||||
),
|
||||
DepthStencilState = DepthStencilState.Disable,
|
||||
VertexShaderInfo = GraphicsShaderInfo.Create(
|
||||
videoVertShader,
|
||||
"main",
|
||||
0
|
||||
),
|
||||
FragmentShaderInfo = GraphicsShaderInfo.Create(
|
||||
videoFragShader,
|
||||
"main",
|
||||
3
|
||||
),
|
||||
VertexInputState = VertexInputState.Empty,
|
||||
RasterizerState = RasterizerState.CCW_CullNone,
|
||||
PrimitiveType = PrimitiveType.TriangleList,
|
||||
MultisampleState = MultisampleState.None
|
||||
}
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
if (File.Exists(textVertPath) && File.Exists(textFragPath))
|
||||
{
|
||||
textVertShader = new ShaderModule(this, textVertPath);
|
||||
textFragShader = new ShaderModule(this, textFragPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// use defaults
|
||||
var assembly = typeof(GraphicsDevice).Assembly;
|
||||
|
||||
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh");
|
||||
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh");
|
||||
|
||||
textVertShader = new ShaderModule(this, vertStream);
|
||||
textFragShader = new ShaderModule(this, fragStream);
|
||||
}
|
||||
|
||||
VideoPipeline = new GraphicsPipeline(
|
||||
this,
|
||||
new GraphicsPipelineCreateInfo
|
||||
{
|
||||
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
|
||||
new ColorAttachmentDescription(
|
||||
TextureFormat.R8G8B8A8,
|
||||
ColorAttachmentBlendState.None
|
||||
)
|
||||
),
|
||||
DepthStencilState = DepthStencilState.Disable,
|
||||
VertexShaderInfo = GraphicsShaderInfo.Create(
|
||||
videoVertShader,
|
||||
"main",
|
||||
0
|
||||
),
|
||||
FragmentShaderInfo = GraphicsShaderInfo.Create(
|
||||
videoFragShader,
|
||||
"main",
|
||||
3
|
||||
),
|
||||
VertexInputState = VertexInputState.Empty,
|
||||
RasterizerState = RasterizerState.CCW_CullNone,
|
||||
PrimitiveType = PrimitiveType.TriangleList,
|
||||
MultisampleState = MultisampleState.None
|
||||
}
|
||||
);
|
||||
|
||||
TextVertexShaderInfo = GraphicsShaderInfo.Create<Math.Float.Matrix4x4>(textVertShader, "main", 0);
|
||||
TextFragmentShaderInfo = GraphicsShaderInfo.Create<float>(textFragShader, "main", 1);
|
||||
TextVertexInputState = VertexInputState.CreateSingleBinding<Font.Vertex>();
|
||||
|
||||
PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp);
|
||||
LinearSampler = new Sampler(this, SamplerCreateInfo.LinearClamp);
|
||||
|
||||
FencePool = new FencePool(this);
|
||||
}
|
||||
|
@ -363,6 +421,16 @@ namespace MoonWorks.Graphics
|
|||
{
|
||||
lock (resources)
|
||||
{
|
||||
// Dispose video players first to avoid race condition on threaded decoding
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
if (resource.Target is VideoPlayer player)
|
||||
{
|
||||
player.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Dispose everything else
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
if (resource.Target is IDisposable disposable)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
|
@ -8,13 +7,11 @@ namespace MoonWorks.Graphics
|
|||
public abstract class GraphicsResource : IDisposable
|
||||
{
|
||||
public GraphicsDevice Device { get; }
|
||||
public IntPtr Handle { get => handle; internal set => handle = value; }
|
||||
private nint handle;
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
|
||||
|
||||
private GCHandle SelfReference;
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
protected GraphicsResource(GraphicsDevice device)
|
||||
{
|
||||
Device = device;
|
||||
|
@ -23,7 +20,7 @@ namespace MoonWorks.Graphics
|
|||
Device.AddResourceReference(SelfReference);
|
||||
}
|
||||
|
||||
protected void Dispose(bool disposing)
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
|
@ -33,13 +30,6 @@ namespace MoonWorks.Graphics
|
|||
SelfReference.Free();
|
||||
}
|
||||
|
||||
// Atomically call destroy function in case this is called from the finalizer thread
|
||||
var toDispose = Interlocked.Exchange(ref handle, IntPtr.Zero);
|
||||
if (toDispose != IntPtr.Zero)
|
||||
{
|
||||
QueueDestroyFunction(Device.Handle, toDispose);
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace MoonWorks.Graphics;
|
||||
|
||||
public abstract class RefreshResource : GraphicsResource
|
||||
{
|
||||
public IntPtr Handle { get => handle; internal set => handle = value; }
|
||||
private IntPtr handle;
|
||||
|
||||
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
|
||||
|
||||
protected RefreshResource(GraphicsDevice device) : base(device)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
// Atomically call destroy function in case this is called from the finalizer thread
|
||||
var toDispose = Interlocked.Exchange(ref handle, IntPtr.Zero);
|
||||
if (toDispose != IntPtr.Zero)
|
||||
{
|
||||
QueueDestroyFunction(Device.Handle, toDispose);
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ namespace MoonWorks.Graphics
|
|||
/// <summary>
|
||||
/// Buffers are generic data containers that can be used by the GPU.
|
||||
/// </summary>
|
||||
public class Buffer : GraphicsResource
|
||||
public class Buffer : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace MoonWorks.Graphics
|
|||
/// <summary>
|
||||
/// Compute pipelines perform arbitrary parallel processing on input data.
|
||||
/// </summary>
|
||||
public class ComputePipeline : GraphicsResource
|
||||
public class ComputePipeline : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace MoonWorks.Graphics
|
|||
/// The Fence object itself is basically just a wrapper for the Refresh_Fence. <br/>
|
||||
/// The internal handle is replaced so that we can pool Fence objects to manage garbage.
|
||||
/// </summary>
|
||||
public class Fence : GraphicsResource
|
||||
public class Fence : RefreshResource
|
||||
{
|
||||
protected override Action<nint, nint> QueueDestroyFunction => Refresh.Refresh_ReleaseFence;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
|
|||
/// Graphics pipelines encapsulate all of the render state in a single object. <br/>
|
||||
/// These pipelines are bound before draw calls are issued.
|
||||
/// </summary>
|
||||
public class GraphicsPipeline : GraphicsResource
|
||||
public class GraphicsPipeline : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace MoonWorks.Graphics
|
|||
/// <summary>
|
||||
/// A sampler specifies how a texture will be sampled in a shader.
|
||||
/// </summary>
|
||||
public class Sampler : GraphicsResource
|
||||
public class Sampler : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
|
|||
/// <summary>
|
||||
/// Shader modules expect input in Refresh bytecode format.
|
||||
/// </summary>
|
||||
public class ShaderModule : GraphicsResource
|
||||
public class ShaderModule : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
|
|||
/// <summary>
|
||||
/// A container for pixel data.
|
||||
/// </summary>
|
||||
public class Texture : GraphicsResource
|
||||
public class Texture : RefreshResource
|
||||
{
|
||||
public uint Width { get; internal set; }
|
||||
public uint Height { get; internal set; }
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,34 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2D msdf;
|
||||
|
||||
layout(location = 0) in vec2 inTexCoord;
|
||||
layout(location = 1) in vec4 inColor;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
layout(binding = 0, set = 3) uniform UBO
|
||||
{
|
||||
float pxRange;
|
||||
} ubo;
|
||||
|
||||
float median(float r, float g, float b)
|
||||
{
|
||||
return max(min(r, g), min(max(r, g), b));
|
||||
}
|
||||
|
||||
float screenPxRange()
|
||||
{
|
||||
vec2 unitRange = vec2(ubo.pxRange)/vec2(textureSize(msdf, 0));
|
||||
vec2 screenTexSize = vec2(1.0)/fwidth(inTexCoord);
|
||||
return max(0.5*dot(unitRange, screenTexSize), 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 msd = texture(msdf, inTexCoord).rgb;
|
||||
float sd = median(msd.r, msd.g, msd.b);
|
||||
float screenPxDistance = screenPxRange() * (sd - 0.5);
|
||||
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
|
||||
outColor = mix(vec4(0.0, 0.0, 0.0, 0.0), inColor, opacity);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 inPos;
|
||||
layout(location = 1) in vec2 inTexCoord;
|
||||
layout(location = 2) in vec4 inColor;
|
||||
|
||||
layout(location = 0) out vec2 outTexCoord;
|
||||
layout(location = 1) out vec4 outColor;
|
||||
|
||||
layout(binding = 0, set = 2) uniform UBO
|
||||
{
|
||||
mat4 ViewProjection;
|
||||
} ubo;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = ubo.ViewProjection * vec4(inPos, 1.0);
|
||||
outTexCoord = inTexCoord;
|
||||
outColor = inColor;
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using MoonWorks.Graphics;
|
||||
|
||||
namespace MoonWorks.Video
|
||||
{
|
||||
/// <summary>
|
||||
/// This class takes in a filename for AV1 data in .obu (open bitstream unit) format
|
||||
/// </summary>
|
||||
public unsafe class VideoAV1
|
||||
public unsafe class VideoAV1 : GraphicsResource
|
||||
{
|
||||
public string Filename { get; }
|
||||
|
||||
|
@ -28,7 +29,7 @@ namespace MoonWorks.Video
|
|||
/// <summary>
|
||||
/// Opens an AV1 file so it can be loaded by VideoPlayer. You must also provide a playback framerate.
|
||||
/// </summary>
|
||||
public VideoAV1(string filename, double framesPerSecond)
|
||||
public VideoAV1(GraphicsDevice device, string filename, double framesPerSecond) : base(device)
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
|
@ -67,8 +68,22 @@ namespace MoonWorks.Video
|
|||
|
||||
Filename = filename;
|
||||
|
||||
StreamA = new VideoAV1Stream(this);
|
||||
StreamB = new VideoAV1Stream(this);
|
||||
StreamA = new VideoAV1Stream(device, this);
|
||||
StreamB = new VideoAV1Stream(device, this);
|
||||
}
|
||||
|
||||
// NOTE: if you call this while a VideoPlayer is playing the stream, your program will explode
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
StreamA.Dispose();
|
||||
StreamB.Dispose();
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
using System;
|
||||
using MoonWorks.Graphics;
|
||||
|
||||
namespace MoonWorks.Video
|
||||
{
|
||||
internal class VideoAV1Stream
|
||||
internal class VideoAV1Stream : GraphicsResource
|
||||
{
|
||||
public IntPtr Handle => handle;
|
||||
IntPtr handle;
|
||||
|
@ -19,9 +20,7 @@ namespace MoonWorks.Video
|
|||
|
||||
public bool FrameDataUpdated { get; set; }
|
||||
|
||||
bool IsDisposed;
|
||||
|
||||
public VideoAV1Stream(VideoAV1 video)
|
||||
public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device)
|
||||
{
|
||||
if (Dav1dfile.df_fopen(video.Filename, out handle) == 0)
|
||||
{
|
||||
|
@ -71,32 +70,13 @@ namespace MoonWorks.Video
|
|||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// dispose managed state (managed objects)
|
||||
}
|
||||
|
||||
// free unmanaged resources (unmanaged objects)
|
||||
Dav1dfile.df_close(Handle);
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~VideoAV1Stream()
|
||||
{
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace MoonWorks.Video
|
|||
/// <summary>
|
||||
/// A structure for continuous decoding of AV1 videos and rendering them into a texture.
|
||||
/// </summary>
|
||||
public unsafe class VideoPlayer : IDisposable
|
||||
public unsafe class VideoPlayer : GraphicsResource
|
||||
{
|
||||
public Texture RenderTexture { get; private set; } = null;
|
||||
public VideoState State { get; private set; } = VideoState.Stopped;
|
||||
|
@ -18,6 +18,10 @@ 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;
|
||||
|
@ -30,17 +34,11 @@ namespace MoonWorks.Video
|
|||
private double lastTimestamp;
|
||||
private double timeElapsed;
|
||||
|
||||
private bool disposed;
|
||||
|
||||
public VideoPlayer(GraphicsDevice graphicsDevice)
|
||||
public VideoPlayer(GraphicsDevice device) : base(device)
|
||||
{
|
||||
GraphicsDevice = graphicsDevice;
|
||||
if (GraphicsDevice.VideoPipeline == null)
|
||||
{
|
||||
throw new InvalidOperationException("Missing video shaders!");
|
||||
}
|
||||
GraphicsDevice = device;
|
||||
|
||||
LinearSampler = new Sampler(graphicsDevice, SamplerCreateInfo.LinearClamp);
|
||||
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
|
||||
|
||||
timer = new Stopwatch();
|
||||
}
|
||||
|
@ -168,6 +166,8 @@ namespace MoonWorks.Video
|
|||
public void Unload()
|
||||
{
|
||||
Stop();
|
||||
ResetStreamATask?.Wait();
|
||||
ResetStreamBTask?.Wait();
|
||||
Video = null;
|
||||
}
|
||||
|
||||
|
@ -194,7 +194,8 @@ namespace MoonWorks.Video
|
|||
}
|
||||
|
||||
currentFrame = thisFrame;
|
||||
Task.Run(CurrentStream.ReadNextFrame).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame);
|
||||
ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
|
||||
if (CurrentStream.Ended)
|
||||
|
@ -202,7 +203,17 @@ namespace MoonWorks.Video
|
|||
timer.Stop();
|
||||
timer.Reset();
|
||||
|
||||
Task.Run(CurrentStream.Reset).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
var task = Task.Run(CurrentStream.Reset);
|
||||
task.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
|
||||
if (CurrentStream == Video.StreamA)
|
||||
{
|
||||
ResetStreamATask = task;
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetStreamBTask = task;
|
||||
}
|
||||
|
||||
if (Loop)
|
||||
{
|
||||
|
@ -280,8 +291,12 @@ namespace MoonWorks.Video
|
|||
|
||||
private void InitializeDav1dStream()
|
||||
{
|
||||
Task.Run(Video.StreamA.Reset).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
Task.Run(Video.StreamB.Reset).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
ReadNextFrameTask?.Wait();
|
||||
|
||||
ResetStreamATask = Task.Run(Video.StreamA.Reset);
|
||||
ResetStreamATask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
ResetStreamBTask = Task.Run(Video.StreamB.Reset);
|
||||
ResetStreamBTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
|
||||
CurrentStream = Video.StreamA;
|
||||
currentFrame = -1;
|
||||
|
@ -289,37 +304,27 @@ namespace MoonWorks.Video
|
|||
|
||||
private static void HandleTaskException(Task task)
|
||||
{
|
||||
throw task.Exception;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
if (task.Exception.InnerException is not TaskCanceledException)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// dispose managed state (managed objects)
|
||||
RenderTexture.Dispose();
|
||||
yTexture.Dispose();
|
||||
uTexture.Dispose();
|
||||
vTexture.Dispose();
|
||||
}
|
||||
|
||||
disposed = true;
|
||||
throw task.Exception;
|
||||
}
|
||||
}
|
||||
|
||||
~VideoPlayer()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Unload();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
RenderTexture?.Dispose();
|
||||
yTexture?.Dispose();
|
||||
uTexture?.Dispose();
|
||||
vTexture?.Dispose();
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue