<commit_before>/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkColorFilter.h"
#include "SkColorSpaceXformCanvas.h"
#include "SkColorSpaceXformer.h"
#include "SkDrawShadowInfo.h"
#include "SkGradientShader.h"
#include "SkImageFilter.h"
#include "SkImagePriv.h"
#include "SkImage_Base.h"
#include "SkMakeUnique.h"
#include "SkNoDrawCanvas.h"
#include "SkSurface.h"
#include "SkTLazy.h"

namespace {
    struct MaybePaint {
       SkTLazy<SkPaint> fStorage;
       const SkPaint* fPaint = nullptr;
       MaybePaint(const SkPaint* p, SkColorSpaceXformer* xformer) {
           if (p) { fPaint = fStorage.set(xformer->apply(*p)); }
       }
       operator const SkPaint*() const { return fPaint; }
    };
};

class SkColorSpaceXformCanvas : public SkNoDrawCanvas {
public:
    SkColorSpaceXformCanvas(SkCanvas* target, sk_sp<SkColorSpace> targetCS,
                            std::unique_ptr<SkColorSpaceXformer> xformer)
        : SkNoDrawCanvas(SkIRect::MakeSize(target->getBaseLayerSize()))
        , fTarget(target)
        , fTargetCS(targetCS)
        , fXformer(std::move(xformer))
    {
        // Set the matrix and clip to match |fTarget|.  Otherwise, we'll answer queries for
        // bounds/matrix differently than |fTarget| would.
        SkCanvas::onClipRect(SkRect::Make(fTarget->getDeviceClipBounds()),
                             SkClipOp::kIntersect, kHard_ClipEdgeStyle);
        SkCanvas::setMatrix(fTarget->getTotalMatrix());
    }

    SkImageInfo onImageInfo() const override {
        return fTarget->imageInfo().makeColorSpace(fTargetCS);
    }

    void onDrawPaint(const SkPaint& paint) override {
        fTarget->drawPaint(fXformer->apply(paint));
    }

    void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
        fTarget->drawRect(rect, fXformer->apply(paint));
    }
    void onDrawOval(const SkRect& oval, const SkPaint& paint) override {
        fTarget->drawOval(oval, fXformer->apply(paint));
    }
    void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override {
        fTarget->drawRRect(rrect, fXformer->apply(paint));
    }
    void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) override {
        fTarget->drawDRRect(outer, inner, fXformer->apply(paint));
    }
    void onDrawPath(const SkPath& path, const SkPaint& paint) override {
        fTarget->drawPath(path, fXformer->apply(paint));
    }
    void onDrawArc(const SkRect& oval, SkScalar start, SkScalar sweep, bool useCenter,
                   const SkPaint& paint) override {
        fTarget->drawArc(oval, start, sweep, useCenter, fXformer->apply(paint));
    }
    void onDrawRegion(const SkRegion& region, const SkPaint& paint) override {
        fTarget->drawRegion(region, fXformer->apply(paint));
    }
    void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], const SkPoint texs[4],
                     SkBlendMode mode, const SkPaint& paint) override {
        SkColor xformed[4];
        if (colors) {
            fXformer->apply(xformed, colors, 4);
            colors = xformed;
        }

        fTarget->drawPatch(cubics, colors, texs, mode, fXformer->apply(paint));
    }
    void onDrawPoints(PointMode mode, size_t count, const SkPoint* pts,
                      const SkPaint& paint) override {
        fTarget->drawPoints(mode, count, pts, fXformer->apply(paint));
    }
    void onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode,
                              const SkPaint& paint) override {
        sk_sp<SkVertices> copy;
        if (vertices->hasColors()) {
            int count = vertices->vertexCount();
            SkSTArray<8, SkColor> xformed(count);
            fXformer->apply(xformed.begin(), vertices->colors(), count);
            copy = SkVertices::MakeCopy(vertices->mode(), count, vertices->positions(),
                                        vertices->texCoords(), xformed.begin(),
                                        vertices->indexCount(), vertices->indices());
            vertices = copy.get();
        }

        fTarget->drawVertices(vertices, mode, fXformer->apply(paint));
    }

    void onDrawText(const void* ptr, size_t len,
                    SkScalar x, SkScalar y,
                    const SkPaint& paint) override {
        fTarget->drawText(ptr, len, x, y, fXformer->apply(paint));
    }
    void onDrawPosText(const void* ptr, size_t len,
                       const SkPoint* xys,
                       const SkPaint& paint) override {
        fTarget->drawPosText(ptr, len, xys, fXformer->apply(paint));
    }
    void onDrawPosTextH(const void* ptr, size_t len,
                        const SkScalar* xs, SkScalar y,
                        const SkPaint& paint) override {
        fTarget->drawPosTextH(ptr, len, xs, y, fXformer->apply(paint));
    }
    void onDrawTextOnPath(const void* ptr, size_t len,
                          const SkPath& path, const SkMatrix* matrix,
                          const SkPaint& paint) override {
        fTarget->drawTextOnPath(ptr, len, path, matrix, fXformer->apply(paint));
    }
    void onDrawTextRSXform(const void* ptr, size_t len,
                           const SkRSXform* xforms, const SkRect* cull,
                           const SkPaint& paint) override {
        fTarget->drawTextRSXform(ptr, len, xforms, cull, fXformer->apply(paint));
    }
    void onDrawTextBlob(const SkTextBlob* blob,
                        SkScalar x, SkScalar y,
                        const SkPaint& paint) override {
        fTarget->drawTextBlob(blob, x, y, fXformer->apply(paint));
    }

    void onDrawImage(const SkImage* img,
                     SkScalar l, SkScalar t,
                     const SkPaint* paint) override {
        fTarget->drawImage(prepareImage(img).get(), l, t, MaybePaint(paint, fXformer.get()));
    }
    void onDrawImageRect(const SkImage* img,
                         const SkRect* src, const SkRect& dst,
                         const SkPaint* paint, SrcRectConstraint constraint) override {
        fTarget->drawImageRect(prepareImage(img).get(),
                               src ? *src : SkRect::MakeIWH(img->width(), img->height()), dst,
                               MaybePaint(paint, fXformer.get()), constraint);
    }
    void onDrawImageNine(const SkImage* img,
                         const SkIRect& center, const SkRect& dst,
                         const SkPaint* paint) override {
        fTarget->drawImageNine(prepareImage(img).get(), center, dst,
                               MaybePaint(paint, fXformer.get()));
    }
    void onDrawImageLattice(const SkImage* img,
                            const Lattice& lattice, const SkRect& dst,
                            const SkPaint* paint) override {
        fTarget->drawImageLattice(prepareImage(img).get(), lattice, dst,
                                  MaybePaint(paint, fXformer.get()));
    }
    void onDrawAtlas(const SkImage* atlas, const SkRSXform* xforms, const SkRect* tex,
                     const SkColor* colors, int count, SkBlendMode mode,
                     const SkRect* cull, const SkPaint* paint) override {
        SkSTArray<8, SkColor> xformed;
        if (colors) {
            xformed.reset(count);
            fXformer->apply(xformed.begin(), colors, count);
            colors = xformed.begin();
        }
        fTarget->drawAtlas(prepareImage(atlas).get(), xforms, tex, colors, count, mode, cull,
                           MaybePaint(paint, fXformer.get()));
    }

    void onDrawBitmap(const SkBitmap& bitmap,
                      SkScalar l, SkScalar t,
                      const SkPaint* paint) override {
        if (this->skipXform(bitmap)) {
            return fTarget->drawBitmap(bitmap, l, t, MaybePaint(paint, fXformer.get()));
        }

        fTarget->drawImage(fXformer->apply(bitmap).get(), l, t, MaybePaint(paint, fXformer.get()));
    }
    void onDrawBitmapRect(const SkBitmap& bitmap,
                          const SkRect* src, const SkRect& dst,
                          const SkPaint* paint, SrcRectConstraint constraint) override {
        if (this->skipXform(bitmap)) {
            return fTarget->drawBitmapRect(bitmap,
                    src ? *src : SkRect::MakeIWH(bitmap.width(), bitmap.height()), dst,
                    MaybePaint(paint, fXformer.get()), constraint);
        }

        fTarget->drawImageRect(fXformer->apply(bitmap).get(),
                               src ? *src : SkRect::MakeIWH(bitmap.width(), bitmap.height()), dst,
                               MaybePaint(paint, fXformer.get()), constraint);
    }
    void onDrawBitmapNine(const SkBitmap& bitmap,
                          const SkIRect& center, const SkRect& dst,
                          const SkPaint* paint) override {
        if (this->skipXform(bitmap)) {
            return fTarget->drawBitmapNine(bitmap, center, dst, MaybePaint(paint, fXformer.get()));
        }

        fTarget->drawImageNine(fXformer->apply(bitmap).get(), center, dst,
                               MaybePaint(paint, fXformer.get()));

    }
    void onDrawBitmapLattice(const SkBitmap& bitmap,
                             const Lattice& lattice, const SkRect& dst,
                             const SkPaint* paint) override {
        if (this->skipXform(bitmap)) {
            return fTarget->drawBitmapLattice(bitmap, lattice, dst,
                                              MaybePaint(paint, fXformer.get()));
        }

        fTarget->drawImageLattice(fXformer->apply(bitmap).get(), lattice, dst,
                                  MaybePaint(paint, fXformer.get()));
    }
    void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) override {
        SkDrawShadowRec newRec(rec);
        newRec.fColor = fXformer->apply(rec.fColor);
        fTarget->private_draw_shadow_rec(path, newRec);
    }
    void onDrawPicture(const SkPicture* pic,
                       const SkMatrix* matrix,
                       const SkPaint* paint) override {
        SkCanvas::onDrawPicture(pic, matrix, MaybePaint(paint, fXformer.get()));
    }
    void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
        SkCanvas::onDrawDrawable(drawable, matrix);
    }

    SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override {
        sk_sp<SkImageFilter> backdrop = rec.fBackdrop ? fXformer->apply(rec.fBackdrop) : nullptr;
        sk_sp<SkImage> clipMask = rec.fClipMask ? fXformer->apply(rec.fClipMask) : nullptr;
        fTarget->saveLayer({
            rec.fBounds,
            MaybePaint(rec.fPaint, fXformer.get()),
            backdrop.get(),
            clipMask.get(),
            rec.fClipMatrix,
            rec.fSaveLayerFlags,
        });
        return kNoLayer_SaveLayerStrategy;
    }

#ifdef SK_SUPPORT_LEGACY_DRAWFILTER
    SkDrawFilter* setDrawFilter(SkDrawFilter* filter) override {
        SkCanvas::setDrawFilter(filter);
        return fTarget->setDrawFilter(filter);
    }
