diff --git a/MoonWorks.csproj b/MoonWorks.csproj index baa210d..2e0eb18 100644 --- a/MoonWorks.csproj +++ b/MoonWorks.csproj @@ -32,5 +32,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);