// Blend2D - 2D Vector Graphics Powered by a JIT Compiler
//
//  * Official Blend2D Home Page: https://blend2d.com
//  * Official Github Repository: https://github.com/blend2d/blend2d
//
// Copyright (c) 2017-2020 The Blend2D Authors
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would be
//    appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//    misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

#include "./app.h"
#include "./module_blend2d.h"

#include <stdio.h>

namespace blbench {

// ============================================================================
// [bench::Blend2DModule - Construction / Destruction]
// ============================================================================

Blend2DModule::Blend2DModule(uint32_t threadCount, uint32_t cpuFeatures) {
  _threadCount = threadCount;
  _cpuFeatures = cpuFeatures;

  const char* feature = nullptr;

  if (_cpuFeatures & BL_RUNTIME_CPU_FEATURE_X86_SSE2  ) feature = "[SSE2]";
  if (_cpuFeatures & BL_RUNTIME_CPU_FEATURE_X86_SSE3  ) feature = "[SSE3]";
  if (_cpuFeatures & BL_RUNTIME_CPU_FEATURE_X86_SSSE3 ) feature = "[SSSE3]";
  if (_cpuFeatures & BL_RUNTIME_CPU_FEATURE_X86_SSE4_1) feature = "[SSE4.1]";
  if (_cpuFeatures & BL_RUNTIME_CPU_FEATURE_X86_SSE4_2) feature = "[SSE4.2]";
  if (_cpuFeatures & BL_RUNTIME_CPU_FEATURE_X86_AVX   ) feature = "[AVX]";
  if (_cpuFeatures & BL_RUNTIME_CPU_FEATURE_X86_AVX2  ) feature = "[AVX2]";

  if (!_threadCount)
    snprintf(_name, sizeof(_name), "Blend2D ST%s%s", feature ? " " : "", feature ? feature : "");
  else
    snprintf(_name, sizeof(_name), "Blend2D %uT%s%s", _threadCount, feature ? " " : "", feature ? feature : "");
}
Blend2DModule::~Blend2DModule() {}

// ============================================================================
// [bench::Blend2DModule - Helpers]
// ============================================================================

template<typename RectT>
static void BlendUtil_setupGradient(Blend2DModule* self, BLGradient& gradient, uint32_t style, const RectT& rect) {
  switch (style) {
    case kBenchStyleLinearPad:
    case kBenchStyleLinearRepeat:
    case kBenchStyleLinearReflect: {
      BLLinearGradientValues values;
      values.x0 = rect.x + rect.w * 0.2;
      values.y0 = rect.y + rect.h * 0.2;
      values.x1 = rect.x + rect.w * 0.8;
      values.y1 = rect.y + rect.h * 0.8;

      gradient.setValues(values);
      gradient.resetStops();
      gradient.addStop(0.0, self->_rndColor.nextRgba32());
      gradient.addStop(0.5, self->_rndColor.nextRgba32());
      gradient.addStop(1.0, self->_rndColor.nextRgba32());
      break;
    }

    case kBenchStyleRadialPad:
    case kBenchStyleRadialRepeat:
    case kBenchStyleRadialReflect: {
      BLRadialGradientValues values;
      values.x0 = rect.x + (rect.w / 2);
      values.y0 = rect.y + (rect.h / 2);
      values.r0 = (rect.w + rect.h) / 4;
      values.x1 = values.x0 - values.r0 / 2.0;
      values.y1 = values.y0 - values.r0 / 2.0;

      gradient.setValues(values);
      gradient.resetStops();
      gradient.addStop(0.0, self->_rndColor.nextRgba32());
      gradient.addStop(0.5, self->_rndColor.nextRgba32());
      gradient.addStop(1.0, self->_rndColor.nextRgba32());
      break;
    }

    default: {
      BLConicalGradientValues values;
      values.x0 = rect.x + (rect.w / 2);
      values.y0 = rect.y + (rect.h / 2);
      values.angle = 0;

      gradient.setValues(values);
      gradient.resetStops();

      BLRgba32 c(self->_rndColor.nextRgba32());

      gradient.addStop(0.00, c);
      gradient.addStop(0.33, self->_rndColor.nextRgba32());
      gradient.addStop(0.66, self->_rndColor.nextRgba32());
      gradient.addStop(1.00, c);
      break;
    }
  }
}

// ============================================================================
// [bench::Blend2DModule - Interface]
// ============================================================================

bool Blend2DModule::supportsCompOp(uint32_t compOp) const {
  return true;
}

bool Blend2DModule::supportsStyle(uint32_t style) const {
  return style == kBenchStyleSolid         ||
         style == kBenchStyleLinearPad     ||
         style == kBenchStyleLinearRepeat  ||
         style == kBenchStyleLinearReflect ||
         style == kBenchStyleRadialPad     ||
         style == kBenchStyleRadialRepeat  ||
         style == kBenchStyleRadialReflect ||
         style == kBenchStyleConical       ||
         style == kBenchStylePatternNN     ||
         style == kBenchStylePatternBI     ;
}

void Blend2DModule::onBeforeRun() {
  int w = int(_params.screenW);
  int h = int(_params.screenH);
  uint32_t style = _params.style;

  BLContextCreateInfo createInfo {};
  createInfo.threadCount = _threadCount;

  if (_cpuFeatures) {
    createInfo.flags = BL_CONTEXT_CREATE_FLAG_ISOLATED_JIT_RUNTIME |
                       BL_CONTEXT_CREATE_FLAG_OVERRIDE_CPU_FEATURES;
    createInfo.cpuFeatures = _cpuFeatures;
  }

  _surface.create(w, h, _params.format);
  _context.begin(_surface, &createInfo);

  _context.setCompOp(BL_COMP_OP_SRC_COPY);
  _context.setFillStyle(BLRgba32(0x00000000));
  _context.fillAll();

  _context.setCompOp(_params.compOp);
  _context.setStrokeWidth(_params.strokeWidth);

  _context.setPatternQuality(
    _params.style == kBenchStylePatternNN
      ? BL_PATTERN_QUALITY_NEAREST
      : BL_PATTERN_QUALITY_BILINEAR);

  // Setup globals.
  _gradientType = BL_GRADIENT_TYPE_LINEAR;
  _gradientExtend = BL_EXTEND_MODE_PAD;

  switch (style) {
    case kBenchStyleLinearPad      : _gradientType = BL_GRADIENT_TYPE_LINEAR ; _gradientExtend = BL_EXTEND_MODE_PAD    ; break;
    case kBenchStyleLinearRepeat   : _gradientType = BL_GRADIENT_TYPE_LINEAR ; _gradientExtend = BL_EXTEND_MODE_REPEAT ; break;
    case kBenchStyleLinearReflect  : _gradientType = BL_GRADIENT_TYPE_LINEAR ; _gradientExtend = BL_EXTEND_MODE_REFLECT; break;
    case kBenchStyleRadialPad      : _gradientType = BL_GRADIENT_TYPE_RADIAL ; _gradientExtend = BL_EXTEND_MODE_PAD    ; break;
    case kBenchStyleRadialRepeat   : _gradientType = BL_GRADIENT_TYPE_RADIAL ; _gradientExtend = BL_EXTEND_MODE_REPEAT ; break;
    case kBenchStyleRadialReflect  : _gradientType = BL_GRADIENT_TYPE_RADIAL ; _gradientExtend = BL_EXTEND_MODE_REFLECT; break;
    case kBenchStyleConical        : _gradientType = BL_GRADIENT_TYPE_CONICAL; break;
  }
}

void Blend2DModule::onAfterRun() {
  _context.end();
}

void Blend2DModule::onDoRectAligned(bool stroke) {
  BLSizeI bounds(_params.screenW, _params.screenH);

  uint32_t style = _params.style;
  BLContextOpType opType = stroke ? BL_CONTEXT_OP_TYPE_STROKE : BL_CONTEXT_OP_TYPE_FILL;

  int wh = _params.shapeSize;

  switch (style) {
    case kBenchStyleSolid: {
      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
        BLRectI rect(_rndCoord.nextRectI(bounds, wh, wh));
        BLRgba32 color(_rndColor.nextRgba32());

        _context.setStyle(opType, color);
        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRect(BLRect(rect.x + 0.5, rect.y + 0.5, rect.w, rect.h));
        else
          _context.fillRect(rect);
      }
      break;
    }

    case kBenchStyleLinearPad:
    case kBenchStyleLinearRepeat:
    case kBenchStyleLinearReflect:
    case kBenchStyleRadialPad:
    case kBenchStyleRadialRepeat:
    case kBenchStyleRadialReflect:
    case kBenchStyleConical: {
      BLGradient gradient(_gradientType);
      gradient.setExtendMode(_gradientExtend);

      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
        BLRectI rect(_rndCoord.nextRectI(bounds, wh, wh));
        BlendUtil_setupGradient<BLRectI>(this, gradient, style, rect);

        _context.save();
        _context.setStyle(opType, gradient);

        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRect(BLRect(rect.x + 0.5, rect.y + 0.5, rect.w, rect.h));
        else
          _context.fillRect(rect);

        _context.restore();
      }
      break;
    }

