InstancedRendering::Batch* InstancedRendering::recordShape(ShapeType type, const SkRect& bounds,
                                                           const SkMatrix& viewMatrix,
                                                           GrColor color, const SkRect& localRect,
                                                           bool antialias,
                                                           const GrInstancedPipelineInfo& info,
                                                           bool* useHWAA) {
    SkASSERT(State::kRecordingDraws == fState);

    if (info.fIsRenderingToFloat && !fCanRenderToFloat) {
        return nullptr;
    }

    AntialiasMode antialiasMode;
    if (!this->selectAntialiasMode(viewMatrix, antialias, info, useHWAA, &antialiasMode)) {
        return nullptr;
    }

    Batch* batch = this->createBatch();
    batch->fInfo.fAntialiasMode = antialiasMode;
    batch->fInfo.fShapeTypes = GetShapeFlag(type);
    batch->fInfo.fCannotDiscard = !info.fCanDiscard;

    Instance& instance = batch->getSingleInstance();
    instance.fInfo = (int)type << kShapeType_InfoBit;

    Batch::HasAABloat aaBloat = (antialiasMode == AntialiasMode::kCoverage)
                                ? Batch::HasAABloat::kYes
                                : Batch::HasAABloat::kNo;
    Batch::IsZeroArea zeroArea = (bounds.isEmpty()) ? Batch::IsZeroArea::kYes
                                                    : Batch::IsZeroArea::kNo;

    // The instanced shape renderer draws rectangles of [-1, -1, +1, +1], so we find the matrix that
    // will map this rectangle to the same device coordinates as "viewMatrix * bounds".
    float sx = 0.5f * bounds.width();
    float sy = 0.5f * bounds.height();
    float tx = sx + bounds.fLeft;
    float ty = sy + bounds.fTop;
    if (!viewMatrix.hasPerspective()) {
        float* m = instance.fShapeMatrix2x3;
        m[0] = viewMatrix.getScaleX() * sx;
        m[1] = viewMatrix.getSkewX() * sy;
        m[2] = viewMatrix.getTranslateX() +
               viewMatrix.getScaleX() * tx + viewMatrix.getSkewX() * ty;

        m[3] = viewMatrix.getSkewY() * sx;
        m[4] = viewMatrix.getScaleY() * sy;
        m[5] = viewMatrix.getTranslateY() +
               viewMatrix.getSkewY() * tx + viewMatrix.getScaleY() * ty;

        // Since 'm' is a 2x3 matrix that maps the rect [-1, +1] into the shape's device-space quad,
        // it's quite simple to find the bounding rectangle:
        float devBoundsHalfWidth = fabsf(m[0]) + fabsf(m[1]);
        float devBoundsHalfHeight = fabsf(m[3]) + fabsf(m[4]);
        SkRect batchBounds;
        batchBounds.fLeft = m[2] - devBoundsHalfWidth;
        batchBounds.fRight = m[2] + devBoundsHalfWidth;
        batchBounds.fTop = m[5] - devBoundsHalfHeight;
        batchBounds.fBottom = m[5] + devBoundsHalfHeight;
        batch->setBounds(batchBounds, aaBloat, zeroArea);

        // TODO: Is this worth the CPU overhead?
        batch->fInfo.fNonSquare =
            fabsf(devBoundsHalfHeight - devBoundsHalfWidth) > 0.5f || // Early out.
            fabs(m[0] * m[3] + m[1] * m[4]) > 1e-3f || // Skew?
            fabs(m[0] * m[0] + m[1] * m[1] - m[3] * m[3] - m[4] * m[4]) > 1e-2f; // Diff. lengths?
    } else {
        SkMatrix shapeMatrix(viewMatrix);
        shapeMatrix.preTranslate(tx, ty);
        shapeMatrix.preScale(sx, sy);
        instance.fInfo |= kPerspective_InfoFlag;

        float* m = instance.fShapeMatrix2x3;
        m[0] = SkScalarToFloat(shapeMatrix.getScaleX());
        m[1] = SkScalarToFloat(shapeMatrix.getSkewX());
        m[2] = SkScalarToFloat(shapeMatrix.getTranslateX());
        m[3] = SkScalarToFloat(shapeMatrix.getSkewY());
        m[4] = SkScalarToFloat(shapeMatrix.getScaleY());
        m[5] = SkScalarToFloat(shapeMatrix.getTranslateY());

        // Send the perspective column as a param.
        batch->appendParamsTexel(shapeMatrix[SkMatrix::kMPersp0], shapeMatrix[SkMatrix::kMPersp1],
                                 shapeMatrix[SkMatrix::kMPersp2]);
        batch->fInfo.fHasPerspective = true;

        batch->setBounds(bounds, aaBloat, zeroArea);
        batch->fInfo.fNonSquare = true;
    }

    instance.fColor = color;

    const float* rectAsFloats = localRect.asScalars(); // Ensure SkScalar == float.
    memcpy(&instance.fLocalRect, rectAsFloats, 4 * sizeof(float));

    batch->fPixelLoad = batch->bounds().height() * batch->bounds().width();
    return batch;
}