diff --git a/MoonWorks.csproj b/MoonWorks.csproj
index 08dc026..f568058 100644
--- a/MoonWorks.csproj
+++ b/MoonWorks.csproj
@@ -33,5 +33,11 @@
MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh
+
+ MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh
+
+
+ MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh
+
diff --git a/src/Graphics/Font/Structs.cs b/src/Graphics/Font/Structs.cs
index b5b1e5a..f5f0eec 100644
--- a/src/Graphics/Font/Structs.cs
+++ b/src/Graphics/Font/Structs.cs
@@ -4,10 +4,17 @@ using MoonWorks.Math.Float;
namespace MoonWorks.Graphics.Font
{
[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 a3b5e28..cccbd59 100644
--- a/src/Graphics/Font/TextBatch.cs
+++ b/src/Graphics/Font/TextBatch.cs
@@ -100,6 +100,24 @@ namespace MoonWorks.Graphics.Font
PrimitiveCount = vertexCount / 2; // FIXME: is this jank?
}
+ // 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)
diff --git a/src/Graphics/GraphicsDevice.cs b/src/Graphics/GraphicsDevice.cs
index 191dbde..7fc0737 100644
--- a/src/Graphics/GraphicsDevice.cs
+++ b/src/Graphics/GraphicsDevice.cs
@@ -4,6 +4,7 @@ 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,14 +51,23 @@ 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))
{
videoVertShader = new ShaderModule(this, videoVertPath);
@@ -66,6 +85,23 @@ namespace MoonWorks.Graphics
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
@@ -94,6 +130,13 @@ namespace MoonWorks.Graphics
}
);
+ 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);
}
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 0000000..ba50a5a
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 0000000..21bee62
Binary files /dev/null and b/src/Graphics/StockShaders/Binary/text_transform.vert.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 0000000..046682e
--- /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 0000000..f64037c
--- /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/VideoPlayer.cs b/src/Video/VideoPlayer.cs
index cf14eeb..efc2814 100644
--- a/src/Video/VideoPlayer.cs
+++ b/src/Video/VideoPlayer.cs
@@ -37,10 +37,6 @@ namespace MoonWorks.Video
public VideoPlayer(GraphicsDevice device) : base(device)
{
GraphicsDevice = device;
- if (GraphicsDevice.VideoPipeline == null)
- {
- throw new InvalidOperationException("Missing video shaders!");
- }
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);