    case kBenchStylePatternNN:
    case kBenchStylePatternBI: {
      BLPattern pattern;

      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
        BLRectI rect(_rndCoord.nextRectI(bounds, wh, wh));

        if (opType == BL_CONTEXT_OP_TYPE_STROKE) {
          pattern.create(_sprites[nextSpriteId()]);
          pattern.setMatrix(BLMatrix2D::makeTranslation(rect.x + 0.5, rect.y + 0.5));

          _context.save();
          _context.setStrokeStyle(pattern);
          _context.strokeRect(BLRect(rect.x + 0.5, rect.y + 0.5, rect.w, rect.h));
          _context.restore();
        }
        else {
          _context.blitImage(BLPointI(rect.x, rect.y), _sprites[nextSpriteId()]);
        }
      }
      break;
    }
  }
}

void Blend2DModule::onDoRectSmooth(bool stroke) {
  BLSize bounds(_params.screenW, _params.screenH);

  uint32_t style = _params.style;
  BLContextOpType opType = stroke ? BL_CONTEXT_OP_TYPE_STROKE : BL_CONTEXT_OP_TYPE_FILL;

  double wh = _params.shapeSize;

  switch (style) {
    case kBenchStyleSolid: {
      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));
        BLRgba32 color(_rndColor.nextRgba32());

        _context.setStyle(opType, color);
        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRect(rect);
        else
          _context.fillRect(rect);
      }
      break;
    }

    case kBenchStyleLinearPad:
    case kBenchStyleLinearRepeat:
    case kBenchStyleLinearReflect:
    case kBenchStyleRadialPad:
    case kBenchStyleRadialRepeat:
    case kBenchStyleRadialReflect:
    case kBenchStyleConical: {
      BLGradient gradient(_gradientType);
      gradient.setExtendMode(_gradientExtend);

      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));
        BlendUtil_setupGradient<BLRect>(this, gradient, style, rect);

        _context.save();
        _context.setStyle(opType, gradient);

        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRect(rect);
        else
          _context.fillRect(rect);

        _context.restore();
      }
      break;
    }

    case kBenchStylePatternNN:
    case kBenchStylePatternBI: {
      BLPattern pattern;

      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));

        if (opType == BL_CONTEXT_OP_TYPE_STROKE) {
          pattern.create(_sprites[nextSpriteId()]);
          pattern.setMatrix(BLMatrix2D::makeTranslation(rect.x + 0.5, rect.y + 0.5));

          _context.save();
          _context.setStrokeStyle(pattern);
          _context.strokeRect(BLRect(rect.x + 0.5, rect.y + 0.5, rect.w, rect.h));
          _context.restore();
        }
        else {
          _context.blitImage(BLPoint(rect.x, rect.y), _sprites[nextSpriteId()]);
        }
      }
      break;
    }
  }
}