#endif

    // Everything from here on should be uninteresting strictly proxied state-change calls.
    void willSave()    override { fTarget->save(); }
    void willRestore() override { fTarget->restore(); }

    void didConcat   (const SkMatrix& m) override { fTarget->concat   (m); }
    void didSetMatrix(const SkMatrix& m) override { fTarget->setMatrix(m); }

    void onClipRect(const SkRect& clip, SkClipOp op, ClipEdgeStyle style) override {
        SkCanvas::onClipRect(clip, op, style);
        fTarget->clipRect(clip, op, style);
    }
    void onClipRRect(const SkRRect& clip, SkClipOp op, ClipEdgeStyle style) override {
        SkCanvas::onClipRRect(clip, op, style);
        fTarget->clipRRect(clip, op, style);
    }
    void onClipPath(const SkPath& clip, SkClipOp op, ClipEdgeStyle style) override {
        SkCanvas::onClipPath(clip, op, style);
        fTarget->clipPath(clip, op, style);
    }
    void onClipRegion(const SkRegion& clip, SkClipOp op) override {
        SkCanvas::onClipRegion(clip, op);
        fTarget->clipRegion(clip, op);
    }

    void onDrawAnnotation(const SkRect& rect, const char* key, SkData* val) override {
        fTarget->drawAnnotation(rect, key, val);
    }

    sk_sp<SkSurface> onNewSurface(const SkImageInfo& info, const SkSurfaceProps& props) override {
        return fTarget->makeSurface(info, &props);
    }

    SkISize getBaseLayerSize() const override { return fTarget->getBaseLayerSize(); }
    SkRect onGetLocalClipBounds() const override { return fTarget->getLocalClipBounds(); }
    SkIRect onGetDeviceClipBounds() const override { return fTarget->getDeviceClipBounds(); }
    bool isClipEmpty() const override { return fTarget->isClipEmpty(); }
    bool isClipRect() const override { return fTarget->isClipRect(); }
    bool onPeekPixels(SkPixmap* pixmap) override { return fTarget->peekPixels(pixmap); }
    bool onAccessTopLayerPixels(SkPixmap* pixmap) override {
        SkImageInfo info;
        size_t rowBytes;
        SkIPoint* origin = nullptr;
        void* addr = fTarget->accessTopLayerPixels(&info, &rowBytes, origin);
        if (addr) {
            *pixmap = SkPixmap(info, addr, rowBytes);
            return true;
        }
        return false;
    }

    GrContext* getGrContext() override { return fTarget->getGrContext(); }
    bool onGetProps(SkSurfaceProps* props) const override { return fTarget->getProps(props); }
    void onFlush() override { return fTarget->flush(); }
    GrRenderTargetContext* internal_private_accessTopLayerRenderTargetContext() override {
        return fTarget->internal_private_accessTopLayerRenderTargetContext();
    }

private:
    sk_sp<SkImage> prepareImage(const SkImage* image) {
        GrContext* gr = fTarget->getGrContext();
        if (gr) {
            // If fTarget is GPU-accelerated, we want to upload to a texture
            // before applying the transform. This way, we can get cache hits
            // in the texture cache and the transform gets applied on the GPU.
            sk_sp<SkImage> textureImage = image->makeTextureImage(gr, nullptr);
            if (textureImage)
                return fXformer->apply(textureImage.get());
        }
        // TODO: Extract a sub image corresponding to the src rect in order
        // to xform only the useful part of the image. Sub image could be reduced
        // even further by taking into account dst_rect+ctm+clip
        return fXformer->apply(image);
    }

    bool skipXform(const SkBitmap& bitmap) {
        return (!bitmap.colorSpace() && fTargetCS->isSRGB()) ||
               (SkColorSpace::Equals(bitmap.colorSpace(), fTargetCS.get())) ||
               (kAlpha_8_SkColorType == bitmap.colorType());
    }

    SkCanvas*                            fTarget;
    sk_sp<SkColorSpace>                  fTargetCS;
    std::unique_ptr<SkColorSpaceXformer> fXformer;
};

std::unique_ptr<SkCanvas> SkCreateColorSpaceXformCanvas(SkCanvas* target,
                                                        sk_sp<SkColorSpace> targetCS) {
    std::unique_ptr<SkColorSpaceXformer> xformer = SkColorSpaceXformer::Make(targetCS);
    if (!xformer) {
        return nullptr;
    }

    return skstd::make_unique<SkColorSpaceXformCanvas>(target, std::move(targetCS),
                                                       std::move(xformer));
}
<commit_msg>remove clip-bounds query overrides from SkColorSpaceXformCanvas<commit_after>/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkColorFilter.h"
#include "SkColorSpaceXformCanvas.h"
#include "SkColorSpaceXformer.h"
#include "SkDrawShadowInfo.h"
#include "SkGradientShader.h"
#include "SkImageFilter.h"
#include "SkImagePriv.h"
#include "SkImage_Base.h"
#include "SkMakeUnique.h"
#include "SkNoDrawCanvas.h"
#include "SkSurface.h"
#include "SkTLazy.h"

namespace {
    struct MaybePaint {
       SkTLazy<SkPaint> fStorage;
       const SkPaint* fPaint = nullptr;
       MaybePaint(const SkPaint* p, SkColorSpaceXformer* xformer) {
           if (p) { fPaint = fStorage.set(xformer->apply(*p)); }
       }
       operator const SkPaint*() const { return fPaint; }
    };
};

class SkColorSpaceXformCanvas : public SkNoDrawCanvas {
public:
    SkColorSpaceXformCanvas(SkCanvas* target, sk_sp<SkColorSpace> targetCS,
                            std::unique_ptr<SkColorSpaceXformer> xformer)
        : SkNoDrawCanvas(SkIRect::MakeSize(target->getBaseLayerSize()))
        , fTarget(target)
        , fTargetCS(targetCS)
        , fXformer(std::move(xformer))
    {
        // Set the matrix and clip to match |fTarget|.  Otherwise, we'll answer queries for
        // bounds/matrix differently than |fTarget| would.
        SkCanvas::onClipRect(SkRect::Make(fTarget->getDeviceClipBounds()),
                             SkClipOp::kIntersect, kHard_ClipEdgeStyle);
        SkCanvas::setMatrix(fTarget->getTotalMatrix());
    }

    SkImageInfo onImageInfo() const override {
        return fTarget->imageInfo().makeColorSpace(fTargetCS);
    }

    void onDrawPaint(const SkPaint& paint) override {
        fTarget->drawPaint(fXformer->apply(paint));
    }

    void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
        fTarget->drawRect(rect, fXformer->apply(paint));
    }
    void onDrawOval(const SkRect& oval, const SkPaint& paint) override {
        fTarget->drawOval(oval, fXformer->apply(paint));
    }
    void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override {
        fTarget->drawRRect(rrect, fXformer->apply(paint));
    }
    void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) override {
        fTarget->drawDRRect(outer, inner, fXformer->apply(paint));
    }
    void onDrawPath(const SkPath& path, const SkPaint& paint) override {
        fTarget->drawPath(path, fXformer->apply(paint));
    }
    void onDrawArc(const SkRect& oval, SkScalar start, SkScalar sweep, bool useCenter,
                   const SkPaint& paint) override {
        fTarget->drawArc(oval, start, sweep, useCenter, fXformer->apply(paint));
    }
    void onDrawRegion(const SkRegion& region, const SkPaint& paint) override {
        fTarget->drawRegion(region, fXformer->apply(paint));
    }
    void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], const SkPoint texs[4],
                     SkBlendMode mode, const SkPaint& paint) override {
        SkColor xformed[4];
        if (colors) {
            fXformer->apply(xformed, colors, 4);
            colors = xformed;
        }

        fTarget->drawPatch(cubics, colors, texs, mode, fXformer->apply(paint));
    }
    void onDrawPoints(PointMode mode, size_t count, const SkPoint* pts,
                      const SkPaint& paint) override {
        fTarget->drawPoints(mode, count, pts, fXformer->apply(paint));
    }
    void onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode,
                              const SkPaint& paint) override {
        sk_sp<SkVertices> copy;
        if (vertices->hasColors()) {
            int count = vertices->vertexCount();
            SkSTArray<8, SkColor> xformed(count);
            fXformer->apply(xformed.begin(), vertices->colors(), count);
            copy = SkVertices::MakeCopy(vertices->mode(), count, vertices->positions(),
                                        vertices->texCoords(), xformed.begin(),
                                        vertices->indexCount(), vertices->indices());
            vertices = copy.get();
        }

        fTarget->drawVertices(vertices, mode, fXformer->apply(paint));
    }

    void onDrawText(const void* ptr, size_t len,
                    SkScalar x, SkScalar y,
                    const SkPaint& paint) override {
        fTarget->drawText(ptr, len, x, y, fXformer->apply(paint));
    }
    void onDrawPosText(const void* ptr, size_t len,
                       const SkPoint* xys,
                       const SkPaint& paint) override {
        fTarget->drawPosText(ptr, len, xys, fXformer->apply(paint));
    }
    void onDrawPosTextH(const void* ptr, size_t len,
                        const SkScalar* xs, SkScalar y,
                        const SkPaint& paint) override {
        fTarget->drawPosTextH(ptr, len, xs, y, fXformer->apply(paint));
    }
    void onDrawTextOnPath(const void* ptr, size_t len,
                          const SkPath& path, const SkMatrix* matrix,
                          const SkPaint& paint) override {
        fTarget->drawTextOnPath(ptr, len, path, matrix, fXformer->apply(paint));
    }
    void onDrawTextRSXform(const void* ptr, size_t len,
                           const SkRSXform* xforms, const SkRect* cull,
                           const SkPaint& paint) override {
        fTarget->drawTextRSXform(ptr, len, xforms, cull, fXformer->apply(paint));
    }
    void onDrawTextBlob(const SkTextBlob* blob,
                        SkScalar x, SkScalar y,
                        const SkPaint& paint) override {
        fTarget->drawTextBlob(blob, x, y, fXformer->apply(paint));
    }

    void onDrawImage(const SkImage* img,
                     SkScalar l, SkScalar t,
                     const SkPaint* paint) override {
        fTarget->drawImage(prepareImage(img).get(), l, t, MaybePaint(paint, fXformer.get()));
    }
    void onDrawImageRect(const SkImage* img,
                         const SkRect* src, const SkRect& dst,
                         const SkPaint* paint, SrcRectConstraint constraint) override {
        fTarget->drawImageRect(prepareImage(img).get(),
                               src ? *src : SkRect::MakeIWH(img->width(), img->height()), dst,
                               MaybePaint(paint, fXformer.get()), constraint);
    }
    void onDrawImageNine(const SkImage* img,
                         const SkIRect& center, const SkRect& dst,
                         const SkPaint* paint) override {
        fTarget->drawImageNine(prepareImage(img).get(), center, dst,
                               MaybePaint(paint, fXformer.get()));
    }
    void onDrawImageLattice(const SkImage* img,
                            const Lattice& lattice, const SkRect& dst,
                            const SkPaint* paint) override {
        fTarget->drawImageLattice(prepareImage(img).get(), lattice, dst,
                                  MaybePaint(paint, fXformer.get()));
    }
    void onDrawAtlas(const SkImage* atlas, const SkRSXform* xforms, const SkRect* tex,
                     const SkColor* colors, int count, SkBlendMode mode,
                     const SkRect* cull, const SkPaint* paint) override {
        SkSTArray<8, SkColor> xformed;
        if (colors) {
            xformed.reset(count);
            fXformer->apply(xformed.begin(), colors, count);
            colors = xformed.begin();
        }
        fTarget->drawAtlas(prepareImage(atlas).get(), xforms, tex, colors, count, mode, cull,
                           MaybePaint(paint, fXformer.get()));
    }

    void onDrawBitmap(const SkBitmap& bitmap,
                      SkScalar l, SkScalar t,
                      const SkPaint* paint) override {
        if (this->skipXform(bitmap)) {
            return fTarget->drawBitmap(bitmap, l, t, MaybePaint(paint, fXformer.get()));
        }

        fTarget->drawImage(fXformer->apply(bitmap).get(), l, t, MaybePaint(paint, fXformer.get()));
    }
    void onDrawBitmapRect(const SkBitmap& bitmap,
                          const SkRect* src, const SkRect& dst,
                          const SkPaint* paint, SrcRectConstraint constraint) override {
        if (this->skipXform(bitmap)) {
            return fTarget->drawBitmapRect(bitmap,
                    src ? *src : SkRect::MakeIWH(bitmap.width(), bitmap.height()), dst,
                    MaybePaint(paint, fXformer.get()), constraint);
        }

        fTarget->drawImageRect(fXformer->apply(bitmap).get(),
                               src ? *src : SkRect::MakeIWH(bitmap.width(), bitmap.height()), dst,
                               MaybePaint(paint, fXformer.get()), constraint);
    }
    void onDrawBitmapNine(const SkBitmap& bitmap,
                          const SkIRect& center, const SkRect& dst,
                          const SkPaint* paint) override {
        if (this->skipXform(bitmap)) {
            return fTarget->drawBitmapNine(bitmap, center, dst, MaybePaint(paint, fXformer.get()));
        }

        fTarget->drawImageNine(fXformer->apply(bitmap).get(), center, dst,
                               MaybePaint(paint, fXformer.get()));

    }
    void onDrawBitmapLattice(const SkBitmap& bitmap,
                             const Lattice& lattice, const SkRect& dst,
                             const SkPaint* paint) override {
        if (this->skipXform(bitmap)) {
            return fTarget->drawBitmapLattice(bitmap, lattice, dst,
                                              MaybePaint(paint, fXformer.get()));
        }

        fTarget->drawImageLattice(fXformer->apply(bitmap).get(), lattice, dst,
                                  MaybePaint(paint, fXformer.get()));
    }
    void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) override {
        SkDrawShadowRec newRec(rec);
        newRec.fColor = fXformer->apply(rec.fColor);
        fTarget->private_draw_shadow_rec(path, newRec);
    }
    void onDrawPicture(const SkPicture* pic,
                       const SkMatrix* matrix,
                       const SkPaint* paint) override {
        SkCanvas::onDrawPicture(pic, matrix, MaybePaint(paint, fXformer.get()));
    }
    void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
        SkCanvas::onDrawDrawable(drawable, matrix);
    }

    SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override {
        sk_sp<SkImageFilter> backdrop = rec.fBackdrop ? fXformer->apply(rec.fBackdrop) : nullptr;
        sk_sp<SkImage> clipMask = rec.fClipMask ? fXformer->apply(rec.fClipMask) : nullptr;
        fTarget->saveLayer({
            rec.fBounds,
            MaybePaint(rec.fPaint, fXformer.get()),
            backdrop.get(),
            clipMask.get(),
            rec.fClipMatrix,
            rec.fSaveLayerFlags,
        });
        return kNoLayer_SaveLayerStrategy;
    }

