diff --git a/MoonWorks.csproj b/MoonWorks.csproj
index e09a9010..2e0eb181 100644
--- a/MoonWorks.csproj
+++ b/MoonWorks.csproj
@@ -24,4 +24,19 @@
Never
+
+
+
+ MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh
+
+
+ MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh
+
+
+ MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh
+
+
+ MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh
+
+
diff --git a/MoonWorks.dll.config b/MoonWorks.dll.config
index 67f28dcf..167fa25b 100644
--- a/MoonWorks.dll.config
+++ b/MoonWorks.dll.config
@@ -13,8 +13,8 @@
-
-
+
+
diff --git a/lib/WellspringCS b/lib/WellspringCS
index f8872bae..074f2afc 160000
--- a/lib/WellspringCS
+++ b/lib/WellspringCS
@@ -1 +1 @@
-Subproject commit f8872bae59e394b0f8a35224bb39ab8fd041af97
+Subproject commit 074f2afc833b221906bb2468735041ce78f2cb89
diff --git a/lib/dav1dfile b/lib/dav1dfile
index 3dcd69ff..5065e2cd 160000
--- a/lib/dav1dfile
+++ b/lib/dav1dfile
@@ -1 +1 @@
-Subproject commit 3dcd69ff85db80eea51481edd323b42c05993e1a
+Subproject commit 5065e2cd4662dbe023b77a45ef967f975170dfff
diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs
index 17f617e8..48ef387c 100644
--- a/src/Audio/AudioDevice.cs
+++ b/src/Audio/AudioDevice.cs
@@ -25,7 +25,7 @@ namespace MoonWorks.Audio
public float DopplerScale = 1f;
public float SpeedOfSound = 343.5f;
- private readonly HashSet resources = new HashSet();
+ private readonly HashSet resourceHandles = new HashSet();
private readonly HashSet updatingSourceVoices = new HashSet();
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;
diff --git a/src/Audio/StreamingVoice.cs b/src/Audio/StreamingVoice.cs
index 528e7eca..22c886cb 100644
--- a/src/Audio/StreamingVoice.cs
+++ b/src/Audio/StreamingVoice.cs
@@ -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]);
+ }
}
}
}
diff --git a/src/Game.cs b/src/Game.cs
index 1b7286ff..096afc76 100644
--- a/src/Game.cs
+++ b/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();
}
diff --git a/src/Graphics/Font/Font.cs b/src/Graphics/Font/Font.cs
index cf4982e1..0dd6d1cd 100644
--- a/src/Graphics/Font/Font.cs
+++ b/src/Graphics/Font/Font.cs
@@ -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(fileByteBuffer, (int) fileStream.Length);
- fileStream.ReadExactly(fileByteSpan);
- fileStream.Close();
+ private byte* StringBytes;
+ private int StringBytesLength;
- Handle = Wellspring.Wellspring_CreateFont((IntPtr) fileByteBuffer, (uint) fileByteSpan.Length);
+ ///
+ /// 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.
+ ///
+ ///
+ 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(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(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);
}
}
}
diff --git a/src/Graphics/Font/Packer.cs b/src/Graphics/Font/Packer.cs
deleted file mode 100644
index 9fa053ca..00000000
--- a/src/Graphics/Font/Packer.cs
+++ /dev/null
@@ -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();
- 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);
- }
- }
-}
diff --git a/src/Graphics/Font/Structs.cs b/src/Graphics/Font/Structs.cs
index 82310390..f5f0eec3 100644
--- a/src/Graphics/Font/Structs.cs
+++ b/src/Graphics/Font/Structs.cs
@@ -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
+ };
}
}
diff --git a/src/Graphics/Font/TextBatch.cs b/src/Graphics/Font/TextBatch.cs
index 119fbde8..7aef845b 100644
--- a/src/Graphics/Font/TextBatch.cs
+++ b/src/Graphics/Font/TextBatch.cs
@@ -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(GraphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT);
+ IndexBuffer = Buffer.Create(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);
}
}
}
diff --git a/src/Graphics/GraphicsDevice.cs b/src/Graphics/GraphicsDevice.cs
index 4fe5bca9..7fc07375 100644
--- a/src/Graphics/GraphicsDevice.cs
+++ b/src/Graphics/GraphicsDevice.cs
@@ -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 resources = new HashSet();
@@ -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(textVertShader, "main", 0);
+ TextFragmentShaderInfo = GraphicsShaderInfo.Create(textFragShader, "main", 1);
+ TextVertexInputState = VertexInputState.CreateSingleBinding();
+
+ 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)
diff --git a/src/Graphics/GraphicsResource.cs b/src/Graphics/GraphicsResource.cs
index 7eeccf9c..87d8576f 100644
--- a/src/Graphics/GraphicsResource.cs
+++ b/src/Graphics/GraphicsResource.cs
@@ -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 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;
}
}
diff --git a/src/Graphics/RefreshResource.cs b/src/Graphics/RefreshResource.cs
new file mode 100644
index 00000000..875d6459
--- /dev/null
+++ b/src/Graphics/RefreshResource.cs
@@ -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 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);
+ }
+}
diff --git a/src/Graphics/Resources/Buffer.cs b/src/Graphics/Resources/Buffer.cs
index 8563ac9c..b0349404 100644
--- a/src/Graphics/Resources/Buffer.cs
+++ b/src/Graphics/Resources/Buffer.cs
@@ -7,7 +7,7 @@ namespace MoonWorks.Graphics
///
/// Buffers are generic data containers that can be used by the GPU.
///
- public class Buffer : GraphicsResource
+ public class Buffer : RefreshResource
{
protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
diff --git a/src/Graphics/Resources/ComputePipeline.cs b/src/Graphics/Resources/ComputePipeline.cs
index 44545012..19312c21 100644
--- a/src/Graphics/Resources/ComputePipeline.cs
+++ b/src/Graphics/Resources/ComputePipeline.cs
@@ -6,7 +6,7 @@ namespace MoonWorks.Graphics
///
/// Compute pipelines perform arbitrary parallel processing on input data.
///
- public class ComputePipeline : GraphicsResource
+ public class ComputePipeline : RefreshResource
{
protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
diff --git a/src/Graphics/Resources/Fence.cs b/src/Graphics/Resources/Fence.cs
index fd380ae5..223620e3 100644
--- a/src/Graphics/Resources/Fence.cs
+++ b/src/Graphics/Resources/Fence.cs
@@ -10,7 +10,7 @@ namespace MoonWorks.Graphics
/// The Fence object itself is basically just a wrapper for the Refresh_Fence.
/// The internal handle is replaced so that we can pool Fence objects to manage garbage.
///
- public class Fence : GraphicsResource
+ public class Fence : RefreshResource
{
protected override Action QueueDestroyFunction => Refresh.Refresh_ReleaseFence;
diff --git a/src/Graphics/Resources/GraphicsPipeline.cs b/src/Graphics/Resources/GraphicsPipeline.cs
index a1e75067..421e9c56 100644
--- a/src/Graphics/Resources/GraphicsPipeline.cs
+++ b/src/Graphics/Resources/GraphicsPipeline.cs
@@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
/// Graphics pipelines encapsulate all of the render state in a single object.
/// These pipelines are bound before draw calls are issued.
///
- public class GraphicsPipeline : GraphicsResource
+ public class GraphicsPipeline : RefreshResource
{
protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
diff --git a/src/Graphics/Resources/Sampler.cs b/src/Graphics/Resources/Sampler.cs
index 39463ad3..ac523018 100644
--- a/src/Graphics/Resources/Sampler.cs
+++ b/src/Graphics/Resources/Sampler.cs
@@ -6,7 +6,7 @@ namespace MoonWorks.Graphics
///
/// A sampler specifies how a texture will be sampled in a shader.
///
- public class Sampler : GraphicsResource
+ public class Sampler : RefreshResource
{
protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
diff --git a/src/Graphics/Resources/ShaderModule.cs b/src/Graphics/Resources/ShaderModule.cs
index 61f87793..fad27cb9 100644
--- a/src/Graphics/Resources/ShaderModule.cs
+++ b/src/Graphics/Resources/ShaderModule.cs
@@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
///
/// Shader modules expect input in Refresh bytecode format.
///
- public class ShaderModule : GraphicsResource
+ public class ShaderModule : RefreshResource
{
protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
diff --git a/src/Graphics/Resources/Texture.cs b/src/Graphics/Resources/Texture.cs
index 8f1c9d0c..7a8fadb6 100644
--- a/src/Graphics/Resources/Texture.cs
+++ b/src/Graphics/Resources/Texture.cs
@@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
///
/// A container for pixel data.
///
- public class Texture : GraphicsResource
+ public class Texture : RefreshResource
{
public uint Width { get; internal set; }
public uint Height { get; internal set; }
diff --git a/src/Graphics/StockShaders/Binary/text_msdf.frag.refresh b/src/Graphics/StockShaders/Binary/text_msdf.frag.refresh
new file mode 100644
index 00000000..ba50a5aa
Binary files /dev/null and b/src/Graphics/StockShaders/Binary/text_msdf.frag.refresh differ
diff --git a/src/Graphics/StockShaders/Binary/text_transform.vert.refresh b/src/Graphics/StockShaders/Binary/text_transform.vert.refresh
new file mode 100644
index 00000000..21bee62b
Binary files /dev/null and b/src/Graphics/StockShaders/Binary/text_transform.vert.refresh differ
diff --git a/src/Graphics/StockShaders/Binary/video_fullscreen.vert.refresh b/src/Graphics/StockShaders/Binary/video_fullscreen.vert.refresh
new file mode 100644
index 00000000..131f3a67
Binary files /dev/null and b/src/Graphics/StockShaders/Binary/video_fullscreen.vert.refresh differ
diff --git a/src/Graphics/StockShaders/Binary/video_yuv2rgba.frag.refresh b/src/Graphics/StockShaders/Binary/video_yuv2rgba.frag.refresh
new file mode 100644
index 00000000..176e8b4c
Binary files /dev/null and b/src/Graphics/StockShaders/Binary/video_yuv2rgba.frag.refresh differ
diff --git a/src/Graphics/StockShaders/Source/text_msdf.frag b/src/Graphics/StockShaders/Source/text_msdf.frag
new file mode 100644
index 00000000..046682e7
--- /dev/null
+++ b/src/Graphics/StockShaders/Source/text_msdf.frag
@@ -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);
+}
diff --git a/src/Graphics/StockShaders/Source/text_transform.vert b/src/Graphics/StockShaders/Source/text_transform.vert
new file mode 100644
index 00000000..f64037c4
--- /dev/null
+++ b/src/Graphics/StockShaders/Source/text_transform.vert
@@ -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;
+}
diff --git a/src/Video/Shaders/video_fullscreen.vert b/src/Graphics/StockShaders/Source/video_fullscreen.vert
similarity index 100%
rename from src/Video/Shaders/video_fullscreen.vert
rename to src/Graphics/StockShaders/Source/video_fullscreen.vert
diff --git a/src/Video/Shaders/video_yuv2rgba.frag b/src/Graphics/StockShaders/Source/video_yuv2rgba.frag
similarity index 100%
rename from src/Video/Shaders/video_yuv2rgba.frag
rename to src/Graphics/StockShaders/Source/video_yuv2rgba.frag
diff --git a/src/Video/VideoAV1.cs b/src/Video/VideoAV1.cs
index 9aaf157e..b1eb6074 100644
--- a/src/Video/VideoAV1.cs
+++ b/src/Video/VideoAV1.cs
@@ -1,12 +1,13 @@
using System;
using System.IO;
+using MoonWorks.Graphics;
namespace MoonWorks.Video
{
///
/// This class takes in a filename for AV1 data in .obu (open bitstream unit) format
///
- public unsafe class VideoAV1
+ public unsafe class VideoAV1 : GraphicsResource
{
public string Filename { get; }
@@ -28,7 +29,7 @@ namespace MoonWorks.Video
///
/// Opens an AV1 file so it can be loaded by VideoPlayer. You must also provide a playback framerate.
///
- 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);
}
}
}
diff --git a/src/Video/VideoAV1Stream.cs b/src/Video/VideoAV1Stream.cs
index ea89f8ce..cc40aa0c 100644
--- a/src/Video/VideoAV1Stream.cs
+++ b/src/Video/VideoAV1Stream.cs
@@ -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);
}
}
}
diff --git a/src/Video/VideoPlayer.cs b/src/Video/VideoPlayer.cs
index 826e7b1b..f2d079a5 100644
--- a/src/Video/VideoPlayer.cs
+++ b/src/Video/VideoPlayer.cs
@@ -8,7 +8,7 @@ namespace MoonWorks.Video
///
/// A structure for continuous decoding of AV1 videos and rendering them into a texture.
///
- 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);
}
}
}