void Blend2DModule::onDoRectRotated(bool stroke) {
  BLSize bounds(_params.screenW, _params.screenH);

  uint32_t style = _params.style;
  BLContextOpType opType = stroke ? BL_CONTEXT_OP_TYPE_STROKE : BL_CONTEXT_OP_TYPE_FILL;

  double cx = double(_params.screenW) * 0.5;
  double cy = double(_params.screenH) * 0.5;
  double wh = _params.shapeSize;
  double angle = 0.0;

  switch (style) {
    case kBenchStyleSolid: {
      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) {
        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));
        BLRgba32 color(_rndColor.nextRgba32());

        _context.save();
        _context.rotate(angle, BLPoint(cx, cy));
        _context.setStyle(opType, color);

        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRect(rect);
        else
          _context.fillRect(rect);

        _context.restore();
      }
      break;
    }

    case kBenchStyleLinearPad:
    case kBenchStyleLinearRepeat:
    case kBenchStyleLinearReflect:
    case kBenchStyleRadialPad:
    case kBenchStyleRadialRepeat:
    case kBenchStyleRadialReflect:
    case kBenchStyleConical: {
      BLGradient gradient(_gradientType);
      gradient.setExtendMode(_gradientExtend);

      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) {
        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));
        BlendUtil_setupGradient<BLRect>(this, gradient, style, rect);

        _context.save();
        _context.rotate(angle, BLPoint(cx, cy));
        _context.setStyle(opType, gradient);

        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRect(rect);
        else
          _context.fillRect(rect);

        _context.restore();
      }
      break;
    }

    case kBenchStylePatternNN:
    case kBenchStylePatternBI: {
      BLPattern pattern;

      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) {
        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));

        _context.save();
        if (opType == BL_CONTEXT_OP_TYPE_STROKE) {
          pattern.create(_sprites[nextSpriteId()]);
          pattern.setMatrix(BLMatrix2D::makeTranslation(rect.x + 0.5, rect.y + 0.5));

          _context.setStrokeStyle(pattern);
          _context.rotate(angle, BLPoint(cx, cy));
          _context.strokeRect(BLRect(rect.x + 0.5, rect.y + 0.5, rect.w, rect.h));
        }
        else {
          _context.rotate(angle, BLPoint(cx, cy));
          _context.blitImage(BLPoint(rect.x, rect.y), _sprites[nextSpriteId()]);
        }
        _context.restore();
      }
      break;
    }
  }
}

