[mtl canvas, wip] Collate queues in a per-tile op list and prune occluded shapes
This commit is contained in:
parent
a6c53649bd
commit
8af4e4eddc
|
@ -18,7 +18,8 @@
|
|||
|
||||
#define LOG_SUBSYSTEM "Graphics"
|
||||
|
||||
const int MG_MTL_INPUT_BUFFERS_COUNT = 3;
|
||||
const int MG_MTL_INPUT_BUFFERS_COUNT = 3,
|
||||
MG_MTL_TILE_SIZE = 16;
|
||||
|
||||
typedef struct mg_mtl_canvas_backend
|
||||
{
|
||||
|
@ -28,6 +29,7 @@ typedef struct mg_mtl_canvas_backend
|
|||
id<MTLComputePipelineState> pathPipeline;
|
||||
id<MTLComputePipelineState> segmentPipeline;
|
||||
id<MTLComputePipelineState> backpropPipeline;
|
||||
id<MTLComputePipelineState> mergePipeline;
|
||||
id<MTLComputePipelineState> rasterPipeline;
|
||||
id<MTLRenderPipelineState> blitPipeline;
|
||||
|
||||
|
@ -46,6 +48,7 @@ typedef struct mg_mtl_canvas_backend
|
|||
id<MTLBuffer> tileQueueCountBuffer;
|
||||
id<MTLBuffer> tileOpBuffer;
|
||||
id<MTLBuffer> tileOpCountBuffer;
|
||||
id<MTLBuffer> screenTilesBuffer;
|
||||
|
||||
} mg_mtl_canvas_backend;
|
||||
|
||||
|
@ -140,7 +143,13 @@ void mg_mtl_canvas_render(mg_canvas_backend* interface,
|
|||
mp_rect frame = mg_surface_get_frame(backend->surface);
|
||||
f32 scale = surface->mtlLayer.contentsScale;
|
||||
vec2 viewportSize = {frame.w * scale, frame.h * scale};
|
||||
int tileSize = 16;
|
||||
int tileSize = MG_MTL_TILE_SIZE;
|
||||
int nTilesX = (int)(frame.w * scale + tileSize - 1)/tileSize;
|
||||
int nTilesY = (int)(frame.h * scale + tileSize - 1)/tileSize;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//TODO: ensure screen tiles buffer is correct size
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//NOTE: encode GPU commands
|
||||
@autoreleasepool
|
||||
|
@ -208,19 +217,35 @@ void mg_mtl_canvas_render(mg_canvas_backend* interface,
|
|||
[backpropEncoder dispatchThreads: backpropGridSize threadsPerThreadgroup: backpropGroupSize];
|
||||
[backpropEncoder endEncoding];
|
||||
|
||||
//NOTE: merge pass
|
||||
id<MTLComputeCommandEncoder> mergeEncoder = [surface->commandBuffer computeCommandEncoder];
|
||||
mergeEncoder.label = @"merge pass";
|
||||
[mergeEncoder setComputePipelineState: backend->mergePipeline];
|
||||
|
||||
[mergeEncoder setBytes:&pathCount length:sizeof(int) atIndex:0];
|
||||
[mergeEncoder setBuffer:backend->pathBuffer[backend->bufferIndex] offset:0 atIndex:1];
|
||||
[mergeEncoder setBuffer:backend->pathQueueBuffer offset:0 atIndex:2];
|
||||
[mergeEncoder setBuffer:backend->tileQueueBuffer offset:0 atIndex:3];
|
||||
[mergeEncoder setBuffer:backend->tileOpBuffer offset:0 atIndex:4];
|
||||
[mergeEncoder setBuffer:backend->tileOpCountBuffer offset:0 atIndex:5];
|
||||
[mergeEncoder setBuffer:backend->screenTilesBuffer offset:0 atIndex:6];
|
||||
|
||||
MTLSize mergeGridSize = MTLSizeMake(nTilesX, nTilesY, 1);
|
||||
MTLSize mergeGroupSize = MTLSizeMake(16, 16, 1);
|
||||
|
||||
[mergeEncoder dispatchThreads: mergeGridSize threadsPerThreadgroup: mergeGroupSize];
|
||||
[mergeEncoder endEncoding];
|
||||
|
||||
//NOTE: raster pass
|
||||
id<MTLComputeCommandEncoder> rasterEncoder = [surface->commandBuffer computeCommandEncoder];
|
||||
rasterEncoder.label = @"raster pass";
|
||||
[rasterEncoder setComputePipelineState: backend->rasterPipeline];
|
||||
|
||||
[rasterEncoder setBytes:&pathCount length:sizeof(int) atIndex:0];
|
||||
[rasterEncoder setBuffer:backend->pathBuffer[backend->bufferIndex] offset:0 atIndex:1];
|
||||
[rasterEncoder setBuffer:backend->segmentCountBuffer offset:0 atIndex:2];
|
||||
[rasterEncoder setBuffer:backend->screenTilesBuffer offset:0 atIndex:0];
|
||||
[rasterEncoder setBuffer:backend->tileOpBuffer offset:0 atIndex:1];
|
||||
[rasterEncoder setBuffer:backend->pathBuffer[backend->bufferIndex] offset:0 atIndex:2];
|
||||
[rasterEncoder setBuffer:backend->segmentBuffer offset:0 atIndex:3];
|
||||
[rasterEncoder setBuffer:backend->pathQueueBuffer offset:0 atIndex:4];
|
||||
[rasterEncoder setBuffer:backend->tileQueueBuffer offset:0 atIndex:5];
|
||||
[rasterEncoder setBuffer:backend->tileOpBuffer offset:0 atIndex:6];
|
||||
[rasterEncoder setBytes:&tileSize length:sizeof(int) atIndex:7];
|
||||
[rasterEncoder setBytes:&tileSize length:sizeof(int) atIndex:4];
|
||||
|
||||
[rasterEncoder setTexture:backend->outTexture atIndex:0];
|
||||
|
||||
|
@ -270,6 +295,7 @@ void mg_mtl_canvas_destroy(mg_canvas_backend* interface)
|
|||
[backend->pathPipeline release];
|
||||
[backend->segmentPipeline release];
|
||||
[backend->backpropPipeline release];
|
||||
[backend->mergePipeline release];
|
||||
[backend->rasterPipeline release];
|
||||
[backend->blitPipeline release];
|
||||
|
||||
|
@ -284,6 +310,7 @@ void mg_mtl_canvas_destroy(mg_canvas_backend* interface)
|
|||
[backend->tileQueueCountBuffer release];
|
||||
[backend->tileOpBuffer release];
|
||||
[backend->tileOpCountBuffer release];
|
||||
[backend->screenTilesBuffer release];
|
||||
}
|
||||
|
||||
free(backend);
|
||||
|
@ -329,6 +356,7 @@ mg_canvas_backend* mg_mtl_canvas_create(mg_surface surface)
|
|||
id<MTLFunction> pathFunction = [library newFunctionWithName:@"mtl_path_setup"];
|
||||
id<MTLFunction> segmentFunction = [library newFunctionWithName:@"mtl_segment_setup"];
|
||||
id<MTLFunction> backpropFunction = [library newFunctionWithName:@"mtl_backprop"];
|
||||
id<MTLFunction> mergeFunction = [library newFunctionWithName:@"mtl_merge"];
|
||||
id<MTLFunction> rasterFunction = [library newFunctionWithName:@"mtl_raster"];
|
||||
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"mtl_vertex_shader"];
|
||||
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"mtl_fragment_shader"];
|
||||
|
@ -345,6 +373,9 @@ mg_canvas_backend* mg_mtl_canvas_create(mg_surface surface)
|
|||
backend->backpropPipeline = [metalSurface->device newComputePipelineStateWithFunction: backpropFunction
|
||||
error:&error];
|
||||
|
||||
backend->mergePipeline = [metalSurface->device newComputePipelineStateWithFunction: mergeFunction
|
||||
error:&error];
|
||||
|
||||
backend->rasterPipeline = [metalSurface->device newComputePipelineStateWithFunction: rasterFunction
|
||||
error:&error];
|
||||
|
||||
|
@ -417,8 +448,13 @@ mg_canvas_backend* mg_mtl_canvas_create(mg_surface surface)
|
|||
|
||||
backend->tileOpCountBuffer = [metalSurface->device newBufferWithLength: sizeof(int)
|
||||
options: bufferOptions];
|
||||
}
|
||||
|
||||
int tileSize = MG_MTL_TILE_SIZE;
|
||||
int nTilesX = (int)(frame.w * scale + tileSize - 1)/tileSize;
|
||||
int nTilesY = (int)(frame.h * scale + tileSize - 1)/tileSize;
|
||||
backend->screenTilesBuffer = [metalSurface->device newBufferWithLength: nTilesX*nTilesY*sizeof(int)
|
||||
options: bufferOptions];
|
||||
}
|
||||
}
|
||||
return((mg_canvas_backend*)backend);
|
||||
}
|
||||
|
|
|
@ -166,6 +166,7 @@ kernel void mtl_segment_setup(constant int* elementCount [[buffer(0)]],
|
|||
|
||||
op->kind = MG_MTL_OP_SEGMENT;
|
||||
op->index = segIndex;
|
||||
op->next = -1;
|
||||
|
||||
int tileIndex = y*pathQueue->area.z + x;
|
||||
device mg_mtl_tile_queue* tile = &tileQueues[tileIndex];
|
||||
|
@ -252,7 +253,7 @@ kernel void mtl_backprop(const device mg_mtl_path_queue* pathQueueBuffer [[buffe
|
|||
rowIndex = atomic_fetch_add_explicit(&nextRowIndex, 1, memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
kernel void mtl_merge(constant int* pathCount [[buffer(0)]],
|
||||
const device mg_mtl_path* pathBuffer [[buffer(1)]],
|
||||
const device mg_mtl_path_queue* pathQueueBuffer [[buffer(2)]],
|
||||
|
@ -266,6 +267,7 @@ kernel void mtl_merge(constant int* pathCount [[buffer(0)]],
|
|||
int2 tileCoord = int2(threadCoord);
|
||||
int tileIndex = tileCoord.y * gridSize.x + tileCoord.x;
|
||||
device int* nextLink = &screenTilesBuffer[tileIndex];
|
||||
*nextLink = -1;
|
||||
|
||||
for(int pathIndex = 0; pathIndex < pathCount[0]; pathIndex++)
|
||||
{
|
||||
|
@ -281,63 +283,69 @@ kernel void mtl_merge(constant int* pathCount [[buffer(0)]],
|
|||
const device mg_mtl_tile_queue* tileQueue = &tileQueueBuffer[pathQueue->tileQueues + pathTileIndex];
|
||||
|
||||
int windingOffset = atomic_load_explicit(&tileQueue->windingOffset, memory_order_relaxed);
|
||||
int opIndex = atomic_load_explicit(&tileQueue->first, memory_order_relaxed);
|
||||
int firstOpIndex = atomic_load_explicit(&tileQueue->first, memory_order_relaxed);
|
||||
|
||||
if((opIndex != -1) || (windingOffset & 1))
|
||||
if(firstOpIndex == -1)
|
||||
{
|
||||
//NOTE: add path start op (with winding offset)
|
||||
int startOpIndex = atomic_fetch_add_explicit(tileOpCount, 1, memory_order_relaxed);
|
||||
device mg_mtl_tile_op* startOp = &tileOpBuffer[startOpIndex];
|
||||
startOp->kind = MG_MTL_OP_START;
|
||||
startOp->index = pathIndex;
|
||||
startOp->windingOffset = windingOffset;
|
||||
|
||||
if(opIndex == -1)
|
||||
if(windingOffset & 1)
|
||||
{
|
||||
//NOTE: the tile is fully covered by path fill. Insert start op,
|
||||
// and if the fill color is opaque, trim tile list.
|
||||
//NOTE: tile is full covered. Add path start op (with winding offset).
|
||||
// Additionally if color is opaque, trim tile list.
|
||||
int pathOpIndex = atomic_fetch_add_explicit(tileOpCount, 1, memory_order_relaxed);
|
||||
device mg_mtl_tile_op* pathOp = &tileOpBuffer[pathOpIndex];
|
||||
pathOp->kind = MG_MTL_OP_START;
|
||||
pathOp->next = -1;
|
||||
pathOp->index = pathIndex;
|
||||
pathOp->windingOffset = windingOffset;
|
||||
|
||||
if(pathBuffer[pathIndex].color.a == 1)
|
||||
{
|
||||
screenTilesBuffer[tileIndex] = startOpIndex;
|
||||
screenTilesBuffer[tileIndex] = pathOpIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
*nextLink = startOpIndex;
|
||||
*nextLink = pathOpIndex;
|
||||
}
|
||||
nextLink = &startOp->next;
|
||||
nextLink = &pathOp->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
//NOTE: add start op
|
||||
*nextLink = startOpIndex;
|
||||
nextLink = &startOp->next;
|
||||
// else, tile is fully uncovered, skip path
|
||||
}
|
||||
else
|
||||
{
|
||||
//NOTE: add path start op (with winding offset)
|
||||
int pathOpIndex = atomic_fetch_add_explicit(tileOpCount, 1, memory_order_relaxed);
|
||||
device mg_mtl_tile_op* pathOp = &tileOpBuffer[pathOpIndex];
|
||||
pathOp->kind = MG_MTL_OP_START;
|
||||
pathOp->next = -1;
|
||||
pathOp->index = pathIndex;
|
||||
pathOp->windingOffset = windingOffset;
|
||||
|
||||
//NOTE: chain path ops to end of tile list
|
||||
device mg_mtl_tile_op* lastOp = &tileOpBuffer[opIndex];
|
||||
*nextLink = opIndex;
|
||||
nextLink = &lastOp->next;
|
||||
}
|
||||
*nextLink = pathOpIndex;
|
||||
nextLink = &pathOp->next;
|
||||
|
||||
//NOTE: chain remaining path ops to end of tile list
|
||||
int lastOpIndex = tileQueue->last;
|
||||
device mg_mtl_tile_op* lastOp = &tileOpBuffer[lastOpIndex];
|
||||
*nextLink = firstOpIndex;
|
||||
nextLink = &lastOp->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
kernel void mtl_raster(constant int* pathCount [[buffer(0)]],
|
||||
const device mg_mtl_path* pathBuffer [[buffer(1)]],
|
||||
constant int* segCount [[buffer(2)]],
|
||||
|
||||
kernel void mtl_raster(const device int* screenTilesBuffer [[buffer(0)]],
|
||||
const device mg_mtl_tile_op* tileOpBuffer [[buffer(1)]],
|
||||
const device mg_mtl_path* pathBuffer [[buffer(2)]],
|
||||
const device mg_mtl_segment* segmentBuffer [[buffer(3)]],
|
||||
const device mg_mtl_path_queue* pathQueueBuffer [[buffer(4)]],
|
||||
const device mg_mtl_tile_queue* tileQueueBuffer [[buffer(5)]],
|
||||
const device mg_mtl_tile_op* tileOpBuffer [[buffer(6)]],
|
||||
constant int* tileSize [[buffer(7)]],
|
||||
constant int* tileSize [[buffer(4)]],
|
||||
texture2d<float, access::write> outTexture [[texture(0)]],
|
||||
uint2 threadCoord [[thread_position_in_grid]],
|
||||
uint2 gridSize [[threads_per_grid]])
|
||||
{
|
||||
int2 pixelCoord = int2(threadCoord);
|
||||
int2 tileCoord = pixelCoord / tileSize[0];
|
||||
|
||||
float4 color = float4(0, 0, 0, 0);
|
||||
int nTilesX = (int(gridSize.x) + tileSize[0] - 1)/tileSize[0];
|
||||
int tileIndex = tileCoord.y * nTilesX + tileCoord.x;
|
||||
|
||||
if( (pixelCoord.x % tileSize[0] == 0)
|
||||
||(pixelCoord.y % tileSize[0] == 0))
|
||||
|
@ -346,83 +354,80 @@ kernel void mtl_raster(constant int* pathCount [[buffer(0)]],
|
|||
return;
|
||||
}
|
||||
|
||||
for(int pathIndex = 0; pathIndex < pathCount[0]; pathIndex++)
|
||||
float4 color = float4(0, 0, 0, 0);
|
||||
int pathIndex = 0;
|
||||
int winding = 0;
|
||||
int opIndex = screenTilesBuffer[tileIndex];
|
||||
|
||||
while(opIndex != -1)
|
||||
{
|
||||
const device mg_mtl_path_queue* pathQueue = &pathQueueBuffer[pathIndex];
|
||||
int2 pathTileCoord = tileCoord - pathQueue->area.xy;
|
||||
const device mg_mtl_tile_op* op = &tileOpBuffer[opIndex];
|
||||
|
||||
if( pathTileCoord.x >= 0
|
||||
&& pathTileCoord.x < pathQueue->area.z
|
||||
&& pathTileCoord.y >= 0
|
||||
&& pathTileCoord.y < pathQueue->area.w)
|
||||
if(op->kind == MG_MTL_OP_START)
|
||||
{
|
||||
int pathTileIndex = pathTileCoord.y * pathQueue->area.z + pathTileCoord.x;
|
||||
const device mg_mtl_tile_queue* tileQueue = &tileQueueBuffer[pathQueue->tileQueues + pathTileIndex];
|
||||
|
||||
int winding = atomic_load_explicit(&tileQueue->windingOffset, memory_order_relaxed);
|
||||
|
||||
int opIndex = atomic_load_explicit(&tileQueue->first, memory_order_relaxed);
|
||||
while(opIndex != -1)
|
||||
{
|
||||
const device mg_mtl_tile_op* op = &tileOpBuffer[opIndex];
|
||||
|
||||
if(op->kind == MG_MTL_OP_SEGMENT)
|
||||
{
|
||||
const device mg_mtl_segment* seg = &segmentBuffer[op->index];
|
||||
|
||||
if(pixelCoord.y >= seg->box.y && pixelCoord.y < seg->box.w)
|
||||
{
|
||||
if(pixelCoord.x < seg->box.x)
|
||||
{
|
||||
winding += seg->windingIncrement;
|
||||
}
|
||||
else if(pixelCoord.x < seg->box.z)
|
||||
{
|
||||
/*TODO: if pixel is on opposite size of diagonal as curve on the right, increment
|
||||
otherwise if not on same size of diagonal as curve, do implicit test
|
||||
*/
|
||||
float alpha = (seg->box.w - seg->box.y)/(seg->box.z - seg->box.x);
|
||||
float ofs = seg->box.w - seg->box.y;
|
||||
float dx = pixelCoord.x - seg->box.x;
|
||||
float dy = pixelCoord.y - seg->box.y;
|
||||
|
||||
if( (seg->config == MG_MTL_BR && dy > alpha*dx)
|
||||
||(seg->config == MG_MTL_TR && dy < ofs - alpha*dx))
|
||||
{
|
||||
winding += seg->windingIncrement;
|
||||
}
|
||||
else if( !(seg->config == MG_MTL_TL && dy < alpha*dx)
|
||||
&& !(seg->config == MG_MTL_BL && dy > ofs - alpha*dx))
|
||||
{
|
||||
//Need implicit test, but for lines, we only have config BR or TR, so the test is always negative for now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(op->crossRight)
|
||||
{
|
||||
if( (seg->config == MG_MTL_BR || seg->config == MG_MTL_TL)
|
||||
&&(pixelCoord.y >= seg->box.w))
|
||||
{
|
||||
winding += seg->windingIncrement;
|
||||
}
|
||||
else if( (seg->config == MG_MTL_BL || seg->config == MG_MTL_TR)
|
||||
&&(pixelCoord.y >= seg->box.y))
|
||||
{
|
||||
winding -= seg->windingIncrement;
|
||||
}
|
||||
}
|
||||
}
|
||||
opIndex = op->next;
|
||||
}
|
||||
|
||||
if(winding & 1)
|
||||
{
|
||||
float4 pathColor = pathBuffer[pathIndex].color;
|
||||
pathColor.rgb *= pathColor.a;
|
||||
color = color*(1-pathColor.a) + pathColor;
|
||||
}
|
||||
pathIndex = op->index;
|
||||
winding = op->windingOffset;
|
||||
}
|
||||
else if(op->kind == MG_MTL_OP_SEGMENT)
|
||||
{
|
||||
const device mg_mtl_segment* seg = &segmentBuffer[op->index];
|
||||
|
||||
if(pixelCoord.y >= seg->box.y && pixelCoord.y < seg->box.w)
|
||||
{
|
||||
if(pixelCoord.x < seg->box.x)
|
||||
{
|
||||
winding += seg->windingIncrement;
|
||||
}
|
||||
else if(pixelCoord.x < seg->box.z)
|
||||
{
|
||||
/*TODO: if pixel is on opposite size of diagonal as curve on the right, increment
|
||||
otherwise if not on same size of diagonal as curve, do implicit test
|
||||
*/
|
||||
float alpha = (seg->box.w - seg->box.y)/(seg->box.z - seg->box.x);
|
||||
float ofs = seg->box.w - seg->box.y;
|
||||
float dx = pixelCoord.x - seg->box.x;
|
||||
float dy = pixelCoord.y - seg->box.y;
|
||||
|
||||
if( (seg->config == MG_MTL_BR && dy > alpha*dx)
|
||||
||(seg->config == MG_MTL_TR && dy < ofs - alpha*dx))
|
||||
{
|
||||
winding += seg->windingIncrement;
|
||||
}
|
||||
else if( !(seg->config == MG_MTL_TL && dy < alpha*dx)
|
||||
&& !(seg->config == MG_MTL_BL && dy > ofs - alpha*dx))
|
||||
{
|
||||
//Need implicit test, but for lines, we only have config BR or TR, so the test is always negative for now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(op->crossRight)
|
||||
{
|
||||
if( (seg->config == MG_MTL_BR || seg->config == MG_MTL_TL)
|
||||
&&(pixelCoord.y >= seg->box.w))
|
||||
{
|
||||
winding += seg->windingIncrement;
|
||||
}
|
||||
else if( (seg->config == MG_MTL_BL || seg->config == MG_MTL_TR)
|
||||
&&(pixelCoord.y >= seg->box.y))
|
||||
{
|
||||
winding -= seg->windingIncrement;
|
||||
}
|
||||
}
|
||||
}
|
||||
opIndex = op->next;
|
||||
}
|
||||
if(winding & 1)
|
||||
{
|
||||
float4 pathColor = pathBuffer[pathIndex].color;
|
||||
pathColor.rgb *= pathColor.a;
|
||||
color = color*(1-pathColor.a) + pathColor;
|
||||
}
|
||||
|
||||
outTexture.write(color, uint2(pixelCoord));
|
||||
|
|
Loading…
Reference in New Issue