1#version 450
2#ifndef SINGLE_RESOURCE
3#extension GL_EXT_nonuniform_qualifier : require
4#endif
5// Draws a solid fill, a client dmabuf (sampled image) or a client wl_shm
6// buffer (B8G8R8A8 words in a storage buffer). Client pixels are
7// premultiplied, as Wayland buffers are: the pipeline blends ONE,
8// ONE_MINUS_SRC_ALPHA. Scaled draws filter linearly; 1:1 draws read the
9// texel under the fragment, pixel exact.
10
11layout(location = 0) in Parameters {
12 flat vec4 map;
13 flat vec4 mapy;
14 flat vec4 color;
15 flat vec4 crop;
16 flat uvec4 buf;
17 flat uvec4 misc;
18 flat uint resourceIndex;
19 flat vec4 round;
20 flat vec4 roundInner;
21 flat vec2 radii;
22} d;
23
24#ifdef SINGLE_RESOURCE
25layout(set = 0, binding = 0) uniform sampler2D tex;
26layout(std430, set = 0, binding = 1) readonly buffer Pixels { uint pixels[]; };
27layout(set = 0, binding = 2) uniform sampler2D chromaTex;
28#define PIXEL(i) pixels[i]
29#else
30layout(constant_id = 0) const int resourceCount = 1;
31layout(set = 0, binding = 0) uniform sampler2D textures[resourceCount];
32layout(std430, set = 0, binding = 1) readonly buffer Pixels { uint pixels[]; } pixelBuffers[resourceCount];
33layout(set = 0, binding = 2) uniform sampler2D chromaTextures[resourceCount];
34#define tex textures[nonuniformEXT(d.resourceIndex)]
35#define chromaTex chromaTextures[nonuniformEXT(d.resourceIndex)]
36#define PIXEL(i) pixelBuffers[nonuniformEXT(d.resourceIndex)].pixels[i]
37#endif
38
39layout(location = 0) out vec4 color;
40
41const uint modeSolid = 0u;
42const uint modeImage = 1u;
43const uint flagOpaque = 1u;
44const uint flagExact = 2u;
45const uint flagPQ = 4u;
46const uint flagExtendedLinear = 8u;
47const uint flagYUV = 16u;
48const uint flagP010 = 32u;
49// flagSmooth averages four samples a quarter of a target pixel around the
50// point: a thumbnail shrunk under half size keeps thin lines and text.
51const uint flagSmooth = 64u;
52const uint flagRounded = 128u;
53const uint flagRing = 256u;
54
55// PQ values are absolute nits. BT.2020 is converted in linear light to
56// BT.709, then clipped to the 8-bit sRGB intermediate. Highlights above
57// SDR reference white are lost (no tone mapping).
58vec3 pqToSRGB(vec3 v, float whiteNits) {
59 vec3 p = pow(clamp(v, 0.0, 1.0), vec3(32.0 / 2523.0));
60 vec3 nits = pow(max(p - vec3(3424.0 / 4096.0), vec3(0.0)) /
61 max(vec3(2413.0 / 128.0) - p * (2392.0 / 128.0), vec3(0.00001)), vec3(16384.0 / 2610.0)) * 10000.0;
62 vec3 rgb = vec3(
63 dot(nits, vec3(1.660491, -0.587641, -0.072850)),
64 dot(nits, vec3(-0.124550, 1.132900, -0.008349)),
65 dot(nits, vec3(-0.018151, -0.100579, 1.118730))
66 );
67 rgb = clamp(rgb / whiteNits, 0.0, 1.0);
68 return mix(rgb * 12.92, 1.055 * pow(rgb, vec3(1.0 / 2.4)) - 0.055, greaterThan(rgb, vec3(0.0031308)));
69}
70
71// H.273 Y'CbCr 4:2:0 reconstruction in electrical (non-linear) light.
72// Type 0 places chroma at x=even luma and halfway between luma rows.
73// vec2 coordinates are normalized to the chroma image for linear filtering.
74vec4 sampleYUV(vec2 src) {
75 ivec2 size = textureSize(tex, 0);
76 vec2 luma = clamp(src, d.crop.xy + 0.5, d.crop.zw - 0.5);
77 float y = texture(tex, luma / vec2(size)).r;
78 // Type 0 chroma is shifted half a luma pixel in x. Clamp in
79 // half-resolution texel space before filtering the chroma plane.
80 vec2 chroma = clamp((src + vec2(0.5, 0.0)) * 0.5,
81 d.crop.xy * 0.5 + 0.5, d.crop.zw * 0.5 - 0.5);
82 vec2 chromaUV = chroma * 2.0 / vec2(size);
83 vec2 cbcr = texture(chromaTex, chromaUV).rg;
84 bool tenBit = (d.misc.y & flagP010) != 0u;
85 bool limited = ((d.buf.w >> 8) & 255u) == 2u;
86 float maxCode = tenBit ? 1023.0 : 255.0;
87 // Packed 10-bit plane views normalize directly to the 0..1023 code range.
88 float yOff = limited ? (tenBit ? 64.0 : 16.0) / maxCode : 0.0;
89 float yScale = limited ? maxCode / (tenBit ? 876.0 : 219.0) : 1.0;
90 float cScale = limited ? maxCode / (tenBit ? 896.0 : 224.0) : 1.0;
91 float yy = (y - yOff) * yScale;
92 vec2 cc = (cbcr - vec2(tenBit ? 512.0/1023.0 : 128.0/255.0)) * cScale;
93 uint coeff = d.buf.w & 255u;
94 float kr = coeff == 4u ? 0.299 : coeff == 6u ? 0.2627 : 0.2126;
95 float kb = coeff == 4u ? 0.114 : coeff == 6u ? 0.0593 : 0.0722;
96 vec3 rgb = vec3(yy + 2.0*(1.0-kr)*cc.y,
97 yy - 2.0*kb*(1.0-kb)/(1.0-kr-kb)*cc.x - 2.0*kr*(1.0-kr)/(1.0-kr-kb)*cc.y,
98 yy + 2.0*(1.0-kb)*cc.x);
99 return vec4(rgb, 1.0);
100}
101
102// texel reads buffer pixel p, clamped to the buffer (clamp to edge).
103vec4 texel(ivec2 p) {
104 p = clamp(p, ivec2(floor(d.crop.xy)), min(ivec2(d.buf.yz) - 1, ivec2(ceil(d.crop.zw)) - 1));
105 // Little-endian B,G,R,A bytes: unpack yields (B, G, R, A).
106 return unpackUnorm4x8(PIXEL(d.buf.x + uint(p.y) * d.buf.y + uint(p.x))).zyxw;
107}
108
109// sampleBuffer reads the client buffer at src (buffer pixels).
110vec4 sampleBuffer(vec2 src, bool exact) {
111 if (d.misc.x == modeImage && (d.misc.y & flagYUV) != 0u) {
112 return sampleYUV(src);
113 }
114 if (d.misc.x == modeImage) {
115 ivec2 size = textureSize(tex, 0);
116 vec2 p = clamp(src, d.crop.xy + 0.5, d.crop.zw - 0.5);
117 return exact ? texelFetch(tex, clamp(ivec2(floor(p)), ivec2(0), size - 1), 0) : texture(tex, p / vec2(size));
118 }
119 if (exact) {
120 return texel(ivec2(floor(src)));
121 }
122 // Bilinear: four texel reads around the sample point.
123 vec2 p = src - 0.5;
124 ivec2 i = ivec2(floor(p));
125 vec2 f = p - vec2(i);
126 return mix(mix(texel(i), texel(i + ivec2(1, 0)), f.x), mix(texel(i + ivec2(0, 1)), texel(i + ivec2(1, 1)), f.x), f.y);
127}
128
129float coverage(vec4 rect, float radius) {
130 vec2 halfSize = (rect.zw - rect.xy) * 0.5;
131 radius = min(radius, min(halfSize.x, halfSize.y));
132 vec2 q = abs(gl_FragCoord.xy - (rect.xy + rect.zw) * 0.5) - halfSize + radius;
133 if ((q.x < 0.0 || q.y < 0.0) && max(q.x, q.y) <= radius - 0.5) return 1.0;
134 float sd = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;
135 return clamp(0.5 - sd, 0.0, 1.0);
136}
137
138void main() {
139 vec2 src = d.map.xy + gl_FragCoord.x * d.map.zw + gl_FragCoord.y * d.mapy.xy;
140 bool exact = (d.misc.y & flagExact) != 0u;
141 vec4 c;
142 if (d.misc.x == modeSolid) {
143 c = d.color;
144 } else if ((d.misc.y & flagSmooth) != 0u) {
145 // Premultiplied samples average without fringes.
146 vec2 dx = d.map.zw * 0.25, dy = d.mapy.xy * 0.25;
147 c = (sampleBuffer(src - dx - dy, false) + sampleBuffer(src + dx - dy, false) +
148 sampleBuffer(src - dx + dy, false) + sampleBuffer(src + dx + dy, false)) * 0.25;
149 } else {
150 c = sampleBuffer(src, exact);
151 }
152 if ((d.misc.y & flagOpaque) != 0u) {
153 c.a = 1.0;
154 }
155 if (c.a > 0.0) {
156 if ((d.misc.y & flagPQ) != 0u) {
157 c.rgb = pqToSRGB(c.rgb / c.a, d.color.x) * c.a;
158 } else if ((d.misc.y & flagExtendedLinear) != 0u) {
159 vec3 v = clamp(c.rgb / c.a * (80.0 / 203.0), 0.0, 1.0);
160 c.rgb = mix(v * 12.92, 1.055 * pow(v, vec3(1.0 / 2.4)) - 0.055, greaterThan(v, vec3(0.0031308))) * c.a;
161 }
162 }
163 if (d.misc.x != modeSolid) {
164 // The surface's alpha modifier; premultiplied, it scales all four.
165 c *= d.color.a;
166 }
167 if ((d.misc.y & flagRounded) != 0u) {
168 c *= coverage(d.round, d.radii.x);
169 if ((d.misc.y & flagRing) != 0u) c *= 1.0 - coverage(d.roundInner, d.radii.y);
170 }
171 color = c;
172}