void Blend2DModule::onDoRoundSmooth(bool stroke) {
  BLSize bounds(_params.screenW, _params.screenH);

  uint32_t style = _params.style;
  BLContextOpType opType = stroke ? BL_CONTEXT_OP_TYPE_STROKE : BL_CONTEXT_OP_TYPE_FILL;

  double wh = _params.shapeSize;

  switch (style) {
    case kBenchStyleSolid: {
      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
        double radius = _rndExtra.nextDouble(4.0, 40.0);

        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));
        BLRoundRect round(rect, radius);
        BLRgba32 color(_rndColor.nextRgba32());

        _context.setStyle(opType, color);

        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRoundRect(round);
        else
          _context.fillRoundRect(round);
      }
      break;
    }

    case kBenchStyleLinearPad:
    case kBenchStyleLinearRepeat:
    case kBenchStyleLinearReflect:
    case kBenchStyleRadialPad:
    case kBenchStyleRadialRepeat:
    case kBenchStyleRadialReflect:
    case kBenchStyleConical: {
      BLGradient gradient(_gradientType);
      gradient.setExtendMode(_gradientExtend);

      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
        double radius = _rndExtra.nextDouble(4.0, 40.0);

        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));
        BLRoundRect round(rect, radius);

        BlendUtil_setupGradient<BLRect>(this, gradient, style, rect);
        _context.save();
        _context.setStyle(opType, gradient);

        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRoundRect(round);
        else
          _context.fillRoundRect(round);

        _context.restore();
      }
      break;
    }

    case kBenchStylePatternNN:
    case kBenchStylePatternBI: {
      BLPattern pattern;

      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
        double radius = _rndExtra.nextDouble(4.0, 40.0);

        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));
        BLRoundRect round(rect, radius);

        pattern.create(_sprites[nextSpriteId()]);
        pattern.setMatrix(BLMatrix2D::makeTranslation(rect.x, rect.y));

        _context.save();
        _context.setStyle(opType, pattern);

        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRoundRect(round);
        else
          _context.fillRoundRect(round);

        _context.restore();
      }
      break;
    }
  }
}

