MSDF font rendering + improved resource tracking #52
|
@ -24,4 +24,19 @@
|
||||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</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>
|
</Project>
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
<dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/>
|
<dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/>
|
||||||
|
|
||||||
<dllmap dll="Wellspring" os="windows" target="Wellspring.dll"/>
|
<dllmap dll="Wellspring" os="windows" target="Wellspring.dll"/>
|
||||||
<dllmap dll="Wellspring" os="osx" target="libWellspring.0.dylib"/>
|
<dllmap dll="Wellspring" os="osx" target="libWellspring.1.dylib"/>
|
||||||
<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.0"/>
|
<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.1"/>
|
||||||
|
|
||||||
<dllmap dll="dav1dfile" os="windows" target="dav1dfile.dll"/>
|
<dllmap dll="dav1dfile" os="windows" target="dav1dfile.dll"/>
|
||||||
<dllmap dll="dav1dfile" os="osx" target="libdav1dfile.1.dylib"/>
|
<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 DopplerScale = 1f;
|
||||||
public float SpeedOfSound = 343.5f;
|
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 readonly HashSet<UpdatingSourceVoice> updatingSourceVoices = new HashSet<UpdatingSourceVoice>();
|
||||||
|
|
||||||
private AudioTweenManager AudioTweenManager;
|
private AudioTweenManager AudioTweenManager;
|
||||||
|
@ -123,7 +123,6 @@ namespace MoonWorks.Audio
|
||||||
AudioTweenManager = new AudioTweenManager();
|
AudioTweenManager = new AudioTweenManager();
|
||||||
VoicePool = new SourceVoicePool(this);
|
VoicePool = new SourceVoicePool(this);
|
||||||
|
|
||||||
Logger.LogInfo("Setting up audio thread...");
|
|
||||||
WakeSignal = new AutoResetEvent(true);
|
WakeSignal = new AutoResetEvent(true);
|
||||||
|
|
||||||
Thread = new Thread(ThreadMain);
|
Thread = new Thread(ThreadMain);
|
||||||
|
@ -265,7 +264,7 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
lock (StateLock)
|
lock (StateLock)
|
||||||
{
|
{
|
||||||
resources.Add(resourceReference);
|
resourceHandles.Add(resourceReference);
|
||||||
|
|
||||||
if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
|
if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
|
||||||
{
|
{
|
||||||
|
@ -278,7 +277,12 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
lock (StateLock)
|
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();
|
Thread.Join();
|
||||||
|
|
||||||
// dispose all voices first
|
// dispose all source voices first
|
||||||
foreach (var resource in resources)
|
foreach (var handle in resourceHandles)
|
||||||
{
|
{
|
||||||
if (resource.Target is Voice voice)
|
if (handle.Target is SourceVoice voice)
|
||||||
{
|
{
|
||||||
voice.Dispose();
|
voice.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// destroy all other audio resources
|
// dispose all submix voices except the faux mastering voice
|
||||||
foreach (var resource in resources)
|
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);
|
FAudio.FAudio_Release(Handle);
|
||||||
|
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
|
|
|
@ -150,13 +150,16 @@ namespace MoonWorks.Audio
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
Stop();
|
lock (StateLock)
|
||||||
|
|
||||||
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
|
||||||
{
|
{
|
||||||
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.Audio;
|
||||||
using MoonWorks.Graphics;
|
using MoonWorks.Graphics;
|
||||||
using MoonWorks.Input;
|
using MoonWorks.Input;
|
||||||
|
@ -58,6 +57,7 @@ namespace MoonWorks
|
||||||
bool debugMode = false
|
bool debugMode = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
Logger.LogInfo("Initializing frame limiter...");
|
||||||
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
|
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
|
||||||
gameTimer = Stopwatch.StartNew();
|
gameTimer = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@ namespace MoonWorks
|
||||||
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
|
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)
|
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0)
|
||||||
{
|
{
|
||||||
Logger.LogError("Failed to initialize SDL!");
|
Logger.LogError("Failed to initialize SDL!");
|
||||||
|
@ -76,13 +77,16 @@ namespace MoonWorks
|
||||||
|
|
||||||
Logger.Initialize();
|
Logger.Initialize();
|
||||||
|
|
||||||
|
Logger.LogInfo("Initializing input...");
|
||||||
Inputs = new Inputs();
|
Inputs = new Inputs();
|
||||||
|
|
||||||
|
Logger.LogInfo("Initializing graphics device...");
|
||||||
GraphicsDevice = new GraphicsDevice(
|
GraphicsDevice = new GraphicsDevice(
|
||||||
Backend.Vulkan,
|
Backend.Vulkan,
|
||||||
debugMode
|
debugMode
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Logger.LogInfo("Initializing main window...");
|
||||||
MainWindow = new Window(windowCreateInfo, GraphicsDevice.WindowFlags | SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN);
|
MainWindow = new Window(windowCreateInfo, GraphicsDevice.WindowFlags | SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN);
|
||||||
|
|
||||||
if (!GraphicsDevice.ClaimWindow(MainWindow, windowCreateInfo.PresentMode))
|
if (!GraphicsDevice.ClaimWindow(MainWindow, windowCreateInfo.PresentMode))
|
||||||
|
@ -90,6 +94,7 @@ namespace MoonWorks
|
||||||
throw new System.SystemException("Could not claim window!");
|
throw new System.SystemException("Could not claim window!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.LogInfo("Initializing audio thread...");
|
||||||
AudioDevice = new AudioDevice();
|
AudioDevice = new AudioDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,9 +115,6 @@ namespace MoonWorks
|
||||||
Logger.LogInfo("Cleaning up game...");
|
Logger.LogInfo("Cleaning up game...");
|
||||||
Destroy();
|
Destroy();
|
||||||
|
|
||||||
Logger.LogInfo("Closing audio thread...");
|
|
||||||
AudioDevice.Dispose();
|
|
||||||
|
|
||||||
Logger.LogInfo("Unclaiming window...");
|
Logger.LogInfo("Unclaiming window...");
|
||||||
GraphicsDevice.UnclaimWindow(MainWindow);
|
GraphicsDevice.UnclaimWindow(MainWindow);
|
||||||
|
|
||||||
|
@ -122,6 +124,9 @@ namespace MoonWorks
|
||||||
Logger.LogInfo("Disposing graphics device...");
|
Logger.LogInfo("Disposing graphics device...");
|
||||||
GraphicsDevice.Dispose();
|
GraphicsDevice.Dispose();
|
||||||
|
|
||||||
|
Logger.LogInfo("Closing audio thread...");
|
||||||
|
AudioDevice.Dispose();
|
||||||
|
|
||||||
SDL.SDL_Quit();
|
SDL.SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,45 +5,117 @@ using WellspringCS;
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.Font
|
namespace MoonWorks.Graphics.Font
|
||||||
{
|
{
|
||||||
public class Font : IDisposable
|
public unsafe class Font : GraphicsResource
|
||||||
{
|
{
|
||||||
public IntPtr Handle { get; }
|
public Texture Texture { get; }
|
||||||
|
public float PixelsPerEm { get; }
|
||||||
|
public float DistanceRange { get; }
|
||||||
|
|
||||||
private bool IsDisposed;
|
internal IntPtr Handle { get; }
|
||||||
|
|
||||||
public unsafe Font(string path)
|
private byte* StringBytes;
|
||||||
{
|
private int StringBytesLength;
|
||||||
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();
|
|
||||||
|
|
||||||
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 (!IsDisposed)
|
||||||
{
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
Texture.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Wellspring.Wellspring_DestroyFont(Handle);
|
Wellspring.Wellspring_DestroyFont(Handle);
|
||||||
IsDisposed = true;
|
|
||||||
}
|
}
|
||||||
}
|
base.Dispose(disposing);
|
||||||
|
|
||||||
~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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
namespace MoonWorks.Graphics.Font
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct FontRange
|
public struct Vertex : IVertexType
|
||||||
{
|
|
||||||
public uint FirstCodepoint;
|
|
||||||
public uint NumChars;
|
|
||||||
public byte OversampleH;
|
|
||||||
public byte OversampleV;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct Vertex
|
|
||||||
{
|
{
|
||||||
public Vector3 Position;
|
public Vector3 Position;
|
||||||
public Vector2 TexCoord;
|
public Vector2 TexCoord;
|
||||||
public Color Color;
|
public Color Color;
|
||||||
|
|
||||||
|
public static VertexElementFormat[] Formats { get; } = new VertexElementFormat[]
|
||||||
|
{
|
||||||
|
VertexElementFormat.Vector3,
|
||||||
|
VertexElementFormat.Vector2,
|
||||||
|
VertexElementFormat.Color
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +1,87 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using WellspringCS;
|
using WellspringCS;
|
||||||
|
|
||||||
namespace MoonWorks.Graphics.Font
|
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; }
|
private GraphicsDevice GraphicsDevice { get; }
|
||||||
public IntPtr Handle { get; }
|
public IntPtr Handle { get; }
|
||||||
|
|
||||||
public Buffer VertexBuffer { get; protected set; } = null;
|
public Buffer VertexBuffer { get; protected set; } = null;
|
||||||
public Buffer IndexBuffer { get; protected set; } = null;
|
public Buffer IndexBuffer { get; protected set; } = null;
|
||||||
public Texture Texture { get; protected set; }
|
|
||||||
public uint PrimitiveCount { 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();
|
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);
|
Wellspring.Wellspring_StartTextBatch(Handle, font.Handle);
|
||||||
Texture = packer.Texture;
|
CurrentFont = font;
|
||||||
PrimitiveCount = 0;
|
PrimitiveCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Draw(
|
// Add text with size and color to the batch
|
||||||
|
public unsafe bool Add(
|
||||||
string text,
|
string text,
|
||||||
float x,
|
int pixelSize,
|
||||||
float y,
|
|
||||||
float depth,
|
|
||||||
Color color,
|
Color color,
|
||||||
HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
|
HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
|
||||||
VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
|
VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
|
||||||
) {
|
) {
|
||||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
|
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 (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,
|
Handle,
|
||||||
x,
|
pixelSize,
|
||||||
y,
|
|
||||||
depth,
|
|
||||||
new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
|
new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
|
||||||
(Wellspring.HorizontalAlignment) horizontalAlignment,
|
(Wellspring.HorizontalAlignment) horizontalAlignment,
|
||||||
(Wellspring.VerticalAlignment) verticalAlignment,
|
(Wellspring.VerticalAlignment) verticalAlignment,
|
||||||
(IntPtr) bytes,
|
(IntPtr) StringBytes,
|
||||||
(uint) byteCount
|
(uint) byteCount
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result == 0)
|
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)
|
public unsafe void UploadBufferData(CommandBuffer commandBuffer)
|
||||||
{
|
{
|
||||||
Wellspring.Wellspring_GetBufferData(
|
Wellspring.Wellspring_GetBufferData(
|
||||||
|
@ -81,24 +93,16 @@ namespace MoonWorks.Graphics.Font
|
||||||
out uint indexDataLengthInBytes
|
out uint indexDataLengthInBytes
|
||||||
);
|
);
|
||||||
|
|
||||||
if (VertexBuffer == null)
|
if (VertexBuffer.Size < vertexDataLengthInBytes)
|
||||||
{
|
|
||||||
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
|
||||||
}
|
|
||||||
else if (VertexBuffer.Size < vertexDataLengthInBytes)
|
|
||||||
{
|
{
|
||||||
VertexBuffer.Dispose();
|
VertexBuffer.Dispose();
|
||||||
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IndexBuffer == null)
|
if (IndexBuffer.Size < indexDataLengthInBytes)
|
||||||
{
|
|
||||||
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes);
|
|
||||||
}
|
|
||||||
else if (IndexBuffer.Size < indexDataLengthInBytes)
|
|
||||||
{
|
{
|
||||||
IndexBuffer.Dispose();
|
IndexBuffer.Dispose();
|
||||||
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes);
|
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
|
if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
|
||||||
|
@ -107,7 +111,41 @@ namespace MoonWorks.Graphics.Font
|
||||||
commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes);
|
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;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using MoonWorks.Video;
|
||||||
using RefreshCS;
|
using RefreshCS;
|
||||||
|
using WellspringCS;
|
||||||
|
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
|
@ -21,6 +22,15 @@ namespace MoonWorks.Graphics
|
||||||
// Built-in video pipeline
|
// Built-in video pipeline
|
||||||
internal GraphicsPipeline VideoPipeline { get; }
|
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; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
|
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
|
||||||
|
@ -41,43 +51,91 @@ namespace MoonWorks.Graphics
|
||||||
Conversions.BoolToByte(debugMode)
|
Conversions.BoolToByte(debugMode)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check for optional video shaders
|
// TODO: check for CreateDevice fail
|
||||||
|
|
||||||
|
// Check for replacement stock shaders
|
||||||
string basePath = System.AppContext.BaseDirectory;
|
string basePath = System.AppContext.BaseDirectory;
|
||||||
|
|
||||||
string videoVertPath = Path.Combine(basePath, "video_fullscreen.vert.refresh");
|
string videoVertPath = Path.Combine(basePath, "video_fullscreen.vert.refresh");
|
||||||
string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.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))
|
if (File.Exists(videoVertPath) && File.Exists(videoFragPath))
|
||||||
{
|
{
|
||||||
ShaderModule videoVertShader = new ShaderModule(this, videoVertPath);
|
videoVertShader = new ShaderModule(this, videoVertPath);
|
||||||
ShaderModule videoFragShader = new ShaderModule(this, videoFragPath);
|
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
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
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);
|
FencePool = new FencePool(this);
|
||||||
}
|
}
|
||||||
|
@ -363,6 +421,16 @@ namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
lock (resources)
|
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)
|
foreach (var resource in resources)
|
||||||
{
|
{
|
||||||
if (resource.Target is IDisposable disposable)
|
if (resource.Target is IDisposable disposable)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace MoonWorks.Graphics
|
namespace MoonWorks.Graphics
|
||||||
{
|
{
|
||||||
|
@ -8,13 +7,11 @@ namespace MoonWorks.Graphics
|
||||||
public abstract class GraphicsResource : IDisposable
|
public abstract class GraphicsResource : IDisposable
|
||||||
{
|
{
|
||||||
public GraphicsDevice Device { get; }
|
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;
|
private GCHandle SelfReference;
|
||||||
|
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
protected GraphicsResource(GraphicsDevice device)
|
protected GraphicsResource(GraphicsDevice device)
|
||||||
{
|
{
|
||||||
Device = device;
|
Device = device;
|
||||||
|
@ -23,7 +20,7 @@ namespace MoonWorks.Graphics
|
||||||
Device.AddResourceReference(SelfReference);
|
Device.AddResourceReference(SelfReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
{
|
{
|
||||||
|
@ -33,13 +30,6 @@ namespace MoonWorks.Graphics
|
||||||
SelfReference.Free();
|
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;
|
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>
|
/// <summary>
|
||||||
/// Buffers are generic data containers that can be used by the GPU.
|
/// Buffers are generic data containers that can be used by the GPU.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Buffer : GraphicsResource
|
public class Buffer : RefreshResource
|
||||||
{
|
{
|
||||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
|
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace MoonWorks.Graphics
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Compute pipelines perform arbitrary parallel processing on input data.
|
/// Compute pipelines perform arbitrary parallel processing on input data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ComputePipeline : GraphicsResource
|
public class ComputePipeline : RefreshResource
|
||||||
{
|
{
|
||||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
|
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 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.
|
/// The internal handle is replaced so that we can pool Fence objects to manage garbage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Fence : GraphicsResource
|
public class Fence : RefreshResource
|
||||||
{
|
{
|
||||||
protected override Action<nint, nint> QueueDestroyFunction => Refresh.Refresh_ReleaseFence;
|
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/>
|
/// Graphics pipelines encapsulate all of the render state in a single object. <br/>
|
||||||
/// These pipelines are bound before draw calls are issued.
|
/// These pipelines are bound before draw calls are issued.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GraphicsPipeline : GraphicsResource
|
public class GraphicsPipeline : RefreshResource
|
||||||
{
|
{
|
||||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
|
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace MoonWorks.Graphics
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A sampler specifies how a texture will be sampled in a shader.
|
/// A sampler specifies how a texture will be sampled in a shader.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Sampler : GraphicsResource
|
public class Sampler : RefreshResource
|
||||||
{
|
{
|
||||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
|
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shader modules expect input in Refresh bytecode format.
|
/// Shader modules expect input in Refresh bytecode format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ShaderModule : GraphicsResource
|
public class ShaderModule : RefreshResource
|
||||||
{
|
{
|
||||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
|
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A container for pixel data.
|
/// A container for pixel data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Texture : GraphicsResource
|
public class Texture : RefreshResource
|
||||||
{
|
{
|
||||||
public uint Width { get; internal set; }
|
public uint Width { get; internal set; }
|
||||||
public uint Height { 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;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using MoonWorks.Graphics;
|
||||||
|
|
||||||
namespace MoonWorks.Video
|
namespace MoonWorks.Video
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class takes in a filename for AV1 data in .obu (open bitstream unit) format
|
/// This class takes in a filename for AV1 data in .obu (open bitstream unit) format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class VideoAV1
|
public unsafe class VideoAV1 : GraphicsResource
|
||||||
{
|
{
|
||||||
public string Filename { get; }
|
public string Filename { get; }
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ namespace MoonWorks.Video
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens an AV1 file so it can be loaded by VideoPlayer. You must also provide a playback framerate.
|
/// Opens an AV1 file so it can be loaded by VideoPlayer. You must also provide a playback framerate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public VideoAV1(string filename, double framesPerSecond)
|
public VideoAV1(GraphicsDevice device, string filename, double framesPerSecond) : base(device)
|
||||||
{
|
{
|
||||||
if (!File.Exists(filename))
|
if (!File.Exists(filename))
|
||||||
{
|
{
|
||||||
|
@ -67,8 +68,22 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
Filename = filename;
|
Filename = filename;
|
||||||
|
|
||||||
StreamA = new VideoAV1Stream(this);
|
StreamA = new VideoAV1Stream(device, this);
|
||||||
StreamB = new VideoAV1Stream(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 System;
|
||||||
|
using MoonWorks.Graphics;
|
||||||
|
|
||||||
namespace MoonWorks.Video
|
namespace MoonWorks.Video
|
||||||
{
|
{
|
||||||
internal class VideoAV1Stream
|
internal class VideoAV1Stream : GraphicsResource
|
||||||
{
|
{
|
||||||
public IntPtr Handle => handle;
|
public IntPtr Handle => handle;
|
||||||
IntPtr handle;
|
IntPtr handle;
|
||||||
|
@ -19,9 +20,7 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
public bool FrameDataUpdated { get; set; }
|
public bool FrameDataUpdated { get; set; }
|
||||||
|
|
||||||
bool IsDisposed;
|
public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device)
|
||||||
|
|
||||||
public VideoAV1Stream(VideoAV1 video)
|
|
||||||
{
|
{
|
||||||
if (Dav1dfile.df_fopen(video.Filename, out handle) == 0)
|
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 (!IsDisposed)
|
||||||
{
|
{
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
// dispose managed state (managed objects)
|
|
||||||
}
|
|
||||||
|
|
||||||
// free unmanaged resources (unmanaged objects)
|
|
||||||
Dav1dfile.df_close(Handle);
|
Dav1dfile.df_close(Handle);
|
||||||
|
|
||||||
IsDisposed = true;
|
|
||||||
}
|
}
|
||||||
}
|
base.Dispose(disposing);
|
||||||
|
|
||||||
~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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace MoonWorks.Video
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A structure for continuous decoding of AV1 videos and rendering them into a texture.
|
/// A structure for continuous decoding of AV1 videos and rendering them into a texture.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class VideoPlayer : IDisposable
|
public unsafe class VideoPlayer : GraphicsResource
|
||||||
{
|
{
|
||||||
public Texture RenderTexture { get; private set; } = null;
|
public Texture RenderTexture { get; private set; } = null;
|
||||||
public VideoState State { get; private set; } = VideoState.Stopped;
|
public VideoState State { get; private set; } = VideoState.Stopped;
|
||||||
|
@ -18,6 +18,10 @@ namespace MoonWorks.Video
|
||||||
private VideoAV1 Video = null;
|
private VideoAV1 Video = null;
|
||||||
private VideoAV1Stream CurrentStream = null;
|
private VideoAV1Stream CurrentStream = null;
|
||||||
|
|
||||||
|
private Task ReadNextFrameTask;
|
||||||
|
private Task ResetStreamATask;
|
||||||
|
private Task ResetStreamBTask;
|
||||||
|
|
||||||
private GraphicsDevice GraphicsDevice;
|
private GraphicsDevice GraphicsDevice;
|
||||||
private Texture yTexture = null;
|
private Texture yTexture = null;
|
||||||
private Texture uTexture = null;
|
private Texture uTexture = null;
|
||||||
|
@ -30,17 +34,11 @@ namespace MoonWorks.Video
|
||||||
private double lastTimestamp;
|
private double lastTimestamp;
|
||||||
private double timeElapsed;
|
private double timeElapsed;
|
||||||
|
|
||||||
private bool disposed;
|
public VideoPlayer(GraphicsDevice device) : base(device)
|
||||||
|
|
||||||
public VideoPlayer(GraphicsDevice graphicsDevice)
|
|
||||||
{
|
{
|
||||||
GraphicsDevice = graphicsDevice;
|
GraphicsDevice = device;
|
||||||
if (GraphicsDevice.VideoPipeline == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Missing video shaders!");
|
|
||||||
}
|
|
||||||
|
|
||||||
LinearSampler = new Sampler(graphicsDevice, SamplerCreateInfo.LinearClamp);
|
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
|
||||||
|
|
||||||
timer = new Stopwatch();
|
timer = new Stopwatch();
|
||||||
}
|
}
|
||||||
|
@ -168,6 +166,8 @@ namespace MoonWorks.Video
|
||||||
public void Unload()
|
public void Unload()
|
||||||
{
|
{
|
||||||
Stop();
|
Stop();
|
||||||
|
ResetStreamATask?.Wait();
|
||||||
|
ResetStreamBTask?.Wait();
|
||||||
Video = null;
|
Video = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +194,8 @@ namespace MoonWorks.Video
|
||||||
}
|
}
|
||||||
|
|
||||||
currentFrame = thisFrame;
|
currentFrame = thisFrame;
|
||||||
Task.Run(CurrentStream.ReadNextFrame).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame);
|
||||||
|
ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CurrentStream.Ended)
|
if (CurrentStream.Ended)
|
||||||
|
@ -202,7 +203,17 @@ namespace MoonWorks.Video
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
timer.Reset();
|
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)
|
if (Loop)
|
||||||
{
|
{
|
||||||
|
@ -280,8 +291,12 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
private void InitializeDav1dStream()
|
private void InitializeDav1dStream()
|
||||||
{
|
{
|
||||||
Task.Run(Video.StreamA.Reset).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
ReadNextFrameTask?.Wait();
|
||||||
Task.Run(Video.StreamB.Reset).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
|
||||||
|
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;
|
CurrentStream = Video.StreamA;
|
||||||
currentFrame = -1;
|
currentFrame = -1;
|
||||||
|
@ -289,37 +304,27 @@ namespace MoonWorks.Video
|
||||||
|
|
||||||
private static void HandleTaskException(Task task)
|
private static void HandleTaskException(Task task)
|
||||||
{
|
{
|
||||||
throw task.Exception;
|
if (task.Exception.InnerException is not TaskCanceledException)
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!disposed)
|
|
||||||
{
|
{
|
||||||
if (disposing)
|
throw task.Exception;
|
||||||
{
|
|
||||||
// dispose managed state (managed objects)
|
|
||||||
RenderTexture.Dispose();
|
|
||||||
yTexture.Dispose();
|
|
||||||
uTexture.Dispose();
|
|
||||||
vTexture.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
disposed = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~VideoPlayer()
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
if (!IsDisposed)
|
||||||
Dispose(disposing: false);
|
{
|
||||||
}
|
if (disposing)
|
||||||
|
{
|
||||||
|
Unload();
|
||||||
|
|
||||||
public void Dispose()
|
RenderTexture?.Dispose();
|
||||||
{
|
yTexture?.Dispose();
|
||||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
uTexture?.Dispose();
|
||||||
Dispose(disposing: true);
|
vTexture?.Dispose();
|
||||||
GC.SuppressFinalize(this);
|
}
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue