[mtl canvas, wip] Fixing inclusive/exclusive bound checks during curve side tests / shortcut / rasterization

This commit is contained in:
Martin Fouilleul 2023-04-04 19:02:53 +02:00
parent 9322db8201
commit 8d7fdf3b1a
4 changed files with 555 additions and 230 deletions

View File

@ -47,6 +47,7 @@ int main()
mp_window_focus(window); mp_window_focus(window);
f64 frameTime = 0; f64 frameTime = 0;
f32 x = 0, y = 0;
while(!mp_should_quit()) while(!mp_should_quit())
{ {
@ -63,6 +64,29 @@ int main()
mp_request_quit(); mp_request_quit();
} break; } break;
case MP_EVENT_KEYBOARD_KEY:
{
if(event.key.action == MP_KEY_PRESS)
{
if(event.key.code == MP_KEY_LEFT)
{
x-=1;
}
if(event.key.code == MP_KEY_RIGHT)
{
x+=1;
}
if(event.key.code == MP_KEY_UP)
{
y-=1;
}
if(event.key.code == MP_KEY_DOWN)
{
y+=1;
}
}
} break;
default: default:
break; break;
} }
@ -95,13 +119,44 @@ int main()
mg_close_path(); mg_close_path();
mg_set_color_rgba(0, 0, 1, 1); mg_set_color_rgba(0, 0, 1, 1);
mg_fill(); mg_fill();
*/
mg_move_to(2*400, 2*400); mg_move_to(2*400, 2*400);
mg_cubic_to(2*400, 2*200, 2*600, 2*500, 2*600, 2*400); mg_cubic_to(2*400, 2*200, 2*600, 2*500, 2*600, 2*400);
mg_close_path(); mg_close_path();
mg_set_color_rgba(0, 0, 1, 1); mg_set_color_rgba(0, 0, 1, 1);
mg_fill(); mg_fill();
*/
mg_move_to(200, 200);
mg_line_to(300, 300);
mg_line_to(200, 400);
mg_line_to(100, 300);
mg_close_path();
mg_set_color_rgba(1, 0, 0, 1);
// mg_set_width(2);
mg_stroke();
mg_move_to(400, 400);
mg_quadratic_to(600, 601, 800, 400);
mg_set_color_rgba(0, 0, 1, 1);
mg_stroke();
mg_move_to(x+2*400, y+2*400);
mg_cubic_to(x+2*400, y+2*200, x+2*600, y+2*500, x+2*600, y+2*400);
mg_close_path();
mg_set_color_rgba(0, 0, 1, 1);
mg_stroke();
/*
mg_move_to(x+8, y+8);
mg_line_to(x+33, y+8);
mg_line_to(x+33, y+19);
mg_line_to(x+8, y+19);
mg_close_path();
mg_set_color_rgba(0, 0, 1, 1);
mg_fill();
*/
printf("Milepost vector graphics test program (frame time = %fs, fps = %f)...\n", printf("Milepost vector graphics test program (frame time = %fs, fps = %f)...\n",
frameTime, frameTime,
1./frameTime); 1./frameTime);

View File

@ -13,6 +13,7 @@
typedef enum { typedef enum {
MG_MTL_FILL, MG_MTL_FILL,
MG_MTL_STROKE,
} mg_mtl_cmd; } mg_mtl_cmd;
typedef struct mg_mtl_path typedef struct mg_mtl_path

View File

