Migrate GLSL to Slang, text shader
This commit is contained in:
271
assets/shaders/gui_text.slang
Normal file
271
assets/shaders/gui_text.slang
Normal file
@@ -0,0 +1,271 @@
|
||||
import "includes/structs";
|
||||
|
||||
struct Varyings {
|
||||
float4 positionCS : SV_Position;
|
||||
nointerpolation uint instance;
|
||||
float2 texCoordEM : U_TEXCOORD;
|
||||
}
|
||||
|
||||
struct GuiText {
|
||||
float4 color;
|
||||
// Index into _GlyphData
|
||||
uint glyph;
|
||||
float2 bottomLeftSSPX;
|
||||
float fontSizePX;
|
||||
}
|
||||
|
||||
struct GlyphData {
|
||||
float2 bottomLeftEM;
|
||||
float2 sizeEM;
|
||||
// Index into _BandData of the first vertical/horizontal band data.
|
||||
// `bandPtr.x` refers to vertical bands and `bandPtr.y` refers to horizontal
|
||||
// bands.
|
||||
uint2 bandPtr;
|
||||
// Number of vertical/horizontal bands. `bandCount.x` is the number of
|
||||
// vertical bands and `bandCount.y` is the number of horizontal bands.
|
||||
uint2 bandCount;
|
||||
}
|
||||
|
||||
[[vk::binding(0, 0)]] ConstantBuffer<GlobalUniforms> _Global;
|
||||
[[vk::binding(1, 0)]] StructuredBuffer<GuiText> _Instances;
|
||||
|
||||
[[vk::binding(0, 1)]] StructuredBuffer<GlyphData> _GlyphData;
|
||||
[[vk::binding(1, 1)]] StructuredBuffer<half2> _ControlPoints;
|
||||
[[vk::binding(2, 1)]] StructuredBuffer<uint> _BandData;
|
||||
|
||||
static const float2 VERTICES[4] = {
|
||||
float2(0, 1),
|
||||
float2(1, 1),
|
||||
float2(0, 0),
|
||||
float2(1, 0),
|
||||
};
|
||||
|
||||
// Classify a quadratic Bézier curve based on the sign bits of the ray-relative
|
||||
// Y coordinates. Returns `(t2 << 1) | t1` where `t1` and `t2` are bits
|
||||
// signifying the root eligibility; `t1` possibly adding one to the winding
|
||||
// number and `t2` possibly subtracting one from the winding number.
|
||||
uint classifyQuadCurve(float y0, float y1, float y2)
|
||||
{
|
||||
// ┌─────┰────┬────┬────┰────┬────┐
|
||||
// │class┃ y2 │ y1 │ y0 ┃ t2 │ t1 │
|
||||
// ├─────╂────┼────┼────╂────┼────┤
|
||||
// │ A ┃ 0 │ 0 │ 0 ┃ 0 │ 0 │
|
||||
// │ B ┃ 0 │ 0 │ 1 ┃ 1 │ 0 │
|
||||
// │ C ┃ 0 │ 1 │ 0 ┃ 1 │ 1 │
|
||||
// │ D ┃ 0 │ 1 │ 1 ┃ 1 │ 0 │
|
||||
// │ E ┃ 1 │ 0 │ 0 ┃ 0 │ 1 │
|
||||
// │ F ┃ 1 │ 0 │ 1 ┃ 1 │ 1 │
|
||||
// │ G ┃ 1 │ 1 │ 0 ┃ 0 │ 1 │
|
||||
// │ H ┃ 1 │ 1 │ 1 ┃ 0 │ 0 │
|
||||
// └─────┸────┴────┴────┸────┴────┘
|
||||
//
|
||||
// Lookup table constant:
|
||||
// H G F E D C B A
|
||||
// 00 01 11 01 10 11 10 00 = 0x1DB8
|
||||
|
||||
let sign0 = asuint(y0) >> 31;
|
||||
let sign1 = asuint(y1) >> 31;
|
||||
let sign2 = asuint(y2) >> 31;
|
||||
|
||||
let class = (sign0 << 0) | (sign1 << 1) | (sign2 << 2);
|
||||
let shift = 2 * class;
|
||||
|
||||
return (0x2E74U >> shift) & 0b11U;
|
||||
}
|
||||
|
||||
// Find intersections between a quadratic Bézier curve and `y = 0` line by
|
||||
// solving a quadratic equation.
|
||||
//
|
||||
// Returns the X coordinates at witch the intersections occur for roots t1 and
|
||||
// t2. Imaginary solutions to the quadratic equation will return double root at
|
||||
// the global minimum.
|
||||
float2 raycastQuadCurveHorizontally(float2 cp0, float2 cp1, float2 cp2)
|
||||
{
|
||||
static const float EPSILON = 1.0 / 65536.0;
|
||||
|
||||
// When solving for y = 0, we get a quadratic polynomial given by:
|
||||
//
|
||||
// at² - 2bt + c
|
||||
//
|
||||
// where:
|
||||
// * a = cp0.y - 2 * cp1.y + cp2.y
|
||||
// * b = cp0.y - cp1.y
|
||||
// * c = cp0.y
|
||||
//
|
||||
// Let Δ = b² - ac
|
||||
// Then:
|
||||
//
|
||||
// t1 = (b - sqrt(Δ)) / a
|
||||
// t2 = (b + sqrt(Δ)) / a
|
||||
|
||||
let a = cp0 - 2.0 * cp1 + cp2;
|
||||
let b = cp0 - cp1;
|
||||
let c = cp0;
|
||||
|
||||
let Δ = sqrt(max(b.y * b.y - a.y * c.y, 0.0));
|
||||
var t = (b.y + float2(-Δ, Δ)) / a.y;
|
||||
|
||||
// If nearly linear, solve -2bt + c = 0 directly
|
||||
if (abs(a.y) < EPSILON) {
|
||||
t = float2(0.5 * c.y / b.y);
|
||||
}
|
||||
|
||||
return (a.x * t - 2.0 * b.x) * t + c.x;
|
||||
}
|
||||
|
||||
// Find intersections between a quadratic Bézier curve and `x = 0` line by
|
||||
// solving a quadratic equation.
|
||||
//
|
||||
// Returns the Y coordinates at witch the intersections occur for roots t1 and
|
||||
// t2. Imaginary solutions to the quadratic equation will return double root at
|
||||
// the global minimum.
|
||||
float2 raycastQuadCurveVertically(float2 cp0, float2 cp1, float2 cp2)
|
||||
{
|
||||
static const float EPSILON = 1.0 / 65536.0;
|
||||
|
||||
let a = cp0 - 2.0 * cp1 + cp2;
|
||||
let b = cp0 - cp1;
|
||||
let c = cp0;
|
||||
|
||||
let Δ = sqrt(max(b.x * b.x - a.x * c.x, 0.0));
|
||||
var t = (b.x + float2(-Δ, Δ)) / a.x;
|
||||
|
||||
if (abs(a.x) < EPSILON) {
|
||||
t = float2(0.5 * c.x / b.x);
|
||||
}
|
||||
|
||||
return (a.y * t - 2.0 * b.y) * t + c.y;
|
||||
}
|
||||
|
||||
float calculateCoverage(float xCoverage, float yCoverage, float xWeight, float yWeight) {
|
||||
static const float EPSILON = 1.0 / 65536.0;
|
||||
|
||||
var coverage = max(
|
||||
abs(xCoverage * xWeight + yCoverage * yWeight) / max(xWeight + yWeight, EPSILON),
|
||||
min(abs(xCoverage), abs(yCoverage)),
|
||||
);
|
||||
// non-zero fill rule
|
||||
coverage = saturate(coverage);
|
||||
|
||||
return coverage;
|
||||
}
|
||||
|
||||
[shader("vertex")]
|
||||
Varyings vertexMain(uint vertexID : SV_VulkanVertexID, uint instanceID : SV_VulkanInstanceID) {
|
||||
Varyings out;
|
||||
|
||||
let vertex = VERTICES[vertexID];
|
||||
let instance = _Instances[instanceID];
|
||||
let glyph = _GlyphData[instance.glyph];
|
||||
let normal = 2.0 * vertex - 1.0;
|
||||
|
||||
let sizePX = instance.fontSizePX * glyph.sizeEM;
|
||||
|
||||
let positionSSPX = vertex * sizePX + instance.bottomLeftSSPX;
|
||||
let dilatedPositionSSPX = positionSSPX + 0.5 * normal;
|
||||
let dilatedPositionCS = float4(mul(_Global.matrixSSPXtoCS, float3(positionSSPX, 1.0)), 0.0, 1.0);
|
||||
|
||||
let texCoordEM = vertex * glyph.sizeEM + glyph.bottomLeftEM;
|
||||
let dilatedTexCoordEM = texCoordEM + (0.5 / instance.fontSizePX) * normal;
|
||||
|
||||
out.positionCS = dilatedPositionCS;
|
||||
out.instance = instanceID;
|
||||
out.texCoordEM = dilatedTexCoordEM;
|
||||
return out;
|
||||
}
|
||||
|
||||
[shader("fragment")]
|
||||
float4 fragmentMain(Varyings frag) : SV_Target {
|
||||
let instance = _Instances[frag.instance];
|
||||
let glyph = _GlyphData[instance.glyph];
|
||||
|
||||
let emPerPX = fwidth(frag.texCoordEM);
|
||||
let pxPerEM = 1.0 / emPerPX;
|
||||
|
||||
let bandF = float2(glyph.bandCount) * (frag.texCoordEM - glyph.bottomLeftEM) / glyph.sizeEM;
|
||||
let band = uint2(clamp(bandF, float2(0, 0), float2(glyph.bandCount - 1U)));
|
||||
|
||||
// --- HORIZONTAL BAND -----------------------------------------------------
|
||||
|
||||
var xCoverage = 0.0;
|
||||
var xWeight = 0.0;
|
||||
|
||||
let horizontalBandPtr = glyph.bandPtr.y + band.y;
|
||||
let horizontalBandData = _BandData[horizontalBandPtr];
|
||||
let horizontalBandCurvePtr = horizontalBandPtr + (horizontalBandData >> 16);
|
||||
let horizontalBandCurveCount = horizontalBandData & 0xFFFF;
|
||||
|
||||
for (uint horizontalBandCurveID = 0; horizontalBandCurveID < horizontalBandCurveCount; horizontalBandCurveID++) {
|
||||
let cpPtr = _BandData[horizontalBandCurvePtr + horizontalBandCurveID];
|
||||
let cp0 = float2(_ControlPoints[cpPtr]) - frag.texCoordEM;
|
||||
let cp1 = float2(_ControlPoints[cpPtr + 1]) - frag.texCoordEM;
|
||||
let cp2 = float2(_ControlPoints[cpPtr + 2]) - frag.texCoordEM;
|
||||
|
||||
if (max(max(cp0.x, cp1.x), cp2.x) * pxPerEM.x < -0.5) break;
|
||||
|
||||
let rootFlags = classifyQuadCurve(cp0.y, cp1.y, cp2.y);
|
||||
if (rootFlags != 0U) {
|
||||
|
||||
let result = raycastQuadCurveHorizontally(cp0, cp1, cp2) * pxPerEM.x;
|
||||
|
||||
// consider t1 at X position result.x
|
||||
if ((rootFlags & 0b01U) != 0U) {
|
||||
xCoverage += saturate(result.x + 0.5);
|
||||
xWeight = max(xWeight, saturate(1.0 - abs(result.x) * 2.0));
|
||||
}
|
||||
|
||||
// consider t2 at X position result.y
|
||||
if ((rootFlags & 0b10U) != 0U) {
|
||||
xCoverage -= saturate(result.y + 0.5);
|
||||
xWeight = max(xWeight, saturate(1.0 - abs(result.y) * 2.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- VERTICAL BAND -------------------------------------------------------
|
||||
|
||||
var yCoverage = 0.0;
|
||||
var yWeight = 0.0;
|
||||
|
||||
let verticalBandPtr = glyph.bandPtr.x + band.x;
|
||||
let verticalBandData = _BandData[verticalBandPtr];
|
||||
let verticalBandCurvePtr = verticalBandPtr + (verticalBandData >> 16);
|
||||
let verticalBandCurveCount = verticalBandData & 0xFFFF;
|
||||
|
||||
for (uint verticalBandCurveID = 0; verticalBandCurveID < verticalBandCurveCount; verticalBandCurveID++) {
|
||||
let cpPtr = _BandData[verticalBandCurvePtr + verticalBandCurveID];
|
||||
let cp0 = float2(_ControlPoints[cpPtr]) - frag.texCoordEM;
|
||||
let cp1 = float2(_ControlPoints[cpPtr + 1]) - frag.texCoordEM;
|
||||
let cp2 = float2(_ControlPoints[cpPtr + 2]) - frag.texCoordEM;
|
||||
|
||||
if (max(max(cp0.y, cp1.y), cp2.y) * pxPerEM.y < -0.5) break;
|
||||
|
||||
let rootFlags = classifyQuadCurve(cp0.x, cp1.x, cp2.x);
|
||||
if (rootFlags != 0U) {
|
||||
|
||||
let result = raycastQuadCurveVertically(cp0, cp1, cp2) * pxPerEM.y;
|
||||
|
||||
// consider t1 at Y position result.x
|
||||
if ((rootFlags & 0b01U) != 0U)
|
||||
{
|
||||
yCoverage -= saturate(result.x + 0.5);
|
||||
yWeight = max(yWeight, saturate(1.0 - abs(result.x) * 2.0));
|
||||
}
|
||||
|
||||
// consider t2 at Y position result.y
|
||||
if ((rootFlags & 0b10U) != 0U)
|
||||
{
|
||||
yCoverage += saturate(result.y + 0.5);
|
||||
yWeight = max(yWeight, saturate(1.0 - abs(result.y) * 2.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- RESOLVE -------------------------------------------------------------
|
||||
|
||||
let coverage = calculateCoverage(xCoverage, yCoverage, xWeight, yWeight) * instance.color.a;
|
||||
return float4(
|
||||
coverage * instance.color.rgb,
|
||||
coverage,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user