#ifdef SK_SUPPORT_LEGACY_DRAWFILTER
    SkDrawFilter* setDrawFilter(SkDrawFilter* filter) override {
        SkCanvas::setDrawFilter(filter);
        return fTarget->setDrawFilter(filter);
    }
#endif

    // Everything from here on should be uninteresting strictly proxied state-change calls.
    void willSave()    override { fTarget->save(); }
    void willRestore() override { fTarget->restore(); }

    void didConcat   (const SkMatrix& m) override { fTarget->concat   (m); }
    void didSetMatrix(const SkMatrix& m) override { fTarget->setMatrix(m); }

    void onClipRect(const SkRect& clip, SkClipOp op, ClipEdgeStyle style) override {
        SkCanvas::onClipRect(clip, op, style);
        fTarget->clipRect(clip, op, style);
    }
    void onClipRRect(const SkRRect& clip, SkClipOp op, ClipEdgeStyle style) override {
        SkCanvas::onClipRRect(clip, op, style);
        fTarget->clipRRect(clip, op, style);
    }
    void onClipPath(const SkPath& clip, SkClipOp op, ClipEdgeStyle style) override {
        SkCanvas::onClipPath(clip, op, style);
        fTarget->clipPath(clip, op, style);
    }
    void onClipRegion(const SkRegion& clip, SkClipOp op) override {
        SkCanvas::onClipRegion(clip, op);
        fTarget->clipRegion(clip, op);
    }

    void onDrawAnnotation(const SkRect& rect, const char* key, SkData* val) override {
        fTarget->drawAnnotation(rect, key, val);
    }

    sk_sp<SkSurface> onNewSurface(const SkImageInfo& info, const SkSurfaceProps& props) override {
        return fTarget->makeSurface(info, &props);
    }

    SkISize getBaseLayerSize() const override { return fTarget->getBaseLayerSize(); }
    bool isClipEmpty() const override { return fTarget->isClipEmpty(); }
    bool isClipRect() const override { return fTarget->isClipRect(); }
    bool onPeekPixels(SkPixmap* pixmap) override { return fTarget->peekPixels(pixmap); }
    bool onAccessTopLayerPixels(SkPixmap* pixmap) override {
        SkImageInfo info;
        size_t rowBytes;
        SkIPoint* origin = nullptr;
        void* addr = fTarget->accessTopLayerPixels(&info, &rowBytes, origin);
        if (addr) {
            *pixmap = SkPixmap(info, addr, rowBytes);
            return true;
        }
        return false;
    }

    GrContext* getGrContext() override { return fTarget->getGrContext(); }
    bool onGetProps(SkSurfaceProps* props) const override { return fTarget->getProps(props); }
    void onFlush() override { return fTarget->flush(); }
    GrRenderTargetContext* internal_private_accessTopLayerRenderTargetContext() override {
        return fTarget->internal_private_accessTopLayerRenderTargetContext();
    }

private:
    sk_sp<SkImage> prepareImage(const SkImage* image) {
        GrContext* gr = fTarget->getGrContext();
        if (gr) {
            // If fTarget is GPU-accelerated, we want to upload to a texture
            // before applying the transform. This way, we can get cache hits
            // in the texture cache and the transform gets applied on the GPU.
            sk_sp<SkImage> textureImage = image->makeTextureImage(gr, nullptr);
            if (textureImage)
                return fXformer->apply(textureImage.get());
        }
        // TODO: Extract a sub image corresponding to the src rect in order
        // to xform only the useful part of the image. Sub image could be reduced
        // even further by taking into account dst_rect+ctm+clip
        return fXformer->apply(image);
    }

    bool skipXform(const SkBitmap& bitmap) {
        return (!bitmap.colorSpace() && fTargetCS->isSRGB()) ||
               (SkColorSpace::Equals(bitmap.colorSpace(), fTargetCS.get())) ||
               (kAlpha_8_SkColorType == bitmap.colorType());
    }

    SkCanvas*                            fTarget;
    sk_sp<SkColorSpace>                  fTargetCS;
    std::unique_ptr<SkColorSpaceXformer> fXformer;
};

std::unique_ptr<SkCanvas> SkCreateColorSpaceXformCanvas(SkCanvas* target,
                                                        sk_sp<SkColorSpace> targetCS) {
    std::unique_ptr<SkColorSpaceXformer> xformer = SkColorSpaceXformer::Make(targetCS);
    if (!xformer) {
        return nullptr;
    }

    return skstd::make_unique<SkColorSpaceXformCanvas>(target, std::move(targetCS),
                                                       std::move(xformer));
}
<|endoftext|>


<commit_before>#include "FuseGPUThreadLoops.h"
#include "CodeGen_GPU_Dev.h"
#include "IRMutator.h"
#include "IROperator.h"
#include "Bounds.h"
#include "Substitute.h"
#include "IREquality.h"
#include "Simplify.h"

namespace Halide {
namespace Internal {

using std::vector;
using std::string;
using std::set;

namespace {
string thread_names[] = {"threadidx", "threadidy", "threadidz", "threadidw"};
string shared_mem_name = "__shared__";
}

class InjectThreadBarriers : public IRMutator {
    bool in_threads;

    using IRMutator::visit;

    void visit(const For *op) {
        bool old_in_threads = in_threads;

        if (CodeGen_GPU_Dev::is_gpu_thread_var(op->name)) {
            in_threads = true;
        }

        IRMutator::visit(op);

        in_threads = old_in_threads;
    }

    Stmt barrier() {
        return Evaluate::make(Call::make(Int(32), Call::gpu_thread_barrier, vector<Expr>(), Call::Intrinsic));
    }

    void visit(const Pipeline *op) {
        if (!in_threads) {
            Stmt produce = mutate(op->produce);
            produce = Block::make(produce, barrier());

            Stmt update;
            if (op->update.defined()) {
                update = mutate(op->update);
                update = Block::make(update, barrier());
            }

            Stmt consume = mutate(op->consume);

            stmt = Pipeline::make(op->name, produce, update, consume);
        } else {
            IRMutator::visit(op);
        }
    }

    void visit(const Block *op) {
        if (!in_threads && op->rest.defined()) {
            Stmt first = mutate(op->first);
            Stmt rest = mutate(op->rest);
            stmt = Block::make(Block::make(first, barrier()), rest);
        } else {
            IRMutator::visit(op);
        }
    }

public:
    InjectThreadBarriers() : in_threads(false) {}
};

class ExtractBlockSize : public IRVisitor {
    Expr block_extent[4];

    Scope<Interval> scope;

    using IRVisitor::visit;

    void found_for(int dim, Expr extent) {
        internal_assert(dim >= 0 && dim < 4);
        if (!block_extent[dim].defined()) {
            block_extent[dim] = extent;
        } else {
            block_extent[dim] = simplify(Max::make(extent, block_extent[dim]));
        }
    }