@ -83,6 +83,233 @@ void mg_mtl_print_log(int bufferIndex, id<MTLBuffer> logBuffer, id<MTLBuffer> lo
} }
typedef struct mg_mtl_encoding_context
{
int mtlEltCount;
mg_mtl_path_elt* elementBufferData;
int pathIndex;
mg_primitive* primitive;
vec4 pathExtents;
} mg_mtl_encoding_context;
void mg_mtl_canvas_encode_element(mg_mtl_encoding_context* context, mg_path_elt_type kind, vec2* p)
{
mg_mtl_path_elt* mtlElt = &context->elementBufferData[context->mtlEltCount];
context->mtlEltCount++;
mtlElt->pathIndex = context->pathIndex;
int count = 0;
switch(kind)
{
case MG_PATH_LINE:
mtlElt->kind = MG_MTL_LINE;
count = 2;
break;
case MG_PATH_QUADRATIC:
mtlElt->kind = MG_MTL_QUADRATIC;
count = 3;
break;
case MG_PATH_CUBIC:
mtlElt->kind = MG_MTL_CUBIC;
count = 4;
break;
default:
break;
}
for(int i=0; i<count; i++)
{
mg_update_path_extents(&context->pathExtents, p[i]);
vec2 screenP = mg_mat2x3_mul(context->primitive->attributes.transform, p[i]);
mtlElt->p[i] = (vector_float2){screenP.x, screenP.y};
}
}
void mg_mtl_canvas_stroke_line(mg_mtl_encoding_context* context, vec2* p)
{
f32 width = context->primitive->attributes.width;
vec2 v = {p[1].x-p[0].x, p[1].y-p[0].y};
vec2 n = {v.y, -v.x};
f32 norm = sqrt(n.x*n.x + n.y*n.y);
vec2 offset = vec2_mul(0.5*width/norm, n);
vec2 left[2] = {vec2_add(p[0], offset), vec2_add(p[1], offset)};
vec2 right[2] = {vec2_add(p[1], vec2_mul(-1, offset)), vec2_add(p[0], vec2_mul(-1, offset))};
vec2 joint0[2] = {vec2_add(p[0], vec2_mul(-1, offset)), vec2_add(p[0], offset)};
vec2 joint1[2] = {vec2_add(p[1], offset), vec2_add(p[1], vec2_mul(-1, offset))};
mg_mtl_canvas_encode_element(context, MG_PATH_LINE, right);
mg_mtl_canvas_encode_element(context, MG_PATH_LINE, left);
mg_mtl_canvas_encode_element(context, MG_PATH_LINE, joint0);
mg_mtl_canvas_encode_element(context, MG_PATH_LINE, joint1);
}
void mg_mtl_canvas_stroke_quadratic(mg_mtl_encoding_context* context, vec2* p)
{
f32 width = context->primitive->attributes.width;
f32 tolerance = minimum(context->primitive->attributes.tolerance, 0.5 * width);
vec2 leftHull[3];
vec2 rightHull[3];
if( !mg_offset_hull(3, p, leftHull, width/2)
|| !mg_offset_hull(3, p, rightHull, -width/2))
{
//TODO split and recurse
//NOTE: offsetting the hull failed, split the curve
vec2 splitLeft[3];
vec2 splitRight[3];
mg_quadratic_split(p, 0.5, splitLeft, splitRight);
mg_mtl_canvas_stroke_quadratic(context, splitLeft);
mg_mtl_canvas_stroke_quadratic(context, splitRight);
}
else
{
const int CHECK_SAMPLE_COUNT = 5;
f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6};
f32 d2LowBound = Square(0.5 * width - tolerance);
f32 d2HighBound = Square(0.5 * width + tolerance);
f32 maxOvershoot = 0;
f32 maxOvershootParameter = 0;
for(int i=0; i<CHECK_SAMPLE_COUNT; i++)
{
f32 t = checkSamples[i];
vec2 c = mg_quadratic_get_point(p, t);
vec2 cp = mg_quadratic_get_point(leftHull, t);
vec2 cn = mg_quadratic_get_point(rightHull, t);
f32 positiveDistSquare = Square(c.x - cp.x) + Square(c.y - cp.y);
f32 negativeDistSquare = Square(c.x - cn.x) + Square(c.y - cn.y);
f32 positiveOvershoot = maximum(positiveDistSquare - d2HighBound, d2LowBound - positiveDistSquare);
f32 negativeOvershoot = maximum(negativeDistSquare - d2HighBound, d2LowBound - negativeDistSquare);
f32 overshoot = maximum(positiveOvershoot, negativeOvershoot);
if(overshoot > maxOvershoot)
{
maxOvershoot = overshoot;
maxOvershootParameter = t;
}
}
if(maxOvershoot > 0)
{
vec2 splitLeft[3];
vec2 splitRight[3];
mg_quadratic_split(p, maxOvershootParameter, splitLeft, splitRight);
mg_mtl_canvas_stroke_quadratic(context, splitLeft);
mg_mtl_canvas_stroke_quadratic(context, splitRight);
}
else
{
vec2 tmp = leftHull[0];
leftHull[0] = leftHull[2];
leftHull[2] = tmp;
mg_mtl_canvas_encode_element(context, MG_PATH_QUADRATIC, rightHull);
mg_mtl_canvas_encode_element(context, MG_PATH_QUADRATIC, leftHull);
vec2 joint0[2] = {rightHull[2], leftHull[0]};
vec2 joint1[2] = {leftHull[2], rightHull[0]};
mg_mtl_canvas_encode_element(context, MG_PATH_LINE, joint0);
mg_mtl_canvas_encode_element(context, MG_PATH_LINE, joint1);
}
}
}
void mg_mtl_canvas_stroke_cubic(mg_mtl_encoding_context* context, vec2* p)
{
f32 width = context->primitive->attributes.width;
f32 tolerance = minimum(context->primitive->attributes.tolerance, 0.5 * width);
vec2 leftHull[4];
vec2 rightHull[4];
if( !mg_offset_hull(4, p, leftHull, width/2)
|| !mg_offset_hull(4, p, rightHull, -width/2))
{
//TODO split and recurse
//NOTE: offsetting the hull failed, split the curve
vec2 splitLeft[4];
vec2 splitRight[4];
mg_cubic_split(p, 0.5, splitLeft, splitRight);
mg_mtl_canvas_stroke_cubic(context, splitLeft);
mg_mtl_canvas_stroke_cubic(context, splitRight);
}
else
{
const int CHECK_SAMPLE_COUNT = 5;
f32 checkSamples[CHECK_SAMPLE_COUNT] = {1./6, 2./6, 3./6, 4./6, 5./6};
f32 d2LowBound = Square(0.5 * width - tolerance);
f32 d2HighBound = Square(0.5 * width + tolerance);
f32 maxOvershoot = 0;
f32 maxOvershootParameter = 0;
for(int i=0; i<CHECK_SAMPLE_COUNT; i++)
{
f32 t = checkSamples[i];
vec2 c = mg_cubic_get_point(p, t);
vec2 cp = mg_cubic_get_point(leftHull, t);
vec2 cn = mg_cubic_get_point(rightHull, t);
f32 positiveDistSquare = Square(c.x - cp.x) + Square(c.y - cp.y);
f32 negativeDistSquare = Square(c.x - cn.x) + Square(c.y - cn.y);
f32 positiveOvershoot = maximum(positiveDistSquare - d2HighBound, d2LowBound - positiveDistSquare);
f32 negativeOvershoot = maximum(negativeDistSquare - d2HighBound, d2LowBound - negativeDistSquare);
f32 overshoot = maximum(positiveOvershoot, negativeOvershoot);
if(overshoot > maxOvershoot)
{
maxOvershoot = overshoot;
maxOvershootParameter = t;
}
}
if(maxOvershoot > 0)
{
vec2 splitLeft[4];
vec2 splitRight[4];
mg_cubic_split(p, maxOvershootParameter, splitLeft, splitRight);
mg_mtl_canvas_stroke_cubic(context, splitLeft);
mg_mtl_canvas_stroke_cubic(context, splitRight);
}
else
{
vec2 tmp = leftHull[0];
leftHull[0] = leftHull[3];
leftHull[3] = tmp;
tmp = leftHull[1];
leftHull[1] = leftHull[2];
leftHull[2] = tmp;
mg_mtl_canvas_encode_element(context, MG_PATH_CUBIC, rightHull);
mg_mtl_canvas_encode_element(context, MG_PATH_CUBIC, leftHull);
vec2 joint0[2] = {rightHull[3], leftHull[0]};
vec2 joint1[2] = {leftHull[3], rightHull[0]};
mg_mtl_canvas_encode_element(context, MG_PATH_LINE, joint0);
mg_mtl_canvas_encode_element(context, MG_PATH_LINE, joint1);
}
}
}
void mg_mtl_canvas_render(mg_canvas_backend* interface, void mg_mtl_canvas_render(mg_canvas_backend* interface,
mg_color clearColor, mg_color clearColor,
u32 primitiveCount, u32 primitiveCount,
@ -101,90 +328,83 @@ void mg_mtl_canvas_render(mg_canvas_backend* interface,
//NOTE: fill renderer input buffers //NOTE: fill renderer input buffers
int pathCount = 0; int pathCount = 0;
int mtlEltCount = 0;
vec2 currentPos = {0}; vec2 currentPos = {0};
mg_mtl_encoding_context context = {.mtlEltCount = 0,
.elementBufferData = elementBufferData};
for(int primitiveIndex = 0; primitiveIndex < primitiveCount; primitiveIndex++) for(int primitiveIndex = 0; primitiveIndex < primitiveCount; primitiveIndex++)
{ {
mg_primitive* primitive = &primitives[primitiveIndex]; mg_primitive* primitive = &primitives[primitiveIndex];
if(primitive->cmd == MG_CMD_FILL && primitive->path.count)
if(primitive->path.count)
{ {
vec4 pathExtents = (vec4){FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX}; context.primitive = primitive;
context.pathIndex = primitiveIndex;
context.pathExtents = (vec4){FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX};
for(int eltIndex = 0; for(int eltIndex = 0;
(eltIndex < primitive->path.count) && (primitive->path.startIndex + eltIndex < eltCount); (eltIndex < primitive->path.count) && (primitive->path.startIndex + eltIndex < eltCount);
eltIndex++) eltIndex++)
{ {
mg_path_elt* elt = &pathElements[primitive->path.startIndex + eltIndex]; mg_path_elt* elt = &pathElements[primitive->path.startIndex + eltIndex];
if(elt->type == MG_PATH_MOVE)
if(elt->type != MG_PATH_MOVE)
{ {
currentPos = elt->p[0]; vec2 p[4] = {currentPos, elt->p[0], elt->p[1], elt->p[2]};
if(primitive->cmd == MG_CMD_FILL)
{
mg_mtl_canvas_encode_element(&context, elt->type, p);
}
else if(primitive->cmd == MG_CMD_STROKE)
{
switch(elt->type)
{
case MG_PATH_LINE:
mg_mtl_canvas_stroke_line(&context, p);
break;
case MG_PATH_QUADRATIC:
mg_mtl_canvas_stroke_quadratic(&context, p);
break;
case MG_PATH_CUBIC:
mg_mtl_canvas_stroke_cubic(&context, p);
break;
default:
break;
}
}
} }
else if(elt->type == MG_PATH_LINE) switch(elt->type)
{ {
///////////////////////////////////////////////////////////////////////////////////// case MG_PATH_MOVE:
//TODO: order control points so that we can collapse all elements into same codepath currentPos = elt->p[0];
///////////////////////////////////////////////////////////////////////////////////// break;
//NOTE: transform and push path elt + update primitive bounding box case MG_PATH_LINE:
vec2 p0 = mg_mat2x3_mul(primitive->attributes.transform, currentPos); currentPos = elt->p[0];
vec2 p1 = mg_mat2x3_mul(primitive->attributes.transform, elt->p[0]); break;
currentPos = elt->p[0];
mg_update_path_extents(&pathExtents, p0); case MG_PATH_QUADRATIC:
mg_update_path_extents(&pathExtents, p1); currentPos = elt->p[1];
break;
mg_mtl_path_elt* mtlElt = &elementBufferData[mtlEltCount]; case MG_PATH_CUBIC:
mtlEltCount++; currentPos = elt->p[2];
break;
mtlElt->pathIndex = primitiveIndex;
mtlElt->kind = (mg_mtl_seg_kind)elt->type;
mtlElt->p[0] = (vector_float2){p0.x, p0.y};
mtlElt->p[1] = (vector_float2){p1.x, p1.y};
} }
else if(elt->type == MG_PATH_QUADRATIC) }
{
vec2 p0 = mg_mat2x3_mul(primitive->attributes.transform, currentPos);
vec2 p1 = mg_mat2x3_mul(primitive->attributes.transform, elt->p[0]);
vec2 p2 = mg_mat2x3_mul(primitive->attributes.transform, elt->p[1]);
currentPos = elt->p[1];
mg_update_path_extents(&pathExtents, p0); if(primitive->cmd == MG_CMD_STROKE)
mg_update_path_extents(&pathExtents, p1); {
mg_update_path_extents(&pathExtents, p2); f32 margin = maximum(primitive->attributes.width, primitive->attributes.maxJointExcursion);
context.pathExtents.x -= margin;
mg_mtl_path_elt* mtlElt = &elementBufferData[mtlEltCount]; context.pathExtents.y -= margin;
mtlEltCount++; context.pathExtents.z += margin;
context.pathExtents.w += margin;
mtlElt->pathIndex = primitiveIndex;
mtlElt->kind = (mg_mtl_seg_kind)elt->type;
mtlElt->p[0] = (vector_float2){p0.x, p0.y};
mtlElt->p[1] = (vector_float2){p1.x, p1.y};
mtlElt->p[2] = (vector_float2){p2.x, p2.y};
}
else if(elt->type == MG_PATH_CUBIC)
{
vec2 p0 = mg_mat2x3_mul(primitive->attributes.transform, currentPos);
vec2 p1 = mg_mat2x3_mul(primitive->attributes.transform, elt->p[0]);
vec2 p2 = mg_mat2x3_mul(primitive->attributes.transform, elt->p[1]);
vec2 p3 = mg_mat2x3_mul(primitive->attributes.transform, elt->p[2]);
currentPos = elt->p[2];
mg_update_path_extents(&pathExtents, p0);
mg_update_path_extents(&pathExtents, p1);
mg_update_path_extents(&pathExtents, p2);
mg_update_path_extents(&pathExtents, p3);
mg_mtl_path_elt* mtlElt = &elementBufferData[mtlEltCount];
mtlEltCount++;
mtlElt->pathIndex = primitiveIndex;
mtlElt->kind = (mg_mtl_seg_kind)elt->type;
mtlElt->p[0] = (vector_float2){p0.x, p0.y};
mtlElt->p[1] = (vector_float2){p1.x, p1.y};
mtlElt->p[2] = (vector_float2){p2.x, p2.y};
mtlElt->p[3] = (vector_float2){p3.x, p3.y};
}
} }
//NOTE: push path //NOTE: push path
@ -192,10 +412,10 @@ void mg_mtl_canvas_render(mg_canvas_backend* interface,
pathCount++; pathCount++;
path->cmd = (mg_mtl_cmd)primitive->cmd; path->cmd = (mg_mtl_cmd)primitive->cmd;
path->box = (vector_float4){maximum(primitive->attributes.clip.x, pathExtents.x), path->box = (vector_float4){maximum(primitive->attributes.clip.x, context.pathExtents.x),
maximum(primitive->attributes.clip.y, pathExtents.y), maximum(primitive->attributes.clip.y, context.pathExtents.y),
minimum(primitive->attributes.clip.x + primitive->attributes.clip.w, pathExtents.z), minimum(primitive->attributes.clip.x + primitive->attributes.clip.w, context.pathExtents.z),
minimum(primitive->attributes.clip.y + primitive->attributes.clip.h, pathExtents.w)}; minimum(primitive->attributes.clip.y + primitive->attributes.clip.h, context.pathExtents.w)};
path->color = (vector_float4){primitive->attributes.color.r, path->color = (vector_float4){primitive->attributes.color.r,
primitive->attributes.color.g, primitive->attributes.color.g,
@ -269,7 +489,7 @@ void mg_mtl_canvas_render(mg_canvas_backend* interface,
[segmentEncoder setBuffer:backend->logBuffer[backend->bufferIndex] offset:0 atIndex:9]; [segmentEncoder setBuffer:backend->logBuffer[backend->bufferIndex] offset:0 atIndex:9];
[segmentEncoder setBuffer:backend->logOffsetBuffer[backend->bufferIndex] offset:0 atIndex:10]; [segmentEncoder setBuffer:backend->logOffsetBuffer[backend->bufferIndex] offset:0 atIndex:10];
MTLSize segmentGridSize = MTLSizeMake(mtlEltCount, 1, 1); MTLSize segmentGridSize = MTLSizeMake(context.mtlEltCount, 1, 1);
MTLSize segmentGroupSize = MTLSizeMake([backend->segmentPipeline maxTotalThreadsPerThreadgroup], 1, 1); MTLSize segmentGroupSize = MTLSizeMake([backend->segmentPipeline maxTotalThreadsPerThreadgroup], 1, 1);
[segmentEncoder dispatchThreads: segmentGridSize threadsPerThreadgroup: segmentGroupSize]; [segmentEncoder dispatchThreads: segmentGridSize threadsPerThreadgroup: segmentGroupSize];
@ -282,6 +502,8 @@ void mg_mtl_canvas_render(mg_canvas_backend* interface,
[backpropEncoder setBuffer:backend->pathQueueBuffer offset:0 atIndex:0]; [backpropEncoder setBuffer:backend->pathQueueBuffer offset:0 atIndex:0];
[backpropEncoder setBuffer:backend->tileQueueBuffer offset:0 atIndex:1]; [backpropEncoder setBuffer:backend->tileQueueBuffer offset:0 atIndex:1];
[backpropEncoder setBuffer:backend->logBuffer[backend->bufferIndex] offset:0 atIndex:2];
[backpropEncoder setBuffer:backend->logOffsetBuffer[backend->bufferIndex] offset:0 atIndex:3];
MTLSize backpropGroupSize = MTLSizeMake([backend->backpropPipeline maxTotalThreadsPerThreadgroup], 1, 1); MTLSize backpropGroupSize = MTLSizeMake([backend->backpropPipeline maxTotalThreadsPerThreadgroup], 1, 1);
MTLSize backpropGridSize = MTLSizeMake(pathCount*backpropGroupSize.width, 1, 1); MTLSize backpropGridSize = MTLSizeMake(pathCount*backpropGroupSize.width, 1, 1);
@ -301,6 +523,8 @@ void mg_mtl_canvas_render(mg_canvas_backend* interface,
[mergeEncoder setBuffer:backend->tileOpBuffer offset:0 atIndex:4]; [mergeEncoder setBuffer:backend->tileOpBuffer offset:0 atIndex:4];
[mergeEncoder setBuffer:backend->tileOpCountBuffer offset:0 atIndex:5]; [mergeEncoder setBuffer:backend->tileOpCountBuffer offset:0 atIndex:5];
[mergeEncoder setBuffer:backend->screenTilesBuffer offset:0 atIndex:6]; [mergeEncoder setBuffer:backend->screenTilesBuffer offset:0 atIndex:6];
[mergeEncoder setBuffer:backend->logBuffer[backend->bufferIndex] offset:0 atIndex:7];
[mergeEncoder setBuffer:backend->logOffsetBuffer[backend->bufferIndex] offset:0 atIndex:8];
MTLSize mergeGridSize = MTLSizeMake(nTilesX, nTilesY, 1); MTLSize mergeGridSize = MTLSizeMake(nTilesX, nTilesY, 1);
MTLSize mergeGroupSize = MTLSizeMake(16, 16, 1); MTLSize mergeGroupSize = MTLSizeMake(16, 16, 1);
@ -535,7 +759,7 @@ mg_canvas_backend* mg_mtl_canvas_create(mg_surface surface)
bufferOptions = MTLResourceStorageModeShared; bufferOptions = MTLResourceStorageModeShared;
for(int i=0; i<MG_MTL_INPUT_BUFFERS_COUNT; i++) for(int i=0; i<MG_MTL_INPUT_BUFFERS_COUNT; i++)
{ {
backend->logBuffer[i] = [metalSurface->device newBufferWithLength: 4<<20 backend->logBuffer[i] = [metalSurface->device newBufferWithLength: 1<<20
options: bufferOptions]; options: bufferOptions];
backend->logOffsetBuffer[i] = [metalSurface->device newBufferWithLength: sizeof(int) backend->logOffsetBuffer[i] = [metalSurface->device newBufferWithLength: sizeof(int)

View File

@ -11,14 +11,14 @@ using namespace metal;
typedef struct mtl_log_context typedef struct mtl_log_context
{ {
device char* buffer; device char* buffer;
device atomic_int* offset; device volatile atomic_int* offset;
bool enabled; bool enabled;
} mtl_log_context; } mtl_log_context;
int strlen(const constant char* msg) int strlen(const constant char* msg)
{ {
int count = 0; int count = 0;
while(msg[count] != 0) while(msg[count] != '\0')
{ {
count++; count++;
} }
@ -28,7 +28,7 @@ int strlen(const constant char* msg)
int strlen(const thread char* msg) int strlen(const thread char* msg)
{ {
int count = 0; int count = 0;
while(msg[count] != 0) while(msg[count] != '\0')
{ {
count++; count++;
} }
@ -42,10 +42,11 @@ void mtl_log(mtl_log_context context, const constant char* msg)
int len = strlen(msg); int len = strlen(msg);
int offset = atomic_fetch_add_explicit(context.offset, len+1, memory_order_relaxed); int offset = atomic_fetch_add_explicit(context.offset, len+1, memory_order_relaxed);
for(int i=0; i<len+1; i++) for(int i=0; i<len; i++)
{ {
context.buffer[offset+i] = msg[i]; context.buffer[offset+i] = msg[i];
} }
context.buffer[offset+len] = '\0';
} }
} }
@ -56,10 +57,11 @@ void mtl_log(mtl_log_context context, const thread char* msg)
int len = strlen(msg); int len = strlen(msg);
int offset = atomic_fetch_add_explicit(context.offset, len+1, memory_order_relaxed); int offset = atomic_fetch_add_explicit(context.offset, len+1, memory_order_relaxed);
for(int i=0; i<len+1; i++) for(int i=0; i<len; i++)
{ {
context.buffer[offset+i] = msg[i]; context.buffer[offset+i] = msg[i];
} }
context.buffer[offset+len] = '\0';
} }
} }
@ -112,6 +114,10 @@ void mtl_log_f32(mtl_log_context context, float value)
{ {
decimal /= 10; decimal /= 10;
} }
if(decimal < 0)
{
decimal *= -1;
}
const int bufSize = 64; const int bufSize = 64;
char buffer[bufSize]; char buffer[bufSize];
@ -138,6 +144,27 @@ void mtl_log_f32(mtl_log_context context, float value)
mtl_log(context, buffer); mtl_log(context, buffer);
} }
void log_cubic_bezier(thread float2* p, mtl_log_context logCtx)
{
mtl_log(logCtx, "(");
mtl_log_f32(logCtx, p[0].x);
mtl_log(logCtx, ", ");
mtl_log_f32(logCtx, p[0].y);
mtl_log(logCtx, ") (");
mtl_log_f32(logCtx, p[1].x);
mtl_log(logCtx, ", ");
mtl_log_f32(logCtx, p[1].y);
mtl_log(logCtx, ") (");
mtl_log_f32(logCtx, p[2].x);
mtl_log(logCtx, ", ");
mtl_log_f32(logCtx, p[2].y);
mtl_log(logCtx, ") (");
mtl_log_f32(logCtx, p[3].x);
mtl_log(logCtx, ", ");
mtl_log_f32(logCtx, p[3].y);
mtl_log(logCtx, ")\n");
}
kernel void mtl_path_setup(constant int* pathCount [[buffer(0)]], kernel void mtl_path_setup(constant int* pathCount [[buffer(0)]],
const device mg_mtl_path* pathBuffer [[buffer(1)]], const device mg_mtl_path* pathBuffer [[buffer(1)]],
device mg_mtl_path_queue* pathQueueBuffer [[buffer(2)]], device mg_mtl_path_queue* pathQueueBuffer [[buffer(2)]],
@ -169,31 +196,34 @@ kernel void mtl_path_setup(constant int* pathCount [[buffer(0)]],
} }
} }
bool mtl_is_left_of_segment(float2 p, const device mg_mtl_segment* seg) int mtl_side_of_segment(float2 p, const device mg_mtl_segment* seg, mtl_log_context log = {.enabled = false})
{ {
//NOTE: test is p is on the left of a curve segment. int side = 0;
if(p.y > seg->box.w || p.y <= seg->box.y)
/*WARN: if p is outside the bounding box of segment, we still consider it left from
the segment if it is left of its diagonal. This is done so that we can test
if tile corners are on the same side of the curve during tiling (corner are
not necessarily inside the bounding box, even if the tile itself overlaps
the curve).
During fine rasterization, this function need to be guarded by a the following
check: if(p.y >= seg->box.y && p.y < seg->box.w) {...}
*/
bool isLeft = false;
//NOTE: if point is left of curve bounding box, it is left of curve
if(p.x < seg->box.x)
{ {
isLeft = true; if(p.x > seg->box.x && p.x <= seg->box.z)
{
if(p.y > seg->box.w)
{
side = (seg->config == MG_MTL_TL || seg->config == MG_MTL_BR)? -1 : 1;
}
else
{
side = (seg->config == MG_MTL_TL || seg->config == MG_MTL_BR)? 1 : -1;
}
}
} }
else if(p.x < seg->box.z) else if(p.x > seg->box.z)
{ {
/*NOTE: if point and curve are on opposite sides of diagonal and on the left of diagonal, side = 1;
it is left from the curve }
otherwise if point and curve are on the same side of diagonal, do implicit test else if(p.x <= seg->box.x)
*/ {
side = -1;
}
else
{
// eval based on diagonal
float alpha = (seg->box.w - seg->box.y)/(seg->box.z - seg->box.x); float alpha = (seg->box.w - seg->box.y)/(seg->box.z - seg->box.x);
float ofs = seg->box.w - seg->box.y; float ofs = seg->box.w - seg->box.y;
float dx = p.x - seg->box.x; float dx = p.x - seg->box.x;
@ -202,42 +232,38 @@ bool mtl_is_left_of_segment(float2 p, const device mg_mtl_segment* seg)
if( (seg->config == MG_MTL_BR && dy > alpha*dx) if( (seg->config == MG_MTL_BR && dy > alpha*dx)
||(seg->config == MG_MTL_TR && dy < ofs - alpha*dx)) ||(seg->config == MG_MTL_TR && dy < ofs - alpha*dx))
{ {
isLeft = true; side = -1;
} }
else if( !(seg->config == MG_MTL_TL && dy < alpha*dx) else if( (seg->config == MG_MTL_TL && dy < alpha*dx)
&& !(seg->config == MG_MTL_BL && dy > ofs - alpha*dx)) ||(seg->config == MG_MTL_BL && dy > ofs - alpha*dx))
{ {
//NOTE: for lines, we only have config BR or TR, so the test is always negative side = 1;
}
else
{
switch(seg->kind)
{
case MG_MTL_LINE:
side = 1;
break;
if(seg->kind == MG_MTL_QUADRATIC) case MG_MTL_QUADRATIC:
{
float3 ph = {p.x, p.y, 1};
float3 klm = seg->implicitMatrix * ph;
if((klm.x*klm.x - klm.y)*klm.z < 0)
{ {
isLeft = true; float3 ph = {p.x, p.y, 1};
} float3 klm = seg->implicitMatrix * ph;
} side = ((klm.x*klm.x - klm.y)*klm.z < 0)? -1 : 1;
else if(seg->kind == MG_MTL_CUBIC) } break;
{
/* case MG_MTL_CUBIC:
//DEBUG: behave as a straight line segment
if((seg->config == MG_MTL_BL || seg->config == MG_MTL_TL))
{ {
isLeft = true; float3 ph = {p.x, p.y, 1};
} float3 klm = seg->implicitMatrix * ph;
/*/ side = (klm.x*klm.x*klm.x - klm.y*klm.z < 0)? -1 : 1;
float3 ph = {p.x, p.y, 1}; } break;
float3 klm = seg->implicitMatrix * ph;
if(klm.x*klm.x*klm.x - klm.y*klm.z < 0)
{
isLeft = true;
}
//*/
} }
} }
} }
return(isLeft); return(side);
} }
typedef struct mtl_segment_setup_context typedef struct mtl_segment_setup_context
@ -275,30 +301,76 @@ void mtl_segment_bin_to_tiles(thread mtl_segment_setup_context* context, device
float(x + pathArea.x + 1), float(x + pathArea.x + 1),
float(y + pathArea.y + 1)} * float(tileSize); float(y + pathArea.y + 1)} * float(tileSize);
//NOTE: select two corners of tile box to test against the curve float2 bl = {tileBox.x, tileBox.y};
float2 testPoint0; float2 br = {tileBox.z, tileBox.y};
float2 testPoint1; float2 tr = {tileBox.z, tileBox.w};
if(seg->config == MG_MTL_BL || seg->config == MG_MTL_TR) float2 tl = {tileBox.x, tileBox.w};
int sbl = mtl_side_of_segment(bl, seg, context->log);
int sbr = mtl_side_of_segment(br, seg, context->log);
int str = mtl_side_of_segment(tr, seg, context->log);
int stl = mtl_side_of_segment(tl, seg, context->log);
bool crossL = (stl*sbl < 0);
bool crossR = (str*sbr < 0);
bool crossT = (stl*str < 0);
bool crossB = (sbl*sbr < 0);
mtl_log(context->log, "tile ");
mtl_log_i32(context->log, x);
mtl_log(context->log, ", ");
mtl_log_i32(context->log, y);
mtl_log(context->log, "\n");
mtl_log(context->log, "crossL ");
mtl_log_i32(context->log, crossL);
mtl_log(context->log, "\n");
mtl_log(context->log, "crossR ");
mtl_log_i32(context->log, crossR);
mtl_log(context->log, "\n");
mtl_log(context->log, "crossT ");
mtl_log_i32(context->log, crossT);
mtl_log(context->log, "\n");
mtl_log(context->log, "crossB ");
mtl_log_i32(context->log, crossB);
mtl_log(context->log, "\n");
float2 s0, s1;
if(seg->config == MG_MTL_TL||seg->config == MG_MTL_BR)
{ {
testPoint0 = (float2){tileBox.x, tileBox.y}, s0 = seg->box.xy;
testPoint1 = (float2){tileBox.z, tileBox.w}; s1 = seg->box.zw;
} }
else else
{ {
testPoint0 = (float2){tileBox.z, tileBox.y}; s0 = seg->box.xw;
testPoint1 = (float2){tileBox.x, tileBox.w}; s1 = seg->box.zy;
} }
bool test0 = mtl_is_left_of_segment(testPoint0, seg); bool s0Inside = s0.x >= tileBox.x
bool test1 = mtl_is_left_of_segment(testPoint1, seg); && s0.x < tileBox.z
&& s0.y >= tileBox.y
&& s0.y < tileBox.w;
//NOTE: the curve overlaps the tile only if test points are on opposite sides of segment bool s1Inside = s1.x >= tileBox.x
if(test0 != test1) && s1.x < tileBox.z
&& s1.y >= tileBox.y
&& s1.y < tileBox.w;
mtl_log(context->log, "s0Inside ");
mtl_log_i32(context->log, s0Inside ? 1 : 0);
mtl_log(context->log, "\n");
mtl_log(context->log, "s1Inside ");
mtl_log_i32(context->log, s1Inside ? 1 : 0);
mtl_log(context->log, "\n");
if(crossL || crossR || crossT || crossB || s0Inside || s1Inside)
{ {
int tileOpIndex = atomic_fetch_add_explicit(context->tileOpCount, 1, memory_order_relaxed); int tileOpIndex = atomic_fetch_add_explicit(context->tileOpCount, 1, memory_order_relaxed);
device mg_mtl_tile_op* op = &context->tileOpBuffer[tileOpIndex]; device mg_mtl_tile_op* op = &context->tileOpBuffer[tileOpIndex];
op->kind = MG_MTL_OP_SEGMENT; op->kind = MG_MTL_OP_SEGMENT;
op->index = segIndex; op->index = segIndex;
op->crossRight = false;
op->next = -1; op->next = -1;
int tileIndex = y*pathArea.z + x; int tileIndex = y*pathArea.z + x;
@ -310,43 +382,16 @@ void mtl_segment_bin_to_tiles(thread mtl_segment_setup_context* context, device
} }
//NOTE: if the segment crosses the tile's bottom boundary, update the tile's winding offset //NOTE: if the segment crosses the tile's bottom boundary, update the tile's winding offset
// testPoint0 is always a bottom point. We select the other one and check if they are on if(crossB)
// opposite sides of the curve.
// We also need to check that the endpoints of the curve are on opposite sides of the bottom
// boundary.
float2 testPoint3;
if(seg->config == MG_MTL_BL || seg->config == MG_MTL_TR)
{
testPoint3 = (float2){tileBox.z, tileBox.y};
}
else
{
testPoint3 = (float2){tileBox.x, tileBox.y};
}
bool test3 = mtl_is_left_of_segment(testPoint3, seg);
if( test0 != test3
&& seg->box.y < testPoint0.y
&& seg->box.w > testPoint0.y)
{ {
atomic_fetch_add_explicit(&tile->windingOffset, seg->windingIncrement, memory_order_relaxed); atomic_fetch_add_explicit(&tile->windingOffset, seg->windingIncrement, memory_order_relaxed);
} }
//NOTE: if the segment crosses the right boundary, mark it. We reuse one of the previous tests //NOTE: if the segment crosses the right boundary, mark it. We reuse one of the previous tests
float2 top = {tileBox.z, tileBox.w}; if(crossR)
bool testTop = mtl_is_left_of_segment(top, seg);
bool testBottom = (seg->config == MG_MTL_BL || seg->config == MG_MTL_TR)? test3 : test0;
if(testTop != testBottom
&& seg->box.x <= top.x
&& seg->box.z > top.x)
{ {
op->crossRight = true; op->crossRight = true;
} }
else
{
op->crossRight = false;
}
} }
} }
} }
@ -387,9 +432,12 @@ device mg_mtl_segment* mtl_segment_push(thread mtl_segment_setup_context* contex
int segIndex = atomic_fetch_add_explicit(context->segmentCount, 1, memory_order_relaxed); int segIndex = atomic_fetch_add_explicit(context->segmentCount, 1, memory_order_relaxed);
device mg_mtl_segment* seg = &context->segmentBuffer[segIndex]; device mg_mtl_segment* seg = &context->segmentBuffer[segIndex];
bool goingUp = e.y >= s.y;
bool goingRight = e.x >= s.x;
seg->kind = kind; seg->kind = kind;
seg->pathIndex = context->pathIndex; seg->pathIndex = context->pathIndex;
seg->windingIncrement = (e.y > s.y)? 1 : -1; seg->windingIncrement = goingUp? 1 : -1;
seg->box = (vector_float4){min(s.x, e.x), seg->box = (vector_float4){min(s.x, e.x),
min(s.y, e.y), min(s.y, e.y),
@ -401,25 +449,13 @@ device mg_mtl_segment* mtl_segment_push(thread mtl_segment_setup_context* contex
float alpha = (seg->box.w - seg->box.y)/(seg->box.z - seg->box.x); float alpha = (seg->box.w - seg->box.y)/(seg->box.z - seg->box.x);
float ofs = seg->box.w - seg->box.y; float ofs = seg->box.w - seg->box.y;
//TODO: check that it works for line segments! if(goingUp == goingRight)
if( (e.x > s.x && e.y < s.y)
||(e.x <= s.x && e.y > s.y))
{ {
if(dy < ofs - alpha*dx) if(seg->kind == MG_MTL_LINE)
{ {
seg->config = MG_MTL_BL; seg->config = MG_MTL_BR;
} }
else else if(dy > alpha*dx)
{
seg->config = MG_MTL_TR;
}
}
else if( (e.x > s.x && e.y >= s.y)
||(e.x <= s.x && e.y <= s.y))
{
//NOTE: it is important to include horizontal segments here, so that the mtl_is_left_of_segment() test
// becomes x > seg->box.x, in order to correctly detect right-crossing horizontal segments
if(dy > alpha*dx)
{ {
seg->config = MG_MTL_TL; seg->config = MG_MTL_TL;
} }
@ -428,6 +464,21 @@ device mg_mtl_segment* mtl_segment_push(thread mtl_segment_setup_context* contex
seg->config = MG_MTL_BR; seg->config = MG_MTL_BR;
} }
} }
else
{
if(seg->kind == MG_MTL_LINE)
{
seg->config = MG_MTL_TR;
}
else if(dy < ofs - alpha*dx)
{
seg->config = MG_MTL_BL;
}
else
{
seg->config = MG_MTL_TR;
}
}
return(seg); return(seg);
} }
@ -545,27 +596,6 @@ int mtl_quadratic_roots(float a, float b, float c, thread float* r)
return(count); return(count);
} }
void log_cubic_bezier(thread float2* p, mtl_log_context logCtx)
{
mtl_log(logCtx, "(");
mtl_log_f32(logCtx, p[0].x);
mtl_log(logCtx, ", ");
mtl_log_f32(logCtx, p[0].y);
mtl_log(logCtx, ") (");
mtl_log_f32(logCtx, p[1].x);
mtl_log(logCtx, ", ");
mtl_log_f32(logCtx, p[1].y);
mtl_log(logCtx, ") (");
mtl_log_f32(logCtx, p[2].x);
mtl_log(logCtx, ", ");
mtl_log_f32(logCtx, p[2].y);
mtl_log(logCtx, ") (");
mtl_log_f32(logCtx, p[3].x);
mtl_log(logCtx, ", ");
mtl_log_f32(logCtx, p[3].y);
mtl_log(logCtx, ")\n");
}
void mtl_cubic_slice(float2 p[4], float s0, float s1, float2 sp[4]) void mtl_cubic_slice(float2 p[4], float s0, float s1, float2 sp[4])
{ {
float sr = (s1 - s0)/(1-s0); float sr = (s1 - s0)/(1-s0);
@ -665,7 +695,7 @@ typedef struct mtl_cubic_info
} mtl_cubic_info; } mtl_cubic_info;
mtl_cubic_info mtl_cubic_classify(thread float2* p) mtl_cubic_info mtl_cubic_classify(thread float2* p, mtl_log_context log = {.enabled = false})
{ {
mtl_cubic_info result = {MTL_CUBIC_ERROR}; mtl_cubic_info result = {MTL_CUBIC_ERROR};
matrix_float4x4 F; matrix_float4x4 F;
@ -787,7 +817,7 @@ mtl_cubic_info mtl_cubic_classify(thread float2* p)
F = (matrix_float4x4){{td*te, -se*td-sd*te, sd*se, 0}, F = (matrix_float4x4){{td*te, -se*td-sd*te, sd*se, 0},
{square(td)*te, -se*square(td)-2*sd*te*td, te*square(sd)+2*se*td*sd, -square(sd)*se}, {square(td)*te, -se*square(td)-2*sd*te*td, te*square(sd)+2*se*td*sd, -square(sd)*se},
{td*square(te), -sd*square(td)-2*se*td*te, td*square(se)+2*sd*te*se, -sd*square(se)}, {td*square(te), -sd*square(te)-2*se*td*te, td*square(se)+2*sd*te*se, -sd*square(se)},
{1, 0, 0, 0}}; {1, 0, 0, 0}};
//NOTE: if necessary, flip sign of k and l to ensure the interior is west from the curve //NOTE: if necessary, flip sign of k and l to ensure the interior is west from the curve
@ -885,6 +915,7 @@ void mtl_cubic_emit(thread mtl_segment_setup_context* context, float2 p[4], mtl_
//NOTE: set implicit matrix and bin segment //NOTE: set implicit matrix and bin segment
seg->implicitMatrix = K*B; seg->implicitMatrix = K*B;
mtl_segment_bin_to_tiles(context, seg); mtl_segment_bin_to_tiles(context, seg);
} }
@ -899,7 +930,7 @@ void mtl_cubic_setup(thread mtl_segment_setup_context* context, float2 p[4])
float2 sp[4]; float2 sp[4];
mtl_cubic_slice(p, splits[sliceIndex], splits[sliceIndex+1], sp); mtl_cubic_slice(p, splits[sliceIndex], splits[sliceIndex+1], sp);
mtl_cubic_info curve = mtl_cubic_classify(sp); mtl_cubic_info curve = mtl_cubic_classify(sp, context->log);
switch(curve.kind) switch(curve.kind)
{ {
case MTL_CUBIC_ERROR: case MTL_CUBIC_ERROR:
@ -908,13 +939,13 @@ void mtl_cubic_setup(thread mtl_segment_setup_context* context, float2 p[4])
case MTL_CUBIC_DEGENERATE_LINE: case MTL_CUBIC_DEGENERATE_LINE:
{ {
float2 l[2] = {p[0], p[1]}; float2 l[2] = {sp[0], sp[1]};
mtl_line_setup(context, l); mtl_line_setup(context, l);
} break; } break;
case MTL_CUBIC_DEGENERATE_QUADRATIC: case MTL_CUBIC_DEGENERATE_QUADRATIC:
{ {
float2 q[3] = {p[0], curve.quadPoint, p[3]}; float2 q[3] = {sp[0], curve.quadPoint, sp[3]};
mtl_quadratic_setup(context, q); mtl_quadratic_setup(context, q);
} break; } break;
@ -945,10 +976,6 @@ void mtl_cubic_setup(thread mtl_segment_setup_context* context, float2 p[4])
case MTL_CUBIC_CUSP: case MTL_CUBIC_CUSP:
case MTL_CUBIC_SERPENTINE: case MTL_CUBIC_SERPENTINE:
{ {
if(sliceIndex == 2)
{
log_cubic_bezier(sp, context->log);
}
mtl_cubic_emit(context, sp, curve); mtl_cubic_emit(context, sp, curve);
} break; } break;
} }
@ -973,6 +1000,7 @@ kernel void mtl_segment_setup(constant int* elementCount [[buffer(0)]],
const device mg_mtl_path_queue* pathQueue = &pathQueueBuffer[elt->pathIndex]; const device mg_mtl_path_queue* pathQueue = &pathQueueBuffer[elt->pathIndex];
device mg_mtl_tile_queue* tileQueues = &tileQueueBuffer[pathQueue->tileQueues]; device mg_mtl_tile_queue* tileQueues = &tileQueueBuffer[pathQueue->tileQueues];
mtl_segment_setup_context setupCtx = {.pathIndex = elt->pathIndex, mtl_segment_setup_context setupCtx = {.pathIndex = elt->pathIndex,
.segmentCount = segmentCount, .segmentCount = segmentCount,
.segmentBuffer = segmentBuffer, .segmentBuffer = segmentBuffer,
@ -982,7 +1010,8 @@ kernel void mtl_segment_setup(constant int* elementCount [[buffer(0)]],
.tileOpCount = tileOpCount, .tileOpCount = tileOpCount,
.tileSize = tileSize[0], .tileSize = tileSize[0],
.log.buffer = logBuffer, .log.buffer = logBuffer,
.log.offset = logOffsetBuffer}; .log.offset = logOffsetBuffer,
.log.enabled = (eltIndex == 1)};
switch(elt->kind) switch(elt->kind)
{ {
@ -1009,9 +1038,13 @@ kernel void mtl_segment_setup(constant int* elementCount [[buffer(0)]],
kernel void mtl_backprop(const device mg_mtl_path_queue* pathQueueBuffer [[buffer(0)]], kernel void mtl_backprop(const device mg_mtl_path_queue* pathQueueBuffer [[buffer(0)]],
device mg_mtl_tile_queue* tileQueueBuffer [[buffer(1)]], device mg_mtl_tile_queue* tileQueueBuffer [[buffer(1)]],
device char* logBuffer [[buffer(2)]],
device atomic_int* logOffsetBuffer [[buffer(3)]],
uint pathIndex [[threadgroup_position_in_grid]], uint pathIndex [[threadgroup_position_in_grid]],
uint localID [[thread_position_in_threadgroup]]) uint localID [[thread_position_in_threadgroup]])
{ {
// mtl_log_context log = {.buffer = logBuffer, .offset = logOffsetBuffer, .enabled = false};
threadgroup atomic_int nextRowIndex; threadgroup atomic_int nextRowIndex;
if(localID == 0) if(localID == 0)
{ {
@ -1048,6 +1081,8 @@ kernel void mtl_merge(constant int* pathCount [[buffer(0)]],
device mg_mtl_tile_op* tileOpBuffer [[buffer(4)]], device mg_mtl_tile_op* tileOpBuffer [[buffer(4)]],
device atomic_int* tileOpCount [[buffer(5)]], device atomic_int* tileOpCount [[buffer(5)]],
device int* screenTilesBuffer [[buffer(6)]], device int* screenTilesBuffer [[buffer(6)]],
device char* logBuffer [[buffer(7)]],
device atomic_int* logOffsetBuffer [[buffer(8)]],
uint2 threadCoord [[thread_position_in_grid]], uint2 threadCoord [[thread_position_in_grid]],
uint2 gridSize [[threads_per_grid]]) uint2 gridSize [[threads_per_grid]])
{ {
@ -1056,6 +1091,11 @@ kernel void mtl_merge(constant int* pathCount [[buffer(0)]],
device int* nextLink = &screenTilesBuffer[tileIndex]; device int* nextLink = &screenTilesBuffer[tileIndex];
*nextLink = -1; *nextLink = -1;
/*
mtl_log_context log = {.buffer = logBuffer,
.offset = logOffsetBuffer,
.enabled = true};
*/
for(int pathIndex = 0; pathIndex < pathCount[0]; pathIndex++) for(int pathIndex = 0; pathIndex < pathCount[0]; pathIndex++)
{ {
const device mg_mtl_path_queue* pathQueue = &pathQueueBuffer[pathIndex]; const device mg_mtl_path_queue* pathQueue = &pathQueueBuffer[pathIndex];
@ -1134,13 +1174,6 @@ kernel void mtl_raster(const device int* screenTilesBuffer [[buffer(0)]],
int nTilesX = (int(gridSize.x) + tileSize[0] - 1)/tileSize[0]; int nTilesX = (int(gridSize.x) + tileSize[0] - 1)/tileSize[0];
int tileIndex = tileCoord.y * nTilesX + tileCoord.x; int tileIndex = tileCoord.y * nTilesX + tileCoord.x;
if( (pixelCoord.x % tileSize[0] == 0)
||(pixelCoord.y % tileSize[0] == 0))
{
outTexture.write(float4(0, 0, 0, 1), uint2(pixelCoord));
return;
}
float4 color = float4(0, 0, 0, 0); float4 color = float4(0, 0, 0, 0);
int pathIndex = 0; int pathIndex = 0;
int winding = 0; int winding = 0;
@ -1152,12 +1185,15 @@ kernel void mtl_raster(const device int* screenTilesBuffer [[buffer(0)]],
if(op->kind == MG_MTL_OP_START) if(op->kind == MG_MTL_OP_START)
{ {
if(winding & 1) bool filled = (pathBuffer[pathIndex].cmd == MG_MTL_FILL && (winding & 1))
||(pathBuffer[pathIndex].cmd == MG_MTL_STROKE && (winding != 0));
if(filled)
{ {
float4 pathColor = pathBuffer[pathIndex].color; float4 pathColor = pathBuffer[pathIndex].color;
pathColor.rgb *= pathColor.a; pathColor.rgb *= pathColor.a;
color = color*(1-pathColor.a) + pathColor; color = color*(1-pathColor.a) + pathColor;
} }
pathIndex = op->index; pathIndex = op->index;
winding = op->windingOffset; winding = op->windingOffset;
} }
@ -1165,23 +1201,22 @@ kernel void mtl_raster(const device int* screenTilesBuffer [[buffer(0)]],
{ {
const device mg_mtl_segment* seg = &segmentBuffer[op->index]; const device mg_mtl_segment* seg = &segmentBuffer[op->index];
if(pixelCoord.y >= seg->box.y && pixelCoord.y < seg->box.w) if( (pixelCoord.y > seg->box.y)
&&(pixelCoord.y <= seg->box.w)
&&(mtl_side_of_segment(float2(pixelCoord), seg) < 0))
{ {
if(mtl_is_left_of_segment(float2(pixelCoord), seg)) winding += seg->windingIncrement;
{
winding += seg->windingIncrement;
}
} }
if(op->crossRight) if(op->crossRight)
{ {
if( (seg->config == MG_MTL_BR || seg->config == MG_MTL_TL) if( (seg->config == MG_MTL_BR || seg->config == MG_MTL_TL)
&&(pixelCoord.y >= seg->box.w)) &&(pixelCoord.y > seg->box.w))
{ {
winding += seg->windingIncrement; winding += seg->windingIncrement;
} }
else if( (seg->config == MG_MTL_BL || seg->config == MG_MTL_TR) else if( (seg->config == MG_MTL_BL || seg->config == MG_MTL_TR)
&&(pixelCoord.y >= seg->box.y)) &&(pixelCoord.y > seg->box.y))
{ {
winding -= seg->windingIncrement; winding -= seg->windingIncrement;
} }
@ -1189,13 +1224,23 @@ kernel void mtl_raster(const device int* screenTilesBuffer [[buffer(0)]],
} }
opIndex = op->next; opIndex = op->next;
} }
if(winding & 1)
bool filled = (pathBuffer[pathIndex].cmd == MG_MTL_FILL && (winding & 1))
||(pathBuffer[pathIndex].cmd == MG_MTL_STROKE && (winding != 0));
if(filled)
{ {
float4 pathColor = pathBuffer[pathIndex].color; float4 pathColor = pathBuffer[pathIndex].color;
pathColor.rgb *= pathColor.a; pathColor.rgb *= pathColor.a;
color = color*(1-pathColor.a) + pathColor; color = color*(1-pathColor.a) + pathColor;
} }
if( (pixelCoord.x % tileSize[0] == 0)
||(pixelCoord.y % tileSize[0] == 0))
{
outTexture.write(float4(0, 0, 0, 1), uint2(pixelCoord));
return;
}
outTexture.write(color, uint2(pixelCoord)); outTexture.write(color, uint2(pixelCoord));
} }