void Blend2DModule::onDoRoundRotated(bool stroke) {
  BLSize bounds(_params.screenW, _params.screenH);

  uint32_t style = _params.style;
  BLContextOpType opType = stroke ? BL_CONTEXT_OP_TYPE_STROKE : BL_CONTEXT_OP_TYPE_FILL;

  double cx = double(_params.screenW) * 0.5;
  double cy = double(_params.screenH) * 0.5;
  double wh = _params.shapeSize;
  double angle = 0.0;

  switch (style) {
    case kBenchStyleSolid: {
      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) {
        double radius = _rndExtra.nextDouble(4.0, 40.0);

        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));
        BLRoundRect round(rect, radius);
        BLRgba32 color(_rndColor.nextRgba32());

        _context.save();
        _context.rotate(angle, BLPoint(cx, cy));
        _context.setStyle(opType, color);

        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRoundRect(round);
        else
          _context.fillRoundRect(round);

        _context.restore();
      }
      break;
    }

    case kBenchStyleLinearPad:
    case kBenchStyleLinearRepeat:
    case kBenchStyleLinearReflect:
    case kBenchStyleRadialPad:
    case kBenchStyleRadialRepeat:
    case kBenchStyleRadialReflect:
    case kBenchStyleConical: {
      BLGradient gradient(_gradientType);
      gradient.setExtendMode(_gradientExtend);

      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) {
        double radius = _rndExtra.nextDouble(4.0, 40.0);

        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));
        BLRoundRect round(rect, radius);

        BlendUtil_setupGradient<BLRect>(this, gradient, style, rect);

        _context.save();
        _context.rotate(angle, BLPoint(cx, cy));
        _context.setStyle(opType, gradient);

        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRoundRect(round);
        else
          _context.fillRoundRect(round);

        _context.restore();
      }
      break;
    }

    case kBenchStylePatternNN:
    case kBenchStylePatternBI: {
      BLPattern pattern;

      for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) {
        double radius = _rndExtra.nextDouble(4.0, 40.0);

        BLRect rect(_rndCoord.nextRect(bounds, wh, wh));
        BLRoundRect round(rect, radius);

        pattern.create(_sprites[nextSpriteId()]);
        pattern.setMatrix(BLMatrix2D::makeTranslation(rect.x, rect.y));

        _context.save();
        _context.rotate(angle, BLPoint(cx, cy));
        _context.setStyle(opType, pattern);

        if (opType == BL_CONTEXT_OP_TYPE_STROKE)
          _context.strokeRoundRect(round);
        else
          _context.fillRoundRect(round);

        _context.restore();
      }
      break;
    }
  }
}

