diff --git a/osu.Game.Rulesets.Swing/Objects/Drawables/Pieces/PathSliderBody.cs b/osu.Game.Rulesets.Swing/Objects/Drawables/Pieces/PathSliderBody.cs index e58a620..eb5c663 100644 --- a/osu.Game.Rulesets.Swing/Objects/Drawables/Pieces/PathSliderBody.cs +++ b/osu.Game.Rulesets.Swing/Objects/Drawables/Pieces/PathSliderBody.cs @@ -68,7 +68,7 @@ public PathSliderBody() [BackgroundDependencyLoader] private void load(ShaderManager shaders) { - shader = shaders.Load("RulesetVertex", "SwingSlider"); + shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "SwingSlider"); } protected override DrawNode CreateDrawNode() => new PathSliderBodyDrawNode(this); diff --git a/osu.Game.Rulesets.Swing/Resources/Shaders/Internal/sh_Compatibility.h b/osu.Game.Rulesets.Swing/Resources/Shaders/Internal/sh_Compatibility.h new file mode 100644 index 0000000..ea33f85 --- /dev/null +++ b/osu.Game.Rulesets.Swing/Resources/Shaders/Internal/sh_Compatibility.h @@ -0,0 +1,4 @@ +// This file is automatically included in every shader. + +#version 450 +#extension GL_ARB_uniform_buffer_object : enable \ No newline at end of file diff --git a/osu.Game.Rulesets.Swing/Resources/Shaders/Internal/sh_GlobalUniforms.h b/osu.Game.Rulesets.Swing/Resources/Shaders/Internal/sh_GlobalUniforms.h new file mode 100644 index 0000000..6b9fe8c --- /dev/null +++ b/osu.Game.Rulesets.Swing/Resources/Shaders/Internal/sh_GlobalUniforms.h @@ -0,0 +1,39 @@ +// This file is automatically included in every shader. + +layout(std140, set = -1, binding = 0) uniform g_GlobalUniforms +{ + // Whether the backbuffer is currently being drawn to. + bool g_BackbufferDraw; + + // Whether the depth values range from 0 to 1. If false, depth values range from -1 to 1. + // OpenGL uses [-1, 1], Vulkan/D3D/MTL all use [0, 1]. + bool g_IsDepthRangeZeroToOne; + + // Whether the clip space ranges from -1 (top) to 1 (bottom). If false, the clip space ranges from -1 (bottom) to 1 (top). + bool g_IsClipSpaceYInverted; + + // Whether the texture coordinates begin in the top-left of the texture. If false, (0, 0) is the bottom-left texel of the texture. + bool g_IsUvOriginTopLeft; + + mat4 g_ProjMatrix; + mat3 g_ToMaskingSpace; + + bool g_IsMasking; + highp float g_CornerRadius; + highp float g_CornerExponent; + highp vec4 g_MaskingRect; + highp float g_BorderThickness; + lowp mat4 g_BorderColour; + mediump float g_MaskingBlendRange; + lowp float g_AlphaExponent; + highp vec2 g_EdgeOffset; + bool g_DiscardInner; + highp float g_InnerCornerRadius; + + // 0 -> None + // 1 -> ClampToEdge + // 2 -> ClampToBorder + // 3 -> Repeat + int g_WrapModeS; + int g_WrapModeT; +}; \ No newline at end of file diff --git a/osu.Game.Rulesets.Swing/Resources/Shaders/Internal/sh_Vertex_Output.h b/osu.Game.Rulesets.Swing/Resources/Shaders/Internal/sh_Vertex_Output.h new file mode 100644 index 0000000..fbaf3e0 --- /dev/null +++ b/osu.Game.Rulesets.Swing/Resources/Shaders/Internal/sh_Vertex_Output.h @@ -0,0 +1,24 @@ +// Automatically included for every vertex shader. + +// The -1 is a placeholder value to offset all vertex input members +// of the actual vertex shader during inclusion of this header. +layout(location = -1) in highp float m_BackbufferDrawDepth; + +void main() +{ + {{ real_main }}(); // Invoke real main func + + if (g_BackbufferDraw) + gl_Position.z = m_BackbufferDrawDepth; + + if (g_IsDepthRangeZeroToOne) + gl_Position.z = gl_Position.z / 2.0 + 0.5; + + // When the device's texture coordinates are inverted, and when we are outputting to a framebuffer, + // we should ensure that the framebuffer output is also inverted so that it's treated as a normal texture + // later on in the frame. + bool requiresFramebufferInvert = !g_BackbufferDraw && !g_IsUvOriginTopLeft; + + if (g_IsClipSpaceYInverted || requiresFramebufferInvert) + gl_Position.y = -gl_Position.y; +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Swing/Resources/Shaders/sh_Masking.h b/osu.Game.Rulesets.Swing/Resources/Shaders/sh_Masking.h new file mode 100644 index 0000000..d756a15 --- /dev/null +++ b/osu.Game.Rulesets.Swing/Resources/Shaders/sh_Masking.h @@ -0,0 +1,124 @@ +layout(location = 0) in highp vec2 v_MaskingPosition; +layout(location = 1) in lowp vec4 v_Colour; + +#ifdef HIGH_PRECISION_VERTEX + layout(location = 3) in highp vec4 v_TexRect; +#else + layout(location = 3) in mediump vec4 v_TexRect; +#endif + +layout(location = 4) in mediump vec2 v_BlendRange; + +highp float distanceFromRoundedRect(highp vec2 offset, highp float radius) +{ + highp vec2 maskingPosition = v_MaskingPosition + offset; + + // Compute offset distance from masking rect in masking space. + highp vec2 topLeftOffset = g_MaskingRect.xy - maskingPosition; + highp vec2 bottomRightOffset = maskingPosition - g_MaskingRect.zw; + + highp vec2 distanceFromShrunkRect = max( + bottomRightOffset + vec2(radius), + topLeftOffset + vec2(radius)); + + highp float maxDist = max(distanceFromShrunkRect.x, distanceFromShrunkRect.y); + + // Inside the shrunk rectangle + if (maxDist <= 0.0) + return maxDist; + // Outside of the shrunk rectangle + else + { + distanceFromShrunkRect = max(vec2(0.0), distanceFromShrunkRect); + return pow(pow(distanceFromShrunkRect.x, g_CornerExponent) + pow(distanceFromShrunkRect.y, g_CornerExponent), 1.0 / g_CornerExponent); + } +} + +highp float distanceFromDrawingRect(mediump vec2 texCoord) +{ + highp vec2 topLeftOffset = v_TexRect.xy - texCoord; + topLeftOffset = vec2( + v_BlendRange.x > 0.0 ? topLeftOffset.x / v_BlendRange.x : 0.0, + v_BlendRange.y > 0.0 ? topLeftOffset.y / v_BlendRange.y : 0.0); + + highp vec2 bottomRightOffset = texCoord - v_TexRect.zw; + bottomRightOffset = vec2( + v_BlendRange.x > 0.0 ? bottomRightOffset.x / v_BlendRange.x : 0.0, + v_BlendRange.y > 0.0 ? bottomRightOffset.y / v_BlendRange.y : 0.0); + + highp vec2 xyDistance = max(topLeftOffset, bottomRightOffset); + return max(xyDistance.x, xyDistance.y); +} + +lowp vec4 getBorderColour() +{ + highp vec2 relativeTexCoord = v_MaskingPosition / (g_MaskingRect.zw - g_MaskingRect.xy); + lowp vec4 top = mix(g_BorderColour[0], g_BorderColour[2], relativeTexCoord.x); + lowp vec4 bottom = mix(g_BorderColour[1], g_BorderColour[3], relativeTexCoord.x); + return mix(top, bottom, relativeTexCoord.y); +} + +lowp vec4 getRoundedColor(lowp vec4 texel, mediump vec2 texCoord) +{ + if (!g_IsMasking && v_BlendRange == vec2(0.0)) + { + return v_Colour * texel; + } + + highp float dist = distanceFromRoundedRect(vec2(0.0), g_CornerRadius); + lowp float alphaFactor = 1.0; + + // Discard inner pixels + if (g_DiscardInner) + { + highp float innerDist = (g_EdgeOffset == vec2(0.0) && g_InnerCornerRadius == g_CornerRadius) ? + dist : distanceFromRoundedRect(g_EdgeOffset, g_InnerCornerRadius); + + // v_BlendRange is set from outside in a hacky way to tell us the g_MaskingBlendRange used for the rounded + // corners of the edge effect container itself. We can then derive the alpha factor for smooth inner edge + // effect from that. + highp float innerBlendFactor = (g_InnerCornerRadius - g_MaskingBlendRange - innerDist) / v_BlendRange.x; + if (innerBlendFactor > 1.0) + { + return vec4(0.0); + } + + // We exponentiate our factor to exactly counteract the later exponentiation by g_AlphaExponent for a smoother inner border. + alphaFactor = pow(min(1.0 - innerBlendFactor, 1.0), 1.0 / g_AlphaExponent); + } + + dist /= g_MaskingBlendRange; + + // This correction is needed to avoid fading of the alpha value for radii below 1px. + highp float radiusCorrection = g_CornerRadius <= 0.0 ? g_MaskingBlendRange : max(0.0, g_MaskingBlendRange - g_CornerRadius); + highp float fadeStart = (g_CornerRadius + radiusCorrection) / g_MaskingBlendRange; + alphaFactor *= min(fadeStart - dist, 1.0); + + if (v_BlendRange.x > 0.0 || v_BlendRange.y > 0.0) + { + alphaFactor *= clamp(1.0 - distanceFromDrawingRect(texCoord), 0.0, 1.0); + } + + if (alphaFactor <= 0.0) + { + return vec4(0.0); + } + + // This ends up softening glow without negatively affecting edge smoothness much. + alphaFactor = pow(alphaFactor, g_AlphaExponent); + + highp float borderStart = 1.0 + fadeStart - g_BorderThickness; + lowp float colourWeight = min(borderStart - dist, 1.0); + + lowp vec4 borderColour = getBorderColour(); + + if (colourWeight <= 0.0) + { + return vec4(borderColour.rgb, borderColour.a * alphaFactor); + } + + lowp vec4 dest = vec4(v_Colour.rgb, v_Colour.a * alphaFactor) * texel; + lowp vec4 src = vec4(borderColour.rgb, borderColour.a * (1.0 - colourWeight)); + + return blend(src, dest); +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Swing/Resources/Shaders/sh_Texture.fs b/osu.Game.Rulesets.Swing/Resources/Shaders/sh_Texture.fs new file mode 100644 index 0000000..e93fe73 --- /dev/null +++ b/osu.Game.Rulesets.Swing/Resources/Shaders/sh_Texture.fs @@ -0,0 +1,16 @@ +#include "sh_Utils.h" +#include "sh_Masking.h" +#include "sh_TextureWrapping.h" + +layout(location = 2) in mediump vec2 v_TexCoord; + +layout(set = 0, binding = 0) uniform lowp texture2D m_Texture; +layout(set = 0, binding = 1) uniform lowp sampler m_Sampler; + +layout(location = 0) out vec4 o_Colour; + +void main(void) +{ + vec2 wrappedCoord = wrap(v_TexCoord, v_TexRect); + o_Colour = getRoundedColor(wrappedSampler(wrappedCoord, v_TexRect, m_Texture, m_Sampler, -0.9), wrappedCoord); +} diff --git a/osu.Game.Rulesets.Swing/Resources/Shaders/sh_RulesetVertex.vs b/osu.Game.Rulesets.Swing/Resources/Shaders/sh_Texture2D.vs similarity index 91% rename from osu.Game.Rulesets.Swing/Resources/Shaders/sh_RulesetVertex.vs rename to osu.Game.Rulesets.Swing/Resources/Shaders/sh_Texture2D.vs index 9de5503..91d8182 100644 --- a/osu.Game.Rulesets.Swing/Resources/Shaders/sh_RulesetVertex.vs +++ b/osu.Game.Rulesets.Swing/Resources/Shaders/sh_Texture2D.vs @@ -1,4 +1,6 @@ -layout(location = 0) in highp vec2 m_Position; +#include "sh_Utils.h" + +layout(location = 0) in highp vec2 m_Position; layout(location = 1) in lowp vec4 m_Colour; layout(location = 2) in highp vec2 m_TexCoord; layout(location = 3) in highp vec4 m_TexRect; diff --git a/osu.Game.Rulesets.Swing/Resources/Shaders/sh_TextureWrapping.h b/osu.Game.Rulesets.Swing/Resources/Shaders/sh_TextureWrapping.h new file mode 100644 index 0000000..1f3334c --- /dev/null +++ b/osu.Game.Rulesets.Swing/Resources/Shaders/sh_TextureWrapping.h @@ -0,0 +1,27 @@ +float wrap(float coord, int mode, float rangeMin, float rangeMax) +{ + if (mode == 1) + { + return clamp(coord, rangeMin, rangeMax); + } + else if (mode == 3) + { + return mod(coord - rangeMin, rangeMax - rangeMin) + rangeMin; + } + + return coord; +} + +vec2 wrap(vec2 texCoord, vec4 texRect) +{ + return vec2(wrap(texCoord.x, g_WrapModeS, texRect[0], texRect[2]), wrap(texCoord.y, g_WrapModeT, texRect[1], texRect[3])); +} + +vec4 wrappedSampler(vec2 wrappedCoord, vec4 texRect, texture2D wrapTexture, sampler wrapSampler, float lodBias) +{ + if (g_WrapModeS == 2 && (wrappedCoord.x < texRect[0] || wrappedCoord.x > texRect[2]) || + g_WrapModeT == 2 && (wrappedCoord.y < texRect[1] || wrappedCoord.y > texRect[3])) + return vec4(0.0); + + return texture(sampler2D(wrapTexture, wrapSampler), wrappedCoord, lodBias); +} diff --git a/osu.Game.Rulesets.Swing/Resources/Shaders/sh_Utils.h b/osu.Game.Rulesets.Swing/Resources/Shaders/sh_Utils.h new file mode 100644 index 0000000..f89a098 --- /dev/null +++ b/osu.Game.Rulesets.Swing/Resources/Shaders/sh_Utils.h @@ -0,0 +1,25 @@ +#define GAMMA 2.4 + +// perform alpha compositing of two colour components. +// see http://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha.html +lowp vec4 blend(lowp vec4 src, lowp vec4 dst) +{ + lowp float finalAlpha = src.a + dst.a * (1.0 - src.a); + + if (finalAlpha == 0.0) + return vec4(0); + + return vec4( + (src.rgb * src.a + dst.rgb * dst.a * (1.0 - src.a)) / finalAlpha, + finalAlpha + ); +} + +// http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl +// slightly amended to also handle alpha +vec4 hsv2rgb(vec4 c) +{ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return vec4(c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y), c.w); +} \ No newline at end of file