    void visit(const For *op) {

        Interval ie = bounds_of_expr_in_scope(op->extent, scope);
        Interval im = bounds_of_expr_in_scope(op->min, scope);

        for (int i = 0; i < 4; i++) {
            if (ends_with(op->name, thread_names[i])) {
                found_for(i, ie.max);
            }
        }

        scope.push(op->name, Interval(im.min, im.max + ie.max - 1));
        IRVisitor::visit(op);
        scope.pop(op->name);
    }

    void visit(const LetStmt *op) {
        scope.push(op->name, bounds_of_expr_in_scope(op->value, scope));
        op->body.accept(this);
        scope.pop(op->name);
    }

public:
    int dimensions() const {
        for (int i = 0; i < 4; i++) {
            if (!block_extent[i].defined()) {
                return i;
            }
        }
        return 4;
    }

    Expr extent(int d) const {
        return block_extent[d];
    }
};

class NormalizeDimensionality : public IRMutator {
    using IRMutator::visit;

    const ExtractBlockSize &block_size;

    int depth;
    int max_depth;

    Stmt wrap(Stmt s) {
        if (depth != 0) {
            return mutate(s);
        }
        max_depth = 0;
        s = mutate(s);
        while (max_depth < block_size.dimensions()) {
            string name = thread_names[max_depth];
            s = For::make(name, 0, 1, For::Parallel, s);
            max_depth++;
        }
        return s;
    }

    void visit(const Pipeline *op) {
        Stmt produce = wrap(op->produce);
        Stmt update;
        if (op->update.defined()) {
            update = wrap(op->update);
        }
        Stmt consume = wrap(op->consume);

        if (produce.same_as(op->produce) &&
            update.same_as(op->update) &&
            consume.same_as(op->consume)) {
            stmt = op;
        } else {
            stmt = Pipeline::make(op->name, produce, update, consume);
        }
    }

    void visit(const Block *op) {
        Stmt first = wrap(op->first);

        Stmt rest;
        if (op->rest.defined()) {
            rest = wrap(op->rest);
        }

        if (first.same_as(op->first) &&
            rest.same_as(op->rest)) {
            stmt = op;
        } else {
            stmt = Block::make(first, rest);
        }
    }

    void visit(const For *op) {
        if (CodeGen_GPU_Dev::is_gpu_thread_var(op->name)) {
            depth++;
            if (depth > max_depth) {
                max_depth = depth;
            }
            if (!is_zero(op->min)) {
                // Shift them all to be zero-based.
                Expr var = Variable::make(Int(32), op->name);
                Stmt body = substitute(op->name, var + op->min, op->body);
                body = mutate(body);
                stmt = For::make(op->name, 0, op->extent, For::Parallel, body);
            } else {
                IRMutator::visit(op);
            }
            depth--;
        } else {
            IRMutator::visit(op);
        }
    }

public:
    NormalizeDimensionality(const ExtractBlockSize &e) : block_size(e), depth(0), max_depth(0) {}
};

class ReplaceForWithIf : public IRMutator {
    using IRMutator::visit;

    const ExtractBlockSize &block_size;

    void visit(const For *op) {
        if (CodeGen_GPU_Dev::is_gpu_thread_var(op->name)) {
            int dim;
            for (dim = 0; dim < 4; dim++) {
                if (ends_with(op->name, thread_names[dim])) {
                    break;
                }
            }

            internal_assert(dim >= 0 && dim < block_size.dimensions());

            Expr var = Variable::make(Int(32), "." + thread_names[dim]);
            internal_assert(is_zero(op->min));
            Stmt body = mutate(op->body);

            body = substitute(op->name, var, body);

            if (equal(op->extent, block_size.extent(dim))) {
                stmt = body;
            } else {
                Expr cond = var < op->extent;
                stmt = IfThenElse::make(cond, body, Stmt());
            }
        } else {
            IRMutator::visit(op);
        }
    }

public:
    ReplaceForWithIf(const ExtractBlockSize &e) : block_size(e) {}
};

class ExtractSharedAllocations : public IRMutator {

    using IRMutator::visit;

    struct SharedAllocation {
        string name;
        Type type;
        Expr size;
    };
    vector<SharedAllocation> allocations;

    Scope<Interval> scope;

    set<string> shared;

    bool in_threads;

    void visit(const For *op) {
        if (CodeGen_GPU_Dev::is_gpu_thread_var(op->name)) {
            bool old = in_threads;
            in_threads = true;
            IRMutator::visit(op);
            in_threads = old;
        } else {
            Interval min_bounds = bounds_of_expr_in_scope(op->min, scope);
            Interval extent_bounds = bounds_of_expr_in_scope(op->extent, scope);
            Interval bounds(min_bounds.min, min_bounds.max + extent_bounds.max - 1);
            scope.push(op->name, bounds);
            IRMutator::visit(op);
            scope.pop(op->name);
        }
    }

    void visit(const Allocate *op) {
        if (in_threads) {
            IRMutator::visit(op);
            return;
        }

        shared.insert(op->name);
        IRMutator::visit(op);
        shared.erase(op->name);
        op = stmt.as<Allocate>();
        internal_assert(op);

        SharedAllocation alloc;
        alloc.name = op->name;
        alloc.type = op->type;
        alloc.size = 1;
        for (size_t i = 0; i < op->extents.size(); i++) {
            alloc.size *= bounds_of_expr_in_scope(op->extents[i], scope).max;
        }
        allocations.push_back(alloc);
        stmt = op->body;

    }

    void visit(const Load *op) {
        if (shared.count(op->name)) {
            Expr base = Variable::make(Int(32), op->name + ".shared_offset");
            Expr index = mutate(op->index);
            expr = Load::make(op->type, shared_mem_name, base + index, op->image, op->param);
        } else {
            IRMutator::visit(op);
        }
    }

    void visit(const Store *op) {
        if (shared.count(op->name)) {
            Expr base = Variable::make(Int(32), op->name + ".shared_offset");
            Expr index = mutate(op->index);
            Expr value = mutate(op->value);
            stmt = Store::make(shared_mem_name, value, base + index);
        } else {
            IRMutator::visit(op);
        }
    }

    void visit(const LetStmt *op) {
        if (in_threads) {
            IRMutator::visit(op);
            return;
        }

        Expr value = mutate(op->value);
        Interval bounds = bounds_of_expr_in_scope(value, scope);
        scope.push(op->name, bounds);
        Stmt body = mutate(op->body);
        scope.pop(op->name);

        if (op->body.same_as(body) && op->value.same_as(value)) {
            stmt = op;
        } else {
            stmt = LetStmt::make(op->name, value, body);
        }
    }

public:
    Stmt rewrap(Stmt s) {
        // Sort the allocations by size in bytes of the primitive
        // type. Use reverse insertion sort.
        for (size_t i = 1; i < allocations.size(); i++) {
            for (size_t j = i; j > 0; j--) {
                if (allocations[j].type.bytes() > allocations[j - 1].type.bytes()) {
                    std::swap(allocations[j], allocations[j - 1]);
                }
            }
        }

        // Add a dummy allocation at the end to get the total size
        SharedAllocation sentinel;
        sentinel.name = "sentinel";
        sentinel.type = UInt(8);
        sentinel.size = 0;
        allocations.push_back(sentinel);

        Expr total_size = Variable::make(Int(32), allocations.back().name + ".shared_offset");
        s = Allocate::make(shared_mem_name, UInt(8), vec(total_size), s);

        // Define an offset for each allocation. The offsets are in
        // elements, not bytes, so that the stores and loads can use
        // them directly.
        for (int i = (int)(allocations.size()) - 1; i >= 0; i--) {
            Expr offset = 0;
            if (i > 0) {
                offset = Variable::make(Int(32), allocations[i-1].name + ".shared_offset");
                offset += allocations[i-1].size;
                int old_elem_size = allocations[i-1].type.bytes();
                int new_elem_size = allocations[i].type.bytes();
                internal_assert(old_elem_size >= new_elem_size);
                if (old_elem_size != new_elem_size) {
                    // We only have power-of-two sized types.
                    internal_assert(old_elem_size % new_elem_size == 0);
                    offset *= (old_elem_size / new_elem_size);
                }
            }

            s = LetStmt::make(allocations[i].name + ".shared_offset", offset, s);
        }

        return s;
    }

    ExtractSharedAllocations() : in_threads(false) {}
};

class FuseGPUThreadLoops : public IRMutator {
    using IRMutator::visit;

    void visit(const For *op) {
        if (ends_with(op->name, ".blockidx")) {

            Stmt body = op->body;

            ExtractBlockSize e;
            body.accept(&e);

            debug(3) << "Fusing thread block:\n" << body << "\n\n";

            NormalizeDimensionality n(e);
            body = n.mutate(body);

            debug(3) << "Normalized dimensionality:\n" << body << "\n\n";

            ExtractSharedAllocations h;
            body = h.mutate(body);

            debug(3) << "Pulled out shared allocations:\n" << body << "\n\n";

            InjectThreadBarriers i;
            body = i.mutate(body);

            debug(3) << "Injected synchronization:\n" << body << "\n\n";

            ReplaceForWithIf f(e);
            body = f.mutate(body);

            debug(3) << "Replaced for with if:\n" << body << "\n\n";

            // Rewrap the whole thing in the loop over threads
            for (int i = 0; i < e.dimensions(); i++) {
                body = For::make("." + thread_names[i], 0, e.extent(i), For::Parallel, body);
            }

            // There at least needs to be a loop over threadidx as a marker for codegen
            if (e.dimensions() == 0) {
                body = For::make(".threadidx", 0, 1, For::Parallel, body);
            }

            debug(3) << "Rewrapped in for loops:\n" << body << "\n\n";

            // Add back in the shared allocations
            body = h.rewrap(body);

            debug(3) << "Add back in shared allocations:\n" << body << "\n\n";

            if (body.same_as(op->body)) {
                stmt = op;
            } else {
                stmt = For::make(op->name, op->min, op->extent, op->for_type, body);
            }
        } else {
            IRMutator::visit(op);
        }

        // Shift the min to be zero
        if (CodeGen_GPU_Dev::is_gpu_var(op->name) && !is_zero(op->min)) {
            op = stmt.as<For>();
            internal_assert(op);
            Expr adjusted = Variable::make(Int(32), op->name) + op->min;
            Stmt body = substitute(op->name, adjusted, op->body);
            stmt = For::make(op->name, 0, op->extent, op->for_type, body);
        }
    }
};

Stmt fuse_gpu_thread_loops(Stmt s) {
    FuseGPUThreadLoops f;
    return f.mutate(s);
}

}
}

<commit_msg>Handle thread blocks with a size that depends on the block index.<commit_after>#include "FuseGPUThreadLoops.h"
#include "CodeGen_GPU_Dev.h"
#include "IRMutator.h"
#include "IROperator.h"
#include "Bounds.h"
#include "Substitute.h"
#include "IREquality.h"
#include "Simplify.h"