void Blend2DModule::onDoPolygon(uint32_t mode, uint32_t complexity) {
  BLSizeI bounds(_params.screenW - _params.shapeSize,
                 _params.screenH - _params.shapeSize);
  uint32_t style = _params.style;
  BLContextOpType opType = mode == 2 ? BL_CONTEXT_OP_TYPE_STROKE : BL_CONTEXT_OP_TYPE_FILL;

  enum { kPointCapacity = 128 };
  if (complexity > kPointCapacity)
    return;

  BLPoint points[kPointCapacity];
  BLGradient gradient(_gradientType);
  BLPattern pattern;

  gradient.setExtendMode(_gradientExtend);

  if (mode == 0) _context.setFillRule(BL_FILL_RULE_NON_ZERO);
  if (mode == 1) _context.setFillRule(BL_FILL_RULE_EVEN_ODD);

  double wh = double(_params.shapeSize);

  for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
    BLPoint base(_rndCoord.nextPoint(bounds));

    for (uint32_t p = 0; p < complexity; p++) {
      double x = _rndCoord.nextDouble(base.x, base.x + wh);
      double y = _rndCoord.nextDouble(base.y, base.y + wh);
      points[p].reset(x, y);
    }

    _context.save();

    switch (style) {
      case kBenchStyleSolid: {
        _context.setStyle(opType, _rndColor.nextRgba32());
        break;
      }

      case kBenchStyleLinearPad:
      case kBenchStyleLinearRepeat:
      case kBenchStyleLinearReflect:
      case kBenchStyleRadialPad:
      case kBenchStyleRadialRepeat:
      case kBenchStyleRadialReflect:
      case kBenchStyleConical: {
        BLRect rect(base.x, base.y, wh, wh);
        BlendUtil_setupGradient<BLRect>(this, gradient, style, rect);
        _context.setStyle(opType, gradient);
        break;
      }

      case kBenchStylePatternNN:
      case kBenchStylePatternBI: {
        pattern.create(_sprites[nextSpriteId()]);
        pattern.setMatrix(BLMatrix2D::makeTranslation(base.x, base.y));
        _context.setStyle(opType, pattern);
        break;
      }
    }

    if (opType == BL_CONTEXT_OP_TYPE_STROKE)
      _context.strokePolygon(points, complexity);
    else
      _context.fillPolygon(points, complexity);

    _context.restore();
  }
}

void Blend2DModule::onDoShape(bool stroke, const BLPoint* pts, size_t count) {
  BLSizeI bounds(_params.screenW - _params.shapeSize,
                 _params.screenH - _params.shapeSize);
  uint32_t style = _params.style;
  BLContextOpType opType = stroke ? BL_CONTEXT_OP_TYPE_STROKE : BL_CONTEXT_OP_TYPE_FILL;

  BLPath path;
  bool start = true;
  double wh = double(_params.shapeSize);

  for (size_t i = 0; i < count; i++) {
    double x = pts[i].x;
    double y = pts[i].y;

    if (x == -1.0 && y == -1.0) {
      start = true;
      continue;
    }

    if (start) {
      path.moveTo(x * wh, y * wh);
      start = false;
    }
    else {
      path.lineTo(x * wh, y * wh);
    }
  }

  BLGradient gradient(_gradientType);
  gradient.setExtendMode(_gradientExtend);

  BLPattern pattern;

  for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
    BLPoint base(_rndCoord.nextPoint(bounds));

    _context.save();

    switch (style) {
      case kBenchStyleSolid: {
        _context.setStyle(opType, _rndColor.nextRgba32());
        break;
      }

      case kBenchStyleLinearPad:
      case kBenchStyleLinearRepeat:
      case kBenchStyleLinearReflect:
      case kBenchStyleRadialPad:
      case kBenchStyleRadialRepeat:
      case kBenchStyleRadialReflect:
      case kBenchStyleConical: {
        BLRect rect(base.x, base.y, wh, wh);
        BlendUtil_setupGradient<BLRect>(this, gradient, style, rect);
        _context.setStyle(opType, gradient);
        break;
      }

      case kBenchStylePatternNN:
      case kBenchStylePatternBI: {
        pattern.create(_sprites[nextSpriteId()]);
        pattern.setMatrix(BLMatrix2D::makeTranslation(base.x, base.y));
        _context.setStyle(opType, pattern);
        break;
      }
    }

    _context.translate(base);
    if (opType == BL_CONTEXT_OP_TYPE_STROKE)
      _context.strokePath(path);
    else
      _context.fillPath(path);

    _context.restore();
  }
}

} // {blbench}
