diff options
Diffstat (limited to 'drawinglayer/source/processor2d/vclhelperbufferdevice.cxx')
-rw-r--r-- | drawinglayer/source/processor2d/vclhelperbufferdevice.cxx | 378 |
1 files changed, 236 insertions, 142 deletions
diff --git a/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx index 647825959108..28d383230eef 100644 --- a/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx +++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx @@ -19,7 +19,6 @@ #include <sal/config.h> #include <sal/log.hxx> -#include <osl/diagnose.h> #include <algorithm> #include <map> @@ -29,30 +28,39 @@ #include <basegfx/range/b2drange.hxx> #include <vcl/bitmapex.hxx> #include <basegfx/matrix/b2dhommatrix.hxx> -#include <tools/stream.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> #include <vcl/timer.hxx> -#include <cppuhelper/basemutex.hxx> #include <vcl/lazydelete.hxx> #include <vcl/dibtools.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <mutex> -// buffered VDev usage +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#endif +// #define SPEED_COMPARE +#ifdef SPEED_COMPARE +#include <tools/time.hxx> +#endif + +// buffered VDev usage namespace { -class VDevBuffer : public Timer, protected cppu::BaseMutex +class VDevBuffer : public Timer { private: struct Entry { VclPtr<VirtualDevice> buf; - bool isTransparent = false; - Entry(const VclPtr<VirtualDevice>& vdev, bool bTransparent) + Entry(const VclPtr<VirtualDevice>& vdev) : buf(vdev) - , isTransparent(bTransparent) { } }; + std::mutex m_aMutex; + // available buffers std::vector<Entry> maFreeBuffers; @@ -64,12 +72,13 @@ private: // virtualdevice because that isn't safe to do at least for Gtk2 std::map<VclPtr<VirtualDevice>, VclPtr<OutputDevice>> maDeviceTemplates; + static bool isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& size); + public: VDevBuffer(); virtual ~VDevBuffer() override; - VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel, bool bClear, - bool bMonoChrome, bool bTransparent); + VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel); void free(VirtualDevice& rDevice); // Timer virtuals @@ -77,17 +86,14 @@ public: }; VDevBuffer::VDevBuffer() - : Timer("VDevBuffer timer") - , maFreeBuffers() - , maUsedBuffers() + : Timer("drawinglayer::VDevBuffer via Invoke()") { SetTimeout(10L * 1000L); // ten seconds - SetDebugName("drawinglayer::VDevBuffer via Invoke()"); } VDevBuffer::~VDevBuffer() { - ::osl::MutexGuard aGuard(m_aMutex); + std::unique_lock aGuard(m_aMutex); Stop(); while (!maFreeBuffers.empty()) @@ -103,13 +109,40 @@ VDevBuffer::~VDevBuffer() } } -VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel, bool bClear, - bool bMonoChrome, bool bTransparent) +bool VDevBuffer::isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& rSizePixel) +{ + if (device->GetOutputWidthPixel() >= rSizePixel.getWidth() + && device->GetOutputHeightPixel() >= rSizePixel.getHeight()) + { + bool requireSmall = false; +#if defined(UNX) + // HACK: See the small size handling in SvpSalVirtualDevice::CreateSurface(). + // Make sure to not reuse a larger device when a small one should be preferred. + if (device->GetRenderBackendName() == "svp") + requireSmall = true; +#endif + // The same for Skia, see renderMethodToUseForSize(). + if (SkiaHelper::isVCLSkiaEnabled()) + requireSmall = true; + if (requireSmall) + { + if (rSizePixel.getWidth() <= 32 && rSizePixel.getHeight() <= 32 + && (device->GetOutputWidthPixel() > 32 || device->GetOutputHeightPixel() > 32)) + { + return false; + } + } + return true; + } + return false; +} + +VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel) { - ::osl::MutexGuard aGuard(m_aMutex); + std::unique_lock aGuard(m_aMutex); VclPtr<VirtualDevice> pRetval; - sal_Int32 nBits = bMonoChrome ? 1 : rOutDev.GetBitCount(); + sal_Int32 nBits = rOutDev.GetBitCount(); bool bOkay(false); if (!maFreeBuffers.empty()) @@ -120,7 +153,7 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize { assert(a->buf && "Empty pointer in VDevBuffer (!)"); - if (nBits == a->buf->GetBitCount() && bTransparent == a->isTransparent) + if (nBits == a->buf->GetBitCount()) { // candidate is valid due to bit depth if (aFound != maFreeBuffers.end()) @@ -129,9 +162,7 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize if (bOkay) { // found is valid - const bool bCandidateOkay( - a->buf->GetOutputWidthPixel() >= rSizePixel.getWidth() - && a->buf->GetOutputHeightPixel() >= rSizePixel.getHeight()); + const bool bCandidateOkay = isSizeSuitable(a->buf, rSizePixel); if (bCandidateOkay) { @@ -156,16 +187,14 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize { // found is invalid, use candidate aFound = a; - bOkay = aFound->buf->GetOutputWidthPixel() >= rSizePixel.getWidth() - && aFound->buf->GetOutputHeightPixel() >= rSizePixel.getHeight(); + bOkay = isSizeSuitable(aFound->buf, rSizePixel); } } else { // none yet, use candidate aFound = a; - bOkay = aFound->buf->GetOutputWidthPixel() >= rSizePixel.getWidth() - && aFound->buf->GetOutputHeightPixel() >= rSizePixel.getHeight(); + bOkay = isSizeSuitable(aFound->buf, rSizePixel); } } } @@ -192,15 +221,12 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize { if (bOkay) { - if (bClear) - { - pRetval->Erase( - ::tools::Rectangle(0, 0, rSizePixel.getWidth(), rSizePixel.getHeight())); - } + pRetval->Erase(pRetval->PixelToLogic( + tools::Rectangle(0, 0, rSizePixel.getWidth(), rSizePixel.getHeight()))); } else { - pRetval->SetOutputSizePixel(rSizePixel, bClear); + pRetval->SetOutputSizePixel(rSizePixel, true); } } } @@ -208,11 +234,9 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize // no success yet, create new buffer if (!pRetval) { - pRetval = VclPtr<VirtualDevice>::Create( - rOutDev, bMonoChrome ? DeviceFormat::BITMASK : DeviceFormat::DEFAULT, - bTransparent ? DeviceFormat::DEFAULT : DeviceFormat::NONE); + pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::WITHOUT_ALPHA); maDeviceTemplates[pRetval] = &rOutDev; - pRetval->SetOutputSizePixel(rSizePixel, bClear); + pRetval->SetOutputSizePixel(rSizePixel, true); } else { @@ -222,14 +246,14 @@ VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSize } // remember allocated buffer - maUsedBuffers.emplace_back(pRetval, bTransparent); + maUsedBuffers.emplace_back(pRetval); return pRetval; } void VDevBuffer::free(VirtualDevice& rDevice) { - ::osl::MutexGuard aGuard(m_aMutex); + std::unique_lock aGuard(m_aMutex); const auto aUsedFound = std::find_if(maUsedBuffers.begin(), maUsedBuffers.end(), [&rDevice](const Entry& el) { return el.buf == &rDevice; }); @@ -247,7 +271,7 @@ void VDevBuffer::free(VirtualDevice& rDevice) void VDevBuffer::Invoke() { - ::osl::MutexGuard aGuard(m_aMutex); + std::unique_lock aGuard(m_aMutex); while (!maFreeBuffers.empty()) { @@ -257,63 +281,138 @@ void VDevBuffer::Invoke() maFreeBuffers.pop_back(); } } + +#ifdef SPEED_COMPARE +void doSpeedCompare(double fTrans, const Bitmap& rContent, const tools::Rectangle& rDestPixel, + OutputDevice& rOutDev) +{ + const int nAvInd(500); + static double fFactors[nAvInd]; + static int nIndex(nAvInd + 1); + static int nRepeat(5); + static int nWorseTotal(0); + static int nBetterTotal(0); + int a(0); + + const Size aSizePixel(rDestPixel.GetSize()); + + // init statics + if (nIndex > nAvInd) + { + for (a = 0; a < nAvInd; a++) + fFactors[a] = 1.0; + nIndex = 0; + } + + // get start time + const sal_uInt64 nTimeA(tools::Time::GetSystemTicks()); + + // loop nRepeat times to get somewhat better timings, else + // numbers are pretty small + for (a = 0; a < nRepeat; a++) + { + // "Former" method using a temporary AlphaMask & DrawBitmapEx + sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0))); + const AlphaMask aAlphaMask(aSizePixel, &nMaskValue); + rOutDev.DrawBitmapEx(rDestPixel.TopLeft(), BitmapEx(rContent, aAlphaMask)); + } + + // get intermediate time + const sal_uInt64 nTimeB(tools::Time::GetSystemTicks()); + + // loop nRepeat times + for (a = 0; a < nRepeat; a++) + { + // New method using DrawTransformedBitmapEx & fTrans directly + rOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix( + aSizePixel.Width(), aSizePixel.Height(), + rDestPixel.TopLeft().X(), rDestPixel.TopLeft().Y()), + BitmapEx(rContent), 1 - fTrans); + } + + // get end time + const sal_uInt64 nTimeC(tools::Time::GetSystemTicks()); + + // calculate deltas + const sal_uInt64 nTimeFormer(nTimeB - nTimeA); + const sal_uInt64 nTimeNew(nTimeC - nTimeB); + + // compare & note down + if (nTimeFormer != nTimeNew && 0 != nTimeFormer && 0 != nTimeNew) + { + if ((nTimeFormer < 10 || nTimeNew < 10) && nRepeat < 500) + { + nRepeat += 1; + SAL_INFO("drawinglayer.processor2d", "Increment nRepeat to " << nRepeat); + return; + } + + const double fNewFactor((double)nTimeFormer / nTimeNew); + fFactors[nIndex % nAvInd] = fNewFactor; + nIndex++; + double fAverage(0.0); + { + for (a = 0; a < nAvInd; a++) + fAverage += fFactors[a]; + fAverage /= nAvInd; + } + if (fNewFactor < 1.0) + nWorseTotal++; + else + nBetterTotal++; + + char buf[300]; + sprintf(buf, + "Former: %ld New: %ld It got %s (factor %f) (av. last %d Former/New is %f, " + "WorseTotal: %d, BetterTotal: %d)", + nTimeFormer, nTimeNew, fNewFactor < 1.0 ? "WORSE" : "BETTER", + fNewFactor < 1.0 ? 1.0 / fNewFactor : fNewFactor, nAvInd, fAverage, nWorseTotal, + nBetterTotal); + SAL_INFO("drawinglayer.processor2d", buf); + } +} +#endif } // support for rendering Bitmap and BitmapEx contents - namespace drawinglayer { -// static global VDev buffer for the VclProcessor2D's (VclMetafileProcessor2D and VclPixelProcessor2D) +// static global VDev buffer for VclProcessor2D/VclPixelProcessor2D VDevBuffer& getVDevBuffer() { // secure global instance with Vcl's safe destroyer of external (seen by // library base) stuff, the remembered VDevs need to be deleted before // Vcl's deinit - static vcl::DeleteOnDeinit<VDevBuffer> aVDevBuffer(new VDevBuffer()); + static vcl::DeleteOnDeinit<VDevBuffer> aVDevBuffer{}; return *aVDevBuffer.get(); } -impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange, - bool bContentTransparent) +impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange) : mrOutDev(rOutDev) , mpContent(nullptr) - , mpMask(nullptr) , mpAlpha(nullptr) - , mbContentTransparent(bContentTransparent) { basegfx::B2DRange aRangePixel(rRange); aRangePixel.transform(mrOutDev.GetViewTransformation()); - const ::tools::Rectangle aRectPixel(static_cast<sal_Int32>(floor(aRangePixel.getMinX())), - static_cast<sal_Int32>(floor(aRangePixel.getMinY())), - static_cast<sal_Int32>(ceil(aRangePixel.getMaxX())), - static_cast<sal_Int32>(ceil(aRangePixel.getMaxY()))); - const Point aEmptyPoint; - maDestPixel = ::tools::Rectangle(aEmptyPoint, mrOutDev.GetOutputSizePixel()); - maDestPixel.Intersection(aRectPixel); + maDestPixel = tools::Rectangle(floor(aRangePixel.getMinX()), floor(aRangePixel.getMinY()), + ceil(aRangePixel.getMaxX()), ceil(aRangePixel.getMaxY())); + maDestPixel.Intersection(tools::Rectangle{ Point{}, mrOutDev.GetOutputSizePixel() }); if (!isVisible()) return; -#ifdef IOS - // Exact mechanism unknown, but for some reason SmartArt - // rendering, especially shadows, is broken on iOS unless - // we pass 'true' here. Are virtual devices always de - // facto cleared when created on other platforms? - mpContent - = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true, false, bContentTransparent); -#else - mpContent - = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), false, false, bContentTransparent); -#endif + mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize()); // #i93485# assert when copying from window to VDev is used SAL_WARN_IF( mrOutDev.GetOutDevType() == OUTDEV_WINDOW, "drawinglayer", "impBufferDevice render helper: Copying from Window to VDev, this should be avoided (!)"); + // initialize buffer by blitting content of source to prepare for + // transparence/ copying back const bool bWasEnabledSrc(mrOutDev.IsMapModeEnabled()); mrOutDev.EnableMapMode(false); - mpContent->DrawOutDev(aEmptyPoint, maDestPixel.GetSize(), maDestPixel.TopLeft(), + mpContent->DrawOutDev(Point(), maDestPixel.GetSize(), maDestPixel.TopLeft(), maDestPixel.GetSize(), mrOutDev); mrOutDev.EnableMapMode(bWasEnabledSrc); @@ -338,11 +437,6 @@ impBufferDevice::~impBufferDevice() getVDevBuffer().free(*mpContent); } - if (mpMask) - { - getVDevBuffer().free(*mpMask); - } - if (mpAlpha) { getVDevBuffer().free(*mpAlpha); @@ -357,23 +451,18 @@ void impBufferDevice::paint(double fTrans) const Point aEmptyPoint; const Size aSizePixel(maDestPixel.GetSize()); const bool bWasEnabledDst(mrOutDev.IsMapModeEnabled()); -#ifdef DBG_UTIL - static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore -#endif mrOutDev.EnableMapMode(false); mpContent->EnableMapMode(false); #ifdef DBG_UTIL - if (bDoSaveForVisualControl) + // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/ + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + static const OUString sDumpPath(OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); + + if (!sDumpPath.isEmpty() && bDoSaveForVisualControl) { - SvFileStream aNew( -#ifdef _WIN32 - "c:\\content.bmp", -#else - "~/content.bmp", -#endif - StreamMode::WRITE | StreamMode::TRUNC); + SvFileStream aNew(sDumpPath + "content.bmp", StreamMode::WRITE | StreamMode::TRUNC); Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); WriteDIB(aContent, aNew, false, true); } @@ -387,67 +476,87 @@ void impBufferDevice::paint(double fTrans) { mpAlpha->EnableMapMode(false); AlphaMask aAlphaMask(mpAlpha->GetBitmap(aEmptyPoint, aSizePixel)); + aAlphaMask.Invert(); // convert transparency to alpha #ifdef DBG_UTIL - if (bDoSaveForVisualControl) + if (!sDumpPath.isEmpty() && bDoSaveForVisualControl) { - SvFileStream aNew( -#ifdef _WIN32 - "c:\\transparence.bmp", -#else - "~/transparence.bmp", -#endif - StreamMode::WRITE | StreamMode::TRUNC); + SvFileStream aNew(sDumpPath + "transparence.bmp", + StreamMode::WRITE | StreamMode::TRUNC); WriteDIB(aAlphaMask.GetBitmap(), aNew, false, true); } #endif - BitmapEx aContent(mpContent->GetBitmapEx(aEmptyPoint, aSizePixel)); - if (mbContentTransparent) - aAlphaMask.BlendWith(aContent.GetAlpha()); - mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent.GetBitmap(), aAlphaMask)); + Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); + mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask)); } - else if (mpMask) + else if (0.0 != fTrans) { - mpMask->EnableMapMode(false); - const Bitmap aMask(mpMask->GetBitmap(aEmptyPoint, aSizePixel)); + const Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); -#ifdef DBG_UTIL - if (bDoSaveForVisualControl) - { - SvFileStream aNew( -#ifdef _WIN32 - "c:\\mask.bmp", -#else - "~/mask.bmp", -#endif - StreamMode::WRITE | StreamMode::TRUNC); - WriteDIB(aMask, aNew, false, true); - } -#endif +#ifdef SPEED_COMPARE + static bool bCompareFormerAndNewTimings(true); - if (mbContentTransparent) + if (bCompareFormerAndNewTimings) { - BitmapEx aContent(mpContent->GetBitmapEx(aEmptyPoint, aSizePixel)); - AlphaMask aAlpha(aContent.GetAlpha()); - aAlpha.BlendWith(aMask); - mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent.GetBitmap(), aAlpha)); + doSpeedCompare(fTrans, aContent, maDestPixel, mrOutDev); } else +#endif + // Note: this extra scope is needed due to 'clang plugin indentation'. It complains + // that lines 494 and (now) 539 are 'statement mis-aligned compared to neighbours'. + // That is true if SPEED_COMPARE is not defined. Not nice, but have to fix this. { - Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); - mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aMask)); + // For the case we have a unified transparency value there is a former + // and new method to paint that which can be used. To decide on measurements, + // I added 'doSpeedCompare' above which can be activated by defining + // SPEED_COMPARE at the top of this file. + // I added the used Testdoc: blurplay3.odg as + // https://bugs.documentfoundation.org/attachment.cgi?id=182463 + // I did measure on + // + // Linux Dbg: + // Former: 21 New: 32 It got WORSE (factor 1.523810) (av. last 500 Former/New is 0.968533, WorseTotal: 515, BetterTotal: 934) + // + // Linux Pro: + // Former: 27 New: 44 It got WORSE (factor 1.629630) (av. last 500 Former/New is 0.923256, WorseTotal: 433, BetterTotal: 337) + // + // Win Dbg: + // Former: 21 New: 78 It got WORSE (factor 3.714286) (av. last 500 Former/New is 1.007176, WorseTotal: 85, BetterTotal: 1428) + // + // Win Pro: + // Former: 3 New: 4 It got WORSE (factor 1.333333) (av. last 500 Former/New is 1.054167, WorseTotal: 143, BetterTotal: 3909) + // + // Note: I am aware that the Dbg are of limited usefulness, but include them here + // for reference. + // + // The important part is "av. last 500 Former/New is %ld" which describes the averaged factor from Former/New + // over the last 500 measurements. When < 1.0 Former is better (Linux), > 1.0 (Win) New is better. Since the + // factor on Win is still close to 1.0 what means we lose nearly nothing and Linux Former is better, I will + // use Former for now. + // + // To easily allow to change this (maybe system-dependent) I add a static switch here, + // also for eventually experimenting (hint: can be changed in the debugger). + static bool bUseNew(false); + + if (bUseNew) + { + // New method using DrawTransformedBitmapEx & fTrans directly + mrOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix( + aSizePixel.Width(), aSizePixel.Height(), + maDestPixel.TopLeft().X(), + maDestPixel.TopLeft().Y()), + BitmapEx(aContent), 1 - fTrans); + } + else + { + // "Former" method using a temporary AlphaMask & DrawBitmapEx + sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0))); + const AlphaMask aAlphaMask(aSizePixel, &nMaskValue); + mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask)); + } } } - else if (0.0 != fTrans) - { - sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0))); - AlphaMask aAlphaMask(aSizePixel, &nMaskValue); - BitmapEx aContent(mpContent->GetBitmapEx(aEmptyPoint, aSizePixel)); - if (mbContentTransparent) - aAlphaMask.BlendWith(aContent.GetAlpha()); - mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent.GetBitmap(), aAlphaMask)); - } else { mrOutDev.DrawOutDev(maDestPixel.TopLeft(), aSizePixel, aEmptyPoint, aSizePixel, *mpContent); @@ -464,28 +573,13 @@ VirtualDevice& impBufferDevice::getContent() return *mpContent; } -VirtualDevice& impBufferDevice::getMask() -{ - SAL_WARN_IF(!mpContent, "drawinglayer", - "impBufferDevice: No content, check isVisible() before accessing (!)"); - if (!mpMask) - { - mpMask = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true, true, false); - mpMask->SetMapMode(mpContent->GetMapMode()); - - // do NOT copy AA flag for mask! - } - - return *mpMask; -} - VirtualDevice& impBufferDevice::getTransparence() { SAL_WARN_IF(!mpContent, "drawinglayer", "impBufferDevice: No content, check isVisible() before accessing (!)"); if (!mpAlpha) { - mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true, false, false); + mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize()); mpAlpha->SetMapMode(mpContent->GetMapMode()); // copy AA flag for new target; masking needs to be smooth |