namespace Halide {
namespace Internal {

using std::vector;
using std::string;
using std::set;

namespace {
string thread_names[] = {"threadidx", "threadidy", "threadidz", "threadidw"};
string shared_mem_name = "__shared__";
}

class InjectThreadBarriers : public IRMutator {
    bool in_threads;

    using IRMutator::visit;

    void visit(const For *op) {
        bool old_in_threads = in_threads;

        if (CodeGen_GPU_Dev::is_gpu_thread_var(op->name)) {
            in_threads = true;
        }

        IRMutator::visit(op);

        in_threads = old_in_threads;
    }

    Stmt barrier() {
        return Evaluate::make(Call::make(Int(32), Call::gpu_thread_barrier, vector<Expr>(), Call::Intrinsic));
    }

    void visit(const Pipeline *op) {
        if (!in_threads) {
            Stmt produce = mutate(op->produce);
            produce = Block::make(produce, barrier());

            Stmt update;
            if (op->update.defined()) {
                update = mutate(op->update);
                update = Block::make(update, barrier());
            }

            Stmt consume = mutate(op->consume);

            stmt = Pipeline::make(op->name, produce, update, consume);
        } else {
            IRMutator::visit(op);
        }
    }

    void visit(const Block *op) {
        if (!in_threads && op->rest.defined()) {
            Stmt first = mutate(op->first);
            Stmt rest = mutate(op->rest);
            stmt = Block::make(Block::make(first, barrier()), rest);
        } else {
            IRMutator::visit(op);
        }
    }

public:
    InjectThreadBarriers() : in_threads(false) {}
};

class ExtractBlockSize : public IRVisitor {
    Expr block_extent[4];

    Scope<Interval> scope;

    using IRVisitor::visit;

    void found_for(int dim, Expr extent) {
        internal_assert(dim >= 0 && dim < 4);
        if (!block_extent[dim].defined()) {
            block_extent[dim] = extent;
        } else {
            block_extent[dim] = simplify(Max::make(extent, block_extent[dim]));
        }
    }

    void visit(const For *op) {

        Interval ie = bounds_of_expr_in_scope(op->extent, scope);
        Interval im = bounds_of_expr_in_scope(op->min, scope);

        for (int i = 0; i < 4; i++) {
            if (ends_with(op->name, thread_names[i])) {
                found_for(i, ie.max);
            }
        }

        scope.push(op->name, Interval(im.min, im.max + ie.max - 1));
        IRVisitor::visit(op);
        scope.pop(op->name);
    }

    void visit(const LetStmt *op) {
        Interval i = bounds_of_expr_in_scope(op->value, scope);
        scope.push(op->name, i);
        op->body.accept(this);
        scope.pop(op->name);
    }

public:
    int dimensions() const {
        for (int i = 0; i < 4; i++) {
            if (!block_extent[i].defined()) {
                return i;
            }
        }
        return 4;
    }

    Expr extent(int d) const {
        return block_extent[d];
    }

    ExtractBlockSize(Scope<Interval> &s) : scope(s) {}
};

class NormalizeDimensionality : public IRMutator {
    using IRMutator::visit;

    const ExtractBlockSize &block_size;

    int depth;
    int max_depth;

    Stmt wrap(Stmt s) {
        if (depth != 0) {
            return mutate(s);
        }
        max_depth = 0;
        s = mutate(s);
        while (max_depth < block_size.dimensions()) {
            string name = thread_names[max_depth];
            s = For::make(name, 0, 1, For::Parallel, s);
            max_depth++;
        }
        return s;
    }

    void visit(const Pipeline *op) {
        Stmt produce = wrap(op->produce);
        Stmt update;
        if (op->update.defined()) {
            update = wrap(op->update);
        }
        Stmt consume = wrap(op->consume);

        if (produce.same_as(op->produce) &&
            update.same_as(op->update) &&
            consume.same_as(op->consume)) {
            stmt = op;
        } else {
            stmt = Pipeline::make(op->name, produce, update, consume);
        }
    }

    void visit(const Block *op) {
        Stmt first = wrap(op->first);

        Stmt rest;
        if (op->rest.defined()) {
            rest = wrap(op->rest);
        }

        if (first.same_as(op->first) &&
            rest.same_as(op->rest)) {
            stmt = op;
        } else {
            stmt = Block::make(first, rest);
        }
    }

    void visit(const For *op) {
        if (CodeGen_GPU_Dev::is_gpu_thread_var(op->name)) {
            depth++;
            if (depth > max_depth) {
                max_depth = depth;
            }
            IRMutator::visit(op);
            depth--;
        } else {
            IRMutator::visit(op);
        }
    }

public:
    NormalizeDimensionality(const ExtractBlockSize &e) : block_size(e), depth(0), max_depth(0) {}
};

class ReplaceForWithIf : public IRMutator {
    using IRMutator::visit;

    const ExtractBlockSize &block_size;

    void visit(const For *op) {
        if (CodeGen_GPU_Dev::is_gpu_thread_var(op->name)) {
            int dim;
            for (dim = 0; dim < 4; dim++) {
                if (ends_with(op->name, thread_names[dim])) {
                    break;
                }
            }

            internal_assert(dim >= 0 && dim < block_size.dimensions());

            Expr var = Variable::make(Int(32), "." + thread_names[dim]);
            internal_assert(is_zero(op->min));
            Stmt body = mutate(op->body);

            body = substitute(op->name, var, body);

            if (equal(op->extent, block_size.extent(dim))) {
                stmt = body;
            } else {
                Expr cond = var < op->extent;
                stmt = IfThenElse::make(cond, body, Stmt());
            }
        } else {
            IRMutator::visit(op);
        }
    }

public:
    ReplaceForWithIf(const ExtractBlockSize &e) : block_size(e) {}
};

class ExtractSharedAllocations : public IRMutator {

    using IRMutator::visit;

    struct SharedAllocation {
        string name;
        Type type;
        Expr size;
    };
    vector<SharedAllocation> allocations;

    Scope<Interval> scope;

    set<string> shared;

    bool in_threads;

    void visit(const For *op) {
        if (CodeGen_GPU_Dev::is_gpu_thread_var(op->name)) {
            bool old = in_threads;
            in_threads = true;
            IRMutator::visit(op);
            in_threads = old;
        } else {
            Interval min_bounds = bounds_of_expr_in_scope(op->min, scope);
            Interval extent_bounds = bounds_of_expr_in_scope(op->extent, scope);
            Interval bounds(min_bounds.min, min_bounds.max + extent_bounds.max - 1);
            scope.push(op->name, bounds);
            IRMutator::visit(op);
            scope.pop(op->name);
        }
    }

    void visit(const Allocate *op) {
        if (in_threads) {
            IRMutator::visit(op);
            return;
        }

        shared.insert(op->name);
        IRMutator::visit(op);
        shared.erase(op->name);
        op = stmt.as<Allocate>();
        internal_assert(op);

        SharedAllocation alloc;
        alloc.name = op->name;
        alloc.type = op->type;
        alloc.size = 1;
        for (size_t i = 0; i < op->extents.size(); i++) {
            alloc.size *= op->extents[i];
        }
        alloc.size = bounds_of_expr_in_scope(simplify(alloc.size), scope).max;
        allocations.push_back(alloc);
        stmt = op->body;

    }

    void visit(const Load *op) {
        if (shared.count(op->name)) {
            Expr base = Variable::make(Int(32), op->name + ".shared_offset");
            Expr index = mutate(op->index);
            expr = Load::make(op->type, shared_mem_name, base + index, op->image, op->param);
        } else {
            IRMutator::visit(op);
        }
    }

    void visit(const Store *op) {
        if (shared.count(op->name)) {
            Expr base = Variable::make(Int(32), op->name + ".shared_offset");
            Expr index = mutate(op->index);
            Expr value = mutate(op->value);
            stmt = Store::make(shared_mem_name, value, base + index);
        } else {
            IRMutator::visit(op);
        }
    }

    void visit(const LetStmt *op) {
        if (in_threads) {
            IRMutator::visit(op);
            return;
        }

        Expr value = mutate(op->value);
        Interval bounds = bounds_of_expr_in_scope(value, scope);
        scope.push(op->name, bounds);
        Stmt body = mutate(op->body);
        scope.pop(op->name);

        if (op->body.same_as(body) && op->value.same_as(value)) {
            stmt = op;
        } else {
            stmt = LetStmt::make(op->name, value, body);
        }
    }

public:
    Stmt rewrap(Stmt s) {
        // Sort the allocations by size in bytes of the primitive
        // type. Use reverse insertion sort.
        for (size_t i = 1; i < allocations.size(); i++) {
            for (size_t j = i; j > 0; j--) {
                if (allocations[j].type.bytes() > allocations[j - 1].type.bytes()) {
                    std::swap(allocations[j], allocations[j - 1]);
                }
            }
        }

        // Add a dummy allocation at the end to get the total size
        SharedAllocation sentinel;
        sentinel.name = "sentinel";
        sentinel.type = UInt(8);
        sentinel.size = 0;
        allocations.push_back(sentinel);

        Expr total_size = Variable::make(Int(32), allocations.back().name + ".shared_offset");
        s = Allocate::make(shared_mem_name, UInt(8), vec(total_size), s);

        // Define an offset for each allocation. The offsets are in
        // elements, not bytes, so that the stores and loads can use
        // them directly.
        for (int i = (int)(allocations.size()) - 1; i >= 0; i--) {
            Expr offset = 0;
            if (i > 0) {
                offset = Variable::make(Int(32), allocations[i-1].name + ".shared_offset");
                offset += allocations[i-1].size;
                int old_elem_size = allocations[i-1].type.bytes();
                int new_elem_size = allocations[i].type.bytes();
                internal_assert(old_elem_size >= new_elem_size);
                if (old_elem_size != new_elem_size) {
                    // We only have power-of-two sized types.
                    internal_assert(old_elem_size % new_elem_size == 0);
                    offset *= (old_elem_size / new_elem_size);
                }
            }

            s = LetStmt::make(allocations[i].name + ".shared_offset", offset, s);
        }

        return s;
    }

    ExtractSharedAllocations() : in_threads(false) {}
};

class FuseGPUThreadLoops : public IRMutator {
    using IRMutator::visit;

    Scope<Interval> scope;

