8288948: Few J2DBench tests indicate lower primitive drawing performance with metal rendering pipeline

Reviewed-by: avu, prr
This commit is contained in:
Ajit Ghaisas 2022-07-18 05:18:37 +00:00
parent 84f23149e2
commit bc7a1ea249
4 changed files with 231 additions and 110 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -51,6 +51,11 @@ enum {
MTL_OP_SYNC,
MTL_OP_SHAPE_CLIP_SPANS,
MTL_OP_MASK_OP,
MTL_OP_FILL_PARALLELOGRAM,
MTL_OP_FILL_RECT,
MTL_OP_DRAW_LINE,
MTL_OP_DRAW_RECT,
MTL_OP_DRAW_PARALLELOGRAM,
MTL_OP_OTHER
};
/*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -48,6 +48,18 @@ jint mtlPreviousOp = MTL_OP_INIT;
extern void MTLGC_DestroyMTLGraphicsConfig(jlong pConfigInfo);
bool isDrawOp (jint op) {
switch(op) {
case MTL_OP_DRAW_LINE:
case MTL_OP_DRAW_RECT:
case MTL_OP_DRAW_PARALLELOGRAM:
case MTL_OP_FILL_RECT:
case MTL_OP_FILL_PARALLELOGRAM:
return true;
default: return false;
}
}
void MTLRenderQueue_CheckPreviousOp(jint op) {
if (mtlPreviousOp == op) {
@ -55,6 +67,12 @@ void MTLRenderQueue_CheckPreviousOp(jint op) {
return;
}
if (isDrawOp(mtlPreviousOp) && !isDrawOp(op)) {
// submit the vertex batch
MTLRenderer_SubmitVertexBatch(mtlc, dstOps);
mtlPreviousOp = op;
}
if (op == MTL_OP_SET_COLOR) {
if (mtlPreviousOp != MTL_OP_MASK_OP) {
return; // SET_COLOR should not cause endEncoder
@ -128,7 +146,7 @@ Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
// draw ops
case sun_java2d_pipe_BufferedOpCodes_DRAW_LINE:
{
CHECK_PREVIOUS_OP(MTL_OP_OTHER);
CHECK_PREVIOUS_OP(MTL_OP_DRAW_LINE);
if ([mtlc useXORComposite]) {
commitEncodedCommands();
@ -144,9 +162,10 @@ Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
}
case sun_java2d_pipe_BufferedOpCodes_DRAW_RECT:
{
CHECK_PREVIOUS_OP(MTL_OP_OTHER);
CHECK_PREVIOUS_OP(MTL_OP_DRAW_RECT);
if ([mtlc useXORComposite]) {
commitEncodedCommands();
J2dTraceLn(J2D_TRACE_VERBOSE,
"DRAW_RECT in XOR mode - Force commit earlier draw calls before DRAW_RECT.");
@ -228,7 +247,7 @@ Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
}
case sun_java2d_pipe_BufferedOpCodes_DRAW_PARALLELOGRAM:
{
CHECK_PREVIOUS_OP(MTL_OP_OTHER);
CHECK_PREVIOUS_OP(MTL_OP_DRAW_PARALLELOGRAM);
if ([mtlc useXORComposite]) {
commitEncodedCommands();
@ -276,7 +295,7 @@ Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
// fill ops
case sun_java2d_pipe_BufferedOpCodes_FILL_RECT:
{
CHECK_PREVIOUS_OP(MTL_OP_OTHER);
CHECK_PREVIOUS_OP(MTL_OP_FILL_RECT);
if ([mtlc useXORComposite]) {
commitEncodedCommands();
@ -308,7 +327,7 @@ Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
}
case sun_java2d_pipe_BufferedOpCodes_FILL_PARALLELOGRAM:
{
CHECK_PREVIOUS_OP(MTL_OP_OTHER);
CHECK_PREVIOUS_OP(MTL_OP_FILL_PARALLELOGRAM);
if ([mtlc useXORComposite]) {
commitEncodedCommands();
@ -582,6 +601,7 @@ Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
jlong pDst = NEXT_LONG(b);
if (mtlc != NULL) {
MTLRenderer_SubmitVertexBatch(mtlc, dstOps);
[mtlc.encoderManager endEncoder];
MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
@ -609,6 +629,7 @@ Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
} else {
if (mtlc != NULL) {
MTLRenderer_SubmitVertexBatch(mtlc, dstOps);
[mtlc.encoderManager endEncoder];
MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
@ -878,6 +899,8 @@ Java_sun_java2d_metal_MTLRenderQueue_flushBuffer
if (mtlPreviousOp == MTL_OP_MASK_OP) {
MTLVertexCache_DisableMaskCache(mtlc);
}
MTLRenderer_SubmitVertexBatch(mtlc, dstOps);
[mtlc.encoderManager endEncoder];
MTLCommandBufferWrapper * cbwrapper = [mtlc pullCommandBufferWrapper];
id<MTLCommandBuffer> commandbuf = [cbwrapper getCommandBuffer];
@ -923,6 +946,9 @@ MTLRenderQueue_GetCurrentDestination()
* these would be rendered to the back-buffer - which is read in shader while rendering in XOR mode
*/
void commitEncodedCommands() {
MTLRenderer_SubmitVertexBatch(mtlc, dstOps);
[mtlc.encoderManager endEncoder];
MTLCommandBufferWrapper *cbwrapper = [mtlc pullCommandBufferWrapper];

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -72,5 +72,9 @@ void MTLRenderer_FillAAParallelogram(MTLContext *mtlc, BMTLSDOps * dstOps,
jfloat fx11, jfloat fy11,
jfloat dx21, jfloat dy21,
jfloat dx12, jfloat dy12);
void MTLRenderer_AddVertexToBatch(float x, float y);
void MTLRenderer_SubmitVertexBatch(MTLContext* mtlc, BMTLSDOps* dstOps);
void MTLRenderer_SetPrimitiveType(MTLPrimitiveType type);
#endif /* MTLRenderer_h_Included */

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -35,6 +35,95 @@
#include "MTLUtils.h"
#import "MTLLayer.h"
/**
* The max size of the vertex batch.
*
* Note:
* This is the max number of vertices (of struct Vertex - 8 bytes)
* that can be accommodated in 4KB.
*
* [MTLRenderCommandEncoder setVertexBytes] expects the data size
* to be less than or equal to 4KB.
*/
static const int VERTEX_BATCH_SIZE = 510;
static const int MAX_NO_OF_BATCHES = 20;
struct primDetail {
MTLPrimitiveType type;
int vertexStart;
int vertexEnd;
};
static struct Vertex vertexBatch[VERTEX_BATCH_SIZE];
static struct primDetail PrimitivesBatch[MAX_NO_OF_BATCHES];
static int currentIndexInBatch = 0;
static MTLPrimitiveType currentMTLPrimitiveType = MTLPrimitiveTypeTriangleStrip; // invalid type that we do not use in this renderer
static int currentBatchNo = -1;
void MTLRenderer_SetPrimitiveType(MTLPrimitiveType type) {
if (type != currentMTLPrimitiveType) {
if (currentBatchNo != -1) {
// close the current batch
PrimitivesBatch[currentBatchNo].vertexEnd = currentIndexInBatch - 1;
}
// Start a new batch
currentBatchNo++;
PrimitivesBatch[currentBatchNo].type = type;
PrimitivesBatch[currentBatchNo].vertexStart = currentIndexInBatch;
PrimitivesBatch[currentBatchNo].vertexEnd = currentIndexInBatch;
J2dRlsTraceLn1(J2D_TRACE_ERROR, "MTLRenderer_SetPrimitiveType: starting a new batch : batch %d", currentBatchNo);
currentMTLPrimitiveType = type;
}
}
inline void MTLRenderer_AddVertexToBatch(float x, float y)
{
vertexBatch[currentIndexInBatch].position[0] = x;
vertexBatch[currentIndexInBatch].position[1] = y;
currentIndexInBatch++;
}
void MTLRenderer_SubmitVertexBatch(MTLContext *mtlc, BMTLSDOps * dstOps)
{
if (currentIndexInBatch == 0) return;
if (currentBatchNo == -1) return;
// close the current batch
PrimitivesBatch[currentBatchNo].vertexEnd = currentIndexInBatch - 1;
id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
if (mtlEncoder == nil) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLRenderer_SubmitVertexBatch: error creating MTLRenderCommandEncoder.");
return;
}
[mtlEncoder setVertexBytes:vertexBatch length:currentIndexInBatch * sizeof(struct Vertex) atIndex:MeshVertexBuffer];
// Iterate through PrimitivesBatch array
for (int i = 0; i <= currentBatchNo; i++) {
int numVertices = PrimitivesBatch[i].vertexEnd - PrimitivesBatch[i].vertexStart + 1;
J2dRlsTraceLn2(J2D_TRACE_ERROR, "MTLRenderer_SubmitVertexBatch: total vertices in batch %d = %d", i, numVertices);
[mtlEncoder drawPrimitives: PrimitivesBatch[i].type
vertexStart: PrimitivesBatch[i].vertexStart
vertexCount: numVertices];
}
// Reset the index
currentIndexInBatch = 0;
// Reset the batches
currentBatchNo = -1;
// Reset to type that we do not use in this renderer
currentMTLPrimitiveType = MTLPrimitiveTypeTriangleStrip;
}
/**
* Note: Some of the methods in this file apply a "magic number"
* translation to line segments. It is same as what we have in
@ -65,12 +154,16 @@ void MTLRenderer_DrawLine(MTLContext *mtlc, BMTLSDOps * dstOps, jint x1, jint y1
J2dTraceLn5(J2D_TRACE_INFO, "MTLRenderer_DrawLine (x1=%d y1=%d x2=%d y2=%d), dst tex=%p", x1, y1, x2, y2, dstOps->pTexture);
id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
if (mtlEncoder == nil)
return;
// Make sure we have space for 2 more vertices in the batch
if (((currentIndexInBatch + 2) > VERTEX_BATCH_SIZE) ||
(currentBatchNo == (MAX_NO_OF_BATCHES - 1))) {
// encode the vertex batch
MTLRenderer_SubmitVertexBatch(mtlc, dstOps);
}
MTLRenderer_SetPrimitiveType(MTLPrimitiveTypeLine);
// DrawLine implementation same as in OGLRenderer.c
struct Vertex verts[2];
if (y1 == y2) {
// horizontal
float fx1 = (float)x1;
@ -80,11 +173,8 @@ void MTLRenderer_DrawLine(MTLContext *mtlc, BMTLSDOps * dstOps, jint x1, jint y1
if (x1 > x2) {
float t = fx1; fx1 = fx2; fx2 = t;
}
verts[0].position[0] = fx1 + 0.2f;
verts[0].position[1] = fy;
verts[1].position[0] = fx2 + 1.2f;
verts[1].position[1] = fy;
MTLRenderer_AddVertexToBatch(fx1 + 0.2f, fy);
MTLRenderer_AddVertexToBatch(fx2 + 1.2f, fy);
} else if (x1 == x2) {
// vertical
float fx = ((float)x1) + 0.2f;
@ -94,11 +184,8 @@ void MTLRenderer_DrawLine(MTLContext *mtlc, BMTLSDOps * dstOps, jint x1, jint y1
if (y1 > y2) {
float t = fy1; fy1 = fy2; fy2 = t;
}
verts[0].position[0] = fx;
verts[0].position[1] = fy1 + 0.2f;
verts[1].position[0] = fx;
verts[1].position[1] = fy2 + 1.2f;
MTLRenderer_AddVertexToBatch(fx, fy1 + 0.2f);
MTLRenderer_AddVertexToBatch(fx, fy2 + 1.2f);
} else {
// diagonal
float fx1 = (float)x1;
@ -121,14 +208,10 @@ void MTLRenderer_DrawLine(MTLContext *mtlc, BMTLSDOps * dstOps, jint x1, jint y1
fy1 += 0.8f;
fy2 -= 0.2f;
}
verts[0].position[0] = fx1;
verts[0].position[1] = fy1;
verts[1].position[0] = fx2;
verts[1].position[1] = fy2;
}
[mtlEncoder setVertexBytes:verts length:sizeof(verts) atIndex:MeshVertexBuffer];
[mtlEncoder drawPrimitives:MTLPrimitiveTypeLine vertexStart:0 vertexCount:2];
MTLRenderer_AddVertexToBatch(fx1, fy1);
MTLRenderer_AddVertexToBatch(fx2, fy2);
}
}
void MTLRenderer_DrawPixel(MTLContext *mtlc, BMTLSDOps * dstOps, jint x, jint y) {
@ -162,27 +245,35 @@ void MTLRenderer_DrawRect(MTLContext *mtlc, BMTLSDOps * dstOps, jint x, jint y,
id<MTLTexture> dest = dstOps->pTexture;
J2dTraceLn5(J2D_TRACE_INFO, "MTLRenderer_DrawRect (x=%d y=%d w=%d h=%d), dst tex=%p", x, y, w, h, dest);
// TODO: use DrawParallelogram(x, y, w, h, lw=1, lh=1)
id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
if (mtlEncoder == nil)
return;
// Make sure we have space for 8 more vertices in the batch
if ( ((currentIndexInBatch + 8) > VERTEX_BATCH_SIZE) ||
(currentBatchNo == (MAX_NO_OF_BATCHES - 1))) {
// encode the vertex batch
MTLRenderer_SubmitVertexBatch(mtlc, dstOps);
}
MTLRenderer_SetPrimitiveType(MTLPrimitiveTypeLine);
// Translate each vertex by a fraction so
// that we hit pixel centers.
const int verticesCount = 5;
float fx = (float)x + 0.2f;
float fy = (float)y + 0.5f;
float fw = (float)w;
float fh = (float)h;
struct Vertex vertices[5] = {
{{fx, fy}},
{{fx + fw, fy}},
{{fx + fw, fy + fh}},
{{fx, fy + fh}},
{{fx, fy}},
};
[mtlEncoder setVertexBytes:vertices length:sizeof(vertices) atIndex:MeshVertexBuffer];
[mtlEncoder drawPrimitives:MTLPrimitiveTypeLineStrip vertexStart:0 vertexCount:verticesCount];
MTLRenderer_AddVertexToBatch(fx, fy);
MTLRenderer_AddVertexToBatch(fx+fw, fy);
MTLRenderer_AddVertexToBatch(fx+fw, fy);
MTLRenderer_AddVertexToBatch(fx+fw, fy+fh);
MTLRenderer_AddVertexToBatch(fx+fw, fy+fh);
MTLRenderer_AddVertexToBatch(fx, fy+fh);
MTLRenderer_AddVertexToBatch(fx, fy+fh);
MTLRenderer_AddVertexToBatch(fx, fy);
}
const int POLYLINE_BUF_SIZE = 64;
@ -415,24 +506,23 @@ MTLRenderer_FillRect(MTLContext *mtlc, BMTLSDOps * dstOps, jint x, jint y, jint
return;
}
struct Vertex verts[QUAD_VERTEX_COUNT] = {
{ {x, y}},
{ {x, y+h}},
{ {x+w, y}},
{ {x+w, y+h}
}};
// Make sure we have space for 6 more vertices in the batch
if ( ((currentIndexInBatch + 6) > VERTEX_BATCH_SIZE) ||
(currentBatchNo == (MAX_NO_OF_BATCHES - 1))) {
// encode the vertex batch
MTLRenderer_SubmitVertexBatch(mtlc, dstOps);
}
MTLRenderer_SetPrimitiveType(MTLPrimitiveTypeTriangle);
id<MTLTexture> dest = dstOps->pTexture;
J2dTraceLn5(J2D_TRACE_INFO, "MTLRenderer_FillRect (x=%d y=%d w=%d h=%d), dst tex=%p", x, y, w, h, dest);
MTLRenderer_AddVertexToBatch(x, y);
MTLRenderer_AddVertexToBatch(x, y+h);
MTLRenderer_AddVertexToBatch(x+w, y);
// Encode render command.
id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
if (mtlEncoder == nil)
return;
[mtlEncoder setVertexBytes:verts length:sizeof(verts) atIndex:MeshVertexBuffer];
[mtlEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount: QUAD_VERTEX_COUNT];
MTLRenderer_AddVertexToBatch(x, y+h);
MTLRenderer_AddVertexToBatch(x+w, y);
MTLRenderer_AddVertexToBatch(x+w, y+h);
}
void MTLRenderer_FillSpans(MTLContext *mtlc, BMTLSDOps * dstOps, jint spanCount, jint *spans)
@ -553,23 +643,22 @@ MTLRenderer_FillParallelogram(MTLContext *mtlc, BMTLSDOps * dstOps,
dx21, dy21,
dx12, dy12, dest);
struct Vertex verts[QUAD_VERTEX_COUNT] = {
{ {fx11, fy11}},
{ {fx11+dx21, fy11+dy21}},
{ {fx11+dx12, fy11+dy12}},
{ {fx11 + dx21 + dx12, fy11+ dy21 + dy12}
}};
// Encode render command.
id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];;
if (mtlEncoder == nil) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLRenderer_FillParallelogram: error creating MTLRenderCommandEncoder.");
return;
// Make sure we have space for 6 more vertices in the batch
if ( ((currentIndexInBatch + 6) > VERTEX_BATCH_SIZE) ||
(currentBatchNo == (MAX_NO_OF_BATCHES - 1))) {
// encode the vertex batch
MTLRenderer_SubmitVertexBatch(mtlc, dstOps);
}
[mtlEncoder setVertexBytes:verts length:sizeof(verts) atIndex:MeshVertexBuffer];
[mtlEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount: QUAD_VERTEX_COUNT];
MTLRenderer_SetPrimitiveType(MTLPrimitiveTypeTriangle);
MTLRenderer_AddVertexToBatch(fx11, fy11);
MTLRenderer_AddVertexToBatch(fx11+dx21, fy11+dy21);
MTLRenderer_AddVertexToBatch(fx11 + dx21 + dx12, fy11+ dy21 + dy12);
MTLRenderer_AddVertexToBatch(fx11 + dx21 + dx12, fy11+ dy21 + dy12);
MTLRenderer_AddVertexToBatch(fx11 + dx12, fy11+dy12);
MTLRenderer_AddVertexToBatch(fx11, fy11);
}
void
@ -631,69 +720,66 @@ MTLRenderer_DrawParallelogram(MTLContext *mtlc, BMTLSDOps * dstOps,
// Each quad is encoded using two triangles
// For 4 segments - there are 8 triangles in total
// Each triangle has 3 vertices
const int TOTAL_VERTICES = 8 * 3;
struct Vertex vertexList[TOTAL_VERTICES];
int i = 0;
// Make sure we have space for 24 more vertices in the batch
if ( ((currentIndexInBatch + TOTAL_VERTICES) > VERTEX_BATCH_SIZE) ||
(currentBatchNo == (MAX_NO_OF_BATCHES - 1))) {
// encode the vertex batch
MTLRenderer_SubmitVertexBatch(mtlc, dstOps);
}
MTLRenderer_SetPrimitiveType(MTLPrimitiveTypeTriangle);
// TOP segment, to left side of RIGHT edge
// "width" of original pgram, "height" of hor. line size
fx11 = ox11;
fy11 = oy11;
fillVertex(vertexList + (i++), fx11, fy11);
fillVertex(vertexList + (i++), fx11 + dx21, fy11 + dy21);
fillVertex(vertexList + (i++), fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
MTLRenderer_AddVertexToBatch(fx11, fy11);
MTLRenderer_AddVertexToBatch(fx11 + dx21, fy11 + dy21);
MTLRenderer_AddVertexToBatch(fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
fillVertex(vertexList + (i++), fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
fillVertex(vertexList + (i++), fx11 + ldx12, fy11 + ldy12);
fillVertex(vertexList + (i++), fx11, fy11);
MTLRenderer_AddVertexToBatch(fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
MTLRenderer_AddVertexToBatch(fx11 + ldx12, fy11 + ldy12);
MTLRenderer_AddVertexToBatch(fx11, fy11);
// RIGHT segment, to top of BOTTOM edge
// "width" of vert. line size , "height" of original pgram
fx11 = ox11 + dx21;
fy11 = oy11 + dy21;
fillVertex(vertexList + (i++), fx11, fy11);
fillVertex(vertexList + (i++), fx11 + ldx21, fy11 + ldy21);
fillVertex(vertexList + (i++), fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
MTLRenderer_AddVertexToBatch(fx11, fy11);
MTLRenderer_AddVertexToBatch(fx11 + ldx21, fy11 + ldy21);
MTLRenderer_AddVertexToBatch(fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
fillVertex(vertexList + (i++), fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
fillVertex(vertexList + (i++), fx11 + dx12, fy11 + dy12);
fillVertex(vertexList + (i++), fx11, fy11);
MTLRenderer_AddVertexToBatch(fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
MTLRenderer_AddVertexToBatch(fx11 + dx12, fy11 + dy12);
MTLRenderer_AddVertexToBatch(fx11, fy11);
// BOTTOM segment, from right side of LEFT edge
// "width" of original pgram, "height" of hor. line size
fx11 = ox11 + dx12 + ldx21;
fy11 = oy11 + dy12 + ldy21;
fillVertex(vertexList + (i++), fx11, fy11);
fillVertex(vertexList + (i++), fx11 + dx21, fy11 + dy21);
fillVertex(vertexList + (i++), fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
MTLRenderer_AddVertexToBatch(fx11, fy11);
MTLRenderer_AddVertexToBatch(fx11 + dx21, fy11 + dy21);
MTLRenderer_AddVertexToBatch(fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
fillVertex(vertexList + (i++), fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
fillVertex(vertexList + (i++), fx11 + ldx12, fy11 + ldy12);
fillVertex(vertexList + (i++), fx11, fy11);
MTLRenderer_AddVertexToBatch(fx11 + dx21 + ldx12, fy11 + dy21 + ldy12);
MTLRenderer_AddVertexToBatch(fx11 + ldx12, fy11 + ldy12);
MTLRenderer_AddVertexToBatch(fx11, fy11);
// LEFT segment, from bottom of TOP edge
// "width" of vert. line size , "height" of inner pgram
fx11 = ox11 + ldx12;
fy11 = oy11 + ldy12;
fillVertex(vertexList + (i++), fx11, fy11);
fillVertex(vertexList + (i++), fx11 + ldx21, fy11 + ldy21);
fillVertex(vertexList + (i++), fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
MTLRenderer_AddVertexToBatch(fx11, fy11);
MTLRenderer_AddVertexToBatch(fx11 + ldx21, fy11 + ldy21);
MTLRenderer_AddVertexToBatch(fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
fillVertex(vertexList + (i++), fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
fillVertex(vertexList + (i++), fx11 + dx12, fy11 + dy12);
fillVertex(vertexList + (i++), fx11, fy11);
MTLRenderer_AddVertexToBatch(fx11 + ldx21 + dx12, fy11 + ldy21 + dy12);
MTLRenderer_AddVertexToBatch(fx11 + dx12, fy11 + dy12);
MTLRenderer_AddVertexToBatch(fx11, fy11);
// Encode render command.
id<MTLRenderCommandEncoder> mtlEncoder = [mtlc.encoderManager getRenderEncoder:dstOps];
if (mtlEncoder == nil) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "MTLRenderer_DrawParallelogram: error creating MTLRenderCommandEncoder.");
return;
}
[mtlEncoder setVertexBytes:vertexList length:sizeof(vertexList) atIndex:MeshVertexBuffer];
[mtlEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:TOTAL_VERTICES];
} else {
// The line width ratios were large enough to consume
// the entire hole in the middle of the parallelogram