private RenderStrategy chooseRenderStrategy(Canvas parentCanvas, ComposeOp op) {
    if (op.isNoop()) {
      // Can draw directly onto the final canvas, results will look the same.
      return RenderStrategy.DIRECT;
    }

    if (!op.hasShadow()) {
      // Canvas.saveLayer() supports alpha-compositing, blend modes, and color filters, which is
      // sufficient for this case, and is faster than manually maintaining an off-screen bitmap.
      // It is not clear if it's faster than RENDER_NODE and when, but this is what we've been
      // doing prior to OffscreenLayer, so keep that behavior to be safe and avoid regressions.
      return RenderStrategy.SAVE_LAYER;
    }

    // Beyond this point, we are sure that we need to render a drop shadow.

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || !parentCanvas.isHardwareAccelerated()) {
      // We don't have support for the RenderNode API, or we're rendering to a software canvas
      // which doesn't support RenderNodes anyhow. This is the slowest path: render to a bitmap,
      // add a shadow manually on CPU.
      return RenderStrategy.BITMAP;
    }

    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) {
      // RenderEffect, which we need for shadows, was only introduced in S. This means that, pre-S,
      // we have to do renderShadow() on a bitmap, like in the BITMAP case. However, since it's not
      // possible to draw a RenderNode to a software-rendering canvas, there would be no way to get
      // our RenderNode contents onto a bitmap where we can renderShadow(). Therefore, fall back to
      // full bitmap mode.
      return RenderStrategy.BITMAP;
    }

    // The RenderNode and RenderEffect APIs are available, so use hardware acceleration.
    return RenderStrategy.RENDER_NODE;
  }
parentCanvas' pre-existing matrix when start() was called
lottie/src/main/java/com/airbnb/lottie/utils/OffscreenLayer.java