#include using namespace metal; struct Rectangle { float2 origin; float2 size; }; constant static float2 quadVertices[6] = { float2(0.0, 0.0), float2(1.0, 0.0), float2(0.0, 1.0), float2(1.0, 0.0), float2(0.0, 1.0), float2(1.0, 1.0) }; struct QuadVertexOut { float4 position [[position]]; float2 uv; }; vertex QuadVertexOut callBackgroundVertex( const device Rectangle &rect [[ buffer(0) ]], unsigned int vid [[ vertex_id ]] ) { float2 quadVertex = quadVertices[vid]; QuadVertexOut out; out.position = float4(rect.origin.x + quadVertex.x * rect.size.x, rect.origin.y + quadVertex.y * rect.size.y, 0.0, 1.0); out.position.x = -1.0 + out.position.x * 2.0; out.position.y = -1.0 + out.position.y * 2.0; out.uv = quadVertex; return out; } half4 rgb2hsv(half4 c) { half4 K = half4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); half4 p = mix(half4(c.bg, K.wz), half4(c.gb, K.xy), step(c.b, c.g)); half4 q = mix(half4(p.xyw, c.r), half4(c.r, p.yzx), step(p.x, c.r)); float d = q.x - min(q.w, q.y); float e = 1.0e-10; return half4(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x, c.a); } half4 hsv2rgb(half4 c) { half4 K = half4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); half3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); return half4(c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y), c.a); } fragment half4 callBackgroundFragment( QuadVertexOut in [[stage_in]], const device float2 *positions [[ buffer(0) ]], const device float4 *colors [[ buffer(1) ]], const device float &brightness [[ buffer(2) ]], const device float &saturation [[ buffer(3) ]], const device float4 &overlay [[ buffer(4) ]] ) { half centerDistanceX = in.uv.x - 0.5; half centerDistanceY = in.uv.y - 0.5; half centerDistance = distance(half2(in.uv), half2(0.5, 0.5)); half swirlFactor = 0.35 * centerDistance; half theta = swirlFactor * swirlFactor * 0.8 * 8.0; half sinTheta = sin(theta); half cosTheta = cos(theta); half pixelX = max(0.0, min(1.0, 0.5 + centerDistanceX * cosTheta - centerDistanceY * sinTheta)); half pixelY = max(0.0, min(1.0, 0.5 + centerDistanceX * sinTheta + centerDistanceY * cosTheta)); half distanceSum = 0.0; half r = 0.0; half g = 0.0; half b = 0.0; for (int i = 0; i < 4; i++) { half4 color = half4(colors[i]); half2 colorXY = half2(positions[i]); half2 distanceXY = half2(pixelX - colorXY.x, pixelY - colorXY.y); half distance = max(0.0, 0.92 - sqrt(distanceXY.x * distanceXY.x + distanceXY.y * distanceXY.y)); distance = distance * distance * distance; distanceSum += distance; r = r + distance * color.r; g = g + distance * color.g; b = b + distance * color.b; } if (distanceSum < 0.00001) { distanceSum = 0.00001; } half pixelB = b / distanceSum; half pixelG = g / distanceSum; half pixelR = r / distanceSum; half4 color(pixelR, pixelG, pixelB, 1.0); color = rgb2hsv(color); color.b = clamp(color.b * brightness, 0.0, 1.0); color.g = clamp(color.g * saturation, 0.0, 1.0); color = hsv2rgb(color); color.rgb += half3(overlay.rgb * overlay.a); color.rgb = min(color.rgb, half3(1.0, 1.0, 1.0)); return color; } struct BlobVertexOut { float4 position [[position]]; }; float2 blobVertex(float2 center, float angle, float radius) { return float2(center.x + radius * cos(angle), center.y + radius * sin(angle)); } float2 mapPointInRect(Rectangle rect, half2 point) { half2 out(rect.origin.x + rect.size.x * point.x, rect.origin.y + rect.size.y * point.y); out.x = -1.0 + out.x * 2.0; out.y = -1.0 + out.y * 2.0; return float2(out); } struct SmoothPoint { half2 point; half inAngle; half inLength; half outAngle; half outLength; half2 smoothIn() { return smooth(inAngle, inLength); } half2 smoothOut() { return smooth(outAngle, outLength); } private: half2 smooth(half angle, half length) { return half2( point.x + length * cos(angle), point.y + length * sin(angle) ); } }; half2 evaluateBlobPoint(const device Rectangle &rect, const device float *positions, int index, int count, int subdivisions) { float position = positions[index]; float segmentAngle = float(index) / float(count) * 2.0 * 3.1415926; return half2(blobVertex(float2(0.5, 0.5), segmentAngle, 0.45 + 0.05 * position)); } SmoothPoint evaluateSmoothBlobPoint(const device Rectangle &rect, const device float *positions, int index, int count, int subdivisions) { int prevIndex = (index - 1) < 0 ? (count - 1) : (index - 1); int nextIndex = (index + 1) % count; half2 prev = evaluateBlobPoint(rect, positions, prevIndex, count, subdivisions); half2 curr = evaluateBlobPoint(rect, positions, index, count, subdivisions); half2 next = evaluateBlobPoint(rect, positions, nextIndex, count, subdivisions); float dx = next.x - prev.x; float dy = -next.y + prev.y; float angle = atan2(dy, dx); if (angle < 0.0) { angle = abs(angle); } else { angle = 2 * 3.1415926 - angle; } float smoothAngle = (3.1415926 * 2.0) / float(count); float smoothness = ((4.0 / 3.0) * tan(smoothAngle / 4.0)) / sin(smoothAngle / 2.0) / 2.0; SmoothPoint point; point.point = curr; point.inAngle = angle + 3.1415926; point.inLength = smoothness * distance(curr, prev); point.outAngle = angle; point.outLength = smoothness * distance(curr, next); return point; } half2 evaluateBezierBlobPoint(thread SmoothPoint &curr, thread SmoothPoint &next, half t) { half oneMinusT = 1.0 - t; half2 p0 = curr.point; half2 p1 = curr.smoothOut(); half2 p2 = next.smoothIn(); half2 p3 = next.point; return oneMinusT * oneMinusT * oneMinusT * p0 + 3.0 * t * oneMinusT * oneMinusT * p1 + 3.0 * t * t * oneMinusT * p2 + t * t * t * p3; } vertex BlobVertexOut callBlobVertex( const device Rectangle &rect [[ buffer(0) ]], const device float *positions [[ buffer(1) ]], const device int &count [[ buffer(2) ]], unsigned int vid [[ vertex_id ]] ) { const int subdivisions = 8; int triangleIndex = vid / 3; int segmentIndex = triangleIndex / subdivisions; int nextIndex = (segmentIndex + 1) % count; half innerPosition = half(triangleIndex - segmentIndex * subdivisions) / half(subdivisions); half nextInnerPosition = half(triangleIndex + 1 - segmentIndex * subdivisions) / half(subdivisions); SmoothPoint curr = evaluateSmoothBlobPoint(rect, positions, segmentIndex, count, subdivisions); SmoothPoint next = evaluateSmoothBlobPoint(rect, positions, nextIndex, count, subdivisions); half2 triangle[3]; triangle[0] = half2(0.5, 0.5); triangle[1] = evaluateBezierBlobPoint(curr, next, innerPosition); triangle[2] = evaluateBezierBlobPoint(curr, next, nextInnerPosition); BlobVertexOut out; out.position = float4(float2(mapPointInRect(rect, triangle[vid % 3])), 0.0, 1.0); return out; } fragment half4 callBlobFragment( BlobVertexOut in [[stage_in]], const device float4 &color [[ buffer(0) ]] ) { return half4(color.r * color.a, color.g * color.a, color.b * color.a, color.a); } kernel void videoBiPlanarToRGBA( texture2d inTextureY [[ texture(0) ]], texture2d inTextureUV [[ texture(1) ]], texture2d outTexture [[ texture(2) ]], uint2 threadPosition [[ thread_position_in_grid ]] ) { half y = inTextureY.read(threadPosition).r; half2 uv = inTextureUV.read(uint2(threadPosition.x / 2, threadPosition.y / 2)).rg - half2(0.5, 0.5); half4 color(y + 1.403 * uv.y, y - 0.344 * uv.x - 0.714 * uv.y, y + 1.770 * uv.x, 1.0); outTexture.write(color, threadPosition); } kernel void videoTriPlanarToRGBA( texture2d inTextureY [[ texture(0) ]], texture2d inTextureU [[ texture(1) ]], texture2d inTextureV [[ texture(2) ]], texture2d outTexture [[ texture(3) ]], uint2 threadPosition [[ thread_position_in_grid ]] ) { half y = inTextureY.read(threadPosition).r; uint2 uvPosition = uint2(threadPosition.x / 2, threadPosition.y / 2); half2 inUV = (inTextureU.read(uvPosition).r, inTextureV.read(uvPosition).r); half2 uv = inUV - half2(0.5, 0.5); half4 color(y + 1.403 * uv.y, y - 0.344 * uv.x - 0.714 * uv.y, y + 1.770 * uv.x, 1.0); outTexture.write(color, threadPosition); } vertex QuadVertexOut mainVideoVertex( const device Rectangle &rect [[ buffer(0) ]], const device uint2 &mirror [[ buffer(1) ]], unsigned int vid [[ vertex_id ]] ) { float2 quadVertex = quadVertices[vid]; QuadVertexOut out; out.position = float4(rect.origin.x + quadVertex.x * rect.size.x, rect.origin.y + quadVertex.y * rect.size.y, 0.0, 1.0); out.position.x = -1.0 + out.position.x * 2.0; out.position.y = -1.0 + out.position.y * 2.0; out.uv = float2(quadVertex.x, 1.0 - quadVertex.y); if (mirror.x == 1) { out.uv.x = 1.0 - out.uv.x; } if (mirror.y == 1) { out.uv.y = 1.0 - out.uv.y; } return out; } fragment half4 mainVideoFragment( QuadVertexOut in [[stage_in]], texture2d texture [[ texture(0) ]], const device float &brightness [[ buffer(0) ]], const device float &saturation [[ buffer(1) ]], const device float4 &overlay [[ buffer(2) ]] ) { constexpr sampler sampler(coord::normalized, address::repeat, filter::linear); half4 color = texture.sample(sampler, in.uv); color = rgb2hsv(color); color.b = clamp(color.b * brightness, 0.0, 1.0); color.g = clamp(color.g * saturation, 0.0, 1.0); color = hsv2rgb(color); color.rgb += half3(overlay.rgb * overlay.a); color.rgb = min(color.rgb, half3(1.0, 1.0, 1.0)); return half4(color.r, color.g, color.b, color.a); } constant int BLUR_SAMPLE_COUNT = 7; constant float BLUR_OFFSETS[BLUR_SAMPLE_COUNT] = { -5.227545617192816, -3.3147990233346842, -1.4174297935376852, 0.47225076494548685, 2.364576440741639, 4.268941421369995, 6 }; constant float BLUR_WEIGHTS[BLUR_SAMPLE_COUNT] = { 0.015167713616041436, 0.10117053983645591, 0.2894431725427234, 0.3570581167968804, 0.19014435646109845, 0.0435647539906345, 0.0034513467561660305 }; static void gaussianBlur( texture2d inTexture, texture2d outTexture, float2 offset, uint2 gid ) { constexpr sampler sampler(coord::normalized, address::clamp_to_edge, filter::linear); uint2 textureDim(outTexture.get_width(), outTexture.get_height()); if(all(gid < textureDim)) { float3 outColor(0.0); float2 size(inTexture.get_width(), inTexture.get_height()); float2 baseTexCoord = float2(gid); for (int i = 0; i < BLUR_SAMPLE_COUNT; i++) { outColor += float3(inTexture.sample(sampler, (baseTexCoord + offset * BLUR_OFFSETS[i]) / size).rgb) * BLUR_WEIGHTS[i]; } outTexture.write(half4(half3(outColor), 1.0), gid); } } kernel void gaussianBlurHorizontal( texture2d inTexture [[ texture(0) ]], texture2d outTexture [[ texture(1) ]], uint2 gid [[ thread_position_in_grid ]] ) { gaussianBlur(inTexture, outTexture, float2(1, 0), gid); } kernel void gaussianBlurVertical( texture2d inTexture [[ texture(0) ]], texture2d outTexture [[ texture(1) ]], uint2 gid [[ thread_position_in_grid ]] ) { gaussianBlur(inTexture, outTexture, float2(0, 1), gid); } vertex QuadVertexOut edgeTestVertex( const device Rectangle &rect [[ buffer(0) ]], unsigned int vid [[ vertex_id ]] ) { float2 quadVertex = quadVertices[vid]; QuadVertexOut out; out.position = float4(rect.origin.x + quadVertex.x * rect.size.x, rect.origin.y + quadVertex.y * rect.size.y, 0.0, 1.0); out.position.x = -1.0 + out.position.x * 2.0; out.position.y = -1.0 + out.position.y * 2.0; out.uv = quadVertex; return out; } fragment half4 edgeTestFragment( QuadVertexOut in [[stage_in]], const device float4 &colorIn ) { half4 color = half4(colorIn); return color; }