    void visit(const For *op) {
        bool should_pop = false;
        if (CodeGen_GPU_Dev::is_gpu_block_var(op->name)) {
            Interval im = bounds_of_expr_in_scope(op->min, scope);
            Interval ie = bounds_of_expr_in_scope(op->extent, scope);
            scope.push(op->name, Interval(im.min, im.max + ie.max - 1));
            should_pop = true;
        }

        if (ends_with(op->name, ".blockidx")) {

            Stmt body = op->body;

            ExtractBlockSize e(scope);
            body.accept(&e);

            debug(3) << "Fusing thread block:\n" << body << "\n\n";

            NormalizeDimensionality n(e);
            body = n.mutate(body);

            debug(3) << "Normalized dimensionality:\n" << body << "\n\n";

            ExtractSharedAllocations h;
            body = h.mutate(body);

            debug(3) << "Pulled out shared allocations:\n" << body << "\n\n";

            InjectThreadBarriers i;
            body = i.mutate(body);

            debug(3) << "Injected synchronization:\n" << body << "\n\n";

            ReplaceForWithIf f(e);
            body = f.mutate(body);

            debug(3) << "Replaced for with if:\n" << body << "\n\n";

            // Rewrap the whole thing in the loop over threads
            for (int i = 0; i < e.dimensions(); i++) {
                body = For::make("." + thread_names[i], 0, e.extent(i), For::Parallel, body);
            }

            // There at least needs to be a loop over threadidx as a marker for codegen
            if (e.dimensions() == 0) {
                body = For::make(".threadidx", 0, 1, For::Parallel, body);
            }

            debug(3) << "Rewrapped in for loops:\n" << body << "\n\n";

            // Add back in the shared allocations
            body = h.rewrap(body);

            debug(3) << "Add back in shared allocations:\n" << body << "\n\n";

            if (body.same_as(op->body)) {
                stmt = op;
            } else {
                stmt = For::make(op->name, op->min, op->extent, op->for_type, body);
            }
        } else {
            IRMutator::visit(op);
        }

        if (should_pop) {
            scope.pop(op->name);
        }
    }
};

// Rewrite all GPU loops to have a min of zero
class ZeroGPULoopMins : public IRMutator {
    using IRMutator::visit;

    void visit(const For *op) {
        IRMutator::visit(op);
        if (CodeGen_GPU_Dev::is_gpu_var(op->name) && !is_zero(op->min)) {
            op = stmt.as<For>();
            internal_assert(op);
            Expr adjusted = Variable::make(Int(32), op->name) + op->min;
            Stmt body = substitute(op->name, adjusted, op->body);
            stmt = For::make(op->name, 0, op->extent, op->for_type, body);
        }
    }
};

Stmt fuse_gpu_thread_loops(Stmt s) {
    ZeroGPULoopMins z;
    s = z.mutate(s);
    FuseGPUThreadLoops f;
    s = f.mutate(s);
    return s;
}

}
}

<|endoftext|>


<commit_before>/*
 * A client for the logging protocol.
 *
 * author: Max Kellermann <mk@cm4all.com>
 */

#include "Client.hxx"
#include "net/UniqueSocketDescriptor.hxx"
#include "util/ByteOrder.hxx"

#include <daemon/log.h>

#include <assert.h>
#include <unistd.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>

struct LogClient {
    UniqueSocketDescriptor fd;

    size_t position;
    char buffer[32768];

    explicit LogClient(UniqueSocketDescriptor &&_fd)
        :fd(std::move(_fd)) {}
};

LogClient *
log_client_new(UniqueSocketDescriptor &&fd)
{
    assert(fd.IsDefined());

    return new LogClient(std::move(fd));
}

void
log_client_free(LogClient *l)
{
    delete l;
}

static void
log_client_append(LogClient *client,
                  const void *p, size_t length)
{
    if (client->position + length <= sizeof(client->buffer))
        memcpy(client->buffer + client->position, p, length);

    client->position += length;
}

void
log_client_begin(LogClient *client)
{
    client->position = 0;

    log_client_append(client, &log_magic, sizeof(log_magic));
}

void
log_client_append_attribute(LogClient *client,
                            enum beng_log_attribute attribute,
                            const void *value, size_t length)
{
    uint8_t attribute8 = (uint8_t)attribute;
    log_client_append(client, &attribute8, sizeof(attribute8));
    log_client_append(client, value, length);
}

void
log_client_append_u16(LogClient *client,
                      enum beng_log_attribute attribute, uint16_t value)
{
    const uint16_t value2 = ToBE16(value);
    log_client_append_attribute(client, attribute, &value2, sizeof(value2));
}

void
log_client_append_u64(LogClient *client,
                      enum beng_log_attribute attribute, uint64_t value)
{
    const uint64_t value2 = ToBE64(value);
    log_client_append_attribute(client, attribute, &value2, sizeof(value2));
}

void
log_client_append_string(LogClient *client,
                         enum beng_log_attribute attribute, const char *value)
{
    assert(value != nullptr);

    log_client_append_attribute(client, attribute, value, strlen(value) + 1);
}

bool
log_client_commit(LogClient *client)
{
    assert(client != nullptr);
    assert(client->fd.IsDefined());
    assert(client->position > 0);

    if (client->position > sizeof(client->buffer))
        /* datagram is too large */
        return false;

    ssize_t nbytes = send(client->fd.Get(), client->buffer, client->position,
                          MSG_DONTWAIT|MSG_NOSIGNAL);
    if (nbytes < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
            /* silently ignore EAGAIN */
            return true;

        daemon_log(1, "Failed to send to logger: %s\n", strerror(errno));
        return false;
    }

    if ((size_t)nbytes != client->position)
        daemon_log(1, "Short send to logger: %s\n", strerror(errno));

    return true;
}
<commit_msg>access_log/Client: move code into the struct<commit_after>/*
 * A client for the logging protocol.
 *
 * author: Max Kellermann <mk@cm4all.com>
 */

#include "Client.hxx"
#include "net/UniqueSocketDescriptor.hxx"
#include "util/ByteOrder.hxx"

#include <daemon/log.h>

#include <assert.h>
#include <unistd.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>

struct LogClient {
    UniqueSocketDescriptor fd;

    size_t position;
    char buffer[32768];

    explicit LogClient(UniqueSocketDescriptor &&_fd)
        :fd(std::move(_fd)) {}

    void Begin() {
        position = 0;
        Append(&log_magic, sizeof(log_magic));
    }

    void Append(const void *p, size_t size) {
        if (position + size <= sizeof(buffer))
            memcpy(buffer + position, p, size);

        position += size;
    }

    void AppendAttribute(enum beng_log_attribute attribute,
                         const void *value, size_t size) {
        const uint8_t attribute8 = (uint8_t)attribute;
        Append(&attribute8, sizeof(attribute8));
        Append(value, size);
    }

    void AppendU16(enum beng_log_attribute attribute, uint16_t value) {
        const uint16_t value2 = ToBE16(value);
        AppendAttribute(attribute, &value2, sizeof(value2));
    }

    void AppendU64(enum beng_log_attribute attribute, uint64_t value) {
        const uint64_t value2 = ToBE64(value);
        AppendAttribute(attribute, &value2, sizeof(value2));
    }

    void AppendString(enum beng_log_attribute attribute, const char *value);

    bool Commit();
};

LogClient *
log_client_new(UniqueSocketDescriptor &&fd)
{
    assert(fd.IsDefined());

    return new LogClient(std::move(fd));
}

void
log_client_free(LogClient *l)
{
    delete l;
}

void
log_client_begin(LogClient *client)
{
    client->Begin();
}

void
log_client_append_attribute(LogClient *client,
                            enum beng_log_attribute attribute,
                            const void *value, size_t length)
{
    client->AppendAttribute(attribute, value, length);
}

void
log_client_append_u16(LogClient *client,
                      enum beng_log_attribute attribute, uint16_t value)
{
    client->AppendU16(attribute, value);
}

void
log_client_append_u64(LogClient *client,
                      enum beng_log_attribute attribute, uint64_t value)
{
    client->AppendU64(attribute, value);
}

void
LogClient::AppendString(enum beng_log_attribute attribute, const char *value)
{
    assert(value != nullptr);

    AppendAttribute(attribute, value, strlen(value) + 1);
}

void
log_client_append_string(LogClient *client,
                         enum beng_log_attribute attribute, const char *value)
{
    client->AppendString(attribute, value);
}

bool
LogClient::Commit()
{
    assert(fd.IsDefined());
    assert(position > 0);

    if (position > sizeof(buffer))
        /* datagram is too large */
        return false;

    ssize_t nbytes = send(fd.Get(), buffer, position,
                          MSG_DONTWAIT|MSG_NOSIGNAL);
    if (nbytes < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
            /* silently ignore EAGAIN */
            return true;

        daemon_log(1, "Failed to send to logger: %s\n", strerror(errno));
        return false;
    }

    if ((size_t)nbytes != position)
        daemon_log(1, "Short send to logger: %s\n", strerror(errno));

    return true;
}

bool
log_client_commit(LogClient *client)
{
    assert(client != nullptr);

    return client->Commit();
}
<|endoftext|>


<commit_before>#include "ImagePanel.h"

#include <set>

using namespace std;

vector< wxRect > GetCoverage( const wxRect& viewport, const wxRect& canvas, const wxSize& gridSize )
{
    const wxRect clippedViewport( canvas.Intersect( viewport ) );

    vector< wxRect > coverage;
    const int top    = clippedViewport.GetTop()    / gridSize.y;
    const int bottom = clippedViewport.GetBottom() / gridSize.y;
    const int left   = clippedViewport.GetLeft()   / gridSize.x;
    const int right  = clippedViewport.GetRight()  / gridSize.x;
    for( int y = top; y <= bottom; ++y )
    {
        for( int x = left; x <= right; ++x )
        {
            const wxRect candidate( x * gridSize.x, y * gridSize.y, gridSize.x, gridSize.y );
            const wxRect clipped( canvas.Intersect( candidate ) );
            coverage.push_back( clipped );
        }
    }
    return coverage;
}

template< typename T >
T clamp( const T& val, const T& minVal, const T& maxVal )
{
    if( val < minVal )  return minVal;
    if( val > maxVal )  return maxVal;
    return val;
}

wxPoint ClampPosition( const wxRect& viewport, const wxRect& extent )
{
    const wxSize delta( viewport.GetSize() - extent.GetSize() );

    wxPoint newTopLeft( viewport.GetPosition() );

    if( delta.x < 0 )
    {
        // viewport smaller than extent
        int minX = extent.GetPosition().x;
        int maxX = ( extent.GetPosition().x + extent.GetSize().x ) - viewport.GetSize().x;
        newTopLeft.x = clamp( newTopLeft.x, minX, maxX );
    }
    else
    {
        // viewport larger than extent
        newTopLeft.x = extent.GetPosition().x - ( delta.x / 2 );
    }

    if( delta.y < 0 )
    {
        // viewport smaller than extent
        int minY = extent.GetPosition().y;
        int maxY = ( extent.GetPosition().y + extent.GetSize().y ) - viewport.GetSize().y;
        newTopLeft.y = clamp( newTopLeft.y, minY, maxY );
    }
    else
    {
        // viewport larger than extent
        newTopLeft.y = extent.GetPosition().y - ( delta.y / 2 );
    }

    return newTopLeft;
}

