#version 430 // pathfinder/shaders/bin.cs.glsl // // Copyright © 2020 The Pathfinder Project Developers. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. // Assigns microlines to tiles. #extension GL_GOOGLE_include_directive : enable #define MAX_ITERATIONS 1024u #define STEP_DIRECTION_NONE 0 #define STEP_DIRECTION_X 1 #define STEP_DIRECTION_Y 2 #define TILE_FIELD_NEXT_TILE_ID 0 #define TILE_FIELD_FIRST_FILL_ID 1 #define TILE_FIELD_BACKDROP_ALPHA_TILE_ID 2 #define TILE_FIELD_CONTROL 3 precision highp float; #ifdef GL_ES precision highp sampler2D; #endif layout(local_size_x = 64) in; uniform int uMicrolineCount; // How many slots we have allocated for fills. uniform int uMaxFillCount; layout(std430, binding = 0) buffer bMicrolines { restrict readonly uvec4 iMicrolines[]; }; layout(std430, binding = 1) buffer bMetadata { // [0]: tile rect // [1].x: tile offset // [1].y: path ID // [1].z: z write flag // [1].w: clip path ID // [2].x: backdrop offset restrict readonly ivec4 iMetadata[]; }; // [0]: vertexCount (6) // [1]: instanceCount (of fills) // [2]: vertexStart (0) // [3]: baseInstance (0) // [4]: alpha tile count layout(std430, binding = 2) buffer bIndirectDrawParams { restrict uint iIndirectDrawParams[]; }; layout(std430, binding = 3) buffer bFills { restrict writeonly uint iFills[]; }; layout(std430, binding = 4) buffer bTiles { // [0]: next tile ID (initialized to -1) // [1]: first fill ID (initialized to -1) // [2]: backdrop delta upper 8 bits, alpha tile ID lower 24 (initialized to 0, -1 respectively) // [3]: color/ctrl/backdrop word restrict uint iTiles[]; }; layout(std430, binding = 5) buffer bBackdrops { // [0]: backdrop // [1]: tile X offset // [2]: path ID restrict uint iBackdrops[]; }; uint computeTileIndexNoCheck(ivec2 tileCoords, ivec4 pathTileRect, uint pathTileOffset) { ivec2 offsetCoords = tileCoords - pathTileRect.xy; return pathTileOffset + offsetCoords.x + offsetCoords.y * (pathTileRect.z - pathTileRect.x); } bvec4 computeTileOutcodes(ivec2 tileCoords, ivec4 pathTileRect) { return bvec4(lessThan(tileCoords, pathTileRect.xy), greaterThanEqual(tileCoords, pathTileRect.zw)); } bool computeTileIndex(ivec2 tileCoords, ivec4 pathTileRect, uint pathTileOffset, out uint outTileIndex) { outTileIndex = computeTileIndexNoCheck(tileCoords, pathTileRect, pathTileOffset); return !any(computeTileOutcodes(tileCoords, pathTileRect)); } void addFill(vec4 lineSegment, ivec2 tileCoords, ivec4 pathTileRect, uint pathTileOffset) { // Compute tile offset. If out of bounds, cull. uint tileIndex; if (!computeTileIndex(tileCoords, pathTileRect, pathTileOffset, tileIndex)) { return; } // Clip line. If too narrow, cull. uvec4 scaledLocalLine = uvec4((lineSegment - vec4(tileCoords.xyxy * ivec4(16))) * vec4(256.0)); if (scaledLocalLine.x == scaledLocalLine.z) return; // Bump instance count. uint fillIndex = atomicAdd(iIndirectDrawParams[1], 1); // Fill out the link field, inserting into the linked list. uint fillLink = atomicExchange(iTiles[tileIndex * 4 + TILE_FIELD_FIRST_FILL_ID], int(fillIndex)); // Write fill. if (fillIndex < uMaxFillCount) { iFills[fillIndex * 3 + 0] = scaledLocalLine.x | (scaledLocalLine.y << 16); iFills[fillIndex * 3 + 1] = scaledLocalLine.z | (scaledLocalLine.w << 16); iFills[fillIndex * 3 + 2] = fillLink; } } void adjustBackdrop(int backdropDelta, ivec2 tileCoords, ivec4 pathTileRect, uint pathTileOffset, uint pathBackdropOffset) { bvec4 outcodes = computeTileOutcodes(tileCoords, pathTileRect); if (any(outcodes)) { if (!outcodes.x && outcodes.y && !outcodes.z) { uint backdropIndex = pathBackdropOffset + uint(tileCoords.x - pathTileRect.x); atomicAdd(iBackdrops[backdropIndex * 3], backdropDelta); } } else { uint tileIndex = computeTileIndexNoCheck(tileCoords, pathTileRect, pathTileOffset); atomicAdd(iTiles[tileIndex * 4 + TILE_FIELD_BACKDROP_ALPHA_TILE_ID], uint(backdropDelta) << 24); } } vec4 unpackMicroline(uvec4 packedMicroline, out uint outPathIndex) { outPathIndex = packedMicroline.w; ivec4 signedMicroline = ivec4(packedMicroline); return vec4((signedMicroline.x << 16) >> 16, signedMicroline.x >> 16, (signedMicroline.y << 16) >> 16, signedMicroline.y >> 16) + vec4(signedMicroline.z & 0xff, (signedMicroline.z >> 8) & 0xff, (signedMicroline.z >> 16) & 0xff, (signedMicroline.z >> 24) & 0xff) / 256.0; } void main() { uint segmentIndex = gl_GlobalInvocationID.x; if (segmentIndex >= uMicrolineCount) return; uint pathIndex; vec4 lineSegment = unpackMicroline(iMicrolines[segmentIndex], pathIndex); ivec4 pathTileRect = iMetadata[pathIndex * 3 + 0]; uint pathTileOffset = uint(iMetadata[pathIndex * 3 + 1].x); uint pathBackdropOffset = uint(iMetadata[pathIndex * 3 + 2].x); // Following is a straight port of `process_line_segment()`: ivec2 tileSize = ivec2(16); ivec4 tileLineSegment = ivec4(floor(lineSegment / vec4(tileSize.xyxy))); ivec2 fromTileCoords = tileLineSegment.xy, toTileCoords = tileLineSegment.zw; vec2 vector = lineSegment.zw - lineSegment.xy; vec2 vectorIsNegative = vec2(vector.x < 0.0 ? -1.0 : 0.0, vector.y < 0.0 ? -1.0 : 0.0); ivec2 tileStep = ivec2(vector.x < 0.0 ? -1 : 1, vector.y < 0.0 ? -1 : 1); vec2 firstTileCrossing = vec2((fromTileCoords + ivec2(vector.x >= 0.0 ? 1 : 0, vector.y >= 0.0 ? 1 : 0)) * tileSize); vec2 tMax = (firstTileCrossing - lineSegment.xy) / vector; vec2 tDelta = abs(tileSize / vector); vec2 currentPosition = lineSegment.xy; ivec2 tileCoords = fromTileCoords; int lastStepDirection = STEP_DIRECTION_NONE; uint iteration = 0; while (iteration < MAX_ITERATIONS) { int nextStepDirection; if (tMax.x < tMax.y) nextStepDirection = STEP_DIRECTION_X; else if (tMax.x > tMax.y) nextStepDirection = STEP_DIRECTION_Y; else if (tileStep.x > 0.0) nextStepDirection = STEP_DIRECTION_X; else nextStepDirection = STEP_DIRECTION_Y; float nextT = min(nextStepDirection == STEP_DIRECTION_X ? tMax.x : tMax.y, 1.0); // If we've reached the end tile, don't step at all. if (tileCoords == toTileCoords) nextStepDirection = STEP_DIRECTION_NONE; vec2 nextPosition = mix(lineSegment.xy, lineSegment.zw, nextT); vec4 clippedLineSegment = vec4(currentPosition, nextPosition); addFill(clippedLineSegment, tileCoords, pathTileRect, pathTileOffset); // Add extra fills if necessary. vec4 auxiliarySegment; bool haveAuxiliarySegment = false; if (tileStep.y < 0 && nextStepDirection == STEP_DIRECTION_Y) { auxiliarySegment = vec4(clippedLineSegment.zw, vec2(tileCoords * tileSize)); haveAuxiliarySegment = true; } else if (tileStep.y > 0 && lastStepDirection == STEP_DIRECTION_Y) { auxiliarySegment = vec4(vec2(tileCoords * tileSize), clippedLineSegment.xy); haveAuxiliarySegment = true; } if (haveAuxiliarySegment) addFill(auxiliarySegment, tileCoords, pathTileRect, pathTileOffset); // Adjust backdrop if necessary. // // NB: Do not refactor the calls below. This exact code sequence is needed to avoid a // miscompilation on the Radeon Metal compiler. if (tileStep.x < 0 && lastStepDirection == STEP_DIRECTION_X) { adjustBackdrop(1, tileCoords, pathTileRect, pathTileOffset, pathBackdropOffset); } else if (tileStep.x > 0 && nextStepDirection == STEP_DIRECTION_X) { adjustBackdrop(-1, tileCoords, pathTileRect, pathTileOffset, pathBackdropOffset); } // Take a step. if (nextStepDirection == STEP_DIRECTION_X) { tMax.x += tDelta.x; tileCoords.x += tileStep.x; } else if (nextStepDirection == STEP_DIRECTION_Y) { tMax.y += tDelta.y; tileCoords.y += tileStep.y; } else if (nextStepDirection == STEP_DIRECTION_NONE) { break; } currentPosition = nextPosition; lastStepDirection = nextStepDirection; iteration++; } }