2014-01-30 18:15:51 +00:00
|
|
|
/*
|
|
|
|
* Copyright 2014 Google Inc.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
|
|
* found in the LICENSE file.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// This test only works with the GPU backend.
|
|
|
|
|
2019-04-23 17:05:21 +00:00
|
|
|
#include "gm/gm.h"
|
2019-05-01 21:28:53 +00:00
|
|
|
#include "include/core/SkBlendMode.h"
|
|
|
|
#include "include/core/SkCanvas.h"
|
|
|
|
#include "include/core/SkMatrix.h"
|
|
|
|
#include "include/core/SkPaint.h"
|
|
|
|
#include "include/core/SkPath.h"
|
|
|
|
#include "include/core/SkPoint.h"
|
|
|
|
#include "include/core/SkRect.h"
|
|
|
|
#include "include/core/SkRefCnt.h"
|
|
|
|
#include "include/core/SkScalar.h"
|
|
|
|
#include "include/core/SkSize.h"
|
|
|
|
#include "include/core/SkString.h"
|
|
|
|
#include "include/core/SkTypes.h"
|
2019-04-23 17:05:21 +00:00
|
|
|
#include "include/gpu/GrContext.h"
|
|
|
|
#include "include/private/GrRecordingContext.h"
|
2019-05-01 21:28:53 +00:00
|
|
|
#include "include/private/GrSharedEnums.h"
|
|
|
|
#include "include/private/GrTypesPriv.h"
|
|
|
|
#include "include/private/SkColorData.h"
|
2019-04-23 17:05:21 +00:00
|
|
|
#include "src/core/SkPointPriv.h"
|
|
|
|
#include "src/core/SkTLList.h"
|
2019-05-01 21:28:53 +00:00
|
|
|
#include "src/gpu/GrCaps.h"
|
2019-04-23 17:05:21 +00:00
|
|
|
#include "src/gpu/GrDefaultGeoProcFactory.h"
|
2019-05-01 21:28:53 +00:00
|
|
|
#include "src/gpu/GrFragmentProcessor.h"
|
|
|
|
#include "src/gpu/GrGeometryProcessor.h"
|
2019-04-23 17:05:21 +00:00
|
|
|
#include "src/gpu/GrMemoryPool.h"
|
|
|
|
#include "src/gpu/GrOpFlushState.h"
|
2019-05-01 21:28:53 +00:00
|
|
|
#include "src/gpu/GrPaint.h"
|
|
|
|
#include "src/gpu/GrProcessorAnalysis.h"
|
|
|
|
#include "src/gpu/GrProcessorSet.h"
|
2019-04-23 17:05:21 +00:00
|
|
|
#include "src/gpu/GrRecordingContextPriv.h"
|
2019-05-01 21:28:53 +00:00
|
|
|
#include "src/gpu/GrRenderTargetContext.h"
|
2019-04-23 17:05:21 +00:00
|
|
|
#include "src/gpu/GrRenderTargetContextPriv.h"
|
2019-05-01 21:28:53 +00:00
|
|
|
#include "src/gpu/GrUserStencilSettings.h"
|
2019-04-23 17:05:21 +00:00
|
|
|
#include "src/gpu/effects/GrConvexPolyEffect.h"
|
2019-05-01 21:28:53 +00:00
|
|
|
#include "src/gpu/effects/GrPorterDuffXferProcessor.h"
|
|
|
|
#include "src/gpu/ops/GrDrawOp.h"
|
2019-04-23 17:05:21 +00:00
|
|
|
#include "src/gpu/ops/GrMeshDrawOp.h"
|
2019-05-01 21:28:53 +00:00
|
|
|
#include "src/gpu/ops/GrOp.h"
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
class GrAppliedClip;
|
2014-01-30 18:15:51 +00:00
|
|
|
|
2016-04-01 13:06:20 +00:00
|
|
|
/** outset rendered rect to visualize anti-aliased poly edges */
|
|
|
|
static SkRect outset(const SkRect& unsorted) {
|
|
|
|
SkRect r = unsorted;
|
|
|
|
r.outset(5.f, 5.f);
|
|
|
|
return r;
|
|
|
|
}
|
2015-02-11 21:45:50 +00:00
|
|
|
|
2016-04-01 13:06:20 +00:00
|
|
|
/** sorts a rect */
|
|
|
|
static SkRect sorted_rect(const SkRect& unsorted) {
|
|
|
|
SkRect r = unsorted;
|
|
|
|
r.sort();
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace skiagm {
|
2017-07-14 15:30:17 +00:00
|
|
|
class PolyBoundsOp : public GrMeshDrawOp {
|
2015-02-11 21:45:50 +00:00
|
|
|
public:
|
2016-12-01 14:36:50 +00:00
|
|
|
DEFINE_OP_CLASS_ID
|
2015-02-11 21:45:50 +00:00
|
|
|
|
2019-02-15 16:33:22 +00:00
|
|
|
static std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
|
2018-06-12 14:11:12 +00:00
|
|
|
GrPaint&& paint,
|
|
|
|
const SkRect& rect) {
|
2019-02-04 18:26:26 +00:00
|
|
|
GrOpMemoryPool* pool = context->priv().opMemoryPool();
|
2018-06-19 17:09:54 +00:00
|
|
|
|
|
|
|
return pool->allocate<PolyBoundsOp>(std::move(paint), rect);
|
2017-07-14 15:30:17 +00:00
|
|
|
}
|
|
|
|
|
2017-09-13 17:10:52 +00:00
|
|
|
const char* name() const override { return "PolyBoundsOp"; }
|
|
|
|
|
2019-05-22 01:35:29 +00:00
|
|
|
void visitProxies(const VisitProxyFunc& func) const override {
|
2017-09-13 17:10:52 +00:00
|
|
|
fProcessors.visitProxies(func);
|
|
|
|
}
|
|
|
|
|
2017-07-14 15:30:17 +00:00
|
|
|
FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
|
|
|
|
|
2019-06-24 00:07:38 +00:00
|
|
|
GrProcessorSet::Analysis finalize(
|
|
|
|
const GrCaps& caps, const GrAppliedClip* clip, bool hasMixedSampledCoverage,
|
|
|
|
GrClampType clampType) override {
|
2019-03-05 17:11:58 +00:00
|
|
|
return fProcessors.finalize(
|
|
|
|
fColor, GrProcessorAnalysisCoverage::kNone, clip, &GrUserStencilSettings::kUnused,
|
2019-06-24 00:07:38 +00:00
|
|
|
hasMixedSampledCoverage, caps, clampType, &fColor);
|
2015-02-11 21:45:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2018-06-12 14:11:12 +00:00
|
|
|
friend class ::GrOpMemoryPool; // for ctor
|
|
|
|
|
2017-07-14 15:30:17 +00:00
|
|
|
PolyBoundsOp(GrPaint&& paint, const SkRect& rect)
|
|
|
|
: INHERITED(ClassID())
|
2018-10-31 18:04:39 +00:00
|
|
|
, fColor(paint.getColor4f())
|
2017-07-14 15:30:17 +00:00
|
|
|
, fProcessors(std::move(paint))
|
|
|
|
, fRect(outset(rect)) {
|
|
|
|
this->setBounds(sorted_rect(fRect), HasAABloat::kNo, IsZeroArea::kNo);
|
|
|
|
}
|
2016-12-16 14:35:49 +00:00
|
|
|
|
2017-08-09 20:02:19 +00:00
|
|
|
void onPrepareDraws(Target* target) override {
|
2016-04-01 13:06:20 +00:00
|
|
|
using namespace GrDefaultGeoProcFactory;
|
2015-02-11 21:45:50 +00:00
|
|
|
|
2017-07-14 15:30:17 +00:00
|
|
|
Color color(fColor);
|
2017-01-04 15:44:42 +00:00
|
|
|
sk_sp<GrGeometryProcessor> gp(GrDefaultGeoProcFactory::Make(
|
2018-07-17 14:19:38 +00:00
|
|
|
target->caps().shaderCaps(),
|
|
|
|
color,
|
|
|
|
Coverage::kSolid_Type,
|
|
|
|
LocalCoords::kUnused_Type,
|
|
|
|
SkMatrix::I()));
|
2015-05-13 21:18:07 +00:00
|
|
|
|
Change how GPs configure attributes
Adds setVertexAttributes and setInstanceAttributes. These take a pointer
to the first attribute, and a count. The count is the total number of
possible attributes, though some may not be initialized. The base class
computes the number of initialized attributes, pre-computes the strides,
and only allows subsequent access to the initialized attributes.
The attributes need to be allocated contiguously. Some GPs place them in
an array, though most just place them as consecutive members, and pass
a pointer to the first one.
Indexed access would be possible, but now it makes more sense to iterate
over all attributes, so enable that, and use range-based for everywhere.
Completely remove the per-attribute offset helper (again - possible, but
not real helpful), and make the stride always available. In many ops,
just use the GP's computed stride, rather than re-computing it.
Bug: skia:
Change-Id: Ie4cccb7969a98ee5a10b373e714fbd702e875b3e
Reviewed-on: https://skia-review.googlesource.com/c/169241
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
2018-11-12 20:34:00 +00:00
|
|
|
SkASSERT(gp->vertexStride() == sizeof(SkPoint));
|
2018-08-07 14:02:38 +00:00
|
|
|
QuadHelper helper(target, sizeof(SkPoint), 1);
|
|
|
|
SkPoint* verts = reinterpret_cast<SkPoint*>(helper.vertices());
|
2015-05-05 14:49:49 +00:00
|
|
|
if (!verts) {
|
2015-03-05 22:33:41 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-05-18 16:52:22 +00:00
|
|
|
SkPointPriv::SetRectTriStrip(verts, fRect, sizeof(SkPoint));
|
2019-02-26 18:13:22 +00:00
|
|
|
helper.recordDraw(target, std::move(gp));
|
|
|
|
}
|
2015-02-11 21:45:50 +00:00
|
|
|
|
2019-02-26 18:13:22 +00:00
|
|
|
void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
|
|
|
|
flushState->executeDrawsAndUploadsForMeshDrawOp(this, chainBounds, std::move(fProcessors));
|
2015-02-11 21:45:50 +00:00
|
|
|
}
|
|
|
|
|
2018-10-31 18:04:39 +00:00
|
|
|
SkPMColor4f fColor;
|
2017-07-14 15:30:17 +00:00
|
|
|
GrProcessorSet fProcessors;
|
2016-04-01 13:06:20 +00:00
|
|
|
SkRect fRect;
|
2015-02-11 21:45:50 +00:00
|
|
|
|
2017-07-14 15:30:17 +00:00
|
|
|
typedef GrMeshDrawOp INHERITED;
|
2015-02-11 21:45:50 +00:00
|
|
|
};
|
|
|
|
|
2014-01-30 18:15:51 +00:00
|
|
|
/**
|
2014-09-23 16:50:21 +00:00
|
|
|
* This GM directly exercises a GrProcessor that draws convex polygons.
|
2014-01-30 18:15:51 +00:00
|
|
|
*/
|
2019-02-07 22:23:36 +00:00
|
|
|
class ConvexPolyEffect : public GpuGM {
|
2014-01-30 18:15:51 +00:00
|
|
|
public:
|
|
|
|
ConvexPolyEffect() {
|
|
|
|
this->setBGColor(0xFFFFFFFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
2015-03-26 01:17:31 +00:00
|
|
|
SkString onShortName() override {
|
2014-01-30 18:15:51 +00:00
|
|
|
return SkString("convex_poly_effect");
|
|
|
|
}
|
|
|
|
|
2015-03-26 01:17:31 +00:00
|
|
|
SkISize onISize() override {
|
2014-06-10 06:59:03 +00:00
|
|
|
return SkISize::Make(720, 800);
|
2014-01-30 18:15:51 +00:00
|
|
|
}
|
|
|
|
|
2015-03-26 01:17:31 +00:00
|
|
|
void onOnceBeforeDraw() override {
|
2014-01-30 18:15:51 +00:00
|
|
|
SkPath tri;
|
|
|
|
tri.moveTo(5.f, 5.f);
|
|
|
|
tri.lineTo(100.f, 20.f);
|
|
|
|
tri.lineTo(15.f, 100.f);
|
2014-01-31 03:01:59 +00:00
|
|
|
|
2014-01-30 18:15:51 +00:00
|
|
|
fPaths.addToTail(tri);
|
|
|
|
fPaths.addToTail(SkPath())->reverseAddPath(tri);
|
2014-01-31 03:01:59 +00:00
|
|
|
|
2014-01-30 18:15:51 +00:00
|
|
|
tri.close();
|
|
|
|
fPaths.addToTail(tri);
|
2014-01-31 03:01:59 +00:00
|
|
|
|
2014-01-30 18:15:51 +00:00
|
|
|
SkPath ngon;
|
2016-09-01 18:24:54 +00:00
|
|
|
constexpr SkScalar kRadius = 50.f;
|
2014-01-30 18:15:51 +00:00
|
|
|
const SkPoint center = { kRadius, kRadius };
|
|
|
|
for (int i = 0; i < GrConvexPolyEffect::kMaxEdges; ++i) {
|
|
|
|
SkScalar angle = 2 * SK_ScalarPI * i / GrConvexPolyEffect::kMaxEdges;
|
2019-04-02 14:59:28 +00:00
|
|
|
SkPoint point = { SkScalarCos(angle), SkScalarSin(angle) };
|
2014-01-30 18:15:51 +00:00
|
|
|
point.scale(kRadius);
|
|
|
|
point = center + point;
|
|
|
|
if (0 == i) {
|
|
|
|
ngon.moveTo(point);
|
|
|
|
} else {
|
|
|
|
ngon.lineTo(point);
|
|
|
|
}
|
|
|
|
}
|
2014-01-31 03:01:59 +00:00
|
|
|
|
2014-01-30 18:15:51 +00:00
|
|
|
fPaths.addToTail(ngon);
|
|
|
|
SkMatrix scaleM;
|
|
|
|
scaleM.setScale(1.1f, 0.4f);
|
|
|
|
ngon.transform(scaleM);
|
|
|
|
fPaths.addToTail(ngon);
|
2014-02-09 03:02:01 +00:00
|
|
|
|
2016-03-28 22:04:45 +00:00
|
|
|
SkPath linePath;
|
|
|
|
linePath.moveTo(5.f, 5.f);
|
|
|
|
linePath.lineTo(6.f, 6.f);
|
|
|
|
fPaths.addToTail(linePath);
|
|
|
|
|
2014-02-08 19:31:05 +00:00
|
|
|
// integer edges
|
|
|
|
fRects.addToTail(SkRect::MakeLTRB(5.f, 1.f, 30.f, 25.f));
|
|
|
|
// half-integer edges
|
|
|
|
fRects.addToTail(SkRect::MakeLTRB(5.5f, 0.5f, 29.5f, 24.5f));
|
|
|
|
// vertically/horizontally thin rects that cover pixel centers
|
|
|
|
fRects.addToTail(SkRect::MakeLTRB(5.25f, 0.5f, 5.75f, 24.5f));
|
|
|
|
fRects.addToTail(SkRect::MakeLTRB(5.5f, 0.5f, 29.5f, 0.75f));
|
|
|
|
// vertically/horizontally thin rects that don't cover pixel centers
|
|
|
|
fRects.addToTail(SkRect::MakeLTRB(5.55f, 0.5f, 5.75f, 24.5f));
|
|
|
|
fRects.addToTail(SkRect::MakeLTRB(5.5f, .05f, 29.5f, .25f));
|
|
|
|
// small in x and y
|
|
|
|
fRects.addToTail(SkRect::MakeLTRB(5.05f, .55f, 5.45f, .85f));
|
|
|
|
// inverted in x and y
|
|
|
|
fRects.addToTail(SkRect::MakeLTRB(100.f, 50.5f, 5.f, 0.5f));
|
2014-01-30 18:15:51 +00:00
|
|
|
}
|
|
|
|
|
2019-02-07 22:23:36 +00:00
|
|
|
void onDraw(GrContext* context, GrRenderTargetContext* renderTargetContext,
|
|
|
|
SkCanvas* canvas) override {
|
2014-01-30 18:15:51 +00:00
|
|
|
SkScalar y = 0;
|
2016-09-01 18:24:54 +00:00
|
|
|
constexpr SkScalar kDX = 12.f;
|
2015-11-19 03:01:12 +00:00
|
|
|
for (PathList::Iter iter(fPaths, PathList::Iter::kHead_IterStart);
|
2014-09-05 20:34:00 +00:00
|
|
|
iter.get();
|
2014-01-30 18:15:51 +00:00
|
|
|
iter.next()) {
|
2014-02-08 19:31:05 +00:00
|
|
|
const SkPath* path = iter.get();
|
2014-01-30 18:15:51 +00:00
|
|
|
SkScalar x = 0;
|
|
|
|
|
2017-11-10 16:58:19 +00:00
|
|
|
for (int et = 0; et < kGrClipEdgeTypeCnt; ++et) {
|
2015-03-27 02:57:08 +00:00
|
|
|
const SkMatrix m = SkMatrix::MakeTrans(x, y);
|
2014-01-30 18:15:51 +00:00
|
|
|
SkPath p;
|
|
|
|
path->transform(m, &p);
|
2014-01-31 03:01:59 +00:00
|
|
|
|
2017-11-09 19:51:17 +00:00
|
|
|
GrClipEdgeType edgeType = (GrClipEdgeType) et;
|
2018-10-15 20:09:54 +00:00
|
|
|
std::unique_ptr<GrFragmentProcessor> fp(GrConvexPolyEffect::Make(edgeType, p));
|
2014-09-23 16:50:21 +00:00
|
|
|
if (!fp) {
|
2014-03-05 18:27:43 +00:00
|
|
|
continue;
|
2014-01-30 18:15:51 +00:00
|
|
|
}
|
2014-01-31 03:01:59 +00:00
|
|
|
|
2016-06-23 21:07:00 +00:00
|
|
|
GrPaint grPaint;
|
2018-10-16 19:19:28 +00:00
|
|
|
grPaint.setColor4f({ 0, 0, 0, 1.f });
|
2017-01-09 16:46:10 +00:00
|
|
|
grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
|
2016-06-23 21:07:00 +00:00
|
|
|
grPaint.addCoverageFragmentProcessor(std::move(fp));
|
2014-11-17 22:22:48 +00:00
|
|
|
|
2017-07-14 15:30:17 +00:00
|
|
|
std::unique_ptr<GrDrawOp> op =
|
2018-06-12 14:11:12 +00:00
|
|
|
PolyBoundsOp::Make(context, std::move(grPaint), p.getBounds());
|
2017-07-14 15:30:17 +00:00
|
|
|
renderTargetContext->priv().testingOnly_addDrawOp(std::move(op));
|
2014-01-31 03:01:59 +00:00
|
|
|
|
2016-03-28 22:04:45 +00:00
|
|
|
x += SkScalarCeilToScalar(path->getBounds().width() + kDX);
|
2014-01-30 18:15:51 +00:00
|
|
|
}
|
2014-01-31 03:01:59 +00:00
|
|
|
|
2014-01-30 18:15:51 +00:00
|
|
|
// Draw AA and non AA paths using normal API for reference.
|
|
|
|
canvas->save();
|
|
|
|
canvas->translate(x, y);
|
|
|
|
SkPaint paint;
|
|
|
|
canvas->drawPath(*path, paint);
|
|
|
|
canvas->translate(path->getBounds().width() + 10.f, 0);
|
|
|
|
paint.setAntiAlias(true);
|
|
|
|
canvas->drawPath(*path, paint);
|
|
|
|
canvas->restore();
|
2014-01-31 03:01:59 +00:00
|
|
|
|
2014-02-08 19:31:05 +00:00
|
|
|
y += SkScalarCeilToScalar(path->getBounds().height() + 20.f);
|
|
|
|
}
|
|
|
|
|
2015-11-19 03:01:12 +00:00
|
|
|
for (RectList::Iter iter(fRects, RectList::Iter::kHead_IterStart);
|
2014-09-05 20:34:00 +00:00
|
|
|
iter.get();
|
2014-02-08 19:31:05 +00:00
|
|
|
iter.next()) {
|
|
|
|
|
|
|
|
SkScalar x = 0;
|
|
|
|
|
2017-11-10 16:58:19 +00:00
|
|
|
for (int et = 0; et < kGrClipEdgeTypeCnt; ++et) {
|
2014-02-28 14:43:26 +00:00
|
|
|
SkRect rect = *iter.get();
|
|
|
|
rect.offset(x, y);
|
2017-11-09 19:51:17 +00:00
|
|
|
GrClipEdgeType edgeType = (GrClipEdgeType) et;
|
2018-10-15 20:09:54 +00:00
|
|
|
std::unique_ptr<GrFragmentProcessor> fp(GrConvexPolyEffect::Make(edgeType, rect));
|
2014-09-23 16:50:21 +00:00
|
|
|
if (!fp) {
|
2014-03-05 18:27:43 +00:00
|
|
|
continue;
|
2014-02-28 14:43:26 +00:00
|
|
|
}
|
2014-02-08 19:31:05 +00:00
|
|
|
|
2016-06-23 21:07:00 +00:00
|
|
|
GrPaint grPaint;
|
2018-10-16 19:19:28 +00:00
|
|
|
grPaint.setColor4f({ 0, 0, 0, 1.f });
|
2017-01-09 16:46:10 +00:00
|
|
|
grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
|
2016-06-23 21:07:00 +00:00
|
|
|
grPaint.addCoverageFragmentProcessor(std::move(fp));
|
2014-02-08 19:31:05 +00:00
|
|
|
|
2018-06-12 14:11:12 +00:00
|
|
|
std::unique_ptr<GrDrawOp> op = PolyBoundsOp::Make(context, std::move(grPaint),
|
|
|
|
rect);
|
2017-07-14 15:30:17 +00:00
|
|
|
renderTargetContext->priv().testingOnly_addDrawOp(std::move(op));
|
2014-02-08 19:31:05 +00:00
|
|
|
|
2016-03-28 22:04:45 +00:00
|
|
|
x += SkScalarCeilToScalar(rect.width() + kDX);
|
2014-02-28 14:43:26 +00:00
|
|
|
}
|
2014-02-08 19:31:05 +00:00
|
|
|
|
2014-02-28 14:43:26 +00:00
|
|
|
// Draw rect without and with AA using normal API for reference
|
2014-02-08 19:31:05 +00:00
|
|
|
canvas->save();
|
|
|
|
canvas->translate(x, y);
|
|
|
|
SkPaint paint;
|
2014-02-28 14:43:26 +00:00
|
|
|
canvas->drawRect(*iter.get(), paint);
|
2016-03-28 22:04:45 +00:00
|
|
|
x += SkScalarCeilToScalar(iter.get()->width() + kDX);
|
2014-02-08 19:31:05 +00:00
|
|
|
paint.setAntiAlias(true);
|
|
|
|
canvas->drawRect(*iter.get(), paint);
|
|
|
|
canvas->restore();
|
|
|
|
|
2014-02-28 14:43:26 +00:00
|
|
|
y += SkScalarCeilToScalar(iter.get()->height() + 20.f);
|
2014-01-30 18:15:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2015-11-19 03:01:12 +00:00
|
|
|
typedef SkTLList<SkPath, 1> PathList;
|
|
|
|
typedef SkTLList<SkRect, 1> RectList;
|
|
|
|
PathList fPaths;
|
|
|
|
RectList fRects;
|
2014-02-09 03:02:01 +00:00
|
|
|
|
2014-01-30 18:15:51 +00:00
|
|
|
typedef GM INHERITED;
|
|
|
|
};
|
|
|
|
|
2015-08-26 20:07:48 +00:00
|
|
|
DEF_GM(return new ConvexPolyEffect;)
|
2014-01-30 18:15:51 +00:00
|
|
|
}
|