wxImagePanel::wxImagePanel( wxWindow* parent )
    : wxWindow( parent, wxID_ANY )
    , mBitmapCache( 512 )   // ~135MB for 512 256x256x4 byte tiles
    , mPosition( 0, 0 )
    , mScale( 1.0 )
    , mImageFactory( this )
{
    // for wxAutoBufferedPaintDC
    SetBackgroundStyle( wxBG_STYLE_PAINT );

    SetBackgroundColour( *wxBLACK );

    Bind( wxEVT_SIZE        , &wxImagePanel::OnSize         , this );
    Bind( wxEVT_PAINT       , &wxImagePanel::OnPaint        , this );
    Bind( wxEVT_KEY_DOWN    , &wxImagePanel::OnKeyDown      , this );
    Bind( wxEVT_KEY_UP      , &wxImagePanel::OnKeyUp        , this );
    Bind( wxEVT_LEFT_DOWN   , &wxImagePanel::OnButtonDown   , this );
    Bind( wxEVT_RIGHT_DOWN  , &wxImagePanel::OnButtonDown   , this );
    Bind( wxEVT_MIDDLE_DOWN , &wxImagePanel::OnButtonDown   , this );
    Bind( wxEVT_MOTION      , &wxImagePanel::OnMotion       , this );
    Bind( wxEVT_THREAD      , &wxImagePanel::OnThread       , this );

    mStipple = wxBitmap( wxImage( "background.png" ) );
}

void wxImagePanel::OnSize( wxSizeEvent& event )
{
    mPosition = ClampPosition( mPosition );

    // invalidate entire panel since we need to redraw everything
    Refresh( true );

    // skip the event so sizers can do their thing
    event.Skip();
}

void wxImagePanel::OnButtonDown( wxMouseEvent& event )
{
    if( event.LeftDown() )
    {
        mLeftPositionStart = mPosition;
        mLeftMouseStart = event.GetPosition();
    }
}

void wxImagePanel::OnMotion( wxMouseEvent& event )
{
    if( event.LeftIsDown() && event.Dragging() )
    {
        wxPoint newPos( mLeftPositionStart - ( event.GetPosition() - mLeftMouseStart ) );
        if( newPos != mPosition )
        {
            ScrollToPosition( newPos );
        }
    }
}

void wxImagePanel::OnIdle( wxIdleEvent & )
{
    wxPoint newPos( mPosition );
    const int step = wxGetKeyState( WXK_CONTROL ) ? 10 : 1;
    if( wxGetKeyState( WXK_LEFT  ) )    newPos += step * wxPoint( -1,  0 );
    if( wxGetKeyState( WXK_RIGHT ) )    newPos += step * wxPoint(  1,  0 );
    if( wxGetKeyState( WXK_UP    ) )    newPos += step * wxPoint(  0, -1 );
    if( wxGetKeyState( WXK_DOWN  ) )    newPos += step * wxPoint(  0,  1 );

    if( newPos == mPosition )
    {
        Unbind( wxEVT_IDLE, &wxImagePanel::OnIdle, this );
        return;
    }

    ScrollToPosition( newPos );

    //event.RequestMore( true );
    //wxMilliSleep( 1 );
}

void wxImagePanel::OnKeyDown( wxKeyEvent& event )
{
    switch( event.GetKeyCode() )
    {
        case WXK_LEFT:
        case WXK_RIGHT:
        case WXK_UP:
        case WXK_DOWN:
            Bind( wxEVT_IDLE, &wxImagePanel::OnIdle, this );
            break;
        default:
            break;
    }
    event.Skip();
}

wxPoint wxImagePanel::ClampPosition( const wxPoint& newPos )
{
    if( NULL == mImage )
    {
        return newPos;
    }

    const wxSize scaledSize( mImage->GetWidth() * mScale, mImage->GetHeight() * mScale );
    return ::ClampPosition
        (
        wxRect( newPos, GetSize() ),
        wxRect( wxPoint(0,0), scaledSize )
        );
}

void wxImagePanel::OnKeyUp( wxKeyEvent& event )
{
    if( NULL == mImage )
    {
        return;
    }

    switch( event.GetKeyCode() )
    {
        // zoom in
        case '=':
        case WXK_ADD:
        case WXK_NUMPAD_ADD:
            SetScale( mScale * 1.1 );
            break;
        // zoom out
        case '-':
        case WXK_SUBTRACT:
        case WXK_NUMPAD_SUBTRACT:
            SetScale( mScale / 1.1 );
            break;
        // fit image to window
        case 'X':
        case WXK_NUMPAD_MULTIPLY:
            {
                const double scaleWidth = ( GetSize().x / static_cast< double >( mImage->GetWidth() ) );
                const double scaleHeight = ( GetSize().y / static_cast< double >( mImage->GetHeight() ) );
                SetScale( min( scaleWidth, scaleHeight ) );
            }
            break;
        // zoom 1:1
        case 'Z':
        case WXK_NUMPAD_DIVIDE:
            SetScale( 1.0 );
            break;
        default:
            break;
    }
}

void wxImagePanel::ScrollToPosition( const wxPoint& newPos )
{
    const wxPoint clamped = ClampPosition( newPos );
    wxPoint delta( clamped - mPosition );
    ScrollWindow( -delta.x, -delta.y );
    mPosition = clamped;
}

void wxImagePanel::QueueRect( const wxRect& rect )
{
    // don't queue rects we have cached
    wxBitmapPtr bmpPtr;
    if( mBitmapCache.get( bmpPtr, rect ) )
        return;

    // don't queue rects we've already queued
    if( mQueuedRects.end() != mQueuedRects.find( rect ) )
        return;

    mQueuedRects.insert( rect );
    mImageFactory.AddRect( rect );
}

struct wxRectPointDistCmp
{
    wxPoint mPoint;
    wxRectPointDistCmp( const wxPoint& pt ) : mPoint( pt ) {}
    bool operator()( const wxRect& left, const wxRect& right ) const
    {
        const wxPoint leftCenter = left.GetPosition() + 0.5 * ( left.GetBottomRight() - left.GetTopLeft() );
        const wxPoint rightCenter = right.GetPosition() + 0.5 * ( right.GetBottomRight() - right.GetTopLeft() );
        const wxPoint leftDiff = ( leftCenter - mPoint );
        const wxPoint rightDiff = ( rightCenter - mPoint );
        const double leftDistSq = ( leftDiff.x * leftDiff.x + leftDiff.y * leftDiff.y );
        const double rightDistSq = ( rightDiff.x * rightDiff.x + rightDiff.y * rightDiff.y );
        return ( leftDistSq < rightDistSq );
    }
};

void wxImagePanel::OnPaint( wxPaintEvent& )
{
    wxPaintDC dc(this);
    //wxAutoBufferedPaintDC dc( this );

    dc.SetDeviceOrigin( -mPosition.x, -mPosition.y );

    dc.Clear();

    if( NULL == mImage )
    {
        return;
    }

    const wxSize scaledSize( mImage->GetWidth() * mScale, mImage->GetHeight() * mScale );
    const wxRect scaledRect( wxPoint( 0, 0 ), scaledSize );
    const wxSize gridSize( TILE_SIZE, TILE_SIZE );

    // get the set of tiles we need to draw
    set< wxRect, wxRectCmp > updateRects;
    for( wxRegionIterator upd( GetUpdateRegion() ); upd.HaveRects(); ++upd )
    {
        wxRect rect( upd.GetRect() );
        rect.SetPosition( rect.GetPosition() + mPosition );

        const vector< wxRect > ret = GetCoverage
            (
            rect,
            scaledRect,
            gridSize
            );
        updateRects.insert( ret.begin(), ret.end() );
    }

    dc.SetBrush( wxBrush( mStipple ) );
    dc.SetPen( *wxTRANSPARENT_PEN );

    for( const wxRect& rect : updateRects )
    {
        wxBitmapPtr bmpPtr;
        if( mBitmapCache.get( bmpPtr, rect ) )
        {
            dc.DrawRectangle( rect );
            dc.DrawBitmap( *bmpPtr, rect.GetPosition() );
        }
    }

    for( const wxRect& rect : updateRects )
        QueueRect( rect );

    const wxRect viewport( wxRect( mPosition, GetSize() ).Inflate( GetSize() * 0.1 ) );
    for( const wxRect& rect : GetCoverage( viewport, scaledRect, gridSize ) )
        QueueRect( rect );

    const wxPoint center = viewport.GetPosition() + 0.5 * ( viewport.GetBottomRight() - viewport.GetTopLeft() );
    mImageFactory.Sort( wxRectPointDistCmp( center ) );
}

void wxImagePanel::SetImage( wxSharedPtr< wxImage > newImage )
{
    mImage = newImage;
    mQueuedRects.clear();
    mImageFactory.SetImage( mImage, mScale );
    mPosition = ClampPosition( mPosition );
}

void wxImagePanel::SetScale( const double newScale )
{
    mScale = newScale;
    mBitmapCache.clear();
    mPosition = ClampPosition( mPosition );

    // invalidate entire panel since we need to redraw everything
    Refresh( true );

    if( NULL == mImage )
    {
        return;
    }

    mQueuedRects.clear();
    mImageFactory.SetScale( mScale );
}

void wxImagePanel::OnThread( wxThreadEvent& )
{
    wxRect rect;
    wxSharedPtr< wxImage > image;
    while( mImageFactory.GetImage( rect, image ) )
    {
        wxBitmapPtr bmp( new wxBitmap( *image ) );
        mBitmapCache.insert( rect, bmp );

        mQueuedRects.erase( rect );

        const wxRect target( rect.GetPosition() - mPosition, rect.GetSize() );
        const wxRect clipped( GetRect().Intersect( target ) );
        RefreshRect( clipped, true );
    }

    // todo: needed/wanted?
    //Update();
}
<commit_msg>Enable key repeat for zoom in/out<commit_after>#include "ImagePanel.h"

#include <set>

using namespace std;

vector< wxRect > GetCoverage( const wxRect& viewport, const wxRect& canvas, const wxSize& gridSize )
{
    const wxRect clippedViewport( canvas.Intersect( viewport ) );

    vector< wxRect > coverage;
    const int top    = clippedViewport.GetTop()    / gridSize.y;
    const int bottom = clippedViewport.GetBottom() / gridSize.y;
    const int left   = clippedViewport.GetLeft()   / gridSize.x;
    const int right  = clippedViewport.GetRight()  / gridSize.x;
    for( int y = top; y <= bottom; ++y )
    {
        for( int x = left; x <= right; ++x )
        {
            const wxRect candidate( x * gridSize.x, y * gridSize.y, gridSize.x, gridSize.y );
            const wxRect clipped( canvas.Intersect( candidate ) );
            coverage.push_back( clipped );
        }
    }
    return coverage;
}

template< typename T >
T clamp( const T& val, const T& minVal, const T& maxVal )
{
    if( val < minVal )  return minVal;
    if( val > maxVal )  return maxVal;
    return val;
}

wxPoint ClampPosition( const wxRect& viewport, const wxRect& extent )
{
    const wxSize delta( viewport.GetSize() - extent.GetSize() );

    wxPoint newTopLeft( viewport.GetPosition() );

    if( delta.x < 0 )
    {
        // viewport smaller than extent
        int minX = extent.GetPosition().x;
        int maxX = ( extent.GetPosition().x + extent.GetSize().x ) - viewport.GetSize().x;
        newTopLeft.x = clamp( newTopLeft.x, minX, maxX );
    }
    else
    {
        // viewport larger than extent
        newTopLeft.x = extent.GetPosition().x - ( delta.x / 2 );
    }

    if( delta.y < 0 )
    {
        // viewport smaller than extent
        int minY = extent.GetPosition().y;
        int maxY = ( extent.GetPosition().y + extent.GetSize().y ) - viewport.GetSize().y;
        newTopLeft.y = clamp( newTopLeft.y, minY, maxY );
    }
    else
    {
        // viewport larger than extent
        newTopLeft.y = extent.GetPosition().y - ( delta.y / 2 );
    }

    return newTopLeft;
}

wxImagePanel::wxImagePanel( wxWindow* parent )
    : wxWindow( parent, wxID_ANY )
    , mBitmapCache( 512 )   // ~135MB for 512 256x256x4 byte tiles
    , mPosition( 0, 0 )
    , mScale( 1.0 )
    , mImageFactory( this )
{
    // for wxAutoBufferedPaintDC
    SetBackgroundStyle( wxBG_STYLE_PAINT );

    SetBackgroundColour( *wxBLACK );

    Bind( wxEVT_SIZE        , &wxImagePanel::OnSize         , this );
    Bind( wxEVT_PAINT       , &wxImagePanel::OnPaint        , this );
    Bind( wxEVT_KEY_DOWN    , &wxImagePanel::OnKeyDown      , this );
    Bind( wxEVT_KEY_UP      , &wxImagePanel::OnKeyUp        , this );
    Bind( wxEVT_LEFT_DOWN   , &wxImagePanel::OnButtonDown   , this );
    Bind( wxEVT_RIGHT_DOWN  , &wxImagePanel::OnButtonDown   , this );
    Bind( wxEVT_MIDDLE_DOWN , &wxImagePanel::OnButtonDown   , this );
    Bind( wxEVT_MOTION      , &wxImagePanel::OnMotion       , this );
    Bind( wxEVT_THREAD      , &wxImagePanel::OnThread       , this );

    mStipple = wxBitmap( wxImage( "background.png" ) );
}

void wxImagePanel::OnSize( wxSizeEvent& event )
{
    mPosition = ClampPosition( mPosition );

    // invalidate entire panel since we need to redraw everything
    Refresh( true );

    // skip the event so sizers can do their thing
    event.Skip();
}

void wxImagePanel::OnButtonDown( wxMouseEvent& event )
{
    if( event.LeftDown() )
    {
        mLeftPositionStart = mPosition;
        mLeftMouseStart = event.GetPosition();
    }
}

void wxImagePanel::OnMotion( wxMouseEvent& event )
{
    if( event.LeftIsDown() && event.Dragging() )
    {
        wxPoint newPos( mLeftPositionStart - ( event.GetPosition() - mLeftMouseStart ) );
        if( newPos != mPosition )
        {
            ScrollToPosition( newPos );
        }
    }
}

void wxImagePanel::OnIdle( wxIdleEvent & )
{
    wxPoint newPos( mPosition );
    const int step = wxGetKeyState( WXK_CONTROL ) ? 10 : 1;
    if( wxGetKeyState( WXK_LEFT  ) )    newPos += step * wxPoint( -1,  0 );
    if( wxGetKeyState( WXK_RIGHT ) )    newPos += step * wxPoint(  1,  0 );
    if( wxGetKeyState( WXK_UP    ) )    newPos += step * wxPoint(  0, -1 );
    if( wxGetKeyState( WXK_DOWN  ) )    newPos += step * wxPoint(  0,  1 );

    if( newPos == mPosition )
    {
        Unbind( wxEVT_IDLE, &wxImagePanel::OnIdle, this );
        return;
    }

    ScrollToPosition( newPos );

    //event.RequestMore( true );
    //wxMilliSleep( 1 );
}

void wxImagePanel::OnKeyDown( wxKeyEvent& event )
{
    switch( event.GetKeyCode() )
    {
        case WXK_LEFT:
        case WXK_RIGHT:
        case WXK_UP:
        case WXK_DOWN:
            Bind( wxEVT_IDLE, &wxImagePanel::OnIdle, this );
            break;
        // zoom in
        case '=':
        case WXK_ADD:
        case WXK_NUMPAD_ADD:
            SetScale( mScale * 1.1 );
            break;
        // zoom out
        case '-':
        case WXK_SUBTRACT:
        case WXK_NUMPAD_SUBTRACT:
            SetScale( mScale / 1.1 );
            break;
        default:
            break;
    }
    event.Skip();
}

wxPoint wxImagePanel::ClampPosition( const wxPoint& newPos )
{
    if( NULL == mImage )
    {
        return newPos;
    }

    const wxSize scaledSize( mImage->GetWidth() * mScale, mImage->GetHeight() * mScale );
    return ::ClampPosition
        (
        wxRect( newPos, GetSize() ),
        wxRect( wxPoint(0,0), scaledSize )
        );
}

void wxImagePanel::OnKeyUp( wxKeyEvent& event )
{
    if( NULL == mImage )
    {
        return;
    }

    switch( event.GetKeyCode() )
    {
        // fit image to window
        case 'X':
        case WXK_NUMPAD_MULTIPLY:
            {
                const double scaleWidth = ( GetSize().x / static_cast< double >( mImage->GetWidth() ) );
                const double scaleHeight = ( GetSize().y / static_cast< double >( mImage->GetHeight() ) );
                SetScale( min( scaleWidth, scaleHeight ) );
            }
            break;
        // zoom 1:1
        case 'Z':
        case WXK_NUMPAD_DIVIDE:
            SetScale( 1.0 );
            break;
        default:
            break;
    }
}

void wxImagePanel::ScrollToPosition( const wxPoint& newPos )
{
    const wxPoint clamped = ClampPosition( newPos );
    wxPoint delta( clamped - mPosition );
    ScrollWindow( -delta.x, -delta.y );
    mPosition = clamped;
}

void wxImagePanel::QueueRect( const wxRect& rect )
{
    // don't queue rects we have cached
    wxBitmapPtr bmpPtr;
    if( mBitmapCache.get( bmpPtr, rect ) )
        return;

    // don't queue rects we've already queued
    if( mQueuedRects.end() != mQueuedRects.find( rect ) )
        return;

    mQueuedRects.insert( rect );
    mImageFactory.AddRect( rect );
}

struct wxRectPointDistCmp
{
    wxPoint mPoint;
    wxRectPointDistCmp( const wxPoint& pt ) : mPoint( pt ) {}
    bool operator()( const wxRect& left, const wxRect& right ) const
    {
        const wxPoint leftCenter = left.GetPosition() + 0.5 * ( left.GetBottomRight() - left.GetTopLeft() );
        const wxPoint rightCenter = right.GetPosition() + 0.5 * ( right.GetBottomRight() - right.GetTopLeft() );
        const wxPoint leftDiff = ( leftCenter - mPoint );
        const wxPoint rightDiff = ( rightCenter - mPoint );
        const double leftDistSq = ( leftDiff.x * leftDiff.x + leftDiff.y * leftDiff.y );
        const double rightDistSq = ( rightDiff.x * rightDiff.x + rightDiff.y * rightDiff.y );
        return ( leftDistSq < rightDistSq );
    }
};

void wxImagePanel::OnPaint( wxPaintEvent& )
{
    wxPaintDC dc(this);
    //wxAutoBufferedPaintDC dc( this );

    dc.SetDeviceOrigin( -mPosition.x, -mPosition.y );

    dc.Clear();

    if( NULL == mImage )
    {
        return;
    }

    const wxSize scaledSize( mImage->GetWidth() * mScale, mImage->GetHeight() * mScale );
    const wxRect scaledRect( wxPoint( 0, 0 ), scaledSize );
    const wxSize gridSize( TILE_SIZE, TILE_SIZE );

    // get the set of tiles we need to draw
    set< wxRect, wxRectCmp > updateRects;
    for( wxRegionIterator upd( GetUpdateRegion() ); upd.HaveRects(); ++upd )
    {
        wxRect rect( upd.GetRect() );
        rect.SetPosition( rect.GetPosition() + mPosition );

        const vector< wxRect > ret = GetCoverage
            (
            rect,
            scaledRect,
            gridSize
            );
        updateRects.insert( ret.begin(), ret.end() );
    }

    dc.SetBrush( wxBrush( mStipple ) );
    dc.SetPen( *wxTRANSPARENT_PEN );

    for( const wxRect& rect : updateRects )
    {
        wxBitmapPtr bmpPtr;
        if( mBitmapCache.get( bmpPtr, rect ) )
        {
            dc.DrawRectangle( rect );
            dc.DrawBitmap( *bmpPtr, rect.GetPosition() );
        }
    }

    for( const wxRect& rect : updateRects )
        QueueRect( rect );

    const wxRect viewport( wxRect( mPosition, GetSize() ).Inflate( GetSize() * 0.1 ) );
    for( const wxRect& rect : GetCoverage( viewport, scaledRect, gridSize ) )
        QueueRect( rect );

    const wxPoint center = viewport.GetPosition() + 0.5 * ( viewport.GetBottomRight() - viewport.GetTopLeft() );
    mImageFactory.Sort( wxRectPointDistCmp( center ) );
}

void wxImagePanel::SetImage( wxSharedPtr< wxImage > newImage )
{
    mImage = newImage;
    mQueuedRects.clear();
    mImageFactory.SetImage( mImage, mScale );
    mPosition = ClampPosition( mPosition );
}

void wxImagePanel::SetScale( const double newScale )
{
    mScale = newScale;
    mBitmapCache.clear();
    mPosition = ClampPosition( mPosition );

    // invalidate entire panel since we need to redraw everything
    Refresh( true );

    if( NULL == mImage )
    {
        return;
    }

    mQueuedRects.clear();
    mImageFactory.SetScale( mScale );
}

void wxImagePanel::OnThread( wxThreadEvent& )
{
    wxRect rect;
    wxSharedPtr< wxImage > image;
    while( mImageFactory.GetImage( rect, image ) )
    {
        wxBitmapPtr bmp( new wxBitmap( *image ) );
        mBitmapCache.insert( rect, bmp );

        mQueuedRects.erase( rect );

        const wxRect target( rect.GetPosition() - mPosition, rect.GetSize() );
        const wxRect clipped( GetRect().Intersect( target ) );
        RefreshRect( clipped, true );
    }

    // todo: needed/wanted?
    //Update();
}
<|endoftext|>